1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
use crate::interval::Interval; use crate::note::errors::NoteError; use regex::{Match, Regex}; use std::fmt; use strum_macros::EnumIter; const REGEX_PITCH: &str = "^[ABCDEFGabcdefg]"; const REGEX_PITCH_ACCIDENTAL: &str = "^[ABCDEFGabcdefg][b♯#s]"; #[derive(Debug, Copy, Clone, PartialEq, EnumIter)] pub enum PitchClass { C, Cs, D, Ds, E, F, Fs, G, Gs, A, As, B, } impl PitchClass { pub fn from_u8(val: u8) -> Self { use PitchClass::*; match val { 0 => C, 1 => Cs, 2 => D, 3 => Ds, 4 => E, 5 => F, 6 => Fs, 7 => G, 8 => Gs, 9 => A, 10 => As, 11 => B, _ => Self::from_u8(val % 12), } } pub fn from_str(string: &str) -> Option<Self> { use PitchClass::*; let characters: Vec<char> = string.chars().collect(); let mut pitch = match characters[0] { 'C' | 'c' => C, 'D' | 'd' => D, 'E' | 'e' => E, 'F' | 'f' => F, 'G' | 'g' => G, 'A' | 'a' => A, 'B' | 'b' => B, _ => C, }; if characters.len() > 1 { let second_char = characters[1]; match second_char { '#' | 's' | 'S' | '♯' => { let interval = Interval::from_semitone(1); if interval.is_ok() { pitch = Self::from_interval(&pitch, &interval.unwrap()); } } 'b' | '♭' => { let interval = Interval::from_semitone(11); if interval.is_ok() { pitch = Self::from_interval(&pitch, &interval.unwrap()); } } _ => return None, } } Some(pitch) } pub fn from_interval(pitch: &Self, interval: &Interval) -> Self { let current_pitch = *pitch as u8; let new_pitch = current_pitch + interval.semitone_count; Self::from_u8(new_pitch) } pub fn from_regex(string: &str) -> Result<(Self, Match), NoteError> { let r_pitch = Regex::new(REGEX_PITCH)?; let r_pitch_accidental = Regex::new(REGEX_PITCH_ACCIDENTAL)?; let pitch_match = r_pitch_accidental .find(&string) .or_else(|| r_pitch.find(&string)) .ok_or(NoteError::InvalidPitch)?; let pitch_class = Self::from_str(&string[pitch_match.start()..pitch_match.end()]) .ok_or(NoteError::InvalidPitch)?; Ok((pitch_class, pitch_match)) } } impl fmt::Display for PitchClass { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use PitchClass::*; match *self { C => write!(fmt, "C"), Cs => write!(fmt, "C#"), D => write!(fmt, "D"), Ds => write!(fmt, "D#"), E => write!(fmt, "E"), F => write!(fmt, "F"), Fs => write!(fmt, "F#"), G => write!(fmt, "G"), Gs => write!(fmt, "G#"), A => write!(fmt, "A"), As => write!(fmt, "A#"), B => write!(fmt, "B"), } } }