Skip to content

Commit

Permalink
Merge pull request #67 from hejops/diatonic
Browse files Browse the repository at this point in the history
Create chords diatonically
  • Loading branch information
yuma-m authored Sep 14, 2021
2 parents e234703 + 193552e commit 43ced8f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 1 deletion.
41 changes: 40 additions & 1 deletion pychord/chord.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -50,7 +51,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")
Expand All @@ -67,6 +68,44 @@ 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:
# 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):
uninverted = []
for note in chord:
if not uninverted:
uninverted.append(note)
elif note > uninverted[-1]:
uninverted.append(note)
else:
uninverted.append(note + 12)
uninverted = [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 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))

@property
Expand Down
20 changes: 20 additions & 0 deletions test/test_chord.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit 43ced8f

Please sign in to comment.