Libraries
First, you'll import libraries for time
, board
, digitalio
, usb_midi
, adafruit_midi
, and the adafruit_debouncer
.
Next, you set your MIDI_CHANNEL
variable to whichever real-world MIDI channel you want to use. This can be anything from 1-16.
The midi
object is created to send over USB.
import time import board from digitalio import DigitalInOut, Direction, Pull import usb_midi import adafruit_midi from adafruit_midi.note_on import NoteOn from adafruit_midi.note_off import NoteOff from adafruit_debouncer import Debouncer print("---Pico MIDI Modal Mech Keyboard---") MIDI_CHANNEL = 1 # pick your MIDI channel here midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=MIDI_CHANNEL-1)
MIDI Panic
The send_midi_panic()
function can be used to send a noteOff command on all 128 MIDI notes, which is used in rare cases where a note or notes get "stuck" in the on state. You'll trigger this function with a special keyboard shortcut.
def send_midi_panic(): print("All MIDI notes off") for x in range(128): midi.send(NoteOff(x, 0))
Key Setup
You'll create a list of the 21 GPIO pins
that will be used on the Pico, and then set them all as digital input debouncer objects in a list named keys[]
.
pins = ( board.GP0, board.GP1, board.GP2, board.GP3, board.GP4, board.GP5, board.GP6, board.GP7, board.GP8, board.GP9, board.GP10, board.GP11, board.GP12, board.GP13, board.GP14, board.GP16, board.GP17, board.GP18, board.GP19, board.GP20, board.GP21, ) keys = [] for pin in pins: tmp_pin = DigitalInOut(pin) tmp_pin.pull = Pull.UP keys.append(Debouncer(tmp_pin))
root_notes = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59) # used during config note_numbers = (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) note_names = ("C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",) scale_root = root_notes[0] # default if nothing is picked root_picked = False # state of root selection mode_picked = False # state of mode selection mode_choice = 0
User Config: Note Selection
You'll allow the user to select a root note using this section of code. It will wait until the user presses the bottom right key, a.k.a. keys[20]
, until it move on.
print("Pick the root using top twelve keys, then press bottom right key to enter:") print(". . . . . . .") print(". . . . . o o") print("o o o o o o .") while not root_picked: for i in range(12): keys[i].update() if keys[i].fell: scale_root = root_notes[i] midi.send(NoteOn(root_notes[i], 120)) print("Root is", note_names[i]) if keys[i].rose: midi.send(NoteOff(root_notes[i], 0)) keys[20].update() if keys[20].rose: root_picked = True print("Root picked.\n")
Mode Lists
You'll create lists of the interval formulas of the seven modes, which are relative to the root note. The modes[]
list is a dictionary of these.
major = ( 0, 2, 4, 5, 7, 9, 11 ) minor = ( 0, 2, 3, 5, 7, 8, 10 ) dorian = ( 0, 2, 3, 5, 7, 9, 10 ) phrygian = ( 0, 1, 3, 5, 7, 8, 10 ) lydian = (0 , 2, 4, 6, 7, 9, 11 ) mixolydian = ( 0, 2, 4, 5, 7, 9, 10) locrian = ( 0, 1, 3, 5, 6, 8, 10) modes = [] modes.append(major) modes.append(minor) modes.append(dorian) modes.append(phrygian) modes.append(lydian) modes.append(mixolydian) modes.append(locrian) mode_names = ("Major/Ionian", "Minor/Aeolian", "Dorian", "Phrygian", "Lydian", "Mixolydian", "Locrian") intervals = list(mixolydian) # intervals for Mixolydian by default
User Config: Mode Selection
The user now picks among the seven modes, with a preview played for each. The bottom right key confirms the selected mode and then moves on.
print("Pick the mode with top seven keys, then press bottom right key to enter:") print(". . . . . . .") print("o o o o o o o") print("o o o o o o .") while not mode_picked: for i in range(7): keys[i].update() if keys[i].fell: mode_choice = i print(mode_names[mode_choice], "mode") for j in range(7): intervals[j] = modes[i][j] # play the scale for k in range(7): midi.send(NoteOn(scale_root+intervals[k], 120)) note_index = note_numbers.index(scale_root+intervals[k]) print(note_names[note_index]) time.sleep(0.15) midi.send(NoteOff(scale_root+intervals[k], 0)) time.sleep(0.15) midi.send(NoteOn(scale_root+12, 120)) note_index = note_numbers.index(scale_root+12) print(note_names[note_index], "\n") time.sleep(0.15) midi.send(NoteOff(scale_root+12, 0)) time.sleep(0.15) keys[20].update() if keys[20].rose: print(mode_names[mode_choice], "mode picked.\n") mode_picked = True
Main Loop
In the main loop of the program the keys are checked for updates with the debouncer. If a key is pressed (fell) the associated noteOn message is sent, and when it it released (rose) the noteOff message is sent.
for i in range(num_keys): keys[i].update() if keys[i].fell: try: midi.send(NoteOn(midi_notes[i], 120)) note_index = note_numbers.index(midi_notes[i]) print("MIDI NoteOn:", note_names[note_index]) except ValueError: # deals w six key limit pass if keys[i].rose: try: midi.send(NoteOff(midi_notes[i], 0)) note_index = note_numbers.index(midi_notes[i]) print("MIDI NoteOff:", note_names[note_index]) except ValueError: pass
Panic Key Combo
If the five key pattern of the outer corners and the center key are pressed, the send_midi_panic()
function runs, turning off all notes.
# Key combo for MIDI panic # . o o o o o . # o o o . o o o # . o o o o o . if (not keys[0].value and not keys[6].value and not keys[10].value and not keys[14].value and not keys[20].value): send_midi_panic() time.sleep(1)
Page last edited March 08, 2024
Text editor powered by tinymce.