Adafruit_Learning_System_Gu.../MX_MIDI_Guitar/code.py
2022-02-18 13:50:39 -05:00

368 lines
15 KiB
Python

# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import board
import simpleio
import busio
import adafruit_lis3dh
import digitalio
from digitalio import DigitalInOut, Direction, Pull
from analogio import AnalogIn
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_midi.control_change import ControlChange
from adafruit_midi.pitch_bend import PitchBend
# imports MIDI
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
# setup for LIS3DH accelerometer
i2c = busio.I2C(board.SCL, board.SDA)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)
lis3dh.range = adafruit_lis3dh.RANGE_2_G
# setup for 3 potentiometers
pitchbend_pot = AnalogIn(board.A1)
mod_pot = AnalogIn(board.A2)
velocity_pot = AnalogIn(board.A3)
# setup for two switches that will switch modes
mod_select = DigitalInOut(board.D52)
mod_select.direction = Direction.INPUT
mod_select.pull = Pull.UP
strum_select = DigitalInOut(board.D53)
strum_select.direction = Direction.INPUT
strum_select.pull = Pull.UP
# setup for strummer switches
strumUP = DigitalInOut(board.D22)
strumUP.direction = Direction.INPUT
strumUP.pull = Pull.UP
strumDOWN = DigitalInOut(board.D23)
strumDOWN.direction = Direction.INPUT
strumDOWN.pull = Pull.UP
# setup for cherry mx switches on neck
note_pins = [board.D14, board.D2, board.D3, board.D4, board.D5,
board.D6, board.D7, board.D8, board.D9, board.D10, board.D11, board.D12]
note_buttons = []
for pin in note_pins:
note_pin = digitalio.DigitalInOut(pin)
note_pin.direction = digitalio.Direction.INPUT
note_pin.pull = digitalio.Pull.UP
note_buttons.append(note_pin)
# setup for rotary switch
oct_sel_pins = [board.D24, board.D25, board.D26, board.D27, board.D28, board.D29,
board.D30, board.D31]
octave_selector = []
for pin in oct_sel_pins:
sel_pin = digitalio.DigitalInOut(pin)
sel_pin.direction = digitalio.Direction.INPUT
sel_pin.pull = digitalio.Pull.UP
octave_selector.append(sel_pin)
# cherry mx switch states
note_e_pressed = None
note_f_pressed = None
note_fsharp_pressed = None
note_g_pressed = None
note_gsharp_pressed = None
note_a_pressed = None
note_asharp_pressed = None
note_b_pressed = None
note_c_pressed = None
note_csharp_pressed = None
note_d_pressed = None
note_dsharp_pressed = None
# state machines
strummed = None
pick = None
up_pick = None
down_pick = None
# states for analog inputs
pitchbend_val2 = 0
mod_val2 = 0
velocity_val2 = 0
acc_pos_val2 = 0
acc_neg_val2 = 0
# array for cherry mx switch states
note_states = [note_e_pressed, note_f_pressed, note_fsharp_pressed, note_g_pressed,
note_gsharp_pressed, note_a_pressed, note_asharp_pressed, note_b_pressed,
note_c_pressed, note_csharp_pressed, note_d_pressed, note_dsharp_pressed]
# array of MIDI note numbers
note_numbers = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
117, 118, 119, 120, 121, 120, 123, 124, 125, 126, 127]
# list of note name variables that are assigned to the note_numbers array
# this allows you to use the note names rather than numbers when assigning
# them to the cherry mx switches
(A0, Bb0, B0, C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1,
A1, Bb1, B1, C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2,
A2, Bb2, B2, C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3,
A3, Bb3, B3, C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4,
A4, Bb4, B4, C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5,
A5, Bb5, B5, C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6,
A6, Bb6, B6, C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7,
A7, Bb7, B7, C8, Db8, D8, Eb8, E8, F8, Gb8, G8, Ab8,
A8, Bb8, B8, C9, Db9, D9, Eb9, E9, F9, Gb9, G9) = note_numbers
# arrays for note inputs that are tied to the rotary switch
octave_8_cc = [E8, F8, Gb8, G8, Ab8, A8, Bb8, B8, C9, Db9, D9, Eb9]
octave_7_cc = [E7, F7, Gb7, G7, Ab7, A7, Bb7, B7, C8, Db8, D8, Eb8]
octave_6_cc = [E6, F6, Gb6, G6, Ab6, A6, Bb6, B6, C7, Db7, D7, Eb7]
octave_5_cc = [E5, F5, Gb5, G5, Ab5, A5, Bb5, B5, C6, Db6, D6, Eb6]
octave_4_cc = [E4, F4, Gb4, G4, Ab4, A4, Bb4, B4, C5, Db5, D5, Eb5]
octave_3_cc = [E3, F3, Gb3, G3, Ab3, A3, Bb3, B3, C4, Db4, D4, Eb4]
octave_2_cc = [E2, F2, Gb2, G2, Ab2, A2, Bb2, B2, C3, Db3, D3, Eb3]
octave_1_cc = [E1, F1, Gb1, G1, Ab1, A1, Bb1, B1, C2, Db2, D2, Eb2]
octave_select = [octave_1_cc, octave_2_cc, octave_3_cc, octave_4_cc,
octave_5_cc, octave_6_cc, octave_7_cc, octave_8_cc]
# function for reading analog inputs
def val(voltage):
return voltage.value
# beginning script REPL printout
print("MX MIDI Guitar")
print("Default output MIDI channel:", midi.out_channel + 1)
# loop
while True:
# values for LIS3DH
x, y, z = [value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration]
# mapping analog values to MIDI value ranges
# PitchBend MIDI has a range of 0 to 16383
# all others used here are 0 to 127
pitchbend_val1 = round(simpleio.map_range(val(pitchbend_pot), 0, 65535, 0, 16383))
mod_val1 = round(simpleio.map_range(val(mod_pot), 0, 65535, 0, 127))
velocity_val1 = round(simpleio.map_range(val(velocity_pot), 0, 65535, 0, 127))
acc_pos_val1 = round(simpleio.map_range(x, 0, 0.650, 127, 0))
acc_neg_val1 = round(simpleio.map_range(y, -0.925, 0, 127, 0))
# checks if modulation switch is engaged
if not mod_select.value:
# if it is, then get modulation MIDI data from LIS3DH
# positive and negative values for LIS3DH depending on
# orientation of the guitar neck
# when the guitar is held "normally" aka horizontal
# then the modulation value is neutral aka 0
# compares previous LIS3DH value to current value
if abs(acc_pos_val1 - acc_pos_val2) < 50:
# updates previous value to hold current value
acc_pos_val2 = acc_pos_val1
# MIDI data has to be sent as an integer
# this converts the LIS3DH data into an int
accelerator_pos = int(acc_pos_val2)
# int is stored as a CC message
accWheel_pos = ControlChange(1, accelerator_pos)
# CC message is sent
midi.send(accWheel_pos)
# delay to settle MIDI data
time.sleep(0.001)
# same code but for negative values
elif abs(acc_neg_val1 - acc_neg_val2) < 50:
acc_neg_val2 = acc_neg_val1
accelerator_neg = int(acc_neg_val2)
accWheel_neg = ControlChange(1, accelerator_neg)
midi.send(accWheel_neg)
time.sleep(0.001)
# if it isn't then get modulation MIDI data from pot
else:
# compares previous mod_pot value to current value
if abs(mod_val1 - mod_val2) > 2:
# updates previous value to hold current value
mod_val2 = mod_val1
# MIDI data has to be sent as an integer
# this converts the pot data into an int
modulation = int(mod_val2)
# int is stored as a CC message
modWheel = ControlChange(1, modulation)
# CC message is sent
midi.send(modWheel)
# delay to settle MIDI data
time.sleep(0.001)
# reads analog input to send MIDI data for Velocity
# compares previous velocity pot value to current value
if abs(velocity_val1 - velocity_val2) > 2:
# updates previous value to hold current value
velocity_val2 = velocity_val1
# MIDI data has to be sent as an integer
# this converts the pot data into an int
# velocity data is sent with NoteOn message
# NoteOn is sent in the loop
velocity = int(velocity_val2)
# delay to settle MIDI data
time.sleep(0.001)
# reads analog input to send MIDI data for PitchBend
# compares previous picthbend pot value to current value
if abs(pitchbend_val1 - pitchbend_val2) > 75:
# updates previous value to hold current value
pitchbend_val2 = pitchbend_val1
# MIDI data has to be sent as an integer
# this converts the pot data into an int
# int is stored as a PitchBend message
a_pitch_bend = PitchBend(int(pitchbend_val2))
# PitchBend message is sent
midi.send(a_pitch_bend)
# delay to settle MIDI data
time.sleep(0.001)
# checks position of the rotary switch
# determines which notes are mapped to the cherry mx switches
for s in octave_selector:
if not s.value:
o = octave_selector.index(s)
octave = octave_select[o]
# checks if strum select switch is engaged
if not strum_select.value:
# if it is, then:
# setup states for both strummer switches
if strumUP.value and up_pick is None:
up_pick = "strummed"
pick = time.monotonic()
if strumDOWN.value and down_pick is None:
down_pick = "strummed"
pick = time.monotonic()
# bug fix using time.monotonic(): if you hit the strummer, but don't hit a note
# the state of the strummer switch is reset
if (not pick) or ((time.monotonic() - pick) > 0.5 and
(down_pick or up_pick == "strummed")):
up_pick = None
down_pick = None
# if either strummer switch is hit
if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
and down_pick == "strummed"):
# indexes the cherry mx switch array
for i in range(12):
buttons = note_buttons[i]
# if any of the mx cherry switches are pressed
# and they weren't previously pressed (checking note_states[i])
# where i is the matching index from the note_buttons array
if not buttons.value and not note_states[i]:
# send the NoteOn message that matches with the octave[i] array
# along with the velocity value
midi.send(NoteOn(octave[i], velocity))
# note number is printed to REPL
print(octave[i])
# note state is updated
note_states[i] = True
# updates strummer switch states
up_pick = None
down_pick = None
# delay to settle MIDI data
time.sleep(0.001)
# the next if statement allows for you to strum notes multiple times without
# having to release the note
# if either strummer switch is hit
if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
and down_pick == "strummed"):
# indexes the cherry mx switch array
for i in range(12):
buttons = note_buttons[i]
# if any of the cherry mx switches are pressed
# and they *were* previously pressed (checking note_states[i])
# where i is the matching index from the note_buttons array
if not buttons.value and note_states[i]:
# send the NoteOn message that matches with the octave[i] array
# along with the velocity value
midi.send(NoteOn(octave[i], velocity))
# note number is printed to REPL
print(octave[i])
# note state is updated
note_states[i] = True
# updates strummer switch states
up_pick = None
down_pick = None
# sends a NoteOff message to prevent notes from
# staying on forever aka preventing glitches
midi.send(NoteOff(octave[i], velocity))
# delay to settle MIDI data
time.sleep(0.001)
# the next for statement sends NoteOff when the cherry mx switches
# are released
# indexes the cherry mx switch array
for i in range(12):
buttons = note_buttons[i]
# if any of the cherry mx switches are released
# and they *were* previously pressed (checking note_states[i])
# where i is the matching index from the note_buttons array
if buttons.value and note_states[i]:
# send the NoteOff message that matches with the octave[i] array
# along with the velocity value
midi.send(NoteOff(octave[i], velocity))
# note state is updated
note_states[i] = False
# updates strummer switch states
down_pick = None
up_pick = None
# delay to settle MIDI data
time.sleep(0.001)
# if strum select switch is not engaged
else:
# indexes the cherry mx switch array
for i in range(12):
buttons = note_buttons[i]
# if any of the mx cherry switches are pressed
# and they weren't previously pressed (checking note_states[i])
# where i is the matching index from the note_buttons array
if not buttons.value and not note_states[i]:
# send the NoteOn message that matches with the octave[i] array
# along with the velocity value
midi.send(NoteOn(octave[i], velocity))
# note number is printed to REPL
print(octave[i])
# note state is updated
note_states[i] = True
# delay to settle MIDI data
time.sleep(0.001)
# if any of the cherry mx switches are released
# and they *were* previously pressed (checking note_states[i])
# where i is the matching index from the note_buttons array
if (buttons.value and note_states[i]):
# send the NoteOff message that matches with the octave[i] array
# along with the velocity value
midi.send(NoteOff(octave[i], velocity))
# note state is updated
note_states[i] = False
# delay to settle MIDI data
time.sleep(0.001)
# delay to settle MIDI data
time.sleep(0.005)