Generation
This commit is contained in:
@@ -52,18 +52,15 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
self.assertLessEqual(len(basses), 1)
|
||||
self.assertLessEqual(len(uppers), 1)
|
||||
grip = tuple(sorted({p for n in span.notes for p in shape(n)}, reverse=True))
|
||||
required, allowed, bass = generator.guitar.chord_requirements(span.harmony, 'acoustic')
|
||||
bass_classes = {bass}
|
||||
degrees = generator.harmony.degrees(span.harmony)
|
||||
if 'bass' not in span.harmony and 'inversion' not in span.harmony and 5 in degrees:
|
||||
bass_classes.add((bass + generator.harmony.degree_semitones(5, degrees[5])) % 12)
|
||||
requirements = generator.guitar.chord_requirements(span.harmony, 'acoustic')
|
||||
if basses and uppers:
|
||||
self.assertIn(tuple(p.string_number for p in grip), generator.guitar.ACOUSTIC_STRINGS)
|
||||
self.assertTrue(set(required) <= {p.midi % 12 for p in grip} <= set(allowed))
|
||||
self.assertTrue(requirements.covers({p.midi % 12 for p in grip}))
|
||||
self.assertTrue({p.midi % 12 for p in grip} <= set(requirements.allowed))
|
||||
if basses:
|
||||
self.assertIn(min(p.midi for p in grip) % 12, bass_classes)
|
||||
self.assertIn(min(p.midi for p in grip) % 12, requirements.basses)
|
||||
self.assertTrue(generator.guitar.within_fret_span(grip))
|
||||
self.assertTrue(all(0 <= p.fret <= 20 for p in grip))
|
||||
self.assertTrue(all(1 <= p.fret <= 20 for p in grip))
|
||||
for note in span.notes:
|
||||
self.assertFalse(any(k.startswith('_') for k in note))
|
||||
self.assertTrue(all('tie_from' not in p and 'tie_to' not in p for p in note['pitches']))
|
||||
@@ -78,16 +75,16 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
generator.acoustic.combine(document)
|
||||
self.assertEqual(tests.test_acoustic.sounding(document), before)
|
||||
generator.model.validate_document(document)
|
||||
def test_simpler_fifth_bass_can_win_without_alternating(self):
|
||||
def test_fifth_bass_can_win_without_alternating(self):
|
||||
document = planned_document([[(0, 'Csus')], []])
|
||||
spans = self.place(document)
|
||||
self.assertEqual(len(spans), 1)
|
||||
bass = [p for n in spans[0].notes for p in shape(n) if p.string_number >= 5]
|
||||
self.assertTrue(bass)
|
||||
self.assertEqual({p.midi % 12 for p in bass}, {7})
|
||||
root_shapes = generator.guitar.chord_positions(*generator.guitar.chord_requirements(spans[0].harmony, 'acoustic'), profile='acoustic')
|
||||
root_shapes = generator.guitar.chord_positions(generator.guitar.chord_requirements(generator.harmony.parse_symbol('Csus/C'), 'acoustic'), profile='acoustic')
|
||||
selected = tuple(sorted({p for n in spans[0].notes for p in shape(n)}, reverse=True))
|
||||
self.assertLess(generator.guitar.voicing_cost(selected, 'acoustic'), min(generator.guitar.voicing_cost(s, 'acoustic') for s in root_shapes))
|
||||
self.assertLessEqual(generator.guitar.voicing_cost(selected, 'acoustic'), min(generator.guitar.voicing_cost(s, 'acoustic') for s in root_shapes))
|
||||
def test_bass_options_follow_defined_fifth_and_explicit_basses(self):
|
||||
inversion = generator.harmony.parse_symbol('Cdim7') | {'inversion': 0}
|
||||
for chord, expected in (('C7', {0, 7}), ('Cdim7', {0, 6}), ('Caug', {0, 8}), ('Cno5', {0}), ('C7/E', {4}), (inversion, {0})):
|
||||
@@ -95,8 +92,8 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
span = generator.acoustic.harmony_spans(planned_document([[(0, chord)]]))[0]
|
||||
candidates = list(generator.guitar.acoustic_candidates(span))
|
||||
self.assertEqual({min(p.midi for p in c.shape) % 12 for c in candidates}, expected)
|
||||
required, allowed, _ = generator.guitar.chord_requirements(span.harmony, 'acoustic')
|
||||
self.assertTrue(all(set(required) <= {p.midi % 12 for p in c.shape} <= set(allowed) for c in candidates))
|
||||
requirements = generator.guitar.chord_requirements(span.harmony, 'acoustic')
|
||||
self.assertTrue(all(requirements.covers({p.midi % 12 for p in c.shape}) for c in candidates))
|
||||
def test_same_grip_survives_repeated_symbols_rest_bars_and_sections(self):
|
||||
attacks = [([(0, 1), (2, 1)], [(0, 1), (3, 1)]), ([], []), ([(2, 1)], [(1, 1), (3, 1)])]
|
||||
document = planned_document([[(0, 'C7')], [], [(0, 'C7'), (2, 'C7')]], attacks)
|
||||
@@ -118,7 +115,9 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
document = planned_document([changes], [([(i, 1) for i in range(4)], [(i, 1) for i in range(4)])])
|
||||
spans = self.place(document)
|
||||
self.assertEqual(len(spans), 4)
|
||||
self.assertEqual([min(p.midi for n in span.notes for p in shape(n)) % 12 for span in spans], [0, 4, 7, 6])
|
||||
basses = [min(p.midi for n in span.notes for p in shape(n)) % 12 for span in spans]
|
||||
self.assertIn(basses[0], (0, 7))
|
||||
self.assertEqual(basses[1:], [4, 7, 6])
|
||||
def test_non_accompaniment_passage_ends_the_span(self):
|
||||
document = planned_document([[(0, 'C7')], [], []])
|
||||
measure = document['staves'][0]['measures'][1]
|
||||
@@ -159,14 +158,34 @@ class AcousticVoicingTests(unittest.TestCase):
|
||||
candidates = list(generator.guitar.acoustic_candidates(span))
|
||||
expected = {}
|
||||
for candidate in candidates:
|
||||
expected[candidate.active] = min((previous.cost.moved(generator.guitar.movement(tuple(p for _, p in previous.active), candidate.shape)).plus(candidate.cost), index) for index, previous in enumerate(states.values()))
|
||||
actual = generator.guitar.advance_attacks(candidates, states)
|
||||
expected[candidate.active] = min((previous.cost.moved(generator.guitar.acoustic_movement(tuple(p for _, p in previous.active), candidate.shape)).plus(candidate.cost), index) for index, previous in enumerate(states.values()))
|
||||
actual = generator.guitar.advance_acoustic(candidates, states)
|
||||
predecessors = {id(p): i for i, p in enumerate(states.values())}
|
||||
self.assertEqual({key: (p.cost, predecessors[id(p.previous)]) for key, p in actual.items()}, expected)
|
||||
states = actual
|
||||
def test_impossible_grip_fails_contextually_without_partial_materialization(self):
|
||||
document = planned_document([[(0, 'C7')], [(0, 'C7b9#11')]])
|
||||
before = copy.deepcopy(document)
|
||||
with self.assertRaisesRegex(ValueError, 'measure 2, event .*no acoustic grip.*0-20'):
|
||||
with self.assertRaisesRegex(ValueError, 'measure 2, event .*no acoustic grip.*1-20'):
|
||||
generator.guitar.place_guitar(document)
|
||||
self.assertEqual(document, before)
|
||||
def test_g_minor_to_c_seventh_preserves_positions_with_fifth_bass(self):
|
||||
tuning = dict(generator.guitar.STANDARD_TUNING)
|
||||
previous = tuple(generator.guitar.TabPosition(s, f, tuning[s] + f) for s, f in ((5, 10), (3, 10), (2, 11)))
|
||||
active = tuple((str(p.string_number), p) for p in previous)
|
||||
states = {active: generator.guitar.Placement(generator.guitar.VoicingCost(), active, None, ())}
|
||||
span = generator.acoustic.harmony_spans(planned_document([[(0, 'C7')]]))[0]
|
||||
candidates = list(generator.guitar.acoustic_candidates(span))
|
||||
result = generator.guitar.advance_acoustic(candidates, states)
|
||||
best = min(result.values(), key=lambda p: (p.cost, p.active))
|
||||
chosen = tuple(p for _, p in best.active)
|
||||
self.assertEqual([(p.string_number, p.fret) for p in chosen], [(5, 10), (3, 9), (2, 11)])
|
||||
self.assertEqual(generator.guitar.acoustic_movement(previous, chosen), 1)
|
||||
self.assertEqual(generator.guitar.acoustic_movement(chosen, previous), 1)
|
||||
self.assertEqual(generator.guitar.acoustic_movement(previous, previous), 0)
|
||||
self.assertEqual(generator.guitar.acoustic_movement((), chosen), 0)
|
||||
def test_acoustic_candidates_are_not_pruned_by_grip_center(self):
|
||||
requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7'), 'acoustic')
|
||||
candidates = generator.guitar.chord_positions(requirements, profile='acoustic')
|
||||
self.assertEqual(candidates, generator.guitar.chord_positions(requirements, profile='acoustic', prune=False))
|
||||
self.assertGreater(len(candidates), len({generator.guitar.position_center(p) for p in candidates}))
|
||||
|
||||
Reference in New Issue
Block a user