Skip to main content

sim_lib_pitch_serial/
mosaic.rs

1//! Partition mosaics over one strict tone row.
2
3use sim_lib_pitch_core::PitchClass;
4use sim_lib_pitch_set::PitchClassMask;
5
6use crate::{
7    AggregateCoverageReport, BlockOrder, RowPartition, ToneRow, analyze_aggregate_coverage,
8};
9
10/// One partition block lifted into pitch-space for mosaic inspection.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MosaicBlock {
13    /// Source partition index in caller order.
14    pub partition_index: usize,
15    /// Source block index within the partition.
16    pub block_index: usize,
17    /// Source partition ordering contract.
18    pub order: BlockOrder,
19    /// Row ordinals carried by this block.
20    pub ordinals: Vec<u8>,
21    /// Pitch classes reached on the source row.
22    pub pitch_classes: Vec<PitchClass>,
23    /// Unordered pitch-class content of the block.
24    pub mask: PitchClassMask,
25}
26
27/// A combined view of several validated partitions over one row.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct MosaicReport {
30    /// Every lifted block from every supplied partition.
31    pub blocks: Vec<MosaicBlock>,
32    /// Aggregate coverage across all lifted blocks.
33    pub aggregate_coverage: AggregateCoverageReport,
34}
35
36/// Lifts several validated partitions into one combined mosaic report.
37pub fn analyze_mosaic(row: &ToneRow, partitions: &[RowPartition]) -> MosaicReport {
38    let mut blocks = Vec::new();
39    let mut masks = Vec::new();
40    for (partition_index, partition) in partitions.iter().enumerate() {
41        for (block_index, block) in partition.blocks().iter().enumerate() {
42            let pitch_classes = block.pitch_classes(row);
43            let mask = PitchClassMask::from_pitch_classes(&pitch_classes);
44            masks.push(mask);
45            blocks.push(MosaicBlock {
46                partition_index,
47                block_index,
48                order: partition.order(),
49                ordinals: block.ordinals().to_vec(),
50                pitch_classes,
51                mask,
52            });
53        }
54    }
55    MosaicReport {
56        blocks,
57        aggregate_coverage: analyze_aggregate_coverage(
58            PitchClassMask::from_pitch_classes(row.classes()),
59            &masks,
60        ),
61    }
62}