concertina_helper.penalties

 1from collections.abc import Callable
 2
 3from .type_defs import Direction
 4
 5from .layouts.bisonoric import (
 6    AnnotatedBisonoricFingering, BisonoricFingering)
 7
 8
 9PenaltyFunction = Callable[[
10    AnnotatedBisonoricFingering, AnnotatedBisonoricFingering], float]
11
12# TODO: Penalize outer columns?
13# TODO: Penalize top row?
14
15
16def penalize_bellows_change(cost: float) -> PenaltyFunction:
17    '''
18    Penalize fingerings where the bellows changes direction between notes
19    '''
20    def calculate(
21            f1: AnnotatedBisonoricFingering,
22            f2: AnnotatedBisonoricFingering) -> float:
23        return cost if f1.fingering.direction != f2.fingering.direction else 0
24    return calculate
25
26
27def penalize_finger_in_same_column(cost: float) -> PenaltyFunction:
28    '''
29    Penalize fingerings where one finger changes rows between notes
30    '''
31    def calculate(
32            f1: AnnotatedBisonoricFingering,
33            f2: AnnotatedBisonoricFingering) -> float:
34        '''
35        This assumes fingers should be moving between notes: It will need to change
36        if this is extended to cover sustained bass notes under a melody.
37        '''
38        return (
39            cost if _find_columns_used(f1.fingering) ==
40            _find_columns_used(f2.fingering)
41            else 0)
42    return calculate
43
44
45def penalize_outer_fingers(cost: float) -> PenaltyFunction:
46    '''
47    Penalize fingerings that use outer fingers of either hand instead of inner.
48    This is useful as a tiebreaker.
49    '''
50    def calculate(
51            f1: AnnotatedBisonoricFingering,
52            f2: AnnotatedBisonoricFingering) -> float:
53        return cost * sum(1 / abs(i) for i in _find_columns_used(f2.fingering))
54    return calculate
55
56
57def penalize_pull_at_start_of_measure(cost: float) -> PenaltyFunction:
58    '''
59    Penalize fingerings where a pull begins a measure;
60    Hitting the downbeat with a push can be more musical.'''
61    def calculate(
62            f1: AnnotatedBisonoricFingering,
63            f2: AnnotatedBisonoricFingering) -> float:
64        return cost if f2.fingering.direction == Direction.PULL else 0
65    return calculate
66
67
68def _find_columns_used(fingering: BisonoricFingering) -> set[int]:
69    '''
70    Returns a set of integers representing the buttons used.
71    - On the left: 1 2 3 4 5
72    - On the right: -5 -4 -3 -2 -1
73
74    Taking the inverse of the absolute value gives us a number
75    which is small for the inner fingers, but larger for the pinkies.
76
77    This is used in `penalize_outer_fingers`.
78    '''
79    used = set()
80    for row in fingering.left_mask:
81        for i, button in enumerate(reversed(row)):
82            if button:
83                used.add(i+1)
84    for row in fingering.right_mask:
85        for i, button in enumerate(row):
86            if button:
87                used.add(-(i+1))
88    return used
def penalize_bellows_change( cost: float) -> collections.abc.Callable[[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering, concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering], float]:
17def penalize_bellows_change(cost: float) -> PenaltyFunction:
18    '''
19    Penalize fingerings where the bellows changes direction between notes
20    '''
21    def calculate(
22            f1: AnnotatedBisonoricFingering,
23            f2: AnnotatedBisonoricFingering) -> float:
24        return cost if f1.fingering.direction != f2.fingering.direction else 0
25    return calculate

Penalize fingerings where the bellows changes direction between notes

def penalize_finger_in_same_column( cost: float) -> collections.abc.Callable[[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering, concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering], float]:
28def penalize_finger_in_same_column(cost: float) -> PenaltyFunction:
29    '''
30    Penalize fingerings where one finger changes rows between notes
31    '''
32    def calculate(
33            f1: AnnotatedBisonoricFingering,
34            f2: AnnotatedBisonoricFingering) -> float:
35        '''
36        This assumes fingers should be moving between notes: It will need to change
37        if this is extended to cover sustained bass notes under a melody.
38        '''
39        return (
40            cost if _find_columns_used(f1.fingering) ==
41            _find_columns_used(f2.fingering)
42            else 0)
43    return calculate

Penalize fingerings where one finger changes rows between notes

def penalize_outer_fingers( cost: float) -> collections.abc.Callable[[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering, concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering], float]:
46def penalize_outer_fingers(cost: float) -> PenaltyFunction:
47    '''
48    Penalize fingerings that use outer fingers of either hand instead of inner.
49    This is useful as a tiebreaker.
50    '''
51    def calculate(
52            f1: AnnotatedBisonoricFingering,
53            f2: AnnotatedBisonoricFingering) -> float:
54        return cost * sum(1 / abs(i) for i in _find_columns_used(f2.fingering))
55    return calculate

Penalize fingerings that use outer fingers of either hand instead of inner. This is useful as a tiebreaker.

def penalize_pull_at_start_of_measure( cost: float) -> collections.abc.Callable[[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering, concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering], float]:
58def penalize_pull_at_start_of_measure(cost: float) -> PenaltyFunction:
59    '''
60    Penalize fingerings where a pull begins a measure;
61    Hitting the downbeat with a push can be more musical.'''
62    def calculate(
63            f1: AnnotatedBisonoricFingering,
64            f2: AnnotatedBisonoricFingering) -> float:
65        return cost if f2.fingering.direction == Direction.PULL else 0
66    return calculate

Penalize fingerings where a pull begins a measure; Hitting the downbeat with a push can be more musical.