automatic formatting
black -S
s-ol
1 year, 3 days ago
16 | 16 | from .matrix import Matrix |
17 | 17 | from .scale import Scale |
18 | 18 | from .layout import Layout, WickiHaydenLayout, HarmonicLayout, GerhardLayout |
19 | from .menu import Settings, SliderSetting, ChoiceSetting, MenuMode, _eq, _thresh_offor, _color_offor | |
19 | from .menu import ( | |
20 | Settings, | |
21 | SliderSetting, | |
22 | ChoiceSetting, | |
23 | MenuMode, | |
24 | _eq, | |
25 | _thresh_offor, | |
26 | _color_offor, | |
27 | ) | |
20 | 28 | from .base import BaseMode, BaseShiftMode |
21 | 29 | |
22 | 30 | |
48 | 56 | displayio.release_displays() |
49 | 57 | i2c = I2C(sda=board.GP14, scl=board.GP15, frequency=1000000) |
50 | 58 | bus = I2CDisplay(i2c, device_address=0x3C) |
51 | self.display = SSD1306(bus, width=128, height=32, rotation=180, auto_refresh=False) | |
59 | self.display = SSD1306( | |
60 | bus, width=128, height=32, rotation=180, auto_refresh=False | |
61 | ) | |
52 | 62 | self.display.show(displayio.Group()) |
53 | 63 | self.display.refresh() |
54 | 64 | |
62 | 72 | self.audio_out = PWMAudioOut(left_channel=board.GP12, right_channel=board.GP13) |
63 | 73 | self.test_file = WaveFile(open("test.wav", "rb")) |
64 | 74 | |
65 | self.settings = Settings({ | |
66 | "active_profile": SliderSetting("CURRENT PROFILE", 24, fmt="Profile {}", thresh=_eq), | |
67 | "midi_ch_usb": SliderSetting("USB MIDI CHANNEL", 15, fmt="CH{}", thresh=_eq), | |
68 | "midi_ch_trs": SliderSetting("TRS MIDI CHANNEL", 15, fmt="CH{}", thresh=_eq), | |
69 | "midi_vel": SliderSetting("MIDI VELOCITY", 127, default=64), | |
70 | "rgb_bright": SliderSetting("LED BRIGHTNESS", 100, default=100, fmt="{}%", color=_color_offor, thresh=_thresh_offor), | |
71 | "jam_timeout": ChoiceSetting("JAM MODE FADE TIME", [0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 10, 12, 20], default=6, fmt="{:.2f}s", color=_color_offor), | |
72 | "layout_name": ChoiceSetting("KEYBOARD LAYOUT", ['wicki/hayden', 'harmonic table', 'gerhard'], default='wicki/hayden'), | |
73 | "layout_offset": SliderSetting("LAYOUT START NOTE", 127, default=24), | |
74 | "scale_name": ChoiceSetting("HIGHLIGHT SCALE", ['major', 'min nat', 'min harm', 'min mel', 'min hung', 'whole', 'penta'], default='major'), | |
75 | "scale_root": SliderSetting("SCALE ROOT NOTE", 127, default=43), | |
76 | }) | |
75 | self.settings = Settings( | |
76 | { | |
77 | "active_profile": SliderSetting( | |
78 | "CURRENT PROFILE", 24, fmt="Profile {}", thresh=_eq | |
79 | ), | |
80 | "midi_ch_usb": SliderSetting( | |
81 | "USB MIDI CHANNEL", 15, fmt="CH{}", thresh=_eq | |
82 | ), | |
83 | "midi_ch_trs": SliderSetting( | |
84 | "TRS MIDI CHANNEL", 15, fmt="CH{}", thresh=_eq | |
85 | ), | |
86 | "midi_vel": SliderSetting("MIDI VELOCITY", 127, default=64), | |
87 | "rgb_bright": SliderSetting( | |
88 | "LED BRIGHTNESS", | |
89 | 100, | |
90 | default=100, | |
91 | fmt="{}%", | |
92 | color=_color_offor, | |
93 | thresh=_thresh_offor, | |
94 | ), | |
95 | "jam_timeout": ChoiceSetting( | |
96 | "JAM MODE FADE TIME", | |
97 | [0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 10, 12, 20], | |
98 | default=6, | |
99 | fmt="{:.2f}s", | |
100 | color=_color_offor, | |
101 | ), | |
102 | "layout_name": ChoiceSetting( | |
103 | "KEYBOARD LAYOUT", | |
104 | ['wicki/hayden', 'harmonic table', 'gerhard'], | |
105 | default='wicki/hayden', | |
106 | ), | |
107 | "layout_offset": SliderSetting("LAYOUT START NOTE", 127, default=24), | |
108 | "scale_name": ChoiceSetting( | |
109 | "HIGHLIGHT SCALE", | |
110 | [ | |
111 | 'major', | |
112 | 'min nat', | |
113 | 'min harm', | |
114 | 'min mel', | |
115 | 'min hung', | |
116 | 'whole', | |
117 | 'penta', | |
118 | ], | |
119 | default='major', | |
120 | ), | |
121 | "scale_root": SliderSetting("SCALE ROOT NOTE", 127, default=43), | |
122 | } | |
123 | ) | |
77 | 124 | |
78 | 125 | self.layout = WickiHaydenLayout() |
79 | 126 | self.scale = Scale(0, Scale.STEPS["major"], "major") |
93 | 140 | self.modes = { |
94 | 141 | "base": BaseMode(self), |
95 | 142 | "base_shift": BaseShiftMode(self), |
96 | "menu": MenuMode(self, settings=self.settings, ui_settings_ids=[ | |
97 | "active_profile", "midi_ch_usb", "midi_ch_trs", "midi_vel", "rgb_bright", "jam_timeout", "layout_name" | |
98 | ]), | |
143 | "menu": MenuMode( | |
144 | self, | |
145 | settings=self.settings, | |
146 | ui_settings_ids=[ | |
147 | "active_profile", | |
148 | "midi_ch_usb", | |
149 | "midi_ch_trs", | |
150 | "midi_vel", | |
151 | "rgb_bright", | |
152 | "jam_timeout", | |
153 | "layout_name", | |
154 | ], | |
155 | ), | |
99 | 156 | } |
100 | 157 | self.mode = self.modes["base"] |
101 | 158 | |
102 | 159 | self.settings.load() |
103 | 160 | self.mode.update_scale() |
104 | 161 | |
105 | def on_midi_ch_usb(self, ch): self.midi_usb.out_channel = ch | |
106 | def on_midi_ch_trs(self, ch): self.midi_trs.out_channel = ch | |
107 | def on_midi_vel(self, vel): self.velocity = vel | |
108 | def on_rgb_bright(self, b): self.pixels.brightness = b/100 | |
109 | def on_jam_timeout(self, t): self.jam_timeout = t * 1000 | |
162 | def on_midi_ch_usb(self, ch): | |
163 | self.midi_usb.out_channel = ch | |
164 | ||
165 | def on_midi_ch_trs(self, ch): | |
166 | self.midi_trs.out_channel = ch | |
167 | ||
168 | def on_midi_vel(self, vel): | |
169 | self.velocity = vel | |
170 | ||
171 | def on_rgb_bright(self, b): | |
172 | self.pixels.brightness = b / 100 | |
173 | ||
174 | def on_jam_timeout(self, t): | |
175 | self.jam_timeout = t * 1000 | |
110 | 176 | |
111 | 177 | def on_layout(self, v): |
112 | 178 | name = self.settings.get('layout_name').value |
27 | 27 | |
28 | 28 | self._notes_dirty = False |
29 | 29 | |
30 | self.scale_label = label.Label(FONT_10, text="", color=0xffffff, anchor_point=(0, 0), anchored_position=(0, 0)) | |
31 | self.notes_label = label.Label(FONT_10, text="", color=0xffffff, anchor_point=(0, 0), anchored_position=(10, 12)) | |
30 | self.scale_label = label.Label( | |
31 | FONT_10, | |
32 | text="", | |
33 | color=0xFFFFFF, | |
34 | anchor_point=(0, 0), | |
35 | anchored_position=(0, 0), | |
36 | ) | |
37 | self.notes_label = label.Label( | |
38 | FONT_10, | |
39 | text="", | |
40 | color=0xFFFFFF, | |
41 | anchor_point=(0, 0), | |
42 | anchored_position=(10, 12), | |
43 | ) | |
32 | 44 | self.group.append(self.scale_label) |
33 | 45 | self.group.append(self.notes_label) |
34 | 46 | |
35 | self.group.append(label.Label( | |
36 | FONT_10, | |
37 | text="OCTVE", | |
38 | color=0xffffff, | |
39 | anchor_point=(1, 0.5), | |
40 | anchored_position=(128, 16), | |
41 | label_direction="DWR", | |
42 | )) | |
43 | self.group.append(rect.Rect( | |
44 | x=118, | |
45 | y=0, | |
46 | width=1, | |
47 | height=32, | |
48 | fill=0xffffff, | |
49 | )) | |
47 | self.group.append( | |
48 | label.Label( | |
49 | FONT_10, | |
50 | text="OCTVE", | |
51 | color=0xFFFFFF, | |
52 | anchor_point=(1, 0.5), | |
53 | anchored_position=(128, 16), | |
54 | label_direction="DWR", | |
55 | ) | |
56 | ) | |
57 | self.group.append( | |
58 | rect.Rect( | |
59 | x=118, | |
60 | y=0, | |
61 | width=1, | |
62 | height=32, | |
63 | fill=0xFFFFFF, | |
64 | ) | |
65 | ) | |
50 | 66 | |
51 | 67 | def update_scale(self): |
52 | 68 | for key in self.keys: |
68 | 84 | if self._notes_dirty: |
69 | 85 | active = [pitch for pitch in self.notes] |
70 | 86 | active.sort() |
71 | self.notes_label.text = ' '.join(self.keyboard.scale.label(p) for p in active) | |
87 | self.notes_label.text = ' '.join( | |
88 | self.keyboard.scale.label(p) for p in active | |
89 | ) | |
72 | 90 | self._notes_dirty = False |
73 | 91 | |
74 | 92 | def key_event(self, i: int, pressed: bool) -> bool: |
115 | 133 | def __init__(self, *args): |
116 | 134 | super().__init__(*args) |
117 | 135 | |
118 | self.scale_label = label.Label(FONT_10, text="", color=0xffffff, anchor_point=(0, 0), anchored_position=(0, 0)) | |
136 | self.scale_label = label.Label( | |
137 | FONT_10, | |
138 | text="", | |
139 | color=0xFFFFFF, | |
140 | anchor_point=(0, 0), | |
141 | anchored_position=(0, 0), | |
142 | ) | |
119 | 143 | self.group.append(self.scale_label) |
120 | 144 | |
121 | self.group.append(label.Label(FONT_10, text="choose root note", color=0xffffff, anchor_point=(0, 0), anchored_position=(10, 12))) | |
122 | ||
123 | self.group.append(label.Label( | |
124 | FONT_10, | |
125 | text="SCALE", | |
126 | color=0xffffff, | |
127 | anchor_point=(1, 0.5), | |
128 | anchored_position=(128, 16), | |
129 | label_direction="DWR", | |
130 | )) | |
131 | self.group.append(rect.Rect( | |
132 | x=118, | |
133 | y=0, | |
134 | width=1, | |
135 | height=32, | |
136 | fill=0xffffff, | |
137 | )) | |
145 | self.group.append( | |
146 | label.Label( | |
147 | FONT_10, | |
148 | text="choose root note", | |
149 | color=0xFFFFFF, | |
150 | anchor_point=(0, 0), | |
151 | anchored_position=(10, 12), | |
152 | ) | |
153 | ) | |
154 | ||
155 | self.group.append( | |
156 | label.Label( | |
157 | FONT_10, | |
158 | text="SCALE", | |
159 | color=0xFFFFFF, | |
160 | anchor_point=(1, 0.5), | |
161 | anchored_position=(128, 16), | |
162 | label_direction="DWR", | |
163 | ) | |
164 | ) | |
165 | self.group.append( | |
166 | rect.Rect( | |
167 | x=118, | |
168 | y=0, | |
169 | width=1, | |
170 | height=32, | |
171 | fill=0xFFFFFF, | |
172 | ) | |
173 | ) | |
138 | 174 | |
139 | 175 | def enter(self): |
140 | 176 | self.entered = supervisor.ticks_ms() |
55 | 55 | |
56 | 56 | |
57 | 57 | class Key: |
58 | MENU_I = const(48+0) | |
59 | PREV_I = const(48+4) | |
60 | NEXT_I = const(48+5) | |
58 | MENU_I = const(48 + 0) | |
59 | PREV_I = const(48 + 4) | |
60 | NEXT_I = const(48 + 5) | |
61 | 61 | |
62 | 62 | keyboard: Keyboard |
63 | 63 |
0 | 0 | import time |
1 | ||
1 | 2 | times = [] |
2 | 3 | names = [] |
3 | 4 | |
15 | 16 | |
16 | 17 | def stop(): |
17 | 18 | timestr = ' + '.join( |
18 | f"{(times[i+1] - times[i]) // 1000000}ms {text}" | |
19 | for i, text in enumerate(names) | |
19 | f"{(times[i+1] - times[i]) // 1000000}ms {text}" for i, text in enumerate(names) | |
20 | 20 | ) |
21 | 21 | print(f"{timestr} = {(times[-1] - times[0]) // 1000000}ms") |
15 | 15 | class WickiHaydenLayout(Layout): |
16 | 16 | def get_pitch(self, key: Key) -> int: |
17 | 17 | x, y = key.pos |
18 | return int(self.offset + 2*x + 6*y) | |
18 | return int(self.offset + 2 * x + 6 * y) | |
19 | 19 | |
20 | 20 | |
21 | 21 | class HarmonicLayout(Layout): |
22 | 22 | def get_pitch(self, key: Key) -> int: |
23 | 23 | x, y = key.pos |
24 | return int(self.offset + 4*x + 5*y) | |
24 | return int(self.offset + 4 * x + 5 * y) | |
25 | 25 | |
26 | 26 | |
27 | 27 | class GerhardLayout(Layout): |
28 | 28 | def get_pitch(self, key: Key) -> int: |
29 | 29 | x, y = key.pos |
30 | return int(self.offset + 3*x + 2.5*y) | |
30 | return int(self.offset + 3 * x + 2.5 * y) |
66 | 66 | _value: int |
67 | 67 | fmt: str | None |
68 | 68 | |
69 | def __init__(self, name: str, max: int, default: int = 0, fmt = None, thresh = _thresh, color = _color): | |
69 | def __init__( | |
70 | self, | |
71 | name: str, | |
72 | max: int, | |
73 | default: int = 0, | |
74 | fmt=None, | |
75 | thresh=_thresh, | |
76 | color=_color, | |
77 | ): | |
70 | 78 | super().__init__(name, default) |
71 | 79 | |
72 | 80 | self.max = max |
106 | 114 | class ChoiceSetting(SliderSetting): |
107 | 115 | def __init__(self, name: str, values: list, default=0, **kwargs): |
108 | 116 | default = values.index(default) |
109 | super().__init__(name, len(values) - 1, default=default, thresh=lambda v, t: v == t, **kwargs) | |
117 | super().__init__( | |
118 | name, len(values) - 1, default=default, thresh=lambda v, t: v == t, **kwargs | |
119 | ) | |
110 | 120 | self.values = values |
111 | 121 | |
112 | 122 | @property |
195 | 205 | self.settings_i = 0 |
196 | 206 | |
197 | 207 | self.group = displayio.Group() |
198 | self.label_settings = label.Label(FONT_10, text="volume", color=0xffffff, anchor_point=(0, 0), anchored_position=(0, 0)) | |
199 | self.label_value = label.Label(FONT_10, text="100%", color=0xffffff, anchor_point=(1, 0), anchored_position=(110, 12)) | |
208 | self.label_settings = label.Label( | |
209 | FONT_10, | |
210 | text="volume", | |
211 | color=0xFFFFFF, | |
212 | anchor_point=(0, 0), | |
213 | anchored_position=(0, 0), | |
214 | ) | |
215 | self.label_value = label.Label( | |
216 | FONT_10, | |
217 | text="100%", | |
218 | color=0xFFFFFF, | |
219 | anchor_point=(1, 0), | |
220 | anchored_position=(110, 12), | |
221 | ) | |
200 | 222 | self.group.append(self.label_settings) |
201 | 223 | self.group.append(self.label_value) |
202 | 224 | |
203 | self.group.append(label.Label( | |
204 | FONT_10, | |
205 | text="VALUE", | |
206 | color=0xffffff, | |
207 | anchor_point=(1, 0.5), | |
208 | anchored_position=(128, 16), | |
209 | label_direction="DWR", | |
210 | )) | |
211 | self.group.append(rect.Rect( | |
212 | x=118, | |
213 | y=0, | |
214 | width=1, | |
215 | height=32, | |
216 | fill=0xffffff, | |
217 | )) | |
225 | self.group.append( | |
226 | label.Label( | |
227 | FONT_10, | |
228 | text="VALUE", | |
229 | color=0xFFFFFF, | |
230 | anchor_point=(1, 0.5), | |
231 | anchored_position=(128, 16), | |
232 | label_direction="DWR", | |
233 | ) | |
234 | ) | |
235 | self.group.append( | |
236 | rect.Rect( | |
237 | x=118, | |
238 | y=0, | |
239 | width=1, | |
240 | height=32, | |
241 | fill=0xFFFFFF, | |
242 | ) | |
243 | ) | |
218 | 244 | |
219 | 245 | def enter(self): |
220 | 246 | self.update_display() |
244 | 270 | pixels[led_map[self.settings_i]] = self.SETTING_ACTIVE |
245 | 271 | |
246 | 272 | for i, color in enumerate(self.setting.get_colors()): |
247 | pixels[led_map[24+i]] = color | |
273 | pixels[led_map[24 + i]] = color | |
248 | 274 | |
249 | 275 | def update_display(self): |
250 | 276 | self.label_settings.text = self.setting.name + ':' |
34 | 34 | |
35 | 35 | if relative_pitch not in self.notes: |
36 | 36 | # out of scale |
37 | return (.3, 0.8, 0.0) | |
37 | return (0.3, 0.8, 0.0) | |
38 | 38 | |
39 | 39 | if self.root <= pitch < self.root + self.size: |
40 | 40 | # in core scale |
41 | return (.1, 1.0, 0.2) | |
41 | return (0.1, 1.0, 0.2) | |
42 | 42 | |
43 | 43 | # in scale |
44 | return (.7, 0.9, 0.15) | |
44 | return (0.7, 0.9, 0.15) | |
45 | 45 | |
46 | 46 | def label(self, pitch): |
47 | 47 | return self.LABELS[pitch % self.size] |
10 | 10 | |
11 | 11 | |
12 | 12 | def ticks_diff(ticks1, ticks2): |
13 | ''' Compute the signed difference between two ticks values, | |
14 | assuming that they are within 2**28 ticks | |
13 | ''' | |
14 | Compute the signed difference between two ticks values, | |
15 | assuming that they are within 2**28 ticks | |
15 | 16 | ''' |
16 | 17 | |
17 | 18 | diff = (ticks1 - ticks2) & _TICKS_MAX |