1use crate::errors::NoteError;
2use crate::num::u7;
3use crate::Result;
4
5#[derive(Debug, Default, Clone, PartialEq)]
7pub struct Note {
8 rhythm: f64,
11 pitch: u7,
13 dynamic: u7,
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21pub enum NoteName {
22 Do = 0,
23 Re = 2,
24 Mi = 4,
25 Fa = 5,
26 Sol = 7,
27 La = 9,
28 Si = 11,
29}
30
31impl NoteName {
32 pub const C: NoteName = NoteName::Do;
33 pub const D: NoteName = NoteName::Re;
34 pub const E: NoteName = NoteName::Mi;
35 pub const F: NoteName = NoteName::Fa;
36 pub const G: NoteName = NoteName::Sol;
37 pub const A: NoteName = NoteName::La;
38 pub const B: NoteName = NoteName::Si;
39}
40
41#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
43pub enum Accidental {
44 Flat,
45 #[default]
46 Natural,
47 Sharp,
48}
49
50impl Note {
51 pub fn new(pitch: u7, rhythm: f64, dynamic: u7) -> Result<Note> {
63 if rhythm < 0.000_001 {
64 return Err(NoteError::InvalidRhythm(rhythm).into());
65 }
66 Ok(Note {
67 pitch,
68 rhythm,
69 dynamic,
70 })
71 }
72
73 pub fn new_sequence<'a, PitchIter: IntoIterator<Item = u7> + 'a>(
77 rhythm: f64,
78 dynamic: u7,
79 pitches: PitchIter,
80 ) -> impl std::iter::Iterator<Item = Result<Note>> + 'a {
81 pitches
82 .into_iter()
83 .map(move |p| Note::new(p, rhythm, dynamic))
84 }
85
86 pub fn pitch(&self) -> u7 {
88 self.pitch
89 }
90
91 pub fn rhythm(&self) -> f64 {
93 self.rhythm
94 }
95
96 pub fn dynamic(&self) -> u7 {
98 self.dynamic
99 }
100
101 pub fn pitch_info(&self, sharps: bool) -> (NoteName, Accidental, u8) {
110 pitch_info(self.pitch, sharps)
111 }
112}
113
114pub fn compute_pitch(note: NoteName, accidental: Accidental, octave: u8) -> Result<u7> {
128 let base_pitch = note as u32;
130 let nat_pitch = 12 * octave as u32 + base_pitch;
131 let pitch = match accidental {
132 Accidental::Natural => nat_pitch,
133 Accidental::Sharp => nat_pitch + 1,
134 Accidental::Flat => nat_pitch - 1,
135 };
136 if pitch > 127 {
137 return Err(NoteError::InvalidPitch(pitch).into());
138 }
139 Ok(u7::new(pitch as u8))
140}
141
142pub fn pitch_info(pitch: u7, sharps: bool) -> (NoteName, Accidental, u8) {
151 let pitch = u8::from(pitch);
152 let octave = pitch / 12;
153 let mut remainder_pitch = pitch % 12;
154 let mut acc = Accidental::Natural;
155 if matches!(remainder_pitch, 1 | 3 | 6 | 8 | 10) {
156 (acc, remainder_pitch) = if sharps {
157 (Accidental::Sharp, remainder_pitch - 1)
158 } else {
159 (Accidental::Flat, remainder_pitch + 1)
160 };
161 }
162 let name = match remainder_pitch {
163 0 => NoteName::Do,
164 2 => NoteName::Re,
165 4 => NoteName::Mi,
166 5 => NoteName::Fa,
167 7 => NoteName::Sol,
168 9 => NoteName::La,
169 11 => NoteName::Si,
170 _ => NoteName::Do, };
172 (name, acc, octave)
173}
174
175#[cfg(test)]
176mod tests {
177 use super::{compute_pitch, pitch_info, u7, Accidental, NoteName};
178
179 #[test]
180 fn pitch_test() {
181 let test_cases = vec![
182 (NoteName::C, Accidental::Sharp, 2, true, 25),
183 (NoteName::B, Accidental::Natural, 1, false, 23),
184 (NoteName::B, Accidental::Natural, 1, true, 23),
185 (NoteName::C, Accidental::Natural, 2, true, 24),
186 (NoteName::C, Accidental::Natural, 2, false, 24),
187 (NoteName::D, Accidental::Natural, 2, false, 26),
188 (NoteName::E, Accidental::Natural, 2, false, 28),
189 (NoteName::F, Accidental::Natural, 2, true, 29),
190 (NoteName::G, Accidental::Sharp, 5, true, 68),
191 (NoteName::A, Accidental::Flat, 9, false, 116),
192 ];
193
194 let compute_only_cases = vec![
195 (NoteName::B, Accidental::Sharp, 1, true, 24),
196 (NoteName::C, Accidental::Flat, 2, false, 23),
197 (NoteName::E, Accidental::Sharp, 2, true, 29),
198 ];
199
200 for (name, acc, octave, sharps, out) in test_cases {
201 assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
202 assert_eq!(pitch_info(u7::new(out), sharps), (name, acc, octave));
203 }
204 for (name, acc, octave, _, out) in compute_only_cases {
205 assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
206 }
207 }
208}