sim_lib_pitch_serial/
matrix.rs1use sim_lib_pitch_core::PitchClass;
4
5use crate::{RowFamily, RowForm, RowLabel, RowLabelConvention, RowOperation, ToneRow};
6
7pub const ROW_MATRIX_SIZE: usize = 12;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct MatrixCoordinate {
13 row: u8,
14 column: u8,
15}
16
17impl MatrixCoordinate {
18 pub const fn new(row: usize, column: usize) -> Option<Self> {
20 if row < ROW_MATRIX_SIZE && column < ROW_MATRIX_SIZE {
21 Some(Self {
22 row: row as u8,
23 column: column as u8,
24 })
25 } else {
26 None
27 }
28 }
29
30 pub const fn row(self) -> usize {
32 self.row as usize
33 }
34
35 pub const fn column(self) -> usize {
37 self.column as usize
38 }
39}
40
41#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
43pub struct RowMatrixCell {
44 coordinate: MatrixCoordinate,
45 class: PitchClass,
46}
47
48impl RowMatrixCell {
49 pub(crate) const fn new(coordinate: MatrixCoordinate, class: PitchClass) -> Self {
50 Self { coordinate, class }
51 }
52
53 pub const fn coordinate(self) -> MatrixCoordinate {
55 self.coordinate
56 }
57
58 pub const fn class(self) -> PitchClass {
60 self.class
61 }
62}
63
64#[derive(Copy, Clone, Debug, PartialEq, Eq)]
69pub struct RowMatrixEdgeLabels {
70 top: [RowLabel; ROW_MATRIX_SIZE],
71 right: [RowLabel; ROW_MATRIX_SIZE],
72 bottom: [RowLabel; ROW_MATRIX_SIZE],
73 left: [RowLabel; ROW_MATRIX_SIZE],
74}
75
76impl RowMatrixEdgeLabels {
77 pub const fn top(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
79 &self.top
80 }
81
82 pub const fn right(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
84 &self.right
85 }
86
87 pub const fn bottom(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
89 &self.bottom
90 }
91
92 pub const fn left(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
94 &self.left
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct RowMatrix {
106 source: ToneRow,
107 convention: RowLabelConvention,
108 cells: [[PitchClass; ROW_MATRIX_SIZE]; ROW_MATRIX_SIZE],
109 row_operations: [RowOperation; ROW_MATRIX_SIZE],
110 column_operations: [RowOperation; ROW_MATRIX_SIZE],
111 edge_labels: RowMatrixEdgeLabels,
112}
113
114impl RowMatrix {
115 pub fn new(source: &ToneRow, convention: RowLabelConvention) -> Self {
117 let source_first = source.classes()[0].value();
118 let row_operations = source.classes().map(|class| {
119 RowOperation::new(
120 RowFamily::P,
121 subtract_mod_twelve(source_first, class.value()),
122 )
123 });
124 let column_operations = source
125 .classes()
126 .map(|class| RowOperation::new(RowFamily::I, (source_first + class.value()) % 12));
127 let cells = std::array::from_fn(|row| *source.apply(row_operations[row]).classes());
128
129 debug_assert!((0..ROW_MATRIX_SIZE).all(|column| {
130 let expected = source.apply(column_operations[column]);
131 (0..ROW_MATRIX_SIZE).all(|row| cells[row][column] == expected.classes()[row])
132 }));
133
134 let edge_labels = RowMatrixEdgeLabels {
135 top: column_operations.map(|operation| source.apply(operation).label(convention)),
136 right: row_operations.map(|operation| {
137 source
138 .apply(RowOperation::new(RowFamily::R, operation.addend))
139 .label(convention)
140 }),
141 bottom: column_operations.map(|operation| {
142 source
143 .apply(RowOperation::new(RowFamily::RI, operation.addend))
144 .label(convention)
145 }),
146 left: row_operations.map(|operation| source.apply(operation).label(convention)),
147 };
148
149 Self {
150 source: source.clone(),
151 convention,
152 cells,
153 row_operations,
154 column_operations,
155 edge_labels,
156 }
157 }
158
159 pub const fn source(&self) -> &ToneRow {
161 &self.source
162 }
163
164 pub const fn convention(&self) -> RowLabelConvention {
166 self.convention
167 }
168
169 pub const fn cells(&self) -> &[[PitchClass; ROW_MATRIX_SIZE]; ROW_MATRIX_SIZE] {
171 &self.cells
172 }
173
174 pub const fn cell(&self, coordinate: MatrixCoordinate) -> RowMatrixCell {
176 RowMatrixCell::new(
177 coordinate,
178 self.cells[coordinate.row()][coordinate.column()],
179 )
180 }
181
182 pub fn row(&self, row: usize) -> Option<&[PitchClass; ROW_MATRIX_SIZE]> {
184 self.cells.get(row)
185 }
186
187 pub fn column(&self, column: usize) -> Option<[PitchClass; ROW_MATRIX_SIZE]> {
189 (column < ROW_MATRIX_SIZE).then(|| std::array::from_fn(|row| self.cells[row][column]))
190 }
191
192 pub fn row_operation(&self, row: usize) -> Option<RowOperation> {
194 self.row_operations.get(row).copied()
195 }
196
197 pub fn column_operation(&self, column: usize) -> Option<RowOperation> {
199 self.column_operations.get(column).copied()
200 }
201
202 pub fn row_form(&self, row: usize) -> Option<RowForm> {
204 self.row_operation(row)
205 .map(|operation| self.source.apply(operation))
206 }
207
208 pub fn column_form(&self, column: usize) -> Option<RowForm> {
210 self.column_operation(column)
211 .map(|operation| self.source.apply(operation))
212 }
213
214 pub const fn edge_labels(&self) -> &RowMatrixEdgeLabels {
216 &self.edge_labels
217 }
218}
219
220const fn subtract_mod_twelve(left: u8, right: u8) -> u8 {
221 (left + 12 - right) % 12
222}