sim_lib_pitch_serial/
invariance.rs1use sim_lib_pitch_core::PitchClass;
4use sim_lib_pitch_set::analyze_set_relations;
5
6use crate::RowSegment;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct SegmentInvariant {
11 pub ordinal_identity: bool,
13 pub pitch_identity: bool,
15 pub transposition: Option<u8>,
17 pub inversion: Option<u8>,
19 pub interval_order_identity: bool,
21 pub set_class_identity: bool,
23}
24
25pub fn analyze_invariance(left: &RowSegment, right: &RowSegment) -> SegmentInvariant {
27 let relation = analyze_set_relations(left.mask(), right.mask());
28 SegmentInvariant {
29 ordinal_identity: left.ordinals() == right.ordinals(),
30 pitch_identity: left.classes() == right.classes(),
31 transposition: ordered_transposition(left.classes(), right.classes()),
32 inversion: ordered_inversion(left.classes(), right.classes()),
33 interval_order_identity: left.ordered_intervals() == right.ordered_intervals(),
34 set_class_identity: relation.transposition_inversion_equivalent,
35 }
36}
37
38fn ordered_transposition(left: &[PitchClass], right: &[PitchClass]) -> Option<u8> {
39 if left.len() != right.len() {
40 return None;
41 }
42 let Some((first_left, first_right)) = left.first().zip(right.first()) else {
43 return Some(0);
44 };
45 let shift = (12 + i16::from(first_right.value()) - i16::from(first_left.value())) as u8 % 12;
46 left.iter()
47 .zip(right)
48 .all(|(source, target)| source.transpose(i32::from(shift)) == *target)
49 .then_some(shift)
50}
51
52fn ordered_inversion(left: &[PitchClass], right: &[PitchClass]) -> Option<u8> {
53 if left.len() != right.len() {
54 return None;
55 }
56 let Some((first_left, first_right)) = left.first().zip(right.first()) else {
57 return Some(0);
58 };
59 let index = (first_left.value() + first_right.value()) % 12;
60 left.iter()
61 .zip(right)
62 .all(|(source, target)| target.value() == (12 + index - source.value()) % 12)
63 .then_some(index)
64}