1use std::{fmt, str::FromStr};
2
3use crate::{Error, Result};
4
5#[derive(Debug, PartialEq, Eq, Copy, Clone)]
7pub enum Note {
8 C,
9 Csharp,
10 Dflat,
11 D,
12 Dsharp,
13 Eflat,
14 E,
15 F,
16 Fsharp,
17 Gflat,
18 G,
19 Gsharp,
20 Aflat,
21 A,
22 Asharp,
23 Bflat,
24 B,
25}
26
27impl fmt::Display for Note {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 use Note::*;
30
31 let s = match self {
32 C => "C",
33 Csharp => "C#",
34 Dflat => "Db",
35 D => "D",
36 Dsharp => "D#",
37 Eflat => "Eb",
38 E => "E",
39 F => "F",
40 Fsharp => "F#",
41 Gflat => "Gb",
42 G => "G",
43 Gsharp => "G#",
44 Aflat => "Ab",
45 A => "A",
46 Asharp => "A#",
47 Bflat => "Bb",
48 B => "B",
49 };
50
51 write!(f, "{s}")
52 }
53}
54
55impl FromStr for Note {
56 type Err = Error;
57
58 fn from_str(s: &str) -> Result<Self> {
59 use Note::*;
60
61 let note = match s {
62 "C" => C,
63 "C#" => Csharp,
64 "Db" => Dflat,
65 "D" => D,
66 "D#" => Dsharp,
67 "Eb" => Eflat,
68 "E" => E,
69 "F" => F,
70 "F#" => Fsharp,
71 "Gb" => Gflat,
72 "G" => G,
73 "G#" => Gsharp,
74 "Ab" => Gflat,
75 "A" => A,
76 "A#" => Asharp,
77 "Bb" => Bflat,
78 "B" => B,
79 _ => return Err(Error::InvalidNote),
80 };
81
82 Ok(note)
83 }
84}
85
86#[derive(Debug, PartialEq, Eq, Copy, Clone)]
88pub enum ChordQuality {
89 Fifth,
90 Second,
91 Add9,
92 Aug,
93 Dim,
94 Half,
95 Sus,
96 Maj,
97 Min,
98 Maj7,
99 Min7,
100 Dom7,
101 Dom7sus,
102 Half7,
103 Dim7,
104 Maj9,
105 Maj13,
106 Sixth,
107 Sixth9,
108 Maj7Sharp11,
109 Maj9Sharp11,
110 Maj7Sharp5,
111 Min6,
112 Min69,
113 MinMaj7,
114 MinMaj9,
115 Min9,
116 Min11,
117 Min7Flat5,
118 Half9,
119 MinFlat6,
120 MinSharp5,
121 Ninth,
122 Dom7Flat9,
123 Dom7Sharp9,
124 Dom7Sharp11,
125 Dom7Flat5,
126 Dom7Sharp5,
127 NinthSharp11,
128 NinthFlat5,
129 NinthSharp5,
130 Dom7Flat13,
131 Dom7Sharp9Sharp5,
132 Dom7Sharp9Flat5,
133 Dom7Sharp9Sharp11,
134 Dom7Flat9Sharp11,
135 Dom7Flat9Flat5,
136 Dom7Flat9Sharp5,
137 Dom7Flat9Sharp9,
138 Dom7Flat9Flat13,
139 Dom7Alt,
140 Dom13,
141 Dom13Sharp11,
142 Dom13Flat9,
143 Dom13Sharp9,
144 Dom7Flat9sus,
145 Dom7susAdd3,
146 Dom9sus,
147 Dom13sus,
148 Dom7Flat13sus,
149 Dom11,
150}
151
152impl fmt::Display for ChordQuality {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 use ChordQuality::*;
155
156 let s = match self {
157 Fifth => "5",
158 Second => "2",
159 Add9 => "add9",
160 Aug => "+",
161 Dim => "o",
162 Half => "h",
163 Sus => "sus",
164 Maj => "^",
165 Min => "-",
166 Maj7 => "^7",
167 Min7 => "-7",
168 Dom7 => "7",
169 Dom7sus => "7sus",
170 Half7 => "h7",
171 Dim7 => "o7",
172 Maj9 => "^9",
173 Maj13 => "^13",
174 Sixth => "6",
175 Sixth9 => "69",
176 Maj7Sharp11 => "^7#11",
177 Maj9Sharp11 => "^9#11",
178 Maj7Sharp5 => "^7#5",
179 Min6 => "-6",
180 Min69 => "-69",
181 MinMaj7 => "-^7",
182 MinMaj9 => "-^9",
183 Min9 => "-9",
184 Min11 => "-11",
185 Min7Flat5 => "-7b5",
186 Half9 => "h9",
187 MinFlat6 => "-b6",
188 MinSharp5 => "-#5",
189 Ninth => "9",
190 Dom7Flat9 => "7b9",
191 Dom7Sharp9 => "7#9",
192 Dom7Sharp11 => "7#11",
193 Dom7Flat5 => "7b5",
194 Dom7Sharp5 => "7#5",
195 NinthSharp11 => "9#11",
196 NinthFlat5 => "9b5",
197 NinthSharp5 => "9#5",
198 Dom7Flat13 => "7b13",
199 Dom7Sharp9Sharp5 => "7#9#5",
200 Dom7Sharp9Flat5 => "7#9b5",
201 Dom7Sharp9Sharp11 => "7#9#11",
202 Dom7Flat9Sharp11 => "7b9#11",
203 Dom7Flat9Flat5 => "7b9b5",
204 Dom7Flat9Sharp5 => "7b9#5",
205 Dom7Flat9Sharp9 => "7b9#9",
206 Dom7Flat9Flat13 => "7b9b13",
207 Dom7Alt => "7alt",
208 Dom13 => "13",
209 Dom13Sharp11 => "13#11",
210 Dom13Flat9 => "13b9",
211 Dom13Sharp9 => "13#9",
212 Dom7Flat9sus => "7b9sus",
213 Dom7susAdd3 => "7susadd3",
214 Dom9sus => "9sus",
215 Dom13sus => "13sus",
216 Dom7Flat13sus => "7b13sus",
217 Dom11 => "11",
218 };
219
220 write!(f, "{s}")
221 }
222}
223
224impl FromStr for ChordQuality {
225 type Err = Error;
226
227 fn from_str(s: &str) -> Result<Self> {
228 use ChordQuality::*;
229
230 let q = match s {
231 "5" => Fifth,
232 "2" => Second,
233 "add9" => Add9,
234 "+" => Aug,
235 "o" => Dim,
236 "h" => Half,
237 "sus" => Sus,
238 "^" => Maj,
239 "-" => Min,
240 "^7" => Maj7,
241 "-7" => Min7,
242 "7" => Dom7,
243 "7sus" => Dom7sus,
244 "h7" => Half7,
245 "o7" => Dim7,
246 "^9" => Maj9,
247 "^13" => Maj13,
248 "6" => Sixth,
249 "69" => Sixth9,
250 "^7#11" => Maj7Sharp11,
251 "^9#11" => Maj9Sharp11,
252 "^7#5" => Maj7Sharp5,
253 "-6" => Min6,
254 "-69" => Min69,
255 "-^7" => MinMaj7,
256 "-^9" => MinMaj9,
257 "-9" => Min9,
258 "-11" => Min11,
259 "-7b5" => Min7Flat5,
260 "h9" => Half9,
261 "-b6" => MinFlat6,
262 "-#5" => MinSharp5,
263 "9" => Ninth,
264 "7b9" => Dom7Flat9,
265 "7#9" => Dom7Sharp9,
266 "7#11" => Dom7Sharp11,
267 "7b5" => Dom7Flat5,
268 "7#5" => Dom7Sharp5,
269 "9#11" => NinthSharp11,
270 "9b5" => NinthFlat5,
271 "9#5" => NinthSharp5,
272 "7b13" => Dom7Flat13,
273 "7#9#5" => Dom7Sharp9Sharp5,
274 "7#9b5" => Dom7Sharp9Flat5,
275 "7#9#11" => Dom7Sharp9Sharp11,
276 "7b9#11" => Dom7Flat9Sharp11,
277 "7b9b5" => Dom7Flat9Flat5,
278 "7b9#5" => Dom7Flat9Sharp5,
279 "7b9#9" => Dom7Flat9Sharp9,
280 "7b9b13" => Dom7Flat9Flat13,
281 "7alt" => Dom7Alt,
282 "13" => Dom13,
283 "13#11" => Dom13Sharp11,
284 "13b9" => Dom13Flat9,
285 "13#9" => Dom13Sharp9,
286 "7b9sus" => Dom7Flat9sus,
287 "7susadd3" => Dom7susAdd3,
288 "9sus" => Dom9sus,
289 "13sus" => Dom13sus,
290 "7b13sus" => Dom7Flat13sus,
291 "11" => Dom11,
292 _ => return Err(Error::InvalidChordQuality),
293 };
294
295 Ok(q)
296 }
297}
298
299#[derive(Debug, PartialEq, Eq, Copy, Clone)]
324pub struct Chord {
325 pub root: Note,
326 pub quality: Option<ChordQuality>,
327 pub inversion: Option<Note>,
328}
329
330impl Chord {
331 pub fn new(root: Note) -> Self {
332 Self {
333 root,
334 quality: None,
335 inversion: None,
336 }
337 }
338
339 pub fn set_quality(&mut self, quality: Option<ChordQuality>) {
340 self.quality = quality;
341 }
342
343 pub fn set_inversion(&mut self, inversion: Option<Note>) {
344 self.inversion = inversion;
345 }
346}
347
348impl fmt::Display for Chord {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 let root = self.root;
351 let quality = self.quality.map(|q| q.to_string()).unwrap_or_default();
352 let inversion = self.inversion.map(|i| format!("/{i}")).unwrap_or_default();
353
354 write!(f, "{root}{quality}{inversion}")
355 }
356}
357
358impl FromStr for Chord {
359 type Err = Error;
360
361 fn from_str(s: &str) -> Result<Self> {
362 let (chord, inv) = s
363 .split_once('/')
364 .map(|(c, i)| (c, Some(i)))
365 .unwrap_or((s, None));
366
367 let ext = chord
368 .chars()
369 .nth(1)
370 .map(|c| c == '#' || c == 'b')
371 .unwrap_or(false);
372 let (note, quality) = chord.split_at(if ext { 2 } else { 1 });
373
374 let mut c = Chord::new(note.parse()?);
375 if !quality.is_empty() {
376 c.set_quality(Some(quality.parse()?));
377 }
378 if let Some(inv) = inv {
379 c.set_inversion(Some(inv.parse()?));
380 }
381
382 Ok(c)
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_to_string() {
392 let mut chord = Chord::new(Note::Dsharp);
393 chord.set_quality(Some(ChordQuality::Min7Flat5));
394 chord.set_inversion(Some(Note::Bflat));
395 assert_eq!(chord.to_string(), "D#-7b5/Bb");
396
397 let c: Chord = "D#-7b5/Bb".parse().unwrap();
398 assert_eq!(c, chord);
399 }
400}