Skip to content

Commit 66aa86a

Browse files
authored
Add dominant and minor seventh chords (#26)
* Add minor seventh to intervals * Rename ChordQuality to ChordType * Add dominant 7th chords * Add minor 7th chords
1 parent c351251 commit 66aa86a

File tree

5 files changed

+186
-39
lines changed

5 files changed

+186
-39
lines changed

src/chord/chord.rs

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,35 @@ impl fmt::Display for ParseChordError {
2020
}
2121
}
2222

23-
/// Chord quality.
24-
/// https://en.wikipedia.org/wiki/Chord_names_and_symbols_(popular_music)#Chord_quality
23+
/// The type of the chord depending on the intervals it contains.
2524
#[derive(Debug, Clone, Copy, PartialEq)]
26-
pub enum ChordQuality {
25+
pub enum ChordType {
2726
Major,
2827
Minor,
28+
DominantSeventh,
29+
MinorSeventh,
2930
}
3031

31-
impl ChordQuality {
32+
impl ChordType {
3233
fn get_intervals(self) -> Vec<Interval> {
3334
use Interval::*;
3435

3536
match self {
3637
Self::Major => vec![PerfectUnison, MajorThird, PerfectFifth],
3738
Self::Minor => vec![PerfectUnison, MinorThird, PerfectFifth],
39+
Self::DominantSeventh => vec![PerfectUnison, MajorThird, PerfectFifth, MinorSeventh],
40+
Self::MinorSeventh => vec![PerfectUnison, MinorThird, PerfectFifth, MinorSeventh],
3841
}
3942
}
4043
}
4144

42-
impl fmt::Display for ChordQuality {
45+
impl fmt::Display for ChordType {
4346
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4447
let s = match self {
4548
Self::Major => "major",
4649
Self::Minor => "minor",
50+
Self::DominantSeventh => "dominant 7th",
51+
Self::MinorSeventh => "minor 7th",
4752
};
4853

4954
write!(f, "{}", s)
@@ -53,7 +58,7 @@ impl fmt::Display for ChordQuality {
5358
/// A chord such as C, Cm and so on.
5459
pub struct Chord {
5560
name: String,
56-
pub quality: ChordQuality,
61+
pub chord_type: ChordType,
5762
pub root: Note,
5863
notes: Vec<Note>,
5964
}
@@ -64,7 +69,7 @@ impl Chord {
6469
}
6570

6671
pub fn get_diagram(self, min_fret: FretID) -> ChordDiagram {
67-
let chord_shapes = ChordShapeSet::new(self.quality);
72+
let chord_shapes = ChordShapeSet::new(self.chord_type);
6873

6974
let (frets, intervals) = chord_shapes.get_config(self.root, min_fret);
7075

@@ -80,7 +85,7 @@ impl Chord {
8085

8186
impl fmt::Display for Chord {
8287
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83-
write!(f, "{} - {} {}", self.name, self.notes[0], self.quality)
88+
write!(f, "{} - {} {}", self.name, self.notes[0], self.chord_type)
8489
}
8590
}
8691

@@ -91,7 +96,7 @@ impl FromStr for Chord {
9196
let name = s.to_string();
9297

9398
// Regular expression for chord names.
94-
let re = Regex::new(r"(?P<root>[CDEFGAB][#b]?)(?P<quality>m?)").unwrap();
99+
let re = Regex::new(r"(?P<root>[CDEFGAB][#b]?)(?P<type>m?7?)").unwrap();
95100

96101
// Match regex.
97102
let caps = match re.captures(s) {
@@ -105,30 +110,33 @@ impl FromStr for Chord {
105110
Err(_) => return Err(ParseChordError { name }),
106111
};
107112

108-
// Get chord quality.
109-
let quality = match &caps["quality"] {
110-
"m" => ChordQuality::Minor,
111-
_ => ChordQuality::Major,
113+
// Get chord type.
114+
let chord_type = match &caps["type"] {
115+
"m" => ChordType::Minor,
116+
"7" => ChordType::DominantSeventh,
117+
"m7" => ChordType::MinorSeventh,
118+
_ => ChordType::Major,
112119
};
113120

114121
// Collect notes of the chord.
115122
let mut notes = vec![];
116123

117-
for interval in quality.get_intervals() {
124+
for interval in chord_type.get_intervals() {
118125
notes.push(root + interval);
119126
}
120127

121128
Ok(Self {
122129
name,
123130
root,
124-
quality,
131+
chord_type,
125132
notes,
126133
})
127134
}
128135
}
129136

130137
#[cfg(test)]
131138
mod tests {
139+
#![allow(clippy::many_single_char_names)]
132140
use super::*;
133141
use rstest::rstest_parametrize;
134142

@@ -161,7 +169,7 @@ mod tests {
161169
let t = Note::from_str(third).unwrap();
162170
let f = Note::from_str(fifth).unwrap();
163171
assert_eq!(c.notes, vec![r, t, f]);
164-
assert_eq!(c.quality, ChordQuality::Major);
172+
assert_eq!(c.chord_type, ChordType::Major);
165173
}
166174

167175
#[rstest_parametrize(
@@ -193,7 +201,87 @@ mod tests {
193201
let t = Note::from_str(third).unwrap();
194202
let f = Note::from_str(fifth).unwrap();
195203
assert_eq!(c.notes, vec![r, t, f]);
196-
assert_eq!(c.quality, ChordQuality::Minor);
204+
assert_eq!(c.chord_type, ChordType::Minor);
205+
}
206+
207+
#[rstest_parametrize(
208+
chord,
209+
root,
210+
third,
211+
fifth,
212+
seventh,
213+
case("C7", "C", "E", "G", "Bb"),
214+
case("C#7", "C#", "F", "G#", "B"),
215+
case("Db7", "Db", "F", "Ab", "B"),
216+
case("D7", "D", "F#", "A", "C"),
217+
case("D#7", "D#", "G", "A#", "C#"),
218+
case("Eb7", "Eb", "G", "Bb", "Db"),
219+
case("E7", "E", "G#", "B", "D"),
220+
case("F7", "F", "A", "C", "Eb"),
221+
case("F#7", "F#", "A#", "C#", "E"),
222+
case("Gb7", "Gb", "Bb", "Db", "E"),
223+
case("G7", "G", "B", "D", "F"),
224+
case("G#7", "G#", "C", "D#", "F#"),
225+
case("Ab7", "Ab", "C", "Eb", "Gb"),
226+
case("A7", "A", "C#", "E", "G"),
227+
case("A#7", "A#", "D", "F", "G#"),
228+
case("Bb7", "Bb", "D", "F", "Ab"),
229+
case("B7", "B", "D#", "F#", "A")
230+
)]
231+
fn test_from_str_dominant_seventh(
232+
chord: &str,
233+
root: &str,
234+
third: &str,
235+
fifth: &str,
236+
seventh: &str,
237+
) {
238+
let c = Chord::from_str(chord).unwrap();
239+
let r = Note::from_str(root).unwrap();
240+
let t = Note::from_str(third).unwrap();
241+
let f = Note::from_str(fifth).unwrap();
242+
let s = Note::from_str(seventh).unwrap();
243+
assert_eq!(c.notes, vec![r, t, f, s]);
244+
assert_eq!(c.chord_type, ChordType::DominantSeventh);
245+
}
246+
247+
#[rstest_parametrize(
248+
chord,
249+
root,
250+
third,
251+
fifth,
252+
seventh,
253+
case("Cm7", "C", "Eb", "G", "Bb"),
254+
case("C#m7", "C#", "E", "G#", "B"),
255+
case("Dbm7", "Db", "E", "Ab", "B"),
256+
case("Dm7", "D", "F", "A", "C"),
257+
case("D#m7", "D#", "F#", "A#", "C#"),
258+
case("Ebm7", "Eb", "Gb", "Bb", "Db"),
259+
case("Em7", "E", "G", "B", "D"),
260+
case("Fm7", "F", "Ab", "C", "Eb"),
261+
case("F#m7", "F#", "A", "C#", "E"),
262+
case("Gbm7", "Gb", "A", "Db", "E"),
263+
case("Gm7", "G", "Bb", "D", "F"),
264+
case("G#m7", "G#", "B", "D#", "F#"),
265+
case("Abm7", "Ab", "B", "Eb", "Gb"),
266+
case("Am7", "A", "C", "E", "G"),
267+
case("A#m7", "A#", "C#", "F", "G#"),
268+
case("Bbm7", "Bb", "Db", "F", "Ab"),
269+
case("Bm7", "B", "D", "F#", "A")
270+
)]
271+
fn test_from_str_minor_seventh(
272+
chord: &str,
273+
root: &str,
274+
third: &str,
275+
fifth: &str,
276+
seventh: &str,
277+
) {
278+
let c = Chord::from_str(chord).unwrap();
279+
let r = Note::from_str(root).unwrap();
280+
let t = Note::from_str(third).unwrap();
281+
let f = Note::from_str(fifth).unwrap();
282+
let s = Note::from_str(seventh).unwrap();
283+
assert_eq!(c.notes, vec![r, t, f, s]);
284+
assert_eq!(c.chord_type, ChordType::MinorSeventh);
197285
}
198286

199287
#[rstest_parametrize(

src/chord/chord_shape.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::chord::ChordQuality;
1+
use crate::chord::ChordType;
22
use crate::chord::FretID;
33
use crate::chord::FretPattern;
44
use crate::note::Interval;
@@ -10,8 +10,8 @@ use std::str::FromStr;
1010
type IntervalPattern = [Interval; STRING_COUNT];
1111

1212
/// A chord shape is a configuration of frets to be pressed to play a
13-
/// chord with a certain chord quality. The shape can be moved along
14-
/// the fretboard to derive several chords.
13+
/// certain type of chord. The shape can be moved along the fretboard
14+
/// to derive several chords.
1515
///
1616
/// http://play-ukulele.simonplantinga.nl/2014/05/ukulele-chords-iii/
1717
/// https://newhamukes.wordpress.com/2013/08/30/moveable-chords/
@@ -58,22 +58,36 @@ pub struct ChordShapeSet {
5858
}
5959

6060
impl ChordShapeSet {
61-
pub fn new(chord_quality: ChordQuality) -> Self {
62-
let chord_shapes = match chord_quality {
63-
ChordQuality::Major => vec![
61+
pub fn new(chord_type: ChordType) -> Self {
62+
let chord_shapes = match chord_type {
63+
ChordType::Major => vec![
6464
ChordShape::new("C", [0, 0, 0, 3], ["P5", "P1", "M3", "P1"]),
6565
ChordShape::new("A", [2, 1, 0, 0], ["P1", "M3", "P5", "P1"]),
6666
ChordShape::new("G", [0, 2, 3, 2], ["P1", "P5", "P1", "M3"]),
6767
ChordShape::new("F", [2, 0, 1, 0], ["M3", "P5", "P1", "M3"]),
6868
ChordShape::new("D", [2, 2, 2, 0], ["P5", "P1", "M3", "P5"]),
6969
],
70-
ChordQuality::Minor => vec![
70+
ChordType::Minor => vec![
7171
ChordShape::new("C", [0, 3, 3, 3], ["P5", "m3", "P5", "P1"]),
7272
ChordShape::new("A", [2, 0, 0, 0], ["P1", "m3", "P5", "P1"]),
7373
ChordShape::new("G", [0, 2, 3, 1], ["P1", "P5", "P1", "m3"]),
7474
ChordShape::new("F", [1, 0, 1, 3], ["m3", "P5", "P1", "P5"]),
7575
ChordShape::new("D", [2, 2, 1, 0], ["P5", "P1", "m3", "P5"]),
7676
],
77+
ChordType::DominantSeventh => vec![
78+
ChordShape::new("C", [0, 0, 0, 1], ["P5", "P1", "M3", "m7"]),
79+
ChordShape::new("A", [0, 1, 0, 0], ["m7", "M3", "P5", "P1"]),
80+
ChordShape::new("G", [0, 2, 1, 2], ["P1", "P5", "m7", "M3"]),
81+
ChordShape::new("E", [1, 2, 0, 2], ["M3", "m7", "P1", "P5"]),
82+
ChordShape::new("D", [2, 0, 2, 0], ["P5", "m7", "M3", "P5"]),
83+
],
84+
ChordType::MinorSeventh => vec![
85+
ChordShape::new("C#", [1, 1, 0, 2], ["P5", "P1", "m3", "m7"]),
86+
ChordShape::new("A", [0, 0, 0, 0], ["m7", "m3", "P5", "P1"]),
87+
ChordShape::new("G", [0, 2, 1, 1], ["P1", "P5", "m7", "m3"]),
88+
ChordShape::new("E", [0, 2, 0, 2], ["m3", "m7", "P1", "P5"]),
89+
ChordShape::new("D", [2, 0, 1, 0], ["P5", "m7", "m3", "P5"]),
90+
],
7791
};
7892

7993
Self { chord_shapes }

src/chord/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod chord;
33
mod chord_shape;
44

55
pub use self::chord::Chord;
6-
pub use self::chord::ChordQuality;
6+
pub use self::chord::ChordType;
77
pub use self::chord_shape::ChordShape;
88
pub use self::chord_shape::ChordShapeSet;
99

src/note/interval.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub enum Interval {
2323
MinorThird,
2424
MajorThird,
2525
PerfectFifth,
26+
MinorSeventh,
2627
}
2728

2829
impl Interval {
@@ -35,6 +36,7 @@ impl Interval {
3536
MinorThird => 3,
3637
MajorThird => 4,
3738
PerfectFifth => 7,
39+
MinorSeventh => 10,
3840
}
3941
}
4042

@@ -48,6 +50,7 @@ impl Interval {
4850
MinorThird => 3,
4951
MajorThird => 3,
5052
PerfectFifth => 5,
53+
MinorSeventh => 7,
5154
}
5255
}
5356
}
@@ -65,6 +68,7 @@ impl FromStr for Interval {
6568
"P5" => PerfectFifth,
6669
"m3" => MinorThird,
6770
"M3" => MajorThird,
71+
"m7" => MinorSeventh,
6872
_ => return Err(ParseIntervalError { name }),
6973
};
7074

0 commit comments

Comments
 (0)