note_pen/
note.rs

1use crate::chord::Chord;
2use crate::pitch::Pitch;
3use crate::{Accidental, Alphabet, Interval};
4use std::ops::{Add, Sub};
5
6#[derive(Copy, Clone, Debug, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Note {
9    pub alphabet: Alphabet,
10    pub accidental: Accidental,
11    pub octave: u8,
12}
13
14impl Note {
15    pub const fn new(alphabet: Alphabet, accidental: Accidental, octave: u8) -> Self {
16        Self {
17            alphabet,
18            accidental,
19            octave,
20        }
21    }
22
23    /// The enharmonic equivalent of a note will be returned in a simpler form on the note.
24    /// # Compatability
25    /// This method might not return the same exact value from release to release,
26    /// however, it is guaranteed to be an enharmonic equivalent to the supplied note.
27    pub const fn simplify(&self) -> Self {
28        match self.accidental {
29            Accidental::None | Accidental::Natural | Accidental::Flat | Accidental::Sharp => *self,
30            Accidental::DoubleFlat | Accidental::DoubleSharp => Self::from_id(self.id()),
31        }
32    }
33
34    /// Returns the number of half-steps from A4.
35    pub const fn id(&self) -> Pitch {
36        let offset = match self.alphabet {
37            Alphabet::A => 0,
38            Alphabet::B => 2,
39            Alphabet::C => 3,
40            Alphabet::D => 5,
41            Alphabet::E => 7,
42            Alphabet::F => 8,
43            Alphabet::G => 10,
44        };
45        let accidental = match self.accidental {
46            Accidental::None => 0,
47            Accidental::DoubleFlat => -2,
48            Accidental::Flat => -1,
49            Accidental::Natural => 0,
50            Accidental::Sharp => 1,
51            Accidental::DoubleSharp => 2,
52        };
53        let octave = (self.octave as i64 - 4) * 12;
54        Pitch((offset + accidental + octave) as i16)
55    }
56
57    pub const fn from_id(id: Pitch) -> Self {
58        let octave = id.0 / 12 + 4;
59        let id = id.0 % 12;
60        let (alphabet, accidental) = match id {
61            0 => (Alphabet::A, Accidental::Natural),
62            1 => (Alphabet::A, Accidental::Sharp),
63            2 => (Alphabet::B, Accidental::Natural),
64            3 => (Alphabet::C, Accidental::Natural),
65            4 => (Alphabet::C, Accidental::Sharp),
66            5 => (Alphabet::D, Accidental::Natural),
67            6 => (Alphabet::D, Accidental::Sharp),
68            7 => (Alphabet::E, Accidental::Natural),
69            8 => (Alphabet::F, Accidental::Natural),
70            9 => (Alphabet::F, Accidental::Sharp),
71            10 => (Alphabet::G, Accidental::Natural),
72            11 => (Alphabet::G, Accidental::Sharp),
73            _ => unreachable!(),
74        };
75        Self {
76            alphabet,
77            accidental,
78            octave: octave as u8,
79        }
80    }
81
82    /// Increments the note by one half-step.
83    #[inline]
84    pub const fn increment(&self) -> Self {
85        Self::from_id(self.id().increment())
86    }
87
88    /// Decrements the note by one half-step.
89    #[inline]
90    pub const fn decrement(&self) -> Self {
91        Self::from_id(self.id().decrement())
92    }
93
94    #[inline]
95    pub const fn increment_by(&self, steps: i64) -> Self {
96        Self::from_id(Pitch(self.id().0 + steps as i16))
97    }
98
99    #[inline]
100    pub const fn decrement_by(&self, steps: i64) -> Self {
101        Self::from_id(Pitch(self.id().0 - steps as i16))
102    }
103}
104
105impl PartialEq for Note {
106    fn eq(&self, other: &Self) -> bool {
107        self.id() == other.id()
108    }
109}
110
111impl Eq for Note {}
112
113impl Add for Note {
114    type Output = Chord;
115
116    fn add(self, other: Self) -> Chord {
117        Chord::new(vec![self, other])
118    }
119}
120
121impl Add<Interval> for Note {
122    type Output = Note;
123    fn add(self, interval: Interval) -> Note {
124        Note::from_id(Pitch(self.id().0 + interval.0 as i16))
125    }
126}
127
128impl Sub for Note {
129    type Output = Interval;
130    fn sub(self, other: Self) -> Interval {
131        self.id() - other.id()
132    }
133}
134
135impl Sub<Interval> for Note {
136    type Output = Note;
137    fn sub(self, interval: Interval) -> Note {
138        Note::from_id(Pitch(self.id().0 - interval.0 as i16))
139    }
140}
141
142#[cfg(feature = "midi")]
143mod midi {
144    use midi_file::core::NoteNumber;
145    use crate::note::Note;
146    impl Note {
147        pub fn to_midi(&self) -> NoteNumber {
148            NoteNumber::new((self.id().0 + 69) as u8)
149        }
150    }
151
152    #[cfg(test)]
153    mod tests {
154        use midi_file::core::NoteNumber;
155        use crate::{Accidental, Alphabet};
156        use crate::note::Note;
157
158        const C4: NoteNumber = NoteNumber::new(72);
159        const D4: NoteNumber = NoteNumber::new(74);
160        const E4: NoteNumber = NoteNumber::new(76);
161        #[test]
162        fn test_basic() {
163            let c4 = Note::new(Alphabet::C, Accidental::Natural, 4);
164            let d4 = Note::new(Alphabet::D, Accidental::Natural, 4);
165            let e4 = Note::new(Alphabet::E, Accidental::Natural, 4);
166
167            assert_eq!(c4.to_midi(), C4);
168            assert_eq!(d4.to_midi(), D4);
169            assert_eq!(e4.to_midi(), E4);
170        }
171    }
172}
173
174#[cfg(test)]
175mod ops_tests {
176    #[test]
177    fn test_add() {
178        use super::{Accidental, Alphabet, Note};
179        use crate::Interval;
180
181        let a = Note::new(Alphabet::A, Accidental::Natural, 4);
182        let b = Note::new(Alphabet::B, Accidental::Natural, 4);
183        let c = Note::new(Alphabet::C, Accidental::Natural, 4);
184        let d = Note::new(Alphabet::D, Accidental::Natural, 4);
185        let e = Note::new(Alphabet::E, Accidental::Natural, 4);
186        let f = Note::new(Alphabet::F, Accidental::Natural, 4);
187        let g = Note::new(Alphabet::G, Accidental::Natural, 4);
188
189        assert_eq!(a + Interval::MAJOR_SECOND, b);
190        assert_eq!(a + Interval::MINOR_THIRD, c);
191        assert_eq!(a + Interval::PERFECT_FOURTH, d);
192        assert_eq!(a + Interval::PERFECT_FIFTH, e);
193        assert_eq!(a + Interval::MINOR_SIXTH, f);
194        assert_eq!(a + Interval::MINOR_SEVENTH, g);
195    }
196
197    #[test]
198    #[allow(clippy::many_single_char_names)]
199    fn test_sub_note_note() {
200        use super::{Accidental, Alphabet, Note};
201        use crate::Interval;
202
203        let a = Note::new(Alphabet::A, Accidental::Natural, 4);
204        let b = Note::new(Alphabet::B, Accidental::Natural, 4);
205        let c = Note::new(Alphabet::C, Accidental::Natural, 4);
206        let d = Note::new(Alphabet::D, Accidental::Natural, 4);
207        let e = Note::new(Alphabet::E, Accidental::Natural, 4);
208        let f = Note::new(Alphabet::F, Accidental::Natural, 4);
209        let g = Note::new(Alphabet::G, Accidental::Natural, 4);
210
211        assert_eq!(b - a, Interval::MAJOR_SECOND);
212        assert_eq!(c - a, Interval::MINOR_THIRD);
213        assert_eq!(d - a, Interval::PERFECT_FOURTH);
214        assert_eq!(e - a, Interval::PERFECT_FIFTH);
215        assert_eq!(f - a, Interval::MINOR_SIXTH);
216        assert_eq!(g - a, Interval::MINOR_SEVENTH);
217    }
218}
219
220#[cfg(test)]
221mod test {
222    #[test]
223    fn test_enharmonic() {
224        use super::{Accidental, Alphabet, Note};
225
226        let a_sharp = Note::new(Alphabet::A, Accidental::Sharp, 4);
227        let b_flat = Note::new(Alphabet::B, Accidental::Flat, 4);
228        assert_eq!(a_sharp, b_flat);
229
230        let c_sharp = Note::new(Alphabet::C, Accidental::Sharp, 4);
231        let d_flat = Note::new(Alphabet::D, Accidental::Flat, 4);
232        assert_eq!(c_sharp, d_flat);
233    }
234
235    #[test]
236    fn test_edge_cases() {
237        use super::{Accidental, Alphabet, Note};
238
239        let b = Note::new(Alphabet::B, Accidental::Natural, 4);
240        let b_sharp = Note::new(Alphabet::B, Accidental::Sharp, 4);
241        let c = Note::new(Alphabet::C, Accidental::Natural, 4);
242        let c_flat = Note::new(Alphabet::C, Accidental::Flat, 4);
243
244        assert_eq!(b_sharp, c);
245        assert_eq!(b, c_flat);
246        assert_ne!(b, c);
247    }
248}