Skip to main content

klib/core/
named_pitch.rs

1//! A module for working with named pitches.
2
3use 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
13// Traits.
14
15/// A trait for types that have a named pitch.
16pub trait HasNamedPitch {
17    /// Returns the named pitch of the type.
18    fn named_pitch(&self) -> NamedPitch;
19}
20
21/// A trait for types that have a letter.
22pub trait HasLetter {
23    /// Returns the letter of the type.
24    fn letter(&self) -> &'static str;
25}
26
27// Enum.
28
29/// An enum representing named pitch.
30///
31/// A [`NamedPitch`] is a pitch that has a name, such as `C` or `F♯`.
32/// While a [`Pitch`] is a pitch that has a frequency, a [`NamedPitch`] is a pitch that has an
33/// enharmonic name (could share the same pitch with another).
34#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[repr(u8)]
37pub enum NamedPitch {
38    /// The pitch F triple flat.
39    FTripleFlat,
40    /// The pitch C triple flat.
41    CTripleFlat,
42    /// The pitch G triple flat.
43    GTripleFlat,
44    /// The pitch D triple flat.
45    DTripleFlat,
46    /// The pitch A triple flat.
47    ATripleFlat,
48    /// The pitch E triple flat.
49    ETripleFlat,
50    /// The pitch B triple flat.
51    BTripleFlat,
52
53    /// The pitch F double flat.
54    FDoubleFlat,
55    /// The pitch C double flat.
56    CDoubleFlat,
57    /// The pitch G double flat.
58    GDoubleFlat,
59    /// The pitch D double flat.
60    DDoubleFlat,
61    /// The pitch A double flat.
62    ADoubleFlat,
63    /// The pitch E double flat.
64    EDoubleFlat,
65    /// The pitch B double flat.
66    BDoubleFlat,
67
68    /// The pitch F flat.
69    FFlat,
70    /// The pitch C flat.
71    CFlat,
72    /// The pitch G flat.
73    GFlat,
74    /// The pitch D flat.
75    DFlat,
76    /// The pitch A flat.
77    AFlat,
78    /// The pitch E flat.
79    EFlat,
80    /// The pitch B flat.
81    BFlat,
82
83    /// The pitch F.
84    F,
85    /// The pitch C.
86    C,
87    /// The pitch G.
88    G,
89    /// The pitch D.
90    D,
91    /// The pitch A.
92    A,
93    /// The pitch E.
94    E,
95    /// The pitch B.
96    B,
97
98    /// The pitch F sharp.
99    FSharp,
100    /// The pitch C sharp.
101    CSharp,
102    /// The pitch G sharp.
103    GSharp,
104    /// The pitch D sharp.
105    DSharp,
106    /// The pitch A sharp.
107    ASharp,
108    /// The pitch E sharp.
109    ESharp,
110    /// The pitch B sharp.
111    BSharp,
112
113    /// The pitch F double sharp.
114    FDoubleSharp,
115    /// The pitch C double sharp.
116    CDoubleSharp,
117    /// The pitch G double sharp.
118    GDoubleSharp,
119    /// The pitch D double sharp.
120    DDoubleSharp,
121    /// The pitch A double sharp.
122    ADoubleSharp,
123    /// The pitch E double sharp.
124    EDoubleSharp,
125    /// The pitch B double sharp.
126    BDoubleSharp,
127
128    /// The pitch F triple sharp.
129    FTripleSharp,
130    /// The pitch C triple sharp.
131    CTripleSharp,
132    /// The pitch G triple sharp.
133    GTripleSharp,
134    /// The pitch D triple sharp.
135    DTripleSharp,
136    /// The pitch A triple sharp.
137    ATripleSharp,
138    /// The pitch E triple sharp.
139    ETripleSharp,
140    /// The pitch B triple sharp.
141    BTripleSharp,
142}
143
144// Impls.
145
146impl 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
388// Statics.
389
390static 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// Tests.
443
444#[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}