Comping Gen
This commit is contained in:
@@ -43,7 +43,7 @@ class LayeredComping(unittest.TestCase):
|
||||
for part, candidates in choices.items():
|
||||
self.assertTrue(expected <= {dict(c.families)['pattern'] for c in candidates})
|
||||
for c in candidates:
|
||||
generator.comping_rhythm.validate_events(c.events, part)
|
||||
generator.comping_rhythm.validate_events(c.events, part, (4, 4))
|
||||
for start in (0, 4):
|
||||
self.assertLessEqual(sum(start <= e.start < start + 4 for e in c.events), 3 if part == 'acoustic' else 2)
|
||||
self.assertAlmostEqual(c.cost, sum(dict(c.contributions).values()))
|
||||
@@ -51,7 +51,7 @@ class LayeredComping(unittest.TestCase):
|
||||
e = {tuple(e.start for e in c.events) for c in choices['electric']}
|
||||
self.assertFalse(a & e)
|
||||
self.assertTrue(any(c.events[0].start > 0 for c in choices['acoustic']))
|
||||
self.assertTrue(any(c.events[0].lane == 'upper' for c in choices['acoustic']))
|
||||
self.assertTrue(all(c.events[0].lane in ('bass', 'combined') for c in choices['acoustic'] if c.events))
|
||||
self.assertTrue(any(e.end - e.start > 1 for c in choices['electric'] for e in c.events))
|
||||
def test_separate_short_attacks_and_only_synchronized_long_combinations(self):
|
||||
F = fractions.Fraction
|
||||
@@ -61,6 +61,61 @@ class LayeredComping(unittest.TestCase):
|
||||
for events in ((attack('combined', F(0), F(1)),), (attack('bass', F(0), F(1)), attack('upper', F(1, 2), F(1))), (attack('bass', F(0), F(2)),)):
|
||||
with self.assertRaises(ValueError):
|
||||
generator.comping_rhythm.validate_events(events, 'acoustic')
|
||||
def test_bass_first_reassigns_layers_without_moving_or_adding_attacks(self):
|
||||
source = tests.solo_fixtures.song(count=2, chords=('C7',))
|
||||
flat = generator.comping_rhythm.flatten(tuple((m, None, i) for i, m in enumerate(source['staves'][0]['measures'])))
|
||||
patterns = self.resources['accompaniment']['instruments']['acoustic']['patterns']
|
||||
before = copy.deepcopy(patterns)
|
||||
for pattern in patterns:
|
||||
if pattern['lengths'] != ['4', '4']:
|
||||
continue
|
||||
events, _ = generator.comping_rhythm.fit(pattern, flat, None, 'acoustic')
|
||||
self.assertEqual([(e.start, e.end - e.start) for e in events], [(fractions.Fraction(r['onset']), fractions.Fraction(r['duration'])) for r in pattern['events']])
|
||||
for start in (0, 4):
|
||||
local = [e for e in events if start <= e.start < start + 4]
|
||||
if local:
|
||||
self.assertIn(local[0].lane, ('bass', 'combined'))
|
||||
self.assertEqual(before, patterns)
|
||||
def test_missing_harmony_reassigns_first_survivor_before_anticipation(self):
|
||||
m = copy.deepcopy(self.source['staves'][0]['measures'][0])
|
||||
m['harmonies'] = [{'id': 'absent', 'kind': 'none', 'onset': generator.model.rational(0)},
|
||||
{**generator.harmony.parse_symbol('C7'), 'id': 'current', 'onset': generator.model.rational(1)},
|
||||
{**generator.harmony.parse_symbol('FM7'), 'id': 'next', 'onset': generator.model.rational(2)}]
|
||||
pattern = {'lengths': ['4'], 'events': [
|
||||
{'onset': '0', 'duration': '1', 'layer': 'bass'},
|
||||
{'onset': '3/2', 'duration': '1', 'layer': 'upper', 'anticipate': True},
|
||||
{'onset': '7/2', 'duration': '1/2', 'layer': 'bass'}]}
|
||||
events, changes = generator.comping_rhythm.fit(pattern, m, None, 'acoustic', anticipate=True)
|
||||
self.assertEqual([(e.lane, e.start, e.end, e.harmonic_time) for e in events], [('bass', fractions.Fraction(3, 2), 2, None), ('upper', fractions.Fraction(7, 2), 4, None)])
|
||||
self.assertIn('layer_assignment', {kind for kind, _ in changes})
|
||||
limited, _ = generator.comping_rhythm.fit(pattern, m, None, 'acoustic', anticipate=True, limit=fractions.Fraction(2))
|
||||
self.assertEqual(limited, (events[0],))
|
||||
def test_combined_first_attack_and_carried_hold_do_not_require_reattacks(self):
|
||||
attack, F = generator.comping_rhythm.Attack, fractions.Fraction
|
||||
valid = (attack('combined', F(3), F(5)), attack('bass', F(6), F(7)))
|
||||
generator.comping_rhythm.validate_events(valid, 'acoustic', (4, 4))
|
||||
generator.comping_rhythm.validate_events(valid[:1], 'acoustic', (4, 4))
|
||||
invalid = (valid[0], attack('upper', F(6), F(7)))
|
||||
with self.assertRaisesRegex(ValueError, 'first acoustic attack'):
|
||||
generator.comping_rhythm.validate_events(invalid, 'acoustic', (4, 4))
|
||||
def test_completed_score_rejects_upper_first_even_after_a_rest(self):
|
||||
source = tests.solo_fixtures.song(count=2, chords=('C7',))
|
||||
pattern = {'id': 'test.delayed', 'family': 'call_response', 'lengths': ['4', '4'], 'events': [
|
||||
{'onset': '1', 'duration': '1/2', 'layer': 'upper'}, {'onset': '2', 'duration': '1', 'layer': 'bass'},
|
||||
{'onset': '5', 'duration': '1', 'layer': 'combined'}]}
|
||||
# The final combined event must be longer than one beat.
|
||||
pattern['events'][-1]['duration'] = '2'
|
||||
resources = copy.deepcopy(self.resources)
|
||||
resources['accompaniment']['instruments']['acoustic']['patterns'] = [pattern]
|
||||
with generator.vocabulary.using(resources, 'acoustic_accompaniment'):
|
||||
canonical = generator.comping.Canonical(source, 'acoustic', 'bass-first')
|
||||
measures = source['staves'][0]['measures']
|
||||
source['staves'][0]['measures'] = [canonical(m, None, i) for i, m in enumerate(measures)]
|
||||
generator.acoustic.validate_comping(source)
|
||||
first = source['staves'][0]['measures'][0]
|
||||
first['voices'] = [v for v in first['voices'] if v['id'] != 'bass']
|
||||
with self.assertRaisesRegex(ValueError, 'first acoustic attack'):
|
||||
generator.acoustic.validate_comping(source)
|
||||
def test_harmonic_changes_clip_direct_holds_without_extra_attacks(self):
|
||||
source = copy.deepcopy(self.source)
|
||||
for m in source['staves'][0]['measures'][:2]:
|
||||
|
||||
Reference in New Issue
Block a user