concertina_helper.layouts.unisonoric
1from __future__ import annotations 2from dataclasses import dataclass 3from typing import Any 4from collections.abc import Callable 5 6from ..type_defs import Shape, Pitch, PitchToStr, PitchMatrix, Mask, Direction 7from .base_classes import Layout, Fingering 8 9 10@dataclass(frozen=True) 11class UnisonoricLayout(Layout['UnisonoricFingering']): 12 left: PitchMatrix 13 right: PitchMatrix 14 15 @property 16 def shape(self) -> Shape: 17 return ( 18 [len(row) for row in self.left], 19 [len(row) for row in self.right], 20 ) 21 22 def __make_masks(self, pitch: Pitch, pm: PitchMatrix) -> set[Mask]: 23 masks = set() 24 for i, row in enumerate(pm): 25 for j, button in enumerate(row): 26 if pitch == button: 27 mutable_mask = [[False] * len(row) for row in pm] 28 mutable_mask[i][j] = True 29 mask = Mask(tuple( 30 tuple(row) for row in mutable_mask 31 )) 32 masks.add(mask) 33 return masks 34 35 def get_fingerings(self, pitch: Pitch) -> set[UnisonoricFingering]: 36 fingerings = set() 37 38 left_all_false = Mask(tuple((False,) * len(row) for row in self.left)) 39 right_all_false = Mask(tuple((False,) * len(row) for row in self.right)) 40 41 for left_mask in self.__make_masks(pitch, self.left): 42 fingerings.add(UnisonoricFingering(self, left_mask, right_all_false)) 43 for right_mask in self.__make_masks(pitch, self.right): 44 fingerings.add(UnisonoricFingering(self, left_all_false, right_mask)) 45 return fingerings 46 47 def __str__(self) -> str: 48 lines = [] 49 for left_row, right_row in zip(self.left, self.right): 50 cols = [] 51 for button in left_row: 52 cols.append(str(button).ljust(3)) 53 cols.append(' ') 54 for button in right_row: 55 cols.append(str(button).ljust(3)) 56 lines.append(' '.join(cols).strip()) 57 return '\n'.join(lines) 58 59 def transpose(self, semitones: int) -> UnisonoricLayout: 60 return UnisonoricLayout( 61 self.left.transpose(semitones), 62 self.right.transpose(semitones)) 63 64 65@dataclass(frozen=True) 66class UnisonoricFingering(Fingering): 67 layout: UnisonoricLayout 68 left_mask: Mask 69 right_mask: Mask 70 71 def __str__(self) -> str: 72 filler = '--- ' 73 74 def button_down_f(pitch: Pitch) -> str: 75 return str(pitch).ljust(len(filler)) 76 77 def button_up_f(pitch: Pitch) -> str: 78 return filler 79 return self.format(button_down_f, button_up_f) 80 81 def __format_button_row( 82 self, 83 layout_row: tuple[Pitch, ...], mask_row: tuple[bool, ...], 84 button_down_f: PitchToStr, button_up_f: PitchToStr) -> str: 85 return ''.join( 86 (button_down_f if button else button_up_f)(pitch) 87 for pitch, button in zip(layout_row, mask_row) 88 ) 89 90 def format( 91 self, 92 button_down_f: PitchToStr = lambda pitch: '@', 93 button_up_f: PitchToStr = lambda pitch: '.', 94 direction_f: Callable[[Direction], str] = 95 lambda direction: direction.name) -> str: 96 lines = [] 97 enumerated_mask_rows = enumerate(zip(self.left_mask, self.right_mask)) 98 for i, (left_mask_row, right_mask_row) in enumerated_mask_rows: 99 cols = [] 100 cols.append(self.__format_button_row( 101 self.layout.left[i], left_mask_row, 102 button_down_f, button_up_f)) 103 cols.append(' ') 104 cols.append(self.__format_button_row( 105 self.layout.right[i], right_mask_row, 106 button_down_f, button_up_f)) 107 lines.append(''.join(cols).strip()) 108 return '\n'.join(lines) 109 110 def __or__(self, other: Any) -> UnisonoricFingering: 111 if type(self) != type(other): 112 raise TypeError('mixed operand types') 113 if self.layout != other.layout: 114 raise ValueError('different layouts') 115 return UnisonoricFingering( 116 self.layout, 117 self.left_mask | other.left_mask, 118 self.right_mask | other.right_mask 119 ) 120 121 def get_pitches(self) -> set[Pitch]: 122 pitches = set() 123 sides = [ 124 (self.layout.left, self.left_mask), 125 (self.layout.right, self.right_mask), 126 ] 127 for side in sides: 128 for layout_row, mask_row in zip(*side): 129 for pitch, button in zip(layout_row, mask_row): 130 if button: 131 pitches.add(pitch) 132 return pitches
@dataclass(frozen=True)
class
UnisonoricLayout11@dataclass(frozen=True) 12class UnisonoricLayout(Layout['UnisonoricFingering']): 13 left: PitchMatrix 14 right: PitchMatrix 15 16 @property 17 def shape(self) -> Shape: 18 return ( 19 [len(row) for row in self.left], 20 [len(row) for row in self.right], 21 ) 22 23 def __make_masks(self, pitch: Pitch, pm: PitchMatrix) -> set[Mask]: 24 masks = set() 25 for i, row in enumerate(pm): 26 for j, button in enumerate(row): 27 if pitch == button: 28 mutable_mask = [[False] * len(row) for row in pm] 29 mutable_mask[i][j] = True 30 mask = Mask(tuple( 31 tuple(row) for row in mutable_mask 32 )) 33 masks.add(mask) 34 return masks 35 36 def get_fingerings(self, pitch: Pitch) -> set[UnisonoricFingering]: 37 fingerings = set() 38 39 left_all_false = Mask(tuple((False,) * len(row) for row in self.left)) 40 right_all_false = Mask(tuple((False,) * len(row) for row in self.right)) 41 42 for left_mask in self.__make_masks(pitch, self.left): 43 fingerings.add(UnisonoricFingering(self, left_mask, right_all_false)) 44 for right_mask in self.__make_masks(pitch, self.right): 45 fingerings.add(UnisonoricFingering(self, left_all_false, right_mask)) 46 return fingerings 47 48 def __str__(self) -> str: 49 lines = [] 50 for left_row, right_row in zip(self.left, self.right): 51 cols = [] 52 for button in left_row: 53 cols.append(str(button).ljust(3)) 54 cols.append(' ') 55 for button in right_row: 56 cols.append(str(button).ljust(3)) 57 lines.append(' '.join(cols).strip()) 58 return '\n'.join(lines) 59 60 def transpose(self, semitones: int) -> UnisonoricLayout: 61 return UnisonoricLayout( 62 self.left.transpose(semitones), 63 self.right.transpose(semitones))
UnisonoricLayout( left: concertina_helper.type_defs.PitchMatrix, right: concertina_helper.type_defs.PitchMatrix)
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.unisonoric.UnisonoricFingering]:
36 def get_fingerings(self, pitch: Pitch) -> set[UnisonoricFingering]: 37 fingerings = set() 38 39 left_all_false = Mask(tuple((False,) * len(row) for row in self.left)) 40 right_all_false = Mask(tuple((False,) * len(row) for row in self.right)) 41 42 for left_mask in self.__make_masks(pitch, self.left): 43 fingerings.add(UnisonoricFingering(self, left_mask, right_all_false)) 44 for right_mask in self.__make_masks(pitch, self.right): 45 fingerings.add(UnisonoricFingering(self, left_all_false, right_mask)) 46 return fingerings
Returns a set of possible fingerings on the layout for a given pitch
@dataclass(frozen=True)
class
UnisonoricFingering66@dataclass(frozen=True) 67class UnisonoricFingering(Fingering): 68 layout: UnisonoricLayout 69 left_mask: Mask 70 right_mask: Mask 71 72 def __str__(self) -> str: 73 filler = '--- ' 74 75 def button_down_f(pitch: Pitch) -> str: 76 return str(pitch).ljust(len(filler)) 77 78 def button_up_f(pitch: Pitch) -> str: 79 return filler 80 return self.format(button_down_f, button_up_f) 81 82 def __format_button_row( 83 self, 84 layout_row: tuple[Pitch, ...], mask_row: tuple[bool, ...], 85 button_down_f: PitchToStr, button_up_f: PitchToStr) -> str: 86 return ''.join( 87 (button_down_f if button else button_up_f)(pitch) 88 for pitch, button in zip(layout_row, mask_row) 89 ) 90 91 def format( 92 self, 93 button_down_f: PitchToStr = lambda pitch: '@', 94 button_up_f: PitchToStr = lambda pitch: '.', 95 direction_f: Callable[[Direction], str] = 96 lambda direction: direction.name) -> str: 97 lines = [] 98 enumerated_mask_rows = enumerate(zip(self.left_mask, self.right_mask)) 99 for i, (left_mask_row, right_mask_row) in enumerated_mask_rows: 100 cols = [] 101 cols.append(self.__format_button_row( 102 self.layout.left[i], left_mask_row, 103 button_down_f, button_up_f)) 104 cols.append(' ') 105 cols.append(self.__format_button_row( 106 self.layout.right[i], right_mask_row, 107 button_down_f, button_up_f)) 108 lines.append(''.join(cols).strip()) 109 return '\n'.join(lines) 110 111 def __or__(self, other: Any) -> UnisonoricFingering: 112 if type(self) != type(other): 113 raise TypeError('mixed operand types') 114 if self.layout != other.layout: 115 raise ValueError('different layouts') 116 return UnisonoricFingering( 117 self.layout, 118 self.left_mask | other.left_mask, 119 self.right_mask | other.right_mask 120 ) 121 122 def get_pitches(self) -> set[Pitch]: 123 pitches = set() 124 sides = [ 125 (self.layout.left, self.left_mask), 126 (self.layout.right, self.right_mask), 127 ] 128 for side in sides: 129 for layout_row, mask_row in zip(*side): 130 for pitch, button in zip(layout_row, mask_row): 131 if button: 132 pitches.add(pitch) 133 return pitches
UnisonoricFingering( layout: concertina_helper.layouts.unisonoric.UnisonoricLayout, left_mask: concertina_helper.type_defs.Mask, right_mask: concertina_helper.type_defs.Mask)
def
format( self, button_down_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function UnisonoricFingering.<lambda>>, button_up_f: collections.abc.Callable[[concertina_helper.type_defs.Pitch], str] = <function UnisonoricFingering.<lambda>>, direction_f: collections.abc.Callable[[concertina_helper.type_defs.Direction], str] = <function UnisonoricFingering.<lambda>>) -> str:
91 def format( 92 self, 93 button_down_f: PitchToStr = lambda pitch: '@', 94 button_up_f: PitchToStr = lambda pitch: '.', 95 direction_f: Callable[[Direction], str] = 96 lambda direction: direction.name) -> str: 97 lines = [] 98 enumerated_mask_rows = enumerate(zip(self.left_mask, self.right_mask)) 99 for i, (left_mask_row, right_mask_row) in enumerated_mask_rows: 100 cols = [] 101 cols.append(self.__format_button_row( 102 self.layout.left[i], left_mask_row, 103 button_down_f, button_up_f)) 104 cols.append(' ') 105 cols.append(self.__format_button_row( 106 self.layout.right[i], right_mask_row, 107 button_down_f, button_up_f)) 108 lines.append(''.join(cols).strip()) 109 return '\n'.join(lines)
Returns a formatted, human-readable string
122 def get_pitches(self) -> set[Pitch]: 123 pitches = set() 124 sides = [ 125 (self.layout.left, self.left_mask), 126 (self.layout.right, self.right_mask), 127 ] 128 for side in sides: 129 for layout_row, mask_row in zip(*side): 130 for pitch, button in zip(layout_row, mask_row): 131 if button: 132 pitches.add(pitch) 133 return pitches
Returns the pitches that would be produced by this fingering