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}