diff --git a/MAP.md b/MAP.md index 27b547e..143a4df 100644 --- a/MAP.md +++ b/MAP.md @@ -59,8 +59,8 @@ anchor + locked decisions) and the `docs/specs/*-spec.md` set. consts (mode_id, engine_id, ml dims, params, voice_spaces, ui) — the SOURCE OF TRUTH for `MF_MODES`. Switching mode reshapes the WASM net to the mode's `ml` dims (ConsoleApp P5.3; boot mode paf_synth → 4→[10,10,14]→33). -- `manifold/src/dock/` — `OutputControlRow` (add/delete card identity + off/fixed/live + mute + solo/arm + min/max/curve), `output-state.ts`, - `OutputsBackendConfig.tsx` (per-backend specialised Outputs panel — the sole per-backend editor). +- `manifold/src/dock/` — `OutputControlRow` (add/delete card identity + off/fixed/live + mute + solo/arm + min/max/curve, plus MIDI card fields), `output-state.ts`, + `OutputsBackendConfig.tsx` (per-backend specialised Outputs panel — the sole per-backend editor; the centered add-card control lives below the rows). - `manifold/src/backends/` — `OutputBackend` adapter + `BackendManager` (spine consumer); `midi-backend.ts` (WebMIDI), `osc-backend.ts`+`osc-client.ts` (OSC-over-WS), `vcv-backend.ts` (VCV-over-WS), `cv-backend.ts` (`UseqCvBackend` — uSEQ CV/gate over USB Web Serial, backend id `cvgate`) + `useq-protocol.ts` (v2 wire @@ -68,7 +68,7 @@ anchor + locked decisions) and the `docs/specs/*-spec.md` set. `particle-backend.ts`, `passthrough-backend.ts`, `presets.ts` (named presets), `manager.ts`. - `manifold/src/midi-devices/` — external-synth device templates. `generated/` is codegen output from `schemas/midi_devices/` (`MIDI_DEVICES` catalogue + `MIDI_DEVICES_BY_ID`, params by name+CC). The MIDI Outputs - config (`dock/OutputsBackendConfig.tsx`) reads it for the device picker + param-select that fills the CC table. + config (`dock/OutputsBackendConfig.tsx`) reads it for the device picker + param-select that fills the per-card MIDI fields. - `manifold/src/inputs/` — modular INPUT layer feeding the ML head. The Inputs dock picks ONE exclusive mode (`InputMode` = `internal` | `gamepad` | `midi`; Internal/XY-pad is default). `input-layer.ts` owns a single rAF loop composing the active source's axes → **one dedicated engine input slot per axis, 1:1, no blending** → one diff --git a/manifold/ONBOARDING.md b/manifold/ONBOARDING.md index 9ed0d59..9ac50ee 100644 --- a/manifold/ONBOARDING.md +++ b/manifold/ONBOARDING.md @@ -166,9 +166,10 @@ for the narrow pane. curve/fixedValue + backend specs `MidiCcSpec`/`OscSpec`/`VcvSpec`), `toOutputControl()`, `buildArmMask()` (solo focus). - `OutputControlRow.tsx` — one output row: name · M(mute) · S(solo/arm) · off|fixed|live · dual-range - · curve pad · live value. **Writes eagerly to the shared `MFParam` store via `onChange`.** - - `OutputsBackendConfig.tsx` — preset bar (save/restore/rename/delete) + per-backend config (MIDI - CC#/channel, OSC path/range, VCV polarity). + · curve pad · live value; MIDI mode adds the card's name, CC#, and channel fields. **Writes eagerly + to the shared `MFParam` store via `onChange`.** The centered `+` control follows the last visible card. + - `OutputsBackendConfig.tsx` — preset bar (save/restore/rename/delete) + transport/device config (MIDI + port/templates, OSC path/range, VCV polarity); MIDI per-output fields live on `OutputControlRow`. ### Primitives — `src/primitives/` (barrel: `index.ts`) Seven: `Button`, `Slider`, `PillToggle`, `Badge`, `Switch`, `XYPad`, `VirtualJoystick`. Dumb, diff --git a/manifold/src/console/Drawers.tsx b/manifold/src/console/Drawers.tsx index 31b222e..6129482 100644 --- a/manifold/src/console/Drawers.tsx +++ b/manifold/src/console/Drawers.tsx @@ -757,9 +757,6 @@ function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) { off {counts.off || 0} muted {mutedN} - {ModeConfig(ctx, depth)} {/* Specialised per-backend config + named-preset bar (MIDI/OSC); hidden when condensed. */} @@ -787,9 +784,32 @@ function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) { onChange={(patch) => ctx.setParam(i, patch)} onDelete={activeParams.length > 1 ? () => ctx.deleteOutput(i) : undefined} showCurve={expanded} + showMidi={ctx.outputMode === 'midi'} /> ); })} + {!expanded && activeParams.length > 6 && ( +{activeParams.length - 6} more — expand to edit diff --git a/manifold/src/dock/OutputControlRow.tsx b/manifold/src/dock/OutputControlRow.tsx index 40c13f9..3246816 100644 --- a/manifold/src/dock/OutputControlRow.tsx +++ b/manifold/src/dock/OutputControlRow.tsx @@ -12,6 +12,7 @@ import { GROUP_COLOR } from '../console/model'; import type { MFParam, ParamStatus } from '../console/model'; import { CurvePad } from '../console/CurvePad'; import { DualRange } from '../console/DualRange'; +import { defaultMidiSpec } from './output-state'; const STATE_META: { v: ParamStatus; label: string; color: string }[] = [ { v: 'off', label: 'off', color: 'var(--fg-dim)' }, @@ -64,6 +65,8 @@ export interface OutputControlRowProps { onDelete?: () => void; /** Show the curve pad inline (expand depth); hidden in compact rows. */ showCurve?: boolean; + /** Show the per-card MIDI name, CC, and channel controls. */ + showMidi?: boolean; } export function OutputControlRow({ @@ -72,12 +75,14 @@ export function OutputControlRow({ onChange, onDelete, showCurve = false, + showMidi = false, }: OutputControlRowProps) { const gc = `var(${GROUP_COLOR[param.group] || '--accent'})`; const muted = param.muted ?? false; const armed = param.armed ?? false; const off = param.status === 'off'; const barVal = param.status === 'fixed' ? param.val : value; + const midi = param.midi ?? defaultMidiSpec(param.engineIndex ?? 0); return (
- - {param.name} - + {showMidi ? ( + onChange({ midi: { ...midi, name: e.target.value } })} + style={{ + flex: 1, + minWidth: 0, + width: 0, + border: 0, + borderBottom: '1px solid var(--line)', + background: 'transparent', + color: 'var(--fg)', + fontFamily: 'var(--font-mono)', + fontSize: 'var(--fs-xs)', + padding: '1px 0', + }} + /> + ) : ( + + {param.name} + + )} {param.group} {onDelete && (
+ {showMidi && ( +
+ + +
+ )} +
{/* tri-state segmented */}
diff --git a/manifold/src/dock/OutputsBackendConfig.tsx b/manifold/src/dock/OutputsBackendConfig.tsx index 59f14c8..b9e5143 100644 --- a/manifold/src/dock/OutputsBackendConfig.tsx +++ b/manifold/src/dock/OutputsBackendConfig.tsx @@ -4,7 +4,8 @@ * * Layout: ONE preset bar (save-as / restore / rename / delete, per-backend * namespace) on top, then a per-backend config section: - * - MIDI → output-port picker, number-of-CCs, per-output CC#/channel/name. + * - MIDI → output-port picker, number-of-CCs, and device templates; each + * output card owns its CC#/channel/name fields. * - OSC → bridge URL + connect status + send-raw toggle, per-output path/range. * - VCV/CV→ bridge URL + connect status + send-raw toggle, per-output polarity * (uni 0–10 V / bipolar ±5 V). The browser drives + trains the VCV @@ -20,7 +21,7 @@ import { useEffect, useState } from 'react'; import type { ConsoleCtx } from '../console/types'; import type { BackendId, CvChannelId } from './output-state'; -import { CV_CHANNELS, defaultCvSpec, defaultMidiSpec, defaultOscSpec, defaultVcvSpec } from './output-state'; +import { CV_CHANNELS, defaultCvSpec, defaultOscSpec, defaultVcvSpec } from './output-state'; import { applyPreset, deletePreset, @@ -212,8 +213,8 @@ function PresetBar({ ctx, backend }: { ctx: ConsoleCtx; backend: BackendId }) { /** * Pick an external synth (e.g. Moog Sub 37), see its parameters BY NAME, tick the - * ones to control, and "Apply" — this fills the per-output CC table (CC#, channel, - * name) from the device template and sets the CC count. Pure local state + the + * ones to control, and "Apply" — this fills the per-output card fields (CC#, + * channel, name) from the device template and sets the CC count. Pure local state + the * existing setParam/setMidiCcCount; the applied result lives in the shared params * store, so it is saved/restored by the named-preset bar like any other config. */ @@ -355,72 +356,13 @@ function MidiConfig({ ctx }: { ctx: ConsoleCtx }) { ))} - {ctx.midiCcCount} CC output{ctx.midiCcCount === 1 ? '' : 's'} · add/delete cards below + {ctx.midiCcCount} MIDI output card{ctx.midiCcCount === 1 ? '' : 's'} {s.message}
- Per-output CC · name · channel -
- - - - - - - - - - - {ctx.params.slice(0, ctx.midiCcCount).map((p, i) => { - const m = p.midi ?? defaultMidiSpec(i); - return ( - - - - - - - ); - })} - -
NameCC#ChState
- ctx.setParam(i, { midi: { ...m, name: e.target.value } })} - /> - - - ctx.setParam(i, { midi: { ...m, cc: Math.max(0, Math.min(127, num(e.target.value, m.cc))) } }) - } - /> - - - ctx.setParam(i, { - midi: { ...m, channel: Math.max(1, Math.min(16, num(e.target.value, m.channel))) }, - }) - } - /> - - {p.status} - {p.muted ? ' · muted' : ''} -
-
); }