Soloing
This commit is contained in:
+38
-10
@@ -7,7 +7,7 @@ from fractions import Fraction as F
|
||||
|
||||
import generator.model
|
||||
import generator.harmony
|
||||
from generator.solo import types, policy, sequences
|
||||
from generator.solo import types, policy, sequences, profiles
|
||||
|
||||
|
||||
def heard(notes, operation, anchor=None):
|
||||
@@ -52,7 +52,11 @@ def repetition(figure, history, bar):
|
||||
return min(1., discount*overuse/2) + .3*oscillation + .3*min(1.,cadence/2)
|
||||
|
||||
|
||||
def register_goal(incoming, part, fallback):
|
||||
def register_goal(incoming, part, fallback, profile=None):
|
||||
if profile and profile.reference is not None:
|
||||
recent = [p for h in incoming.history[-2:] for _,_,p in h.events]
|
||||
center = round(sum(recent)/len(recent)) if recent else profile.reference
|
||||
return max(profile.reference-4,min(profile.reference+4,center))
|
||||
recent = [p for h in incoming.history[-2:] for _,_,p in h.events]
|
||||
if not recent:
|
||||
return fallback
|
||||
@@ -76,7 +80,7 @@ def endpoint_cost(finish, start, end, count, harmony, bar):
|
||||
degrees=generator.harmony.degrees(generator.harmony.parse_symbol(span.chord)) if span and span.scale else {}
|
||||
dominant=degrees.get(3)==0 and degrees.get(7)==-1
|
||||
short_remainder=.2*max(0,4-remaining) if remaining else 0.
|
||||
return pacing+.035*abs(length-5)+.16*dominant+.1*(length==8)+short_remainder
|
||||
return pacing+(.08 if ending_effect(finish-F(1,100),harmony,False)=='local_settle' else 0)+.035*abs(length-5)+.16*dominant+.1*(length==8)+short_remainder
|
||||
|
||||
|
||||
def window_options(analysis, measures, start, end, harmony, incoming, *, limit=2):
|
||||
@@ -105,11 +109,11 @@ def window_options(analysis, measures, start, end, harmony, incoming, *, limit=2
|
||||
return tuple(sorted(values,key=lambda s:(endpoint_cost(s.end,start,end,incoming.closures,harmony,bar),s.end))[:limit])
|
||||
|
||||
|
||||
def endings(source, incoming, part, harmony, bar, *, limit=8):
|
||||
def endings(source, incoming, part, harmony, bar, *, limit=8, final=False):
|
||||
"""Choose pitch AND sounding interval before generating the approach."""
|
||||
from generator.solo.leads import available
|
||||
base=incoming.last.pitch.number() if incoming.last else source.notes[0].pitch.number() if source.notes else 55
|
||||
goal=register_goal(incoming,part,base)
|
||||
goal=register_goal(incoming,part,base,harmony.profile)
|
||||
values=[]
|
||||
duration=source.end-source.start
|
||||
holds=(F(2),F(5,2),F(3),F(7,2),F(4)) if duration>=4 else (min(F(2),duration/2),)
|
||||
@@ -120,10 +124,10 @@ def endings(source, incoming, part, harmony, bar, *, limit=8):
|
||||
for pitch in available(harmony,arrival,arrival+hold,members=True):
|
||||
if abs(pitch.number()-goal)>12:continue
|
||||
anchor=types.Anchor(f'{source.id}:close:{arrival}:{pitch.number()}',arrival,arrival,pitch,
|
||||
harmony.members(arrival,arrival+hold),(41,76),hold,'terminal',harmony.at(arrival).chord,
|
||||
hold_until=arrival+hold)
|
||||
harmony.members(arrival,arrival+hold),harmony.profile.register,hold,'terminal',harmony.at(arrival).chord,
|
||||
hold_until=arrival+hold,effect=ending_effect(arrival,harmony,final))
|
||||
cadence=sum(h.ending==(pitch.number()%12,arrival%4,hold) for h in incoming.history)
|
||||
rank=destination_cost(pitch,arrival,arrival+hold,harmony,incoming,goal,True)+.16*min(2,cadence)+.035*abs(float(hold)-3)+.015*float(release)
|
||||
rank=harmony.profile.cost(pitch.number())+destination_cost(pitch,arrival,arrival+hold,harmony,incoming,goal,True)+.16*min(2,cadence)+.035*abs(float(hold)-3)+.015*float(release)
|
||||
values.append((rank,anchor))
|
||||
selected=destination_options(values,lambda x:(x[0],policy.stable_rank(source.id,x[1])),
|
||||
lambda x:x[1].pitch,lambda x:x[1].earliest,harmony,limit)
|
||||
@@ -158,7 +162,7 @@ def evaluate(notes, incoming, harmony, start, end, bar):
|
||||
entry=max(0,abs(notes[0].pitch.number()-incoming.last.pitch.number())-5)/12 if incoming.last else 0.
|
||||
leaps=sum(max(0,abs(b.pitch.number()-a.pitch.number())-5)/12 for a,b in zip(notes,notes[1:]))/max(1,len(notes)-1)
|
||||
history_cost=repetition(heard(notes,'develop'),incoming.history,bar)
|
||||
return 2*checked.exposure+entry+leaps+.35*max(0,4-density,density-6)+.8*history_cost
|
||||
return harmony.profile.cost(profiles.weighted_center(notes))+2*checked.exposure+entry+leaps+.35*max(0,4-density,density-6)+.8*history_cost
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=512)
|
||||
@@ -197,7 +201,8 @@ def destination_cost(pitch, start, end, harmony, incoming=None, goal=None, termi
|
||||
if incoming and incoming.home and len(incoming.home.notes)>1 and incoming.last:
|
||||
motion=incoming.home.notes[-1].pitch.number()-incoming.home.notes[0].pitch.number()
|
||||
direction=.025 if (number-incoming.last.pitch.number())*motion<0 else 0.
|
||||
return function+register+entry+recurrence+lookahead+direction
|
||||
pending=.08 if incoming and incoming.next_destination and pc not in incoming.next_destination else 0.
|
||||
return function+register+entry+recurrence+lookahead+direction+pending
|
||||
|
||||
|
||||
def destination_family(pitch, time, harmony):
|
||||
@@ -217,3 +222,26 @@ def destination_options(values, rank, pitch, time, harmony, limit):
|
||||
if value not in selected:selected.append(value)
|
||||
if len(selected)==limit:break
|
||||
return tuple(selected)
|
||||
|
||||
|
||||
def ending_effect(time, harmony, final=False):
|
||||
if final: return 'handoff'
|
||||
current=harmony.at(time)
|
||||
previous=next((s for s in reversed(harmony.spans) if current and s.end<=current.start and s.chord!=current.chord and s.scale),None)
|
||||
if not current or not current.scale or not previous: return 'local_settle'
|
||||
old=generator.harmony.parse_symbol(previous.chord)
|
||||
new=generator.harmony.parse_symbol(current.chord)
|
||||
degrees=generator.harmony.degrees(old)
|
||||
def root(chord):
|
||||
p=chord['root'];return int(generator.model.NATURAL[p['letter']]+generator.model.alteration(p['alter']))%12
|
||||
if degrees.get(3)==0 and degrees.get(7)==-1 and (root(old)-root(new))%12==7:
|
||||
return 'cadential_arrival'
|
||||
return 'local_settle'
|
||||
|
||||
|
||||
def next_destination(anchor,harmony):
|
||||
if anchor.effect!='local_settle': return ()
|
||||
following=[s for s in harmony.spans if s.start>=anchor.hold_until and s.scale]
|
||||
resolution=next((s for s in following[:2] if ending_effect(s.start,harmony)=='cadential_arrival'),None)
|
||||
selected=resolution or next(iter(following),None)
|
||||
return tuple(sorted(selected.roles.basic & selected.scale.pitches)) if selected else ()
|
||||
|
||||
Reference in New Issue
Block a user