forked from viernullvier/neogrid
246 lines
6.8 KiB
Python
246 lines
6.8 KiB
Python
# MicroPython USB MIDI example
|
|
#
|
|
# This example demonstrates creating a custom MIDI device.
|
|
#
|
|
# To run this example:
|
|
#
|
|
# 1. Make sure `usb-device-midi` is installed via: mpremote mip install usb-device-midi
|
|
#
|
|
# 2. Run the example via: mpremote run midi_example.py
|
|
#
|
|
# 3. mpremote will exit with an error after the previous step, because when the
|
|
# example runs the existing USB device disconnects and then re-enumerates with
|
|
# the MIDI interface present. At this point, the example is running.
|
|
#
|
|
# 4. To see output from the example, re-connect: mpremote connect PORTNAME
|
|
#
|
|
#
|
|
# MIT license; Copyright (c) 2023-2024 Angus Gratton
|
|
import usb.device
|
|
from usb.device.midi import MIDIInterface
|
|
import time
|
|
|
|
from machine import Pin
|
|
|
|
import array, time
|
|
import rp2
|
|
|
|
NUM_COL = 4
|
|
NUM_ROW = 4
|
|
NUM_LEDS = NUM_COL * NUM_ROW
|
|
PIN_NUM = 16
|
|
brightness = 0.2
|
|
|
|
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
|
|
def ws2812():
|
|
T1 = 2
|
|
T2 = 5
|
|
T3 = 3
|
|
wrap_target()
|
|
label("bitloop")
|
|
out(x, 1) .side(1) [T3 - 1]
|
|
jmp(not_x, "do_zero") .side(0) [T1 - 1]
|
|
jmp("bitloop") .side(0) [T2 - 1]
|
|
label("do_zero")
|
|
nop() .side(1) [T2 - 1]
|
|
wrap()
|
|
|
|
def init_leds():
|
|
# Create the StateMachine with the ws2812 program, outputting on pin
|
|
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
|
|
|
|
# Start the StateMachine, it will wait for data on its FIFO.
|
|
sm.active(1)
|
|
|
|
# Display a pattern on the LEDs via an array of LED RGB values.
|
|
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
|
|
|
##########################################################################
|
|
def show_all():
|
|
dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
|
for i, c in enumerate(ar):
|
|
r = int(((c >> 8) & 0xFF) * brightness)
|
|
g = int(((c >> 16) & 0xFF) * brightness)
|
|
b = int((c & 0xFF) * brightness)
|
|
dimmer_ar[i] = (g<<16) + (r<<8) + b
|
|
sm.put(dimmer_ar, 8)
|
|
|
|
def set_led(i, color):
|
|
row = i // NUM_COL
|
|
col = i - row * NUM_COL
|
|
new_col = NUM_COL - col - 1
|
|
new_row = NUM_ROW - row - 1 if new_col % 2 == 0 else row
|
|
ar[new_col * NUM_ROW + new_row] = (color[1]<<16) + (color[0]<<8) + color[2]
|
|
|
|
return (set_led, show_all)
|
|
|
|
class MIDIExample(MIDIInterface):
|
|
# Very simple example event handler functions, showing how to receive note
|
|
# and control change messages sent from the host to the device.
|
|
#
|
|
# If you need to send MIDI data to the host, then it's fine to instantiate
|
|
# MIDIInterface class directly.
|
|
|
|
def on_open(self):
|
|
super().on_open()
|
|
print("Device opened by host")
|
|
|
|
def on_note_on(self, channel, pitch, vel):
|
|
print(f"RX Note On channel {channel} pitch {pitch} velocity {vel}")
|
|
|
|
def on_note_off(self, channel, pitch, vel):
|
|
print(f"RX Note Off channel {channel} pitch {pitch} velocity {vel}")
|
|
|
|
def on_control_change(self, channel, controller, value):
|
|
print(f"RX Control channel {channel} controller {controller} value {value}")
|
|
|
|
|
|
m = MIDIExample()
|
|
# Remove builtin_driver=True if you don't want the MicroPython serial REPL available.
|
|
usb.device.get().init(m, builtin_driver=True)
|
|
|
|
print("Waiting for USB host to configure the interface...")
|
|
|
|
while not m.is_open():
|
|
time.sleep_ms(100)
|
|
|
|
# TX constants
|
|
CHANNEL = 0
|
|
PITCH_C1 = 37
|
|
# CONTROLLER = 64
|
|
|
|
# control_val = 0
|
|
|
|
def init_btns():
|
|
row_pins = [
|
|
Pin(0, Pin.OUT),
|
|
Pin(1, Pin.OUT),
|
|
Pin(2, Pin.OUT),
|
|
Pin(3, Pin.OUT),
|
|
]
|
|
|
|
col_pins = [
|
|
Pin(32, Pin.IN, Pin.PULL_UP),
|
|
Pin(33, Pin.IN, Pin.PULL_UP),
|
|
Pin(34, Pin.IN, Pin.PULL_UP),
|
|
Pin(35, Pin.IN, Pin.PULL_UP),
|
|
]
|
|
|
|
pressed_keys = set()
|
|
|
|
# reset cols
|
|
for pin in row_pins:
|
|
pin.value(1)
|
|
|
|
def loop():
|
|
pressed_keys.clear()
|
|
for row, row_pin in enumerate(row_pins):
|
|
# set only active pin low, keep rest high
|
|
row_pin.value(0)
|
|
for col, col_pin in enumerate(col_pins):
|
|
key = (
|
|
(NUM_ROW - row - 1) +
|
|
(NUM_COL - col - 1) * NUM_ROW
|
|
)
|
|
if not col_pin.value():
|
|
pressed_keys.add(key)
|
|
row_pin.value(1)
|
|
return set(pressed_keys)
|
|
|
|
return loop
|
|
|
|
btn_loop = init_btns()
|
|
turned_on = set()
|
|
|
|
(set_led, show_all) = init_leds()
|
|
|
|
|
|
C_ACTIVE = (140, 60, 140)
|
|
C_INACTIVE = (0, 0, 0)
|
|
|
|
def shader(x, y, t):
|
|
st_x = float(x + 0.5) / NUM_COL
|
|
st_y = float(y + 0.5) / NUM_ROW
|
|
inv_t = 0.5 * (1.0 - t)
|
|
|
|
if st_x < t or st_y < t or (1.0 - st_x) < t or (1.0 - st_y) < t:
|
|
if t < 0.5:
|
|
return (
|
|
int(C_ACTIVE[0] * 0.5 * t),
|
|
int(C_ACTIVE[1] * 0.5 * t),
|
|
int(C_ACTIVE[2] * 0.5 * t),
|
|
)
|
|
if t >= 0.5:
|
|
return (
|
|
int(C_ACTIVE[0] * inv_t),
|
|
int(C_ACTIVE[1] * inv_t),
|
|
int(C_ACTIVE[2] * inv_t),
|
|
)
|
|
|
|
return C_INACTIVE
|
|
|
|
def play_animation(dur_s, fps):
|
|
cur_ms = float(0)
|
|
dur_ms = dur_s * 1000
|
|
interval_ms = int(1000 / fps)
|
|
processing_ms = 200
|
|
|
|
while cur_ms < dur_ms:
|
|
for r in range(0, NUM_ROW):
|
|
for c in range(0, NUM_COL):
|
|
t = cur_ms / dur_ms
|
|
color = shader(c, r, t)
|
|
set_led(c + r * NUM_COL, color)
|
|
show_all()
|
|
|
|
cur_ms += interval_ms + processing_ms
|
|
time.sleep_ms(interval_ms)
|
|
|
|
for l in range(NUM_LEDS):
|
|
set_led(l, C_INACTIVE)
|
|
show_all()
|
|
|
|
play_animation(3, 10)
|
|
|
|
print("Starting loop...")
|
|
while m.is_open():
|
|
time.sleep_ms(5)
|
|
# print(f"TX Note On channel {CHANNEL} pitch {PITCH_C1}")
|
|
# m.note_on(CHANNEL, PITCH_C1) # Velocity is an optional third argument
|
|
# time.sleep(0.5)
|
|
# print(f"TX Note Off channel {CHANNEL} pitch {PITCH_C1}")
|
|
# m.note_off(CHANNEL, PITCH_C1)
|
|
# time.sleep(1)
|
|
# print(f"TX Control channel {CHANNEL} controller {CONTROLLER} value {control_val}")
|
|
# m.control_change(CHANNEL, CONTROLLER, control_val)
|
|
# control_val += 1
|
|
# if control_val == 0x7F:
|
|
# control_val = 0
|
|
# time.sleep(1)
|
|
|
|
pressed = btn_loop()
|
|
|
|
to_turn_on = pressed.difference(turned_on)
|
|
to_turn_off = turned_on.difference(pressed)
|
|
|
|
if len(to_turn_on):
|
|
print("to_turn_on", to_turn_on)
|
|
|
|
if len(to_turn_off):
|
|
print("to_turn_off", to_turn_off)
|
|
|
|
|
|
for key in to_turn_on:
|
|
m.note_on(CHANNEL, PITCH_C1 + key - 1)
|
|
set_led(key, C_ACTIVE)
|
|
turned_on.add(key)
|
|
|
|
for key in to_turn_off:
|
|
m.note_off(CHANNEL, PITCH_C1 + key - 1)
|
|
turned_on.remove(key)
|
|
set_led(key, C_INACTIVE)
|
|
|
|
show_all()
|
|
|
|
print("USB host has reset device, example done.")
|
|
|