"""Literal training-score import, timing, pitch convention and TAB checks.""" import copy import fractions import pathlib import tempfile import unittest import generator.musicxml_writer import generator.score import generator.training_io import generator.solo.notation import generator.solo.placement import tests.solo_fixtures def xml(notes, *, transpose=False, leadin=''): return 'Vocal' + leadin + '60G2' + ('0-1' if transpose else '') + 'Cmajor-seventh' + notes + '' def pitched(duration, tie='', octave=5): return f'C{octave}{duration}{tie}quarter' def events(review): return [(n['start'], n['end'], n['sounding_midi']) for n in review['sounding_events']] class TrainingXmlTests(unittest.TestCase): def read(self, directory, name, content, **kwargs): path = pathlib.Path(directory) / name path.write_text(content) return generator.training_io.read(path, 'fixture/acoustic', **kwargs) def test_note_container_does_not_turn_color_label_into_fingering(self): with tempfile.TemporaryDirectory() as root: note_xml = pitched(24).replace('', '6') r = self.read(root, 'annotation.xml', xml(note_xml), form_start=1) self.assertEqual(r['annotations'][0]['text'], '6') self.assertEqual(r['annotations'][0]['container'], 'notations/technical/fingering') self.assertIsNone(r['notes'][0]['tablature']) def test_ties_not_reattacks_and_one_guitar_transposition(self): with tempfile.TemporaryDirectory() as root: a = self.read(root, 'actual.xml', xml(pitched(24), transpose=True), form_start=1) b = self.read(root, 'authored.xml', xml(pitched(12, '') + pitched(12, '')), form_start=1, pitch_convention='guitar-written') self.assertEqual(events(a), [('0', '4', '60')]) self.assertEqual(events(a), events(b)) repeated = self.read(root, 'repeat.xml', xml(pitched(12) + pitched(12)), form_start=1, pitch_convention='guitar-written') self.assertEqual(events(repeated), [('0', '2', '60'), ('2', '4', '60')]) def test_leadin_is_reported_without_harmony_or_alignment_padding(self): with tempfile.TemporaryDirectory() as root: prelude = '6' + pitched(6) + '18' r = self.read(root, 'leadin.xml', xml(pitched(24), leadin=prelude), form_start=2, pitch_convention='guitar-written') self.assertEqual(events(r), [('0', '1', '60'), ('4', '8', '60')]) self.assertEqual(r['alignment']['encoded_offset'], '4') self.assertIn('uncertainty', r['notes'][0]['harmonic_spans'][0]) def test_unplayable_pitch_is_reported_not_folded(self): with tempfile.TemporaryDirectory() as root: r = self.read(root, 'high.xml', xml(pitched(24, octave=7)), form_start=1, pitch_convention='guitar-written') self.assertEqual(r['sounding_events'][0]['sounding_midi'], '84') self.assertFalse(r['notes'][0]['legal_positions_at_sounding_pitch']) self.assertEqual(r['notes'][0]['pitch']['octave'], 7) def test_triplet_time_and_annotations_are_literal(self): with tempfile.TemporaryDirectory() as root: triplet = pitched(2).replace('quarter', 'eighth32') r = self.read(root, 'thirds.xml', xml('Lead in' + triplet * 3 + '18'), form_start=1) self.assertEqual([n['start'] for n in r['sounding_events']], ['0', '1/3', '2/3']) self.assertEqual(r['annotations'][0]['text'], 'Lead in') def test_roundtrip_preserves_actual_sounding_events_and_tab(self): document = tests.solo_fixtures.song(count=2, chords=('CM7',)) raw = (tests.solo_fixtures.note(0, fractions.Fraction(1, 3), 'c'), tests.solo_fixtures.note(fractions.Fraction(1, 3), fractions.Fraction(2, 3), 'd'), tests.solo_fixtures.note(fractions.Fraction(2, 3), 1, 'e'), tests.solo_fixtures.note(fractions.Fraction(7, 2), 5, 'g'), tests.solo_fixtures.note(5, 6, 'g')) placed = generator.solo.placement.place(raw, fractions.Fraction(120))[1] generator.solo.notation.replace_region(document, 0, generator.solo.notation.measures(document['staves'][0]['measures'], placed, 'roundtrip')) before = copy.deepcopy(document) with tempfile.TemporaryDirectory() as root: path = pathlib.Path(root) / 'fixture.xml' generator.musicxml_writer.write_musicxml(path, document, tablature=True) loaded = generator.training_io.read(path, 'fixture/acoustic', 1) sounded = [(fractions.Fraction(n['start']), fractions.Fraction(n['end']), fractions.Fraction(n['sounding_midi'])) for n in loaded['sounding_events']] self.assertEqual([(n.start, n.end, n.pitch.number()) for n in raw], sounded) tabs = [p['tablature'] for m in document['staves'][0]['measures'] for n in generator.score.measure_notes(m) for p in n['pitches']] self.assertEqual(tabs, [n['tablature'] for n in loaded['notes'] if n['pitch']]) self.assertEqual(before, document) if __name__ == '__main__': unittest.main()