Skip to main content

klib/core/
octave.rs

1//! A module for the octave of a note.
2
3use std::ops::{Add, AddAssign, Sub, SubAssign};
4
5use std::sync::LazyLock;
6
7use crate::core::base::HasStaticName;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12// Traits.
13
14/// A trait for types that have an octave property.
15pub trait HasOctave {
16    /// Returns the octave of the type.
17    fn octave(&self) -> Octave;
18}
19
20// Enum.
21
22/// An enum representing the octave of a note.
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Default, Ord, PartialOrd)]
25#[repr(u8)]
26pub enum Octave {
27    /// The octave 0.
28    Zero,
29    /// The octave 1.
30    One,
31    /// The octave 2.
32    Two,
33    /// The octave 3.
34    Three,
35    /// The octave 4.
36    #[default]
37    Four,
38    /// The octave 5.
39    Five,
40    /// The octave 6.
41    Six,
42    /// The octave 7.
43    Seven,
44    /// The octave 8.
45    Eight,
46    /// The octave 9.
47    Nine,
48    /// The octave 10.
49    Ten,
50    /// The octave 11.
51    Eleven,
52    /// The octave 12.
53    Twelve,
54    /// The octave 13.
55    Thirteen,
56    /// The octave 14.
57    Fourteen,
58    /// The octave 15.
59    Fifteen,
60}
61
62// Octave impls.
63
64impl HasStaticName for Octave {
65    #[inline]
66    fn static_name(&self) -> &'static str {
67        match self {
68            Octave::Zero => "0",
69            Octave::One => "1",
70            Octave::Two => "2",
71            Octave::Three => "3",
72            Octave::Four => "4",
73            Octave::Five => "5",
74            Octave::Six => "6",
75            Octave::Seven => "7",
76            Octave::Eight => "8",
77            Octave::Nine => "9",
78            Octave::Ten => "10",
79            Octave::Eleven => "11",
80            Octave::Twelve => "12",
81            Octave::Thirteen => "13",
82            Octave::Fourteen => "14",
83            Octave::Fifteen => "15",
84        }
85    }
86}
87
88impl Add for Octave {
89    type Output = Self;
90
91    fn add(self, rhs: Self) -> Self::Output {
92        let new_octave = self as u8 + rhs as u8;
93
94        assert!(new_octave <= 15, "Octave overflow");
95
96        // SAFETY: The new octave is guaranteed to be less than or equal to 15.
97        unsafe { std::mem::transmute(new_octave) }
98    }
99}
100
101impl Sub for Octave {
102    type Output = Self;
103
104    fn sub(self, rhs: Self) -> Self::Output {
105        let new_octave = (self as u8).checked_sub(rhs as u8).expect("Octave underflow.");
106
107        assert!(new_octave <= 15, "Octave overflow");
108
109        // SAFETY: The new octave is guaranteed to be less than or equal to 15.
110        unsafe { std::mem::transmute(new_octave) }
111    }
112}
113
114impl TryFrom<u8> for Octave {
115    type Error = &'static str;
116
117    fn try_from(value: u8) -> Result<Self, Self::Error> {
118        if value > 15 {
119            Err("Octave overflow.")
120        } else {
121            // SAFETY: The new octave is guaranteed to be less than or equal to 15.
122            Ok(unsafe { std::mem::transmute::<u8, Octave>(value) })
123        }
124    }
125}
126
127impl Add<i8> for Octave {
128    type Output = Self;
129
130    fn add(self, rhs: i8) -> Self::Output {
131        let new_octave = self as i8 + rhs;
132
133        if new_octave > 15 {
134            panic!("Octave overflow.");
135        } else if new_octave < 0 {
136            panic!("Octave underflow.");
137        }
138
139        // SAFETY: The new octave is guaranteed to be less than or equal to 15.
140        unsafe { std::mem::transmute(new_octave) }
141    }
142}
143
144impl Sub<i8> for Octave {
145    type Output = Self;
146
147    fn sub(self, rhs: i8) -> Self::Output {
148        self + (-rhs)
149    }
150}
151
152impl AddAssign for Octave {
153    fn add_assign(&mut self, rhs: Self) {
154        *self = *self + rhs;
155    }
156}
157
158impl AddAssign<i8> for Octave {
159    fn add_assign(&mut self, rhs: i8) {
160        *self = *self + rhs;
161    }
162}
163
164impl SubAssign<i8> for Octave {
165    fn sub_assign(&mut self, rhs: i8) {
166        *self = *self - rhs;
167    }
168}
169
170impl HasOctave for Octave {
171    fn octave(&self) -> Octave {
172        *self
173    }
174}
175
176// Statics.
177
178/// An array of all octaves.
179pub static ALL_OCTAVES: LazyLock<[Octave; 16]> = LazyLock::new(|| {
180    [
181        Octave::Zero,
182        Octave::One,
183        Octave::Two,
184        Octave::Three,
185        Octave::Four,
186        Octave::Five,
187        Octave::Six,
188        Octave::Seven,
189        Octave::Eight,
190        Octave::Nine,
191        Octave::Ten,
192        Octave::Eleven,
193        Octave::Twelve,
194        Octave::Thirteen,
195        Octave::Fourteen,
196        Octave::Fifteen,
197    ]
198});
199
200// Tests.
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::core::octave::HasOctave;
206    use pretty_assertions::assert_eq;
207
208    #[test]
209    #[should_panic]
210    fn test_self_overflow() {
211        let _ = Octave::Fifteen + Octave::One;
212    }
213
214    #[test]
215    #[should_panic]
216    fn test_i8_add_overflow() {
217        let _ = Octave::Fifteen + 1;
218    }
219
220    #[test]
221    #[should_panic]
222    fn test_i8_add_underflow() {
223        let _ = Octave::Zero + -1;
224    }
225
226    #[test]
227    #[should_panic]
228    fn test_i8_sub_overflow() {
229        let _ = Octave::Fifteen - -1;
230    }
231
232    #[test]
233    #[should_panic]
234    fn test_i8_sub_underflow() {
235        let _ = Octave::Zero - 1;
236    }
237
238    #[test]
239    fn test_add_assign_self() {
240        let mut a = Octave::Four;
241        a += Octave::One;
242        assert_eq!(a, Octave::Five);
243    }
244
245    #[test]
246    fn test_add_assign_i8() {
247        let mut a = Octave::Four;
248        a += 1;
249        assert_eq!(a, Octave::Five);
250    }
251
252    #[test]
253    fn test_sub_assign_i8() {
254        let mut a = Octave::Four;
255        a -= 1;
256        assert_eq!(a, Octave::Three);
257    }
258
259    #[test]
260    fn test_properties() {
261        assert_eq!(Octave::Four.octave(), Octave::Four);
262        assert_eq!(Octave::default(), Octave::Four);
263    }
264
265    #[test]
266    fn test_names() {
267        assert_eq!(ALL_OCTAVES.map(|o| o.static_name()).join(" "), "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15");
268    }
269}