1use crate::{
2 defaults::{FloatType, IntegerType},
3 error::{Error, Result},
4};
5
6use std::fmt::{Display, Formatter};
7use std::str::FromStr;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[non_exhaustive]
17pub enum DurationType {
18 DuplexMaxima,
20 Maxima,
22 Longa,
24 Breve,
26 Whole,
28 Half,
30 Quarter,
32 Eighth,
34 Sixteenth,
36 ThirtySecond,
38 SixtyFourth,
40 HundredTwentyEighth,
42 TwoHundredFiftySixth,
44 FiveHundredTwelfth,
46 TenTwentyFourth,
48 TwentyFortyEighth,
50 Zero,
52}
53
54impl DurationType {
55 pub const ALL: [DurationType; 17] = [
57 Self::DuplexMaxima,
58 Self::Maxima,
59 Self::Longa,
60 Self::Breve,
61 Self::Whole,
62 Self::Half,
63 Self::Quarter,
64 Self::Eighth,
65 Self::Sixteenth,
66 Self::ThirtySecond,
67 Self::SixtyFourth,
68 Self::HundredTwentyEighth,
69 Self::TwoHundredFiftySixth,
70 Self::FiveHundredTwelfth,
71 Self::TenTwentyFourth,
72 Self::TwentyFortyEighth,
73 Self::Zero,
74 ];
75
76 pub fn music21_name(self) -> &'static str {
78 match self {
79 Self::DuplexMaxima => "duplex-maxima",
80 Self::Maxima => "maxima",
81 Self::Longa => "longa",
82 Self::Breve => "breve",
83 Self::Whole => "whole",
84 Self::Half => "half",
85 Self::Quarter => "quarter",
86 Self::Eighth => "eighth",
87 Self::Sixteenth => "16th",
88 Self::ThirtySecond => "32nd",
89 Self::SixtyFourth => "64th",
90 Self::HundredTwentyEighth => "128th",
91 Self::TwoHundredFiftySixth => "256th",
92 Self::FiveHundredTwelfth => "512th",
93 Self::TenTwentyFourth => "1024th",
94 Self::TwentyFortyEighth => "2048th",
95 Self::Zero => "zero",
96 }
97 }
98
99 pub fn quarter_length(self) -> FloatType {
101 match self {
102 Self::DuplexMaxima => 64.0,
103 Self::Maxima => 32.0,
104 Self::Longa => 16.0,
105 Self::Breve => 8.0,
106 Self::Whole => 4.0,
107 Self::Half => 2.0,
108 Self::Quarter => 1.0,
109 Self::Eighth => 0.5,
110 Self::Sixteenth => 0.25,
111 Self::ThirtySecond => 0.125,
112 Self::SixtyFourth => 0.0625,
113 Self::HundredTwentyEighth => 0.03125,
114 Self::TwoHundredFiftySixth => 0.015625,
115 Self::FiveHundredTwelfth => 0.0078125,
116 Self::TenTwentyFourth => 0.00390625,
117 Self::TwentyFortyEighth => 0.001953125,
118 Self::Zero => 0.0,
119 }
120 }
121
122 pub fn from_music21_name(name: &str) -> Option<Self> {
124 match name {
125 "duplex-maxima" => Some(Self::DuplexMaxima),
126 "maxima" => Some(Self::Maxima),
127 "longa" => Some(Self::Longa),
128 "breve" => Some(Self::Breve),
129 "whole" => Some(Self::Whole),
130 "half" => Some(Self::Half),
131 "quarter" => Some(Self::Quarter),
132 "eighth" => Some(Self::Eighth),
133 "16th" => Some(Self::Sixteenth),
134 "32nd" => Some(Self::ThirtySecond),
135 "64th" => Some(Self::SixtyFourth),
136 "128th" => Some(Self::HundredTwentyEighth),
137 "256th" => Some(Self::TwoHundredFiftySixth),
138 "512th" => Some(Self::FiveHundredTwelfth),
139 "1024th" => Some(Self::TenTwentyFourth),
140 "2048th" => Some(Self::TwentyFortyEighth),
141 "zero" => Some(Self::Zero),
142 _ => None,
143 }
144 }
145
146 pub fn from_quarter_length(quarter_length: FloatType) -> Option<Self> {
148 Self::ALL
149 .into_iter()
150 .find(|candidate| candidate.quarter_length() == quarter_length)
151 }
152
153 pub fn quarter_length_with_dots(self, dots: u32) -> FloatType {
158 self.quarter_length() * (2.0 - (0.5 as FloatType).powi(dots as i32))
159 }
160}
161
162impl Display for DurationType {
163 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164 f.write_str(self.music21_name())
165 }
166}
167
168impl FromStr for DurationType {
169 type Err = Error;
170
171 fn from_str(value: &str) -> Result<Self> {
172 Self::from_music21_name(value)
173 .ok_or_else(|| Error::Ordinal(format!("unknown duration type {value:?}")))
174 }
175}
176
177#[derive(Clone, Debug)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179pub struct Duration {
184 quarter_length: FloatType,
185}
186
187impl Duration {
188 pub fn new(quarter_length: FloatType) -> Result<Self> {
190 if !quarter_length.is_finite() || quarter_length < 0.0 {
191 return Err(Error::Ordinal(format!(
192 "duration quarter length must be finite and non-negative, got {quarter_length}"
193 )));
194 }
195
196 Ok(Self { quarter_length })
197 }
198
199 pub fn quarter() -> Self {
201 Self::default()
202 }
203
204 pub fn half() -> Self {
206 Self::new(2.0).expect("constant duration is valid")
207 }
208
209 pub fn whole() -> Self {
211 Self::new(4.0).expect("constant duration is valid")
212 }
213
214 pub fn eighth() -> Self {
216 Self::new(0.5).expect("constant duration is valid")
217 }
218
219 pub fn from_type(duration_type: DurationType) -> Self {
221 Self {
222 quarter_length: duration_type.quarter_length(),
223 }
224 }
225
226 pub fn from_type_with_dots(duration_type: DurationType, dots: u32) -> Self {
228 Self {
229 quarter_length: duration_type.quarter_length_with_dots(dots),
230 }
231 }
232
233 pub fn duration_type(&self) -> Option<DurationType> {
238 DurationType::from_quarter_length(self.quarter_length)
239 }
240
241 pub fn quarter_length(&self) -> FloatType {
243 self.quarter_length
244 }
245
246 pub fn set_quarter_length(&mut self, quarter_length: FloatType) -> Result<()> {
248 *self = Self::new(quarter_length)?;
249 Ok(())
250 }
251}
252
253impl Default for Duration {
254 fn default() -> Self {
255 Self {
256 quarter_length: 1.0,
257 }
258 }
259}
260
261impl PartialEq for Duration {
262 fn eq(&self, other: &Self) -> bool {
263 self.quarter_length == other.quarter_length
264 }
265}
266
267impl TryFrom<FloatType> for Duration {
268 type Error = Error;
269
270 fn try_from(value: FloatType) -> Result<Self> {
271 Self::new(value)
272 }
273}
274
275impl TryFrom<IntegerType> for Duration {
276 type Error = Error;
277
278 fn try_from(value: IntegerType) -> Result<Self> {
279 Self::new(value as FloatType)
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 const MUSIC21_TYPE_TO_DURATION: [(&str, FloatType); 17] = [
289 ("duplex-maxima", 64.0),
290 ("maxima", 32.0),
291 ("longa", 16.0),
292 ("breve", 8.0),
293 ("whole", 4.0),
294 ("half", 2.0),
295 ("quarter", 1.0),
296 ("eighth", 0.5),
297 ("16th", 0.25),
298 ("32nd", 0.125),
299 ("64th", 0.0625),
300 ("128th", 0.03125),
301 ("256th", 0.015625),
302 ("512th", 0.0078125),
303 ("1024th", 0.00390625),
304 ("2048th", 0.001953125),
305 ("zero", 0.0),
306 ];
307
308 #[test]
309 fn duration_types_match_music21s_table() {
310 assert_eq!(DurationType::ALL.len(), MUSIC21_TYPE_TO_DURATION.len());
311 for (duration_type, (name, quarter_length)) in
312 DurationType::ALL.into_iter().zip(MUSIC21_TYPE_TO_DURATION)
313 {
314 assert_eq!(duration_type.music21_name(), name);
315 assert_eq!(duration_type.quarter_length(), quarter_length, "{name}");
316 assert_eq!(DurationType::from_music21_name(name), Some(duration_type));
317 }
318 }
319
320 #[test]
321 fn duration_types_round_trip_through_their_names() {
322 for duration_type in DurationType::ALL {
323 let name = duration_type.music21_name();
324 assert_eq!(name.parse::<DurationType>().unwrap(), duration_type);
325 assert_eq!(duration_type.to_string(), name);
326 }
327 assert!("not-a-duration".parse::<DurationType>().is_err());
328 }
329
330 #[test]
331 fn each_type_is_half_the_one_before_it() {
332 let ordered = &DurationType::ALL[..DurationType::ALL.len() - 1];
334 for pair in ordered.windows(2) {
335 assert_eq!(
336 pair[1].quarter_length() * 2.0,
337 pair[0].quarter_length(),
338 "{} should be half of {}",
339 pair[1],
340 pair[0]
341 );
342 }
343 }
344
345 #[test]
346 fn dots_add_half_of_what_came_before() {
347 assert_eq!(DurationType::Half.quarter_length_with_dots(0), 2.0);
348 assert_eq!(DurationType::Half.quarter_length_with_dots(1), 3.0);
349 assert_eq!(DurationType::Half.quarter_length_with_dots(2), 3.5);
350 assert_eq!(DurationType::Half.quarter_length_with_dots(3), 3.75);
351 assert_eq!(DurationType::Quarter.quarter_length_with_dots(1), 1.5);
352 }
353
354 #[test]
355 fn durations_convert_to_and_from_note_values() {
356 assert_eq!(
357 Duration::from_type(DurationType::Whole).quarter_length(),
358 4.0
359 );
360 assert_eq!(
361 Duration::from_type(DurationType::Whole).duration_type(),
362 Some(DurationType::Whole)
363 );
364 assert_eq!(
365 Duration::from_type_with_dots(DurationType::Half, 1).quarter_length(),
366 3.0
367 );
368 assert_eq!(
370 Duration::from_type_with_dots(DurationType::Half, 1).duration_type(),
371 None
372 );
373 assert_eq!(Duration::new(1.0 / 3.0).unwrap().duration_type(), None);
375 }
376
377 #[test]
378 fn the_named_helpers_agree_with_their_types() {
379 assert_eq!(
380 Duration::quarter(),
381 Duration::from_type(DurationType::Quarter)
382 );
383 assert_eq!(Duration::half(), Duration::from_type(DurationType::Half));
384 assert_eq!(Duration::whole(), Duration::from_type(DurationType::Whole));
385 assert_eq!(
386 Duration::eighth(),
387 Duration::from_type(DurationType::Eighth)
388 );
389 }
390
391 #[test]
392 fn duration_tracks_quarter_lengths() {
393 assert_eq!(Duration::quarter().quarter_length(), 1.0);
394 assert_eq!(Duration::half().quarter_length(), 2.0);
395 assert_eq!(Duration::whole().quarter_length(), 4.0);
396 assert_eq!(Duration::eighth().quarter_length(), 0.5);
397 }
398
399 #[test]
400 fn duration_rejects_invalid_values() {
401 assert!(Duration::new(-1.0).is_err());
402 assert!(Duration::new(FloatType::INFINITY).is_err());
403 }
404
405 #[test]
406 fn duration_supports_conversions_and_updates() {
407 let mut duration = Duration::try_from(3 as IntegerType).unwrap();
408 assert_eq!(duration.quarter_length(), 3.0);
409
410 duration.set_quarter_length(1.5).unwrap();
411 assert_eq!(duration, Duration::try_from(1.5).unwrap());
412 assert!(duration.set_quarter_length(FloatType::NAN).is_err());
413 }
414}