Skip to main content

use_meter/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7pub mod prelude {
8    pub use crate::{
9        BarlineKind, BeatUnit, BeatsPerMeasure, MeasureNumber, MeterError, MeterKind,
10        MetricAccentPattern, PickupMeasureKind, TimeSignature,
11    };
12}
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct BeatsPerMeasure(u16);
15
16impl BeatsPerMeasure {
17    pub fn new(value: u16) -> Result<Self, MeterError> {
18        if !(1..=256).contains(&value) {
19            return Err(MeterError::OutOfRange);
20        }
21
22        Ok(Self(value))
23    }
24
25    pub const fn value(self) -> u16 {
26        self.0
27    }
28}
29
30impl fmt::Display for BeatsPerMeasure {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        self.0.fmt(formatter)
33    }
34}
35
36impl FromStr for BeatsPerMeasure {
37    type Err = MeterError;
38
39    fn from_str(value: &str) -> Result<Self, Self::Err> {
40        let parsed = value
41            .trim()
42            .parse::<u16>()
43            .map_err(|_| MeterError::InvalidFormat)?;
44        Self::new(parsed)
45    }
46}
47
48impl TryFrom<u16> for BeatsPerMeasure {
49    type Error = MeterError;
50
51    fn try_from(value: u16) -> Result<Self, Self::Error> {
52        Self::new(value)
53    }
54}
55#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
56pub struct MeasureNumber(u16);
57
58impl MeasureNumber {
59    pub fn new(value: u16) -> Result<Self, MeterError> {
60        if !(1..=65535).contains(&value) {
61            return Err(MeterError::OutOfRange);
62        }
63
64        Ok(Self(value))
65    }
66
67    pub const fn value(self) -> u16 {
68        self.0
69    }
70}
71
72impl fmt::Display for MeasureNumber {
73    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74        self.0.fmt(formatter)
75    }
76}
77
78impl FromStr for MeasureNumber {
79    type Err = MeterError;
80
81    fn from_str(value: &str) -> Result<Self, Self::Err> {
82        let parsed = value
83            .trim()
84            .parse::<u16>()
85            .map_err(|_| MeterError::InvalidFormat)?;
86        Self::new(parsed)
87    }
88}
89
90impl TryFrom<u16> for MeasureNumber {
91    type Error = MeterError;
92
93    fn try_from(value: u16) -> Result<Self, Self::Error> {
94        Self::new(value)
95    }
96}
97#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
98pub enum MeterKind {
99    Simple,
100    Compound,
101    Complex,
102    Additive,
103    Irregular,
104    Free,
105    Unknown,
106}
107
108impl MeterKind {
109    pub const ALL: &'static [Self] = &[
110        Self::Simple,
111        Self::Compound,
112        Self::Complex,
113        Self::Additive,
114        Self::Irregular,
115        Self::Free,
116        Self::Unknown,
117    ];
118
119    pub const fn as_str(self) -> &'static str {
120        match self {
121            Self::Simple => "simple",
122            Self::Compound => "compound",
123            Self::Complex => "complex",
124            Self::Additive => "additive",
125            Self::Irregular => "irregular",
126            Self::Free => "free",
127            Self::Unknown => "unknown",
128        }
129    }
130}
131
132impl fmt::Display for MeterKind {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        formatter.write_str(self.as_str())
135    }
136}
137
138impl FromStr for MeterKind {
139    type Err = MeterError;
140
141    fn from_str(value: &str) -> Result<Self, Self::Err> {
142        match normalized_label(value)?.as_str() {
143            "simple" => Ok(Self::Simple),
144            "compound" => Ok(Self::Compound),
145            "complex" => Ok(Self::Complex),
146            "additive" => Ok(Self::Additive),
147            "irregular" => Ok(Self::Irregular),
148            "free" => Ok(Self::Free),
149            "unknown" => Ok(Self::Unknown),
150            _ => Err(MeterError::UnknownLabel),
151        }
152    }
153}
154#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
155pub enum BeatUnit {
156    Whole,
157    Half,
158    Quarter,
159    Eighth,
160    Sixteenth,
161    ThirtySecond,
162}
163
164impl BeatUnit {
165    pub const ALL: &'static [Self] = &[
166        Self::Whole,
167        Self::Half,
168        Self::Quarter,
169        Self::Eighth,
170        Self::Sixteenth,
171        Self::ThirtySecond,
172    ];
173
174    pub const fn as_str(self) -> &'static str {
175        match self {
176            Self::Whole => "whole",
177            Self::Half => "half",
178            Self::Quarter => "quarter",
179            Self::Eighth => "eighth",
180            Self::Sixteenth => "sixteenth",
181            Self::ThirtySecond => "thirty-second",
182        }
183    }
184}
185
186impl fmt::Display for BeatUnit {
187    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188        formatter.write_str(self.as_str())
189    }
190}
191
192impl FromStr for BeatUnit {
193    type Err = MeterError;
194
195    fn from_str(value: &str) -> Result<Self, Self::Err> {
196        match normalized_label(value)?.as_str() {
197            "whole" => Ok(Self::Whole),
198            "half" => Ok(Self::Half),
199            "quarter" => Ok(Self::Quarter),
200            "eighth" => Ok(Self::Eighth),
201            "sixteenth" => Ok(Self::Sixteenth),
202            "thirty-second" => Ok(Self::ThirtySecond),
203            _ => Err(MeterError::UnknownLabel),
204        }
205    }
206}
207#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
208pub enum BarlineKind {
209    Single,
210    Double,
211    Final,
212    RepeatStart,
213    RepeatEnd,
214    RepeatBoth,
215}
216
217impl BarlineKind {
218    pub const ALL: &'static [Self] = &[
219        Self::Single,
220        Self::Double,
221        Self::Final,
222        Self::RepeatStart,
223        Self::RepeatEnd,
224        Self::RepeatBoth,
225    ];
226
227    pub const fn as_str(self) -> &'static str {
228        match self {
229            Self::Single => "single",
230            Self::Double => "double",
231            Self::Final => "final",
232            Self::RepeatStart => "repeat-start",
233            Self::RepeatEnd => "repeat-end",
234            Self::RepeatBoth => "repeat-both",
235        }
236    }
237}
238
239impl fmt::Display for BarlineKind {
240    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
241        formatter.write_str(self.as_str())
242    }
243}
244
245impl FromStr for BarlineKind {
246    type Err = MeterError;
247
248    fn from_str(value: &str) -> Result<Self, Self::Err> {
249        match normalized_label(value)?.as_str() {
250            "single" => Ok(Self::Single),
251            "double" => Ok(Self::Double),
252            "final" => Ok(Self::Final),
253            "repeat-start" => Ok(Self::RepeatStart),
254            "repeat-end" => Ok(Self::RepeatEnd),
255            "repeat-both" => Ok(Self::RepeatBoth),
256            _ => Err(MeterError::UnknownLabel),
257        }
258    }
259}
260#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
261pub enum PickupMeasureKind {
262    None,
263    Anacrusis,
264}
265
266impl PickupMeasureKind {
267    pub const ALL: &'static [Self] = &[Self::None, Self::Anacrusis];
268
269    pub const fn as_str(self) -> &'static str {
270        match self {
271            Self::None => "none",
272            Self::Anacrusis => "anacrusis",
273        }
274    }
275}
276
277impl fmt::Display for PickupMeasureKind {
278    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
279        formatter.write_str(self.as_str())
280    }
281}
282
283impl FromStr for PickupMeasureKind {
284    type Err = MeterError;
285
286    fn from_str(value: &str) -> Result<Self, Self::Err> {
287        match normalized_label(value)?.as_str() {
288            "none" => Ok(Self::None),
289            "anacrusis" => Ok(Self::Anacrusis),
290            _ => Err(MeterError::UnknownLabel),
291        }
292    }
293}
294#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
295pub struct TimeSignature {
296    numerator: u16,
297    denominator: u16,
298}
299
300impl TimeSignature {
301    pub fn new(numerator: u16, denominator: u16) -> Result<Self, MeterError> {
302        if numerator == 0 || !matches!(denominator, 1 | 2 | 4 | 8 | 16 | 32 | 64) {
303            return Err(MeterError::OutOfRange);
304        }
305        Ok(Self {
306            numerator,
307            denominator,
308        })
309    }
310
311    pub const fn numerator(self) -> u16 {
312        self.numerator
313    }
314    pub const fn denominator(self) -> u16 {
315        self.denominator
316    }
317    pub const fn is_common_time_like(self) -> bool {
318        self.numerator == 4 && self.denominator == 4
319    }
320    pub const fn is_cut_time_like(self) -> bool {
321        self.numerator == 2 && self.denominator == 2
322    }
323    pub const fn is_compound(self) -> bool {
324        self.numerator > 3 && self.numerator.is_multiple_of(3)
325    }
326    pub const fn is_simple(self) -> bool {
327        !self.is_compound()
328    }
329}
330
331#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
332pub struct MetricAccentPattern(Vec<u8>);
333
334impl MetricAccentPattern {
335    pub fn new(accents: impl Into<Vec<u8>>) -> Result<Self, MeterError> {
336        let accents = accents.into();
337        if accents.is_empty() {
338            return Err(MeterError::Empty);
339        }
340        Ok(Self(accents))
341    }
342    pub fn accents(&self) -> &[u8] {
343        &self.0
344    }
345}
346#[derive(Clone, Copy, Debug, Eq, PartialEq)]
347pub enum MeterError {
348    Empty,
349    InvalidFormat,
350    OutOfRange,
351    NonFinite,
352    NonPositive,
353    UnknownLabel,
354}
355
356impl fmt::Display for MeterError {
357    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
358        match self {
359            Self::Empty => formatter.write_str("meter metadata text cannot be empty"),
360            Self::InvalidFormat => formatter.write_str("meter metadata has an invalid format"),
361            Self::OutOfRange => formatter.write_str("meter metadata value is out of range"),
362            Self::NonFinite => formatter.write_str("meter metadata value must be finite"),
363            Self::NonPositive => formatter.write_str("meter metadata value must be positive"),
364            Self::UnknownLabel => formatter.write_str("unknown meter metadata label"),
365        }
366    }
367}
368
369impl Error for MeterError {}
370
371#[allow(dead_code)]
372fn non_empty_text(value: impl AsRef<str>) -> Result<String, MeterError> {
373    let trimmed = value.as_ref().trim();
374    if trimmed.is_empty() {
375        Err(MeterError::Empty)
376    } else {
377        Ok(trimmed.to_string())
378    }
379}
380
381fn normalized_label(value: &str) -> Result<String, MeterError> {
382    let trimmed = value.trim();
383    if trimmed.is_empty() {
384        Err(MeterError::Empty)
385    } else {
386        Ok(trimmed.to_ascii_lowercase().replace(['_', ' '], "-"))
387    }
388}
389#[cfg(test)]
390#[allow(
391    unused_imports,
392    clippy::unnecessary_wraps,
393    clippy::assertions_on_constants
394)]
395mod tests {
396    use super::{
397        BarlineKind, BeatUnit, BeatsPerMeasure, MeasureNumber, MeterError, MeterKind,
398        MetricAccentPattern, PickupMeasureKind, TimeSignature,
399    };
400    use core::{fmt, str::FromStr};
401
402    fn assert_enum_family<T>(variants: &[T]) -> Result<(), MeterError>
403    where
404        T: Copy + Eq + fmt::Debug + fmt::Display + FromStr<Err = MeterError>,
405    {
406        for variant in variants {
407            let label = variant.to_string();
408            assert_eq!(label.parse::<T>()?, *variant);
409            assert_eq!(label.replace('-', "_").parse::<T>()?, *variant);
410            assert_eq!(label.replace('-', " ").parse::<T>()?, *variant);
411        }
412        Ok(())
413    }
414
415    #[test]
416    fn validates_text_newtypes() -> Result<(), MeterError> {
417        assert!(true);
418        Ok(())
419    }
420
421    #[test]
422    fn validates_numeric_newtypes() -> Result<(), MeterError> {
423        let value = BeatsPerMeasure::new(1)?;
424        assert_eq!(value.value(), 1);
425        assert_eq!("1".parse::<BeatsPerMeasure>()?, value);
426        assert_eq!(BeatsPerMeasure::new(257), Err(MeterError::OutOfRange));
427        let value = MeasureNumber::new(1)?;
428        assert_eq!(value.value(), 1);
429        assert_eq!("1".parse::<MeasureNumber>()?, value);
430        assert_eq!(MeasureNumber::new(0), Err(MeterError::OutOfRange));
431        Ok(())
432    }
433
434    #[test]
435    fn displays_and_parses_enums() -> Result<(), MeterError> {
436        assert_enum_family(MeterKind::ALL)?;
437        assert_enum_family(BeatUnit::ALL)?;
438        assert_enum_family(BarlineKind::ALL)?;
439        assert_enum_family(PickupMeasureKind::ALL)?;
440        Ok(())
441    }
442
443    #[test]
444    fn validates_time_signatures() -> Result<(), MeterError> {
445        let common = TimeSignature::new(4, 4)?;
446        let cut = TimeSignature::new(2, 2)?;
447        let compound = TimeSignature::new(6, 8)?;
448        assert!(common.is_common_time_like());
449        assert!(cut.is_cut_time_like());
450        assert!(compound.is_compound());
451        assert_eq!(TimeSignature::new(0, 4), Err(MeterError::OutOfRange));
452        assert_eq!(TimeSignature::new(4, 3), Err(MeterError::OutOfRange));
453        Ok(())
454    }
455}