Package
ci / checks (push) Successful in 48m34s

This commit is contained in:
khodowany
2026-09-30 19:46:37 -05:00
parent 2a1aa2c67a
commit 09656a9d60
6621 changed files with 698 additions and 774 deletions
+14 -14
View File
@@ -40,7 +40,7 @@ def tied_library(continuation_duration):
second += '<note><rest/><duration>2</duration><type>half</type></note>'
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
path = root / '4-4/2/tied.xml'
path = root / '4_4/2/tied.xml'
path.parent.mkdir(parents=True)
path.write_text(xml_bars([first, second]), encoding='utf-8')
return generator.rhythm.load_library(root)
@@ -57,7 +57,7 @@ class RhythmLibraryTests(unittest.TestCase):
extra = '<time-modification><actual-notes>3</actual-notes><normal-notes>2</normal-notes></time-modification>'
source = xml_bars([tests.test_musicxml.xml_note(duration=2, kind='quarter', extra=extra) * 6], divisions=3)
with self.assertRaisesRegex(ValueError, 'time modifications'):
self.load({'4-4/1/ratio.xml': source})
self.load({'4_4/1/ratio.xml': source})
def load(self, sources):
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
@@ -71,9 +71,9 @@ class RhythmLibraryTests(unittest.TestCase):
content += tests.test_musicxml.xml_note(duration=1, kind='16th', extra='<beam number="1">end</beam><beam number="2">backward hook</beam>')
content += '<note><rest/><duration>4</duration><type>quarter</type></note>'
content += tests.test_musicxml.xml_note(duration=8, kind='half')
library = self.load({'4-4/1/dotted.xml': xml_bars([content], divisions=4)})
library = self.load({'4_4/1/dotted.xml': xml_bars([content], divisions=4)})
snippet = library.for_meter('4/4')[0]
self.assertEqual((snippet.id, snippet.meter), ('4-4/1/dotted.xml', '4/4'))
self.assertEqual((snippet.id, snippet.meter), ('4_4/1/dotted.xml', '4/4'))
self.assertEqual(snippet.bars, (generator.rhythm.Bar((
generator.rhythm.Event(fractions.Fraction(0), fractions.Fraction(3, 4), True, 'eighth', 1, ((1, 'begin'),)),
generator.rhythm.Event(fractions.Fraction(3, 4), fractions.Fraction(1, 4), True, '16th', 0, ((1, 'end'), (2, 'backward hook'))),
@@ -84,7 +84,7 @@ class RhythmLibraryTests(unittest.TestCase):
self.assertTrue(all(type(e.onset) is fractions.Fraction and type(e.duration) is fractions.Fraction for e in snippet.bars[0].events))
def test_two_bar_ties_are_continuations_without_new_attacks(self):
content = xml_bars([tests.test_musicxml.xml_note(extra='<tie type="start"/>'), tests.test_musicxml.xml_note(extra='<tie type="stop"/>')])
snippet = self.load({'4-4/2/tied.xml': content}).for_meter('4/4')[0]
snippet = self.load({'4_4/2/tied.xml': content}).for_meter('4/4')[0]
self.assertEqual(len(snippet.bars), 2)
self.assertEqual([bar.attacks() for bar in snippet.bars], [(0,), ()])
self.assertEqual([bar.events[0].continuation for bar in snippet.bars], [False, True])
@@ -96,13 +96,13 @@ class RhythmLibraryTests(unittest.TestCase):
content = tests.test_musicxml.xml_note(duration=duration, kind=kind, extra='<dot/>' if dots else '') if sounding else f'<note><rest/><duration>{duration}</duration><type>{kind}</type>{"<dot/>" if dots else ""}</note>'
if beats == 5:
content += tests.test_musicxml.xml_note(duration=1, kind='quarter')
snippet = self.load({f'{beats}-4/3/0001.xml': xml_bars([content], beats=beats)}).for_meter(f'{beats}/4')[0]
snippet = self.load({f'{beats}_4/3/0001.xml': xml_bars([content], beats=beats)}).for_meter(f'{beats}/4')[0]
self.assertEqual(snippet.bars[0].events[0], generator.rhythm.Event(fractions.Fraction(), fractions.Fraction(duration), sounding, kind, dots))
self.assertEqual(snippet.bars[0].length, beats)
def test_all_three_series_participate_in_deterministic_long_note_selection(self):
silence = '<note><rest/><duration>4</duration><type>whole</type></note>'
halves = tests.test_musicxml.xml_note(duration=2, kind='half') * 2
library = self.load({'4-4/1/0001.xml': xml_bars([silence] * 2), '4-4/2/0001.xml': xml_bars([silence]), '4-4/3/0001.xml': xml_bars([halves])})
library = self.load({'4_4/1/0001.xml': xml_bars([silence] * 2), '4_4/2/0001.xml': xml_bars([silence]), '4_4/3/0001.xml': xml_bars([halves])})
self.assertEqual(len(library.for_meter('4/4')), 3)
measures = melody_measures(8)
plan = generator.accompaniment.plan_section(measures, library)
@@ -134,10 +134,10 @@ class RhythmLibraryTests(unittest.TestCase):
for beats in (2, 3, 4, 5):
content = xml_bars([''.join(tests.test_musicxml.xml_note(duration=1, kind='quarter') for _ in range(beats))], beats=beats)
for name in ('z', 'a'):
sources[f'{beats}-4/1/{name}.xml'] = content
sources[f'{beats}_4/1/{name}.xml'] = content
library = self.load(sources)
for beats in (2, 3, 4, 5):
self.assertEqual([s.id for s in library.for_meter(f'{beats}/4')], [f'{beats}-4/1/a.xml', f'{beats}-4/1/z.xml'])
self.assertEqual([s.id for s in library.for_meter(f'{beats}/4')], [f'{beats}_4/1/a.xml', f'{beats}_4/1/z.xml'])
self.assertTrue(all(s.bars[0].length == beats for s in library.for_meter(f'{beats}/4')))
def test_missing_empty_unsupported_and_misfiled_libraries_fail(self):
with tempfile.TemporaryDirectory() as temporary, self.assertRaisesRegex(ValueError, 'not found'):
@@ -145,12 +145,12 @@ class RhythmLibraryTests(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'empty'):
self.load({})
source = xml_bars([tests.test_musicxml.xml_note()])
for path in ('snippet.xml', '4-4/snippet.xml', '4-4/4/snippet.xml', '4-4/1/nested/snippet.xml', '6-8/1/snippet.xml'):
for path in ('snippet.xml', '4_4/snippet.xml', '4_4/4/snippet.xml', '4_4/1/nested/snippet.xml', '6-8/1/snippet.xml'):
with self.subTest(path=path), self.assertRaisesRegex(ValueError, 'meter/series'):
self.load({path: source})
with self.assertRaisesRegex(ValueError, 'meter disagrees'):
self.load({'3-4/1/snippet.xml': source})
library = self.load({'4-4/1/snippet.xml': source})
self.load({'3_4/1/snippet.xml': source})
library = self.load({'4_4/1/snippet.xml': source})
for meter in ('2/4', '6/8', '7/4', '4/8', 'invalid'):
with self.subTest(meter=meter), self.assertRaisesRegex(ValueError, 'rhythm snippets'):
library.for_meter(meter)
@@ -175,7 +175,7 @@ class RhythmLibraryTests(unittest.TestCase):
}
for name, content in cases.items():
with self.subTest(source=name), self.assertRaises(ValueError):
self.load({'4-4/1/invalid.xml': content})
self.load({'4_4/1/invalid.xml': content})
class AccompanimentPlanningTests(unittest.TestCase):
def test_pickup_coverage_excludes_notes_that_ended_before_the_excerpt(self):
@@ -251,7 +251,7 @@ class AccompanimentPlanningTests(unittest.TestCase):
self.assertTrue(attacks <= melody)
self.assertTrue(any(bar.attacks() for bar in plan.electric))
def test_section_and_local_meter_changes_restart_the_motif(self):
library = generator.rhythm.Library(tuple(generator.rhythm.Snippet(f'{beats}-4/pair', f'{beats}/4', (quarter_bar(beats, {0}), quarter_bar(beats, {1}))) for beats in (3, 4)))
library = generator.rhythm.Library(tuple(generator.rhythm.Snippet(f'{beats}_4/pair', f'{beats}/4', (quarter_bar(beats, {0}), quarter_bar(beats, {1}))) for beats in (3, 4)))
segments = [melody_measures(3), melody_measures(2, beats=3), melody_measures(3), melody_measures(4)]
segments[-1][0]['section_label'] = 'Bridge'
measures = [m for segment in segments for m in segment]