Sepearate Generation
This commit is contained in:
+33
-12
@@ -10,7 +10,8 @@ from fractions import Fraction as F
|
||||
|
||||
import generator.model
|
||||
import generator.harmony
|
||||
from generator.solo import types, policy, sequences, routes, references, similarity, phrasing, profiles
|
||||
import generator.vocabulary
|
||||
from generator.solo import types, policy, sequences, routes, features as references, similarity, phrasing, profiles
|
||||
|
||||
|
||||
def source_notes(analysis):
|
||||
@@ -60,20 +61,23 @@ def seeds(analysis, bar, seed):
|
||||
event_id=n.sources[0]) for n in analysis.melody)
|
||||
groups=[]
|
||||
for i in range(len(melody)):
|
||||
for count in ((len(melody),) if len(melody)<3 else (3,4,5)):
|
||||
for count in ((len(melody),) if len(melody)<3 else (3,4,5,6,7,8)):
|
||||
group=melody[i:i+count]
|
||||
if len(group)!=count or group[-1].end-group[0].start>max(bar,F(3)) or group[0].start>=8*bar:
|
||||
if len(group)!=count or group[-1].end-group[0].start>max(2*bar,F(3)):
|
||||
continue
|
||||
local=tuple(dataclasses.replace(n,start=n.start-group[0].start,end=n.end-group[0].start) for n in group)
|
||||
parent=next((s.id for s in analysis.source_phrases if s.start<=max(0,group[0].start)<s.end),'pickup')
|
||||
for treatment in ('rhythmic_seed','contour_seed','opening_fragment','characteristic_interval'):
|
||||
for treatment in generator.vocabulary.role().get('preferred_features',('rhythmic_seed','contour_seed','opening_fragment','characteristic_interval')):
|
||||
identity=policy.stable_rank(seed,(group,treatment))[:12]
|
||||
groups.append(types.SourceReference(identity,parent,group[0].start,group[-1].end,local,treatment))
|
||||
def rank(ref):
|
||||
rhythm={(n.start,n.end-n.start) for n in ref.notes}
|
||||
intervals={b.pitch.number()-a.pitch.number() for a,b in zip(ref.notes,ref.notes[1:])}
|
||||
return (-len(intervals),-len(rhythm),ref.start,ref.id)
|
||||
return sequences.diverse(groups,rank,lambda r:r.treatment,policy.DEFAULT.theme_seed_limit)
|
||||
preferred=generator.vocabulary.role().get('preferred_features',[])
|
||||
preference=preferred.index(ref.treatment) if ref.treatment in preferred else 0
|
||||
turn=any(a*b<0 for a,b in zip(ref.notes and tuple(b.pitch.number()-a.pitch.number() for a,b in zip(ref.notes,ref.notes[1:])),tuple(b.pitch.number()-a.pitch.number() for a,b in zip(ref.notes,ref.notes[1:]))[1:]))
|
||||
return (-min(4,len(intervals))-.3*turn+.15*preference,-min(3,len(rhythm)),ref.id)
|
||||
return sequences.diverse(groups,rank,lambda r:(r.treatment,int(max(0,r.start)//(4*bar))),policy.DEFAULT.theme_seed_limit)
|
||||
|
||||
|
||||
def feature(reference, actual, *, treatment=None):
|
||||
@@ -85,6 +89,8 @@ def feature(reference, actual, *, treatment=None):
|
||||
'return':('rhythm','intervals'), 'develop_rhythm':('rhythm',),
|
||||
'develop_contour':('intervals',), 'continue':('contour',)}[treatment]
|
||||
original=reference.notes
|
||||
authored=next((r for r in generator.vocabulary.data('grammar')['transformations'] if r['id']==treatment),None)
|
||||
if authored:required=tuple(authored['preserve'])
|
||||
origin=original[0].start
|
||||
octave=12*round((actual[0].pitch.number()-original[0].pitch.number())/12)
|
||||
return types.IdentifyingFeature(reference.id+':'+treatment,tuple(n.event_id for n in actual),
|
||||
@@ -95,7 +101,8 @@ def feature(reference, actual, *, treatment=None):
|
||||
|
||||
def rhythm_windows(library,meter,length,context):
|
||||
from generator.solo import drafts
|
||||
key=('theme-rhythm',id(library),meter,length)
|
||||
tempo=getattr(context,'tempo',120)
|
||||
key=('theme-rhythm',id(library),meter,length,tempo)
|
||||
if key not in context.pools:
|
||||
patterns={}
|
||||
bar=F(meter.split('/')[0])*4/int(meter.split('/')[1])
|
||||
@@ -115,9 +122,18 @@ def rhythm_windows(library,meter,length,context):
|
||||
if offset+length>total or any(a<offset<b or a<offset+length<b for a,b in pattern):continue
|
||||
selected=tuple((a-offset,b-offset) for a,b in pattern if offset<=a and b<=offset+length)
|
||||
if selected and len(selected)<=16:patterns.setdefault(selected,identifier)
|
||||
# Authored cells can start late and continue over computational barlines.
|
||||
for identifier,pattern,total in generator.vocabulary.rhythm_cells(tempo):
|
||||
if total>length:continue
|
||||
for offset in (F(),F(1,2),max(F(),length-total)):
|
||||
if offset+total>length:continue
|
||||
head=tuple((a+offset,b+offset) for a,b in pattern)
|
||||
tails=tuple((a,b) for a,b in next(iter(patterns),()) if a>=offset+total)
|
||||
combined=(*head,*tails)
|
||||
if 2<=len(combined)<=16:patterns.setdefault(combined,'authored:'+identifier)
|
||||
context.pools[key]=sequences.diverse(tuple(patterns.items()),
|
||||
lambda x:(not .7*float(length)<=len(x[0])<=1.8*float(length),policy.stable_rank('rhythm-window',x[0])),
|
||||
lambda x:(len(x[0]),x[0][0][0],any(a[1]!=b[0] for a,b in zip(x[0],x[0][1:]))),8)
|
||||
lambda x:(len(x[0])>16,policy.stable_rank('rhythm-window',x[0])),
|
||||
lambda x:(x[1].startswith('authored:'),len(x[0]),x[0][0][0],any(a[1]!=b[0] for a,b in zip(x[0],x[0][1:]))),8)
|
||||
return context.pools[key]
|
||||
|
||||
|
||||
@@ -366,6 +382,11 @@ def phrase_candidates(source,analysis,incoming,part,harmony,library,rhythms,mete
|
||||
cursor=source.start
|
||||
while cursor<source.end and states:
|
||||
finish=min(cursor+unit_length,source.end)
|
||||
if cursor==source.start and source.end-source.start>=3*bar:
|
||||
# Selected home rhythms finish as objects, not at the nearest barline.
|
||||
signature=incoming.home.signature if incoming.home else ()
|
||||
if signature and signature[-1].end>unit_length:
|
||||
finish=min(source.end,cursor+signature[-1].end)
|
||||
if source.end-finish<6 or any(ending.earliest<finish for *_,ending in states):finish=source.end
|
||||
next_states=[]
|
||||
for cost,units,home,state,ending in states:
|
||||
@@ -409,7 +430,7 @@ def phrase_candidates(source,analysis,incoming,part,harmony,library,rhythms,mete
|
||||
if introducing and similarity.near_copy(unit.notes,head,cursor,finish)['near_copy']:
|
||||
context.diagnostics['near_copy_rejections']=context.diagnostics.get('near_copy_rejections',0)+1;continue
|
||||
actual_home=home or make_home(unit,harmony,cursor,finish)
|
||||
figure=phrasing.heard(unit.notes,operation,unit.anchor)
|
||||
figure=phrasing.heard(unit.notes,operation,unit.anchor,bar)
|
||||
repetition=phrasing.repetition(figure,state.history,bar)
|
||||
appearance=phrasing.appearance_cost(operation,state,cursor,solo_end) if home else 0.
|
||||
history=phrasing.remember(state.history,figure,bar)
|
||||
@@ -423,7 +444,7 @@ def phrase_candidates(source,analysis,incoming,part,harmony,library,rhythms,mete
|
||||
remaining=max(F(),ending.earliest-finish)
|
||||
possible=attacks+2*float(remaining)+(0 if finish==source.end else 1)
|
||||
expected=float(source.end-source.start)
|
||||
activity_bound=.35*max(0,expected-possible,attacks-1.5*expected)/max(1,float(source.end-source.start)/4)
|
||||
activity_bound=0.0 # Activity is a gesture/difficulty property, not a chorus quota.
|
||||
return cost/max(1,len(units))+activity_bound,policy.stable_rank(seed,tuple(u.notes for u in units))
|
||||
states=sequences.diverse(next_states,rank,
|
||||
lambda v:(v[2].id,v[4].pitch.number(),v[4].duration,len(v[1][-1].notes),v[1][-1].operation),policy.DEFAULT.theme_continuations)
|
||||
@@ -474,7 +495,7 @@ def phrase_candidates(source,analysis,incoming,part,harmony,library,rhythms,mete
|
||||
history=incoming.history
|
||||
for transform in transforms:
|
||||
group=tuple(n for n in notes if n.event_id in transform.events)
|
||||
if group:history=phrasing.remember(history,phrasing.heard(group,transform.operation,ending if group[-1].end==ending.hold_until else None),bar)
|
||||
if group:history=phrasing.remember(history,phrasing.heard(group,transform.operation,ending if group[-1].end==ending.hold_until else None,bar),bar)
|
||||
current=dataclasses.replace(phrase.state,next_destination=phrasing.next_destination(ending,harmony),home=home,source_reference=source_ref,callbacks=incoming.callbacks+sum(u.operation=='callback' for u in units),
|
||||
history=history,returns=state.returns,closures=incoming.closures+1,register_peak=max(state.register_peak,*(n.pitch.number() for n in notes)))
|
||||
if not incoming.home:
|
||||
|
||||
Reference in New Issue
Block a user