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#[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 DuplexMaxima,
22 Maxima,
24 Longa,
26 Breve,
28 Whole,
30 Half,
32 Quarter,
34 Eighth,
36 Sixteenth,
38 ThirtySecond,
40 SixtyFourth,
42 HundredTwentyEighth,
44 TwoHundredFiftySixth,
46 FiveHundredTwelfth,
48 TenTwentyFourth,
50 TwentyFortyEighth,
52 Zero,
54}
55
56impl DurationType {
57 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 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 pub fn ordinal(self) -> Option<usize> {
104 (self != Self::Zero).then(|| Self::ALL.iter().position(|kind| *kind == self))?
105 }
106
107 pub fn next_larger(self) -> Option<DurationType> {
110 let ordinal = self.ordinal()?;
111 Self::ALL.get(ordinal.checked_sub(1)?).copied()
112 }
113
114 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 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 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 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 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 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#[must_use]
232pub struct Duration {
233 quarter_length: FloatType,
234 #[cfg_attr(feature = "serde", serde(default))]
241 tuplets: Option<Vec<Tuplet>>,
242}
243
244const TUPLET_NUMERATORS: [u32; 5] = [3, 5, 7, 11, 13];
248
249const TUPLET_DOTS: [u32; 2] = [0, 1];
252
253const MAX_TIED_COMPONENTS: usize = 8;
256
257const DENOMINATOR_LIMIT: i128 = 65535;
260
261fn 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
270pub(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 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 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
330const TUPLET_TOLERANCE: FloatType = 1e-5;
337
338#[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 normal_type: DurationType,
358 normal_dots: u32,
360}
361
362impl Tuplet {
363 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 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 pub fn normal_duration_type(&self) -> DurationType {
386 self.normal_type
387 }
388
389 pub fn duration_actual(&self) -> (DurationType, u32) {
393 (self.duration_type, self.dots)
394 }
395
396 pub fn duration_normal(&self) -> (DurationType, u32) {
399 (self.normal_type, self.normal_dots)
400 }
401
402 pub fn tuplet_actual(&self) -> (u32, (DurationType, u32)) {
405 (self.actual, self.duration_actual())
406 }
407
408 pub fn tuplet_normal(&self) -> (u32, (DurationType, u32)) {
411 (self.normal, self.duration_normal())
412 }
413
414 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 pub fn set_ratio(&mut self, actual: u32, normal: u32) {
426 self.actual = actual;
427 self.normal = normal;
428 }
429
430 pub fn normal_dots(&self) -> u32 {
432 self.normal_dots
433 }
434
435 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 pub fn actual(&self) -> u32 {
443 self.actual
444 }
445
446 pub fn normal(&self) -> u32 {
448 self.normal
449 }
450
451 pub fn duration_type(&self) -> DurationType {
453 self.duration_type
454 }
455
456 pub fn dots(&self) -> u32 {
458 self.dots
459 }
460
461 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 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
486fn 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
500fn 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 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 pub fn quarter() -> Self {
532 Self::from_type(DurationType::Quarter)
533 }
534
535 pub fn half() -> Self {
537 Self::from_type(DurationType::Half)
538 }
539
540 pub fn whole() -> Self {
542 Self::from_type(DurationType::Whole)
543 }
544
545 pub fn eighth() -> Self {
547 Self::from_type(DurationType::Eighth)
548 }
549
550 pub fn from_type(duration_type: DurationType) -> Self {
552 Self {
553 quarter_length: duration_type.quarter_length(),
554 tuplets: None,
555 }
556 }
557
558 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 pub fn type_and_dots(&self) -> Option<(DurationType, u32)> {
570 exact_type_and_dots(self.quarter_length)
571 }
572
573 pub fn dots(&self) -> u32 {
576 self.type_and_dots().map_or(0, |(_, dots)| dots)
577 }
578
579 pub fn ordinal(&self) -> Option<usize> {
583 self.type_and_dots()?.0.ordinal()
584 }
585
586 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 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 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 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 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 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 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 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 pub fn components(&self) -> Vec<(DurationType, u32)> {
700 convert(self.written_quarter_length(), self.tuplets.is_none()).0
701 }
702
703 pub fn is_complex(&self) -> bool {
706 self.components().len() > 1
707 }
708
709 pub fn dot_groups(&self) -> Vec<u32> {
713 vec![self.dots()]
714 }
715
716 pub fn clear(&mut self) {
720 self.quarter_length = 0.0;
721 }
722
723 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 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 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 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 "Zero Duration (0 total QL)".to_string()
801 } else {
802 "Inexpressible".to_string()
803 };
804 }
805 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 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 pub fn duration_type(&self) -> Option<DurationType> {
845 self.type_and_dots().map(|(duration_type, _)| duration_type)
846 }
847
848 pub fn quarter_length(&self) -> FloatType {
850 self.quarter_length
851 }
852
853 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
862fn 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
871fn float_from_fraction(ratio: FractionType) -> FloatType {
874 ratio.to_f64().unwrap_or(0.0)
875}
876
877pub 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
910pub 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
954pub 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 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 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
999pub fn quarter_conversion(quarter_length: FloatType) -> (Vec<(DurationType, u32)>, Option<Tuplet>) {
1014 convert(quarter_length, true)
1015}
1016
1017fn convert(
1020 written: FloatType,
1021 look_for_tuplet: bool,
1022) -> (Vec<(DurationType, u32)>, Option<Tuplet>) {
1023 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 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
1063const MAX_MIXED_NUMERAL_DENOMINATOR: u32 = 1024;
1068
1069const MIXED_NUMERAL_TOLERANCE: FloatType = 1e-6;
1072
1073fn 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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}