chord_parser/chord.rs
1/// Represents basic musical pitch types (excluding accidentals)
2#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
3pub enum Pitch {
4 /// Base A pitch
5 A,
6 /// Base B pitch
7 B,
8 /// Base C pitch
9 C,
10 /// Base D pitch
11 D,
12 /// Base E pitch
13 E,
14 /// Base F pitch
15 F,
16 /// Base G pitch
17 G,
18}
19
20impl Pitch {
21 /// Tries to parse a single character into `Pitch`. Case insensitive.
22 /// # Examples
23 /// ```
24 /// use chord_parser::chord::*;
25 ///
26 /// assert_eq!(Some(Pitch::A), Pitch::from_char(&'a')); // Case insensitive
27 /// assert_eq!(None, Pitch::from_char(&' ')); // Invalid input
28 /// ```
29 pub fn from_char(ch: &char) -> Option<Self> {
30 let formatted = match ch.to_lowercase().next() {
31 Some(ch) => ch,
32 None => return None,
33 };
34
35 match formatted {
36 'a' => Some(Self::A),
37 'b' => Some(Self::B),
38 'c' => Some(Self::C),
39 'd' => Some(Self::D),
40 'e' => Some(Self::E),
41 'f' => Some(Self::F),
42 'g' => Some(Self::G),
43 _ => None,
44 }
45 }
46}
47
48impl ToString for Pitch {
49 fn to_string(&self) -> String {
50 match self {
51 Self::A => "A".into(),
52 Self::B => "B".into(),
53 Self::C => "C".into(),
54 Self::D => "D".into(),
55 Self::E => "E".into(),
56 Self::F => "F".into(),
57 Self::G => "G".into(),
58 }
59 }
60}
61
62/// Represents an accidental alteration on a note.
63#[derive(PartialEq, Eq, Clone, Debug)]
64pub enum Accidental {
65 /// Natural (no accidental). '♮' symbol.
66 Natural,
67 /// Sharp accidental. '♯', '#' symbols.
68 Sharp,
69 /// Double sharp accidental. '𝄪', '##' symbols.
70 DoubleSharp,
71 /// Flat accidental. '♭', 'b' symbols.
72 Flat,
73 /// Double flat accidental. '𝄫', 'bb' symbols.
74 DoubleFlat,
75}
76
77impl Accidental {
78 /// Tries to parse a string into an accidental. Case insensitive (for both flat alterations).
79 /// # Examples
80 /// ```
81 /// use chord_parser::chord::*;
82 ///
83 /// assert_eq!(Some(Accidental::Flat), Accidental::from_str("B")); // Case insensitive
84 /// assert_eq!(Some(Accidental::DoubleFlat), Accidental::from_str("𝄫")); // Unicode works
85 /// assert_eq!(None, Accidental::from_str("as")); // Invalid input
86 /// ```
87 pub fn from_str(s: &str) -> Option<Self> {
88 let len = s.chars().count();
89
90 if len > 2 || s.is_empty() {
91 return None;
92 }
93
94 let fst = s.chars().next().unwrap();
95
96 match fst {
97 '♮' => return Some(Self::Natural),
98 '♯' => return Some(Self::Sharp),
99 '𝄪' => return Some(Self::DoubleSharp),
100 '♭' => return Some(Self::Flat),
101 '𝄫' => return Some(Self::DoubleFlat),
102 _ => (),
103 }
104
105 let is_sharp = match fst {
106 '#' => true,
107 'b' | 'B' => false,
108 _ => return None,
109 };
110
111 if len > 1 {
112 let snd = s.chars().nth(1).unwrap();
113
114 match is_sharp {
115 true => {
116 if snd != '#' {
117 return None;
118 }
119
120 Some(Self::DoubleSharp)
121 }
122 false => {
123 if snd != 'b' && snd != 'B' {
124 return None;
125 }
126
127 Some(Self::DoubleFlat)
128 }
129 }
130 } else {
131 match is_sharp {
132 true => Some(Self::Sharp),
133 false => Some(Self::Flat),
134 }
135 }
136 }
137}
138
139impl ToString for Accidental {
140 fn to_string(&self) -> String {
141 match self {
142 Self::Natural => "♮".into(),
143 Self::Sharp => "♯".into(),
144 Self::DoubleSharp => "𝄪".into(),
145 Self::Flat => "♭".into(),
146 Self::DoubleFlat => "𝄫".into(),
147 }
148 }
149}
150
151/// Represents a chord note.
152///
153/// # Note
154/// `pitch` and `accidental` can be used to calculate the precise note or a frequency in Hz,
155/// however would require additional input of octave.
156/// Since octave is not a part of a note on a chord SIGNATURE, this property is therefore omitted.
157#[derive(PartialEq, Eq, Clone, Debug)]
158pub struct Note {
159 /// The pitch of the note
160 pub pitch: Pitch,
161 /// The accidental of the note
162 pub accidental: Accidental,
163}
164
165impl ToString for Note {
166 fn to_string(&self) -> String {
167 let mut s = self.pitch.to_string();
168 s.push_str(self.accidental.to_string().as_str());
169
170 s
171 }
172}
173
174/// Represents a triad type of a chord.
175///
176/// # Note
177/// Chord signatures like "C7#5" or "Cm7b5" would NOT be considered augmented and diminished triad respectively.
178/// This enum refers to the triad type right after the specified pitch.
179/// Therefore, the provided examples in this note would be considered
180/// `Self::Major` and `Self::Minor` type respectively.
181#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
182pub enum ChordTriadType {
183 /// Major triad
184 Major,
185 /// Minor triad
186 Minor,
187 /// Augmented triad
188 Augmented,
189 /// Diminished triad
190 Diminished,
191}
192
193impl ToString for ChordTriadType {
194 fn to_string(&self) -> String {
195 match self {
196 Self::Major => "".into(),
197 Self::Minor => "m".into(),
198 Self::Augmented => "aug".into(),
199 Self::Diminished => "dim".into(),
200 }
201 }
202}
203
204/// Represents the type of seventh in a chord. Usually a part of [`Alterations`] struct.
205#[derive(PartialEq, Eq, Clone, Debug)]
206pub enum Seventh {
207 /// No seventh present in a chord
208 None,
209 /// Flatten (regular) seventh. Used for dominant and regular minor chords.
210 Flat,
211 /// Major (sharpened) seventh. In a chord signature - "maj7".
212 Major,
213}
214
215impl ToString for Seventh {
216 fn to_string(&self) -> String {
217 match self {
218 Self::None => "".into(),
219 Self::Flat => "7".into(),
220 Self::Major => "maj7".into(),
221 }
222 }
223}
224
225/// Represents the interval of an alteration. Used in [`ChordAlter`] enum.
226#[derive(PartialEq, Eq, Clone, Debug)]
227pub enum AlteredInterval {
228 /// "2" interval
229 Second,
230 /// "4" interval
231 Fourth,
232 /// "5" interval
233 Fifth,
234 /// "6" interval
235 Sixth,
236 /// "7" interval
237 Seventh,
238 /// "9" interval
239 Ninth,
240 /// "10" interval
241 Tenth,
242 /// "11" interval
243 Eleventh,
244 /// "13" interval
245 Thirteenth,
246}
247
248impl AlteredInterval {
249 /// Tries to parse a [`usize`] numer into `AlteredInterval`.
250 ///
251 /// # Examples
252 /// ```
253 /// use chord_parser::chord::*;
254 ///
255 /// assert_eq!(Some(AlteredInterval::Second), AlteredInterval::from_usize(2));
256 /// assert_eq!(Some(AlteredInterval::Ninth), AlteredInterval::from_usize(9));
257 /// assert_eq!(Some(AlteredInterval::Thirteenth), AlteredInterval::from_usize(13));
258 /// assert_eq!(None, AlteredInterval::from_usize(15)); // Invalid input
259 /// ```
260 pub fn from_usize(num: usize) -> Option<AlteredInterval> {
261 match num {
262 2 => Some(Self::Second),
263 4 => Some(Self::Fourth),
264 5 => Some(Self::Fifth),
265 6 => Some(Self::Sixth),
266 7 => Some(Self::Seventh),
267 9 => Some(Self::Ninth),
268 10 => Some(Self::Tenth),
269 11 => Some(Self::Eleventh),
270 13 => Some(Self::Thirteenth),
271 _ => None,
272 }
273 }
274}
275
276impl ToString for AlteredInterval {
277 fn to_string(&self) -> String {
278 match self {
279 Self::Second => "2".into(),
280 Self::Fourth => "4".into(),
281 Self::Fifth => "5".into(),
282 Self::Sixth => "6".into(),
283 Self::Seventh => "7".into(),
284 Self::Ninth => "9".into(),
285 Self::Tenth => "10".into(),
286 Self::Eleventh => "11".into(),
287 Self::Thirteenth => "13".into(),
288 }
289 }
290}
291
292/// Represents an ålteration of a note
293#[derive(PartialEq, Eq, Clone, Debug)]
294pub struct ChordNoteAlter {
295 /// The interval note being altered
296 pub interval: AlteredInterval,
297 /// The new accidental of the interval note
298 pub accidental: Accidental,
299}
300
301impl ToString for ChordNoteAlter {
302 fn to_string(&self) -> String {
303 let mut s = self.interval.to_string();
304 s.push_str(self.accidental.to_string().as_str());
305
306 s
307 }
308}
309
310/// Represents an alteration in a chord.
311///
312/// Typically used in [`Alterations.alters(&self)`] vector array.
313#[derive(PartialEq, Eq, Clone, Debug)]
314pub enum ChordAlter {
315 /// Added note interval alteration
316 Add(ChordNoteAlter),
317 /// Suspension intreval alteration
318 Suspended(AlteredInterval),
319}
320
321impl ToString for ChordAlter {
322 fn to_string(&self) -> String {
323 match self {
324 Self::Add(alter) => {
325 "add".to_string() + alter.to_string().as_str()
326 }
327 Self::Suspended(interval) => {
328 "sus".to_string() + interval.to_string().as_str()
329 }
330 }
331 }
332}
333
334/// Represents a "no." notation of chords that a tone is missing.
335///
336/// "5" chords are technically "no3" chords.
337#[derive(PartialEq, Eq, Clone, Debug)]
338pub enum No {
339 /// "no." is not present in a chord
340 None,
341 /// Omit 3rd. "no3"
342 Third,
343 /// Omit 5th. "no5"
344 Fifth,
345}
346
347impl ToString for No {
348 fn to_string(&self) -> String {
349 match &self {
350 Self::None => "".into(),
351 Self::Third => "no3".into(),
352 Self::Fifth => "no5".into(),
353 }
354 }
355}
356
357/// Represents a list of all the alterations presented in a chord.
358///
359/// This struct provides a simple and intuitive way to add alterations using the safe included methods.
360#[derive(PartialEq, Eq, Clone, Debug)]
361pub struct Alterations {
362 /// Represents an omitted tone in the
363 pub no: No,
364 /// Represents a seventh in the chord
365 pub seventh: Seventh,
366 /// Represents the bass note override (slash chord)
367 pub slash: Option<Note>,
368
369 alters: Vec<ChordAlter>,
370}
371
372impl Alterations {
373 /// Creates a new empty instance of `Alterations`.
374 ///
375 /// # Examples
376 /// ```
377 /// use chord_parser::chord::*;
378 ///
379 /// let mut alterations = Alterations::new();
380 ///
381 /// // Do whatever
382 /// alterations.set_suspension(&AlteredInterval::Fourth);
383 /// ```
384 pub fn new() -> Self {
385 Alterations { no: No::None, seventh: Seventh::None, slash: None, alters: vec![] }
386 }
387
388 /// Returns a list of all the added alterations.
389 ///
390 /// # Examples
391 /// ```
392 /// use chord_parser::chord::*;
393 ///
394 /// let mut alterations = Alterations::new();
395 ///
396 /// alterations.set_note(&ChordNoteAlter {
397 /// interval: AlteredInterval::Ninth,
398 /// accidental: Accidental::Sharp,
399 /// });
400 ///
401 /// alterations.set_suspension(&AlteredInterval::Fourth);
402 ///
403 /// alterations.alters(); // Returns the added ninth and suspended fourth
404 /// ```
405 pub fn alters(&self) -> &Vec<ChordAlter> {
406 &self.alters
407 }
408
409 /// Tries to get a note alteration associated with the passed interval.
410 ///
411 /// # Examples
412 /// ```
413 /// use chord_parser::chord::*;
414 ///
415 /// let mut alterations = Alterations::new();
416 ///
417 /// alterations.set_note(&ChordNoteAlter {
418 /// interval: AlteredInterval::Ninth,
419 /// accidental: Accidental::Sharp,
420 /// });
421 ///
422 /// alterations.get_note(&AlteredInterval::Ninth); // Returns the interval and accidental
423 /// ```
424 pub fn get_note(&self, interval: &AlteredInterval) -> Option<&ChordNoteAlter> {
425 for alter in self.alters.iter() {
426 match alter {
427 ChordAlter::Add(a) => if a.interval == *interval {
428 return Some(a);
429 }
430 _ => continue,
431 }
432 }
433
434 None
435 }
436
437 /// Tries to get the accidental altered for an interval.
438 ///
439 /// # Examples
440 /// ```
441 /// use chord_parser::chord::*;
442 ///
443 /// let mut alterations = Alterations::new();
444 ///
445 /// alterations.set_note(&ChordNoteAlter {
446 /// interval: AlteredInterval::Ninth,
447 /// accidental: Accidental::Sharp,
448 /// });
449 ///
450 /// alterations.get_accidental(&AlteredInterval::Ninth); // Returns Accidental::Sharp
451 /// ```
452 pub fn get_accidental(&self, interval: &AlteredInterval) -> Option<Accidental> {
453 if let Some(alter) = self.get_note(interval) {
454 return Some(alter.accidental.clone());
455 }
456
457 None
458 }
459
460 /// Set a new alteration for an interval.
461 ///
462 /// If an alteration for the interval already exists, the accidental will be overwritten.
463 /// If it doesn't exist, a new alteration is added.
464 ///
465 /// # Examples
466 /// ```
467 /// use chord_parser::chord::*;
468 ///
469 /// let mut alterations = Alterations::new();
470 ///
471 /// alterations.set_note(&ChordNoteAlter {
472 /// interval: AlteredInterval::Ninth,
473 /// accidental: Accidental::Sharp,
474 /// });
475 ///
476 /// // Now the 9th is flatten
477 /// alterations.set_note(&ChordNoteAlter {
478 /// interval: AlteredInterval::Ninth,
479 /// accidental: Accidental::Flat,
480 /// });
481 ///
482 /// alterations.set_note(&ChordNoteAlter {
483 /// interval: AlteredInterval::Eleventh,
484 /// accidental: Accidental::Sharp,
485 /// });
486 ///
487 /// // Get back all the 2 alterations added
488 /// alterations.alters();
489 /// ```
490 pub fn set_note(&mut self, alter: &ChordNoteAlter) {
491 for other in self.alters.iter_mut() {
492 match other {
493 ChordAlter::Add(other) => if other.interval == alter.interval {
494 other.accidental = alter.accidental.clone();
495 return;
496 }
497 _ => continue,
498 }
499 }
500
501 self.alters.push(ChordAlter::Add( alter.clone()));
502 }
503
504 /// Tries to get the suspended note alteration in the chord
505 ///
506 /// # Examples
507 /// ```
508 /// use chord_parser::chord::*;
509 ///
510 /// let mut alterations = Alterations::new();
511 ///
512 /// alterations.set_suspension(&AlteredInterval::Fourth);
513 /// alterations.get_suspension(); // Returns Some(&AlteredInterval::Fourth)
514 /// ```
515 pub fn get_suspension(&self) -> Option<&AlteredInterval> {
516 for alter in self.alters.iter() {
517 match alter {
518 ChordAlter::Suspended(interval) => return Some(interval),
519 _ => (),
520 }
521 }
522
523 None
524 }
525
526 /// Set the new suspended interval.
527 ///
528 /// Old suspended interval will be replaced with the new one,
529 /// if the old interval is present.
530 ///
531 /// # Examples
532 /// ```
533 /// use chord_parser::chord::*;
534 ///
535 /// let mut alterations = Alterations::new();
536 ///
537 /// alterations.set_suspension(&AlteredInterval::Second);
538 /// alterations.get_suspension(); // Returns Some(&AlteredInterval::Second)
539 /// ```
540 pub fn set_suspension(&mut self, interval: &AlteredInterval) {
541 for (i, alter) in self.alters.iter_mut().enumerate() {
542 match alter {
543 ChordAlter::Suspended(_) => {
544 self.alters[i] = ChordAlter::Suspended(interval.clone());
545 return;
546 }
547 _ => (),
548 }
549 }
550
551 self.alters.push(ChordAlter::Suspended(interval.clone()));
552 }
553}
554
555impl ToString for Alterations {
556 fn to_string(&self) -> String {
557 let mut s = self.no.to_string();
558 s.push_str(self.seventh.to_string().as_str());
559
560 self.alters.iter().for_each(|a| s.push_str(a.to_string().as_str()));
561
562 if let Some(slash) = &self.slash {
563 let slash_str = "/".to_string() + slash.to_string().as_str();
564 s.push_str(slash_str.as_str());
565 }
566
567 s
568 }
569}
570
571/// Represents a full chord signature.
572#[derive(PartialEq, Eq, Clone, Debug)]
573pub struct Chord {
574 /// Root note
575 pub note: Note,
576 /// Triad type
577 pub chord_type: ChordTriadType,
578 /// Alterations
579 pub alterations: Alterations,
580}
581
582impl ToString for Chord {
583 fn to_string(&self) -> String {
584 let mut s = self.note.to_string();
585 s.push_str(self.chord_type.to_string().as_str());
586 s.push_str(self.alterations.to_string().as_str());
587
588 s
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[test]
597 fn pitch_quality() {
598 assert_eq!(Pitch::C, Pitch::from_char(&'C').unwrap());
599 assert_eq!(Pitch::D, Pitch::from_char(&'D').unwrap());
600 assert_eq!(Pitch::E, Pitch::from_char(&'E').unwrap());
601 assert_eq!(Pitch::F, Pitch::from_char(&'F').unwrap());
602 assert_eq!(Pitch::G, Pitch::from_char(&'G').unwrap());
603 assert_eq!(Pitch::A, Pitch::from_char(&'A').unwrap());
604 assert_eq!(Pitch::B, Pitch::from_char(&'B').unwrap());
605
606 assert_eq!(None, Pitch::from_char(&'w'));
607 assert_eq!(None, Pitch::from_char(&'1'));
608 }
609
610 #[test]
611 fn pitch_insensitivity() {
612 assert_eq!(Pitch::C, Pitch::from_char(&'C').unwrap());
613 assert_eq!(Pitch::B, Pitch::from_char(&'b').unwrap());
614 assert_eq!(Pitch::B, Pitch::from_char(&'B').unwrap());
615 }
616
617 #[test]
618 fn accidental_from() {
619 assert_eq!(Accidental::Sharp, Accidental::from_str("#").unwrap());
620 assert_eq!(Accidental::DoubleSharp, Accidental::from_str("##").unwrap());
621 assert_eq!(Accidental::Flat, Accidental::from_str("b").unwrap());
622
623 assert_eq!(Accidental::DoubleFlat, Accidental::from_str("bb").unwrap());
624 assert_eq!(Accidental::DoubleFlat, Accidental::from_str("bB").unwrap());
625 assert_eq!(Accidental::DoubleFlat, Accidental::from_str("Bb").unwrap());
626 assert_eq!(Accidental::DoubleFlat, Accidental::from_str("BB").unwrap());
627
628 assert_eq!(None, Accidental::from_str("b#"));
629 assert_eq!(None, Accidental::from_str("#b"));
630
631 assert_eq!(None, Accidental::from_str(""));
632 assert_eq!(None, Accidental::from_str("nonsense"));
633 }
634
635 #[test]
636 fn alterations_struct_fns() {
637 let mut a = Alterations::new();
638
639 assert_eq!(None, a.get_note(&AlteredInterval::Ninth));
640
641 a.set_note(&ChordNoteAlter { interval: AlteredInterval::Ninth, accidental: Accidental::Sharp });
642
643 assert_eq!(Some(Accidental::Sharp), a.get_accidental(&AlteredInterval::Ninth));
644
645 a.set_note(&ChordNoteAlter { interval: AlteredInterval::Ninth, accidental: Accidental::Flat });
646
647 assert_eq!(Some(Accidental::Flat), a.get_accidental(&AlteredInterval::Ninth));
648 assert_eq!(1, a.alters.len());
649 }
650
651 #[test]
652 fn interval_from() {
653 assert_eq!(AlteredInterval::Second, AlteredInterval::from_usize(2).unwrap());
654 assert_eq!(AlteredInterval::Thirteenth, AlteredInterval::from_usize(13).unwrap());
655 assert_eq!(AlteredInterval::Eleventh, AlteredInterval::from_usize(11).unwrap());
656
657 assert_eq!(None, AlteredInterval::from_usize(0));
658 assert_eq!(None, AlteredInterval::from_usize(999));
659 }
660}