Soloing
This commit is contained in:
@@ -162,9 +162,12 @@ def patterns(ref, start, finish, rhythms, meter, context, *, home=None, operatio
|
||||
lambda x:(len(x[0]),tuple(b-a for a,b in x[0][:3]),x[0][0][0]),policy.DEFAULT.theme_rhythm_alternatives)
|
||||
|
||||
|
||||
def pitches(harmony,note,base,*,members=False,limit=6):
|
||||
def pitches(harmony,note,base,*,members=False,limit=6,incoming=None):
|
||||
from generator.solo.leads import available
|
||||
domain=available(harmony,note.start,note.end,members=members or note.end-note.start>=2)
|
||||
if members:
|
||||
return phrasing.destination_options(domain,lambda p:(phrasing.destination_cost(p,note.start,note.end,harmony,incoming,base),p.number()),
|
||||
lambda p:p,lambda p:note.start,harmony,limit)
|
||||
return sequences.diverse(domain,lambda p:(abs(p.number()-base),p.number()),
|
||||
lambda p:((p.number()-base>0)-(p.number()-base<0),p.number()//12),limit)
|
||||
|
||||
@@ -245,7 +248,7 @@ def unit_candidates(ref,start,finish,incoming,harmony,library,rhythms,meter,cont
|
||||
# History is evaluated by the caller after retrieving these concrete options.
|
||||
# Every state component used to construct this pool is in its cache key.
|
||||
if not hasattr(harmony,'fingerprint'):harmony.fingerprint=policy.stable_rank('harmony',harmony.choices)
|
||||
key=('theme-unit',ref,start,finish,incoming.last,incoming.direction,incoming.treatments,home,operation,ending,goal,batch,harmony.fingerprint,id(library),id(rhythms),policy.DEFAULT)
|
||||
key=('theme-unit',ref,start,finish,incoming.last,incoming.direction,incoming.treatments,incoming.history,home,operation,ending,goal,batch,harmony.fingerprint,id(library),id(rhythms),policy.DEFAULT)
|
||||
if key in context.results:
|
||||
context.diagnostics['cache_hits']+=1;return context.results[key]
|
||||
result=[]; count=len(ref.notes)
|
||||
@@ -256,19 +259,24 @@ def unit_candidates(ref,start,finish,incoming,harmony,library,rhythms,meter,cont
|
||||
'theme',event_id=f'{ref.id}:{start}:{i}') for i,(a,b) in enumerate(pattern))
|
||||
base=incoming.last.pitch.number() if incoming.last else ref.notes[0].pitch.number()
|
||||
firsts=pitches(harmony,templates[0],base,limit=6 if batch else 3)
|
||||
targets=(ending.pitch,) if ending else pitches(harmony,templates[-1],goal if goal is not None else base,members=True,limit=policy.DEFAULT.theme_destinations)
|
||||
targets=(ending.pitch,) if ending else pitches(harmony,templates[-1],goal if goal is not None else base,members=True,limit=policy.DEFAULT.theme_destinations,incoming=incoming)
|
||||
for first in firsts:
|
||||
for target in targets:
|
||||
# Rank joint jobs, retaining rhythm and destination coverage before
|
||||
# expensive path instantiation. The budget is spent on complete routes.
|
||||
rank=max(0,abs(first.number()-base)-5)/12+abs(target.number()-(goal if goal is not None else base))/48
|
||||
rank=max(0,abs(first.number()-base)-5)/12+phrasing.destination_cost(target,templates[-1].start,templates[-1].end,harmony,incoming,goal if goal is not None else base,terminal)
|
||||
jobs.append((rank,templates,label,first,target))
|
||||
jobs.sort(key=lambda j:(j[0],policy.stable_rank(ref.id,(j[1],j[3],j[4]))))
|
||||
selected=[];rhythm_seen=set();target_seen=set();entry_seen=set()
|
||||
selected=[];rhythm_seen=set();target_seen=set();entry_seen=set();degrees_seen=set()
|
||||
for job in jobs:
|
||||
family=phrasing.destination_family(job[4],job[1][-1].start,harmony)
|
||||
if family not in degrees_seen:
|
||||
selected.append(job);degrees_seen.add(family)
|
||||
for job in jobs:
|
||||
rhythm=tuple((n.start,n.end) for n in job[1]);target=job[4].number();entry=job[3].number()
|
||||
if rhythm not in rhythm_seen or target not in target_seen or entry not in entry_seen:
|
||||
selected.append(job);rhythm_seen.add(rhythm);target_seen.add(target);entry_seen.add(entry)
|
||||
if job not in selected:selected.append(job)
|
||||
rhythm_seen.add(rhythm);target_seen.add(target);entry_seen.add(entry)
|
||||
job_limit=policy.DEFAULT.theme_route_jobs*(batch+1)
|
||||
for job in jobs:
|
||||
if len(selected)>=job_limit:break
|
||||
@@ -301,7 +309,7 @@ def unit_candidates(ref,start,finish,incoming,harmony,library,rhythms,meter,cont
|
||||
leaps=sum(max(0,abs(b.pitch.number()-a.pitch.number())-5)/12 for a,b in zip(actual,actual[1:]))/max(1,len(actual)-1)
|
||||
direction=min(1,abs(last.pitch.number()-goal)/12) if goal is not None else 0.
|
||||
# Activity belongs to the complete phrase; contracts are pass/fail.
|
||||
score=2*checked.exposure+entry+leaps+.35*direction
|
||||
score=2*checked.exposure+entry+leaps+phrasing.destination_cost(last.pitch,last.start,last.end,harmony,incoming,goal,terminal)
|
||||
anchor=ending or types.Anchor(last.event_id+':arrival',last.start,last.start,last.pitch,harmony.members(last.start,last.end),(41,76),
|
||||
last.end-last.start,'intermediate',harmony.at(last.start).chord,hold_until=last.end)
|
||||
result.append(Unit(actual,contract,ref,operation,anchor,score,label,connections))
|
||||
@@ -445,6 +453,7 @@ def phrase_candidates(source,analysis,incoming,part,harmony,library,rhythms,mete
|
||||
phrase=realization.checked_phrase(plan,draft,notes,incoming.treatments,incoming,harmony,tempo,edits)
|
||||
if phrase is None:continue
|
||||
score=phrasing.evaluate(notes,incoming,harmony,source.start,source.end,bar)
|
||||
score+=sum(phrasing.destination_cost(u.anchor.pitch,u.anchor.earliest,u.anchor.hold_until,harmony,incoming,phrasing.register_goal(incoming,part,u.notes[0].pitch.number()),u.anchor.role=='terminal') for u in units)/len(units)
|
||||
appearance_state=incoming
|
||||
for unit in units:
|
||||
score+=phrasing.appearance_cost(unit.operation,appearance_state,unit.notes[0].start,solo_end)/len(units)
|
||||
|
||||
Reference in New Issue
Block a user