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