Ingeniería inversa · Web · Audio
JamJum JP-1 Studio: ingeniería inversa de un controlador MIDI
Decodifiqué el protocolo de configuración de un controlador MIDI sin documentación y construí una app web propia para editarlo: pads, knobs, botones, color RGB y efectos.
- Año
- 2026
- Estado
- Validado en hardware
- Rol
- Ingeniería inversa y desarrollo · proyecto personal
- Repositorio
- Repositorio en preparación
01 · Problema
El JamJum JP-1 (16 pads, 8 knobs, 6 botones de transporte, USB y Bluetooth) solo se configura con una app oficial limitada. No hay documentación del protocolo y el microcontrolador Holtek está bloqueado: su firmware no se puede leer.
02 · Enfoque
Capturé el tráfico USB-MIDI con USBPcap y Wireshark, comparé volcados bit a bit y probé hipótesis de checksum con fuerza bruta y RANSAC hasta reproducir, byte por byte, cada mensaje de la app oficial. Con eso escribí JamJum Studio, una app en Web MIDI que corre en Chrome.
03 · Resultado
Protocolo decodificado: la app lee el volcado completo de cada preset al conectar y escribe pads, knobs, botones de transporte y color RGB por pad. Los cuatro checksums están resueltos y verificados byte a byte contra la app oficial (en color, 384 de 384 bloques). Suma efectos RGB dinámicos, entre ellos uno reactivo de «ondas» al golpear los pads.
04 · Stack
- Web MIDI API
- JavaScript
- USBPcap
- Wireshark / tshark
- Python
- Node.js (pruebas)
El protocolo en una línea
Todo mensaje de configuración viaja como SysEx propietario: F0 78 32 <comando de 3 bytes> … F7. El contenido no son bytes «normales»: es un flujo de bits de 7 bits, LSB primero (de cada byte se toman sus 7 bits bajos). Por eso los campos no caen en fronteras de byte y hubo que mapearlos bit a bit.
| Comando | Dirección | Qué hace |
|---|---|---|
45 00 00 00 40 7F |
PC → JP-1 | Abre la sesión de edición (sin esto, el aparato ignora las escrituras) |
1D 0B … |
PC → JP-1 | Selecciona preset y dispara su volcado |
4D 0A 04 … |
PC → JP-1 | Escribe el banco de pads activo |
51 4A 02 … |
PC → JP-1 | Escribe el banco de knobs activo |
49 02 03 … |
PC → JP-1 | Escribe los botones de transporte |
45 3A 03 … |
PC → JP-1 | Color RGB de los 16 pads |
21 03 00 00 40 7F |
PC → JP-1 | Guarda en flash |
El checksum que no era CRC
Cada bloque de escritura lleva un checksum. La primera hipótesis —un CRC lineal— resultó falsa: venía de una prueba de solvencia con muy pocas ecuaciones. Los cuatro checksums son sumas modulares de la misma familia:
def checksum_color(canales: list[int], indice_pad: int) -> tuple[int, int]: """canales: los 48 valores de 8 bits (16 pads × G, R, B).""" s = sum(canales) + indice_pad # la pieza que faltaba: sumar el índice chk = (2720 - 32 * s) % 8192 return chk & 0x7F, (chk >> 7) & 0x7F # bytes 64 y 65
def checksum_pads(pads: list[dict]) -> tuple[int, int]: s = sum(p["canal"] + 16 * p["tipo"] + p["nota"] + p["min"] + p["max"] for p in pads) c = (510 - 2 * s) % 512 return c & 0x7F, (c >> 7) & 0x7F # bytes 76 y 77El de botones es (8175 − 32·Σ) mod 8192 y el de knobs (15456 − 32·Σ) mod 16384. Para el color, el modelo «uniforme» encajaba en las primeras capturas por puro alias; lo resolvió una captura nueva con índices y brillos variados, más una búsqueda del tipo (K + w·Σ) mod M sobre varias definiciones de Σ.
Lo que se ve en la app
- Auto-carga al conectar: deduce el preset y el banco activos del handshake y decodifica el volcado (incluido el color de los 3 bancos, que vive a partir del bit 2517).
- Editor por pad: tipo (nota, CC, programa), canal, rango de velocidad y color, con vista previa en vivo.
- Escritura como la app oficial: un solo bloque por cambio y guardado explícito en flash.
- Efectos RGB: arcoíris, persecución, pulso, degradado, reactivo a golpes y «ondas» (anillos que se expanden desde el pad golpeado), con parámetros por efecto.
Lo que me llevo
- Una hipótesis no vale hasta verificarla en el hardware. Los modelos que encajaban en la computadora fallaron en el aparato hasta que la validación fue byte a byte.
- Hay que probar la capa que usa la persona. Al principio validé el motor desde la consola y no la interfaz; la interfaz tenía otro error.
- Las capturas bien diseñadas ahorran pruebas. Una sola captura con datos variados resolvió lo que decenas de pruebas al azar no.