Marv's Blog


Roland K-25m USB MIDI via Raspberry Pi Pico

Published: March 7, 2026

I was missing a good small keyboard for playing synths and I came across the Roland K-25m. It's meant to be used with the Roland boutique series and connects via a 16 pin IDC connector (the same that Eurorack also uses). I found some documentation about it here and decided to pick one up and wire it to a Raspberry Pi Pico:

Why not get a cheap USB keyboard? This way I can run my own code and add a sequencer later.

For some synths, I can also implement bug fixes (the MFB Nanozwerg triggers on note on and off in some cases).


Looking directly at the cable connector, I counted pins from top to bottom, right to left.

                 notch
  15   13   11    9    7    5    3    1
   ○    ○    ○    ○    ○    ○    ○    ○
   ○    ○    ○    ○    ○    ○    ○    ○
  16   14   12   10    8    6    4    2

import time
import board
import digitalio
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

# ── Config ───────────────────────────────────────────────
BASE_NOTE = 48       # C3
VEL_MIN_MS = 2.0     # fastest strike (ms) -> vel 127
VEL_MAX_MS = 60.0    # slowest strike (ms) -> vel 1
SCAN_DELAY = 0.0003  # 300us row settle time
TIMEOUT_S = 0.08     # 80ms max wait for second contact

# ── Pins ─────────────────────────────────────────────────
colA_pins = [board.GP3, board.GP5, board.GP7, board.GP9]
colB_pins = [board.GP2, board.GP4, board.GP6, board.GP8]
row_pins = [
    board.GP22, board.GP21, board.GP20, board.GP15,
    board.GP14, board.GP12, board.GP11, board.GP10,
]

NUM_ROWS = 8
NUM_COLS = 4

# ── Init pins ────────────────────────────────────────────
colA = []
colB = []
rows = []

for p in colA_pins:
    pin = digitalio.DigitalInOut(p)
    pin.direction = digitalio.Direction.INPUT
    pin.pull = digitalio.Pull.UP
    colA.append(pin)

for p in colB_pins:
    pin = digitalio.DigitalInOut(p)
    pin.direction = digitalio.Direction.INPUT
    pin.pull = digitalio.Pull.UP
    colB.append(pin)

for p in row_pins:
    pin = digitalio.DigitalInOut(p)
    pin.direction = digitalio.Direction.OUTPUT
    pin.value = True
    rows.append(pin)

# ── MIDI ─────────────────────────────────────────────────
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)

# ── Key state ────────────────────────────────────────────
# States: 0=idle, 1=B pressed (waiting for A), 2=note on
KEY_IDLE = 0
KEY_WAIT_A = 1
KEY_ON = 2

state = [KEY_IDLE] * 32   # max 8*4=32 slots
b_time = [0.0] * 32       # when B was first detected
note_vel = [0] * 32       # velocity sent with note_on

def key_index(row, col):
    return col * 8 + row

def key_to_note(row, col):
    return BASE_NOTE + col * 8 + (row ^ 1)

def calc_velocity(delta_s):
    ms = delta_s * 1000.0
    if ms <= VEL_MIN_MS:
        return 127
    if ms >= VEL_MAX_MS:
        return 1
    # linear map
    frac = (ms - VEL_MIN_MS) / (VEL_MAX_MS - VEL_MIN_MS)
    return int(127 - frac * 126)

# ── Main loop ────────────────────────────────────────────
print("K-25m MIDI ready. Base note:", BASE_NOTE)

while True:
    now = time.monotonic()

    for r in range(NUM_ROWS):
        rows[r].value = False
        time.sleep(SCAN_DELAY)

        for c in range(NUM_COLS):
            a = not colA[c].value
            b = not colB[c].value
            ki = key_index(r, c)
            s = state[ki]
            note = key_to_note(r, c)

            if s == KEY_IDLE:
                if b:
                    # B contact closed first (normal)
                    if a:
                        # both at once = very fast strike
                        midi.send(NoteOn(note, 127))
                        note_vel[ki] = 127
                        state[ki] = KEY_ON
                    else:
                        # B only, start timing
                        b_time[ki] = now
                        state[ki] = KEY_WAIT_A
                elif a:
                    # A first (rare but handle it)
                    b_time[ki] = now
                    state[ki] = KEY_WAIT_A

            elif s == KEY_WAIT_A:
                if a and b:
                    # second contact arrived
                    delta = now - b_time[ki]
                    vel = calc_velocity(delta)
                    midi.send(NoteOn(note, vel))
                    note_vel[ki] = vel
                    state[ki] = KEY_ON
                elif now - b_time[ki] > TIMEOUT_S:
                    # timeout, send with minimum velocity
                    midi.send(NoteOn(note, 1))
                    note_vel[ki] = 1
                    state[ki] = KEY_ON

            elif s == KEY_ON:
                if not a and not b:
                    # fully released
                    midi.send(NoteOff(note, 0))
                    state[ki] = KEY_IDLE

        rows[r].value = True

← Back to Blog