concertina_helper.layouts.bisonoric
1from __future__ import annotations 2from typing import Any 3from collections.abc import Callable 4from dataclasses import dataclass 5 6from .unisonoric import UnisonoricFingering, UnisonoricLayout 7from ..type_defs import Shape, PitchToStr, Mask, Pitch, Direction, Annotation 8from .base_classes import Layout, Fingering 9 10 11@dataclass(frozen=True, kw_only=True) 12class BisonoricLayout(Layout['BisonoricFingering']): 13 ''' 14 Represents a bisonoric concertina layout: 15 the layout of the buttons on the left and right, 16 and the pitches they produce on push and pull. 17 18 >>> from concertina_helper.layouts.layout_loader import ( 19 ... load_bisonoric_layout_by_name) 20 >>> layout = load_bisonoric_layout_by_name('30_wheatstone_cg') 21 >>> print(layout) 22 PUSH: 23 E3 A3 C#4 A4 G#4 C#5 A5 G#5 C#6 A6 24 C3 G3 C4 E4 G4 C5 E5 G5 C6 E6 25 B3 D4 G4 B4 D5 G5 B5 D6 G6 B6 26 PULL: 27 F3 Bb3 D#4 G4 Bb4 D#5 G5 Bb5 D#6 F6 28 G3 B3 D4 F4 A4 B4 D5 F5 A5 B5 29 A3 F#4 A4 C5 E5 F#5 A5 C6 E6 F#6 30 31 With a layout, you can get all fingerings for a particular pitch. 32 Fingerings can be combined to produce chords: 33 34 >>> c = layout.get_fingerings(Pitch('C4')).pop() 35 >>> e = layout.get_fingerings(Pitch('E4')).pop() 36 >>> print(c | e) 37 PUSH: 38 --- --- --- --- --- --- --- --- --- --- 39 --- --- C4 E4 --- --- --- --- --- --- 40 --- --- --- --- --- --- --- --- --- --- 41 42 Fingerings with different bellow directions can not be combined: 43 >>> f = layout.get_fingerings(Pitch('F4')).pop() 44 >>> print(c | f) 45 Traceback (most recent call last): 46 ... 47 ValueError: different bellows directions 48 ''' 49 push_layout: UnisonoricLayout 50 pull_layout: UnisonoricLayout 51 52 def __post_init__(self) -> None: 53 if self.push_layout.shape != self.pull_layout.shape: 54 raise ValueError( 55 'Push and pull layout shapes must match: ' 56 f'{self.push_layout.shape} != {self.pull_layout.shape}') 57 58 @property 59 def shape(self) -> Shape: 60 return ( 61 [len(row) for row in self.push_layout.left], 62 [len(row) for row in self.push_layout.right], 63 ) 64 65 def get_fingerings(self, pitch: Pitch) -> set[BisonoricFingering]: 66 ''' 67 Given a pitch, return all possible fingerings as a set. 68 ''' 69 push_fingerings = self.push_layout.get_fingerings(pitch) 70 pull_fingerings = self.pull_layout.get_fingerings(pitch) 71 return ( 72 {BisonoricFingering(Direction.PUSH, pf) for pf in push_fingerings} | 73 {BisonoricFingering(Direction.PULL, pf) for pf in pull_fingerings} 74 ) 75 76 def __str__(self) -> str: 77 return f'{Direction.PUSH.name}:\n{self.push_layout}\n' \ 78 f'{Direction.PULL.name}:\n{self.pull_layout}' 79 80 def transpose(self, semitones: int) -> BisonoricLayout: 81 ''' 82 Given a number of semitones, return a new layout, 83 transposed up or down. 84 ''' 85 return BisonoricLayout( 86 push_layout=self.push_layout.transpose(semitones), 87 pull_layout=self.pull_layout.transpose(semitones)) 88 89 90@dataclass(frozen=True) 91class BisonoricFingering(Fingering): 92 ''' 93 Represents a fingering on a bisonoric concertina. 94 ''' 95 direction: Direction 96 _fingering: UnisonoricFingering 97 98 @property 99 def left_mask(self) -> Mask: 100 return self._fingering.left_mask 101 102 @property 103 def right_mask(self) -> Mask: 104 return self._fingering.right_mask 105 106 def __str__(self) -> str: 107 return f'{self.direction.name}:\n{self._fingering}' 108 109 def format( 110 self, 111 button_down_f: PitchToStr = lambda pitch: '@', 112 button_up_f: PitchToStr = lambda pitch: '.', 113 direction_f: Callable[[Direction], str] = 114 lambda direction: direction.name) -> str: 115 return f'{direction_f(self.direction)}:\n' \ 116 f'{self._fingering.format(button_down_f, button_up_f)}' 117 118 def __or__(self, other: Any) -> BisonoricFingering: 119 if type(self) != type(other): 120 raise TypeError('mixed operand types') 121 if self.direction != other.direction: 122 raise ValueError('different bellows directions') 123 return BisonoricFingering(self.direction, self._fingering | other._fingering) 124 125 def get_pitches(self) -> set[Pitch]: 126 return self._fingering.get_pitches() 127 128 129@dataclass(frozen=True, kw_only=True) 130class AnnotatedBisonoricFingering: 131 ''' 132 Adds contextual information to the fingering 133 that is useful in finding the best fingering for a tune. 134 ''' 135 fingering: BisonoricFingering 136 annotation: Annotation 137 138 def __str__(self) -> str: 139 a = self.annotation 140 return f'Measure {a.measure} - {a.pitch}\n{self.fingering}' 141 142 def format( # pragma: no branch 143 self, 144 button_down_f: PitchToStr = lambda pitch: '@', 145 button_up_f: PitchToStr = lambda pitch: '.', 146 direction_f: Callable[[Direction], str] = 147 lambda direction: direction.name) -> str: 148 a = self.annotation 149 formatted = self.fingering.format( 150 button_down_f=button_down_f, 151 button_up_f=button_up_f, 152 direction_f=direction_f) 153 return f'Measure {a.measure} - {a.pitch}\n{formatted}'
@dataclass(frozen=True, kw_only=True)
class
BisonoricLayout12@dataclass(frozen=True, kw_only=True) 13class BisonoricLayout(Layout['BisonoricFingering']): 14 ''' 15 Represents a bisonoric concertina layout: 16 the layout of the buttons on the left and right, 17 and the pitches they produce on push and pull. 18 19 >>> from concertina_helper.layouts.layout_loader import ( 20 ... load_bisonoric_layout_by_name) 21 >>> layout = load_bisonoric_layout_by_name('30_wheatstone_cg') 22 >>> print(layout) 23 PUSH: 24 E3 A3 C#4 A4 G#4 C#5 A5 G#5 C#6 A6 25 C3 G3 C4 E4 G4 C5 E5 G5 C6 E6 26 B3 D4 G4 B4 D5 G5 B5 D6 G6 B6 27 PULL: 28 F3 Bb3 D#4 G4 Bb4 D#5 G5 Bb5 D#6 F6 29 G3 B3 D4 F4 A4 B4 D5 F5 A5 B5 30 A3 F#4 A4 C5 E5 F#5 A5 C6 E6 F#6 31 32 With a layout, you can get all fingerings for a particular pitch. 33 Fingerings can be combined to produce chords: 34 35 >>> c = layout.get_fingerings(Pitch('C4')).pop() 36 >>> e = layout.get_fingerings(Pitch('E4')).pop() 37 >>> print(c | e) 38 PUSH: 39 --- --- --- --- --- --- --- --- --- --- 40 --- --- C4 E4 --- --- --- --- --- --- 41 --- --- --- --- --- --- --- --- --- --- 42 43 Fingerings with different bellow directions can not be combined: 44 >>> f = layout.get_fingerings(Pitch('F4')).pop() 45 >>> print(c | f) 46 Traceback (most recent call last): 47 ... 48 ValueError: different bellows directions 49 ''' 50 push_layout: UnisonoricLayout 51 pull_layout: UnisonoricLayout 52 53 def __post_init__(self) -> None: 54 if self.push_layout.shape != self.pull_layout.shape: 55 raise ValueError( 56 'Push and pull layout shapes must match: ' 57 f'{self.push_layout.shape} != {self.pull_layout.shape}') 58 59 @property 60 def shape(self) -> Shape: 61 return ( 62 [len(row) for row in self.push_layout.left], 63 [len(row) for row in self.push_layout.right], 64 ) 65 66 def get_fingerings(self, pitch: Pitch) -> set[BisonoricFingering]: 67 ''' 68 Given a pitch, return all possible fingerings as a set. 69 ''' 70 push_fingerings = self.push_layout.get_fingerings(pitch) 71 pull_fingerings = self.pull_layout.get_fingerings(pitch) 72 return ( 73 {BisonoricFingering(Direction.PUSH, pf) for pf in push_fingerings} | 74 {BisonoricFingering(Direction.PULL, pf) for pf in pull_fingerings} 75 ) 76 77 def __str__(self) -> str: 78 return f'{Direction.PUSH.name}:\n{self.push_layout}\n' \ 79 f'{Direction.PULL.name}:\n{self.pull_layout}' 80 81 def transpose(self, semitones: int) -> BisonoricLayout: 82 ''' 83 Given a number of semitones, return a new layout, 84 transposed up or down. 85 ''' 86 return BisonoricLayout( 87 push_layout=self.push_layout.transpose(semitones), 88 pull_layout=self.pull_layout.transpose(semitones))
Represents a bisonoric concertina layout: the layout of the buttons on the left and right, and the pitches they produce on push and pull.
>>> from concertina_helper.layouts.layout_loader import (
... load_bisonoric_layout_by_name)
>>> layout = load_bisonoric_layout_by_name('30_wheatstone_cg')
>>> print(layout)
PUSH:
E3 A3 C#4 A4 G#4 C#5 A5 G#5 C#6 A6
C3 G3 C4 E4 G4 C5 E5 G5 C6 E6
B3 D4 G4 B4 D5 G5 B5 D6 G6 B6
PULL:
F3 Bb3 D#4 G4 Bb4 D#5 G5 Bb5 D#6 F6
G3 B3 D4 F4 A4 B4 D5 F5 A5 B5
A3 F#4 A4 C5 E5 F#5 A5 C6 E6 F#6
With a layout, you can get all fingerings for a particular pitch. Fingerings can be combined to produce chords:
>>> c = layout.get_fingerings(Pitch('C4')).pop()
>>> e = layout.get_fingerings(Pitch('E4')).pop()
>>> print(c | e)
PUSH:
--- --- --- --- --- --- --- --- --- ---
--- --- C4 E4 --- --- --- --- --- ---
--- --- --- --- --- --- --- --- --- ---
Fingerings with different bellow directions can not be combined:
>>> f = layout.get_fingerings(Pitch('F4')).pop()
>>> print(c | f)
Traceback (most recent call last):
...
ValueError: different bellows directions
BisonoricLayout( *, push_layout: concertina_helper.layouts.unisonoric.UnisonoricLayout, pull_layout: concertina_helper.layouts.unisonoric.UnisonoricLayout)
shape: tuple[typing.Iterable[int], typing.Iterable[int]]
Returns tuple representing the number of buttons in each row, left and right.
def
get_fingerings( self, pitch: concertina_helper.type_defs.Pitch) -> set[concertina_helper.layouts.bisonoric.BisonoricFingering]:
66 def get_fingerings(self, pitch: Pitch) -> set[BisonoricFingering]: 67 ''' 68 Given a pitch, return all possible fingerings as a set. 69 ''' 70 push_fingerings = self.push_layout.get_fingerings(pitch) 71 pull_fingerings = self.pull_layout.get_fingerings(pitch) 72 return ( 73 {BisonoricFingering(Direction.PUSH, pf) for pf in push_fingerings} | 74 {BisonoricFingering(Direction.PULL, pf) for pf in pull_fingerings} 75 )
Given a pitch, return all possible fingerings as a set.
81 def transpose(self, semitones: int) -> BisonoricLayout: 82 ''' 83 Given a number of semitones, return a new layout, 84 transposed up or down. 85 ''' 86 return BisonoricLayout( 87 push_layout=self.push_layout.transpose(semitones), 88 pull_layout=self.pull_layout.transpose(semitones))
Given a number of semitones, return a new layout, transposed up or down.
91@dataclass(frozen=True) 92class BisonoricFingering(Fingering): 93 ''' 94 Represents a fingering on a bisonoric concertina. 95 ''' 96 direction: Direction 97 _fingering: UnisonoricFingering 98 99 @property 100 def left_mask(self) -> Mask: 101 return self._fingering.left_mask 102 103 @property 104 def right_mask(self) -> Mask: 105 return self._fingering.right_mask 106 107 def __str__(self) -> str: 108 return f'{self.direction.name}:\n{self._fingering}' 109 110 def format( 111 self, 112 button_down_f: PitchToStr = lambda pitch: '@', 113 button_up_f: PitchToStr = lambda pitch: '.', 114 direction_f: Callable[[Direction], str] = 115 lambda direction: direction.name) -> str: 116 return f'{direction_f(self.direction)}:\n' \ 117 f'{self._fingering.format(button_down_f, button_up_f)}' 118 119 def __or__(self, other: Any) -> BisonoricFingering: 120 if type(self) != type(other): 121 raise TypeError('mixed operand types') 122 if self.direction != other.direction: 123 raise ValueError('different bellows directions') 124 return BisonoricFingering(self.direction, self._fingering | other._fingering) 125 126 def get_pitches(self) -> set[Pitch]: 127 return self._fingering.get_pitches()
Represents a fingering on a bisonoric concertina.
BisonoricFingering( direction: concertina_helper.type_defs.Direction, _fingering: concertina_helper.layouts.unisonoric.UnisonoricFingering)
def
format( self, button_down_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function BisonoricFingering.<lambda>>, button_up_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function BisonoricFingering.<lambda>>, direction_f: collections.abc.Callable[[concertina_helper.type_defs.Direction], str] = <function BisonoricFingering.<lambda>>) -> str:
110 def format( 111 self, 112 button_down_f: PitchToStr = lambda pitch: '@', 113 button_up_f: PitchToStr = lambda pitch: '.', 114 direction_f: Callable[[Direction], str] = 115 lambda direction: direction.name) -> str: 116 return f'{direction_f(self.direction)}:\n' \ 117 f'{self._fingering.format(button_down_f, button_up_f)}'
Returns a formatted, human-readable string
@dataclass(frozen=True, kw_only=True)
class
AnnotatedBisonoricFingering:
130@dataclass(frozen=True, kw_only=True) 131class AnnotatedBisonoricFingering: 132 ''' 133 Adds contextual information to the fingering 134 that is useful in finding the best fingering for a tune. 135 ''' 136 fingering: BisonoricFingering 137 annotation: Annotation 138 139 def __str__(self) -> str: 140 a = self.annotation 141 return f'Measure {a.measure} - {a.pitch}\n{self.fingering}' 142 143 def format( # pragma: no branch 144 self, 145 button_down_f: PitchToStr = lambda pitch: '@', 146 button_up_f: PitchToStr = lambda pitch: '.', 147 direction_f: Callable[[Direction], str] = 148 lambda direction: direction.name) -> str: 149 a = self.annotation 150 formatted = self.fingering.format( 151 button_down_f=button_down_f, 152 button_up_f=button_up_f, 153 direction_f=direction_f) 154 return f'Measure {a.measure} - {a.pitch}\n{formatted}'
Adds contextual information to the fingering that is useful in finding the best fingering for a tune.
AnnotatedBisonoricFingering( *, fingering: concertina_helper.layouts.bisonoric.BisonoricFingering, annotation: concertina_helper.type_defs.Annotation)
def
format( self, button_down_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function AnnotatedBisonoricFingering.<lambda>>, button_up_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function AnnotatedBisonoricFingering.<lambda>>, direction_f: collections.abc.Callable[[concertina_helper.type_defs.Direction], str] = <function AnnotatedBisonoricFingering.<lambda>>) -> str:
143 def format( # pragma: no branch 144 self, 145 button_down_f: PitchToStr = lambda pitch: '@', 146 button_up_f: PitchToStr = lambda pitch: '.', 147 direction_f: Callable[[Direction], str] = 148 lambda direction: direction.name) -> str: 149 a = self.annotation 150 formatted = self.fingering.format( 151 button_down_f=button_down_f, 152 button_up_f=button_up_f, 153 direction_f=direction_f) 154 return f'Measure {a.measure} - {a.pitch}\n{formatted}'