Adafruit_Learning_System_Gu.../MIDI_Modal_Keyboard/code.py
2021-06-25 13:50:40 -07:00

210 lines
5.8 KiB
Python

# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Pico RP2040 Mechanical MIDI Modal Keyboard
# 7x3 mech keyboard
# Each key sends MIDI NoteOn / NoteOff message over USB
# Can be any scale/mode
# Key combo sends MIDI panic (see bottom section of code)
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)
def send_midi_panic():
print("All MIDI notes off")
for x in range(128):
midi.send(NoteOff(x, 0))
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = True
num_keys = 21
# list of pins to use (skipping GP15 on Pico because it's funky)
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 selection of the root note ----- #
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")
# lists of mode intervals relative to root
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
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
scale = [] # create the base scale
for i in range(7):
scale.append(scale_root + intervals[i])
midi_notes = [] # build the list with three octaves
for k in range(7):
midi_notes.append(scale[k]+24)
for l in range(7):
midi_notes.append(scale[l]+12)
for m in range(7):
midi_notes.append(scale[m])
led.value = False
print("Ready, set, play!")
while True:
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
# 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)