Restores the April-2026 "uSEQ-Celium" functionality (browser → uSEQ
hardware + CV expander over USB Web Serial) as a first-class Manifold
Outputs backend, and re-vendors the RP2040 firmware into the repo.
- protocol v2 (uSEQ-CV): firmware/useq-celium/shared/protocol.h is the
single source of truth, mirrored by manifold/src/backends/useq-protocol.ts.
26-byte OUTPUT frame, 11×u16 CV (12-bit) + 3-gate bitfield + XOR; fixed
topology; host-agnostic so the MEMLNaut RP2350 can emit identical bytes.
Spec in docs/useq-celium/protocol.md.
- firmware/useq-celium/{main,expander}: PlatformIO RP2040 firmware rewritten
to v2 from the real April pin maps (expander I2C addr 0x10).
- UseqCvBackend (id cvgate): Web Serial connect/identify/disconnect, 100 Hz
stream, per-channel dead-zone, gate thresholding; modeled on midi-backend.
Per-output CvSpec (channel + gateThreshold) on MFParam; config UI in
OutputsBackendConfig + BackendAdvanced; new "CV / uSEQ" top-dock mode.
- bun-test for the protocol frame layout; MAP.md updated.
|
||
|---|---|---|
| .. | ||
| backend.ts | ||
| cv-backend.ts | ||
| index.ts | ||
| manager.ts | ||
| mapping.ts | ||
| midi-backend.ts | ||
| osc-backend.ts | ||
| osc-client.ts | ||
| particle-backend.ts | ||
| passthrough-backend.ts | ||
| presets.ts | ||
| README.md | ||
| useBackendManager.ts | ||
| useq-protocol.test.ts | ||
| useq-protocol.ts | ||
| vcv-backend.ts | ||
| webmidi.d.ts | ||
Output Backends (manifold/src/backends/)
Real output transports for the Manifold app. Exactly one backend is active at a
time, chosen by the dock Mode (Particle / MIDI / OSC / VCV / Built-in Synth /
Editor → BackendId). The BackendManager consumes the engine spine and
forwards each routed output vector to the active backend's send().
See docs/redesign/backends-spec.md for the authoritative design.
Files
| File | Role |
|---|---|
backend.ts |
The OutputBackend interface + BackendContext / OutputMapping / BackendStatus. |
mapping.ts |
Universal per-output baseline mapping (applyCurve, mapOutput) — shared by all backends, input-clamped. |
manager.ts |
BackendManager — single spine consumer; switches/teardowns backends; gates synth audio. |
midi-backend.ts |
WebMidiBackend — real Web MIDI CC out (per-output CC#/channel/range/name, throttled + dead-zone). |
osc-client.ts |
NispsOscClient — WS transport to the Deno OSC bridge (JSON protocol, auto-reconnect). |
osc-backend.ts |
OscBridgeBackend — OSC out over WS; per-output address path + physical range. |
vcv-backend.ts |
VcvBackend — drives + trains the VCV Rack NISPS module over the OSC↔WS bridge (streams /nisps/input, forwards the verdict loop to /nisps/feedback, receives /nisps/output + /nisps/state). |
passthrough-backend.ts |
No-op sink for synth (plays in-engine) / particles (rAF consumer) / editor. |
presets.ts |
Named per-backend output-config presets (localStorage, per-backend namespace). |
useBackendManager.ts |
Thin React binding: builds BackendContext from the store, switches Mode, surfaces status. |
Audio gating
The Built-in Synth plays inside the engine (EngineHost pushes params to the
worklet on every spine tick). So the manager mutes audio on every non-synth Mode
via engine.audio.setMuted(true) and unmutes on the synth Mode. This is the
documented gate — cleanly suppressing the worklet push would need an engine
change that this workstream doesn't make.
OSC bridge — the process must be running
Browsers cannot send UDP, so the OSC backend connects over WebSocket to the Deno bridge, which encodes OSC and forwards over UDP. Start the bridge locally or the OSC backend shows "bridge not running" (it auto-reconnects):
cd manifold/osc-bridge
deno run --allow-net bridge.ts
# --osc-host 127.0.0.1 --osc-port 9000 --ws-port 8765 --listen-port 9001
# or, without Deno:
node bridge.mjs
Default bridge URL: ws://localhost:8765 (configurable in the OSC config panel).
WS protocol (browser → bridge): { type:'params', payload:[[path,value],…] }.
VCV bridge — module and bridge must both be running
When Mode = VCV, the browser is authoritative in bridged mode: the
VcvBackend connects over the same Deno bridge and
- streams the current input vector continuously to
/nisps/input(the browser drives the module's inputs), - streams the routed per-output CV (uni 0–10 V / bipolar ±5 V), and
- forwards the verdict loop — thumbs-up/down + explore-and-place — to
/nisps/feedback({ op:'up'|'down'|'rand'|'clear', spread, input[], output[] }), so the module's embedded net trains in lock-step with the browser session.
It receives /nisps/output + /nisps/state back for status /
visualisation. The verdict forwarding is gated in BackendManager.forwardFeedback
— a no-op unless VCV is the active backend (otherwise the browser engine is the
learner). Both processes are required:
# 1) the VCV Rack NISPS module (vcv/) — default UDP listen port 7001
# 2) the Deno bridge, pointed at the module's UDP port:
cd manifold/osc-bridge
deno run --allow-net bridge.ts --osc-port 7001
Default VCV bridge URL: ws://localhost:8765 (configurable in the VCV config
panel). Until the WS connects the backend shows "bridge not running"; until the
module replies it shows "waiting for module…".