1use core::fmt;
69use pest::Parser;
70use pest_derive::Parser;
71
72#[derive(Parser)]
73#[grammar = "grammars/chord.pest"]
74pub struct ChordParser;
75
76#[derive(Debug, Clone, Copy)]
77pub enum Note {
78 A,
79 B,
80 C,
81 D,
82 E,
83 F,
84 G,
85 ASHARP,
86 CSHARP,
87 DSHARP,
88 FSHARP,
89 GSHARP,
90 DFLAT,
91 EFLAT,
92 GFLAT,
93 AFLAT,
94 BFLAT,
95}
96
97impl fmt::Display for Note {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Note::A => write!(f, "A"),
101 Note::B => write!(f, "B"),
102 Note::C => write!(f, "C"),
103 Note::D => write!(f, "D"),
104 Note::E => write!(f, "E"),
105 Note::F => write!(f, "F"),
106 Note::G => write!(f, "G"),
107 Note::ASHARP => write!(f, "A#"),
108 Note::CSHARP => write!(f, "C#"),
109 Note::DSHARP => write!(f, "D#"),
110 Note::FSHARP => write!(f, "F#"),
111 Note::GSHARP => write!(f, "G#"),
112 Note::DFLAT => write!(f, "Db"),
113 Note::EFLAT => write!(f, "Eb"),
114 Note::GFLAT => write!(f, "Gb"),
115 Note::AFLAT => write!(f, "Ab"),
116 Note::BFLAT => write!(f, "Bb"),
117 }
118 }
119}
120
121impl PartialEq for Note {
122 fn eq(&self, other: &Self) -> bool {
123 use Note::*;
124 matches!(
125 (self, other),
126 (A, A)
127 | (B, B)
128 | (C, C)
129 | (D, D)
130 | (E, E)
131 | (F, F)
132 | (G, G)
133 | (ASHARP, BFLAT)
134 | (BFLAT, ASHARP)
135 | (CSHARP, DFLAT)
136 | (DFLAT, CSHARP)
137 | (DSHARP, EFLAT)
138 | (EFLAT, DSHARP)
139 | (FSHARP, GFLAT)
140 | (GFLAT, FSHARP)
141 | (GSHARP, AFLAT)
142 | (AFLAT, GSHARP)
143 )
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq)]
148pub enum Chord {
149 MajorChord(Note),
150 MinorChord(Note),
151}
152
153pub fn to_note(string_rep: &str) -> Option<Note> {
154 use Note::*;
155 match string_rep {
156 "A" => Some(A),
157 "B" => Some(B),
158 "C" => Some(C),
159 "D" => Some(D),
160 "E" => Some(E),
161 "F" => Some(F),
162 "G" => Some(G),
163
164 "A#" => Some(ASHARP),
165 "C#" => Some(CSHARP),
166 "D#" => Some(DSHARP),
167 "F#" => Some(FSHARP),
168 "G#" => Some(GSHARP),
169
170 "Db" => Some(DFLAT),
171 "Eb" => Some(EFLAT),
172 "Gb" => Some(GFLAT),
173 "Ab" => Some(AFLAT),
174 "Bb" => Some(BFLAT),
175
176 &_ => None,
177 }
178}
179
180fn next_note(note: Note) -> Note {
181 use Note::*;
182 match note {
183 A => ASHARP,
184 B => C,
185 C => CSHARP,
186 D => DSHARP,
187 E => F,
188 F => FSHARP,
189 G => GSHARP,
190 ASHARP => B,
191 CSHARP => D,
192 DSHARP => E,
193 FSHARP => G,
194 GSHARP => A,
195 DFLAT => D,
196 EFLAT => E,
197 GFLAT => G,
198 AFLAT => A,
199 BFLAT => B,
200 }
201}
202
203fn plus_perfect_fifth(note: Note) -> Note {
204 next_note(next_note(next_note(next_note(next_note(next_note(
205 next_note(note),
206 ))))))
207}
208
209fn plus_minor_third(note: Note) -> Note {
210 next_note(next_note(next_note(note)))
211}
212
213fn plus_major_third(note: Note) -> Note {
214 next_note(plus_minor_third(note))
215}
216
217pub fn parse_root(input: &str) -> Result<Chord, Box<pest::error::Error<Rule>>> {
218 let mut pairs = ChordParser::parse(Rule::root, input)?;
219
220 for pair in pairs.clone() {
221 for inner_pair in pair.into_inner() {
222 if inner_pair.as_rule() == Rule::chord {
223 let chord_pair = inner_pair.into_inner().next().unwrap();
224 match chord_pair.as_rule() {
225 Rule::minor_chord => {
226 let tonic = chord_pair.into_inner().next().unwrap().as_str();
227 let tonic_note = to_note(tonic).unwrap();
228 return Ok(Chord::MinorChord(tonic_note));
229 }
230 Rule::major_chord => {
231 let tonic = chord_pair.into_inner().next().unwrap().as_str();
232 let tonic_note = to_note(tonic).unwrap();
233 return Ok(Chord::MajorChord(tonic_note));
234 }
235 _ => {
236 return Err(Box::new(pest::error::Error::new_from_span(
237 pest::error::ErrorVariant::CustomError {
238 message: String::from("Unknown chord rule"),
239 },
240 chord_pair.as_span(),
241 )));
242 }
243 }
244 }
245 }
246 }
247
248 Err(Box::new(pest::error::Error::new_from_span(
249 pest::error::ErrorVariant::CustomError {
250 message: String::from("No chord rule found"),
251 },
252 pairs.next().unwrap().as_span(),
253 )))
254}
255
256pub fn map_chord_to_notes(chord: Chord) -> Vec<Note> {
257 match chord {
258 Chord::MinorChord(tonic) => {
259 let minor_third = plus_minor_third(tonic);
260 let perfect_fifth = plus_perfect_fifth(tonic);
261 vec![tonic, minor_third, perfect_fifth]
262 }
263 Chord::MajorChord(tonic) => {
264 let major_third = plus_major_third(tonic);
265 let perfect_fifth = plus_perfect_fifth(tonic);
266 vec![tonic, major_third, perfect_fifth]
267 }
268 }
269}