53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Four-fret hand windows shared by chord and single-note placement.
|
|
|
|
This describes playable regions, not an anatomical fingering solver. Chord
|
|
and single tones may occupy any frets within the window. Only shifting the
|
|
window is movement. Actual sounded-note limits remain in fretboard.py.
|
|
"""
|
|
import functools
|
|
import typing
|
|
|
|
import generator.fretboard
|
|
|
|
VERSION = 'hand-window-2'
|
|
|
|
class Motion(typing.NamedTuple):
|
|
shift: int = 0
|
|
fingers: int = 0
|
|
strings: int = 0
|
|
@property
|
|
def ergonomics(self):
|
|
return self.fingers + self.strings
|
|
|
|
def base(shape):
|
|
return min((p.fret for p in shape), default=0)
|
|
|
|
@functools.lru_cache(maxsize=4096)
|
|
def regions(shape):
|
|
if not shape:
|
|
return tuple([0])
|
|
return tuple(range(max(1, max(p.fret for p in shape) - 3), min(base(shape), generator.fretboard.MAX_ANCHOR_FRET) + 1))
|
|
|
|
@functools.lru_cache(maxsize=32768)
|
|
def motion(previous, current, old_hand, hand, acoustic=False):
|
|
if not previous or not current:
|
|
return Motion()
|
|
if len(previous) == len(current) == 1:
|
|
pairs = ((previous[0], current[0]),)
|
|
unmatched = 0
|
|
else:
|
|
|
|
def voices(shape):
|
|
return {('bass' if acoustic and p.string_number in (5, 6) else p.string_number): p for p in shape}
|
|
|
|
a, b = voices(previous), voices(current)
|
|
pairs = tuple((a[s], b[s]) for s in a.keys() & b.keys())
|
|
unmatched = len(a.keys() ^ b.keys())
|
|
return Motion(abs(hand - old_hand),
|
|
unmatched + sum(abs((a.fret - old_hand) - (b.fret - hand)) for a, b in pairs),
|
|
sum(abs(a.string_number - b.string_number) for a, b in pairs))
|
|
|
|
def summary(shifts):
|
|
shifts = tuple(shifts)
|
|
return {'total_travel': sum(shifts), 'shift_count': sum(s > 0 for s in shifts), 'largest_shift': max(shifts, default=0)}
|