2026-06-28 03:28:45 +02:00
|
|
|
|
# Output Backends (`manifold/src/backends/`)
|
|
|
|
|
|
|
|
|
|
|
|
Real output transports for the Manifold app. Exactly one backend is *active* at a
|
2026-06-28 04:28:45 +02:00
|
|
|
|
time, chosen by the dock **Mode** (Particle / MIDI / OSC / VCV / Built-in Synth /
|
2026-06-28 03:28:45 +02:00
|
|
|
|
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. |
|
2026-06-28 04:28:45 +02:00
|
|
|
|
| `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`). |
|
2026-06-28 03:28:45 +02:00
|
|
|
|
| `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):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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],…] }`.
|
2026-06-28 04:28:45 +02:00
|
|
|
|
|
|
|
|
|
|
## 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:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 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…".
|