1use 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
12pub trait HasOctave {
16 fn octave(&self) -> Octave;
18}
19
20#[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 Zero,
29 One,
31 Two,
33 Three,
35 #[default]
37 Four,
38 Five,
40 Six,
42 Seven,
44 Eight,
46 Nine,
48 Ten,
50 Eleven,
52 Twelve,
54 Thirteen,
56 Fourteen,
58 Fifteen,
60}
61
62impl 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 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 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 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 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
176pub 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#[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}