Skip to main content

music21_rs/stream/
span.rs

1//! The elements standing in a span of offsets, or answering to a class:
2//! music21's `getElementsByOffset`, `getElementsByClass`,
3//! `getElementsNotOfClass` and `hasElementOfClass`.
4
5use super::exact::Exact;
6use super::{Stream, StreamEvent};
7use crate::defaults::FloatType;
8
9/// A span of offsets to search, and which elements on its edges the search
10/// takes: music21's `getElementsByOffset` and the keywords it reads.
11///
12/// [`Self::at`] searches one offset and [`Self::between`] a stretch. The
13/// four flags start as music21's own defaults, so a span built by either
14/// answers what music21 answers with no keywords given.
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct OffsetSpan {
17    /// Where the span starts: `offsetStart`.
18    pub start: FloatType,
19    /// Where it ends, or nothing for a span of no length at `start`:
20    /// `offsetEnd`.
21    pub end: Option<FloatType>,
22    /// Whether an element starting exactly where the span ends is taken:
23    /// `includeEndBoundary`.
24    pub include_end_boundary: bool,
25    /// Whether an element sounding past the end of the span is left out:
26    /// `mustFinishInSpan`.
27    pub must_finish_in_span: bool,
28    /// Whether an element starting before the span is left out:
29    /// `mustBeginInSpan`.
30    pub must_begin_in_span: bool,
31    /// Whether an element ending exactly where the span starts is taken:
32    /// `includeElementsThatEndAtStart`.
33    pub include_elements_that_end_at_start: bool,
34}
35
36impl OffsetSpan {
37    /// One offset, with music21's defaults: a span of no length, which
38    /// takes every element sounding across it and every mark standing on
39    /// it.
40    pub fn at(offset: FloatType) -> Self {
41        Self {
42            start: offset,
43            end: None,
44            include_end_boundary: true,
45            must_finish_in_span: false,
46            must_begin_in_span: true,
47            include_elements_that_end_at_start: true,
48        }
49    }
50
51    /// A stretch from one offset to another, with music21's defaults.
52    pub fn between(start: FloatType, end: FloatType) -> Self {
53        Self {
54            end: Some(end),
55            ..Self::at(start)
56        }
57    }
58
59    /// Where the span ends, as music21 holds it: snapped with `opFrac`,
60    /// and for a span of no length the snapped start itself, so that a
61    /// float a hair off the fraction an element stands at still finds it.
62    fn end_held(&self) -> Exact {
63        Exact::held(self.end.unwrap_or(self.start))
64    }
65
66    /// Whether the span has no length, so that every mark standing in it
67    /// is taken however long it lasts: music21's `zeroLengthSearch`.
68    fn is_one_offset(&self) -> bool {
69        self.end.is_none_or(|end| end <= self.start)
70    }
71}
72
73impl Stream {
74    /// The elements standing in a span of offsets: music21's
75    /// `getElementsByOffset`.
76    ///
77    /// Everything starting after the span ends is out, and so is
78    /// everything finishing before it starts. What touches an edge is
79    /// decided by the span's four flags, and a mark of no length standing
80    /// anywhere in a span of no length is always taken.
81    ///
82    /// **An element is only found where it begins**, because music21's
83    /// `mustBeginInSpan` starts true: a half note at nought is not found
84    /// at the offset halfway through it, though it is still sounding
85    /// there. Setting `must_begin_in_span` false is what reaches back to
86    /// it, and is how music21 asks what sounds at a point.
87    ///
88    /// Offsets are compared as music21 compares them, a snapped fraction
89    /// against a float, so an element at a third of a quarter is found by
90    /// the float that names it.
91    ///
92    /// ```
93    /// use music21_rs::stream::OffsetSpan;
94    /// use music21_rs::{Duration, Note, Stream, StreamKind};
95    ///
96    /// let mut measure = Stream::with_kind(StreamKind::Measure);
97    /// measure.insert(0.0, Note::from_name("C4")?.with_duration(Duration::half()));
98    /// measure.insert(2.0, Note::from_name("E4")?.with_duration(Duration::half()));
99    ///
100    /// // Nothing begins halfway through the first note.
101    /// assert!(measure.elements_in_span(&OffsetSpan::at(1.0)).is_empty());
102    ///
103    /// // What sounds there is asked for by reaching back.
104    /// let mut sounding = OffsetSpan::at(1.0);
105    /// sounding.must_begin_in_span = false;
106    /// let found = measure.elements_in_span(&sounding);
107    /// assert_eq!(found.len(), 1);
108    /// assert_eq!(found[0].offset(), 0.0);
109    ///
110    /// // A stretch takes what begins anywhere in it, its end included.
111    /// let found = measure.elements_in_span(&OffsetSpan::between(1.0, 2.0));
112    /// assert_eq!(found.len(), 1);
113    /// assert_eq!(found[0].offset(), 2.0);
114    /// # Ok::<(), music21_rs::Error>(())
115    /// ```
116    pub fn elements_in_span(&self, span: &OffsetSpan) -> Vec<&StreamEvent> {
117        let start = Exact::held(span.start);
118        let end = span.end_held();
119        let mut found = Vec::new();
120        for event in &self.events {
121            let offset = Exact::held(event.offset());
122            if offset.cmp(end) == std::cmp::Ordering::Greater {
123                // The stream is in offset order, so nothing later can be
124                // in the span either: music21's `stopAfterEnd`.
125                break;
126            }
127            if span.takes(
128                event.offset(),
129                event.element().quarter_length(),
130                offset,
131                start,
132                end,
133            ) {
134                found.push(event);
135            }
136        }
137        found
138    }
139
140    /// The elements answering to any of these music21 class names:
141    /// music21's `getElementsByClass`.
142    ///
143    /// ```
144    /// use music21_rs::{Note, Stream, StreamKind, TimeSignature};
145    ///
146    /// let mut measure = Stream::with_kind(StreamKind::Measure);
147    /// measure.insert(0.0, TimeSignature::new(4, 4)?);
148    /// measure.insert(0.0, Note::from_name("C4")?);
149    /// assert_eq!(measure.elements_of_class(&["Note"]).len(), 1);
150    /// assert_eq!(measure.elements_of_class(&["GeneralNote"]).len(), 1);
151    /// assert!(measure.has_element_of_class(&["TimeSignature"]));
152    /// assert_eq!(measure.elements_not_of_class(&["Note"]).len(), 1);
153    /// # Ok::<(), music21_rs::Error>(())
154    /// ```
155    pub fn elements_of_class(&self, classes: &[&str]) -> Vec<&StreamEvent> {
156        self.events
157            .iter()
158            .filter(|event| is_of_class(event, classes))
159            .collect()
160    }
161
162    /// The elements answering to none of these music21 class names:
163    /// music21's `getElementsNotOfClass`.
164    pub fn elements_not_of_class(&self, classes: &[&str]) -> Vec<&StreamEvent> {
165        self.events
166            .iter()
167            .filter(|event| !is_of_class(event, classes))
168            .collect()
169    }
170
171    /// Whether any element answers to one of these music21 class names:
172    /// music21's `hasElementOfClass`.
173    pub fn has_element_of_class(&self, classes: &[&str]) -> bool {
174        self.events.iter().any(|event| is_of_class(event, classes))
175    }
176}
177
178/// Whether an element answers to one of the class names.
179fn is_of_class(event: &StreamEvent, classes: &[&str]) -> bool {
180    classes
181        .iter()
182        .any(|class| event.element().is_instance(class))
183}
184
185impl OffsetSpan {
186    /// Whether something standing at `offset` and lasting `quarter_length`
187    /// is in the span: music21's `isElementOffsetInRange`.
188    ///
189    /// The decision turns on nothing else, which is why a caller holding
190    /// its own elements -- the Python package holds music21's objects --
191    /// can ask this of each rather than keep a second copy of the rules.
192    ///
193    /// ```
194    /// use music21_rs::stream::OffsetSpan;
195    ///
196    /// // A half note at nought does not begin in a span of no length at
197    /// // 1, which is what music21's `mustBeginInSpan` asks by default.
198    /// let at_one = OffsetSpan::at(1.0);
199    /// assert!(!at_one.takes_at(0.0, 2.0));
200    /// assert!(at_one.takes_at(1.0, 1.0));
201    ///
202    /// let mut sounding = OffsetSpan::at(1.0);
203    /// sounding.must_begin_in_span = false;
204    /// assert!(sounding.takes_at(0.0, 2.0));
205    /// ```
206    pub fn takes_at(&self, offset: FloatType, quarter_length: FloatType) -> bool {
207        let start = Exact::held(self.start);
208        let end = self.end_held();
209        let held = Exact::held(offset);
210        if held.cmp(end) == std::cmp::Ordering::Greater {
211            return false;
212        }
213        self.takes(offset, quarter_length, held, start, end)
214    }
215
216    /// Whether one element standing at an offset is in the span, which is
217    /// music21's `isElementOffsetInRange` once the elements past the end
218    /// have been left behind.
219    fn takes(
220        &self,
221        event_offset: FloatType,
222        length: FloatType,
223        offset: Exact,
224        start: Exact,
225        end: Exact,
226    ) -> bool {
227        use std::cmp::Ordering::{Equal, Greater, Less};
228
229        // music21 snaps the sum, not the two numbers it adds.
230        let element_end = Exact::held(event_offset + length);
231        if element_end.cmp(start) == Less {
232            // It finishes before the span starts.
233            return false;
234        }
235        let no_length = length == 0.0;
236        if self.is_one_offset() && no_length {
237            return true;
238        }
239        if self.must_finish_in_span {
240            if element_end.cmp(end) == Greater {
241                return false;
242            }
243            if !self.include_end_boundary && element_end.cmp(end) == Equal {
244                return false;
245            }
246        }
247        if self.must_begin_in_span {
248            if offset.cmp(start) == Less {
249                return false;
250            }
251            if !self.include_end_boundary && offset.cmp(end) == Equal {
252                return false;
253            }
254        } else if !no_length && element_end.cmp(end) == Equal && self.is_one_offset() {
255            return false;
256        }
257        if !self.include_end_boundary && offset.cmp(end) == Equal {
258            return false;
259        }
260        if !self.include_elements_that_end_at_start && element_end.cmp(start) == Equal {
261            return false;
262        }
263        true
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270    use crate::duration::Duration;
271    use crate::note::Note;
272    use crate::stream::StreamKind;
273
274    fn measure() -> Stream {
275        let mut measure = Stream::with_kind(StreamKind::Measure);
276        for (offset, name, length) in [(0.0, "C4", 2.0), (2.0, "E4", 1.0), (3.0, "G4", 1.0)] {
277            let note = Note::from_name(name)
278                .expect("a note name")
279                .with_duration(Duration::new(length).expect("a length"));
280            measure.insert(offset, note);
281        }
282        measure
283    }
284
285    /// Every expectation below is music21's own answer for the same
286    /// measure, read off `getElementsByOffset`.
287    #[test]
288    fn one_offset_finds_only_what_begins_there() {
289        let measure = measure();
290        let offsets = |span: &OffsetSpan| -> Vec<FloatType> {
291            measure
292                .elements_in_span(span)
293                .iter()
294                .map(|event| event.offset())
295                .collect()
296        };
297        // The note at nought is still sounding at 1, but it does not
298        // begin in the span, which music21 asks for by default.
299        assert_eq!(offsets(&OffsetSpan::at(1.0)), []);
300        let mut sounding = OffsetSpan::at(1.0);
301        sounding.must_begin_in_span = false;
302        assert_eq!(offsets(&sounding), [0.0]);
303
304        assert_eq!(offsets(&OffsetSpan::at(3.0)), [3.0]);
305        assert_eq!(offsets(&OffsetSpan::at(4.0)), []);
306
307        // Reaching back at the offset where the first note ends does not
308        // find it: a span of no length refuses an element ending exactly
309        // at its end.
310        let mut sounding = OffsetSpan::at(2.0);
311        sounding.must_begin_in_span = false;
312        assert_eq!(offsets(&sounding), [2.0]);
313    }
314
315    #[test]
316    fn a_stretch_reads_its_edges_as_the_flags_say() {
317        let measure = measure();
318        let offsets = |span: &OffsetSpan| -> Vec<FloatType> {
319            measure
320                .elements_in_span(span)
321                .iter()
322                .map(|event| event.offset())
323                .collect()
324        };
325        assert_eq!(offsets(&OffsetSpan::between(1.0, 3.0)), [2.0, 3.0]);
326
327        let mut no_end = OffsetSpan::between(1.0, 3.0);
328        no_end.include_end_boundary = false;
329        assert_eq!(offsets(&no_end), [2.0]);
330
331        let mut reaching_back = OffsetSpan::between(1.0, 3.0);
332        reaching_back.must_begin_in_span = false;
333        assert_eq!(offsets(&reaching_back), [0.0, 2.0, 3.0]);
334
335        let mut inside = OffsetSpan::between(1.0, 3.0);
336        inside.must_finish_in_span = true;
337        assert_eq!(offsets(&inside), [2.0]);
338
339        assert_eq!(offsets(&OffsetSpan::between(0.0, 4.0)), [0.0, 2.0, 3.0]);
340
341        // Reaching back over a stretch does find an element ending where
342        // it starts, until that is refused.
343        let mut reaching_back = OffsetSpan::between(2.0, 3.0);
344        reaching_back.must_begin_in_span = false;
345        assert_eq!(offsets(&reaching_back), [0.0, 2.0, 3.0]);
346        reaching_back.include_elements_that_end_at_start = false;
347        assert_eq!(offsets(&reaching_back), [2.0, 3.0]);
348
349        let mut no_end = OffsetSpan::between(1.0, 3.0);
350        no_end.include_end_boundary = false;
351        no_end.must_begin_in_span = false;
352        assert_eq!(offsets(&no_end), [0.0, 2.0]);
353    }
354
355    #[test]
356    fn a_float_a_hair_off_a_fraction_still_finds_the_element() {
357        let mut measure = Stream::with_kind(StreamKind::Measure);
358        let note = Note::from_name("C4")
359            .expect("a note name")
360            .with_duration(Duration::new(1.0 / 3.0).expect("a third"));
361        // Five sixths, where music21 keeps the offset as a fraction.
362        measure.insert(1.0 / 3.0 + 0.5, note);
363        let found = measure.elements_in_span(&OffsetSpan::at(1.0 / 3.0 + 0.5));
364        assert_eq!(found.len(), 1);
365    }
366
367    #[test]
368    fn a_class_name_answers_for_the_classes_an_element_stands_in() {
369        let measure = measure();
370        assert_eq!(measure.elements_of_class(&["Note"]).len(), 3);
371        assert_eq!(measure.elements_of_class(&["NotRest", "Rest"]).len(), 3);
372        assert!(measure.elements_of_class(&["Clef"]).is_empty());
373        assert!(!measure.has_element_of_class(&["Clef"]));
374        assert!(measure.has_element_of_class(&["Clef", "Note"]));
375        assert_eq!(measure.elements_not_of_class(&["Note"]).len(), 0);
376        assert_eq!(measure.elements_not_of_class(&["Clef"]).len(), 3);
377    }
378}