Solo
This commit is contained in:
+52
-14
@@ -19,25 +19,63 @@ def transition(previous, current, duration, tempo):
|
||||
# Movement is a preference, not an unsupported claim about a player's limits.
|
||||
return round(100 * distance / max(seconds, 0.05))
|
||||
|
||||
def hand_positions(position):
|
||||
return tuple(h for h in range(1, 10) if h <= position.fret <= h + 3)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=512)
|
||||
def position_rank(position):
|
||||
from generator.solo.policy import stable_rank
|
||||
return stable_rank('guitar-position', position)
|
||||
|
||||
def stable_path(path):
|
||||
return tuple(position_rank(n.position) for n in path)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=4096)
|
||||
def place(notes: tuple[generator.solo.types.Note, ...], tempo: fractions.Fraction, previous: generator.solo.types.Note | None = None) -> tuple[int, tuple[generator.solo.types.Note, ...]] | None:
|
||||
states = [(0, ())]
|
||||
def alternatives(notes, tempo, previous=None, limit=2):
|
||||
"""Phrase paths with a four-fret hand region and distinct endpoint choices."""
|
||||
states = [(0, (), h) for h in hand_positions(previous.position)] if previous and previous.position else [(0, (), None)]
|
||||
for note in notes:
|
||||
following = []
|
||||
following = {}
|
||||
for position in positions(note.pitch.number()):
|
||||
current = dataclasses.replace(note, position=position)
|
||||
candidates = []
|
||||
for cost, path in states:
|
||||
prior = path[-1] if path else previous
|
||||
if note.continuation and (prior is None or prior.pitch != note.pitch or prior.position != position or prior.end != note.start):
|
||||
continue
|
||||
candidates.append((cost + (0 if note.continuation else travel(prior, current, tempo)), (*path, current)))
|
||||
if candidates:
|
||||
following.append(min(candidates, key=lambda item: (item[0], tuple(n.position for n in item[1]))))
|
||||
for hand in hand_positions(position):
|
||||
best = None
|
||||
for cost, path, old_hand in states:
|
||||
prior = path[-1] if path else previous
|
||||
if note.continuation and (prior is None or prior.pitch != note.pitch or prior.position != position or prior.end != note.start):
|
||||
continue
|
||||
movement = 0
|
||||
if prior and not note.continuation:
|
||||
seconds = max(.05, float((note.start-prior.start)*60/tempo))
|
||||
distance = abs(hand-old_hand) if old_hand is not None else 0
|
||||
distance += .25*abs(position.fret-prior.position.fret) + .5*abs(position.string_number-prior.position.string_number)
|
||||
movement = round(100*distance/seconds)
|
||||
candidate = (cost+movement, (*path,current), hand)
|
||||
if best is None or candidate[0] < best[0] or candidate[0] == best[0] and stable_path(candidate[1]) < stable_path(best[1]):
|
||||
best = candidate
|
||||
if best is not None:
|
||||
following[position,hand] = best
|
||||
if not following:
|
||||
return None
|
||||
states = following
|
||||
return min(states, key=lambda item: (item[0], tuple(n.position for n in item[1])))
|
||||
return ()
|
||||
states = list(following.values())
|
||||
# Endpoints survive so the following phrase can choose a better transition.
|
||||
selected, seen = [], set()
|
||||
for cost,path,hand in sorted(states,key=lambda v:(v[0],stable_path(v[1]))):
|
||||
endpoint = path[-1].position if path else None
|
||||
if endpoint in seen:
|
||||
continue
|
||||
seen.add(endpoint); selected.append((cost,path))
|
||||
if len(selected) == limit:
|
||||
break
|
||||
return tuple(selected)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=4096)
|
||||
def place(notes, tempo, previous=None):
|
||||
result = alternatives(notes,tempo,previous,1)
|
||||
return result[0] if result else None
|
||||
|
||||
@functools.lru_cache(maxsize=128)
|
||||
def positions(number: int):
|
||||
|
||||
Reference in New Issue
Block a user