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}