94 lines
4.7 KiB
Python
94 lines
4.7 KiB
Python
"""The held recording tail has exact score time and no vocal attacks."""
|
|
import copy
|
|
import fractions
|
|
import pathlib
|
|
import unittest
|
|
|
|
import generator.acoustic
|
|
import generator.arrange.ending
|
|
import generator.arrange.generate
|
|
import generator.fretboard
|
|
import generator.harmony
|
|
import generator.model
|
|
import generator.musicxml
|
|
import generator.parts
|
|
import generator.performance
|
|
import generator.production
|
|
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.model.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.production.generate_role(generator.production.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.production.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()
|