Skip to content

Instantly share code, notes, and snippets.

@sandyjmacdonald
Created January 21, 2021 22:27
Show Gist options
  • Save sandyjmacdonald/804dc737e7cb798d1b4fa34adc87e2d4 to your computer and use it in GitHub Desktop.
Save sandyjmacdonald/804dc737e7cb798d1b4fa34adc87e2d4 to your computer and use it in GitHub Desktop.
RGB MIDI controller example for Pimoroni RGB Keypad for Raspberry Pi Pico
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
@drtimoschrader
Copy link

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!

@CrippNipp
Copy link

Would anyone here have any info on how to add a few buttons and a slide pot to this code?
I made a small macropad (have not wired it yet) and I would like to add it in addition to the pico Rob keypad.
Any help would be super appreciated!!
IMG_4087

@dsalaj
Copy link

dsalaj commented May 19, 2024

Would anyone here have any info on how to add a few buttons and a slide pot to this code? I made a small macropad (have not wired it yet) and I would like to add it in addition to the pico Rob keypad. Any help would be super appreciated!!

I have just started playing with circuitpython midi libs and I have a similar setup: two buttons instead of 9, and a rotary encoder instead of a slide pot. But maybe you could adapt it.

import time
import board
import digitalio
import rotaryio

import usb_midi
import adafruit_midi
from adafruit_midi.control_change import ControlChange

midi_usb_c0 = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
midi_usb_c1 = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=1)
cc_number = 0

button = digitalio.DigitalInOut(board.GP1)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP
button_last = None

btns = [digitalio.DigitalInOut(board.GP4), digitalio.DigitalInOut(board.GP5)]
btns_last = [None, None]
btns_cc_values = [0, 0]
for b in btns:
    b.direction = digitalio.Direction.INPUT
    b.pull = digitalio.Pull.UP

encoder = rotaryio.IncrementalEncoder(board.GP2, board.GP3)
enc_last = None
enc_min = 0
enc_max = 127
encoder.position = 64

while True:
    enc = encoder.position
    btns_changed = any([bl != b.value for bl, b in zip(btns_last, btns)])
    if (enc_last != enc) or (button_last != button.value) or btns_changed:

        if enc > enc_max:
            enc = enc_max
        elif enc < enc_min:
            enc = enc_min
        encoder.position = enc
        
        # set current encoder value as button cc
        if (button_last != button.value) and (button.value is False):
            btns_cc_values[0] = enc
        
        # send discreete midi cc from button
        if btns_changed:
            midi_usb_c1.send(ControlChange(cc_number, btns_cc_values[0]))
            print("Button seinding midi cc", btns_cc_values[0])
        
        if (enc_last != enc):
            midi_usb_c0.send(ControlChange(cc_number, enc))
            print(
                "Button values", button.value,
                [btns[i].value for i in range(len(btns))],
                "Rotary encoder", enc)

        enc_last = enc
        button_last = button.value
        for i in range(len(btns)):
            btns_last[i] = btns[i].value
        

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment