Skip to main content

music21_rs/
tempo.rs

1//! Metronome marks, a port of the stream-free part of music21's `tempo`.
2
3use crate::{
4    defaults::FloatType,
5    display::Drawn,
6    duration::Duration,
7    error::{Error, Result},
8    notation::Placement,
9    stream::Stream,
10};
11
12/// The tempo words music21 knows and the beats per minute each implies,
13/// in music21's order.
14pub const DEFAULT_TEMPO_VALUES: [(&str, FloatType); 30] = [
15    ("larghissimo", 16.0),
16    ("largamente", 32.0),
17    ("grave", 40.0),
18    ("molto adagio", 40.0),
19    ("largo", 46.0),
20    ("lento", 52.0),
21    ("adagio", 56.0),
22    ("slow", 56.0),
23    ("langsam", 56.0),
24    ("larghetto", 60.0),
25    ("adagietto", 66.0),
26    ("andante", 72.0),
27    ("andantino", 80.0),
28    ("andante moderato", 83.0),
29    ("maestoso", 88.0),
30    ("moderato", 92.0),
31    ("moderate", 92.0),
32    ("allegretto", 108.0),
33    ("animato", 120.0),
34    ("allegro moderato", 128.0),
35    ("allegro", 132.0),
36    ("fast", 132.0),
37    ("schnell", 132.0),
38    ("allegrissimo", 140.0),
39    ("molto allegro", 144.0),
40    ("très vite", 144.0),
41    ("vivace", 160.0),
42    ("vivacissimo", 168.0),
43    ("presto", 184.0),
44    ("prestissimo", 208.0),
45];
46
47/// Converts a tempo counted in one note value into the same tempo counted in
48/// another, both given in quarter lengths.
49///
50/// Sixty half notes a minute is a hundred and twenty quarters.
51pub fn convert_tempo_by_referent(
52    number: FloatType,
53    source_quarter_length: FloatType,
54    destination_quarter_length: FloatType,
55) -> FloatType {
56    let seconds_per_source_beat = 60.0 / number;
57    let seconds_per_quarter = seconds_per_source_beat / source_quarter_length;
58    60.0 / (seconds_per_quarter * destination_quarter_length)
59}
60
61/// A number within a millionth of a whole one as that whole number, which is
62/// what music21's `numToIntOrFloat` makes of every number a mark is given:
63/// a tempo counted in another note value comes back a hair off, and the mark
64/// keeps `165`, not `165.00000000000003`.
65fn whole_where_nearly(number: FloatType) -> FloatType {
66    let rounded = number.round_ties_even();
67    if (rounded - number).abs() < 1e-6 {
68        rounded
69    } else {
70        number
71    }
72}
73
74/// Returns the tempo word music21 pairs with a beats-per-minute value, when
75/// one lies within two beats of it. Ties go to the lower value, then the
76/// alphabetically earlier word, as music21 sorts them.
77pub fn default_text_for_number(number: FloatType) -> Option<&'static str> {
78    let mut sorted = DEFAULT_TEMPO_VALUES;
79    sorted.sort_by(|left, right| {
80        left.1
81            .partial_cmp(&right.1)
82            .unwrap_or(std::cmp::Ordering::Equal)
83            .then_with(|| left.0.cmp(right.0))
84    });
85    sorted
86        .iter()
87        .find(|(_, value)| (value - 2.0..=value + 2.0).contains(&number))
88        .map(|(text, _)| *text)
89}
90
91/// Returns the beats per minute music21 pairs with a tempo word, matching the
92/// whole text case-insensitively first and any single word of it second.
93pub fn default_number_for_text(text: &str) -> Option<FloatType> {
94    let lowered = text.to_lowercase();
95    let lookup = |candidate: &str| {
96        DEFAULT_TEMPO_VALUES
97            .iter()
98            .find(|(name, _)| *name == candidate)
99            .map(|(_, value)| *value)
100    };
101    lookup(&lowered).or_else(|| lookup(text)).or_else(|| {
102        text.split(' ')
103            .filter_map(|word| lookup(&word.to_lowercase()))
104            .next_back()
105    })
106}
107
108/// A metronome marking: a beats-per-minute number, a tempo word, and the
109/// note value the number counts.
110///
111/// Either half may be implied from the other: a number alone picks up the
112/// nearest tempo word, and a word alone picks up its conventional number.
113#[derive(Clone, Debug, PartialEq)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[must_use]
116pub struct MetronomeMark {
117    number: Option<FloatType>,
118    text: Option<String>,
119    referent: Duration,
120    number_implicit: bool,
121    text_implicit: bool,
122    /// What the mark is played at, where that is not what it says.
123    #[cfg_attr(feature = "serde", serde(default))]
124    number_sounding: Option<FloatType>,
125    #[cfg_attr(feature = "serde", serde(default))]
126    parentheses: Drawn<bool>,
127    #[cfg_attr(feature = "serde", serde(default))]
128    placement: Drawn<Option<Placement>>,
129}
130
131impl Default for MetronomeMark {
132    /// A mark that says nothing yet: no number, no word, counting quarters.
133    /// music21 builds one of these and fills it in.
134    fn default() -> Self {
135        Self::build(None, None, Duration::quarter())
136    }
137}
138
139impl MetronomeMark {
140    /// A mark of `number` beats per minute, counting quarter notes, with the
141    /// tempo word implied from the number when one is close enough.
142    pub fn new(number: FloatType) -> Self {
143        Self::build(Some(number), None, Duration::quarter())
144    }
145
146    /// A mark from a tempo word alone, with the number implied from the word
147    /// when music21 knows it.
148    pub fn from_text(text: impl Into<String>) -> Self {
149        Self::build(None, Some(text.into()), Duration::quarter())
150    }
151
152    /// A mark carrying both an explicit number and an explicit word.
153    pub fn with_number_and_text(number: FloatType, text: impl Into<String>) -> Self {
154        Self::build(Some(number), Some(text.into()), Duration::quarter())
155    }
156
157    /// Changes the note value the number counts, so `Duration::half()` makes
158    /// the number a count of half notes.
159    pub fn with_referent(mut self, referent: Duration) -> Self {
160        self.referent = referent;
161        self
162    }
163
164    fn build(number: Option<FloatType>, text: Option<String>, referent: Duration) -> Self {
165        let number = number.map(whole_where_nearly);
166        let number_implicit = number.is_none();
167        let text_implicit = text.is_none();
168        let number = number.or_else(|| text.as_deref().and_then(default_number_for_text));
169        let text = text.or_else(|| number.and_then(default_text_for_number).map(String::from));
170        Self {
171            number_implicit: number_implicit && number.is_some(),
172            text_implicit: text_implicit && text.is_some(),
173            number,
174            text,
175            referent,
176            number_sounding: None,
177            parentheses: Drawn(false),
178            placement: Drawn(None),
179        }
180    }
181
182    /// Whether the mark is written in parentheses: music21's
183    /// `parentheses`.
184    pub fn parentheses(&self) -> bool {
185        self.parentheses.0
186    }
187
188    /// Says whether the mark is written in parentheses.
189    pub fn set_parentheses(&mut self, parentheses: bool) {
190        self.parentheses.0 = parentheses;
191    }
192
193    /// Which side of the staff it is written on, where the score says.
194    pub fn placement(&self) -> Option<Placement> {
195        self.placement.0
196    }
197
198    /// Says which side of the staff it is written on.
199    pub fn set_placement(&mut self, placement: Option<Placement>) {
200        self.placement.0 = placement;
201    }
202
203    /// The same tempo counted in a different note value: music21's
204    /// `getEquivalentByReferent`, so quarter = 60 becomes eighth = 120. The
205    /// tempo word is carried over unchanged, implied or not.
206    pub fn equivalent_by_referent(&self, referent: Duration) -> MetronomeMark {
207        let number = self.number.map(|number| {
208            convert_tempo_by_referent(
209                number,
210                self.referent.quarter_length(),
211                referent.quarter_length(),
212            )
213        });
214        Self::build(number, self.text.clone(), referent)
215    }
216
217    /// The same number counted in a different note value, so the tempo
218    /// itself changes: music21's `getMaintainedNumberWithReferent`.
219    pub fn maintained_number_with_referent(&self, referent: Duration) -> MetronomeMark {
220        Self::build(self.number, self.text.clone(), referent)
221    }
222
223    /// The beats per minute, if known.
224    pub fn number(&self) -> Option<FloatType> {
225        self.number
226    }
227
228    /// Sets the beats per minute, which is then no longer implied. A mark
229    /// with no word of its own picks one up, as music21 does.
230    pub fn set_number(&mut self, number: Option<FloatType>) {
231        let number = number.map(whole_where_nearly);
232        self.number = number;
233        self.number_implicit = false;
234        if self.text.is_none()
235            && let Some(number) = number
236            && let Some(text) = default_text_for_number(number)
237        {
238            self.text = Some(text.to_string());
239            self.text_implicit = true;
240        }
241    }
242
243    /// Sets the tempo word, which is then no longer implied. A mark with no
244    /// number of its own picks one up.
245    pub fn set_text(&mut self, text: impl Into<String>) {
246        let text = text.into();
247        self.text_implicit = false;
248        if self.number.is_none()
249            && let Some(number) = default_number_for_text(&text)
250        {
251            self.number = Some(number);
252            self.number_implicit = true;
253        }
254        self.text = Some(text);
255    }
256
257    /// Sets the note value the number counts, leaving the number alone — so
258    /// the tempo itself changes.
259    pub fn set_referent(&mut self, referent: Duration) {
260        self.referent = referent;
261    }
262
263    /// The tempo word, if any.
264    pub fn text(&self) -> Option<&str> {
265        self.text.as_deref()
266    }
267
268    /// The tempo word as something a score would carry: music21's
269    /// `getTextExpression`. A word only implied from the number is not
270    /// answered unless `return_implicit` asks for it.
271    pub fn text_expression(&self, return_implicit: bool) -> Option<&str> {
272        if self.text_implicit && !return_implicit {
273            return None;
274        }
275        self.text.as_deref()
276    }
277
278    /// The note value the number counts.
279    pub fn referent(&self) -> &Duration {
280        &self.referent
281    }
282
283    /// Whether the number was implied from the tempo word.
284    pub fn number_implicit(&self) -> bool {
285        self.number_implicit
286    }
287
288    /// Says so, or unsays it. music21 lets a caller write this: its MIDI
289    /// reader copies a mark onto every staff and marks the copies implicit,
290    /// which is how the parts after the first hide the number.
291    pub fn set_number_implicit(&mut self, implicit: bool) {
292        self.number_implicit = implicit;
293    }
294
295    /// Whether the tempo word was implied from the number.
296    pub fn text_implicit(&self) -> bool {
297        self.text_implicit
298    }
299
300    /// music21's `numberSounding`: how fast the mark is actually played,
301    /// where that is not the number written over the staff.
302    ///
303    /// A score may say *Allegro* and be played at a hundred and forty-four,
304    /// and a MusicXML `<sound tempo=...>` with no metronome mark beside it
305    /// is a tempo that sounds and is never written. Nothing here is
306    /// answered from it unless it is asked for: [`Self::quarter_bpm`] reads
307    /// the written number, and [`Self::sounding_quarter_bpm`] reads this.
308    pub fn number_sounding(&self) -> Option<FloatType> {
309        self.number_sounding
310    }
311
312    /// Sets how fast the mark is played, apart from what it says.
313    pub fn set_number_sounding(&mut self, number: Option<FloatType>) {
314        self.number_sounding = number;
315    }
316
317    /// A mark built to be played at this speed.
318    ///
319    /// It does not become the mark's number: a mark that says nothing and
320    /// sounds at a hundred and sixty-eight still says nothing, which is how
321    /// music21 carries a tempo that is played and never written.
322    pub fn with_number_sounding(mut self, number: FloatType) -> Self {
323        self.number_sounding = Some(number);
324        self
325    }
326
327    /// The tempo as quarter notes per minute, whatever the referent.
328    pub fn quarter_bpm(&self) -> Option<FloatType> {
329        self.number
330            .map(|number| convert_tempo_by_referent(number, self.referent.quarter_length(), 1.0))
331    }
332
333    /// The same, at the speed the mark is played rather than the one it
334    /// says: music21's `getQuarterBPM(useNumberSounding=True)`.
335    pub fn sounding_quarter_bpm(&self) -> Option<FloatType> {
336        match self.number_sounding {
337            Some(number) => Some(convert_tempo_by_referent(
338                number,
339                self.referent.quarter_length(),
340                1.0,
341            )),
342            None => self.quarter_bpm(),
343        }
344    }
345
346    /// Seconds each quarter note lasts.
347    ///
348    /// A mark of nought beats a minute is refused rather than answered with
349    /// an infinity, which is what dividing by it gives and what every
350    /// arithmetic downstream of it would then carry.
351    pub fn seconds_per_quarter(&self) -> Result<FloatType> {
352        // music21 reads the speed the mark is played at, its
353        // getQuarterBPM() taking useNumberSounding=True by default, so a
354        // mark that says no number but carries a sounding one -- what a
355        // MusicXML <sound tempo="..."> reads as -- still answers.
356        let quarter_bpm = self.sounding_quarter_bpm().ok_or_else(|| {
357            Error::Tempo("cannot derive seconds as getQuarterBPM() returns None".to_string())
358        })?;
359        if quarter_bpm == 0.0 {
360            return Err(Error::Tempo(
361                "a tempo of no beats a minute lasts no seconds a beat".to_string(),
362            ));
363        }
364        Ok(60.0 / quarter_bpm)
365    }
366
367    /// Seconds a span of the given quarter length lasts at this tempo.
368    pub fn quarter_length_to_seconds(&self, quarter_length: FloatType) -> Result<FloatType> {
369        Ok(self.seconds_per_quarter()? * quarter_length)
370    }
371
372    /// Seconds a duration lasts at this tempo.
373    pub fn duration_to_seconds(&self, duration: &Duration) -> Result<FloatType> {
374        self.quarter_length_to_seconds(duration.quarter_length())
375    }
376
377    /// The duration that lasts the given number of seconds at this tempo.
378    pub fn seconds_to_duration(&self, seconds: FloatType) -> Result<Duration> {
379        if seconds.is_nan() || seconds <= 0.0 {
380            return Err(Error::Tempo(
381                "seconds must be a number greater than zero".to_string(),
382            ));
383        }
384        Duration::new(seconds / self.seconds_per_quarter()?)
385    }
386}
387
388/// Carries what lies between two points of one stream into another, keeping
389/// each thing's place between them: music21's `interpolateElements`.
390///
391/// `start` and `end` each say where one point stands in the source and where
392/// it stands in the destination -- a downbeat at offset 10 of a score and
393/// 20.5 of a recording, the next at 14 and at 25. Everything strictly
394/// between them in the source is inserted into the destination at the same
395/// proportion of the way across, so a note at 11 lands at 21.625.
396///
397/// music21 finds the two points by asking one object for its offset in each
398/// stream. A stream here owns what it holds, so there is no such object to
399/// ask and the offsets are given instead; and for the same reason this is
400/// always music21's `autoAdd`, since nothing can already be in both.
401pub fn interpolate_elements(
402    source: &Stream,
403    destination: &mut Stream,
404    start: (FloatType, FloatType),
405    end: (FloatType, FloatType),
406) -> Result<()> {
407    let (start_source, end_source) = (start.0, end.0);
408    // Checked before the walk, so two points at one offset are refused even
409    // with nothing between them.
410    interpolated_offset(start_source, start, end)?;
411    for event in source.events() {
412        if event.offset() > start_source && event.offset() < end_source {
413            destination.insert(
414                interpolated_offset(event.offset(), start, end)?,
415                event.element().clone(),
416            );
417        }
418    }
419    Ok(())
420}
421
422/// Where a thing standing at `offset` in the source lands in the destination,
423/// the same proportion of the way between the two points: the arithmetic of
424/// [`interpolate_elements`], for a caller that moves things of its own.
425///
426/// `start` and `end` are each a point's offset in the source and in the
427/// destination. It is an error for the two to stand at one offset in the
428/// source, since then nothing lies between them to scale.
429///
430/// ```
431/// use music21_rs::tempo::interpolated_offset;
432///
433/// assert_eq!(interpolated_offset(11.0, (10.0, 20.5), (14.0, 25.0))?, 21.625);
434/// # Ok::<(), music21_rs::Error>(())
435/// ```
436pub fn interpolated_offset(
437    offset: FloatType,
438    start: (FloatType, FloatType),
439    end: (FloatType, FloatType),
440) -> Result<FloatType> {
441    let ((start_source, start_destination), (end_source, end_destination)) = (start, end);
442    if end_source == start_source {
443        return Err(Error::Tempo(
444            "the two points stand at the same offset in the source, so nothing lies between them"
445                .to_string(),
446        ));
447    }
448    let scale = (end_destination - start_destination) / (end_source - start_source);
449    Ok(scale * (offset - start_source) + start_destination)
450}
451
452/// Which side of a [`MetricModulation`] something is on: the mark in force
453/// before it, or the mark after. music21 calls them `'left'` and `'right'`.
454#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
455#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
456pub enum ModulationSide {
457    /// The mark in force before the modulation: music21's `'left'`.
458    Old,
459    /// The mark in force after it: music21's `'right'`.
460    New,
461}
462
463/// A tempo said in words and not in numbers: music21's `TempoText`.
464///
465/// ```
466/// use music21_rs::tempo::TempoText;
467///
468/// let slow = TempoText::new("slow");
469/// assert_eq!(slow.metronome_mark().number(), Some(56.0));
470/// assert!(TempoText::new("Largo e piano").is_common_tempo_text());
471/// assert!(!TempoText::new("undulating").is_common_tempo_text());
472/// ```
473#[derive(Clone, Debug, PartialEq)]
474#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
475#[must_use]
476pub struct TempoText {
477    text: String,
478    #[cfg_attr(feature = "serde", serde(default))]
479    placement: Drawn<Option<Placement>>,
480}
481
482impl TempoText {
483    /// A tempo said as `text`.
484    pub fn new(text: impl Into<String>) -> Self {
485        Self {
486            text: text.into(),
487            placement: Drawn(None),
488        }
489    }
490
491    /// Which side of the staff it is written on, where the score says.
492    pub fn placement(&self) -> Option<Placement> {
493        self.placement.0
494    }
495
496    /// Says which side of the staff it is written on.
497    pub fn set_placement(&mut self, placement: Option<Placement>) {
498        self.placement.0 = placement;
499    }
500
501    /// The words the tempo is said in.
502    pub fn text(&self) -> &str {
503        &self.text
504    }
505
506    /// Says the tempo in other words.
507    pub fn set_text(&mut self, text: impl Into<String>) {
508        self.text = text.into();
509    }
510
511    /// The metronome mark the words imply: music21's `getMetronomeMark`.
512    pub fn metronome_mark(&self) -> MetronomeMark {
513        MetronomeMark::from_text(self.text.clone())
514    }
515
516    /// Whether the words read as a tempo: music21's `isCommonTempoText`,
517    /// which looks for one of its tempo words inside them, or them inside
518    /// one, ignoring case, spaces and full stops.
519    pub fn is_common_tempo_text(&self) -> bool {
520        is_common_tempo_text(&self.text)
521    }
522}
523
524/// Whether `text` reads as a tempo, as [`TempoText::is_common_tempo_text`]
525/// asks it.
526pub fn is_common_tempo_text(text: &str) -> bool {
527    let stripped = |value: &str| {
528        value
529            .trim()
530            .chars()
531            .filter(|character| *character != ' ' && *character != '.')
532            .collect::<String>()
533            .to_lowercase()
534    };
535    let text = stripped(text);
536    DEFAULT_TEMPO_VALUES.iter().any(|(candidate, _)| {
537        let candidate = stripped(candidate);
538        text.contains(&candidate) || candidate.contains(&text)
539    })
540}
541
542/// A change of tempo written as an equation between two metronome marks:
543/// music21's `MetricModulation`.
544///
545/// The usual one keeps a number and moves it to another note value --
546/// quarter = 60 becoming dotted quarter = 60 -- so the old mark and the new
547/// are what the equation is written with. Either side may be given by its
548/// note value alone and take its number from the other, and the old side may
549/// take it from the mark in force before the modulation, which a score knows
550/// and this type is handed ([`MetricModulation::update_from`]).
551///
552/// ```
553/// use music21_rs::{tempo::{MetricModulation, MetronomeMark}, Duration};
554///
555/// let mut modulation = MetricModulation::new();
556/// modulation.set_old_metronome(Some(MetronomeMark::new(60.0)));
557/// modulation.set_new_referent(Duration::half());
558/// assert_eq!(modulation.number(), Some(60.0));
559/// assert_eq!(modulation.new_metronome().unwrap().quarter_bpm(), Some(120.0));
560/// ```
561#[derive(Clone, Debug, PartialEq)]
562#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
563#[must_use]
564pub struct MetricModulation {
565    old: Option<MetronomeMark>,
566    new: Option<MetronomeMark>,
567    classical_style: bool,
568    maintain_beat: bool,
569    transition_symbol: String,
570    arrow_direction: Option<String>,
571    parentheses: bool,
572    #[cfg_attr(feature = "serde", serde(default))]
573    placement: Drawn<Option<Placement>>,
574}
575
576impl Default for MetricModulation {
577    fn default() -> Self {
578        Self::new()
579    }
580}
581
582impl MetricModulation {
583    /// A modulation between two marks not yet given, written with `=`.
584    pub fn new() -> Self {
585        Self {
586            old: None,
587            new: None,
588            classical_style: false,
589            maintain_beat: false,
590            transition_symbol: "=".to_string(),
591            arrow_direction: None,
592            parentheses: false,
593            placement: Drawn(None),
594        }
595    }
596
597    /// Which side of the staff the modulation is written on, where the
598    /// score says.
599    pub fn placement(&self) -> Option<Placement> {
600        self.placement.0
601    }
602
603    /// Says which side of the staff the modulation is written on.
604    pub fn set_placement(&mut self, placement: Option<Placement>) {
605        self.placement.0 = placement;
606    }
607
608    /// The mark in force before the modulation, if one is given.
609    pub fn old_metronome(&self) -> Option<&MetronomeMark> {
610        self.old.as_ref()
611    }
612
613    /// The mark in force after it, if one is given.
614    pub fn new_metronome(&self) -> Option<&MetronomeMark> {
615        self.new.as_ref()
616    }
617
618    /// Sets the mark in force before the modulation, or takes it away.
619    pub fn set_old_metronome(&mut self, mark: Option<MetronomeMark>) {
620        self.old = mark;
621    }
622
623    /// Sets the mark in force after the modulation, or takes it away.
624    pub fn set_new_metronome(&mut self, mark: Option<MetronomeMark>) {
625        self.new = mark;
626    }
627
628    /// The note value the old mark counts.
629    pub fn old_referent(&self) -> Option<&Duration> {
630        self.old.as_ref().map(MetronomeMark::referent)
631    }
632
633    /// The note value the new mark counts.
634    pub fn new_referent(&self) -> Option<&Duration> {
635        self.new.as_ref().map(MetronomeMark::referent)
636    }
637
638    /// Counts the old side in `referent`: the old mark's own tempo counted
639    /// that way, or where there is none the tempo of `previous` -- the mark
640    /// in force before the modulation -- counted that way, or failing both a
641    /// mark with no number yet. music21's `oldReferent` setter.
642    pub fn set_old_referent(&mut self, referent: Duration, previous: Option<&MetronomeMark>) {
643        self.old = Some(match (&self.old, previous) {
644            (Some(old), _) => old.equivalent_by_referent(referent),
645            (None, Some(previous)) => previous.equivalent_by_referent(referent),
646            (None, None) => MetronomeMark::default().with_referent(referent),
647        });
648    }
649
650    /// Counts the new side in `referent`: the new mark's own tempo counted
651    /// that way, or where there is none the old mark's *number* moved to
652    /// that note value -- the modulation proper -- or failing both a mark
653    /// with no number yet. music21's `newReferent` setter.
654    pub fn set_new_referent(&mut self, referent: Duration) {
655        self.new = Some(match (&self.new, &self.old) {
656            (Some(new), _) => new.equivalent_by_referent(referent),
657            (None, Some(old)) => old.maintained_number_with_referent(referent),
658            (None, None) => MetronomeMark::default().with_referent(referent),
659        });
660    }
661
662    /// The number of the new mark, which is what the modulation sets.
663    pub fn number(&self) -> Option<FloatType> {
664        self.new.as_ref().and_then(MetronomeMark::number)
665    }
666
667    /// Fills in what the marks leave unsaid from `previous`, the mark in
668    /// force before the modulation: music21's `updateByContext`, with the
669    /// context search done by whoever knows the score.
670    ///
671    /// The old side becomes `previous` counted in the old side's note value,
672    /// or `previous` itself where there is no old side; and a new side with
673    /// a note value takes the old side's number, since the number is what a
674    /// modulation keeps.
675    pub fn update_from(&mut self, previous: Option<&MetronomeMark>) {
676        if let Some(previous) = previous {
677            self.old = Some(match &self.old {
678                Some(old) => previous.equivalent_by_referent(old.referent().clone()),
679                None => previous.maintained_number_with_referent(previous.referent().clone()),
680            });
681        }
682        let number = self.old.as_ref().and_then(MetronomeMark::number);
683        if let (Some(new), Some(number)) = (self.new.as_mut(), number) {
684            new.set_number(Some(number));
685        }
686    }
687
688    /// Sets one side to the same tempo as the other counted in `referent`:
689    /// music21's `setEqualityByReferent`. With no side named, the side not
690    /// yet given is the one set; it is an error when both are given or when
691    /// the other side is missing.
692    pub fn set_equality_by_referent(
693        &mut self,
694        side: Option<ModulationSide>,
695        referent: Duration,
696    ) -> Result<()> {
697        self.set_from_other(side, |other| other.equivalent_by_referent(referent))
698    }
699
700    /// Sets one side to the other's number counted in `referent`, which is a
701    /// different tempo: music21's `setOtherByReferent`. The side is chosen
702    /// as [`MetricModulation::set_equality_by_referent`] chooses it.
703    pub fn set_other_by_referent(
704        &mut self,
705        side: Option<ModulationSide>,
706        referent: Duration,
707    ) -> Result<()> {
708        self.set_from_other(side, |other| {
709            other.maintained_number_with_referent(referent)
710        })
711    }
712
713    fn side_to_set(&self, side: Option<ModulationSide>) -> Result<ModulationSide> {
714        side.or(match (&self.old, &self.new) {
715            (None, _) => Some(ModulationSide::Old),
716            (_, None) => Some(ModulationSide::New),
717            _ => None,
718        })
719        .ok_or_else(|| Error::Tempo("cannot set equality for a side of None".to_string()))
720    }
721
722    fn no_other_side() -> Error {
723        Error::Tempo("there is no mark on the other side to take the tempo from".to_string())
724    }
725
726    /// Sets the side `side_to_set` chooses to a mark derived from the other.
727    fn set_from_other(
728        &mut self,
729        side: Option<ModulationSide>,
730        derive: impl FnOnce(&MetronomeMark) -> MetronomeMark,
731    ) -> Result<()> {
732        let (target, other) = match self.side_to_set(side)? {
733            ModulationSide::New => (&mut self.new, &self.old),
734            ModulationSide::Old => (&mut self.old, &self.new),
735        };
736        let other = other.as_ref().ok_or_else(Self::no_other_side)?;
737        *target = Some(derive(other));
738        Ok(())
739    }
740
741    /// Whether the first mark written is the new tempo rather than the old,
742    /// the reverse of the usual order: music21's `classicalStyle`.
743    pub fn classical_style(&self) -> bool {
744        self.classical_style
745    }
746
747    /// Sets whether the first mark written is the new tempo.
748    pub fn set_classical_style(&mut self, classical: bool) {
749        self.classical_style = classical;
750    }
751
752    /// Whether the beat is kept after the equation, as it is going from 3/4
753    /// to 6/8: music21's `maintainBeat`.
754    pub fn maintain_beat(&self) -> bool {
755        self.maintain_beat
756    }
757
758    /// Sets whether the beat is kept after the equation.
759    pub fn set_maintain_beat(&mut self, maintain: bool) {
760        self.maintain_beat = maintain;
761    }
762
763    /// What the equation is written with, `=` unless an edition says
764    /// otherwise.
765    pub fn transition_symbol(&self) -> &str {
766        &self.transition_symbol
767    }
768
769    /// Sets what the equation is written with.
770    pub fn set_transition_symbol(&mut self, symbol: impl Into<String>) {
771        self.transition_symbol = symbol.into();
772    }
773
774    /// Which way an arrow drawn for the equation points, where older
775    /// editions draw one: `"left"`, `"right"`, or none.
776    pub fn arrow_direction(&self) -> Option<&str> {
777        self.arrow_direction.as_deref()
778    }
779
780    /// Sets which way the arrow points, or takes it away.
781    pub fn set_arrow_direction(&mut self, direction: Option<String>) {
782        self.arrow_direction = direction;
783    }
784
785    /// Whether the equation is written in parentheses.
786    pub fn parentheses(&self) -> bool {
787        self.parentheses
788    }
789
790    /// Sets whether the equation is written in parentheses.
791    pub fn set_parentheses(&mut self, parentheses: bool) {
792        self.parentheses = parentheses;
793    }
794}
795
796#[cfg(test)]
797mod tests {
798    #[test]
799    fn a_number_a_hair_off_a_whole_one_is_that_whole_number() {
800        // Read off music21: dotted quarter = 110 counted in quarters is 165,
801        // and a dotted quarter at 165 is 247.5 quarters a minute.
802        let mark = super::MetronomeMark::new(110.0)
803            .with_referent(super::Duration::from_type_with_dots(
804                crate::duration::DurationType::Quarter,
805                1,
806            ))
807            .equivalent_by_referent(super::Duration::quarter());
808        assert_eq!(mark.number(), Some(165.0));
809        let dotted = mark.maintained_number_with_referent(super::Duration::from_type_with_dots(
810            crate::duration::DurationType::Quarter,
811            1,
812        ));
813        assert_eq!(dotted.quarter_bpm(), Some(247.5));
814    }
815
816    use super::*;
817
818    #[test]
819    fn a_mark_can_be_rewritten_one_half_at_a_time() {
820        let mut mark = MetronomeMark::from_text("adagio");
821        assert!(mark.number_implicit());
822        mark.set_number(Some(60.0));
823        assert!(!mark.number_implicit());
824        assert_eq!(mark.number(), Some(60.0));
825        mark.set_number_implicit(true);
826        assert!(mark.number_implicit());
827        mark.set_referent(Duration::half());
828        assert_eq!(mark.referent().quarter_length(), 2.0);
829        assert_eq!(mark.quarter_bpm(), Some(120.0));
830
831        let mut numbered = MetronomeMark::new(90.0);
832        assert!(numbered.text_implicit());
833        numbered.set_text("largo");
834        assert!(!numbered.text_implicit());
835        assert_eq!(numbered.text(), Some("largo"));
836        assert_eq!(numbered.number(), Some(90.0));
837        let mut wordless = MetronomeMark::default();
838        wordless.set_number(Some(184.0));
839        assert_eq!(wordless.text(), Some("presto"));
840        assert!(wordless.text_implicit());
841        wordless.set_number(None);
842        assert_eq!(wordless.number(), None);
843    }
844
845    /// music21's own `getTextExpression` examples.
846    #[test]
847    fn an_implied_word_is_only_answered_when_asked_for() {
848        let presto = MetronomeMark::from_text("presto");
849        assert_eq!(presto.number(), Some(184.0));
850        assert_eq!(presto.text_expression(false), Some("presto"));
851
852        let ninety = MetronomeMark::new(90.0);
853        assert!(ninety.text_implicit());
854        assert_eq!(ninety.text_expression(false), None);
855        assert_eq!(ninety.text_expression(true), Some("maestoso"));
856    }
857
858    #[test]
859    fn a_mark_may_be_played_faster_than_it_is_written() {
860        // music21's own example: a tempo that sounds and is never written.
861        let playback = MetronomeMark::default().with_number_sounding(168.0);
862        assert_eq!(playback.number_sounding(), Some(168.0));
863        // It says nothing: the tempo is played and never written.
864        assert_eq!(playback.number(), None);
865        assert_eq!(playback.text(), None);
866        assert_eq!(playback.quarter_bpm(), None);
867        assert_eq!(playback.sounding_quarter_bpm(), Some(168.0));
868
869        // A mark that says a number keeps it, and only the sounding tempo
870        // reads the other.
871        let mut written = MetronomeMark::new(60.0).with_referent(Duration::half());
872        assert_eq!(written.quarter_bpm(), Some(120.0));
873        assert_eq!(written.sounding_quarter_bpm(), Some(120.0));
874        written.set_number_sounding(Some(90.0));
875        assert_eq!(written.number(), Some(60.0));
876        assert_eq!(written.quarter_bpm(), Some(120.0));
877        assert_eq!(written.sounding_quarter_bpm(), Some(180.0));
878    }
879
880    #[test]
881    fn referent_changes_match_music21() {
882        let quarter_sixty = MetronomeMark::new(60.0);
883        let eighths = quarter_sixty.equivalent_by_referent(Duration::eighth());
884        assert_eq!(eighths.number(), Some(120.0));
885        assert_eq!(eighths.referent().quarter_length(), 0.5);
886        assert_eq!(eighths.text(), Some("larghetto"));
887        let halves = quarter_sixty.equivalent_by_referent(Duration::half());
888        assert_eq!(halves.number(), Some(30.0));
889
890        let andante = MetronomeMark::with_number_and_text(72.0, "andante")
891            .with_referent(Duration::new(1.5).unwrap());
892        let quarters = andante.equivalent_by_referent(Duration::quarter());
893        assert_eq!(quarters.number(), Some(108.0));
894        assert_eq!(quarters.text(), Some("andante"));
895        assert_eq!(
896            andante.equivalent_by_referent(Duration::half()).number(),
897            Some(54.0)
898        );
899
900        let kept = andante.maintained_number_with_referent(Duration::eighth());
901        assert_eq!(kept.number(), Some(72.0));
902        assert_eq!(kept.referent().quarter_length(), 0.5);
903        assert_eq!(kept.text(), Some("andante"));
904    }
905
906    #[test]
907    fn numbers_imply_the_tempo_words_music21_picks() {
908        let cases = [
909            (120.0, Some("animato")),
910            (60.0, Some("larghetto")),
911            (61.0, Some("larghetto")),
912            (62.0, Some("larghetto")),
913            (63.0, None),
914            (90.0, Some("maestoso")),
915            (92.0, Some("moderate")),
916            (100.0, None),
917            (118.0, Some("animato")),
918            (122.0, Some("animato")),
919            (123.0, None),
920            (16.0, Some("larghissimo")),
921            (12.0, None),
922            (56.5, Some("adagio")),
923            (300.0, None),
924        ];
925        for (number, text) in cases {
926            let mark = MetronomeMark::new(number);
927            assert_eq!(mark.text(), text, "{number}");
928            assert_eq!(mark.text_implicit(), text.is_some(), "{number}");
929            assert!(!mark.number_implicit());
930        }
931    }
932
933    #[test]
934    fn words_imply_their_conventional_numbers() {
935        let cases = [
936            ("allegro", Some(132.0)),
937            ("Allegro", Some(132.0)),
938            ("ALLEGRO ", Some(132.0)),
939            ("très vite", Some(144.0)),
940            ("andante moderato", Some(83.0)),
941            ("unknown words", None),
942        ];
943        for (text, number) in cases {
944            let mark = MetronomeMark::from_text(text);
945            assert_eq!(mark.number(), number, "{text}");
946            assert_eq!(mark.number_implicit(), number.is_some(), "{text}");
947            assert_eq!(mark.text(), Some(text));
948            assert!(!mark.text_implicit());
949        }
950        let both = MetronomeMark::with_number_and_text(120.0, "fast");
951        assert_eq!(both.number(), Some(120.0));
952        assert_eq!(both.text(), Some("fast"));
953        assert!(!both.number_implicit() && !both.text_implicit());
954    }
955
956    #[test]
957    fn referents_convert_to_quarter_bpm_and_seconds() {
958        let quarter = MetronomeMark::new(120.0);
959        assert_eq!(quarter.quarter_bpm(), Some(120.0));
960        assert_eq!(quarter.quarter_length_to_seconds(1.0).unwrap(), 0.5);
961        assert_eq!(
962            quarter.seconds_to_duration(0.75).unwrap().quarter_length(),
963            1.5
964        );
965
966        let half = MetronomeMark::new(60.0).with_referent(Duration::half());
967        assert_eq!(half.quarter_bpm(), Some(120.0));
968        assert_eq!(half.text(), Some("larghetto"));
969        assert_eq!(half.quarter_length_to_seconds(1.0).unwrap(), 0.5);
970        assert_eq!(
971            half.duration_to_seconds(&Duration::new(3.0).unwrap())
972                .unwrap(),
973            1.5
974        );
975        assert_eq!(half.seconds_to_duration(1.0).unwrap().quarter_length(), 2.0);
976
977        let eighth = MetronomeMark::new(120.0).with_referent(Duration::eighth());
978        assert_eq!(eighth.quarter_bpm(), Some(60.0));
979        assert_eq!(eighth.seconds_per_quarter().unwrap(), 1.0);
980
981        let dotted = MetronomeMark::new(56.5).with_referent(Duration::half());
982        assert!((dotted.quarter_bpm().unwrap() - 113.0).abs() < 1e-9);
983
984        assert_eq!(convert_tempo_by_referent(60.0, 1.0, 2.0), 30.0);
985        assert_eq!(convert_tempo_by_referent(60.0, 2.0, 1.0), 120.0);
986
987        let unknown = MetronomeMark::from_text("unknown words");
988        assert_eq!(unknown.quarter_bpm(), None);
989        assert!(unknown.seconds_per_quarter().is_err());
990
991        // A mark that says no number but sounds one -- what a MusicXML
992        // <sound tempo="96"> reads as -- is counted at the one it sounds,
993        // as music21's getQuarterBPM() takes useNumberSounding=True.
994        let mut playback = MetronomeMark::from_text("unknown words");
995        playback.set_number_sounding(Some(96.0));
996        assert_eq!(playback.number(), None);
997        assert_eq!(playback.sounding_quarter_bpm(), Some(96.0));
998        assert!((playback.seconds_per_quarter().expect("seconds") - 0.625).abs() < 1e-12);
999        assert!(quarter.seconds_to_duration(0.0).is_err());
1000
1001        let motionless = MetronomeMark::new(0.0);
1002        assert_eq!(motionless.quarter_bpm(), Some(0.0));
1003        assert!(motionless.seconds_per_quarter().is_err());
1004        assert!(
1005            motionless
1006                .duration_to_seconds(&Duration::quarter())
1007                .is_err()
1008        );
1009        assert!(motionless.quarter_length_to_seconds(1.0).is_err());
1010    }
1011
1012    /// music21's own example: a bar of a score laid against a recording.
1013    #[test]
1014    fn what_lies_between_two_points_keeps_its_place_between_them() {
1015        use crate::{Note, Stream};
1016
1017        let mut source = Stream::new();
1018        for (offset, name) in [
1019            (10.0, "C4"),
1020            (11.0, "D4"),
1021            (12.0, "E4"),
1022            (13.0, "F4"),
1023            (14.0, "G4"),
1024        ] {
1025            source.insert(offset, Note::from_name(name).unwrap());
1026        }
1027        let offsets = |stream: &Stream| -> Vec<FloatType> {
1028            stream.events().iter().map(|event| event.offset()).collect()
1029        };
1030
1031        let mut recording = Stream::new();
1032        interpolate_elements(&source, &mut recording, (10.0, 20.5), (14.0, 25.0)).unwrap();
1033        assert_eq!(offsets(&recording), [21.625, 22.75, 23.875]);
1034
1035        let mut slower = Stream::new();
1036        interpolate_elements(&source, &mut slower, (10.0, 10.1), (14.0, 50.5)).unwrap();
1037        for (found, expected) in offsets(&slower).iter().zip([20.2, 30.3, 40.4]) {
1038            assert!((found - expected).abs() < 1e-9);
1039        }
1040
1041        assert!(interpolate_elements(&source, &mut slower, (10.0, 0.0), (10.0, 4.0)).is_err());
1042    }
1043
1044    #[test]
1045    fn a_metric_modulation_moves_a_number_to_another_note_value() {
1046        // Each answer read off music21 11.0.0b9's MetricModulation.
1047        use super::{MetricModulation, MetronomeMark, ModulationSide};
1048        use crate::Duration;
1049
1050        // Setting both referents first leaves both numbers unsaid.
1051        let mut modulation = MetricModulation::new();
1052        modulation.set_old_referent(Duration::half(), None);
1053        modulation.set_new_referent(Duration::new(0.25).unwrap());
1054        assert_eq!(modulation.number(), None);
1055
1056        // Then an old mark with a number: the new side takes the number.
1057        let mut modulation = MetricModulation::new();
1058        modulation.set_old_referent(Duration::quarter(), None);
1059        modulation.set_new_referent(Duration::eighth());
1060        modulation.set_old_metronome(Some(MetronomeMark::new(60.0)));
1061        modulation.update_from(None);
1062        assert_eq!(modulation.number(), Some(60.0));
1063        assert_eq!(
1064            modulation.new_metronome().unwrap().text(),
1065            Some("larghetto")
1066        );
1067
1068        // Equality keeps the tempo and recounts it: half = 30 is quarter = 60.
1069        let mut modulation = MetricModulation::new();
1070        modulation.set_new_metronome(Some(MetronomeMark::new(60.0)));
1071        modulation
1072            .set_equality_by_referent(None, Duration::half())
1073            .unwrap();
1074        assert_eq!(modulation.old_metronome().unwrap().number(), Some(30.0));
1075
1076        // The other translation keeps the number and changes the tempo.
1077        let mut modulation = MetricModulation::new();
1078        modulation.set_old_metronome(Some(MetronomeMark::new(60.0)));
1079        modulation
1080            .set_other_by_referent(Some(ModulationSide::New), Duration::half())
1081            .unwrap();
1082        assert_eq!(modulation.number(), Some(60.0));
1083        assert_eq!(
1084            modulation.new_metronome().unwrap().quarter_bpm(),
1085            Some(120.0)
1086        );
1087
1088        // With both sides given there is no side to choose.
1089        assert!(
1090            modulation
1091                .set_equality_by_referent(None, Duration::quarter())
1092                .is_err()
1093        );
1094
1095        // The mark in force before fills in an old side counted in eighths.
1096        let mut modulation = MetricModulation::new();
1097        modulation.set_old_referent(Duration::eighth(), None);
1098        modulation.update_from(Some(&MetronomeMark::new(90.0)));
1099        assert_eq!(modulation.old_metronome().unwrap().number(), Some(180.0));
1100    }
1101}