@@ -210,6 +210,45 @@ class AccompanimentPlanningTests(unittest.TestCase):
|
||||
for bar in plan.electric[:3]:
|
||||
self.assertIn(len(bar.attacks()), {beats // 2, (beats + 1) // 2})
|
||||
self.assertLessEqual(len(plan.electric[3].attacks()), (beats + 1) // 2 + 1)
|
||||
def test_upper_density_targets_three_attacks_per_four_quarter_beats(self):
|
||||
library = tests.rhythm_fixtures.library()
|
||||
for beats, counts in ((2, {1, 2}), (3, {2}), (4, {3}), (5, {4})):
|
||||
with self.subTest(beats=beats):
|
||||
plan = generator.accompaniment.plan_section(melody_measures(8, beats=beats, dense=True), library)
|
||||
for bar in plan.acoustic_upper[:3]:
|
||||
self.assertIn(len(bar.attacks()), counts)
|
||||
for bar in plan.acoustic_bass[:3]:
|
||||
self.assertIn(len(bar.attacks()), {beats // 2, (beats + 1) // 2})
|
||||
self.assertEqual(plan.acoustic_upper[:4], plan.acoustic_upper[4:])
|
||||
def test_upper_density_scales_with_pickup_and_active_harmony_duration(self):
|
||||
for beats in (1, 2, 3):
|
||||
for pickup in (False, True):
|
||||
with self.subTest(beats=beats, pickup=pickup):
|
||||
length = beats if pickup else 4
|
||||
content = tests.test_parts.xml_harmony(offset=0 if pickup else 4 - beats) + tests.test_musicxml.xml_note(duration=1, kind='quarter') * length
|
||||
measure = tests.test_musicxml.parse(xml_bars([content], implicit=pickup))['staves'][0]['measures'][0]
|
||||
item = generator.accompaniment.context(measure, None)
|
||||
bar = quarter_bar(4, {0, 1, 2, 3})
|
||||
self.assertEqual(item.offset, 4 - beats if pickup else 0)
|
||||
self.assertEqual(len(generator.accompaniment.sounding_attacks(bar, item)), beats)
|
||||
self.assertEqual(generator.accompaniment.rank(bar, [(item, ())], 'upper')[2], fractions.Fraction(beats, 4))
|
||||
def test_harmonic_coverage_outweighs_upper_density_target(self):
|
||||
content = ''.join(tests.test_parts.xml_harmony(root, offset=i) for i, root in enumerate(('C', 'F', 'G', 'C'))) + tests.test_musicxml.xml_note()
|
||||
measure = tests.test_musicxml.parse(xml_bars([content]))['staves'][0]['measures'][0]
|
||||
item = generator.accompaniment.context(measure, None)
|
||||
sparse, covered = quarter_bar(4, {0, 1, 2}), quarter_bar(4, {0, 1, 2, 3})
|
||||
sparse_rank = generator.accompaniment.rank(sparse, [(item, ())], 'upper')
|
||||
covered_rank = generator.accompaniment.rank(covered, [(item, ())], 'upper')
|
||||
self.assertEqual((sparse_rank[2], covered_rank[2]), (0, 1))
|
||||
self.assertLess(covered_rank, sparse_rank)
|
||||
library = generator.rhythm.Library(tuple(generator.rhythm.Snippet(name, '4/4', (bar,)) for name, bar in (('sparse', sparse), ('covered', covered))))
|
||||
plan = generator.accompaniment.plan_section([measure] * 3, library)
|
||||
self.assertEqual(plan.acoustic_upper, (covered,) * 3)
|
||||
def test_upper_density_is_not_an_attack_cap(self):
|
||||
bar = quarter_bar(4, {0, 1, 2, 3})
|
||||
library = generator.rhythm.Library((generator.rhythm.Snippet('dense', '4/4', (bar,)),))
|
||||
plan = generator.accompaniment.plan_section(melody_measures(4, dense=True), library)
|
||||
self.assertEqual(plan.acoustic_upper, (bar,) * 4)
|
||||
def test_section_repeats_four_bar_motif_with_at_most_one_beat_variation(self):
|
||||
for beats in (2, 3, 4, 5):
|
||||
measures = melody_measures(12, beats=beats, dense=True)
|
||||
@@ -227,17 +266,33 @@ class AccompanimentPlanningTests(unittest.TestCase):
|
||||
reordered = generator.rhythm.Library(tuple(reversed([s for group in library.snippets.values() for s in group])))
|
||||
self.assertEqual(generator.accompaniment.plan_section(measures, reordered), plan)
|
||||
self.assertEqual(measures, before)
|
||||
def test_acoustic_and_electric_coordinate_when_space_is_available(self):
|
||||
def test_acoustic_and_electric_coordinate_when_target_density_has_gaps(self):
|
||||
content = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note()
|
||||
measures = tests.test_musicxml.parse(xml_bars([content] * 4))['staves'][0]['measures']
|
||||
plan = generator.accompaniment.plan_section(measures, tests.rhythm_fixtures.library())
|
||||
for index in range(3):
|
||||
acoustic, electric = set(plan.acoustic_upper[index].attacks()), set(plan.electric[index].attacks())
|
||||
self.assertTrue(acoustic)
|
||||
self.assertTrue(electric)
|
||||
self.assertFalse(acoustic & electric)
|
||||
self.assertNotIn(0, acoustic | electric)
|
||||
self.assertGreater(len(acoustic), len(electric))
|
||||
bass = quarter_bar(4, {0, 2})
|
||||
electric = quarter_bar(4, {1, 3})
|
||||
upper = generator.rhythm.Bar(tuple(generator.rhythm.Event(fractions.Fraction(i, 2), fractions.Fraction(1, 2), i in (1, 3, 5), 'eighth') for i in range(8)), fractions.Fraction(4))
|
||||
library = generator.rhythm.Library(tuple(generator.rhythm.Snippet(name, '4/4', (bar,)) for name, bar in (('bass', bass), ('electric', electric), ('upper', upper))))
|
||||
plan = generator.accompaniment.plan_section(measures, library)
|
||||
self.assertEqual(plan.acoustic_bass, (bass,) * 4)
|
||||
self.assertEqual(plan.acoustic_upper, (upper,) * 4)
|
||||
self.assertEqual(plan.electric, (electric,) * 4)
|
||||
def test_equal_density_candidates_favor_gaps_around_melody_and_companions(self):
|
||||
content = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note()
|
||||
measure = tests.test_musicxml.parse(xml_bars([content]))['staves'][0]['measures'][0]
|
||||
item = generator.accompaniment.context(measure, None)
|
||||
for role, companions, collision, gap in (('upper', (), {0, 1, 2}, {1, 2, 3}), ('electric', (2,), {0, 2}, {1, 3})):
|
||||
with self.subTest(role=role):
|
||||
ranks = [generator.accompaniment.rank(quarter_bar(4, attacks), [(item, companions)], role) for attacks in (gap, collision)]
|
||||
self.assertLess(ranks[0], ranks[1])
|
||||
def test_equal_density_candidates_consider_sustained_melody(self):
|
||||
content = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note(duration=2, kind='half') + '<note><rest/><duration>2</duration><type>half</type></note>'
|
||||
measure = tests.test_musicxml.parse(xml_bars([content]))['staves'][0]['measures'][0]
|
||||
item = generator.accompaniment.context(measure, None)
|
||||
occupied, gap = quarter_bar(4, {1, 3}), quarter_bar(4, {2, 3})
|
||||
for role in ('upper', 'electric'):
|
||||
with self.subTest(role=role):
|
||||
self.assertLess(generator.accompaniment.rank(gap, [(item, ())], role), generator.accompaniment.rank(occupied, [(item, ())], role))
|
||||
def test_dense_melody_allows_collisions_and_keeps_accompaniment_audible(self):
|
||||
measures = melody_measures(8, dense=True)
|
||||
plan = generator.accompaniment.plan_section(measures, tests.rhythm_fixtures.library())
|
||||
|
||||
Reference in New Issue
Block a user