210 lines
5.8 KiB
Python
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)
|