-
Notifications
You must be signed in to change notification settings - Fork 28
/
pitch_class.rs
124 lines (110 loc) · 3.25 KB
/
pitch_class.rs
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"),
}
}
}