#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Note { A, ASharpBFlat, B, C, CSharpDFlat, D, DSharpEFlat, E, F, FSharpGFlat, G, GSharpAFlat, } impl Note { pub fn semitone(&self) -> u8 { match self { Note::C => 0, Note::CSharpDFlat => 1, Note::D => 2, Note::DSharpEFlat => 3, Note::E => 4, Note::F => 5, Note::FSharpGFlat => 6, Note::G => 7, Note::GSharpAFlat => 8, Note::A => 9, Note::ASharpBFlat => 10, Note::B => 11, } } pub fn from_semitone(s: u8) -> Note { match s % 12 { 0 => Note::C, 1 => Note::CSharpDFlat, 2 => Note::D, 3 => Note::DSharpEFlat, 4 => Note::E, 5 => Note::F, 6 => Note::FSharpGFlat, 7 => Note::G, 8 => Note::GSharpAFlat, 9 => Note::A, 10 => Note::ASharpBFlat, 11 => Note::B, _ => unreachable!(), } } pub fn to_sharp_str(&self) -> &'static str { match self { Note::C => "C", Note::CSharpDFlat => "C#", Note::D => "D", Note::DSharpEFlat => "D#", Note::E => "E", Note::F => "F", Note::FSharpGFlat => "F#", Note::G => "G", Note::GSharpAFlat => "G#", Note::A => "A", Note::ASharpBFlat => "A#", Note::B => "B", } } pub fn to_flat_str(&self) -> &'static str { match self { Note::C => "C", Note::CSharpDFlat => "Db", Note::D => "D", Note::DSharpEFlat => "Eb", Note::E => "E", Note::F => "F", Note::FSharpGFlat => "Gb", Note::G => "G", Note::GSharpAFlat => "Ab", Note::A => "A", Note::ASharpBFlat => "Bb", Note::B => "B", } } pub fn parse_prefix(s: &str) -> Option<(Note, usize)> { let mut chars = s.chars(); let root = match chars.next()? { 'A' => Note::A, 'B' => Note::B, 'C' => Note::C, 'D' => Note::D, 'E' => Note::E, 'F' => Note::F, 'G' => Note::G, _ => return None, }; match chars.next() { Some('#') => Some((Self::sharp_of(root), 2)), Some('b') if s.len() > 1 => { let flatted = Self::flat_of(root)?; Some((flatted, 2)) } _ => Some((root, 1)), } } pub fn parse(s: &str) -> Option { let (note, consumed) = Self::parse_prefix(s)?; if consumed == s.len() { Some(note) } else { None } } fn sharp_of(root: Note) -> Note { Note::from_semitone((root.semitone() + 1) % 12) } fn flat_of(root: Note) -> Option { Some(Note::from_semitone((root.semitone() + 11) % 12)) } } #[cfg(test)] #[path = "../tests/note.rs"] mod tests;