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))]
8pub struct Duration {
13 quarter_length: FloatType,
14}
15
16impl Duration {
17 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 pub fn quarter() -> Self {
30 Self::default()
31 }
32
33 pub fn half() -> Self {
35 Self::new(2.0).expect("constant duration is valid")
36 }
37
38 pub fn whole() -> Self {
40 Self::new(4.0).expect("constant duration is valid")
41 }
42
43 pub fn eighth() -> Self {
45 Self::new(0.5).expect("constant duration is valid")
46 }
47
48 pub fn quarter_length(&self) -> FloatType {
50 self.quarter_length
51 }
52
53 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}