import time | |
import board | |
import busio | |
import usb_midi | |
import adafruit_midi | |
from adafruit_midi.note_off import NoteOff | |
from adafruit_midi.note_on import NoteOn | |
from adafruit_bus_device.i2c_device import I2CDevice | |
import adafruit_dotstar | |
from digitalio import DigitalInOut, Direction, Pull | |
# RGB MIDI controller example for Pimoroni RGB Keypad for Raspberry Pi Pico | |
# Prerequisites | |
# | |
# Requires Adafruit CircuitPython: https://learn.adafruit.com/getting-started-with-raspberry-pi-pico-circuitpython | |
# | |
# Also requires the following CircuitPython libs: adafruit_midi, adafruit_bus_device, adafruit_dotstar | |
# (drop them into the lib folder) | |
# | |
# Save this code in code.py on your Raspberry Pi Pico CIRCUITPY drive | |
# Pull CS pin low to enable level shifter | |
cs = DigitalInOut(board.GP17) | |
cs.direction = Direction.OUTPUT | |
cs.value = 0 | |
# Set up APA102 pixels | |
num_pixels = 16 | |
pixels = adafruit_dotstar.DotStar(board.GP18, board.GP19, num_pixels, brightness=0.1, auto_write=True) | |
# Set up I2C for IO expander (addr: 0x20) | |
i2c = busio.I2C(board.GP5, board.GP4) | |
device = I2CDevice(i2c, 0x20) | |
# Set USB MIDI up on channel 0 | |
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) | |
# Function to map 0-255 to position on colour wheel | |
def colourwheel(pos): | |
if pos < 0 or pos > 255: | |
return (0, 0, 0) | |
if pos < 85: | |
return (255 - pos * 3, pos * 3, 0) | |
if pos < 170: | |
pos -= 85 | |
return (0, 255 - pos * 3, pos * 3) | |
pos -= 170 | |
return (pos * 3, 0, 255 - pos * 3) | |
# List to store the button states | |
held = [0] * 16 | |
# Keep reading button states, setting pixels, sending notes | |
while True: | |
with device: | |
# Read from IO expander, 2 bytes (8 bits) correspond to the 16 buttons | |
device.write(bytes([0x0])) | |
result = bytearray(2) | |
device.readinto(result) | |
b = result[0] | result[1] << 8 | |
# Loop through the buttons | |
for i in range(16): | |
if not (1 << i) & b: # Pressed state | |
pixels[i] = colourwheel(i * 16) # Map pixel index to 0-255 range | |
if not held[i]: | |
midi.send(NoteOn(36 + i, 100)) # If not already held, then send note | |
held[i] = 1 | |
else: # Released state | |
pixels[i] = (0, 0, 0) # Turn pixel off | |
midi.send(NoteOff(32 + i, 0)) # If not held, send note off | |
held[i] = 0 # Set held state to off |
Can you see any MIDI notes being received in Ableton (a little yellow square should blink on the Ableton channel)?
No, I wasn't able to see the notes registering but when I tried again today, it all worked magically. I honestly don't know why. This time I only connected the keypad after opening Ableton but when I closed the program and opened again without disconnecting the keypad, it still worked. So I will just have to monitor myself how it will do in the future.
One other question I have is about the layout of keys. On my screenshot, the drum rack layout is inverted with the top row representing the bottom row on my keypad etc. Can I just switch out something in the code to reverse the layout so it displays accurately?
I think (have not tested) you should be able to say midi.send(NoteOn(52 - i, 100))
and midi.send(NoteOff(52 - i, 0))
(lines 72 and 76) and that'll invert the notes played.
This makes it more strange. I guess I didn't mean inverted, I meant mirrored. In my screenshot, if I hit the key for Cowbell I actually play the Bassdrum and if I hit the key for Crash I actually play Rimshot. So Top left corner becomes bottom left corner. So the direction from left to right per row is normal but top row is switched with bottom row and second to top row switches with second to bottom row on the keypad. Very odd.
If you want to use it with Ableton, u need to map the buttons for mirrored movement.
Use the next line before reading the button states:
button_map = [12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3]
then change
midi.send(NoteOn(36 + i, 100))
and midi.send(NoteOff(36 + i, 0))
to
midi.send(NoteOn(36 + button_map[i], 100))
and midi.send(NoteOff(36 + button_map[i], 0))
I'm trying to run this code and looking at my midi monitor the board is streaming midi note off events continuously. . Is this normal? Seems like the midi monitor is stuck on in Ableton. My normal midi controllers dont appear to be streaming not off events when looking at them via a midi monitor. Is this just a limitation with an implementation or am I doing something wrong?
Also, in the posted code, shouldn't
midi.send(NoteOff(32 + i, 0)) # If not held, send note off
Be set to 36 as well? Change the code so both are 36 and seems to be acting better. Note offs still streaming but now held notes appear to work.
@joeynuggetz You’re quite correct about the note numbers, I think, although I don’t have mine to hand right now to test it out.
As to why it’s sending notes constantly, could one of the buttons be pushed down, and hence stuck on, by the retaining plate? Can you remove that plate (if it’s fitted) and try again?
@joeynuggetz You’re quite correct about the note numbers, I think, although I don’t have mine to hand right now to test it out.
As to why it’s sending notes constantly, could one of the buttons be pushed down, and hence stuck on, by the retaining plate? Can you remove that plate (if it’s fitted) and try again?
I dont believe its an issue with stuck keys. I've gotten the code to work correctly and midi appears to be functioning ok except that the midi indicator stays lit. If I press and release each key, samples/notes play and then release/sustain properly so not sure what's going on.
Hey @joeynuggetz! Finally had a chance to plug my keypad in and try this out. I've realised what's causing this. It's because if the keys aren't pressed then logic dictates that they are released, so the else clause sends out the note off messages, and this will happen continuously. It's not really a problem, I think, as the MIDI bus should be capable of handling it.
Hi @sandyjmacdonald
I have tested your code with the modifications of @kenimaister and it does work fine with Ableton Live 11 lite. So I would like to thank you for your good work!
After reading your last post I deleted the NoteOff command in Line 76 and the program is still working fine with Ableton.
I also tested it with two other DAWs without any problems.
Hi @kenimaister thank you for your comment, it fixed my problem of the mirrored (inverted?) keys! Now I can enjoy using the keypad with Ableton without having to do some additional mental gymnastics! And also thank you again to @sandyjmacdonald for the code! It has gotten me into playing around with making some fun tracks and I have also gotten addtional MIDI equipment to do more fun stuff! You're all wonderful!
Oh, thank you so much for the quick reply! I have Ableton here and I can choose CircuitPython as input device, but it still won't work. It doesn't register key presses. I see there is a midi map but I can't seem to register key presses or edit them there either. What am I missing? I've attached a screenshot. I am trying to map the keys to the drum rack here.