Skip to main content

music21_rs/
duration.rs

1use crate::{
2    defaults::{FloatType, FractionType, IntegerType},
3    error::{Error, Result},
4};
5
6use fraction::ToPrimitive;
7use std::fmt::{Display, Formatter};
8use std::str::FromStr;
9
10/// A note-value name, as music21's `duration.typeToDuration` defines them.
11///
12/// Each type is a power-of-two multiple of a quarter note, from the
13/// `duplex-maxima` (sixteen whole notes) down to the `2048th`, plus the `zero`
14/// length music21 uses for grace notes.
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[non_exhaustive]
18#[must_use]
19pub enum DurationType {
20    /// Duplex maxima, sixteen whole notes.
21    DuplexMaxima,
22    /// Maxima, eight whole notes.
23    Maxima,
24    /// Longa, four whole notes.
25    Longa,
26    /// Breve, or double whole note.
27    Breve,
28    /// Whole note.
29    Whole,
30    /// Half note.
31    Half,
32    /// Quarter note.
33    Quarter,
34    /// Eighth note.
35    Eighth,
36    /// Sixteenth note.
37    Sixteenth,
38    /// Thirty-second note.
39    ThirtySecond,
40    /// Sixty-fourth note.
41    SixtyFourth,
42    /// Hundred-twenty-eighth note.
43    HundredTwentyEighth,
44    /// Two-hundred-fifty-sixth note.
45    TwoHundredFiftySixth,
46    /// Five-hundred-twelfth note.
47    FiveHundredTwelfth,
48    /// Ten-twenty-fourth note.
49    TenTwentyFourth,
50    /// Twenty-forty-eighth note.
51    TwentyFortyEighth,
52    /// A grace-note duration of no length.
53    Zero,
54}
55
56impl DurationType {
57    /// Every duration type, longest first, matching music21's ordering.
58    pub const ALL: [DurationType; 17] = [
59        Self::DuplexMaxima,
60        Self::Maxima,
61        Self::Longa,
62        Self::Breve,
63        Self::Whole,
64        Self::Half,
65        Self::Quarter,
66        Self::Eighth,
67        Self::Sixteenth,
68        Self::ThirtySecond,
69        Self::SixtyFourth,
70        Self::HundredTwentyEighth,
71        Self::TwoHundredFiftySixth,
72        Self::FiveHundredTwelfth,
73        Self::TenTwentyFourth,
74        Self::TwentyFortyEighth,
75        Self::Zero,
76    ];
77
78    /// Returns the music21 type name, such as `"whole"` or `"16th"`.
79    pub fn music21_name(self) -> &'static str {
80        match self {
81            Self::DuplexMaxima => "duplex-maxima",
82            Self::Maxima => "maxima",
83            Self::Longa => "longa",
84            Self::Breve => "breve",
85            Self::Whole => "whole",
86            Self::Half => "half",
87            Self::Quarter => "quarter",
88            Self::Eighth => "eighth",
89            Self::Sixteenth => "16th",
90            Self::ThirtySecond => "32nd",
91            Self::SixtyFourth => "64th",
92            Self::HundredTwentyEighth => "128th",
93            Self::TwoHundredFiftySixth => "256th",
94            Self::FiveHundredTwelfth => "512th",
95            Self::TenTwentyFourth => "1024th",
96            Self::TwentyFortyEighth => "2048th",
97            Self::Zero => "zero",
98        }
99    }
100
101    /// music21's `ordinal`: the position in the list of note values from the
102    /// duplex maxima (`0`) down to the 2048th (`15`). `None` for `Zero`.
103    pub fn ordinal(self) -> Option<usize> {
104        (self != Self::Zero).then(|| Self::ALL.iter().position(|kind| *kind == self))?
105    }
106
107    /// The next longer note value: music21's `nextLargerType`, so a quarter
108    /// gives a half. `None` above the duplex maxima and for `Zero`.
109    pub fn next_larger(self) -> Option<DurationType> {
110        let ordinal = self.ordinal()?;
111        Self::ALL.get(ordinal.checked_sub(1)?).copied()
112    }
113
114    /// The next shorter note value: music21's `nextSmallerType`, so a quarter
115    /// gives an eighth. `None` below the 2048th and for `Zero`.
116    pub fn next_smaller(self) -> Option<DurationType> {
117        let ordinal = self.ordinal()?;
118        Self::ALL
119            .get(ordinal + 1)
120            .copied()
121            .filter(|kind| *kind != Self::Zero)
122    }
123
124    /// Returns music21's type number: how many of this note value make a
125    /// whole note, so a quarter is `4` and a breve `0.5`. `None` for `Zero`.
126    pub fn type_number(self) -> Option<FloatType> {
127        (self != Self::Zero).then(|| 4.0 / self.quarter_length())
128    }
129
130    fn title(self) -> String {
131        let name = self.music21_name();
132        if name.starts_with(|c: char| c.is_ascii_digit()) {
133            return name.to_string();
134        }
135        name.split('-')
136            .map(|word| {
137                let mut chars = word.chars();
138                chars
139                    .next()
140                    .map(|first| first.to_ascii_uppercase().to_string() + chars.as_str())
141                    .unwrap_or_default()
142            })
143            .collect::<Vec<_>>()
144            .join("-")
145    }
146
147    /// Returns the length of one undotted note of this type, in quarter lengths.
148    pub fn quarter_length(self) -> FloatType {
149        match self {
150            Self::DuplexMaxima => 64.0,
151            Self::Maxima => 32.0,
152            Self::Longa => 16.0,
153            Self::Breve => 8.0,
154            Self::Whole => 4.0,
155            Self::Half => 2.0,
156            Self::Quarter => 1.0,
157            Self::Eighth => 0.5,
158            Self::Sixteenth => 0.25,
159            Self::ThirtySecond => 0.125,
160            Self::SixtyFourth => 0.0625,
161            Self::HundredTwentyEighth => 0.03125,
162            Self::TwoHundredFiftySixth => 0.015625,
163            Self::FiveHundredTwelfth => 0.0078125,
164            Self::TenTwentyFourth => 0.00390625,
165            Self::TwentyFortyEighth => 0.001953125,
166            Self::Zero => 0.0,
167        }
168    }
169
170    /// Parses a music21 type name.
171    pub fn from_music21_name(name: &str) -> Option<Self> {
172        match name {
173            "duplex-maxima" => Some(Self::DuplexMaxima),
174            "maxima" => Some(Self::Maxima),
175            "longa" => Some(Self::Longa),
176            "breve" => Some(Self::Breve),
177            "whole" => Some(Self::Whole),
178            "half" => Some(Self::Half),
179            "quarter" => Some(Self::Quarter),
180            "eighth" => Some(Self::Eighth),
181            "16th" => Some(Self::Sixteenth),
182            "32nd" => Some(Self::ThirtySecond),
183            "64th" => Some(Self::SixtyFourth),
184            "128th" => Some(Self::HundredTwentyEighth),
185            "256th" => Some(Self::TwoHundredFiftySixth),
186            "512th" => Some(Self::FiveHundredTwelfth),
187            "1024th" => Some(Self::TenTwentyFourth),
188            "2048th" => Some(Self::TwentyFortyEighth),
189            "zero" => Some(Self::Zero),
190            _ => None,
191        }
192    }
193
194    /// Returns the type whose undotted length is exactly `quarter_length`.
195    pub fn from_quarter_length(quarter_length: FloatType) -> Option<Self> {
196        Self::ALL
197            .into_iter()
198            .find(|candidate| candidate.quarter_length() == quarter_length)
199    }
200
201    /// Returns the length of this type carrying `dots` augmentation dots.
202    ///
203    /// Each dot adds half of what came before, so a dotted half is `3.0` and a
204    /// double-dotted half is `3.5`.
205    pub fn quarter_length_with_dots(self, dots: u32) -> FloatType {
206        self.quarter_length() * (2.0 - (0.5 as FloatType).powi(dots as i32))
207    }
208}
209
210impl Display for DurationType {
211    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
212        f.write_str(self.music21_name())
213    }
214}
215
216impl FromStr for DurationType {
217    type Err = Error;
218
219    fn from_str(value: &str) -> Result<Self> {
220        Self::from_music21_name(value)
221            .ok_or_else(|| Error::Duration(format!("unknown duration type {value:?}")))
222    }
223}
224
225#[derive(Clone, Debug)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227/// Rhythmic duration measured in quarter lengths.
228///
229/// A quarter note has a quarter length of `1.0`; an eighth note is `0.5`;
230/// a whole note is `4.0`.
231#[must_use]
232pub struct Duration {
233    quarter_length: FloatType,
234    /// The tuplets this length is written inside, when a caller has said so.
235    ///
236    /// `None` is the ordinary case: nobody has said, so the tuplet is read
237    /// off the length by [`Duration::tuplet`]. `Some` is what a caller set,
238    /// an empty list included — saying "written inside no tuplet at all" is
239    /// different from saying nothing, and music21 keeps the difference too.
240    #[cfg_attr(feature = "serde", serde(default))]
241    tuplets: Option<Vec<Tuplet>>,
242}
243
244/// The numerators music21 searches when reading a length as a tuplet: its
245/// `defaultTupletNumerators`. Four in the time of three is deliberately
246/// absent, since that length is a dotted note.
247const TUPLET_NUMERATORS: [u32; 5] = [3, 5, 7, 11, 13];
248
249/// The dot counts music21 allows inside a tuplet: its
250/// `POSSIBLE_DOTS_IN_TUPLETS`.
251const TUPLET_DOTS: [u32; 2] = [0, 1];
252
253/// How many written values music21 will tie together before it gives up, its
254/// `range(8)`.
255const MAX_TIED_COMPONENTS: usize = 8;
256
257/// music21's `defaults.limitOffsetDenominator`: the largest denominator it
258/// will read a length as a fraction with.
259const DENOMINATOR_LIMIT: i128 = 65535;
260
261/// Reduces a fraction to its lowest terms.
262fn reduce(numerator: &mut i128, denominator: &mut i128) {
263    let divisor = num::integer::gcd(*numerator, *denominator);
264    if divisor > 1 {
265        *numerator /= divisor;
266        *denominator /= divisor;
267    }
268}
269
270/// The fraction closest to a value with a denominator no larger than the
271/// limit, as Python's `Fraction.limit_denominator` finds it.
272pub(crate) fn limited_fraction(value: FloatType, max_denominator: i128) -> Option<(i128, i128)> {
273    if !value.is_finite() || value <= 0.0 {
274        return None;
275    }
276    // The exact fraction the float stands for, as `Fraction.from_float` reads
277    // it: a float is a binary fraction, so doubling reaches a whole number.
278    let mut exact = value;
279    let mut denominator: i128 = 1;
280    for _ in 0..64 {
281        if exact.fract() == 0.0 {
282            break;
283        }
284        exact *= 2.0;
285        denominator = denominator.checked_mul(2)?;
286    }
287    let mut numerator = exact as i128;
288    if exact.fract() != 0.0 {
289        return None;
290    }
291    if denominator <= max_denominator {
292        reduce(&mut numerator, &mut denominator);
293        return Some((numerator, denominator));
294    }
295    // Python's own walk up the Stern-Brocot tree.
296    let (mut p0, mut q0, mut p1, mut q1) = (0i128, 1i128, 1i128, 0i128);
297    let (mut n, mut d) = (numerator, denominator);
298    loop {
299        let a = n / d;
300        let q2 = q0 + a * q1;
301        if q2 > max_denominator {
302            break;
303        }
304        (p0, q0, p1, q1) = (p1, q1, p0 + a * p1, q2);
305        let next = n - a * d;
306        n = d;
307        d = next;
308        if d == 0 {
309            break;
310        }
311    }
312    if q1 == 0 {
313        return None;
314    }
315    let k = (max_denominator - q0) / q1;
316    let (bound_numerator, bound_denominator) = (p0 + k * p1, q0 + k * q1);
317    let value_as =
318        |numerator: i128, denominator: i128| numerator as FloatType / denominator as FloatType;
319    let one = (value_as(bound_numerator, bound_denominator) - value).abs();
320    let two = (value_as(p1, q1) - value).abs();
321    let (mut numerator, mut denominator) = if one <= two {
322        (bound_numerator, bound_denominator)
323    } else {
324        (p1, q1)
325    };
326    reduce(&mut numerator, &mut denominator);
327    Some((numerator, denominator))
328}
329
330/// How close a length has to be to a tuplet's to be read as one, relative to
331/// the length itself.
332///
333/// music21 snaps a quarter length onto a limited-denominator fraction before
334/// it looks, which is why `0.333333` is a triplet eighth there; this is the
335/// same latitude for a crate that keeps the length as a float.
336const TUPLET_TOLERANCE: FloatType = 1e-5;
337
338/// A length written as a tuplet: `actual` notes of one written value in the
339/// time of `normal` of them.
340///
341/// This is the naming half of music21's `Tuplet`. A [`Duration`] here is a
342/// quarter length, so a tuplet is something a length is *read as* rather
343/// than something a duration carries — what it is for is being able to say
344/// that two thirds of a quarter is a quarter triplet.
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
346#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
347#[must_use]
348pub struct Tuplet {
349    actual: u32,
350    normal: u32,
351    duration_type: DurationType,
352    dots: u32,
353    /// The written value the `normal` count is counted in, which is not
354    /// always the one the `actual` count is written as: three eighths in the
355    /// time of one quarter is the same ratio as three in the time of two
356    /// eighths, and music21 keeps both spellings.
357    normal_type: DurationType,
358    /// The dots on that value.
359    normal_dots: u32,
360}
361
362impl Tuplet {
363    /// A tuplet of `actual` notes of a written value in the time of
364    /// `normal` of them.
365    pub fn new(actual: u32, normal: u32, duration_type: DurationType, dots: u32) -> Self {
366        Self {
367            actual,
368            normal,
369            duration_type,
370            dots,
371            normal_type: duration_type,
372            normal_dots: dots,
373        }
374    }
375
376    /// The same tuplet counting the `normal` side in a different written
377    /// value: music21's `durationNormal` apart from its `durationActual`.
378    pub fn with_normal(mut self, duration_type: DurationType, dots: u32) -> Self {
379        self.normal_type = duration_type;
380        self.normal_dots = dots;
381        self
382    }
383
384    /// The written value the `normal` count is counted in.
385    pub fn normal_duration_type(&self) -> DurationType {
386        self.normal_type
387    }
388
389    /// The written value the `actual` notes are written as, with its dots:
390    /// music21's `durationActual`, in the pair [`Duration::components`]
391    /// writes a value as.
392    pub fn duration_actual(&self) -> (DurationType, u32) {
393        (self.duration_type, self.dots)
394    }
395
396    /// The written value the `normal` count is counted in, with its dots:
397    /// music21's `durationNormal`.
398    pub fn duration_normal(&self) -> (DurationType, u32) {
399        (self.normal_type, self.normal_dots)
400    }
401
402    /// The `actual` count beside the value it is written in: music21's
403    /// `tupletActual`.
404    pub fn tuplet_actual(&self) -> (u32, (DurationType, u32)) {
405        (self.actual, self.duration_actual())
406    }
407
408    /// The `normal` count beside the value it is counted in: music21's
409    /// `tupletNormal`.
410    pub fn tuplet_normal(&self) -> (u32, (DurationType, u32)) {
411        (self.normal, self.duration_normal())
412    }
413
414    /// Writes both sides of the tuplet in one value: music21's
415    /// `setDurationType`.
416    pub fn set_duration_type(&mut self, duration_type: DurationType, dots: u32) {
417        self.duration_type = duration_type;
418        self.dots = dots;
419        self.normal_type = duration_type;
420        self.normal_dots = dots;
421    }
422
423    /// Changes how many notes are played in the time of how many: music21's
424    /// `setRatio`.
425    pub fn set_ratio(&mut self, actual: u32, normal: u32) {
426        self.actual = actual;
427        self.normal = normal;
428    }
429
430    /// The dots on that value.
431    pub fn normal_dots(&self) -> u32 {
432        self.normal_dots
433    }
434
435    /// How long the whole tuplet lasts: music21's `totalTupletLength`, the
436    /// `normal` count of the value it is counted in.
437    pub fn total_tuplet_length(&self) -> FloatType {
438        FloatType::from(self.normal) * self.normal_type.quarter_length_with_dots(self.normal_dots)
439    }
440
441    /// How many notes are played: music21's `numberNotesActual`.
442    pub fn actual(&self) -> u32 {
443        self.actual
444    }
445
446    /// How many notes they are played in the time of: `numberNotesNormal`.
447    pub fn normal(&self) -> u32 {
448        self.normal
449    }
450
451    /// The note value each one is written as.
452    pub fn duration_type(&self) -> DurationType {
453        self.duration_type
454    }
455
456    /// How many augmentation dots that written value carries.
457    pub fn dots(&self) -> u32 {
458        self.dots
459    }
460
461    /// What the written length is multiplied by inside the tuplet:
462    /// music21's `tupletMultiplier`, `normal / actual`.
463    pub fn multiplier(&self) -> FractionType {
464        FractionType::new(
465            IntegerType::try_from(self.normal).unwrap_or(IntegerType::MAX),
466            IntegerType::try_from(self.actual).unwrap_or(IntegerType::MAX),
467        )
468    }
469
470    /// music21's `Tuplet.fullName`: the familiar name for the ratios that
471    /// have one, and `Tuplet of 17/14ths` for the ones that do not.
472    pub fn full_name(&self) -> String {
473        match (self.actual, self.normal) {
474            (3, 2) => "Triplet".to_string(),
475            (5, 4 | 2) => "Quintuplet".to_string(),
476            (6, 4) => "Sextuplet".to_string(),
477            (7, 4) => "Septuplet".to_string(),
478            (actual, normal) => format!(
479                "Tuplet of {actual}/{normal}{}s",
480                ordinal_abbreviation(normal)
481            ),
482        }
483    }
484}
485
486/// The `st`, `nd`, `rd` or `th` that follows a number: music21's
487/// `ordinalAbbreviation`, which the tuplet ratios without a name use.
488fn ordinal_abbreviation(value: u32) -> &'static str {
489    if matches!(value % 100, 11..=13) {
490        return "th";
491    }
492    match value % 10 {
493        1 => "st",
494        2 => "nd",
495        3 => "rd",
496        _ => "th",
497    }
498}
499
500/// The word music21 puts in front of a note value for its dots. The mensural
501/// values say it differently: an undotted longa is *imperfect* and a dotted
502/// one *perfect*.
503fn dot_prefix(dots: u32, mensural: bool) -> &'static str {
504    match dots {
505        0 if mensural => "Imperfect ",
506        1 if mensural => "Perfect ",
507        0 => "",
508        1 => "Dotted ",
509        2 => "Double Dotted ",
510        3 => "Triple Dotted ",
511        _ => "Quadruple Dotted ",
512    }
513}
514
515impl Duration {
516    /// Creates a duration from a quarter-length value.
517    pub fn new(quarter_length: FloatType) -> Result<Self> {
518        if !quarter_length.is_finite() || quarter_length < 0.0 {
519            return Err(Error::Duration(format!(
520                "duration quarter length must be finite and non-negative, got {quarter_length}"
521            )));
522        }
523
524        Ok(Self {
525            quarter_length,
526            tuplets: None,
527        })
528    }
529
530    /// Returns a quarter-note duration.
531    pub fn quarter() -> Self {
532        Self::from_type(DurationType::Quarter)
533    }
534
535    /// Returns a half-note duration.
536    pub fn half() -> Self {
537        Self::from_type(DurationType::Half)
538    }
539
540    /// Returns a whole-note duration.
541    pub fn whole() -> Self {
542        Self::from_type(DurationType::Whole)
543    }
544
545    /// Returns an eighth-note duration.
546    pub fn eighth() -> Self {
547        Self::from_type(DurationType::Eighth)
548    }
549
550    /// Creates a duration from a note-value type.
551    pub fn from_type(duration_type: DurationType) -> Self {
552        Self {
553            quarter_length: duration_type.quarter_length(),
554            tuplets: None,
555        }
556    }
557
558    /// Creates a duration from a note-value type carrying augmentation dots.
559    pub fn from_type_with_dots(duration_type: DurationType, dots: u32) -> Self {
560        Self {
561            quarter_length: duration_type.quarter_length_with_dots(dots),
562            tuplets: None,
563        }
564    }
565
566    /// Returns the note-value type and dot count that together make exactly
567    /// this length, such as a half note with one dot for `3.0`. `None` for a
568    /// length no single dotted note value has, such as a tuplet or a tie.
569    pub fn type_and_dots(&self) -> Option<(DurationType, u32)> {
570        exact_type_and_dots(self.quarter_length)
571    }
572
573    /// Returns the augmentation dots on this duration, or `0` when it is not
574    /// a single dotted note value.
575    pub fn dots(&self) -> u32 {
576        self.type_and_dots().map_or(0, |(_, dots)| dots)
577    }
578
579    /// music21's `ordinal` for the duration's type, or `None` where music21
580    /// says `complex` or the duration is zero: a duration whose quarter length
581    /// is not a single dotted note value.
582    pub fn ordinal(&self) -> Option<usize> {
583        self.type_and_dots()?.0.ordinal()
584    }
585
586    /// Returns the duration scaled by a positive factor: music21's
587    /// `augmentOrDiminish`, so a quarter by two is a half.
588    pub fn augment_or_diminish(&self, factor: FloatType) -> Result<Duration> {
589        if factor.is_nan() || factor <= 0.0 {
590            return Err(Error::Duration(
591                "amountToScale must be greater than zero".to_string(),
592            ));
593        }
594        Duration::new(self.quarter_length * factor)
595    }
596
597    /// The tuplet this length is written as, if any: the first of
598    /// [`quarter_length_to_tuplet`].
599    ///
600    /// A length that is already a plain written value is that value rather
601    /// than a tuplet of some other one, so a quarter answers `None`: music21
602    /// tries the exact match before it tries any ratio, and a plain quarter
603    /// is a quarter even though it is also two thirds of a dotted quarter in
604    /// a triplet.
605    pub fn tuplet(&self) -> Option<Tuplet> {
606        if self.type_and_dots().is_some() {
607            return None;
608        }
609        quarter_length_to_tuplet(self.quarter_length, 1)
610            .into_iter()
611            .next()
612    }
613
614    /// The tuplets this length is written inside: the ones a caller set, or
615    /// the one read off the length when nobody has.
616    ///
617    /// This is music21's `tuplets`, which is likewise inferred until it is
618    /// assigned. Setting it to nothing is not the same as never setting it:
619    /// two thirds of a quarter reads as a quarter triplet on its own, and
620    /// stays two thirds of a quarter written as no tuplet once told so.
621    pub fn tuplets(&self) -> Vec<Tuplet> {
622        match &self.tuplets {
623            Some(tuplets) => tuplets.clone(),
624            None => convert(self.quarter_length, true).1.into_iter().collect(),
625        }
626    }
627
628    /// What the written values are multiplied by to give the sounding
629    /// length: music21's `aggregateTupletMultiplier`, every tuplet's ratio
630    /// multiplied together, so a triplet inside a quintuplet is `8/15`.
631    pub fn aggregate_tuplet_multiplier(&self) -> FractionType {
632        self.tuplets()
633            .iter()
634            .map(Tuplet::multiplier)
635            .fold(FractionType::from(1), |total, ratio| total * ratio)
636    }
637
638    /// The total of the written values, before any tuplet shortens them:
639    /// music21's `quarterLengthNoTuplets`.
640    pub fn quarter_length_no_tuplets(&self) -> FloatType {
641        self.components()
642            .into_iter()
643            .map(|(duration_type, dots)| duration_type.quarter_length_with_dots(dots))
644            .sum()
645    }
646
647    /// Says what tuplets this length is written inside, keeping the written
648    /// values and changing the sounding length to match: music21's `tuplets`
649    /// setter.
650    pub fn set_tuplets(&mut self, tuplets: Vec<Tuplet>) {
651        let written = self.quarter_length_no_tuplets();
652        self.tuplets = Some(tuplets);
653        self.quarter_length = written * float_from_fraction(self.aggregate_tuplet_multiplier());
654    }
655
656    /// Writes this length inside one more tuplet, shortening it by that
657    /// tuplet's ratio: music21's `appendTuplet`.
658    pub fn append_tuplet(&mut self, tuplet: Tuplet) {
659        let mut tuplets = self.tuplets();
660        tuplets.push(tuplet);
661        self.set_tuplets(tuplets);
662    }
663
664    /// The length of the written values alone, with the tuplets a caller set
665    /// divided back out.
666    ///
667    /// Only the set ones: an inferred tuplet is read *off* this length, so
668    /// dividing by it here would be circular. Dividing in binary floating
669    /// point rarely lands back on an exact note value, so the result is
670    /// snapped onto one when it is within a hair of it.
671    fn written_quarter_length(&self) -> FloatType {
672        let Some(tuplets) = &self.tuplets else {
673            return self.quarter_length;
674        };
675        if tuplets.is_empty() {
676            return self.quarter_length;
677        }
678        let multiplier = float_from_fraction(self.aggregate_tuplet_multiplier());
679        if multiplier == 0.0 {
680            return self.quarter_length;
681        }
682        let written = self.quarter_length / multiplier;
683        let tolerance = written.abs() * TUPLET_TOLERANCE;
684        DurationType::ALL
685            .into_iter()
686            .flat_map(|duration_type| {
687                (0..=MAX_DOTS).map(move |dots| duration_type.quarter_length_with_dots(dots))
688            })
689            .find(|candidate| (candidate - written).abs() <= tolerance)
690            .unwrap_or(written)
691    }
692
693    /// The written note values this length is made of, tied together:
694    /// music21's `components`.
695    ///
696    /// This is the first half of [`quarter_conversion`], read over the
697    /// written length. A length whose tuplets a caller has set is read as
698    /// the tie those tuplets leave, without looking for a tuplet of its own.
699    pub fn components(&self) -> Vec<(DurationType, u32)> {
700        convert(self.written_quarter_length(), self.tuplets.is_none()).0
701    }
702
703    /// Whether the length needs more than one written value, tied: music21's
704    /// `isComplex`.
705    pub fn is_complex(&self) -> bool {
706        self.components().len() > 1
707    }
708
709    /// The dots on the written value, as the one dot group the crate keeps:
710    /// music21's `dotGroups`, which can also write one length twice over
711    /// where a mensural notation asks for it.
712    pub fn dot_groups(&self) -> Vec<u32> {
713        vec![self.dots()]
714    }
715
716    /// Takes the length away: music21's `clear`, which empties the written
717    /// values so the duration sounds for no time. The tuplets it was told
718    /// stay.
719    pub fn clear(&mut self) {
720        self.quarter_length = 0.0;
721    }
722
723    /// Ties one more written value onto the end: music21's
724    /// `addDurationTuple`, which lengthens the duration by that value inside
725    /// whatever tuplets it is written in.
726    pub fn add_duration_tuple(&mut self, duration_type: DurationType, dots: u32) {
727        let written =
728            self.quarter_length_no_tuplets() + duration_type.quarter_length_with_dots(dots);
729        self.quarter_length = written * float_from_fraction(self.aggregate_tuplet_multiplier());
730    }
731
732    /// Which written value is sounding at a position within the duration:
733    /// music21's `componentIndexAtQtrPosition`. The positions are counted
734    /// in the written values, before any tuplet scales them, and the start
735    /// and the very end answer the first and the last value.
736    pub fn component_index_at_qtr_position(&self, position: FloatType) -> Result<usize> {
737        let components = self.components();
738        if components.is_empty() {
739            return Err(Error::Duration(
740                "Need components to run getComponentIndexAtQtrPosition".to_string(),
741            ));
742        }
743        let total = self.quarter_length_no_tuplets();
744        if position.is_nan() || position < 0.0 {
745            return Err(Error::Value(
746                "position is before the start of the duration".to_string(),
747            ));
748        }
749        if position > total {
750            return Err(Error::Value(
751                "position is after the end of the duration".to_string(),
752            ));
753        }
754        if position == total {
755            return Ok(components.len() - 1);
756        }
757        let mut reached = 0.0;
758        for (index, (duration_type, dots)) in components.iter().enumerate() {
759            reached += duration_type.quarter_length_with_dots(*dots);
760            if reached > position {
761                return Ok(index);
762            }
763        }
764        Ok(components.len() - 1)
765    }
766
767    /// Where a written value starts within the duration, counted in the
768    /// written values: music21's `componentStartTime`. An index past the
769    /// values is an error.
770    pub fn component_start_time(&self, index: usize) -> Result<FloatType> {
771        let components = self.components();
772        if index >= components.len() {
773            return Err(Error::Duration(format!(
774                "invalid component index value {index} submitted; value must be an integer between 0 and {}",
775                components.len().saturating_sub(1)
776            )));
777        }
778        Ok(components[..index]
779            .iter()
780            .map(|(duration_type, dots)| duration_type.quarter_length_with_dots(*dots))
781            .sum())
782    }
783
784    /// Returns music21's `fullName` for a single written note value, such as
785    /// `"Dotted Quarter"`, `"Double Dotted Half"`, `"Imperfect Longa"` or
786    /// `"Quarter Triplet (2/3 QL)"`.
787    ///
788    /// A length that has to be written as a tie names each of its values and
789    /// joins them, `"Quarter tied to 16th (1 1/4 total QL)"`. A length no
790    /// note value reaches is `"Inexpressible"` and a zero one is
791    /// `"Zero Duration (0 total QL)"`, both as music21 names them.
792    pub fn full_name(&self) -> String {
793        let components = self.components();
794        if components.is_empty() {
795            return if self.quarter_length == 0.0 {
796                // music21 writes a stray second space here, from joining a
797                // name that already ends in one; its own docstring for this
798                // shows a single space, which is what the whitespace its
799                // test runner normalises away comes to.
800                "Zero Duration (0 total QL)".to_string()
801            } else {
802                "Inexpressible".to_string()
803            };
804        }
805        // A tuplet is only asked for once the length has failed to be a
806        // plain or dotted note value, which is music21's order: a quarter is
807        // a quarter, even though it is also a dotted quarter in a triplet.
808        let tuplet = if components.len() == 1 && self.type_and_dots().is_none() {
809            self.tuplets().first().copied()
810        } else {
811            None
812        };
813        let names: Vec<String> = components
814            .iter()
815            .map(|(duration_type, dots)| {
816                let mensural = matches!(duration_type, DurationType::Longa | DurationType::Maxima);
817                let mut name = format!("{}{}", dot_prefix(*dots, mensural), duration_type.title());
818                if let Some(tuplet) = &tuplet {
819                    name.push(' ');
820                    name.push_str(&tuplet.full_name());
821                }
822                // music21 shows the length itself once the name alone stops
823                // saying what it is: past two dots, or inside a tuplet.
824                if tuplet.is_some() || *dots >= 3 {
825                    name.push_str(&format!(" ({} QL)", mixed_numeral(self.quarter_length)));
826                }
827                name
828            })
829            .collect();
830        let mut name = names.join(" tied to ");
831        if components.len() != 1 {
832            name.push_str(&format!(
833                " ({} total QL)",
834                mixed_numeral(self.quarter_length)
835            ));
836        }
837        name
838    }
839
840    /// Returns the note-value type whose undotted length this duration is.
841    ///
842    /// Returns `None` for a length that is not a plain note value, such as a
843    /// dotted or tuplet duration.
844    pub fn duration_type(&self) -> Option<DurationType> {
845        self.type_and_dots().map(|(duration_type, _)| duration_type)
846    }
847
848    /// Returns the duration in quarter lengths.
849    pub fn quarter_length(&self) -> FloatType {
850        self.quarter_length
851    }
852
853    /// Updates the duration in quarter lengths.
854    pub fn set_quarter_length(&mut self, quarter_length: FloatType) -> Result<()> {
855        *self = Self::new(quarter_length)?;
856        Ok(())
857    }
858}
859
860const MAX_DOTS: u32 = 4;
861
862/// The note value and dot count whose length is exactly this one, if any.
863fn exact_type_and_dots(quarter_length: FloatType) -> Option<(DurationType, u32)> {
864    DurationType::ALL.into_iter().find_map(|duration_type| {
865        (0..=MAX_DOTS)
866            .find(|dots| duration_type.quarter_length_with_dots(*dots) == quarter_length)
867            .map(|dots| (duration_type, dots))
868    })
869}
870
871/// A ratio as a float, which a tuplet's is whenever it meets a quarter
872/// length. Zero for the ratio that has no value, which no tuplet has.
873fn float_from_fraction(ratio: FractionType) -> FloatType {
874    ratio.to_f64().unwrap_or(0.0)
875}
876
877/// Returns the note-value type closest to a quarter length, and whether it is
878/// exact, as music21's `quarterLengthToClosestType` does: a length between two
879/// types reports the longer one, so a triplet quarter is an inexact eighth.
880pub fn quarter_length_to_closest_type(quarter_length: FloatType) -> Result<(DurationType, bool)> {
881    let too_small = || {
882        Error::Duration(format!(
883            "cannot return types smaller than 2048th; quarter length was {quarter_length}"
884        ))
885    };
886    if quarter_length.is_nan() || quarter_length <= 0.0 {
887        return Err(too_small());
888    }
889    let note_length = 4.0 / quarter_length;
890    if let Some(exact) = DurationType::ALL
891        .into_iter()
892        .find(|duration_type| duration_type.type_number() == Some(note_length))
893    {
894        return Ok((exact, true));
895    }
896    let upper_bound = 8.0 / quarter_length;
897    if let Some(closest) = DurationType::ALL.into_iter().find(|duration_type| {
898        duration_type
899            .type_number()
900            .is_some_and(|number| note_length < number && number < upper_bound)
901    }) {
902        return Ok((closest, false));
903    }
904    if quarter_length > 128.0 {
905        return Ok((DurationType::DuplexMaxima, false));
906    }
907    Err(too_small())
908}
909
910/// The tuplets a length can be written as, shortest note value first:
911/// music21's `quarterLengthToTuplet`, stopping after `max_to_return` of
912/// them.
913///
914/// The search walks the note values from shortest to longest and, for
915/// each, tries every tuplet numerator and every count of them that fits.
916/// Order is what decides the answer: two thirds of a quarter matches a
917/// quarter in a triplet before it matches anything longer, which is why
918/// music21 calls it a quarter triplet and not two eighth triplets. Dotted
919/// tuplets are found; nested ones and four in the time of three are not,
920/// the latter being a dotted note.
921pub fn quarter_length_to_tuplet(quarter_length: FloatType, max_to_return: usize) -> Vec<Tuplet> {
922    let mut found = Vec::new();
923    if quarter_length.is_nan() || quarter_length <= 0.0 || max_to_return == 0 {
924        return found;
925    }
926    let mut values = DurationType::ALL;
927    values.sort_by(|left, right| {
928        left.quarter_length()
929            .partial_cmp(&right.quarter_length())
930            .unwrap_or(std::cmp::Ordering::Equal)
931    });
932    let tolerance = quarter_length * TUPLET_TOLERANCE;
933    for duration_type in values {
934        for actual in TUPLET_NUMERATORS {
935            for normal in 1..actual {
936                for dots in TUPLET_DOTS {
937                    let candidate = duration_type.quarter_length_with_dots(dots)
938                        * FloatType::from(normal)
939                        / FloatType::from(actual);
940                    if (candidate - quarter_length).abs() <= tolerance {
941                        found.push(Tuplet::new(actual, normal, duration_type, dots));
942                        break;
943                    }
944                }
945            }
946            if found.len() >= max_to_return {
947                return found;
948            }
949        }
950    }
951    found
952}
953
954/// The one enormous tuplet music21 falls back on for a length no tie of
955/// written values reaches: its `quarterLengthToNonPowerOf2Tuplet`.
956///
957/// Any length can be written as a single note inside a strange enough
958/// tuplet — 53/25 of a quarter is a whole note in a tuplet of a hundred
959/// in the time of fifty-three — and music21 tries that before it calls a
960/// length inexpressible. The answer is the tuplet together with the note
961/// value and dots written inside it, or nothing for a length that defeats
962/// even that.
963pub fn quarter_length_to_non_power_of_2_tuplet(
964    quarter_length: FloatType,
965) -> Option<(Tuplet, DurationType, u32)> {
966    if quarter_length.is_nan() || quarter_length <= 0.0 {
967        return None;
968    }
969    let (original_actual, original_normal) =
970        limited_fraction(1.0 / quarter_length, DENOMINATOR_LIMIT)?;
971    let (mut actual, mut normal) = (original_actual, original_normal);
972    // Between one and two, which is where a tuplet ratio belongs.
973    while actual < normal {
974        actual *= 2;
975        reduce(&mut actual, &mut normal);
976    }
977    while actual > normal * 2 {
978        normal *= 2;
979        reduce(&mut actual, &mut normal);
980    }
981    let (written, _) = quarter_length_to_closest_type(quarter_length / normal as FloatType).ok()?;
982    // What is written inside the tuplet, which is the ratio the normalising
983    // undid.
984    let inside = (actual as FloatType / normal as FloatType)
985        / (original_actual as FloatType / original_normal as FloatType);
986    let (kind, dots) = exact_type_and_dots(inside)?;
987    Some((
988        Tuplet::new(
989            u32::try_from(actual).ok()?,
990            u32::try_from(normal).ok()?,
991            written,
992            0,
993        ),
994        kind,
995        dots,
996    ))
997}
998
999/// The written note values a length is made of and the tuplet, if any, they
1000/// are written inside: music21's `quarterConversion`.
1001///
1002/// One value for a plain or dotted note, one for a tuplet (the written
1003/// value, which the tuplet's ratio then scales), and several for a length
1004/// that can only be written as a tie — a quarter tied to a sixteenth for
1005/// five sixteenths. Empty for a length that runs off the end of the note
1006/// values, which is music21's `inexpressible`, and for a length of nought.
1007///
1008/// The tie is found greedily, largest value first, as music21 finds it:
1009/// take the largest note that fits, and look for a single dotted value
1010/// covering what is left before taking another bite. A length no tie of
1011/// eight values reaches is written as one note inside the tuplet
1012/// [`quarter_length_to_non_power_of_2_tuplet`] finds.
1013pub fn quarter_conversion(quarter_length: FloatType) -> (Vec<(DurationType, u32)>, Option<Tuplet>) {
1014    convert(quarter_length, true)
1015}
1016
1017/// [`quarter_conversion`], with the search for an ordinary tuplet turned off
1018/// where a caller has already said which tuplets the length is written in.
1019fn convert(
1020    written: FloatType,
1021    look_for_tuplet: bool,
1022) -> (Vec<(DurationType, u32)>, Option<Tuplet>) {
1023    // Zero is written as nothing at all, which is what music21's empty
1024    // `components` says; `type_and_dots` would call it a `zero` note.
1025    if written == 0.0 {
1026        return (Vec::new(), None);
1027    }
1028    if let Some(value) = exact_type_and_dots(written) {
1029        return (vec![value], None);
1030    }
1031    // Shorter than the shortest note value, or longer than a tie of the
1032    // longest can reach: music21 calls both *inexpressible*, and asks this
1033    // before it looks for a tuplet.
1034    let Ok((largest, _)) = quarter_length_to_closest_type(written) else {
1035        return (Vec::new(), None);
1036    };
1037    if largest.next_larger().is_none() {
1038        return (Vec::new(), None);
1039    }
1040    if look_for_tuplet && let Some(tuplet) = quarter_length_to_tuplet(written, 1).into_iter().next()
1041    {
1042        return (vec![(tuplet.duration_type(), tuplet.dots())], Some(tuplet));
1043    }
1044    let mut components = vec![(largest, 0)];
1045    let mut remainder = written - largest.quarter_length();
1046    for _ in 0..MAX_TIED_COMPONENTS {
1047        if let Some(rest) = exact_type_and_dots(remainder) {
1048            components.push(rest);
1049            return (components, None);
1050        }
1051        let Ok((next, _)) = quarter_length_to_closest_type(remainder) else {
1052            break;
1053        };
1054        remainder -= next.quarter_length();
1055        components.push((next, 0));
1056    }
1057    match quarter_length_to_non_power_of_2_tuplet(written) {
1058        Some((tuplet, kind, dots)) => (vec![(kind, dots)], Some(tuplet)),
1059        None => (Vec::new(), None),
1060    }
1061}
1062
1063/// The largest denominator [`mixed_numeral`] will write. music21 allows any
1064/// up to 65535, but it prints the quarter length of a written note, and a
1065/// note nobody can write does not want a fraction with a four-digit
1066/// denominator in its name.
1067const MAX_MIXED_NUMERAL_DENOMINATOR: u32 = 1024;
1068
1069/// How close a fraction has to be to be written as one, relative to the
1070/// value.
1071const MIXED_NUMERAL_TOLERANCE: FloatType = 1e-6;
1072
1073/// Writes a quarter length the way music21's `mixedNumeral` does: `"2"`,
1074/// `"1/2"`, `"1 3/4"`.
1075fn mixed_numeral(value: FloatType) -> String {
1076    let whole = value.trunc() as IntegerType;
1077    let remainder = value - value.trunc();
1078    if remainder == 0.0 {
1079        return whole.to_string();
1080    }
1081    // Ascending denominators, so the simplest fraction that fits wins: a
1082    // third has to be reachable as well as a quarter, since a tuplet length
1083    // is not a power of two. The tolerance stands in for the snapping
1084    // music21 does when it reads a quarter length, which is what makes its
1085    // own `0.333333` a triplet third rather than a decimal.
1086    let tolerance = MIXED_NUMERAL_TOLERANCE * value.abs().max(1.0);
1087    let fractional = (1..=MAX_MIXED_NUMERAL_DENOMINATOR)
1088        .find_map(|denominator| {
1089            let numerator = (remainder * FloatType::from(denominator)).round();
1090            ((remainder - numerator / FloatType::from(denominator)).abs() <= tolerance)
1091                .then(|| format!("{}/{denominator}", numerator as IntegerType))
1092        })
1093        .unwrap_or_else(|| remainder.to_string());
1094    if whole == 0 {
1095        fractional
1096    } else {
1097        format!("{whole} {fractional}")
1098    }
1099}
1100
1101impl Default for Duration {
1102    fn default() -> Self {
1103        Self::quarter()
1104    }
1105}
1106
1107impl PartialEq for Duration {
1108    fn eq(&self, other: &Self) -> bool {
1109        self.quarter_length == other.quarter_length
1110    }
1111}
1112
1113impl TryFrom<FloatType> for Duration {
1114    type Error = Error;
1115
1116    fn try_from(value: FloatType) -> Result<Self> {
1117        Self::new(value)
1118    }
1119}
1120
1121impl TryFrom<IntegerType> for Duration {
1122    type Error = Error;
1123
1124    fn try_from(value: IntegerType) -> Result<Self> {
1125        Self::new(value as FloatType)
1126    }
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131
1132    #[test]
1133    fn a_type_is_found_from_its_undotted_length_alone() {
1134        assert_eq!(
1135            DurationType::from_quarter_length(2.0),
1136            Some(DurationType::Half)
1137        );
1138        assert_eq!(
1139            DurationType::from_quarter_length(0.125),
1140            Some(DurationType::ThirtySecond)
1141        );
1142        assert_eq!(DurationType::from_quarter_length(3.0), None);
1143        assert_eq!(super::mixed_numeral(2.0 / 3.0), "2/3");
1144        assert_eq!(super::mixed_numeral(1.0 / 3.0 + 1.0), "1 1/3");
1145    }
1146
1147    #[test]
1148    fn a_duration_is_cleared_and_lengthened_a_value_at_a_time() {
1149        let mut duration = Duration::new(1.5).unwrap();
1150        assert_eq!(duration.dot_groups(), [1]);
1151        duration.add_duration_tuple(DurationType::Eighth, 0);
1152        assert_eq!(duration.quarter_length(), 2.0);
1153        duration.clear();
1154        assert_eq!(duration.quarter_length(), 0.0);
1155        assert_eq!(duration.dot_groups(), [0]);
1156        // Inside a triplet the value added is scaled as the rest is.
1157        let mut triplet = Duration::new(2.0 / 3.0).unwrap();
1158        triplet.add_duration_tuple(DurationType::Quarter, 0);
1159        assert!((triplet.quarter_length() - 4.0 / 3.0).abs() < 1e-9);
1160    }
1161
1162    /// music21's own examples, over a tie the crate reads off the length.
1163    #[test]
1164    fn a_position_within_a_tie_names_the_value_sounding_there() {
1165        let tied = Duration::new(2.5).unwrap();
1166        assert_eq!(tied.components().len(), 2);
1167        assert_eq!(tied.component_index_at_qtr_position(0.0).unwrap(), 0);
1168        assert_eq!(tied.component_index_at_qtr_position(1.5).unwrap(), 0);
1169        assert_eq!(tied.component_index_at_qtr_position(2.0).unwrap(), 1);
1170        assert_eq!(tied.component_index_at_qtr_position(2.5).unwrap(), 1);
1171        assert!(tied.component_index_at_qtr_position(3.0).is_err());
1172        assert!(tied.component_index_at_qtr_position(-1.0).is_err());
1173        assert!(
1174            Duration::new(0.0)
1175                .unwrap()
1176                .component_index_at_qtr_position(0.0)
1177                .is_err()
1178        );
1179        assert_eq!(tied.component_start_time(0).unwrap(), 0.0);
1180        assert_eq!(tied.component_start_time(1).unwrap(), 2.0);
1181        assert!(tied.component_start_time(2).is_err());
1182    }
1183
1184    #[test]
1185    fn a_tuplet_reads_and_writes_both_of_its_sides() {
1186        let mut tuplet = Tuplet::new(3, 2, DurationType::Eighth, 0);
1187        assert_eq!(tuplet.duration_actual(), (DurationType::Eighth, 0));
1188        assert_eq!(tuplet.duration_normal(), (DurationType::Eighth, 0));
1189        assert_eq!(tuplet.tuplet_actual(), (3, (DurationType::Eighth, 0)));
1190        assert_eq!(tuplet.tuplet_normal(), (2, (DurationType::Eighth, 0)));
1191        tuplet.set_duration_type(DurationType::Quarter, 1);
1192        assert_eq!(tuplet.duration_actual(), (DurationType::Quarter, 1));
1193        assert_eq!(tuplet.normal_duration_type(), DurationType::Quarter);
1194        assert_eq!(tuplet.normal_dots(), 1);
1195        tuplet.set_ratio(5, 4);
1196        assert_eq!((tuplet.actual(), tuplet.normal()), (5, 4));
1197        assert_eq!(tuplet.multiplier(), FractionType::new(4, 5));
1198    }
1199
1200    /// music21's own examples for `quarterLengthToTuplet`,
1201    /// `quarterLengthToNonPowerOf2Tuplet` and `quarterConversion`.
1202    #[test]
1203    fn a_length_is_read_as_the_tuplets_and_ties_music21_reads() {
1204        use super::{
1205            Duration, DurationType, quarter_conversion, quarter_length_to_non_power_of_2_tuplet,
1206            quarter_length_to_tuplet,
1207        };
1208
1209        let names = |tuplets: Vec<Tuplet>| -> Vec<String> {
1210            tuplets
1211                .iter()
1212                .map(|tuplet| {
1213                    format!(
1214                        "{}/{}/{}",
1215                        tuplet.actual(),
1216                        tuplet.normal(),
1217                        tuplet.duration_type().music21_name()
1218                    )
1219                })
1220                .collect()
1221        };
1222        assert_eq!(
1223            names(quarter_length_to_tuplet(0.333_333_33, 4)),
1224            ["3/2/eighth", "3/1/quarter"]
1225        );
1226        assert_eq!(
1227            names(quarter_length_to_tuplet(0.20, 4)),
1228            ["5/4/16th", "5/2/eighth", "5/1/quarter"]
1229        );
1230        assert_eq!(
1231            names(quarter_length_to_tuplet(0.333_333_3, 1)),
1232            ["3/2/eighth"]
1233        );
1234        // A plain quarter is also two dotted quarters in the time of three,
1235        // which is why `Duration::tuplet` asks for the exact value first.
1236        let plain = quarter_length_to_tuplet(1.0, 1);
1237        assert_eq!(names(plain.clone()), ["3/2/quarter"]);
1238        assert_eq!(plain[0].dots(), 1);
1239        assert!(quarter_length_to_tuplet(0.0, 4).is_empty());
1240
1241        let (tuplet, kind, dots) = quarter_length_to_non_power_of_2_tuplet(7.0).unwrap();
1242        assert_eq!(names(vec![tuplet]), ["8/7/quarter"]);
1243        assert_eq!((kind, dots), (DurationType::Breve, 0));
1244        let (tuplet, kind, _) = quarter_length_to_non_power_of_2_tuplet(7.0 / 3.0).unwrap();
1245        assert_eq!(names(vec![tuplet]), ["12/7/16th"]);
1246        assert_eq!(kind, DurationType::Whole);
1247        assert!(quarter_length_to_non_power_of_2_tuplet(0.0).is_none());
1248
1249        let (components, tuplet) = quarter_conversion(2.5);
1250        assert_eq!(
1251            components,
1252            [(DurationType::Half, 0), (DurationType::Eighth, 0)]
1253        );
1254        assert!(tuplet.is_none());
1255        let (components, tuplet) = quarter_conversion(2.0 / 3.0);
1256        assert_eq!(components, [(DurationType::Quarter, 0)]);
1257        assert_eq!(names(tuplet.into_iter().collect()), ["3/2/quarter"]);
1258        let (components, tuplet) = quarter_conversion(3.75);
1259        assert_eq!(components, [(DurationType::Half, 3)]);
1260        assert!(tuplet.is_none());
1261        assert_eq!(quarter_conversion(99.0), (Vec::new(), None));
1262        assert_eq!(quarter_conversion(0.0), (Vec::new(), None));
1263
1264        assert!(Duration::new(2.5).unwrap().is_complex());
1265        assert!(!Duration::new(3.0).unwrap().is_complex());
1266        assert!(!Duration::new(2.0 / 3.0).unwrap().is_complex());
1267    }
1268
1269    #[test]
1270    fn neighbouring_types_match_music21() {
1271        assert_eq!(
1272            DurationType::Quarter.next_larger(),
1273            Some(DurationType::Half)
1274        );
1275        assert_eq!(
1276            DurationType::Quarter.next_smaller(),
1277            Some(DurationType::Eighth)
1278        );
1279        assert_eq!(DurationType::Whole.next_larger(), Some(DurationType::Breve));
1280        assert_eq!(DurationType::Breve.next_larger(), Some(DurationType::Longa));
1281        assert_eq!(
1282            DurationType::Sixteenth.next_smaller(),
1283            Some(DurationType::ThirtySecond)
1284        );
1285        assert_eq!(DurationType::DuplexMaxima.next_larger(), None);
1286        assert_eq!(DurationType::ALL[15].next_smaller(), None);
1287        assert_eq!(DurationType::Zero.next_larger(), None);
1288        assert_eq!(DurationType::Zero.next_smaller(), None);
1289    }
1290
1291    #[test]
1292    fn ordinal_and_scaling_match_music21() {
1293        assert_eq!(DurationType::DuplexMaxima.ordinal(), Some(0));
1294        assert_eq!(DurationType::Quarter.ordinal(), Some(6));
1295        assert_eq!(DurationType::Sixteenth.ordinal(), Some(8));
1296        assert_eq!(DurationType::Zero.ordinal(), None);
1297        assert_eq!(Duration::new(1.5).unwrap().ordinal(), Some(6));
1298        assert_eq!(Duration::new(2.5).unwrap().ordinal(), None);
1299        assert_eq!(Duration::new(0.0).unwrap().ordinal(), None);
1300        assert_eq!(
1301            Duration::new(1.0)
1302                .unwrap()
1303                .augment_or_diminish(2.0)
1304                .unwrap()
1305                .quarter_length(),
1306            2.0
1307        );
1308        assert_eq!(
1309            Duration::new(1.5)
1310                .unwrap()
1311                .augment_or_diminish(0.5)
1312                .unwrap()
1313                .quarter_length(),
1314            0.75
1315        );
1316        assert!(
1317            Duration::new(1.0)
1318                .unwrap()
1319                .augment_or_diminish(0.0)
1320                .is_err()
1321        );
1322        assert!(
1323            Duration::new(1.0)
1324                .unwrap()
1325                .augment_or_diminish(-1.0)
1326                .is_err()
1327        );
1328    }
1329    use super::*;
1330
1331    /// music21's `duration.typeToDuration`, verbatim.
1332    const MUSIC21_TYPE_TO_DURATION: [(&str, FloatType); 17] = [
1333        ("duplex-maxima", 64.0),
1334        ("maxima", 32.0),
1335        ("longa", 16.0),
1336        ("breve", 8.0),
1337        ("whole", 4.0),
1338        ("half", 2.0),
1339        ("quarter", 1.0),
1340        ("eighth", 0.5),
1341        ("16th", 0.25),
1342        ("32nd", 0.125),
1343        ("64th", 0.0625),
1344        ("128th", 0.03125),
1345        ("256th", 0.015625),
1346        ("512th", 0.0078125),
1347        ("1024th", 0.00390625),
1348        ("2048th", 0.001953125),
1349        ("zero", 0.0),
1350    ];
1351
1352    #[test]
1353    fn dots_types_and_full_names_match_music21() {
1354        let cases = [
1355            (
1356                4.0,
1357                Some((DurationType::Whole, 0)),
1358                "Whole",
1359                (DurationType::Whole, true),
1360            ),
1361            (
1362                2.0,
1363                Some((DurationType::Half, 0)),
1364                "Half",
1365                (DurationType::Half, true),
1366            ),
1367            (
1368                1.0,
1369                Some((DurationType::Quarter, 0)),
1370                "Quarter",
1371                (DurationType::Quarter, true),
1372            ),
1373            (
1374                0.5,
1375                Some((DurationType::Eighth, 0)),
1376                "Eighth",
1377                (DurationType::Eighth, true),
1378            ),
1379            (
1380                0.25,
1381                Some((DurationType::Sixteenth, 0)),
1382                "16th",
1383                (DurationType::Sixteenth, true),
1384            ),
1385            (
1386                3.0,
1387                Some((DurationType::Half, 1)),
1388                "Dotted Half",
1389                (DurationType::Half, false),
1390            ),
1391            (
1392                1.5,
1393                Some((DurationType::Quarter, 1)),
1394                "Dotted Quarter",
1395                (DurationType::Quarter, false),
1396            ),
1397            (
1398                0.75,
1399                Some((DurationType::Eighth, 1)),
1400                "Dotted Eighth",
1401                (DurationType::Eighth, false),
1402            ),
1403            (
1404                6.0,
1405                Some((DurationType::Whole, 1)),
1406                "Dotted Whole",
1407                (DurationType::Whole, false),
1408            ),
1409            (
1410                1.75,
1411                Some((DurationType::Quarter, 2)),
1412                "Double Dotted Quarter",
1413                (DurationType::Quarter, false),
1414            ),
1415            (
1416                0.875,
1417                Some((DurationType::Eighth, 2)),
1418                "Double Dotted Eighth",
1419                (DurationType::Eighth, false),
1420            ),
1421            (
1422                7.0,
1423                Some((DurationType::Whole, 2)),
1424                "Double Dotted Whole",
1425                (DurationType::Whole, false),
1426            ),
1427            (
1428                0.375,
1429                Some((DurationType::Sixteenth, 1)),
1430                "Dotted 16th",
1431                (DurationType::Sixteenth, false),
1432            ),
1433            (
1434                3.5,
1435                Some((DurationType::Half, 2)),
1436                "Double Dotted Half",
1437                (DurationType::Half, false),
1438            ),
1439            (
1440                3.75,
1441                Some((DurationType::Half, 3)),
1442                "Triple Dotted Half (3 3/4 QL)",
1443                (DurationType::Half, false),
1444            ),
1445            (
1446                1.875,
1447                Some((DurationType::Quarter, 3)),
1448                "Triple Dotted Quarter (1 7/8 QL)",
1449                (DurationType::Quarter, false),
1450            ),
1451            (
1452                8.0,
1453                Some((DurationType::Breve, 0)),
1454                "Breve",
1455                (DurationType::Breve, true),
1456            ),
1457            (
1458                16.0,
1459                Some((DurationType::Longa, 0)),
1460                "Imperfect Longa",
1461                (DurationType::Longa, true),
1462            ),
1463            (
1464                24.0,
1465                Some((DurationType::Longa, 1)),
1466                "Perfect Longa",
1467                (DurationType::Longa, false),
1468            ),
1469        ];
1470        for (quarter_length, type_and_dots, full_name, closest) in cases {
1471            let duration = Duration::new(quarter_length).unwrap();
1472            assert_eq!(duration.type_and_dots(), type_and_dots, "{quarter_length}");
1473            assert_eq!(duration.full_name(), full_name, "{quarter_length}");
1474            assert_eq!(
1475                quarter_length_to_closest_type(quarter_length).unwrap(),
1476                closest,
1477                "{quarter_length}"
1478            );
1479        }
1480
1481        let inexact = [
1482            (2.0 / 3.0, DurationType::Eighth),
1483            (1.0 / 3.0, DurationType::Sixteenth),
1484            (4.0 / 3.0, DurationType::Quarter),
1485            (0.2, DurationType::ThirtySecond),
1486            (0.4, DurationType::Sixteenth),
1487            (1.25, DurationType::Quarter),
1488            (5.0, DurationType::Whole),
1489            (2.5, DurationType::Half),
1490        ];
1491        // None of these is a single written note value. The ones that are a
1492        // tuplet are named as one; the ones that are a tie of two values are
1493        // not named at all, since the crate keeps a length and not the
1494        // components music21 spells them out from.
1495        let tuplet_names = [
1496            (2.0 / 3.0, "Quarter Triplet (2/3 QL)"),
1497            (1.0 / 3.0, "Eighth Triplet (1/3 QL)"),
1498            (4.0 / 3.0, "Half Triplet (1 1/3 QL)"),
1499            (0.2, "16th Quintuplet (1/5 QL)"),
1500            (0.4, "Eighth Quintuplet (2/5 QL)"),
1501        ];
1502        // and the ones music21 has to write as two values tied together
1503        let tied = [
1504            (1.25, "Quarter tied to 16th (1 1/4 total QL)"),
1505            (5.0, "Whole tied to Quarter (5 total QL)"),
1506            (2.5, "Half tied to Eighth (2 1/2 total QL)"),
1507        ];
1508        for (quarter_length, closest) in inexact {
1509            let duration = Duration::new(quarter_length).unwrap();
1510            assert_eq!(duration.type_and_dots(), None, "{quarter_length}");
1511            assert_eq!(duration.dots(), 0, "{quarter_length}");
1512            let tuplet_name = tuplet_names
1513                .iter()
1514                .find(|(length, _)| *length == quarter_length);
1515            let tied_name = tied.iter().find(|(length, _)| *length == quarter_length);
1516            let expected = tuplet_name.or(tied_name).map(|(_, name)| *name);
1517            assert_eq!(
1518                duration.full_name(),
1519                expected.expect("every inexact length here is named"),
1520                "{quarter_length}"
1521            );
1522            assert_eq!(
1523                duration.tuplet().is_some(),
1524                tuplet_name.is_some(),
1525                "{quarter_length}"
1526            );
1527            assert_eq!(
1528                duration.components().len(),
1529                if tuplet_name.is_some() { 1 } else { 2 },
1530                "{quarter_length}"
1531            );
1532            assert_eq!(
1533                quarter_length_to_closest_type(quarter_length).unwrap(),
1534                (closest, false),
1535                "{quarter_length}"
1536            );
1537        }
1538
1539        // the two lengths music21 refuses to write at all
1540        for quarter_length in [0.001, 100.0] {
1541            let duration = Duration::new(quarter_length).unwrap();
1542            assert!(duration.components().is_empty(), "{quarter_length}");
1543            assert_eq!(duration.full_name(), "Inexpressible", "{quarter_length}");
1544        }
1545        let zero = Duration::new(0.0).unwrap();
1546        assert!(zero.components().is_empty());
1547        assert_eq!(zero.full_name(), "Zero Duration (0 total QL)");
1548
1549        // music21 snaps a quarter length before it looks, so a truncated
1550        // third is still a triplet there and here.
1551        let truncated = Duration::new(0.333_333).unwrap();
1552        assert_eq!(truncated.full_name(), "Eighth Triplet (1/3 QL)");
1553
1554        let triplet = Duration::new(2.0 / 3.0).unwrap().tuplet().unwrap();
1555        assert_eq!((triplet.actual(), triplet.normal()), (3, 2));
1556        assert_eq!(triplet.duration_type(), DurationType::Quarter);
1557        assert_eq!(triplet.dots(), 0);
1558        assert_eq!(triplet.full_name(), "Triplet");
1559        assert_eq!(triplet.multiplier(), FractionType::new(2i32, 3i32));
1560
1561        // the ratios music21 has no word for say the ratio instead
1562        let odd = Tuplet::new(17, 14, DurationType::Quarter, 0);
1563        assert_eq!(odd.full_name(), "Tuplet of 17/14ths");
1564        assert_eq!(odd.total_tuplet_length(), 14.0);
1565        // Three eighths in the time of one quarter is the same ratio written
1566        // the other way, and lasts exactly as long.
1567        let across =
1568            Tuplet::new(3, 1, DurationType::Eighth, 0).with_normal(DurationType::Quarter, 0);
1569        assert_eq!(across.total_tuplet_length(), 1.0);
1570        assert_eq!(across.multiplier(), FractionType::new(1i32, 3i32));
1571        assert_eq!(Duration::new(3.75).unwrap().dots(), 3);
1572        assert_eq!(
1573            quarter_length_to_closest_type(200.0).unwrap(),
1574            (DurationType::DuplexMaxima, false)
1575        );
1576        assert!(quarter_length_to_closest_type(0.0).is_err());
1577        assert!(quarter_length_to_closest_type(0.0001).is_err());
1578    }
1579
1580    #[test]
1581    fn duration_types_match_music21s_table() {
1582        assert_eq!(DurationType::ALL.len(), MUSIC21_TYPE_TO_DURATION.len());
1583        for (duration_type, (name, quarter_length)) in
1584            DurationType::ALL.into_iter().zip(MUSIC21_TYPE_TO_DURATION)
1585        {
1586            assert_eq!(duration_type.music21_name(), name);
1587            assert_eq!(duration_type.quarter_length(), quarter_length, "{name}");
1588            assert_eq!(DurationType::from_music21_name(name), Some(duration_type));
1589        }
1590    }
1591
1592    #[test]
1593    fn duration_types_round_trip_through_their_names() {
1594        for duration_type in DurationType::ALL {
1595            let name = duration_type.music21_name();
1596            assert_eq!(name.parse::<DurationType>().unwrap(), duration_type);
1597            assert_eq!(duration_type.to_string(), name);
1598        }
1599        assert!("not-a-duration".parse::<DurationType>().is_err());
1600    }
1601
1602    #[test]
1603    fn each_type_is_half_the_one_before_it() {
1604        // `zero` is the exception and is excluded.
1605        let ordered = &DurationType::ALL[..DurationType::ALL.len() - 1];
1606        for pair in ordered.windows(2) {
1607            assert_eq!(
1608                pair[1].quarter_length() * 2.0,
1609                pair[0].quarter_length(),
1610                "{} should be half of {}",
1611                pair[1],
1612                pair[0]
1613            );
1614        }
1615    }
1616
1617    #[test]
1618    fn dots_add_half_of_what_came_before() {
1619        assert_eq!(DurationType::Half.quarter_length_with_dots(0), 2.0);
1620        assert_eq!(DurationType::Half.quarter_length_with_dots(1), 3.0);
1621        assert_eq!(DurationType::Half.quarter_length_with_dots(2), 3.5);
1622        assert_eq!(DurationType::Half.quarter_length_with_dots(3), 3.75);
1623        assert_eq!(DurationType::Quarter.quarter_length_with_dots(1), 1.5);
1624    }
1625
1626    #[test]
1627    fn durations_convert_to_and_from_note_values() {
1628        assert_eq!(
1629            Duration::from_type(DurationType::Whole).quarter_length(),
1630            4.0
1631        );
1632        assert_eq!(
1633            Duration::from_type(DurationType::Whole).duration_type(),
1634            Some(DurationType::Whole)
1635        );
1636        assert_eq!(
1637            Duration::from_type_with_dots(DurationType::Half, 1).quarter_length(),
1638            3.0
1639        );
1640        // A dotted value keeps the type it is dotted from, as music21 reads
1641        // it: a dotted half is a half with one dot, not a type of its own.
1642        assert_eq!(
1643            Duration::from_type_with_dots(DurationType::Half, 1).duration_type(),
1644            Some(DurationType::Half)
1645        );
1646        assert_eq!(
1647            Duration::from_type_with_dots(DurationType::Half, 1).dots(),
1648            1
1649        );
1650        assert_eq!(
1651            Duration::new(3.75).unwrap().duration_type(),
1652            Some(DurationType::Half)
1653        );
1654        assert_eq!(Duration::new(3.75).unwrap().dots(), 3);
1655        // A triplet eighth is no note value at all.
1656        assert_eq!(Duration::new(1.0 / 3.0).unwrap().duration_type(), None);
1657    }
1658
1659    #[test]
1660    fn the_named_helpers_agree_with_their_types() {
1661        assert_eq!(
1662            Duration::quarter(),
1663            Duration::from_type(DurationType::Quarter)
1664        );
1665        assert_eq!(Duration::half(), Duration::from_type(DurationType::Half));
1666        assert_eq!(Duration::whole(), Duration::from_type(DurationType::Whole));
1667        assert_eq!(
1668            Duration::eighth(),
1669            Duration::from_type(DurationType::Eighth)
1670        );
1671    }
1672
1673    #[test]
1674    fn duration_tracks_quarter_lengths() {
1675        assert_eq!(Duration::quarter().quarter_length(), 1.0);
1676        assert_eq!(Duration::half().quarter_length(), 2.0);
1677        assert_eq!(Duration::whole().quarter_length(), 4.0);
1678        assert_eq!(Duration::eighth().quarter_length(), 0.5);
1679    }
1680
1681    #[test]
1682    fn duration_rejects_invalid_values() {
1683        assert!(Duration::new(-1.0).is_err());
1684        assert!(Duration::new(FloatType::INFINITY).is_err());
1685    }
1686
1687    #[test]
1688    fn a_plain_note_value_is_not_read_as_a_tuplet() {
1689        // music21 tries the exact written value before any ratio, so a
1690        // quarter is a quarter and not two thirds of a dotted quarter.
1691        assert_eq!(Duration::quarter().tuplet(), None);
1692        assert_eq!(Duration::new(3.0).unwrap().tuplet(), None);
1693        assert_eq!(
1694            Duration::new(1.0 / 3.0).unwrap().tuplet(),
1695            Some(Tuplet::new(3, 2, DurationType::Eighth, 0))
1696        );
1697    }
1698
1699    #[test]
1700    fn appending_a_tuplet_shortens_the_length_by_its_ratio() {
1701        // music21's own `appendTuplet` docstring, exactly.
1702        let mut duration = Duration::new(1.0).unwrap();
1703        duration.append_tuplet(Tuplet::new(3, 2, DurationType::Quarter, 0));
1704        assert!((duration.quarter_length() - 2.0 / 3.0).abs() < 1e-12);
1705        assert_eq!(duration.quarter_length_no_tuplets(), 1.0);
1706
1707        duration.append_tuplet(Tuplet::new(5, 4, DurationType::Quarter, 0));
1708        assert!((duration.quarter_length() - 8.0 / 15.0).abs() < 1e-12);
1709        assert_eq!(
1710            duration.aggregate_tuplet_multiplier(),
1711            FractionType::new(8, 15)
1712        );
1713        // The written value is untouched by either tuplet.
1714        assert_eq!(
1715            duration.components(),
1716            vec![(DurationType::Quarter, 0)],
1717            "the written value stays a quarter inside both tuplets"
1718        );
1719    }
1720
1721    #[test]
1722    fn saying_a_length_is_in_no_tuplet_is_not_the_same_as_saying_nothing() {
1723        let inferred = Duration::new(1.0 / 3.0).unwrap();
1724        assert_eq!(inferred.tuplets().len(), 1);
1725
1726        let mut told = Duration::new(1.0 / 3.0).unwrap();
1727        told.set_tuplets(Vec::new());
1728        assert!(told.tuplets().is_empty());
1729        // Losing the triplet leaves the written eighth sounding in full.
1730        assert_eq!(told.quarter_length(), 0.5);
1731    }
1732
1733    #[test]
1734    fn setting_a_length_forgets_the_tuplets_it_was_told() {
1735        let mut duration = Duration::new(1.0).unwrap();
1736        duration.append_tuplet(Tuplet::new(3, 2, DurationType::Quarter, 0));
1737        duration.set_quarter_length(2.0).unwrap();
1738        assert!(duration.tuplets().is_empty());
1739        assert_eq!(duration.quarter_length(), 2.0);
1740    }
1741
1742    #[test]
1743    fn duration_supports_conversions_and_updates() {
1744        let mut duration = Duration::try_from(3 as IntegerType).unwrap();
1745        assert_eq!(duration.quarter_length(), 3.0);
1746
1747        duration.set_quarter_length(1.5).unwrap();
1748        assert_eq!(duration, Duration::try_from(1.5).unwrap());
1749        assert!(duration.set_quarter_length(FloatType::NAN).is_err());
1750    }
1751}