Remove XML Solo Gen
ci / checks (push) Successful in 49m36s

This commit is contained in:
khodowany
2026-10-06 04:27:29 -05:00
parent bb3bba6bbb
commit e15dc31568
11 changed files with 120 additions and 485 deletions
+28 -11
View File
@@ -11,6 +11,8 @@ import unittest.mock
import generator.production
import generator.parts
import generator.musicxml
import generator.model
import generator.score
import generator.train
import generator.training_io
import generator.vocabulary
@@ -20,7 +22,7 @@ import generator.solo.composer
import generator.solo.decoder
import generator.solo.harmonic
import generator.solo.analysis
import generator.solo.export
import tests.score_events
import tests.solo_fixtures
import tests.test_musicxml
import generator.harmonic_time
@@ -45,23 +47,38 @@ class TrainingAndRepertoire(unittest.TestCase):
self.assertNotIn('electric', json.dumps(baseline))
frozen = json.loads((ROOT / 'data/models/solo.json').read_text())
self.assertEqual(frozen, generator.solo.learned.fit(self.examples, self.provenance))
def test_repertoire_exports_exact_sounding_music_for_every_registered_pair(self):
def test_repertoire_preserves_exact_sounding_music_for_every_registered_pair(self):
manifest = json.loads((ROOT / 'data/training/manifest.json').read_text())
with tempfile.TemporaryDirectory() as directory:
for entry in manifest['examples']:
for entry in manifest['examples']:
with self.subTest(pair=entry['id']):
part = 'acoustic' if entry['slot'] == 1 else 'electric'
doc = generator.parts.prepare_document(generator.musicxml.load_musicxml(ROOT / 'data/training' / entry['source']))
source = generator.production.SourceAnalysis.read(doc)
with unittest.mock.patch('generator.solo.composer.compose', side_effect=AssertionError('authored pair must not be predicted')):
result = generator.production.generate_role(source, part + '_solo', ROOT / 'data')
path = pathlib.Path(directory) / (part + '.xml')
generator.solo.export.write_role(result, path)
actual = generator.training_io.read(path, part)
measures = result['document']['staves'][0]['measures']
first = next(i for i, m in enumerate(measures) if m.get('section_label') == part.capitalize() + ' Solo')
last = next((i for i in range(first + 1, len(measures)) if measures[i].get('section_label')), len(measures))
first -= result['details']['allocation']['lead_in_measures']
offset = sum((generator.model.fraction(m['duration']) for m in measures[:first]), fractions.Fraction())
timeline = tests.score_events.timeline(result['document'])
actual = tuple((generator.model.fraction(n['onset']) - offset,
generator.model.fraction(n['onset']) + generator.model.fraction(n['duration']) - offset,
generator.model.fraction(n['pitch'])) for n in timeline['events'])
expected = generator.training_io.read(ROOT / 'data/training' / entry['solo'], part, pitch_convention=entry['pitch_convention'])
self.assertEqual(music(actual['sounding_events']), music(expected['sounding_events']), entry['id'])
self.assertEqual([(m['start'], m['duration']) for m in actual['measures']], [(m['start'], m['duration']) for m in expected['measures']])
self.assertEqual(actual['discrepancies'], [])
self.assertTrue(all(n['legal_positions_at_sounding_pitch'] for n in actual['notes'] if n['pitch']))
self.assertEqual(actual, music(expected['sounding_events']))
cursor, spans = fractions.Fraction(), []
for measure in measures[first:last]:
duration = generator.model.fraction(measure['duration'])
spans.append((str(cursor), str(duration)))
cursor += duration
for note in generator.score.measure_notes(measure):
for pitch in note['pitches']:
position = pitch['tablature']
self.assertIn(position['string'], range(1, 7))
self.assertIn(position['fret'], range(1, 13))
self.assertEqual(spans, [(m['start'], m['duration']) for m in expected['measures']])
self.assertEqual(expected['discrepancies'], [])
def test_lookup_uses_source_music_and_slot_not_title_or_path(self):
entry = json.loads((ROOT / 'data/training/manifest.json').read_text())['examples'][0]
doc = generator.parts.prepare_document(generator.musicxml.load_musicxml(ROOT / 'data/training' / entry['source']))