Files
product-vamp/tests/test_six_parts.py
T
2026-10-05 16:40:22 -05:00

76 lines
3.6 KiB
Python

import copy
import dataclasses
from fractions import Fraction as F
import unittest
import generator.arrange.generate
import generator.arrange.form
import generator.model
import generator.parts
import generator.solo.arrangement
from generator.solo import analysis, profiles, placement, types, harmonic
from tests.solo_fixtures import song
from tests.solo_fixtures import note
from tests.solo_fixtures import choices
import tests.rhythm_fixtures
def events(measure):
return tuple((generator.model.fraction(n['onset']), generator.model.fraction(n['duration']),
tuple((p['letter'],p['alter']['numerator'],p['octave'],p['tablature']['string'],p['tablature']['fret']) for p in n['pitches']))
for v in measure['voices'] for n in v['notes'])
class ReusableAccompaniment(unittest.TestCase):
def test_corresponding_bars_repeat_rhythm_pitch_and_fingering(self):
source=song(count=12,chords=('CM7','Dm7','G7'))
library=tests.rhythm_fixtures.library()
prepared=generator.parts.prepare_document(source)
plans=generator.arrange.generate.plan_arrangement(prepared,library)
for part in ('acoustic','electric'):
result=generator.arrange.generate._arrange_document(prepared,part,plans)
seen={};offset=0;checked=0
sections=generator.arrange.form.full_arrangement_sections(source['staves'][0]['measures'],part)
for section in sections:
for j,m in enumerate(result['staves'][0]['measures'][offset:offset+section.measure_count]):
if section.role!='accompaniment':continue
key=section.source_measure_index+j
if key in seen:
self.assertEqual(events(m),seen[key]);checked+=1
seen[key]=events(m)
self.assertFalse(any('tie_from' in p or 'tie_to' in p for v in m['voices'] for n in v['notes'] for p in n['pitches']))
offset+=section.measure_count
self.assertGreater(checked,12)
generator.model.validate_document(result)
def test_excerpt_rhythms_retain_form_phase(self):
source=generator.parts.prepare_document(song(count=13,chords=('CM7',)))
plans=generator.arrange.generate.plan_arrangement(source,tests.rhythm_fixtures.library())
for lane in ('acoustic_bass','acoustic_upper','electric'):
full=getattr(plans[1],lane)
self.assertEqual(getattr(plans[0],lane),full[-8:])
self.assertEqual(getattr(plans[6],lane),full[-8:])
self.assertEqual(getattr(plans[3],lane),full)
self.assertEqual(getattr(plans[4],lane),full)
class SoloInstrumentAndIdentity(unittest.TestCase):
def test_role_ranges_and_no_open_strings(self):
acoustic=profiles.for_part('acoustic');electric=profiles.for_part('electric')
self.assertEqual(acoustic.register,(41,76));self.assertEqual(electric.register,(41,76))
self.assertTrue(acoustic.permits(41));self.assertTrue(electric.permits(50))
self.assertTrue(acoustic.permits(72))
for profile in (acoustic,electric):
for midi in range(40,78):
for p in placement.positions(midi,profile):
self.assertIn(p.string_number,profile.strings)
self.assertTrue(1<=p.fret<=12)
self.assertTrue(any(p.string_number==1 for p in placement.positions(65,electric)))
self.assertTrue(any(p.string_number==1 for p in placement.positions(65,acoustic)))
def test_soft_overlapping_registers_do_not_inherit_a_mandatory_octave(self):
source=analysis.analyze(generator.parts.prepare_document(song(count=4,chords=('CM7',))))
acoustic=profiles.for_part('acoustic',source);electric=profiles.for_part('electric',source)
self.assertLess(electric.reference-acoustic.reference,12)
self.assertEqual(acoustic.melody_range,electric.melody_range)