Skip to content

Commit 0c6efa2

Browse files
authored
Merge pull request #2447 from jedgarpark/hexpad
first commit Hexpad code
2 parents 30dd7f8 + 9bca118 commit 0c6efa2

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

Hexpad/code.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# SPDX-FileCopyrightText: 2023 John Park for Adafruit
2+
#
3+
# SPDX-License-Identifier: MIT
4+
# Hexboard seven key modal note/chord pad for MIDI instruments
5+
# Runs on QT Py RP2040
6+
# (other QT Pys should work, but the BOOT button is handy for initiating configuration)
7+
8+
import time
9+
import board
10+
from digitalio import DigitalInOut, Pull
11+
import keypad
12+
import neopixel
13+
import rainbowio
14+
import usb_midi
15+
import adafruit_midi
16+
from adafruit_midi.note_on import NoteOn
17+
from adafruit_midi.note_off import NoteOff
18+
19+
button = DigitalInOut(board.BUTTON)
20+
button.pull = Pull.UP
21+
22+
num_switches = 7
23+
leds = neopixel.NeoPixel(board.A0, num_switches, brightness=0.7)
24+
leds.fill(rainbowio.colorwheel(5))
25+
leds.show()
26+
27+
# root_picked = False
28+
note = 0
29+
root = 0 # defaults to a C
30+
31+
# lists of modal intervals (relative to root). Customize these if you want other scales/keys
32+
major = (0, 2, 4, 5, 7, 9, 11)
33+
minor = (0, 2, 3, 5, 7, 8, 10)
34+
dorian = (0, 2, 3, 5, 7, 9, 10)
35+
phrygian = (0, 1, 3, 5, 7, 8, 10)
36+
lydian = (0, 2, 4, 6, 7, 9, 11)
37+
mixolydian = (0, 2, 4, 5, 7, 9, 10)
38+
locrian = (0, 1, 3, 5, 6, 8, 10)
39+
40+
modes = []
41+
modes.append(major)
42+
modes.append(minor)
43+
modes.append(dorian)
44+
modes.append(phrygian)
45+
modes.append(lydian)
46+
modes.append(mixolydian)
47+
modes.append(locrian)
48+
49+
octv = 4
50+
mode = 0 # default to major scale
51+
play_chords = True # default to play chords
52+
pre_notes = modes[mode] # initial mapping
53+
keymap = (4, 3, 5, 0, 2, 6, 1) # physical to logical key mapping
54+
55+
# Key chart | logical |Interval chart example
56+
# 6 1 | 6 7 | 9 11
57+
# 5 0 2 | 3 4 5 | 4 5 7
58+
# 4 3 | 0 1 | 0 2
59+
60+
# MIDI Setup
61+
midi_usb_channel = 1 # change this to your desired MIDI out channel, 1-16
62+
midi_usb = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=midi_usb_channel-1)
63+
64+
# Keyswitch setup
65+
keyswitch_pins = (board.A3, board.A2, board.SDA, board.SCL, board.TX, board.RX, board.A1)
66+
keyswitches = keypad.Keys(keyswitch_pins, value_when_pressed=False, pull=True)
67+
68+
def pick_mode():
69+
print("Choose mode...")
70+
mode_picked = False
71+
# pylint: disable=global-statement
72+
global mode
73+
while not mode_picked:
74+
# pylint: disable=redefined-outer-name
75+
keyswitch = keyswitches.events.get() # check for key events
76+
if keyswitch:
77+
if keyswitch.pressed:
78+
mode = keymap.index(keyswitch.key_number) # bottom left key is 0/major
79+
print("Mode is:", mode)
80+
if keyswitch.released:
81+
mode_picked = True
82+
leds.fill(rainbowio.colorwheel(8))
83+
leds.show()
84+
pick_octave()
85+
86+
def pick_octave():
87+
print("Choose octave...")
88+
octave_picked = False
89+
# pylint: disable=global-statement
90+
global octv
91+
while not octave_picked:
92+
if button.value is False: # pressed
93+
launch_config()
94+
time.sleep(0.1)
95+
# pylint: disable=redefined-outer-name
96+
keyswitch = keyswitches.events.get() # check for key events
97+
if keyswitch:
98+
if keyswitch.pressed:
99+
octv = keymap.index(keyswitch.key_number) # get remapped position, lower left is 0
100+
print("Octave is:", octv)
101+
if keyswitch.released:
102+
octave_picked = True
103+
leds.fill(rainbowio.colorwheel(16))
104+
pick_root()
105+
106+
def pick_root():# user selects key in which to play
107+
print("Choose root note...")
108+
root_picked = False
109+
# pylint: disable=global-statement
110+
global root
111+
while not root_picked:
112+
if button.value is False: # pressed
113+
launch_config()
114+
time.sleep(0.1)
115+
# pylint: disable=redefined-outer-name
116+
keyswitch = keyswitches.events.get() # check for key events
117+
if keyswitch:
118+
if keyswitch.pressed:
119+
root = keymap.index(keyswitch.key_number) # get remapped position, lower left is 0
120+
print("ksw:", keyswitch.key_number, "keymap index:", root)
121+
note = pre_notes[root]
122+
print("note:", note)
123+
midi_usb.send(NoteOn(note + (12*octv), 120))
124+
root_notes.clear()
125+
# pylint: disable=redefined-outer-name
126+
for mode_interval in range(num_switches):
127+
root_notes.append(modes[mode][mode_interval] + note)
128+
print("root note intervals:", root_notes)
129+
if keyswitch.released:
130+
note = pre_notes[root]
131+
midi_usb.send(NoteOff(note + (12*octv), 0))
132+
root_picked = True
133+
leds.fill(0x0)
134+
leds[3] = rainbowio.colorwheel(12)
135+
leds[4] = rainbowio.colorwheel(5)
136+
leds.show()
137+
pick_chords()
138+
139+
def pick_chords():
140+
print("Choose chords vs. single notes...")
141+
chords_picked = False
142+
# pylint: disable=global-statement
143+
global play_chords
144+
while not chords_picked:
145+
if button.value is False: # pressed
146+
launch_config()
147+
time.sleep(0.1)
148+
# pylint: disable=redefined-outer-name
149+
keyswitch = keyswitches.events.get() # check for key events
150+
if keyswitch:
151+
if keyswitch.pressed:
152+
if keyswitch.key_number == 4:
153+
play_chords = True
154+
print("Chords are on")
155+
chords_picked = True
156+
playback_led_colors()
157+
if keyswitch.key_number == 3:
158+
play_chords = False
159+
print("Chords are off")
160+
chords_picked = True
161+
playback_led_colors()
162+
163+
# create the interval list based on root key and mode that's been picked in variable
164+
root_notes = []
165+
for mode_interval in range(num_switches):
166+
root_notes.append(modes[mode][mode_interval] + note)
167+
print("---Hexpad---")
168+
print("\nRoot note intervals:", root_notes)
169+
170+
key_colors = (18, 10, 18, 26, 26, 18, 10)
171+
172+
def playback_led_colors():
173+
for i in range(num_switches):
174+
leds[i]=(rainbowio.colorwheel(key_colors[i]))
175+
leds.show()
176+
time.sleep(0.1)
177+
178+
playback_led_colors()
179+
180+
# MIDI Note Message Functions
181+
def send_note_on(note_num):
182+
if play_chords is True:
183+
note_num = root_notes[note_num] + (12*octv)
184+
midi_usb.send(NoteOn(note_num, 120))
185+
midi_usb.send(NoteOn(note_num + modes[mode][2], 80))
186+
midi_usb.send(NoteOn(note_num + modes[mode][4], 60))
187+
midi_usb.send(NoteOn(note_num+12, 80))
188+
else:
189+
note_num = root_notes[note_num] + (12*octv)
190+
midi_usb.send(NoteOn(note_num, 120))
191+
192+
193+
def send_note_off(note_num):
194+
if play_chords is True:
195+
note_num = root_notes[note_num] + (12*octv)
196+
midi_usb.send(NoteOff(note_num, 0))
197+
midi_usb.send(NoteOff(note_num + modes[mode][2], 0))
198+
midi_usb.send(NoteOff(note_num + modes[mode][4], 0))
199+
midi_usb.send(NoteOff(note_num+12, 0))
200+
else:
201+
note_num = root_notes[note_num] + (12*octv)
202+
midi_usb.send(NoteOff(note, 0))
203+
204+
def send_midi_panic():
205+
for x in range(128):
206+
midi_usb.send(NoteOff(x, 0))
207+
208+
def launch_config():
209+
print("-launching config-")
210+
send_midi_panic()
211+
leds.fill(rainbowio.colorwheel(5))
212+
leds.show()
213+
pick_mode()
214+
215+
send_midi_panic() # turn off any stuck notes at startup
216+
217+
218+
while True:
219+
keyswitch = keyswitches.events.get() # check for key events
220+
if keyswitch:
221+
keyswitch_number=keyswitch.key_number
222+
if keyswitch.pressed:
223+
note_picked = keymap.index(keyswitch.key_number)
224+
send_note_on(note_picked)
225+
leds[keyswitch_number]=(rainbowio.colorwheel(10))
226+
227+
leds.show()
228+
if keyswitch.released:
229+
note_picked = keymap.index(keyswitch.key_number)
230+
send_note_off(note_picked)
231+
leds[keyswitch_number]=(rainbowio.colorwheel(key_colors[keyswitch_number]))
232+
leds.show()
233+
234+
if button.value is False: # pressed
235+
launch_config()
236+
time.sleep(0.1)

0 commit comments

Comments
 (0)