This commit is contained in:
khodowany
2026-10-03 12:44:53 -05:00
parent 59afd78205
commit 04c6ea2e00
25 changed files with 673 additions and 185 deletions
+37 -25
View File
@@ -34,7 +34,7 @@ def approach(target, harmony):
letter=letters[(index-1)%7]; octave=target.pitch.octave-(index==0)
alter=number-1-generator.model.NATURAL[letter]-12*(octave+1)
lower=types.Pitch(letter,F(alter),octave)
if not all(placement.positions(p.number()) for p in (upper,lower)):return None
if not all(placement.positions(p.number(),harmony.profile) for p in (upper,lower)):return None
identity=target.event_id+':enclosure'
return (types.Note(t-1,t-F(1,2),upper,target.sources,'enclosure_upper',event_id=identity+':upper'),
types.Note(t-F(1,2),t,lower,target.sources,'enclosure_lower',event_id=identity+':lower'))
@@ -113,28 +113,40 @@ def candidates(phrase,harmony,library,tempo,rejections):
for begin,replacement,route in proposals[:1+policy.DEFAULT.enclosure_routes]:
actual=replace_window(notes,begin,target.start,replacement)
if actual is None:continue
valid,error=drafts.contract_valid(draft,actual)
if not valid:continue
checked=harmony.evaluate(actual,phrase.state.treatments if phrase.state else (),until=phrase.plan.end,complete=True,enclosures=(contract,))
if not checked.valid:continue
valid,error=harmonic.anchors_valid(phrase.anchors,actual,phrase.plan.end,complete=True,closure=phrase.plan.closure)
if not valid:continue
placed=placement.place(actual,tempo)
if placed is None:continue
old_travel=placement.place(notes,tempo)[0]
delta=.6*(movement(actual)-movement(notes))+.025*(placed[0]-old_travel)/max(1,len(notes))/2000
density=4*len(actual)/float(phrase.plan.end-phrase.plan.start)
before_density=4*len(notes)/float(phrase.plan.end-phrase.plan.start)
delta+=.1*(max(0,density-6)-max(0,before_density-6))
transforms=tuple(dataclasses.replace(t,events=tuple(n.event_id for n in actual if t.start<=n.start<t.end),
rhythm=tuple((n.start-t.start,n.end-t.start) for n in actual if t.start<=n.start<t.end)) for t in draft.transformations)
changed=dataclasses.replace(draft,notes=actual,transformations=transforms,enclosures=(contract,))
decisions=tuple(dataclasses.replace(d,notes=tuple(n for n in actual if d.start<=n.start<d.end)) for d in phrase.decisions)
if tuple(n for d in decisions for n in d.notes)!=actual:continue
retained=tuple(r for r in phrase.routes if all(any((n.event_id,n.start,n.end,n.pitch)==(a.event_id,a.start,a.end,a.pitch) for a in actual) for n in r.notes))
result.append((delta,dataclasses.replace(phrase,draft=changed,decisions=decisions,before_enclosure=draft,
routes=(*retained,*((route,) if route else ())))))
accepted+=1
variants=[(actual,contract)]
upper_index=next((i for i,n in enumerate(actual) if n.event_id==upper.event_id),0)
if upper_index:
prior=actual[upper_index-1]
if prior.end==upper.start and prior.pitch==upper.pitch and editable(draft,prior.start,upper.start):
shared=dataclasses.replace(prior,end=upper.end)
combined=(*actual[:upper_index-1],shared,*actual[upper_index+1:])
variants.append((combined,dataclasses.replace(contract,upper=shared,approach_start=upper.start)))
for actual,actual_contract in variants:
valid,error=drafts.contract_valid(draft,actual)
if not valid:continue
checked=harmony.evaluate(actual,phrase.state.treatments if phrase.state else (),until=phrase.plan.end,complete=True,enclosures=(actual_contract,))
if not checked.valid:continue
valid,error=harmonic.anchors_valid(phrase.anchors,actual,phrase.plan.end,complete=True,closure=phrase.plan.closure)
if not valid:continue
placed=placement.place(actual,tempo,profile=harmony.profile)
if placed is None:continue
old_travel=placement.place(notes,tempo,profile=harmony.profile)[0]
delta=.6*(movement(actual)-movement(notes))+.025*(placed[0]-old_travel)/max(1,len(notes))/2000
density=4*len(actual)/float(phrase.plan.end-phrase.plan.start)
before_density=4*len(notes)/float(phrase.plan.end-phrase.plan.start)
repeated=sum(a.pitch.number()==b.pitch.number() and a.end==b.start for a,b in zip(actual,actual[1:]))
old_repeated=sum(a.pitch.number()==b.pitch.number() and a.end==b.start for a,b in zip(notes,notes[1:]))
delta+=.15*max(0,repeated-old_repeated)
delta+=.1*(max(0,density-6)-max(0,before_density-6))
transforms=tuple(dataclasses.replace(t,events=tuple(n.event_id for n in actual if t.start<=n.start<t.end),
rhythm=tuple((n.start-t.start,n.end-t.start) for n in actual if t.start<=n.start<t.end)) for t in draft.transformations)
changed=dataclasses.replace(draft,notes=actual,transformations=transforms,enclosures=(actual_contract,))
decisions=tuple(dataclasses.replace(d,notes=tuple(n for n in actual if d.start<=n.start<d.end)) for d in phrase.decisions)
if tuple(n for d in decisions for n in d.notes)!=actual:continue
retained=tuple(r for r in phrase.routes if all(any((n.event_id,n.start,n.end,n.pitch)==(a.event_id,a.start,a.end,a.pitch) for a in actual) for n in r.notes))
result.append((delta,dataclasses.replace(phrase,draft=changed,decisions=decisions,before_enclosure=draft,
routes=(*retained,*((route,) if route else ())))))
accepted+=1
if not accepted:rejections.append({'target':target.event_id,'reason':'no valid complete enclosure within local route budget'})
return tuple(result)
@@ -152,7 +164,7 @@ def strict(solo):
def refine(solo,library,enabled=True):
started=time.monotonic();harmony=harmonic.Harmony(solo.choices);rejections=[]
started=time.monotonic();harmony=harmonic.Harmony(solo.choices,solo.profile);rejections=[]
baseline=[]
for phrase in solo.phrases:
draft=dataclasses.replace(phrase.draft,notes=tuple(n for d in phrase.decisions for n in d.notes)) if phrase.draft else None
@@ -179,7 +191,7 @@ def refine(solo,library,enabled=True):
_,phrases,arrivals=min(states,key=lambda x:(x[0]+sparse(len(x[2])),policy.stable_rank('enclosure-selection',x[2])))
# Resolve the complete guitar path once, including both sides of phrase joins.
raw=tuple(n for p in phrases for d in p.decisions for n in d.notes)
placed=placement.place(raw,solo.tempo)
placed=placement.place(raw,solo.tempo,profile=harmony.profile)
if placed is None:raise ValueError('selected enclosure solo has no complete guitar path')
by_id={n.event_id:n for n in placed[1]};final=[]
for phrase in phrases: