1pub mod sequence;
17
18pub use sequence::{MeterTerminal, OffsetAlign};
19
20use crate::defaults::{FloatType, UnsignedIntegerType};
21use crate::duration::Duration;
22use crate::duration::DurationType;
23use crate::error::{Error, Result};
24use crate::notation::{BeamType, Beams};
25
26const BEAT_COUNT_NAMES: [&str; 9] = [
30 "Empty",
31 "Single",
32 "Duple",
33 "Triple",
34 "Quadruple",
35 "Quintuple",
36 "Sextuple",
37 "Septuple",
38 "Octuple",
39];
40
41#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub enum BeatDivision {
47 Other,
50 Simple,
52 Compound,
54}
55
56impl BeatDivision {
57 pub fn music21_name(&self) -> &'static str {
59 match self {
60 Self::Other => "Other",
61 Self::Simple => "Simple",
62 Self::Compound => "Compound",
63 }
64 }
65
66 pub fn count(&self) -> UnsignedIntegerType {
71 match self {
72 Self::Other => 1,
73 Self::Simple => 2,
74 Self::Compound => 3,
75 }
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94#[must_use]
95pub struct TimeSignature {
96 numerator: UnsignedIntegerType,
97 denominator: UnsignedIntegerType,
98 favor_compound: bool,
99 display_sequence: MeterTerminal,
100 beat_sequence: MeterTerminal,
101 beam_sequence: MeterTerminal,
102 accent_sequence: MeterTerminal,
103}
104
105impl Default for TimeSignature {
106 fn default() -> Self {
108 Self::common()
109 }
110}
111
112impl TimeSignature {
113 pub fn new(numerator: UnsignedIntegerType, denominator: UnsignedIntegerType) -> Result<Self> {
118 if numerator == 0 {
119 return Err(Error::Meter(
120 "time signature numerator must be non-zero".to_string(),
121 ));
122 }
123 if denominator == 0 {
124 return Err(Error::Meter(
125 "time signature denominator must be non-zero".to_string(),
126 ));
127 }
128 let mut signature = Self {
129 numerator,
130 denominator,
131 favor_compound: favor_compound(numerator, denominator, None),
132 display_sequence: whole_bar(numerator, denominator)?,
133 beat_sequence: whole_bar(numerator, denominator)?,
134 beam_sequence: whole_bar(numerator, denominator)?,
135 accent_sequence: whole_bar(numerator, denominator)?,
136 };
137 signature.set_default_partitions()?;
138 Ok(signature)
139 }
140
141 #[must_use]
146 pub fn display_sequence(&self) -> &MeterTerminal {
147 &self.display_sequence
148 }
149
150 #[must_use]
153 pub fn beat_sequence(&self) -> &MeterTerminal {
154 &self.beat_sequence
155 }
156
157 #[must_use]
160 pub fn beam_sequence(&self) -> &MeterTerminal {
161 &self.beam_sequence
162 }
163
164 #[must_use]
168 pub fn accent_sequence(&self) -> &MeterTerminal {
169 &self.accent_sequence
170 }
171
172 #[must_use]
175 pub fn favors_compound(&self) -> bool {
176 self.favor_compound
177 }
178
179 pub fn beat_sequence_mut(&mut self) -> &mut MeterTerminal {
182 &mut self.beat_sequence
183 }
184
185 pub fn beam_sequence_mut(&mut self) -> &mut MeterTerminal {
187 &mut self.beam_sequence
188 }
189
190 pub fn accent_sequence_mut(&mut self) -> &mut MeterTerminal {
192 &mut self.accent_sequence
193 }
194
195 pub fn display_sequence_mut(&mut self) -> &mut MeterTerminal {
197 &mut self.display_sequence
198 }
199
200 pub fn set_display(&mut self, value: &str) -> Result<()> {
208 let parts = Self::parts(value)?;
209 let denominator = parts
210 .iter()
211 .map(|(_, part)| *part)
212 .max()
213 .unwrap_or(self.denominator);
214 let numerator = parts
215 .iter()
216 .map(|(count, part)| count * (denominator / part))
217 .sum();
218 let mut display = whole_bar(numerator, denominator)?;
219 if (display.quarter_length() - self.bar_quarter_length()).abs() > OFFSET_TOLERANCE {
220 return Err(Error::Meter(format!(
221 "cannot write a {} bar as {value:?}: that is {} quarter lengths, not {}",
222 self.ratio_string(),
223 display.quarter_length(),
224 self.bar_quarter_length()
225 )));
226 }
227 if parts.len() > 1 {
228 let written: Vec<String> = parts
229 .iter()
230 .map(|(count, part)| format!("{count}/{part}"))
231 .collect();
232 let borrowed: Vec<&str> = written.iter().map(String::as_str).collect();
233 display.partition_by_parts(&borrowed)?;
234 }
235 self.display_sequence = display;
236 Ok(())
237 }
238
239 pub fn divide_beats(&mut self, count: UnsignedIntegerType) -> Result<()> {
246 if count == 0 {
247 return Err(Error::Meter(
248 "a bar cannot be divided into no parts".to_string(),
249 ));
250 }
251 let mut divided = whole_bar(self.numerator, self.denominator)?;
252 divided.partition_by_count(count as usize, false)?;
253 self.beat_sequence = divided.clone();
256 self.accent_sequence = divided.clone();
257 self.beam_sequence = divided;
258 Ok(())
259 }
260
261 pub fn set_beat_count(&mut self, count: UnsignedIntegerType) -> Result<()> {
268 if count == 0 {
269 return Err(Error::Meter(
270 "a bar cannot be counted in no beats".to_string(),
271 ));
272 }
273 let mut beats = MeterTerminal::new(self.numerator, self.denominator)?;
274 beats.partition_by_count(count as usize, false)?;
275 if beats.len() > 1 {
276 let _ = beats.subdivide_partitions_equal(None);
277 }
278 self.beat_sequence = beats;
279 Ok(())
280 }
281
282 fn set_default_partitions(&mut self) -> Result<()> {
286 self.set_default_beam_partitions()?;
287 self.set_default_beat_partitions()?;
288 self.set_default_accent_weights();
289 Ok(())
290 }
291
292 fn set_default_beam_partitions(&mut self) -> Result<()> {
300 let numerator = self.numerator;
301 let denominator = self.denominator;
302 if (denominator == 8 && matches!(numerator, 1..=3))
303 || (denominator == 16 && matches!(numerator, 1..=5))
304 || (denominator == 32 && matches!(numerator, 1..=11))
305 {
306 return Ok(());
307 }
308 match numerator {
309 2..=4 => {
310 self.beam_sequence
311 .partition_by_count(numerator as usize, true)?;
312 if denominator == 4 {
313 for part in self.beam_sequence.parts_mut() {
314 part.subdivide(2)?;
315 }
316 }
317 }
318 5 => {
319 self.beam_sequence.partition_by_list(&[2, 3])?;
320 if denominator == 4 {
321 for (part, count) in self.beam_sequence.parts_mut().iter_mut().zip([2, 3]) {
322 part.subdivide(count)?;
323 }
324 }
325 }
326 7 => self.beam_sequence.partition_by_count(3, true)?,
327 6 | 9 | 12 | 15 | 18 | 21 => {
328 let threes = vec![3; (numerator / 3) as usize];
329 self.beam_sequence.partition_by_list(&threes)?;
330 }
331 _ => {}
332 }
333 Ok(())
334 }
335
336 fn set_default_beat_partitions(&mut self) -> Result<()> {
343 let numerator = self.numerator;
344 let compound = self.favor_compound;
345 if self.display_sequence.len() == 1 {
346 match numerator {
347 2 => self.beat_sequence.partition_by_count(2, true)?,
348 6 if compound => self.beat_sequence.partition_by_count(2, true)?,
349 3 if compound => self.beat_sequence.partition_by_count(1, true)?,
350 3 => self.beat_sequence.partition_by_list(&[1, 1, 1])?,
351 9 if compound => self.beat_sequence.partition_by_list(&[3, 3, 3])?,
352 4 => self.beat_sequence.partition_by_count(4, true)?,
353 12 if compound => self.beat_sequence.partition_by_count(4, true)?,
354 other if other >= 15 && other.is_multiple_of(3) && compound => {
355 let threes = vec![3; (other / 3) as usize];
356 self.beat_sequence.partition_by_list(&threes)?;
357 }
358 other => self
359 .beat_sequence
360 .partition_by_count(other as usize, true)?,
361 }
362 } else {
363 let written: Vec<String> = self
364 .display_sequence
365 .parts()
366 .iter()
367 .map(|part| format!("{}/{}", part.numerator(), part.denominator()))
368 .collect();
369 let borrowed: Vec<&str> = written.iter().map(String::as_str).collect();
370 self.beat_sequence.partition_by_parts(&borrowed)?;
371 }
372 if self.beat_sequence.len() > 1 {
373 match self.beat_sequence.subdivide_partitions_equal(None) {
376 Ok(()) => {}
377 Err(error) if self.denominator >= 128 => {
378 let _ = error;
379 }
380 Err(error) => return Err(error),
381 }
382 }
383 Ok(())
384 }
385
386 fn set_default_accent_weights(&mut self) {
390 let weights = self.default_accent_weights();
391 let ones = vec![1; weights.len()];
392 if self.accent_sequence.partition_by_list(&ones).is_err() {
393 return;
394 }
395 for (part, weight) in self.accent_sequence.parts_mut().iter_mut().zip(weights) {
396 part.set_weight(weight);
397 }
398 }
399
400 pub fn from_ratio_string(ratio: &str) -> Result<Self> {
409 let parts = Self::parts(ratio)?;
410 let word = division_word(ratio);
411 let denominator = parts[0].1;
412 if parts.iter().all(|(_, part)| *part == denominator) {
413 let numerator = parts.iter().map(|(count, _)| count).sum();
414 let mut signature = Self::new(numerator, denominator)?;
415 signature.write_as(&parts, word)?;
416 return Ok(signature);
417 }
418 let denominator = parts
421 .iter()
422 .map(|(_, part)| *part)
423 .max()
424 .unwrap_or(denominator);
425 let numerator = parts
426 .iter()
427 .map(|(count, part)| count * (denominator / part))
428 .sum();
429 let mut signature = Self::new(numerator, denominator)?;
430 signature.write_as(&parts, word)?;
431 Ok(signature)
432 }
433
434 fn write_as(
438 &mut self,
439 parts: &[(UnsignedIntegerType, UnsignedIntegerType)],
440 word: Option<&str>,
441 ) -> Result<()> {
442 self.favor_compound = favor_compound(self.numerator, self.denominator, word);
443 if parts.len() > 1 {
444 let written: Vec<String> = parts
445 .iter()
446 .map(|(count, part)| format!("{count}/{part}"))
447 .collect();
448 let borrowed: Vec<&str> = written.iter().map(String::as_str).collect();
449 self.display_sequence.partition_by_parts(&borrowed)?;
450 }
451 self.beat_sequence = whole_bar(self.numerator, self.denominator)?;
452 self.beam_sequence = whole_bar(self.numerator, self.denominator)?;
453 self.accent_sequence = whole_bar(self.numerator, self.denominator)?;
454 self.set_default_partitions()
455 }
456
457 pub fn parts(ratio: &str) -> Result<Vec<(UnsignedIntegerType, UnsignedIntegerType)>> {
466 let written = ratio.trim();
467 if written.is_empty() {
468 return Err(Error::Meter("a time signature says nothing".to_string()));
469 }
470 let mut pre: Vec<(UnsignedIntegerType, Option<UnsignedIntegerType>)> = Vec::new();
471 for part in written.split('+') {
472 let part = part.trim();
473 match part.split_once('/') {
474 Some((numerator, denominator)) => pre.push((
475 Self::count(numerator, "numerator", written)?,
476 Some(Self::count(denominator, "denominator", written)?),
477 )),
478 None => pre.push((Self::count(part, "numerator", written)?, None)),
479 }
480 }
481 let mut out = Vec::with_capacity(pre.len());
482 for (index, (numerator, denominator)) in pre.iter().enumerate() {
483 let denominator = match denominator {
484 Some(denominator) => *denominator,
485 None => pre[index + 1..]
486 .iter()
487 .find_map(|(_, later)| *later)
488 .ok_or_else(|| {
489 Error::Meter(format!(
490 "cannot match a denominator to every numerator in {written:?}"
491 ))
492 })?,
493 };
494 out.push((*numerator, denominator));
495 }
496 Ok(out)
497 }
498
499 fn count(part: &str, label: &str, ratio: &str) -> Result<UnsignedIntegerType> {
502 let digits = part
503 .trim()
504 .rsplit(|character: char| character.is_whitespace())
505 .next()
506 .unwrap_or("")
507 .trim();
508 digits
509 .parse::<UnsignedIntegerType>()
510 .map_err(|_| Error::Meter(format!("cannot read a {label} from {part:?} in {ratio:?}")))
511 }
512
513 pub fn common() -> Self {
515 Self::new(4, 4).expect("4/4 is a meter")
516 }
517
518 pub fn cut() -> Self {
520 Self::new(2, 2).expect("2/2 is a meter")
521 }
522
523 pub fn numerator(&self) -> UnsignedIntegerType {
525 self.numerator
526 }
527
528 pub fn denominator(&self) -> UnsignedIntegerType {
530 self.denominator
531 }
532
533 pub fn ratio_string(&self) -> String {
535 if self.display_sequence.len() > 1 {
540 return self.display_sequence.partition_display();
541 }
542 format!("{}/{}", self.numerator, self.denominator)
543 }
544
545 pub fn bar_quarter_length(&self) -> FloatType {
547 FloatType::from(self.numerator) * 4.0 / FloatType::from(self.denominator)
548 }
549
550 pub fn bar_duration(&self) -> Duration {
552 Duration::new(self.bar_quarter_length())
553 .expect("a non-zero numerator and denominator give a positive finite bar length")
554 }
555
556 pub fn beat_count(&self) -> UnsignedIntegerType {
562 self.beat_sequence.len() as UnsignedIntegerType
563 }
564
565 pub fn beat_count_name(&self) -> String {
569 let count = self.beat_count();
570 BEAT_COUNT_NAMES
571 .get(count as usize)
572 .map_or_else(|| format!("{count}-uple"), |name| (*name).to_string())
573 }
574
575 pub fn beat_quarter_length(&self) -> Result<FloatType> {
582 let spans = self.beat_spans();
583 let first = spans[0].1 - spans[0].0;
584 if spans
585 .iter()
586 .any(|(start, end)| ((end - start) - first).abs() > OFFSET_TOLERANCE)
587 {
588 let lengths: Vec<FloatType> = spans.iter().map(|(s, e)| e - s).collect();
589 return Err(Error::Meter(format!("non-uniform beat unit: {lengths:?}")));
590 }
591 Ok(first)
592 }
593
594 pub fn beat_duration(&self) -> Result<Duration> {
596 Duration::new(self.beat_quarter_length()?)
597 }
598
599 pub fn beat_division(&self) -> BeatDivision {
601 let parts = self.beat_sequence.parts();
602 if parts.len() <= 1 {
603 return BeatDivision::Other;
604 }
605 let mut counts = parts.iter().map(MeterTerminal::len);
606 let Some(first) = counts.next() else {
607 return BeatDivision::Other;
608 };
609 if first == 0 || !counts.all(|count| count == first) {
610 return BeatDivision::Other;
613 }
614 match first {
615 2 => BeatDivision::Simple,
616 3 => BeatDivision::Compound,
617 _ => BeatDivision::Other,
618 }
619 }
620
621 pub fn beat_division_count(&self) -> UnsignedIntegerType {
623 self.beat_division().count()
624 }
625
626 pub fn is_compound(&self) -> bool {
628 self.beat_division() == BeatDivision::Compound
629 }
630
631 pub fn classification(&self) -> String {
633 format!(
634 "{} {}",
635 self.beat_division().music21_name(),
636 self.beat_count_name()
637 )
638 }
639
640 pub fn beat_division_count_name(&self) -> &'static str {
643 self.beat_division().music21_name()
644 }
645
646 pub fn ratio_equal(&self, other: &TimeSignature) -> bool {
649 self.numerator == other.numerator && self.denominator == other.denominator
650 }
651
652 pub fn beat_length_to_quarter_length_ratio(&self) -> FloatType {
655 4.0 / FloatType::from(self.denominator)
656 }
657
658 pub fn quarter_length_to_beat_length_ratio(&self) -> FloatType {
661 FloatType::from(self.denominator) / 4.0
662 }
663
664 pub fn beat_division_quarter_lengths(&self) -> Result<Vec<FloatType>> {
668 let count = self.beat_division_count().max(1);
669 let beat = self.beat_quarter_length()?;
670 Ok(vec![beat / FloatType::from(count); count as usize])
671 }
672
673 pub fn beat_division_durations(&self) -> Result<Vec<Duration>> {
676 self.beat_division_quarter_lengths()?
677 .into_iter()
678 .map(Duration::new)
679 .collect()
680 }
681
682 pub fn beat_sub_division_durations(&self) -> Result<Vec<Duration>> {
685 let mut out = Vec::new();
686 for quarter_length in self.beat_division_quarter_lengths()? {
687 let half = Duration::new(quarter_length / 2.0)?;
688 out.push(half.clone());
689 out.push(half);
690 }
691 Ok(out)
692 }
693
694 pub fn offset_from_beat(&self, beat: FloatType) -> Result<FloatType> {
698 let whole = beat.floor();
699 if !beat.is_finite() || whole < 1.0 || whole > FloatType::from(self.beat_count()) {
700 return Err(Error::Meter(format!(
701 "requested beat value ({beat}) not found in the {} beats of {}",
702 self.beat_count(),
703 self.ratio_string()
704 )));
705 }
706 let (start, end) = self.beat_spans()[whole as usize - 1];
707 let fraction = snapped_fraction(beat - whole);
708 Ok(start + fraction * (end - start))
709 }
710
711 pub fn beat_progress(&self, offset: FloatType) -> Result<(UnsignedIntegerType, FloatType)> {
714 let index = self.beat_index(offset)?;
715 let (start, _) = self.beat_spans()[index];
716 Ok((index as UnsignedIntegerType + 1, offset - start))
717 }
718
719 pub fn beat_proportion(&self, offset: FloatType) -> Result<FloatType> {
723 let (beat, progress) = self.beat_progress(offset)?;
724 let (start, end) = self.beat_spans()[beat as usize - 1];
725 Ok(FloatType::from(beat) + progress / (end - start))
726 }
727
728 pub fn beat_proportion_string(&self, offset: FloatType) -> Result<String> {
733 let (beat, progress) = self.beat_progress(offset)?;
734 let (start, end) = self.beat_spans()[beat as usize - 1];
735 let proportion = progress / (end - start);
736 if proportion == 0.0 {
737 return Ok(beat.to_string());
738 }
739 let (numerator, denominator) = closest_fraction(proportion, 16);
740 Ok(format!("{beat} {numerator}/{denominator}"))
741 }
742
743 fn beat_spans(&self) -> Vec<(FloatType, FloatType)> {
747 let mut spans = Vec::new();
748 let mut position = 0.0;
749 for part in self.beat_sequence.parts() {
750 let end = position + part.quarter_length();
751 spans.push((position, end));
752 position = end;
753 }
754 if spans.is_empty() {
755 spans.push((0.0, self.bar_quarter_length()));
756 }
757 spans
758 }
759
760 pub fn beat_duration_at(&self, offset: FloatType) -> Result<Duration> {
766 let index = self.beat_index(offset)?;
767 let (start, end) = self.beat_spans()[index];
768 Duration::new(end - start)
769 }
770
771 fn beat_index(&self, offset: FloatType) -> Result<usize> {
773 if !offset.is_finite() || offset < 0.0 || offset >= self.bar_quarter_length() {
774 return Err(Error::Meter(format!(
775 "offset {offset} is outside a {} bar of {} quarter lengths",
776 self.ratio_string(),
777 self.bar_quarter_length()
778 )));
779 }
780 let spans = self.beat_spans();
781 for (index, (start, end)) in spans.iter().enumerate() {
782 let _ = start;
783 if offset < end - OFFSET_TOLERANCE {
784 return Ok(index);
785 }
786 }
787 Ok(spans.len() - 1)
788 }
789
790 pub fn beat_offsets(&self) -> Vec<FloatType> {
792 self.beat_spans()
793 .into_iter()
794 .map(|(start, _)| start)
795 .collect()
796 }
797
798 pub fn beat_at_offset(&self, offset: FloatType) -> Result<UnsignedIntegerType> {
803 if !offset.is_finite() || offset < 0.0 || offset >= self.bar_quarter_length() {
804 return Err(Error::Meter(format!(
805 "offset {offset} is outside a {} bar of {} quarter lengths",
806 self.ratio_string(),
807 self.bar_quarter_length()
808 )));
809 }
810 Ok(self.beat_index(offset)? as UnsignedIntegerType + 1)
811 }
812}
813
814fn closest_fraction(value: FloatType, max_denominator: u32) -> (u32, u32) {
818 let mut best = (value.round() as u32, 1);
819 let mut best_error = (value - value.round()).abs();
820 for denominator in 2..=max_denominator {
821 let numerator = (value * FloatType::from(denominator)).round();
822 let error = (value - numerator / FloatType::from(denominator)).abs();
823 if error < best_error {
824 best = (numerator as u32, denominator);
825 best_error = error;
826 }
827 }
828 best
829}
830
831impl std::fmt::Display for TimeSignature {
832 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
833 f.write_str(&self.ratio_string())
834 }
835}
836
837const OFFSET_TOLERANCE: FloatType = 1e-9;
840
841const SHORTEST_PARTITION: FloatType = 4.0 / 128.0;
843
844const LONGEST_PARTITION: FloatType = 4.0;
846
847impl TimeSignature {
848 fn accent_hierarchy(
860 &self,
861 ) -> (
862 UnsignedIntegerType,
863 UnsignedIntegerType,
864 UnsignedIntegerType,
865 ) {
866 let beats = self.beat_count();
867 let first = if beats > 1 { beats } else { self.numerator };
868 let top = match first {
869 1 | 2 | 4 | 8 | 16 | 32 => 2,
870 3 => 3,
871 other => other,
872 };
873 let (span, unit) = split_span(self.numerator, self.denominator, top);
874 let second = default_division(span, unit);
875 let (span, unit) = split_span(span, unit, second);
876 let third = default_division(span, unit);
877 let partition = self.bar_quarter_length() / FloatType::from(top * second * third);
882 if !(SHORTEST_PARTITION..=LONGEST_PARTITION).contains(&partition) {
883 return (1, 1, 1);
884 }
885 (top, second, third)
886 }
887
888 pub fn accent_partition_quarter_length(&self) -> FloatType {
891 let parts = self.accent_sequence.parts();
892 if parts.len() > 1 {
893 return parts[0].quarter_length();
894 }
895 let (top, second, third) = self.accent_hierarchy();
896 self.bar_quarter_length() / FloatType::from(top * second * third)
897 }
898
899 pub fn accent_weights(&self) -> Vec<FloatType> {
904 let carried: Vec<FloatType> = self
905 .accent_sequence
906 .parts()
907 .iter()
908 .map(MeterTerminal::weight)
909 .collect();
910 if carried.len() > 1 {
911 return carried;
912 }
913 self.default_accent_weights()
914 }
915
916 fn default_accent_weights(&self) -> Vec<FloatType> {
921 let (top, second, third) = self.accent_hierarchy();
922 let count = top * second * third;
923 (0..count)
924 .map(|index| {
925 let depth = 1
926 + UnsignedIntegerType::from(index.is_multiple_of(third))
927 + UnsignedIntegerType::from(index.is_multiple_of(second * third))
928 + UnsignedIntegerType::from(index == 0);
929 FloatType::from(2u32.pow(depth - 1)) / 8.0
930 })
931 .collect()
932 }
933
934 pub fn accent(&self, offset: FloatType) -> bool {
938 let partition = self.accent_partition_quarter_length();
939 let index = (offset / partition).round();
940 index >= 0.0
941 && index < FloatType::from(self.accent_weights().len() as u32)
942 && (offset - index * partition).abs() < OFFSET_TOLERANCE
943 }
944
945 pub fn accent_weight(&self, offset: FloatType) -> Result<FloatType> {
949 self.accent_weight_with(offset, false, false)
950 }
951
952 pub fn accent_weight_at_level(
955 &self,
956 offset: FloatType,
957 level: usize,
958 force_position_match: bool,
959 permit_meter_modulus: bool,
960 ) -> Result<FloatType> {
961 let bar = self.bar_quarter_length();
962 let offset = if permit_meter_modulus {
963 offset.rem_euclid(bar)
964 } else {
965 offset
966 };
967 if offset.is_nan() || offset < 0.0 || offset >= bar {
968 return Err(Error::Meter(format!(
969 "cannot access from qLenPos {} where total duration is {}",
970 offset_repr(offset),
971 offset_repr(bar)
972 )));
973 }
974 let terminals = self.accent_sequence.level_list(level, true);
975 if terminals.len() <= 1 {
976 return self.accent_weight_with(offset, force_position_match, permit_meter_modulus);
977 }
978 let spans = self.accent_sequence.level_span(level);
979 let smallest = terminals
980 .iter()
981 .map(MeterTerminal::weight)
982 .fold(FloatType::INFINITY, FloatType::min);
983 for (index, (start, end)) in spans.iter().enumerate() {
984 if offset < end - OFFSET_TOLERANCE {
985 if force_position_match && (offset - start).abs() >= OFFSET_TOLERANCE {
986 return Ok(smallest * 0.5);
987 }
988 return Ok(terminals[index].weight());
989 }
990 }
991 Ok(terminals[terminals.len() - 1].weight())
992 }
993
994 pub fn set_accent_weight(&mut self, weights: &[FloatType], level: usize) -> Result<()> {
997 self.accent_sequence.set_weights_at_level(level, weights)
998 }
999
1000 pub fn accent_weight_with(
1005 &self,
1006 offset: FloatType,
1007 force_position_match: bool,
1008 permit_meter_modulus: bool,
1009 ) -> Result<FloatType> {
1010 let bar = self.bar_quarter_length();
1011 let offset = if permit_meter_modulus {
1012 offset.rem_euclid(bar)
1013 } else {
1014 offset
1015 };
1016 if offset.is_nan() || offset < 0.0 || offset >= bar {
1017 return Err(Error::Meter(format!(
1018 "cannot access from qLenPos {} where total duration is {}",
1019 offset_repr(offset),
1020 offset_repr(bar)
1021 )));
1022 }
1023 let weights = self.accent_weights();
1024 let partition = self.accent_partition_quarter_length();
1025 let index = ((offset + OFFSET_TOLERANCE) / partition).floor() as usize;
1026 let index = index.min(weights.len() - 1);
1027 if force_position_match
1028 && (offset - index as FloatType * partition).abs() >= OFFSET_TOLERANCE
1029 {
1030 let smallest = weights
1031 .iter()
1032 .copied()
1033 .fold(FloatType::INFINITY, FloatType::min);
1034 return Ok(smallest * 0.5);
1035 }
1036 Ok(weights[index])
1037 }
1038
1039 pub fn average_beat_strength(&self, stream: &crate::Stream, notes_only: bool) -> FloatType {
1045 let bar = self.bar_quarter_length();
1046 let offsets: Vec<FloatType> = if notes_only {
1047 stream
1048 .notes()
1049 .into_iter()
1050 .map(|(offset, _)| offset)
1051 .collect()
1052 } else {
1053 stream
1054 .recurse()
1055 .into_iter()
1056 .filter(|(_, element)| element.as_stream().is_none())
1057 .map(|(offset, _)| offset)
1058 .collect()
1059 };
1060 if offsets.is_empty() {
1061 return 0.0;
1062 }
1063 let total: FloatType = offsets
1064 .iter()
1065 .map(|offset| {
1066 self.accent_weight_with(offset.rem_euclid(bar), true, false)
1067 .unwrap_or(0.0)
1068 })
1069 .sum();
1070 total / offsets.len() as FloatType
1071 }
1072
1073 pub fn beat_depth(&self, offset: FloatType) -> Result<u8> {
1079 let bar = self.bar_quarter_length();
1080 if offset.is_nan() || offset < 0.0 || offset >= bar {
1081 return Err(Error::Meter(format!(
1082 "cannot access from qLenPos {}",
1083 offset_repr(offset)
1084 )));
1085 }
1086 let depth = self
1087 .beat_sequence
1088 .offset_to_depth(offset, crate::meter::OffsetAlign::Quantize)?;
1089 Ok(depth as u8)
1090 }
1091}
1092
1093#[must_use]
1100pub fn snapped_fraction(value: FloatType) -> FloatType {
1101 const GRAIN: FloatType = 1e-2;
1102 let thirds = [1.0 / 3.0, 2.0 / 3.0, 1.0 / 6.0, 5.0 / 6.0];
1103 for candidate in thirds {
1104 if (value - candidate).abs() <= GRAIN {
1105 return candidate;
1106 }
1107 }
1108 value
1109}
1110
1111#[derive(Clone, Copy, Debug, PartialEq)]
1118pub struct BeamedNote {
1119 pub offset: FloatType,
1121 pub quarter_length: FloatType,
1123 pub duration_type: DurationType,
1125 pub sounds: bool,
1127}
1128
1129impl TimeSignature {
1130 pub fn beams_for(
1141 &self,
1142 notes: &[BeamedNote],
1143 measure_start_offset: FloatType,
1144 measure_padding: Option<FloatType>,
1145 ) -> Result<Vec<Option<Beams>>> {
1146 if notes.len() <= 1 {
1147 return Ok(notes.iter().map(|_| None).collect());
1148 }
1149
1150 let mut beamed: Vec<Option<Beams>> = Vec::with_capacity(notes.len());
1153 for note in notes {
1154 let levels = Beams::levels_for(note.duration_type).filter(|_| note.sounds);
1155 beamed.push(match levels {
1156 Some(levels) => {
1157 let mut made = Beams::new();
1158 made.fill_levels(levels, None)?;
1159 Some(made)
1160 }
1161 None => None,
1162 });
1163 }
1164 crate::notation::remove_sandwiched_unbeamables(&mut beamed);
1165
1166 for depth in 0..Beams::LEVELS {
1167 for index in 0..notes.len() {
1168 self.fix_one_beam(
1169 &mut beamed,
1170 notes,
1171 index,
1172 depth,
1173 measure_start_offset,
1174 measure_padding,
1175 )?;
1176 }
1177 }
1178
1179 crate::notation::sanitize_partial_beams(&mut beamed);
1180 crate::notation::merge_connecting_partial_beams(&mut beamed);
1181 Ok(beamed)
1182 }
1183
1184 fn fix_one_beam(
1187 &self,
1188 beamed: &mut [Option<Beams>],
1189 notes: &[BeamedNote],
1190 index: usize,
1191 depth: usize,
1192 measure_start_offset: FloatType,
1193 measure_padding: Option<FloatType>,
1194 ) -> Result<()> {
1195 let beam_number = depth as u32 + 1;
1196 let carries = |beams: Option<&Beams>| {
1197 beams.is_some_and(|beams| {
1198 beams
1199 .numbers()
1200 .into_iter()
1201 .flatten()
1202 .any(|n| n == beam_number)
1203 })
1204 };
1205 if !carries(beamed[index].as_ref()) {
1206 return Ok(());
1207 }
1208
1209 let note = notes[index];
1210 let start = note.offset + measure_start_offset;
1211 let end = start + note.quarter_length;
1212 let start_next = end;
1213
1214 let is_first = index == 0;
1215 let is_last = index + 1 == notes.len();
1216
1217 let previous_is_none = is_first || beamed[index - 1].is_none();
1220 let next_is_none = is_last || beamed[index + 1].is_none();
1221 let previous_carries = !is_first && carries(beamed[index - 1].as_ref());
1222 let next_carries = !is_last && carries(beamed[index + 1].as_ref());
1223 let previous_broke = !is_first
1224 && beamed[index - 1].as_ref().is_some_and(|beams| {
1225 beams.by_number(beam_number).is_some_and(|beam| {
1226 matches!(beam.beam_type(), Some(BeamType::Stop))
1227 || (matches!(beam.beam_type(), Some(BeamType::PartialBeam))
1228 && beam.direction() == Some(crate::notation::BeamDirection::Left))
1229 })
1230 });
1231
1232 let archetype = self.beam_sequence.level(depth, true)?;
1233 let (span_start, span_end) = archetype.offset_to_span(start, false)?;
1234 let span_next_start = if next_is_none {
1235 0.0
1236 } else {
1237 archetype.offset_to_span(start_next, false)?.0
1238 };
1239
1240 let same = |a: FloatType, b: FloatType| (a - b).abs() < OFFSET_TOLERANCE;
1241
1242 if same(end, span_end)
1244 && (same(start, span_start) || (previous_is_none && beam_number == 1))
1245 {
1246 beamed[index] = None;
1247 return Ok(());
1248 }
1249
1250 let ends_the_measure = is_last && measure_padding.is_none_or(|padding| padding == 0.0);
1251
1252 let (beam_type, direction) = if is_first && measure_start_offset == 0.0 {
1253 if next_is_none || !next_carries {
1254 (
1255 BeamType::PartialBeam,
1256 Some(crate::notation::BeamDirection::Right),
1257 )
1258 } else {
1259 (BeamType::Start, None)
1260 }
1261 } else if ends_the_measure {
1262 if previous_is_none || !previous_carries {
1263 (
1264 BeamType::PartialBeam,
1265 Some(crate::notation::BeamDirection::Left),
1266 )
1267 } else {
1268 (BeamType::Stop, None)
1269 }
1270 } else if previous_is_none || !previous_carries {
1271 if beam_number == 1 && next_is_none {
1273 beamed[index] = None;
1274 return Ok(());
1275 } else if (next_is_none && beam_number > 1) || start_next >= span_end - OFFSET_TOLERANCE
1276 {
1277 (
1281 BeamType::PartialBeam,
1282 Some(crate::notation::BeamDirection::Left),
1283 )
1284 } else if next_is_none || !next_carries {
1285 (
1286 BeamType::PartialBeam,
1287 Some(crate::notation::BeamDirection::Right),
1288 )
1289 } else {
1290 (BeamType::Start, None)
1291 }
1292 } else if previous_broke {
1293 if next_is_none {
1294 (
1295 BeamType::PartialBeam,
1296 Some(crate::notation::BeamDirection::Left),
1297 )
1298 } else if next_carries {
1299 (BeamType::Start, None)
1300 } else {
1301 (
1302 BeamType::PartialBeam,
1303 Some(crate::notation::BeamDirection::Right),
1304 )
1305 }
1306 } else if next_is_none || !next_carries {
1307 (BeamType::Stop, None)
1308 } else if start_next < span_end - OFFSET_TOLERANCE {
1309 (BeamType::Continue, None)
1310 } else if start_next >= span_next_start - OFFSET_TOLERANCE {
1311 (BeamType::Stop, None)
1312 } else {
1313 return Err(Error::Meter("cannot match beamType".to_string()));
1314 };
1315
1316 if let Some(beams) = beamed[index].as_mut() {
1317 beams.set_by_number(beam_number, beam_type, direction)?;
1318 }
1319 Ok(())
1320 }
1321}
1322
1323fn whole_bar(
1327 numerator: UnsignedIntegerType,
1328 denominator: UnsignedIntegerType,
1329) -> Result<MeterTerminal> {
1330 let mut bar = MeterTerminal::new(numerator, denominator)?;
1331 let whole = format!("{numerator}/{denominator}");
1332 bar.partition_by_parts(&[whole.as_str()])?;
1333 Ok(bar)
1334}
1335
1336fn division_word(ratio: &str) -> Option<&str> {
1339 let first = ratio.trim().split('+').next()?.trim();
1340 let word = first.split_whitespace().next()?;
1341 matches!(word, "slow" | "fast").then_some(word)
1342}
1343
1344fn favor_compound(
1350 numerator: UnsignedIntegerType,
1351 denominator: UnsignedIntegerType,
1352 word: Option<&str>,
1353) -> bool {
1354 match word {
1355 Some("slow") => false,
1356 Some("fast") => true,
1357 _ => !(numerator == 3 && denominator < 8),
1358 }
1359}
1360
1361pub fn best_time_signature(measure: &crate::Stream) -> Result<TimeSignature> {
1373 let elements = measure.recurse();
1374 let sum = elements
1375 .iter()
1376 .filter(|(_, element)| element.as_stream().is_none())
1377 .map(|(offset, element)| offset + element.quarter_length())
1378 .fold(0.0, FloatType::max);
1379
1380 let mut min_dur = 4.0;
1382 let mut min_dots = 0;
1383 for (_, element) in &elements {
1384 let sounds =
1385 element.is_note_or_chord() || matches!(element, crate::stream::StreamElement::Rest(_));
1386 let quarter_length = element.quarter_length();
1387 if sounds && quarter_length != 0.0 && quarter_length < min_dur && is_binary(quarter_length)
1388 {
1389 min_dur = quarter_length;
1390 min_dots = element.duration().map_or(0, Duration::dots);
1391 }
1392 }
1393
1394 let (numerator, denominator) = if is_binary(sum) {
1395 binary_signature(sum, min_dur, min_dots)?
1396 } else {
1397 let (numerator, denominator) = crate::duration::limited_fraction(sum, 65535)
1398 .ok_or_else(|| Error::Meter("Cannot find a good match for this measure".to_string()))?;
1399 (
1400 numerator as UnsignedIntegerType,
1401 denominator as UnsignedIntegerType,
1402 )
1403 };
1404 let (numerator, denominator) = simplified_signature(numerator, denominator);
1405
1406 let strength =
1407 |ratio: (UnsignedIntegerType, UnsignedIntegerType)| -> Result<(TimeSignature, FloatType)> {
1408 let signature = TimeSignature::new(ratio.0, ratio.1)?;
1409 let strength = signature.average_beat_strength(measure, true);
1410 Ok((signature, strength))
1411 };
1412 match (numerator, denominator) {
1413 (3, 4) => {
1415 let (three_four, simple) = strength((3, 4))?;
1416 let (six_eight, compound) = strength((6, 8))?;
1417 Ok(if simple <= compound {
1418 six_eight
1419 } else {
1420 three_four
1421 })
1422 }
1423 (6, 4) => {
1425 let (six_four, first) = strength((6, 4))?;
1426 let (twelve_eight, second) = strength((12, 8))?;
1427 let (three_two, third) = strength((3, 2))?;
1428 let most = first.max(second).max(third);
1429 Ok(if most == first {
1430 six_four
1431 } else if most == third {
1432 three_two
1433 } else {
1434 twelve_eight
1435 })
1436 }
1437 _ => TimeSignature::new(numerator, denominator),
1438 }
1439}
1440
1441fn binary_signature(
1446 sum: FloatType,
1447 min_dur: FloatType,
1448 min_dots: u32,
1449) -> Result<(UnsignedIntegerType, UnsignedIntegerType)> {
1450 let no_match = || Error::Meter("Cannot find a good match for this measure".to_string());
1451 let smallest_type = crate::duration::DurationType::from_music21_name("128th")
1452 .expect("music21 names the 128th note");
1453 let limit = smallest_type.quarter_length();
1454 let dot_multiplier =
1455 FloatType::from(2u32.pow(min_dots + 1) - 1) / FloatType::from(2u32.pow(min_dots));
1456
1457 let mut min_test = min_dur;
1458 let mut remaining = 10;
1459 while remaining > 0 {
1460 let parts = sum / min_test;
1461 if parts.floor() == parts || min_test <= limit {
1462 break;
1463 }
1464 min_test /= 2.0 * dot_multiplier;
1465 remaining -= 1;
1466 }
1467 let mut remaining = 10;
1468 while remaining > 0 {
1469 if min_test < limit {
1470 min_test = limit;
1471 break;
1472 }
1473 let (duration_type, matched) =
1474 crate::duration::quarter_length_to_closest_type(min_test).map_err(|_| no_match())?;
1475 if matched || duration_type == smallest_type {
1476 break;
1477 }
1478 min_test /= 2.0 * dot_multiplier;
1479 remaining -= 1;
1480 }
1481 let (duration_type, matched) =
1482 crate::duration::quarter_length_to_closest_type(min_test).map_err(|_| no_match())?;
1483 if !matched {
1484 return Err(Error::Meter(format!(
1485 "cannot find a type for denominator {min_test}"
1486 )));
1487 }
1488 let mut float_denominator = duration_type.type_number().unwrap_or(1.0);
1489 let mut multiplier = 1.0;
1490 let mut numerator_float = 0.0;
1491 while remaining > 0 {
1492 numerator_float = multiplier * sum / min_test;
1493 if numerator_float == numerator_float.floor() {
1494 break;
1495 }
1496 multiplier *= 2.0;
1497 remaining -= 1;
1498 }
1499 float_denominator *= multiplier;
1500 let numerator = numerator_float as UnsignedIntegerType;
1501 let denominator = float_denominator as UnsignedIntegerType;
1502 let divisor = num::integer::gcd(numerator, denominator).max(1);
1503 Ok((numerator / divisor, denominator / divisor))
1504}
1505
1506fn simplified_signature(
1509 numerator: UnsignedIntegerType,
1510 denominator: UnsignedIntegerType,
1511) -> (UnsignedIntegerType, UnsignedIntegerType) {
1512 if numerator == denominator && !matches!(numerator, 2 | 4) {
1513 (4, 4)
1514 } else if numerator != denominator && denominator == 1 {
1515 (numerator * 4, 4)
1516 } else if numerator != denominator && denominator == 2 {
1517 (numerator * 2, 4)
1518 } else {
1519 (numerator, denominator)
1520 }
1521}
1522
1523fn is_binary(quarter_length: FloatType) -> bool {
1527 (quarter_length * 32768.0).fract() == 0.0
1528}
1529
1530fn split_span(
1533 count: UnsignedIntegerType,
1534 unit: UnsignedIntegerType,
1535 parts: UnsignedIntegerType,
1536) -> (UnsignedIntegerType, UnsignedIntegerType) {
1537 if count.is_multiple_of(parts) {
1538 (count / parts, unit)
1539 } else {
1540 (count, unit * parts)
1541 }
1542}
1543
1544fn default_division(count: UnsignedIntegerType, unit: UnsignedIntegerType) -> UnsignedIntegerType {
1548 let _ = unit;
1549 if count > 3 && count.is_multiple_of(3) {
1550 count / 3
1551 } else if count == 1 || count.is_multiple_of(2) {
1552 2
1553 } else {
1554 count
1555 }
1556}
1557
1558fn offset_repr(offset: FloatType) -> String {
1561 if offset.fract() == 0.0 {
1562 format!("{offset:.1}")
1563 } else {
1564 offset.to_string()
1565 }
1566}
1567
1568#[cfg(test)]
1569mod tests {
1570 #[test]
1574 fn a_meter_is_read_as_music21_writes_it() {
1575 use crate::meter::TimeSignature;
1576
1577 let plain = TimeSignature::from_ratio_string("6/8").unwrap();
1581 let slow = TimeSignature::from_ratio_string("slow 6/8").unwrap();
1582 assert_eq!(TimeSignature::from_ratio_string("fast 6/8").unwrap(), plain);
1583 assert_ne!(slow, plain);
1584 assert_eq!(
1585 (slow.numerator(), slow.denominator()),
1586 (plain.numerator(), plain.denominator())
1587 );
1588 assert_eq!(plain.beat_sequence().len(), 2);
1589 assert_eq!(slow.beat_sequence().len(), 6);
1590
1591 let additive = TimeSignature::from_ratio_string("3/8+2/8").unwrap();
1592 assert_eq!((additive.numerator(), additive.denominator()), (5, 8));
1593 assert_eq!(
1594 TimeSignature::parts("3/8+2/8").unwrap(),
1595 vec![(3, 8), (2, 8)]
1596 );
1597 assert_eq!(TimeSignature::parts("3+2/8").unwrap(), vec![(3, 8), (2, 8)]);
1599 assert_eq!(
1600 TimeSignature::parts("3+2+5/8+3/4").unwrap(),
1601 vec![(3, 8), (2, 8), (5, 8), (3, 4)]
1602 );
1603 let mixed = TimeSignature::from_ratio_string("3/8+1/4").unwrap();
1605 assert_eq!((mixed.numerator(), mixed.denominator()), (5, 8));
1606
1607 assert!(TimeSignature::parts("3+2+5").is_err());
1608 assert!(TimeSignature::from_ratio_string("").is_err());
1609 assert!(TimeSignature::from_ratio_string("3.0/4.0").is_err());
1610 }
1611
1612 #[test]
1613 fn the_sequences_are_partitioned_as_music21_partitions_them() {
1614 use crate::meter::TimeSignature;
1615
1616 let expected = [
1619 ("1/4", "{1/4}", "{1/4}"),
1620 ("2/4", "{{1/8+1/8}+{1/8+1/8}}", "{{1/8+1/8}+{1/8+1/8}}"),
1621 (
1622 "3/4",
1623 "{{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}",
1624 "{{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}",
1625 ),
1626 (
1627 "4/4",
1628 "{{1/8+1/8}+{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}",
1629 "{{1/8+1/8}+{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}",
1630 ),
1631 (
1632 "5/4",
1633 "{{1/8+1/8}+{1/8+1/8}+{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}",
1634 "{{1/4+1/4}+{1/4+1/4+1/4}}",
1635 ),
1636 ("6/4", "{{1/4+1/4+1/4}+{1/4+1/4+1/4}}", "{3/4+3/4}"),
1637 ("2/2", "{{1/4+1/4}+{1/4+1/4}}", "{1/2+1/2}"),
1638 ("3/2", "{{1/4+1/4}+{1/4+1/4}+{1/4+1/4}}", "{1/2+1/2+1/2}"),
1639 ("3/8", "{3/8}", "{3/8}"),
1640 (
1641 "5/8",
1642 "{{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}",
1643 "{2/8+3/8}",
1644 ),
1645 ("6/8", "{{1/8+1/8+1/8}+{1/8+1/8+1/8}}", "{3/8+3/8}"),
1646 (
1647 "9/8",
1648 "{{1/8+1/8+1/8}+{1/8+1/8+1/8}+{1/8+1/8+1/8}}",
1649 "{3/8+3/8+3/8}",
1650 ),
1651 (
1652 "12/8",
1653 "{{1/8+1/8+1/8}+{1/8+1/8+1/8}+{1/8+1/8+1/8}+{1/8+1/8+1/8}}",
1654 "{3/8+3/8+3/8+3/8}",
1655 ),
1656 (
1657 "5/16",
1658 "{{1/32+1/32}+{1/32+1/32}+{1/32+1/32}+{1/32+1/32}+{1/32+1/32}}",
1659 "{5/16}",
1660 ),
1661 (
1662 "slow 6/8",
1663 "{{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}",
1664 "{3/8+3/8}",
1665 ),
1666 ("fast 6/8", "{{1/8+1/8+1/8}+{1/8+1/8+1/8}}", "{3/8+3/8}"),
1667 ("3/8+2/8", "{{1/8+1/8+1/8}+{1/8+1/8}}", "{2/8+3/8}"),
1668 ("2/4+3/8", "{{1/4+1/4}+{1/8+1/8+1/8}}", "{2/8+2/8+3/8}"),
1669 ];
1670 for (ratio, beats, beams) in expected {
1671 let signature = TimeSignature::from_ratio_string(ratio).unwrap();
1672 assert_eq!(
1673 signature.beat_sequence().to_string(),
1674 beats,
1675 "beats of {ratio}"
1676 );
1677 assert_eq!(
1678 signature.beam_sequence().to_string(),
1679 beams,
1680 "beams of {ratio}"
1681 );
1682 }
1683 }
1684
1685 #[test]
1687 fn accent_weights_and_beat_depths_follow_the_default_hierarchy() {
1688 let weights = |ratio: &str| {
1689 TimeSignature::from_ratio_string(ratio)
1690 .unwrap()
1691 .accent_weights()
1692 };
1693 assert_eq!(
1694 weights("4/4"),
1695 [1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125]
1696 );
1697 assert_eq!(
1698 weights("6/8"),
1699 [
1700 1.0, 0.125, 0.25, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125, 0.25, 0.125
1701 ]
1702 );
1703 assert_eq!(
1704 weights("12/8"),
1705 [
1706 1.0, 0.125, 0.125, 0.25, 0.125, 0.125, 0.5, 0.125, 0.125, 0.25, 0.125, 0.125
1707 ]
1708 );
1709 assert_eq!(weights("3/4").len(), 12);
1710 assert_eq!(weights("24/8").len(), 24);
1711 assert_eq!(
1712 TimeSignature::new(1, 4)
1713 .unwrap()
1714 .accent_partition_quarter_length(),
1715 0.125
1716 );
1717
1718 let three_four = TimeSignature::new(3, 4).unwrap();
1719 let read: Vec<FloatType> = (0..3)
1720 .map(|beat| three_four.accent_weight(FloatType::from(beat)).unwrap())
1721 .collect();
1722 assert_eq!(read, [1.0, 0.5, 0.5]);
1723 let beyond = three_four.accent_weight(3.0).unwrap_err().to_string();
1724 assert!(beyond.ends_with("cannot access from qLenPos 3.0 where total duration is 3.0"));
1725 assert_eq!(weights("16/1"), [1.0]);
1728 assert_eq!(weights("1/32"), [1.0]);
1729 assert_eq!(
1730 TimeSignature::new(24, 4)
1731 .unwrap()
1732 .accent_partition_quarter_length(),
1733 1.0
1734 );
1735 assert_eq!(
1736 three_four.accent_weight_with(4.0, false, true).unwrap(),
1737 0.5
1738 );
1739 assert_eq!(
1740 three_four.accent_weight_with(0.1, true, false).unwrap(),
1741 0.0625
1742 );
1743 assert_eq!(
1744 three_four.accent_weight_with(0.1, false, false).unwrap(),
1745 1.0
1746 );
1747 assert!(three_four.accent(2.0));
1748 assert!(!three_four.accent(0.1));
1749 assert!(!three_four.accent(3.0));
1750
1751 assert_eq!(three_four.beat_depth(0.0).unwrap(), 2);
1752 assert_eq!(three_four.beat_depth(0.25).unwrap(), 2);
1753 assert_eq!(three_four.beat_depth(0.5).unwrap(), 1);
1754 assert_eq!(three_four.beat_depth(1.0).unwrap(), 2);
1755 assert!(three_four.beat_depth(3.0).is_err());
1756 assert_eq!(
1757 TimeSignature::new(3, 8).unwrap().beat_depth(0.5).unwrap(),
1758 1
1759 );
1760 assert_eq!(
1761 TimeSignature::new(6, 8).unwrap().beat_depth(1.0).unwrap(),
1762 1
1763 );
1764 assert_eq!(
1765 TimeSignature::new(6, 8).unwrap().beat_depth(1.5).unwrap(),
1766 2
1767 );
1768 }
1769
1770 #[test]
1772 fn the_best_time_signature_fits_what_the_measure_holds() {
1773 use crate::{Note, Pitch, Stream};
1774
1775 let measure = |lengths: &[FloatType]| {
1776 let mut stream = Stream::new();
1777 for length in lengths {
1778 let mut note = Note::from_pitch(Pitch::from_name("C4").unwrap());
1779 note.set_duration(Duration::new(*length).unwrap());
1780 stream.push(note);
1781 }
1782 stream
1783 };
1784 let best = |lengths: &[FloatType]| {
1785 best_time_signature(&measure(lengths))
1786 .unwrap()
1787 .ratio_string()
1788 };
1789 assert_eq!(best(&[1.0, 1.0, 0.5, 0.5]), "3/4");
1790 assert_eq!(best(&[0.75, 0.25, 0.5, 0.75, 0.25, 0.5]), "6/8");
1791 assert_eq!(best(&[2.0, 2.0, 2.0]), "3/2");
1792 assert_eq!(best(&[0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 1.5, 1.5]), "12/8");
1793 assert_eq!(best(&[1.0, 2.0, 1.0, 2.0]), "6/4");
1794 assert_eq!(best(&[1.0, 0.375]), "11/32");
1795 assert_eq!(best(&[3.5, 5.5]), "9/4");
1796 assert_eq!(best(&[1.0, 1.0, 1.0, 1.0]), "4/4");
1797 assert_eq!(best(&[4.0]), "4/4");
1798 assert_eq!(best(&[1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0]), "2/4");
1800 }
1801
1802 #[test]
1805 fn beat_strength_is_averaged_over_a_stream() {
1806 use crate::{Note, Pitch, Stream};
1807
1808 let mut stream = Stream::new();
1809 for (name, length) in [("C4", 1.0), ("D4", 1.0), ("E4", 0.5), ("F4", 0.5)] {
1810 let mut note = Note::from_pitch(Pitch::from_name(name).unwrap());
1811 note.set_duration(Duration::new(length).unwrap());
1812 stream.push(note);
1813 }
1814 assert_eq!(
1815 TimeSignature::new(6, 8)
1816 .unwrap()
1817 .average_beat_strength(&stream, true),
1818 0.4375
1819 );
1820 assert_eq!(
1821 TimeSignature::new(3, 4)
1822 .unwrap()
1823 .average_beat_strength(&stream, true),
1824 0.5625
1825 );
1826 stream.insert(0.0, TimeSignature::new(3, 4).unwrap());
1827 stream.insert(0.0, TimeSignature::new(6, 8).unwrap());
1828 assert_eq!(
1829 TimeSignature::new(6, 8)
1830 .unwrap()
1831 .average_beat_strength(&stream, false),
1832 0.625
1833 );
1834 assert_eq!(
1835 TimeSignature::new(4, 4)
1836 .unwrap()
1837 .average_beat_strength(&Stream::new(), true),
1838 0.0
1839 );
1840 }
1841
1842 #[test]
1843 fn a_signature_reports_its_two_numbers_and_its_bar() {
1844 let six_eight = TimeSignature::new(6, 8).unwrap();
1845 assert_eq!((six_eight.numerator(), six_eight.denominator()), (6, 8));
1846 assert_eq!(six_eight.bar_duration().quarter_length(), 3.0);
1847 }
1848
1849 #[test]
1850 fn division_helpers_match_music21() {
1851 let quarter_lengths = |durations: Vec<Duration>| -> Vec<FloatType> {
1852 durations.into_iter().map(|d| d.quarter_length()).collect()
1853 };
1854 let cases: [(&str, FloatType, &str, &[FloatType], usize); 7] = [
1855 ("4/4", 1.0, "Simple", &[0.5, 0.5], 4),
1856 ("6/8", 0.5, "Compound", &[0.5, 0.5, 0.5], 6),
1857 ("2/2", 2.0, "Simple", &[1.0, 1.0], 4),
1858 ("3/8", 0.5, "Other", &[1.5], 2),
1859 ("7/8", 0.5, "Simple", &[0.25, 0.25], 4),
1860 ("12/16", 0.25, "Compound", &[0.25, 0.25, 0.25], 6),
1861 ("1/4", 1.0, "Other", &[1.0], 2),
1862 ];
1863 for (ratio, beat_to_quarter, division_name, divisions, sub_divisions) in cases {
1864 let ts = ts(ratio);
1865 assert_eq!(
1866 ts.beat_length_to_quarter_length_ratio(),
1867 beat_to_quarter,
1868 "{ratio}"
1869 );
1870 assert_eq!(
1871 ts.quarter_length_to_beat_length_ratio(),
1872 1.0 / beat_to_quarter,
1873 "{ratio}"
1874 );
1875 assert_eq!(ts.beat_division_count_name(), division_name, "{ratio}");
1876 assert_eq!(
1877 quarter_lengths(ts.beat_division_durations().unwrap()),
1878 divisions,
1879 "{ratio}"
1880 );
1881 let subs = quarter_lengths(ts.beat_sub_division_durations().unwrap());
1882 assert_eq!(subs.len(), sub_divisions, "{ratio}");
1883 assert!(subs.iter().all(|ql| *ql == divisions[0] / 2.0), "{ratio}");
1884 }
1885 assert!(ts("4/4").ratio_equal(&ts("4/4")));
1886 assert!(!ts("4/4").ratio_equal(&ts("2/2")));
1887 }
1888
1889 #[test]
1890 fn beat_positions_match_music21() {
1891 let common = ts("4/4");
1892 let cases: [(FloatType, UnsignedIntegerType, FloatType, FloatType, &str); 7] = [
1893 (0.0, 1, 0.0, 1.0, "1"),
1894 (0.5, 1, 0.5, 1.5, "1 1/2"),
1895 (1.25, 2, 0.25, 2.25, "2 1/4"),
1896 (2.75, 3, 0.75, 3.75, "3 3/4"),
1897 (3.0, 4, 0.0, 4.0, "4"),
1898 (3.75, 4, 0.75, 4.75, "4 3/4"),
1899 (3.9, 4, 0.9, 4.9, "4 9/10"),
1900 ];
1901 for (offset, beat, progress, proportion, text) in cases {
1902 let (actual_beat, actual_progress) = common.beat_progress(offset).unwrap();
1903 assert_eq!(actual_beat, beat, "{offset}");
1904 assert!((actual_progress - progress).abs() < 1e-9, "{offset}");
1905 assert!(
1906 (common.beat_proportion(offset).unwrap() - proportion).abs() < 1e-9,
1907 "{offset}"
1908 );
1909 assert_eq!(
1910 common.beat_proportion_string(offset).unwrap(),
1911 text,
1912 "{offset}"
1913 );
1914 }
1915
1916 let compound = ts("6/8");
1917 assert_eq!(compound.beat_proportion_string(0.5).unwrap(), "1 1/3");
1918 assert_eq!(compound.beat_proportion_string(1.0).unwrap(), "1 2/3");
1919 assert_eq!(compound.beat_proportion_string(2.5).unwrap(), "2 2/3");
1920 assert!((compound.beat_proportion(2.0).unwrap() - 7.0 / 3.0).abs() < 1e-9);
1921 assert_eq!(ts("3/8").beat_proportion_string(1.0).unwrap(), "1 2/3");
1922 assert!(common.beat_progress(4.0).is_err());
1923 }
1924
1925 #[test]
1926 fn offset_from_beat_matches_music21() {
1927 let common = ts("4/4");
1928 for (beat, offset) in [
1929 (1.0, 0.0),
1930 (1.5, 0.5),
1931 (2.5, 1.5),
1932 (3.25, 2.25),
1933 (4.75, 3.75),
1934 ] {
1935 assert_eq!(common.offset_from_beat(beat).unwrap(), offset, "{beat}");
1936 }
1937 assert!(common.offset_from_beat(5.0).is_err());
1938 assert!(common.offset_from_beat(0.5).is_err());
1939 let compound = ts("6/8");
1940 assert_eq!(compound.offset_from_beat(1.5).unwrap(), 0.75);
1941 assert_eq!(compound.offset_from_beat(2.5).unwrap(), 2.25);
1942 assert!((compound.offset_from_beat(2.999).unwrap() - 2.9985).abs() < 1e-9);
1943 assert_eq!(ts("3/8").offset_from_beat(1.5).unwrap(), 0.75);
1944 }
1945
1946 #[test]
1947 fn closest_fraction_limits_the_denominator_like_python() {
1948 assert_eq!(closest_fraction(0.5, 16), (1, 2));
1949 assert_eq!(closest_fraction(1.0 / 3.0, 16), (1, 3));
1950 assert_eq!(closest_fraction(0.9, 16), (9, 10));
1951 assert_eq!(closest_fraction(0.75, 16), (3, 4));
1952 assert_eq!(closest_fraction(0.1234, 16), (1, 8));
1953 }
1954 use super::*;
1955
1956 fn ts(ratio: &str) -> TimeSignature {
1957 TimeSignature::from_ratio_string(ratio).expect("valid time signature")
1958 }
1959
1960 #[test]
1961 fn common_and_cut_time_match_their_ratios() {
1962 assert_eq!(TimeSignature::common().ratio_string(), "4/4");
1963 assert_eq!(TimeSignature::cut().ratio_string(), "2/2");
1964 assert_eq!(TimeSignature::default(), TimeSignature::common());
1965 }
1966
1967 #[test]
1968 fn bar_and_beat_lengths_follow_the_ratio() {
1969 assert_eq!(ts("4/4").bar_quarter_length(), 4.0);
1970 assert_eq!(ts("5/16").bar_quarter_length(), 1.25);
1971 assert_eq!(ts("3/8").bar_quarter_length(), 1.5);
1972 assert_eq!(ts("6/8").beat_quarter_length().unwrap(), 1.5);
1973 assert_eq!(ts("4/4").beat_quarter_length().unwrap(), 1.0);
1974 assert_eq!(ts("2/2").beat_duration().unwrap().quarter_length(), 2.0);
1975 }
1976
1977 #[test]
1978 fn compound_meters_beat_in_threes() {
1979 for (ratio, beats, division) in [
1980 ("6/8", 2, BeatDivision::Compound),
1981 ("9/8", 3, BeatDivision::Compound),
1982 ("12/8", 4, BeatDivision::Compound),
1983 ("15/8", 5, BeatDivision::Compound),
1984 ("18/8", 6, BeatDivision::Compound),
1985 ("24/8", 8, BeatDivision::Compound),
1986 ] {
1987 assert_eq!(ts(ratio).beat_count(), beats, "{ratio}");
1988 assert_eq!(ts(ratio).beat_division(), division, "{ratio}");
1989 assert!(ts(ratio).is_compound(), "{ratio}");
1990 }
1991 }
1992
1993 #[test]
1994 fn three_is_the_one_denominator_sensitive_numerator() {
1995 assert_eq!(ts("3/2").beat_count(), 3);
1997 assert_eq!(ts("3/4").beat_count(), 3);
1998 assert_eq!(ts("3/8").beat_count(), 1);
1999 assert_eq!(ts("3/16").beat_count(), 1);
2000 assert_eq!(ts("3/32").beat_count(), 1);
2001 assert_eq!(ts("3/3").beat_count(), 3);
2004 assert_eq!(ts("3/6").beat_count(), 3);
2005 assert_eq!(ts("3/12").beat_count(), 1);
2006 for denominator in [2, 4, 8, 16] {
2008 assert_eq!(TimeSignature::new(6, denominator).unwrap().beat_count(), 2);
2009 assert_eq!(TimeSignature::new(5, denominator).unwrap().beat_count(), 5);
2010 }
2011 }
2012
2013 #[test]
2014 fn a_bar_written_in_unequal_parts_is_counted_along_its_own_beats() {
2015 use crate::meter::TimeSignature;
2016
2017 let mixed = TimeSignature::from_ratio_string("2/4+3/8").unwrap();
2020 assert_eq!(mixed.bar_quarter_length(), 3.5);
2021 assert_eq!(mixed.beat_count(), 2);
2022 assert_eq!(mixed.beat_offsets(), vec![0.0, 2.0]);
2023 assert_eq!(mixed.beat_at_offset(2.5).unwrap(), 2);
2024 assert_eq!(mixed.beat_duration_at(0.0).unwrap().quarter_length(), 2.0);
2025 assert_eq!(mixed.beat_duration_at(2.5).unwrap().quarter_length(), 1.5);
2026 assert_eq!(mixed.offset_from_beat(2.0).unwrap(), 2.0);
2027 assert_eq!(mixed.offset_from_beat(1.5).unwrap(), 1.0);
2030 assert_eq!(mixed.beat_proportion_string(2.5).unwrap(), "2 1/3");
2031 assert_eq!(mixed.beat_depth(0.0).unwrap(), 2);
2032
2033 let other = TimeSignature::from_ratio_string("3/8+2/8").unwrap();
2034 assert_eq!(other.beat_offsets(), vec![0.0, 1.5]);
2035 assert_eq!(other.offset_from_beat(2.0).unwrap(), 1.5);
2036 assert_eq!(other.offset_from_beat(1.5).unwrap(), 0.75);
2037 assert!(other.beat_proportion_string(2.5).is_err());
2039
2040 let common = TimeSignature::common();
2042 assert_eq!(common.beat_offsets(), vec![0.0, 1.0, 2.0, 3.0]);
2043 assert_eq!(common.offset_from_beat(2.0).unwrap(), 1.0);
2044 assert_eq!(common.offset_from_beat(1.5).unwrap(), 0.5);
2045 assert_eq!(common.beat_proportion_string(2.5).unwrap(), "3 1/2");
2046 }
2047
2048 #[test]
2049 fn a_bar_whose_beats_differ_has_no_one_beat_length() {
2050 use crate::meter::TimeSignature;
2051
2052 let mixed = TimeSignature::from_ratio_string("2/4+3/8").unwrap();
2055 assert!(mixed.beat_quarter_length().is_err());
2056 assert!(mixed.beat_duration().is_err());
2057 assert!(mixed.beat_division_durations().is_err());
2058 assert_eq!(mixed.beat_division_count(), 1);
2059 assert_eq!(mixed.beat_division_count_name(), "Other");
2060 assert_eq!(mixed.classification(), "Other Duple");
2061
2062 let other = TimeSignature::from_ratio_string("3/8+2/8").unwrap();
2063 assert!(other.beat_quarter_length().is_err());
2064 assert_eq!(other.beat_division_count(), 1);
2065 assert_eq!(other.classification(), "Other Duple");
2066
2067 assert_eq!(TimeSignature::common().beat_quarter_length().unwrap(), 1.0);
2069 assert_eq!(
2070 TimeSignature::from_ratio_string("6/8")
2071 .unwrap()
2072 .beat_quarter_length()
2073 .unwrap(),
2074 1.5
2075 );
2076 }
2077
2078 #[test]
2079 fn weights_a_caller_sets_are_the_weights_read_back() {
2080 use crate::meter::TimeSignature;
2081
2082 let mut common = TimeSignature::common();
2083 let default = common.accent_weights();
2084 assert_eq!(default[0], 1.0);
2085
2086 common.set_accent_weight(&[0.8, 0.2], 0).unwrap();
2088 let set = common.accent_weights();
2089 assert_eq!(set.len(), default.len());
2090 assert!((set[0] - 0.8).abs() < 1e-9);
2091 assert!((set[1] - 0.2).abs() < 1e-9);
2092 assert!((set[2] - 0.8).abs() < 1e-9);
2093
2094 assert!((common.accent_weight(0.0).unwrap() - 0.8).abs() < 1e-9);
2096
2097 assert_eq!(TimeSignature::common().accent_weights()[0], 1.0);
2099 }
2100
2101 #[test]
2102 fn a_bar_can_be_counted_in_a_different_number_of_beats() {
2103 use crate::meter::TimeSignature;
2104
2105 for (ratio, count, partitioned) in [
2107 (
2108 "6/8",
2109 6,
2110 "{{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}",
2111 ),
2112 ("6/8", 2, "{{1/8+1/8+1/8}+{1/8+1/8+1/8}}"),
2113 ("4/4", 2, "{{1/4+1/4}+{1/4+1/4}}"),
2114 ("3/4", 1, "{3/4}"),
2115 (
2116 "2/4",
2117 4,
2118 "{{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}",
2119 ),
2120 ] {
2121 let mut signature = TimeSignature::from_ratio_string(ratio).unwrap();
2122 signature.set_beat_count(count).unwrap();
2123 assert_eq!(
2124 signature.beat_sequence().to_string(),
2125 partitioned,
2126 "{ratio} in {count}"
2127 );
2128 assert_eq!(signature.beat_count(), count, "{ratio} in {count}");
2129 }
2130 }
2131
2132 #[test]
2133 fn a_bar_can_be_written_a_different_way_than_it_is_counted() {
2134 use crate::meter::TimeSignature;
2135
2136 let mut signature = TimeSignature::from_ratio_string("3/4").unwrap();
2139 let counted = signature.beat_sequence().to_string();
2140 signature.set_display("2/8+2/8+2/8").unwrap();
2141 assert_eq!(signature.display_sequence().to_string(), "{2/8+2/8+2/8}");
2142 assert_eq!(
2143 signature.display_sequence().partition_display(),
2144 "2/8+2/8+2/8"
2145 );
2146 assert_eq!(signature.beat_sequence().to_string(), counted);
2148 assert_eq!(signature.beat_count(), 3);
2149 assert!(signature.set_display("2/4").is_err());
2155
2156 let mut common = TimeSignature::common();
2158 common
2159 .beat_sequence_mut()
2160 .partition_by_count(2, true)
2161 .unwrap();
2162 assert_eq!(common.beat_count(), 2);
2163 }
2164
2165 #[test]
2166 fn the_divisions_a_meter_is_built_with_partition_its_beats() {
2167 use crate::meter::TimeSignature;
2168
2169 for (ratio, divisions, partitioned) in [
2171 ("3/4", 1, "{3/4}"),
2172 ("6/8", 2, "{3/8+3/8}"),
2173 ("4/4", 2, "{1/2+1/2}"),
2174 ("3/4", 3, "{1/4+1/4+1/4}"),
2175 ] {
2176 let mut signature = TimeSignature::from_ratio_string(ratio).unwrap();
2177 signature.divide_beats(divisions).unwrap();
2178 assert_eq!(
2179 signature.beat_sequence().to_string(),
2180 partitioned,
2181 "{ratio} in {divisions}"
2182 );
2183 }
2184
2185 let mut counted = TimeSignature::from_ratio_string("6/8").unwrap();
2187 counted.set_beat_count(2).unwrap();
2188 assert_eq!(
2189 counted.beat_sequence().to_string(),
2190 "{{1/8+1/8+1/8}+{1/8+1/8+1/8}}"
2191 );
2192 }
2193
2194 #[test]
2195 fn a_fractional_beat_is_read_as_the_fraction_it_suggests() {
2196 use crate::meter::TimeSignature;
2197
2198 let compound = TimeSignature::from_ratio_string("6/8").unwrap();
2203 assert!((compound.offset_from_beat(2.33).unwrap() - 2.0).abs() < 1e-9);
2204 assert!((compound.offset_from_beat(2.66).unwrap() - 2.5).abs() < 1e-9);
2205 assert!((compound.offset_from_beat(1.66).unwrap() - 1.0).abs() < 1e-9);
2206
2207 let simple = TimeSignature::common();
2210 let third = 1.0 / 3.0;
2211 assert!((simple.offset_from_beat(2.33).unwrap() - (1.0 + third)).abs() < 1e-9);
2212 assert!((simple.offset_from_beat(2.66).unwrap() - (1.0 + 2.0 * third)).abs() < 1e-9);
2213 assert!((simple.offset_from_beat(1.66).unwrap() - (2.0 * third)).abs() < 1e-9);
2214
2215 let mut divided = TimeSignature::common();
2217 divided.divide_beats(4).unwrap();
2218 assert_eq!(divided.beat_sequence().to_string(), "{1/4+1/4+1/4+1/4}");
2219 assert_eq!(divided.accent_sequence().to_string(), "{1/4+1/4+1/4+1/4}");
2220 assert_eq!(divided.beam_sequence().to_string(), "{1/4+1/4+1/4+1/4}");
2221 assert_eq!(divided.display_sequence().to_string(), "{4/4}");
2222
2223 divided.set_accent_weight(&[0.8, 0.2], 0).unwrap();
2225 let weights = divided.accent_weights();
2226 assert_eq!(weights.len(), 4);
2227 assert!((weights[0] - 0.8).abs() < 1e-9);
2228 assert!((weights[1] - 0.2).abs() < 1e-9);
2229 }
2230
2231 #[test]
2232 fn classification_joins_division_and_count() {
2233 assert_eq!(ts("4/4").classification(), "Simple Quadruple");
2234 assert_eq!(ts("6/8").classification(), "Compound Duple");
2235 assert_eq!(ts("3/8").classification(), "Other Single");
2236 assert_eq!(ts("5/4").classification(), "Simple Quintuple");
2237 assert_eq!(ts("13/8").classification(), "Simple 13-uple");
2238 assert_eq!(ts("21/16").classification(), "Compound Septuple");
2239 }
2240
2241 #[test]
2242 fn beat_offsets_partition_the_bar() {
2243 assert_eq!(ts("4/4").beat_offsets(), [0.0, 1.0, 2.0, 3.0]);
2244 assert_eq!(ts("6/8").beat_offsets(), [0.0, 1.5]);
2245 assert_eq!(ts("5/8").beat_offsets(), [0.0, 0.5, 1.0, 1.5, 2.0]);
2246 }
2247
2248 #[test]
2249 fn beat_at_offset_is_one_based_and_bounded() {
2250 assert_eq!(ts("4/4").beat_at_offset(1.5).unwrap(), 2);
2251 assert_eq!(ts("6/8").beat_at_offset(1.5).unwrap(), 2);
2252 assert_eq!(ts("5/8").beat_at_offset(1.5).unwrap(), 4);
2253 assert_eq!(ts("4/4").beat_at_offset(0.0).unwrap(), 1);
2254 assert!(ts("4/4").beat_at_offset(4.0).is_err());
2255 assert!(ts("4/4").beat_at_offset(-0.5).is_err());
2256 assert!(ts("4/4").beat_at_offset(FloatType::NAN).is_err());
2257 }
2258
2259 #[test]
2260 fn irrational_denominators_are_accepted_as_music21_accepts_them() {
2261 let four_three = ts("4/3");
2262 assert!((four_three.bar_quarter_length() - 16.0 / 3.0).abs() < 1e-12);
2263 assert_eq!(four_three.beat_count(), 4);
2264 }
2265
2266 #[test]
2267 fn malformed_ratios_error_instead_of_panicking() {
2268 for ratio in [
2269 "", "4", "4/", "/4", "4/4/4", "x/4", "4/x", "0/4", "4/0", "-1/4",
2270 ] {
2271 assert!(
2272 TimeSignature::from_ratio_string(ratio).is_err(),
2273 "{ratio:?} should not parse"
2274 );
2275 }
2276 }
2277
2278 #[test]
2279 fn display_is_the_ratio_string() {
2280 assert_eq!(ts("7/8").to_string(), "7/8");
2281 }
2282
2283 #[test]
2284 fn a_meter_says_how_it_was_written() {
2285 use crate::meter::TimeSignature;
2286
2287 let written = TimeSignature::from_ratio_string("2/4+3/8").unwrap();
2289 assert_eq!(written.ratio_string(), "2/4+3/8");
2290 assert_eq!(written.to_string(), "2/4+3/8");
2291 assert_eq!((written.numerator(), written.denominator()), (7, 8));
2293
2294 let two_three = TimeSignature::from_ratio_string("2/8+3/8").unwrap();
2297 let three_two = TimeSignature::from_ratio_string("3/8+2/8").unwrap();
2298 assert_eq!(two_three.ratio_string(), "2/8+3/8");
2299 assert_eq!(three_two.ratio_string(), "3/8+2/8");
2300 assert_ne!(two_three, three_two);
2301 assert!(two_three.ratio_equal(&three_two));
2304
2305 let again = TimeSignature::from_ratio_string(&written.ratio_string()).unwrap();
2307 assert_eq!(again.ratio_string(), written.ratio_string());
2308
2309 let slow = TimeSignature::from_ratio_string("slow 6/8").unwrap();
2312 let fast = TimeSignature::from_ratio_string("6/8").unwrap();
2313 assert_eq!(slow.ratio_string(), "6/8");
2314 assert_eq!(fast.ratio_string(), "6/8");
2315 assert_ne!(slow, fast);
2316
2317 assert_eq!(TimeSignature::common().ratio_string(), "4/4");
2319 }
2320}