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