The default (Particle) view no longer renders the 'MEMLNaut' wordmark
since the particle top bar became a heatmap strip; assert on the always-
present 'Learning' dock drawer label instead so the smoke test reflects
the current Console chrome.
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
Replace the a-immersive original's clashing 20-colour palette (hot magenta,
lime, pink) with a ramp interpolated between Manifold's two brand accents —
--accent (#ff6a00) → --accent-2 (#00ccff) — softened to the app's semantic-
token saturation register so the strip reads as native chrome.
ParticleStage showed the Boldness/Memory/Precision macro sliders across the
top, but the a-immersive design this mode clones uses a thin per-output
heatmap strip (one colored bar per visual output, width = live value). The
deployed a-immersive never used the macro-axis surface.
- flow-field.ts: export VISUAL_PARAM_NAMES/COLORS + N_VISUAL_OUTPUTS (verbatim
from a-app.js:46/51)
- ParticleStage: replace macro-slider bar with the 22px heatmap strip; bar
widths updated imperatively in the existing rAF loop; hover tooltip with
live value; drop unused axes props
- ConsoleApp: update call site
Joystick -> MLP -> MIDI CC for an external hardware synth, using the
compile-time device templates from nisps/midi/generated. ExternalSynthMIDIMode
<const MidiDevice&, NOut> mirrors SoundAnalysisMIDIMode (NoOpEngine,
kRouteOutputsToEngine=false, pushes ControlChange events). A consteval
pick_cc_slots curates which NOut params fill the output slots (prefers musical
params over Bank Select/global). Adds six flashable variants
(MEMLNautModeExtSynth{Sub37,SubPhatty,Pro12,AnalogKeys,Hydrasynth,JD800}) wired
into mode_select.hpp + the .ino variant list + NISPS_ST guards, and a src/nisps/
midi symlink so the sketch tree can reach nisps/midi.
Host-compile-verified under C++20 (incl. via the sketch include path) and
lint-clean; full arduino-cli build + flash is on the hardware (no toolchain here.
Adds a device picker to the MIDI backend config: choose an external synth
(Moog Sub 37, Hydrasynth, etc.), see its parameters grouped and BY NAME with
their CC numbers, tick which ones to control, and Apply — fills the per-output
CC table (CC#/channel/name) and sets the CC count from the template. Clamps to
the engine output count. Self-contained (local state + setParam/setMidiCcCount);
the result lives in the shared params store, so the existing named-preset bar
saves/restores it. Sourced from manifold/src/midi-devices (codegen).
Add a durable, committed source of truth for external MIDI synth control:
- synth-midi-cc.json: verified CC maps + provenance/sources (8 devices researched)
- schemas/midi_device.schema.json + schemas/midi_devices/*.json: 6 CC-controllable
device templates (Moog Sub 37/Sub Phatty, Creamware Pro-12 ASB, Elektron Analog
Keys, ASM Hydrasynth, Roland JD-800), params keyed {id, cc, label, min, max,
default, group}.
- codegen/generate-midi-devices.ts (isolated from the mode golden test) emits both
nisps/midi/generated/midi_devices.hpp (no-heap constexpr, firmware+WASM) and
manifold/src/midi-devices/generated/ (typed catalogue for the browser).
- codegen/seed-midi-devices.ts: reproducible seed from the research artifact.
Lets a performer pick a device and address its parameters by name (not CC number)
on both the firmware and the Manifold browser engine.
- vcv/build-mac.sh: bounded Docker osxcross build (system clang, NOT LLVM-from-
source; cached toolchain image for fast reruns) → ad-hoc-signed mac-x64 +
mac-arm64 .vcvplugin. MacOSX12.3 SDK from joseluisq/macosx-sdks.
- DISTRIBUTION.md: macOS is now a host bounded cross-build, not CI-only.
All four platforms (lin/win/mac-x64/mac-arm64) now published at /next/vcv.
- vcv/Makefile: include arch.mk early, link -lws2_32 when ARCH_WIN (the OSC
server uses Winsock; mingw ignores the MSVC #pragma comment lib).
- vcv/build-win.sh: bounded Docker mingw cross-build (hard CPU/mem caps, no
extra swap, prebuilt mingw — no toolchain compile). Produces win-x64 .vcvplugin.
- DISTRIBUTION.md: reflect the no-dedicated-box reality — Linux + Windows built
locally (supervised + bounded), macOS needs the Apple SDK (CI or a Mac).
- add .github/workflows/vcv-plugin.yml: official rack-plugin-toolchain
(pinned to commit 4fd1318, Rack SDK 2.6.6, image v19) cross-builds the
MEMLNaut plugin for lin-x64/win-x64/mac-x64/mac-arm64 on CI, uploads
.vcvplugin artifacts, and attaches them to GitHub Releases on v* tags.
- add vcv/DISTRIBUTION.md: build + publish flow, how to add CI release
artifacts to meml.lnfinitemonkeys.org/next/vcv.
- add vcv/.gitignore for build products (build/, dist/, *.vcvplugin, binaries).
- BUILDING.md: point at DISTRIBUTION.md; note make dist supersedes Makefile.dist.
Linux x64 .vcvplugin built + verified locally via make dist and published to
/next/vcv (additive subdir). Windows/macOS deferred to CI per prod-host policy.
Route onMove through the XY-pad source's pushPad; instantiate useInputLayer
per engine and expose it on ConsoleCtx. The Inputs drawer now picks the
source(s) (XY pad / MIDI / gamepad toggles), configures each (gamepad
single/double stick; MIDI learn-map with per-binding clear), and shows the
composed channel layout + the blend-to-2 reshape notice referencing the
multi-WASM TODO (inputs-spec).
Workstream F core. Pull-based InputSource interface (N axes + discrete
actions), three sources (XYPadSource push-based, WebMidiInputSource with
learn-map CCs/notes, GamepadSource single/double stick), and an InputLayer
that composes active sources into one N-dim vector at the head of the
reactive spine. Arity reduction blends >2 axes down to the fixed 2-input
WASM head; the true multi-WASM reshape is a documented TODO (inputs-spec).
useInputLayer is the React binding (enable/config/status/channel layout).
Registers a named ParticleBackend in the BackendManager + barrel in place of
the inline particles PassthroughBackend. Transport is a no-op (the flow-field
canvas reads engine outputs directly via rAF); selecting it gates synth audio
(setMuted) like the other non-synth backends and reports a clear ready status.
Particle System is the default output Mode. When active it renders the
full-bleed ParticleStage (flow field driven by engine outputs each rAF) in
place of the manifold stage, regardless of the in/split/out/composite focus,
and suppresses the duplicate corner MEMLNaut label (the stage's top axis bar
already shows it).
Faithful TypeScript port of the a-immersive visualiser
(deployments/meml-aimmersive/js/ui/visualizer.js): 400 particles, identical
value-noise + 20-output param mapping + advection/attractor/dispersion/repulsor
integration. ParticleStage renders the full-bleed canvas (driven by engine
outputs each rAF) plus the macro-axis bar and a corner input pad.
Map enter/exit-explore, reroll, nudge, begin-place, commit-place to the
peripheral buttons via the shared FeedbackController. (Not compiled here —
no arduino-cli; Placing-audition static_output hook is a flagged follow-up.)
8 inputs x 16 outputs; per-output LED ring widget (drawLayer+nvgArc); palette
from frontend tokens; OSC bridge verbs for bidirectional browser training;
vendored self-contained iml.hpp (retired nisps-core); compiles against Rack
SDK 2.6.4. See SPEC.md BUILD DELTAS.
Adds the Manifold app (manifold/) — the convertible-mode React front-end on
the real NISPS ML+audio engine, served live at meml.lnfinitemonkeys.org/next/.
Console chrome trimmed per UI pass:
- drop mode label + subtitle from the top-left overlay (keep MEMLNaut wordmark)
- remove the composite split preset/ratio readout (top-centre)
- remove the OUTPUT corner tag above the bars
- remove the A/B compare toggle from the verdict cluster
- remove the follow button + input/noise readout (bottom-left)
- remove AltitudeNav focus switcher (bottom-right)
addModule() was handed new URL('./nisps-processor.ts', import.meta.url), which
Vite copies as raw TypeScript (served as video/mp2t) instead of compiling it —
that pattern only bundles for new Worker(). Import the worklet with
?worker&url so Vite emits compiled, hashed .js. Fixes the synth engine failing
to load on meml.lnfinitemonkeys.org.
Builds the SolidJS-side feature parity with the legacy a-immersive
playground. Every Phase 1–4 feature called out in
.local/recon/04-playground.md now flows through the runtime → stores →
primitives pipeline.
Phase 1 (compound axes / zoom / drawer / trail):
- control-routing.ts: resolves Boldness/Memory/Precision into
input/output/exploration store writes via createEffect.
- SettingsDrawer (new): tabbed sections for Input / Training /
Exploration / Output / Per-param overrides / Advanced. Per-param
ParamEditor list wires through modeStore.setOverride.
- JoyMap navigator added to ModeShell; tap-to-return + long-press
region pin handlers wired into the runtime.
- Anchor mode toggle (auto/sticky/center) exposed via drawer.
Phase 2 (pinning / history / A/B):
- features/snapshots.ts: bridge sessionStore push/pop to actual
Float32Array weights. Auto-snapshot before train/randomize/thumbs-down.
- Undo button + long-press snapshot list popup wired in ModeShell.
- A/B capture/toggle/accept/revert in SettingsDrawer.
- features/overrides.ts buildPinMask() composes override `pinned`
flags + sessionStore param pins; passed to mlStore.moveWeights.
- Region pinning long-press → addRegionPin + auto-snapshot tag
"pinned baseline".
Phase 3 (refinement / exploration):
- features/heatmap-sampler.ts: 16x16 grid via mlStore.inferBatch,
three colour modes, throttled 5/sec, refreshed on ml.trained /
ml.delta_update bus events.
- exploration-store.ts (new): RL noise level, floor/cap/growth/decay,
spread, learning rate, weight decay, auto-explore, pressure.
- Auto-explore timer in mode-runtime drives moveWeights at user
interval, zoom-scaled intensity.
- Pressure feedback: window pointerdown/up timestamps mapped to
explorationStore.setPressure; feeds growNoise/decayNoise.
Phase 4 (output / persistence / polish):
- features/weight-health.ts: histogram, status, per-layer L2 deltas,
vanishing/exploding/converged classification.
- features/session-preset.ts: full state capture/restore +
base64url URL sharing; main.tsx applies on load.
- SettingsDrawer Advanced tab renders WeightHealth, GradientFlow,
LayerStats, Heatmap, session preset save/load/share.
Override application:
- features/overrides.ts applies per-param mute/freeze/curve/range
between MLP outputs and the engine. Mode runtime exposes
`paramOutputs` (length = schema.params) which all modes now bind to
OutputDisplay so ranges and mutes are visible.
- Freeze flags push into outputStore.freezeMask reactively.
Mic input:
- features/mic-input.ts: getUserMedia + AnalyserNode-derived
{energy, brightness, pitch, aperiodicity}. Mode runtime feeds
them into channels 2..(input_size-1) when active. ModeShell shows
a Mic toggle for audio_in modes.
Debug probe (probe.ts):
- Synchronous bypass for snapshot, A/B, pins, overrides, axes,
spread, output freeze, heatmap, weight health, session presets,
URL params, bus emit/on. Untracked reads/writes throughout to
avoid SolidJS reactivity surprises in tests.
Modes:
- All 8 firmware mode TSX files now use the default SettingsDrawer
(no per-mode SliderBank scaffolding). They bind OutputDisplay to
runtime.paramOutputs (post-override) instead of processedOutputs.
Build status:
- bun run typecheck: clean
- bun run build: clean
- dev server smoke: index, /modes, mode-runtime, SettingsDrawer,
features/* all serve.
Note: bd close meml-5wg failed because Dolt server unreachable from
this worktree. Issue should be closed manually by orchestrator.
Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the
hardware (memllib) to the platform-agnostic nisps/ library through a
slim glue layer. Delete the legacy root-level *AudioApp.hpp,
modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis,
and the src/memlp submodule.
Glue layout (firmware/MEMLNaut-NISPS/glue/):
audio_driver.hpp - bridge memllib block callback to Mode::process
via per-Mode templated trampoline (no virtual dispatch)
peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives
midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing,
drains mode ControlEvent ring -> MIDI UART
mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to
nisps::modes::*Mode (build script rewrites the
#define MEMLNAUT_MODE_TYPE line)
input_router.hpp / output_router.hpp - top-level wire/drain entry points
The sketch tree uses src/{memllib,daisysp,nisps} symlinks because
Arduino-CLI rejects ".." in include paths from sketch-tree headers.
mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before
including nisps headers (some nisps engines use those identifiers as
method names). The audio bridge struct is extern in the header and
defined in the .ino because inline + __not_in_flash section attribute
collide at link time.
Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip,
and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3,
-std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries,
110+ tests). Build script (scripts/build-firmware.sh) updated to point
at the new sketch path; mode-rewrite logic unchanged.
Closes meml-gkm.