+34
-73
@@ -8,6 +8,7 @@ import xml.etree.ElementTree
|
||||
|
||||
import generator.accompaniment
|
||||
import generator.model
|
||||
import generator.parts
|
||||
import generator.playback
|
||||
import generator.practice.generate
|
||||
import generator.rhythm
|
||||
@@ -301,85 +302,45 @@ class AccompanimentRealizationTests(unittest.TestCase):
|
||||
self.assertEqual(plan.acoustic_bass, (bar,))
|
||||
self.assertEqual(plan.electric, (bar,))
|
||||
|
||||
class TiedAccompanimentTests(unittest.TestCase):
|
||||
def source(self, root='C', kind='major', offset=0):
|
||||
class DetachedAccompanimentTests(unittest.TestCase):
|
||||
def source(self):
|
||||
first = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note(extra='<tie type="start"/>')
|
||||
second = tests.test_parts.xml_harmony(root, kind, offset) + tests.test_musicxml.xml_note(extra='<tie type="stop"/>')
|
||||
second = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note(extra='<tie type="stop"/>')
|
||||
return tests.test_musicxml.parse(xml_bars([first, second]))
|
||||
def assert_tied_chord(self, first, continuation):
|
||||
self.assertTrue(first['pitches'])
|
||||
self.assertEqual(len(first['pitches']), len(continuation['pitches']))
|
||||
for previous, current in zip(first['pitches'], continuation['pitches']):
|
||||
self.assertEqual(current['tie_from'], previous['id'])
|
||||
self.assertEqual(previous['tie_to'], current['id'])
|
||||
self.assertEqual(generator.model.pitch_number(current), generator.model.pitch_number(previous))
|
||||
self.assertEqual(current['tablature'], previous['tablature'])
|
||||
for note in (first, continuation):
|
||||
self.assertNotIn('_rhythm_continuation', note)
|
||||
for pitch in note['pitches']:
|
||||
self.assertEqual(pitch['duration'], note['duration'])
|
||||
self.assertEqual(pitch['written'], note['written'])
|
||||
def test_whole_note_tie_keeps_second_bar_audible_without_a_new_attack(self):
|
||||
self.check_tied_rhythm(4)
|
||||
def test_half_note_continuation_preserves_tone_duration_strings_and_following_rest(self):
|
||||
self.check_tied_rhythm(2)
|
||||
def check_tied_rhythm(self, continuation_duration):
|
||||
def test_rhythm_continuations_are_silent_in_all_accompaniment_lanes(self):
|
||||
source = self.source()
|
||||
first, second = source['staves'][0]['measures']
|
||||
bars = tied_library(2).for_meter('4/4')[0].bars
|
||||
for lane in ('bass', 'upper', 'electric'):
|
||||
events = generator.accompaniment.realize(second, bars[1], first['harmonies'][0], lane=lane)
|
||||
self.assertTrue(all(n['kind'] == 'rest' and '_rhythm_continuation' not in n for n in events))
|
||||
def test_continuation_only_bar_does_not_count_as_audible_accompaniment(self):
|
||||
measures = self.source()['staves'][0]['measures']
|
||||
with self.assertRaisesRegex(ValueError, 'no rhythm motif'):
|
||||
generator.accompaniment.plan_section(measures, tied_library(4))
|
||||
def test_planner_chooses_attacks_instead_of_continuation_only_bars(self):
|
||||
source = self.source()
|
||||
library = generator.rhythm.Library(tied_library(4).for_meter('4/4') + tests.rhythm_fixtures.library().for_meter('4/4'))
|
||||
plan = generator.accompaniment.plan_section(source['staves'][0]['measures'], library)
|
||||
for bar in (*plan.acoustic_bass, *plan.acoustic_upper):
|
||||
self.assertTrue(bar.attacks())
|
||||
def test_acoustic_stops_at_barline_without_inventing_continuation_attacks(self):
|
||||
source = self.source()
|
||||
before = copy.deepcopy(source)
|
||||
library = tied_library(continuation_duration)
|
||||
bars = library.for_meter('4/4')[0].bars
|
||||
self.assertEqual(bars[1].attacks(), ())
|
||||
plan = generator.accompaniment.plan_section(source['staves'][0]['measures'], library)
|
||||
self.assertEqual(plan.acoustic_upper, bars)
|
||||
self.assertEqual(plan.acoustic_bass, bars)
|
||||
self.assertEqual(plan.electric, bars)
|
||||
result = generator.practice.generate.practice_document(source, 'acoustic', rhythms=library)
|
||||
first, second = tests.test_musicxml.notes(result), tests.test_musicxml.notes(result, 1)
|
||||
self.assertEqual(timing(first), [(0, 4, 'note')])
|
||||
expected = [(0, continuation_duration, 'note')]
|
||||
if continuation_duration == 2:
|
||||
expected.append((2, 2, 'rest'))
|
||||
self.assertEqual(second[1]['pitches'], [])
|
||||
self.assertEqual(timing(second), expected)
|
||||
self.assert_tied_chord(first[0], second[0])
|
||||
first_tones = [p for v in result['staves'][0]['measures'][0]['voices'] for n in v['notes'] for p in n['pitches']]
|
||||
self.assertEqual({generator.model.pitch_number(p) % 12 for p in first_tones}, {0, 4, 7})
|
||||
self.assertEqual(len(result['staves'][0]['measures'][0]['voices']), 1)
|
||||
bars = tied_library(2).for_meter('4/4')[0].bars
|
||||
plan = generator.accompaniment.Plan(bars, bars, ())
|
||||
prepared = generator.parts.prepare_document(source, practice=True)
|
||||
result = generator.practice.generate._practice_document(prepared, 'acoustic', plan)
|
||||
generator.model.validate_document(result)
|
||||
self.assertEqual(timing(tests.test_musicxml.notes(result)), [(0, 4, 'note')])
|
||||
self.assertEqual(timing(tests.test_musicxml.notes(result, 1)), [(0, 4, 'rest')])
|
||||
timeline = generator.playback.compile_timeline(result)
|
||||
self.assertEqual(generator.model.fraction(timeline['duration']), 8)
|
||||
self.assertEqual(len(timeline['events']), len(first_tones))
|
||||
self.assertTrue(all(generator.model.fraction(e['onset']) == 0 and generator.model.fraction(e['duration']) == 4 + continuation_duration for e in timeline['events']))
|
||||
self.assertEqual(source, before)
|
||||
self.assertEqual(library.for_meter('4/4')[0].bars, bars)
|
||||
def test_changed_harmony_breaks_rhythm_ties_without_inventing_attacks(self):
|
||||
for root, kind in (('F', 'major'), ('C', 'minor'), ('C', 'none')):
|
||||
with self.subTest(root=root, kind=kind):
|
||||
source = self.source(root, kind)
|
||||
before = copy.deepcopy(source)
|
||||
result = generator.practice.generate.practice_document(source, 'acoustic', rhythms=tied_library(2))
|
||||
first, second = tests.test_musicxml.notes(result), tests.test_musicxml.notes(result, 1)
|
||||
self.assertEqual(timing(first), [(0, 4, 'note')])
|
||||
self.assertEqual(timing(second), [(0, 4, 'rest')])
|
||||
self.assertEqual({generator.model.pitch_number(p) % 12 for v in result['staves'][0]['measures'][0]['voices'] for n in v['notes'] for p in n['pitches']}, {0, 4, 7})
|
||||
self.assertEqual(second[0]['pitches'], [])
|
||||
for note in first + second:
|
||||
self.assertTrue(timeline['events'])
|
||||
self.assertTrue(all(generator.model.fraction(e['onset']) == 0 and generator.model.fraction(e['duration']) == 4 for e in timeline['events']))
|
||||
for measure in result['staves'][0]['measures']:
|
||||
for voice in measure['voices']:
|
||||
for note in voice['notes']:
|
||||
for pitch in note['pitches']:
|
||||
self.assertNotIn('tie_from', pitch)
|
||||
self.assertNotIn('tie_to', pitch)
|
||||
self.assertEqual(pitch['duration'], note['duration'])
|
||||
generator.model.validate_document(result)
|
||||
events = generator.playback.compile_timeline(result)['events']
|
||||
self.assertEqual({(generator.model.fraction(e['onset']), generator.model.fraction(e['duration'])) for e in events}, {(0, 4)})
|
||||
self.assertEqual(source, before)
|
||||
def test_harmony_change_inside_continuation_cuts_tied_sound_and_preserves_rest(self):
|
||||
source = self.source('F', offset=1)
|
||||
result = generator.practice.generate.practice_document(source, 'acoustic', rhythms=tied_library(2))
|
||||
first, second = tests.test_musicxml.notes(result), tests.test_musicxml.notes(result, 1)
|
||||
self.assertEqual(timing(first), [(0, 4, 'note')])
|
||||
self.assertEqual(timing(second), [(0, 1, 'note'), (1, 3, 'rest')])
|
||||
self.assert_tied_chord(first[0], second[0])
|
||||
generator.model.validate_document(result)
|
||||
events = generator.playback.compile_timeline(result)['events']
|
||||
self.assertEqual(len(events), sum(len(n['pitches']) for v in result['staves'][0]['measures'][0]['voices'] for n in v['notes']))
|
||||
self.assertTrue(all(generator.model.fraction(e['onset']) == 0 and generator.model.fraction(e['duration']) == 5 for e in events))
|
||||
self.assertEqual(source, before)
|
||||
|
||||
Reference in New Issue
Block a user