/ 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)