Spec-only (firmware fork is WIP, built by another agent). Captures: memlcelium
is a single shared-net hybrid (14 ratio-seq + 42 synth outputs); the two-stream
model (Continuous + Rhythm); firmware 3 compile-time modes + browser's 4th
split-net hybrid; gate-sequence config for CV & MIDI (counts + shared/separate
net toggle; XY-pad→2nd pad / gamepad→double-stick); CV jack allocation
(11 CV + 3 gate, swap CV→gate); RatioSeq gate generation; no wire-protocol change.
Restores the April-2026 "uSEQ-Celium" functionality (browser → uSEQ
hardware + CV expander over USB Web Serial) as a first-class Manifold
Outputs backend, and re-vendors the RP2040 firmware into the repo.
- protocol v2 (uSEQ-CV): firmware/useq-celium/shared/protocol.h is the
single source of truth, mirrored by manifold/src/backends/useq-protocol.ts.
26-byte OUTPUT frame, 11×u16 CV (12-bit) + 3-gate bitfield + XOR; fixed
topology; host-agnostic so the MEMLNaut RP2350 can emit identical bytes.
Spec in docs/useq-celium/protocol.md.
- firmware/useq-celium/{main,expander}: PlatformIO RP2040 firmware rewritten
to v2 from the real April pin maps (expander I2C addr 0x10).
- UseqCvBackend (id cvgate): Web Serial connect/identify/disconnect, 100 Hz
stream, per-channel dead-zone, gate thresholding; modeled on midi-backend.
Per-output CvSpec (channel + gateThreshold) on MFParam; config UI in
OutputsBackendConfig + BackendAdvanced; new "CV / uSEQ" top-dock mode.
- bun-test for the protocol frame layout; MAP.md updated.
Playground parity for the two learning gestures, TS-only (reuses the
existing nisps_ml_get/set_weights bindings; no C++/wasm change):
- playground/src/ml/jolt.ts — TS port of nisps/ml/jolt.hpp.
- playground/src/output/ou-explore.ts — TS port of nisps/ml/ou_noise.hpp.
- mode-runtime.ts — jolt (~200Hz weight-morph timer via mlStore weights)
+ explore (OU stage in recomputeOutputs, ~30Hz roam timer). Both inert
by default and cleaned up on unmount, so other modes are unaffected.
- SLPWorkshopMode.tsx — hold-to-Jolt button + Explore slider.
Uses Math.random() (documented); stochastic exploration aids don't need
firmware-parity. Verified: playground tsc --noEmit clean.
The OutputEditor popover was placed with a fixed top + an index-based
left/right heuristic and never measured the viewport, so it clipped the
screen edges when the window wasn't full-screen. It now measures its
anchored rect on layout and translates itself back inside the viewport
(8px gutter), re-checking on resize.
Single-file map of the Manifold front-end so an agent can make a tweak/fix
without re-grepping the tree or reading the 40KB design specs: run/build/
deploy/test commands, the three-layer architecture (UI / engine spine / WASM),
the convertible Stage system, the Dock + drawers, engine/inputs/feedback/
backends layout, and the non-obvious gotchas (imperative output reads,
baseURI asset URLs, hardwired WASM arch, curves↔C++ lockstep, no 'C15').
New SLP-Workshop firmware variant (Synth Library Portland), built on the
MEMLCelium engine + MLP shape. Ports the two post-fork learning-algorithm
changes from upstream memllib InterfaceRL into the shared nisps/ml core,
runtime-configurable (no compile-time switch), inert by default:
- nisps/ml/jolt.hpp: Jolt — held continuous weight morph over the flat
weight buffer + post-release LR ramp (kJolt* constants verbatim).
- nisps/ml/ou_noise.hpp: OUNoise<N> — Ornstein-Uhlenbeck exploration walk
on the output vector (theta=0.02, dt=0.001, kMaxAmplitude=0.65).
Both wired into ModeBase so every mode gains jolt_press/jolt_release/
jolt_lr_scale + set_explore_intensity; gated so existing modes stay
bit-identical (parity + golden tests green). Firmware surfaces them on
TogB1 (Jolt) and RVX1 (explore). New SLPWorkshopMode mode + schema +
codegen; firmware alias + .ino variant; playground mode registration.
Tests: jolt + OU unit tests, ModeBase learning integration incl. an
inert-parity test proving SLP-Workshop == MEMLCelium with features off.
Verified: cpp tests, wasm build, native↔wasm parity, lint, codegen
golden, playground typecheck. Firmware compile/e2e/hardware are
environment-bound (no arduino-cli/submodules/browser here).
Refs ergo 019f0fca.
- mf-slider-input fill used calc(var(--mf-pct)*100%) but three consumers
(OutputControlRow held slider, OutputEditor, shared-ui Field) never set
--mf-pct, so the coloured track-fill stayed empty while only the native
thumb moved. Set --mf-pct inline on all three; harden the base class
(appearance:none + transparent bg + --mf-pct fallback + moz-range-progress).
- hide the two concentric noise-cap exploration rings around the input dot.
Fuse the /learn parameter-landscape sandbox into the main console:
- New SandwichStage: ports the 3D landscape renderer to React, driven by the
REAL EngineApi (inferBatch samples each output channel over the 2D input grid;
getOutputs feeds the live probe). Re-samples on engine version bumps; orbit
camera (top-down = flat 2D, tilt to reveal the stack), centred + grows to fill.
- Dock: new bottom toggle (SandwichIcon) → ctx.sandwich.
- ConsoleApp: when on, the sandwich replaces the centre stage in a 3-zone flex —
shrunken Manifold (left) · sandwich (centre, fills) · compact OutputStage
(right) — reusing pos/markers/verdicts. Opening it auto-closes the drawer so
the output zone is visible. Layers = the mode's params (capped 8).
typecheck + build green; verified headless (toggle on/off, sample, tilt).
Add glue/settings_view.hpp: wire_settings(mode) registers a "Joystick"
SingleSelectView on the MEMLNaut display carousel (TFT + rotary encoder, same
DisplayDriver as SystemView/SelfTest). For the 4-input two-joystick modes,
"Single" pins ML input channels 2,3 (the second joystick) to neutral via
ModeBase::set_input_pinned — the network is never rebuilt and trained state
survives toggling. Default is Dual. Only registered when input arity == 4.
Called from MEMLNaut-NISPS.ino setup() after addSystemInfoView(). MAP.md +
CLAUDE.md glue listings updated.
NOTE: compile-unverified — no arduino-cli/RP2350 toolchain on this host and no
hardware; needs scripts/build-firmware.sh + a flash test (chokepoint A).
Platform-agnostic neutral-pin mechanism (set_input_pinned/pin_value) on
ModeBase, lifted from ebb953a. Consumed by the firmware Joystick Dual/Single
settings menu; the browser fixed-MLP<4> half of ebb953a is superseded by main's
MLP<32> N-D input work and intentionally dropped.
Interactive /learn page rebuilt on the real NISPS engine (nisps.wasm):
- Stacked 3D landscapes = MLP output channels sampled over the 2D input grid
(the N-dimensional 'sandwich'); orbitable yaw/pitch camera, top-down = flat 2D.
- Three zones L→R: XY control (with +/- feedback markers) → MLP stack → output faders.
- Real training: push-away (feedback_down) + explore-and-place (enter/reroll/nudge/
commit), undo, randomise, nudge; landscapes re-sample and morph after each verdict.
- Params numberbox (1..8); new layers grow downward with the top slab pinned.
- Minimal-by-default with a discrete info switch revealing placeholder copy.
Standalone no-build page; loads ../nisps.{js,wasm}; deployed at /next/learn/.
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.