Generation

This commit is contained in:
khodowany
2026-09-26 10:12:51 -05:00
parent 9fd5abd075
commit 1d5f3a9372
20 changed files with 681 additions and 155 deletions
+62 -2
View File
@@ -23,6 +23,7 @@ import generator.musicxml
import generator.playback
import generator.practice.generate
import tests.rhythm_fixtures
import tests.test_accompaniment
import tests.test_musicxml
class EngravingTests(unittest.TestCase):
@@ -36,6 +37,65 @@ class EngravingTests(unittest.TestCase):
return layout
def primitives(self, layout, role):
return [p for page in layout.pages for p in page.primitives if p.role == role]
def test_tab_omits_rests_but_keeps_standard_notation(self):
content = tests.test_musicxml.xml_note('E', 2, 'half', octave=5) + '<note><rest/><duration>2</duration><type>half</type></note>'
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(content))
document = generator.practice.generate.practice_document(source, 'electric', rhythms=tests.rhythm_fixtures.library())
layout = self.layout(document)
self.assertEqual(len(self.primitives(layout, 'rest')), 1)
self.assertEqual(self.primitives(layout, 'tab-rest'), [])
self.assertEqual(len(self.primitives(layout, 'tab')), 1)
def assert_paired_pages(self, layout):
self.assertTrue(all(len(page.systems) == 2 for page in layout.pages[:-1]))
self.assertIn(len(layout.pages[-1].systems), (1, 2))
for page in layout.pages:
self.assertEqual(len({system.scale for system in page.systems}), 1)
for first, second in zip(page.systems, page.systems[1:]):
self.assertGreaterEqual(second.box.top - first.box.bottom, layout.style.system_gap - 0.001)
def test_guitar_pages_always_pair_systems_including_tall_passages(self):
content = tests.test_musicxml.xml_note('E', octave=5)
source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 32))
document = generator.practice.generate.practice_document(source, 'electric', rhythms=tests.rhythm_fixtures.library())
layout = self.layout(document)
self.assert_paired_pages(layout)
self.assertEqual(layout.style.space, 10)
self.assertEqual(layout.style.tab_font_size, 12)
wide = tests.test_musicxml.xml_note('E', octave=3) + tests.test_musicxml.xml_note('C', octave=7, prefix='<chord/>')
document = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([wide] * 32))
for measure in document['staves'][0]['measures']:
measure['transpose']['octave_change'] = -1
generator.guitar.place_guitar(document)
tall = self.layout(document)
self.assertGreater(len(tall.pages), 1)
self.assert_paired_pages(tall)
self.assertTrue(all(system.scale < 1 for page in tall.pages for system in page.systems))
self.assertEqual(tall.style, layout.style)
def test_vocal_pages_have_two_systems_and_an_odd_final_system(self):
content = tests.test_musicxml.xml_note(extra='<lyric><syllabic>single</syllabic><text>doo</text></lyric>')
source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 32))
style = generator.engraving.types.Style()
systems = generator.engraving.layout.layout_systems(source, style, self.fonts)
self.assertGreaterEqual(len(systems), 3)
pages = generator.engraving.layout.paginate(systems[:3], source, self.fonts, style)
self.assertEqual([len(page.systems) for page in pages], [2, 1])
self.assert_paired_pages(self.layout(source))
def test_two_voice_acoustic_systems_can_share_a_page(self):
harmony = '<harmony><root><root-step>E</root-step></root><kind>major</kind></harmony>'
content = harmony + tests.test_musicxml.xml_note('G', 2, 'half', alter=1) + tests.test_musicxml.xml_note('B', 2, 'half', prefix='<chord/>') + '<note><rest/><duration>2</duration><type>half</type></note>'
content += '<backup><duration>4</duration></backup>' + tests.test_musicxml.xml_note('E', extra='<voice>2</voice>')
document = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 16))
positions = {'e': {'string': 5, 'fret': 7}, 'g': {'string': 4, 'fret': 6}, 'b': {'string': 3, 'fret': 4}}
for measure in document['staves'][0]['measures']:
measure['transpose']['octave_change'] = -1
for voice in measure['voices']:
for note in voice['notes']:
for pitch in note['pitches']:
pitch['tablature'] = positions[pitch['letter']]
generator.guitar.place_guitar(document)
layout = self.layout(document)
self.assert_paired_pages(layout)
self.assertTrue(self.primitives(layout, 'rest'))
self.assertFalse(self.primitives(layout, 'tab-rest'))
def test_notation_fixture_visual_golden(self):
layout = self.layout(generator.musicxml.load_musicxml(pathlib.Path('tests/fixtures/notation.xml')))
self.assertEqual(len(layout.pages), 1)
@@ -206,7 +266,7 @@ class EngravingTests(unittest.TestCase):
def test_wide_pitch_range_can_break_inside_a_measure_vertically(self):
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(duration=2, kind='half', octave=10) + tests.test_musicxml.xml_note(duration=2, kind='half', octave=-1)))
layout = self.layout(source)
self.assertEqual(len(layout.pages), 2)
self.assertEqual([len(page.systems) for page in layout.pages], [2])
self.assertEqual(len(self.primitives(layout, 'head')), 2)
def test_cue_grace_and_tremolo_are_drawn(self):
grace = '<note><grace slash="yes"/><pitch><step>D</step><octave>4</octave></pitch><type>16th</type></note>'
@@ -251,7 +311,7 @@ class EngravingTests(unittest.TestCase):
for bar, tab in zip(notation, tabs):
for (x1, y1), (x2, y2) in zip(bar.points, tab.points):
self.assertAlmostEqual(x1, x2)
self.assertAlmostEqual((y1 - baselines['staff']) * 5 / 4, y2 - baselines['tab-staff'])
self.assertAlmostEqual((y1 - baselines['staff']) * 5 * layout.style.tab_space / (4 * layout.style.space), y2 - baselines['tab-staff'])
def test_adjacent_string_tab_numerals_do_not_overlap(self):
content = ''.join(tests.test_musicxml.xml_note(step, octave=octave, prefix='<chord/>' if index else '') for index, (step, octave) in enumerate((('G', 3), ('C', 4), ('E', 4), ('G', 4), ('C', 5), ('E', 5))))
source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(content))