Skip to main content

music21_rs/stream/
lookups.rs

1//! Finding elements by where they stand: music21's `getElementAtOrBefore`,
2//! `getElementBeforeOffset`, `getElementAfterElement`,
3//! `beatAndMeasureFromOffset`, `getTimeSignatures` and `getInstruments`.
4
5use super::exact::Exact;
6use super::{Stream, StreamElement, StreamEvent};
7use crate::defaults::FloatType;
8use crate::error::{Error, Result};
9use crate::instrument::Instrument;
10use crate::makenotation::{event_order, op_frac};
11use crate::meter::TimeSignature;
12
13fn stream_error(message: impl Into<String>) -> Error {
14    Error::Stream(message.into())
15}
16
17/// Whether an element is of one of `classes`, or of any where none are
18/// named.
19fn of_classes(element: &StreamElement, classes: &[&str]) -> bool {
20    classes.is_empty() || classes.iter().any(|class| element.is_instance(class))
21}
22
23/// The measure an event is, with where it stands.
24fn measure_of(event: &StreamEvent) -> Option<(FloatType, &Stream)> {
25    match event.element() {
26        StreamElement::Stream(inner) => Some((event.offset(), &**inner)),
27        _ => None,
28    }
29}
30
31impl Stream {
32    /// The element this stream holds itself that starts at `offset`, or
33    /// nearest before it, of one of `classes` (any where none are named):
34    /// music21's `getElementAtOrBefore`. Of several starting at the same
35    /// place, the last in the stream's order.
36    ///
37    /// music21 also looks among a stream's elements stored at its end, which
38    /// is where it keeps a measure's right barline; the crate keeps barlines
39    /// beside the elements ([`Stream::right_barline`]), so they are not
40    /// found here.
41    ///
42    /// ```
43    /// use music21_rs::tinynotation::from_tiny_notation;
44    ///
45    /// let line = from_tiny_notation("4/4 c4 d4 e2")?.flatten();
46    /// let found = line.element_at_or_before(1.5, &["Note"]).unwrap();
47    /// assert_eq!(found.offset(), 1.0);
48    /// # Ok::<(), music21_rs::Error>(())
49    /// ```
50    pub fn element_at_or_before(
51        &self,
52        offset: FloatType,
53        classes: &[&str],
54    ) -> Option<&StreamEvent> {
55        self.nearest_before(offset, classes, false)
56    }
57
58    /// The element nearest before `offset`, not at it: music21's
59    /// `getElementBeforeOffset`. As [`Stream::element_at_or_before`]
60    /// otherwise.
61    pub fn element_before_offset(
62        &self,
63        offset: FloatType,
64        classes: &[&str],
65    ) -> Option<&StreamEvent> {
66        self.nearest_before(offset, classes, true)
67    }
68
69    fn nearest_before(
70        &self,
71        offset: FloatType,
72        classes: &[&str],
73        before_not_at: bool,
74    ) -> Option<&StreamEvent> {
75        let offset = op_frac(offset);
76        let mut best: Option<(FloatType, &StreamEvent)> = None;
77        for event in &self.events {
78            if !of_classes(event.element(), classes) {
79                continue;
80            }
81            let span = op_frac(offset - event.offset());
82            if span < 0.0 || (span == 0.0 && before_not_at) {
83                continue;
84            }
85            // music21 starts the nearest span at the offset itself, so an
86            // element standing before nought is never found.
87            let better = match best {
88                None => span <= offset,
89                Some((nearest, held)) => {
90                    span < nearest
91                        || (span == nearest && event_order(event, held) != std::cmp::Ordering::Less)
92                }
93            };
94            if better {
95                best = Some((span, event));
96            }
97        }
98        best.map(|(_, event)| event)
99    }
100
101    /// The element after the one at `index` among this stream's own, of one
102    /// of `classes` (any where none are named): music21's
103    /// `getElementAfterElement`.
104    ///
105    /// # Errors
106    ///
107    /// An index this stream has no element at.
108    pub fn element_after(&self, index: usize, classes: &[&str]) -> Result<Option<&StreamEvent>> {
109        if index >= self.events.len() {
110            return Err(stream_error("cannot find object in this Stream"));
111        }
112        Ok(self.events[index + 1..]
113            .iter()
114            .find(|event| of_classes(event.element(), classes)))
115    }
116
117    /// Which beat of which measure `offset` falls on: music21's
118    /// `beatAndMeasureFromOffset`. The beat counts from one, as a
119    /// proportion of the time signature in force at the measure.
120    ///
121    /// A measure with a number suffix, or numbered nought where
122    /// `fix_zeros`, is taken as the second half of the measure before it,
123    /// which is answered instead, with the beat counted on from that
124    /// measure's last; a measure so numbered that opens the piece is taken
125    /// as a pickup, its beats counted from the end of a full bar. A score
126    /// answers for its first part with measures.
127    ///
128    /// ```
129    /// use music21_rs::tinynotation::from_tiny_notation;
130    ///
131    /// let line = from_tiny_notation("3/4 c4 d4 e4 f2.")?;
132    /// let (beat, measure) = line.beat_and_measure_from_offset(4.0, true)?;
133    /// assert_eq!((beat, measure.number()), (2.0, 2));
134    /// # Ok::<(), music21_rs::Error>(())
135    /// ```
136    ///
137    /// # Errors
138    ///
139    /// A stream with no measures, or none in any part; no measure at or
140    /// before `offset`; no time signature in force there; or an offset past
141    /// the bar it falls in.
142    pub fn beat_and_measure_from_offset(
143        &self,
144        offset: FloatType,
145        fix_zeros: bool,
146    ) -> Result<(FloatType, &Stream)> {
147        let mut holder = self;
148        if !self.has_measures() {
149            if !self.has_part_like_streams() {
150                return Err(stream_error(
151                    "beatAndMeasureFromOffset: could not find any measures!",
152                ));
153            }
154            holder = self
155                .events
156                .iter()
157                .filter_map(|event| event.element().as_stream())
158                .find(|inner| inner.has_measures())
159                .ok_or_else(|| {
160                    stream_error("beatAndMeasureFromOffset: could not find any parts!")
161                })?;
162        }
163        let (measure_offset, measure) = holder
164            .element_at_or_before(offset, &["Measure"])
165            .and_then(measure_of)
166            .ok_or_else(|| stream_error("beatAndMeasureFromOffset: no measure at that offset."))?;
167        let meter = holder.time_signature_at(measure_offset).ok_or_else(|| {
168            stream_error(
169                "beatAndMeasureFromOffset: could not find a time signature for that place.",
170            )
171        })?;
172        let beat = meter
173            .beat_proportion(op_frac(offset - measure_offset))
174            .map_err(|_| {
175                stream_error("beatAndMeasureFromOffset: offset is beyond the end of the piece")
176            })?;
177
178        let suffixed = measure
179            .number_suffix()
180            .is_some_and(|suffix| !suffix.is_empty());
181        if !suffixed && !(fix_zeros && measure.number() == 0) {
182            return Ok((beat, measure));
183        }
184        // music21 asks whether the measure before is truthy: whether it
185        // holds anything.
186        let previous = holder
187            .element_before_offset(measure_offset, &["Measure"])
188            .and_then(measure_of)
189            .filter(|(_, previous)| {
190                !previous.events.is_empty() || previous.right_barline().is_some()
191            });
192        match previous {
193            Some((previous_offset, previous)) => {
194                let earlier = holder.time_signature_at(previous_offset).ok_or_else(|| {
195                    stream_error(
196                        "beatAndMeasureFromOffset: partial measure found, but could not find a time signature for the preceding measure",
197                    )
198                })?;
199                let filled = op_frac(previous.end_offset());
200                let pad = if filled == earlier.bar_quarter_length() {
201                    Exact::integer(i64::from(earlier.beat_count()))
202                } else {
203                    Exact::held(earlier.beat_proportion(filled)?).plus(Exact::integer(-1))
204                };
205                Ok((Exact::held(beat).plus(pad).to_float(), previous))
206            }
207            None => {
208                let pad =
209                    Exact::held(meter.beat_proportion(
210                        meter.bar_quarter_length() - op_frac(measure.end_offset()),
211                    )?)
212                    .plus(Exact::integer(-1));
213                Ok((Exact::held(beat).plus(pad).to_float(), measure))
214            }
215        }
216    }
217
218    /// The time signatures this stream holds, however deep, each where it
219    /// stands from the stream's start: music21's `getTimeSignatures`. With
220    /// `return_default`, 4/4 is put first at nought where none stands there.
221    ///
222    /// music21 also looks outside a stream holding none, through the
223    /// streams that hold it; a crate stream knows nothing outside itself.
224    ///
225    /// # Errors
226    ///
227    /// None in practice: the default 4/4 is always made.
228    pub fn time_signatures(&self, return_default: bool) -> Result<Vec<(FloatType, TimeSignature)>> {
229        let mut found: Vec<(FloatType, TimeSignature)> = self
230            .flatten()
231            .events
232            .into_iter()
233            .filter_map(|event| match event.element {
234                StreamElement::TimeSignature(meter) => Some((event.offset, meter)),
235                _ => None,
236            })
237            .collect();
238        if return_default && found.first().is_none_or(|(offset, _)| *offset > 0.0) {
239            found.insert(0, (0.0, TimeSignature::new(4, 4)?));
240        }
241        Ok(found)
242    }
243
244    /// The instruments this stream holds, however deep where `recurse`, in
245    /// order: music21's `getInstruments`. With `return_default`, an
246    /// instrument with an empty part name stands for none found.
247    ///
248    /// music21 also asks the stream holding this one where it holds none; a
249    /// crate stream knows nothing outside itself.
250    pub fn instruments(&self, recurse: bool, return_default: bool) -> Vec<Instrument> {
251        let held = if recurse {
252            self.flatten()
253        } else {
254            self.clone()
255        };
256        let mut found: Vec<Instrument> = held
257            .events
258            .into_iter()
259            .filter_map(|event| match event.element {
260                StreamElement::Instrument(instrument) => Some(*instrument),
261                _ => None,
262            })
263            .collect();
264        if return_default && found.is_empty() {
265            let mut instrument = Instrument::new();
266            instrument.set_part_name(Some(String::new()));
267            found.push(instrument);
268        }
269        found
270    }
271
272    /// The first instrument this stream holds itself, or with `recurse`
273    /// however deep: music21's `getInstrument`. With `return_default`, an
274    /// instrument with an empty part name where none is.
275    pub fn instrument(&self, recurse: bool, return_default: bool) -> Option<Instrument> {
276        self.instruments(recurse, return_default).into_iter().next()
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use crate::duration::Duration;
284    use crate::note::Note;
285    use crate::rest::Rest;
286    use crate::stream::StreamKind;
287
288    #[test]
289    fn the_nearest_before_is_the_last_of_its_offset() -> Result<()> {
290        let mut stream = Stream::new();
291        stream.insert(0.0, Note::from_name("C4")?);
292        stream.insert(1.0, Rest::new(Duration::quarter()));
293        stream.insert(1.0, Note::from_name("D4")?);
294        let at = stream.element_at_or_before(1.0, &[]).unwrap();
295        assert_eq!(at.offset(), 1.0);
296        assert!(matches!(at.element(), StreamElement::Note(_)));
297        assert!(matches!(
298            stream
299                .element_at_or_before(1.0, &["Rest"])
300                .unwrap()
301                .element(),
302            StreamElement::Rest(_)
303        ));
304        assert_eq!(
305            stream.element_before_offset(1.0, &[]).unwrap().offset(),
306            0.0
307        );
308        assert!(stream.element_before_offset(0.0, &[]).is_none());
309        assert!(stream.element_at_or_before(-1.0, &[]).is_none());
310        assert!(matches!(
311            stream.element_after(0, &["Note"])?.unwrap().element(),
312            StreamElement::Note(_)
313        ));
314        assert!(stream.element_after(2, &[])?.is_none());
315        assert!(stream.element_after(3, &[]).is_err());
316        Ok(())
317    }
318
319    #[test]
320    fn a_pickup_counts_its_beats_from_the_end_of_a_bar() -> Result<()> {
321        let mut part = Stream::with_kind(StreamKind::Part);
322        let mut pickup = Stream::with_kind(StreamKind::Measure);
323        pickup.insert(0.0, TimeSignature::new(4, 4)?);
324        pickup.insert(0.0, Note::from_name("G4")?);
325        let mut first = Stream::with_kind(StreamKind::Measure);
326        first.set_number(1);
327        first.insert(0.0, Note::from_name("C5")?.with_duration(Duration::whole()));
328        part.insert(0.0, pickup);
329        part.insert(1.0, first);
330        let (beat, measure) = part.beat_and_measure_from_offset(0.0, true)?;
331        assert_eq!((beat, measure.number()), (4.0, 0));
332        let (beat, measure) = part.beat_and_measure_from_offset(3.0, true)?;
333        assert_eq!((beat, measure.number()), (3.0, 1));
334        Ok(())
335    }
336
337    #[test]
338    fn a_default_meter_and_instrument_stand_in_for_none() -> Result<()> {
339        let stream = Stream::new();
340        let meters = stream.time_signatures(true)?;
341        assert_eq!(meters.len(), 1);
342        assert_eq!(meters[0].1.ratio_string(), "4/4");
343        assert!(stream.time_signatures(false)?.is_empty());
344        assert_eq!(stream.instrument(true, true).unwrap().part_name(), Some(""));
345        assert!(stream.instrument(true, false).is_none());
346        Ok(())
347    }
348}