Skip to content

Instantly share code, notes, and snippets.

@mvanga
Last active May 13, 2021 18:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvanga/8ff8b7f841d82652403cc8575b5287fa to your computer and use it in GitHub Desktop.
Save mvanga/8ff8b7f841d82652403cc8575b5287fa to your computer and use it in GitHub Desktop.
# MIT License
#
# Copyright (c) 2021 Manohar Vanga
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from pprint import pprint
from itertools import tee
def pairwise_skip0(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def pairwise_skip1(iterable):
"s -> (s0,s2), (s1,s3), (s2,s4), ..."
a, b = tee(iterable)
next(b, None)
next(b, None)
return zip(a, b)
def pairwise_skip2(iterable):
"s -> (s0,s3), (s1,s4), (s2,s5), ..."
a, b = tee(iterable)
next(b, None)
next(b, None)
next(b, None)
return zip(a, b)
scale = [(0, 8), (0, 10), (1, 7), (1, 8), (1, 10), (2, 7), (2, 9), (2, 10)]
CONFIG_FINGER_STRETCH_TOTAL = 8
CONFIG_FINGER_STRETCH_PAIRS = [2, 2, 2]
CONFIG_FINGER_STRETCH_PAIRS_SKIP1 = [2, 2]
CONFIG_FINGER_STRETCH_PAIRS_SKIP2 = [6]
def is_valid_fingering(old_state, new_state):
#print(old_state, new_state)
# Basic sanity checks on new position
new_finger_pos = [new_state[i][1] for i in new_state if new_state[i] is not None]
# Check that finger stretch is not too large
if max(new_finger_pos) - min(new_finger_pos) > CONFIG_FINGER_STRETCH_TOTAL:
return False
# Check that shifts in individual finger positions are not too large
shifts = [(old_state[k], new_state[k]) for k in old_state]
for old_pos, new_pos in shifts:
# TODO: If same finger forced to move, fail?
if old_pos is not None and new_pos is not None:
# Exception: allow for rolls of one fret down or up
if abs(old_pos[0] - new_pos[0]) > 1:
return False
if abs(old_pos[1] - new_pos[1]) > 0:
return False
# Check that individual finger stretches are not too large
new_finger_pos_all = []
for k in old_state:
if new_state[k] is not None:
new_finger_pos_all.append(new_state[k])
elif old_state[k] is not None:
new_finger_pos_all.append(old_state[k])
else:
new_finger_pos_all.append(None)
for i, (f1, f2) in enumerate(pairwise_skip0(new_finger_pos)):
if f1 is None or f2 is None:
continue
if max([f1, f2]) - min([f2, f1]) > CONFIG_FINGER_STRETCH_PAIRS[i]:
return False
for i, (f1, f2) in enumerate(pairwise_skip1(new_finger_pos)):
if f1 is None or f2 is None:
continue
if max([f1, f2]) - min([f2, f1]) > CONFIG_FINGER_STRETCH_PAIRS_SKIP1[i]:
return False
for i, (f1, f2) in enumerate(pairwise_skip2(new_finger_pos)):
if f1 is None or f2 is None:
continue
if max([f1, f2]) - min([f2, f1]) > CONFIG_FINGER_STRETCH_PAIRS_SKIP2[i]:
return False
# Check that fingers don't cross over each other
def is_shift(old, new):
found_in_old = False
for i in old:
if old[i] is not None:
found_in_old = True
break
if not found_in_old:
return False
found_in_new = False
for j in new:
if new[j] is not None:
found_in_new = True
break
return found_in_old and found_in_new
if is_shift(old_state, new_state):
print(old_state, new_state)
old_finger_pos = [k for k in old_state if old_state[k] is not None][0]
new_finger_pos = [k for k in new_state if new_state[k] is not None][0]
#print(old_finger_pos, new_finger_pos, old_state[old_finger_pos], new_state[new_finger_pos])
old_finger_fret = old_state[old_finger_pos][1]
new_finger_fret = new_state[new_finger_pos][1]
if new_finger_fret > old_finger_fret and new_finger_pos < old_finger_pos:
return False
if new_finger_fret < old_finger_fret and new_finger_pos > old_finger_pos:
return False
return True
def find_all_fingerings(scale, state):
if scale == []:
return []
first = scale[0]
rest = scale[1:]
free_fingers = [i for i in state if state[i] is None]
busy_fingers = [i for i in state if state[i] is not None]
candidates = []
for finger in free_fingers:
new_state = {1: None, 2: None, 3: None, 4: None}
new_state[finger] = first
if is_valid_fingering(state, new_state):
children = find_all_fingerings(rest, new_state)
candidates.append({'state': new_state, 'children': children})
return candidates
def traverse(fingerings, original, level=0):
output = []
#print('found {} fingerings for note'.format(len(fingerings)))
for x in fingerings:
if x['children'] == []:
output.append([x['state']])
else:
for fragment in traverse(x['children'], original, level+1):
output.append([x['state']] + fragment)
if level == 0:
final = []
for out in output:
if len(out) == len(original):
final.append(out)
return final
else:
return output
pprint(traverse(find_all_fingerings(scale, {1: None, 2: None, 3: None, 4: None}), scale))
#pprint(find_all_fingerings(scale, {1: None, 2: None, 3: None, 4: None}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment