Generation
ci / checks (push) Successful in 1h10m52s

This commit is contained in:
khodowany
2026-09-26 17:26:22 -05:00
parent 11222a9f8c
commit c8581ea51a
11 changed files with 343 additions and 186 deletions
+25 -28
View File
@@ -46,6 +46,11 @@ def acoustic_document(bars):
class AcousticTests(unittest.TestCase):
def combine(self, document):
generator.acoustic.prepare(document)
generator.model.validate_document(document)
prepared = copy.deepcopy(document)
generator.acoustic.prepare(document)
self.assertEqual(document, prepared)
before = sounding(document)
generator.acoustic.combine(document)
generator.model.validate_document(document)
@@ -57,6 +62,8 @@ class AcousticTests(unittest.TestCase):
self.assertNotIn('beams', note)
for pitch in note['pitches']:
self.assertNotIn('stem', pitch)
self.assertNotIn('tie_from', pitch)
self.assertNotIn('tie_to', pitch)
once = copy.deepcopy(document)
generator.acoustic.combine(document)
self.assertEqual(document, once)
@@ -65,37 +72,25 @@ class AcousticTests(unittest.TestCase):
notes = self.combine(acoustic_document([([(0, 2), (3, 1)], [(0, 2), (3, 1)])]))[0]
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 2, 'note'), (2, 1, 'rest'), (3, 1, 'note')])
self.assertEqual([len(n['pitches']) for n in notes], [3, 0, 3])
def test_sustained_bass_ties_through_upper_attacks_and_releases(self):
def test_bass_releases_at_next_upper_attack_without_reattacking(self):
notes = self.combine(acoustic_document([([(0, 4)], [(1, 1), (3, 1)])]))[0]
self.assertTrue(all(n['kind'] == 'note' for n in notes))
self.assertEqual([len(n['pitches']) for n in notes], [1, 3, 1, 3])
for first, second in zip(notes, notes[1:]):
self.assertEqual(first['pitches'][0]['tie_to'], second['pitches'][0]['id'])
self.assertEqual(first['pitches'][0]['tablature'], second['pitches'][0]['tablature'])
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 1, 'note'), (1, 1, 'note'), (2, 1, 'rest'), (3, 1, 'note')])
self.assertEqual([len(n['pitches']) for n in notes], [1, 2, 0, 2])
def test_repeated_pitch_is_an_attack_not_a_tie(self):
notes = self.combine(acoustic_document([([(0, 2), (2, 2)], [(0, 4)])]))[0]
self.assertEqual(len(notes), 2)
self.assertNotIn('tie_to', notes[0]['pitches'][0])
self.assertNotIn('tie_from', notes[1]['pitches'][0])
for first, second in zip(notes[0]['pitches'][1:], notes[1]['pitches'][1:]):
self.assertEqual(first['tie_to'], second['id'])
def test_existing_tie_boundary_does_not_add_a_redundant_event(self):
document = acoustic_document([([(0, 1), (1, 1)], [(2, 2)])])
first, second = document['staves'][0]['measures'][0]['voices'][0]['notes'][:2]
first['pitches'][0]['tie_to'] = second['pitches'][0]['id']
second['pitches'][0]['tie_from'] = first['pitches'][0]['id']
notes = self.combine(document)[0]
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 2, 'note'), (2, 2, 'note')])
def test_cross_measure_ties_preserve_both_rhythms(self):
self.assertEqual([len(n['pitches']) for n in notes], [3, 1])
def test_simultaneous_tones_release_at_shortest_planned_duration(self):
notes = self.combine(acoustic_document([([(0, 4)], [(0, 1)])]))[0]
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 1, 'note'), (1, 3, 'rest')])
self.assertEqual(len(notes[0]['pitches']), 3)
def test_barline_releases_notes_before_next_measures_attacks(self):
document = acoustic_document([([(0, 4)], [(2, 2)]), ([(0, 2)], [(0, 1), (2, 1)])])
first, second = document['staves'][0]['measures']
for lane in range(2):
old = next(n for n in first['voices'][lane]['notes'] if n['pitches'])
new = second['voices'][lane]['notes'][0]
for a, b in zip(old['pitches'], new['pitches']):
a['tie_to'], b['tie_from'] = b['id'], a['id']
notes = self.combine(document)
self.assertTrue(all('tie_from' in p for p in notes[1][0]['pitches']))
self.assertEqual(tests.test_accompaniment.timing(notes[0]), [(0, 2, 'note'), (2, 2, 'note')])
self.assertEqual(tests.test_accompaniment.timing(notes[1]), [(0, 1, 'note'), (1, 1, 'rest'), (2, 1, 'note'), (3, 1, 'rest')])
self.assertEqual(notes[1][-1]['kind'], 'rest')
def test_silence_collapses_to_a_single_measure_rest(self):
notes = self.combine(acoustic_document([([], [])]))[0]
@@ -109,21 +104,23 @@ class AcousticTests(unittest.TestCase):
voice['notes'] = voice['notes'][:1]
notes = self.combine(document)[0]
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 1, 'note')])
def test_five_eighths_uses_conventional_tied_notes_not_a_tuplet(self):
def test_five_eighths_shortens_to_one_note_without_ties_or_reattacks(self):
notes = self.combine(acoustic_document([([(0, fractions.Fraction(5, 2))], [])]))[0]
self.assertEqual([n['written'] for n in notes], [{'type': 'half', 'dots': 0}, {'type': 'eighth', 'dots': 0}, {'type': 'quarter', 'dots': 1}])
self.assertEqual(notes[0]['pitches'][0]['tie_to'], notes[1]['pitches'][0]['id'])
self.assertEqual([n['written'] for n in notes], [{'type': 'half', 'dots': 0}, {'type': 'half', 'dots': 0}])
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 2, 'note'), (2, 2, 'rest')])
def test_unrelated_voices_and_solo_slashes_are_unchanged(self):
document = acoustic_document([([(0, 4)], [(0, 4)])])
for part in ('electric', 'vocal', None):
document['staves'][0]['arrangement_part_type'] = part
before = copy.deepcopy(document)
generator.acoustic.prepare(document)
generator.acoustic.combine(document)
self.assertEqual(document, before)
document['staves'][0]['arrangement_part_type'] = 'acoustic'
measure = document['staves'][0]['measures'][0]
measure['voices'] = [{'id': 'voice-arranged', 'notes': [generator.model.make_event('slash', fractions.Fraction(), fractions.Fraction(4), 'note') | {'voice': 'voice-arranged', 'notehead': 'slash'}]}]
before = copy.deepcopy(document)
generator.acoustic.prepare(document)
generator.acoustic.combine(document)
self.assertEqual(document, before)
def test_song_and_practice_integration_preserve_playback(self):
@@ -137,7 +134,7 @@ class AcousticTests(unittest.TestCase):
generator.model.validate_document(combined)
self.assertTrue(all(len(m['voices']) == 1 for m in combined['staves'][0]['measures']))
def test_engraved_chord_has_one_stem_and_no_competing_rests(self):
document = acoustic_document([([(0, 4)], [(1, 1), (3, 1)])])
document = acoustic_document([([(0, 4)], [(1, 2), (3, 1)])])
self.combine(document)
fonts = generator.engraving.fonts.Fonts(pathlib.Path('package/assets/fonts/petaluma'))
layout = generator.engraving.layout.layout_score(document, generator.engraving.types.Style(), fonts)
@@ -145,4 +142,4 @@ class AcousticTests(unittest.TestCase):
self.assertFalse([p for p in primitives if p.role in ('rest', 'tab-rest')])
for note in document['staves'][0]['measures'][0]['voices'][0]['notes']:
self.assertEqual(len([p for p in primitives if p.role == 'stem' and note['id'] in p.sources]), 1)
self.assertTrue([p for p in primitives if p.role == 'tie'])
self.assertFalse([p for p in primitives if p.role in ('tie', 'tab-tie')])