Chord Families

This commit is contained in:
khodowany
2026-10-06 03:18:02 -05:00
parent e71385dad1
commit 8423f3d788
14 changed files with 196 additions and 57 deletions
+8 -9
View File
@@ -22,16 +22,14 @@ import tests.test_voicing
class ChordFamilies(unittest.TestCase):
def test_common_jazz_qualities_in_multiple_keys(self):
suffixes = ('', 'm', 'M7', 'm7', '7', 'mM7', '6', 'm6', 'dim', 'dim7', 'm7b5', 'aug', 'aug7', 'augM7', 'sus2', 'sus4', '7sus4', '9sus4', '9', 'M9', 'm9', '11', 'M11', 'm11', '13', 'M13', 'm13', '7b9', '7#9', '7b5', '7#5', '7#11', '7b13', '7b9#5', '6add9')
for root in ('C', 'F#', 'Bb'):
for root in ('C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'):
for suffix in suffixes:
chord = generator.harmony.parse_symbol(root + suffix)
for part in ('acoustic', 'electric'):
with self.subTest(chord=root + suffix, part=part):
# These transpositions have no inversion satisfying all of the
# three-consecutive-frets, bass <=12 and written-tone constraints.
# A familiar low Bb9 root grip needs fret zero; its octave needs
# bass fret 13. The fifth-bass grip 8/10/7/9 spans four positions.
if part == 'acoustic' and root == 'Bb' and suffix in ('9', 'M9', 'm9', '7#9', '6add9'):
# Even four positions cannot satisfy these written tones with
# a root/fifth bass <=12 and upper notes on strings 2/3/4.
if part == 'acoustic' and root in ('Bb', 'B') and suffix == 'm9':
with self.assertRaisesRegex(ValueError, 'no legal acoustic voicing'):
generator.comping.voicings(generator.comping.semantic_key(chord), part)
continue
@@ -65,8 +63,8 @@ class ChordFamilies(unittest.TestCase):
self.assertNotIn(tests.test_voicing.grip((7, 8, 6, 8), (5, 4, 3, 2)), tests.test_comping_voicings.options('A7', 'acoustic'))
self.assertNotIn(tests.test_voicing.grip((5, 7, 5, 6), (5, 4, 3, 2)), tests.test_comping_voicings.options('Dm', 'acoustic'))
def test_impossible_family_is_explained_without_weakening_harmony(self):
chord = generator.harmony.parse_symbol('C9')
coverage = generator.chord_families.coverage(chord, tests.test_comping_voicings.options('C9', 'acoustic'), 'acoustic')
chord = generator.harmony.parse_symbol('Bb9')
coverage = generator.chord_families.coverage(chord, tests.test_comping_voicings.options('Bb9', 'acoustic'), 'acoustic')
missing = [c for c in coverage if not c['candidates']]
self.assertTrue(missing)
self.assertTrue(all('fret/span' in c['reason'] for c in missing))
@@ -79,7 +77,8 @@ class ChordFamilies(unittest.TestCase):
self.assertTrue(choices)
def top_string(v):
return min(p.string_number for p in v if p.midi == midi)
self.assertEqual(top_string(choices[0]), min(map(top_string, choices)))
tier = generator.fretboard.span_penalty(choices[0])
self.assertEqual(top_string(choices[0]), min(top_string(v) for v in choices if generator.fretboard.span_penalty(v) == tier))
self.assertEqual(max(p.midi for p in choices[0]), midi)
self.assertTrue(all(p.string_number in (1, 2, 3, 4) for v in choices for p in v))
self.assertTrue(all(midi in {p.midi for p in v} for v in choices))