use crate::chord::Chord;
use crate::chord::FretID;
use crate::chord::FretPattern;
use crate::chord::Tuning;
use crate::diagram::StringDiagram;
use crate::diagram::CHART_WIDTH;
use crate::note::Note;
use crate::STRING_COUNT;
use std::fmt;
use std::str::FromStr;
type NotePattern = [Note; STRING_COUNT];
pub struct ChordDiagram {
chord: Chord,
roots: NotePattern,
frets: FretPattern,
notes: NotePattern,
root_width: usize,
}
impl ChordDiagram {
pub fn new(chord: Chord, frets: FretPattern, notes: NotePattern, tuning: Tuning) -> Self {
let interval = tuning.get_interval();
let roots = [
Note::from_str("G").unwrap() + interval,
Note::from_str("C").unwrap() + interval,
Note::from_str("E").unwrap() + interval,
Note::from_str("A").unwrap() + interval,
];
Self {
roots,
chord,
frets,
notes,
root_width: tuning.get_root_width(),
}
}
fn get_base_fret(&self) -> FretID {
let max_fret = *self.frets.iter().max().unwrap();
match max_fret {
max_fret if max_fret <= CHART_WIDTH => 1,
_ => *self.frets.iter().min().unwrap(),
}
}
}
impl fmt::Display for ChordDiagram {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = format!("[{}]\n\n", self.chord);
let base_fret = self.get_base_fret();
for i in (0..STRING_COUNT).rev() {
let root = self.roots[i];
let fret = self.frets[i];
let note = self.notes[i];
let sd = StringDiagram::new(root, base_fret, fret, note, self.root_width);
s.push_str(&format!("{}\n", sd.to_string()));
}
if base_fret > 1 {
s.push_str(&format!(
"{:width$}\n",
base_fret,
width = self.root_width + 6
))
}
write!(f, "{}", s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use rstest::rstest;
use std::str::FromStr;
#[rstest(chord_name, min_fret, tuning, diagram,
case(
"C",
0,
Tuning::C,
indoc!("
[C - C major]
A ||---|---|-o-|---|- C
E o||---|---|---|---|- E
C o||---|---|---|---|- C
G o||---|---|---|---|- G
"),
),
case(
"C",
1,
Tuning::C,
indoc!("
[C - C major]
A -|-o-|---|---|---|- C
E -|-o-|---|---|---|- G
C -|---|-o-|---|---|- E
G -|---|---|-o-|---|- C
3
")
),
case(
"C#",
0,
Tuning::C,
indoc!("
[C# - C# major]
A ||---|---|---|-o-|- C#
E ||-o-|---|---|---|- F
C ||-o-|---|---|---|- C#
G ||-o-|---|---|---|- G#
")
),
case(
"Db",
0,
Tuning::C,
indoc!("
[Db - Db major]
A ||---|---|---|-o-|- Db
E ||-o-|---|---|---|- F
C ||-o-|---|---|---|- Db
G ||-o-|---|---|---|- Ab
")
),
case(
"Cm",
0,
Tuning::C,
indoc!("
[Cm - C minor]
A ||---|---|-o-|---|- C
E ||---|---|-o-|---|- G
C ||---|---|-o-|---|- Eb
G o||---|---|---|---|- G
"),
),
case(
"C#m",
0,
Tuning::C,
indoc!("
[C#m - C# minor]
A ||---|---|---|-o-|- C#
E ||---|---|---|-o-|- G#
C ||---|---|---|-o-|- E
G ||-o-|---|---|---|- G#
")
),
case(
"Dbm",
0,
Tuning::C,
indoc!("
[Dbm - Db minor]
A ||---|---|---|-o-|- Db
E ||---|---|---|-o-|- Ab
C ||---|---|---|-o-|- E
G ||-o-|---|---|---|- Ab
")
),
case(
"D",
0,
Tuning::D,
indoc!("
[D - D major]
B ||---|---|-o-|---|- D
F# o||---|---|---|---|- F#
D o||---|---|---|---|- D
A o||---|---|---|---|- A
"),
),
case(
"D",
5,
Tuning::D,
indoc!("
[D - D major]
B -|---|---|-o-|---|- F#
F# -|---|---|---|-o-|- D
D -|---|---|-o-|---|- A
A -|-o-|---|---|---|- D
5
"),
),
case(
"G",
0,
Tuning::G,
indoc!("
[G - G major]
E ||---|---|-o-|---|- G
B o||---|---|---|---|- B
G o||---|---|---|---|- G
D o||---|---|---|---|- D
"),
),
)]
fn test_to_diagram(chord_name: &str, min_fret: FretID, tuning: Tuning, diagram: &str) {
let chord = Chord::from_str(chord_name).unwrap();
let chord_diagram = chord.get_diagram(min_fret, tuning);
assert_eq!(chord_diagram.to_string(), diagram);
}
}