Skip to main content

music21_rs/stream/
seconds.rs

1//! How long a stream lasts in seconds, and which tempo holds where:
2//! music21's `Stream.seconds` and `metronomeMarkBoundaries`.
3//!
4//! Both read the tempi the stream holds. music21's `seconds` also searches
5//! the stream's context for a tempo when the stream states none from its
6//! start; a crate stream knows nothing outside itself, so the tempo in
7//! force is given to it.
8
9use super::{Stream, StreamElement};
10use crate::defaults::FloatType;
11use crate::error::{Error, Result};
12use crate::tempo::MetronomeMark;
13
14/// One stretch of a stream and the tempo sounding through it: a boundary
15/// of music21's `metronomeMarkBoundaries`.
16#[derive(Clone, Debug, PartialEq)]
17#[must_use]
18pub struct TempoSpan {
19    /// Where the stretch begins.
20    pub start: FloatType,
21    /// Where it ends, which is where the next tempo stands, or the end of
22    /// the stream for the last.
23    pub end: FloatType,
24    /// The tempo sounding through it.
25    pub mark: MetronomeMark,
26    /// The position among [`Stream::leaves`] of the mark the stream holds,
27    /// where the tempo is one of its own: nothing for the quarter at 120
28    /// standing in for a stream that states none, and nothing for what a
29    /// tempo word or a metric modulation means.
30    pub position: Option<usize>,
31}
32
33impl Stream {
34    /// The stretches of the stream each tempo holds, start, end and the
35    /// mark sounding there: music21's `metronomeMarkBoundaries`.
36    ///
37    /// The stream is read flattened, so a tempo written on a part is found
38    /// from the score. Where it states no tempo, or none from its start, a
39    /// quarter at 120 holds.
40    ///
41    /// # Errors
42    ///
43    /// A metric modulation whose new tempo cannot be worked out.
44    pub fn metronome_mark_boundaries(&self) -> Result<Vec<(FloatType, FloatType, MetronomeMark)>> {
45        Ok(self
46            .metronome_mark_positions()?
47            .into_iter()
48            .map(|boundary| (boundary.start, boundary.end, boundary.mark))
49            .collect())
50    }
51
52    /// [`Stream::metronome_mark_boundaries`], each stretch also saying
53    /// which element the tempo was read off, by its position among
54    /// [`Stream::leaves`].
55    ///
56    /// music21 hands back the mark objects the stream holds, so a caller
57    /// keeping something of its own beside each element -- a binding
58    /// keeping the object it was handed -- finds the mark again by that
59    /// position. A stretch standing under no mark of the stream's carries
60    /// none: the quarter at 120 standing in for a stream that states no
61    /// tempo, and the mark a tempo word or a metric modulation *means*,
62    /// which music21 builds afresh as well.
63    ///
64    /// # Errors
65    ///
66    /// A metric modulation whose new tempo cannot be worked out.
67    pub fn metronome_mark_positions(&self) -> Result<Vec<TempoSpan>> {
68        let leaves = self.leaves();
69        // The leaves as `flatten` orders them: the stream is read
70        // flattened, so a tempo written on a part is found from the score.
71        let order = Stream::flatten_order(&leaves);
72        let highest = crate::makenotation::op_frac(
73            leaves
74                .iter()
75                .map(|(offset, element)| offset + element.quarter_length())
76                .fold(0.0, FloatType::max),
77        );
78        let lowest = order.first().map_or(0.0, |place| leaves[*place].0);
79        let mut marks: Vec<(FloatType, MetronomeMark, Option<usize>)> = Vec::new();
80        for place in order {
81            let (offset, element) = leaves[place];
82            let previous = marks.last().map(|(_, mark, _)| mark.clone());
83            let Some(sounding) = sounding_mark(element, previous.as_ref())? else {
84                continue;
85            };
86            // Only a mark of the stream's own is the object music21 hands
87            // back; what a tempo word or a modulation means is built here.
88            let held = matches!(element, StreamElement::MetronomeMark(_)).then_some(place);
89            marks.push((offset, sounding, held));
90        }
91        let default = |start, end| TempoSpan {
92            start,
93            end,
94            mark: MetronomeMark::new(120.0),
95            position: None,
96        };
97        let span = |(offset, mark, position): &(FloatType, MetronomeMark, Option<usize>),
98                    end: FloatType| TempoSpan {
99            start: *offset,
100            end,
101            mark: mark.clone(),
102            position: *position,
103        };
104        let mut boundaries = Vec::new();
105        match marks.as_slice() {
106            [] => boundaries.push(default(lowest, highest)),
107            [only] => {
108                if only.0 > lowest {
109                    boundaries.push(default(lowest, only.0));
110                }
111                boundaries.push(span(only, highest));
112            }
113            _ => {
114                if marks[0].0 > lowest {
115                    boundaries.push(default(lowest, marks[0].0));
116                }
117                boundaries.push(span(&marks[0], marks[1].0));
118                for index in 1..marks.len() {
119                    let end = marks.get(index + 1).map_or(highest, |next| next.0);
120                    boundaries.push(span(&marks[index], end));
121                }
122            }
123        }
124        Ok(boundaries)
125    }
126
127    /// How long the stream lasts in seconds: music21's `seconds`.
128    ///
129    /// Only the tempi the stream holds *itself* count, as music21's do --
130    /// a tempo inside a part of a score is not one of the score's own --
131    /// and each holds until the next, the last to the end of the stream.
132    /// Where the stream states no tempo, or none from its start,
133    /// `in_force` stands for what music21 searches its context for, and is
134    /// read as a mark standing at nought, which is where music21 puts what
135    /// it finds. What music21's own search finds turns on sort order: a
136    /// measure standing *after* a tempo in its part finds it, one standing
137    /// at the same offset does not, a stream sorting before the marks at
138    /// its offset.
139    ///
140    /// With no tempo anywhere, a stream holding nothing that lasts is
141    /// nought seconds long, and one holding something is `NaN`, which is
142    /// what music21 answers rather than refusing.
143    ///
144    /// ```
145    /// use music21_rs::{Duration, MetronomeMark, Note, Stream, StreamKind};
146    ///
147    /// let mut part = Stream::with_kind(StreamKind::Part);
148    /// part.insert(0.0, Note::from_name("C4")?.with_duration(Duration::whole()));
149    /// // A quarter at 60 makes the whole note four seconds.
150    /// assert_eq!(part.seconds(Some(&MetronomeMark::new(60.0)))?, 4.0);
151    ///
152    /// // A tempo the stream holds itself is used from where it stands.
153    /// part.insert(0.0, MetronomeMark::new(120.0));
154    /// assert_eq!(part.seconds(None)?, 2.0);
155    ///
156    /// // With no tempo at all, something that lasts answers NaN.
157    /// let mut bare = Stream::new();
158    /// assert_eq!(bare.seconds(None)?, 0.0);
159    /// bare.insert(0.0, Note::from_name("C4")?.with_duration(Duration::half()));
160    /// assert!(bare.seconds(None)?.is_nan());
161    /// # Ok::<(), music21_rs::Error>(())
162    /// ```
163    ///
164    /// # Errors
165    ///
166    /// A tempo that says no number, or a metric modulation whose new tempo
167    /// cannot be worked out.
168    pub fn seconds(&self, in_force: Option<&MetronomeMark>) -> Result<FloatType> {
169        let highest = self.end_offset();
170        let mut marks: Vec<(FloatType, MetronomeMark)> = Vec::new();
171        for event in self.events() {
172            let previous = marks.last().map(|(_, mark)| mark.clone());
173            let Some(sounding) = sounding_mark(event.element(), previous.as_ref())? else {
174                continue;
175            };
176            marks.push((event.offset(), sounding));
177        }
178        if marks.first().is_none_or(|(offset, _)| *offset > 0.0) {
179            let Some(in_force) = in_force else {
180                // music21 answers nought for a stream with nothing in it
181                // that lasts, and NaN rather than refusing otherwise.
182                return Ok(if highest == 0.0 { 0.0 } else { FloatType::NAN });
183            };
184            marks.insert(0, (0.0, in_force.clone()));
185        }
186        if let [(_, only)] = marks.as_slice() {
187            // With one tempo music21 counts the whole stream at it, from
188            // nought rather than from where the tempo stands.
189            return only.quarter_length_to_seconds(highest);
190        }
191        let mut total = 0.0;
192        for (at, (offset, mark)) in marks.iter().enumerate() {
193            let end = marks.get(at + 1).map_or(highest, |(next, _)| *next);
194            total += mark.quarter_length_to_seconds(end - offset)?;
195        }
196        Ok(total)
197    }
198}
199
200/// The tempo one element sounds, or nothing where the element is not a
201/// tempo at all: music21's `getSoundingMetronomeMark`.
202///
203/// A metric modulation's new tempo is worked out from the tempo before it
204/// where it says no number of its own, which music21 has filled in by the
205/// time it reads the modulation.
206///
207/// # Errors
208///
209/// A metric modulation whose new tempo cannot be worked out.
210fn sounding_mark(
211    element: &StreamElement,
212    previous: Option<&MetronomeMark>,
213) -> Result<Option<MetronomeMark>> {
214    let sounding = match element {
215        StreamElement::MetronomeMark(mark) => mark.clone(),
216        StreamElement::TempoText(text) => text.metronome_mark(),
217        StreamElement::MetricModulation(modulation) => {
218            let mut modulation = (**modulation).clone();
219            if modulation
220                .new_metronome()
221                .is_some_and(|mark| mark.number().is_none())
222            {
223                modulation.update_from(previous);
224            }
225            modulation
226                .new_metronome()
227                .cloned()
228                .ok_or_else(|| Error::Tempo("a metric modulation with no new tempo".to_string()))?
229        }
230        _ => return Ok(None),
231    };
232    Ok(Some(sounding))
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use crate::duration::Duration;
239    use crate::note::Note;
240    use crate::stream::StreamKind;
241
242    fn four_quarters() -> Stream {
243        let mut part = Stream::with_kind(StreamKind::Part);
244        for place in 0..4 {
245            let note = Note::from_name("C4")
246                .expect("a note name")
247                .with_duration(Duration::new(1.0).expect("a quarter"));
248            part.insert(FloatType::from(place), note);
249        }
250        part
251    }
252
253    #[test]
254    fn a_tempo_the_stream_holds_counts_from_where_it_stands() {
255        let mut part = four_quarters();
256        part.insert(0.0, MetronomeMark::new(60.0));
257        assert_eq!(part.seconds(None).expect("seconds"), 4.0);
258        // The second tempo holds from offset 2 to the end.
259        part.insert(2.0, MetronomeMark::new(120.0));
260        assert_eq!(part.seconds(None).expect("seconds"), 3.0);
261    }
262
263    /// music21 prepends the tempo it finds in context at offset nought, so
264    /// each answer here is music21's own for the same stream with the mark
265    /// standing at nought: 2.0 and 2.5.
266    #[test]
267    fn a_tempo_in_force_stands_for_what_music21_finds_in_context() {
268        let part = four_quarters();
269        assert_eq!(
270            part.seconds(Some(&MetronomeMark::new(120.0)))
271                .expect("seconds"),
272            2.0
273        );
274        // A tempo from offset 1 leaves the first quarter to the one in
275        // force, as music21 does by inserting it at nought.
276        let mut later = four_quarters();
277        later.insert(1.0, MetronomeMark::new(120.0));
278        assert_eq!(
279            later
280                .seconds(Some(&MetronomeMark::new(60.0)))
281                .expect("seconds"),
282            2.5
283        );
284    }
285
286    #[test]
287    fn nothing_that_lasts_is_nought_seconds_and_anything_else_is_not_a_number() {
288        let empty = Stream::new();
289        assert_eq!(empty.seconds(None).expect("seconds"), 0.0);
290        assert!(four_quarters().seconds(None).expect("seconds").is_nan());
291    }
292
293    /// The positions are how a caller holding an object per element finds
294    /// the very mark music21 would have handed back.
295    #[test]
296    fn a_boundary_says_which_mark_of_the_streams_own_sounds_through_it() {
297        let mut part = four_quarters();
298        part.insert(2.0, MetronomeMark::new(90.0));
299        let spans = part.metronome_mark_positions().expect("boundaries");
300        assert_eq!(spans.len(), 2);
301
302        // The quarter at 120 standing in for the tempo the stream says
303        // nothing of up to offset 2 is nobody's mark.
304        assert_eq!((spans[0].start, spans[0].end), (0.0, 2.0));
305        assert_eq!(spans[0].mark.number(), Some(120.0));
306        assert_eq!(spans[0].position, None);
307
308        assert_eq!((spans[1].start, spans[1].end), (2.0, 4.0));
309        let leaves = part.leaves();
310        let position = spans[1].position.expect("the mark the stream holds");
311        assert!(matches!(
312            leaves[position].1,
313            StreamElement::MetronomeMark(mark) if mark.number() == Some(90.0)
314        ));
315    }
316
317    /// A tempo word *means* a tempo rather than holding one, as a metric
318    /// modulation does, so there is no object to point at.
319    #[test]
320    fn the_mark_a_tempo_word_means_is_nobodys() {
321        let mut part = four_quarters();
322        part.insert(0.0, crate::tempo::TempoText::new("adagio"));
323        let spans = part.metronome_mark_positions().expect("boundaries");
324        assert_eq!(spans.len(), 1);
325        assert_eq!(spans[0].mark.number(), Some(56.0));
326        assert_eq!(spans[0].position, None);
327    }
328
329    #[test]
330    fn a_stream_with_no_tempo_is_counted_at_a_quarter_at_120() {
331        let boundaries = four_quarters()
332            .metronome_mark_boundaries()
333            .expect("boundaries");
334        assert_eq!(boundaries.len(), 1);
335        assert_eq!(boundaries[0].2.number(), Some(120.0));
336    }
337}