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}