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 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 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 #[inline]
84 pub const fn increment(&self) -> Self {
85 Self::from_id(self.id().increment())
86 }
87
88 #[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}