Skip to main content

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