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