Chord Families
This commit is contained in:
@@ -68,7 +68,7 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
requirements = generator.fretboard.chord_requirements(span.harmony)
|
||||
if basses and uppers:
|
||||
self.assertTrue(generator.acoustic_voicing.playable(generator.acoustic_voicing.Voicing(*grip)))
|
||||
self.assertLessEqual(max(p.fret for p in grip) - min(p.fret for p in grip) + 1, 3)
|
||||
self.assertLessEqual(max(p.fret for p in grip) - min(p.fret for p in grip) + 1, 4)
|
||||
self.assertTrue(requirements.covers({p.midi % 12 for p in grip}))
|
||||
self.assertTrue({p.midi % 12 for p in grip} <= set(requirements.allowed))
|
||||
for note in span.notes:
|
||||
@@ -188,17 +188,20 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
frets[index] += 1
|
||||
self.assertEqual(generator.acoustic_voicing.movement(before, voicing(frets)), 1)
|
||||
def test_fret_and_register_limits_are_hard_constraints(self):
|
||||
for frets, valid in (((1, 1, 1, 1), True), ((1, 3, 1, 3), True), ((1, 4, 1, 4), False), ((1, 5, 1, 4), False), ((0, 1, 1, 1), False), ((12, 14, 14, 12), True), ((12, 15, 14, 12), False), ((13, 13, 13, 13), False), ((12, 16, 14, 12), False), ((12, 21, 12, 12), False)):
|
||||
for frets, valid in (((1, 1, 1, 1), True), ((1, 3, 1, 3), True), ((1, 4, 1, 4), True), ((1, 5, 1, 4), False), ((0, 1, 1, 1), False), ((12, 14, 14, 12), True), ((12, 15, 14, 12), True), ((13, 13, 13, 13), False), ((12, 16, 14, 12), False), ((12, 21, 12, 12), False)):
|
||||
with self.subTest(frets=frets):
|
||||
self.assertEqual(generator.acoustic_voicing.playable(voicing(frets)), valid)
|
||||
for symbol in ('CM', 'C7b9', 'C13', 'Cdim7'):
|
||||
self.assertTrue(all(generator.acoustic_voicing.playable(v) for v in candidates(symbol)))
|
||||
def test_autumn_leaves_cm7_and_f7_cannot_span_four_fret_positions(self):
|
||||
def test_four_fret_cm7_and_f7_are_fallbacks_not_preferred_shapes(self):
|
||||
for symbol, frets in (('Cm7', (3, 1, 3, 4)), ('F7', (3, 1, 2, 4))):
|
||||
with self.subTest(symbol=symbol):
|
||||
self.assertFalse(generator.acoustic_voicing.playable(voicing(frets)))
|
||||
self.assertNotIn(voicing(frets), candidates(symbol))
|
||||
self.place(planned_document([[(0, symbol)]]))
|
||||
self.assertTrue(generator.acoustic_voicing.playable(voicing(frets)))
|
||||
self.assertIn(voicing(frets), candidates(symbol))
|
||||
document = planned_document([[(0, symbol)]])
|
||||
selected = generator.acoustic_voicing.select(generator.acoustic.harmony_spans(document))
|
||||
self.assertTrue(all(generator.fretboard.span_penalty(v) == 0 for v in selected))
|
||||
self.place(document)
|
||||
def test_acoustic_cost_has_no_register_span_or_shape_penalties(self):
|
||||
options = (voicing((1, 1, 1, 1)), voicing((1, 3, 2, 3)), voicing((12, 12, 12, 12)), voicing((12, 14, 13, 14)))
|
||||
states = [generator.acoustic_voicing.Placement(generator.acoustic_voicing.Cost())]
|
||||
@@ -218,7 +221,7 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
options = [candidates(s) for s in harmonies]
|
||||
requirements = [generator.fretboard.chord_requirements(span.harmony) for span in spans]
|
||||
def cost(path):
|
||||
return generator.acoustic_voicing.Cost(sum(generator.acoustic_voicing.movement(a, b) for a, b in zip(path, path[1:])), sum(len(set(r.allowed) - {p.midi % 12 for p in v}) for r, v in zip(requirements, path)))
|
||||
return generator.acoustic_voicing.Cost(sum(generator.fretboard.span_penalty(v) for v in path), sum(generator.acoustic_voicing.movement(a, b) for a, b in zip(path, path[1:])), sum(len(set(r.allowed) - {p.midi % 12 for p in v}) for r, v in zip(requirements, path)))
|
||||
selected = generator.acoustic_voicing.select(spans)
|
||||
self.assertEqual(cost(selected), min(cost(path) for path in itertools.product(*options)))
|
||||
self.assertEqual(selected, generator.acoustic_voicing.select(spans))
|
||||
@@ -229,16 +232,16 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
options = candidates(symbol)
|
||||
self.assertTrue(options)
|
||||
self.assertTrue(all(len(v) == 4 and {p.midi % 12 for p in v} == classes for v in options))
|
||||
for symbol, omitted in (('C7no1', 0), ('C7no5', 7)):
|
||||
for symbol, omitted in (('C7no1', 0), ('C7no5', 7), ('Dmno1', 2), ('CMno5', 7)):
|
||||
self.assertTrue(candidates(symbol))
|
||||
self.assertTrue(all(omitted not in {p.midi % 12 for p in v} for v in candidates(symbol)))
|
||||
def test_unplayable_omission_is_not_restored_to_fill_upper_strings(self):
|
||||
for symbol in ('Dmno5', 'Dmno1', 'CMno5'):
|
||||
for symbol in ['Dmno5']:
|
||||
with self.subTest(symbol=symbol):
|
||||
self.assertFalse(candidates(symbol))
|
||||
document = planned_document([[(0, symbol)]])
|
||||
before = copy.deepcopy(document)
|
||||
with self.assertRaisesRegex(ValueError, f'{symbol}: no acoustic grip.*3 consecutive fret positions'):
|
||||
with self.assertRaisesRegex(ValueError, f'{symbol}: no acoustic grip.*4 consecutive fret positions'):
|
||||
generator.acoustic_voicing.place(document)
|
||||
self.assertEqual(document, before)
|
||||
def test_explicit_inversions_and_nonchord_slash_basses_are_preserved(self):
|
||||
|
||||
Reference in New Issue
Block a user