/ MX_MIDI_Guitar / code.py
code.py
  1  # SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  import board
  7  import simpleio
  8  import busio
  9  import adafruit_lis3dh
 10  import digitalio
 11  from digitalio import DigitalInOut, Direction, Pull
 12  from analogio import AnalogIn
 13  import usb_midi
 14  import adafruit_midi
 15  from adafruit_midi.note_on          import NoteOn
 16  from adafruit_midi.note_off         import NoteOff
 17  from adafruit_midi.control_change   import ControlChange
 18  from adafruit_midi.pitch_bend       import PitchBend
 19  
 20  #  imports MIDI
 21  midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
 22  
 23  #  setup for LIS3DH accelerometer
 24  i2c = busio.I2C(board.SCL, board.SDA)
 25  lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)
 26  
 27  lis3dh.range = adafruit_lis3dh.RANGE_2_G
 28  
 29  #  setup for 3 potentiometers
 30  pitchbend_pot = AnalogIn(board.A1)
 31  mod_pot = AnalogIn(board.A2)
 32  velocity_pot = AnalogIn(board.A3)
 33  
 34  #  setup for two switches that will switch modes
 35  mod_select = DigitalInOut(board.D52)
 36  mod_select.direction = Direction.INPUT
 37  mod_select.pull = Pull.UP
 38  
 39  strum_select = DigitalInOut(board.D53)
 40  strum_select.direction = Direction.INPUT
 41  strum_select.pull = Pull.UP
 42  
 43  #  setup for strummer switches
 44  strumUP = DigitalInOut(board.D22)
 45  strumUP.direction = Direction.INPUT
 46  strumUP.pull = Pull.UP
 47  
 48  strumDOWN = DigitalInOut(board.D23)
 49  strumDOWN.direction = Direction.INPUT
 50  strumDOWN.pull = Pull.UP
 51  
 52  #  setup for cherry mx switches on neck
 53  note_pins = [board.D14, board.D2, board.D3, board.D4, board.D5,
 54               board.D6, board.D7, board.D8, board.D9, board.D10, board.D11, board.D12]
 55  
 56  note_buttons = []
 57  
 58  for pin in note_pins:
 59      note_pin = digitalio.DigitalInOut(pin)
 60      note_pin.direction = digitalio.Direction.INPUT
 61      note_pin.pull = digitalio.Pull.UP
 62      note_buttons.append(note_pin)
 63  
 64  #  setup for rotary switch
 65  oct_sel_pins = [board.D24, board.D25, board.D26, board.D27, board.D28, board.D29,
 66                  board.D30, board.D31]
 67  
 68  octave_selector = []
 69  
 70  for pin in oct_sel_pins:
 71      sel_pin = digitalio.DigitalInOut(pin)
 72      sel_pin.direction = digitalio.Direction.INPUT
 73      sel_pin.pull = digitalio.Pull.UP
 74      octave_selector.append(sel_pin)
 75  
 76  #  cherry mx switch states
 77  note_e_pressed = None
 78  note_f_pressed = None
 79  note_fsharp_pressed = None
 80  note_g_pressed = None
 81  note_gsharp_pressed = None
 82  note_a_pressed = None
 83  note_asharp_pressed = None
 84  note_b_pressed = None
 85  note_c_pressed = None
 86  note_csharp_pressed = None
 87  note_d_pressed = None
 88  note_dsharp_pressed = None
 89  
 90  #  state machines
 91  strummed = None
 92  pick = None
 93  up_pick = None
 94  down_pick = None
 95  
 96  #  states for analog inputs
 97  pitchbend_val2 = 0
 98  mod_val2 = 0
 99  velocity_val2 = 0
