-
Notifications
You must be signed in to change notification settings - Fork 787
/
Copy pathcode.py
310 lines (294 loc) · 12.2 KB
/
code.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# SPDX-FileCopyrightText: 2023 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
import time
import asyncio
import board
import digitalio
from rainbowio import colorwheel
import keypad
import displayio
import i2cdisplaybus
import busio
import adafruit_seesaw.seesaw
import adafruit_seesaw.neopixel
import adafruit_seesaw.rotaryio
import adafruit_seesaw.digitalio
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import adafruit_displayio_ssd1306
import adafruit_midi
from adafruit_midi.control_change import ControlChange
import neopixel
# default MIDI channel (1-16)
midi_in_channel = 2
midi_out_channel = 2
# MIDI CC messages, values and names assigned to each encoder
cc_values = [
{'cc_val': (0, 127), 'cc_message': (14), 'cc_name': "Volume"},
{'cc_val': (0, 127), 'cc_message': (15), 'cc_name': "Repeats"},
{'cc_val': (0, 127), 'cc_message': (16), 'cc_name': "Size"},
{'cc_val': (0, 127), 'cc_message': (17), 'cc_name': "Mod"},
{'cc_val': (0, 127), 'cc_message': (18), 'cc_name': "Spread"},
{'cc_val': (0, 127), 'cc_message': (19), 'cc_name': "Scan"},
{'cc_val': (0, 127), 'cc_message': (20), 'cc_name': "Ramp"},
{'cc_val': (1, 3), 'cc_message': (21), 'cc_name': "Mod Number"},
{'cc_val': (1, 3), 'cc_message': (22), 'cc_name': "Mod Bank"},
{'cc_val': (1, 3), 'cc_message': (23), 'cc_name': "Mode"},
{'cc_val': (0, 1), 'cc_message': (102), 'cc_name': "Bypass/Engage"},
{'cc_val': (0, 127), 'cc_message': (93), 'cc_name': "Tap Tempo"},
{'cc_val': (0, 1), 'cc_message': (24), 'cc_name': "Loop (R Hold)"},
{'cc_val': (0, 1), 'cc_message': (25), 'cc_name': "Scan (L Hold)"},
{'cc_val': (0, 127), 'cc_message': (26), 'cc_name': "Clear (Both Hold)"},
{'cc_val': (0, 1), 'cc_message': (51), 'cc_name': "MIDI Clock Ignore"}
]
displayio.release_displays()
oled_reset = board.D13
i2c = board.STEMMA_I2C()
# STEMMA OLED setup
display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3D, reset=oled_reset)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)
splash = displayio.Group()
display.root_group = splash
font = bitmap_font.load_font('/OCRA_small.pcf')
# main label/MIDI message name text; centered
main_area = label.Label(
font, text="4x4 MIDI Messenger", color=0xFFFFFF)
main_area.anchor_point = (0.5, 0.0)
main_area.anchored_position = (display.width / 2, 0)
# MIDI message number text
msg_area = label.Label(
font, text="CC Msg: 10", color=0xFFFFFF)
msg_area.anchor_point = (0.0, 0.5)
msg_area.anchored_position = (0, display.height / 2)
# MIDI message value text
val_area = label.Label(
font, text="CC Val: 50", color=0xFFFFFF)
val_area.anchor_point = (0.0, 1.0)
val_area.anchored_position = (0, display.height)
# MIDI message status text
status_area = label.Label(
font, text="Sent!", color=0xFFFFFF)
status_area.anchor_point = (1.0, 1.0)
status_area.anchored_position = (display.width, display.height)
splash.append(main_area)
splash.append(msg_area)
splash.append(val_area)
splash.append(status_area)
# MIDI over UART setup for MIDI FeatherWing
uart = busio.UART(board.TX, board.RX, baudrate=31250, timeout=0.001)
midi = adafruit_midi.MIDI(
midi_in=uart,
midi_out=uart,
in_channel=(midi_in_channel - 1),
out_channel=(midi_out_channel - 1),
debug=False,
)
# quad rotary encoder setup
ss0 = adafruit_seesaw.seesaw.Seesaw(i2c, 0x49)
ss1 = adafruit_seesaw.seesaw.Seesaw(i2c, 0x4A)
ss2 = adafruit_seesaw.seesaw.Seesaw(i2c, 0x4B)
ss3 = adafruit_seesaw.seesaw.Seesaw(i2c, 0x4C)
# button pins for the encoders
pins = [12, 14, 17, 9]
# interrupts for the button pins. pins are passed as a bitmask
ss0.set_GPIO_interrupts(1 << pins[0] | 1 << pins[1] | 1 << pins[2] | 1 << pins[3], True)
ss1.set_GPIO_interrupts(1 << pins[0] | 1 << pins[1] | 1 << pins[2] | 1 << pins[3], True)
ss2.set_GPIO_interrupts(1 << pins[0] | 1 << pins[1] | 1 << pins[2] | 1 << pins[3], True)
ss3.set_GPIO_interrupts(1 << pins[0] | 1 << pins[1] | 1 << pins[2] | 1 << pins[3], True)
# arrays for the encoders and switches
enc0 = []
enc1 = []
enc2 = []
enc3 = []
sw0 = []
sw1 = []
sw2 = []
sw3 = []
# creating encoders and switches, enabling interrupts for encoders
for i in range(4):
enc0.append(adafruit_seesaw.rotaryio.IncrementalEncoder(ss0, i))
enc1.append(adafruit_seesaw.rotaryio.IncrementalEncoder(ss1, i))
enc2.append(adafruit_seesaw.rotaryio.IncrementalEncoder(ss2, i))
enc3.append(adafruit_seesaw.rotaryio.IncrementalEncoder(ss3, i))
sw0.append(adafruit_seesaw.digitalio.DigitalIO(ss0, pins[i]))
sw0[i].switch_to_input(digitalio.Pull.UP)
sw1.append(adafruit_seesaw.digitalio.DigitalIO(ss1, pins[i]))
sw1[i].switch_to_input(digitalio.Pull.UP)
sw2.append(adafruit_seesaw.digitalio.DigitalIO(ss2, pins[i]))
sw2[i].switch_to_input(digitalio.Pull.UP)
sw3.append(adafruit_seesaw.digitalio.DigitalIO(ss3, pins[i]))
sw3[i].switch_to_input(digitalio.Pull.UP)
ss0.enable_encoder_interrupt(encoder=i)
ss1.enable_encoder_interrupt(encoder=i)
ss2.enable_encoder_interrupt(encoder=i)
ss3.enable_encoder_interrupt(encoder=i)
# neopixels on each PCB
pix0 = adafruit_seesaw.neopixel.NeoPixel(ss0, 18, 4, auto_write = True)
pix0.brightness = 0.5
pix1 = adafruit_seesaw.neopixel.NeoPixel(ss1, 18, 4, auto_write = True)
pix1.brightness = 0.5
pix2 = adafruit_seesaw.neopixel.NeoPixel(ss2, 18, 4, auto_write = True)
pix2.brightness = 0.5
pix3 = adafruit_seesaw.neopixel.NeoPixel(ss3, 18, 4, auto_write = True)
pix3.brightness = 0.5
# onboard Feather neopixel
pix_feather = neopixel.NeoPixel(board.NEOPIXEL, 1, auto_write = True)
pix_feather.brightness = 0.5
# encoder position arrays
last_pos0 = [60, 60, 60, 60]
last_pos1 = [60, 60, 60, 0]
last_pos2 = [0, 0, 0, 120]
last_pos3 = [0, 0, 0, 0]
pos0 = [60, 60, 60, 60]
pos1 = [60, 60, 60, 0]
pos2 = [0, 0, 0, 120]
pos3 = [0, 0, 0, 0]
# color arrays for the neopixels
c0 = [0, 16, 32, 48]
c1 = [64, 80, 96, 112]
c2 = [128, 144, 160, 176]
c3 = [192, 208, 224, 240]
# setting starting colors for neopixels
for r in range(4):
pix0[r] = colorwheel(c0[r])
pix1[r] = colorwheel(c1[r])
pix2[r] = colorwheel(c2[r])
pix3[r] = colorwheel(c3[r])
# feather neopixel color
c_feather = 0
pix_feather[0] = colorwheel(c_feather)
# array of all 16 encoder positions
encoder_posititions = [60, 60, 60, 60, 60, 60, 60, 60, 0, 0, 0, 120, 0, 0, 0, 0]
class MIDI_Messages:
# tracks sending a message and index 0-15
def __init__(self):
self.send_msg = False
self.midi_index = 0
class NeoPixel_Attributes:
# tracks color, neopixel index and seesaw
def __init__(self):
self.color = c0
self.index = 0
self.strip = pix0
self.feather_color = c_feather
async def send_midi(midi_msg):
# sends MIDI message if send_msg is True/button pressed
while True:
if midi_msg.send_msg is True:
m = midi_msg.midi_index
main_area.text = f"{cc_values[m]['cc_name']}"
msg_area.text = f"CC Msg: {cc_values[m]['cc_message']}"
val_area.text = f"CC Val: {encoder_posititions[m]}"
midi.send(ControlChange(cc_values[m]['cc_message'], encoder_posititions[m]))
status_area.text = "Sent!"
print(f"sending midi: {m}, {encoder_posititions[m]}, {cc_values[m]['cc_message']}")
time.sleep(1)
midi_msg.send_msg = False
else:
status_area.text = " "
await asyncio.sleep(0)
async def rainbows(the_color):
# Updates colors of the neopixels to scroll through rainbow
while True:
the_color.feather_color += 8
the_color.strip[the_color.index] = colorwheel(the_color.color[the_color.index])
pix_feather[0] = colorwheel(the_color.feather_color)
await asyncio.sleep(0)
async def monitor_interrupts(pin0, pin1, pin2, pin3, the_color, midi_msg): #pylint: disable=too-many-statements
# function to keep encoder value pinned between CC value range
def normalize(val, min_v, max_v):
return max(min(max_v, val), min_v)
# read encoder function
def read_encoder(enc_group, pos, last_pos, pix, colors, index_diff):
# check all four encoders if interrupt is detected
for p in range(4):
pos[p] = enc_group[p].position
if pos[p] != last_pos[p]:
main_index = p + index_diff
# update CC value
if pos[p] > last_pos[p]:
colors[p] += 8
encoder_posititions[main_index] = encoder_posititions[main_index] + 1
else:
colors[p] -= 8
encoder_posititions[main_index] = encoder_posititions[main_index] - 1
encoder_posititions[main_index] = normalize(encoder_posititions[main_index],
cc_values[main_index]['cc_val'][0],
cc_values[main_index]['cc_val'][1])
colors[p] = (colors[p] + 256) % 256 # wrap around to 0-256
print(main_index, encoder_posititions[main_index])
main_area.text = f"{cc_values[main_index]['cc_name']}"
msg_area.text = f"CC Msg: {cc_values[main_index]['cc_message']}"
val_area.text = f"CC Val: {encoder_posititions[main_index]}"
last_pos[p] = pos[p]
# update NeoPixel colors
the_color.color = colors
the_color.index = p
the_color.strip = pix
# function to read button press
def press_switches(sw, index):
if not sw[index].value:
# signals that a MIDI message should be sent
midi_msg.send_msg = True
midi_msg.midi_index = index
print(f"button {index} pressed")
# interrupt pins are passed as a keypad
with keypad.Keys(
(pin0, pin1, pin2, pin3,), value_when_pressed=False, pull=True
) as keys:
while True:
key_event = keys.events.get()
if key_event and key_event.pressed:
key_number = key_event.key_number
# seesaw 0
if key_number == 0:
read_encoder(enc0, pos0, last_pos0, pix0, c0, 0)
press_switches(sw0, 0)
press_switches(sw0, 1)
press_switches(sw0, 2)
press_switches(sw0, 3)
# seesaw 1
elif key_number == 1:
read_encoder(enc1, pos1, last_pos1, pix1, c1, 4)
press_switches(sw1, 0)
press_switches(sw1, 1)
press_switches(sw1, 2)
press_switches(sw1, 3)
# update index to 4-7
midi_msg.midi_index = midi_msg.midi_index + 4
# seesaw 2
elif key_number == 2:
read_encoder(enc2, pos2, last_pos2, pix2, c2, 8)
press_switches(sw2, 0)
press_switches(sw2, 1)
press_switches(sw2, 2)
press_switches(sw2, 3)
# update index 8-11
midi_msg.midi_index = midi_msg.midi_index + 8
# seesaw 3
else:
read_encoder(enc3, pos3, last_pos3, pix3, c3, 12)
press_switches(sw3, 0)
press_switches(sw3, 1)
press_switches(sw3, 2)
press_switches(sw3, 3)
# update index 12-15
midi_msg.midi_index = midi_msg.midi_index + 12
# clear interrupt flag to reset interrupt pin
ss0.get_GPIO_interrupt_flag()
ss1.get_GPIO_interrupt_flag()
ss2.get_GPIO_interrupt_flag()
ss3.get_GPIO_interrupt_flag()
await asyncio.sleep(0)
async def main():
the_color = NeoPixel_Attributes()
midi_msg = MIDI_Messages()
# interrupt listener task
interrupt_task = asyncio.create_task(monitor_interrupts(board.D5, board.D6, board.D9,
board.D10, the_color, midi_msg))
# neopixel task
pixels_task = asyncio.create_task(rainbows(the_color))
# midi task
midi_task = asyncio.create_task(send_midi(midi_msg))
await asyncio.gather(interrupt_task, pixels_task, midi_task)
asyncio.run(main())