music_note/
pitch.rs

1use crate::{midi::MidiNote, Interval, Natural};
2use core::ops::{Add, Sub};
3use core::{fmt, mem};
4
5/// Pitch class that can be found on the chromatic scale.
6#[repr(u8)]
7#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
8pub enum Pitch {
9    C,
10    CSharp,
11    D,
12    DSharp,
13    E,
14    F,
15    FSharp,
16    G,
17    GSharp,
18    A,
19    ASharp,
20    B,
21}
22
23impl Pitch {
24    /// Returns the natural pitch for the given `Natural`.
25    /// ```
26    /// use music_note::{Pitch, Natural};
27    ///
28    /// let pitch = Pitch::natural(Natural::F);
29    /// assert_eq!(pitch, Pitch::F);
30    /// ```
31    pub const fn natural(letter: Natural) -> Self {
32        match letter {
33            Natural::C => Self::C,
34            Natural::D => Self::D,
35            Natural::E => Self::E,
36            Natural::F => Self::F,
37            Natural::G => Self::G,
38            Natural::A => Self::A,
39            Natural::B => Self::B,
40        }
41    }
42
43    pub const fn from_byte(byte: u8) -> Self {
44        unsafe { mem::transmute(byte % (Self::B.into_byte() + 1)) }
45    }
46
47    pub const fn add_interval(self, interval: Interval) -> Self {
48        unsafe { mem::transmute((self as u8 + interval.semitones()) % (Self::B as u8 + 1)) }
49    }
50
51    pub const fn sub_interval(self, interval: Interval) -> Self {
52        Self::from_byte((self as u8 as i8 - interval.semitones() as i8).abs() as u8)
53    }
54
55    pub const fn into_byte(self) -> u8 {
56        self as _
57    }
58
59    pub const fn sub(self, rhs: Self) -> Interval {
60        Interval::new(self as u8 - rhs as u8)
61    }
62
63    pub fn transpose(self, key: Pitch, to: Pitch) -> Pitch {
64        let f = self - key;
65        to + f
66    }
67}
68
69impl From<u8> for Pitch {
70    fn from(byte: u8) -> Self {
71        Self::from_byte(byte)
72    }
73}
74
75impl From<Natural> for Pitch {
76    fn from(letter: Natural) -> Self {
77        match letter {
78            Natural::C => Self::C,
79            Natural::D => Self::D,
80            Natural::E => Self::E,
81            Natural::F => Self::F,
82            Natural::G => Self::G,
83            Natural::A => Self::A,
84            Natural::B => Self::B,
85        }
86    }
87}
88
89impl From<MidiNote> for Pitch {
90    fn from(midi: MidiNote) -> Self {
91        midi.pitch()
92    }
93}
94
95impl From<Pitch> for u8 {
96    fn from(pitch: Pitch) -> Self {
97        pitch.into_byte()
98    }
99}
100
101impl Add<Interval> for Pitch {
102    type Output = Self;
103
104    fn add(self, interval: Interval) -> Self {
105        self.add_interval(interval)
106    }
107}
108
109impl Sub for Pitch {
110    type Output = Interval;
111
112    fn sub(self, rhs: Self) -> Interval {
113        self.sub(rhs)
114    }
115}
116
117impl Sub<Interval> for Pitch {
118    type Output = Self;
119
120    fn sub(self, interval: Interval) -> Self {
121        self.sub_interval(interval)
122    }
123}
124
125impl fmt::Display for Pitch {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        let s = match self {
128            Pitch::C => "C",
129            Pitch::CSharp => "C#",
130            Pitch::D => "D",
131            Pitch::DSharp => "D#",
132            Pitch::E => "E",
133            Pitch::F => "F",
134            Pitch::FSharp => "F#",
135            Pitch::G => "G",
136            Pitch::GSharp => "G#",
137            Pitch::A => "A",
138            Pitch::ASharp => "A#",
139            Pitch::B => "B",
140        };
141        f.write_str(s)
142    }
143}