Skip to main content

sim_lib_pitch_serial/
row.rs

1//! Strict tone-row values and invariant-preserving operation results.
2
3use sim_lib_pitch_core::PitchClass;
4use sim_lib_serial_core::{AggregateRule, Series};
5
6use crate::{PitchClassAlphabet, RowError, RowLabel, RowLabelConvention, RowOperation};
7
8/// An ordered aggregate containing every canonical pitch class exactly once.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct ToneRow {
11    classes: [PitchClass; 12],
12}
13
14impl ToneRow {
15    /// Constructs a row after exact membership and multiplicity validation.
16    ///
17    /// The fixed array establishes cardinality. [`Series`] with the exhaustive
18    /// exactly-once rule supplies the canonical alphabet membership and
19    /// multiplicity proof.
20    pub fn try_from_classes(classes: [PitchClass; 12]) -> Result<Self, RowError> {
21        let alphabet = PitchClassAlphabet::try_new()?;
22        Series::try_new(
23            alphabet,
24            AggregateRule::exhaustive_exactly_once(),
25            classes.to_vec(),
26        )?;
27        Ok(Self::from_valid_classes(classes))
28    }
29
30    /// Returns the twelve classes in row order.
31    pub const fn classes(&self) -> &[PitchClass; 12] {
32        &self.classes
33    }
34
35    /// Applies a total P/I/R/RI operation and retains its normalized identity.
36    pub fn apply(&self, operation: RowOperation) -> RowForm {
37        let operation = operation.normalized();
38        let mut classes = std::array::from_fn(|position| {
39            let class = self.classes[position];
40            let class = if operation.family.is_inverted() {
41                class.invert(PitchClass::C)
42            } else {
43                class
44            };
45            class.transpose(i32::from(operation.addend))
46        });
47        if operation.family.is_retrograde() {
48            classes.reverse();
49        }
50        RowForm {
51            row: Self::from_valid_classes(classes),
52            operation,
53        }
54    }
55
56    pub(crate) const fn from_valid_classes(classes: [PitchClass; 12]) -> Self {
57        Self { classes }
58    }
59}
60
61/// A strict tone row paired with the operation identity that produced it.
62///
63/// The operation is algebraic provenance. Printed labels are derived separately
64/// with [`RowForm::label`] and an explicit [`RowLabelConvention`].
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct RowForm {
67    row: ToneRow,
68    operation: RowOperation,
69}
70
71impl RowForm {
72    /// Returns the normalized operation identity that produced this form.
73    pub const fn operation(&self) -> RowOperation {
74        self.operation
75    }
76
77    /// Returns the strict row value carried by this form.
78    pub const fn row(&self) -> &ToneRow {
79        &self.row
80    }
81
82    /// Returns the twelve pitch classes in form order.
83    pub const fn classes(&self) -> &[PitchClass; 12] {
84        self.row.classes()
85    }
86
87    /// Derives a printed label under the selected convention.
88    pub fn label(&self, convention: RowLabelConvention) -> RowLabel {
89        convention.label(self)
90    }
91
92    /// Discards operation provenance and returns the strict row value.
93    pub fn into_row(self) -> ToneRow {
94        self.row
95    }
96}