Skip to content

Commit 1a6af0c

Browse files
authored
Merge pull request #1057 from adafruit/midi_guitar
Uploading MIDI guitar code
2 parents d5d5e71 + cc16d00 commit 1a6af0c

File tree

1 file changed

+364
-0
lines changed

1 file changed

+364
-0
lines changed

MX_MIDI_Guitar/code.py

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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

Comments
 (0)