Skip to main content

music21_rs/
duration.rs

1use crate::{
2    defaults::{FloatType, IntegerType},
3    error::{Error, Result},
4};
5
6#[derive(Clone, Debug)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8/// Rhythmic duration measured in quarter lengths.
9///
10/// A quarter note has a quarter length of `1.0`; an eighth note is `0.5`;
11/// a whole note is `4.0`.
12pub struct Duration {
13    quarter_length: FloatType,
14}
15
16impl Duration {
17    /// Creates a duration from a quarter-length value.
18    pub fn new(quarter_length: FloatType) -> Result<Self> {
19        if !quarter_length.is_finite() || quarter_length < 0.0 {
20            return Err(Error::Ordinal(format!(
21                "duration quarter length must be finite and non-negative, got {quarter_length}"
22            )));
23        }
24
25        Ok(Self { quarter_length })
26    }
27
28    /// Returns a quarter-note duration.
29    pub fn quarter() -> Self {
30        Self::default()
31    }
32
33    /// Returns a half-note duration.
34    pub fn half() -> Self {
35        Self::new(2.0).expect("constant duration is valid")
36    }
37
38    /// Returns a whole-note duration.
39    pub fn whole() -> Self {
40        Self::new(4.0).expect("constant duration is valid")
41    }
42
43    /// Returns an eighth-note duration.
44    pub fn eighth() -> Self {
45        Self::new(0.5).expect("constant duration is valid")
46    }
47
48    /// Returns the duration in quarter lengths.
49    pub fn quarter_length(&self) -> FloatType {
50        self.quarter_length
51    }
52
53    /// Updates the duration in quarter lengths.
54    pub fn set_quarter_length(&mut self, quarter_length: FloatType) -> Result<()> {
55        *self = Self::new(quarter_length)?;
56        Ok(())
57    }
58}
59
60impl Default for Duration {
61    fn default() -> Self {
62        Self {
63            quarter_length: 1.0,
64        }
65    }
66}
67
68impl PartialEq for Duration {
69    fn eq(&self, other: &Self) -> bool {
70        self.quarter_length == other.quarter_length
71    }
72}
73
74impl TryFrom<FloatType> for Duration {
75    type Error = Error;
76
77    fn try_from(value: FloatType) -> Result<Self> {
78        Self::new(value)
79    }
80}
81
82impl TryFrom<IntegerType> for Duration {
83    type Error = Error;
84
85    fn try_from(value: IntegerType) -> Result<Self> {
86        Self::new(value as FloatType)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn duration_tracks_quarter_lengths() {
96        assert_eq!(Duration::quarter().quarter_length(), 1.0);
97        assert_eq!(Duration::half().quarter_length(), 2.0);
98        assert_eq!(Duration::whole().quarter_length(), 4.0);
99        assert_eq!(Duration::eighth().quarter_length(), 0.5);
100    }
101
102    #[test]
103    fn duration_rejects_invalid_values() {
104        assert!(Duration::new(-1.0).is_err());
105        assert!(Duration::new(FloatType::INFINITY).is_err());
106    }
107
108    #[test]
109    fn duration_supports_conversions_and_updates() {
110        let mut duration = Duration::try_from(3 as IntegerType).unwrap();
111        assert_eq!(duration.quarter_length(), 3.0);
112
113        duration.set_quarter_length(1.5).unwrap();
114        assert_eq!(duration, Duration::try_from(1.5).unwrap());
115        assert!(duration.set_quarter_length(FloatType::NAN).is_err());
116    }
117}