Bubba Machine

Bubba Machine (FM-1 "K.O. Edition") — Especificación Técnica V3.1

V3.1 (implementación 0.1.0): la sección 12 lista lo que se ajustó al escribir el código. Donde la sección 12 y el resto del documento no coinciden, manda la sección 12. Qué cambió respecto a V2: V2 se escribió con un contexto de hardware desactualizado (OLED SSD1306, 25+2 teclas, 4 encoders, ADC de 12 bits, flash "de sobra"). V3 toma los datos del hardware real, sacados del código fuente público de Felucca (firmware custom para el FM-1). Cada dato lleva su fuente en la sección 11. Lo que nadie ha medido todavía está marcado como VERIFICAR. Las decisiones que el autor aún no ha tomado están en la sección 10.


0. Directivas para la IA que escriba el código

Actúa como arquitecto principal de software embebido y DSP. Escribes C99 para el M-VAVE FM-1.

  1. Fuente de verdad: la sección 1 es el hardware. Si algo no está ahí, no lo supongas: escribe // TODO(HW-VERIFY): ... y sigue.
  2. Decisiones abiertas: si una función depende de algo de la sección 10, implementa la opción marcada como por defecto detrás de un #define y deja // TODO(DECISION-n).
  3. Sin sistema de archivos: se escribe en direcciones raw de la flash, solo dentro de las particiones de la sección 4.4.
  4. Sin memoria dinámica en la ISR de audio, en el secuenciador ni en la UI. Todo se reserva estáticamente al arrancar.
  5. Sin código bloqueante en la UI (while de espera, delay()). Única excepción: las rutinas de escritura en flash (4.5), que por hardware bloquean.
  6. No inventes funciones que no estén en este documento.
  7. Diseño propio, no derivado de Felucca (DECISIÓN-1 resuelta): está prohibido copiar, traducir o parafrasear código de Felucca (GPL-3.0-only). De Felucca solo se toman hechos de hardware (pines, direcciones de registro, tiempos, IDs de la matriz), que no tienen copyright. Para drivers a nivel de registro, la referencia es el SDK de JieLi AC79 (Apache-2.0) y los datasheets (ST7789V, 74HC595, flash NOR). La arquitectura, los nombres, las estructuras de datos y la UI son propios de este documento.

1. Hardware real del M-VAVE FM-1

1.1 SoC, memoria y arranque

ElementoValorNotas
SoCJieLi familia AC79 (AC791N según la ingeniería inversa). En el SDK aparece como cpu/wl82Núcleo pi32v2
ToolchainJieLi clang 4.0.1 para pi32v2No es pi32v2-gcc como decía V2
SDKAC79NN_SDK_V1.2.1_2023-12-13 (Apache-2.0)Solo se usan 3 archivos de boot para empaquetar
Reloj del CPUVERIFICAR
RAM96 KiB de .data/.bss (+ 24 KiB de RAM ejecutable para rutinas de flash), según el mapa de memoria publicado de los firmwares customPor eso el sampler es de 1.1 s (5.2)
Flash1 MiB NOR SPI, ejecución XIP. Borrado por sector de 4 KiB (hasta ~400 ms), programación por página de 256 B (hasta ~3 ms)Comparte espacio con el firmware
Formato de actualización.fwsc (app + loader + uboot.boot, cfg_tool.bin, eq_cfg_hw.bin del SDK)Identidad de paquete FM-1_910
InstalaciónPor USB (el instalador de Felucca usa USB-MIDI)
RecuperaciónModo boot del chip: USB WL80UBOOT / mass storage 4C4A:8057. Recuperación completa con FM-1 Transporter (RP2040)Respaldar la flash antes de la primera prueba

1.2 Pantalla

ElementoValor
PanelTFT a color, controlador ST7789V, 240×240, RGB565
BusSPI1 a 12 MHz (BAUD 4), con DMA. Pines: PC9 CLK, PC10 MOSI, PC7 CS, PC8 D/C
BacklightPA2, activo en bajo. Esa línea también habilita botones y teclas: no apagarla para ahorrar energía si el teclado debe seguir vivo
FramebufferNo hay (240×240×2 = 115 200 B). Se dibuja por franjas o rectángulos desde buffers en RAM
Coste de un frame completo115 200 B a 12 MHz ≈ 77 ms, o sea ≈ 13 FPS como máximo redibujando todo

