memlnaut-nisps/manifold/src/backends/README.md

78 lines
3.9 KiB
Markdown
Raw Normal View History

# 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):
```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],…] }`.
## 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 010 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…".