acorde_core/model/
duration.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub enum Duration {
6 Longa,
8 Breve,
10 Whole,
11 Half,
12 Quarter,
13 Eighth,
14 Sixteenth,
15 ThirtySecond,
16 SixtyFourth,
17 HundredTwentyEighth,
18}
19
20impl Duration {
21 pub fn as_fraction(&self) -> (u32, u32) {
23 match self {
24 Duration::Longa => (4, 1),
25 Duration::Breve => (2, 1),
26 Duration::Whole => (1, 1),
27 Duration::Half => (1, 2),
28 Duration::Quarter => (1, 4),
29 Duration::Eighth => (1, 8),
30 Duration::Sixteenth => (1, 16),
31 Duration::ThirtySecond => (1, 32),
32 Duration::SixtyFourth => (1, 64),
33 Duration::HundredTwentyEighth => (1, 128),
34 }
35 }
36
37 pub fn to_ticks(&self, dot_count: u8) -> u32 {
39 let (num, den) = self.as_fraction();
40 let base_ticks = 4 * 480 * num / den;
41 let mut ticks = base_ticks;
42 let mut dot_value = base_ticks / 2;
43 for _ in 0..dot_count {
44 ticks += dot_value;
45 dot_value /= 2;
46 }
47 ticks
48 }
49
50 pub fn to_musicxml_type(&self) -> &'static str {
52 match self {
53 Duration::Longa => "long",
54 Duration::Breve => "breve",
55 Duration::Whole => "whole",
56 Duration::Half => "half",
57 Duration::Quarter => "quarter",
58 Duration::Eighth => "eighth",
59 Duration::Sixteenth => "16th",
60 Duration::ThirtySecond => "32nd",
61 Duration::SixtyFourth => "64th",
62 Duration::HundredTwentyEighth => "128th",
63 }
64 }
65
66 pub fn beats(&self, dot_count: u8) -> f64 {
68 let (num, den) = self.as_fraction();
69 let base = 4.0 * num as f64 / den as f64;
70 let mut total = base;
71 let mut dot = base / 2.0;
72 for _ in 0..dot_count {
73 total += dot;
74 dot /= 2.0;
75 }
76 total
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn quarter_ticks() {
86 assert_eq!(Duration::Quarter.to_ticks(0), 480);
87 }
88
89 #[test]
90 fn dotted_quarter_ticks() {
91 assert_eq!(Duration::Quarter.to_ticks(1), 720);
92 }
93
94 #[test]
95 fn whole_beats() {
96 assert!((Duration::Whole.beats(0) - 4.0).abs() < 1e-9);
97 }
98
99 #[test]
100 fn dotted_half_beats() {
101 assert!((Duration::Half.beats(1) - 3.0).abs() < 1e-9);
102 }
103
104 #[test]
105 fn musicxml_type_strings() {
106 assert_eq!(Duration::Longa.to_musicxml_type(), "long");
107 assert_eq!(Duration::Longa.to_ticks(0), 7680);
108 assert!((Duration::Longa.beats(0) - 16.0).abs() < 1e-9);
109 assert_eq!(Duration::Sixteenth.to_musicxml_type(), "16th");
110 assert_eq!(Duration::ThirtySecond.to_musicxml_type(), "32nd");
111 }
112}