sim_lib_pitch_serial/
derivation.rs1use crate::{RowError, RowFamily, RowOperation, ToneRow};
4
5const DERIVATION_PARTITIONS: [usize; 4] = [2, 3, 4, 6];
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct DerivationMatch {
10 pub generator_size: usize,
12 pub kind: DerivationKind,
14 pub cells: Vec<DerivationCellRelation>,
16}
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub enum DerivationKind {
21 Dyadic,
23 Trichordal,
25 Tetrachordal,
27 Hexachordal,
29}
30
31impl DerivationKind {
32 fn of_size(size: usize) -> Option<Self> {
33 match size {
34 2 => Some(Self::Dyadic),
35 3 => Some(Self::Trichordal),
36 4 => Some(Self::Tetrachordal),
37 6 => Some(Self::Hexachordal),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct DerivationCellRelation {
46 pub cell_index: usize,
48 pub ordinals: Vec<u8>,
50 pub operation: RowOperation,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Default)]
56pub struct DerivationReport {
57 pub generator_size: Option<usize>,
59 pub matches: Vec<DerivationMatch>,
61}
62
63pub fn analyze_derivation(row: &ToneRow) -> DerivationReport {
65 let matches = DERIVATION_PARTITIONS
66 .into_iter()
67 .filter_map(|size| analyze_derivation_partition(row, size).ok().flatten())
68 .collect::<Vec<_>>();
69 let generator_size = matches.iter().map(|entry| entry.generator_size).max();
70 DerivationReport {
71 generator_size,
72 matches,
73 }
74}
75
76pub fn analyze_derivation_partition(
78 row: &ToneRow,
79 generator_size: usize,
80) -> Result<Option<DerivationMatch>, RowError> {
81 let Some(kind) = DerivationKind::of_size(generator_size) else {
82 return Err(RowError::InvalidPartitionSize {
83 size: generator_size,
84 });
85 };
86 let generator = &row.classes()[0..generator_size];
87 let mut cells = Vec::with_capacity(12 / generator_size);
88 for (cell_index, chunk) in row.classes().chunks(generator_size).enumerate() {
89 let Some(operation) = related_operation(generator, chunk) else {
90 return Ok(None);
91 };
92 let start = cell_index * generator_size;
93 cells.push(DerivationCellRelation {
94 cell_index,
95 ordinals: (start..start + generator_size)
96 .map(|ordinal| ordinal as u8)
97 .collect(),
98 operation,
99 });
100 }
101 Ok(Some(DerivationMatch {
102 generator_size,
103 kind,
104 cells,
105 }))
106}
107
108fn related_operation(
109 left: &[sim_lib_pitch_core::PitchClass],
110 right: &[sim_lib_pitch_core::PitchClass],
111) -> Option<RowOperation> {
112 [RowFamily::P, RowFamily::I, RowFamily::R, RowFamily::RI]
113 .into_iter()
114 .flat_map(|family| (0..12).map(move |addend| RowOperation::new(family, addend)))
115 .find(|operation| apply_operation(left, *operation) == right)
116}
117
118fn apply_operation(
119 source: &[sim_lib_pitch_core::PitchClass],
120 operation: RowOperation,
121) -> Vec<sim_lib_pitch_core::PitchClass> {
122 let mut classes = source.to_vec();
123 if operation.family.is_retrograde() {
124 classes.reverse();
125 }
126 classes
127 .into_iter()
128 .map(|class| {
129 let class = if operation.family.is_inverted() {
130 class.invert(sim_lib_pitch_core::PitchClass::C)
131 } else {
132 class
133 };
134 class.transpose(i32::from(operation.addend))
135 })
136 .collect()
137}