1.3 Controles (matriz de teclado)

Todo pasa por una matriz con diodos de 11 columnas × 6 filas, manejada por 2× 74HC595 y escaneada desde la ISR de TIMER5 a ~10 kHz (una columna por tick). Antirrebote: 2 frames para presionar y 8 para soltar. Sin ghosting.

GrupoCantidadDetalle
Teclas de piano27, IDs 14–40F3 a G5: 16 blancas + 11 negras. Las negras van en grupos 3-2-3-2-1 (F#3 G#3 A#3 · C#4 D#4 · F#4 G#4 A#4 · C#5 D#5 · F#5)
Botones14, IDs 0–13Serigrafía: OCT−, OCT+, FX, SCL, ENV, LFO, EDIT, GLO, HOME, SAVE, ARP, SEQ, PLAY, REC. Solo los IDs de PLAY y REC están medidos; el resto debe calibrarse. No hay botón SHIFT dedicado
Encoders7, con detentSerigrafía: SELECT, ALGORITHM, PRESETS, más KNOB 1–4. Se leen dentro de la matriz (no por interrupción GPIO), con decodificador de cuadratura por tabla: 1 detent = 4 transiciones, hasta ~300 clics/s. No tienen pulsador (no hay IDs libres en la matriz)
Pot MASTER1PB6, ADC canal 4, 10 bits (0–1023), sin filtrado por hardware
Batería (lectura)ADCPB1, ADC canal 3. PB1 a nivel bajo durante 8 s reinicia el chip: configurarlo solo como entrada analógica
LEDsEn botones y teclasMultiplexados en las columnas de la matriz (líneas PH6, PH9, PA9, PA10). Brillo por tiempo de encendido (apagado / tenue / encendido / respiración). Colores: VERIFICAR

1.4 Audio y conectividad

ElementoValor
SalidaCodec externo por I2S (ALNK0), DMA de doble buffer, estéreo de 24 bits en palabras int32
Frecuencia de muestreo≈ 44 117.6 Hz (24 MHz / 544)
BloqueFelucca usa 128 frames por medio buffer (2.9 ms) y renderiza dentro de la ISR de ALNK0
AltavozIntegrado; se silencia por hardware al conectar el jack
Jack3.5 mm TRS estéreo
USB-CMIDI in/out class-compliant; audio USB (Felucca envía la mezcla al computador a 44.1/48 kHz); consola serie opcional
MIDI TRSSolo entrada (UART). No hay salida MIDI TRS
BluetoothEl firmware de fábrica tiene MIDI y audio BT. Ningún firmware custom lo ha demostrado (ver DECISIÓN-4)
Batería2000 mAh

2. Arquitectura de software

typedef struct SoundEngine SoundEngine;
struct SoundEngine {
    void (*init)(SoundEngine *self);
    void (*note_on)(SoundEngine *self, uint8_t note, uint8_t vel);
    void (*note_off)(SoundEngine *self, uint8_t note);
    void (*set_macro)(SoundEngine *self, uint8_t param_id, uint8_t val);
    bool (*process)(SoundEngine *self, int32_t *buf, uint16_t frames);
};

3. Motor de audio

3.1 Bus y frecuencia

3.2 Interpolación de muestras

3.3 Salida maestra (orden de la cadena)

suma 32-bit → soft-clipper (DRIVE) → volumen MASTER (pot) → DAC

3.4 Polifonía y robo de voces


4. Secuenciador y almacenamiento

4.1 StepEvent: 8 bytes (cambio V3: antes 7)

ByteCampoRango / codificación
0note0–127; 0xFF = evento vacío
1vel1–127
2lenbits 0–6 = largo en pasos (1–127); bit 7 = TIE/GLIDE
3prob0–100 (%)
4p_cutoff0–254; 0xFF = valor de la pista
5p_resídem
6p_decayídem
7p_posposición de wavetable / inicio de sample; ídem

4.2 Formato de patrón en flash (disperso)

(Cambio V3: el formato denso ocuparía 12 KiB por patrón, y con 1 MiB de flash no caben.)

Header (16 B): magic 'KOP3' | slot u8 | rsv u8 | len u16 | seq u32 | crc32 u32
Meta   (64 B): bpm, swing, largo, delay, sidechain, kit, waves...
Pistas (6 × 32 B): parámetros por pista (macros, nivel, pan, envío, duck, motor)
Eventos: por pista → count u16 + count × [step u8 + StepEvent 8 B]

4.3 Reloj

4.4 Mapa de flash (1 MiB)

Tomado del mapa que usa Felucca. VERIFICAR el tamaño final del binario antes de fijarlo.

RangoTamañoUsoRegla
0x000000–0x096FFF~604 KiBBoot + firmware + ROM de sonidos (drums, AKWF)Solo lo escribe el instalador
0x097000–0x0DFFFF292 KiBStore de usuario (log de patrones, song, samples guardados)Escribible
0x0E0000–0x0E4FFF20 KiBStaging del loader OTANo tocar si se usa ese loader
0x0E5000–0x0E7FFF12 KiBReservadoNo tocar en V3
0x0E8000–0x0FBFFF80 KiBRespaldo VM del firmware de fábricaNunca escribir
0x0FC000–0x0FEFFF12 KiBGlobales (A/B)Escribible
0x0FF000–0x0FFFFF4 KiBDesconocidoVERIFICAR; no tocar

Presupuesto propuesto de la ROM de sonidos: Drums ≤ 192 KiB (16 bits a 22 kHz, o IMA-ADPCM 4:1) · AKWF: 64 ondas × 256 muestras × 16 bits = 32 KiB (≈ 64 KiB con mipmaps) · Código ≤ ~340 KiB.

Store de usuario: 292 KiB / 8 KiB = 36 slots. Por defecto: 16 patrones + 1 song, y el resto queda como espacio libre para el wear-leveling (ver DECISIÓN-3 sobre guardar samples).

4.5 Escritura en flash (wear-leveling y seguridad)


5. Pistas

PistaMotorDetalle
T1 DrumsROMplerHits CC0 (estilo 808/909/Linn) desde la ROM. Choke: HH cerrado corta HH abierto. 4 voces reservadas. Interpolación LINEAL por defecto
T2 SamplerRAMVer 5.2
T3 BassWavetable mono1 voz, glide cuando el evento tiene TIE, SVF paso-bajo de 2 polos
T4–T6 SynthWavetable polyPool compartido; ondas AKWF remuestreadas a 256 muestras; SVF de 2 polos

Sidechain: cada NoteOn de T1 dispara una envolvente de atenuación (attack 0, release ajustable) que multiplica la ganancia de T3–T6. Profundidad por pista (0–100 %). Se calcula por bloque con interpolación lineal (es barato, pero no "gratis").

5.2 Sampler


6. UI/UX

6.1 Entrada asíncrona

6.2 Gramática de gestos

GestoRegla
TAPSe suelta antes de 300 ms → se ejecuta al soltar: abre la página del botón
HOLDPasan 300 ms sin soltar → se ejecuta al vencer el timer (no al soltar): modo live momentáneo. Al soltar, vuelve a la vista anterior
COMBOOtra tecla o encoder antes de los 300 ms → se cancela el TAP y se entra al HOLD de inmediato
InstantáneosPLAY, teclas de nota, selección de pista y GLO (como modificador) actúan al presionar, sin esperar umbral
Hold largoSAVE = 1 000 ms (porque escribe en flash)

Umbrales en config.h: UI_HOLD_MS 300, UI_SAVE_HOLD_MS 1000, UI_OVERLAY_MS 1500.

6.3 Pantalla 240×240

y=0   ┌────────────────────────────┐
      │ ESTADO (24 px, siempre fijo)│  ▶/■ · patrón · compás.tiempo · pista (color) · BPM · batería
y=24  ├────────────────────────────┤
      │ CONTEXTO (176 px)          │  vista del modo actual; los overlays solo tapan esta zona
y=200 ├────────────────────────────┤
      │ K1 │ K2 │ K3 │ K4  (40 px) │  leyenda de KNOB 1–4, en el mismo orden físico
y=240 └────────────────────────────┘

6.4 LEDs (nuevo en V3: el hardware los tiene)

6.5 Mapa de controles

Botones (GLO hace de modificador SHIFT):

BotónTAPHOLDGLO +
PLAYPlay/Stop (al presionar)—Reinicia desde el paso 1
RECGrabación en vivo on/off——
SEQVista de pasosBanco de patrones en vivo (blanca = encolar)Song mode
HOMEVista general de las 6 pistas + mixerMute en vivo (blancas 1–6) y solo (9–14)—
EDITParámetros del motor de la pista (páginas con SELECT)——
ENVEnvolvente de la pista——
LFODECISIÓN-5
FXPágina de FX globales (delay, sidechain)Punch-in FX: blancas = 16 efectos—
SCLPágina de escala y acordesLive Key-Map: tocar escala y acordes—
ARPPágina del arpegiadorArpegiador en vivo mientras se mantiene—
SAVEMenú de guardado1 s = guardado rápido (solo en STOP)—
GLOAjustes globales (reloj, sync, MIDI, fuente del sampler)Modificador—
OCT− / OCT+Octava − / + · en SEQ: página − / +—Transponer la pista ± semitono

Encoders:

EncoderFunciónGLO +
KNOB 1–4Macros de la pista: CUTOFF · RESO · DECAY · POS (son los mismos 4 P-Locks)BPM · SWING · LARGO · DRIVE
SELECTNavegar páginas o filas de menú—
ALGORITHMSelección de pista 1–6 (la pantalla muestra el nombre real)Motor o kit de la pista
PRESETSPatrón A01–A16 (se encola al final si está sonando)Onda AKWF / kit
MASTER (pot)Volumen final, no se guarda—

Teclas en SEQ: blancas 1–16 = pasos de la página · mantener paso + KNOB = P-Lock · negras 1–6 = pista (GLO + negra = mute) · negra 7 = COPY · 8 = PASTE · 9 = CLEAR · 10 = FILL (mantener) · 11 = reservada. Teclas fuera de SEQ: tocan la pista seleccionada (en T1, cada tecla es un sonido del kit). SCL live: blancas = notas de la escala · negras 1–7 = acordes diatónicos · negras 8–11 = +7, +9, SUS2, SUS4.

Portapapeles: struct { uint8_t type; union { StepEvent ev[4]; TrackData trk; PatternData pat; } }. No usar void*. Undo de un nivel para la última edición (paso, CLEAR o PASTE).


7. Conectividad


8. Herramientas

  1. Packer (Python): WAV → 22 050 Hz mono (16 bits o IMA-ADPCM) con filtro antialiasing. AKWF (600 muestras) → 256 muestras + mipmaps por octava. Genera rom_assets.c (const) + índice. Comprueba que se respete el presupuesto de 4.4.
  2. CMake con 3 targets: sim (PC, miniaudio + SDL2), wasm (emcc) y fm1 (JieLi clang 4.0.1 pi32v2 + empaquetado .fwsc).
  3. Companion web: Web MIDI + SysEx para volcar o cargar patrones, con 7 bits por byte (8 bytes → 7 de datos).
  4. Instalador propio: escrito desde cero (no se reutiliza fm1_install.py). Instala por USB-MIDI y, antes de instalar, verifica que existe un respaldo completo de la flash. Mientras no exista, la primera prueba se hace con un método de recuperación ya probado a mano (modo boot WL80UBOOT o el Transporter RP2040). Usarlos como herramientas externas no copia código dentro del firmware.

9. Seguridad del dispositivo


10. Decisiones abiertas

#DecisiónOpcionesPor defecto
1Licencia y base de códigoRESUELTA: firmware propio desde cero, sin código de Felucca. La licencia del proyecto la elige el autor—
1bLicencia del proyectoRESUELTA (2026-10-10): GPL-3.0-or-later. Compatible con usar el SDK de JieLi (Apache-2.0): sus binarios se distribuyen con su licencia—
2Frecuencia de los motoresFs completa (44.1k) vs Fs/2 (22k, lo-fi, mitad de CPU)Fs/2
3¿Guardar samples?Volátil · 1 slot de ~2 s (≈ 88 KiB, quitando 11 slots de patrón)Volátil
4Fuente del samplerResample → USB audio → BT (cada fase depende de la anterior)Resample
5Botón LFOLFO por pista (función nueva) · asignarlo al mixer · reservadoReservado (pantalla "RESERVADO" en 0.1.0)
6Polifonía final12 si el CPU lo permite, si no 8Medir

11. Fuentes


12. Ajustes hechos al implementar (V3.1, firmware 0.1.0)

TemaV3ImplementadoPor qué
RAMVERIFICAR96 KiB; la app completa usa ~81 KiB estáticos + ~6 KiB de gráficosVer docs/MEMORIA.md
Sampler16 bits, RAM libre8 bits, 24 KiB = 1.11 s, volátilCabe en la RAM
Delaysin detalleMono, 16 bits, a Fs/4 efectivos (escribe una de cada dos muestras del motor): 8192 muestras = 0.74 s. Tiempos 1/16, 1/8, 3/16, 1/4Repeticiones oscuras, carácter lo-fi y poca RAM
Buffer de punch-insin detalle8 KiB mu-law = 0.37 s; si 1/4 no cabe, usa la mitadRAM
Punch-in FX (16)STUT, REV, TAPE STOP/START...STUT 1/4·1/8·1/16·1/32, REV 1/8·1/4, TAPE STOP, GATE 1/16, LPF SWEEP, HPF SWEEP, BITCRUSH, SR DOWN, DLY THROW, ECHO FREEZE, KILL LOW, KILL HIGHTAPE START necesita leer el futuro: se cambió por GATE
Nombres de las 4 macrosCUTOFF RESO DECAY POSSynth: CUTOFF RESO DECAY WAVE · Drums/Sampler: TONE TUNE DECAY STARTMismos 4 bytes de P-Lock, nombre según el motor
HOMEvista generalVista general + mixer: K1 NIVEL, K2 PAN, K3 ENVÍO, K4 DUCKEl mixer necesita un lugar sin botón propio
ENVenvolventePágina 1: ATTACK DECAY SUSTAIN RELEASE · ENV otra vez = página 2: ENV>FILTRO, GLIDERegla "el mismo botón pasa páginas"
FX (tap)FX globalesK1 tiempo de delay, K2 feedback, K3 mix, K4 release del sidechain—
SCLescala y acordesK1 tónica, K2 escala (9), K3 acorde base (tríada/+7/+9/SUS2/SUS4), K4 LOCK (teclado siempre en escala)—
ARParpegiadorK1 modo (OFF/UP/DOWN/UP-DN/RAND), K2 rate (1/8 1/16 1/32), K3 octavas (1-3), K4 gateSolo pistas synth
Negra 11 en SEQreservadaUNDO (un nivel)El undo existía pero no tenía tecla
GLO + ALGORITHMmotor o kitT1: kit (KO-808 / KO-909) · T2: interpolación (LINEAL/HERMITE) · T3-T6: banco de ondas—
GLO + PRESETSonda / kitSin usoQuedó cubierto por GLO + ALGORITHM
Paso mantenidoP-Lock con KNOBsAdemás: SELECT = nota/sonido, ALGORITHM = probabilidad (pasos de 5 %), PRESETS = largo 1-16 o TIELos 7 encoders tienen trabajo
Formato dispersopaso u8Byte de posición = paso (bits 0-5) + slot (bits 6-7)Conservar el slot de cada evento al recargar
Ganancia del bussin detalleNivel 100 = -12 dB de headroom; DRIVE por defecto 25 (≈ x1.35)Medido con la demo: satura en ~0.5 % de los bloques
Primer arranquesin detalleSi la flash está vacía, se carga una demo (Am-F-C-G, 112 BPM)Para que PLAY suene de inmediato
Sobremuestreo x2ZOH (escalón)Interpolación linealEl ZOH dejaba -22 dB de imágenes arriba de 11 kHz; lineal baja a -27 dB por casi el mismo costo
Botones sin capaTAP al soltar antes de 300 msEDIT, ENV y LFO hacen TAP al soltar aunque se hayan mantenido (salvo combo)Más tolerante, sin función que perder

13. Versión 0.2

El diseño de la 0.2 está en DISENO_0.2.md y reemplaza a las secciones 4.1, 5, 6.2 y 6.5 donde no coinciden:

Esta página se genera desde docs/SPEC_V3.md. ¿Ves un error? Edita el archivo y manda un pull request.