100  acc_pos_val2 = 0
101  acc_neg_val2 = 0
102  
103  #  array for cherry mx switch states
104  note_states = [note_e_pressed, note_f_pressed, note_fsharp_pressed, note_g_pressed,
105                 note_gsharp_pressed, note_a_pressed, note_asharp_pressed, note_b_pressed,
106                 note_c_pressed, note_csharp_pressed, note_d_pressed, note_dsharp_pressed]
107  
108  #  array of MIDI note numbers
109  note_numbers = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
110                  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
111                  61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
112                  81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
113                  101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
114                  117, 118, 119, 120, 121, 120, 123, 124, 125, 126, 127]
115  
116  #  list of note name variables that are assigned to the note_numbers array
117  #  this allows you to use the note names rather than numbers when assigning
118  #  them to the cherry mx switches
119  (A0, Bb0, B0, C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1,
120   A1, Bb1, B1, C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2,
121   A2, Bb2, B2, C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3,
122   A3, Bb3, B3, C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4,
123   A4, Bb4, B4, C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5,
124   A5, Bb5, B5, C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6,
125   A6, Bb6, B6, C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7,
126   A7, Bb7, B7, C8, Db8, D8, Eb8, E8, F8, Gb8, G8, Ab8,
127   A8, Bb8, B8, C9, Db9, D9, Eb9, E9, F9, Gb9, G9) = note_numbers
128  
129  #  arrays for note inputs that are tied to the rotary switch
130  octave_8_cc = [E8, F8, Gb8, G8, Ab8, A8, Bb8, B8, C9, Db9, D9, Eb9]
131  octave_7_cc = [E7, F7, Gb7, G7, Ab7, A7, Bb7, B7, C8, Db8, D8, Eb8]
132  octave_6_cc = [E6, F6, Gb6, G6, Ab6, A6, Bb6, B6, C7, Db7, D7, Eb7]
133  octave_5_cc = [E5, F5, Gb5, G5, Ab5, A5, Bb5, B5, C6, Db6, D6, Eb6]
134  octave_4_cc = [E4, F4, Gb4, G4, Ab4, A4, Bb4, B4, C5, Db5, D5, Eb5]
135  octave_3_cc = [E3, F3, Gb3, G3, Ab3, A3, Bb3, B3, C4, Db4, D4, Eb4]
136  octave_2_cc = [E2, F2, Gb2, G2, Ab2, A2, Bb2, B2, C3, Db3, D3, Eb3]
137  octave_1_cc = [E1, F1, Gb1, G1, Ab1, A1, Bb1, B1, C2, Db2, D2, Eb2]
138  
139  octave_select = [octave_1_cc, octave_2_cc, octave_3_cc, octave_4_cc,
140                   octave_5_cc, octave_6_cc, octave_7_cc, octave_8_cc]
141  
142  #  function for reading analog inputs
143  def val(voltage):
144      return voltage.value
145  
146  #  beginning script REPL printout
147  print("MX MIDI Guitar")
148  
149  print("Default output MIDI channel:", midi.out_channel + 1)
150  
151  #  loop
152  while True:
153      #  values for LIS3DH
154      x, y, z = [value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration]
155  
156      #  mapping analog values to MIDI value ranges
157      #  PitchBend MIDI has a range of 0 to 16383
158      #  all others used here are 0 to 127
159      pitchbend_val1 = round(simpleio.map_range(val(pitchbend_pot), 0, 65535, 0, 16383))
160      mod_val1 = round(simpleio.map_range(val(mod_pot), 0, 65535, 0, 127))
161      velocity_val1 = round(simpleio.map_range(val(velocity_pot), 0, 65535, 0, 127))
162      acc_pos_val1 = round(simpleio.map_range(x, 0, 0.650, 127, 0))
163      acc_neg_val1 = round(simpleio.map_range(y, -0.925, 0, 127, 0))
164  
165      #  checks if modulation switch is engaged
166      if not mod_select.value:
167          #  if it is, then get modulation MIDI data from LIS3DH
168          #  positive and negative values for LIS3DH depending on
169          #  orientation of the guitar neck
170          #  when the guitar is held "normally" aka horizontal
171          #  then the modulation value is neutral aka 0
172  
173          #  compares previous LIS3DH value to current value
174          if abs(acc_pos_val1 - acc_pos_val2) < 50:
175              #  updates previous value to hold current value
176              acc_pos_val2 = acc_pos_val1
177              #  MIDI data has to be sent as an integer
178              #  this converts the LIS3DH data into an int
179              accelerator_pos = int(acc_pos_val2)
180              #  int is stored as a CC message
181              accWheel_pos = ControlChange(1, accelerator_pos)
182              #  CC message is sent
183              midi.send(accWheel_pos)
184              #  delay to settle MIDI data
185              time.sleep(0.001)
186  
187          #  same code but for negative values
188          elif abs(acc_neg_val1 - acc_neg_val2) < 50:
189              acc_neg_val2 = acc_neg_val1
190              accelerator_neg = int(acc_neg_val2)
191              accWheel_neg = ControlChange(1, accelerator_neg)
192              midi.send(accWheel_neg)
193              time.sleep(0.001)
194  
195      #  if it isn't then get modulation MIDI data from pot
196      else:
197          #  compares previous mod_pot value to current value
198          if abs(mod_val1 - mod_val2) > 2:
199              #  updates previous value to hold current value
200              mod_val2 = mod_val1
201              #  MIDI data has to be sent as an integer
202              #  this converts the pot data into an int
203              modulation = int(mod_val2)
204              #  int is stored as a CC message
205              modWheel = ControlChange(1, modulation)
206              #  CC message is sent
207              midi.send(modWheel)
208              #  delay to settle MIDI data
209              time.sleep(0.001)
210  
211      #  reads analog input to send MIDI data for Velocity
212      #  compares previous velocity pot value to current value
213      if abs(velocity_val1 - velocity_val2) > 2:
214          #  updates previous value to hold current value
215          velocity_val2 = velocity_val1
216          #  MIDI data has to be sent as an integer
217          #  this converts the pot data into an int
218          #  velocity data is sent with NoteOn message
219          #  NoteOn is sent in the loop
220          velocity = int(velocity_val2)
221          #  delay to settle MIDI data
222          time.sleep(0.001)
223  
224      #  reads analog input to send MIDI data for PitchBend
225      #  compares previous picthbend pot value to current value
226      if abs(pitchbend_val1 - pitchbend_val2) > 75:
227          #  updates previous value to hold current value
228          pitchbend_val2 = pitchbend_val1
229          #  MIDI data has to be sent as an integer
230          #  this converts the pot data into an int
231          #  int is stored as a PitchBend message
232          a_pitch_bend = PitchBend(int(pitchbend_val2))
233          #  PitchBend message is sent
234          midi.send(a_pitch_bend)
235          #  delay to settle MIDI data
236          time.sleep(0.001)
237  
238      #  checks position of the rotary switch
239      #  determines which notes are mapped to the cherry mx switches
240      for s in octave_selector:
241          if not s.value:
242              o = octave_selector.index(s)
243              octave = octave_select[o]
244  
245          #  checks if strum select switch is engaged
246          if not strum_select.value:
247              #  if it is, then:
248              #  setup states for both strummer switches
249              if strumUP.value and up_pick is None:
250                  up_pick = "strummed"
251                  pick = time.monotonic()
252              if strumDOWN.value and down_pick is None:
253                  down_pick = "strummed"
254                  pick = time.monotonic()
255              #  bug fix using time.monotonic(): if you hit the strummer, but don't hit a note
256              #  the state of the strummer switch is reset
257              if (not pick) or ((time.monotonic() - pick) > 0.5 and
258                                (down_pick or up_pick == "strummed")):
259                  up_pick = None
260                  down_pick = None
261  
262              #  if either strummer switch is hit
263              if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
264                                                                   and down_pick == "strummed"):
265                  #  indexes the cherry mx switch array
266                  for i in range(12):
267                      buttons = note_buttons[i]
268                      #  if any of the mx cherry switches are pressed
269                      #  and they weren't previously pressed (checking note_states[i])
270                      #  where i is the matching index from the note_buttons array
271                      if not buttons.value and not note_states[i]:
272                          #  send the NoteOn message that matches with the octave[i] array
273                          #  along with the velocity value
274                          midi.send(NoteOn(octave[i], velocity))
275                          #  note number is printed to REPL
276                          print(octave[i])
277                          #  note state is updated
278                          note_states[i] = True
279                          #  updates strummer switch states
280                          up_pick = None
281                          down_pick = None
282                          #  delay to settle MIDI data
283                          time.sleep(0.001)
284  
285              #  the next if statement allows for you to strum notes multiple times without
286              #  having to release the note
287  
288              #  if either strummer switch is hit
289              if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
290                                                                   and down_pick == "strummed"):
291                  #  indexes the cherry mx switch array
292                  for i in range(12):
293                      buttons = note_buttons[i]
294                      #  if any of the cherry mx switches are pressed
295                      #  and they *were* previously pressed (checking note_states[i])
296                      #  where i is the matching index from the note_buttons array
297                      if not buttons.value and note_states[i]:
298                          #  send the NoteOn message that matches with the octave[i] array
299                          #  along with the velocity value
300                          midi.send(NoteOn(octave[i], velocity))
301                          #  note number is printed to REPL
302                          print(octave[i])
303                          #  note state is updated
304                          note_states[i] = True
305                          #  updates strummer switch states
306                          up_pick = None
307                          down_pick = None
308                          #  sends a NoteOff message to prevent notes from
309                          #  staying on forever aka preventing glitches
310                          midi.send(NoteOff(octave[i], velocity))
311                          #  delay to settle MIDI data
312                          time.sleep(0.001)
313  
314              #  the next for statement sends NoteOff when the cherry mx switches
315              #  are released
316  
317              #  indexes the cherry mx switch array
318              for i in range(12):
319                  buttons = note_buttons[i]
320                  #  if any of the cherry mx switches are released
321                  #  and they *were* previously pressed (checking note_states[i])
322                  #  where i is the matching index from the note_buttons array
323                  if buttons.value and note_states[i]:
324                      #  send the NoteOff message that matches with the octave[i] array
325                      #  along with the velocity value
326                      midi.send(NoteOff(octave[i], velocity))
327                      #  note state is updated
328                      note_states[i] = False
329                      #  updates strummer switch states
330                      down_pick = None
331                      up_pick = None
332                      #  delay to settle MIDI data
333                      time.sleep(0.001)
334  
335          #  if strum select switch is not engaged
336  
337          else:
338              #  indexes the cherry mx switch array
339              for i in range(12):
340                  buttons = note_buttons[i]
341                  #  if any of the mx cherry switches are pressed
342                  #  and they weren't previously pressed (checking note_states[i])
343                  #  where i is the matching index from the note_buttons array
344                  if not buttons.value and not note_states[i]:
345                      #  send the NoteOn message that matches with the octave[i] array
346                      #  along with the velocity value
347                      midi.send(NoteOn(octave[i], velocity))
348                      #  note number is printed to REPL
349                      print(octave[i])
350                      #  note state is updated
351                      note_states[i] = True
352                      #  delay to settle MIDI data
353                      time.sleep(0.001)
354  
355                  #  if any of the cherry mx switches are released
356                  #  and they *were* previously pressed (checking note_states[i])
357                  #  where i is the matching index from the note_buttons array
358                  if (buttons.value and note_states[i]):
359                      #  send the NoteOff message that matches with the octave[i] array
360                      #  along with the velocity value
361                      midi.send(NoteOff(octave[i], velocity))
362                      #  note state is updated
363                      note_states[i] = False
364                      #  delay to settle MIDI data
365                      time.sleep(0.001)
366  
367      #  delay to settle MIDI data
368      time.sleep(0.005)