|
| 1 | +""" |
| 2 | +CLUE BLE MIDI |
| 3 | +Sends MIDI CC values based on accelerometer x & y and proximity sensor |
| 4 | +Touch #0 switches Bank/Preset patches |
| 5 | +Touch #1 picks among the three CC lines w A&B buttons adjusting CC numbers |
| 6 | +Touch #2 starts/stops sending CC messages (still allows Program Change) |
| 7 | +""" |
| 8 | +import time |
| 9 | +from adafruit_clue import clue |
| 10 | +import adafruit_ble |
| 11 | +from adafruit_ble.advertising.standard import ProvideServicesAdvertisement |
| 12 | +import adafruit_ble_midi |
| 13 | +import adafruit_midi |
| 14 | +from adafruit_midi.control_change import ControlChange |
| 15 | +from adafruit_midi.program_change import ProgramChange |
| 16 | + |
| 17 | +# from adafruit_midi.note_on import NoteOn |
| 18 | +# from adafruit_midi.pitch_bend import PitchBend |
| 19 | +import simpleio |
| 20 | +import displayio |
| 21 | +import terminalio |
| 22 | +from adafruit_display_text import label |
| 23 | +from adafruit_display_shapes.rect import Rect |
| 24 | + |
| 25 | +# --- Pick your midi out channel here --- |
| 26 | +midi_channel = 1 |
| 27 | +# --- Pick your MIDI CC numbers here --- |
| 28 | +cc_x_num = 71 |
| 29 | +cc_y_num = 72 |
| 30 | +cc_prox_num = 73 |
| 31 | +# --- Pick Bank & Preset pairs here --- |
| 32 | +touch_patch = [ # first number is the Bank, second is the Preset |
| 33 | + (4, 16), # minimoog: Leads > Original MINI |
| 34 | + (5, 8), # Pads > Intergalactic Pass |
| 35 | + (0, 13), # Bass > Kraft Bass |
| 36 | + (6, 9), # Percussion > Space Hat |
| 37 | +] |
| 38 | + |
| 39 | +patch_count = len(touch_patch) |
| 40 | +patch_index = ( |
| 41 | + patch_count - 1 |
| 42 | +) # start on the last one so first time it is pressed it goes to first |
| 43 | + |
| 44 | +cc_x = 0 |
| 45 | +cc_y = 0 |
| 46 | +cc_prox = 0 |
| 47 | + |
| 48 | +# Use default HID descriptor |
| 49 | +midi_service = adafruit_ble_midi.MIDIService() |
| 50 | +advertisement = ProvideServicesAdvertisement(midi_service) |
| 51 | + |
| 52 | +ble = adafruit_ble.BLERadio() |
| 53 | +if ble.connected: |
| 54 | + for c in ble.connections: |
| 55 | + c.disconnect() |
| 56 | + |
| 57 | +midi = adafruit_midi.MIDI(midi_out=midi_service, out_channel=midi_channel - 1) |
| 58 | + |
| 59 | +print("advertising") |
| 60 | +ble.name = "CLUE BLE MIDI" |
| 61 | +ble.start_advertising(advertisement) |
| 62 | + |
| 63 | +clue.display.brightness = 1.0 |
| 64 | +clue.pixel.brightness = 0.2 |
| 65 | +screen = displayio.Group(max_size=17) |
| 66 | + |
| 67 | +ORANGE = 0xCE6136 |
| 68 | +GRAY = 0x080808 |
| 69 | +BLACK = 0x121212 |
| 70 | +BLUE = 0x668190 |
| 71 | +SILVER = 0xAAAAAA |
| 72 | +BROWN = 0x805D40 |
| 73 | + |
| 74 | +# --- Setup screen --- |
| 75 | +# BG |
| 76 | +color_bitmap = displayio.Bitmap(240, 240, 1) |
| 77 | +color_palette = displayio.Palette(1) |
| 78 | +color_palette[0] = GRAY |
| 79 | +bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette) |
| 80 | +screen.append(bg_sprite) |
| 81 | +column_a = 20 |
| 82 | +column_b = 168 |
| 83 | +# positions that are distributed relative to cc_x and cc_prox y positions |
| 84 | +row_a = 80 |
| 85 | +row_c = 170 |
| 86 | +row_b = int(row_a + ((row_c - row_a) / 2)) |
| 87 | +line_row_a = int(row_a + ((row_b - row_a) / 2)) |
| 88 | +line_row_b = int(row_b + ((row_c - row_b) / 2)) |
| 89 | +picker_box_row = [row_a, row_b, row_c] |
| 90 | + |
| 91 | +# trim |
| 92 | +top_trim_box = Rect(0, 0, 240, 8, fill=BROWN, outline=None) |
| 93 | +screen.append(top_trim_box) |
| 94 | +bottom_trim_box = Rect(0, 232, 240, 8, fill=BROWN, outline=None) |
| 95 | +screen.append(bottom_trim_box) |
| 96 | + |
| 97 | +# title text |
| 98 | +title_label = label.Label( |
| 99 | + terminalio.FONT, text="MIDI CLUE", scale=4, color=SILVER, max_glyphs=14 |
| 100 | +) |
| 101 | +title_label.x = 14 |
| 102 | +title_label.y = 27 |
| 103 | +screen.append(title_label) |
| 104 | + |
| 105 | +# title box |
| 106 | +title_box = Rect(0, 54, 240, 8, fill=BROWN, outline=None) |
| 107 | +screen.append(title_box) |
| 108 | + |
| 109 | +# cc x num |
| 110 | +cc_x_num_label = label.Label( |
| 111 | + terminalio.FONT, |
| 112 | + text=("CC {}".format(cc_x_num)), |
| 113 | + scale=3, |
| 114 | + color=ORANGE, |
| 115 | + max_glyphs=6, |
| 116 | +) |
| 117 | +cc_x_num_label.x = column_a |
| 118 | +cc_x_num_label.y = row_a |
| 119 | +screen.append(cc_x_num_label) |
| 120 | + |
| 121 | +# cc x value |
| 122 | +cc_x_label = label.Label( |
| 123 | + terminalio.FONT, text=(cc_x), scale=3, color=ORANGE, max_glyphs=3 |
| 124 | +) |
| 125 | +cc_x_label.x = column_b |
| 126 | +cc_x_label.y = row_a |
| 127 | +screen.append(cc_x_label) |
| 128 | + |
| 129 | +# picker box |
| 130 | +picker_box = Rect(3, row_a, 6, 6, fill=ORANGE, outline=None) |
| 131 | +screen.append(picker_box) |
| 132 | + |
| 133 | +# mid line |
| 134 | +mid_line_a = Rect(0, line_row_a, 240, 2, fill=SILVER, outline=None) |
| 135 | +screen.append(mid_line_a) |
| 136 | + |
| 137 | +# cc y num |
| 138 | +cc_y_num_label = label.Label( |
| 139 | + terminalio.FONT, text=("CC {}".format(cc_y_num)), scale=3, color=BLUE, max_glyphs=6 |
| 140 | +) |
| 141 | +cc_y_num_label.x = column_a |
| 142 | +cc_y_num_label.y = row_b |
| 143 | +screen.append(cc_y_num_label) |
| 144 | + |
| 145 | +# cc y value text |
| 146 | +cc_y_label = label.Label(terminalio.FONT, text=cc_y, scale=3, color=BLUE, max_glyphs=3,) |
| 147 | +cc_y_label.x = column_b |
| 148 | +cc_y_label.y = row_b |
| 149 | +screen.append(cc_y_label) |
| 150 | + |
| 151 | +# mid line |
| 152 | +mid_line_b = Rect(0, line_row_b, 240, 2, fill=SILVER, outline=None) |
| 153 | +screen.append(mid_line_b) |
| 154 | + |
| 155 | +# cc prox num text |
| 156 | +cc_prox_num_label = label.Label( |
| 157 | + terminalio.FONT, |
| 158 | + text=("CC {}".format(cc_prox_num)), |
| 159 | + scale=3, |
| 160 | + color=SILVER, |
| 161 | + max_glyphs=6, |
| 162 | +) |
| 163 | +cc_prox_num_label.x = column_a |
| 164 | +cc_prox_num_label.y = row_c |
| 165 | +screen.append(cc_prox_num_label) |
| 166 | + |
| 167 | +# cc prox value text |
| 168 | +cc_prox_label = label.Label( |
| 169 | + terminalio.FONT, text=cc_prox, scale=3, color=SILVER, max_glyphs=3, |
| 170 | +) |
| 171 | +cc_prox_label.x = column_b |
| 172 | +cc_prox_label.y = row_c |
| 173 | +screen.append(cc_prox_label) |
| 174 | + |
| 175 | +# footer line |
| 176 | +footer_line = Rect(0, 192, 240, 2, fill=SILVER, outline=None) |
| 177 | +screen.append(footer_line) |
| 178 | + |
| 179 | + |
| 180 | +# patch label |
| 181 | +patch_label = label.Label( |
| 182 | + terminalio.FONT, text=("Patch _"), scale=2, color=BLUE, max_glyphs=12, |
| 183 | +) |
| 184 | +patch_label.x = 4 |
| 185 | +patch_label.y = 216 |
| 186 | +screen.append(patch_label) |
| 187 | + |
| 188 | +# footer label |
| 189 | +footer_label = label.Label( |
| 190 | + terminalio.FONT, text=("connect BLE"), scale=2, color=ORANGE, max_glyphs=14 |
| 191 | +) |
| 192 | +footer_label.x = 102 |
| 193 | +footer_label.y = 216 |
| 194 | +screen.append(footer_label) |
| 195 | + |
| 196 | +# show the screen |
| 197 | +clue.display.show(screen) |
| 198 | + |
| 199 | +cc_num_pick_toggle = 0 # which cc to adjust w buttons |
| 200 | +cc_send_toggle = True # to start and stop sending cc |
| 201 | + |
| 202 | +debug = False # set debug mode True to test raw values, set False to run BLE MIDI |
| 203 | + |
| 204 | +while True: |
| 205 | + if debug: |
| 206 | + accel_data = clue.acceleration # get accelerometer reading |
| 207 | + accel_x = accel_data[0] |
| 208 | + accel_y = accel_data[1] |
| 209 | + prox_data = clue.proximity |
| 210 | + print("x:{} y:{}".format(accel_x, accel_y,)) |
| 211 | + print("proximity: {}".format(clue.proximity)) |
| 212 | + time.sleep(0.2) |
| 213 | + |
| 214 | + else: |
| 215 | + print("Waiting for connection") |
| 216 | + while not ble.connected: |
| 217 | + pass |
| 218 | + print("Connected") |
| 219 | + footer_label.x = 80 |
| 220 | + footer_label.color = BLUE |
| 221 | + footer_label.text = "BLE Connected" |
| 222 | + time.sleep(2) |
| 223 | + footer_label.x = 110 |
| 224 | + footer_label.color = SILVER |
| 225 | + footer_label.text = "sending CC" |
| 226 | + |
| 227 | + while ble.connected: |
| 228 | + # Clue sensor readings to CC |
| 229 | + accel_data = clue.acceleration # get accelerometer reading |
| 230 | + accel_x = accel_data[0] |
| 231 | + accel_y = accel_data[1] |
| 232 | + prox_data = clue.proximity |
| 233 | + |
| 234 | + # Remap analog readings to cc range |
| 235 | + cc_x = int(simpleio.map_range(accel_x, -9, 9, 0, 127)) |
| 236 | + cc_y = int(simpleio.map_range(accel_y, 0, 9, 0, 127)) |
| 237 | + cc_prox = int(simpleio.map_range(prox_data, 0, 255, 0, 127)) |
| 238 | + |
| 239 | + # send all the midi messages in a list |
| 240 | + if cc_send_toggle: |
| 241 | + midi.send( |
| 242 | + [ |
| 243 | + ControlChange(cc_x_num, cc_x), |
| 244 | + ControlChange(cc_y_num, cc_y), |
| 245 | + ControlChange(cc_prox_num, cc_prox), |
| 246 | + ] |
| 247 | + ) |
| 248 | + cc_x_label.text = cc_x |
| 249 | + cc_y_label.text = cc_y |
| 250 | + cc_prox_label.text = cc_prox |
| 251 | + |
| 252 | + # If you want to send NoteOn or Pitch Bend, here are examples: |
| 253 | + # midi.send(NoteOn(44, 1column_a)) # G sharp 2nd octave |
| 254 | + # a_pitch_bend = PitchBend(random.randint(0, 16383)) |
| 255 | + # midi.send(a_pitch_bend) |
| 256 | + |
| 257 | + if clue.button_a: |
| 258 | + if cc_num_pick_toggle == 0: |
| 259 | + cc_x_num = cc_x_num - 1 |
| 260 | + cc_x_num_label.text = "CC {}".format(cc_x_num) |
| 261 | + time.sleep(0.05) # Debounce |
| 262 | + elif cc_num_pick_toggle == 1: |
| 263 | + cc_y_num = cc_y_num - 1 |
| 264 | + cc_y_num_label.text = "CC {}".format(cc_y_num) |
| 265 | + time.sleep(0.05) |
| 266 | + else: |
| 267 | + cc_prox_num = cc_prox_num - 1 |
| 268 | + cc_prox_num_label.text = "CC {}".format(cc_prox_num) |
| 269 | + time.sleep(0.05) |
| 270 | + |
| 271 | + if clue.button_b: |
| 272 | + if cc_num_pick_toggle == 0: |
| 273 | + cc_x_num = cc_x_num + 1 |
| 274 | + cc_x_num_label.text = "CC {}".format(cc_x_num) |
| 275 | + time.sleep(0.05) |
| 276 | + elif cc_num_pick_toggle == 1: |
| 277 | + cc_y_num = cc_y_num + 1 |
| 278 | + cc_y_num_label.text = "CC {}".format(cc_y_num) |
| 279 | + time.sleep(0.05) |
| 280 | + else: |
| 281 | + cc_prox_num = cc_prox_num + 1 |
| 282 | + cc_prox_num_label.text = "CC {}".format(cc_prox_num) |
| 283 | + time.sleep(0.05) |
| 284 | + |
| 285 | + if clue.touch_0: |
| 286 | + patch_index = (patch_index + 1) % patch_count |
| 287 | + midi.send( # Bank select |
| 288 | + [ |
| 289 | + ControlChange(0, 0), # MSB |
| 290 | + ControlChange(32, touch_patch[patch_index][0]), # LSB |
| 291 | + ] |
| 292 | + ) |
| 293 | + midi.send(ProgramChange(touch_patch[patch_index][1])) # Program Change |
| 294 | + patch_label.text = "Patch {}".format(patch_index + 1) |
| 295 | + time.sleep(0.2) |
| 296 | + |
| 297 | + if clue.touch_1: |
| 298 | + cc_num_pick_toggle = (cc_num_pick_toggle + 1) % 3 |
| 299 | + picker_box.y = picker_box_row[cc_num_pick_toggle] |
| 300 | + time.sleep(0.1) |
| 301 | + |
| 302 | + if clue.touch_2: |
| 303 | + cc_send_toggle = not cc_send_toggle |
| 304 | + if cc_send_toggle: |
| 305 | + footer_label.x = 110 |
| 306 | + footer_label.color = SILVER |
| 307 | + footer_label.text = "sending CC" |
| 308 | + else: |
| 309 | + footer_label.x = 114 |
| 310 | + footer_label.color = ORANGE |
| 311 | + footer_label.text = "CC paused" |
| 312 | + time.sleep(0.1) |
| 313 | + |
| 314 | + print("Disconnected") |
| 315 | + print() |
| 316 | + ble.start_advertising(advertisement) |
0 commit comments