Training Model

This commit is contained in:
khodowany
2026-10-05 19:44:33 -05:00
parent db7966a430
commit e7180e557b
6603 changed files with 8919 additions and 523063 deletions
+12 -77
View File
@@ -12,8 +12,6 @@ import generator.model
import generator.parts
import generator.playback
import tests.arrangement_fixtures
import generator.rhythm
import tests.rhythm_fixtures
import tests.test_accompaniment
import tests.test_musicxml
import tests.test_parts
@@ -21,7 +19,7 @@ import tests.test_parts
class GuitarPartTests(unittest.TestCase):
def section(self, content, part='electric', divisions=1):
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(content, divisions=divisions))
result = tests.arrangement_fixtures.section_document(source, part, rhythms=tests.rhythm_fixtures.library())
result = tests.arrangement_fixtures.section_document(source, part)
generator.model.validate_document(result)
return result
def test_profile_shapes_cover_defining_chord_tones(self):
@@ -48,56 +46,21 @@ class GuitarPartTests(unittest.TestCase):
self.assertEqual(set(requirements.allowed), {0, 7})
self.assertFalse(generator.acoustic_voicing.candidates(requirements))
self.assertFalse(generator.guitar.chord_positions(requirements, profile='electric'))
def test_acoustic_combines_independent_rhythms_with_a_fixed_grip(self):
result = self.section(tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note(), 'acoustic')
measure = result['staves'][0]['measures'][0]
self.assertEqual(len(measure['voices']), 1)
notes = measure['voices'][0]['notes']
bass = [(n['onset'], p) for n in notes for p in n['pitches'] if p['tablature']['string'] >= 5 and 'tie_from' not in p]
upper = {generator.model.fraction(n['onset']) for n in notes for p in n['pitches'] if p['tablature']['string'] <= 4 and 'tie_from' not in p}
self.assertNotEqual({generator.model.fraction(onset) for onset, _ in bass}, upper)
self.assertEqual(len(bass), 2)
self.assertIn([generator.model.pitch_number(p) % 12 for _, p in bass], ([0, 0], [7, 7]))
self.assertEqual(bass[0][1]['tablature'], bass[1][1]['tablature'])
upper_shapes = {tuple((p['tablature']['string'], p['tablature']['fret']) for p in n['pitches'] if p['tablature']['string'] <= 4) for n in notes}
self.assertEqual(len(upper_shapes - {()}), 1)
self.assertEqual(sum(generator.model.fraction(n['duration']) for n in notes), 4)
for note in notes:
self.assertNotIn('stem', note)
strings = sorted(p['tablature']['string'] for p in note['pitches'] if p['tablature']['string'] <= 4)
if strings:
self.assertEqual(strings, [2, 3, 4])
timeline = generator.playback.compile_timeline(result)['events']
by_id = {p['id']: p for v in measure['voices'] for n in v['notes'] for p in n['pitches']}
for event in timeline:
start = generator.model.fraction(event['onset'])
simultaneous = [e for e in timeline if generator.model.fraction(e['onset']) <= start < generator.model.fraction(e['onset']) + generator.model.fraction(e['duration'])]
positions = [generator.fretboard.TabPosition(by_id[e['id']]['tablature']['string'], by_id[e['id']]['tablature']['fret'], int(generator.model.fraction(e['pitch']))) for e in simultaneous]
self.assertEqual(len(positions), len({p.string_number for p in positions}))
self.assertTrue(generator.fretboard.within_fret_span(positions))
def test_sparse_acoustic_uses_compact_playable_chords(self):
result=self.section(tests.test_parts.xml_harmony()+tests.test_musicxml.xml_note(),'acoustic')
generator.model.validate_document(result)
generator.fretboard.validate_document(result)
sounding=[n for m in result['staves'][0]['measures'] for n in generator.score.measure_notes(m) if n['pitches']]
self.assertTrue(sounding)
self.assertTrue(all(2<=len(n['pitches'])<=3 for n in sounding))
def test_explicit_bass_is_preserved_for_triads_and_extended_chords(self):
for symbol, expected in (('CM/E', 4), ('C7b9/E', 4)):
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note()))
chord = generator.harmony.parse_symbol(symbol)
source['staves'][0]['measures'][0]['harmonies'] = [{**chord, 'id': 'h1', 'onset': generator.model.rational(0)}]
result = tests.arrangement_fixtures.section_document(source, 'acoustic', rhythms=tests.rhythm_fixtures.library())
result = tests.arrangement_fixtures.section_document(source, 'acoustic')
notes = tests.test_musicxml.notes(result)
self.assertEqual({generator.model.pitch_number(p) % 12 for n in notes for p in n['pitches'] if p['tablature']['string'] >= 5}, {expected})
def test_acoustic_bass_and_upper_chord_can_attack_together(self):
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note()))
prepared = generator.parts.prepare_document(source)
bar = tests.test_accompaniment.quarter_bar(4, {0, 2})
plan = generator.accompaniment.Plan((bar,), (bar,), ())
result = tests.arrangement_fixtures.section_document(prepared, 'acoustic', rhythms=tests.rhythm_fixtures.library(), plan=plan)
generator.model.validate_document(result)
self.assertEqual(len(result['staves'][0]['measures'][0]['voices']), 1)
notes = tests.test_musicxml.notes(result)
self.assertEqual([generator.model.fraction(n['onset']) for n in notes if n['pitches']], [0, 2])
for onset in (0, 2):
pitches = [p for n in notes if generator.model.fraction(n['onset']) == onset for p in n['pitches']]
positions = [generator.fretboard.TabPosition(p['tablature']['string'], p['tablature']['fret'], int(generator.model.pitch_number(p) - 12)) for p in pitches]
self.assertIn(tuple(sorted((p.string_number for p in positions), reverse=True)), ((5, 4, 3, 2), (6, 4, 3, 2)))
self.assertTrue(generator.fretboard.within_fret_span(positions))
self.assertEqual({min(generator.model.pitch_number(p) for p in n['pitches']) % 12 for n in notes if n['pitches']}, {expected})
def test_long_melody_chords_and_short_notes(self):
content = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note('E', 2, 'half', octave=4) + tests.test_musicxml.xml_note('G', 1, 'quarter', octave=4) + tests.test_musicxml.xml_note('C', 1, 'quarter', octave=5)
notes = tests.test_musicxml.notes(self.section(content))
@@ -131,7 +94,7 @@ class GuitarPartTests(unittest.TestCase):
first = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note('E', octave=4, extra='<tie type="start"/>')
second = tests.test_parts.xml_harmony(root) + tests.test_musicxml.xml_note('E', octave=4, extra='<tie type="stop"/>')
source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([first, second]))
result = tests.arrangement_fixtures.section_document(source, 'electric', rhythms=tests.rhythm_fixtures.library())
result = tests.arrangement_fixtures.section_document(source, 'electric')
generator.model.validate_document(result)
notes = [n for m in result['staves'][0]['measures'] for v in m['voices'] for n in v['notes']]
self.assertTrue(all(len(n['pitches']) in (3, 4) for n in notes))
@@ -172,36 +135,8 @@ class GuitarPartTests(unittest.TestCase):
before = copy.deepcopy(source)
for builder in (tests.arrangement_fixtures.section_document, generator.arrange.generate.arrange_document):
for part in ('electric', 'vocal'):
result = builder(source, part, rhythms=tests.rhythm_fixtures.library())
result = builder(source, part)
notes = [n for m in result['staves'][0]['measures'] for v in m['voices'] for n in v['notes'] if n['pitches']]
expected = 76 if part == 'electric' else 64
self.assertEqual({generator.model.pitch_number(n['pitches'][0]) for n in notes}, {expected})
self.assertEqual(source, before)
def test_electric_whole_notes_are_uncapped_but_do_not_continue(self):
source = tests.test_accompaniment.melody_measures(1)[0]
for continuation in (False, True):
bar = generator.rhythm.Bar((generator.rhythm.Event(fractions.Fraction(), fractions.Fraction(4), True, 'whole', continuation=continuation),), fractions.Fraction(4))
events = generator.accompaniment.realize(source, bar, None, lane='electric')
self.assertEqual(tests.test_accompaniment.timing(events), [(0, 4, 'rest')] if continuation else [(0, 4, 'note')])
def test_long_electric_accompaniment_preserves_playback_durations_without_ties(self):
content = tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note('E', octave=4)
source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 8))
bar = generator.rhythm.Bar((generator.rhythm.Event(fractions.Fraction(), fractions.Fraction(4), True, 'whole'),), fractions.Fraction(4))
library = generator.rhythm.Library((generator.rhythm.Snippet('4_4/3/whole', '4/4', (bar,)),))
result = generator.arrange.generate.arrange_document(source, 'electric', rhythms=library)
generator.model.validate_document(result)
durations = {}
section = ''
for measure in result['staves'][0]['measures']:
section = measure.get('section_label') or section
if section not in ('Vocal Melody', 'Ending'):
continue
notes = measure['voices'][0]['notes']
self.assertEqual(tests.test_accompaniment.timing(notes), [(0, 4, 'note')])
for pitch in notes[0]['pitches']:
self.assertNotIn('tie_from', pitch)
self.assertNotIn('tie_to', pitch)
durations[pitch['id']] = generator.model.fraction(pitch['duration'])
self.assertTrue(durations)
timeline = generator.playback.compile_timeline(result)['events']
self.assertEqual({e['id']: generator.model.fraction(e['duration']) for e in timeline if e['id'] in durations}, durations)