Skip to content

Commit cafb581

Browse files
committed
Uploading MIDI guitar code
Code for MX MIDI Guitar learn guide
1 parent d5d5e71 commit cafb581

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed

MX MIDI Guitar/code.py

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
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+
print(accelerator_neg)
190+
time.sleep(0.001)
191+
192+
# if it isn't then get modulation MIDI data from pot
193+
else:
194+
# compares previous mod_pot value to current value
195+
if abs(mod_val1 - mod_val2) > 2:
196+
# updates previous value to hold current value
197+
mod_val2 = mod_val1
198+
# MIDI data has to be sent as an integer
199+
# this converts the pot data into an int
200+
modulation = int(mod_val2)
201+
# int is stored as a CC message
202+
modWheel = ControlChange(1, modulation)
203+
# CC message is sent
204+
midi.send(modWheel)
205+
# delay to settle MIDI data
206+
time.sleep(0.001)
207+
208+
# reads analog input to send MIDI data for Velocity
209+
# compares previous velocity pot value to current value
210+
if abs(velocity_val1 - velocity_val2) > 2:
211+
# updates previous value to hold current value
212+
velocity_val2 = velocity_val1
213+
# MIDI data has to be sent as an integer
214+
# this converts the pot data into an int
215+
# velocity data is sent with NoteOn message
216+
# NoteOn is sent in the loop
217+
velocity = int(velocity_val2)
218+
# delay to settle MIDI data
219+
time.sleep(0.001)
220+
221+
# reads analog input to send MIDI data for PitchBend
222+
# compares previous picthbend pot value to current value
223+
if abs(pitchbend_val1 - pitchbend_val2) > 75:
224+
# updates previous value to hold current value
225+
pitchbend_val2 = pitchbend_val1
226+
# MIDI data has to be sent as an integer
227+
# this converts the pot data into an int
228+
# int is stored as a PitchBend message
229+
a_pitch_bend = PitchBend(int(pitchbend_val2))
230+
# PitchBend message is sent
231+
midi.send(a_pitch_bend)
232+
# delay to settle MIDI data
233+
time.sleep(0.001)
234+
235+
# checks position of the rotary switch
236+
# determines which notes are mapped to the cherry mx switches
237+
for s in octave_selector:
238+
if not s.value:
239+
o = octave_selector.index(s)
240+
octave = octave_select[o]
241+
242+
# checks if strum select switch is engaged
243+
if not strum_select.value:
244+
# if it is, then:
245+
# setup states for both strummer switches
246+
if strumUP.value and up_pick is None:
247+
up_pick = "strummed"
248+
pick = time.monotonic()
249+
if strumDOWN.value and down_pick is None:
250+
down_pick = "strummed"
251+
pick = time.monotonic()
252+
# bug fix using time.monotonic(): if you hit the strummer, but don't hit a note
253+
# the state of the strummer switch is reset
254+
if (not pick) or ((time.monotonic() - pick) > 0.5 and
255+
(down_pick or up_pick == "strummed")):
256+
up_pick = None
257+
down_pick = None
258+
259+
# if either strummer switch is hit
260+
if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
261+
and down_pick == "strummed"):
262+
# indexes the cherry mx switch array
263+
for i in range(12):
264+
buttons = note_buttons[i]
265+
# if any of the mx cherry switches are pressed
266+
# and they weren't previously pressed (checking note_states[i])
267+
# where i is the matching index from the note_buttons array
268+
if not buttons.value and not note_states[i]:
269+
# send the NoteOn message that matches with the octave[i] array
270+
# along with the velocity value
271+
midi.send(NoteOn(octave[i], velocity))
272+
# note state is updated
273+
note_states[i] = True
274+
# updates strummer switch states
275+
up_pick = None
276+
down_pick = None
277+
# delay to settle MIDI data
278+
time.sleep(0.001)
279+
280+
# the next if statement allows for you to strum notes multiple times without
281+
# having to release the note
282+
283+
# if either strummer switch is hit
284+
if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
285+
and down_pick == "strummed"):
286+
# indexes the cherry mx switch array
287+
for i in range(12):
288+
buttons = note_buttons[i]
289+
# if any of the cherry mx switches are pressed
290+
# and they *were* previously pressed (checking note_states[i])
291+
# where i is the matching index from the note_buttons array
292+
if not buttons.value and note_states[i]:
293+
# send the NoteOn message that matches with the octave[i] array
294+
# along with the velocity value
295+
midi.send(NoteOn(octave[i], velocity))
296+
# note state is updated
297+
note_states[i] = True
298+
# updates strummer switch states
299+
up_pick = None
300+
down_pick = None
301+
# sends a NoteOff message to prevent notes from
302+
# staying on forever aka preventing glitches
303+
midi.send(NoteOff(octave[i], velocity))
304+
# delay to settle MIDI data
305+
time.sleep(0.001)
306+
307+
# the next for statement sends NoteOff when the cherry mx switches
308+
# are released
309+
310+
# indexes the cherry mx switch array
311+
for i in range(12):
312+
buttons = note_buttons[i]
313+
# if any of the cherry mx switches are released
314+
# and they *were* previously pressed (checking note_states[i])
315+
# where i is the matching index from the note_buttons array
316+
if buttons.value and note_states[i]:
317+
# send the NoteOff message that matches with the octave[i] array
318+
# along with the velocity value
319+
midi.send(NoteOff(octave[i], velocity))
320+
# note state is updated
321+
note_states[i] = False
322+
# updates strummer switch states
323+
down_pick = None
324+
up_pick = None
325+
# delay to settle MIDI data
326+
time.sleep(0.001)
327+
328+
# if strum select switch is not engaged
329+
330+
else:
331+
# indexes the cherry mx switch array
332+
for i in range(12):
333+
buttons = note_buttons[i]
334+
# if any of the mx cherry switches are pressed
335+
# and they weren't previously pressed (checking note_states[i])
336+
# where i is the matching index from the note_buttons array
337+
if not buttons.value and not note_states[i]:
338+
# send the NoteOn message that matches with the octave[i] array
339+
# along with the velocity value
340+
midi.send(NoteOn(octave[i], velocity))
341+
# note state is updated
342+
note_states[i] = True
343+
# delay to settle MIDI data
344+
time.sleep(0.001)
345+
346+
# if any of the cherry mx switches are released
347+
# and they *were* previously pressed (checking note_states[i])
348+
# where i is the matching index from the note_buttons array
349+
if (buttons.value and note_states[i]):
350+
# send the NoteOff message that matches with the octave[i] array
351+
# along with the velocity value
352+
midi.send(NoteOff(octave[i], velocity))
353+
# note state is updated
354+
note_states[i] = False
355+
# delay to settle MIDI data
356+
time.sleep(0.001)
357+
358+
# delay to settle MIDI data
359+
time.sleep(0.005)

0 commit comments

Comments
 (0)