use std::{fmt, str::FromStr};
use crate::{Error, Result};
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Note {
C,
Csharp,
Dflat,
D,
Dsharp,
Eflat,
E,
F,
Fsharp,
Gflat,
G,
Gsharp,
Aflat,
A,
Asharp,
Bflat,
B,
}
impl fmt::Display for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Note::*;
let s = match self {
C => "C",
Csharp => "C#",
Dflat => "Db",
D => "D",
Dsharp => "D#",
Eflat => "Eb",
E => "E",
F => "F",
Fsharp => "F#",
Gflat => "Gb",
G => "G",
Gsharp => "G#",
Aflat => "Ab",
A => "A",
Asharp => "A#",
Bflat => "Bb",
B => "B",
};
write!(f, "{s}")
}
}
impl FromStr for Note {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
use Note::*;
let note = match s {
"C" => C,
"C#" => Csharp,
"Db" => Dflat,
"D" => D,
"D#" => Dsharp,
"Eb" => Eflat,
"E" => E,
"F" => F,
"F#" => Fsharp,
"Gb" => Gflat,
"G" => G,
"G#" => Gsharp,
"Ab" => Gflat,
"A" => A,
"A#" => Asharp,
"Bb" => Bflat,
"B" => B,
_ => return Err(Error::InvalidNote),
};
Ok(note)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum ChordQuality {
Fifth,
Second,
Add9,
Aug,
Dim,
Half,
Sus,
Maj,
Min,
Maj7,
Min7,
Dom7,
Dom7sus,
Half7,
Dim7,
Maj9,
Maj13,
Sixth,
Sixth9,
Maj7Sharp11,
Maj9Sharp11,
Maj7Sharp5,
Min6,
Min69,
MinMaj7,
MinMaj9,
Min9,
Min11,
Min7Flat5,
Half9,
MinFlat6,
MinSharp5,
Ninth,
Dom7Flat9,
Dom7Sharp9,
Dom7Sharp11,
Dom7Flat5,
Dom7Sharp5,
NinthSharp11,
NinthFlat5,
NinthSharp5,
Dom7Flat13,
Dom7Sharp9Sharp5,
Dom7Sharp9Flat5,
Dom7Sharp9Sharp11,
Dom7Flat9Sharp11,
Dom7Flat9Flat5,
Dom7Flat9Sharp5,
Dom7Flat9Sharp9,
Dom7Flat9Flat13,
Dom7Alt,
Dom13,
Dom13Sharp11,
Dom13Flat9,
Dom13Sharp9,
Dom7Flat9sus,
Dom7susAdd3,
Dom9sus,
Dom13sus,
Dom7Flat13sus,
Dom11,
}
impl fmt::Display for ChordQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ChordQuality::*;
let s = match self {
Fifth => "5",
Second => "2",
Add9 => "add9",
Aug => "+",
Dim => "o",
Half => "h",
Sus => "sus",
Maj => "^",
Min => "-",
Maj7 => "^7",
Min7 => "-7",
Dom7 => "7",
Dom7sus => "7sus",
Half7 => "h7",
Dim7 => "o7",
Maj9 => "^9",
Maj13 => "^13",
Sixth => "6",
Sixth9 => "69",
Maj7Sharp11 => "^7#11",
Maj9Sharp11 => "^9#11",
Maj7Sharp5 => "^7#5",
Min6 => "-6",
Min69 => "-69",
MinMaj7 => "-^7",
MinMaj9 => "-^9",
Min9 => "-9",
Min11 => "-11",
Min7Flat5 => "-7b5",
Half9 => "h9",
MinFlat6 => "-b6",
MinSharp5 => "-#5",
Ninth => "9",
Dom7Flat9 => "7b9",
Dom7Sharp9 => "7#9",
Dom7Sharp11 => "7#11",
Dom7Flat5 => "7b5",
Dom7Sharp5 => "7#5",
NinthSharp11 => "9#11",
NinthFlat5 => "9b5",
NinthSharp5 => "9#5",
Dom7Flat13 => "7b13",
Dom7Sharp9Sharp5 => "7#9#5",
Dom7Sharp9Flat5 => "7#9b5",
Dom7Sharp9Sharp11 => "7#9#11",
Dom7Flat9Sharp11 => "7b9#11",
Dom7Flat9Flat5 => "7b9b5",
Dom7Flat9Sharp5 => "7b9#5",
Dom7Flat9Sharp9 => "7b9#9",
Dom7Flat9Flat13 => "7b9b13",
Dom7Alt => "7alt",
Dom13 => "13",
Dom13Sharp11 => "13#11",
Dom13Flat9 => "13b9",
Dom13Sharp9 => "13#9",
Dom7Flat9sus => "7b9sus",
Dom7susAdd3 => "7susadd3",
Dom9sus => "9sus",
Dom13sus => "13sus",
Dom7Flat13sus => "7b13sus",
Dom11 => "11",
};
write!(f, "{s}")
}
}
impl FromStr for ChordQuality {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
use ChordQuality::*;
let q = match s {
"5" => Fifth,
"2" => Second,
"add9" => Add9,
"+" => Aug,
"o" => Dim,
"h" => Half,
"sus" => Sus,
"^" => Maj,
"-" => Min,
"^7" => Maj7,
"-7" => Min7,
"7" => Dom7,
"7sus" => Dom7sus,
"h7" => Half7,
"o7" => Dim7,
"^9" => Maj9,
"^13" => Maj13,
"6" => Sixth,
"69" => Sixth9,
"^7#11" => Maj7Sharp11,
"^9#11" => Maj9Sharp11,
"^7#5" => Maj7Sharp5,
"-6" => Min6,
"-69" => Min69,
"-^7" => MinMaj7,
"-^9" => MinMaj9,
"-9" => Min9,
"-11" => Min11,
"-7b5" => Min7Flat5,
"h9" => Half9,
"-b6" => MinFlat6,
"-#5" => MinSharp5,
"9" => Ninth,
"7b9" => Dom7Flat9,
"7#9" => Dom7Sharp9,
"7#11" => Dom7Sharp11,
"7b5" => Dom7Flat5,
"7#5" => Dom7Sharp5,
"9#11" => NinthSharp11,
"9b5" => NinthFlat5,
"9#5" => NinthSharp5,
"7b13" => Dom7Flat13,
"7#9#5" => Dom7Sharp9Sharp5,
"7#9b5" => Dom7Sharp9Flat5,
"7#9#11" => Dom7Sharp9Sharp11,
"7b9#11" => Dom7Flat9Sharp11,
"7b9b5" => Dom7Flat9Flat5,
"7b9#5" => Dom7Flat9Sharp5,
"7b9#9" => Dom7Flat9Sharp9,
"7b9b13" => Dom7Flat9Flat13,
"7alt" => Dom7Alt,
"13" => Dom13,
"13#11" => Dom13Sharp11,
"13b9" => Dom13Flat9,
"13#9" => Dom13Sharp9,
"7b9sus" => Dom7Flat9sus,
"7susadd3" => Dom7susAdd3,
"9sus" => Dom9sus,
"13sus" => Dom13sus,
"7b13sus" => Dom7Flat13sus,
"11" => Dom11,
_ => return Err(Error::InvalidChordQuality),
};
Ok(q)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Chord {
pub root: Note,
pub quality: Option<ChordQuality>,
pub inversion: Option<Note>,
}
impl Chord {
pub fn new(root: Note) -> Self {
Self {
root,
quality: None,
inversion: None,
}
}
pub fn set_quality(&mut self, quality: Option<ChordQuality>) {
self.quality = quality;
}
pub fn set_inversion(&mut self, inversion: Option<Note>) {
self.inversion = inversion;
}
}
impl fmt::Display for Chord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let root = self.root;
let quality = self.quality.map(|q| q.to_string()).unwrap_or_default();
let inversion = self.inversion.map(|i| format!("/{i}")).unwrap_or_default();
write!(f, "{root}{quality}{inversion}")
}
}
impl FromStr for Chord {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let (chord, inv) = s
.split_once('/')
.map(|(c, i)| (c, Some(i)))
.unwrap_or((s, None));
let ext = chord
.chars()
.nth(1)
.map(|c| c == '#' || c == 'b')
.unwrap_or(false);
let (note, quality) = chord.split_at(if ext { 2 } else { 1 });
let mut c = Chord::new(note.parse()?);
if !quality.is_empty() {
c.set_quality(Some(quality.parse()?));
}
if let Some(inv) = inv {
c.set_inversion(Some(inv.parse()?));
}
Ok(c)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_string() {
let mut chord = Chord::new(Note::Dsharp);
chord.set_quality(Some(ChordQuality::Min7Flat5));
chord.set_inversion(Some(Note::Bflat));
assert_eq!(chord.to_string(), "D#-7b5/Bb");
let c: Chord = "D#-7b5/Bb".parse().unwrap();
assert_eq!(c, chord);
}
}