From fdbeddb1062ea3895b8120d1defafa24bc932e69 Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 1 Sep 2021 11:01:42 +0200 Subject: [PATCH 1/6] Add diatonic option --- pychord/chord.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/pychord/chord.py b/pychord/chord.py index 90becc0..90c1f5e 100644 --- a/pychord/chord.py +++ b/pychord/chord.py @@ -50,7 +50,7 @@ def __ne__(self, other): return not self.__eq__(other) @classmethod - def from_note_index(cls, note, quality, scale): + def from_note_index(cls, note, quality, scale, diatonic=False): """ Create a Chord from note index in a scale Chord.from_note_index(1, "", "Cmaj") returns I of C major => Chord("C") @@ -67,6 +67,49 @@ def from_note_index(cls, note, quality, scale): relative_key = RELATIVE_KEY_DICT[scale[-3:]][note - 1] root_num = NOTE_VAL_DICT[scale[:-3]] root = VAL_NOTE_DICT[(root_num + relative_key) % 12][0] + + scale_degrees = RELATIVE_KEY_DICT[scale[-3:]] + + if diatonic: + + from .constants.qualities import DEFAULT_QUALITIES + from collections import OrderedDict + + # construct the chord based on scale degrees, within 1 octave + third = scale_degrees[(note + 1) % 7] + fifth = scale_degrees[(note + 3) % 7] + seventh = scale_degrees[(note + 5) % 7] + + # adjust the chord to its root position (as a stack of thirds), + # then set the root to 0 + def get_diatonic_chord(chord: tuple): + uninverted = [] + for note in chord: + if not uninverted: + uninverted.append(note) + elif note > uninverted[-1]: + uninverted.append(note) + else: + uninverted.append(note + 12) + uninverted = tuple([x - uninverted[0] for x in uninverted]) + return uninverted + + if quality in ["", "-", "maj", "m", "min"]: + triad = (relative_key, third, fifth) + q = get_diatonic_chord(triad) + elif quality in ["7", "M7", "maj7", "m7"]: + seventh_chord = (relative_key, third, fifth, seventh) + q = get_diatonic_chord(seventh_chord) + else: + raise NotImplementedError("Only generic chords (triads, sevenths) are supported") + + # look up DEFAULT_QUALITIES to determine chord quality + # tuples are used as keys for easier lookup + # note: because first matching tuple is used, (0, 3, 7) becomes + # "-", which might not be preferred by some + qualities = OrderedDict([(c, q) for q, c in DEFAULT_QUALITIES]) + quality = qualities[q] + return cls("{}{}".format(root, quality)) @property From 348287ab07edae3e713eff31564c66da230f5e72 Mon Sep 17 00:00:00 2001 From: Joseph Date: Sat, 11 Sep 2021 22:39:18 +0200 Subject: [PATCH 2/6] Add test cases for Chord.from_note_index(diatonic=True) --- test/test_chord.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_chord.py b/test/test_chord.py index 16c3075..0c2b52c 100644 --- a/test/test_chord.py +++ b/test/test_chord.py @@ -123,6 +123,26 @@ def test_note_9(self): with self.assertRaises(ValueError): Chord.from_note_index(note=9, quality="", scale="Fmaj") + def test_diatonic_note_1(self): + chord = Chord.from_note_index(note=1, quality="", diatonic=True, scale="Dmaj") + self.assertEqual(chord, Chord("D")) + + def test_diatonic_note_2_mode(self): + chord = Chord.from_note_index(note=2, quality="7", diatonic=True, scale="BLoc") + self.assertEqual(chord, Chord("Cmaj7")) + + def test_diatonic_note_3_mode(self): + chord = Chord.from_note_index(note=3, quality="m", diatonic=True, scale="G#Mix") + self.assertEqual(chord, Chord("Cdim")) + + def test_diatonic_note_4_mode(self): + chord = Chord.from_note_index(note=4, quality="-", diatonic=True, scale="AbDor") + self.assertEqual(chord, Chord("C#")) + + def test_diatonic_note_nongeneric(self): + with self.assertRaises(NotImplementedError): + Chord.from_note_index(note=5, quality="sus", diatonic=True, scale="Fmaj") + if __name__ == '__main__': unittest.main() From 2ff91bb261f447d2d13f967873713cc027a41a8b Mon Sep 17 00:00:00 2001 From: Yuma Mihira Date: Sun, 12 Sep 2021 09:56:33 +0900 Subject: [PATCH 3/6] Keep compatibility for Python 2.7 --- pychord/chord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pychord/chord.py b/pychord/chord.py index 90c1f5e..aab1a9a 100644 --- a/pychord/chord.py +++ b/pychord/chord.py @@ -82,7 +82,7 @@ def from_note_index(cls, note, quality, scale, diatonic=False): # adjust the chord to its root position (as a stack of thirds), # then set the root to 0 - def get_diatonic_chord(chord: tuple): + def get_diatonic_chord(chord): uninverted = [] for note in chord: if not uninverted: From 05a108cd4b2db381c32697d40ca8a1b04a9219dc Mon Sep 17 00:00:00 2001 From: Yuma Mihira Date: Sun, 12 Sep 2021 10:13:44 +0900 Subject: [PATCH 4/6] Use QualityManager instead of DEFAULT_QUALITIES --- pychord/chord.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pychord/chord.py b/pychord/chord.py index aab1a9a..468923e 100644 --- a/pychord/chord.py +++ b/pychord/chord.py @@ -2,6 +2,7 @@ from .constants import NOTE_VAL_DICT, VAL_NOTE_DICT from .constants.scales import RELATIVE_KEY_DICT from .parser import parse +from .quality import QualityManager from .utils import transpose_note, display_appended, display_on, note_to_val, val_to_note @@ -71,10 +72,6 @@ def from_note_index(cls, note, quality, scale, diatonic=False): scale_degrees = RELATIVE_KEY_DICT[scale[-3:]] if diatonic: - - from .constants.qualities import DEFAULT_QUALITIES - from collections import OrderedDict - # construct the chord based on scale degrees, within 1 octave third = scale_degrees[(note + 1) % 7] fifth = scale_degrees[(note + 3) % 7] @@ -91,7 +88,7 @@ def get_diatonic_chord(chord): uninverted.append(note) else: uninverted.append(note + 12) - uninverted = tuple([x - uninverted[0] for x in uninverted]) + uninverted = [x - uninverted[0] for x in uninverted] return uninverted if quality in ["", "-", "maj", "m", "min"]: @@ -103,12 +100,11 @@ def get_diatonic_chord(chord): else: raise NotImplementedError("Only generic chords (triads, sevenths) are supported") - # look up DEFAULT_QUALITIES to determine chord quality - # tuples are used as keys for easier lookup - # note: because first matching tuple is used, (0, 3, 7) becomes - # "-", which might not be preferred by some - qualities = OrderedDict([(c, q) for q, c in DEFAULT_QUALITIES]) - quality = qualities[q] + # look up QualityManager to determine chord quality + quality_manager = QualityManager() + quality = quality_manager.find_quality_from_components(q) + if not quality: + raise RuntimeError("Quality with components {} not found".format(q)) return cls("{}{}".format(root, quality)) From fde94190ba1b1bb6cd66cdabb62653c1fa7eada2 Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 1 Sep 2021 11:01:42 +0200 Subject: [PATCH 5/6] Add diatonic option --- pychord/chord.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/pychord/chord.py b/pychord/chord.py index 90becc0..90c1f5e 100644 --- a/pychord/chord.py +++ b/pychord/chord.py @@ -50,7 +50,7 @@ def __ne__(self, other): return not self.__eq__(other) @classmethod - def from_note_index(cls, note, quality, scale): + def from_note_index(cls, note, quality, scale, diatonic=False): """ Create a Chord from note index in a scale Chord.from_note_index(1, "", "Cmaj") returns I of C major => Chord("C") @@ -67,6 +67,49 @@ def from_note_index(cls, note, quality, scale): relative_key = RELATIVE_KEY_DICT[scale[-3:]][note - 1] root_num = NOTE_VAL_DICT[scale[:-3]] root = VAL_NOTE_DICT[(root_num + relative_key) % 12][0] + + scale_degrees = RELATIVE_KEY_DICT[scale[-3:]] + + if diatonic: + + from .constants.qualities import DEFAULT_QUALITIES + from collections import OrderedDict + + # construct the chord based on scale degrees, within 1 octave + third = scale_degrees[(note + 1) % 7] + fifth = scale_degrees[(note + 3) % 7] + seventh = scale_degrees[(note + 5) % 7] + + # adjust the chord to its root position (as a stack of thirds), + # then set the root to 0 + def get_diatonic_chord(chord: tuple): + uninverted = [] + for note in chord: + if not uninverted: + uninverted.append(note) + elif note > uninverted[-1]: + uninverted.append(note) + else: + uninverted.append(note + 12) + uninverted = tuple([x - uninverted[0] for x in uninverted]) + return uninverted + + if quality in ["", "-", "maj", "m", "min"]: + triad = (relative_key, third, fifth) + q = get_diatonic_chord(triad) + elif quality in ["7", "M7", "maj7", "m7"]: + seventh_chord = (relative_key, third, fifth, seventh) + q = get_diatonic_chord(seventh_chord) + else: + raise NotImplementedError("Only generic chords (triads, sevenths) are supported") + + # look up DEFAULT_QUALITIES to determine chord quality + # tuples are used as keys for easier lookup + # note: because first matching tuple is used, (0, 3, 7) becomes + # "-", which might not be preferred by some + qualities = OrderedDict([(c, q) for q, c in DEFAULT_QUALITIES]) + quality = qualities[q] + return cls("{}{}".format(root, quality)) @property From f14847fbddb5b235658a2b71d9b4948644fb9c8a Mon Sep 17 00:00:00 2001 From: Joseph Date: Sat, 11 Sep 2021 22:39:18 +0200 Subject: [PATCH 6/6] Add test cases for Chord.from_note_index(diatonic=True) --- test/test_chord.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_chord.py b/test/test_chord.py index 16c3075..0c2b52c 100644 --- a/test/test_chord.py +++ b/test/test_chord.py @@ -123,6 +123,26 @@ def test_note_9(self): with self.assertRaises(ValueError): Chord.from_note_index(note=9, quality="", scale="Fmaj") + def test_diatonic_note_1(self): + chord = Chord.from_note_index(note=1, quality="", diatonic=True, scale="Dmaj") + self.assertEqual(chord, Chord("D")) + + def test_diatonic_note_2_mode(self): + chord = Chord.from_note_index(note=2, quality="7", diatonic=True, scale="BLoc") + self.assertEqual(chord, Chord("Cmaj7")) + + def test_diatonic_note_3_mode(self): + chord = Chord.from_note_index(note=3, quality="m", diatonic=True, scale="G#Mix") + self.assertEqual(chord, Chord("Cdim")) + + def test_diatonic_note_4_mode(self): + chord = Chord.from_note_index(note=4, quality="-", diatonic=True, scale="AbDor") + self.assertEqual(chord, Chord("C#")) + + def test_diatonic_note_nongeneric(self): + with self.assertRaises(NotImplementedError): + Chord.from_note_index(note=5, quality="sus", diatonic=True, scale="Fmaj") + if __name__ == '__main__': unittest.main()