use sim_lib_pitch_core::PitchClass;
use crate::{RowFamily, RowForm, RowLabel, RowLabelConvention, RowOperation, ToneRow};
pub const ROW_MATRIX_SIZE: usize = 12;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MatrixCoordinate {
row: u8,
column: u8,
}
impl MatrixCoordinate {
pub const fn new(row: usize, column: usize) -> Option<Self> {
if row < ROW_MATRIX_SIZE && column < ROW_MATRIX_SIZE {
Some(Self {
row: row as u8,
column: column as u8,
})
} else {
None
}
}
pub const fn row(self) -> usize {
self.row as usize
}
pub const fn column(self) -> usize {
self.column as usize
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RowMatrixCell {
coordinate: MatrixCoordinate,
class: PitchClass,
}
impl RowMatrixCell {
pub(crate) const fn new(coordinate: MatrixCoordinate, class: PitchClass) -> Self {
Self { coordinate, class }
}
pub const fn coordinate(self) -> MatrixCoordinate {
self.coordinate
}
pub const fn class(self) -> PitchClass {
self.class
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RowMatrixEdgeLabels {
top: [RowLabel; ROW_MATRIX_SIZE],
right: [RowLabel; ROW_MATRIX_SIZE],
bottom: [RowLabel; ROW_MATRIX_SIZE],
left: [RowLabel; ROW_MATRIX_SIZE],
}
impl RowMatrixEdgeLabels {
pub const fn top(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
&self.top
}
pub const fn right(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
&self.right
}
pub const fn bottom(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
&self.bottom
}
pub const fn left(&self) -> &[RowLabel; ROW_MATRIX_SIZE] {
&self.left
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RowMatrix {
source: ToneRow,
convention: RowLabelConvention,
cells: [[PitchClass; ROW_MATRIX_SIZE]; ROW_MATRIX_SIZE],
row_operations: [RowOperation; ROW_MATRIX_SIZE],
column_operations: [RowOperation; ROW_MATRIX_SIZE],
edge_labels: RowMatrixEdgeLabels,
}
impl RowMatrix {
pub fn new(source: &ToneRow, convention: RowLabelConvention) -> Self {
let source_first = source.classes()[0].value();
let row_operations = source.classes().map(|class| {
RowOperation::new(
RowFamily::P,
subtract_mod_twelve(source_first, class.value()),
)
});
let column_operations = source
.classes()
.map(|class| RowOperation::new(RowFamily::I, (source_first + class.value()) % 12));
let cells = std::array::from_fn(|row| *source.apply(row_operations[row]).classes());
debug_assert!((0..ROW_MATRIX_SIZE).all(|column| {
let expected = source.apply(column_operations[column]);
(0..ROW_MATRIX_SIZE).all(|row| cells[row][column] == expected.classes()[row])
}));
let edge_labels = RowMatrixEdgeLabels {
top: column_operations.map(|operation| source.apply(operation).label(convention)),
right: row_operations.map(|operation| {
source
.apply(RowOperation::new(RowFamily::R, operation.addend))
.label(convention)
}),
bottom: column_operations.map(|operation| {
source
.apply(RowOperation::new(RowFamily::RI, operation.addend))
.label(convention)
}),
left: row_operations.map(|operation| source.apply(operation).label(convention)),
};
Self {
source: source.clone(),
convention,
cells,
row_operations,
column_operations,
edge_labels,
}
}
pub const fn source(&self) -> &ToneRow {
&self.source
}
pub const fn convention(&self) -> RowLabelConvention {
self.convention
}
pub const fn cells(&self) -> &[[PitchClass; ROW_MATRIX_SIZE]; ROW_MATRIX_SIZE] {
&self.cells
}
pub const fn cell(&self, coordinate: MatrixCoordinate) -> RowMatrixCell {
RowMatrixCell::new(
coordinate,
self.cells[coordinate.row()][coordinate.column()],
)
}
pub fn row(&self, row: usize) -> Option<&[PitchClass; ROW_MATRIX_SIZE]> {
self.cells.get(row)
}
pub fn column(&self, column: usize) -> Option<[PitchClass; ROW_MATRIX_SIZE]> {
(column < ROW_MATRIX_SIZE).then(|| std::array::from_fn(|row| self.cells[row][column]))
}
pub fn row_operation(&self, row: usize) -> Option<RowOperation> {
self.row_operations.get(row).copied()
}
pub fn column_operation(&self, column: usize) -> Option<RowOperation> {
self.column_operations.get(column).copied()
}
pub fn row_form(&self, row: usize) -> Option<RowForm> {
self.row_operation(row)
.map(|operation| self.source.apply(operation))
}
pub fn column_form(&self, column: usize) -> Option<RowForm> {
self.column_operation(column)
.map(|operation| self.source.apply(operation))
}
pub const fn edge_labels(&self) -> &RowMatrixEdgeLabels {
&self.edge_labels
}
}
const fn subtract_mod_twelve(left: u8, right: u8) -> u8 {
(left + 12 - right) % 12
}