Skip to main content

sim_lib_pitch_serial/
segment.rs

1//! Bounds-checked ordered row segments with reused pitch-set evidence.
2
3use sim_lib_pitch_core::PitchClass;
4use sim_lib_pitch_namer_forte::lookup_forte_label;
5use sim_lib_pitch_set::{IntervalVector, PitchClassMask, SetClass, SetEquivalence, classify_set};
6
7use crate::{OrderedIntervalString, RowError, ToneRow, interval::ordered_intervals_vec};
8
9/// How a [`RowSegment`] was extracted from its source row.
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub enum RowSegmentSource {
12    /// A contiguous slice `[start, start + len)`.
13    Contiguous {
14        /// Zero-based ordinal at which the segment starts.
15        start: usize,
16        /// Number of row positions included.
17        len: usize,
18    },
19    /// A wrapping slice starting at `start` and continuing modulo twelve.
20    Wrapped {
21        /// Zero-based ordinal at which the wrapped segment starts.
22        start: usize,
23        /// Number of row positions included.
24        len: usize,
25    },
26    /// An explicit ordinal sequence.
27    Indexed,
28}
29
30/// An order-preserving segment of a tone row plus unordered pitch-set facts.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct RowSegment {
33    source: RowSegmentSource,
34    ordinals: Vec<u8>,
35    classes: Vec<PitchClass>,
36    mask: PitchClassMask,
37    interval_vector: IntervalVector,
38    set_class: SetClass,
39    forte_label: Option<&'static str>,
40}
41
42impl RowSegment {
43    /// Returns how the segment was extracted from its source row.
44    pub const fn source(&self) -> &RowSegmentSource {
45        &self.source
46    }
47
48    /// Returns the source ordinals in retained presentation order.
49    pub fn ordinals(&self) -> &[u8] {
50        &self.ordinals
51    }
52
53    /// Returns the pitch classes in retained presentation order.
54    pub fn classes(&self) -> &[PitchClass] {
55        &self.classes
56    }
57
58    /// Returns the derived unordered pitch-class mask.
59    pub const fn mask(&self) -> PitchClassMask {
60        self.mask
61    }
62
63    /// Returns the interval-class census of the unordered projection.
64    pub const fn interval_vector(&self) -> IntervalVector {
65        self.interval_vector
66    }
67
68    /// Returns the transposition-and-inversion set class of the unordered projection.
69    pub fn set_class(&self) -> &SetClass {
70        &self.set_class
71    }
72
73    /// Returns the reused Forte label when the existing naming table has one.
74    pub const fn forte_label(&self) -> Option<&'static str> {
75        self.forte_label
76    }
77
78    /// Returns the directed ordered intervals between adjacent segment members.
79    pub fn ordered_intervals(&self) -> Vec<u8> {
80        ordered_intervals_vec(&self.classes)
81    }
82
83    pub(crate) fn new(
84        source: RowSegmentSource,
85        ordinals: Vec<u8>,
86        classes: Vec<PitchClass>,
87    ) -> Self {
88        let mask = PitchClassMask::from_pitch_classes(&classes);
89        let set_class = classify_set(mask, SetEquivalence::TranspositionInversion);
90        Self {
91            source,
92            ordinals,
93            classes,
94            mask,
95            interval_vector: mask.interval_vector(),
96            forte_label: lookup_forte_label(mask),
97            set_class,
98        }
99    }
100}
101
102impl ToneRow {
103    /// Extracts a contiguous segment `[start, start + len)` from the row.
104    pub fn segment(&self, start: usize, len: usize) -> Result<RowSegment, RowError> {
105        if start > self.classes().len() || start + len > self.classes().len() {
106            return Err(RowError::SegmentOutOfBounds { start, len });
107        }
108        let ordinals = (start..start + len).map(|ordinal| ordinal as u8).collect();
109        let classes = self.classes()[start..start + len].to_vec();
110        Ok(RowSegment::new(
111            RowSegmentSource::Contiguous { start, len },
112            ordinals,
113            classes,
114        ))
115    }
116
117    /// Extracts a wrapping segment starting at `start` and continuing modulo twelve.
118    pub fn wrapped_segment(&self, start: usize, len: usize) -> Result<RowSegment, RowError> {
119        if start >= self.classes().len() {
120            return Err(RowError::InvalidOrdinal { ordinal: start });
121        }
122        if len > self.classes().len() {
123            return Err(RowError::WrappedSegmentTooLong { len });
124        }
125        let ordinals = (0..len)
126            .map(|offset| ((start + offset) % self.classes().len()) as u8)
127            .collect::<Vec<_>>();
128        let classes = ordinals
129            .iter()
130            .map(|ordinal| self.classes()[usize::from(*ordinal)])
131            .collect();
132        Ok(RowSegment::new(
133            RowSegmentSource::Wrapped { start, len },
134            ordinals,
135            classes,
136        ))
137    }
138
139    /// Extracts a segment from an explicit ordinal sequence.
140    pub fn indexed_segment(&self, ordinals: &[usize]) -> Result<RowSegment, RowError> {
141        let mut classes = Vec::with_capacity(ordinals.len());
142        let mut stored_ordinals = Vec::with_capacity(ordinals.len());
143        for ordinal in ordinals {
144            let Some(class) = self.classes().get(*ordinal).copied() else {
145                return Err(RowError::InvalidOrdinal { ordinal: *ordinal });
146            };
147            classes.push(class);
148            stored_ordinals.push(*ordinal as u8);
149        }
150        Ok(RowSegment::new(
151            RowSegmentSource::Indexed,
152            stored_ordinals,
153            classes,
154        ))
155    }
156
157    /// Returns the directed ordered intervals between adjacent row positions.
158    pub fn ordered_intervals(&self) -> OrderedIntervalString {
159        OrderedIntervalString::of_row(self)
160    }
161}