sim_lib_pitch_serial/family.rs
1//! Complete row families with alias-preserving symmetry reduction.
2
3use crate::{RowFamily, RowForm, RowOperation, ToneRow};
4
5const FAMILIES: [RowFamily; 4] = [RowFamily::P, RowFamily::I, RowFamily::R, RowFamily::RI];
6
7/// One operation alias in a complete twelve-tone row family.
8///
9/// Aliases are never removed when two operations produce the same row. The
10/// [`RowAlias::distinct_form_index`] links the operation-bearing form to the
11/// corresponding deduplicated row in [`RowFamilySet::distinct_forms`].
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct RowAlias {
14 /// The normalized operation represented by this alias.
15 pub operation: RowOperation,
16 /// The strict row form produced by applying `operation` to the source row.
17 pub form: RowForm,
18 distinct_form_index: usize,
19}
20
21impl RowAlias {
22 /// Returns the index of this alias's row in the deduplicated form collection.
23 pub const fn distinct_form_index(&self) -> usize {
24 self.distinct_form_index
25 }
26}
27
28/// All 48 P/I/R/RI aliases for one row, plus its distinct resulting rows.
29///
30/// The alias order is stable: P0..P11, I0..I11, R0..R11, then RI0..RI11.
31/// Symmetric rows may have fewer than 48 distinct values, but every operation
32/// remains addressable through [`RowFamilySet::aliases`].
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct RowFamilySet {
35 source: ToneRow,
36 aliases: Vec<RowAlias>,
37 distinct_forms: Vec<ToneRow>,
38}
39
40impl RowFamilySet {
41 /// Builds the complete operation family for `source`.
42 pub fn of(source: &ToneRow) -> Self {
43 let mut aliases = Vec::with_capacity(48);
44 let mut distinct_forms = Vec::with_capacity(48);
45
46 for family in FAMILIES {
47 for addend in 0..12 {
48 let operation = RowOperation::new(family, addend);
49 let form = source.apply(operation);
50 let distinct_form_index = distinct_forms
51 .iter()
52 .position(|distinct| distinct == form.row())
53 .unwrap_or_else(|| {
54 distinct_forms.push(form.row().clone());
55 distinct_forms.len() - 1
56 });
57 aliases.push(RowAlias {
58 operation,
59 form,
60 distinct_form_index,
61 });
62 }
63 }
64
65 Self {
66 source: source.clone(),
67 aliases,
68 distinct_forms,
69 }
70 }
71
72 /// Returns the row from which every alias was derived.
73 pub const fn source(&self) -> &ToneRow {
74 &self.source
75 }
76
77 /// Returns all 48 aliases in stable family-and-addend order.
78 pub fn aliases(&self) -> &[RowAlias] {
79 &self.aliases
80 }
81
82 /// Returns the deduplicated row values in first-alias order.
83 pub fn distinct_forms(&self) -> &[ToneRow] {
84 &self.distinct_forms
85 }
86
87 /// Iterates over every alias that resolves to one distinct form.
88 ///
89 /// An out-of-range index yields an empty iterator.
90 pub fn aliases_for_distinct_form(
91 &self,
92 distinct_form_index: usize,
93 ) -> impl Iterator<Item = &RowAlias> {
94 self.aliases.iter().filter(move |alias| {
95 alias.distinct_form_index == distinct_form_index
96 && distinct_form_index < self.distinct_forms.len()
97 })
98 }
99}