1use std::ops::{Add, Sub};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::core::{
9 base::HasStaticName,
10 pitch::{HasPitch, Pitch},
11};
12
13pub trait HasNamedPitch {
17 fn named_pitch(&self) -> NamedPitch;
19}
20
21pub trait HasLetter {
23 fn letter(&self) -> &'static str;
25}
26
27#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[repr(u8)]
37pub enum NamedPitch {
38 FTripleFlat,
40 CTripleFlat,
42 GTripleFlat,
44 DTripleFlat,
46 ATripleFlat,
48 ETripleFlat,
50 BTripleFlat,
52
53 FDoubleFlat,
55 CDoubleFlat,
57 GDoubleFlat,
59 DDoubleFlat,
61 ADoubleFlat,
63 EDoubleFlat,
65 BDoubleFlat,
67
68 FFlat,
70 CFlat,
72 GFlat,
74 DFlat,
76 AFlat,
78 EFlat,
80 BFlat,
82
83 F,
85 C,
87 G,
89 D,
91 A,
93 E,
95 B,
97
98 FSharp,
100 CSharp,
102 GSharp,
104 DSharp,
106 ASharp,
108 ESharp,
110 BSharp,
112
113 FDoubleSharp,
115 CDoubleSharp,
117 GDoubleSharp,
119 DDoubleSharp,
121 ADoubleSharp,
123 EDoubleSharp,
125 BDoubleSharp,
127
128 FTripleSharp,
130 CTripleSharp,
132 GTripleSharp,
134 DTripleSharp,
136 ATripleSharp,
138 ETripleSharp,
140 BTripleSharp,
142}
143
144impl HasNamedPitch for NamedPitch {
147 fn named_pitch(&self) -> NamedPitch {
148 *self
149 }
150}
151
152impl HasLetter for NamedPitch {
153 #[coverage(off)]
154 fn letter(&self) -> &'static str {
155 match self {
156 NamedPitch::FTripleFlat => "F",
157 NamedPitch::CTripleFlat => "C",
158 NamedPitch::GTripleFlat => "G",
159 NamedPitch::DTripleFlat => "D",
160 NamedPitch::ATripleFlat => "A",
161 NamedPitch::ETripleFlat => "E",
162 NamedPitch::BTripleFlat => "B",
163
164 NamedPitch::FDoubleFlat => "F",
165 NamedPitch::CDoubleFlat => "C",
166 NamedPitch::GDoubleFlat => "G",
167 NamedPitch::DDoubleFlat => "D",
168 NamedPitch::ADoubleFlat => "A",
169 NamedPitch::EDoubleFlat => "E",
170 NamedPitch::BDoubleFlat => "B",
171
172 NamedPitch::FFlat => "F",
173 NamedPitch::CFlat => "C",
174 NamedPitch::GFlat => "G",
175 NamedPitch::DFlat => "D",
176 NamedPitch::AFlat => "A",
177 NamedPitch::EFlat => "E",
178 NamedPitch::BFlat => "B",
179
180 NamedPitch::F => "F",
181 NamedPitch::C => "C",
182 NamedPitch::G => "G",
183 NamedPitch::D => "D",
184 NamedPitch::A => "A",
185 NamedPitch::E => "E",
186 NamedPitch::B => "B",
187
188 NamedPitch::FSharp => "F",
189 NamedPitch::CSharp => "C",
190 NamedPitch::GSharp => "G",
191 NamedPitch::DSharp => "D",
192 NamedPitch::ASharp => "A",
193 NamedPitch::ESharp => "E",
194 NamedPitch::BSharp => "B",
195
196 NamedPitch::FDoubleSharp => "F",
197 NamedPitch::CDoubleSharp => "C",
198 NamedPitch::GDoubleSharp => "G",
199 NamedPitch::DDoubleSharp => "D",
200 NamedPitch::ADoubleSharp => "A",
201 NamedPitch::EDoubleSharp => "E",
202 NamedPitch::BDoubleSharp => "B",
203
204 NamedPitch::FTripleSharp => "F",
205 NamedPitch::CTripleSharp => "C",
206 NamedPitch::GTripleSharp => "G",
207 NamedPitch::DTripleSharp => "D",
208 NamedPitch::ATripleSharp => "A",
209 NamedPitch::ETripleSharp => "E",
210 NamedPitch::BTripleSharp => "B",
211 }
212 }
213}
214
215impl HasStaticName for NamedPitch {
216 #[coverage(off)]
217 fn static_name(&self) -> &'static str {
218 match self {
219 NamedPitch::FTripleFlat => "F♭𝄫",
220 NamedPitch::CTripleFlat => "C♭𝄫",
221 NamedPitch::GTripleFlat => "G♭𝄫",
222 NamedPitch::DTripleFlat => "D♭𝄫",
223 NamedPitch::ATripleFlat => "A♭𝄫",
224 NamedPitch::ETripleFlat => "E♭𝄫",
225 NamedPitch::BTripleFlat => "B♭𝄫",
226
227 NamedPitch::FDoubleFlat => "F𝄫",
228 NamedPitch::CDoubleFlat => "C𝄫",
229 NamedPitch::GDoubleFlat => "G𝄫",
230 NamedPitch::DDoubleFlat => "D𝄫",
231 NamedPitch::ADoubleFlat => "A𝄫",
232 NamedPitch::EDoubleFlat => "E𝄫",
233 NamedPitch::BDoubleFlat => "B𝄫",
234
235 NamedPitch::FFlat => "F♭",
236 NamedPitch::CFlat => "C♭",
237 NamedPitch::GFlat => "G♭",
238 NamedPitch::DFlat => "D♭",
239 NamedPitch::AFlat => "A♭",
240 NamedPitch::EFlat => "E♭",
241 NamedPitch::BFlat => "B♭",
242
243 NamedPitch::F => "F",
244 NamedPitch::C => "C",
245 NamedPitch::G => "G",
246 NamedPitch::D => "D",
247 NamedPitch::A => "A",
248 NamedPitch::E => "E",
249 NamedPitch::B => "B",
250
251 NamedPitch::FSharp => "F♯",
252 NamedPitch::CSharp => "C♯",
253 NamedPitch::GSharp => "G♯",
254 NamedPitch::DSharp => "D♯",
255 NamedPitch::ASharp => "A♯",
256 NamedPitch::ESharp => "E♯",
257 NamedPitch::BSharp => "B♯",
258
259 NamedPitch::FDoubleSharp => "F𝄪",
260 NamedPitch::CDoubleSharp => "C𝄪",
261 NamedPitch::GDoubleSharp => "G𝄪",
262 NamedPitch::DDoubleSharp => "D𝄪",
263 NamedPitch::ADoubleSharp => "A𝄪",
264 NamedPitch::EDoubleSharp => "E𝄪",
265 NamedPitch::BDoubleSharp => "B𝄪",
266
267 NamedPitch::FTripleSharp => "F♯𝄪",
268 NamedPitch::CTripleSharp => "C♯𝄪",
269 NamedPitch::GTripleSharp => "G♯𝄪",
270 NamedPitch::DTripleSharp => "D♯𝄪",
271 NamedPitch::ATripleSharp => "A♯𝄪",
272 NamedPitch::ETripleSharp => "E♯𝄪",
273 NamedPitch::BTripleSharp => "B♯𝄪",
274 }
275 }
276}
277
278impl HasPitch for NamedPitch {
279 #[coverage(off)]
280 fn pitch(&self) -> Pitch {
281 match self {
282 NamedPitch::FTripleFlat => Pitch::D,
283 NamedPitch::CTripleFlat => Pitch::A,
284 NamedPitch::GTripleFlat => Pitch::E,
285 NamedPitch::DTripleFlat => Pitch::B,
286 NamedPitch::ATripleFlat => Pitch::GFlat,
287 NamedPitch::ETripleFlat => Pitch::DFlat,
288 NamedPitch::BTripleFlat => Pitch::AFlat,
289
290 NamedPitch::FDoubleFlat => Pitch::EFlat,
291 NamedPitch::CDoubleFlat => Pitch::BFlat,
292 NamedPitch::GDoubleFlat => Pitch::F,
293 NamedPitch::DDoubleFlat => Pitch::C,
294 NamedPitch::ADoubleFlat => Pitch::G,
295 NamedPitch::EDoubleFlat => Pitch::D,
296 NamedPitch::BDoubleFlat => Pitch::A,
297
298 NamedPitch::FFlat => Pitch::E,
299 NamedPitch::CFlat => Pitch::B,
300 NamedPitch::GFlat => Pitch::GFlat,
301 NamedPitch::DFlat => Pitch::DFlat,
302 NamedPitch::AFlat => Pitch::AFlat,
303 NamedPitch::EFlat => Pitch::EFlat,
304 NamedPitch::BFlat => Pitch::BFlat,
305
306 NamedPitch::F => Pitch::F,
307 NamedPitch::C => Pitch::C,
308 NamedPitch::G => Pitch::G,
309 NamedPitch::D => Pitch::D,
310 NamedPitch::A => Pitch::A,
311 NamedPitch::E => Pitch::E,
312 NamedPitch::B => Pitch::B,
313
314 NamedPitch::FSharp => Pitch::GFlat,
315 NamedPitch::CSharp => Pitch::DFlat,
316 NamedPitch::GSharp => Pitch::AFlat,
317 NamedPitch::DSharp => Pitch::EFlat,
318 NamedPitch::ASharp => Pitch::BFlat,
319 NamedPitch::ESharp => Pitch::F,
320 NamedPitch::BSharp => Pitch::C,
321
322 NamedPitch::FDoubleSharp => Pitch::G,
323 NamedPitch::CDoubleSharp => Pitch::D,
324 NamedPitch::GDoubleSharp => Pitch::A,
325 NamedPitch::DDoubleSharp => Pitch::E,
326 NamedPitch::ADoubleSharp => Pitch::B,
327 NamedPitch::EDoubleSharp => Pitch::GFlat,
328 NamedPitch::BDoubleSharp => Pitch::DFlat,
329
330 NamedPitch::FTripleSharp => Pitch::AFlat,
331 NamedPitch::CTripleSharp => Pitch::EFlat,
332 NamedPitch::GTripleSharp => Pitch::BFlat,
333 NamedPitch::DTripleSharp => Pitch::F,
334 NamedPitch::ATripleSharp => Pitch::C,
335 NamedPitch::ETripleSharp => Pitch::G,
336 NamedPitch::BTripleSharp => Pitch::D,
337 }
338 }
339}
340
341impl Add<i8> for NamedPitch {
342 type Output = Self;
343
344 fn add(self, rhs: i8) -> Self {
345 let index = ALL_PITCHES.iter().position(|&p| p == self).unwrap();
346
347 let new_index = index as i8 + rhs;
348
349 assert!((0..=49).contains(&new_index), "NamedPitch out of range.");
350
351 ALL_PITCHES[new_index as usize]
352 }
353}
354
355impl Sub<i8> for NamedPitch {
356 type Output = Self;
357
358 fn sub(self, rhs: i8) -> Self {
359 self + (-rhs)
360 }
361}
362
363impl From<Pitch> for NamedPitch {
364 fn from(pitch: Pitch) -> Self {
365 NamedPitch::from(&pitch)
366 }
367}
368
369impl From<&Pitch> for NamedPitch {
370 fn from(pitch: &Pitch) -> Self {
371 match pitch {
372 Pitch::C => NamedPitch::C,
373 Pitch::DFlat => NamedPitch::DFlat,
374 Pitch::D => NamedPitch::D,
375 Pitch::EFlat => NamedPitch::EFlat,
376 Pitch::E => NamedPitch::E,
377 Pitch::F => NamedPitch::F,
378 Pitch::GFlat => NamedPitch::GFlat,
379 Pitch::G => NamedPitch::G,
380 Pitch::AFlat => NamedPitch::AFlat,
381 Pitch::A => NamedPitch::A,
382 Pitch::BFlat => NamedPitch::BFlat,
383 Pitch::B => NamedPitch::B,
384 }
385 }
386}
387
388static ALL_PITCHES: [NamedPitch; 49] = [
391 NamedPitch::FTripleFlat,
392 NamedPitch::CTripleFlat,
393 NamedPitch::GTripleFlat,
394 NamedPitch::DTripleFlat,
395 NamedPitch::ATripleFlat,
396 NamedPitch::ETripleFlat,
397 NamedPitch::BTripleFlat,
398 NamedPitch::FDoubleFlat,
399 NamedPitch::CDoubleFlat,
400 NamedPitch::GDoubleFlat,
401 NamedPitch::DDoubleFlat,
402 NamedPitch::ADoubleFlat,
403 NamedPitch::EDoubleFlat,
404 NamedPitch::BDoubleFlat,
405 NamedPitch::FFlat,
406 NamedPitch::CFlat,
407 NamedPitch::GFlat,
408 NamedPitch::DFlat,
409 NamedPitch::AFlat,
410 NamedPitch::EFlat,
411 NamedPitch::BFlat,
412 NamedPitch::F,
413 NamedPitch::C,
414 NamedPitch::G,
415 NamedPitch::D,
416 NamedPitch::A,
417 NamedPitch::E,
418 NamedPitch::B,
419 NamedPitch::FSharp,
420 NamedPitch::CSharp,
421 NamedPitch::GSharp,
422 NamedPitch::DSharp,
423 NamedPitch::ASharp,
424 NamedPitch::ESharp,
425 NamedPitch::BSharp,
426 NamedPitch::FDoubleSharp,
427 NamedPitch::CDoubleSharp,
428 NamedPitch::GDoubleSharp,
429 NamedPitch::DDoubleSharp,
430 NamedPitch::ADoubleSharp,
431 NamedPitch::EDoubleSharp,
432 NamedPitch::BDoubleSharp,
433 NamedPitch::FTripleSharp,
434 NamedPitch::CTripleSharp,
435 NamedPitch::GTripleSharp,
436 NamedPitch::DTripleSharp,
437 NamedPitch::ATripleSharp,
438 NamedPitch::ETripleSharp,
439 NamedPitch::BTripleSharp,
440];
441
442#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::core::note::*;
448 use pretty_assertions::assert_eq;
449
450 #[test]
451 #[should_panic]
452 fn test_improper_add() {
453 let _ = C.named_pitch() + 50;
454 }
455
456 #[test]
457 fn test_properties() {
458 assert_eq!(NamedPitch::A.named_pitch(), NamedPitch::A);
459 }
460
461 #[test]
462 fn test_pitch_conversion() {
463 assert_eq!(NamedPitch::from(Pitch::C), NamedPitch::C);
464 assert_eq!(NamedPitch::from(&Pitch::C), NamedPitch::C);
465 assert_eq!(NamedPitch::from(Pitch::DFlat), NamedPitch::DFlat);
466 assert_eq!(NamedPitch::from(&Pitch::DFlat), NamedPitch::DFlat);
467 assert_eq!(NamedPitch::from(Pitch::D), NamedPitch::D);
468 assert_eq!(NamedPitch::from(&Pitch::D), NamedPitch::D);
469 assert_eq!(NamedPitch::from(Pitch::EFlat), NamedPitch::EFlat);
470 assert_eq!(NamedPitch::from(&Pitch::EFlat), NamedPitch::EFlat);
471 assert_eq!(NamedPitch::from(Pitch::E), NamedPitch::E);
472 assert_eq!(NamedPitch::from(&Pitch::E), NamedPitch::E);
473 assert_eq!(NamedPitch::from(Pitch::F), NamedPitch::F);
474 assert_eq!(NamedPitch::from(&Pitch::F), NamedPitch::F);
475 assert_eq!(NamedPitch::from(Pitch::GFlat), NamedPitch::GFlat);
476 assert_eq!(NamedPitch::from(&Pitch::GFlat), NamedPitch::GFlat);
477 assert_eq!(NamedPitch::from(Pitch::G), NamedPitch::G);
478 assert_eq!(NamedPitch::from(&Pitch::G), NamedPitch::G);
479 assert_eq!(NamedPitch::from(Pitch::AFlat), NamedPitch::AFlat);
480 assert_eq!(NamedPitch::from(&Pitch::AFlat), NamedPitch::AFlat);
481 assert_eq!(NamedPitch::from(Pitch::A), NamedPitch::A);
482 assert_eq!(NamedPitch::from(&Pitch::A), NamedPitch::A);
483 assert_eq!(NamedPitch::from(Pitch::BFlat), NamedPitch::BFlat);
484 assert_eq!(NamedPitch::from(&Pitch::BFlat), NamedPitch::BFlat);
485 assert_eq!(NamedPitch::from(Pitch::B), NamedPitch::B);
486 assert_eq!(NamedPitch::from(&Pitch::B), NamedPitch::B);
487 }
488}