klavier_core/
duration.rs

1use std::{fmt, hash::Hash};
2
3/// Note duration numerator (note type).
4///
5/// Represents the type of note: whole note, half note, quarter note, etc.
6#[derive(serde::Deserialize, serde::Serialize)]
7#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)]
8pub enum Numerator {
9    /// Whole note (1)
10    Whole,
11    /// Half note (1/2)
12    Half,
13    /// Quarter note (1/4)
14    #[default]
15    Quarter,
16    /// Eighth note (1/8)
17    N8th,
18    /// Sixteenth note (1/16)
19    N16th,
20    /// Thirty-second note (1/32)
21    N32nd,
22    /// Sixty-fourth note (1/64)
23    N64th,
24    /// One hundred twenty-eighth note (1/128)
25    N128th,
26}
27
28impl Numerator {
29    pub const fn ord(self) -> u8 {
30        match self {
31            Numerator::Whole => 0,
32            Numerator::Half => 1,
33            Numerator::Quarter => 2,
34            Numerator::N8th => 3,
35            Numerator::N16th => 4,
36            Numerator::N32nd => 5,
37            Numerator::N64th => 6,
38            Numerator::N128th => 7,
39        }
40    }
41
42    pub const fn from_ord(ord: u8) -> Option<Numerator> {
43        match ord {
44            0 => Some(Numerator::Whole),
45            1 => Some(Numerator::Half),
46            2 => Some(Numerator::Quarter),
47            3 => Some(Numerator::N8th),
48            4 => Some(Numerator::N16th),
49            5 => Some(Numerator::N32nd),
50            6 => Some(Numerator::N64th),
51            7 => Some(Numerator::N128th),
52            _ => None,
53        }
54    }
55}
56
57/// Tuplet denominator for note duration.
58///
59/// Represents the denominator in tuplets (e.g., 3 for triplets, 5 for quintuplets).
60/// A value of 2 means normal (non-tuplet) duration.
61#[derive(serde::Deserialize, serde::Serialize)]
62#[serde(from="SerializedDenominator")]
63#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
64pub struct Denominator(u8);
65
66impl Default for Denominator {
67    fn default() -> Self {
68        Self(2)
69    }
70}
71
72#[derive(serde::Deserialize)]
73struct SerializedDenominator(u8);
74
75impl From<SerializedDenominator> for Denominator {
76    fn from(ser: SerializedDenominator) -> Self {
77        Denominator::from_value(ser.0).unwrap_or(Denominator(2))
78    }
79}
80
81impl fmt::Display for Denominator {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{}", self.0)
84    }
85}
86
87impl Denominator {
88    pub const fn value(self) -> u8 {
89        self.0
90    }
91
92    pub const fn from_value(value: u8) -> Option<Denominator> {
93        if value < 2 {
94            None
95        } else {
96            Some(Denominator(value))
97        }
98    }
99}
100
101/// Number of dots extending a note's duration.
102///
103/// Each dot adds half the value of the previous duration.
104/// For example, a dotted quarter note = quarter + eighth.
105#[derive(serde::Deserialize, serde::Serialize)]
106#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
107pub struct Dots(u8);
108
109impl Default for Dots {
110    fn default() -> Self {
111        Self::ZERO
112    }
113}
114
115impl Dots {
116    pub const ZERO: Dots = Dots(0);
117    pub const ONE: Dots = Dots(1);
118    pub const TWO: Dots = Dots(2);
119    pub const THREE: Dots = Dots(3);
120    pub const FOUR: Dots = Dots(4);
121    pub const FIVE: Dots = Dots(5);
122    pub const SIX: Dots = Dots(6);
123    pub const SEVEN: Dots = Dots(7);
124
125    pub const fn value(self) -> u8 {
126        self.0
127    }
128
129    pub const fn from_value(value: u8) -> Option<Dots> {
130        if 7 < value {
131            None
132        } else {
133            Some(Dots(value))
134        }
135    }
136}
137
138/// Represents the duration of a musical note.
139///
140/// A duration combines:
141/// - **Numerator**: The note type (whole, half, quarter, etc.)
142/// - **Denominator**: The tuplet grouping (2 = normal, 3 = triplet, etc.)
143/// - **Dots**: Number of dots extending the duration
144///
145/// # Examples
146///
147/// ```
148/// # use klavier_core::duration::{Duration, Numerator, Denominator, Dots};
149/// // Quarter note
150/// let quarter = Duration::new(
151///     Numerator::Quarter,
152///     Denominator::from_value(2).unwrap(),
153///     Dots::ZERO
154/// );
155/// assert_eq!(quarter.tick_length(), 240);
156///
157/// // Dotted quarter note
158/// let dotted_quarter = Duration::new(
159///     Numerator::Quarter,
160///     Denominator::from_value(2).unwrap(),
161///     Dots::ONE
162/// );
163/// assert_eq!(dotted_quarter.tick_length(), 360);
164/// ```
165#[derive(serde::Deserialize, serde::Serialize)]
166#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
167pub struct Duration {
168    /// The note type (whole, half, quarter, etc.).
169    pub numerator: Numerator,
170    /// The tuplet denominator (2 = normal, 3 = triplet, etc.).
171    pub denominator: Denominator,
172    /// The number of dots (0-7).
173    pub dots: Dots,
174}
175
176impl Duration {
177    /// Ticks per quarter note (MIDI standard).
178    pub const TICK_RESOLUTION: i32 = 240;
179    /// Maximum tick length (whole note * 2).
180    pub const MAX_TICK_LENGTH: i32 = Duration::TICK_RESOLUTION * 8;
181    /// Minimum denominator value.
182    pub const MIN_DENOMINATOR: u8 = 2;
183    /// Maximum denominator value.
184    pub const MAX_DENOMINATOR: u8 = 255;
185    /// Maximum number of dots.
186    pub const MAX_DOT: u8 = 7;
187    /// Maximum numerator ordinal.
188    pub const MAX_NUMERATOR: u8 = 7;
189
190    /// Creates a new duration.
191    ///
192    /// # Arguments
193    ///
194    /// * `numerator` - The note type.
195    /// * `denominator` - The tuplet denominator.
196    /// * `dots` - The number of dots.
197    pub fn new(numerator: Numerator, denominator: Denominator, dots: Dots) -> Duration {
198        Self { numerator, denominator, dots }
199    }
200
201    /// Calculates the duration in ticks.
202    ///
203    /// # Returns
204    ///
205    /// The duration in ticks, accounting for note type, tuplets, and dots.
206    pub const fn tick_length(self) -> u32 {
207        let numerator = self.numerator.ord();
208        let len =
209            if numerator <= 2 {
210                (Duration::TICK_RESOLUTION << (2 - numerator)) as u32
211            } else {
212                (Duration::TICK_RESOLUTION >> (numerator - 2)) as u32
213            };
214
215        if self.dots.value() == 0 && self.denominator.value() == 2 {
216            len
217        } else {
218            ((len + (len - (len >> self.dots.value()))) as i64 * 2 / (self.denominator.value() as i64)) as u32
219        }
220    }
221
222    /// Creates a new duration with a different numerator.
223    pub fn with_numerator(self, numerator: Numerator) -> Duration {
224        Self::new(numerator, self.denominator, self.dots)
225    }
226
227    /// Creates a new duration with a different denominator.
228    pub fn with_denominator(self, denominator: Denominator) -> Duration {
229        Self::new(self.numerator, denominator, self.dots)
230    }
231
232    /// Creates a new duration with a different number of dots.
233    pub fn with_dots(self, dots: Dots) -> Duration {
234        Self::new(self.numerator, self.denominator, dots)
235    }
236
237    /// Returns the shorter of two durations.
238    pub fn min(self, other: Self) -> Self {
239        if self.tick_length() < other.tick_length() { self } else { other }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use crate::duration::Duration;
246
247    use super::{Numerator, Denominator, Dots};
248
249    #[test]
250    #[should_panic]
251    fn low_denominator() {
252        Duration::new(Numerator::Half, Denominator::from_value(1).unwrap(), Dots::ZERO);
253    }
254
255    #[test]
256    #[should_panic]
257    fn high_dot() {
258        Duration::new(Numerator::N128th, Denominator::from_value(2).unwrap(), Dots::from_value(8).unwrap());
259    }
260
261    #[test]
262    fn getter() {
263        let d = Duration::new(Numerator::Half, Denominator::from_value(2).unwrap(), Dots::from_value(3).unwrap());
264        assert_eq!(d.numerator, Numerator::Half);
265        assert_eq!(d.denominator.value(), 2);
266        assert_eq!(d.dots.value(), 3);
267    }
268
269    #[test]
270    fn tick_length() {
271        assert_eq!(Duration::new(Numerator::Quarter, Denominator::from_value(2).unwrap(), Dots::ZERO).tick_length(), 240);
272        assert_eq!(Duration::new(Numerator::Half, Denominator::from_value(2).unwrap(), Dots::ZERO).tick_length(), 480);
273        assert_eq!(Duration::new(Numerator::Quarter, Denominator::from_value(3).unwrap(), Dots::ZERO).tick_length(), 160);
274        assert_eq!(Duration::new(Numerator::Quarter, Denominator::from_value(2).unwrap(), Dots::from_value(1).unwrap()).tick_length(), 360);
275    }
276}