/ MIDI_Modal_Keyboard / code.py
code.py
1 # SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries 2 # SPDX-License-Identifier: MIT 3 4 # Pico RP2040 Mechanical MIDI Modal Keyboard 5 # 7x3 mech keyboard 6 # Each key sends MIDI NoteOn / NoteOff message over USB 7 # Can be any scale/mode 8 # Key combo sends MIDI panic (see bottom section of code) 9 10 import time 11 import board 12 from digitalio import DigitalInOut, Direction, Pull 13 import usb_midi 14 import adafruit_midi 15 from adafruit_midi.note_on import NoteOn 16 from adafruit_midi.note_off import NoteOff 17 from adafruit_debouncer import Debouncer 18 19 print("---Pico MIDI Modal Mech Keyboard---") 20 21 MIDI_CHANNEL = 1 # pick your MIDI channel here 22 23 midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=MIDI_CHANNEL-1) 24 25 def send_midi_panic(): 26 print("All MIDI notes off") 27 for x in range(128): 28 midi.send(NoteOff(x, 0)) 29 30 led = DigitalInOut(board.LED) 31 led.direction = Direction.OUTPUT 32 led.value = True 33 34 num_keys = 21 35 36 # list of pins to use (skipping GP15 on Pico because it's funky) 37 pins = ( 38 board.GP0, 39 board.GP1, 40 board.GP2, 41 board.GP3, 42 board.GP4, 43 board.GP5, 44 board.GP6, 45 board.GP7, 46 board.GP8, 47 board.GP9, 48 board.GP10, 49 board.GP11, 50 board.GP12, 51 board.GP13, 52 board.GP14, 53 board.GP16, 54 board.GP17, 55 board.GP18, 56 board.GP19, 57 board.GP20, 58 board.GP21, 59 ) 60 61 62 keys = [] 63 for pin in pins: 64 tmp_pin = DigitalInOut(pin) 65 tmp_pin.pull = Pull.UP 66 keys.append(Debouncer(tmp_pin)) 67 68 root_notes = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59) # used during config 69 note_numbers = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 70 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 71 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83) 72 note_names = ("C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", 73 "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", 74 "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",) 75 scale_root = root_notes[0] # default if nothing is picked 76 root_picked = False # state of root selection 77 mode_picked = False # state of mode selection 78 mode_choice = 0 79 80 81 # ----- User selection of the root note ----- # 82 print("Pick the root using top twelve keys, then press bottom right key to enter:") 83 print(". . . . . . .") 84 print(". . . . . o o") 85 print("o o o o o o .") 86 87 while not root_picked: 88 for i in range(12): 89 keys[i].update() 90 if keys[i].fell: 91 scale_root = root_notes[i] 92 midi.send(NoteOn(root_notes[i], 120)) 93 print("Root is", note_names[i]) 94 if keys[i].rose: 95 midi.send(NoteOff(root_notes[i], 0)) 96 keys[20].update() 97 98 if keys[20].rose: 99 root_picked = True 100 print("Root picked.\n") 101 102 # lists of mode intervals relative to root 103 major = ( 0, 2, 4, 5, 7, 9, 11 ) 104 minor = ( 0, 2, 3, 5, 7, 8, 10 ) 105 dorian = ( 0, 2, 3, 5, 7, 9, 10 ) 106 phrygian = ( 0, 1, 3, 5, 7, 8, 10 ) 107 lydian = (0 , 2, 4, 6, 7, 9, 11 ) 108 mixolydian = ( 0, 2, 4, 5, 7, 9, 10) 109 locrian = ( 0, 1, 3, 5, 6, 8, 10) 110 111 modes = [] 112 modes.append(major) 113 modes.append(minor) 114 modes.append(dorian) 115 modes.append(phrygian) 116 modes.append(lydian) 117 modes.append(mixolydian) 118 modes.append(locrian) 119 120 mode_names = ("Major/Ionian", 121 "Minor/Aeolian", 122 "Dorian", 123 "Phrygian", 124 "Lydian", 125 "Mixolydian", 126 "Locrian") 127 128 intervals = list(mixolydian) # intervals for Mixolydian by default 129 130 print("Pick the mode with top seven keys, then press bottom right key to enter:") 131 print(". . . . . . .") 132 print("o o o o o o o") 133 print("o o o o o o .") 134 135 while not mode_picked: 136 for i in range(7): 137 keys[i].update() 138 if keys[i].fell: 139 mode_choice = i 140 print(mode_names[mode_choice], "mode") 141 for j in range(7): 142 intervals[j] = modes[i][j] 143 # play the scale 144 for k in range(7): 145 midi.send(NoteOn(scale_root+intervals[k], 120)) 146 note_index = note_numbers.index(scale_root+intervals[k]) 147 print(note_names[note_index]) 148 time.sleep(0.15) 149 midi.send(NoteOff(scale_root+intervals[k], 0)) 150 time.sleep(0.15) 151 midi.send(NoteOn(scale_root+12, 120)) 152 note_index = note_numbers.index(scale_root+12) 153 print(note_names[note_index], "\n") 154 time.sleep(0.15) 155 midi.send(NoteOff(scale_root+12, 0)) 156 time.sleep(0.15) 157 158 keys[20].update() 159 if keys[20].rose: 160 print(mode_names[mode_choice], "mode picked.\n") 161 mode_picked = True 162 163 scale = [] # create the base scale 164 for i in range(7): 165 scale.append(scale_root + intervals[i]) 166 167 midi_notes = [] # build the list with three octaves 168 for k in range(7): 169 midi_notes.append(scale[k]+24) 170 for l in range(7): 171 midi_notes.append(scale[l]+12) 172 for m in range(7): 173 midi_notes.append(scale[m]) 174 175 led.value = False 176 print("Ready, set, play!") 177 178 179 while True: 180 181 for i in range(num_keys): 182 keys[i].update() 183 if keys[i].fell: 184 try: 185 midi.send(NoteOn(midi_notes[i], 120)) 186 note_index = note_numbers.index(midi_notes[i]) 187 print("MIDI NoteOn:", note_names[note_index]) 188 except ValueError: # deals w six key limit 189 pass 190 191 if keys[i].rose: 192 try: 193 midi.send(NoteOff(midi_notes[i], 0)) 194 note_index = note_numbers.index(midi_notes[i]) 195 print("MIDI NoteOff:", note_names[note_index]) 196 except ValueError: 197 pass 198 199 # Key combo for MIDI panic 200 # . o o o o o . 201 # o o o . o o o 202 # . o o o o o . 203 204 if (not keys[0].value and 205 not keys[6].value 206 and not keys[10].value 207 and not keys[14].value 208 and not keys[20].value): 209 send_midi_panic() 210 time.sleep(1)