Files
product-vamp/tests/test_joint_arrivals.py
T
khodowany 68df7292ee Cleanup
2026-10-04 17:14:13 -05:00

115 lines
6.8 KiB
Python

"""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()