This commit is contained in:
khodowany
2026-10-04 17:14:13 -05:00
parent f234dd6d51
commit 68df7292ee
33 changed files with 990 additions and 1872 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
These are **evaluation-only original-song inputs**, not expected solos, production vocabulary, or the four target solos. They are deliberately outside `data/` and are never loaded by ordinary generation or required tests. Source provenance is beside each XML. The original 1917/1918 editions are from Johns Hopkins' Levy collection.
The manually transcribed first four chorus measures retain the vocal rhythm, including tied values. Chord symbols are our declared harmonic reductions of the piano score. These short excerpts do not establish whole-form generalization. Preceding pickups/verses and piano figuration are outside the excerpt scope. Original scans remain in ignored `build/composer_repair/405066e/evaluation_sources/` for review.
The manually transcribed first four chorus measures retain the vocal rhythm, including tied values. Chord symbols are our declared harmonic reductions of the piano score. These short excerpts do not establish whole-form generalization. Preceding pickups/verses and piano figuration are outside the excerpt scope. Original scan downloads were removed with the historical audition artifacts; the provenance files retain their institutional source links.
St. Louis Blues was considered, but the institutional original-score download was unavailable (403). Hindustan supplies the third independent source instead; no target arrangement was used to reconstruct missing music.
+114
View File
@@ -0,0 +1,114 @@
"""Independent harmonic relationships; no optional reference reads."""
import dataclasses
from fractions import Fraction as F
import unittest
from generator.solo import gestures, harmonic, routes, types, expressions, placement, themes, realization
from tests.test_solo_passages import choices
from tests.test_solo_development import note
import tests.solo_fixtures
import tests.scale_fixtures
import tests.rhythm_fixtures
class JointArrivals(unittest.TestCase):
def fixture(self):
h=harmonic.Harmony(choices(('Dm7','G7'),scale=tests.scale_fixtures.library().scale('major',1,'C')))
ns=tuple(note(F(i,2),F(i+1,2),'d' if i<8 else 'g',event_id=str(i)) for i in range(16))
return h,ns
def test_anticipation_is_planned_before_route_and_requires_its_contract(self):
h,ns=self.fixture()
alternatives=list(gestures.proposals(ns,h,F(),F(8)))
p=next(p for p in alternatives if p.contracts and p.contracts[0].kind=='anticipation' and p.contracts[0].target.pitch.letter=='b' and p.contracts[0].target.end-p.contracts[0].target.start>=2)
result=routes.candidates(p.notes,(),h,tests.solo_fixtures.library(),(41,76),expressions=p.contracts)
self.assertTrue(result)
for r in result:
bound=p.bind(r.notes)
self.assertTrue(h.evaluate(r.notes,expressions=bound,complete=True).valid)
self.assertFalse(h.evaluate(r.notes,complete=True).valid)
self.assertLess(bound[0].target.start,F(4));self.assertGreater(bound[0].target.end,F(4))
self.assertIsNotNone(placement.place(r.notes,F(120)))
def test_approach_and_anticipation_share_exact_future_target(self):
h,ns=self.fixture()
base=next(p for p in gestures.proposals(ns,h,F(),F(8)) if p.contracts and p.contracts[0].kind=='anticipation')
proposals=list(gestures.approach(base,h,F()))
self.assertTrue(proposals)
realized=[]
for p in proposals:
for r in routes.candidates(p.notes,(),h,tests.solo_fixtures.library(),(41,76),expressions=p.contracts):
bound=p.bind(r.notes);self.assertTrue(h.evaluate(r.notes,expressions=bound,complete=True).valid)
self.assertFalse(h.evaluate(r.notes,expressions=bound[1:],complete=True).valid)
realized.append((r,bound))
self.assertTrue(realized)
r,contracts=realized[0];target=contracts[0].target
changed=tuple(dataclasses.replace(n,pitch=dataclasses.replace(n.pitch,octave=n.pitch.octave+1)) if n.event_id==target.event_id else n for n in r.notes)
self.assertFalse(h.evaluate(changed,expressions=contracts,complete=True).valid)
def test_on_time_and_delayed_arrivals_are_real_alternatives(self):
h,ns=self.fixture();ps=list(gestures.proposals(ns,h,F(),F(8)))
offsets={p.notes[next(i for i,n in enumerate(p.notes) if n.role=='arrival')].start-4 for p in ps if p.purpose.startswith('harmonic_arrival')}
self.assertEqual(offsets,{F(-1,2),F(),F(1,2)})
def test_proposal_cache_preserves_music_and_restores_each_callers_provenance(self):
h,ns=self.fixture()
direct=gestures._select(ns,h,F(),F(8))
cached=gestures.select(ns,h,F(),F(8))
self.assertEqual(direct,cached)
changed=tuple(dataclasses.replace(n,event_id='caller:'+n.event_id,sources=('new-source',)) for n in ns)
self.assertEqual(gestures.select(changed,h,F(),F(8)),gestures._select(changed,h,F(),F(8)))
self.assertEqual(len(h.gesture_cache),1)
def test_route_cache_preserves_contract_conditioned_realization(self):
h,ns=self.fixture()
p=next(p for p in gestures.proposals(ns,h,F(),F(8)) if p.contracts and p.contracts[0].kind=='anticipation')
direct=routes._planned_candidates(p.notes,(),h,(41,76),'test',p.contracts)
cached=routes.planned_candidates(p.notes,(),h,(41,76),'test',p.contracts)
self.assertTrue(cached)
self.assertEqual(cached,direct)
self.assertEqual(cached,routes.planned_candidates(p.notes,(),h,(41,76),'test',p.contracts))
def test_color_is_a_planned_route_destination_not_a_blanket_permission(self):
h=harmonic.Harmony(choices(('CM7','CM7'),scale=tests.scale_fixtures.library().scale('major',1,'C')))
ns=(note(0,1,'c',event_id='a'),note(1,3,'e',event_id='b'),note(3,4,'g',event_id='c'))
p=next(p for p in gestures.proposals(ns,h,F(),F(4)) if p.contracts and p.contracts[0].rule=='major_ninth' and p.contracts[0].events[0].end-p.contracts[0].events[0].start>=2)
rs=routes.candidates(p.notes,(),h,tests.solo_fixtures.library(),(41,76),expressions=p.contracts)
self.assertTrue(rs)
self.assertFalse(h.evaluate(rs[0].notes).valid)
self.assertTrue(h.evaluate(rs[0].notes,expressions=p.bind(rs[0].notes),complete=True).valid)
def test_independent_transpositions_and_harmonic_rhythms(self):
for tonic,symbols in [('C',('Dm7','G7')),('F',('Gm7','C7')),('G',('Am7','D7'))]:
for length in (3,4):
h=harmonic.Harmony(choices(symbols,duration=length,scale=tests.scale_fixtures.library().scale('major',1,tonic)))
ns=tuple(note(F(i,2),F(i+1,2),'g',event_id=str(i)) for i in range(length*4))
ps=[p for p in gestures.proposals(ns,h,F(),F(length*2)) if p.contracts and p.contracts[0].kind=='anticipation']
self.assertTrue(ps,(tonic,length))
self.assertTrue(any(routes.candidates(p.notes,(),h,tests.solo_fixtures.library(),(41,76),expressions=p.contracts) for p in ps),(tonic,length))
def test_closed_hold_cannot_be_consumed_by_a_joint_action(self):
h,ns=self.fixture();target=ns[-1]
ending=types.Anchor('close',F(6),F(6),target.pitch,frozenset({7}),(41,76),F(2),'terminal','G7',hold_until=F(8))
for p in gestures.proposals(ns,h,F(),F(8),ending):
self.assertFalse(any(n.role=='arrival' and n.end>6 for n in p.notes))
def test_minor_sixth_is_bounded_stable_color_not_required_neighbor_resolution(self):
h=harmonic.Harmony(choices(('Dm','Dm')))
n=note(0,3,'b',octave=3,event_id='color')
c=types.ExpressionContract('sixth','color','minor_sixth',(n,),n,'Dm')
self.assertTrue(h.evaluate((n,),expressions=(c,),complete=True).valid)
self.assertFalse(h.evaluate((n,),complete=True).valid)
too_long=dataclasses.replace(n,end=F(5));bad=dataclasses.replace(c,events=(too_long,),target=too_long)
self.assertFalse(h.evaluate((too_long,),expressions=(bad,),complete=True).valid)
def test_whole_tone_enclosure_neighbor_must_actually_be_mapped(self):
h=harmonic.Harmony(choices(('C7',),scale=tests.scale_fixtures.library().scale('major',5,'C')))
target=note(2,4,'g',event_id='target')
upper=note(1,F(3,2),'a',event_id='upper');lower=note(F(3,2),2,'f',alter=1,event_id='lower')
c=types.ExpressionContract('enclosure','enclosure','mapped_upper_lower',(upper,lower),target,'C7')
self.assertTrue(h.evaluate((upper,lower,target),expressions=(c,),complete=True).valid)
altered=harmonic.Harmony(choices(('C7',),scale=tests.scale_fixtures.library().scale('harmonic_minor',5,'C')))
self.assertFalse(altered.evaluate((upper,lower,target),expressions=(c,),complete=True).valid)
if __name__=='__main__':unittest.main()
+8
View File
@@ -26,6 +26,14 @@ def pitched(duration, tie='', octave=5):
class ComparisonTests(unittest.TestCase):
def test_note_container_does_not_turn_color_label_into_fingering(self):
with tempfile.TemporaryDirectory() as root:
note_xml=pitched(24).replace('</note>','<notations><technical><fingering>6</fingering></technical></notations></note>')
r=self.review(root,'annotation.xml',xml(note_xml),form_start=1)
self.assertEqual(r['annotations'][0]['text'],'6')
self.assertEqual(r['annotations'][0]['container'],'notations/technical/fingering')
self.assertIsNone(r['notes'][0]['tablature'])
def review(self,directory,name,content,**kwargs):
path=Path(directory)/name;path.write_text(content)
return reference_review.read(path,'fixture/acoustic',**kwargs)