From ecb5bf2bb6058b785ae7732fe1a05598bd951ca1 Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Sun, 28 Jun 2026 04:22:05 +0200 Subject: [PATCH] feat(backends): register VcvBackend in manager + roster; add defaultVcvSpec, feedback forwarding, input-vector streaming --- manifold/src/backends/index.ts | 2 ++ manifold/src/backends/manager.ts | 35 +++++++++++++++++++++++++++++-- manifold/src/dock/output-state.ts | 5 +++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/manifold/src/backends/index.ts b/manifold/src/backends/index.ts index a0bea84..ca2431c 100644 --- a/manifold/src/backends/index.ts +++ b/manifold/src/backends/index.ts @@ -16,6 +16,8 @@ export { WebMidiBackend } from './midi-backend'; export type { MidiBackendConfig } from './midi-backend'; export { OscBridgeBackend } from './osc-backend'; export type { OscBackendConfig } from './osc-backend'; +export { VcvBackend } from './vcv-backend'; +export type { VcvBackendConfig, VcvFeedbackOp } from './vcv-backend'; export { NispsOscClient } from './osc-client'; export { PassthroughBackend } from './passthrough-backend'; export { diff --git a/manifold/src/backends/manager.ts b/manifold/src/backends/manager.ts index 4afe0c2..4112258 100644 --- a/manifold/src/backends/manager.ts +++ b/manifold/src/backends/manager.ts @@ -21,12 +21,19 @@ import type { BackendId } from '../dock/output-state'; import { WebMidiBackend } from './midi-backend'; import { OscBridgeBackend } from './osc-backend'; import { PassthroughBackend } from './passthrough-backend'; +import { VcvBackend, type VcvFeedbackOp } from './vcv-backend'; /** The slice of EngineApi the manager depends on (keeps it decoupled/testable). */ export interface ManagerEngine { subscribe(cb: () => void): () => void; routedOutput(): Float32Array | null; audio: { setMuted(muted: boolean): void }; + /** + * Current control input vector (2-D for the fixed 2→N MLP). Optional — when + * present the VCV backend streams it to the module so the browser drives the + * module's inputs in bridged mode. + */ + inputVector?(): ReadonlyArray; } export class BackendManager { @@ -49,12 +56,17 @@ export class BackendManager { ['synth', backends?.synth ?? new PassthroughBackend('synth', 'Built-in Synth — audio plays in the engine')], ['particles', backends?.particles ?? new PassthroughBackend('particles', 'Particle visualiser')], ['cvgate', backends?.cvgate ?? new PassthroughBackend('cvgate', 'CV / gate (via VCV bridge)')], - ['vcv', backends?.vcv ?? new PassthroughBackend('vcv', 'VCV bridge')], + ['vcv', backends?.vcv ?? new VcvBackend()], ]); - // Single subscription to the spine: forward routed → active backend. + // Single subscription to the spine: forward routed → active backend. For the + // VCV backend we also stream the current input vector each tick so the + // browser drives the module's inputs in bridged mode. this.unsub = this.engine.subscribe(() => { if (!this.active) return; + if (this.active instanceof VcvBackend && this.engine.inputVector) { + this.active.setInputVector(this.engine.inputVector()); + } const routed = this.engine.routedOutput(); if (routed) this.active.send(routed); }); @@ -71,6 +83,25 @@ export class BackendManager { return b instanceof OscBridgeBackend ? b : null; } + vcv(): VcvBackend | null { + const b = this.backends.get('vcv'); + return b instanceof VcvBackend ? b : null; + } + + /** + * Forward a verdict op to the VCV module's embedded learner over the bridge. + * No-op unless the VCV backend is the ACTIVE one — the verdict loop only + * trains the module when Mode = VCV (otherwise the browser engine is the + * learner). Returns true if it was forwarded. + */ + forwardFeedback(op: VcvFeedbackOp): boolean { + if (this.activeId !== 'vcv') return false; + const vcv = this.vcv(); + if (!vcv) return false; + vcv.sendFeedback(op); + return true; + } + get(id: BackendId): OutputBackend | undefined { return this.backends.get(id); } diff --git a/manifold/src/dock/output-state.ts b/manifold/src/dock/output-state.ts index 52c912d..b240a97 100644 --- a/manifold/src/dock/output-state.ts +++ b/manifold/src/dock/output-state.ts @@ -130,3 +130,8 @@ export function defaultMidiSpec(index: number): MidiCcSpec { export function defaultOscSpec(name: string): OscSpec { return { path: `/nisps/${name.toLowerCase()}`, rangeMin: 0, rangeMax: 1 }; } + +/** Default VCV spec for an output — unipolar 0–10 V by default. */ +export function defaultVcvSpec(): VcvSpec { + return { bipolar: false }; +}