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