@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import fractions
|
||||
import functools
|
||||
|
||||
import generator.fretboard
|
||||
import generator.solo.types
|
||||
|
||||
def travel(previous: generator.solo.types.Note | None, current: generator.solo.types.Note, tempo: fractions.Fraction) -> int:
|
||||
if previous is None:
|
||||
return 0
|
||||
return transition(previous.position, current.position, current.start - previous.start, tempo)
|
||||
|
||||
@functools.lru_cache(maxsize=16384)
|
||||
def transition(previous, current, duration, tempo):
|
||||
distance = abs(previous.fret - current.fret) + abs(previous.string_number - current.string_number)
|
||||
seconds = float(duration * 60 / tempo)
|
||||
# Movement is a preference, not an unsupported claim about a player's limits.
|
||||
return round(100 * distance / max(seconds, 0.05))
|
||||
|
||||
@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, ())]
|
||||
for note in notes:
|
||||
following = []
|
||||
for position in positions(note.pitch.number()):
|
||||
current = dataclasses.replace(note, position=position)
|
||||
candidates = [(cost + travel(path[-1] if path else previous, current, tempo), (*path, current)) for cost, path in states]
|
||||
following.append(min(candidates, key=lambda item: (item[0], tuple(n.position for n in item[1]))))
|
||||
if not following:
|
||||
return None
|
||||
states = following
|
||||
return min(states, key=lambda item: (item[0], tuple(n.position for n in item[1])))
|
||||
|
||||
@functools.lru_cache(maxsize=128)
|
||||
def positions(number: int):
|
||||
return generator.fretboard.positions_for_pitch(fractions.Fraction(number))
|
||||
Reference in New Issue
Block a user