Comping
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user