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