Skip to main content

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}