Cleanup
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user