use crate::errors::NoteError;
use crate::num::u7;
use crate::Result;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Note {
rhythm: f64,
pitch: u7,
dynamic: u7,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum NoteName {
Do = 0,
Re = 2,
Mi = 4,
Fa = 5,
Sol = 7,
La = 9,
Si = 11,
}
impl NoteName {
pub const C: NoteName = NoteName::Do;
pub const D: NoteName = NoteName::Re;
pub const E: NoteName = NoteName::Mi;
pub const F: NoteName = NoteName::Fa;
pub const G: NoteName = NoteName::Sol;
pub const A: NoteName = NoteName::La;
pub const B: NoteName = NoteName::Si;
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Accidental {
Flat,
#[default]
Natural,
Sharp,
}
impl Note {
pub fn new(pitch: u7, rhythm: f64, dynamic: u7) -> Result<Note> {
if rhythm < 0.000_001 {
return Err(NoteError::InvalidRhythm(rhythm).into());
}
Ok(Note {
pitch,
rhythm,
dynamic,
})
}
pub fn new_sequence<'a, PitchIter: IntoIterator<Item = u7> + 'a>(
rhythm: f64,
dynamic: u7,
pitches: PitchIter,
) -> impl std::iter::Iterator<Item = Result<Note>> + 'a {
pitches
.into_iter()
.map(move |p| Note::new(p, rhythm, dynamic))
}
pub fn pitch(&self) -> u7 {
self.pitch
}
pub fn rhythm(&self) -> f64 {
self.rhythm
}
pub fn dynamic(&self) -> u7 {
self.dynamic
}
pub fn pitch_info(&self, sharps: bool) -> (NoteName, Accidental, u8) {
pitch_info(self.pitch, sharps)
}
}
pub fn compute_pitch(note: NoteName, accidental: Accidental, octave: u8) -> Result<u7> {
let base_pitch = note as u32;
let nat_pitch = 12 * octave as u32 + base_pitch;
let pitch = match accidental {
Accidental::Natural => nat_pitch,
Accidental::Sharp => nat_pitch + 1,
Accidental::Flat => nat_pitch - 1,
};
if pitch > 127 {
return Err(NoteError::InvalidPitch(pitch).into());
}
Ok(u7::new(pitch as u8))
}
pub fn pitch_info(pitch: u7, sharps: bool) -> (NoteName, Accidental, u8) {
let pitch = u8::from(pitch);
let octave = pitch / 12;
let mut remainder_pitch = pitch % 12;
let mut acc = Accidental::Natural;
if matches!(remainder_pitch, 1 | 3 | 6 | 8 | 10) {
(acc, remainder_pitch) = if sharps {
(Accidental::Sharp, remainder_pitch - 1)
} else {
(Accidental::Flat, remainder_pitch + 1)
};
}
let name = match remainder_pitch {
0 => NoteName::Do,
2 => NoteName::Re,
4 => NoteName::Mi,
5 => NoteName::Fa,
7 => NoteName::Sol,
9 => NoteName::La,
11 => NoteName::Si,
_ => NoteName::Do, };
(name, acc, octave)
}
#[cfg(test)]
mod tests {
use super::{compute_pitch, pitch_info, u7, Accidental, NoteName};
#[test]
fn pitch_test() {
let test_cases = vec![
(NoteName::C, Accidental::Sharp, 2, true, 25),
(NoteName::B, Accidental::Natural, 1, false, 23),
(NoteName::B, Accidental::Natural, 1, true, 23),
(NoteName::C, Accidental::Natural, 2, true, 24),
(NoteName::C, Accidental::Natural, 2, false, 24),
(NoteName::D, Accidental::Natural, 2, false, 26),
(NoteName::E, Accidental::Natural, 2, false, 28),
(NoteName::F, Accidental::Natural, 2, true, 29),
(NoteName::G, Accidental::Sharp, 5, true, 68),
(NoteName::A, Accidental::Flat, 9, false, 116),
];
let compute_only_cases = vec![
(NoteName::B, Accidental::Sharp, 1, true, 24),
(NoteName::C, Accidental::Flat, 2, false, 23),
(NoteName::E, Accidental::Sharp, 2, true, 29),
];
for (name, acc, octave, sharps, out) in test_cases {
assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
assert_eq!(pitch_info(u7::new(out), sharps), (name, acc, octave));
}
for (name, acc, octave, _, out) in compute_only_cases {
assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
}
}
}