Drum rack and lights firmware examples #1
4 changed files with 235 additions and 0 deletions
Drum Rack example
commit
9c57c04a00
|
|
@ -16,6 +16,7 @@ Custom micropython build for the Olimex RP2350B-XL. Comes with some features ena
|
||||||
9. Run `make BOARD=OLIMEX_PICO2_XL submodules` to install dependencies
|
9. Run `make BOARD=OLIMEX_PICO2_XL submodules` to install dependencies
|
||||||
10. Run `make BOARD=OLIMEX_PICO2_XL clean` to remove previous build artefacts (if any)
|
10. Run `make BOARD=OLIMEX_PICO2_XL clean` to remove previous build artefacts (if any)
|
||||||
11. Run `make BOARD=OLIMEX_PICO2_XL` to build the firmware
|
11. Run `make BOARD=OLIMEX_PICO2_XL` to build the firmware
|
||||||
|
12. To rebuild: `make BOARD=OLIMEX_PICO2_XL clean && make BOARD=OLIMEX_PICO2_XL`
|
||||||
|
|
||||||
## flashing
|
## flashing
|
||||||
|
|
||||||
|
|
|
||||||
20
firmware/examples/nanogrid-drum-rack/README.md
Normal file
20
firmware/examples/nanogrid-drum-rack/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# MIDI Drum Rack example
|
||||||
|
|
||||||
|
Press to send a MIDI note in the range from C1 to D#2. Buttons are
|
||||||
|
ordered in the same way the pads are in Ableton Drum Rack: with the USB
|
||||||
|
port poiting from the player left-to-right, bottom-to-top.
|
||||||
|
|
||||||
|
|
||||||
|
## build
|
||||||
|
|
||||||
|
1. Make a micropython build with the libraries added:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $MICROPYTHON_PATH/ports/rp2
|
||||||
|
make BOARD=OLIMEX_PICO2_XL clean && make V=1 BOARD=OLIMEX_PICO2_XL FROZEN_MANIFEST={path-to-neogrid-project}/firmware/examples/nanogrid-drum-rack/manifest.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Flush the board with the resulting uf2 (see
|
||||||
|
[firmware/README.md](/firmware/README.md))
|
||||||
|
|
||||||
|
3. Copy the `main.py` to the board
|
||||||
210
firmware/examples/nanogrid-drum-rack/main.py
Normal file
210
firmware/examples/nanogrid-drum-rack/main.py
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
# for i in range(NUM_LEDS):
|
||||||
|
# set_led(i, CYAN)
|
||||||
|
# for j in range(NUM_LEDS):
|
||||||
|
# if i != j:
|
||||||
|
# set_led(j, BLACK)
|
||||||
|
# show_all()
|
||||||
|
# time.sleep(2)
|
||||||
|
#
|
||||||
|
|
||||||
|
CYAN = (0, 255, 255)
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
|
||||||
|
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, CYAN)
|
||||||
|
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, BLACK)
|
||||||
|
|
||||||
|
show_all()
|
||||||
|
|
||||||
|
print("USB host has reset device, example done.")
|
||||||
|
|
||||||
4
firmware/examples/nanogrid-drum-rack/manifest.py
Normal file
4
firmware/examples/nanogrid-drum-rack/manifest.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
metadata(version="0.1.0")
|
||||||
|
include("$(PORT_DIR)/boards/manifest.py")
|
||||||
|
require("usb-device")
|
||||||
|
require("usb-device-midi")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
This is a (minor) memory
leakallocation inefficiency as it will create a newsetobject on every loop iteration. It's fine to returnpressed_keysdirectly.Yes, makes sense to remove it.
Out of curiousity, shouldn't python take care of cleaning up that memory once the references to the new set go out of scope?just saw your correction :)Also, not sure if it's overoptimizing things, but we could use a single integer instead of a set here. Each bit representing an on/off state of a btn. We'd have to deal with bitwise operations, but the memory footprint would likely be smaller. Maybe the amount of cycles too?
E.g.