note_pen/
time_signature.rs

1use crate::duration::PrimitiveDuration;
2
3#[derive(Clone, Debug)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5pub enum TimeSignatureSymbol {
6    CommonTime,
7    CutTime,
8    Custom(String)
9}
10
11/// Represents a time signature.
12///
13/// Internally, it stores the number of notes in a measure and the beat value.
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct TimeSignature {
17    pub(crate) notes: u64,
18    pub(crate) beat_value: u64,
19    pub(crate) symbol: Option<TimeSignatureSymbol>
20}
21
22impl TimeSignature {
23    /// 4/4 time signature
24    pub const COMMON_TIME: Self = Self {
25        notes: 4,
26        beat_value: 4,
27        symbol: Some(TimeSignatureSymbol::CommonTime)
28    };
29    /// 2/2 time signature
30    pub const CUT_TIME: Self = Self {
31        notes: 2,
32        beat_value: 2,
33        symbol: Some(TimeSignatureSymbol::CutTime)
34    };
35
36    /// Create a new time signature.
37    /// It takes the number of notes in a measure and the beat value.
38    /// # Examples
39    /// ```rust
40    /// use note_pen::prelude::*;
41    /// let time_signature = TimeSignature::new(4, 4);
42    /// assert_eq!(time_signature, TimeSignature::COMMON_TIME);
43    /// let time_signature = TimeSignature::new(2, 2);
44    /// assert_eq!(time_signature, TimeSignature::CUT_TIME);
45    /// ```
46    #[inline]
47    pub const fn new(notes: u64, beat_value: u64) -> Self {
48        Self {
49            notes,
50            beat_value,
51            symbol: None
52        }
53    }
54
55    /// Create a simple time signature.
56    #[inline]
57    pub const fn simple(beats: u64, value: PrimitiveDuration) -> Self {
58        Self::new(beats, value.value())
59    }
60
61    /// Create a compound time signature.
62    ///
63    /// # Examples
64    /// ```rust
65    /// use note_pen::prelude::*;
66    /// // 6/8 time signature
67    /// let time_signature = TimeSignature::compound(2, PrimitiveDuration::EIGHTH);
68    /// let expected = TimeSignature::new(6, 8);
69    /// assert_eq!(time_signature, expected);
70    /// ```
71    #[inline]
72    pub const fn compound(beats: u64, value: PrimitiveDuration) -> Self {
73        Self::new(beats * 3, value.value())
74    }
75
76    /// Check if the time signature is compound.
77    /// # Examples
78    /// ```rust
79    /// use note_pen::prelude::*;
80    /// let time_signature = TimeSignature::new(6, 8);
81    /// assert!(time_signature.is_compound());
82    /// ```
83    #[inline]
84    pub const fn is_compound(&self) -> bool {
85        self.notes % 3 == 0
86    }
87
88    /// Check if the time signature is simple.
89    /// # Examples
90    /// ```rust
91    /// use note_pen::prelude::*;
92    /// let time_signature = TimeSignature::new(4, 4);
93    /// assert!(time_signature.is_simple());
94    /// let time_signature = TimeSignature::new(5, 4);
95    /// assert!(time_signature.is_simple());
96    #[inline]
97    pub const fn is_simple(&self) -> bool {
98        !self.is_compound()
99    }
100
101    /// Get the number of beats in a measure.
102    /// For compound time signatures, it returns the number of dotted notes
103    /// -- the number of notes in a measure divided by 3.
104    /// # Examples
105    /// ```rust
106    /// use note_pen::prelude::*;
107    /// let time_signature = TimeSignature::new(6, 8);
108    /// assert_eq!(time_signature.beats(), 2);
109    /// let time_signature = TimeSignature::new(4, 4);
110    /// assert_eq!(time_signature.beats(), 4);
111    /// ```
112    #[inline]
113    pub const fn beats(&self) -> u64 {
114        if self.is_compound() {
115            self.notes / 3
116        } else {
117            self.notes
118        }
119    }
120
121    /// Get the beat value.
122    /// # Examples
123    /// ```rust
124    /// use note_pen::prelude::*;
125    /// let time_signature = TimeSignature::new(6, 8);
126    /// assert_eq!(time_signature.value(), PrimitiveDuration::EIGHTH);
127    /// let time_signature = TimeSignature::new(4, 4);
128    /// assert_eq!(time_signature.value(), PrimitiveDuration::QUARTER);
129    /// ```
130    /// # Panics
131    /// It panics
132    /// if the beat value is invalid and [`PrimitiveDuration::try_from`] returns an error.
133    #[inline]
134    pub fn value(&self) -> PrimitiveDuration {
135        PrimitiveDuration::try_from(self.beat_value).expect("Invalid time signature value")
136    }
137}
138
139impl PartialEq for TimeSignature {
140    fn eq(&self, other: &Self) -> bool {
141        self.notes == other.notes && self.beat_value == other.beat_value
142    }
143}
144
145#[cfg(feature = "midi")]
146mod midi {
147    use crate::duration::PrimitiveDuration;
148    use crate::TimeSignature;
149    use midi_file::core::{Clocks, DurationName};
150
151    impl TimeSignature {
152        /// Convert the time signature to MIDI time signature.
153        pub fn denominator_to_midi(&self) -> DurationName {
154            match self.value() {
155                PrimitiveDuration::WHOLE => DurationName::Whole,
156                PrimitiveDuration::HALF => DurationName::Half,
157                PrimitiveDuration::QUARTER => DurationName::Quarter,
158                PrimitiveDuration::EIGHTH => DurationName::Eighth,
159                PrimitiveDuration::SIXTEENTH => DurationName::Sixteenth,
160                _ => unimplemented!("Unsupported time signature: {:?}", self),
161            }
162        }
163
164        pub fn midi_clicks(&self) -> Clocks {
165            if self.is_compound() {
166                match self.value() {
167                    PrimitiveDuration::HALF => Clocks::DottedWhole,
168                    PrimitiveDuration::QUARTER => Clocks::DottedHalf,
169                    PrimitiveDuration::EIGHTH => Clocks::DottedQuarter,
170                    PrimitiveDuration::SIXTEENTH => Clocks::DottedEighth,
171                    PrimitiveDuration::THIRTY_SECOND => Clocks::DottedSixteenth,
172                    _ => unimplemented!("Unsupported time signature: {:?}", self),
173                }
174            } else {
175                match self.value() {
176                    PrimitiveDuration::WHOLE => Clocks::Whole,
177                    PrimitiveDuration::HALF => Clocks::Half,
178                    PrimitiveDuration::QUARTER => Clocks::Quarter,
179                    PrimitiveDuration::EIGHTH => Clocks::Eighth,
180                    PrimitiveDuration::SIXTEENTH => Clocks::Sixteenth,
181                    _ => unimplemented!("Unsupported time signature: {:?}", self),
182                }
183            }
184        }
185    }
186}