This commit is contained in:
khodowany
2026-10-04 15:51:18 -05:00
parent 405066eb43
commit f234dd6d51
54 changed files with 2104 additions and 330 deletions
+15 -11
View File
@@ -27,13 +27,17 @@ def shift(notes, octave):
def available(harmony, start, end, *, members=False):
span = harmony.at(start)
allowed = types.permitted(harmony.choices, start, end)
if members:
allowed &= harmony.members(start, end)
if not span or not span.scale:
return ()
return tuple(p for i in range(-35, 28) if harmony.profile.register[0] <= (p := types.scale_pitch(span.scale, i)).number() <= harmony.profile.register[1] and p.number()%12 in allowed and placement.positions(p.number(),harmony.profile))
if not hasattr(harmony,'available_pitches'):harmony.available_pitches={}
span=harmony.at(start)
allowed=types.permitted(harmony.choices,start,end)
if members:allowed &= harmony.members(start,end)
if not span or not span.scale:return ()
key=(span.scale,allowed,harmony.profile)
if key not in harmony.available_pitches:
harmony.available_pitches[key]=tuple(p for i in range(-35,28)
if harmony.profile.register[0]<=(p:=types.scale_pitch(span.scale,i)).number()<=harmony.profile.register[1]
and p.number()%12 in allowed and placement.positions(p.number(),harmony.profile))
return harmony.available_pitches[key]
def adapt(notes, harmony):
@@ -350,6 +354,7 @@ def thematic_options(analysis,measures,part,end,choices,library,rhythms,context,
if elapsed>start:
meter=measure['time_signature'];break
bar=F(meter.split('/')[0])*4/int(meter.split('/')[1])
harmony.bar=bar
for source in sources:
key=('phrasing',source,incoming,part,end,stage,seed,id(library),id(rhythms),policy.DEFAULT,harmony.profile)
if key in context.results:
@@ -367,11 +372,10 @@ def thematic_options(analysis,measures,part,end,choices,library,rhythms,context,
context.results[key]=pool
context.diagnostics.setdefault('source_candidate_pools',{}).setdefault(source.id,[]).extend(
{'id':p.draft.id,'score':p.score,'attacks':len(p.draft.notes),'theme':p.state.home.id if p.state.home else None,
'ending':str(p.anchors[-1].duration) if p.anchors else None,'returns':len(p.state.returns)} for p in pool)
'ending':str(p.anchors[-1].duration) if p.anchors else None,'returns':len(p.state.returns),'evaluation':dataclasses.asdict(p.evaluation) if p.evaluation else None} for p in pool)
for candidate in pool:
span=float((source.end-source.start)/end)
total=cost+span*(candidate.score+phrasing.endpoint_cost(source.end,start,end,incoming.closures,harmony,bar))
total+=transition(history[-1] if history else None,candidate)
total=cost+span*candidate.score
following.append((total,(*history,candidate)))
if not pool:failures.append(f'{source.id}: planned sustained destination, feature or incoming continuation')
if not following:
@@ -385,7 +389,7 @@ def thematic_options(analysis,measures,part,end,choices,library,rhythms,context,
finished=[(cost,history) for cost,history in states if history[-1].plan.end==end]
if not finished:raise realization.SearchExhausted(f'{part}: phrase-boundary search budget exhausted; not proof of incompatibility')
return tuple((rank((cost,phrases))[0],types.Solo(part,choices,phrases,analysis.tempo,end,
{**context.diagnostics,'construction':'thematic_recomposition','policy':policy.describe()['fingerprint']},
{**context.diagnostics,'selection_total':rank((cost,phrases))[0], 'selection_scope':'duration-weighted complete phrase evaluations; partial frontiers use elapsed-duration rates', 'construction':'thematic_recomposition','policy':policy.describe()['fingerprint']},
tuple(types.MelodicPassagePlan(p.plan.passage_id,p.plan.start,p.plan.end,(p.plan.id,),seed) for p in phrases),harmony.profile)) for cost,phrases in finished)