Files
product-vamp/tests/test_arrangement_ending.py
khodowany bc7baea7c4
ci / checks (push) Failing after 23m22s
Cleanup
2026-10-08 02:13:45 -05:00

96 lines
4.8 KiB
Python

"""The held recording tail has exact score time and no vocal attacks."""
import generator.arrange.form
import generator.composition
import generator.source
import generator.validation
import copy
import fractions
import pathlib
import unittest
import generator.acoustic
import generator.arrange.generate
import generator.fretboard
import generator.harmony
import generator.model
import generator.musicxml
import generator.parts
import generator.performance
import generator.score
import tests.solo_fixtures
class ArrangementEnding(unittest.TestCase):
def test_current_sources_end_on_effective_harmony_with_one_attack(self):
for name, expected in (('all-of-me', 'GM6'), ('autumn_leaves', 'Dm')):
source = generator.musicxml.load_musicxml(pathlib.Path('data/songs') / (name + '.xml'))
before = copy.deepcopy(source)
for part in ('acoustic', 'electric', 'vocal'):
result = generator.arrange.generate.arrange_document(source, part)
measures = result['staves'][0]['measures']
tail = measures[-3:]
self.assertEqual(len(measures), 171)
self.assertEqual([m['number'] for m in tail], ['169', '170', '171'])
self.assertEqual(generator.harmony.symbol(tail[0]['harmonies'][0]), expected)
self.assertEqual([m['section_label'] for m in tail], ['Ending', '', ''])
self.assertTrue(all(m['ending_sustain'] for m in tail))
notes = [n for m in tail for n in generator.score.measure_notes(m)]
attacks = [n for n in notes if n['pitches'] and not any(p.get('tie_from') for p in n['pitches'])]
self.assertEqual(len(attacks), 0 if part == 'vocal' else 1)
if part == 'vocal':
self.assertTrue(all(n['kind'] == 'rest' and not n.get('lyrics') for n in notes))
else:
self.assertEqual(sum(generator.model.fraction(n['duration']) for n in notes if n['pitches']), 12)
self.assertEqual([p['tablature'] for p in notes[0]['pitches']], [p['tablature'] for p in notes[-1]['pitches']])
if part == 'acoustic':
self.assertIn(tuple(p['tablature']['string'] for p in attacks[0]['pitches']), ((5, 4, 3, 2), (6, 4, 3, 2)))
timing = generator.performance.timing(result)
self.assertEqual(generator.model.fraction(timing['sustain_start']), 672)
self.assertEqual(generator.model.fraction(timing['duration']), 684)
self.assertEqual(len(timing['clicks']), 672)
generator.validation.validate_document(result)
generator.fretboard.validate_document(result)
self.assertEqual(source, before)
def test_ending_uses_final_meter_and_does_not_extend_solo_windows(self):
for meter in (3, 5):
source = tests.solo_fixtures.song(count=2, meter=meter, chords=('C7', 'FM7'))
prepared = generator.parts.prepare_document(source)
for part in ('acoustic', 'electric'):
role = part + '_accompaniment'
result = generator.composition.generate_role(generator.source.SourceAnalysis.read(prepared), role, pathlib.Path('data'))
tail = result['document']['staves'][0]['measures'][-3:]
self.assertEqual([generator.model.fraction(m['duration']) for m in tail], [meter] * 3)
windows = generator.arrange.form.windows(prepared, part)
self.assertEqual(windows[-1][2], role)
self.assertEqual(windows[-1][1] - windows[-1][0], 3 * meter)
self.assertEqual(sum(b - a for a, b, r in windows if r == part + '_solo'), 2 * meter)
def test_missing_or_explicitly_absent_final_chord_is_not_invented(self):
for no_chord in (False, True):
source = tests.solo_fixtures.song(count=1, chords=('C7',))
last = source['staves'][0]['measures'][-1]
if no_chord:
last['harmonies'][0]['kind'] = 'none'
else:
last['harmonies'] = []
with self.assertRaisesRegex(ValueError, 'final performed harmony'):
generator.arrange.generate.arrange_document(source, 'acoustic')
def test_partial_combination_and_short_overlap_fail_after_tie_merging(self):
source = tests.solo_fixtures.song(count=1, chords=('C7',))
doc = generator.arrange.generate.arrange_document(source, 'acoustic')
tail = copy.deepcopy(doc)
tail['staves'][0]['measures'] = tail['staves'][0]['measures'][-3:]
generator.acoustic.validate_comping(tail)
first = generator.score.measure_notes(tail['staves'][0]['measures'][0])[0]
bass = first['pitches'][0]
bass.pop('tie_to')
bass['duration'] = generator.model.rational(1)
with self.assertRaises(ValueError):
generator.acoustic.validate_comping(tail)
def test_ending_markers_cannot_interrupt_the_main_score(self):
source = tests.solo_fixtures.song(count=3, chords=('C7',))
source['staves'][0]['measures'][0]['ending_sustain'] = True
with self.assertRaisesRegex(ValueError, 'contiguous'):
generator.performance.timing(source)
if __name__ == '__main__':
unittest.main()