+71
-38
@@ -31,11 +31,34 @@ def candidates(notes, fixed, harmony, library, register, relationship='connect t
|
||||
if not hasattr(harmony,'route_cache'):
|
||||
harmony.route_cache={}
|
||||
harmony.route_libraries={}
|
||||
harmony.concrete_routes={}
|
||||
harmony.route_libraries[id(library)]=library
|
||||
key=(notes,frozenset(fixed),id(library),register)
|
||||
concrete_key=(notes,frozenset(fixed),id(library),register)
|
||||
if concrete_key in harmony.concrete_routes:
|
||||
return tuple(dataclasses.replace(r,relationship=relationship) for r in harmony.concrete_routes[concrete_key])
|
||||
# Event/source IDs and non-fixed placeholder pitches do not affect route
|
||||
# feasibility. Cache the pitch work across different thematic histories,
|
||||
# then restore this caller's provenance before ranking complete routes.
|
||||
fixed_indices=frozenset(i for i,n in enumerate(notes) if n.event_id in fixed)
|
||||
canonical=tuple(dataclasses.replace(n,sources=(),event_id=str(i),role='connection',position=None,
|
||||
pitch=n.pitch if i==0 or i==len(notes)-1 or i in fixed_indices else types.Pitch('c',fractions.Fraction(),0)) for i,n in enumerate(notes))
|
||||
key=(canonical,fixed_indices,id(library),register)
|
||||
if key not in harmony.route_cache:
|
||||
harmony.route_cache[key]=_candidates(notes,fixed,harmony,library,register)
|
||||
return tuple(dataclasses.replace(r,relationship=relationship) for r in harmony.route_cache[key])
|
||||
harmony.route_cache[key]=_candidates(canonical,{str(i) for i in fixed_indices},harmony,library,register)
|
||||
result=[]
|
||||
for route in harmony.route_cache[key]:
|
||||
actual=tuple(n if i==0 else dataclasses.replace(n,pitch=n.pitch if n.pitch.number()==r.pitch.number() else r.pitch,position=None)
|
||||
for i,(n,r) in enumerate(zip(notes,route.notes)))
|
||||
identity=generator.solo.policy.stable_rank('complete-route',(actual,route.sources))[:16]
|
||||
result.append(dataclasses.replace(route,id=identity,notes=actual,destination=notes[-1],relationship=relationship))
|
||||
selected=generator.solo.sequences.diverse(result,
|
||||
lambda r:(sum(abs(b.pitch.number()-a.pitch.number()) for a,b in zip(r.notes,r.notes[1:])),r.id),
|
||||
lambda r:generator.solo.sequences.family(tuple(b.pitch.number()-a.pitch.number() for a,b in zip(r.notes,r.notes[1:]))),
|
||||
generator.solo.policy.DEFAULT.join_alternatives)
|
||||
if len(harmony.concrete_routes)>=8192:harmony.concrete_routes.pop(next(iter(harmony.concrete_routes)))
|
||||
if len(harmony.route_cache)>=8192:harmony.route_cache.pop(next(iter(harmony.route_cache)))
|
||||
harmony.concrete_routes[concrete_key]=selected
|
||||
return selected
|
||||
|
||||
def _candidates(notes, fixed, harmony, library, register):
|
||||
"""Every returned candidate includes its entry, complete rhythm and destination.
|
||||
@@ -49,41 +72,51 @@ def _candidates(notes, fixed, harmony, library, register):
|
||||
span = harmony.at(first.start)
|
||||
if not span or not span.scale:
|
||||
return ()
|
||||
contexts=tuple((harmony.at(n.start).scale if harmony.at(n.start) else None,
|
||||
types.permitted(harmony.choices,n.start,n.end),
|
||||
n.pitch.number() if n.event_id in fixed or i==len(notes)-1 else None) for i,n in enumerate(notes))
|
||||
if not hasattr(harmony,'pitch_routes'):harmony.pitch_routes={}
|
||||
key=(first.pitch,contexts,id(library),register)
|
||||
if key not in harmony.pitch_routes:
|
||||
harmony.pitch_routes[key]=pitch_paths(first.pitch,contexts,library,register)
|
||||
if len(harmony.pitch_routes)>8192:harmony.pitch_routes.pop(next(iter(harmony.pitch_routes)))
|
||||
route_spans=tuple((max(first.start,c.span.start),min(destination.end,c.span.end),c.scale.label())
|
||||
for c in harmony.choices if c.scale and c.span.start<destination.end and c.span.end>first.start)
|
||||
result=[]
|
||||
for pitches,sources in harmony.pitch_routes[key]:
|
||||
realized=(first,*(dataclasses.replace(n,pitch=n.pitch if n.pitch.number()==p.number() else p,position=None)
|
||||
for n,p in zip(notes[1:],pitches[1:])))
|
||||
identity=generator.solo.policy.stable_rank('complete-route',(realized,sources))[:16]
|
||||
result.append(types.RouteCandidate(identity,realized,destination,route_spans,'connect theme',sources))
|
||||
return tuple(result)
|
||||
|
||||
|
||||
def pitch_paths(first, contexts, library, register):
|
||||
"""Time-independent pitch work; context includes every overlapped collection.
|
||||
|
||||
Sounding durations and chord roles are still validated on each concrete
|
||||
realization. Equal collection contexts can reuse paths across rhythmic edits.
|
||||
"""
|
||||
destination=contexts[-1][2]
|
||||
try:
|
||||
displacement = types.scale_index(span.scale, destination.pitch.number()) - types.scale_index(span.scale, first.pitch.number())
|
||||
displacements = tuple([displacement])
|
||||
displacement=types.scale_index(contexts[0][0],destination)-types.scale_index(contexts[0][0],first.number())
|
||||
displacements=(displacement,)
|
||||
except ValueError:
|
||||
# Enumerate possible degree routes when the destination changes collection;
|
||||
# actual endpoint equality below decides compatibility, not a pitch estimate.
|
||||
displacements = tuple(range(-7, 8))
|
||||
direction = (destination.pitch.number() > first.pitch.number()) - (destination.pitch.number() < first.pitch.number())
|
||||
paths = dict.fromkeys(path for d in displacements for path in library.retrieve(len(notes), d, direction))
|
||||
result = []
|
||||
low, high = generator.fretboard.single_register((register[0] - 7, register[1] + 5))
|
||||
allowed = {n.start: types.permitted(harmony.choices, n.start, n.end) for n in notes}
|
||||
displacements=tuple(range(-7,8))
|
||||
direction=(destination>first.number())-(destination<first.number())
|
||||
paths=dict.fromkeys(path for d in displacements for path in library.retrieve(len(contexts),d,direction))
|
||||
low,high=generator.fretboard.single_register((register[0]-7,register[1]+5))
|
||||
result=[]
|
||||
for path in paths:
|
||||
realized = [first]
|
||||
for template, step in zip(notes[1:], path.steps):
|
||||
current = harmony.at(template.start)
|
||||
if not current or not current.scale:
|
||||
break
|
||||
options = advance(current.scale, realized[-1].pitch.number(), step)
|
||||
if not options:
|
||||
break
|
||||
pitch = options[0]
|
||||
if not low <= pitch.number() <= high or pitch.number() % 12 not in allowed[template.start]:
|
||||
break
|
||||
if (template.event_id in fixed or template is destination) and pitch.number() != template.pitch.number():
|
||||
break
|
||||
if not generator.solo.placement.positions(pitch.number()):
|
||||
break
|
||||
realized.append(dataclasses.replace(template, pitch=template.pitch if pitch.number() == template.pitch.number() else pitch, position=None))
|
||||
if len(realized) != len(notes):
|
||||
continue
|
||||
route_spans = tuple((max(first.start, c.span.start), min(destination.end, c.span.end), c.scale.label()) for c in harmony.choices if c.scale and c.span.start < destination.end and c.span.end > first.start)
|
||||
identity = generator.solo.policy.stable_rank('complete-route', (tuple(realized), path.sources))[:16]
|
||||
result.append(types.RouteCandidate(identity, tuple(realized), destination, route_spans, 'connect theme', path.sources))
|
||||
return generator.solo.sequences.diverse(result,
|
||||
lambda r: (sum(abs(b.pitch.number() - a.pitch.number()) for a, b in zip(r.notes, r.notes[1:])), r.id),
|
||||
lambda r: generator.solo.sequences.family(tuple(b.pitch.number() - a.pitch.number() for a, b in zip(r.notes, r.notes[1:]))),
|
||||
generator.solo.policy.DEFAULT.join_alternatives)
|
||||
realized=[first]
|
||||
for (scale,allowed,fixed),step in zip(contexts[1:],path.steps):
|
||||
if not scale:break
|
||||
options=advance(scale,realized[-1].number(),step)
|
||||
if not options:break
|
||||
pitch=options[0];number=pitch.number()
|
||||
if not low<=number<=high or number%12 not in allowed:break
|
||||
if fixed is not None and number!=fixed:break
|
||||
if not generator.solo.placement.positions(number):break
|
||||
realized.append(pitch)
|
||||
if len(realized)==len(contexts):result.append((tuple(realized),path.sources))
|
||||
return tuple(result)
|
||||
|
||||
Reference in New Issue
Block a user