Skip to main content

sim_lib_pitch_serial/
all_interval.rs

1//! All-interval evidence for strict tone rows.
2
3use crate::ToneRow;
4
5/// One duplicated adjacent directed interval together with its multiplicity.
6#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
7pub struct AllIntervalMultiplicity {
8    /// The directed interval value in semitones modulo twelve.
9    pub interval: u8,
10    /// The number of times `interval` occurs in the row.
11    pub count: u8,
12}
13
14/// Whether a row uses each non-zero directed adjacent interval exactly once.
15#[derive(Clone, Debug, PartialEq, Eq, Default)]
16pub struct AllIntervalReport {
17    /// Whether the row contains each interval `1..=11` exactly once.
18    pub is_all_interval: bool,
19    /// Intervals whose multiplicity exceeds one.
20    pub duplicates: Vec<AllIntervalMultiplicity>,
21    /// Intervals in `1..=11` that do not occur.
22    pub missing: Vec<u8>,
23}
24
25/// Computes all-interval evidence from the row's directed adjacent intervals.
26pub fn analyze_all_interval(row: &ToneRow) -> AllIntervalReport {
27    let mut counts = [0u8; 12];
28    for interval in row.ordered_intervals().intervals() {
29        counts[usize::from(*interval)] += 1;
30    }
31    let duplicates = (1..12)
32        .filter_map(|interval| {
33            (counts[interval] > 1).then_some(AllIntervalMultiplicity {
34                interval: interval as u8,
35                count: counts[interval],
36            })
37        })
38        .collect::<Vec<_>>();
39    let missing = (1..12)
40        .filter_map(|interval| (counts[interval] == 0).then_some(interval as u8))
41        .collect::<Vec<_>>();
42    AllIntervalReport {
43        is_all_interval: duplicates.is_empty() && missing.is_empty(),
44        duplicates,
45        missing,
46    }
47}