From 0d1a2102ebe8f2a1d507dc2715f4899f8eaedef2 Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Mon, 13 Jul 2026 23:27:56 +0200 Subject: [PATCH] =?UTF-8?q?feat(manifold)!:=20P1=20=E2=80=94=20retire=20pl?= =?UTF-8?q?ayground/,=20manifold=20is=20the=20sole=20browser=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playground archived at branch archive/playground-solidjs (tag playground-solidjs-final) and deleted from main. Retargets: - run-all-tests.sh stage 5 → manifold (typecheck + bun test + build + playwright via non-snap node runner, BUILD-PLAN gotcha) - ci.yml playground-tests → manifold-tests; osc-bridge.yml → manifold/osc-bridge (was already broken: playground/osc-bridge no longer existed) - codegen: TS emission target removed (returns at P5 → manifold); golden test now C++-only; TS emitters retained dormant - .gitignore: manifold/osc-bridge paths; drop dead playground faust exception - docs: AGENTS.md gates, README quickstart, MAP.md, ALIGNMENT.md (C15 now archive-only — defect #1 updated), AGENT-REFERENCE.md, specs/MAIN.md Part of docs/specs/plans/one-core-engine-refactor.md P1. --- .github/workflows/ci.yml | 33 +- .github/workflows/osc-bridge.yml | 2 +- .gitignore | 8 +- AGENTS.md | 8 +- ALIGNMENT.md | 14 +- MAP.md | 37 +- README.md | 20 +- codegen/generate.ts | 26 +- codegen/tests/golden_test.ts | 13 +- docs/AGENT-REFERENCE.md | 74 +- docs/specs/MAIN.md | 9 +- playground/.gitignore | 5 - .../assets/PrimitivesShowcase-B55FOguW.css | 1 + .../assets/PrimitivesShowcase-D2lYbVrQ.js | 2 + .../assets/PrimitivesShowcase-D2lYbVrQ.js.map | 1 + playground/dist/assets/index-C1-a2ZPT.js | 3 + playground/dist/assets/index-C1-a2ZPT.js.map | 1 + playground/dist/assets/index-D19lnkK_.css | 1 + .../dist/assets/nisps-processor-BuKuNOY1.js | 2 + .../assets/nisps-processor-BuKuNOY1.js.map | 1 + playground/dist/assets/wasm-iml-FZSgVMmn.js | 2 + .../dist/assets/wasm-iml-FZSgVMmn.js.map | 1 + .../dist/assets/wasm-worker-OATBBHaT.js | 2 + .../dist/assets/wasm-worker-OATBBHaT.js.map | 1 + playground/{ => dist}/index.html | 3 +- playground/dist/nisps.js | 2 + playground/dist/nisps.wasm | Bin 0 -> 95864 bytes playground/package-lock.json | 1886 ----------------- playground/package.json | 25 - playground/playwright.config.ts | 36 - playground/public/nisps.js | 19 - playground/public/nisps.wasm | Bin 102867 -> 0 bytes playground/src/App.module.css | 84 - playground/src/App.tsx | 145 -- playground/src/audio/engine-host.ts | 232 -- playground/src/audio/worklet/README.md | 52 - .../audio/worklet/audioworklet-globals.d.ts | 26 - .../src/audio/worklet/nisps-processor.ts | 309 --- playground/src/debug/probe.ts | 516 ----- .../src/dev/PrimitivesShowcase.module.css | 70 - playground/src/dev/PrimitivesShowcase.tsx | 80 - playground/src/dev/probe-smoke.ts | 121 -- playground/src/features/control-routing.ts | 77 - playground/src/features/heatmap-sampler.ts | 160 -- playground/src/features/mic-input.ts | 138 -- playground/src/features/overrides.ts | 131 -- playground/src/features/session-preset.ts | 288 --- playground/src/features/snapshots.ts | 123 -- playground/src/features/trail.ts | 55 - playground/src/features/weight-health.ts | 124 -- playground/src/input/pipeline.ts | 307 --- playground/src/main.tsx | 25 - playground/src/ml/dataset.ts | 193 -- playground/src/ml/jolt.ts | 122 -- playground/src/ml/types.ts | 156 -- playground/src/ml/wasm-iml.ts | 650 ------ playground/src/ml/wasm-worker.ts | 334 --- playground/src/modes/BreakOrMode.tsx | 50 - playground/src/modes/C15Mode.tsx | 48 - playground/src/modes/ChannelStripMode.tsx | 53 - playground/src/modes/ElysiamorfMode.tsx | 50 - playground/src/modes/MEMLCeliumMode.tsx | 54 - playground/src/modes/ModeShell.module.css | 162 -- playground/src/modes/ModeShell.tsx | 300 --- playground/src/modes/ModeSwitcher.module.css | 43 - playground/src/modes/ModeSwitcher.tsx | 54 - playground/src/modes/PAFSynthMode.tsx | 59 - playground/src/modes/SLPWorkshopMode.tsx | 118 -- .../src/modes/SettingsDrawer.module.css | 250 --- playground/src/modes/SettingsDrawer.tsx | 656 ------ .../src/modes/SoundAnalysisMIDIMode.tsx | 96 - playground/src/modes/VerbFXMode.tsx | 54 - playground/src/modes/XIASRIMode.tsx | 54 - .../src/modes/generated/breakor_schema.ts | 596 ------ .../modes/generated/channel_strip_schema.ts | 283 --- .../src/modes/generated/elysiamorf_schema.ts | 436 ---- playground/src/modes/generated/index.ts | 13 - .../src/modes/generated/memlcelium_schema.ts | 598 ------ .../src/modes/generated/paf_synth_schema.ts | 374 ---- .../modes/generated/slp_workshop_schema.ts | 598 ------ .../generated/sound_analysis_midi_schema.ts | 122 -- playground/src/modes/generated/types.ts | 54 - .../src/modes/generated/verb_fx_schema.ts | 519 ----- .../src/modes/generated/xiasri_schema.ts | 278 --- playground/src/modes/index.ts | 106 - playground/src/modes/mode-helpers.ts | 66 - playground/src/modes/mode-runtime.ts | 785 ------- playground/src/output/curves.ts | 128 -- playground/src/output/ou-explore.ts | 122 -- playground/src/output/pipeline.ts | 153 -- .../src/primitives/ControlAxis.demo.tsx | 43 - .../src/primitives/ControlAxis.module.css | 99 - playground/src/primitives/ControlAxis.tsx | 73 - playground/src/primitives/Drawer.demo.tsx | 21 - playground/src/primitives/Drawer.module.css | 92 - playground/src/primitives/Drawer.tsx | 69 - .../src/primitives/GradientFlow.demo.tsx | 36 - .../src/primitives/GradientFlow.module.css | 6 - playground/src/primitives/GradientFlow.tsx | 69 - playground/src/primitives/Heatmap.demo.tsx | 46 - playground/src/primitives/Heatmap.module.css | 7 - playground/src/primitives/Heatmap.tsx | 138 -- playground/src/primitives/JoyMap.demo.tsx | 52 - playground/src/primitives/JoyMap.module.css | 8 - playground/src/primitives/JoyMap.tsx | 309 --- playground/src/primitives/LossPlot.demo.tsx | 27 - playground/src/primitives/LossPlot.module.css | 6 - playground/src/primitives/LossPlot.tsx | 90 - .../src/primitives/OutputDisplay.demo.tsx | 33 - .../src/primitives/OutputDisplay.module.css | 10 - playground/src/primitives/OutputDisplay.tsx | 67 - .../src/primitives/ParamEditor.demo.tsx | 24 - .../src/primitives/ParamEditor.module.css | 66 - playground/src/primitives/ParamEditor.tsx | 106 - playground/src/primitives/PillToggle.demo.tsx | 30 - .../src/primitives/PillToggle.module.css | 34 - playground/src/primitives/PillToggle.tsx | 41 - .../src/primitives/ProgressRing.demo.tsx | 20 - playground/src/primitives/ProgressRing.tsx | 73 - playground/src/primitives/Slider.demo.tsx | 15 - playground/src/primitives/Slider.module.css | 96 - playground/src/primitives/Slider.tsx | 92 - playground/src/primitives/SliderBank.demo.tsx | 24 - .../src/primitives/SliderBank.module.css | 56 - playground/src/primitives/SliderBank.tsx | 99 - .../src/primitives/TrainingControls.demo.tsx | 31 - .../primitives/TrainingControls.module.css | 85 - .../src/primitives/TrainingControls.tsx | 81 - .../src/primitives/VirtualJoystick.demo.tsx | 18 - .../src/primitives/VirtualJoystick.module.css | 71 - playground/src/primitives/VirtualJoystick.tsx | 124 -- .../src/primitives/WeightHealth.demo.tsx | 38 - .../src/primitives/WeightHealth.module.css | 37 - playground/src/primitives/WeightHealth.tsx | 75 - playground/src/primitives/XYPad.demo.tsx | 14 - playground/src/primitives/XYPad.module.css | 63 - playground/src/primitives/XYPad.tsx | 108 - playground/src/primitives/index.ts | 23 - playground/src/stores/bus.ts | 129 -- playground/src/stores/control-store.ts | 257 --- playground/src/stores/exploration-store.ts | 254 --- playground/src/stores/index.ts | 38 - playground/src/stores/input-store.ts | 186 -- playground/src/stores/ml-store.ts | 221 -- playground/src/stores/mode-store.ts | 130 -- playground/src/stores/output-store.ts | 103 - playground/src/stores/persistence.ts | 62 - playground/src/stores/session-store.ts | 333 --- playground/src/styles/tokens.css | 123 -- playground/src/types.d.ts | 8 - playground/tests/e2e/helpers.ts | 125 -- playground/tests/e2e/ml-engine.spec.ts | 200 -- playground/tests/e2e/modes.spec.ts | 93 - playground/tests/e2e/persistence.spec.ts | 113 - playground/tests/e2e/ui-interactions.spec.ts | 93 - playground/tsconfig.json | 32 - playground/vite.config.ts | 25 - scripts/build-wasm.sh | 10 - scripts/run-all-tests.sh | 20 +- 159 files changed, 141 insertions(+), 19347 deletions(-) delete mode 100644 playground/.gitignore create mode 100644 playground/dist/assets/PrimitivesShowcase-B55FOguW.css create mode 100644 playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js create mode 100644 playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js.map create mode 100644 playground/dist/assets/index-C1-a2ZPT.js create mode 100644 playground/dist/assets/index-C1-a2ZPT.js.map create mode 100644 playground/dist/assets/index-D19lnkK_.css create mode 100644 playground/dist/assets/nisps-processor-BuKuNOY1.js create mode 100644 playground/dist/assets/nisps-processor-BuKuNOY1.js.map create mode 100644 playground/dist/assets/wasm-iml-FZSgVMmn.js create mode 100644 playground/dist/assets/wasm-iml-FZSgVMmn.js.map create mode 100644 playground/dist/assets/wasm-worker-OATBBHaT.js create mode 100644 playground/dist/assets/wasm-worker-OATBBHaT.js.map rename playground/{ => dist}/index.html (67%) create mode 100644 playground/dist/nisps.js create mode 100755 playground/dist/nisps.wasm delete mode 100644 playground/package-lock.json delete mode 100644 playground/package.json delete mode 100644 playground/playwright.config.ts delete mode 100644 playground/public/nisps.js delete mode 100755 playground/public/nisps.wasm delete mode 100644 playground/src/App.module.css delete mode 100644 playground/src/App.tsx delete mode 100644 playground/src/audio/engine-host.ts delete mode 100644 playground/src/audio/worklet/README.md delete mode 100644 playground/src/audio/worklet/audioworklet-globals.d.ts delete mode 100644 playground/src/audio/worklet/nisps-processor.ts delete mode 100644 playground/src/debug/probe.ts delete mode 100644 playground/src/dev/PrimitivesShowcase.module.css delete mode 100644 playground/src/dev/PrimitivesShowcase.tsx delete mode 100644 playground/src/dev/probe-smoke.ts delete mode 100644 playground/src/features/control-routing.ts delete mode 100644 playground/src/features/heatmap-sampler.ts delete mode 100644 playground/src/features/mic-input.ts delete mode 100644 playground/src/features/overrides.ts delete mode 100644 playground/src/features/session-preset.ts delete mode 100644 playground/src/features/snapshots.ts delete mode 100644 playground/src/features/trail.ts delete mode 100644 playground/src/features/weight-health.ts delete mode 100644 playground/src/input/pipeline.ts delete mode 100644 playground/src/main.tsx delete mode 100644 playground/src/ml/dataset.ts delete mode 100644 playground/src/ml/jolt.ts delete mode 100644 playground/src/ml/types.ts delete mode 100644 playground/src/ml/wasm-iml.ts delete mode 100644 playground/src/ml/wasm-worker.ts delete mode 100644 playground/src/modes/BreakOrMode.tsx delete mode 100644 playground/src/modes/C15Mode.tsx delete mode 100644 playground/src/modes/ChannelStripMode.tsx delete mode 100644 playground/src/modes/ElysiamorfMode.tsx delete mode 100644 playground/src/modes/MEMLCeliumMode.tsx delete mode 100644 playground/src/modes/ModeShell.module.css delete mode 100644 playground/src/modes/ModeShell.tsx delete mode 100644 playground/src/modes/ModeSwitcher.module.css delete mode 100644 playground/src/modes/ModeSwitcher.tsx delete mode 100644 playground/src/modes/PAFSynthMode.tsx delete mode 100644 playground/src/modes/SLPWorkshopMode.tsx delete mode 100644 playground/src/modes/SettingsDrawer.module.css delete mode 100644 playground/src/modes/SettingsDrawer.tsx delete mode 100644 playground/src/modes/SoundAnalysisMIDIMode.tsx delete mode 100644 playground/src/modes/VerbFXMode.tsx delete mode 100644 playground/src/modes/XIASRIMode.tsx delete mode 100644 playground/src/modes/generated/breakor_schema.ts delete mode 100644 playground/src/modes/generated/channel_strip_schema.ts delete mode 100644 playground/src/modes/generated/elysiamorf_schema.ts delete mode 100644 playground/src/modes/generated/index.ts delete mode 100644 playground/src/modes/generated/memlcelium_schema.ts delete mode 100644 playground/src/modes/generated/paf_synth_schema.ts delete mode 100644 playground/src/modes/generated/slp_workshop_schema.ts delete mode 100644 playground/src/modes/generated/sound_analysis_midi_schema.ts delete mode 100644 playground/src/modes/generated/types.ts delete mode 100644 playground/src/modes/generated/verb_fx_schema.ts delete mode 100644 playground/src/modes/generated/xiasri_schema.ts delete mode 100644 playground/src/modes/index.ts delete mode 100644 playground/src/modes/mode-helpers.ts delete mode 100644 playground/src/modes/mode-runtime.ts delete mode 100644 playground/src/output/curves.ts delete mode 100644 playground/src/output/ou-explore.ts delete mode 100644 playground/src/output/pipeline.ts delete mode 100644 playground/src/primitives/ControlAxis.demo.tsx delete mode 100644 playground/src/primitives/ControlAxis.module.css delete mode 100644 playground/src/primitives/ControlAxis.tsx delete mode 100644 playground/src/primitives/Drawer.demo.tsx delete mode 100644 playground/src/primitives/Drawer.module.css delete mode 100644 playground/src/primitives/Drawer.tsx delete mode 100644 playground/src/primitives/GradientFlow.demo.tsx delete mode 100644 playground/src/primitives/GradientFlow.module.css delete mode 100644 playground/src/primitives/GradientFlow.tsx delete mode 100644 playground/src/primitives/Heatmap.demo.tsx delete mode 100644 playground/src/primitives/Heatmap.module.css delete mode 100644 playground/src/primitives/Heatmap.tsx delete mode 100644 playground/src/primitives/JoyMap.demo.tsx delete mode 100644 playground/src/primitives/JoyMap.module.css delete mode 100644 playground/src/primitives/JoyMap.tsx delete mode 100644 playground/src/primitives/LossPlot.demo.tsx delete mode 100644 playground/src/primitives/LossPlot.module.css delete mode 100644 playground/src/primitives/LossPlot.tsx delete mode 100644 playground/src/primitives/OutputDisplay.demo.tsx delete mode 100644 playground/src/primitives/OutputDisplay.module.css delete mode 100644 playground/src/primitives/OutputDisplay.tsx delete mode 100644 playground/src/primitives/ParamEditor.demo.tsx delete mode 100644 playground/src/primitives/ParamEditor.module.css delete mode 100644 playground/src/primitives/ParamEditor.tsx delete mode 100644 playground/src/primitives/PillToggle.demo.tsx delete mode 100644 playground/src/primitives/PillToggle.module.css delete mode 100644 playground/src/primitives/PillToggle.tsx delete mode 100644 playground/src/primitives/ProgressRing.demo.tsx delete mode 100644 playground/src/primitives/ProgressRing.tsx delete mode 100644 playground/src/primitives/Slider.demo.tsx delete mode 100644 playground/src/primitives/Slider.module.css delete mode 100644 playground/src/primitives/Slider.tsx delete mode 100644 playground/src/primitives/SliderBank.demo.tsx delete mode 100644 playground/src/primitives/SliderBank.module.css delete mode 100644 playground/src/primitives/SliderBank.tsx delete mode 100644 playground/src/primitives/TrainingControls.demo.tsx delete mode 100644 playground/src/primitives/TrainingControls.module.css delete mode 100644 playground/src/primitives/TrainingControls.tsx delete mode 100644 playground/src/primitives/VirtualJoystick.demo.tsx delete mode 100644 playground/src/primitives/VirtualJoystick.module.css delete mode 100644 playground/src/primitives/VirtualJoystick.tsx delete mode 100644 playground/src/primitives/WeightHealth.demo.tsx delete mode 100644 playground/src/primitives/WeightHealth.module.css delete mode 100644 playground/src/primitives/WeightHealth.tsx delete mode 100644 playground/src/primitives/XYPad.demo.tsx delete mode 100644 playground/src/primitives/XYPad.module.css delete mode 100644 playground/src/primitives/XYPad.tsx delete mode 100644 playground/src/primitives/index.ts delete mode 100644 playground/src/stores/bus.ts delete mode 100644 playground/src/stores/control-store.ts delete mode 100644 playground/src/stores/exploration-store.ts delete mode 100644 playground/src/stores/index.ts delete mode 100644 playground/src/stores/input-store.ts delete mode 100644 playground/src/stores/ml-store.ts delete mode 100644 playground/src/stores/mode-store.ts delete mode 100644 playground/src/stores/output-store.ts delete mode 100644 playground/src/stores/persistence.ts delete mode 100644 playground/src/stores/session-store.ts delete mode 100644 playground/src/styles/tokens.css delete mode 100644 playground/src/types.d.ts delete mode 100644 playground/tests/e2e/helpers.ts delete mode 100644 playground/tests/e2e/ml-engine.spec.ts delete mode 100644 playground/tests/e2e/modes.spec.ts delete mode 100644 playground/tests/e2e/persistence.spec.ts delete mode 100644 playground/tests/e2e/ui-interactions.spec.ts delete mode 100644 playground/tsconfig.json delete mode 100644 playground/vite.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f81fcbc..4f6530f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,11 @@ name: CI # Stream 11 verification pipeline. # # Two parallel jobs: -# * cpp-tests — builds nisps host C++ tests, builds nisps.wasm, runs -# the parity check, runs the lint script. -# * playground-tests — typechecks the SolidJS playground, builds the -# production bundle, runs Playwright e2e tests. +# * cpp-tests — builds nisps host C++ tests, builds nisps.wasm, runs +# the parity check, runs the lint script. +# * manifold-tests — typechecks the React manifold app, runs bun unit +# tests, builds the production bundle, runs Playwright +# e2e tests. # # Firmware compilation is NOT included in this workflow. Arduino-cli + # rp2040 board package add ~2 minutes per run, and the verification value @@ -84,8 +85,8 @@ jobs: if-no-files-found: ignore retention-days: 7 - playground-tests: - name: Playground typecheck + e2e + manifold-tests: + name: Manifold typecheck + unit + e2e runs-on: ubuntu-latest steps: - name: Checkout @@ -96,24 +97,28 @@ jobs: with: bun-version: latest - - name: Install playground deps - working-directory: playground + - name: Install manifold deps + working-directory: manifold run: bun install --frozen-lockfile - name: Typecheck - working-directory: playground + working-directory: manifold run: bun run typecheck - - name: Build playground bundle - working-directory: playground + - name: Unit tests + working-directory: manifold + run: bun run test + + - name: Build manifold bundle + working-directory: manifold run: bun run build - name: Install Playwright browsers - working-directory: playground + working-directory: manifold run: bunx playwright install --with-deps chromium - name: Run Playwright tests - working-directory: playground + working-directory: manifold run: bunx playwright test - name: Upload Playwright report on failure @@ -121,5 +126,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: playwright-report - path: playground/playwright-report/ + path: manifold/playwright-report/ retention-days: 7 diff --git a/.github/workflows/osc-bridge.yml b/.github/workflows/osc-bridge.yml index f22937f..ec9596d 100644 --- a/.github/workflows/osc-bridge.yml +++ b/.github/workflows/osc-bridge.yml @@ -40,7 +40,7 @@ jobs: - name: Compile run: | - cd playground/osc-bridge + cd manifold/osc-bridge deno compile --allow-net --unstable-net --target ${{ matrix.target }} --output ../../dist/nisps-osc-bridge-${{ matrix.suffix }}${{ matrix.os == 'windows-latest' && '.exe' || '' }} bridge.ts - uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 6f59050..7e410c1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,6 @@ *.la *.a *.lib -# Exception: Faust shared library files (text source, .lib extension by -# Faust convention) should be tracked. -!playground/faust/*.lib - # Executables *.exe *.out @@ -38,8 +34,8 @@ build # OSC bridge compiled binaries (built via compile.sh or CI) -playground/osc-bridge/dist/ -playground/osc-bridge/node_modules/ +manifold/osc-bridge/dist/ +manifold/osc-bridge/node_modules/ # Dolt database files (added by bd init) .dolt/ diff --git a/AGENTS.md b/AGENTS.md index c030131..3222bb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,9 @@ # Agent Instructions — MEMLNaut-NISPS -One C++20 NISPS core targets RP2350 firmware and a SolidJS/WASM playground. Read +One C++20 NISPS core targets RP2350 firmware and the Manifold React/WASM browser app. Read `MAP.md`, `ALIGNMENT.md`, and `docs/AGENT-REFERENCE.md`; for Manifold UI work read -`manifold/ONBOARDING.md` first. Parameter JSON schemas generate both C++ and TypeScript -contracts. +`manifold/ONBOARDING.md` first. Parameter JSON schemas generate the C++ contracts +(TS codegen returns at P5 of `docs/specs/plans/one-core-engine-refactor.md`). ## Gates @@ -12,7 +12,7 @@ bash scripts/build-cpp-tests.sh bash scripts/parity-check.sh bash scripts/lint-cpp.sh scripts/build-firmware.sh [VARIANT] -cd playground && bun run typecheck && bun run build +cd manifold && bun run typecheck && bun run test && bun run build ``` - `nisps/` must remain platform-neutral and allocation-free in hot paths: no heap, no diff --git a/ALIGNMENT.md b/ALIGNMENT.md index 2e6f2b2..db1fcdf 100644 --- a/ALIGNMENT.md +++ b/ALIGNMENT.md @@ -6,17 +6,17 @@ A research platform for interactive ML control of audio. We're building it to figure out what works and what doesn't — different ergonomics and ergodynamics of parameter sets, modes, ML architectures, audio engines, UI, and UX. Therefore: keep most/all parameters tweakable, ML/engine/UI/UX should each be configurable on their own axis, and the codebase has to enable/assist agentic AI coding patterns (confident changes, verifiable without hardware). -The clean-slate rewrite (2026-04-29) consolidates everything into one C++20 codebase compiling to firmware AND WASM, with SolidJS playground primitives composed into TSX mode components, and JSON schemas as the firmware↔browser parameter contract. +The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 codebase compiling to firmware AND WASM. Since 2026-07-13 (P1 of `docs/specs/plans/one-core-engine-refactor.md`) the sole browser app is the React Manifold; the SolidJS playground is archived (`archive/playground-solidjs`). JSON schemas remain the firmware↔browser parameter contract. ## Top defects (ranked by mission impact) -### 1. Browser-only audio engines incomplete (2026-04-29) +### 1. Browser-only audio engines incomplete (2026-04-29; updated 2026-07-13) -**What.** Stream 9 stubbed `C15Mode` as a placeholder ("C15 mode TODO"). The C15 worklet/bridge from the legacy playground is in `.local/playground-archive/js/synth/` but not wired in. Mic input for XIASRI / SoundAnalysisMIDI is also TODO — the UI renders but doesn't capture audio. +**What.** C15 never got past a stubbed placeholder mode, and with the playground retired at P1 (2026-07-13) it has NO home on main — the stub UI, `c15.wasm`, and `c15-glue.js` live only on branch `archive/playground-solidjs`. Mic input for XIASRI / SoundAnalysisMIDI is likewise not wired in manifold. -**Why it blocks the mission.** "Browser engines ⊇ firmware engines" was a non-negotiable. Without C15 + mic input, the playground can't fully demonstrate the modes; users can't audition XIASRI or SoundAnalysisMIDI in the browser. +**Why it blocks the mission.** "Browser engines ⊇ firmware engines" was a non-negotiable. Without C15 + mic input, manifold can't fully demonstrate the modes; users can't audition XIASRI or SoundAnalysisMIDI in the browser. -**Rough cost.** ~1–2 days. C15 wiring is mostly straightforward porting from the archived bridge. Mic input requires the engine-host to expose an input stream to the worklet (small worklet refactor). +**Rough cost.** ~2 days. Reviving C15 now means porting the archived bridge into manifold's engine host. Mic input requires the engine-host to expose an input stream to the worklet (small worklet refactor). ### 2. Per-iteration loss curve not plumbed through WASM (2026-04-29) @@ -61,13 +61,13 @@ Schemas currently declare per-mode `hidden_layers` (some `[10, 10, 14]`, some `[ ### Q2: How to express "advanced" features (gradient flow, weight health) without cluttering modes? (2026-04-29) -Current playground reproduces the a-immersive "Advanced" toggle. Power features hide behind it. Is this the right model, or should the mode UI itself decide what's exposed (some modes are "expert-only", some are simpler)? +The retired playground reproduced the a-immersive "Advanced" toggle; manifold hides power features in drawers instead. Is this the right model, or should the mode UI itself decide what's exposed (some modes are "expert-only", some are simpler)? ### Q3: Engine event taxonomy (2026-04-29) `nisps/modes/base.hpp` exposes a `ControlEvent` ring buffer pop_events interface for sequencer modes (BreakOr, Elysiamorf). Currently events are a flat enum. As we add more event-emitting modes (custom MIDI mappings, lighting, networked control), how should the event vocabulary grow? Open question; revisit when we add the third event-emitting mode. -### Q4: Should the playground stay desktop-first? (2026-04-29) +### Q4: Should the browser app (manifold) stay desktop-first? (2026-04-29) The original a-immersive was mobile-first ("designed for touch / foldable phone use"). The SolidJS rewrite is desktop-first by default. If the research story is "the user holds a phone and pinches to zoom while a synth runs in their pocket", we'll need a responsive pass. Defer until we have user data. diff --git a/MAP.md b/MAP.md index 335f354..0217e32 100644 --- a/MAP.md +++ b/MAP.md @@ -1,6 +1,6 @@ # MAP -MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 codebase (`nisps/`) compiles to two targets: (1) Arduino/RP2350 firmware for the MEMLNaut hardware, (2) WASM in a SolidJS browser playground that runs the same engines + ML through an AudioWorklet. Browser audio engines are a superset of firmware engines (C15 is browser-only). See `CLAUDE.md` for the long-form architecture narrative and `ALIGNMENT.md` for current strategic gaps. +MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 codebase (`nisps/`) compiles to two targets: (1) Arduino/RP2350 firmware for the MEMLNaut hardware, (2) WASM in the Manifold React browser app that runs the same engines + ML through an AudioWorklet. (The former SolidJS playground was retired 2026-07-13 — branch `archive/playground-solidjs`, tag `playground-solidjs-final`; the browser-only C15 engine lives only there for now.) See `CLAUDE.md` for the long-form architecture narrative and `ALIGNMENT.md` for current strategic gaps. ## Layout @@ -29,22 +29,7 @@ MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 cod - `firmware/MEMLCelium-upstream/` — **vendored** verbatim snapshot of the upstream `MusicallyEmbodiedML/MEMLNaut-NISPS` @ `main` Arduino sketch (pre-refactor monorepo, does NOT use `nisps/`), preset to `MODE_MEMLCELIUM`. Self-contained: upstream `memllib`@`e291192d` + `memlp`@`ea777502` vendored as plain files; `src/daisysp` in-tree. Built directly with `arduino-cli` (not the repo build scripts) — see its `README.md` for provenance + the compile command. - `firmware/useq-celium/` — standalone RP2040 firmware (PlatformIO, Arduino-Pico core) that turns a uSEQ module + CV expander into a USB→CV/gate converter driven by the manifold `cvgate` backend. `shared/protocol.h` is the v2 wire-protocol single source of truth (mirrored by `manifold/src/backends/useq-protocol.ts`); `main/` (USB serial → CV1–3 + GATE1–3, I2C → expander) and `expander/` (I2C slave → CV4–11). Wire spec: `docs/specs/useq-cv-protocol.md`. Restored from the April-2026 "uSEQ-Celium" mode. -### `playground/` — SolidJS + Vite + TypeScript app -- `playground/index.html`, `vite.config.ts`, `tsconfig.json`, `package.json` — scaffold. COOP/COEP headers configured. -- `playground/src/main.tsx`, `App.tsx` — entry + router (`/`, `/dev/primitives`, `/modes`). -- `playground/src/primitives/` — 16 UI building blocks: `Slider`, `SliderBank`, `VirtualJoystick`, `XYPad`, `Heatmap`, `OutputDisplay`, `TrainingControls`, `Drawer`, `ControlAxis`, `ProgressRing`, `PillToggle`, `ParamEditor`, `JoyMap`, `WeightHealth`, `GradientFlow`, `LossPlot`. Each has a `.demo.tsx` showcased on `/dev/primitives`. -- `playground/src/modes/` — one TSX per firmware mode (+ `C15Mode` browser-only). `ModeShell.tsx` is the shared scaffold; `ModeSwitcher.tsx` picks the active mode; `mode-runtime.ts` is the schema → ML → audio wiring hook; `mode-helpers.ts` for SliderBank configs. `generated/` holds codegen-produced TS schemas (do not edit). -- `playground/src/stores/` — Solid stores: `ml-store`, `input-store`, `output-store`, `mode-store`, `control-store` (compound axes Boldness/Memory/Precision), `session-store` (snapshots, A/B, presets), `exploration-store`, `bus` (typed signal bus). `persistence.ts` debounces localStorage writes. -- `playground/src/audio/engine-host.ts`, `worklet/nisps-processor.ts` — main-thread engine host + AudioWorklet processor. WASM loaded twice (main thread for ML, worklet for engines). -- `playground/src/ml/wasm-iml.ts`, `wasm-worker.ts`, `dataset.ts`, `types.ts` — main-thread WasmIML class + disposable async-training worker + FIFO dataset. -- `playground/src/input/pipeline.ts`, `playground/src/output/pipeline.ts`, `playground/src/output/curves.ts` — pure-fn pipelines (deadzone→zoom→curve→smoothing→momentum, then global curve→smoothing→slew→freeze). -- `playground/src/features/` — additional feature modules (heatmap sampling, snapshot stack, A/B compare, region pin, param pin, trail, weight health, etc.). -- `playground/src/debug/probe.ts` — synchronous `window.__nisps` debug probe for Playwright. -- `playground/public/nisps.{wasm,js}`, `c15.wasm`, `c15-glue.js` — WASM artifacts. `nisps.*` are a transitional copy from `scripts/build-wasm.sh` (canonical output is `manifold/public/`; copy dies with playground at P1). -- `playground/tests/e2e/` — Playwright specs (`ml-engine`, `modes`, `persistence`, `ui-interactions`) + `helpers.ts`. -- `playground/playwright.config.ts` — Vite preview server setup. - -### `manifold/` — Vite + React + TS convertible-mode app (the NEW front-end, WIP) +### `manifold/` — Vite + React + TS convertible-mode app (the sole browser app) The Manifold "convertible" Console on the real engine, deployed at `meml.lnfinitemonkeys.org/next` (staging, alongside the live vanilla a-immersive at `/`). Built 2026-06-27/28; see `docs/specs/plans/BUILD-PLAN.md` (resume anchor + locked decisions) and the `docs/specs/*-spec.md` set. @@ -102,7 +87,7 @@ the "BUILD DELTAS" block at the top of `docs/specs/vcv-module.md`). `src/MEMLNau - `schemas/midi_devices/.json` (×6) — CC-controllable external synths (Moog Sub 37 / Sub Phatty, Creamware Pro-12 ASB, Elektron Analog Keys, ASM Hydrasynth, Roland JD-800). Each param: `{id, cc, label, min, max, default, group}`. Canonical source for both firmware + browser device pickers. Verified-CC provenance + sources live in `synth-midi-cc.json` (repo root). ### `codegen/` — schema → C++/TS code -- `codegen/generate.ts` — Bun script: validates schemas via ajv, emits per-mode `nisps/modes/generated/_schema.hpp` (`constexpr`, `nisps::modes::generated`) and `playground/src/modes/generated/_schema.ts`. Idempotent. +- `codegen/generate.ts` — Bun script: validates schemas via ajv, emits per-mode `nisps/modes/generated/_schema.hpp` (`constexpr`, `nisps::modes::generated`). Idempotent. (TS emitters retained but dormant; re-targeted at `manifold/src/modes/generated/` in P5.) - `codegen/generate-midi-devices.ts` — separate Bun script (isolated from the mode golden test): validates `schemas/midi_devices/` via ajv, emits `nisps/midi/generated/midi_devices.hpp` (no-heap `constexpr`) and `manifold/src/midi-devices/generated/{types,devices,index}.ts`. Idempotent. - `codegen/seed-midi-devices.ts` — one-time/idempotent seed deriving `schemas/midi_devices/*.json` from the `synth-midi-cc.json` research artifact (slugifies labels → ids, heuristic groups). - `codegen/templates/`, `codegen/tests/golden/` — reference templates + golden snapshot for paf_synth. @@ -113,14 +98,14 @@ the "BUILD DELTAS" block at the top of `docs/specs/vcv-module.md`). `src/MEMLNau ### `scripts/` — build + verify entry points - `build-firmware.sh`, `flash-firmware.sh`, `build-and-flash-firmware.sh`, `firmware-common.sh` — Arduino-CLI wrapper for RP2350 target with C++20 flag. -- `build-wasm.sh` — Emscripten compile producing `manifold/public/nisps.{wasm,js}` (+ transitional copy to `playground/public/` until P1). +- `build-wasm.sh` — Emscripten compile producing `manifold/public/nisps.{wasm,js}`. - `build-cpp-tests.sh` — CMake configure + build + ctest (Ninja). - `parity-check.sh` — runs native + WASM and diffs binary outputs. - `lint-cpp.sh` — `.f` literal warn + heap/`Arduino.h` violation fail. - `run-all-tests.sh` — master verification script. ### `.github/workflows/` -- `ci.yml` — GitHub Actions: cmake build + ctest + WASM build + parity check + lint + Playwright (cpp-tests + playground-tests jobs). Firmware compile is documented as manual. +- `ci.yml` — GitHub Actions: cmake build + ctest + WASM build + parity check + lint + Playwright (cpp-tests + manifold-tests jobs). Firmware compile is documented as manual. ### Submodules (in `src/`) - `src/memllib/` — hardware abstraction (audio driver, peripherals, MIDI). **Not auto-initialized** — fresh clones need `git submodule update --init --recursive`. @@ -136,22 +121,22 @@ the "BUILD DELTAS" block at the top of `docs/specs/vcv-module.md`). `src/MEMLNau ## Entry points - **Firmware**: `scripts/build-firmware.sh [VARIANT]` (interactive prompt if omitted), `scripts/flash-firmware.sh`, `scripts/build-and-flash-firmware.sh`. Target: `rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3`, `-std=gnu++20`. -- **Playground dev**: `cd playground && bun install && bun run dev` (Vite, port 5173, COOP/COEP headers). -- **Playground build**: `cd playground && bun run build`. +- **Manifold dev**: `cd manifold && bun install && bun run dev` (Vite, COOP/COEP headers). +- **Manifold build**: `cd manifold && bun run build`. - **WASM rebuild**: `bash scripts/build-wasm.sh` (needs `emcc`). - **Host C++ tests**: `bash scripts/build-cpp-tests.sh`. - **Parity check**: `bash scripts/parity-check.sh`. - **All tests**: `bash scripts/run-all-tests.sh`. -- **Playwright**: `cd playground && bunx playwright test`. -- **Codegen**: `cd codegen && bun run generate.ts` (regenerates `nisps/modes/generated/` and `playground/src/modes/generated/`). +- **Playwright**: `cd manifold && node node_modules/.bin/playwright test` (non-snap node runner on the VPS — BUILD-PLAN gotcha; `bunx playwright test` works elsewhere). +- **Codegen**: `cd codegen && bun run generate.ts` (regenerates `nisps/modes/generated/`; TS target returns at P5 for manifold). ## Conventions - Firmware mode selection is compile-time only — `#define MEMLNAUT_MODE_TYPE` in the `.ino`. - `nisps/` follows Chris's RP2350 perf rules globally: no heap, `static const float` for non-trivial constants, strict `.f` suffix, memory section attrs (`NISPS_AUDIO_MEM`, `NISPS_AUDIO_FUNC`, `NISPS_APP_SRAM`, `NISPS_HOT`, `NISPS_FORCE_INLINE`). - C++ identifiers: `PascalCase` types, `snake_case` functions/variables, `kPascalCase` constexpr. JSON keys `snake_case`. TS types `PascalCase`, components `PascalCase.tsx`, modules `kebab-case.ts`. -- `Curve` enum lives in `nisps/core/math.hpp` (lowercase: `linear/exp/log/square/sqrt/sigmoid/cubic`); generated mode headers re-export via `using Curve = ::nisps::Curve;`. TS mirror at `playground/src/output/curves.ts` with same names. -- Modes are TSX components composed of primitives; mode parameter contracts are JSON schemas with codegen → C++/TS types. **No declarative JSON UI.** +- `Curve` enum lives in `nisps/core/math.hpp` (lowercase: `linear/exp/log/square/sqrt/sigmoid/cubic`); generated mode headers re-export via `using Curve = ::nisps::Curve;`. TS mirror at `manifold/src/engine/curves.ts` with same names (moves into core at P4). +- Modes are TSX components composed of primitives; mode parameter contracts are JSON schemas with codegen → C++ types (TS codegen returns at P5). **No declarative JSON UI.** - WASM and firmware share the same C++; WASM is fixed at `MLP<32, 10, 14, 18, 126>` (`nisps_ml_create` ignores requested dims — see `plans/one-core-engine-refactor.md` P2) and modes use a slice of outputs based on schema's `output_size`. - Cross-platform parity: `scripts/parity-check.sh` enforces native vs WASM agreement within 1e-5. diff --git a/README.md b/README.md index cf16bab..14260d3 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,22 @@ Notes: - `build-firmware.sh` accepts an optional variant name such as `MEMLCelium` or `BreakOr`. Matching remains case-insensitive, so `memlcelium` still works. If you omit it in an interactive shell, the script parses `MEMLNaut-NISPS.ino`, prompts for a variant, and rewrites the active `MEMLNAUT_MODE_TYPE` before building. - `flash-firmware.sh` accepts an optional mountpoint argument, or auto-detects common UF2 bootloader mounts such as `/run/media/$USER/RP2350` and `/run/media/$USER/RPI-RP2`. -## Web Playground +## Manifold (browser app) -Try NISPS in your browser — no hardware required: +Try NISPS in your browser — no hardware required. Manifold is the React front-end +running the same C++ engines + ML as the firmware, compiled to WASM: ```bash -cd playground -python3 -m http.server -# Open http://localhost:8000 +cd manifold +bun install +bun run dev ``` -Train a neural network to map joystick positions to generative visuals through interactive machine learning. Two learning modes: direct example mapping and reinforcement learning with thumbs up/down feedback. +Staging deployment: https://meml.lnfinitemonkeys.org/next/ -The playground UI includes an **Expand** toggle on the visual surface so you can make the canvas nearly full-screen while compressing parameter/control panels into a minimal strip beneath it. +Train a neural network to map input gestures to synth parameters through interactive +machine learning: place examples, or use verdict-based feedback (explore-and-place, +geometric dislike). + +(The former SolidJS playground was retired in July 2026 — archived on branch +`archive/playground-solidjs`, tag `playground-solidjs-final`.) diff --git a/codegen/generate.ts b/codegen/generate.ts index ab96afe..94acc9d 100644 --- a/codegen/generate.ts +++ b/codegen/generate.ts @@ -7,9 +7,11 @@ * * Writes: nisps/modes/generated/_schema.hpp * nisps/modes/generated/schema_types.hpp - * playground/src/modes/generated/_schema.ts - * playground/src/modes/generated/types.ts - * playground/src/modes/generated/index.ts + * + * The TS emission target (formerly playground/src/modes/generated/) was + * removed with the playground at P1 of + * docs/specs/plans/one-core-engine-refactor.md; the TS emitters below are + * retained and re-targeted at manifold/src/modes/generated/ in P5. * * Idempotent: regenerating the same schemas yields byte-identical output. * Exits non-zero on validation failure. @@ -64,7 +66,6 @@ const SCHEMAS_DIR = join(REPO_ROOT, "schemas"); const MODES_DIR = join(SCHEMAS_DIR, "modes"); const META_SCHEMA_PATH = join(SCHEMAS_DIR, "schema.json"); const CPP_OUT_DIR = join(REPO_ROOT, "nisps", "modes", "generated"); -const TS_OUT_DIR = join(REPO_ROOT, "playground", "src", "modes", "generated"); // ----- Helpers -------------------------------------------------------------- @@ -374,6 +375,8 @@ function emitModeHpp(schema: ModeSchema, sourceFile: string): string { } // ----- Per-mode TS emission ------------------------------------------------- +// Currently unused: the playground TS target was removed at P1; these emitters +// return in P5 targeting manifold/src/modes/generated/ (one-core-engine plan). function emitModeTs(schema: ModeSchema, sourceFile: string): string { const constName = `${toPascalCase(schema.mode_id)}Schema`; @@ -545,22 +548,9 @@ function main(): number { writeFileSync(out, emitModeHpp(schema, source)); } - // 5. Emit TS outputs - ensureDir(TS_OUT_DIR); - writeFileSync(join(TS_OUT_DIR, "types.ts"), emitSharedTsTypes()); - for (const { source, schema } of schemas) { - const out = join(TS_OUT_DIR, `${schema.mode_id}_schema.ts`); - writeFileSync(out, emitModeTs(schema, source)); - } - writeFileSync( - join(TS_OUT_DIR, "index.ts"), - emitTsIndex(schemas.map(s => s.schema.mode_id).sort()) - ); - - // 6. Report + // 5. Report console.log(`OK ${schemas.length} mode schema(s) processed.`); console.log(` C++ -> ${CPP_OUT_DIR}`); - console.log(` TS -> ${TS_OUT_DIR}`); for (const { schema } of schemas) { console.log( ` - ${schema.mode_id}: ${schema.params.length} params, ` + diff --git a/codegen/tests/golden_test.ts b/codegen/tests/golden_test.ts index ba2777e..8c07620 100644 --- a/codegen/tests/golden_test.ts +++ b/codegen/tests/golden_test.ts @@ -1,13 +1,17 @@ #!/usr/bin/env bun /** * Golden test: re-runs codegen and asserts that the freshly-generated - * paf_synth_schema.{hpp,ts} files are byte-identical to the snapshots in + * paf_synth_schema.hpp is byte-identical to the snapshot in * codegen/tests/golden/. * + * (The TS golden case was removed with the playground target at P1 of + * docs/specs/plans/one-core-engine-refactor.md; it returns in P5 against + * manifold/src/modes/generated/. The golden/paf_synth_schema.ts snapshot is + * kept as the P5 template reference.) + * * If you change the codegen template intentionally, regenerate the goldens: * bun run generate.ts * cp ../nisps/modes/generated/paf_synth_schema.hpp tests/golden/ - * cp ../playground/src/modes/generated/paf_synth_schema.ts tests/golden/ */ import { readFileSync, existsSync } from "node:fs"; @@ -31,11 +35,6 @@ const CASES: Case[] = [ generatedPath: join(REPO_ROOT, "nisps", "modes", "generated", "paf_synth_schema.hpp"), goldenPath: join(GOLDEN_DIR, "paf_synth_schema.hpp"), }, - { - name: "paf_synth_schema.ts", - generatedPath: join(REPO_ROOT, "playground", "src", "modes", "generated", "paf_synth_schema.ts"), - goldenPath: join(GOLDEN_DIR, "paf_synth_schema.ts"), - }, ]; function diff(name: string, expected: string, actual: string): string { diff --git a/docs/AGENT-REFERENCE.md b/docs/AGENT-REFERENCE.md index bb5acd7..894433d 100644 --- a/docs/AGENT-REFERENCE.md +++ b/docs/AGENT-REFERENCE.md @@ -10,9 +10,9 @@ This file provides guidance to coding agents working with this repository. MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. A research platform for interactive ML control of audio. **One C++20 codebase** (`nisps/`) compiles to two targets: 1. **RP2350 firmware** for the MEMLNaut hardware platform (`firmware/`). -2. **WASM** in a SolidJS browser playground (`playground/`) — same engines + ML, run through an AudioWorklet. +2. **WASM** in the Manifold React browser app (`manifold/`) — same engines + ML, run through an AudioWorklet. -Browser audio engines are a superset of firmware engines (C15 is browser-only). Parameter contracts are JSON schemas (`schemas/`) with codegen producing both C++ headers and TypeScript types. +(The former SolidJS playground was retired 2026-07-13 at P1 of `docs/specs/plans/one-core-engine-refactor.md`; archived on branch `archive/playground-solidjs`, tag `playground-solidjs-final`. The browser-only C15 engine currently lives only there.) Parameter contracts are JSON schemas (`schemas/`) with codegen producing the C++ headers (TS emission returns at P5). Project documentation: https://musicallyembodiedml.github.io/memlnaut/approaches/nisps @@ -76,36 +76,24 @@ Build: `scripts/build-firmware.sh [VARIANT]`. Verified compiling for PAFSynth, C - **Core 1**: Real-time audio processing (`Mode::process`), MIDI polling. - **Sync**: `nisps::core::ring_buffer` (templated SPSC lock-free, replaces pico/util/queue) + memory barriers (`nisps::core::memory_barrier`, `write_volatile`/`read_volatile`). -## The `playground/` target +## The `manifold/` target ``` -playground/ # Vite + SolidJS + TypeScript +manifold/ # Vite + React + TypeScript (the sole browser app) ├── src/ -│ ├── primitives/ # 16 UI building blocks (Slider, JoyMap, Heatmap, …) + .demo.tsx for /dev/primitives -│ ├── modes/ # one TSX per firmware mode + C15Mode (browser-only); ModeShell + ModeSwitcher + mode-runtime -│ ├── stores/ # Solid stores (ml, input, output, mode, control, session, exploration, bus + persistence) -│ ├── audio/ # engine-host + AudioWorklet processor (loads nisps.wasm separately on each thread) -│ ├── ml/ # WasmIML class + disposable async-training Worker + dataset -│ ├── input/, output/ # pure-fn pipelines (deadzone→zoom→curve→smoothing→momentum, then global curve→smoothing→slew→freeze) -│ ├── features/ # heatmap, snapshots, A/B compare, region/param pin, trail, weight health, gradient flow -│ └── debug/probe.ts # synchronous window.__nisps for Playwright -├── public/ # nisps.{wasm,js}, c15.{wasm,glue} -└── tests/e2e/ # Playwright specs + helpers +│ ├── engine/ # framework-neutral TS engine spine: wasm-iml, engine-host + worklet, +│ │ # input/output pipelines + curves (move into core at P4), spine.ts, EngineProvider +│ ├── primitives/ # 12 design primitives as typed React +│ ├── console/ # convertible Console (CompositeStage, Dock, Drawers, Manifold canvas, …) +│ ├── dock/, backends/, inputs/, feedback/, settings/, serial/, midi-devices/ +│ └── debug/probe.ts # window.__nisps behind ?debug=1 for Playwright +├── public/ # nisps.{wasm,js} — canonical build-wasm.sh output +└── tests/ # e2e Playwright specs + fixtures/ (P4 golden parity fixtures) ``` -Dev: `cd playground && bun install && bun run dev`. Build: `bun run build`. Typecheck: `bun run typecheck`. E2E: `bunx playwright test`. +Dev: `cd manifold && bun install && bun run dev`. Build: `bun run build`. Typecheck: `bun run typecheck`. Unit: `bun run test`. E2E: `bunx playwright test` (on the VPS run the runner via non-snap node — BUILD-PLAN gotcha). -### Stores + reactivity - -All stores use SolidJS `createStore` for objects, `createSignal` for primitives. ML outputs are stored in a separate Float32Array signal (per the migration plan's perf guidance). Persistence (debounced 200ms localStorage round-trip) wired in `playground/src/stores/persistence.ts`. The signal bus (`bus.ts`) handles cross-store events (`ml.*`, `mode.*`, `pin.*`, `ui.*`). - -### Control surface - -Three compound axes (Boldness / Memory / Precision) interpolate per-axis tables to drive ~6 underlying parameters each, with offset overrides ("trim-pot" model). State is in `control-store`. Six built-in control presets (Default, First Touch, Jazz Hands, Sculptor, Improviser, Microscope) available via the ModeShell control bar. - -### Debug probe (Playwright) - -`window.__nisps` is exposed synchronously and bypasses Solid reactivity (uses `untrack`/`batch`). API matches the `.local/recon/04-playground.md` spec — `setInputs`, `getOutputs`, `getLoss`, `train`, `thumbsUp`/`thumbsDown`, `randomise`, `clearExamples`, `inferBatch`, `getLayerStats`, `saveState`, etc. +Read `manifold/ONBOARDING.md` before touching manifold UI — layering, Stages, Dock, gotchas. ## The `schemas/` + `codegen/` contract @@ -113,7 +101,7 @@ Each mode has a `schemas/modes/.json` describing its parameters (name, lab Codegen (`bun run codegen/generate.ts`) emits: - `nisps/modes/generated/_schema.hpp` — `constexpr` C++ data, namespace `nisps::modes::generated`, re-exports `nisps::Curve` from `nisps/core/math.hpp`. -- `playground/src/modes/generated/_schema.ts` — typed const objects + per-mode params interface. +- (TS emission is dormant since P1; it returns at P5 targeting `manifold/src/modes/generated/`.) Codegen is idempotent. Golden test ensures regenerating produces byte-identical output. @@ -121,29 +109,28 @@ Codegen is idempotent. Golden test ensures regenerating produces byte-identical Two WASM instances at runtime: -1. **Main thread** (`playground/src/ml/wasm-iml.ts`): ML inference + sync training + RL primitives. Update store after each call. Async training via disposable Web Worker (`wasm-worker.ts`). -2. **AudioWorklet** (`playground/src/audio/worklet/nisps-processor.ts`): runs engine `process_block` per audio block. Loads `nisps.wasm` directly via `WebAssembly.compile` (no Emscripten glue in worklet). Bytes posted from main thread. +1. **Main thread** (`manifold/src/engine/wasm-iml.ts`): ML inference + sync training + RL primitives, reporting into an injected `EngineSink`. Async training via disposable Web Worker (`wasm-worker.ts`). +2. **AudioWorklet** (`manifold/src/engine/worklet/nisps-processor.ts`): runs engine `process_block` per audio block. Loads `nisps.wasm` directly via `WebAssembly.compile` (no Emscripten glue in worklet). Bytes posted from main thread. -C API is in `nisps/wasm/bindings.cpp`. Build: `bash scripts/build-wasm.sh` (~94KB output to `manifold/public/`, transitional copy to `playground/public/` until P1). +C API is in `nisps/wasm/bindings.cpp`. Build: `bash scripts/build-wasm.sh` (~94KB output to `manifold/public/`). The WASM target is fixed at `MLP<32u, 10u, 14u, 18u, 126u>` (`nisps_ml_create` ignores requested dims — see `docs/specs/plans/one-core-engine-refactor.md` P2). Modes with smaller `input_size`/`output_size` use a slice. ### Known limitations -- Loss history not yet plumbed through C API; `lossHistory` in the store is a single-element array per training run. -- Engine MLP architecture is fixed at compile time — supporting per-mode hidden-layer shapes would need either multiple WASM modules or runtime variation. -- Mic input through the worklet for XIASRI / SoundAnalysisMIDI is not wired; UI scaffolds render but feature is TODO. -- C15 voice space integration in C15Mode is a placeholder. -- The browser Jolt/OU controls (`playground/src/ml/jolt.ts`, `playground/src/output/ou-explore.ts`) reimplement the gesture math in TS rather than calling the C++ `ml::Jolt`/`ml::OUNoise` through WASM. They drive weights via the existing `nisps_ml_get/set_weights` bindings and use `Math.random()` (not the deterministic `Rng`) — fine for stochastic exploration aids, but firmware↔browser bit-parity of the noise itself is intentionally not guaranteed. +- Loss history not yet plumbed through C API; only the final loss of a training run reaches TS. +- Engine MLP architecture is fixed at compile time — runtime-shaped browser MLP arrives at P2 of `docs/specs/plans/one-core-engine-refactor.md`. +- Mic input through the worklet for XIASRI / SoundAnalysisMIDI is not wired in manifold. +- C15 has no home on main (see `ALIGNMENT.md` defect #1). +- The browser Jolt/OU controls in manifold reimplement the gesture math in TS (interim, ported from the retired playground) rather than calling the C++ `ml::Jolt`/`ml::OUNoise` through WASM. They drive weights via the existing `nisps_ml_get/set_weights` bindings — the P3 phase of the one-core-engine plan replaces them with `nisps_ml_jolt_press/release` + `nisps_ml_explore_intensity` bindings. -## URL parameters (playground) +## URL parameters (manifold) | Param | Range | Default | Effect | |-------|-------|---------|--------| -| `tame` | 0–1 | 1 | Constrains synth output ranges toward safe limits. | -| `spread` | 0–1 | 0.6 | Master noise regime (init scale, RL noise cap, per-layer Xavier scaling, weight decay). | -| `preset` | preset id | _(none)_ | Auto-loads a synth preset on first visit. | -| `debug` | 1 | _(off)_ | Exposes `window.__nisps` debug probe. | +| `debug` | 1 | _(off)_ | Exposes the `window.__nisps` debug probe. | + +(The playground-era `tame`/`spread`/`preset` URL params died with the playground; `spread` survives as an engine concept — see below.) ### `spread` — sigmoid saturation control @@ -187,12 +174,13 @@ scripts/build-firmware.sh PAFSynth # or any other variant scripts/flash-firmware.sh scripts/build-and-flash-firmware.sh -# Playground -cd playground && bun install +# Manifold +cd manifold && bun install bun run dev # Vite dev (COOP/COEP enabled) bun run typecheck +bun run test # bun unit tests bun run build -bunx playwright test +bunx playwright test # on the VPS: node node_modules/.bin/playwright test # All tests bash scripts/run-all-tests.sh diff --git a/docs/specs/MAIN.md b/docs/specs/MAIN.md index d5f4c37..cf30605 100644 --- a/docs/specs/MAIN.md +++ b/docs/specs/MAIN.md @@ -6,7 +6,7 @@ layer: cross-cutting # MEMLNaut-NISPS Spec Corpus -**Neural Interactive Shaping of Parameter Spaces** — a research platform for interactive ML control of audio. One C++20 codebase compiles to two targets: RP2350 firmware for the MEMLNaut hardware, and WASM in a SolidJS browser playground running the same engines + ML through an AudioWorklet. +**Neural Interactive Shaping of Parameter Spaces** — a research platform for interactive ML control of audio. One C++20 codebase compiles to two targets: RP2350 firmware for the MEMLNaut hardware, and WASM in the Manifold React browser app running the same engines + ML through an AudioWorklet. (The SolidJS playground was retired 2026-07-13, P1 of plans/one-core-engine-refactor.md; archived on `archive/playground-solidjs`.) --- @@ -30,8 +30,7 @@ The codebase's most architecturally significant areas referenced by this corpus: | **Shared C++20** | `nisps/core/`, `nisps/ml/`, `nisps/dsp/`, `nisps/engines/`, `nisps/modes/` | One audio+ML library compiling to both firmware and WASM | | **Schema/Codegen** | `schemas/`, `codegen/` | Parameter contracts + code generation (C++ headers, TS types) | | **Firmware** | `firmware/MEMLNaut-NISPS/glue/` | Hardware bindings (audio, peripherals, MIDI, settings) | -| **Browser** | `playground/src/engine/`, `playground/src/audio/` | Headless TS engine + AudioWorklet, wired to Solid stores | -| **Manifold** | `manifold/src/engine/`, `manifold/src/console/` | Same TS engine + React wrapper (the new convertible front-end) | +| **Browser (Manifold)** | `manifold/src/engine/`, `manifold/src/console/` | Headless TS engine + AudioWorklet + the convertible React Console | --- @@ -40,7 +39,7 @@ The codebase's most architecturally significant areas referenced by this corpus: **What it is.** MEMLNaut-NISPS is a single C++20 codebase that compiles once to two distinct targets: 1. **Firmware** — runs on RP2350 hardware (MEMLNaut instrument). Real-time audio engines (8 variants) + interactive ML (4-layer MLP, SGD + RL feedback) with deterministic RNG + dual-core orchestration (audio on core 1, control on core 0). -2. **Browser (WASM)** — runs in a SolidJS playground (or React Manifold front-end) via AudioWorklet. Same C++20 engines + ML, compiled to WASM; audio engines are a superset of firmware (C15 synth browser-only). +2. **Browser (WASM)** — runs in the React Manifold front-end via AudioWorklet. Same C++20 engines + ML, compiled to WASM; audio engines are a superset of firmware (C15 synth browser-only). **The unifying constraint**: one source tree, one ML architecture, cross-platform parity (native ↔ WASM within 1e-5 numerical tolerance). Parameter shapes are JSON schemas with codegen producing both C++ headers and TypeScript types. @@ -65,7 +64,7 @@ The codebase's most architecturally significant areas referenced by this corpus: Each firmware mode has a `schemas/modes/.json` describing parameters (name, label, range, default, curve, group), ML config (input/output sizes, hidden layers), voice spaces (names; bodies are inline lambdas in the engine), and UI config. The meta-schema at `schemas/schema.json` validates these. Codegen (`bun run codegen/generate.ts`) is idempotent and produces: - `nisps/modes/generated/_schema.hpp` — `constexpr` C++ data under `nisps::modes::generated`. -- `playground/src/modes/generated/_schema.ts` — typed const objects + per-mode params interface. +- TS schema emission is dormant since P1; it returns at P5 targeting `manifold/src/modes/generated/`. Regenerate after editing any `schemas/modes/*.json`. Golden test ensures output is byte-identical. diff --git a/playground/.gitignore b/playground/.gitignore deleted file mode 100644 index dfa9518..0000000 --- a/playground/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -dist -.vite -*.log -.DS_Store diff --git a/playground/dist/assets/PrimitivesShowcase-B55FOguW.css b/playground/dist/assets/PrimitivesShowcase-B55FOguW.css new file mode 100644 index 0000000..097442d --- /dev/null +++ b/playground/dist/assets/PrimitivesShowcase-B55FOguW.css @@ -0,0 +1 @@ +._root_1attb_1{max-width:1200px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-5)}._header_1attb_9{display:flex;flex-direction:column;gap:var(--sp-2)}._header_1attb_9 h1{margin:0;font-size:var(--fs-xl);color:var(--accent)}._lede_1attb_21{color:var(--fg-mute);margin:0;max-width:720px}._grid_1attb_27{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:var(--sp-4)}._card_1attb_33{background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-3);padding:var(--sp-4);display:flex;flex-direction:column;gap:var(--sp-3);min-height:280px}._cardHead_1attb_44{display:flex;flex-direction:column;gap:var(--sp-1);border-bottom:1px solid var(--line);padding-bottom:var(--sp-2)}._cardTitle_1attb_52{margin:0;font-size:var(--fs-md);color:var(--fg)}._cardDesc_1attb_58{margin:0;font-size:var(--fs-xs);color:var(--fg-mute)}._cardBody_1attb_64{display:flex;flex-direction:column;align-items:stretch;flex:1;justify-content:flex-start}._bank_3njyn_1{display:flex;flex-direction:column;gap:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3)}._title_3njyn_11{font-size:var(--fs-md);margin:0 0 var(--sp-2) 0;color:var(--fg)}._section_3njyn_17{display:flex;flex-direction:column;gap:var(--sp-2)}._sectionHeader_3njyn_23{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-1) var(--sp-2);background:var(--bg-2);border:1px solid var(--line);text-align:left;width:100%;font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute)}._caret_3njyn_38{width:1ch;display:inline-block}._sectionName_3njyn_43{flex:1}._sectionCount_3njyn_47{color:var(--fg-dim)}._list_3njyn_51{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-1) 0} diff --git a/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js b/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js new file mode 100644 index 0000000..d5fc798 --- /dev/null +++ b/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js @@ -0,0 +1,2 @@ +import{c as d,i as o,a as u,S,t as p,b as D,d as x,e as z,s as O,F as R,f as F,V as j,X as V,o as M,g as T,h as P,H as I,P as k,O as L,T as q,D as B,C as H,j as A,k as J,J as G,W as U,G as X,L as W}from"./index-C1-a2ZPT.js";const Y="_root_1attb_1",Z="_header_1attb_9",K="_lede_1attb_21",Q="_grid_1attb_27",ee="_card_1attb_33",te="_cardHead_1attb_44",ne="_cardTitle_1attb_52",ae="_cardDesc_1attb_58",le="_cardBody_1attb_64",b={root:Y,header:Z,lede:K,grid:Q,card:ee,cardHead:te,cardTitle:ne,cardDesc:ae,cardBody:le};var re=p("
");const ie=()=>{const[a,i]=d(.5),[r,e]=d(20),[l,t]=d(0);return(()=>{var n=re();return o(n,u(S,{get value(){return a()},onChange:i,min:0,max:1,step:.01,label:"linear"}),null),o(n,u(S,{get value(){return r()},onChange:e,min:1,max:1e3,curve:"exp",label:"exp (1..1000)",unit:"hz"}),null),o(n,u(S,{get value(){return l()},onChange:t,min:-50,max:50,step:1,label:"bipolar (-50..50)"}),null),n})()},oe="_bank_3njyn_1",se="_title_3njyn_11",ce="_section_3njyn_17",ue="_sectionHeader_3njyn_23",de="_caret_3njyn_38",me="_sectionName_3njyn_43",pe="_sectionCount_3njyn_47",ve="_list_3njyn_51",$={bank:oe,title:se,section:ce,sectionHeader:ue,caret:de,sectionName:me,sectionCount:pe,list:ve};var he=p("

"),N=p("
"),ge=p("\n \n \n
\n {({ cfg, index }) => (\n props.onChange(cfg.id, v, index)}\n min={cfg.min}\n max={cfg.max}\n step={cfg.step}\n curve={cfg.curve}\n curveParam={cfg.curveParam}\n label={cfg.label}\n unit={cfg.unit}\n />\n )}\n
\n
\n \n )}\n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { SliderBank, type SliderConfig } from './SliderBank';\n\nconst CFG: SliderConfig[] = [\n { id: 'cutoff', label: 'Cutoff', min: 20, max: 20000, curve: 'exp', section: 'Filter', unit: 'Hz' },\n { id: 'res', label: 'Resonance', min: 0, max: 1, step: 0.01, section: 'Filter' },\n { id: 'attack', label: 'Attack', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n { id: 'decay', label: 'Decay', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n { id: 'sustain', label: 'Sustain', min: 0, max: 1, step: 0.01, section: 'Envelope' },\n { id: 'release', label: 'Release', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n];\n\nexport const SliderBankDemo: Component = () => {\n const [values, setValues] = createSignal([8000, 0.3, 0.05, 0.4, 0.7, 1.0]);\n return (\n setValues((prev) => prev.map((x, j) => (j === i ? v : x)))}\n title=\"Synth\"\n collapsedSections={[]}\n />\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { VirtualJoystick } from './VirtualJoystick';\n\nexport const VirtualJoystickDemo: Component = () => {\n const [pos, setPos] = createSignal([0.5, 0.5]);\n return (\n
\n setPos([x, y])}\n />\n \n x={pos()[0].toFixed(3)} y={pos()[1].toFixed(3)}\n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { XYPad } from './XYPad';\n\nexport const XYPadDemo: Component = () => {\n const [pos, setPos] = createSignal([0.5, 0.5]);\n return (\n
\n setPos([x, y])} />\n \n x={pos()[0].toFixed(3)} y={pos()[1].toFixed(3)}\n \n
\n );\n};\n","import { Component, createMemo, createSignal, onCleanup, onMount } from 'solid-js';\nimport { Heatmap, type HeatmapColorMode } from './Heatmap';\nimport { PillToggle } from './PillToggle';\n\nconst N = 16;\n\nfunction generateField(seed: number): Float32Array {\n const arr = new Float32Array(N * N);\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const fx = (x - N / 2) / (N / 2);\n const fy = (y - N / 2) / (N / 2);\n const r = Math.sqrt(fx * fx + fy * fy);\n const v = 0.5 + 0.5 * Math.sin(r * 3 + seed * 0.5) * Math.cos(fx * 4 + seed);\n arr[y * N + x] = Math.max(0, Math.min(1, v));\n }\n }\n return arr;\n}\n\nexport const HeatmapDemo: Component = () => {\n const [mode, setMode] = createSignal('luminance');\n const [tick, setTick] = createSignal(0);\n let timer: ReturnType;\n onMount(() => {\n timer = setInterval(() => setTick((t) => t + 1), 200);\n });\n onCleanup(() => clearInterval(timer));\n\n const samples = createMemo(() => generateField(tick()));\n\n return (\n
\n \n setMode(v as HeatmapColorMode)}\n />\n
\n );\n};\n","import { Component, createMemo, createSignal, onCleanup, onMount } from 'solid-js';\nimport { OutputDisplay } from './OutputDisplay';\n\nexport const OutputDisplayDemo: Component = () => {\n const [tick, setTick] = createSignal(0);\n let raf = 0;\n let last = performance.now();\n const animate = (t: number) => {\n if (t - last > 80) {\n setTick((x) => x + 1);\n last = t;\n }\n raf = requestAnimationFrame(animate);\n };\n onMount(() => { raf = requestAnimationFrame(animate); });\n onCleanup(() => cancelAnimationFrame(raf));\n\n const values = createMemo(() => {\n const t = tick();\n const arr = new Float32Array(126);\n for (let i = 0; i < arr.length; i++) {\n arr[i] = 0.5 + 0.5 * Math.sin(t * 0.05 + i * 0.21);\n }\n return arr;\n });\n\n return (\n
\n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { TrainingControls } from './TrainingControls';\n\nexport const TrainingControlsDemo: Component = () => {\n const [count, setCount] = createSignal(3);\n const [loss, setLoss] = createSignal(0.0123);\n const [busy, setBusy] = createSignal(false);\n const [stack, setStack] = createSignal([]);\n\n const train = () => {\n setBusy(true);\n setTimeout(() => {\n setLoss(Math.random() * 0.05);\n setBusy(false);\n }, 250);\n };\n\n return (\n setLoss(0.5)}\n onThumbsUp={() => { setStack((s) => [...s, count()]); setCount((c) => c + 1); train(); }}\n onThumbsDown={() => { setStack((s) => [...s, count()]); setLoss((l) => (l ?? 0) * 1.4); }}\n onUndo={() => { const prev = [...stack()]; const v = prev.pop(); if (v !== undefined) setCount(v); setStack(prev); }}\n exampleCount={count}\n lastLoss={loss}\n busy={busy}\n canUndo={() => stack().length > 0}\n />\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { Drawer } from './Drawer';\n\nexport const DrawerDemo: Component = () => {\n const [openR, setOpenR] = createSignal(false);\n const [openL, setOpenL] = createSignal(false);\n return (\n
\n \n \n setOpenL(false)} side=\"left\" title=\"Left drawer\">\n

Slides in from the left.

\n

Press Escape, click outside, or click × to close.

\n
\n setOpenR(false)} side=\"right\" title=\"Right drawer\">\n

Slides in from the right.

\n

Mode-specific drawers will use this primitive.

\n
\n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { ControlAxis } from './ControlAxis';\n\nexport const ControlAxisDemo: Component = () => {\n const [boldness, setBoldness] = createSignal(0.5);\n const [memory, setMemory] = createSignal(0.7);\n const [precision, setPrecision] = createSignal(0.3);\n const [preset, setPreset] = createSignal('default');\n\n const onAny = () => setPreset(null);\n\n return (\n
\n { setBoldness(v); onAny(); }}\n preset={preset}\n endpoints={['Caution', 'Bold']}\n accentColor=\"var(--accent)\"\n onDoubleTap={() => setBoldness(0.5)}\n />\n { setMemory(v); onAny(); }}\n preset={preset}\n endpoints={['Amnesia', 'Elephant']}\n accentColor=\"var(--accent-2)\"\n onDoubleTap={() => setMemory(0.5)}\n />\n { setPrecision(v); onAny(); }}\n preset={preset}\n endpoints={['Raw', 'Precise']}\n accentColor=\"var(--good)\"\n onDoubleTap={() => setPrecision(0.3)}\n />\n
\n );\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { ProgressRing } from './ProgressRing';\n\nexport const ProgressRingDemo: Component = () => {\n const [p, setP] = createSignal(0);\n let timer: ReturnType;\n onMount(() => {\n timer = setInterval(() => {\n setP((q) => (q + 0.02) % 1);\n }, 50);\n });\n onCleanup(() => clearInterval(timer));\n return (\n
\n \n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { PillToggle } from './PillToggle';\n\nexport const PillToggleDemo: Component = () => {\n const [mode, setMode] = createSignal('synth');\n const [size, setSize] = createSignal('m');\n return (\n
\n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { ParamEditor, type ParamDef, type ParamOverride } from './ParamEditor';\n\nconst PARAM: ParamDef = {\n name: 'osc1_freq',\n label: 'Osc 1 Freq',\n hardMin: 20,\n hardMax: 8000,\n default: 440,\n curve: 'exp',\n group: 'oscillators',\n};\n\nexport const ParamEditorDemo: Component = () => {\n const [override, setOverride] = createSignal({\n min: 100,\n max: 2000,\n curve: 'exp',\n muted: false,\n pinned: false,\n fixedValue: 440,\n });\n return ;\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { JoyMap, type TrailPoint, type RegionPin } from './JoyMap';\nimport { Slider } from './Slider';\n\nexport const JoyMapDemo: Component = () => {\n const [zoom, setZoom] = createSignal(0.5);\n const [pos, setPos] = createSignal([0.5, 0.5]);\n const [trail, setTrail] = createSignal([]);\n const [pins] = createSignal([\n { id: 'p1', x: 0.6, y: 0.5, width: 0.18, height: 0.18, colorSlot: 0 },\n { id: 'p2', x: 0.18, y: 0.18, width: 0.12, height: 0.12, colorSlot: 1 },\n ]);\n const [frozen, setFrozen] = createSignal(false);\n\n // Simulated wandering position\n let raf = 0;\n let t0 = performance.now();\n const animate = () => {\n const t = (performance.now() - t0) / 1000;\n const x = 0.5 + 0.4 * Math.sin(t * 0.4);\n const y = 0.5 + 0.4 * Math.cos(t * 0.65);\n setPos([x, y]);\n setTrail((prev) => {\n const next = [...prev, { x, y, t: performance.now() }];\n // Cap trail length to avoid runaway growth\n return next.slice(-200);\n });\n raf = requestAnimationFrame(animate);\n };\n onMount(() => { raf = requestAnimationFrame(animate); });\n onCleanup(() => cancelAnimationFrame(raf));\n\n return (\n
\n [0.18, 0.06]}\n onTrailTap={(p) => setPos([p.x, p.y])}\n frozen={frozen}\n />\n \n \n
\n );\n};\n","import { Component, createMemo, createSignal } from 'solid-js';\nimport { WeightHealth, type WeightStatus } from './WeightHealth';\nimport { PillToggle } from './PillToggle';\n\nexport const WeightHealthDemo: Component = () => {\n const [status, setStatus] = createSignal('healthy');\n\n const histogram = createMemo(() => {\n const bins = new Array(10).fill(0);\n const s = status();\n if (s === 'dead') {\n bins[0] = 80; bins[1] = 12; bins[2] = 4; bins[3] = 2;\n } else if (s === 'saturating') {\n bins[7] = 12; bins[8] = 24; bins[9] = 60;\n } else {\n // healthy bell-ish\n for (let i = 0; i < 10; i++) {\n bins[i] = 30 * Math.exp(-Math.pow((i - 4.5) / 2.5, 2));\n }\n }\n return bins;\n });\n\n return (\n
\n \n setStatus(v as WeightStatus)}\n />\n
\n );\n};\n","import { Component, createMemo, createSignal } from 'solid-js';\nimport { GradientFlow, type GradientStatus } from './GradientFlow';\nimport { PillToggle } from './PillToggle';\n\nexport const GradientFlowDemo: Component = () => {\n const [pattern, setPattern] = createSignal<'healthy' | 'vanishing' | 'exploding'>('healthy');\n\n const norms = createMemo(() => {\n const p = pattern();\n if (p === 'vanishing') return [1.0, 0.4, 0.15, 0.04];\n if (p === 'exploding') return [0.4, 0.8, 1.6, 3.2];\n return [0.6, 0.55, 0.5, 0.5];\n });\n\n const status = createMemo(() => {\n const p = pattern();\n if (p === 'vanishing') return ['healthy', 'vanishing', 'vanishing', 'vanishing'];\n if (p === 'exploding') return ['healthy', 'healthy', 'exploding', 'exploding'];\n return ['healthy', 'healthy', 'healthy', 'healthy'];\n });\n\n return (\n
\n \n setPattern(v as 'healthy' | 'vanishing' | 'exploding')}\n />\n
\n );\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { LossPlot } from './LossPlot';\n\nexport const LossPlotDemo: Component = () => {\n const [history, setHistory] = createSignal([]);\n\n let timer: ReturnType;\n let i = 0;\n onMount(() => {\n timer = setInterval(() => {\n i += 1;\n const v = 0.5 * Math.exp(-i * 0.04) + 0.005 + 0.02 * Math.random();\n setHistory((h) => {\n const next = [...h, v];\n return next.slice(-200);\n });\n }, 80);\n });\n onCleanup(() => clearInterval(timer));\n\n return (\n
\n \n \n
\n );\n};\n","/**\n * Primitives showcase — Storybook-equivalent at /dev/primitives.\n *\n * Each section wraps one primitive's demo. Demos use realistic interactive\n * state so a quick visual scan answers \"does this thing actually work?\".\n */\n\nimport { Component, For, JSX } from 'solid-js';\nimport styles from './PrimitivesShowcase.module.css';\n\nimport { SliderDemo } from '../primitives/Slider.demo';\nimport { SliderBankDemo } from '../primitives/SliderBank.demo';\nimport { VirtualJoystickDemo } from '../primitives/VirtualJoystick.demo';\nimport { XYPadDemo } from '../primitives/XYPad.demo';\nimport { HeatmapDemo } from '../primitives/Heatmap.demo';\nimport { OutputDisplayDemo } from '../primitives/OutputDisplay.demo';\nimport { TrainingControlsDemo } from '../primitives/TrainingControls.demo';\nimport { DrawerDemo } from '../primitives/Drawer.demo';\nimport { ControlAxisDemo } from '../primitives/ControlAxis.demo';\nimport { ProgressRingDemo } from '../primitives/ProgressRing.demo';\nimport { PillToggleDemo } from '../primitives/PillToggle.demo';\nimport { ParamEditorDemo } from '../primitives/ParamEditor.demo';\nimport { JoyMapDemo } from '../primitives/JoyMap.demo';\nimport { WeightHealthDemo } from '../primitives/WeightHealth.demo';\nimport { GradientFlowDemo } from '../primitives/GradientFlow.demo';\nimport { LossPlotDemo } from '../primitives/LossPlot.demo';\n\ninterface Section {\n name: string;\n description: string;\n Demo: Component;\n}\n\nconst SECTIONS: ReadonlyArray
= [\n { name: 'Slider', description: 'Single-value slider with optional curve mapping.', Demo: SliderDemo },\n { name: 'SliderBank', description: 'Vertical stack with collapsible sections.', Demo: SliderBankDemo },\n { name: 'VirtualJoystick', description: 'Touch + pointer 2D input, returns [0,1]^2.', Demo: VirtualJoystickDemo },\n { name: 'XYPad', description: 'Rectangular 2D input pad.', Demo: XYPadDemo },\n { name: 'Heatmap', description: 'NxN colored grid, three color modes.', Demo: HeatmapDemo },\n { name: 'OutputDisplay', description: 'Bar chart of N output values.', Demo: OutputDisplayDemo },\n { name: 'TrainingControls', description: 'RL feedback + status panel.', Demo: TrainingControlsDemo },\n { name: 'Drawer', description: 'Slide-in panel from left/right.', Demo: DrawerDemo },\n { name: 'ControlAxis', description: 'Compound axis slider (Boldness/Memory/Precision shape).', Demo: ControlAxisDemo },\n { name: 'ProgressRing', description: 'Circular progress indicator.', Demo: ProgressRingDemo },\n { name: 'PillToggle', description: 'Segmented control.', Demo: PillToggleDemo },\n { name: 'ParamEditor', description: 'Range/curve/mute/pin for one parameter.', Demo: ParamEditorDemo },\n { name: 'JoyMap', description: 'Zoom minimap with trail, noise rings, region pins.', Demo: JoyMapDemo },\n { name: 'WeightHealth', description: 'Histogram with status glow.', Demo: WeightHealthDemo },\n { name: 'GradientFlow', description: 'Per-layer gradient bars.', Demo: GradientFlowDemo },\n { name: 'LossPlot', description: 'Training-loss line chart over time.', Demo: LossPlotDemo },\n];\n\nconst PrimitivesShowcase: Component = () => {\n return (\n
\n
\n

Primitive Library

\n

\n Each card is a live demo. Use this page as a sanity-check during the rewrite.\n Modes (stream 9) and feature parity (stream 10) compose these primitives.\n

\n
\n
\n {(section) => (\n
\n
\n

{section.name}

\n

{section.description}

\n
\n
\n {section.Demo({}) as unknown as JSX.Element}\n
\n
\n )}
\n
\n
\n );\n};\n\nexport default PrimitivesShowcase;\n"],"names":["SliderDemo","linear","setLinear","createSignal","exp","setExp","neg","setNeg","_el$","_tmpl$","_$insert","_$createComponent","Slider","value","onChange","min","max","step","label","curve","unit","group","configs","sections","current","forEach","cfg","index","sectionName","section","name","items","push","SliderBank","props","collapsed","setCollapsed","Object","fromEntries","collapsedSections","map","s","sliders","toggle","prev","_tmpl$2","Show","when","title","children","_el$2","_$effect","_$className","styles","For","each","_el$3","_tmpl$4","_el$4","_tmpl$3","_el$5","firstChild","_el$6","nextSibling","_el$7","$$click","length","_p$","_v$","sectionHeader","_v$2","_v$3","caret","_v$4","_v$5","sectionCount","e","t","_$setAttribute","a","o","i","undefined","_el$8","values","v","id","curveParam","list","bank","_$delegateEvents","CFG","SliderBankDemo","setValues","_id","x","j","VirtualJoystickDemo","pos","setPos","VirtualJoystick","size","position","onMove","y","toFixed","XYPadDemo","XYPad","N","generateField","seed","arr","Float32Array","fx","fy","r","Math","sqrt","sin","cos","HeatmapDemo","mode","setMode","tick","setTick","timer","onMount","setInterval","onCleanup","clearInterval","samples","createMemo","Heatmap","resolution","colorMode","showGrid","PillToggle","options","OutputDisplayDemo","raf","last","performance","now","animate","requestAnimationFrame","cancelAnimationFrame","OutputDisplay","width","height","compact","TrainingControlsDemo","count","setCount","loss","setLoss","busy","setBusy","stack","setStack","train","setTimeout","random","TrainingControls","onTrain","onRandomize","onThumbsUp","c","onThumbsDown","l","onUndo","pop","exampleCount","lastLoss","canUndo","DrawerDemo","openR","setOpenR","openL","setOpenL","_tmpl$5","Drawer","open","onClose","side","ControlAxisDemo","boldness","setBoldness","memory","setMemory","precision","setPrecision","preset","setPreset","onAny","ControlAxis","endpoints","accentColor","onDoubleTap","ProgressRingDemo","p","setP","q","ProgressRing","progress","color","showLabel","PillToggleDemo","setSize","PARAM","hardMin","hardMax","default","ParamEditorDemo","override","setOverride","muted","pinned","fixedValue","ParamEditor","param","JoyMapDemo","zoom","setZoom","trail","setTrail","pins","colorSlot","frozen","setFrozen","t0","slice","JoyMap","anchor","regionPins","noiseRings","onTrailTap","f","WeightHealthDemo","status","setStatus","histogram","bins","Array","fill","pow","WeightHealth","GradientFlowDemo","pattern","setPattern","norms","GradientFlow","layerNorms","LossPlotDemo","history","setHistory","h","LossPlot","log","SECTIONS","description","Demo","PrimitivesShowcase","_el$9","_el$0","card","_v$6","cardHead","_v$7","cardTitle","_v$8","cardDesc","_v$9","cardBody","root","header","lede","grid"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/index-C1-a2ZPT.js b/playground/dist/assets/index-C1-a2ZPT.js new file mode 100644 index 0000000..98e0804 --- /dev/null +++ b/playground/dist/assets/index-C1-a2ZPT.js @@ -0,0 +1,3 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/PrimitivesShowcase-D2lYbVrQ.js","assets/PrimitivesShowcase-B55FOguW.css"])))=>i.map(i=>d[i]); +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&a(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();const J={context:void 0,registry:void 0,effects:void 0,done:!1,getContextId(){return ea(this.context.count)},getNextContextId(){return ea(this.context.count++)}};function ea(e){const t=String(e),n=t.length-1;return J.context.id+(n?String.fromCharCode(96+n):"")+t}function Zt(e){J.context=e}const Fa=!1,Pr=(e,t)=>e===t,tt=Symbol("solid-proxy"),kr=typeof Proxy=="function",bn=Symbol("solid-track"),pn={equals:Pr};let Ea=za;const ut=1,_n=2,Ia={owned:null,cleanups:null,context:null,owner:null},kn={};var be=null;let Ln=null,Lr=null,ve=null,De=null,lt=null,Cn=0;function Jt(e,t){const n=ve,a=be,r=e.length===0,i=t===void 0?a:t,o=r?Ia:{owned:null,cleanups:null,context:i?i.context:null,owner:i},s=r?e:()=>e(()=>pe(()=>nn(o)));be=o,ve=null;try{return st(s,!0)}finally{ve=n,be=a}}function ee(e,t){t=t?Object.assign({},pn,t):pn;const n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0},a=r=>(typeof r=="function"&&(r=r(n.value)),Ta(n,r));return[Oa.bind(n),a]}function Fr(e,t,n){const a=$n(e,t,!0,ut);Bt(a)}function A(e,t,n){const a=$n(e,t,!1,ut);Bt(a)}function ze(e,t,n){Ea=zr;const a=$n(e,t,!1,ut);(!n||!n.render)&&(a.user=!0),lt?lt.push(a):Bt(a)}function $e(e,t,n){n=n?Object.assign({},pn,n):pn;const a=$n(e,t,!0,0);return a.observers=null,a.observerSlots=null,a.comparator=n.equals||void 0,Bt(a),Oa.bind(a)}function Er(e){return e&&typeof e=="object"&&"then"in e}function Ir(e,t,n){let a,r,i;a=!0,r=e,i={};let o=null,s=kn,l=null,d=!1,c="initialValue"in i,u=typeof a=="function"&&$e(a);const m=new Set,[v,g]=(i.storage||ee)(i.initialValue),[f,w]=ee(void 0),[x,_]=ee(void 0,{equals:!1}),[M,O]=ee(c?"ready":"unresolved");J.context&&(l=J.getNextContextId(),i.ssrLoadFrom==="initial"?s=i.initialValue:J.load&&J.has(l)&&(s=J.load(l)));function y(F,$,U,ne){return o===F&&(o=null,ne!==void 0&&(c=!0),(F===s||$===s)&&i.onHydrated&&queueMicrotask(()=>i.onHydrated(ne,{value:$})),s=kn,q($,U)),$}function q(F,$){st(()=>{$===void 0&&g(()=>F),O($!==void 0?"errored":c?"ready":"unresolved"),w($);for(const U of m.keys())U.decrement();m.clear()},!1)}function W(){const F=Rr,$=v(),U=f();if(U!==void 0&&!o)throw U;return ve&&ve.user,$}function N(F=!0){if(F!==!1&&d)return;d=!1;const $=u?u():a;if($==null||$===!1){y(o,pe(v));return}let U;const ne=s!==kn?s:pe(()=>{try{return r($,{value:v(),refetching:F})}catch(oe){U=oe}});if(U!==void 0){y(o,void 0,hn(U),$);return}else if(!Er(ne))return y(o,ne,void 0,$),ne;return o=ne,"v"in ne?(ne.s===1?y(o,ne.v,void 0,$):y(o,void 0,hn(ne.v),$),ne):(d=!0,queueMicrotask(()=>d=!1),st(()=>{O(c?"refreshing":"pending"),_()},!1),ne.then(oe=>y(ne,oe,void 0,$),oe=>y(ne,void 0,hn(oe),$)))}Object.defineProperties(W,{state:{get:()=>M()},error:{get:()=>f()},loading:{get(){const F=M();return F==="pending"||F==="refreshing"}},latest:{get(){if(!c)return W();const F=f();if(F&&!o)throw F;return v()}}});let Y=be;return u?Fr(()=>(Y=be,N(!1))):N(!1),[W,{refetch:F=>Ra(Y,()=>N(F)),mutate:g}]}function Qt(e){return st(e,!1)}function pe(e){if(ve===null)return e();const t=ve;ve=null;try{return e()}finally{ve=t}}function Yn(e){ze(()=>pe(e))}function et(e){return be===null||(be.cleanups===null?be.cleanups=[e]:be.cleanups.push(e)),e}function Vn(){return ve}function ta(){return be}function Ra(e,t){const n=be,a=ve;be=e,ve=null;try{return st(t,!0)}catch(r){Zn(r)}finally{be=n,ve=a}}const[nd,ad]=ee(!1);let Rr;function Oa(){if(this.sources&&this.state)if(this.state===ut)Bt(this);else{const e=De;De=null,st(()=>xn(this),!1),De=e}if(ve){const e=this.observers;if(!e||e[e.length-1]!==ve){const t=e?e.length:0;ve.sources?(ve.sources.push(this),ve.sourceSlots.push(t)):(ve.sources=[this],ve.sourceSlots=[t]),e?(e.push(ve),this.observerSlots.push(ve.sources.length-1)):(this.observers=[ve],this.observerSlots=[ve.sources.length-1])}}return this.value}function Ta(e,t,n){let a=e.value;return(!e.comparator||!e.comparator(a,t))&&(e.value=t,e.observers&&e.observers.length&&st(()=>{for(let r=0;r1e6)throw De=[],new Error},!1)),t}function Bt(e){if(!e.fn)return;nn(e);const t=Cn;Or(e,e.value,t)}function Or(e,t,n){let a;const r=be,i=ve;ve=be=e;try{a=e.fn(t)}catch(o){return e.pure&&(e.state=ut,e.owned&&e.owned.forEach(nn),e.owned=null),e.updatedAt=n+1,Zn(o)}finally{ve=i,be=r}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&"observers"in e?Ta(e,a):e.value=a,e.updatedAt=n)}function $n(e,t,n,a=ut,r){const i={fn:e,state:a,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:be,context:be?be.context:null,pure:n};return be===null||be!==Ia&&(be.owned?be.owned.push(i):be.owned=[i]),i}function yn(e){if(e.state===0)return;if(e.state===_n)return xn(e);if(e.suspense&&pe(e.suspense.inFallback))return e.suspense.effects.push(e);const t=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt=0;n--)if(e=t[n],e.state===ut)Bt(e);else if(e.state===_n){const a=De;De=null,st(()=>xn(e,t[0]),!1),De=a}}function st(e,t){if(De)return e();let n=!1;t||(De=[]),lt?n=!0:lt=[],Cn++;try{const a=e();return Tr(n),a}catch(a){n||(lt=null),De=null,Zn(a)}}function Tr(e){if(De&&(za(De),De=null),e)return;const t=lt;lt=null,t.length&&st(()=>Ea(t),!1)}function za(e){for(let t=0;t=0;t--)nn(e.tOwned[t]);delete e.tOwned}if(e.owned){for(t=e.owned.length-1;t>=0;t--)nn(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}e.state=0}function hn(e){return e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error",{cause:e})}function Zn(e,t=be){throw hn(e)}const Dr=Symbol("fallback");function na(e){for(let t=0;t1?[]:null;return et(()=>na(i)),()=>{let l=e()||[],d=l.length,c,u;return l[bn],pe(()=>{let v,g,f,w,x,_,M,O,y;if(d===0)o!==0&&(na(i),i=[],a=[],r=[],o=0,s&&(s=[])),n.fallback&&(a=[Dr],r[0]=Jt(q=>(i[0]=q,n.fallback())),o=1);else if(o===0){for(r=new Array(d),u=0;u=_&&O>=_&&a[M]===l[O];M--,O--)f[O]=r[M],w[O]=i[M],s&&(x[O]=s[M]);for(v=new Map,g=new Array(O+1),u=O;u>=_;u--)y=l[u],c=v.get(y),g[u]=c===void 0?-1:c,v.set(y,u);for(c=_;c<=M;c++)y=a[c],u=v.get(y),u!==void 0&&u!==-1?(f[u]=r[c],w[u]=i[c],s&&(x[u]=s[c]),u=g[u],v.set(y,u)):i[c]();for(u=_;ue(t||{}))}function ln(){return!0}const aa={get(e,t,n){return t===tt?n:e.get(t)},has(e,t){return t===tt?!0:e.has(t)},set:ln,deleteProperty:ln,getOwnPropertyDescriptor(e,t){return{configurable:!0,enumerable:!0,get(){return e.get(t)},set:ln,deleteProperty:ln}},ownKeys(e){return e.keys()}};function Nr(e,...t){const n=t.length;if(kr&&tt in e){const r=n>1?t.flat():t[0],i=t.map(o=>new Proxy({get(s){return o.includes(s)?e[s]:void 0},has(s){return o.includes(s)&&s in e},keys(){return o.filter(s=>s in e)}},aa));return i.push(new Proxy({get(o){return r.includes(o)?void 0:e[o]},has(o){return r.includes(o)?!1:o in e},keys(){return Object.keys(e).filter(o=>!r.includes(o))}},aa)),i}const a=[];for(let r=0;r<=n;r++)a[r]={};for(const r of Object.getOwnPropertyNames(e)){let i=n;for(let l=0;l{const i=J.context;if(i){const[s,l]=ee();J.count||(J.count=0),J.count++,(n||(n=e())).then(d=>{!J.done&&Zt(i),J.count--,l(()=>d.default),Zt()}),t=s}else if(!t){const[s]=Ir(()=>(n||(n=e())).then(l=>l.default));t=s}let o;return $e(()=>(o=t())?pe(()=>{if(!i||J.done)return o(r);const s=J.context;Zt(i);const l=o(r);return Zt(s),l}):"")};return a.preload=()=>n||((n=e()).then(r=>t=()=>r.default),n),a}const Vr=e=>`Stale read from <${e}>.`;function vt(e){const t="fallback"in e&&{fallback:()=>e.fallback};return $e(qr(()=>e.each,e.children,t||void 0))}function re(e){const t=e.keyed,n=$e(()=>e.when,void 0,void 0),a=t?n:$e(n,void 0,{equals:(r,i)=>!r==!i});return $e(()=>{const r=a();if(r){const i=e.children;return typeof i=="function"&&i.length>0?pe(()=>i(t?r:()=>{if(!pe(a))throw Vr("Show");return n()})):i}return e.fallback},void 0,void 0)}const jr=["allowfullscreen","async","alpha","autofocus","autoplay","checked","controls","default","disabled","formnovalidate","hidden","indeterminate","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","seamless","selected","adauctionheaders","browsingtopics","credentialless","defaultchecked","defaultmuted","defaultselected","defer","disablepictureinpicture","disableremoteplayback","preservespitch","shadowrootclonable","shadowrootcustomelementregistry","shadowrootdelegatesfocus","shadowrootserializable","sharedstoragewritable"],Hr=new Set(["className","value","readOnly","noValidate","formNoValidate","isMap","noModule","playsInline","adAuctionHeaders","allowFullscreen","browsingTopics","defaultChecked","defaultMuted","defaultSelected","disablePictureInPicture","disableRemotePlayback","preservesPitch","shadowRootClonable","shadowRootCustomElementRegistry","shadowRootDelegatesFocus","shadowRootSerializable","sharedStorageWritable",...jr]),Wr=new Set(["innerHTML","textContent","innerText","children"]),Ur=Object.assign(Object.create(null),{className:"class",htmlFor:"for"}),Gr=Object.assign(Object.create(null),{class:"className",novalidate:{$:"noValidate",FORM:1},formnovalidate:{$:"formNoValidate",BUTTON:1,INPUT:1},ismap:{$:"isMap",IMG:1},nomodule:{$:"noModule",SCRIPT:1},playsinline:{$:"playsInline",VIDEO:1},readonly:{$:"readOnly",INPUT:1,TEXTAREA:1},adauctionheaders:{$:"adAuctionHeaders",IFRAME:1},allowfullscreen:{$:"allowFullscreen",IFRAME:1},browsingtopics:{$:"browsingTopics",IMG:1},defaultchecked:{$:"defaultChecked",INPUT:1},defaultmuted:{$:"defaultMuted",AUDIO:1,VIDEO:1},defaultselected:{$:"defaultSelected",OPTION:1},disablepictureinpicture:{$:"disablePictureInPicture",VIDEO:1},disableremoteplayback:{$:"disableRemotePlayback",AUDIO:1,VIDEO:1},preservespitch:{$:"preservesPitch",AUDIO:1,VIDEO:1},shadowrootclonable:{$:"shadowRootClonable",TEMPLATE:1},shadowrootdelegatesfocus:{$:"shadowRootDelegatesFocus",TEMPLATE:1},shadowrootserializable:{$:"shadowRootSerializable",TEMPLATE:1},sharedstoragewritable:{$:"sharedStorageWritable",IFRAME:1,IMG:1}});function Xr(e,t){const n=Gr[e];return typeof n=="object"?n[t]?n.$:void 0:n}const Yr=new Set(["beforeinput","click","dblclick","contextmenu","focusin","focusout","input","keydown","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","pointerdown","pointermove","pointerout","pointerover","pointerup","touchend","touchmove","touchstart"]),Zr=new Set(["altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignObject","g","glyph","glyphRef","hkern","image","line","linearGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","set","stop","svg","switch","symbol","text","textPath","tref","tspan","use","view","vkern"]),Kr={xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace"},zt=e=>$e(()=>e());function Jr(e,t,n){let a=n.length,r=t.length,i=a,o=0,s=0,l=t[r-1].nextSibling,d=null;for(;oc-s){const g=t[o];for(;s{r=i,t===document?e():b(t,e(),t.firstChild?null:void 0,n)},a.owner),()=>{r(),t.textContent=""}}function E(e,t,n,a){let r;const i=()=>{const s=a?document.createElementNS("http://www.w3.org/1998/Math/MathML","template"):document.createElement("template");return s.innerHTML=e,n?s.content.firstChild.firstChild:a?s.firstChild:s.content.firstChild},o=t?()=>pe(()=>document.importNode(r||(r=i()),!0)):()=>(r||(r=i())).cloneNode(!0);return o.cloneNode=o,o}function Je(e,t=window.document){const n=t[ra]||(t[ra]=new Set);for(let a=0,r=e.length;ar.call(e,n[1],i))}else e.addEventListener(t,n,typeof n!="function"&&n)}function Ke(e,t,n={}){const a=Object.keys(t||{}),r=Object.keys(n);let i,o;for(i=0,o=r.length;ir.children=an(e,t.children,r.children)),A(()=>typeof t.ref=="function"&&ct(t.ref,e)),A(()=>ai(e,t,n,!0,r,!0)),r}function ct(e,t,n){return pe(()=>e(t,n))}function b(e,t,n,a){if(n!==void 0&&!a&&(a=[]),typeof t!="function")return an(e,t,a,n);A(r=>an(e,t(),r,n),a)}function ai(e,t,n,a,r={},i=!1){t||(t={});for(const o in r)if(!(o in t)){if(o==="children")continue;r[o]=oa(e,o,null,r[o],n,i,t)}for(const o in t){if(o==="children")continue;const s=t[o];r[o]=oa(e,o,s,r[o],n,i,t)}}function ri(e){let t,n;return!kt()||!(t=J.registry.get(n=li()))?e():(J.completed&&J.completed.add(t),J.registry.delete(n),t)}function kt(e){return!!J.context&&!J.done&&(!e||e.isConnected)}function ii(e){return e.toLowerCase().replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}function ia(e,t,n){const a=t.trim().split(/\s+/);for(let r=0,i=a.length;r-1&&Kr[t.split(":")[0]];m?ei(e,m,t,n):X(e,Ur[t]||t,n)}return n}function oi(e){if(J.registry&&J.events&&J.events.find(([l,d])=>d===e))return;let t=e.target;const n=`$$${e.type}`,a=e.target,r=e.currentTarget,i=l=>Object.defineProperty(e,"target",{configurable:!0,value:l}),o=()=>{const l=t[n];if(l&&!t.disabled){const d=t[`${n}Data`];if(d!==void 0?l.call(t,d,e):l.call(t,e),e.cancelBubble)return}return t.host&&typeof t.host!="string"&&!t.host._$host&&t.contains(e.target)&&i(t.host),!0},s=()=>{for(;o()&&(t=t._$host||t.parentNode||t.host););};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return t||document}}),J.registry&&!J.done&&(J.done=_$HY.done=!0),e.composedPath){const l=e.composedPath();i(l[0]);for(let d=0;d{let l=t();for(;typeof l=="function";)l=l();n=an(e,l,n,a)}),()=>n;if(Array.isArray(t)){const l=[],d=n&&Array.isArray(n);if(jn(l,t,n,r))return A(()=>n=an(e,l,n,a,!0)),()=>n;if(i){if(!l.length)return n;if(a===void 0)return n=[...e.childNodes];let c=l[0];if(c.parentNode!==e)return n;const u=[c];for(;(c=c.nextSibling)!==a;)u.push(c);return n=u}if(l.length===0){if(n=Ft(e,n,a),s)return n}else d?n.length===0?la(e,l,a):Jr(e,n,l):(n&&Ft(e),la(e,l));n=l}else if(t.nodeType){if(i&&t.parentNode)return n=s?[t]:t;if(Array.isArray(n)){if(s)return n=Ft(e,n,a,t);Ft(e,n,null,t)}else n==null||n===""||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}}return n}function jn(e,t,n,a){let r=!1;for(let i=0,o=t.length;i=0;o--){const s=t[o];if(r!==s){const l=s.parentNode===e;!i&&!o?l?e.replaceChild(r,s):e.insertBefore(r,n):l&&s.remove()}else i=!0}}else e.insertBefore(r,n);return[r]}function li(){return J.getNextContextId()}const si="http://www.w3.org/2000/svg";function qa(e,t=!1,n=void 0){return t?document.createElementNS(si,e):document.createElement(e,{is:n})}function ui(e){const{useShadow:t}=e,n=document.createTextNode(""),a=()=>e.mount||document.body,r=ta();let i,o=!!J.context;return ze(()=>{o&&(ta().user=o=!1),i||(i=Ra(r,()=>$e(()=>e.children)));const s=a();if(s instanceof HTMLHeadElement){const[l,d]=ee(!1),c=()=>d(!0);Jt(u=>b(s,()=>l()?u():i(),null)),et(c)}else{const l=qa(e.isSVG?"g":"div",e.isSVG),d=t&&l.attachShadow?l.attachShadow({mode:"open"}):l;Object.defineProperty(l,"_$host",{get(){return n.parentNode},configurable:!0}),b(d,i),s.appendChild(l),e.ref&&e.ref(l),et(()=>s.removeChild(l))}},void 0,{render:!o}),n}function ci(e,t){const n=$e(e);return $e(()=>{const a=n();switch(typeof a){case"function":return pe(()=>a(t));case"string":const r=Zr.has(a),i=J.context?ri():qa(a,r,pe(()=>t.is));return ni(i,t,r),i}})}function di(e){const[,t]=Nr(e,["component"]);return ci(()=>e.component,t)}const mi="modulepreload",fi=function(e){return"/"+e},sa={},Na=function(t,n,a){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),s=o?.nonce||o?.getAttribute("nonce");r=Promise.allSettled(n.map(l=>{if(l=fi(l),l in sa)return;sa[l]=!0;const d=l.endsWith(".css"),c=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${c}`))return;const u=document.createElement("link");if(u.rel=d?"stylesheet":mi,d||(u.as="script"),u.crossOrigin="",u.href=l,s&&u.setAttribute("nonce",s),document.head.appendChild(u),d)return new Promise((m,v)=>{u.addEventListener("load",m),u.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${l}`)))})}))}function i(o){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=o,window.dispatchEvent(s),!s.defaultPrevented)throw o}return r.then(o=>{for(const s of o||[])s.status==="rejected"&&i(s.reason);return t().catch(i)})},wn=Symbol("store-raw"),At=Symbol("store-node"),rt=Symbol("store-has"),Ba=Symbol("store-self");function Va(e){let t=e[tt];if(!t&&(Object.defineProperty(e,tt,{value:t=new Proxy(e,gi)}),!Array.isArray(e))){const n=Object.keys(e),a=Object.getOwnPropertyDescriptors(e),r=Object.getPrototypeOf(e),i=r!==null&&e!==null&&typeof e=="object"&&!Array.isArray(e)&&r!==Object.prototype;if(i){const o=Object.getOwnPropertyDescriptors(r);n.push(...Object.keys(o)),Object.assign(a,o)}for(let o=0,s=n.length;oe[tt][t]),n}function ja(e){Vn()&&rn(Sn(e,At),Ba)()}function vi(e){return ja(e),Reflect.ownKeys(e)}const gi={get(e,t,n){if(t===wn)return e;if(t===tt)return n;if(t===bn)return ja(e),n;const a=Sn(e,At),r=a[t];let i=r?r():e[t];if(t===At||t===rt||t==="__proto__")return i;if(!r){const o=Object.getOwnPropertyDescriptor(e,t);Vn()&&(typeof i!="function"||e.hasOwnProperty(t))&&!(o&&o.get)&&(i=rn(a,t,i)())}return Dt(i)?Va(i):i},has(e,t){return t===wn||t===tt||t===bn||t===At||t===rt||t==="__proto__"?!0:(Vn()&&rn(Sn(e,rt),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:vi,getOwnPropertyDescriptor:hi};function Nt(e,t,n,a=!1){if(t==="__proto__"||!a&&e[t]===n)return;const r=e[t],i=e.length;n===void 0?(delete e[t],e[rt]&&e[rt][t]&&r!==void 0&&e[rt][t].$()):(e[t]=n,e[rt]&&e[rt][t]&&r===void 0&&e[rt][t].$());let o=Sn(e,At),s;if((s=rn(o,t,r))&&s.$(()=>n),Array.isArray(e)&&e.length!==i){for(let l=e.length;l1){a=t.shift();const o=typeof a,s=Array.isArray(e);if(o==="string"&&(a==="__proto__"||t.length>1&&Wa(a)))return;if(Array.isArray(a)){for(let l=0;l1){Kt(e[a],t,[a].concat(n));return}r=e[a],n=[a].concat(n)}let i=t[0];typeof i=="function"&&(i=i(r,n),i===r)||a===void 0&&i==null||(i=qt(i),a===void 0||Dt(r)&&Dt(i)&&!Array.isArray(i)?Ha(r,i):Nt(e,a,i))}function gt(...[e,t]){const n=qt(e||{}),a=Array.isArray(n),r=Va(n);function i(...o){Qt(()=>{a&&o.length===1?bi(n,o[0]):Kt(n,o)})}return[r,i]}const Mn=new WeakMap,Ua={get(e,t){if(t===wn)return e;const n=e[t];if(t===tt||t===bn||t===At||t===rt||t==="__proto__")return n;let a;return Dt(n)?Mn.get(n)||(Mn.set(n,a=new Proxy(n,Ua)),a):n},set(e,t,n){return Nt(e,t,qt(n)),!0},deleteProperty(e,t){return Nt(e,t,void 0,!0),!0}};function D(e){return t=>{if(Dt(t)){let n;(n=Mn.get(t))||Mn.set(t,n=new Proxy(t,Ua)),e(n)}return t}}function pi(){const e=new Map,t=[];function n(o,s){const l=e.get(o);if(l)for(const d of l)try{d(s,o)}catch(c){console.error(`[bus] handler for "${o}" threw:`,c)}for(const{prefix:d,handler:c}of t)if(o===d||o.startsWith(d+"."))try{c(s,o)}catch(u){console.error(`[bus] prefix handler "${d}" threw on "${o}":`,u)}}function a(o,s){let l=e.get(o);return l||(l=new Set,e.set(o,l)),l.add(s),()=>{l.delete(s)}}function r(o,s){const l={prefix:o,handler:s};return t.push(l),()=>{const d=t.indexOf(l);d>=0&&t.splice(d,1)}}function i(){e.clear(),t.length=0}return{emit:n,on:a,onPrefix:r,clear:i}}const Fe=pi(),_i=200,Hn=new Map;let Wn=null;function yi(){Wn=null;for(const[e,t]of Hn)try{const n=t();n===void 0?localStorage.removeItem(e):localStorage.setItem(e,JSON.stringify(n))}catch(n){console.warn(`[persistence] failed to write "${e}":`,n)}Hn.clear()}function Vt(e,t){typeof localStorage>"u"||(Hn.set(e,t),Wn===null&&(Wn=setTimeout(yi,_i)))}function jt(e,t){if(typeof localStorage>"u")return t;try{const n=localStorage.getItem(e);return n===null?t:JSON.parse(n)}catch{return t}}const Ga="nisps:mode";function xi(){const e=jt(Ga,{});return{activeModeId:e.activeModeId??null,overrides:e.overrides??{}}}const[en,Ht]=gt(xi());function Wt(){Vt(Ga,()=>({activeModeId:en.activeModeId,overrides:en.overrides}))}const xe={state:en,switchMode(e){const t=en.activeModeId;t!==e&&(Ht(D(n=>{n.activeModeId=e,n.overrides[e]||(n.overrides[e]={})})),Wt(),Fe.emit("ui.mode_switch",{from:t,to:e}))},setOverride(e,t,n){Ht(D(a=>{a.overrides[e]||(a.overrides[e]={}),a.overrides[e][t]=n})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},clearOverride(e,t){Ht(D(n=>{const a=n.overrides[e];a&&delete a[t]})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},clearAllOverrides(e){Ht(D(t=>{t.overrides[e]={}})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},getOverride(e,t){return en.overrides[e]?.[t]},reset(){Ht({activeModeId:null,overrides:{}}),Wt()}},wi="_wrap_1uogo_1",Si="_row_1uogo_12",Mi="_btn_1uogo_17",Ci="_thumbsUp_1uogo_25",$i="_thumbsDown_1uogo_34",Ai="_undo_1uogo_43",Pi="_btnAlt_1uogo_48",ki="_status_1uogo_55",Li="_statusItem_1uogo_63",Fi="_statusLabel_1uogo_70",Ei="_statusValue_1uogo_76",Ii="_busy_1uogo_81",Pe={wrap:wi,row:Si,btn:Mi,thumbsUp:Ci,thumbsDown:$i,undo:Ai,btnAlt:Pi,status:ki,statusItem:Li,statusLabel:Fi,statusValue:Ei,busy:Ii};var Ri=E("loss"),Oi=E("training…"),Ti=E('
examples');const zi=e=>{const t=()=>e.busy?.()??!1,n=()=>e.canUndo?.()??!0;return(()=>{var a=Ti(),r=a.firstChild,i=r.firstChild,o=i.nextSibling,s=o.nextSibling,l=r.nextSibling,d=l.firstChild,c=d.nextSibling,u=l.nextSibling,m=u.firstChild,v=m.firstChild,g=v.nextSibling;return $t(i,"click",e.onThumbsDown,!0),$t(o,"click",e.onThumbsUp,!0),$t(s,"click",e.onUndo,!0),$t(d,"click",e.onTrain,!0),$t(c,"click",e.onRandomize,!0),b(g,()=>e.exampleCount()),b(u,p(re,{get when(){return e.lastLoss()!==null},get children(){var f=Ri(),w=f.firstChild,x=w.nextSibling;return b(x,()=>e.lastLoss().toExponential(2)),A(_=>{var M=Pe.statusItem,O=Pe.statusLabel,y=Pe.statusValue;return M!==_.e&&h(f,_.e=M),O!==_.t&&h(w,_.t=O),y!==_.a&&h(x,_.a=y),_},{e:void 0,t:void 0,a:void 0}),f}}),null),b(u,p(re,{get when(){return t()},get children(){var f=Oi();return A(()=>h(f,`${Pe.statusItem} ${Pe.busy}`)),f}}),null),A(f=>{var w=Pe.wrap,x=Pe.row,_=`${Pe.btn} ${Pe.thumbsDown}`,M=t(),O=`${Pe.btn} ${Pe.thumbsUp}`,y=t(),q=`${Pe.btn} ${Pe.undo}`,W=t()||!n(),N=Pe.row,Y=Pe.btnAlt,F=t(),$=Pe.btnAlt,U=t(),ne=Pe.status,oe=Pe.statusItem,S=Pe.statusLabel,te=Pe.statusValue;return w!==f.e&&h(a,f.e=w),x!==f.t&&h(r,f.t=x),_!==f.a&&h(i,f.a=_),M!==f.o&&(i.disabled=f.o=M),O!==f.i&&h(o,f.i=O),y!==f.n&&(o.disabled=f.n=y),q!==f.s&&h(s,f.s=q),W!==f.h&&(s.disabled=f.h=W),N!==f.r&&h(l,f.r=N),Y!==f.d&&h(d,f.d=Y),F!==f.l&&(d.disabled=f.l=F),$!==f.u&&h(c,f.u=$),U!==f.c&&(c.disabled=f.c=U),ne!==f.w&&h(u,f.w=ne),oe!==f.m&&h(m,f.m=oe),S!==f.f&&h(v,f.f=S),te!==f.y&&h(g,f.y=te),f},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0,l:void 0,u:void 0,c:void 0,w:void 0,m:void 0,f:void 0,y:void 0}),a})()};Je(["click"]);const Di="_axis_1gq0t_1",qi="_head_1gq0t_12",Ni="_label_1gq0t_19",Bi="_preset_1gq0t_27",Vi="_value_1gq0t_34",ji="_input_1gq0t_42",Hi="_endpoints_1gq0t_87",Wi="_disabled_1gq0t_96",ft={axis:Di,head:qi,label:Ni,preset:Bi,value:Vi,input:ji,endpoints:Hi,disabled:Wi};var Ui=E(""),Gi=E("
"),Xi=E("
");const Yi=e=>{let t=0;const n=()=>{const i=performance.now();i-t<350&&e.onDoubleTap?(e.onDoubleTap(),t=0):t=i},a=i=>{const o=parseFloat(i.currentTarget.value);Number.isNaN(o)||e.onChange(Math.max(0,Math.min(1,o)))},r=()=>e.accentColor??"var(--accent)";return(()=>{var i=Xi(),o=i.firstChild,s=o.firstChild,l=s.nextSibling,d=o.nextSibling;return b(s,()=>e.label),b(o,p(re,{get when(){return e.preset?.()},get children(){var c=Ui();return b(c,e.preset),A(()=>h(c,ft.preset)),c}}),l),b(l,()=>e.value().toFixed(2)),d.$$pointerdown=n,d.$$input=a,b(i,p(re,{get when(){return e.endpoints},get children(){var c=Gi(),u=c.firstChild,m=u.nextSibling;return b(u,()=>e.endpoints[0]),b(m,()=>e.endpoints[1]),A(()=>h(c,ft.endpoints)),c}}),null),A(c=>{var u=ft.axis,m={[ft.disabled]:!!e.disabled},v=r(),g=ft.head,f=ft.label,w=ft.value,x=ft.input,_=e.disabled,M=`${e.label} control axis`;return u!==c.e&&h(i,c.e=u),c.t=Ke(i,m,c.t),v!==c.a&&ke(i,"--axis-accent",c.a=v),g!==c.o&&h(o,c.o=g),f!==c.i&&h(s,c.i=f),w!==c.n&&h(l,c.n=w),x!==c.s&&h(d,c.s=x),_!==c.h&&(d.disabled=c.h=_),M!==c.r&&X(d,"aria-label",c.r=M),c},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0}),A(()=>d.value=e.value()),i})()};Je(["input","pointerdown"]);const Zi="_group_1erfx_1",Ki="_pill_1erfx_10",Ji="_active_1erfx_22",Qi="_disabled_1erfx_31",sn={group:Zi,pill:Ki,active:Ji,disabled:Qi};var eo=E("
"),to=E("
');const ua=e=>{const t=n=>{e.open&&e.dismissable!==!1&&n.key==="Escape"&&(n.stopPropagation(),e.onClose())};return Yn(()=>{document.addEventListener("keydown",t)}),et(()=>{document.removeEventListener("keydown",t)}),p(re,{get when(){return e.open},get children(){return p(ui,{get children(){var n=mo(),a=n.firstChild,r=a.nextSibling,i=r.firstChild,o=i.firstChild,s=o.nextSibling,l=i.nextSibling;return a.$$click=()=>{e.dismissable!==!1&&e.onClose()},b(o,()=>e.title??""),$t(s,"click",e.onClose,!0),b(l,()=>e.children),A(d=>{var c=ot.root,u={[ot.left]:e.side==="left",[ot.right]:e.side==="right"},m=e.title??"drawer",v=ot.scrim,g=ot.panel,f=`${e.width??360}px`,w=ot.header,x=ot.title,_=ot.close,M=ot.body;return c!==d.e&&h(n,d.e=c),d.t=Ke(n,u,d.t),m!==d.a&&X(n,"aria-label",d.a=m),v!==d.o&&h(a,d.o=v),g!==d.i&&h(r,d.i=g),f!==d.n&&ke(r,"width",d.n=f),w!==d.s&&h(i,d.s=w),x!==d.h&&h(o,d.h=x),_!==d.r&&h(s,d.r=_),M!==d.d&&h(l,d.d=M),d},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0}),n}})}})};Je(["click"]);const fo="_canvas_mlv8d_1",ho={canvas:fo};var vo=E("");const ca=12,Fn=5e3,da=1.5,go=4,bo=600,ma=["rgba(255, 106, 0, 0.25)","rgba(0, 204, 255, 0.25)","rgba(180, 100, 255, 0.25)","rgba(80, 200, 120, 0.25)","rgba(255, 200, 80, 0.25)"],fa=["rgba(255, 106, 0, 0.7)","rgba(0, 204, 255, 0.7)","rgba(180, 100, 255, 0.7)","rgba(80, 200, 120, 0.7)","rgba(255, 200, 80, 0.7)"];function po(e,t){if(!(t.length<2)){e.beginPath(),e.moveTo(t[0].x,t[0].y);for(let n=0;n{let t,n=0,a=null;const r=()=>e.size??200,i=(c,u,m,v)=>{const g=m<=.1?16:m<=.3?8:m<=.6?4:2;c.strokeStyle="rgba(255,255,255,0.04)",c.lineWidth=1;for(let x=1;x{if(!t)return;const c=window.devicePixelRatio||1,u=r()*c;(t.width!==u||t.height!==u)&&(t.width=u,t.height=u);const m=t.getContext("2d");if(!m)return;m.clearRect(0,0,u,u);const v=Math.max(.01,Math.min(1,e.zoom())),g=e.anchor(),f=e.position(),w=e.trail(),x=e.regionPins?.()??[];m.fillStyle="#0a0a0a",m.fillRect(0,0,u,u),i(m,u,v,g);for(const y of x){const q=y.x*u,W=(1-y.y-y.height)*u,N=y.width*u,Y=y.height*u,F=ma[y.colorSlot%ma.length],$=fa[y.colorSlot%fa.length];m.fillStyle=F,m.fillRect(q,W,N,Y),m.strokeStyle=$,m.lineWidth=2*c,m.strokeRect(q,W,N,Y)}if(w.length>=2){const y=performance.now(),q=da+(go-da)*(1-v),W=w.filter(N=>y-N.t<=Fn).map(N=>({x:N.x*u,y:(1-N.y)*u}));W.length>=2&&(m.lineWidth=q*c,m.lineCap="round",m.lineJoin="round",m.strokeStyle="rgba(255, 106, 0, 0.55)",po(m,W));for(const N of w){const Y=y-N.t;if(Y>Fn)continue;const F=1-Y/Fn;m.fillStyle=`rgba(255, 106, 0, ${F*.6})`;const $=N.x*u,U=(1-N.y)*u;m.beginPath(),m.arc($,U,2*c,0,Math.PI*2),m.fill()}}const _=f[0]*u,M=(1-f[1])*u;m.fillStyle="#00ccff",m.beginPath(),m.arc(_,M,4*c,0,Math.PI*2),m.fill(),m.strokeStyle="rgba(0, 204, 255, 0.4)",m.lineWidth=1,m.beginPath(),m.arc(_,M,9*c,0,Math.PI*2),m.stroke();const O=e.noiseRings?.();if(O){const[y,q]=O;m.strokeStyle="rgba(255, 200, 80, 0.4)",m.beginPath(),m.arc(_,M,y*u,0,Math.PI*2),m.stroke(),m.strokeStyle="rgba(255, 200, 80, 0.6)",m.beginPath(),m.arc(_,M,q*u,0,Math.PI*2),m.stroke()}e.frozen?.()&&(m.fillStyle="rgba(100, 180, 255, 0.15)",m.fillRect(0,0,u,u),m.fillStyle="rgba(100, 180, 255, 0.85)",m.font=`${12*c}px var(--font-mono)`,m.textAlign="center",m.fillText("FROZEN",u/2,u-12*c))},s=()=>{o(),n=requestAnimationFrame(s)};Yn(()=>{n=requestAnimationFrame(s)}),et(()=>{n&&cancelAnimationFrame(n),a&&clearTimeout(a)}),ze(()=>{e.zoom(),e.position(),e.trail(),o()});const l=c=>{if(e.onLongPress&&(a=setTimeout(()=>{e.onLongPress?.(),a=null},bo)),t&&e.onTrailTap){const u=t.getBoundingClientRect(),m=c.clientX-u.left,v=c.clientY-u.top,g=e.trail();for(const f of g){const w=f.x*u.width,x=(1-f.y)*u.height,_=w-m,M=x-v;if(_*_+M*M<=ca*ca){e.onTrailTap({x:f.x,y:f.y,t:f.t}),a&&(clearTimeout(a),a=null);return}}}},d=()=>{a&&(clearTimeout(a),a=null)};return(()=>{var c=vo();c.addEventListener("pointercancel",d),c.$$pointerup=d,c.$$pointerdown=l;var u=t;return typeof u=="function"?ct(u,c):t=c,A(m=>{var v=ho.canvas,g=`${r()}px`,f=`${r()}px`,w=e.ariaLabel??"Joy map";return v!==m.e&&h(c,m.e=v),g!==m.t&&ke(c,"width",m.t=g),f!==m.a&&ke(c,"height",m.a=f),w!==m.o&&X(c,"aria-label",m.o=w),m},{e:void 0,t:void 0,a:void 0,o:void 0}),c})()};Je(["pointerdown","pointerup"]);function We(e){return e<0?0:e>1?1:e}function K(e,t,n){return en?n:e}function yo(e){return We(e)}function xo(e,t=4){if(e<=0)return 0;if(e>=1)return 1;const n=Math.exp(t)-1;return n===0?e:(Math.exp(t*e)-1)/n}function wo(e,t=4){if(e<=0)return 0;if(e>=1)return 1;const n=Math.exp(t)-1;return n===0?e:Math.log(1+e*n)/t}function So(e){const t=We(e);return t*t}function Mo(e){return Math.sqrt(We(e))}function Co(e,t=8){const n=(e-.5)*t,a=1/(1+Math.exp(-n)),r=1/(1+Math.exp(t/2)),i=1/(1+Math.exp(-t/2));return(a-r)/(i-r)}function $o(e){const t=We(e);return t*t*(3-2*t)}function Un(e,t){if(t===1)return We(e);const n=e-.5,r=(n<0?-1:1)*Math.pow(Math.abs(n)*2,t)/2;return We(r+.5)}function Xa(e,t,n){switch(e){case"linear":return yo(t);case"exp":return xo(t,n??4);case"log":return wo(t,n??4);case"square":return So(t);case"sqrt":return Mo(t);case"sigmoid":return Co(t,n??8);case"cubic":return $o(t);case"centered_power":return Un(t,n??1)}}const Ao=["linear","exp","log","square","sqrt","sigmoid","cubic","centered_power"],Ya="nisps:control",Po=[{axis:0,values:{zoom:.1,noiseCap:.02,noiseGrowth:1.1,learningRate:.1,weightDecay:.15,noiseDistribution:"gaussian"}},{axis:.5,values:{zoom:.5,noiseCap:.12,noiseGrowth:1.5,learningRate:1,weightDecay:.06,noiseDistribution:"gaussian"}},{axis:1,values:{zoom:1,noiseCap:.3,noiseGrowth:2.5,learningRate:3,weightDecay:0,noiseDistribution:"cauchy"}}],ko=[{axis:0,values:{maxExamples:5,exampleDecay:.3,memoryWeightDecay:.2,noiseDecay:.85,convergenceThreshold:.001}},{axis:.5,values:{maxExamples:50,exampleDecay:.7,memoryWeightDecay:.06,noiseDecay:.97,convergenceThreshold:1e-5}},{axis:1,values:{maxExamples:500,exampleDecay:1,memoryWeightDecay:0,noiseDecay:.995,convergenceThreshold:1e-8}}],Lo=[{axis:0,values:{inputCurve:1,deadzone:0,smoothing:0,slewRate:1,momentumZoom:"off"}},{axis:.5,values:{inputCurve:1.5,deadzone:.05,smoothing:.15,slewRate:.3,momentumZoom:"off"}},{axis:1,values:{inputCurve:3,deadzone:.15,smoothing:.4,slewRate:.1,momentumZoom:"off"}}],Fo=[{id:"default",label:"Default",boldness:.5,memory:.5,precision:.3},{id:"first-touch",label:"First Touch",boldness:.2,memory:.7,precision:.6},{id:"jazz-hands",label:"Jazz Hands",boldness:.8,memory:.2,precision:0},{id:"sculptor",label:"Sculptor",boldness:.3,memory:.9,precision:.8},{id:"improviser",label:"Improviser",boldness:.6,memory:.3,precision:.2},{id:"microscope",label:"Microscope",boldness:.1,memory:1,precision:1}];function En(e,t){const n=K(t,0,1);let a=e[0],r=e[e.length-1];for(let s=0;s=l.axis&&n<=d.axis){a=l,r=d;break}}if(a===r)return{...a.values};const i=(n-a.axis)/(r.axis-a.axis),o={};for(const s of Object.keys(a.values)){const l=a.values[s],d=r.values[s];typeof l=="number"&&typeof d=="number"?o[s]=l+(d-l)*i:o[s]=i>=.75?d??l:l}return o}function Eo(){const e={boldness:.5,memory:.5,precision:.3,offsets:{boldness:{},memory:{},precision:{}},presetId:"default"},t=jt(Ya,{});return{boldness:t.boldness??e.boldness,memory:t.memory??e.memory,precision:t.precision??e.precision,offsets:t.offsets??e.offsets,presetId:t.presetId??e.presetId}}const[it,Ut]=gt(Eo());function Gt(){Vt(Ya,()=>({boldness:it.boldness,memory:it.memory,precision:it.precision,offsets:it.offsets,presetId:it.presetId}))}const Me={state:it,setAxis(e,t){const n=K(t,0,1);Ut(D(a=>{a[e]=n,a.presetId!==null&&(a.presetId=null)})),Gt()},setOffset(e,t,n){Ut(D(a=>{a.offsets[e][t]=n})),Gt()},clearOffsets(e){Ut(D(t=>{t.offsets[e]={}})),Gt()},applyPreset(e){const t=Fo.find(n=>n.id===e);return t?(Ut(D(n=>{n.boldness=t.boldness,n.memory=t.memory,n.precision=t.precision,n.presetId=t.id})),Gt(),!0):!1},resolveParams(){const e=En(Po,it.boldness),t=En(ko,it.memory),n=En(Lo,it.precision),a={...e,...t,...n},r=i=>{const o=it.offsets[i];for(const[s,l]of Object.entries(o)){const d=a[s];typeof d=="number"&&(a[s]=d+l)}};return r("boldness"),r("memory"),r("precision"),a},reset(){Ut({boldness:.5,memory:.5,precision:.3,offsets:{boldness:{},memory:{},precision:{}},presetId:"default"}),Gt()}},Pt=.01,Ot=1,un=Pt,Za=.4,vn=.2,gn=5,Ka=.95,Io=150,Gn=1/60,Ro={off:null,gentle:{factor:.6,minZoomMul:.3,maxZoomMul:1},strong:{factor:1.5,minZoomMul:.15,maxZoomMul:1}};function Ja(){return{zoom:1,zoomX:null,zoomY:null,anchorX:.5,anchorY:.5,anchorMode:"center",deadzone:0,inputCurve:1,inputCurveX:null,inputCurveY:null,smoothing:0,momentumZoom:"off",velocityWindow:Io,invertX:!1,invertY:!1}}function Kn(){return{smoothedX:.5,smoothedY:.5,velocityHistory:[],momentumZoomMultiplier:1,frozen:!1}}function ha(e,t){if(t<=0)return e;const n=e-.5,a=Math.abs(n),r=t*.5;if(a<=r)return .5;const i=n<0?-1:1,o=(a-r)/(.5-r)*.5;return .5+i*o}function va(e,t,n){return K(t+(e-.5)*n,0,1)}function ga(e,t,n,a){if(n<=0)return t;const r=a>0?a:Gn,i=1-n,o=1-Math.pow(1-i,r/Gn);return e+o*(t-e)}function Oo(e,t,n,a,r){const i=Ro[e.momentumZoom];if(!i)return{multiplier:1,history:[]};const o=performance.now(),s=e.velocityWindow,d=[...t.velocityHistory.filter(y=>o-y.t<=s),{x:n,y:a,t:o}];if(d.length<2)return{multiplier:1,history:d};const c=d[0],u=d[d.length-1],m=u.t-c.t;if(m<=0)return{multiplier:t.momentumZoomMultiplier,history:d};const v=u.x-c.x,g=u.y-c.y,w=Math.sqrt(v*v+g*g)/(m/1e3),x=K(w*i.factor,0,1),_=i.maxZoomMul-(i.maxZoomMul-i.minZoomMul)*x,M=K(r*6,0,1);return{multiplier:t.momentumZoomMultiplier+M*(_-t.momentumZoomMultiplier),history:d}}function To(e,t){return e.anchorMode==="center"?.5:(e.anchorMode==="sticky",e.anchorX)}function zo(e,t){return e.anchorMode==="center"?.5:(e.anchorMode==="sticky",e.anchorY)}function Do(e,t,n,a=Gn){const r=Math.max(0,a),i=t.zoomX??t.zoom,o=t.zoomY??t.zoom,s=i<=un,l=o<=un;if(s&&l)return{x:n.smoothedX,y:n.smoothedY,frozen:!0,state:{...n,frozen:!0}};let[c,u]=e,m=t.invertX?1-c:c,v=t.invertY?1-u:u;m=ha(m,t.deadzone),v=ha(v,t.deadzone);{const N=m-.5,Y=v-.5,F=Math.sqrt(N*N+Y*Y);if(F>.5&&F>1e-12){const $=.5/F;m=.5+N*$,v=.5+Y*$}}const g=To(t),f=zo(t),w=s?un:K(i*n.momentumZoomMultiplier,Pt,Ot),x=l?un:K(o*n.momentumZoomMultiplier,Pt,Ot);m=s?n.smoothedX:va(m,g,w),v=l?n.smoothedY:va(v,f,x);const _=t.inputCurveX??t.inputCurve,M=t.inputCurveY??t.inputCurve;s||(m=Un(m,_)),l||(v=Un(v,M));const O=s?n.smoothedX:ga(n.smoothedX,m,t.smoothing,r),y=l?n.smoothedY:ga(n.smoothedY,v,t.smoothing,r),{multiplier:q,history:W}=Oo(t,n,c,u,r);return{x:O,y,frozen:!1,state:{smoothedX:O,smoothedY:y,velocityHistory:W,momentumZoomMultiplier:q,frozen:!1}}}const Qa="nisps:input";function qo(){const e=Ja(),t=jt(Qa,{});return{...e,...t}}const[er,Ue]=gt(qo()),[xt,In]=gt(Kn());function Ge(){Vt(Qa,()=>({...er}))}const H={config:er,liveState:xt,setZoom(e){const t=K(e,Pt,Ot);Ue(D(n=>{const a=n.zoom;n.zoom=t,n.anchorMode==="auto"&&a!==t&&(n.anchorX=xt.smoothedX,n.anchorY=xt.smoothedY)})),Ge()},setZoomPerAxis(e,t){Ue(D(n=>{n.zoomX=e!==null?K(e,Pt,Ot):null,n.zoomY=t!==null?K(t,Pt,Ot):null,n.anchorMode==="auto"&&(n.anchorX=xt.smoothedX,n.anchorY=xt.smoothedY)})),Ge()},setAnchor(e,t){Ue(D(n=>{n.anchorX=K(e,0,1),n.anchorY=K(t,0,1)})),Ge()},setAnchorMode(e){Ue(D(t=>{t.anchorMode=e,e==="center"?(t.anchorX=.5,t.anchorY=.5):e==="auto"&&(t.anchorX=xt.smoothedX,t.anchorY=xt.smoothedY)})),Ge()},setDeadzone(e){Ue(D(t=>{t.deadzone=K(e,0,Za)})),Ge()},setInputCurve(e){Ue(D(t=>{t.inputCurve=K(e,vn,gn)})),Ge()},setInputCurvePerAxis(e,t){Ue(D(n=>{n.inputCurveX=e!==null?K(e,vn,gn):null,n.inputCurveY=t!==null?K(t,vn,gn):null})),Ge()},setSmoothing(e){Ue(D(t=>{t.smoothing=K(e,0,Ka)})),Ge()},setMomentumZoom(e){Ue(D(t=>{t.momentumZoom=e})),e==="off"&&In(D(t=>{t.velocityHistory=[],t.momentumZoomMultiplier=1})),Ge()},setInvert(e,t){Ue(D(n=>{n.invertX=e,n.invertY=t})),Ge()},setVelocityWindow(e){Ue(D(t=>{t.velocityWindow=K(e,50,500)})),Ge()},__setLiveState(e){In(e)},reset(){Ue(Ja()),In(Kn()),Ge()}},tr="nisps:session",No=20;function nr(){const e=jt(tr,{});return{snapshots:[],ab:{a:null,b:null,live:"B"},regionPins:e.regionPins??[],paramPins:e.paramPins??[],presets:e.presets??[]}}const[Ye,Ee]=gt(nr());function wt(){Vt(tr,()=>({regionPins:Ye.regionPins,paramPins:Ye.paramPins,presets:Ye.presets}))}let Xn=0;function Bo(){return Xn+=1,`snap-${Date.now().toString(36)}-${Xn}`}const Z={state:Ye,pushSnapshot(e,t={}){const n={id:Bo(),tag:e,timestamp:Date.now(),noiseLevel:t.noiseLevel??0,zoomLevel:t.zoomLevel??null,weights:t.weights?new Float32Array(t.weights):null};return Ee(D(a=>{a.snapshots.push(n),a.snapshots.length>No&&a.snapshots.shift()})),Fe.emit("snap.push",{tag:e}),n},peekSnapshot(){return Ye.snapshots.length>0?Ye.snapshots[Ye.snapshots.length-1]:null},listSnapshots(){return Ye.snapshots},popSnapshot(){let e=null;Ee(D(n=>{e=n.snapshots.pop()??null}));const t=e?.tag??null;return Fe.emit("snap.pop",{tag:t}),e},jumpToSnapshot(e){const t=Ye.snapshots.find(n=>n.id===e)??null;return t?(Ee(D(n=>{const a=n.snapshots.findIndex(r=>r.id===e);a>=0&&(n.snapshots=n.snapshots.slice(0,a+1))})),Fe.emit("snap.pop",{tag:t.tag}),t):null},clearSnapshots(){Ee(D(e=>{e.snapshots=[]})),Fe.emit("snap.cleared",void 0)},captureA(e){Ee(D(t=>{t.ab.a=e,t.ab.live="B"}))},captureB(e){Ee(D(t=>{t.ab.b=e}))},toggleAB(){let e="B";return Ee(D(t=>{e=t.ab.live==="A"?"B":"A",t.ab.live=e})),e},acceptB(){Ee(D(e=>{e.ab.a=null,e.ab.live="B"}))},revertToA(){Ee(D(e=>{e.ab.b=null,e.ab.live="A"}))},resetAB(){Ee(D(e=>{e.ab={a:null,b:null,live:"B"}}))},addRegionPin(e){const t=`pin-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6)}`,n=e.colorSlot??Ye.regionPins.length%5,a={...e,colorSlot:n,id:t,createdAt:Date.now()};return Ee(D(r=>{r.regionPins.length>=5&&r.regionPins.shift(),r.regionPins.push(a)})),wt(),Fe.emit("pin.create",{kind:"region",id:t}),a},removeRegionPin(e){Ee(D(t=>{const n=t.regionPins.findIndex(a=>a.id===e);n>=0&&t.regionPins.splice(n,1)})),wt(),Fe.emit("pin.remove",{kind:"region",id:e})},clearRegionPins(){Ee(D(e=>{e.regionPins=[]})),wt()},toggleParamPin(e,t){let n=!1;return Ee(D(a=>{const r=a.paramPins.findIndex(i=>i.key===e);r>=0?(a.paramPins.splice(r,1),n=!1):(a.paramPins.push({key:e,outputIndex:t}),n=!0)})),wt(),n?Fe.emit("pin.create",{kind:"param",id:e}):Fe.emit("pin.remove",{kind:"param",id:e}),n},isParamPinned(e){return Ye.paramPins.some(t=>t.key===e)},clearParamPins(){Ee(D(e=>{e.paramPins=[]})),wt()},paramPinMask(e,t){const n=new Uint8Array(e);for(const a of Ye.paramPins)t&&!a.key.startsWith(t+":")||a.outputIndex>=0&&a.outputIndex{a.presets.push(n)})),wt(),n},removePreset(e){Ee(D(t=>{const n=t.presets.findIndex(a=>a.id===e);n>=0&&t.presets.splice(n,1)})),wt()},reset(){Ee(nr()),Xn=0}},ar="nisps:exploration";function rr(){return{noiseLevel:.05,noiseFloor:.005,noiseCap:.12,noiseGrowth:1.5,noiseDecay:.97,spread:.6,learningRate:1,weightDecay:.06,autoExploreEnabled:!1,autoExploreIntervalMs:2e3,autoExploreIntensity:.5,pressureForce:0,holdMs:0}}function Vo(){const e=rr(),t=jt(ar,{});return{...e,noiseFloor:t.noiseFloor??e.noiseFloor,noiseCap:t.noiseCap??e.noiseCap,noiseGrowth:t.noiseGrowth??e.noiseGrowth,noiseDecay:t.noiseDecay??e.noiseDecay,spread:t.spread??e.spread,learningRate:t.learningRate??e.learningRate,weightDecay:t.weightDecay??e.weightDecay,autoExploreEnabled:t.autoExploreEnabled??e.autoExploreEnabled,autoExploreIntervalMs:t.autoExploreIntervalMs??e.autoExploreIntervalMs,autoExploreIntensity:t.autoExploreIntensity??e.autoExploreIntensity}}const[He,Te]=gt(Vo());function Xe(){Vt(ar,()=>({noiseFloor:He.noiseFloor,noiseCap:He.noiseCap,noiseGrowth:He.noiseGrowth,noiseDecay:He.noiseDecay,spread:He.spread,learningRate:He.learningRate,weightDecay:He.weightDecay,autoExploreEnabled:He.autoExploreEnabled,autoExploreIntervalMs:He.autoExploreIntervalMs,autoExploreIntensity:He.autoExploreIntensity}))}const I={state:He,applyCompoundParams(e){Te(D(t=>{const n=l=>{const d=e[l];return typeof d=="number"?d:void 0},a=n("noiseCap");a!==void 0&&(t.noiseCap=K(a,.005,1));const r=n("noiseGrowth");r!==void 0&&(t.noiseGrowth=K(r,1,4));const i=n("noiseDecay");i!==void 0&&(t.noiseDecay=K(i,0,1));const o=n("learningRate");o!==void 0&&(t.learningRate=K(o,.01,10));const s=n("weightDecay");s!==void 0&&(t.weightDecay=K(s,0,.5)),t.noiseLevel>t.noiseCap&&(t.noiseLevel=t.noiseCap),t.noiseLevel{t.noiseLevel=K(e,t.noiseFloor,t.noiseCap)}))},setSpread(e){Te(D(t=>{t.spread=K(e,0,1)})),Xe()},setNoiseFloor(e){Te(D(t=>{t.noiseFloor=K(e,0,t.noiseCap),t.noiseLevel{t.noiseCap=K(e,t.noiseFloor,1),t.noiseLevel>t.noiseCap&&(t.noiseLevel=t.noiseCap)})),Xe()},setNoiseGrowth(e){Te(D(t=>{t.noiseGrowth=K(e,1,4)})),Xe()},setNoiseDecay(e){Te(D(t=>{t.noiseDecay=K(e,0,1)})),Xe()},setLearningRate(e){Te(D(t=>{t.learningRate=K(e,.001,10)})),Xe()},setWeightDecay(e){Te(D(t=>{t.weightDecay=K(e,0,.5)})),Xe()},growNoise(e=.5){let t=He.noiseLevel;return Te(D(n=>{const a=K(e,0,1),r=1+(n.noiseGrowth-1)*(.5+a);t=K(n.noiseLevel*r,n.noiseFloor,n.noiseCap),n.noiseLevel=t})),t},decayNoise(e=.5){let t=He.noiseLevel;return Te(D(n=>{const a=K(e,0,1),r=n.noiseDecay-(1-n.noiseDecay)*(a-.5)*.4;t=K(n.noiseLevel*K(r,0,1),n.noiseFloor,n.noiseCap),n.noiseLevel=t})),t},setAutoExplore(e){Te(D(t=>{t.autoExploreEnabled=e})),Xe()},setAutoExploreInterval(e){Te(D(t=>{t.autoExploreIntervalMs=K(e,500,1e4)})),Xe()},setAutoExploreIntensity(e){Te(D(t=>{t.autoExploreIntensity=K(e,.1,1)})),Xe()},setPressure(e,t){Te(D(n=>{n.pressureForce=K(e,0,1),n.holdMs=Math.max(0,t)}))},reset(){Te(rr()),Xe()}},jo="_wrap_lq3ei_1",Ho="_label_lq3ei_10",Wo="_row_lq3ei_17",Uo="_input_lq3ei_23",Go="_value_lq3ei_80",Xo="_unit_lq3ei_88",Yo="_disabled_lq3ei_93",St={wrap:jo,label:Ho,row:Wo,input:Uo,value:Go,unit:Xo,disabled:Yo};var ba=E(""),Zo=E("
\n );\n};\n\nexport default C15Mode;\n","/**\n * Mode registry — maps mode_id (and the special \"c15\" placeholder) to the\n * TSX component that renders it. The ModeSwitcher reads from this list to\n * populate its dropdown, and `App.tsx` looks up the active mode here.\n *\n * Order matters — the switcher renders modes in this order.\n */\n\nimport type { Component } from 'solid-js';\nimport { PAFSynthMode } from './PAFSynthMode';\nimport { ChannelStripMode } from './ChannelStripMode';\nimport { XIASRIMode } from './XIASRIMode';\nimport { VerbFXMode } from './VerbFXMode';\nimport { MEMLCeliumMode } from './MEMLCeliumMode';\nimport { BreakOrMode } from './BreakOrMode';\nimport { ElysiamorfMode } from './ElysiamorfMode';\nimport { SoundAnalysisMIDIMode } from './SoundAnalysisMIDIMode';\nimport { C15Mode } from './C15Mode';\n\nexport interface ModeRegistration {\n /** Stable id; matches the schema's `mode_id` for firmware modes. */\n id: string;\n /** Human-readable label for the switcher. */\n label: string;\n /** Short description for the switcher tooltip. */\n description: string;\n /** TSX component rendering the mode. */\n Component: Component;\n /** True for browser-only placeholders (currently just C15). */\n placeholder?: boolean;\n}\n\nexport const MODE_REGISTRY: ReadonlyArray = [\n {\n id: 'paf_synth',\n label: 'PAF Synth',\n description: 'Phase-aligned formant synth (XY pad).',\n Component: PAFSynthMode,\n },\n {\n id: 'channel_strip',\n label: 'Channel Strip',\n description: 'EQ + dynamics processing channel.',\n Component: ChannelStripMode,\n },\n {\n id: 'xiasri',\n label: 'XIASRI',\n description: 'Audio-reactive verb / pitch engine.',\n Component: XIASRIMode,\n },\n {\n id: 'verb_fx',\n label: 'Verb FX',\n description: 'Reverb / multi-effects unit.',\n Component: VerbFXMode,\n },\n {\n id: 'memlcelium',\n label: 'MEML Celium',\n description: 'Voice + dual-MLP CV/gate via uSEQ.',\n Component: MEMLCeliumMode,\n },\n {\n id: 'breakor',\n label: 'Breakor',\n description: 'Drum / breakbeat synthesis.',\n Component: BreakOrMode,\n },\n {\n id: 'elysiamorf',\n label: 'Elysiamorf',\n description: 'Granular morphing synth.',\n Component: ElysiamorfMode,\n },\n {\n id: 'sound_analysis_midi',\n label: 'Sound Analysis → MIDI',\n description: 'Audio features → MIDI CC routing.',\n Component: SoundAnalysisMIDIMode,\n },\n {\n id: 'c15',\n label: 'C15 (browser-only)',\n description: 'C15 WASM synth. Not yet ported.',\n Component: C15Mode,\n placeholder: true,\n },\n];\n\n/** Look up a mode by id, falling back to the first registration. */\nexport function getModeById(id: string | null): ModeRegistration {\n if (!id) return MODE_REGISTRY[0]!;\n return MODE_REGISTRY.find((m) => m.id === id) ?? MODE_REGISTRY[0]!;\n}\n\nexport { ModeShell } from './ModeShell';\nexport { useModeRuntime } from './mode-runtime';\nexport type { ModeRuntime } from './mode-runtime';\n","/**\n * ModeSwitcher — top-level select that picks the active mode.\n *\n * Writes through `modeStore.switchMode(id)` so persistence + the bus event\n * fire correctly. Reads the current selection back from the store so it\n * stays in sync with persisted state on first paint.\n *\n * Audio engine switching is handled inside the mode runtime (each mode\n * routes its own engine_id through `EngineHost.setEngine` when started).\n */\n\nimport { Component, createMemo, For, Show } from 'solid-js';\nimport { modeStore } from '../stores/mode-store';\nimport { MODE_REGISTRY, getModeById } from './index';\nimport styles from './ModeSwitcher.module.css';\n\nexport const ModeSwitcher: Component = () => {\n const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id;\n const active = createMemo(() => getModeById(activeId()));\n\n return (\n
\n Mode\n modeStore.switchMode(e.currentTarget.value)}\n aria-label=\"Select active mode\"\n >\n \n {(m) => (\n \n )}\n \n \n \n {active().description}\n \n \n \n ⚠\n \n \n
\n );\n};\n\nexport default ModeSwitcher;\n","import { Component, createMemo, createSignal, lazy, onCleanup, Show } from 'solid-js';\nimport { Dynamic } from 'solid-js/web';\nimport { modeStore } from './stores/mode-store';\nimport { MODE_REGISTRY, getModeById } from './modes';\nimport { ModeSwitcher } from './modes/ModeSwitcher';\nimport styles from './App.module.css';\n\ntype Route = 'home' | 'primitives' | 'modes' | 'unknown';\n\nfunction parseRoute(path: string): Route {\n if (path === '' || path === '/' || path === '/index.html') return 'home';\n if (path === '/dev/primitives' || path === '/dev/primitives/') return 'primitives';\n if (path === '/modes' || path === '/modes/' || path.startsWith('/modes/')) return 'modes';\n return 'unknown';\n}\n\n// Lazy-loaded so the home route doesn't pay the cost of loading every\n// primitive demo. Stream 9/10 can grow the showcase freely.\nconst PrimitivesShowcase = lazy(() => import('./dev/PrimitivesShowcase'));\n\nconst App: Component = () => {\n const [route, setRoute] = createSignal(parseRoute(window.location.pathname));\n\n const onPop = () => setRoute(parseRoute(window.location.pathname));\n window.addEventListener('popstate', onPop);\n onCleanup(() => window.removeEventListener('popstate', onPop));\n\n const navigate = (path: string) => {\n if (window.location.pathname === path) return;\n window.history.pushState({}, '', path);\n setRoute(parseRoute(path));\n };\n\n return (\n
\n
\n MEMLNaut\n playground\n \n
\n
\n \n \n \n \n \n \n \n \n \n \n
\n

404

\n

No route at {window.location.pathname}.

\n

\n { e.preventDefault(); navigate('/'); }}>back to home\n

\n
\n
\n
\n
\n );\n};\n\nconst ModesPage: Component = () => {\n const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id;\n const ActiveComponent = createMemo(() => getModeById(activeId()).Component);\n\n return (\n
\n \n \n
\n );\n};\n\ninterface HomeProps {\n navigate: (path: string) => void;\n}\n\nconst Home: Component = (props) => {\n return (\n
\n

MEMLNaut Playground

\n

\n Interactive ML control of audio. Stream 9: nine modes, one shell.\n

\n
    \n
  • \n {\n e.preventDefault();\n props.navigate('/modes');\n }}\n >\n Modes\n {' '}\n — pick a mode and start playing\n
  • \n
  • \n {\n e.preventDefault();\n props.navigate('/dev/primitives');\n }}\n >\n Primitives showcase\n {' '}\n — UI building blocks\n
  • \n
\n

\n ML inference runs on the main thread via WASM. Audio synthesis runs\n in an AudioWorklet — start it from inside any mode using the \"Start\n audio\" button. Audio cannot autoplay (browser policy).\n

\n
\n );\n};\n\nexport default App;\n","/**\n * Debug probe: window.__nisps\n *\n * Synchronous-or-immediate Promise API for Playwright tests and dev console\n * use. Bypasses SolidJS reactivity by reading/writing the underlying stores\n * and WASM directly — values propagate to the UI on the next reactive tick,\n * but the probe always returns the freshest state.\n *\n * Stream 10 expanded the surface to cover stream 8/9 features:\n * - Snapshot stack (push, pop, list)\n * - A/B compare (capture, toggle, accept, revert)\n * - Region pinning (add, remove, clear)\n * - Auto-explore (toggle, interval, intensity)\n * - Heatmap (refresh, color mode, getCells)\n * - Weight health (histogram, status, layer stats)\n * - Session presets (save, load, list, share URL)\n * - Override application (read, write, clear)\n *\n * Test contract: every method either returns a value, returns null/empty,\n * or returns an immediately-resolved Promise. None of them throw — bad\n * input is silently ignored.\n */\n\nimport { batch, untrack } from 'solid-js';\nimport { mlStore } from '../stores/ml-store';\nimport { modeStore } from '../stores/mode-store';\nimport { sessionStore } from '../stores/session-store';\nimport { explorationStore } from '../stores/exploration-store';\nimport { controlStore } from '../stores/control-store';\nimport { inputStore } from '../stores/input-store';\nimport { outputStore } from '../stores/output-store';\nimport { coreBus } from '../stores/bus';\nimport {\n autoSnapshot,\n undoLastSnapshot,\n captureA as snapCaptureA,\n toggleAB as snapToggleAB,\n acceptB as snapAcceptB,\n revertToA as snapRevertToA,\n} from '../features/snapshots';\nimport { HeatmapSampler } from '../features/heatmap-sampler';\nimport {\n weightHistogram,\n weightStatus as healthStatus,\n layerNorms,\n gradientStatuses,\n layerWeightCounts,\n} from '../features/weight-health';\nimport {\n captureSessionPreset,\n restoreSessionPreset,\n saveNamedPreset,\n loadNamedPreset,\n buildShareUrl,\n applyUrlParams,\n type SessionPresetPayload,\n} from '../features/session-preset';\nimport type { HeatmapColorMode } from '../features/heatmap-sampler';\n\nexport interface DebugProbe {\n /** Current 126-element output vector (Float32Array). */\n getOutputs(): Float32Array;\n /** Current per-param post-override values (length = active mode params). */\n getParamOutputs(): Float32Array;\n /** Last training loss, or null if no training has occurred. */\n getLoss(): number | null;\n /** Loss history of last training run. */\n getLossHistory(): ReadonlyArray;\n /** Flat weight array. */\n getWeights(): Float32Array;\n /** Number of training examples currently in the dataset. */\n getExampleCount(): number;\n /** Set joystick X/Y in [0,1] and run inference. */\n setInputs(x: number, y: number): void;\n /** Trigger thumbs-up RL feedback. */\n thumbsUp(): void;\n /** Trigger thumbs-down RL feedback. */\n thumbsDown(): void;\n /** Synchronous training; returns final loss. */\n train(): number;\n /** Async training; returns Promise. */\n trainAsync(): Promise;\n /** Randomize weights with default spread. */\n randomise(): void;\n /** Clear all training examples. */\n clearExamples(): void;\n /** Force a save to localStorage now (no debounce). */\n saveState(): void;\n /** Non-destructive loss query against current dataset. */\n evalLoss(): number | null;\n /** Batch inference: input is Nx2 array of [x,y] pairs. */\n inferBatch(points: ReadonlyArray): Float32Array;\n /** Per-layer weight statistics: layerCount * 4 floats (mean|w|, max|w|, dead%, sat%). */\n getLayerStats(): Float32Array;\n\n // Snapshot / undo / A/B\n pushSnapshot(tag: string): void;\n popSnapshot(): boolean;\n listSnapshots(): ReadonlyArray<{ id: string; tag: string; timestamp: number; noiseLevel: number }>;\n clearSnapshots(): void;\n captureA(): void;\n toggleAB(): 'A' | 'B';\n acceptB(): void;\n revertToA(): void;\n\n // Pins\n addRegionPin(pin: { x: number; y: number; width: number; height: number }): string;\n removeRegionPin(id: string): void;\n toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean;\n isParamPinned(modeId: string, paramName: string): boolean;\n\n // Auto-explore\n setAutoExplore(enabled: boolean): void;\n setAutoExploreInterval(ms: number): void;\n setAutoExploreIntensity(v: number): void;\n\n // Pressure\n setPressure(force: number, holdMs?: number): void;\n\n // Heatmap\n refreshHeatmap(force?: boolean): boolean;\n getHeatmapCells(): Float32Array;\n setHeatmapColorMode(mode: HeatmapColorMode): void;\n\n // Weight health\n getWeightHistogram(): number[];\n getWeightStatus(): 'dead' | 'saturating' | 'healthy';\n getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array): { norms: number[]; status: ReturnType };\n\n // Overrides\n setOverride(modeId: string, paramName: string, override: Partial<{ min: number; max: number; curve: string; muted: boolean; pinned: boolean; frozen: boolean; fixedValue: number }>): void;\n clearOverride(modeId: string, paramName: string): void;\n clearAllOverrides(modeId: string): void;\n getOverride(modeId: string, paramName: string): unknown;\n\n // Compound axes\n setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void;\n applyControlPreset(id: string): boolean;\n\n // Pipelines\n setZoom(zoom: number): void;\n setSpread(spread: number): void;\n setOutputFreeze(frozen: boolean): void;\n\n // Session presets\n captureSessionPreset(withWeights?: boolean): SessionPresetPayload;\n restoreSessionPreset(payload: SessionPresetPayload): boolean;\n saveSessionPreset(name: string, withWeights?: boolean): void;\n loadSessionPreset(id: string): boolean;\n buildShareUrl(): string;\n applyUrlParams(search?: string): boolean;\n\n // Bus\n emit(topic: string, data: unknown): void;\n on(topic: string, handler: (data: unknown) => void): () => void;\n\n /** True once the WASM is fully initialised. */\n readonly __ready: boolean;\n /** Force initialisation. Returns a promise that resolves when the WASM is ready. */\n __init(): Promise;\n}\n\ndeclare global {\n interface Window {\n __nisps?: DebugProbe;\n }\n}\n\nconst EMPTY_F32 = new Float32Array(0);\n\nlet lazyInitPromise: Promise | null = null;\nfunction lazyInit(): Promise {\n if (mlStore.iml) return Promise.resolve();\n if (!lazyInitPromise) {\n lazyInitPromise = mlStore.initialize().then(() => undefined);\n }\n return lazyInitPromise;\n}\n\n// Probe-local heatmap sampler (independent of any active mode runtime).\nconst probeHeatmap = new HeatmapSampler({ resolution: 16 });\n\nconst probe: DebugProbe = {\n get __ready(): boolean {\n return !!mlStore.iml && mlStore.state.ready;\n },\n\n __init(): Promise {\n return lazyInit();\n },\n\n getOutputs(): Float32Array {\n return mlStore.outputs();\n },\n\n getParamOutputs(): Float32Array {\n // The probe doesn't have a runtime reference; recompute from raw outputs.\n const raw = mlStore.outputs();\n if (raw.length === 0) return EMPTY_F32;\n // Just return the raw outputs — actual override application requires\n // schema knowledge that the probe doesn't track. Tests can read\n // overrides via `getOverride` instead.\n return raw;\n },\n\n getLoss(): number | null {\n return mlStore.state.lastLoss;\n },\n\n getLossHistory(): ReadonlyArray {\n return mlStore.state.lossHistory;\n },\n\n getWeights(): Float32Array {\n return mlStore.getWeights();\n },\n\n getExampleCount(): number {\n return mlStore.state.exampleCount;\n },\n\n setInputs(x: number, y: number): void {\n if (!mlStore.iml) {\n void lazyInit();\n return;\n }\n untrack(() => mlStore.iml!.inferXY(x, y));\n },\n\n thumbsUp(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before thumbs-up (probe)');\n mlStore.iml!.train(explorationStore.state.learningRate);\n explorationStore.decayNoise(0.5);\n });\n },\n\n thumbsDown(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before thumbs-down (probe)');\n const cap = explorationStore.state.noiseCap;\n const spread = explorationStore.state.spread;\n mlStore.iml!.moveWeights(cap, spread);\n explorationStore.growNoise(0.5);\n });\n },\n\n train(): number {\n if (!mlStore.iml) {\n void lazyInit();\n return 0;\n }\n return untrack(() => {\n autoSnapshot('before train (probe)');\n return mlStore.iml!.train(explorationStore.state.learningRate);\n });\n },\n\n async trainAsync(): Promise {\n await lazyInit();\n if (!mlStore.iml) return 0;\n autoSnapshot('before trainAsync (probe)');\n return mlStore.iml.trainAsync(explorationStore.state.learningRate);\n },\n\n randomise(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before randomize (probe)');\n mlStore.iml!.randomiseWeights(explorationStore.state.spread);\n });\n },\n\n clearExamples(): void {\n mlStore.clearExamples();\n },\n\n saveState(): void {\n mlStore.saveNow();\n },\n\n evalLoss(): number | null {\n if (!mlStore.iml) return null;\n return mlStore.iml.evalLoss();\n },\n\n inferBatch(points: ReadonlyArray): Float32Array {\n if (!mlStore.iml) return new Float32Array(points.length * mlStore.state.outputSize);\n return mlStore.iml.inferBatch(points);\n },\n\n getLayerStats(): Float32Array {\n if (!mlStore.iml) return EMPTY_F32;\n return mlStore.iml.getLayerStatsFlat();\n },\n\n // ----- Snapshot / undo / A/B ---------------------------------------------\n\n pushSnapshot(tag: string): void {\n autoSnapshot(tag);\n },\n\n popSnapshot(): boolean {\n return undoLastSnapshot();\n },\n\n listSnapshots() {\n return sessionStore.listSnapshots().map((s) => ({\n id: s.id,\n tag: s.tag,\n timestamp: s.timestamp,\n noiseLevel: s.noiseLevel,\n }));\n },\n\n clearSnapshots(): void {\n sessionStore.clearSnapshots();\n },\n\n captureA(): void {\n snapCaptureA();\n },\n\n toggleAB(): 'A' | 'B' {\n return snapToggleAB();\n },\n\n acceptB(): void {\n snapAcceptB();\n },\n\n revertToA(): void {\n snapRevertToA();\n },\n\n // ----- Pins ---------------------------------------------------------------\n\n addRegionPin(pin) {\n const created = sessionStore.addRegionPin(pin);\n return created.id;\n },\n\n removeRegionPin(id: string): void {\n sessionStore.removeRegionPin(id);\n },\n\n toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean {\n return sessionStore.toggleParamPin(`${modeId}:${paramName}`, outputIndex);\n },\n\n isParamPinned(modeId: string, paramName: string): boolean {\n return sessionStore.isParamPinned(`${modeId}:${paramName}`);\n },\n\n // ----- Auto-explore -------------------------------------------------------\n\n setAutoExplore(enabled: boolean): void {\n explorationStore.setAutoExplore(enabled);\n },\n\n setAutoExploreInterval(ms: number): void {\n explorationStore.setAutoExploreInterval(ms);\n },\n\n setAutoExploreIntensity(v: number): void {\n explorationStore.setAutoExploreIntensity(v);\n },\n\n setPressure(force: number, holdMs: number = 0): void {\n explorationStore.setPressure(force, holdMs);\n },\n\n // ----- Heatmap ------------------------------------------------------------\n\n refreshHeatmap(force = false): boolean {\n if (!mlStore.iml) return false;\n const cfg = inputStore.config;\n const z = cfg.zoom;\n const cx = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorX;\n const cy = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorY;\n return probeHeatmap.update({ cx, cy, zoom: z }, force);\n },\n\n getHeatmapCells(): Float32Array {\n return probeHeatmap.getCells();\n },\n\n setHeatmapColorMode(mode: HeatmapColorMode): void {\n probeHeatmap.setColorMode(mode);\n },\n\n // ----- Weight health ------------------------------------------------------\n\n getWeightHistogram(): number[] {\n return weightHistogram(mlStore.weights());\n },\n\n getWeightStatus() {\n return healthStatus(mlStore.weights());\n },\n\n getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array) {\n if (!mlStore.iml) return { norms: [], status: [] };\n const arch = mlStore.iml.architecture;\n const sizes = layerWeightCounts({\n inputSize: arch.inputSize,\n hidden: arch.hidden,\n outputSize: arch.outputSize,\n });\n const norms = layerNorms(beforeWeights, afterWeights, sizes);\n return { norms, status: gradientStatuses(norms) };\n },\n\n // ----- Overrides ----------------------------------------------------------\n\n setOverride(modeId: string, paramName: string, patch): void {\n const existing = modeStore.getOverride(modeId, paramName);\n const next = {\n min: existing?.min ?? 0,\n max: existing?.max ?? 1,\n curve: existing?.curve ?? 'linear',\n curveParam: existing?.curveParam,\n muted: existing?.muted ?? false,\n pinned: existing?.pinned ?? false,\n frozen: existing?.frozen ?? false,\n fixedValue: existing?.fixedValue ?? 0.5,\n ...patch,\n } as Parameters[2];\n modeStore.setOverride(modeId, paramName, next);\n },\n\n clearOverride(modeId: string, paramName: string): void {\n modeStore.clearOverride(modeId, paramName);\n },\n\n clearAllOverrides(modeId: string): void {\n modeStore.clearAllOverrides(modeId);\n },\n\n getOverride(modeId: string, paramName: string) {\n return modeStore.getOverride(modeId, paramName);\n },\n\n // ----- Compound axes ------------------------------------------------------\n\n setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void {\n controlStore.setAxis(axis, value);\n },\n\n applyControlPreset(id: string): boolean {\n return controlStore.applyPreset(id);\n },\n\n // ----- Pipelines ----------------------------------------------------------\n\n setZoom(zoom: number): void {\n inputStore.setZoom(zoom);\n },\n\n setSpread(spread: number): void {\n explorationStore.setSpread(spread);\n },\n\n setOutputFreeze(frozen: boolean): void {\n outputStore.setFreezeOutput(frozen);\n },\n\n // ----- Session presets ----------------------------------------------------\n\n captureSessionPreset(withWeights = false): SessionPresetPayload {\n return captureSessionPreset(withWeights);\n },\n\n restoreSessionPreset(payload: SessionPresetPayload): boolean {\n return batch(() => restoreSessionPreset(payload));\n },\n\n saveSessionPreset(name: string, withWeights = false): void {\n saveNamedPreset(name, withWeights);\n },\n\n loadSessionPreset(id: string): boolean {\n return batch(() => loadNamedPreset(id));\n },\n\n buildShareUrl(): string {\n return buildShareUrl();\n },\n\n applyUrlParams(search?: string): boolean {\n return batch(() => applyUrlParams(search));\n },\n\n // ----- Bus ----------------------------------------------------------------\n\n emit(topic: string, data: unknown): void {\n // Lossy cast — the probe is intentionally weakly typed.\n coreBus.emit(topic as never, data as never);\n },\n\n on(topic: string, handler): () => void {\n return coreBus.onPrefix(topic, handler);\n },\n};\n\n/**\n * Install the probe on window. Idempotent — the probe object is a\n * singleton, so capturing `window.__nisps` once is safe across hot\n * reloads and re-installs.\n */\nexport function installDebugProbe(): void {\n if (typeof window === 'undefined') return;\n window.__nisps = probe;\n}\n","/* @refresh reload */\nimport { render } from 'solid-js/web';\nimport App from './App';\nimport './styles/tokens.css';\nimport { installDebugProbe } from './debug/probe';\nimport { applyUrlParams } from './features/session-preset';\n\nconst root = document.getElementById('root');\nif (!root) {\n throw new Error('Root element #root not found');\n}\n\n// Install debug probe early. Playwright tests use it to drive the ML\n// engine programmatically.\ninstallDebugProbe();\n\n// Apply URL params (?session=..., ?boldness=..., etc) before mounting so\n// stores read the resolved values on first render.\ntry {\n applyUrlParams();\n} catch (err) {\n console.warn('[main] applyUrlParams failed:', err);\n}\n\nrender(() => , root);\n"],"file":"assets/index-C1-a2ZPT.js"} \ No newline at end of file diff --git a/playground/dist/assets/index-D19lnkK_.css b/playground/dist/assets/index-D19lnkK_.css new file mode 100644 index 0000000..8502003 --- /dev/null +++ b/playground/dist/assets/index-D19lnkK_.css @@ -0,0 +1 @@ +._wrap_1uogo_1{display:flex;flex-direction:column;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3);font-size:var(--fs-sm)}._row_1uogo_12{display:flex;gap:var(--sp-2)}._btn_1uogo_17{flex:1;height:48px;font-size:var(--fs-lg);border-radius:var(--r-1);font-weight:600}._thumbsUp_1uogo_25{background:#6bc26b29;border-color:#6bc26b66;color:var(--good)}._thumbsUp_1uogo_25:hover:not(:disabled){background:#6bc26b47}._thumbsDown_1uogo_34{background:#ef5b5b29;border-color:#ef5b5b66;color:var(--bad)}._thumbsDown_1uogo_34:hover:not(:disabled){background:#ef5b5b47}._undo_1uogo_43{background:var(--bg-2);color:var(--fg-mute)}._btnAlt_1uogo_48{flex:1;font-size:var(--fs-sm);text-transform:uppercase;letter-spacing:.06em}._status_1uogo_55{display:flex;gap:var(--sp-3);padding-top:var(--sp-2);border-top:1px solid var(--line);flex-wrap:wrap}._statusItem_1uogo_63{display:flex;flex-direction:column;gap:2px;font-size:var(--fs-xs)}._statusLabel_1uogo_70{color:var(--fg-dim);text-transform:uppercase;letter-spacing:.08em}._statusValue_1uogo_76{color:var(--fg);font-variant-numeric:tabular-nums}._busy_1uogo_81{color:var(--accent-2);align-self:center;font-style:italic}._axis_1gq0t_1{display:flex;flex-direction:column;gap:var(--sp-1);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-2) var(--sp-3);--axis-accent: var(--accent)}._head_1gq0t_12{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-sm)}._label_1gq0t_19{font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--fg);flex:1}._preset_1gq0t_27{color:var(--axis-accent);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.06em}._value_1gq0t_34{font-variant-numeric:tabular-nums;color:var(--fg-mute);font-size:var(--fs-xs);min-width:4ch;text-align:right}._input_1gq0t_42{appearance:none;-webkit-appearance:none;width:100%;height:24px;background:transparent;margin:0;cursor:pointer}._input_1gq0t_42::-webkit-slider-runnable-track{height:6px;border-radius:999px;background:var(--bg-3)}._input_1gq0t_42::-moz-range-track{height:6px;border-radius:999px;background:var(--bg-3)}._input_1gq0t_42::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:18px;height:18px;border-radius:50%;background:var(--axis-accent);margin-top:-6px;box-shadow:0 0 10px var(--axis-accent);cursor:pointer}._input_1gq0t_42::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--axis-accent);border:none;box-shadow:0 0 10px var(--axis-accent)}._input_1gq0t_42:focus{outline:none}._endpoints_1gq0t_87{display:flex;justify-content:space-between;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-dim)}._disabled_1gq0t_96{opacity:.5;pointer-events:none}._group_1erfx_1{display:inline-flex;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-pill);padding:2px;gap:2px}._pill_1erfx_10{background:transparent;border:0;border-radius:var(--r-pill);padding:6px 14px;font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}._pill_1erfx_10:hover:not(:disabled):not(._active_1erfx_22){color:var(--fg)}._active_1erfx_22{background:var(--accent);color:var(--bg)}._disabled_1erfx_31{opacity:.5;pointer-events:none}._root_xdkqq_1{position:fixed;inset:0;z-index:var(--z-drawer);pointer-events:none}._scrim_xdkqq_8{position:absolute;inset:0;background:#00000073;pointer-events:auto;animation:_fadeIn_xdkqq_1 var(--dur-fast) var(--ease)}._panel_xdkqq_16{position:absolute;top:0;bottom:0;background:var(--bg-1);border-left:1px solid var(--line);pointer-events:auto;display:flex;flex-direction:column;box-shadow:0 0 24px #00000080;animation:_slideIn_xdkqq_1 var(--dur-med) var(--ease)}._left_xdkqq_29 ._panel_xdkqq_16{left:0;border-right:1px solid var(--line);border-left:0;animation-name:_slideInLeft_xdkqq_1}._right_xdkqq_36 ._panel_xdkqq_16{right:0}._header_xdkqq_40{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line);flex:0 0 auto}._title_xdkqq_49{flex:1;font-size:var(--fs-md);text-transform:uppercase;letter-spacing:.06em;color:var(--fg-mute)}._close_xdkqq_57{background:transparent;border:0;font-size:22px;color:var(--fg-mute);width:32px;height:32px;border-radius:var(--r-1);padding:0}._close_xdkqq_57:hover{background:var(--bg-2);color:var(--fg)}._body_xdkqq_73{flex:1;overflow:auto;padding:var(--sp-3) var(--sp-4)}@keyframes _fadeIn_xdkqq_1{0%{opacity:0}to{opacity:1}}@keyframes _slideIn_xdkqq_1{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes _slideInLeft_xdkqq_1{0%{transform:translate(-100%)}to{transform:translate(0)}}._canvas_mlv8d_1{display:block;border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-1);touch-action:none;cursor:pointer}._wrap_lq3ei_1{display:flex;flex-direction:column;gap:var(--sp-1);font-size:var(--fs-sm);color:var(--fg);user-select:none}._label_lq3ei_10{color:var(--fg-mute);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.06em}._row_lq3ei_17{display:flex;gap:var(--sp-3);align-items:center}._input_lq3ei_23{flex:1;appearance:none;-webkit-appearance:none;background:transparent;height:24px;margin:0;cursor:pointer}._input_lq3ei_23::-webkit-slider-runnable-track{height:4px;border-radius:999px;background:linear-gradient(to right,var(--accent) 0%,var(--accent) calc(var(--sp, 0) * 100%),var(--bg-3) 0%);background-color:var(--bg-3)}._input_lq3ei_23::-moz-range-track{height:4px;border-radius:999px;background:var(--bg-3)}._input_lq3ei_23::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);margin-top:-6px;box-shadow:0 0 8px #ff6a0066;cursor:pointer;transition:transform var(--dur-fast) var(--ease)}._input_lq3ei_23::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--accent);border:none;box-shadow:0 0 8px #ff6a0066}._input_lq3ei_23:hover::-webkit-slider-thumb{transform:scale(1.15)}._input_lq3ei_23:focus{outline:none}._input_lq3ei_23:focus::-webkit-slider-thumb{box-shadow:0 0 0 3px #ff6a004d}._value_lq3ei_80{font-variant-numeric:tabular-nums;font-size:var(--fs-xs);color:var(--fg-mute);min-width:4ch;text-align:right}._unit_lq3ei_88{color:var(--fg-dim);margin-left:2px}._disabled_lq3ei_93{opacity:.5;pointer-events:none}._editor_1yxvy_1{display:flex;flex-direction:column;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3)}._compact_1yxvy_11{padding:var(--sp-2);gap:var(--sp-1)}._header_1yxvy_16{display:flex;align-items:baseline;gap:var(--sp-2)}._name_1yxvy_22{flex:1;font-size:var(--fs-sm);color:var(--fg)}._id_1yxvy_28{color:var(--fg-dim);font-size:var(--fs-xs)}._toggles_1yxvy_33{display:flex;gap:var(--sp-2)}._fields_1yxvy_38{display:flex;flex-direction:column;gap:var(--sp-2)}._curveRow_1yxvy_44{display:flex;align-items:center;gap:var(--sp-3);font-size:var(--fs-xs)}._fieldLabel_1yxvy_51{color:var(--fg-mute);text-transform:uppercase;letter-spacing:.06em;flex:0 0 auto}._select_1yxvy_58{flex:1;background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-2);font-size:var(--fs-sm)}._wrap_389vj_1{display:flex;flex-direction:column;align-items:stretch;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-2)}._canvas_389vj_12{display:block;background:var(--bg-2);border-radius:var(--r-1)}._label_389vj_18{align-self:center;padding:4px 12px;font-size:var(--fs-xs);letter-spacing:.1em;border-radius:var(--r-pill);background:var(--bg-2);color:var(--fg);font-weight:600;transition:box-shadow var(--dur-med) var(--ease)}._pulsing_389vj_30{animation:_pulse_389vj_1 1.6s var(--ease) infinite}@keyframes _pulse_389vj_1{0%,to{opacity:1}50%{opacity:.65}}._canvas_rdbrc_1{display:block;border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-1);image-rendering:pixelated}._canvas_1ra7r_1{display:block;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._wrap_14adf_1{display:flex;flex-direction:column;gap:var(--sp-2)}._section_14adf_7{border-top:1px solid var(--line);padding-top:var(--sp-2)}._section_14adf_7:first-of-type{border-top:none;padding-top:0}._sectionHeader_14adf_17{display:flex;align-items:center;gap:var(--sp-2);width:100%;background:transparent;border:none;padding:var(--sp-2) 0;font-size:var(--fs-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);cursor:pointer;text-align:left;font-family:var(--font-mono)}._sectionHeader_14adf_17:hover{color:var(--fg)}._caret_14adf_38{color:var(--fg-dim);font-size:var(--fs-xs);width:12px;display:inline-block}._badge_14adf_45{margin-left:auto;font-size:var(--fs-xs);background:var(--bg-3);color:var(--fg-mute);padding:1px 6px;border-radius:var(--r-1);font-family:var(--font-mono)}._body_14adf_55{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-2) 0 var(--sp-3)}._row_14adf_62{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}._fieldLabel_14adf_69{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.06em;min-width:90px}._checkbox_14adf_77{display:flex;align-items:center;gap:var(--sp-1);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._subhead_14adf_86{font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);margin:var(--sp-2) 0 var(--sp-1)}._muted_14adf_94{color:var(--fg-dim);font-size:var(--fs-xs);font-family:var(--font-mono)}._btnAlt_14adf_100{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-3);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._btnAlt_14adf_100:hover:not(:disabled){background:var(--bg-3)}._btnAlt_14adf_100:disabled{opacity:.5;cursor:not-allowed}._btnTiny_14adf_119{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:2px 8px;font-size:var(--fs-xs);color:var(--fg-mute);cursor:pointer}._btnTiny_14adf_119:hover{color:var(--fg);background:var(--bg-3)}._snapshotList_14adf_134,._presetList_14adf_135{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:220px;overflow-y:auto}._snapshotBtn_14adf_146{display:flex;flex-direction:column;width:100%;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-2);text-align:left;cursor:pointer;color:var(--fg)}._snapshotBtn_14adf_146:hover{background:var(--bg-3)}._snapshotTag_14adf_163{font-size:var(--fs-sm);font-family:var(--font-mono)}._snapshotMeta_14adf_168{font-size:var(--fs-xs);color:var(--fg-mute);font-family:var(--font-mono)}._presetRow_14adf_174{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-1);border-bottom:1px dashed var(--line)}._presetRow_14adf_174>span{flex:1;font-size:var(--fs-sm)}._paramList_14adf_187{display:flex;flex-direction:column;gap:var(--sp-2);max-height:360px;overflow-y:auto}._layerStats_14adf_195{display:flex;flex-direction:column;gap:var(--sp-1);font-family:var(--font-mono);font-size:var(--fs-xs)}._layerStatRow_14adf_203{display:flex;align-items:center;gap:var(--sp-2);padding:2px var(--sp-2);background:var(--bg-2);border-radius:var(--r-1)}._layerStatLabel_14adf_212{color:var(--accent);font-weight:600;min-width:24px}._layerStatValue_14adf_218{flex:1;color:var(--fg-mute)}._layerStatStatus_14adf_223{font-size:10px;text-transform:uppercase;padding:1px 6px;border-radius:var(--r-1);background:var(--bg-3)}._layerStatStatus_14adf_223[data-status=dead]{color:#999}._layerStatStatus_14adf_223[data-status=saturating]{color:var(--warn)}._layerStatStatus_14adf_223[data-status=healthy]{color:var(--good)}._textInput_14adf_235{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);color:var(--fg);font-family:var(--font-mono);font-size:var(--fs-sm);padding:var(--sp-1) var(--sp-2);flex:1}._hr_14adf_246{border:none;border-top:1px dashed var(--line);margin:var(--sp-2) 0}._shell_jqlsx_1{display:grid;grid-template-rows:auto 1fr auto auto;gap:var(--sp-4);min-height:100%;padding:var(--sp-4);background:var(--bg);color:var(--fg)}._header_jqlsx_11{display:flex;align-items:center;gap:var(--sp-3);padding-bottom:var(--sp-3);border-bottom:1px solid var(--line)}._title_jqlsx_19{font-size:var(--fs-lg);font-weight:600;color:var(--accent);margin:0}._subtitle_jqlsx_26{font-size:var(--fs-sm);color:var(--fg-mute);margin:0}._audioToggle_jqlsx_32{margin-left:auto;display:flex;align-items:center;gap:var(--sp-2)}._audioBtn_jqlsx_39{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._audioBtn_jqlsx_39._on_jqlsx_49{border-color:var(--accent);color:var(--accent)}._audioBtn_jqlsx_39:hover{background:var(--bg-3)}._voiceSpaces_jqlsx_58{display:flex;align-items:center;gap:var(--sp-2)}._voiceSpacesLabel_jqlsx_64{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.08em}._body_jqlsx_71{display:grid;grid-template-columns:minmax(320px,1fr) minmax(280px,380px);gap:var(--sp-4);align-items:start}@media (max-width: 900px){._body_jqlsx_71{grid-template-columns:1fr}}._primaryArea_jqlsx_84{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:var(--sp-4);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);min-height:280px}._outputArea_jqlsx_96{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);min-height:200px}._controls_jqlsx_107{display:flex;gap:var(--sp-4);flex-wrap:wrap;align-items:stretch;padding:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._controlAxes_jqlsx_118{flex:1 1 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--sp-3)}._trainingPanel_jqlsx_125{flex:0 0 auto}._drawerToggleBar_jqlsx_129{display:flex;gap:var(--sp-2);justify-content:flex-end}._drawerToggleBtn_jqlsx_135{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);cursor:pointer;font-size:var(--fs-sm);color:var(--fg-mute)}._drawerToggleBtn_jqlsx_135:hover{color:var(--fg);background:var(--bg-3)}._statusLine_jqlsx_150{font-size:var(--fs-xs);color:var(--fg-dim);font-family:var(--font-mono)}._frozen_jqlsx_156{color:var(--accent-2)}._notReady_jqlsx_160{color:var(--warn)}._pad_1w4bb_1{position:relative;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);touch-action:none;cursor:crosshair;outline:none;user-select:none;overflow:hidden}._pad_1w4bb_1:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px #ff6a004d}._dragging_1w4bb_18{cursor:crosshair}._disabled_1w4bb_22{opacity:.4;pointer-events:none}._grid_1w4bb_27{position:absolute;inset:0;pointer-events:none;opacity:.5}._gridH_1w4bb_34,._gridV_1w4bb_34{position:absolute;background:var(--line-strong)}._gridH_1w4bb_34{left:0;right:0;top:50%;height:1px}._gridV_1w4bb_34{top:0;bottom:0;left:50%;width:1px}._dot_1w4bb_53{position:absolute;width:14px;height:14px;border-radius:50%;background:var(--accent-2);box-shadow:0 0 10px #00ccff80;pointer-events:none;top:0;left:0}._canvas_136xq_1{display:block;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._compact_136xq_8{border-radius:var(--r-1)}._container_wvoct_1{position:relative;background:var(--bg-1);border-radius:50%;border:1px solid var(--line);touch-action:none;cursor:grab;outline:none;user-select:none;overflow:hidden}._container_wvoct_1:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px #ff6a004d}._dragging_wvoct_18{cursor:grabbing}._disabled_wvoct_22{opacity:.4;pointer-events:none}._boundary_wvoct_27{position:absolute;inset:6%;border-radius:50%;border:1px dashed var(--line-strong);pointer-events:none}._crosshair_wvoct_35{position:absolute;inset:0;pointer-events:none;opacity:.4}._hLine_wvoct_42,._vLine_wvoct_42{position:absolute;background:var(--line-strong)}._hLine_wvoct_42{left:0;right:0;top:50%;height:1px}._vLine_wvoct_42{top:0;bottom:0;left:50%;width:1px}._knob_wvoct_61{position:absolute;width:24px;height:24px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px #ff6a0080;pointer-events:none;top:0;left:0}._bar_m3f4r_1{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-4);background:var(--bg-1);border-bottom:1px solid var(--line)}._label_m3f4r_10{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.08em}._select_m3f4r_17{background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-3);font-family:var(--font-mono);font-size:var(--fs-sm);cursor:pointer}._select_m3f4r_17:hover{border-color:var(--line-strong)}._select_m3f4r_17:focus{outline:1px solid var(--accent)}._description_m3f4r_36{font-size:var(--fs-xs);color:var(--fg-dim)}._placeholder_m3f4r_41{color:var(--warn)}._app_vixlr_1{display:flex;flex-direction:column;min-height:100%}._header_vixlr_7{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line);background:var(--bg-1)}._dim_vixlr_16{color:var(--fg-mute);font-size:var(--fs-sm)}._nav_vixlr_21{margin-left:auto;display:flex;gap:var(--sp-2)}._nav_vixlr_21 button{padding:var(--sp-1) var(--sp-3);font-size:var(--fs-sm)}._active_vixlr_32{border-color:var(--accent)!important;color:var(--accent)}._main_vixlr_37{flex:1;padding:var(--sp-5);overflow:auto}._home_vixlr_43{max-width:720px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-4)}._title_vixlr_51{font-size:var(--fs-xl);margin:0;color:var(--accent)}._tagline_vixlr_57{color:var(--fg-mute);margin:0}._linkList_vixlr_62{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--sp-2)}._note_vixlr_71{color:var(--fg-dim);font-size:var(--fs-sm);margin-top:var(--sp-4)}._notFound_vixlr_77{max-width:540px;margin:var(--sp-6) auto}._notFound_vixlr_77 h1{color:var(--accent)}:root{--bg: #0d0d0d;--bg-1: #141414;--bg-2: #1c1c1c;--bg-3: #242424;--fg: #e8e8e8;--fg-mute: #9a9a9a;--fg-dim: #5a5a5a;--line: #2a2a2a;--line-strong: #3a3a3a;--accent: #ff6a00;--accent-2: #00ccff;--accent-3: #ffa860;--good: #6bc26b;--warn: #f5c45e;--bad: #ef5b5b;--info: #5b9eef;--pin-1: rgba(255, 106, 0, .25);--pin-2: rgba(0, 204, 255, .25);--pin-3: rgba(180, 100, 255, .25);--pin-4: rgba(80, 200, 120, .25);--pin-5: rgba(255, 200, 80, .25);--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace;--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--fs-xs: 11px;--fs-sm: 13px;--fs-md: 15px;--fs-lg: 18px;--fs-xl: 24px;--sp-0: 2px;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-5: 24px;--sp-6: 32px;--r-1: 4px;--r-2: 8px;--r-3: 14px;--r-pill: 999px;--ease: cubic-bezier(.25, .8, .35, 1);--dur-fast: .12s;--dur-med: .22s;--dur-slow: .36s;--z-bg: 0;--z-content: 10;--z-overlay: 100;--z-drawer: 200;--z-modal: 1000}*{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:var(--bg);color:var(--fg);font-family:var(--font-mono);font-size:var(--fs-md);-webkit-tap-highlight-color:transparent}button{font-family:inherit;font-size:inherit;background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);cursor:pointer;transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}button:hover:not(:disabled){background:var(--bg-3);border-color:var(--line-strong)}button:disabled{color:var(--fg-dim);cursor:not-allowed}input,select{font-family:inherit;font-size:inherit;color:var(--fg)}a{color:var(--accent-2);text-decoration:none}a:hover{text-decoration:underline}::selection{background:var(--accent);color:var(--bg)} diff --git a/playground/dist/assets/nisps-processor-BuKuNOY1.js b/playground/dist/assets/nisps-processor-BuKuNOY1.js new file mode 100644 index 0000000..1c2f3b2 --- /dev/null +++ b/playground/dist/assets/nisps-processor-BuKuNOY1.js @@ -0,0 +1,2 @@ +(function(){"use strict";class f extends AudioWorkletProcessor{instance=null;engineHandle=0;engineId="thru";muted=!0;inLPtr=0;inRPtr=0;outLPtr=0;outRPtr=0;idPtr=0;paramsPtr=0;inLView=null;inRView=null;outLView=null;outRView=null;paramsView=null;idView=null;mem=null;pendingParams=null;constructor(){super(),this.port.onmessage=i=>this.onMessage_(i.data)}async onMessage_(i){if(i.kind==="init")try{await this.init_(i.wasmBinary,i.sampleRate),this.post_({kind:"ready"})}catch(r){this.post_({kind:"error",message:r instanceof Error?r.message:String(r)})}else i.kind==="engine"?this.switchEngine_(i.engineId):i.kind==="params"?this.applyParams_(i.params):i.kind==="mute"&&(this.muted=i.muted)}post_(i){this.port.postMessage(i)}async init_(i,r){const t=new WebAssembly.Memory({initial:128,maximum:4096,shared:!1}),a=await WebAssembly.compile(i),h=WebAssembly.Module.imports(a),n={};for(const e of h){n[e.module]||(n[e.module]={});const s=n[e.module];e.kind==="function"?(e.name,s[e.name]=(...o)=>0):e.kind==="memory"?s[e.name]=t:e.kind==="table"?s[e.name]=new WebAssembly.Table({element:"anyfunc",initial:0}):e.kind==="global"&&(s[e.name]=new WebAssembly.Global({value:"i32",mutable:!0},0))}for(const e of h){const s=n[e.module];e.name==="a"&&e.kind==="function"&&(s[e.name]=()=>{throw new Error("wasm aborted")}),e.name==="b"&&e.kind==="function"&&(s[e.name]=o=>0)}const w=await WebAssembly.instantiate(a,n),_=WebAssembly.Module.exports(a),P=new Map;for(const e of _)P.set(e.name,e.name);const p=w.exports;function u(...e){for(const s of e){const o=p[s];if(typeof o=="function")return o}throw new Error(`worklet: missing wasm export, tried: ${e.join(", ")}`)}function m(...e){return u(...e)}const l=u("_malloc","malloc"),g=m("_free","free"),y=u("_nisps_engine_create"),R=m("_nisps_engine_destroy"),b=m("_nisps_engine_set_params"),L=m("_nisps_engine_process_block");let d=null;for(const e of _)if(e.kind==="memory"){const s=p[e.name];if(s instanceof WebAssembly.Memory){d=s;break}}this.mem=d??t,this.instance={exports:{memory:this.mem,malloc:l,free:g,_nisps_engine_create:(e,s)=>y(e,s),_nisps_engine_destroy:e=>R(e),_nisps_engine_set_params:(e,s,o)=>b(e,s,o),_nisps_engine_process_block:(e,s,o,V,A,C)=>L(e,s,o,V,A,C)}},this.inLPtr=l(128*4),this.inRPtr=l(128*4),this.outLPtr=l(128*4),this.outRPtr=l(128*4),this.paramsPtr=l(256*4),this.idPtr=l(32);const c=this.mem.buffer;this.inLView=new Float32Array(c,this.inLPtr,128),this.inRView=new Float32Array(c,this.inRPtr,128),this.outLView=new Float32Array(c,this.outLPtr,128),this.outRView=new Float32Array(c,this.outRPtr,128),this.paramsView=new Float32Array(c,this.paramsPtr,256),this.idView=new Uint8Array(c,this.idPtr,32),this.spawnEngine_("thru",r),this.pendingParams&&(this.applyParams_(this.pendingParams),this.pendingParams=null),this.muted=!1}spawnEngine_(i,r){if(!this.instance||!this.idView)return;this.engineHandle&&(this.instance.exports._nisps_engine_destroy(this.engineHandle),this.engineHandle=0);const a=new TextEncoder().encode(i);this.idView.fill(0),this.idView.set(a.subarray(0,Math.min(a.length,31))),this.engineHandle=this.instance.exports._nisps_engine_create(this.idPtr,r),this.engineId=i}switchEngine_(i){this.spawnEngine_(i,sampleRate)}applyParams_(i){if(!this.instance||!this.paramsView){this.pendingParams=i;return}const r=Math.min(i.length,256);for(let t=0;t1?t[1]:t[0];if(this.muted||!this.instance||!this.engineHandle||!this.inLView||!this.outLView||!this.outRView||!this.inRView)return a.fill(0),t.length>1&&h.fill(0),!0;const n=i[0];return n&&n[0]?this.inLView.set(n[0].subarray(0,128)):this.inLView.fill(0),n&&n[1]?this.inRView.set(n[1].subarray(0,128)):n&&n[0]?this.inRView.set(n[0].subarray(0,128)):this.inRView.fill(0),this.instance.exports._nisps_engine_process_block(this.engineHandle,this.inLPtr,this.inRPtr,this.outLPtr,this.outRPtr,128),a.set(this.outLView.subarray(0,a.length)),t.length>1&&h.set(this.outRView.subarray(0,h.length)),!0}}registerProcessor("nisps-processor",f)})(); +//# sourceMappingURL=nisps-processor-BuKuNOY1.js.map diff --git a/playground/dist/assets/nisps-processor-BuKuNOY1.js.map b/playground/dist/assets/nisps-processor-BuKuNOY1.js.map new file mode 100644 index 0000000..86840d1 --- /dev/null +++ b/playground/dist/assets/nisps-processor-BuKuNOY1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"nisps-processor-BuKuNOY1.js","sources":["../src/audio/worklet/nisps-processor.ts"],"sourcesContent":["/**\n * AudioWorkletProcessor that runs `nisps.wasm` engines.\n *\n * Why a separate WASM instance from the main thread? AudioWorklet runs in\n * its own thread + global scope; reusing a single instance would require\n * SharedArrayBuffer + locking on the heap. Architecture.md §6.4 specifies\n * separate instances connected by `port` messages instead.\n *\n * Wasm load path: AudioWorklet has NO `fetch` and NO ESM `import`. The\n * main thread fetches `nisps.wasm` once and posts the bytes here as an\n * ArrayBuffer; we then `WebAssembly.compile` and `instantiate` directly,\n * skipping the Emscripten glue entirely. This is fine because the\n * exported functions don't need any of the JS-side runtime.\n *\n * Block size: AudioWorklet ALWAYS calls process() with 128-sample blocks.\n * We allocate 128-sample input and output buffers in the WASM linear\n * memory and shuttle samples in/out per call.\n */\n\n/// \n\nimport type { EngineId } from '../../ml/types';\nimport type { HostToWorkletMessage, WorkletToHostMessage } from '../engine-host';\n\nconst PROC_BLOCK = 128;\nconst MAX_PARAMS = 256; // upper bound across all engines\n\ninterface WasmInstance {\n exports: {\n memory: WebAssembly.Memory;\n malloc: (n: number) => number;\n free: (p: number) => void;\n _nisps_engine_create: (id_ptr: number, sample_rate: number) => number;\n _nisps_engine_destroy: (engine: number) => void;\n _nisps_engine_set_params: (engine: number, params_ptr: number, n: number) => void;\n _nisps_engine_process_block: (\n engine: number,\n in_l: number, in_r: number,\n out_l: number, out_r: number,\n n_samples: number,\n ) => void;\n };\n}\n\nclass NispsProcessor extends AudioWorkletProcessor {\n private instance: WasmInstance | null = null;\n private engineHandle = 0;\n private engineId: EngineId = 'thru';\n private muted = true;\n\n // Pointers + buffer views (allocated once instance is up).\n private inLPtr = 0;\n private inRPtr = 0;\n private outLPtr = 0;\n private outRPtr = 0;\n private idPtr = 0;\n private paramsPtr = 0;\n private inLView: Float32Array | null = null;\n private inRView: Float32Array | null = null;\n private outLView: Float32Array | null = null;\n private outRView: Float32Array | null = null;\n private paramsView: Float32Array | null = null;\n private idView: Uint8Array | null = null;\n private mem: WebAssembly.Memory | null = null;\n\n // Pending params posted before the engine was ready.\n private pendingParams: Float32Array | null = null;\n\n constructor() {\n super();\n this.port.onmessage = (ev) => this.onMessage_(ev.data as HostToWorkletMessage);\n }\n\n private async onMessage_(msg: HostToWorkletMessage): Promise {\n if (msg.kind === 'init') {\n try {\n await this.init_(msg.wasmBinary, msg.sampleRate);\n this.post_({ kind: 'ready' });\n } catch (err) {\n this.post_({\n kind: 'error',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n } else if (msg.kind === 'engine') {\n this.switchEngine_(msg.engineId);\n } else if (msg.kind === 'params') {\n this.applyParams_(msg.params);\n } else if (msg.kind === 'mute') {\n this.muted = msg.muted;\n }\n }\n\n private post_(msg: WorkletToHostMessage): void {\n this.port.postMessage(msg);\n }\n\n /**\n * Compile + instantiate the wasm module. We provide minimal imports —\n * the Emscripten module needs `__abort_js` and `_emscripten_resize_heap`\n * (we keep memory non-resizing so the latter is a stub).\n */\n private async init_(bytes: ArrayBuffer, sampleRate: number): Promise {\n const memory = new WebAssembly.Memory({ initial: 128, maximum: 4096, shared: false });\n const imports: WebAssembly.Imports = {\n // Emscripten import \"a\" group; field names match the generated JS.\n a: {\n a: () => { throw new Error('wasm aborted'); },\n b: () => false, // _emscripten_resize_heap returning 0 disables growth\n },\n };\n\n const compiled = await WebAssembly.compile(bytes);\n // Discover the actual import shape from the module — names like \"a\",\n // \"b\" depend on emcc's mangling; we accept whatever it produces.\n const importDesc = WebAssembly.Module.imports(compiled);\n const reshaped: WebAssembly.Imports = {};\n for (const desc of importDesc) {\n if (!reshaped[desc.module]) reshaped[desc.module] = {} as WebAssembly.ModuleImports;\n const mod = reshaped[desc.module] as WebAssembly.ModuleImports;\n if (desc.kind === 'function') {\n if (desc.name === 'c') {\n // unused\n }\n mod[desc.name] = (() => {\n // Generic stub: log + return 0.\n return (..._args: unknown[]) => 0;\n })();\n } else if (desc.kind === 'memory') {\n mod[desc.name] = memory;\n } else if (desc.kind === 'table') {\n mod[desc.name] = new WebAssembly.Table({ element: 'anyfunc', initial: 0 });\n } else if (desc.kind === 'global') {\n mod[desc.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0);\n }\n }\n // For known-needed Emscripten imports, supply real implementations.\n for (const desc of importDesc) {\n const mod = reshaped[desc.module] as WebAssembly.ModuleImports;\n // __abort_js\n if (desc.name === 'a' && desc.kind === 'function') {\n mod[desc.name] = () => { throw new Error('wasm aborted'); };\n }\n // _emscripten_resize_heap\n if (desc.name === 'b' && desc.kind === 'function') {\n mod[desc.name] = (_size: number) => 0; // refuse growth in worklet\n }\n }\n\n void imports; // silence unused\n const wasmInst = await WebAssembly.instantiate(compiled, reshaped);\n\n // Many Emscripten exports use single-letter mangled names. Discover\n // by reading the export descriptors.\n const exDesc = WebAssembly.Module.exports(compiled);\n const exMap = new Map(); // logical name → mangled\n for (const e of exDesc) {\n // The exports list includes both the original (with leading\n // underscore for C funcs) and the mangled single-letter alias used\n // in the import section. We only see the export side here, but\n // Emscripten in modern versions also re-exports the C names with\n // their leading-underscore form. Walk both.\n exMap.set(e.name, e.name);\n }\n const exports = wasmInst.exports as Record;\n\n function pickFn(...names: string[]): (...args: number[]) => number {\n for (const n of names) {\n const v = exports[n];\n if (typeof v === 'function') return v as unknown as (...a: number[]) => number;\n }\n throw new Error(`worklet: missing wasm export, tried: ${names.join(', ')}`);\n }\n function pickFnVoid(...names: string[]): (...args: number[]) => void {\n return pickFn(...names) as unknown as (...args: number[]) => void;\n }\n\n // The exports we need.\n const malloc = pickFn('_malloc', 'malloc');\n const free = pickFnVoid('_free', 'free');\n const ec = pickFn('_nisps_engine_create');\n const ed = pickFnVoid('_nisps_engine_destroy');\n const esp = pickFnVoid('_nisps_engine_set_params');\n const epb = pickFnVoid('_nisps_engine_process_block');\n\n // The wasm-exported memory might be named `memory` or another mangled\n // alias. Find it.\n let wasmMemory: WebAssembly.Memory | null = null;\n for (const e of exDesc) {\n if (e.kind === 'memory') {\n const v = exports[e.name];\n if (v instanceof WebAssembly.Memory) { wasmMemory = v; break; }\n }\n }\n // If the module imports memory (which our build does — we passed it),\n // there will be no exported memory; use the imported one.\n this.mem = wasmMemory ?? memory;\n\n this.instance = {\n exports: {\n memory: this.mem,\n malloc,\n free,\n _nisps_engine_create: (id, sr) => ec(id, sr),\n _nisps_engine_destroy: (h) => ed(h),\n _nisps_engine_set_params: (h, p, n) => esp(h, p, n),\n _nisps_engine_process_block: (h, il, ir, ol, or_, n) => epb(h, il, ir, ol, or_, n),\n },\n };\n\n // Allocate buffers.\n this.inLPtr = malloc(PROC_BLOCK * 4);\n this.inRPtr = malloc(PROC_BLOCK * 4);\n this.outLPtr = malloc(PROC_BLOCK * 4);\n this.outRPtr = malloc(PROC_BLOCK * 4);\n this.paramsPtr = malloc(MAX_PARAMS * 4);\n // Engine ids are short ASCII; 32 bytes covers everything we have.\n this.idPtr = malloc(32);\n\n const buf = this.mem.buffer;\n this.inLView = new Float32Array(buf, this.inLPtr, PROC_BLOCK);\n this.inRView = new Float32Array(buf, this.inRPtr, PROC_BLOCK);\n this.outLView = new Float32Array(buf, this.outLPtr, PROC_BLOCK);\n this.outRView = new Float32Array(buf, this.outRPtr, PROC_BLOCK);\n this.paramsView = new Float32Array(buf, this.paramsPtr, MAX_PARAMS);\n this.idView = new Uint8Array(buf, this.idPtr, 32);\n\n // Default engine: thru.\n this.spawnEngine_('thru', sampleRate);\n\n // Apply pending params if any arrived before init completed.\n if (this.pendingParams) {\n this.applyParams_(this.pendingParams);\n this.pendingParams = null;\n }\n\n this.muted = false;\n }\n\n private spawnEngine_(id: EngineId, sampleRate: number): void {\n if (!this.instance || !this.idView) return;\n if (this.engineHandle) {\n this.instance.exports._nisps_engine_destroy(this.engineHandle);\n this.engineHandle = 0;\n }\n // Write engine_id as ASCII into idView, NUL-terminated.\n const enc = new TextEncoder();\n const bytes = enc.encode(id);\n this.idView.fill(0);\n this.idView.set(bytes.subarray(0, Math.min(bytes.length, 31)));\n this.engineHandle = this.instance.exports._nisps_engine_create(this.idPtr, sampleRate);\n this.engineId = id;\n }\n\n private switchEngine_(id: EngineId): void {\n // sampleRate global from AudioWorkletGlobalScope.\n this.spawnEngine_(id, sampleRate);\n }\n\n private applyParams_(params: Float32Array): void {\n if (!this.instance || !this.paramsView) {\n this.pendingParams = params;\n return;\n }\n const n = Math.min(params.length, MAX_PARAMS);\n for (let i = 0; i < n; ++i) this.paramsView[i] = params[i];\n if (this.engineHandle) {\n this.instance.exports._nisps_engine_set_params(this.engineHandle, this.paramsPtr, n);\n }\n }\n\n override process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean {\n const out = outputs[0];\n if (!out || out.length === 0) return true;\n\n const outL = out[0];\n const outR = out.length > 1 ? out[1] : out[0];\n\n if (this.muted || !this.instance || !this.engineHandle ||\n !this.inLView || !this.outLView || !this.outRView || !this.inRView) {\n // Silence.\n outL.fill(0);\n if (out.length > 1) outR.fill(0);\n return true;\n }\n\n // Copy inputs into wasm buffers (zero-fill if missing).\n const inp = inputs[0];\n if (inp && inp[0]) this.inLView.set(inp[0].subarray(0, PROC_BLOCK));\n else this.inLView.fill(0);\n if (inp && inp[1]) this.inRView.set(inp[1].subarray(0, PROC_BLOCK));\n else if (inp && inp[0]) this.inRView.set(inp[0].subarray(0, PROC_BLOCK));\n else this.inRView.fill(0);\n\n this.instance.exports._nisps_engine_process_block(\n this.engineHandle,\n this.inLPtr, this.inRPtr,\n this.outLPtr, this.outRPtr,\n PROC_BLOCK,\n );\n\n outL.set(this.outLView.subarray(0, outL.length));\n if (out.length > 1) outR.set(this.outRView.subarray(0, outR.length));\n\n return true;\n }\n}\n\nregisterProcessor('nisps-processor', NispsProcessor);\n"],"names":["NispsProcessor","ev","msg","err","bytes","sampleRate","memory","compiled","importDesc","reshaped","desc","mod","_args","_size","wasmInst","exDesc","exMap","exports","pickFn","names","n","v","pickFnVoid","malloc","free","ec","ed","esp","epb","wasmMemory","id","sr","h","p","il","ir","ol","or_","buf","params","i","inputs","outputs","out","outL","outR","inp"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/wasm-iml-FZSgVMmn.js b/playground/dist/assets/wasm-iml-FZSgVMmn.js new file mode 100644 index 0000000..530dba0 --- /dev/null +++ b/playground/dist/assets/wasm-iml-FZSgVMmn.js @@ -0,0 +1,2 @@ +import{m as d,p,l as y}from"./index-C1-a2ZPT.js";class T{maxSize;inputSize_=0;outputSize_=0;features_=new Float32Array(0);labels_=new Float32Array(0);size_=0;constructor(t=100){if(t<=0)throw new Error("Dataset.maxSize must be > 0");this.maxSize=t}get size(){return this.size_}isEmpty(){return this.size_===0}add(t,e){if(this.size_===0&&(this.inputSize_=t.length,this.outputSize_=e.length,this.features_=new Float32Array(this.maxSize*this.inputSize_),this.labels_=new Float32Array(this.maxSize*this.outputSize_)),t.length!==this.inputSize_||e.length!==this.outputSize_)return!1;this.size_>=this.maxSize&&(this.features_.copyWithin(0,this.inputSize_),this.labels_.copyWithin(0,this.outputSize_),this.size_=this.maxSize-1);const s=this.size_*this.inputSize_,a=this.size_*this.outputSize_;for(let r=0;r=this.size_)throw new RangeError(`feature index ${t} out of bounds`);return this.features_.subarray(t*this.inputSize_,(t+1)*this.inputSize_)}label(t){if(t<0||t>=this.size_)throw new RangeError(`label index ${t} out of bounds`);return this.labels_.subarray(t*this.outputSize_,(t+1)*this.outputSize_)}featuresFlat(){return this.features_.subarray(0,this.size_*this.inputSize_)}labelsFlat(){return this.labels_.subarray(0,this.size_*this.outputSize_)}get inputSize(){return this.inputSize_}get outputSize(){return this.outputSize_}computeWeights(t="uniform",e={}){const s=this.size_;if(s===0)return new Float32Array(0);if(s===1)return new Float32Array([1]);const a=new Float32Array(s).fill(1);if(t==="global"||t==="combined"){const i=e.recencyBias??.6;if(i>0){const n=1-.3*i;for(let o=s-2;o>=0;--o)a[o]=a[o+1]*n}}if((t==="local"||t==="combined")&&e.queryInput){const i=e.queryInput,n=e.radius??.15,o=n*n,_=this.inputSize_;for(let u=0;u0&&(a[u]*=Math.pow(1-m,g))}}}let r=0;for(let i=0;i0)for(let i=0;ithis.onMessage_(t.data),this.worker.onerror=t=>{for(const{reject:e}of this.pending.values())e(t.message??"worker error");this.pending.clear()}}init_(){return new Promise((t,e)=>{const s=r=>{const i=r.data;i.kind==="ready"?(this.worker.removeEventListener("message",s),t()):i.kind==="error"&&(this.worker.removeEventListener("message",s),e(new Error(i.message)))};this.worker.addEventListener("message",s);const a=(Date.now()^Math.floor(Math.random()*4294967295))>>>0;this.worker.postMessage({kind:"init",seed:a})})}train(t){if(this.disposed)return Promise.reject(new Error("WasmTrainer disposed"));const e=this.nextId++;return new Promise((s,a)=>{this.pending.set(e,{resolve:s,reject:a});const r={kind:"train",requestId:e,weights:t.weights,features:t.features,labels:t.labels,sampleWeights:t.sampleWeights,lr:t.lr,maxIter:t.maxIter,minErr:t.minErr,inputSize:t.inputSize,outputSize:t.outputSize};this.worker.postMessage(r,[t.weights.buffer,t.features.buffer,t.labels.buffer,t.sampleWeights.buffer])})}dispose(){if(!this.disposed){this.disposed=!0;try{this.worker.postMessage({kind:"dispose"})}catch{}this.worker.terminate();for(const{reject:t}of this.pending.values())t(new Error("disposed"));this.pending.clear()}}onMessage_(t){if(t.kind==="result"){const e=this.pending.get(t.requestId);e&&(this.pending.delete(t.requestId),e.resolve({loss:t.loss,weights:t.weights,lossHistory:t.lossHistory}))}else if(t.kind==="error"){const e=this.pending.get(t.requestId);e&&(this.pending.delete(t.requestId),e.reject(new Error(t.message)))}}}function M(){return H.create()}const W=typeof window>"u"&&typeof self<"u"&&typeof self.importScripts<"u";if(W){let v=function(l,h,f,w){if(!s)throw new Error("worker module not loaded");n!==r&&(i&&s._free(i),i=s._malloc(r*4),n=r),l.length!==_&&(o&&s._free(o),o=s._malloc(l.length*4),_=l.length),h.length!==S&&(u&&s._free(u),u=s._malloc(h.length*4),S=h.length),f.length!==m&&(c&&s._free(c),c=f.length>0?s._malloc(f.length*4):0,m=f.length),new Float32Array(s.HEAPF32.buffer,i,r).set(w),new Float32Array(s.HEAPF32.buffer,o,l.length).set(l),new Float32Array(s.HEAPF32.buffer,u,h.length).set(h),c&&new Float32Array(s.HEAPF32.buffer,c,f.length).set(f)},t=function(l){if(!s)return{kind:"error",requestId:l.requestId,message:"worker not initialised"};try{v(l.features,l.labels,l.sampleWeights,l.weights),s._nisps_ml_set_weights(a,i),s._nisps_ml_clear_examples(a);const h=l.inputSize,f=l.outputSize,w=l.features.length/h;for(let B=0;B0?c:0,E=s._nisps_ml_train(a,l.lr,l.maxIter,l.minErr,A);s._nisps_ml_get_weights(a,i);const k=new Float32Array(s.HEAPF32.buffer,i,r),L=new Float32Array(k),x=new Float32Array([E]);return{kind:"result",requestId:l.requestId,loss:E,weights:L,lossHistory:x}}catch(h){return{kind:"error",requestId:l.requestId,message:h instanceof Error?h.message:String(h)}}},e=function(){s&&(a&&(s._nisps_ml_destroy(a),a=0),i&&(s._free(i),i=0),o&&(s._free(o),o=0),u&&(s._free(u),u=0),c&&(s._free(c),c=0),s=null)},s=null,a=0,r=0,i=0,n=0,o=0,_=0,u=0,S=0,c=0,m=0;async function g(l){const h=await import(new URL("/nisps.js",self.location.origin).toString());s=await(h.default??h.createNispsModule)({locateFile:w=>w.endsWith(".wasm")?new URL("/nisps.wasm",self.location.origin).toString():w}),a=s._nisps_ml_create(0,0,0,0,l>>>0),r=s._nisps_ml_weight_count(a)}self.addEventListener("message",async l=>{const h=l.data;if(h.kind==="init")try{await g(h.seed),self.postMessage({kind:"ready"})}catch(f){self.postMessage({kind:"error",requestId:0,message:f instanceof Error?f.message:String(f)})}else if(h.kind==="train"){const f=t(h);f.kind==="result"?self.postMessage(f,[f.weights.buffer,f.lossHistory.buffer]):self.postMessage(f)}else h.kind==="dispose"&&e()})}const C="/nisps.js",O="/nisps.wasm",U=2,j=126;let F=null;async function N(){if(F)return F;const t=await import(new URL(C,window.location.origin).toString()),e=t.default??t.createNispsModule;if(!e)throw new Error("[wasm-iml] nisps.js does not export a module factory");return F=e,e}class z{constructor(t,e){if(this.mod=t,this.count=e,this.ptr=t._malloc(e*4),!this.ptr)throw new Error(`malloc(${e*4}) failed`);this.view=new Float32Array(t.HEAPF32.buffer,this.ptr,e)}ptr;view;rebind(){Object.defineProperty(this,"view",{value:new Float32Array(this.mod.HEAPF32.buffer,this.ptr,this.count),writable:!1})}free(){this.mod._free(this.ptr)}}class ${constructor(t,e){if(this.mod=t,this.count=e,this.ptr=t._malloc(e),!this.ptr)throw new Error(`malloc(${e}) failed`);this.view=new Uint8Array(t.HEAPU8.buffer,this.ptr,e)}ptr;view;rebind(){Object.defineProperty(this,"view",{value:new Uint8Array(this.mod.HEAPU8.buffer,this.ptr,this.count),writable:!1})}free(){this.mod._free(this.ptr)}}class b{module;mlHandle=0;weightCount_=0;arch_={inputSize:U,hidden:[10,14,18],outputSize:j,numLayers:4};featuresBuf;labelsBuf;weightsBuf;statsBuf;batchInBuf;batchOutBuf;pinMaskBuf;describeBuf;describePtr=0;dataset;lastLoss_=null;trainer=null;storageKey;saveTimer=null;destroyed=!1;static MAX_BATCH=4096;constructor(t){this.dataset=new T(t.maxExamples??100),this.storageKey=t.storageKey??"nisps:wasm-iml"}static async create(t={}){const e=new b(t);return await e.init_(t),e}async init_(t){const e=await N();this.module=await e({locateFile:n=>n.endsWith(".wasm")?new URL(O,window.location.origin).toString():n}),this.describePtr=this.module._malloc(6*4),this.module._nisps_ml_describe(this.describePtr);const s=new Int32Array(this.module.HEAP32.buffer,this.describePtr,6);this.arch_={inputSize:s[0],hidden:[s[1],s[2],s[3]],outputSize:s[4],numLayers:s[5]};const a=t.inputSize??this.arch_.inputSize,r=t.outputSize??this.arch_.outputSize;(a!==this.arch_.inputSize||r!==this.arch_.outputSize)&&console.warn(`[wasm-iml] requested ${a}->${r} but WASM build is fixed at ${this.arch_.inputSize}->${this.arch_.outputSize}; extras are ignored.`);const i=(t.seed??Date.now()>>>0)>>>0;if(this.mlHandle=this.module._nisps_ml_create(this.arch_.inputSize,this.arch_.outputSize,0,0,i),!this.mlHandle)throw new Error("[wasm-iml] nisps_ml_create returned null");this.weightCount_=this.module._nisps_ml_weight_count(this.mlHandle),this.featuresBuf=new z(this.module,this.arch_.inputSize),this.labelsBuf=new z(this.module,this.arch_.outputSize),this.weightsBuf=new z(this.module,this.weightCount_),this.statsBuf=new z(this.module,this.arch_.numLayers*4),this.batchInBuf=new z(this.module,b.MAX_BATCH*this.arch_.inputSize),this.batchOutBuf=new z(this.module,b.MAX_BATCH*this.arch_.outputSize),this.pinMaskBuf=new $(this.module,this.arch_.outputSize),d.__setState(p(n=>{n.inputSize=this.arch_.inputSize,n.outputSize=this.arch_.outputSize,n.exampleCount=0,n.lastLoss=null,n.lossHistory=[],n.training=!1,n.ready=!0})),d.__setOutputs(new Float32Array(this.arch_.outputSize)),this.publishWeights_(),this.tryLoadFromStorage_()}dispose(){this.destroyed||(this.destroyed=!0,this.saveTimer!==null&&(clearTimeout(this.saveTimer),this.saveTimer=null),this.trainer&&(this.trainer.dispose(),this.trainer=null),this.module&&this.mlHandle&&(this.module._nisps_ml_destroy(this.mlHandle),this.mlHandle=0),this.featuresBuf&&this.featuresBuf.free(),this.labelsBuf&&this.labelsBuf.free(),this.weightsBuf&&this.weightsBuf.free(),this.statsBuf&&this.statsBuf.free(),this.batchInBuf&&this.batchInBuf.free(),this.batchOutBuf&&this.batchOutBuf.free(),this.pinMaskBuf&&this.pinMaskBuf.free(),this.describePtr&&this.module._free(this.describePtr),d.__setState(p(t=>{t.ready=!1})))}get architecture(){return this.arch_}get weightCount(){return this.weightCount_}get exampleCount(){return this.dataset.size}get lastLoss(){return this.lastLoss_}setInput(t,e){this.module._nisps_ml_set_input(this.mlHandle,t,e)}process(){this.module._nisps_ml_process(this.mlHandle);const t=this.module._nisps_ml_outputs(this.mlHandle),e=new Float32Array(this.module.HEAPF32.buffer,t,this.arch_.outputSize),s=new Float32Array(e);return d.__setOutputs(s),s}inferXY(t,e){return this.setInput(0,t),this.setInput(1,e),this.process()}inferBatch(t){const e=t.length,s=this.arch_.inputSize,a=this.arch_.outputSize,r=new Float32Array(e*a);let i=0;for(let n=0;n{a.exampleCount=this.dataset.size})),y.emit("ml.example_added",{count:this.dataset.size}),this.scheduleSave_(),!0):!1}copyExampleToWasm_(t,e){const s=this.featuresBuf.view,a=this.labelsBuf.view,r=this.arch_.inputSize,i=this.arch_.outputSize;for(let n=0;n{o.lastLoss=0})),0;let r=0,i=null;a&&a.length===this.dataset.size&&(i=new z(this.module,a.length),i.view.set(a),r=i.ptr),d.__setState(p(o=>{o.training=!0}));let n=0;try{n=this.module._nisps_ml_train(this.mlHandle,t,e,s,r)}finally{i&&i.free(),d.__setState(p(o=>{o.training=!1}))}return this.lastLoss_=n,d.__setState(p(o=>{o.lastLoss=n,o.lossHistory=[n]})),this.publishWeights_(),y.emit("ml.trained",{loss:n}),this.scheduleSave_(),n}async trainAsync(t=1,e=1e3,s=.001,a){if(this.dataset.isEmpty())return this.lastLoss_=0,0;this.trainer||(this.trainer=await M());const r=this.getWeights(),i=new Float32Array(this.dataset.featuresFlat()),n=new Float32Array(this.dataset.labelsFlat()),o=a?new Float32Array(a):new Float32Array(0);d.__setState(p(_=>{_.training=!0}));try{const _=await this.trainer.train({weights:r,features:i,labels:n,sampleWeights:o,lr:t,maxIter:e,minErr:s,inputSize:this.arch_.inputSize,outputSize:this.arch_.outputSize});return this.setWeights(_.weights),this.lastLoss_=_.loss,d.__setState(p(u=>{u.lastLoss=_.loss,u.lossHistory=Array.from(_.lossHistory)})),y.emit("ml.trained",{loss:_.loss}),this.scheduleSave_(),_.loss}finally{d.__setState(p(_=>{_.training=!1}))}}evalLoss(){return this.module._nisps_ml_eval_loss(this.mlHandle)}clearExamples(){this.dataset.clear(),this.module._nisps_ml_clear_examples(this.mlHandle),d.__setState(p(t=>{t.exampleCount=0})),y.emit("ml.examples_cleared",void 0),this.scheduleSave_()}randomiseWeights(t=.6){this.module._nisps_ml_draw_weights(this.mlHandle,t),this.publishWeights_(),y.emit("ml.delta_update",{reason:"randomize"}),this.scheduleSave_()}moveWeights(t,e,s){let a=0;if(s){const r=Math.min(s.length,this.arch_.outputSize);for(let i=0;i{t.exampleCount=0,t.lastLoss=null,t.lossHistory=[]})),this.publishWeights_(),y.emit("ml.examples_cleared",void 0),this.scheduleSave_()}scheduleSave_(){this.saveTimer!==null&&clearTimeout(this.saveTimer),this.saveTimer=window.setTimeout(()=>this.saveNow(),500)}saveNow(){if(!this.destroyed){this.saveTimer!==null&&(clearTimeout(this.saveTimer),this.saveTimer=null);try{const t=this.getWeights(),e={v:1,arch:this.arch_,weights:Array.from(t),features:Array.from(this.dataset.featuresFlat()),labels:Array.from(this.dataset.labelsFlat()),size:this.dataset.size,lastLoss:this.lastLoss_};localStorage.setItem(this.storageKey,JSON.stringify(e))}catch(t){console.warn("[wasm-iml] saveNow failed:",t)}}}tryLoadFromStorage_(){try{const t=localStorage.getItem(this.storageKey);if(!t)return;const e=JSON.parse(t);if(e.v!==1)return;const s=this.arch_.inputSize,a=this.arch_.outputSize;if(e.size>0&&e.features.length===e.size*s&&e.labels.length===e.size*a)for(let r=0;r{r.exampleCount=this.dataset.size,r.lastLoss=this.lastLoss_}))}catch(t){console.warn("[wasm-iml] tryLoadFromStorage failed:",t)}}publishWeights_(){const t=this.getWeights();d.__setWeights(t)}}export{b as WasmIML}; +//# sourceMappingURL=wasm-iml-FZSgVMmn.js.map diff --git a/playground/dist/assets/wasm-iml-FZSgVMmn.js.map b/playground/dist/assets/wasm-iml-FZSgVMmn.js.map new file mode 100644 index 0000000..42bf685 --- /dev/null +++ b/playground/dist/assets/wasm-iml-FZSgVMmn.js.map @@ -0,0 +1 @@ +{"version":3,"file":"wasm-iml-FZSgVMmn.js","sources":["../../src/ml/dataset.ts","../../src/ml/wasm-worker.ts","../../src/ml/wasm-iml.ts"],"sourcesContent":["/**\n * Dataset — JS-side training-example store.\n *\n * Why duplicate the C++ ring buffer? Two reasons:\n * 1. Sample-weight computation (recency / spatial / combined) lives in JS so\n * that adjusting weighting modes doesn't burn a WASM round-trip.\n * 2. The dataset is part of session state we serialize to localStorage —\n * the WASM heap is wiped on reload.\n *\n * On train() we ship features + labels into WASM via `addExample` calls. The\n * order of insertion is preserved; FIFO eviction matches the C++ MLP's\n * `dataset_head_` pointer so weighting stays consistent.\n *\n * The implementation is a faithful TypeScript port of the legacy\n * `playground/_archive/js/nisps/dataset.js` with:\n * - Float32Array backing instead of `Array>`\n * - Stricter types\n * - No `withBias` flag (the WASM bindings don't take a bias term)\n */\n\nexport type WeightMode = 'global' | 'local' | 'combined' | 'uniform';\n\nexport interface ComputeWeightsParams {\n /** [0,1] — how strongly to bias toward newest examples (global/combined). */\n recencyBias?: number;\n /** Current input position, used for local/combined spatial weighting. */\n queryInput?: ReadonlyArray;\n /** Spatial radius in input space (local/combined). */\n radius?: number;\n}\n\nexport class Dataset {\n /** Maximum number of examples retained. FIFO eviction beyond this. */\n readonly maxSize: number;\n /** Length of feature vectors. Set on first add(); locked thereafter. */\n private inputSize_ = 0;\n /** Length of label vectors. Set on first add(); locked thereafter. */\n private outputSize_ = 0;\n\n /** Flat arrays — entries `[i*inputSize, (i+1)*inputSize)` belong to example i. */\n private features_: Float32Array = new Float32Array(0);\n private labels_: Float32Array = new Float32Array(0);\n private size_ = 0;\n\n constructor(maxSize = 100) {\n if (maxSize <= 0) throw new Error('Dataset.maxSize must be > 0');\n this.maxSize = maxSize;\n }\n\n /** Number of examples currently stored. */\n get size(): number {\n return this.size_;\n }\n\n isEmpty(): boolean {\n return this.size_ === 0;\n }\n\n /**\n * Add a feature/label pair. Returns true on success, false if the\n * dimensions don't match a previously-added example.\n *\n * Eviction: when at capacity, the oldest example is removed (shift),\n * then the new one is appended. This matches the legacy JS behaviour\n * (and is conceptually equivalent to the C++ side's ring buffer with\n * `head_` advancement).\n */\n add(features: ReadonlyArray, labels: ReadonlyArray): boolean {\n if (this.size_ === 0) {\n this.inputSize_ = features.length;\n this.outputSize_ = labels.length;\n // Allocate full-capacity buffers up front to avoid growth thrash.\n this.features_ = new Float32Array(this.maxSize * this.inputSize_);\n this.labels_ = new Float32Array(this.maxSize * this.outputSize_);\n }\n\n if (features.length !== this.inputSize_ || labels.length !== this.outputSize_) {\n return false;\n }\n\n if (this.size_ >= this.maxSize) {\n // FIFO: shift left in place. This is O(n*dim) and could be replaced\n // with a head pointer; for maxSize ≤ a few hundred it's fine.\n this.features_.copyWithin(0, this.inputSize_);\n this.labels_.copyWithin(0, this.outputSize_);\n this.size_ = this.maxSize - 1;\n }\n\n const fOff = this.size_ * this.inputSize_;\n const lOff = this.size_ * this.outputSize_;\n for (let i = 0; i < this.inputSize_; ++i) this.features_[fOff + i] = features[i];\n for (let i = 0; i < this.outputSize_; ++i) this.labels_[lOff + i] = labels[i];\n this.size_++;\n return true;\n }\n\n clear(): void {\n this.size_ = 0;\n }\n\n /** Read-only view of the i-th feature vector. */\n feature(i: number): Float32Array {\n if (i < 0 || i >= this.size_) throw new RangeError(`feature index ${i} out of bounds`);\n return this.features_.subarray(i * this.inputSize_, (i + 1) * this.inputSize_);\n }\n\n /** Read-only view of the i-th label vector. */\n label(i: number): Float32Array {\n if (i < 0 || i >= this.size_) throw new RangeError(`label index ${i} out of bounds`);\n return this.labels_.subarray(i * this.outputSize_, (i + 1) * this.outputSize_);\n }\n\n /** Flat view of all features (size * inputSize). */\n featuresFlat(): Float32Array {\n return this.features_.subarray(0, this.size_ * this.inputSize_);\n }\n\n /** Flat view of all labels (size * outputSize). */\n labelsFlat(): Float32Array {\n return this.labels_.subarray(0, this.size_ * this.outputSize_);\n }\n\n get inputSize(): number {\n return this.inputSize_;\n }\n get outputSize(): number {\n return this.outputSize_;\n }\n\n /**\n * Compute per-sample training weights. Returns Float32Array (size=this.size)\n * normalized to sum to 1. For an empty dataset returns a 0-length array;\n * for a singleton, [1.0].\n *\n * Modes:\n * - `uniform` — every weight = 1/n.\n * - `global` — exponential recency decay over insertion order.\n * - `local` — within `radius` of `queryInput`, suppress older neighbours.\n * - `combined` — global × local.\n */\n computeWeights(mode: WeightMode = 'uniform', params: ComputeWeightsParams = {}): Float32Array {\n const n = this.size_;\n if (n === 0) return new Float32Array(0);\n if (n === 1) return new Float32Array([1.0]);\n\n const weights = new Float32Array(n).fill(1.0);\n\n if (mode === 'global' || mode === 'combined') {\n const bias = params.recencyBias ?? 0.6;\n if (bias > 0) {\n const decay = 1 - 0.3 * bias;\n for (let i = n - 2; i >= 0; --i) weights[i] = weights[i + 1] * decay;\n }\n }\n\n if ((mode === 'local' || mode === 'combined') && params.queryInput) {\n const query = params.queryInput;\n const radius = params.radius ?? 0.15;\n const radiusSq = radius * radius;\n const dim = this.inputSize_;\n\n for (let i = 0; i < n; ++i) {\n const fOffI = i * dim;\n let distSq = 0;\n for (let d = 0; d < dim; ++d) {\n const diff = this.features_[fOffI + d] - (query[d] ?? 0);\n distSq += diff * diff;\n }\n if (distSq < radiusSq) {\n const proximity = 1 - Math.sqrt(distSq) / radius;\n let newerNearby = 0;\n for (let j = i + 1; j < n; ++j) {\n const fOffJ = j * dim;\n let djSq = 0;\n for (let d = 0; d < dim; ++d) {\n const diff = this.features_[fOffI + d] - this.features_[fOffJ + d];\n djSq += diff * diff;\n }\n if (djSq < radiusSq) ++newerNearby;\n }\n if (newerNearby > 0) {\n weights[i] *= Math.pow(1 - proximity, newerNearby);\n }\n }\n }\n }\n\n let sum = 0;\n for (let i = 0; i < n; ++i) sum += weights[i];\n if (sum > 0) for (let i = 0; i < n; ++i) weights[i] /= sum;\n return weights;\n }\n}\n","/**\n * Disposable Web Worker that runs SGD off the main thread.\n *\n * The worker holds its own `nisps.wasm` instance (architecture.md §6.4).\n * The main thread sends:\n * - current weights (so the worker is in the same state as the UI),\n * - dataset (features + labels),\n * - SGD hyperparameters,\n * and receives updated weights + final loss.\n *\n * This module exposes:\n * - `createTrainer()` - factory that spawns the worker, loads its WASM,\n * and returns a `WasmTrainer` handle.\n * - The worker entry-point itself (when this file runs in a Worker).\n *\n * The worker is implemented inline so a single TS file becomes both the\n * main-thread API and the worker bundle. Vite's `new Worker(new URL(...,\n * import.meta.url))` pattern packs it correctly.\n *\n * Lifecycle: each `WasmTrainer` is disposable via `.dispose()` which\n * terminates the worker. Tests should always dispose to avoid leaks.\n */\n\nimport type { NispsModule, NispsModuleFactory, WorkerRequest, WorkerResponse } from './types';\n\n// ---------------------------------------------------------------------------\n// Main-thread side\n// ---------------------------------------------------------------------------\n\nexport interface TrainArgs {\n weights: Float32Array;\n features: Float32Array;\n labels: Float32Array;\n /** Optional; pass empty for uniform weighting. */\n sampleWeights: Float32Array;\n lr: number;\n maxIter: number;\n minErr: number;\n inputSize: number;\n outputSize: number;\n}\n\nexport interface TrainResult {\n loss: number;\n weights: Float32Array;\n lossHistory: Float32Array;\n}\n\nexport class WasmTrainer {\n private worker: Worker;\n private nextId = 1;\n private pending = new Map void; reject: (e: unknown) => void }>();\n private disposed = false;\n\n static async create(): Promise {\n const trainer = new WasmTrainer();\n await trainer.init_();\n return trainer;\n }\n\n private constructor() {\n this.worker = new Worker(new URL('./wasm-worker.ts', import.meta.url), { type: 'module' });\n this.worker.onmessage = (ev) => this.onMessage_(ev.data as WorkerResponse);\n this.worker.onerror = (ev) => {\n // Fail any pending requests.\n for (const { reject } of this.pending.values()) reject(ev.message ?? 'worker error');\n this.pending.clear();\n };\n }\n\n private init_(): Promise {\n return new Promise((resolve, reject) => {\n const handler = (ev: MessageEvent) => {\n const msg = ev.data as WorkerResponse;\n if (msg.kind === 'ready') {\n this.worker.removeEventListener('message', handler);\n resolve();\n } else if (msg.kind === 'error') {\n this.worker.removeEventListener('message', handler);\n reject(new Error(msg.message));\n }\n };\n this.worker.addEventListener('message', handler);\n const seed = (Date.now() ^ Math.floor(Math.random() * 0xffffffff)) >>> 0;\n this.worker.postMessage({ kind: 'init', seed } satisfies WorkerRequest);\n });\n }\n\n train(args: TrainArgs): Promise {\n if (this.disposed) return Promise.reject(new Error('WasmTrainer disposed'));\n const requestId = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(requestId, { resolve, reject });\n const msg: WorkerRequest = {\n kind: 'train',\n requestId,\n weights: args.weights,\n features: args.features,\n labels: args.labels,\n sampleWeights: args.sampleWeights,\n lr: args.lr,\n maxIter: args.maxIter,\n minErr: args.minErr,\n inputSize: args.inputSize,\n outputSize: args.outputSize,\n };\n // Transfer all the typed-array buffers we no longer need on the\n // main thread; faster than copying. Caller has already cloned.\n this.worker.postMessage(msg, [\n args.weights.buffer,\n args.features.buffer,\n args.labels.buffer,\n args.sampleWeights.buffer,\n ]);\n });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.worker.postMessage({ kind: 'dispose' } satisfies WorkerRequest);\n } catch {\n /* ignore */\n }\n this.worker.terminate();\n for (const { reject } of this.pending.values()) reject(new Error('disposed'));\n this.pending.clear();\n }\n\n private onMessage_(msg: WorkerResponse): void {\n if (msg.kind === 'result') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.resolve({ loss: msg.loss, weights: msg.weights, lossHistory: msg.lossHistory });\n }\n } else if (msg.kind === 'error') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.reject(new Error(msg.message));\n }\n }\n // 'ready' handled in init_().\n }\n}\n\nexport function createTrainer(): Promise {\n return WasmTrainer.create();\n}\n\n// ---------------------------------------------------------------------------\n// Worker-thread side\n// ---------------------------------------------------------------------------\n//\n// When this module is loaded in a Worker, `self` is `WorkerGlobalScope` and\n// `window` is undefined. We use that as the dispatch.\n//\n// We import the wasm via a same-origin fetch (no DOM available so we can't\n// use a regular import URL — but Vite's bundler treats `new Worker(...)`\n// specially and `?url` imports work for assets).\n\n// Inside a Worker, `self` is a `DedicatedWorkerGlobalScope`. To keep TS\n// happy in both build contexts we use a structural cast.\ndeclare const self: {\n postMessage: (msg: unknown, transfer?: Transferable[]) => void;\n addEventListener: (event: string, handler: (ev: MessageEvent) => void) => void;\n location: { origin: string };\n importScripts?: unknown;\n};\n\nconst isWorker =\n typeof window === 'undefined' &&\n typeof self !== 'undefined' &&\n typeof (self as { importScripts?: unknown }).importScripts !== 'undefined';\n\nif (isWorker) {\n // Module-level state in worker scope.\n let mod: NispsModule | null = null;\n let mlHandle = 0;\n let weightCount = 0;\n\n // Heap buffers (allocated on first train).\n let weightsPtr = 0;\n let weightsViewLen = 0;\n let featuresPtr = 0;\n let featuresLen = 0;\n let labelsPtr = 0;\n let labelsLen = 0;\n let sampleWeightsPtr = 0;\n let sampleWeightsLen = 0;\n\n async function loadModule(seed: number): Promise {\n // Same-origin fetch to /nisps.js. The worker is served by the dev\n // server with COOP/COEP set, so this works.\n const factoryMod = await import(/* @vite-ignore */ new URL('/nisps.js', self.location.origin).toString());\n const factory: NispsModuleFactory =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (factoryMod as any).default ?? (factoryMod as any).createNispsModule;\n mod = await factory({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL('/nisps.wasm', self.location.origin).toString();\n return path;\n },\n });\n mlHandle = mod._nisps_ml_create(0, 0, 0, 0, seed >>> 0);\n weightCount = mod._nisps_ml_weight_count(mlHandle);\n }\n\n function ensureBuffers(features: Float32Array, labels: Float32Array, sampleWeights: Float32Array, weights: Float32Array): void {\n if (!mod) throw new Error('worker module not loaded');\n\n if (weightsViewLen !== weightCount) {\n if (weightsPtr) mod._free(weightsPtr);\n weightsPtr = mod._malloc(weightCount * 4);\n weightsViewLen = weightCount;\n }\n if (features.length !== featuresLen) {\n if (featuresPtr) mod._free(featuresPtr);\n featuresPtr = mod._malloc(features.length * 4);\n featuresLen = features.length;\n }\n if (labels.length !== labelsLen) {\n if (labelsPtr) mod._free(labelsPtr);\n labelsPtr = mod._malloc(labels.length * 4);\n labelsLen = labels.length;\n }\n if (sampleWeights.length !== sampleWeightsLen) {\n if (sampleWeightsPtr) mod._free(sampleWeightsPtr);\n sampleWeightsPtr = sampleWeights.length > 0 ? mod._malloc(sampleWeights.length * 4) : 0;\n sampleWeightsLen = sampleWeights.length;\n }\n\n new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount).set(weights);\n new Float32Array(mod.HEAPF32.buffer, featuresPtr, features.length).set(features);\n new Float32Array(mod.HEAPF32.buffer, labelsPtr, labels.length).set(labels);\n if (sampleWeightsPtr) {\n new Float32Array(mod.HEAPF32.buffer, sampleWeightsPtr, sampleWeights.length).set(sampleWeights);\n }\n }\n\n function trainOnce(req: Extract): WorkerResponse {\n if (!mod) {\n return { kind: 'error', requestId: req.requestId, message: 'worker not initialised' };\n }\n try {\n ensureBuffers(req.features, req.labels, req.sampleWeights, req.weights);\n // Push current weights into our MLP.\n mod._nisps_ml_set_weights(mlHandle, weightsPtr);\n\n // Seed the example ring buffer. We must clear first because past\n // train calls may have left examples there.\n mod._nisps_ml_clear_examples(mlHandle);\n const inSz = req.inputSize;\n const outSz = req.outputSize;\n const n = req.features.length / inSz;\n // Allocate small per-example scratch (re-used across iterations of\n // this loop).\n // We use stack-equivalents by allocating once, then shifting pointers.\n for (let i = 0; i < n; ++i) {\n const fPtr = featuresPtr + i * inSz * 4;\n const lPtr = labelsPtr + i * outSz * 4;\n mod._nisps_ml_add_example(mlHandle, fPtr, lPtr);\n }\n\n // Run training.\n const swPtr = req.sampleWeights.length > 0 ? sampleWeightsPtr : 0;\n const loss = mod._nisps_ml_train(mlHandle, req.lr, req.maxIter, req.minErr, swPtr);\n\n // Read out final weights.\n mod._nisps_ml_get_weights(mlHandle, weightsPtr);\n const view = new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount);\n const outWeights = new Float32Array(view); // copy\n\n // Loss history not yet plumbed via WASM; emit just final loss.\n const lossHistory = new Float32Array([loss]);\n\n return {\n kind: 'result',\n requestId: req.requestId,\n loss,\n weights: outWeights,\n lossHistory,\n };\n } catch (err) {\n return {\n kind: 'error',\n requestId: req.requestId,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n function disposeModule(): void {\n if (!mod) return;\n if (mlHandle) {\n mod._nisps_ml_destroy(mlHandle);\n mlHandle = 0;\n }\n if (weightsPtr) { mod._free(weightsPtr); weightsPtr = 0; }\n if (featuresPtr) { mod._free(featuresPtr); featuresPtr = 0; }\n if (labelsPtr) { mod._free(labelsPtr); labelsPtr = 0; }\n if (sampleWeightsPtr) { mod._free(sampleWeightsPtr); sampleWeightsPtr = 0; }\n mod = null;\n }\n\n self.addEventListener('message', async (ev: MessageEvent) => {\n const req = ev.data;\n if (req.kind === 'init') {\n try {\n await loadModule(req.seed);\n self.postMessage({ kind: 'ready' } satisfies WorkerResponse);\n } catch (err) {\n self.postMessage({\n kind: 'error',\n requestId: 0,\n message: err instanceof Error ? err.message : String(err),\n } satisfies WorkerResponse);\n }\n } else if (req.kind === 'train') {\n const res = trainOnce(req);\n // Transfer weights back to main thread to avoid copy.\n if (res.kind === 'result') {\n self.postMessage(res, [res.weights.buffer, res.lossHistory.buffer]);\n } else {\n self.postMessage(res);\n }\n } else if (req.kind === 'dispose') {\n disposeModule();\n // Worker terminates from main thread side via .terminate().\n }\n });\n}\n","/**\n * WasmIML — main-thread ML interface backed by `nisps.wasm`.\n *\n * Owns:\n * - One `nisps.wasm` instance.\n * - One MLP handle.\n * - A JS-side `Dataset` (mirrors the C++ ring buffer; see dataset.ts).\n * - Pre-allocated heap buffers for inputs/outputs/weights/etc.\n * - A lazy `WasmTrainer` worker for off-thread async training.\n *\n * Threading model (architecture.md §6.4):\n * - Inference + sync training run on this main-thread instance.\n * - `trainAsync()` spawns/uses a worker which holds a SECOND wasm\n * instance; weights round-trip through `getWeights()`/`setWeights()`.\n *\n * Side effects: every mutation that should be visible to the UI calls into\n * `mlStore`. The store is the single source of truth for Solid components.\n *\n * Concept compatibility: this class re-implements the legacy WasmIML\n * surface area documented in `recon/04-playground.md §6` so the existing\n * Playwright debug-probe tests can keep passing once the new probe is\n * wired up.\n */\n\nimport { produce } from 'solid-js/store';\n\nimport { mlStore } from '../stores/ml-store';\nimport { coreBus } from '../stores/bus';\nimport { Dataset } from './dataset';\nimport type {\n LayerStats,\n MLArchitecture,\n NispsModule,\n NispsModuleFactory,\n} from './types';\nimport { createTrainer, type WasmTrainer } from './wasm-worker';\n\nconst NISPS_JS_URL = '/nisps.js';\nconst NISPS_WASM_URL = '/nisps.wasm';\n\n/** Default architecture matches `nisps/wasm/bindings.cpp` instantiation. */\nconst DEFAULT_INPUT_SIZE = 2;\nconst DEFAULT_OUTPUT_SIZE = 126;\n\nlet cachedFactory: NispsModuleFactory | null = null;\n\n/**\n * Load the Emscripten glue once, cache the factory.\n *\n * The glue is served from `playground/public/nisps.js` (committed). We\n * dynamically import it so the WASM only loads when the ML system is first\n * used — `/dev/primitives` doesn't pay for it.\n */\nasync function getFactory(): Promise {\n if (cachedFactory) return cachedFactory;\n // Vite serves `/nisps.js` as a normal asset; we use a dynamic-eval import\n // to avoid Vite trying to resolve it at build time.\n const url = new URL(NISPS_JS_URL, window.location.origin).toString();\n const mod = await import(/* @vite-ignore */ url);\n // Emscripten MODULARIZE=1 default export key is `default`.\n // tslint:disable-next-line:no-any\n const factory = (mod as { default?: NispsModuleFactory; createNispsModule?: NispsModuleFactory })\n .default ?? (mod as { createNispsModule?: NispsModuleFactory }).createNispsModule;\n if (!factory) throw new Error('[wasm-iml] nisps.js does not export a module factory');\n cachedFactory = factory;\n return factory;\n}\n\n/** Aligned float-array allocation helper. Returns ptr + a view. */\nclass HeapBuffer {\n readonly ptr: number;\n readonly view: Float32Array;\n constructor(private mod: NispsModule, public readonly count: number) {\n this.ptr = mod._malloc(count * 4);\n if (!this.ptr) throw new Error(`malloc(${count * 4}) failed`);\n this.view = new Float32Array(mod.HEAPF32.buffer, this.ptr, count);\n }\n /** After memory growth, refresh the view onto the new ArrayBuffer. */\n rebind(): void {\n // Re-create the view with the (possibly new) underlying buffer.\n Object.defineProperty(this, 'view', {\n value: new Float32Array(this.mod.HEAPF32.buffer, this.ptr, this.count),\n writable: false,\n });\n }\n free(): void {\n this.mod._free(this.ptr);\n }\n}\n\nclass HeapU8 {\n readonly ptr: number;\n readonly view: Uint8Array;\n constructor(private mod: NispsModule, public readonly count: number) {\n this.ptr = mod._malloc(count);\n if (!this.ptr) throw new Error(`malloc(${count}) failed`);\n this.view = new Uint8Array(mod.HEAPU8.buffer, this.ptr, count);\n }\n rebind(): void {\n Object.defineProperty(this, 'view', {\n value: new Uint8Array(this.mod.HEAPU8.buffer, this.ptr, this.count),\n writable: false,\n });\n }\n free(): void {\n this.mod._free(this.ptr);\n }\n}\n\nexport interface WasmIMLOptions {\n inputSize?: number;\n outputSize?: number;\n hiddenLayers?: ReadonlyArray;\n seed?: number;\n /** localStorage key the loaded weights/dataset will be persisted under. */\n storageKey?: string;\n maxExamples?: number;\n}\n\nexport class WasmIML {\n // WASM binding state.\n private module!: NispsModule;\n private mlHandle = 0;\n private weightCount_ = 0;\n\n // Architecture descriptor (resolved post-init from the WASM build).\n private arch_: MLArchitecture = {\n inputSize: DEFAULT_INPUT_SIZE,\n hidden: [10, 14, 18],\n outputSize: DEFAULT_OUTPUT_SIZE,\n numLayers: 4,\n };\n\n // Pre-allocated heap buffers.\n private featuresBuf!: HeapBuffer;\n private labelsBuf!: HeapBuffer;\n private weightsBuf!: HeapBuffer;\n private statsBuf!: HeapBuffer;\n private batchInBuf!: HeapBuffer;\n private batchOutBuf!: HeapBuffer;\n private pinMaskBuf!: HeapU8;\n private describeBuf!: HeapBuffer; // 6 ints, reused as 6 floats on the heap is wrong;\n // We use HEAP32 directly via a tiny scratch malloc:\n private describePtr = 0;\n\n // JS-side state.\n readonly dataset: Dataset;\n private lastLoss_: number | null = null;\n private trainer: WasmTrainer | null = null;\n private storageKey: string;\n private saveTimer: number | null = null;\n private destroyed = false;\n\n static MAX_BATCH = 4096;\n\n private constructor(opts: WasmIMLOptions) {\n this.dataset = new Dataset(opts.maxExamples ?? 100);\n this.storageKey = opts.storageKey ?? 'nisps:wasm-iml';\n }\n\n /**\n * Async factory. Loads the wasm, creates the MLP handle, allocates heap\n * buffers, and rehydrates persisted state.\n */\n static async create(opts: WasmIMLOptions = {}): Promise {\n const inst = new WasmIML(opts);\n await inst.init_(opts);\n return inst;\n }\n\n private async init_(opts: WasmIMLOptions): Promise {\n const factory = await getFactory();\n this.module = await factory({\n // Vite serves /nisps.wasm at the root; the default locateFile would\n // resolve relative to nisps.js (also at root) so this is the same\n // result, but explicit is better.\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL(NISPS_WASM_URL, window.location.origin).toString();\n return path;\n },\n });\n\n // Resolve architecture via the WASM module (compile-time fixed; we\n // stash the values for callers that need them).\n this.describePtr = this.module._malloc(6 * 4);\n this.module._nisps_ml_describe(this.describePtr);\n const dims = new Int32Array(this.module.HEAP32.buffer, this.describePtr, 6);\n this.arch_ = {\n inputSize: dims[0],\n hidden: [dims[1], dims[2], dims[3]],\n outputSize: dims[4],\n numLayers: dims[5],\n };\n\n // Caller-supplied dimensions are accepted but ignored; warn if mismatch.\n const wantedIn = opts.inputSize ?? this.arch_.inputSize;\n const wantedOut = opts.outputSize ?? this.arch_.outputSize;\n if (wantedIn !== this.arch_.inputSize || wantedOut !== this.arch_.outputSize) {\n console.warn(\n `[wasm-iml] requested ${wantedIn}->${wantedOut} but WASM build is fixed at ` +\n `${this.arch_.inputSize}->${this.arch_.outputSize}; extras are ignored.`,\n );\n }\n\n // Create the MLP. We pass dummy hidden ptr/count — the binding ignores them.\n const seed = (opts.seed ?? (Date.now() >>> 0)) >>> 0;\n this.mlHandle = this.module._nisps_ml_create(\n this.arch_.inputSize,\n this.arch_.outputSize,\n 0, // hidden ptr (unused)\n 0, // n_hidden (unused)\n seed,\n );\n if (!this.mlHandle) throw new Error('[wasm-iml] nisps_ml_create returned null');\n\n this.weightCount_ = this.module._nisps_ml_weight_count(this.mlHandle);\n\n // Heap buffers (created after we know the architecture).\n this.featuresBuf = new HeapBuffer(this.module, this.arch_.inputSize);\n this.labelsBuf = new HeapBuffer(this.module, this.arch_.outputSize);\n this.weightsBuf = new HeapBuffer(this.module, this.weightCount_);\n this.statsBuf = new HeapBuffer(this.module, this.arch_.numLayers * 4);\n this.batchInBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.inputSize);\n this.batchOutBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.outputSize);\n this.pinMaskBuf = new HeapU8(this.module, this.arch_.outputSize);\n\n // Push initial state to the store.\n mlStore.__setState(produce((s) => {\n s.inputSize = this.arch_.inputSize;\n s.outputSize = this.arch_.outputSize;\n s.exampleCount = 0;\n s.lastLoss = null;\n s.lossHistory = [];\n s.training = false;\n s.ready = true;\n }));\n mlStore.__setOutputs(new Float32Array(this.arch_.outputSize));\n this.publishWeights_();\n\n // Load persisted state if present (best-effort).\n this.tryLoadFromStorage_();\n }\n\n // -------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------\n\n dispose(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n if (this.trainer) {\n this.trainer.dispose();\n this.trainer = null;\n }\n if (this.module && this.mlHandle) {\n this.module._nisps_ml_destroy(this.mlHandle);\n this.mlHandle = 0;\n }\n if (this.featuresBuf) this.featuresBuf.free();\n if (this.labelsBuf) this.labelsBuf.free();\n if (this.weightsBuf) this.weightsBuf.free();\n if (this.statsBuf) this.statsBuf.free();\n if (this.batchInBuf) this.batchInBuf.free();\n if (this.batchOutBuf) this.batchOutBuf.free();\n if (this.pinMaskBuf) this.pinMaskBuf.free();\n if (this.describePtr) this.module._free(this.describePtr);\n mlStore.__setState(produce((s) => {\n s.ready = false;\n }));\n }\n\n get architecture(): MLArchitecture {\n return this.arch_;\n }\n get weightCount(): number {\n return this.weightCount_;\n }\n get exampleCount(): number {\n return this.dataset.size;\n }\n get lastLoss(): number | null {\n return this.lastLoss_;\n }\n\n // -------------------------------------------------------------------\n // Inference\n // -------------------------------------------------------------------\n\n setInput(idx: number, value: number): void {\n this.module._nisps_ml_set_input(this.mlHandle, idx, value);\n }\n\n process(): Float32Array {\n this.module._nisps_ml_process(this.mlHandle);\n const ptr = this.module._nisps_ml_outputs(this.mlHandle);\n // Copy out so the caller can hold onto it across memory growth.\n const view = new Float32Array(this.module.HEAPF32.buffer, ptr, this.arch_.outputSize);\n const out = new Float32Array(view); // copy\n mlStore.__setOutputs(out);\n return out;\n }\n\n /** Convenience: setInput(0,x); setInput(1,y); process(). */\n inferXY(x: number, y: number): Float32Array {\n this.setInput(0, x);\n this.setInput(1, y);\n return this.process();\n }\n\n /**\n * Batch inference. `points` is an array of [x,y] tuples (or any vector\n * length <= inputSize; trailing entries zero-padded). Returns a flat\n * Float32Array of length n * outputSize.\n *\n * Larger requests than `MAX_BATCH` are chunked transparently.\n */\n inferBatch(points: ReadonlyArray>): Float32Array {\n const n = points.length;\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n const result = new Float32Array(n * outSz);\n\n let written = 0;\n for (let offset = 0; offset < n; offset += WasmIML.MAX_BATCH) {\n const chunk = Math.min(WasmIML.MAX_BATCH, n - offset);\n // Pack into batchInBuf.\n for (let i = 0; i < chunk; ++i) {\n const src = points[offset + i];\n const base = i * inSz;\n for (let j = 0; j < inSz; ++j) this.batchInBuf.view[base + j] = src[j] ?? 0;\n }\n this.module._nisps_ml_infer_batch(\n this.mlHandle,\n this.batchInBuf.ptr,\n chunk,\n this.batchOutBuf.ptr,\n );\n // Copy out into result.\n const slice = this.batchOutBuf.view.subarray(0, chunk * outSz);\n result.set(slice, written);\n written += chunk * outSz;\n }\n return result;\n }\n\n // -------------------------------------------------------------------\n // Training\n // -------------------------------------------------------------------\n\n /**\n * Add a feature/label pair to BOTH the JS dataset and the WASM ring\n * buffer. The two stay in sync because every train() call pushes the\n * full JS dataset back into WASM (in case of weight recompute, undo\n * restore, etc.). For the sake of correctness, we re-sync on each add\n * too — cheap relative to training.\n */\n addExample(features: ReadonlyArray, labels: ReadonlyArray): boolean {\n const ok = this.dataset.add(features, labels);\n if (!ok) return false;\n this.copyExampleToWasm_(features, labels);\n mlStore.__setState(produce((s) => {\n s.exampleCount = this.dataset.size;\n }));\n coreBus.emit('ml.example_added', { count: this.dataset.size });\n this.scheduleSave_();\n return true;\n }\n\n private copyExampleToWasm_(features: ReadonlyArray, labels: ReadonlyArray): void {\n const fv = this.featuresBuf.view;\n const lv = this.labelsBuf.view;\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n for (let i = 0; i < inSz; ++i) fv[i] = features[i] ?? 0;\n for (let i = 0; i < outSz; ++i) lv[i] = labels[i] ?? 0;\n this.module._nisps_ml_add_example(this.mlHandle, this.featuresBuf.ptr, this.labelsBuf.ptr);\n }\n\n /**\n * Synchronous training. Returns the final loss (also stored in\n * `lastLoss`). Updates `mlStore.lastLoss` and emits `ml.trained`.\n *\n * Caller can pass per-sample weights; if omitted the WASM side uses\n * uniform 1/n weighting.\n */\n train(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): number {\n if (this.dataset.isEmpty()) {\n this.lastLoss_ = 0;\n mlStore.__setState(produce((s) => { s.lastLoss = 0; }));\n return 0;\n }\n\n let weightsPtr = 0;\n let weightsHandle: HeapBuffer | null = null;\n if (sampleWeights && sampleWeights.length === this.dataset.size) {\n weightsHandle = new HeapBuffer(this.module, sampleWeights.length);\n weightsHandle.view.set(sampleWeights);\n weightsPtr = weightsHandle.ptr;\n }\n\n mlStore.__setState(produce((s) => { s.training = true; }));\n let loss = 0;\n try {\n loss = this.module._nisps_ml_train(this.mlHandle, lr, maxIter, minErr, weightsPtr);\n } finally {\n if (weightsHandle) weightsHandle.free();\n mlStore.__setState(produce((s) => { s.training = false; }));\n }\n\n this.lastLoss_ = loss;\n mlStore.__setState(produce((s) => {\n s.lastLoss = loss;\n // The C++ MLP stores per-iter history but we don't currently expose\n // it via the WASM bindings. Stream 9 may add nisps_ml_loss_history.\n s.lossHistory = [loss];\n }));\n this.publishWeights_();\n coreBus.emit('ml.trained', { loss });\n this.scheduleSave_();\n return loss;\n }\n\n /**\n * Async training via worker. The worker holds a SECOND wasm instance,\n * receives current weights + dataset, runs SGD, and returns updated\n * weights. Main-thread weights are then `setWeights()`-restored.\n */\n async trainAsync(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): Promise {\n if (this.dataset.isEmpty()) {\n this.lastLoss_ = 0;\n return 0;\n }\n if (!this.trainer) this.trainer = await createTrainer();\n\n const weights = this.getWeights();\n const features = new Float32Array(this.dataset.featuresFlat());\n const labels = new Float32Array(this.dataset.labelsFlat());\n const sw = sampleWeights ? new Float32Array(sampleWeights) : new Float32Array(0);\n\n mlStore.__setState(produce((s) => { s.training = true; }));\n try {\n const result = await this.trainer.train({\n weights,\n features,\n labels,\n sampleWeights: sw,\n lr,\n maxIter,\n minErr,\n inputSize: this.arch_.inputSize,\n outputSize: this.arch_.outputSize,\n });\n this.setWeights(result.weights);\n this.lastLoss_ = result.loss;\n mlStore.__setState(produce((s) => {\n s.lastLoss = result.loss;\n s.lossHistory = Array.from(result.lossHistory);\n }));\n coreBus.emit('ml.trained', { loss: result.loss });\n this.scheduleSave_();\n return result.loss;\n } finally {\n mlStore.__setState(produce((s) => { s.training = false; }));\n }\n }\n\n /** Non-destructive loss query. */\n evalLoss(): number {\n return this.module._nisps_ml_eval_loss(this.mlHandle);\n }\n\n clearExamples(): void {\n this.dataset.clear();\n this.module._nisps_ml_clear_examples(this.mlHandle);\n mlStore.__setState(produce((s) => { s.exampleCount = 0; }));\n coreBus.emit('ml.examples_cleared', undefined);\n this.scheduleSave_();\n }\n\n // -------------------------------------------------------------------\n // RL ops\n // -------------------------------------------------------------------\n\n randomiseWeights(spread = 0.6): void {\n this.module._nisps_ml_draw_weights(this.mlHandle, spread);\n this.publishWeights_();\n coreBus.emit('ml.delta_update', { reason: 'randomize' });\n this.scheduleSave_();\n }\n\n moveWeights(speed: number, spread: number, pinMask?: Uint8Array): void {\n let maskPtr = 0;\n if (pinMask) {\n const sz = Math.min(pinMask.length, this.arch_.outputSize);\n for (let i = 0; i < sz; ++i) this.pinMaskBuf.view[i] = pinMask[i];\n for (let i = sz; i < this.arch_.outputSize; ++i) this.pinMaskBuf.view[i] = 0;\n maskPtr = this.pinMaskBuf.ptr;\n }\n this.module._nisps_ml_move_weights(this.mlHandle, speed, spread, maskPtr);\n this.publishWeights_();\n // The caller (RL handler) decides whether this is a thumbs-up/down;\n // we emit a generic delta_update.\n coreBus.emit('ml.delta_update', { reason: 'thumbs_down' });\n }\n\n // -------------------------------------------------------------------\n // Weights I/O\n // -------------------------------------------------------------------\n\n getWeights(): Float32Array {\n this.module._nisps_ml_get_weights(this.mlHandle, this.weightsBuf.ptr);\n // Copy out so caller can mutate freely.\n return new Float32Array(this.weightsBuf.view);\n }\n\n setWeights(w: Float32Array | Uint8Array): void {\n if (w.length < this.weightCount_) {\n throw new Error(`setWeights: expected ${this.weightCount_} floats, got ${w.length}`);\n }\n this.weightsBuf.view.set(w as Float32Array, 0);\n this.module._nisps_ml_set_weights(this.mlHandle, this.weightsBuf.ptr);\n this.publishWeights_();\n }\n\n /** Per-layer weight stats. Returns one record per layer. */\n getLayerStats(): LayerStats[] {\n this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr);\n const out: LayerStats[] = [];\n for (let i = 0; i < this.arch_.numLayers; ++i) {\n const base = i * 4;\n out.push({\n meanAbs: this.statsBuf.view[base],\n maxAbs: this.statsBuf.view[base + 1],\n deadFrac: this.statsBuf.view[base + 2],\n saturatingFrac: this.statsBuf.view[base + 3],\n });\n }\n return out;\n }\n\n /** Flat layer-stats Float32Array (numLayers * 4). For probe API. */\n getLayerStatsFlat(): Float32Array {\n this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr);\n return new Float32Array(this.statsBuf.view);\n }\n\n // -------------------------------------------------------------------\n // Misc\n // -------------------------------------------------------------------\n\n reset(): void {\n this.module._nisps_ml_reset(this.mlHandle);\n this.dataset.clear();\n this.lastLoss_ = null;\n mlStore.__setState(produce((s) => {\n s.exampleCount = 0;\n s.lastLoss = null;\n s.lossHistory = [];\n }));\n this.publishWeights_();\n coreBus.emit('ml.examples_cleared', undefined);\n this.scheduleSave_();\n }\n\n // -------------------------------------------------------------------\n // Persistence\n // -------------------------------------------------------------------\n\n private scheduleSave_(): void {\n if (this.saveTimer !== null) clearTimeout(this.saveTimer);\n this.saveTimer = window.setTimeout(() => this.saveNow(), 500);\n }\n\n saveNow(): void {\n if (this.destroyed) return;\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n try {\n const weights = this.getWeights();\n const payload = {\n v: 1,\n arch: this.arch_,\n weights: Array.from(weights),\n features: Array.from(this.dataset.featuresFlat()),\n labels: Array.from(this.dataset.labelsFlat()),\n size: this.dataset.size,\n lastLoss: this.lastLoss_,\n };\n localStorage.setItem(this.storageKey, JSON.stringify(payload));\n } catch (err) {\n // localStorage might be full or unavailable; not fatal.\n console.warn('[wasm-iml] saveNow failed:', err);\n }\n }\n\n private tryLoadFromStorage_(): void {\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) return;\n const payload = JSON.parse(raw) as {\n v: number;\n weights: number[];\n features: number[];\n labels: number[];\n size: number;\n lastLoss: number | null;\n };\n if (payload.v !== 1) return;\n // Restore dataset (rebuild via add()).\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n if (payload.size > 0 && payload.features.length === payload.size * inSz &&\n payload.labels.length === payload.size * outSz) {\n for (let i = 0; i < payload.size; ++i) {\n const f = payload.features.slice(i * inSz, (i + 1) * inSz);\n const l = payload.labels.slice(i * outSz, (i + 1) * outSz);\n this.dataset.add(f, l);\n // Also push to the WASM ring buffer.\n this.copyExampleToWasm_(f, l);\n }\n }\n // Restore weights.\n if (payload.weights.length === this.weightCount_) {\n this.setWeights(new Float32Array(payload.weights));\n }\n this.lastLoss_ = payload.lastLoss;\n mlStore.__setState(produce((s) => {\n s.exampleCount = this.dataset.size;\n s.lastLoss = this.lastLoss_;\n }));\n } catch (err) {\n console.warn('[wasm-iml] tryLoadFromStorage failed:', err);\n }\n }\n\n // -------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------\n\n private publishWeights_(): void {\n const w = this.getWeights();\n mlStore.__setWeights(w);\n }\n}\n"],"names":["Dataset","maxSize","features","labels","fOff","lOff","i","mode","params","n","weights","bias","decay","query","radius","radiusSq","dim","fOffI","distSq","d","diff","proximity","newerNearby","j","fOffJ","djSq","sum","WasmTrainer","trainer","ev","reject","resolve","handler","msg","seed","args","requestId","p","createTrainer","isWorker","ensureBuffers","sampleWeights","mod","weightsViewLen","weightCount","weightsPtr","featuresLen","featuresPtr","labelsLen","labelsPtr","sampleWeightsLen","sampleWeightsPtr","trainOnce","req","mlHandle","inSz","outSz","fPtr","lPtr","swPtr","loss","view","outWeights","lossHistory","err","disposeModule","loadModule","factoryMod","path","res","NISPS_JS_URL","NISPS_WASM_URL","DEFAULT_INPUT_SIZE","DEFAULT_OUTPUT_SIZE","cachedFactory","getFactory","factory","HeapBuffer","count","HeapU8","WasmIML","opts","inst","dims","wantedIn","wantedOut","mlStore","produce","s","idx","value","ptr","out","x","y","points","result","written","offset","chunk","src","base","slice","coreBus","fv","lv","lr","maxIter","minErr","weightsHandle","sw","spread","speed","pinMask","maskPtr","sz","w","payload","raw","f","l"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/wasm-worker-OATBBHaT.js b/playground/dist/assets/wasm-worker-OATBBHaT.js new file mode 100644 index 0000000..295739c --- /dev/null +++ b/playground/dist/assets/wasm-worker-OATBBHaT.js @@ -0,0 +1,2 @@ +(function(){"use strict";if(typeof window>"u"&&typeof self<"u"&&typeof self.importScripts<"u"){let h=function(t,s,n,u){if(!e)throw new Error("worker module not loaded");c!==a&&(i&&e._free(i),i=e._malloc(a*4),c=a),t.length!==g&&(l&&e._free(l),l=e._malloc(t.length*4),g=t.length),s.length!==_&&(f&&e._free(f),f=e._malloc(s.length*4),_=s.length),n.length!==w&&(o&&e._free(o),o=n.length>0?e._malloc(n.length*4):0,w=n.length),new Float32Array(e.HEAPF32.buffer,i,a).set(u),new Float32Array(e.HEAPF32.buffer,l,t.length).set(t),new Float32Array(e.HEAPF32.buffer,f,s.length).set(s),o&&new Float32Array(e.HEAPF32.buffer,o,n.length).set(n)},p=function(t){if(!e)return{kind:"error",requestId:t.requestId,message:"worker not initialised"};try{h(t.features,t.labels,t.sampleWeights,t.weights),e._nisps_ml_set_weights(r,i),e._nisps_ml_clear_examples(r);const s=t.inputSize,n=t.outputSize,u=t.features.length/s;for(let d=0;d0?o:0,m=e._nisps_ml_train(r,t.lr,t.maxIter,t.minErr,F);e._nisps_ml_get_weights(r,i);const A=new Float32Array(e.HEAPF32.buffer,i,a),P=new Float32Array(A),E=new Float32Array([m]);return{kind:"result",requestId:t.requestId,loss:m,weights:P,lossHistory:E}}catch(s){return{kind:"error",requestId:t.requestId,message:s instanceof Error?s.message:String(s)}}},y=function(){e&&(r&&(e._nisps_ml_destroy(r),r=0),i&&(e._free(i),i=0),l&&(e._free(l),l=0),f&&(e._free(f),f=0),o&&(e._free(o),o=0),e=null)},e=null,r=0,a=0,i=0,c=0,l=0,g=0,f=0,_=0,o=0,w=0;async function k(t){const s=await import(new URL("/nisps.js",self.location.origin).toString());e=await(s.default??s.createNispsModule)({locateFile:u=>u.endsWith(".wasm")?new URL("/nisps.wasm",self.location.origin).toString():u}),r=e._nisps_ml_create(0,0,0,0,t>>>0),a=e._nisps_ml_weight_count(r)}self.addEventListener("message",async t=>{const s=t.data;if(s.kind==="init")try{await k(s.seed),self.postMessage({kind:"ready"})}catch(n){self.postMessage({kind:"error",requestId:0,message:n instanceof Error?n.message:String(n)})}else if(s.kind==="train"){const n=p(s);n.kind==="result"?self.postMessage(n,[n.weights.buffer,n.lossHistory.buffer]):self.postMessage(n)}else s.kind==="dispose"&&y()})}})(); +//# sourceMappingURL=wasm-worker-OATBBHaT.js.map diff --git a/playground/dist/assets/wasm-worker-OATBBHaT.js.map b/playground/dist/assets/wasm-worker-OATBBHaT.js.map new file mode 100644 index 0000000..289c674 --- /dev/null +++ b/playground/dist/assets/wasm-worker-OATBBHaT.js.map @@ -0,0 +1 @@ +{"version":3,"file":"wasm-worker-OATBBHaT.js","sources":["../src/ml/wasm-worker.ts"],"sourcesContent":["/**\n * Disposable Web Worker that runs SGD off the main thread.\n *\n * The worker holds its own `nisps.wasm` instance (architecture.md §6.4).\n * The main thread sends:\n * - current weights (so the worker is in the same state as the UI),\n * - dataset (features + labels),\n * - SGD hyperparameters,\n * and receives updated weights + final loss.\n *\n * This module exposes:\n * - `createTrainer()` - factory that spawns the worker, loads its WASM,\n * and returns a `WasmTrainer` handle.\n * - The worker entry-point itself (when this file runs in a Worker).\n *\n * The worker is implemented inline so a single TS file becomes both the\n * main-thread API and the worker bundle. Vite's `new Worker(new URL(...,\n * import.meta.url))` pattern packs it correctly.\n *\n * Lifecycle: each `WasmTrainer` is disposable via `.dispose()` which\n * terminates the worker. Tests should always dispose to avoid leaks.\n */\n\nimport type { NispsModule, NispsModuleFactory, WorkerRequest, WorkerResponse } from './types';\n\n// ---------------------------------------------------------------------------\n// Main-thread side\n// ---------------------------------------------------------------------------\n\nexport interface TrainArgs {\n weights: Float32Array;\n features: Float32Array;\n labels: Float32Array;\n /** Optional; pass empty for uniform weighting. */\n sampleWeights: Float32Array;\n lr: number;\n maxIter: number;\n minErr: number;\n inputSize: number;\n outputSize: number;\n}\n\nexport interface TrainResult {\n loss: number;\n weights: Float32Array;\n lossHistory: Float32Array;\n}\n\nexport class WasmTrainer {\n private worker: Worker;\n private nextId = 1;\n private pending = new Map void; reject: (e: unknown) => void }>();\n private disposed = false;\n\n static async create(): Promise {\n const trainer = new WasmTrainer();\n await trainer.init_();\n return trainer;\n }\n\n private constructor() {\n this.worker = new Worker(new URL('./wasm-worker.ts', import.meta.url), { type: 'module' });\n this.worker.onmessage = (ev) => this.onMessage_(ev.data as WorkerResponse);\n this.worker.onerror = (ev) => {\n // Fail any pending requests.\n for (const { reject } of this.pending.values()) reject(ev.message ?? 'worker error');\n this.pending.clear();\n };\n }\n\n private init_(): Promise {\n return new Promise((resolve, reject) => {\n const handler = (ev: MessageEvent) => {\n const msg = ev.data as WorkerResponse;\n if (msg.kind === 'ready') {\n this.worker.removeEventListener('message', handler);\n resolve();\n } else if (msg.kind === 'error') {\n this.worker.removeEventListener('message', handler);\n reject(new Error(msg.message));\n }\n };\n this.worker.addEventListener('message', handler);\n const seed = (Date.now() ^ Math.floor(Math.random() * 0xffffffff)) >>> 0;\n this.worker.postMessage({ kind: 'init', seed } satisfies WorkerRequest);\n });\n }\n\n train(args: TrainArgs): Promise {\n if (this.disposed) return Promise.reject(new Error('WasmTrainer disposed'));\n const requestId = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(requestId, { resolve, reject });\n const msg: WorkerRequest = {\n kind: 'train',\n requestId,\n weights: args.weights,\n features: args.features,\n labels: args.labels,\n sampleWeights: args.sampleWeights,\n lr: args.lr,\n maxIter: args.maxIter,\n minErr: args.minErr,\n inputSize: args.inputSize,\n outputSize: args.outputSize,\n };\n // Transfer all the typed-array buffers we no longer need on the\n // main thread; faster than copying. Caller has already cloned.\n this.worker.postMessage(msg, [\n args.weights.buffer,\n args.features.buffer,\n args.labels.buffer,\n args.sampleWeights.buffer,\n ]);\n });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.worker.postMessage({ kind: 'dispose' } satisfies WorkerRequest);\n } catch {\n /* ignore */\n }\n this.worker.terminate();\n for (const { reject } of this.pending.values()) reject(new Error('disposed'));\n this.pending.clear();\n }\n\n private onMessage_(msg: WorkerResponse): void {\n if (msg.kind === 'result') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.resolve({ loss: msg.loss, weights: msg.weights, lossHistory: msg.lossHistory });\n }\n } else if (msg.kind === 'error') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.reject(new Error(msg.message));\n }\n }\n // 'ready' handled in init_().\n }\n}\n\nexport function createTrainer(): Promise {\n return WasmTrainer.create();\n}\n\n// ---------------------------------------------------------------------------\n// Worker-thread side\n// ---------------------------------------------------------------------------\n//\n// When this module is loaded in a Worker, `self` is `WorkerGlobalScope` and\n// `window` is undefined. We use that as the dispatch.\n//\n// We import the wasm via a same-origin fetch (no DOM available so we can't\n// use a regular import URL — but Vite's bundler treats `new Worker(...)`\n// specially and `?url` imports work for assets).\n\n// Inside a Worker, `self` is a `DedicatedWorkerGlobalScope`. To keep TS\n// happy in both build contexts we use a structural cast.\ndeclare const self: {\n postMessage: (msg: unknown, transfer?: Transferable[]) => void;\n addEventListener: (event: string, handler: (ev: MessageEvent) => void) => void;\n location: { origin: string };\n importScripts?: unknown;\n};\n\nconst isWorker =\n typeof window === 'undefined' &&\n typeof self !== 'undefined' &&\n typeof (self as { importScripts?: unknown }).importScripts !== 'undefined';\n\nif (isWorker) {\n // Module-level state in worker scope.\n let mod: NispsModule | null = null;\n let mlHandle = 0;\n let weightCount = 0;\n\n // Heap buffers (allocated on first train).\n let weightsPtr = 0;\n let weightsViewLen = 0;\n let featuresPtr = 0;\n let featuresLen = 0;\n let labelsPtr = 0;\n let labelsLen = 0;\n let sampleWeightsPtr = 0;\n let sampleWeightsLen = 0;\n\n async function loadModule(seed: number): Promise {\n // Same-origin fetch to /nisps.js. The worker is served by the dev\n // server with COOP/COEP set, so this works.\n const factoryMod = await import(/* @vite-ignore */ new URL('/nisps.js', self.location.origin).toString());\n const factory: NispsModuleFactory =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (factoryMod as any).default ?? (factoryMod as any).createNispsModule;\n mod = await factory({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL('/nisps.wasm', self.location.origin).toString();\n return path;\n },\n });\n mlHandle = mod._nisps_ml_create(0, 0, 0, 0, seed >>> 0);\n weightCount = mod._nisps_ml_weight_count(mlHandle);\n }\n\n function ensureBuffers(features: Float32Array, labels: Float32Array, sampleWeights: Float32Array, weights: Float32Array): void {\n if (!mod) throw new Error('worker module not loaded');\n\n if (weightsViewLen !== weightCount) {\n if (weightsPtr) mod._free(weightsPtr);\n weightsPtr = mod._malloc(weightCount * 4);\n weightsViewLen = weightCount;\n }\n if (features.length !== featuresLen) {\n if (featuresPtr) mod._free(featuresPtr);\n featuresPtr = mod._malloc(features.length * 4);\n featuresLen = features.length;\n }\n if (labels.length !== labelsLen) {\n if (labelsPtr) mod._free(labelsPtr);\n labelsPtr = mod._malloc(labels.length * 4);\n labelsLen = labels.length;\n }\n if (sampleWeights.length !== sampleWeightsLen) {\n if (sampleWeightsPtr) mod._free(sampleWeightsPtr);\n sampleWeightsPtr = sampleWeights.length > 0 ? mod._malloc(sampleWeights.length * 4) : 0;\n sampleWeightsLen = sampleWeights.length;\n }\n\n new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount).set(weights);\n new Float32Array(mod.HEAPF32.buffer, featuresPtr, features.length).set(features);\n new Float32Array(mod.HEAPF32.buffer, labelsPtr, labels.length).set(labels);\n if (sampleWeightsPtr) {\n new Float32Array(mod.HEAPF32.buffer, sampleWeightsPtr, sampleWeights.length).set(sampleWeights);\n }\n }\n\n function trainOnce(req: Extract): WorkerResponse {\n if (!mod) {\n return { kind: 'error', requestId: req.requestId, message: 'worker not initialised' };\n }\n try {\n ensureBuffers(req.features, req.labels, req.sampleWeights, req.weights);\n // Push current weights into our MLP.\n mod._nisps_ml_set_weights(mlHandle, weightsPtr);\n\n // Seed the example ring buffer. We must clear first because past\n // train calls may have left examples there.\n mod._nisps_ml_clear_examples(mlHandle);\n const inSz = req.inputSize;\n const outSz = req.outputSize;\n const n = req.features.length / inSz;\n // Allocate small per-example scratch (re-used across iterations of\n // this loop).\n // We use stack-equivalents by allocating once, then shifting pointers.\n for (let i = 0; i < n; ++i) {\n const fPtr = featuresPtr + i * inSz * 4;\n const lPtr = labelsPtr + i * outSz * 4;\n mod._nisps_ml_add_example(mlHandle, fPtr, lPtr);\n }\n\n // Run training.\n const swPtr = req.sampleWeights.length > 0 ? sampleWeightsPtr : 0;\n const loss = mod._nisps_ml_train(mlHandle, req.lr, req.maxIter, req.minErr, swPtr);\n\n // Read out final weights.\n mod._nisps_ml_get_weights(mlHandle, weightsPtr);\n const view = new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount);\n const outWeights = new Float32Array(view); // copy\n\n // Loss history not yet plumbed via WASM; emit just final loss.\n const lossHistory = new Float32Array([loss]);\n\n return {\n kind: 'result',\n requestId: req.requestId,\n loss,\n weights: outWeights,\n lossHistory,\n };\n } catch (err) {\n return {\n kind: 'error',\n requestId: req.requestId,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n function disposeModule(): void {\n if (!mod) return;\n if (mlHandle) {\n mod._nisps_ml_destroy(mlHandle);\n mlHandle = 0;\n }\n if (weightsPtr) { mod._free(weightsPtr); weightsPtr = 0; }\n if (featuresPtr) { mod._free(featuresPtr); featuresPtr = 0; }\n if (labelsPtr) { mod._free(labelsPtr); labelsPtr = 0; }\n if (sampleWeightsPtr) { mod._free(sampleWeightsPtr); sampleWeightsPtr = 0; }\n mod = null;\n }\n\n self.addEventListener('message', async (ev: MessageEvent) => {\n const req = ev.data;\n if (req.kind === 'init') {\n try {\n await loadModule(req.seed);\n self.postMessage({ kind: 'ready' } satisfies WorkerResponse);\n } catch (err) {\n self.postMessage({\n kind: 'error',\n requestId: 0,\n message: err instanceof Error ? err.message : String(err),\n } satisfies WorkerResponse);\n }\n } else if (req.kind === 'train') {\n const res = trainOnce(req);\n // Transfer weights back to main thread to avoid copy.\n if (res.kind === 'result') {\n self.postMessage(res, [res.weights.buffer, res.lossHistory.buffer]);\n } else {\n self.postMessage(res);\n }\n } else if (req.kind === 'dispose') {\n disposeModule();\n // Worker terminates from main thread side via .terminate().\n }\n });\n}\n"],"names":["ensureBuffers","features","labels","sampleWeights","weights","mod","weightsViewLen","weightCount","weightsPtr","featuresLen","featuresPtr","labelsLen","labelsPtr","sampleWeightsLen","sampleWeightsPtr","trainOnce","req","mlHandle","inSz","outSz","n","i","fPtr","lPtr","swPtr","loss","view","outWeights","lossHistory","err","disposeModule","loadModule","seed","factoryMod","path","ev","res"],"mappings":"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"} \ No newline at end of file diff --git a/playground/index.html b/playground/dist/index.html similarity index 67% rename from playground/index.html rename to playground/dist/index.html index 60b50c7..5aaf5b0 100644 --- a/playground/index.html +++ b/playground/dist/index.html @@ -5,9 +5,10 @@ MEMLNaut Playground + +
- diff --git a/playground/dist/nisps.js b/playground/dist/nisps.js new file mode 100644 index 0000000..3efe02b --- /dev/null +++ b/playground/dist/nisps.js @@ -0,0 +1,2 @@ +var createNispsModule=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["d"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("nisps.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var __abort_js=()=>abort("");var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["ccall"]=ccall;Module["cwrap"]=cwrap;var _nisps_ml_create,_nisps_ml_destroy,_nisps_ml_set_input,_nisps_ml_process,_nisps_ml_outputs,_nisps_ml_infer_batch,_nisps_ml_add_example,_nisps_ml_train,_nisps_ml_eval_loss,_nisps_ml_weight_count,_nisps_ml_get_weights,_nisps_ml_set_weights,_nisps_ml_draw_weights,_nisps_ml_move_weights,_nisps_ml_get_layer_stats,_nisps_ml_example_count,_nisps_ml_clear_examples,_nisps_ml_reset,_nisps_ml_describe,_nisps_engine_create,_nisps_engine_destroy,_nisps_engine_set_params,_nisps_engine_process_block,_malloc,_free,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_nisps_ml_create=Module["_nisps_ml_create"]=wasmExports["e"];_nisps_ml_destroy=Module["_nisps_ml_destroy"]=wasmExports["f"];_nisps_ml_set_input=Module["_nisps_ml_set_input"]=wasmExports["g"];_nisps_ml_process=Module["_nisps_ml_process"]=wasmExports["h"];_nisps_ml_outputs=Module["_nisps_ml_outputs"]=wasmExports["i"];_nisps_ml_infer_batch=Module["_nisps_ml_infer_batch"]=wasmExports["j"];_nisps_ml_add_example=Module["_nisps_ml_add_example"]=wasmExports["k"];_nisps_ml_train=Module["_nisps_ml_train"]=wasmExports["l"];_nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=wasmExports["m"];_nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=wasmExports["n"];_nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=wasmExports["o"];_nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=wasmExports["p"];_nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=wasmExports["q"];_nisps_ml_move_weights=Module["_nisps_ml_move_weights"]=wasmExports["r"];_nisps_ml_get_layer_stats=Module["_nisps_ml_get_layer_stats"]=wasmExports["s"];_nisps_ml_example_count=Module["_nisps_ml_example_count"]=wasmExports["t"];_nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=wasmExports["u"];_nisps_ml_reset=Module["_nisps_ml_reset"]=wasmExports["v"];_nisps_ml_describe=Module["_nisps_ml_describe"]=wasmExports["w"];_nisps_engine_create=Module["_nisps_engine_create"]=wasmExports["x"];_nisps_engine_destroy=Module["_nisps_engine_destroy"]=wasmExports["y"];_nisps_engine_set_params=Module["_nisps_engine_set_params"]=wasmExports["z"];_nisps_engine_process_block=Module["_nisps_engine_process_block"]=wasmExports["A"];_malloc=Module["_malloc"]=wasmExports["B"];_free=Module["_free"]=wasmExports["C"];__emscripten_stack_restore=wasmExports["D"];__emscripten_stack_alloc=wasmExports["E"];_emscripten_stack_get_current=wasmExports["F"];memory=wasmMemory=wasmExports["c"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:__abort_js,b:_emscripten_resize_heap};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=createNispsModule;module.exports.default=createNispsModule}else if(typeof define==="function"&&define["amd"])define([],()=>createNispsModule); diff --git a/playground/dist/nisps.wasm b/playground/dist/nisps.wasm new file mode 100755 index 0000000000000000000000000000000000000000..1eecd696149741a8513c5e28ccc39c78688be0a9 GIT binary patch literal 95864 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 literal 0 HcmV?d00001 diff --git a/playground/package-lock.json b/playground/package-lock.json deleted file mode 100644 index c8a1de6..0000000 --- a/playground/package-lock.json +++ /dev/null @@ -1,1886 +0,0 @@ -{ - "name": "memlnaut-playground", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "memlnaut-playground", - "version": "0.0.0", - "dependencies": { - "solid-js": "^1.8.22" - }, - "devDependencies": { - "@playwright/test": "^1.48.0", - "@types/node": "^22.7.0", - "typescript": "^5.5.4", - "vite": "^5.4.10", - "vite-plugin-solid": "^2.10.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@playwright/test": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0.tgz", - "integrity": "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.61.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", - "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", - "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", - "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", - "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", - "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", - "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", - "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", - "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", - "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", - "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", - "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", - "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", - "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", - "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", - "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", - "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", - "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", - "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", - "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", - "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", - "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", - "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", - "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", - "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", - "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", - "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/babel-plugin-jsx-dom-expressions": { - "version": "0.40.7", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", - "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "7.18.6", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.20.7", - "html-entities": "2.3.3", - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.20.12" - } - }, - "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/babel-preset-solid": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", - "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.40.6" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "solid-js": "^1.9.12" - }, - "peerDependenciesMeta": { - "solid-js": { - "optional": true - } - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.374", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.374.tgz", - "integrity": "sha512-HCF5i7izveksHSGqa7mhDh6tr3Uz9Dar2RAjwuh69bw3QGPVObjQIgLwQWeO/Rxp9/r0KdboKy9RbpQDl97fjg==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/merge-anything": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", - "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/playwright": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", - "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.61.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", - "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", - "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.0", - "@rollup/rollup-android-arm64": "4.62.0", - "@rollup/rollup-darwin-arm64": "4.62.0", - "@rollup/rollup-darwin-x64": "4.62.0", - "@rollup/rollup-freebsd-arm64": "4.62.0", - "@rollup/rollup-freebsd-x64": "4.62.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", - "@rollup/rollup-linux-arm-musleabihf": "4.62.0", - "@rollup/rollup-linux-arm64-gnu": "4.62.0", - "@rollup/rollup-linux-arm64-musl": "4.62.0", - "@rollup/rollup-linux-loong64-gnu": "4.62.0", - "@rollup/rollup-linux-loong64-musl": "4.62.0", - "@rollup/rollup-linux-ppc64-gnu": "4.62.0", - "@rollup/rollup-linux-ppc64-musl": "4.62.0", - "@rollup/rollup-linux-riscv64-gnu": "4.62.0", - "@rollup/rollup-linux-riscv64-musl": "4.62.0", - "@rollup/rollup-linux-s390x-gnu": "4.62.0", - "@rollup/rollup-linux-x64-gnu": "4.62.0", - "@rollup/rollup-linux-x64-musl": "4.62.0", - "@rollup/rollup-openbsd-x64": "4.62.0", - "@rollup/rollup-openharmony-arm64": "4.62.0", - "@rollup/rollup-win32-arm64-msvc": "4.62.0", - "@rollup/rollup-win32-ia32-msvc": "4.62.0", - "@rollup/rollup-win32-x64-gnu": "4.62.0", - "@rollup/rollup-win32-x64-msvc": "4.62.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/seroval": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", - "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/seroval-plugins": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", - "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "seroval": "^1.0" - } - }, - "node_modules/solid-js": { - "version": "1.9.13", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.13.tgz", - "integrity": "sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==", - "license": "MIT", - "dependencies": { - "csstype": "^3.1.0", - "seroval": "~1.5.0", - "seroval-plugins": "~1.5.0" - } - }, - "node_modules/solid-refresh": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", - "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.23.6", - "@babel/helper-module-imports": "^7.22.15", - "@babel/types": "^7.23.6" - }, - "peerDependencies": { - "solid-js": "^1.3" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-solid": { - "version": "2.11.12", - "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.12.tgz", - "integrity": "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.3", - "@types/babel__core": "^7.20.4", - "babel-preset-solid": "^1.8.4", - "merge-anything": "^5.1.7", - "solid-refresh": "^0.6.3", - "vitefu": "^1.0.4" - }, - "peerDependencies": { - "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", - "solid-js": "^1.7.2", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@testing-library/jest-dom": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/vitefu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", - "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/playground/package.json b/playground/package.json deleted file mode 100644 index 29f17b7..0000000 --- a/playground/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "memlnaut-playground", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc --noEmit && vite build", - "preview": "vite preview --port 4173", - "typecheck": "tsc --noEmit", - "test:e2e": "playwright test", - "test:e2e:headed": "playwright test --headed", - "test:e2e:ui": "playwright test --ui" - }, - "dependencies": { - "solid-js": "^1.8.22" - }, - "devDependencies": { - "@playwright/test": "^1.48.0", - "@types/node": "^22.7.0", - "typescript": "^5.5.4", - "vite": "^5.4.10", - "vite-plugin-solid": "^2.10.2" - } -} diff --git a/playground/playwright.config.ts b/playground/playwright.config.ts deleted file mode 100644 index de4bc06..0000000 --- a/playground/playwright.config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { defineConfig } from '@playwright/test'; - -/** - * Playwright config for the SolidJS playground. - * - * The webServer block runs `vite preview` against the build output, so the - * tests exercise the real production bundle. Vite's preview command honors - * the COOP/COEP headers configured in vite.config.ts, which the AudioWorklet - * + WASM bridge needs. - * - * To run a fresh build before tests, run `bun run build` separately — the - * preview server expects `dist/` to already exist. CI does this in the - * workflow before invoking Playwright. - */ -export default defineConfig({ - testDir: './tests/e2e', - timeout: 30_000, - expect: { timeout: 10_000 }, - use: { - baseURL: 'http://localhost:4173', - headless: true, - ignoreHTTPSErrors: true, - }, - webServer: { - // `bun run preview` is `vite preview --port 4173` (see playground/package.json). - // Reuses an already-running server so `npx playwright test` after `bun - // dev` Just Works. - command: 'bun run preview', - cwd: '.', - url: 'http://localhost:4173', - reuseExistingServer: !process.env.CI, - timeout: 30_000, - }, - projects: [{ name: 'chromium', use: { browserName: 'chromium' } }], - reporter: process.env.CI ? [['list'], ['github']] : [['list'], ['html', { open: 'never' }]], -}); diff --git a/playground/public/nisps.js b/playground/public/nisps.js deleted file mode 100644 index 9c76fe9..0000000 --- a/playground/public/nisps.js +++ /dev/null @@ -1,19 +0,0 @@ - -var createNispsModule = (() => { - var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; - - return ( -function(moduleArg = {}) { - var moduleRtn; - -var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string"&&process.type!="renderer";var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){var preRuns=Module["preRun"];if(preRuns){if(typeof preRuns=="function")preRuns=[preRuns];preRuns.forEach(addOnPreRun)}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){var postRuns=Module["postRun"];if(postRuns){if(typeof postRuns=="function")postRuns=[postRuns];postRuns.forEach(addOnPostRun)}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);function findWasmBinary(){var f="nisps.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return readAsync(binaryFile).then(response=>new Uint8Array(response),()=>getBinarySync(binaryFile))}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["d"];updateMemoryViews();addOnInit(wasmExports["e"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}wasmBinaryFile??=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var callRuntimeCallbacks=callbacks=>{callbacks.forEach(f=>f(Module))};var noExitRuntime=Module["noExitRuntime"]||true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var __abort_js=()=>{abort("")};var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead=NaN)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};var wasmImports={a:__abort_js,c:__emscripten_memcpy_js,b:_emscripten_resize_heap};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["e"])();var _nisps_ml_create=Module["_nisps_ml_create"]=(a0,a1,a2,a3,a4)=>(_nisps_ml_create=Module["_nisps_ml_create"]=wasmExports["f"])(a0,a1,a2,a3,a4);var _nisps_ml_destroy=Module["_nisps_ml_destroy"]=a0=>(_nisps_ml_destroy=Module["_nisps_ml_destroy"]=wasmExports["g"])(a0);var _nisps_ml_set_input=Module["_nisps_ml_set_input"]=(a0,a1,a2)=>(_nisps_ml_set_input=Module["_nisps_ml_set_input"]=wasmExports["h"])(a0,a1,a2);var _nisps_ml_process=Module["_nisps_ml_process"]=a0=>(_nisps_ml_process=Module["_nisps_ml_process"]=wasmExports["i"])(a0);var _nisps_ml_outputs=Module["_nisps_ml_outputs"]=a0=>(_nisps_ml_outputs=Module["_nisps_ml_outputs"]=wasmExports["j"])(a0);var _nisps_ml_infer_batch=Module["_nisps_ml_infer_batch"]=(a0,a1,a2,a3)=>(_nisps_ml_infer_batch=Module["_nisps_ml_infer_batch"]=wasmExports["k"])(a0,a1,a2,a3);var _nisps_ml_add_example=Module["_nisps_ml_add_example"]=(a0,a1,a2)=>(_nisps_ml_add_example=Module["_nisps_ml_add_example"]=wasmExports["l"])(a0,a1,a2);var _nisps_ml_train=Module["_nisps_ml_train"]=(a0,a1,a2,a3,a4)=>(_nisps_ml_train=Module["_nisps_ml_train"]=wasmExports["m"])(a0,a1,a2,a3,a4);var _nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=a0=>(_nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=wasmExports["n"])(a0);var _nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=a0=>(_nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=wasmExports["o"])(a0);var _nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=(a0,a1)=>(_nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=wasmExports["p"])(a0,a1);var _nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=(a0,a1)=>(_nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=wasmExports["q"])(a0,a1);var _nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=(a0,a1)=>(_nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=wasmExports["r"])(a0,a1);var _nisps_ml_move_weights=Module["_nisps_ml_move_weights"]=(a0,a1,a2,a3)=>(_nisps_ml_move_weights=Module["_nisps_ml_move_weights"]=wasmExports["s"])(a0,a1,a2,a3);var _nisps_ml_feedback_set_mode=Module["_nisps_ml_feedback_set_mode"]=(a0,a1)=>(_nisps_ml_feedback_set_mode=Module["_nisps_ml_feedback_set_mode"]=wasmExports["t"])(a0,a1);var _nisps_ml_feedback_get_mode=Module["_nisps_ml_feedback_get_mode"]=a0=>(_nisps_ml_feedback_get_mode=Module["_nisps_ml_feedback_get_mode"]=wasmExports["u"])(a0);var _nisps_ml_feedback_exploring=Module["_nisps_ml_feedback_exploring"]=a0=>(_nisps_ml_feedback_exploring=Module["_nisps_ml_feedback_exploring"]=wasmExports["v"])(a0);var _nisps_ml_feedback_learning_paused=Module["_nisps_ml_feedback_learning_paused"]=a0=>(_nisps_ml_feedback_learning_paused=Module["_nisps_ml_feedback_learning_paused"]=wasmExports["w"])(a0);var _nisps_ml_feedback_set_focus=Module["_nisps_ml_feedback_set_focus"]=(a0,a1,a2)=>(_nisps_ml_feedback_set_focus=Module["_nisps_ml_feedback_set_focus"]=wasmExports["x"])(a0,a1,a2);var _nisps_ml_feedback_down=Module["_nisps_ml_feedback_down"]=(a0,a1,a2,a3,a4)=>(_nisps_ml_feedback_down=Module["_nisps_ml_feedback_down"]=wasmExports["y"])(a0,a1,a2,a3,a4);var _nisps_ml_feedback_up=Module["_nisps_ml_feedback_up"]=a0=>(_nisps_ml_feedback_up=Module["_nisps_ml_feedback_up"]=wasmExports["z"])(a0);var _nisps_ml_feedback_drag=Module["_nisps_ml_feedback_drag"]=a0=>(_nisps_ml_feedback_drag=Module["_nisps_ml_feedback_drag"]=wasmExports["A"])(a0);var _nisps_ml_feedback_static_output=Module["_nisps_ml_feedback_static_output"]=(a0,a1)=>(_nisps_ml_feedback_static_output=Module["_nisps_ml_feedback_static_output"]=wasmExports["B"])(a0,a1);var _nisps_ml_feedback_enter_explore=Module["_nisps_ml_feedback_enter_explore"]=(a0,a1)=>(_nisps_ml_feedback_enter_explore=Module["_nisps_ml_feedback_enter_explore"]=wasmExports["C"])(a0,a1);var _nisps_ml_feedback_exit_explore=Module["_nisps_ml_feedback_exit_explore"]=a0=>(_nisps_ml_feedback_exit_explore=Module["_nisps_ml_feedback_exit_explore"]=wasmExports["D"])(a0);var _nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=(a0,a1)=>(_nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=wasmExports["E"])(a0,a1);var _nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=(a0,a1)=>(_nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=wasmExports["F"])(a0,a1);var _nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=a0=>(_nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=wasmExports["G"])(a0);var _nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=a0=>(_nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=wasmExports["H"])(a0);var _nisps_ml_feedback_commit_place=Module["_nisps_ml_feedback_commit_place"]=a0=>(_nisps_ml_feedback_commit_place=Module["_nisps_ml_feedback_commit_place"]=wasmExports["I"])(a0);var _nisps_ml_feedback_cancel_place=Module["_nisps_ml_feedback_cancel_place"]=a0=>(_nisps_ml_feedback_cancel_place=Module["_nisps_ml_feedback_cancel_place"]=wasmExports["J"])(a0);var _nisps_ml_feedback_placing=Module["_nisps_ml_feedback_placing"]=a0=>(_nisps_ml_feedback_placing=Module["_nisps_ml_feedback_placing"]=wasmExports["K"])(a0);var _nisps_ml_feedback_state=Module["_nisps_ml_feedback_state"]=a0=>(_nisps_ml_feedback_state=Module["_nisps_ml_feedback_state"]=wasmExports["L"])(a0);var _nisps_ml_feedback_undo_depth=Module["_nisps_ml_feedback_undo_depth"]=a0=>(_nisps_ml_feedback_undo_depth=Module["_nisps_ml_feedback_undo_depth"]=wasmExports["M"])(a0);var _nisps_ml_feedback_placed_output=Module["_nisps_ml_feedback_placed_output"]=(a0,a1)=>(_nisps_ml_feedback_placed_output=Module["_nisps_ml_feedback_placed_output"]=wasmExports["N"])(a0,a1);var _nisps_ml_get_layer_stats=Module["_nisps_ml_get_layer_stats"]=(a0,a1)=>(_nisps_ml_get_layer_stats=Module["_nisps_ml_get_layer_stats"]=wasmExports["O"])(a0,a1);var _nisps_ml_example_count=Module["_nisps_ml_example_count"]=a0=>(_nisps_ml_example_count=Module["_nisps_ml_example_count"]=wasmExports["P"])(a0);var _nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=a0=>(_nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=wasmExports["Q"])(a0);var _nisps_ml_reset=Module["_nisps_ml_reset"]=a0=>(_nisps_ml_reset=Module["_nisps_ml_reset"]=wasmExports["R"])(a0);var _nisps_ml_describe=Module["_nisps_ml_describe"]=a0=>(_nisps_ml_describe=Module["_nisps_ml_describe"]=wasmExports["S"])(a0);var _nisps_engine_create=Module["_nisps_engine_create"]=(a0,a1)=>(_nisps_engine_create=Module["_nisps_engine_create"]=wasmExports["T"])(a0,a1);var _nisps_engine_destroy=Module["_nisps_engine_destroy"]=a0=>(_nisps_engine_destroy=Module["_nisps_engine_destroy"]=wasmExports["U"])(a0);var _nisps_engine_set_params=Module["_nisps_engine_set_params"]=(a0,a1,a2)=>(_nisps_engine_set_params=Module["_nisps_engine_set_params"]=wasmExports["V"])(a0,a1,a2);var _nisps_engine_process_block=Module["_nisps_engine_process_block"]=(a0,a1,a2,a3,a4,a5)=>(_nisps_engine_process_block=Module["_nisps_engine_process_block"]=wasmExports["W"])(a0,a1,a2,a3,a4,a5);var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["Y"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["Z"])(a0);var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["_"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["$"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["aa"])();Module["ccall"]=ccall;Module["cwrap"]=cwrap;var calledRun;var calledPrerun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}if(!calledPrerun){calledPrerun=1;preRun();if(runDependencies>0){return}}function doRun(){if(calledRun)return;calledRun=1;Module["calledRun"]=1;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; - - - return moduleRtn; -} -); -})(); -if (typeof exports === 'object' && typeof module === 'object') - module.exports = createNispsModule; -else if (typeof define === 'function' && define['amd']) - define([], () => createNispsModule); diff --git a/playground/public/nisps.wasm b/playground/public/nisps.wasm deleted file mode 100755 index cf9dbdb0eed86d7a75d7569fa7682afa86ab2907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102867 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

^O_B$H&?Ihw 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 import('./dev/PrimitivesShowcase')); - -const App: Component = () => { - const [route, setRoute] = createSignal(parseRoute(window.location.pathname)); - - const onPop = () => setRoute(parseRoute(window.location.pathname)); - window.addEventListener('popstate', onPop); - onCleanup(() => window.removeEventListener('popstate', onPop)); - - const navigate = (path: string) => { - if (window.location.pathname === path) return; - window.history.pushState({}, '', path); - setRoute(parseRoute(path)); - }; - - return ( -

-
- MEMLNaut - playground - -
-
- - - - - - - - - - -
-

404

-

No route at {window.location.pathname}.

-

- { e.preventDefault(); navigate('/'); }}>back to home -

-
-
-
-
- ); -}; - -const ModesPage: Component = () => { - const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id; - const ActiveComponent = createMemo(() => getModeById(activeId()).Component); - - return ( -
- - -
- ); -}; - -interface HomeProps { - navigate: (path: string) => void; -} - -const Home: Component = (props) => { - return ( -
-

MEMLNaut Playground

-

- Interactive ML control of audio. Stream 9: nine modes, one shell. -

- -

- ML inference runs on the main thread via WASM. Audio synthesis runs - in an AudioWorklet — start it from inside any mode using the "Start - audio" button. Audio cannot autoplay (browser policy). -

-
- ); -}; - -export default App; diff --git a/playground/src/audio/engine-host.ts b/playground/src/audio/engine-host.ts deleted file mode 100644 index 027d8e9..0000000 --- a/playground/src/audio/engine-host.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * EngineHost — main-thread side of the WASM AudioWorklet pipeline. - * - * Responsibilities: - * - Lazy-create AudioContext (browsers gate this on user gesture). - * - Register the AudioWorklet processor module. - * - Hand the worklet a copy of `nisps.wasm` bytes (we fetch on the main - * thread because AudioWorklet has no `fetch`/`importScripts`). - * - Send engine selection + parameter updates over the worklet `port`. - * - Tear everything down on `dispose()`. - * - * The actual DSP runs in `playground/src/audio/worklet/nisps-processor.ts`, - * which calls into a SECOND WASM instance owned by the worklet thread. - * - * IMPORTANT: this class never auto-starts audio. The caller must drive - * `start()` from a user gesture (button click, etc.) so browsers don't - * block AudioContext creation. - */ - -import type { EngineId } from '../ml/types'; - -// `?worker&url` makes Vite COMPILE the worklet TS→JS, bundle its imports, and -// hand back a hashed .js URL. Plain `new URL('./x.ts', import.meta.url)` does -// NOT work for audioWorklet.addModule (Vite only treats that as a worker entry -// for `new Worker(...)`) — it copies raw .ts, which the browser rejects. -import workletUrl from './worklet/nisps-processor.ts?worker&url'; - -const NISPS_WASM_URL = '/nisps.wasm'; -const PROCESSOR_NAME = 'nisps-processor'; - -/** Message protocol: main → worklet. */ -export type HostToWorkletMessage = - | { - kind: 'init'; - // ArrayBuffer transferred so the worklet can `WebAssembly.compile` it. - wasmBinary: ArrayBuffer; - sampleRate: number; - } - | { - kind: 'engine'; - engineId: EngineId; - } - | { - kind: 'params'; - // Float32Array transferred to avoid per-tick copy. - params: Float32Array; - } - | { - kind: 'mute'; - muted: boolean; - }; - -/** Message protocol: worklet → main. */ -export type WorkletToHostMessage = - | { kind: 'ready' } - | { kind: 'error'; message: string }; - -export interface EngineHostOptions { - /** Override sample rate (default: AudioContext.sampleRate). */ - sampleRate?: number; - /** Override worklet processor URL (testing). */ - processorUrl?: string; -} - -export class EngineHost { - private ctx: AudioContext | null = null; - private node: AudioWorkletNode | null = null; - private workletReady = false; - private currentEngine: EngineId = 'thru'; - private disposed = false; - private options: EngineHostOptions; - - // Cached bytes of nisps.wasm (we fetch once per host instance). - private wasmBytes: ArrayBuffer | null = null; - - constructor(options: EngineHostOptions = {}) { - this.options = options; - } - - get isStarted(): boolean { - return !!this.ctx && this.workletReady; - } - - get sampleRate(): number { - return this.ctx?.sampleRate ?? this.options.sampleRate ?? 48000; - } - - /** - * Start audio. Must be called from a user gesture for AudioContext to - * resume. After this resolves, `setEngine()` and `setParams()` can be - * called. - */ - async start(engineId: EngineId = 'thru'): Promise { - if (this.ctx) { - // Already started; just switch engine. - this.setEngine(engineId); - await this.ctx.resume(); - return; - } - this.ctx = new AudioContext({ - sampleRate: this.options.sampleRate, - latencyHint: 'interactive', - }); - - // Fetch the WASM bytes on the main thread (the worklet doesn't have - // fetch). We pass the buffer to the worklet as a transferable. - if (!this.wasmBytes) { - this.wasmBytes = await this.fetchWasm_(); - } - - // Register the processor module. `workletUrl` is the Vite-compiled, - // bundled .js (see import note at top); addModule needs real JS, not raw TS. - const procUrl = this.options.processorUrl ?? workletUrl; - await this.ctx.audioWorklet.addModule(procUrl); - - this.node = new AudioWorkletNode(this.ctx, PROCESSOR_NAME, { - numberOfInputs: 1, - numberOfOutputs: 1, - outputChannelCount: [2], - }); - this.node.connect(this.ctx.destination); - - // Wire up message handler before sending init. - this.workletReady = false; - const ready = new Promise((resolve, reject) => { - const onMsg = (ev: MessageEvent) => { - if (ev.data.kind === 'ready') { - this.workletReady = true; - this.node?.port.removeEventListener('message', onMsg); - resolve(); - } else if (ev.data.kind === 'error') { - this.node?.port.removeEventListener('message', onMsg); - reject(new Error(ev.data.message)); - } - }; - this.node!.port.addEventListener('message', onMsg); - this.node!.port.start(); - }); - - // Send the WASM binary + sample rate. ArrayBuffer is transferable — - // we keep a copy on the main thread for re-init. - const copy = this.wasmBytes.slice(0); - this.node.port.postMessage( - { kind: 'init', wasmBinary: copy, sampleRate: this.ctx.sampleRate } satisfies HostToWorkletMessage, - [copy], - ); - - await ready; - this.currentEngine = engineId; - if (engineId !== 'thru') { - this.setEngine(engineId); - } - } - - /** - * Switch which engine the worklet is processing. Cheap — just a message. - * The worklet handles engine destruction/creation internally. - */ - setEngine(engineId: EngineId): void { - if (!this.node || !this.workletReady) return; - this.currentEngine = engineId; - this.node.port.postMessage({ kind: 'engine', engineId } satisfies HostToWorkletMessage); - } - - /** - * Push a fresh parameter vector. Caller should NOT reuse the buffer - * after this call — we transfer it. If you need to keep yours, pass a - * copy: `host.setParams(new Float32Array(myBuf))`. - */ - setParams(params: Float32Array): void { - if (!this.node || !this.workletReady) return; - this.node.port.postMessage( - { kind: 'params', params } satisfies HostToWorkletMessage, - [params.buffer], - ); - } - - setMuted(muted: boolean): void { - if (!this.node || !this.workletReady) return; - this.node.port.postMessage({ kind: 'mute', muted } satisfies HostToWorkletMessage); - } - - async stop(): Promise { - if (!this.ctx) return; - if (this.node) { - try { - this.node.disconnect(); - } catch { /* ignore */ } - this.node = null; - } - try { - await this.ctx.close(); - } catch { /* ignore */ } - this.ctx = null; - this.workletReady = false; - } - - dispose(): void { - if (this.disposed) return; - this.disposed = true; - void this.stop(); - this.wasmBytes = null; - } - - private async fetchWasm_(): Promise { - const url = new URL(NISPS_WASM_URL, window.location.origin).toString(); - const resp = await fetch(url); - if (!resp.ok) throw new Error(`fetch nisps.wasm: ${resp.status} ${resp.statusText}`); - return await resp.arrayBuffer(); - } -} - -/** - * Smoke-test helper: returns true iff the WASM module exposes the C - * functions we need. Useful as a build-time check inside `engine-host.ts` - * tests; not used in production. - */ -export async function smokeCheckWasm(): Promise { - const url = new URL('/nisps.js', window.location.origin).toString(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mod: any = await import(/* @vite-ignore */ url); - const factory = mod.default ?? mod.createNispsModule; - if (!factory) return false; - const m = await factory({ - locateFile: (p: string) => p.endsWith('.wasm') - ? new URL(NISPS_WASM_URL, window.location.origin).toString() - : p, - }); - return typeof m._nisps_ml_create === 'function' - && typeof m._nisps_engine_create === 'function' - && typeof m._nisps_engine_process_block === 'function'; -} diff --git a/playground/src/audio/worklet/README.md b/playground/src/audio/worklet/README.md deleted file mode 100644 index 9f115c9..0000000 --- a/playground/src/audio/worklet/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Why two WASM instances? - -The playground loads `nisps.wasm` twice: - -1. **Main thread**, via `playground/src/ml/wasm-iml.ts`. Used for ML - inference + sync training + RL operations + UI feedback. -2. **AudioWorklet thread**, via `nisps-processor.ts`. Used for engine - processing (per-128-sample-block). - -Why not share? - -- AudioWorklet runs on its own thread. Sharing memory across threads - requires SharedArrayBuffer + locking on every heap access; far more - expensive than two heaps. -- AudioWorklet has neither `fetch` nor ESM `import`, so it can't load - the Emscripten glue (`nisps.js`). The main thread fetches the WASM - bytes once and posts them here as a transferable `ArrayBuffer`; the - worklet then `WebAssembly.instantiate`s directly. -- Engines and ML never interact in the audio path. The main thread - computes the parameter vector each frame and pushes it into the - worklet via `port.postMessage`. The worklet pushes nothing back per - block (analysis features, if needed, are batched and sent at low - rate). - -Per-frame data flow: - -``` -Joystick → input pipeline → mlStore.outputs (Float32Array, length=126) - ↓ EngineHost.setParams() - ↓ port.postMessage (transferable) - AudioWorklet ← WASM engine.process_block ← WASM engine.set_params -``` - -## Custom WASM loader - -We do **not** use the Emscripten JS glue inside the worklet — the glue -contains `URL`, `Worker`, and `fetch` references that don't exist in -AudioWorkletGlobalScope. Instead `nisps-processor.ts` calls -`WebAssembly.instantiate` directly with hand-rolled imports and -discovers exports by walking the export descriptors. This makes the -worklet bundle small (just the processor TS) and avoids touching the -Emscripten init path. - -The trade-off: only the engine API is callable here, not the ML API. -If you ever need ML inference inside the worklet (we don't), use the -main thread copy and post params over. - -## Block size - -AudioWorklet always calls `process()` with 128-sample blocks. Our -heap buffers in `nisps-processor.ts` are sized to match. Don't change -the block size without changing the buffer allocations. diff --git a/playground/src/audio/worklet/audioworklet-globals.d.ts b/playground/src/audio/worklet/audioworklet-globals.d.ts deleted file mode 100644 index b7feb1c..0000000 --- a/playground/src/audio/worklet/audioworklet-globals.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Type declarations for AudioWorkletGlobalScope. The default `lib.dom` - * and `lib.dom.iterable` files don't include these because they only - * exist inside an AudioWorklet thread. - * - * Keep this file minimal — only what `nisps-processor.ts` actually uses. - */ - -declare const sampleRate: number; -declare const currentFrame: number; -declare const currentTime: number; - -declare class AudioWorkletProcessor { - constructor(options?: { numberOfInputs?: number; numberOfOutputs?: number; processorOptions?: unknown }); - readonly port: MessagePort; - process( - inputs: Float32Array[][], - outputs: Float32Array[][], - parameters: Record, - ): boolean; -} - -declare function registerProcessor( - name: string, - processorCtor: new (options?: unknown) => AudioWorkletProcessor, -): void; diff --git a/playground/src/audio/worklet/nisps-processor.ts b/playground/src/audio/worklet/nisps-processor.ts deleted file mode 100644 index 7ea402c..0000000 --- a/playground/src/audio/worklet/nisps-processor.ts +++ /dev/null @@ -1,309 +0,0 @@ -/** - * AudioWorkletProcessor that runs `nisps.wasm` engines. - * - * Why a separate WASM instance from the main thread? AudioWorklet runs in - * its own thread + global scope; reusing a single instance would require - * SharedArrayBuffer + locking on the heap. Architecture.md §6.4 specifies - * separate instances connected by `port` messages instead. - * - * Wasm load path: AudioWorklet has NO `fetch` and NO ESM `import`. The - * main thread fetches `nisps.wasm` once and posts the bytes here as an - * ArrayBuffer; we then `WebAssembly.compile` and `instantiate` directly, - * skipping the Emscripten glue entirely. This is fine because the - * exported functions don't need any of the JS-side runtime. - * - * Block size: AudioWorklet ALWAYS calls process() with 128-sample blocks. - * We allocate 128-sample input and output buffers in the WASM linear - * memory and shuttle samples in/out per call. - */ - -/// - -import type { EngineId } from '../../ml/types'; -import type { HostToWorkletMessage, WorkletToHostMessage } from '../engine-host'; - -const PROC_BLOCK = 128; -const MAX_PARAMS = 256; // upper bound across all engines - -interface WasmInstance { - exports: { - memory: WebAssembly.Memory; - malloc: (n: number) => number; - free: (p: number) => void; - _nisps_engine_create: (id_ptr: number, sample_rate: number) => number; - _nisps_engine_destroy: (engine: number) => void; - _nisps_engine_set_params: (engine: number, params_ptr: number, n: number) => void; - _nisps_engine_process_block: ( - engine: number, - in_l: number, in_r: number, - out_l: number, out_r: number, - n_samples: number, - ) => void; - }; -} - -class NispsProcessor extends AudioWorkletProcessor { - private instance: WasmInstance | null = null; - private engineHandle = 0; - private engineId: EngineId = 'thru'; - private muted = true; - - // Pointers + buffer views (allocated once instance is up). - private inLPtr = 0; - private inRPtr = 0; - private outLPtr = 0; - private outRPtr = 0; - private idPtr = 0; - private paramsPtr = 0; - private inLView: Float32Array | null = null; - private inRView: Float32Array | null = null; - private outLView: Float32Array | null = null; - private outRView: Float32Array | null = null; - private paramsView: Float32Array | null = null; - private idView: Uint8Array | null = null; - private mem: WebAssembly.Memory | null = null; - - // Pending params posted before the engine was ready. - private pendingParams: Float32Array | null = null; - - constructor() { - super(); - this.port.onmessage = (ev) => this.onMessage_(ev.data as HostToWorkletMessage); - } - - private async onMessage_(msg: HostToWorkletMessage): Promise { - if (msg.kind === 'init') { - try { - await this.init_(msg.wasmBinary, msg.sampleRate); - this.post_({ kind: 'ready' }); - } catch (err) { - this.post_({ - kind: 'error', - message: err instanceof Error ? err.message : String(err), - }); - } - } else if (msg.kind === 'engine') { - this.switchEngine_(msg.engineId); - } else if (msg.kind === 'params') { - this.applyParams_(msg.params); - } else if (msg.kind === 'mute') { - this.muted = msg.muted; - } - } - - private post_(msg: WorkletToHostMessage): void { - this.port.postMessage(msg); - } - - /** - * Compile + instantiate the wasm module. We provide minimal imports — - * the Emscripten module needs `__abort_js` and `_emscripten_resize_heap` - * (we keep memory non-resizing so the latter is a stub). - */ - private async init_(bytes: ArrayBuffer, sampleRate: number): Promise { - const memory = new WebAssembly.Memory({ initial: 128, maximum: 4096, shared: false }); - const imports: WebAssembly.Imports = { - // Emscripten import "a" group; field names match the generated JS. - a: { - a: () => { throw new Error('wasm aborted'); }, - b: () => false, // _emscripten_resize_heap returning 0 disables growth - }, - }; - - const compiled = await WebAssembly.compile(bytes); - // Discover the actual import shape from the module — names like "a", - // "b" depend on emcc's mangling; we accept whatever it produces. - const importDesc = WebAssembly.Module.imports(compiled); - const reshaped: WebAssembly.Imports = {}; - for (const desc of importDesc) { - if (!reshaped[desc.module]) reshaped[desc.module] = {} as WebAssembly.ModuleImports; - const mod = reshaped[desc.module] as WebAssembly.ModuleImports; - if (desc.kind === 'function') { - if (desc.name === 'c') { - // unused - } - mod[desc.name] = (() => { - // Generic stub: log + return 0. - return (..._args: unknown[]) => 0; - })(); - } else if (desc.kind === 'memory') { - mod[desc.name] = memory; - } else if (desc.kind === 'table') { - mod[desc.name] = new WebAssembly.Table({ element: 'anyfunc', initial: 0 }); - } else if (desc.kind === 'global') { - mod[desc.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); - } - } - // For known-needed Emscripten imports, supply real implementations. - for (const desc of importDesc) { - const mod = reshaped[desc.module] as WebAssembly.ModuleImports; - // __abort_js - if (desc.name === 'a' && desc.kind === 'function') { - mod[desc.name] = () => { throw new Error('wasm aborted'); }; - } - // _emscripten_resize_heap - if (desc.name === 'b' && desc.kind === 'function') { - mod[desc.name] = (_size: number) => 0; // refuse growth in worklet - } - } - - void imports; // silence unused - const wasmInst = await WebAssembly.instantiate(compiled, reshaped); - - // Many Emscripten exports use single-letter mangled names. Discover - // by reading the export descriptors. - const exDesc = WebAssembly.Module.exports(compiled); - const exMap = new Map(); // logical name → mangled - for (const e of exDesc) { - // The exports list includes both the original (with leading - // underscore for C funcs) and the mangled single-letter alias used - // in the import section. We only see the export side here, but - // Emscripten in modern versions also re-exports the C names with - // their leading-underscore form. Walk both. - exMap.set(e.name, e.name); - } - const exports = wasmInst.exports as Record; - - function pickFn(...names: string[]): (...args: number[]) => number { - for (const n of names) { - const v = exports[n]; - if (typeof v === 'function') return v as unknown as (...a: number[]) => number; - } - throw new Error(`worklet: missing wasm export, tried: ${names.join(', ')}`); - } - function pickFnVoid(...names: string[]): (...args: number[]) => void { - return pickFn(...names) as unknown as (...args: number[]) => void; - } - - // The exports we need. - const malloc = pickFn('_malloc', 'malloc'); - const free = pickFnVoid('_free', 'free'); - const ec = pickFn('_nisps_engine_create'); - const ed = pickFnVoid('_nisps_engine_destroy'); - const esp = pickFnVoid('_nisps_engine_set_params'); - const epb = pickFnVoid('_nisps_engine_process_block'); - - // The wasm-exported memory might be named `memory` or another mangled - // alias. Find it. - let wasmMemory: WebAssembly.Memory | null = null; - for (const e of exDesc) { - if (e.kind === 'memory') { - const v = exports[e.name]; - if (v instanceof WebAssembly.Memory) { wasmMemory = v; break; } - } - } - // If the module imports memory (which our build does — we passed it), - // there will be no exported memory; use the imported one. - this.mem = wasmMemory ?? memory; - - this.instance = { - exports: { - memory: this.mem, - malloc, - free, - _nisps_engine_create: (id, sr) => ec(id, sr), - _nisps_engine_destroy: (h) => ed(h), - _nisps_engine_set_params: (h, p, n) => esp(h, p, n), - _nisps_engine_process_block: (h, il, ir, ol, or_, n) => epb(h, il, ir, ol, or_, n), - }, - }; - - // Allocate buffers. - this.inLPtr = malloc(PROC_BLOCK * 4); - this.inRPtr = malloc(PROC_BLOCK * 4); - this.outLPtr = malloc(PROC_BLOCK * 4); - this.outRPtr = malloc(PROC_BLOCK * 4); - this.paramsPtr = malloc(MAX_PARAMS * 4); - // Engine ids are short ASCII; 32 bytes covers everything we have. - this.idPtr = malloc(32); - - const buf = this.mem.buffer; - this.inLView = new Float32Array(buf, this.inLPtr, PROC_BLOCK); - this.inRView = new Float32Array(buf, this.inRPtr, PROC_BLOCK); - this.outLView = new Float32Array(buf, this.outLPtr, PROC_BLOCK); - this.outRView = new Float32Array(buf, this.outRPtr, PROC_BLOCK); - this.paramsView = new Float32Array(buf, this.paramsPtr, MAX_PARAMS); - this.idView = new Uint8Array(buf, this.idPtr, 32); - - // Default engine: thru. - this.spawnEngine_('thru', sampleRate); - - // Apply pending params if any arrived before init completed. - if (this.pendingParams) { - this.applyParams_(this.pendingParams); - this.pendingParams = null; - } - - this.muted = false; - } - - private spawnEngine_(id: EngineId, sampleRate: number): void { - if (!this.instance || !this.idView) return; - if (this.engineHandle) { - this.instance.exports._nisps_engine_destroy(this.engineHandle); - this.engineHandle = 0; - } - // Write engine_id as ASCII into idView, NUL-terminated. - const enc = new TextEncoder(); - const bytes = enc.encode(id); - this.idView.fill(0); - this.idView.set(bytes.subarray(0, Math.min(bytes.length, 31))); - this.engineHandle = this.instance.exports._nisps_engine_create(this.idPtr, sampleRate); - this.engineId = id; - } - - private switchEngine_(id: EngineId): void { - // sampleRate global from AudioWorkletGlobalScope. - this.spawnEngine_(id, sampleRate); - } - - private applyParams_(params: Float32Array): void { - if (!this.instance || !this.paramsView) { - this.pendingParams = params; - return; - } - const n = Math.min(params.length, MAX_PARAMS); - for (let i = 0; i < n; ++i) this.paramsView[i] = params[i]; - if (this.engineHandle) { - this.instance.exports._nisps_engine_set_params(this.engineHandle, this.paramsPtr, n); - } - } - - override process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean { - const out = outputs[0]; - if (!out || out.length === 0) return true; - - const outL = out[0]; - const outR = out.length > 1 ? out[1] : out[0]; - - if (this.muted || !this.instance || !this.engineHandle || - !this.inLView || !this.outLView || !this.outRView || !this.inRView) { - // Silence. - outL.fill(0); - if (out.length > 1) outR.fill(0); - return true; - } - - // Copy inputs into wasm buffers (zero-fill if missing). - const inp = inputs[0]; - if (inp && inp[0]) this.inLView.set(inp[0].subarray(0, PROC_BLOCK)); - else this.inLView.fill(0); - if (inp && inp[1]) this.inRView.set(inp[1].subarray(0, PROC_BLOCK)); - else if (inp && inp[0]) this.inRView.set(inp[0].subarray(0, PROC_BLOCK)); - else this.inRView.fill(0); - - this.instance.exports._nisps_engine_process_block( - this.engineHandle, - this.inLPtr, this.inRPtr, - this.outLPtr, this.outRPtr, - PROC_BLOCK, - ); - - outL.set(this.outLView.subarray(0, outL.length)); - if (out.length > 1) outR.set(this.outRView.subarray(0, outR.length)); - - return true; - } -} - -registerProcessor('nisps-processor', NispsProcessor); diff --git a/playground/src/debug/probe.ts b/playground/src/debug/probe.ts deleted file mode 100644 index ca1b991..0000000 --- a/playground/src/debug/probe.ts +++ /dev/null @@ -1,516 +0,0 @@ -/** - * Debug probe: window.__nisps - * - * Synchronous-or-immediate Promise API for Playwright tests and dev console - * use. Bypasses SolidJS reactivity by reading/writing the underlying stores - * and WASM directly — values propagate to the UI on the next reactive tick, - * but the probe always returns the freshest state. - * - * Stream 10 expanded the surface to cover stream 8/9 features: - * - Snapshot stack (push, pop, list) - * - A/B compare (capture, toggle, accept, revert) - * - Region pinning (add, remove, clear) - * - Auto-explore (toggle, interval, intensity) - * - Heatmap (refresh, color mode, getCells) - * - Weight health (histogram, status, layer stats) - * - Session presets (save, load, list, share URL) - * - Override application (read, write, clear) - * - * Test contract: every method either returns a value, returns null/empty, - * or returns an immediately-resolved Promise. None of them throw — bad - * input is silently ignored. - */ - -import { batch, untrack } from 'solid-js'; -import { mlStore } from '../stores/ml-store'; -import { modeStore } from '../stores/mode-store'; -import { sessionStore } from '../stores/session-store'; -import { explorationStore } from '../stores/exploration-store'; -import { controlStore } from '../stores/control-store'; -import { inputStore } from '../stores/input-store'; -import { outputStore } from '../stores/output-store'; -import { coreBus } from '../stores/bus'; -import { - autoSnapshot, - undoLastSnapshot, - captureA as snapCaptureA, - toggleAB as snapToggleAB, - acceptB as snapAcceptB, - revertToA as snapRevertToA, -} from '../features/snapshots'; -import { HeatmapSampler } from '../features/heatmap-sampler'; -import { - weightHistogram, - weightStatus as healthStatus, - layerNorms, - gradientStatuses, - layerWeightCounts, -} from '../features/weight-health'; -import { - captureSessionPreset, - restoreSessionPreset, - saveNamedPreset, - loadNamedPreset, - buildShareUrl, - applyUrlParams, - type SessionPresetPayload, -} from '../features/session-preset'; -import type { HeatmapColorMode } from '../features/heatmap-sampler'; - -export interface DebugProbe { - /** Current 126-element output vector (Float32Array). */ - getOutputs(): Float32Array; - /** Current per-param post-override values (length = active mode params). */ - getParamOutputs(): Float32Array; - /** Last training loss, or null if no training has occurred. */ - getLoss(): number | null; - /** Loss history of last training run. */ - getLossHistory(): ReadonlyArray; - /** Flat weight array. */ - getWeights(): Float32Array; - /** Number of training examples currently in the dataset. */ - getExampleCount(): number; - /** Set joystick X/Y in [0,1] and run inference. */ - setInputs(x: number, y: number): void; - /** Trigger thumbs-up RL feedback. */ - thumbsUp(): void; - /** Trigger thumbs-down RL feedback. */ - thumbsDown(): void; - /** Synchronous training; returns final loss. */ - train(): number; - /** Async training; returns Promise. */ - trainAsync(): Promise; - /** Randomize weights with default spread. */ - randomise(): void; - /** Clear all training examples. */ - clearExamples(): void; - /** Force a save to localStorage now (no debounce). */ - saveState(): void; - /** Non-destructive loss query against current dataset. */ - evalLoss(): number | null; - /** Batch inference: input is Nx2 array of [x,y] pairs. */ - inferBatch(points: ReadonlyArray): Float32Array; - /** Per-layer weight statistics: layerCount * 4 floats (mean|w|, max|w|, dead%, sat%). */ - getLayerStats(): Float32Array; - - // Snapshot / undo / A/B - pushSnapshot(tag: string): void; - popSnapshot(): boolean; - listSnapshots(): ReadonlyArray<{ id: string; tag: string; timestamp: number; noiseLevel: number }>; - clearSnapshots(): void; - captureA(): void; - toggleAB(): 'A' | 'B'; - acceptB(): void; - revertToA(): void; - - // Pins - addRegionPin(pin: { x: number; y: number; width: number; height: number }): string; - removeRegionPin(id: string): void; - toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean; - isParamPinned(modeId: string, paramName: string): boolean; - - // Auto-explore - setAutoExplore(enabled: boolean): void; - setAutoExploreInterval(ms: number): void; - setAutoExploreIntensity(v: number): void; - - // Pressure - setPressure(force: number, holdMs?: number): void; - - // Heatmap - refreshHeatmap(force?: boolean): boolean; - getHeatmapCells(): Float32Array; - setHeatmapColorMode(mode: HeatmapColorMode): void; - - // Weight health - getWeightHistogram(): number[]; - getWeightStatus(): 'dead' | 'saturating' | 'healthy'; - getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array): { norms: number[]; status: ReturnType }; - - // Overrides - setOverride(modeId: string, paramName: string, override: Partial<{ min: number; max: number; curve: string; muted: boolean; pinned: boolean; frozen: boolean; fixedValue: number }>): void; - clearOverride(modeId: string, paramName: string): void; - clearAllOverrides(modeId: string): void; - getOverride(modeId: string, paramName: string): unknown; - - // Compound axes - setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void; - applyControlPreset(id: string): boolean; - - // Pipelines - setZoom(zoom: number): void; - setSpread(spread: number): void; - setOutputFreeze(frozen: boolean): void; - - // Session presets - captureSessionPreset(withWeights?: boolean): SessionPresetPayload; - restoreSessionPreset(payload: SessionPresetPayload): boolean; - saveSessionPreset(name: string, withWeights?: boolean): void; - loadSessionPreset(id: string): boolean; - buildShareUrl(): string; - applyUrlParams(search?: string): boolean; - - // Bus - emit(topic: string, data: unknown): void; - on(topic: string, handler: (data: unknown) => void): () => void; - - /** True once the WASM is fully initialised. */ - readonly __ready: boolean; - /** Force initialisation. Returns a promise that resolves when the WASM is ready. */ - __init(): Promise; -} - -declare global { - interface Window { - __nisps?: DebugProbe; - } -} - -const EMPTY_F32 = new Float32Array(0); - -let lazyInitPromise: Promise | null = null; -function lazyInit(): Promise { - if (mlStore.iml) return Promise.resolve(); - if (!lazyInitPromise) { - lazyInitPromise = mlStore.initialize().then(() => undefined); - } - return lazyInitPromise; -} - -// Probe-local heatmap sampler (independent of any active mode runtime). -const probeHeatmap = new HeatmapSampler({ resolution: 16 }); - -const probe: DebugProbe = { - get __ready(): boolean { - return !!mlStore.iml && mlStore.state.ready; - }, - - __init(): Promise { - return lazyInit(); - }, - - getOutputs(): Float32Array { - return mlStore.outputs(); - }, - - getParamOutputs(): Float32Array { - // The probe doesn't have a runtime reference; recompute from raw outputs. - const raw = mlStore.outputs(); - if (raw.length === 0) return EMPTY_F32; - // Just return the raw outputs — actual override application requires - // schema knowledge that the probe doesn't track. Tests can read - // overrides via `getOverride` instead. - return raw; - }, - - getLoss(): number | null { - return mlStore.state.lastLoss; - }, - - getLossHistory(): ReadonlyArray { - return mlStore.state.lossHistory; - }, - - getWeights(): Float32Array { - return mlStore.getWeights(); - }, - - getExampleCount(): number { - return mlStore.state.exampleCount; - }, - - setInputs(x: number, y: number): void { - if (!mlStore.iml) { - void lazyInit(); - return; - } - untrack(() => mlStore.iml!.inferXY(x, y)); - }, - - thumbsUp(): void { - if (!mlStore.iml) return; - untrack(() => { - autoSnapshot('before thumbs-up (probe)'); - mlStore.iml!.train(explorationStore.state.learningRate); - explorationStore.decayNoise(0.5); - }); - }, - - thumbsDown(): void { - if (!mlStore.iml) return; - untrack(() => { - autoSnapshot('before thumbs-down (probe)'); - const cap = explorationStore.state.noiseCap; - const spread = explorationStore.state.spread; - mlStore.iml!.moveWeights(cap, spread); - explorationStore.growNoise(0.5); - }); - }, - - train(): number { - if (!mlStore.iml) { - void lazyInit(); - return 0; - } - return untrack(() => { - autoSnapshot('before train (probe)'); - return mlStore.iml!.train(explorationStore.state.learningRate); - }); - }, - - async trainAsync(): Promise { - await lazyInit(); - if (!mlStore.iml) return 0; - autoSnapshot('before trainAsync (probe)'); - return mlStore.iml.trainAsync(explorationStore.state.learningRate); - }, - - randomise(): void { - if (!mlStore.iml) return; - untrack(() => { - autoSnapshot('before randomize (probe)'); - mlStore.iml!.randomiseWeights(explorationStore.state.spread); - }); - }, - - clearExamples(): void { - mlStore.clearExamples(); - }, - - saveState(): void { - mlStore.saveNow(); - }, - - evalLoss(): number | null { - if (!mlStore.iml) return null; - return mlStore.iml.evalLoss(); - }, - - inferBatch(points: ReadonlyArray): Float32Array { - if (!mlStore.iml) return new Float32Array(points.length * mlStore.state.outputSize); - return mlStore.iml.inferBatch(points); - }, - - getLayerStats(): Float32Array { - if (!mlStore.iml) return EMPTY_F32; - return mlStore.iml.getLayerStatsFlat(); - }, - - // ----- Snapshot / undo / A/B --------------------------------------------- - - pushSnapshot(tag: string): void { - autoSnapshot(tag); - }, - - popSnapshot(): boolean { - return undoLastSnapshot(); - }, - - listSnapshots() { - return sessionStore.listSnapshots().map((s) => ({ - id: s.id, - tag: s.tag, - timestamp: s.timestamp, - noiseLevel: s.noiseLevel, - })); - }, - - clearSnapshots(): void { - sessionStore.clearSnapshots(); - }, - - captureA(): void { - snapCaptureA(); - }, - - toggleAB(): 'A' | 'B' { - return snapToggleAB(); - }, - - acceptB(): void { - snapAcceptB(); - }, - - revertToA(): void { - snapRevertToA(); - }, - - // ----- Pins --------------------------------------------------------------- - - addRegionPin(pin) { - const created = sessionStore.addRegionPin(pin); - return created.id; - }, - - removeRegionPin(id: string): void { - sessionStore.removeRegionPin(id); - }, - - toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean { - return sessionStore.toggleParamPin(`${modeId}:${paramName}`, outputIndex); - }, - - isParamPinned(modeId: string, paramName: string): boolean { - return sessionStore.isParamPinned(`${modeId}:${paramName}`); - }, - - // ----- Auto-explore ------------------------------------------------------- - - setAutoExplore(enabled: boolean): void { - explorationStore.setAutoExplore(enabled); - }, - - setAutoExploreInterval(ms: number): void { - explorationStore.setAutoExploreInterval(ms); - }, - - setAutoExploreIntensity(v: number): void { - explorationStore.setAutoExploreIntensity(v); - }, - - setPressure(force: number, holdMs: number = 0): void { - explorationStore.setPressure(force, holdMs); - }, - - // ----- Heatmap ------------------------------------------------------------ - - refreshHeatmap(force = false): boolean { - if (!mlStore.iml) return false; - const cfg = inputStore.config; - const z = cfg.zoom; - const cx = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorX; - const cy = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorY; - return probeHeatmap.update({ cx, cy, zoom: z }, force); - }, - - getHeatmapCells(): Float32Array { - return probeHeatmap.getCells(); - }, - - setHeatmapColorMode(mode: HeatmapColorMode): void { - probeHeatmap.setColorMode(mode); - }, - - // ----- Weight health ------------------------------------------------------ - - getWeightHistogram(): number[] { - return weightHistogram(mlStore.weights()); - }, - - getWeightStatus() { - return healthStatus(mlStore.weights()); - }, - - getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array) { - if (!mlStore.iml) return { norms: [], status: [] }; - const arch = mlStore.iml.architecture; - const sizes = layerWeightCounts({ - inputSize: arch.inputSize, - hidden: arch.hidden, - outputSize: arch.outputSize, - }); - const norms = layerNorms(beforeWeights, afterWeights, sizes); - return { norms, status: gradientStatuses(norms) }; - }, - - // ----- Overrides ---------------------------------------------------------- - - setOverride(modeId: string, paramName: string, patch): void { - const existing = modeStore.getOverride(modeId, paramName); - const next = { - min: existing?.min ?? 0, - max: existing?.max ?? 1, - curve: existing?.curve ?? 'linear', - curveParam: existing?.curveParam, - muted: existing?.muted ?? false, - pinned: existing?.pinned ?? false, - frozen: existing?.frozen ?? false, - fixedValue: existing?.fixedValue ?? 0.5, - ...patch, - } as Parameters[2]; - modeStore.setOverride(modeId, paramName, next); - }, - - clearOverride(modeId: string, paramName: string): void { - modeStore.clearOverride(modeId, paramName); - }, - - clearAllOverrides(modeId: string): void { - modeStore.clearAllOverrides(modeId); - }, - - getOverride(modeId: string, paramName: string) { - return modeStore.getOverride(modeId, paramName); - }, - - // ----- Compound axes ------------------------------------------------------ - - setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void { - controlStore.setAxis(axis, value); - }, - - applyControlPreset(id: string): boolean { - return controlStore.applyPreset(id); - }, - - // ----- Pipelines ---------------------------------------------------------- - - setZoom(zoom: number): void { - inputStore.setZoom(zoom); - }, - - setSpread(spread: number): void { - explorationStore.setSpread(spread); - }, - - setOutputFreeze(frozen: boolean): void { - outputStore.setFreezeOutput(frozen); - }, - - // ----- Session presets ---------------------------------------------------- - - captureSessionPreset(withWeights = false): SessionPresetPayload { - return captureSessionPreset(withWeights); - }, - - restoreSessionPreset(payload: SessionPresetPayload): boolean { - return batch(() => restoreSessionPreset(payload)); - }, - - saveSessionPreset(name: string, withWeights = false): void { - saveNamedPreset(name, withWeights); - }, - - loadSessionPreset(id: string): boolean { - return batch(() => loadNamedPreset(id)); - }, - - buildShareUrl(): string { - return buildShareUrl(); - }, - - applyUrlParams(search?: string): boolean { - return batch(() => applyUrlParams(search)); - }, - - // ----- Bus ---------------------------------------------------------------- - - emit(topic: string, data: unknown): void { - // Lossy cast — the probe is intentionally weakly typed. - coreBus.emit(topic as never, data as never); - }, - - on(topic: string, handler): () => void { - return coreBus.onPrefix(topic, handler); - }, -}; - -/** - * Install the probe on window. Idempotent — the probe object is a - * singleton, so capturing `window.__nisps` once is safe across hot - * reloads and re-installs. - */ -export function installDebugProbe(): void { - if (typeof window === 'undefined') return; - window.__nisps = probe; -} diff --git a/playground/src/dev/PrimitivesShowcase.module.css b/playground/src/dev/PrimitivesShowcase.module.css deleted file mode 100644 index 25640d5..0000000 --- a/playground/src/dev/PrimitivesShowcase.module.css +++ /dev/null @@ -1,70 +0,0 @@ -.root { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: var(--sp-5); -} - -.header { - display: flex; - flex-direction: column; - gap: var(--sp-2); -} - -.header h1 { - margin: 0; - font-size: var(--fs-xl); - color: var(--accent); -} - -.lede { - color: var(--fg-mute); - margin: 0; - max-width: 720px; -} - -.grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); - gap: var(--sp-4); -} - -.card { - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--r-3); - padding: var(--sp-4); - display: flex; - flex-direction: column; - gap: var(--sp-3); - min-height: 280px; -} - -.cardHead { - display: flex; - flex-direction: column; - gap: var(--sp-1); - border-bottom: 1px solid var(--line); - padding-bottom: var(--sp-2); -} - -.cardTitle { - margin: 0; - font-size: var(--fs-md); - color: var(--fg); -} - -.cardDesc { - margin: 0; - font-size: var(--fs-xs); - color: var(--fg-mute); -} - -.cardBody { - display: flex; - flex-direction: column; - align-items: stretch; - flex: 1; - justify-content: flex-start; -} diff --git a/playground/src/dev/PrimitivesShowcase.tsx b/playground/src/dev/PrimitivesShowcase.tsx deleted file mode 100644 index 07cac36..0000000 --- a/playground/src/dev/PrimitivesShowcase.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Primitives showcase — Storybook-equivalent at /dev/primitives. - * - * Each section wraps one primitive's demo. Demos use realistic interactive - * state so a quick visual scan answers "does this thing actually work?". - */ - -import { Component, For, JSX } from 'solid-js'; -import styles from './PrimitivesShowcase.module.css'; - -import { SliderDemo } from '../primitives/Slider.demo'; -import { SliderBankDemo } from '../primitives/SliderBank.demo'; -import { VirtualJoystickDemo } from '../primitives/VirtualJoystick.demo'; -import { XYPadDemo } from '../primitives/XYPad.demo'; -import { HeatmapDemo } from '../primitives/Heatmap.demo'; -import { OutputDisplayDemo } from '../primitives/OutputDisplay.demo'; -import { TrainingControlsDemo } from '../primitives/TrainingControls.demo'; -import { DrawerDemo } from '../primitives/Drawer.demo'; -import { ControlAxisDemo } from '../primitives/ControlAxis.demo'; -import { ProgressRingDemo } from '../primitives/ProgressRing.demo'; -import { PillToggleDemo } from '../primitives/PillToggle.demo'; -import { ParamEditorDemo } from '../primitives/ParamEditor.demo'; -import { JoyMapDemo } from '../primitives/JoyMap.demo'; -import { WeightHealthDemo } from '../primitives/WeightHealth.demo'; -import { GradientFlowDemo } from '../primitives/GradientFlow.demo'; -import { LossPlotDemo } from '../primitives/LossPlot.demo'; - -interface Section { - name: string; - description: string; - Demo: Component; -} - -const SECTIONS: ReadonlyArray
= [ - { name: 'Slider', description: 'Single-value slider with optional curve mapping.', Demo: SliderDemo }, - { name: 'SliderBank', description: 'Vertical stack with collapsible sections.', Demo: SliderBankDemo }, - { name: 'VirtualJoystick', description: 'Touch + pointer 2D input, returns [0,1]^2.', Demo: VirtualJoystickDemo }, - { name: 'XYPad', description: 'Rectangular 2D input pad.', Demo: XYPadDemo }, - { name: 'Heatmap', description: 'NxN colored grid, three color modes.', Demo: HeatmapDemo }, - { name: 'OutputDisplay', description: 'Bar chart of N output values.', Demo: OutputDisplayDemo }, - { name: 'TrainingControls', description: 'RL feedback + status panel.', Demo: TrainingControlsDemo }, - { name: 'Drawer', description: 'Slide-in panel from left/right.', Demo: DrawerDemo }, - { name: 'ControlAxis', description: 'Compound axis slider (Boldness/Memory/Precision shape).', Demo: ControlAxisDemo }, - { name: 'ProgressRing', description: 'Circular progress indicator.', Demo: ProgressRingDemo }, - { name: 'PillToggle', description: 'Segmented control.', Demo: PillToggleDemo }, - { name: 'ParamEditor', description: 'Range/curve/mute/pin for one parameter.', Demo: ParamEditorDemo }, - { name: 'JoyMap', description: 'Zoom minimap with trail, noise rings, region pins.', Demo: JoyMapDemo }, - { name: 'WeightHealth', description: 'Histogram with status glow.', Demo: WeightHealthDemo }, - { name: 'GradientFlow', description: 'Per-layer gradient bars.', Demo: GradientFlowDemo }, - { name: 'LossPlot', description: 'Training-loss line chart over time.', Demo: LossPlotDemo }, -]; - -const PrimitivesShowcase: Component = () => { - return ( -
-
-

Primitive Library

-

- Each card is a live demo. Use this page as a sanity-check during the rewrite. - Modes (stream 9) and feature parity (stream 10) compose these primitives. -

-
-
- {(section) => ( -
-
-

{section.name}

-

{section.description}

-
-
- {section.Demo({}) as unknown as JSX.Element} -
-
- )}
-
-
- ); -}; - -export default PrimitivesShowcase; diff --git a/playground/src/dev/probe-smoke.ts b/playground/src/dev/probe-smoke.ts deleted file mode 100644 index 3cafbc9..0000000 --- a/playground/src/dev/probe-smoke.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Tiny smoke check that exercises the debug probe API. Run from the dev - * console after the page loads: - * - * import('./dev/probe-smoke.ts').then((m) => m.smokeCheck()); - * - * Returns a list of pass/fail results. Useful for manual verification when - * Playwright isn't available. - * - * This file is NOT bundled by default — Vite only includes referenced - * modules and `dev/probe-smoke.ts` is opt-in. - */ - -export interface SmokeResult { - name: string; - ok: boolean; - detail?: string; -} - -export async function smokeCheck(): Promise { - const results: SmokeResult[] = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const probe = (window as unknown as { __nisps?: any }).__nisps; - if (!probe) { - results.push({ name: 'probe installed', ok: false, detail: 'window.__nisps missing' }); - return results; - } - results.push({ name: 'probe installed', ok: true }); - - try { - await probe.__init(); - results.push({ name: '__init', ok: true }); - } catch (err) { - results.push({ name: '__init', ok: false, detail: String(err) }); - } - - try { - probe.setInputs(0.5, 0.5); - const outs = probe.getOutputs(); - results.push({ - name: 'setInputs(0.5, 0.5)', - ok: outs.length > 0, - detail: `outputs.length = ${outs.length}`, - }); - } catch (err) { - results.push({ name: 'setInputs', ok: false, detail: String(err) }); - } - - try { - probe.thumbsUp(); - results.push({ name: 'thumbsUp', ok: true }); - } catch (err) { - results.push({ name: 'thumbsUp', ok: false, detail: String(err) }); - } - - try { - probe.thumbsDown(); - results.push({ name: 'thumbsDown', ok: true }); - } catch (err) { - results.push({ name: 'thumbsDown', ok: false, detail: String(err) }); - } - - try { - const before = probe.getWeights(); - probe.pushSnapshot('smoke'); - probe.randomise(); - probe.popSnapshot(); - const after = probe.getWeights(); - const restored = before.length > 0 && after.length === before.length; - results.push({ name: 'snapshot push/pop', ok: restored }); - } catch (err) { - results.push({ name: 'snapshot push/pop', ok: false, detail: String(err) }); - } - - try { - probe.setAxis('boldness', 0.7); - probe.setAxis('memory', 0.3); - probe.setAxis('precision', 0.5); - results.push({ name: 'compound axes', ok: true }); - } catch (err) { - results.push({ name: 'compound axes', ok: false, detail: String(err) }); - } - - try { - const ok = probe.refreshHeatmap(true); - const cells = probe.getHeatmapCells(); - results.push({ - name: 'heatmap', - ok: cells.length > 0, - detail: `took=${ok} cells=${cells.length}`, - }); - } catch (err) { - results.push({ name: 'heatmap', ok: false, detail: String(err) }); - } - - try { - const hist = probe.getWeightHistogram(); - const status = probe.getWeightStatus(); - results.push({ - name: 'weight health', - ok: hist.length === 10, - detail: `status=${status} bins=${hist.length}`, - }); - } catch (err) { - results.push({ name: 'weight health', ok: false, detail: String(err) }); - } - - try { - const url = probe.buildShareUrl(); - const ok = typeof url === 'string' && url.startsWith('?session='); - results.push({ name: 'buildShareUrl', ok, detail: url.slice(0, 60) }); - } catch (err) { - results.push({ name: 'buildShareUrl', ok: false, detail: String(err) }); - } - - return results; -} - -if (typeof window !== 'undefined') { - (window as unknown as { smokeCheck?: typeof smokeCheck }).smokeCheck = smokeCheck; -} diff --git a/playground/src/features/control-routing.ts b/playground/src/features/control-routing.ts deleted file mode 100644 index 55e6a7a..0000000 --- a/playground/src/features/control-routing.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Compound-axis → store routing. - * - * Converts the resolved parameter map from `controlStore.resolveParams()` - * into concrete writes against: - * - `inputStore` (zoom, deadzone, inputCurve, smoothing, momentumZoom) - * - `outputStore` (slewRate) - * - `explorationStore` (noiseCap, noiseGrowth, noiseDecay, learningRate, weightDecay) - * - * Called as a Solid `createEffect` from the mode runtime; whenever any axis - * changes, this re-applies. Per-store setters internally clamp the values. - * - * Note: `inputCurve` and `slewRate` from the Precision axis intentionally - * use the centered_power exponent and slew-rate-per-frame contracts; the - * resolver mirrors the legacy table values. - */ - -import { controlStore } from '../stores/control-store'; -import { inputStore } from '../stores/input-store'; -import { outputStore } from '../stores/output-store'; -import { explorationStore } from '../stores/exploration-store'; -import type { MomentumZoomMode } from '../input/pipeline'; - -let lastApplied = ''; - -/** - * Apply the resolved compound-axis params. Returns true if any store was - * modified. - */ -export function applyControlRouting(): boolean { - const params = controlStore.resolveParams(); - // Coalesce: only re-apply if any value changed. - const sig = JSON.stringify({ - z: params['zoom'], - nc: params['noiseCap'], - ng: params['noiseGrowth'], - nd: params['noiseDecay'], - lr: params['learningRate'], - wd: params['weightDecay'], - ic: params['inputCurve'], - dz: params['deadzone'], - sm: params['smoothing'], - sr: params['slewRate'], - mz: params['momentumZoom'], - }); - if (sig === lastApplied) return false; - lastApplied = sig; - - const num = (k: string): number | undefined => - typeof params[k] === 'number' ? (params[k] as number) : undefined; - - // Input pipeline - const z = num('zoom'); - if (z !== undefined) inputStore.setZoom(z); - const dz = num('deadzone'); - if (dz !== undefined) inputStore.setDeadzone(dz); - const ic = num('inputCurve'); - if (ic !== undefined) inputStore.setInputCurve(ic); - const ism = num('smoothing'); - if (ism !== undefined) inputStore.setSmoothing(ism); - const mz = params['momentumZoom']; - if (typeof mz === 'string') { - const valid: MomentumZoomMode[] = ['off', 'gentle', 'strong']; - if (valid.includes(mz as MomentumZoomMode)) { - inputStore.setMomentumZoom(mz as MomentumZoomMode); - } - } - - // Output pipeline - const sr = num('slewRate'); - if (sr !== undefined) outputStore.setSlewRate(sr); - - // Exploration / RL - explorationStore.applyCompoundParams(params); - - return true; -} diff --git a/playground/src/features/heatmap-sampler.ts b/playground/src/features/heatmap-sampler.ts deleted file mode 100644 index a908a1d..0000000 --- a/playground/src/features/heatmap-sampler.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Heatmap sampler — samples MLP across a 2D input window, reduces outputs - * to a scalar per cell. - * - * Three color modes: - * - 'luminance' → mean output magnitude - * - 'variance' → output variance - * - 'divergence' → distance from a reference (center) output - * - * Sampler is throttled: `update` no-ops if called more than once per - * `MIN_INTERVAL_MS`. Caller drives updates via `coreBus.on('ml.delta_update')` - * and `coreBus.on('ml.trained')` events plus the input `zoom` accessor. - * - * The sampler returns a `Float32Array` of length `resolution * resolution` - * directly consumable by the `Heatmap` primitive. Stale grid values stay - * around between updates so the canvas doesn't blank. - */ - -import { mlStore } from '../stores/ml-store'; - -export type HeatmapColorMode = 'luminance' | 'variance' | 'divergence'; - -const MIN_INTERVAL_MS = 200; // 5 updates / sec - -export interface HeatmapSamplerOptions { - /** Grid size N×N. Default 16. */ - resolution?: number; - /** Initial color mode. */ - colorMode?: HeatmapColorMode; -} - -export interface HeatmapWindow { - /** Anchor point in input space [0,1]. */ - cx: number; - cy: number; - /** Side length of the sampled window (zoom level). */ - zoom: number; -} - -export class HeatmapSampler { - resolution: number; - colorMode: HeatmapColorMode; - private cells: Float32Array; - private lastSampleMs = 0; - private pending = false; - - constructor(opts: HeatmapSamplerOptions = {}) { - this.resolution = Math.max(2, Math.min(64, opts.resolution ?? 16)); - this.colorMode = opts.colorMode ?? 'luminance'; - this.cells = new Float32Array(this.resolution * this.resolution); - } - - /** Current grid for binding to the Heatmap primitive. */ - getCells(): Float32Array { - return this.cells; - } - - setColorMode(mode: HeatmapColorMode): void { - this.colorMode = mode; - } - - setResolution(n: number): void { - const r = Math.max(2, Math.min(64, n | 0)); - if (r === this.resolution) return; - this.resolution = r; - this.cells = new Float32Array(r * r); - } - - /** - * Refresh the grid. Throttled to MIN_INTERVAL_MS. - * - * Returns `true` if a fresh sample was taken, `false` if throttled. - * Reads MLP via `mlStore.inferBatch` (no-op if WASM not ready). - */ - update(window: HeatmapWindow, force: boolean = false): boolean { - const now = performance.now(); - if (!force && now - this.lastSampleMs < MIN_INTERVAL_MS) { - this.pending = true; - return false; - } - this.lastSampleMs = now; - this.pending = false; - - const N = this.resolution; - const total = N * N; - const points: Array = new Array(total); - - const z = Math.max(0.01, Math.min(1, window.zoom)); - const half = z * 0.5; - for (let y = 0; y < N; y++) { - for (let x = 0; x < N; x++) { - const u = N > 1 ? x / (N - 1) : 0.5; - const v = N > 1 ? y / (N - 1) : 0.5; - const px = window.cx + (u - 0.5) * z; // could go negative; the sampler clamps - const py = window.cy + (v - 0.5) * z; - points[y * N + x] = [ - Math.max(0, Math.min(1, px)), - Math.max(0, Math.min(1, py)), - ]; - // Note: we'd love to compute half-based bounds but the existing input - // pipeline already clamps, so just clamp here. - void half; - } - } - - const flat = mlStore.inferBatch(points); - if (flat.length === 0) { - // WASM not ready — cells remain at last value. - return false; - } - const outSize = mlStore.state.outputSize; - - // Reduce per cell. - let referenceMean = 0; - if (this.colorMode === 'divergence') { - // Reference = center cell (closest to N/2, N/2). - const cy = Math.floor(N / 2); - const cx = Math.floor(N / 2); - const base = (cy * N + cx) * outSize; - let sum = 0; - for (let i = 0; i < outSize; i++) sum += flat[base + i] ?? 0; - referenceMean = sum / outSize; - } - - for (let i = 0; i < total; i++) { - const base = i * outSize; - let sum = 0; - let sumSq = 0; - for (let j = 0; j < outSize; j++) { - const v = flat[base + j] ?? 0; - sum += v; - sumSq += v * v; - } - const mean = sum / outSize; - const variance = Math.max(0, sumSq / outSize - mean * mean); - switch (this.colorMode) { - case 'luminance': - this.cells[i] = mean; - break; - case 'variance': - this.cells[i] = variance; - break; - case 'divergence': - this.cells[i] = Math.abs(mean - referenceMean); - break; - } - } - return true; - } - - /** True if a call is queued behind throttling. */ - hasPendingUpdate(): boolean { - return this.pending; - } - - reset(): void { - this.cells.fill(0); - this.lastSampleMs = 0; - } -} diff --git a/playground/src/features/mic-input.ts b/playground/src/features/mic-input.ts deleted file mode 100644 index a5fac47..0000000 --- a/playground/src/features/mic-input.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Microphone input — captures user audio and emits audio analysis features - * for modes that take audio_in as primary input (XIASRI, SoundAnalysisMIDI). - * - * Uses an `AudioWorkletNode` for the analysis. Keeps things simple by - * computing four lightweight features in the main thread via AnalyserNode: - * - * - energy: RMS amplitude (0..1) - * - brightness: spectral centroid normalised to 0..1 - * - pitch: dominant peak frequency normalised log scale - * - aperiodicity: 1 - peak_strength (0..1) - * - * Stream 10 doesn't ship a fully featured DSP analyser — this is a - * pragmatic pipeline that gets values into the MLP. Firmware-equivalent - * XIASRI analysis is a deeper port (out of scope here). - * - * Usage: call `start()` from a user gesture; subscribe via `getFeatures()` - * (Float32Array of length 4). `stop()` releases the mic. - */ - -const MIN_FREQ = 80; -const MAX_FREQ = 1200; -const FFT_SIZE = 1024; - -export interface MicFeatures { - energy: number; - brightness: number; - pitch: number; - aperiodicity: number; -} - -export interface MicInputOptions { - /** Optional shared AudioContext. If absent, creates one. */ - audioContext?: AudioContext; -} - -export class MicInput { - private ctx: AudioContext | null = null; - private stream: MediaStream | null = null; - private analyser: AnalyserNode | null = null; - private freqBuf: Float32Array = new Float32Array(new ArrayBuffer(FFT_SIZE / 2 * 4)); - private timeBuf: Float32Array = new Float32Array(new ArrayBuffer(FFT_SIZE * 4)); - private features: MicFeatures = { energy: 0, brightness: 0, pitch: 0, aperiodicity: 1 }; - private running = false; - - constructor(private opts: MicInputOptions = {}) {} - - /** Start mic capture. Resolves once audio is flowing. */ - async start(): Promise { - if (this.running) return; - if (typeof navigator === 'undefined' || !navigator.mediaDevices) { - throw new Error('[mic-input] navigator.mediaDevices unavailable'); - } - this.ctx = this.opts.audioContext ?? new AudioContext(); - if (this.ctx.state === 'suspended') await this.ctx.resume(); - this.stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - const src = this.ctx.createMediaStreamSource(this.stream); - this.analyser = this.ctx.createAnalyser(); - this.analyser.fftSize = FFT_SIZE; - this.analyser.smoothingTimeConstant = 0.6; - src.connect(this.analyser); - this.running = true; - } - - async stop(): Promise { - if (!this.running) return; - this.running = false; - if (this.stream) { - for (const t of this.stream.getTracks()) t.stop(); - this.stream = null; - } - if (this.ctx && !this.opts.audioContext) { - try { await this.ctx.close(); } catch { /* ignore */ } - } - this.ctx = null; - this.analyser = null; - } - - isRunning(): boolean { - return this.running; - } - - /** Compute and return the latest feature vector. */ - getFeatures(): MicFeatures { - if (!this.running || !this.analyser || !this.ctx) return this.features; - const sr = this.ctx.sampleRate; - const a = this.analyser; - a.getFloatFrequencyData(this.freqBuf); - a.getFloatTimeDomainData(this.timeBuf); - - // RMS energy - let sumSq = 0; - for (let i = 0; i < this.timeBuf.length; ++i) { - const s = this.timeBuf[i]!; - sumSq += s * s; - } - const rms = Math.sqrt(sumSq / this.timeBuf.length); - const energy = Math.min(1, rms * 4); - - // Convert dB → linear, find spectral centroid + peak - let sumMag = 0; - let sumWeighted = 0; - let peakBin = 0; - let peakMag = -Infinity; - const binHz = sr / FFT_SIZE; - const minBin = Math.floor(MIN_FREQ / binHz); - const maxBin = Math.min(this.freqBuf.length - 1, Math.ceil(MAX_FREQ / binHz)); - for (let i = 0; i < this.freqBuf.length; ++i) { - const db = this.freqBuf[i]!; - const lin = Math.pow(10, db / 20); - sumMag += lin; - sumWeighted += i * binHz * lin; - if (i >= minBin && i <= maxBin && db > peakMag) { - peakMag = db; - peakBin = i; - } - } - const centroidHz = sumMag > 1e-12 ? sumWeighted / sumMag : 0; - const brightness = Math.min(1, centroidHz / (sr * 0.25)); - - // Pitch (log scale 80–1200 Hz) - const pitchHz = peakBin * binHz; - const pitchNorm = pitchHz <= MIN_FREQ - ? 0 - : pitchHz >= MAX_FREQ - ? 1 - : (Math.log(pitchHz / MIN_FREQ) / Math.log(MAX_FREQ / MIN_FREQ)); - - // Aperiodicity: 1 - relative peak height (vs spectral mean) - const meanLin = sumMag / this.freqBuf.length; - const peakLin = Math.pow(10, peakMag / 20); - const ratio = meanLin > 1e-12 ? peakLin / meanLin : 0; - const aperiodicity = Math.max(0, Math.min(1, 1 - Math.min(1, ratio / 30))); - - this.features = { energy, brightness, pitch: pitchNorm, aperiodicity }; - return this.features; - } -} diff --git a/playground/src/features/overrides.ts b/playground/src/features/overrides.ts deleted file mode 100644 index e9c134b..0000000 --- a/playground/src/features/overrides.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Override application — apply per-parameter overrides between MLP outputs - * and the engine's parameter destination. - * - * The MLP emits values in [0, 1]. The mode schema specifies a per-parameter - * `min`, `max`, and `curve`. Modes can layer overrides on top via - * `modeStore.setOverride` to: - * - Re-curve the value (`curve` and `curveParam`). - * - Mute it (replace with `fixedValue` ∈ [hardMin, hardMax]). - * - Freeze it (hold last processed value). - * - Tighten the active range (`min`/`max` ⊂ schema bounds). - * - * `applyOverrides` walks the schema params in order, looks up overrides for - * each, and writes results into a target Float32Array of the same length as - * the MLP slice. The function is pure — caller manages buffer reuse. - * - * The output buffer values are NORMALISED to [0, 1] in their effective - * range so the mode's audio engine, MIDI router, or visualiser can scale - * uniformly. Engines/MIDI consumers that need actual min/max units can use - * `paramsToSliderValues` from `mode-helpers`. - */ - -import type { ParamOverride } from '../stores/mode-store'; -import type { Param } from '../modes/generated/types'; -import { applyCurve, type CurveName, clamp01 } from '../output/curves'; - -/** - * Result of applying overrides: - * - `values` is a Float32Array of length `params.length`, each in [0, 1] - * representing the post-override normalised parameter value. - * - `freezeMask` (optional) marks per-output frozen flags. Only allocated - * when at least one param is frozen — saves work on the typical case. - */ -export interface OverrideResult { - values: Float32Array; - freezeMask: Uint8Array | null; - /** Number of muted/frozen entries (for telemetry). */ - affectedCount: number; -} - -/** - * Apply per-param overrides to the slice of MLP outputs corresponding to - * the schema's params. - * - * @param raw processed (post output-pipeline) MLP outputs in [0,1] - * @param prev previous post-override outputs (for freeze hold) - * @param params mode schema params - * @param overrides per-name override map (sparse; missing keys fall through) - * @param dest optional destination Float32Array; allocated if absent - */ -export function applyOverrides( - raw: Float32Array, - prev: Float32Array | null, - params: ReadonlyArray, - overrides: Record, - dest?: Float32Array, -): OverrideResult { - const n = params.length; - const out = dest && dest.length === n ? dest : new Float32Array(n); - let affected = 0; - let freezeMask: Uint8Array | null = null; - - for (let i = 0; i < n; ++i) { - const p = params[i]!; - const ov = overrides[p.name]; - const r = clamp01(raw[i] ?? 0); - - if (!ov) { - // No override → identity (raw normalised value). - out[i] = r; - continue; - } - - if (ov.frozen) { - // Freeze: hold last processed value. Track in freezeMask so the - // output pipeline can also honor it next frame. - if (!freezeMask) freezeMask = new Uint8Array(n); - freezeMask[i] = 1; - out[i] = prev?.[i] ?? r; - affected++; - continue; - } - - if (ov.muted) { - // Mute: replace with fixedValue. fixedValue is stored in schema units - // [min, max] — normalise back to [0, 1] in the override's effective range. - const range = ov.max - ov.min; - const norm = range > 0 ? (ov.fixedValue - ov.min) / range : 0.5; - out[i] = clamp01(norm); - affected++; - continue; - } - - // Apply curve (if any), then clamp. - let v = r; - if (ov.curve && ov.curve !== 'linear') { - v = applyCurve(ov.curve as CurveName, r, ov.curveParam); - } - out[i] = clamp01(v); - } - - return { values: out, freezeMask, affectedCount: affected }; -} - -/** - * Build a Uint8Array of length `outputSize` marking every pinned output - * with a 1. Used by `WasmIML.moveWeights` to skip pinned weights during RL - * perturbation. Combines: - * - explicit param-pins from sessionStore - * - per-param `pinned` flag in modeStore overrides - */ -export function buildPinMask( - outputSize: number, - modeId: string, - params: ReadonlyArray, - overrides: Record, - paramPins: ReadonlyArray<{ key: string; outputIndex: number }>, -): Uint8Array { - const mask = new Uint8Array(outputSize); - for (let i = 0; i < params.length && i < outputSize; ++i) { - const ov = overrides[params[i]!.name]; - if (ov && ov.pinned) mask[i] = 1; - } - for (const pin of paramPins) { - if (!pin.key.startsWith(modeId + ':')) continue; - if (pin.outputIndex >= 0 && pin.outputIndex < outputSize) { - mask[pin.outputIndex] = 1; - } - } - return mask; -} diff --git a/playground/src/features/session-preset.ts b/playground/src/features/session-preset.ts deleted file mode 100644 index 81a4849..0000000 --- a/playground/src/features/session-preset.ts +++ /dev/null @@ -1,288 +0,0 @@ -/** - * Session preset — full app-state snapshot for save/load and URL sharing. - * - * A preset captures: control axes + offsets, exploration config, output - * pipeline settings, input pipeline settings, mode override map, the - * active mode id. ML weights are NOT included by default (large), but - * `serializeWithWeights` is provided for power users. - * - * URL sharing uses a compact encoding: - * ?session= - * ?boldness=...&memory=...&precision=... - * Either form rehydrates on next load. - */ - -import { controlStore } from '../stores/control-store'; -import { explorationStore } from '../stores/exploration-store'; -import { modeStore } from '../stores/mode-store'; -import { inputStore } from '../stores/input-store'; -import { outputStore } from '../stores/output-store'; -import { mlStore } from '../stores/ml-store'; -import { sessionStore } from '../stores/session-store'; - -export interface SessionPresetPayload { - v: 1; - modeId: string | null; - control: { - boldness: number; - memory: number; - precision: number; - presetId: string | null; - offsets: typeof controlStore.state.offsets; - }; - exploration: { - spread: number; - noiseFloor: number; - noiseCap: number; - noiseGrowth: number; - noiseDecay: number; - learningRate: number; - weightDecay: number; - }; - output: { - globalCurve: number; - smoothing: number; - slewRate: number | null; // null encodes Infinity - freezeOutput: boolean; - }; - input: { - zoom: number; - anchorMode: 'auto' | 'sticky' | 'center'; - deadzone: number; - inputCurve: number; - smoothing: number; - momentumZoom: 'off' | 'gentle' | 'strong'; - invertX: boolean; - invertY: boolean; - }; - modeOverrides: typeof modeStore.state.overrides; - /** Base64-encoded weights, optional. */ - weights?: string; -} - -function encode(s: string): string { - // base64url encoding - const b64 = btoa(s); - return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); -} - -function decode(s: string): string | null { - try { - let b64 = s.replace(/-/g, '+').replace(/_/g, '/'); - while (b64.length % 4 !== 0) b64 += '='; - return atob(b64); - } catch { - return null; - } -} - -function f32ToBase64(arr: Float32Array): string { - const u8 = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); - let s = ''; - for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]!); - return encode(s); -} - -function base64ToF32(s: string): Float32Array | null { - const raw = decode(s); - if (!raw) return null; - const u8 = new Uint8Array(raw.length); - for (let i = 0; i < raw.length; i++) u8[i] = raw.charCodeAt(i); - // Round to 4 bytes - const len = u8.byteLength - (u8.byteLength % 4); - return new Float32Array(u8.buffer, u8.byteOffset, len / 4).slice(); -} - -/** - * Read every store and assemble a SessionPresetPayload. If - * `withWeights = true`, also encodes the current MLP weight blob. - */ -export function captureSessionPreset(withWeights: boolean = false): SessionPresetPayload { - const c = controlStore.state; - const e = explorationStore.state; - const o = outputStore.config; - const i = inputStore.config; - - const payload: SessionPresetPayload = { - v: 1, - modeId: modeStore.state.activeModeId, - control: { - boldness: c.boldness, - memory: c.memory, - precision: c.precision, - presetId: c.presetId, - offsets: JSON.parse(JSON.stringify(c.offsets)), - }, - exploration: { - spread: e.spread, - noiseFloor: e.noiseFloor, - noiseCap: e.noiseCap, - noiseGrowth: e.noiseGrowth, - noiseDecay: e.noiseDecay, - learningRate: e.learningRate, - weightDecay: e.weightDecay, - }, - output: { - globalCurve: o.globalCurve, - smoothing: o.smoothing, - slewRate: isFinite(o.slewRate) ? o.slewRate : null, - freezeOutput: o.freezeOutput, - }, - input: { - zoom: i.zoom, - anchorMode: i.anchorMode, - deadzone: i.deadzone, - inputCurve: i.inputCurve, - smoothing: i.smoothing, - momentumZoom: i.momentumZoom, - invertX: i.invertX, - invertY: i.invertY, - }, - modeOverrides: JSON.parse(JSON.stringify(modeStore.state.overrides)), - }; - - if (withWeights) { - const w = mlStore.getWeights(); - if (w.length > 0) { - payload.weights = f32ToBase64(w); - } - } - return payload; -} - -/** - * Restore a SessionPresetPayload into all stores. Best-effort: missing - * fields fall back to current values. Returns true if at least one store - * was modified. - */ -export function restoreSessionPreset(payload: SessionPresetPayload): boolean { - if (!payload || payload.v !== 1) return false; - - // Mode first (so override map is keyed correctly). - if (payload.modeId && payload.modeId !== modeStore.state.activeModeId) { - modeStore.switchMode(payload.modeId); - } - - // Control axes & offsets. - if (payload.control) { - controlStore.setAxis('boldness', payload.control.boldness); - controlStore.setAxis('memory', payload.control.memory); - controlStore.setAxis('precision', payload.control.precision); - if (payload.control.presetId) controlStore.applyPreset(payload.control.presetId); - // Manually rewrite offsets — the store doesn't have a public bulk setter - // (control-store doesn't expose it because it's a private field). - // Use clearOffsets + setOffset roundtrip. - for (const axis of ['boldness', 'memory', 'precision'] as const) { - controlStore.clearOffsets(axis); - const off = payload.control.offsets[axis] ?? {}; - for (const [k, v] of Object.entries(off)) { - if (typeof v === 'number') controlStore.setOffset(axis, k, v); - } - } - } - - if (payload.exploration) { - explorationStore.setSpread(payload.exploration.spread); - explorationStore.setNoiseFloor(payload.exploration.noiseFloor); - explorationStore.setNoiseCap(payload.exploration.noiseCap); - explorationStore.setNoiseGrowth(payload.exploration.noiseGrowth); - explorationStore.setNoiseDecay(payload.exploration.noiseDecay); - explorationStore.setLearningRate(payload.exploration.learningRate); - explorationStore.setWeightDecay(payload.exploration.weightDecay); - } - - if (payload.output) { - outputStore.setGlobalCurve(payload.output.globalCurve); - outputStore.setSmoothing(payload.output.smoothing); - outputStore.setSlewRate(payload.output.slewRate ?? Infinity); - outputStore.setFreezeOutput(payload.output.freezeOutput); - } - - if (payload.input) { - inputStore.setZoom(payload.input.zoom); - inputStore.setAnchorMode(payload.input.anchorMode); - inputStore.setDeadzone(payload.input.deadzone); - inputStore.setInputCurve(payload.input.inputCurve); - inputStore.setSmoothing(payload.input.smoothing); - inputStore.setMomentumZoom(payload.input.momentumZoom); - inputStore.setInvert(payload.input.invertX, payload.input.invertY); - } - - if (payload.modeOverrides) { - for (const modeId of Object.keys(payload.modeOverrides)) { - const map = payload.modeOverrides[modeId] ?? {}; - modeStore.clearAllOverrides(modeId); - for (const [name, ov] of Object.entries(map)) { - modeStore.setOverride(modeId, name, ov); - } - } - } - - if (payload.weights) { - const w = base64ToF32(payload.weights); - if (w && mlStore.iml && w.length > 0) { - try { mlStore.setWeights(w); } catch { /* ignore size mismatch */ } - } - } - - return true; -} - -/** Save preset to sessionStore (named blob in localStorage). */ -export function saveNamedPreset(name: string, withWeights: boolean = false): void { - const payload = captureSessionPreset(withWeights); - sessionStore.savePreset(name, payload); -} - -/** Load a named preset by id from sessionStore. */ -export function loadNamedPreset(id: string): boolean { - const preset = sessionStore.state.presets.find((p) => p.id === id); - if (!preset) return false; - return restoreSessionPreset(preset.payload as SessionPresetPayload); -} - -// --------------------------------------------------------------------------- -// URL sharing -// --------------------------------------------------------------------------- - -/** Build a query-string fragment from current state (without weights). */ -export function buildShareUrl(): string { - const payload = captureSessionPreset(false); - const json = JSON.stringify(payload); - const enc = encode(json); - return `?session=${enc}`; -} - -/** Parse `window.location.search` and apply a session if present. */ -export function applyUrlParams(search: string = window.location.search): boolean { - const sp = new URLSearchParams(search); - // Compact ?boldness=...&memory=... form. - const cb = sp.get('boldness'); - const cm = sp.get('memory'); - const cp = sp.get('precision'); - let applied = false; - if (cb !== null || cm !== null || cp !== null) { - if (cb !== null) controlStore.setAxis('boldness', clamp01(parseFloat(cb))); - if (cm !== null) controlStore.setAxis('memory', clamp01(parseFloat(cm))); - if (cp !== null) controlStore.setAxis('precision', clamp01(parseFloat(cp))); - applied = true; - } - const sess = sp.get('session'); - if (sess) { - const json = decode(sess); - if (json) { - try { - const payload = JSON.parse(json) as SessionPresetPayload; - applied = restoreSessionPreset(payload) || applied; - } catch { - // Ignore malformed - } - } - } - return applied; -} - -function clamp01(v: number): number { - if (!isFinite(v)) return 0; - return v < 0 ? 0 : v > 1 ? 1 : v; -} diff --git a/playground/src/features/snapshots.ts b/playground/src/features/snapshots.ts deleted file mode 100644 index 7e1db3f..0000000 --- a/playground/src/features/snapshots.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Snapshot helpers — bridge sessionStore.pushSnapshot/popSnapshot to the - * actual MLP weights via mlStore. Auto-snapshot before train/randomize/ - * thumbs-down. - */ - -import { mlStore } from '../stores/ml-store'; -import { sessionStore } from '../stores/session-store'; -import { explorationStore } from '../stores/exploration-store'; -import { inputStore } from '../stores/input-store'; - -/** Capture current weights + noise level + zoom level. */ -export function autoSnapshot(tag: string): void { - if (!mlStore.iml) return; - const w = mlStore.getWeights(); - if (w.length === 0) return; - sessionStore.pushSnapshot(tag, { - noiseLevel: explorationStore.state.noiseLevel, - zoomLevel: inputStore.config.zoom, - weights: w, - }); -} - -/** - * Pop the most recent snapshot and restore weights. Returns true on success. - */ -export function undoLastSnapshot(): boolean { - const snap = sessionStore.popSnapshot(); - if (!snap || !snap.weights) return false; - if (!mlStore.iml) return false; - try { - mlStore.setWeights(snap.weights); - explorationStore.setNoiseLevel(snap.noiseLevel); - return true; - } catch { - return false; - } -} - -/** Restore a specific snapshot by id. */ -export function restoreSnapshotById(id: string): boolean { - const snap = sessionStore.jumpToSnapshot(id); - if (!snap || !snap.weights) return false; - if (!mlStore.iml) return false; - try { - mlStore.setWeights(snap.weights); - explorationStore.setNoiseLevel(snap.noiseLevel); - return true; - } catch { - return false; - } -} - -/** - * A/B compare helpers — capture the current weights as A, the next state - * becomes B. Toggle swaps in O(1). - */ -export function captureA(): void { - if (!mlStore.iml) return; - const w = mlStore.getWeights(); - if (w.length === 0) return; - sessionStore.captureA({ - id: `a-${Date.now().toString(36)}`, - tag: 'A', - timestamp: Date.now(), - noiseLevel: explorationStore.state.noiseLevel, - zoomLevel: inputStore.config.zoom, - weights: w, - }); -} - -export function toggleAB(): 'A' | 'B' { - if (!mlStore.iml) return sessionStore.state.ab.live; - // Capture the current live state on first toggle. - const cur = mlStore.getWeights(); - const live = sessionStore.state.ab.live; - if (live === 'B' && !sessionStore.state.ab.b) { - // First toggle from B → A: capture the current state as B. - sessionStore.captureB({ - id: `b-${Date.now().toString(36)}`, - tag: 'B', - timestamp: Date.now(), - noiseLevel: explorationStore.state.noiseLevel, - zoomLevel: inputStore.config.zoom, - weights: cur, - }); - } else if (live === 'A') { - // Going back from A → B: update B with the current edits before flipping. - sessionStore.captureB({ - id: `b-${Date.now().toString(36)}`, - tag: 'B', - timestamp: Date.now(), - noiseLevel: explorationStore.state.noiseLevel, - zoomLevel: inputStore.config.zoom, - weights: cur, - }); - } - const next = sessionStore.toggleAB(); - // Apply the side we just switched to. - const target = next === 'A' ? sessionStore.state.ab.a : sessionStore.state.ab.b; - if (target?.weights) { - try { mlStore.setWeights(target.weights); } catch { /* ignore */ } - explorationStore.setNoiseLevel(target.noiseLevel); - } - return next; -} - -export function acceptB(): void { - sessionStore.acceptB(); -} - -export function revertToA(): void { - if (!mlStore.iml) { - sessionStore.revertToA(); - return; - } - const a = sessionStore.state.ab.a; - if (a?.weights) { - try { mlStore.setWeights(a.weights); } catch { /* ignore */ } - explorationStore.setNoiseLevel(a.noiseLevel); - } - sessionStore.revertToA(); -} diff --git a/playground/src/features/trail.ts b/playground/src/features/trail.ts deleted file mode 100644 index 77612e7..0000000 --- a/playground/src/features/trail.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Trail tracking — fixed-size ring of recent input positions for the JoyMap - * vanishing trail and tap-to-return. - * - * Uses `performance.now()` for timestamps. The JoyMap primitive filters by - * age internally (5-second window), so the buffer can be larger than the - * displayed window. - */ - -import { createSignal, type Accessor } from 'solid-js'; - -export interface TrailPoint { - x: number; - y: number; - t: number; // performance.now() -} - -const MAX_POINTS = 300; -const MIN_DELTA_PX = 0.005; // skip near-duplicate points (in 0..1 space) - -export interface TrailRing { - /** Reactive accessor for current points. */ - points: Accessor>; - push(x: number, y: number): void; - clear(): void; -} - -export function createTrailRing(): TrailRing { - const [points, setPoints] = createSignal>([], { equals: false }); - - let buf: TrailPoint[] = []; - - return { - points, - push(x, y) { - const now = performance.now(); - // Skip if too close to last point. - const last = buf.length > 0 ? buf[buf.length - 1] : null; - if (last) { - const dx = x - last.x; - const dy = y - last.y; - if (dx * dx + dy * dy < MIN_DELTA_PX * MIN_DELTA_PX && now - last.t < 50) { - return; - } - } - buf.push({ x, y, t: now }); - if (buf.length > MAX_POINTS) buf.shift(); - setPoints(buf.slice()); - }, - clear() { - buf = []; - setPoints([]); - }, - }; -} diff --git a/playground/src/features/weight-health.ts b/playground/src/features/weight-health.ts deleted file mode 100644 index 31ff255..0000000 --- a/playground/src/features/weight-health.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Weight health + gradient flow analysis. - * - * Pure functions over Float32Array weight buffers and `LayerStats[]` from - * the WasmIML. Mode UI binds these to the WeightHealth and GradientFlow - * primitives. - */ - -import type { LayerStats } from '../ml/types'; -import type { WeightStatus } from '../primitives/WeightHealth'; -import type { GradientStatus } from '../primitives/GradientFlow'; - -const HISTOGRAM_BINS = 10; -/** Magnitude bin edges: [0, 0.01, 0.05, 0.15, 0.4, 0.8, 1.5, 2.5, 4, 7, ∞] */ -const BIN_EDGES = [0, 0.01, 0.05, 0.15, 0.4, 0.8, 1.5, 2.5, 4, 7]; - -/** Build a 10-bin histogram of weight magnitudes from a flat weight array. */ -export function weightHistogram(weights: Float32Array): number[] { - const bins = new Array(HISTOGRAM_BINS).fill(0); - if (weights.length === 0) return bins; - for (let i = 0; i < weights.length; ++i) { - const m = Math.abs(weights[i]!); - let bin = HISTOGRAM_BINS - 1; - for (let b = 0; b < HISTOGRAM_BINS - 1; ++b) { - if (m < BIN_EDGES[b + 1]!) { - bin = b; - break; - } - } - bins[bin]++; - } - return bins; -} - -/** Determine overall network health status. */ -export function weightStatus(weights: Float32Array): WeightStatus { - if (weights.length === 0) return 'healthy'; - let dead = 0; - let saturating = 0; - for (let i = 0; i < weights.length; ++i) { - const m = Math.abs(weights[i]!); - if (m < 0.01) dead++; - else if (m > 3.0) saturating++; - } - const deadFrac = dead / weights.length; - const satFrac = saturating / weights.length; - if (satFrac > 0.4) return 'saturating'; - if (deadFrac > 0.3) return 'dead'; - return 'healthy'; -} - -/** - * Per-layer L2 norm of weight delta (after - before). Used to populate - * GradientFlow primitive. - */ -export function layerNorms(beforeWeights: Float32Array, afterWeights: Float32Array, layerSizes: ReadonlyArray): number[] { - const out: number[] = []; - let offset = 0; - for (const sz of layerSizes) { - let sum = 0; - const end = Math.min(beforeWeights.length, afterWeights.length, offset + sz); - for (let i = offset; i < end; ++i) { - const d = (afterWeights[i] ?? 0) - (beforeWeights[i] ?? 0); - sum += d * d; - } - out.push(Math.sqrt(sum)); - offset += sz; - } - return out; -} - -/** - * Detect vanishing/exploding/converged gradient flow given per-layer norms. - */ -export function gradientStatuses(norms: ReadonlyArray): GradientStatus[] { - if (norms.length === 0) return []; - // Converged check first: all near-zero. - const allTiny = norms.every((n) => n < 1e-6); - if (allTiny) return norms.map(() => 'converged' as GradientStatus); - - const out: GradientStatus[] = []; - for (let i = 0; i < norms.length; ++i) { - if (i === 0) { - out.push('healthy'); - continue; - } - const prev = norms[i - 1] ?? 0; - const curr = norms[i] ?? 0; - if (prev <= 1e-12) { - out.push('healthy'); - } else if (curr < 0.5 * prev) { - out.push('vanishing'); - } else if (curr > 2.0 * prev) { - out.push('exploding'); - } else { - out.push('healthy'); - } - } - return out; -} - -/** - * Estimate per-layer weight count given an MLP architecture. - * - * MLP layout: layer i has weights = inputs * outputs + outputs (bias). - * `[inputSize, ...hidden, outputSize]` — N+1 layer sizes, N layers. - */ -export function layerWeightCounts(arch: { inputSize: number; hidden: ReadonlyArray; outputSize: number }): number[] { - const sizes = [arch.inputSize, ...arch.hidden, arch.outputSize]; - const counts: number[] = []; - for (let i = 0; i < sizes.length - 1; ++i) { - const ins = sizes[i]!; - const outs = sizes[i + 1]!; - counts.push(ins * outs + outs); - } - return counts; -} - -/** Map LayerStats to a healthy/dead/saturating per-layer status. */ -export function layerStatsToStatus(stats: LayerStats): WeightStatus { - if (stats.saturatingFrac > 0.4) return 'saturating'; - if (stats.deadFrac > 0.3) return 'dead'; - return 'healthy'; -} diff --git a/playground/src/input/pipeline.ts b/playground/src/input/pipeline.ts deleted file mode 100644 index 4cf94e1..0000000 --- a/playground/src/input/pipeline.ts +++ /dev/null @@ -1,307 +0,0 @@ -/** - * Input pipeline — pure TS port of legacy `js/ui/input-pipeline.js`. - * - * Stages (in order), each input/output in [0,1]: - * 0. Invert (per-axis flip) - * 1. Deadzone (suppress jitter near center, remap live zone to [0,1]) - * 2. Circular clamp (constrain to unit disk centered at 0.5,0.5) - * 3. Zoom (narrow window around anchor, modulated by momentum) - * 4. Centered power curve (per-axis exponent) - * 5. EMA smoothing (frame-rate-independent) - * 6. Momentum-as-zoom update (consumed next frame) - * - * `processInput` is a pure function over (raw, cfg, prev): returns the new - * processed coordinate plus the next-frame state. Consumer (input-store) - * holds the state and calls this each frame. - * - * Math is intentionally bit-for-bit equivalent to the legacy implementation. - */ - -import { clamp, curveCenteredPower } from '../output/curves'; - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -export const ZOOM_MIN = 0.01; -export const ZOOM_MAX = 1.0; -export const FREEZE_THRESHOLD = ZOOM_MIN; - -export const DEADZONE_MAX = 0.4; -export const INPUT_CURVE_MIN = 0.2; -export const INPUT_CURVE_MAX = 5.0; -export const SMOOTHING_MAX = 0.95; -export const VELOCITY_WINDOW_DEFAULT = 150; // ms - -const REFERENCE_DT = 1 / 60; - -export type MomentumZoomMode = 'off' | 'gentle' | 'strong'; -export type AnchorMode = 'auto' | 'sticky' | 'center'; - -interface MomentumPreset { - factor: number; - minZoomMul: number; - maxZoomMul: number; -} - -const MOMENTUM_PRESETS: Record = { - off: null, - gentle: { factor: 0.6, minZoomMul: 0.3, maxZoomMul: 1.0 }, - strong: { factor: 1.5, minZoomMul: 0.15, maxZoomMul: 1.0 }, -}; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -export interface InputConfig { - /** Global zoom level [0.01, 1.0] */ - zoom: number; - /** Optional per-axis zoom; overrides global when not null */ - zoomX: number | null; - zoomY: number | null; - /** Anchor point [0,1]^2 (used in sticky/auto modes) */ - anchorX: number; - anchorY: number; - anchorMode: AnchorMode; - /** Deadzone fraction of half-travel [0, 0.4] */ - deadzone: number; - /** Centered power curve exponent [0.2, 5.0] (1.0 = linear) */ - inputCurve: number; - inputCurveX: number | null; - inputCurveY: number | null; - /** EMA smoothing factor [0, 0.95] */ - smoothing: number; - /** Momentum-as-zoom preset */ - momentumZoom: MomentumZoomMode; - velocityWindow: number; - /** Per-axis inversion */ - invertX: boolean; - invertY: boolean; -} - -export interface InputState { - /** Last smoothed output x; seed at 0.5 */ - smoothedX: number; - smoothedY: number; - /** Velocity history ring used for momentum-zoom */ - velocityHistory: ReadonlyArray<{ x: number; y: number; t: number }>; - /** Most recent momentum-zoom multiplier (1 = no scale) */ - momentumZoomMultiplier: number; - /** Whether last process call returned frozen=true */ - frozen: boolean; -} - -export interface ProcessResult { - x: number; - y: number; - frozen: boolean; - /** Next frame's state — consumer should keep this and pass it back. */ - state: InputState; -} - -// --------------------------------------------------------------------------- -// Defaults -// --------------------------------------------------------------------------- - -export function defaultInputConfig(): InputConfig { - return { - zoom: 1.0, - zoomX: null, - zoomY: null, - anchorX: 0.5, - anchorY: 0.5, - anchorMode: 'center', - deadzone: 0, - inputCurve: 1.0, - inputCurveX: null, - inputCurveY: null, - smoothing: 0, - momentumZoom: 'off', - velocityWindow: VELOCITY_WINDOW_DEFAULT, - invertX: false, - invertY: false, - }; -} - -export function defaultInputState(): InputState { - return { - smoothedX: 0.5, - smoothedY: 0.5, - velocityHistory: [], - momentumZoomMultiplier: 1, - frozen: false, - }; -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function applyDeadzone(input: number, deadzone: number): number { - if (deadzone <= 0) return input; - const offset = input - 0.5; - const absOff = Math.abs(offset); - const halfDz = deadzone * 0.5; - if (absOff <= halfDz) return 0.5; - const sign = offset < 0 ? -1 : 1; - const remapped = ((absOff - halfDz) / (0.5 - halfDz)) * 0.5; - return 0.5 + sign * remapped; -} - -function applyZoom(input: number, anchor: number, zoomLevel: number): number { - return clamp(anchor + (input - 0.5) * zoomLevel, 0, 1); -} - -function emaSmooth(prev: number, raw: number, smoothing: number, dt: number): number { - if (smoothing <= 0) return raw; - const effectiveDt = dt > 0 ? dt : REFERENCE_DT; - const alpha = 1 - smoothing; - const alphaEff = 1 - Math.pow(1 - alpha, effectiveDt / REFERENCE_DT); - return prev + alphaEff * (raw - prev); -} - -function updateMomentumZoomMultiplier( - cfg: InputConfig, - state: InputState, - rawX: number, - rawY: number, - dt: number, -): { multiplier: number; history: InputState['velocityHistory'] } { - const preset = MOMENTUM_PRESETS[cfg.momentumZoom]; - if (!preset) { - return { multiplier: 1, history: [] }; - } - const now = performance.now(); - const window = cfg.velocityWindow; - // Append, drop entries older than `window` ms - const trimmed = state.velocityHistory.filter((p) => now - p.t <= window); - const newHist = [...trimmed, { x: rawX, y: rawY, t: now }]; - if (newHist.length < 2) { - return { multiplier: 1, history: newHist }; - } - const a = newHist[0]!; - const b = newHist[newHist.length - 1]!; - const dtMs = b.t - a.t; - if (dtMs <= 0) return { multiplier: state.momentumZoomMultiplier, history: newHist }; - const dx = b.x - a.x; - const dy = b.y - a.y; - const dist = Math.sqrt(dx * dx + dy * dy); - const speed = dist / (dtMs / 1000); // [0,1]-space units per second - const normSpeed = clamp(speed * preset.factor, 0, 1); - // Higher speed → smaller multiplier (zoom out faster movements) - const target = preset.maxZoomMul - (preset.maxZoomMul - preset.minZoomMul) * normSpeed; - // Smooth toward target so the zoom doesn't jitter - const smoothCoeff = clamp(dt * 6, 0, 1); - const next = state.momentumZoomMultiplier + smoothCoeff * (target - state.momentumZoomMultiplier); - return { multiplier: next, history: newHist }; -} - -function resolveAnchorX(cfg: InputConfig, state: InputState): number { - if (cfg.anchorMode === 'center') return 0.5; - if (cfg.anchorMode === 'sticky') return cfg.anchorX; - // auto: use stored anchor (input-store updates it on zoom changes) - return cfg.anchorX; -} - -function resolveAnchorY(cfg: InputConfig, state: InputState): number { - if (cfg.anchorMode === 'center') return 0.5; - if (cfg.anchorMode === 'sticky') return cfg.anchorY; - return cfg.anchorY; -} - -// --------------------------------------------------------------------------- -// Public API -// --------------------------------------------------------------------------- - -/** - * Process raw 2D input through the pipeline. - * - * @param raw raw input [x, y] in [0,1] - * @param cfg pipeline configuration - * @param state prior state (use {@link defaultInputState} on first call) - * @param dt seconds since last call (default 1/60) - */ -export function processInput( - raw: readonly [number, number], - cfg: InputConfig, - state: InputState, - dt: number = REFERENCE_DT, -): ProcessResult { - const safeDt = Math.max(0, dt); - const baseZoomX = cfg.zoomX ?? cfg.zoom; - const baseZoomY = cfg.zoomY ?? cfg.zoom; - - const frozenX = baseZoomX <= FREEZE_THRESHOLD; - const frozenY = baseZoomY <= FREEZE_THRESHOLD; - const fullyFrozen = frozenX && frozenY; - - if (fullyFrozen) { - return { - x: state.smoothedX, - y: state.smoothedY, - frozen: true, - state: { ...state, frozen: true }, - }; - } - - let [rawX, rawY] = raw; - - // 0. Invert - let x = cfg.invertX ? 1 - rawX : rawX; - let y = cfg.invertY ? 1 - rawY : rawY; - - // 1. Deadzone - x = applyDeadzone(x, cfg.deadzone); - y = applyDeadzone(y, cfg.deadzone); - - // 2. Circular clamp to unit disk centered at (0.5, 0.5) - { - const cx = x - 0.5; - const cy = y - 0.5; - const dist = Math.sqrt(cx * cx + cy * cy); - if (dist > 0.5 && dist > 1e-12) { - const scale = 0.5 / dist; - x = 0.5 + cx * scale; - y = 0.5 + cy * scale; - } - } - - // 3. Zoom around anchor (with momentum modulation) - const anchorX = resolveAnchorX(cfg, state); - const anchorY = resolveAnchorY(cfg, state); - const effZoomX = frozenX - ? FREEZE_THRESHOLD - : clamp(baseZoomX * state.momentumZoomMultiplier, ZOOM_MIN, ZOOM_MAX); - const effZoomY = frozenY - ? FREEZE_THRESHOLD - : clamp(baseZoomY * state.momentumZoomMultiplier, ZOOM_MIN, ZOOM_MAX); - x = frozenX ? state.smoothedX : applyZoom(x, anchorX, effZoomX); - y = frozenY ? state.smoothedY : applyZoom(y, anchorY, effZoomY); - - // 4. Centered power curve - const curveX = cfg.inputCurveX ?? cfg.inputCurve; - const curveY = cfg.inputCurveY ?? cfg.inputCurve; - if (!frozenX) x = curveCenteredPower(x, curveX); - if (!frozenY) y = curveCenteredPower(y, curveY); - - // 5. EMA smoothing - const smoothedX = frozenX ? state.smoothedX : emaSmooth(state.smoothedX, x, cfg.smoothing, safeDt); - const smoothedY = frozenY ? state.smoothedY : emaSmooth(state.smoothedY, y, cfg.smoothing, safeDt); - - // 6. Update momentum-zoom for next frame - const { multiplier, history } = updateMomentumZoomMultiplier(cfg, state, rawX, rawY, safeDt); - - return { - x: smoothedX, - y: smoothedY, - frozen: false, - state: { - smoothedX, - smoothedY, - velocityHistory: history, - momentumZoomMultiplier: multiplier, - frozen: false, - }, - }; -} diff --git a/playground/src/main.tsx b/playground/src/main.tsx deleted file mode 100644 index 69850f7..0000000 --- a/playground/src/main.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* @refresh reload */ -import { render } from 'solid-js/web'; -import App from './App'; -import './styles/tokens.css'; -import { installDebugProbe } from './debug/probe'; -import { applyUrlParams } from './features/session-preset'; - -const root = document.getElementById('root'); -if (!root) { - throw new Error('Root element #root not found'); -} - -// Install debug probe early. Playwright tests use it to drive the ML -// engine programmatically. -installDebugProbe(); - -// Apply URL params (?session=..., ?boldness=..., etc) before mounting so -// stores read the resolved values on first render. -try { - applyUrlParams(); -} catch (err) { - console.warn('[main] applyUrlParams failed:', err); -} - -render(() => , root); diff --git a/playground/src/ml/dataset.ts b/playground/src/ml/dataset.ts deleted file mode 100644 index c8e6323..0000000 --- a/playground/src/ml/dataset.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Dataset — JS-side training-example store. - * - * Why duplicate the C++ ring buffer? Two reasons: - * 1. Sample-weight computation (recency / spatial / combined) lives in JS so - * that adjusting weighting modes doesn't burn a WASM round-trip. - * 2. The dataset is part of session state we serialize to localStorage — - * the WASM heap is wiped on reload. - * - * On train() we ship features + labels into WASM via `addExample` calls. The - * order of insertion is preserved; FIFO eviction matches the C++ MLP's - * `dataset_head_` pointer so weighting stays consistent. - * - * The implementation is a faithful TypeScript port of the legacy - * `playground/_archive/js/nisps/dataset.js` with: - * - Float32Array backing instead of `Array>` - * - Stricter types - * - No `withBias` flag (the WASM bindings don't take a bias term) - */ - -export type WeightMode = 'global' | 'local' | 'combined' | 'uniform'; - -export interface ComputeWeightsParams { - /** [0,1] — how strongly to bias toward newest examples (global/combined). */ - recencyBias?: number; - /** Current input position, used for local/combined spatial weighting. */ - queryInput?: ReadonlyArray; - /** Spatial radius in input space (local/combined). */ - radius?: number; -} - -export class Dataset { - /** Maximum number of examples retained. FIFO eviction beyond this. */ - readonly maxSize: number; - /** Length of feature vectors. Set on first add(); locked thereafter. */ - private inputSize_ = 0; - /** Length of label vectors. Set on first add(); locked thereafter. */ - private outputSize_ = 0; - - /** Flat arrays — entries `[i*inputSize, (i+1)*inputSize)` belong to example i. */ - private features_: Float32Array = new Float32Array(0); - private labels_: Float32Array = new Float32Array(0); - private size_ = 0; - - constructor(maxSize = 100) { - if (maxSize <= 0) throw new Error('Dataset.maxSize must be > 0'); - this.maxSize = maxSize; - } - - /** Number of examples currently stored. */ - get size(): number { - return this.size_; - } - - isEmpty(): boolean { - return this.size_ === 0; - } - - /** - * Add a feature/label pair. Returns true on success, false if the - * dimensions don't match a previously-added example. - * - * Eviction: when at capacity, the oldest example is removed (shift), - * then the new one is appended. This matches the legacy JS behaviour - * (and is conceptually equivalent to the C++ side's ring buffer with - * `head_` advancement). - */ - add(features: ReadonlyArray, labels: ReadonlyArray): boolean { - if (this.size_ === 0) { - this.inputSize_ = features.length; - this.outputSize_ = labels.length; - // Allocate full-capacity buffers up front to avoid growth thrash. - this.features_ = new Float32Array(this.maxSize * this.inputSize_); - this.labels_ = new Float32Array(this.maxSize * this.outputSize_); - } - - if (features.length !== this.inputSize_ || labels.length !== this.outputSize_) { - return false; - } - - if (this.size_ >= this.maxSize) { - // FIFO: shift left in place. This is O(n*dim) and could be replaced - // with a head pointer; for maxSize ≤ a few hundred it's fine. - this.features_.copyWithin(0, this.inputSize_); - this.labels_.copyWithin(0, this.outputSize_); - this.size_ = this.maxSize - 1; - } - - const fOff = this.size_ * this.inputSize_; - const lOff = this.size_ * this.outputSize_; - for (let i = 0; i < this.inputSize_; ++i) this.features_[fOff + i] = features[i]; - for (let i = 0; i < this.outputSize_; ++i) this.labels_[lOff + i] = labels[i]; - this.size_++; - return true; - } - - clear(): void { - this.size_ = 0; - } - - /** Read-only view of the i-th feature vector. */ - feature(i: number): Float32Array { - if (i < 0 || i >= this.size_) throw new RangeError(`feature index ${i} out of bounds`); - return this.features_.subarray(i * this.inputSize_, (i + 1) * this.inputSize_); - } - - /** Read-only view of the i-th label vector. */ - label(i: number): Float32Array { - if (i < 0 || i >= this.size_) throw new RangeError(`label index ${i} out of bounds`); - return this.labels_.subarray(i * this.outputSize_, (i + 1) * this.outputSize_); - } - - /** Flat view of all features (size * inputSize). */ - featuresFlat(): Float32Array { - return this.features_.subarray(0, this.size_ * this.inputSize_); - } - - /** Flat view of all labels (size * outputSize). */ - labelsFlat(): Float32Array { - return this.labels_.subarray(0, this.size_ * this.outputSize_); - } - - get inputSize(): number { - return this.inputSize_; - } - get outputSize(): number { - return this.outputSize_; - } - - /** - * Compute per-sample training weights. Returns Float32Array (size=this.size) - * normalized to sum to 1. For an empty dataset returns a 0-length array; - * for a singleton, [1.0]. - * - * Modes: - * - `uniform` — every weight = 1/n. - * - `global` — exponential recency decay over insertion order. - * - `local` — within `radius` of `queryInput`, suppress older neighbours. - * - `combined` — global × local. - */ - computeWeights(mode: WeightMode = 'uniform', params: ComputeWeightsParams = {}): Float32Array { - const n = this.size_; - if (n === 0) return new Float32Array(0); - if (n === 1) return new Float32Array([1.0]); - - const weights = new Float32Array(n).fill(1.0); - - if (mode === 'global' || mode === 'combined') { - const bias = params.recencyBias ?? 0.6; - if (bias > 0) { - const decay = 1 - 0.3 * bias; - for (let i = n - 2; i >= 0; --i) weights[i] = weights[i + 1] * decay; - } - } - - if ((mode === 'local' || mode === 'combined') && params.queryInput) { - const query = params.queryInput; - const radius = params.radius ?? 0.15; - const radiusSq = radius * radius; - const dim = this.inputSize_; - - for (let i = 0; i < n; ++i) { - const fOffI = i * dim; - let distSq = 0; - for (let d = 0; d < dim; ++d) { - const diff = this.features_[fOffI + d] - (query[d] ?? 0); - distSq += diff * diff; - } - if (distSq < radiusSq) { - const proximity = 1 - Math.sqrt(distSq) / radius; - let newerNearby = 0; - for (let j = i + 1; j < n; ++j) { - const fOffJ = j * dim; - let djSq = 0; - for (let d = 0; d < dim; ++d) { - const diff = this.features_[fOffI + d] - this.features_[fOffJ + d]; - djSq += diff * diff; - } - if (djSq < radiusSq) ++newerNearby; - } - if (newerNearby > 0) { - weights[i] *= Math.pow(1 - proximity, newerNearby); - } - } - } - } - - let sum = 0; - for (let i = 0; i < n; ++i) sum += weights[i]; - if (sum > 0) for (let i = 0; i < n; ++i) weights[i] /= sum; - return weights; - } -} diff --git a/playground/src/ml/jolt.ts b/playground/src/ml/jolt.ts deleted file mode 100644 index 8b10f34..0000000 --- a/playground/src/ml/jolt.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * jolt.ts — "Jolt": held-button continuous weight morph. - * - * Faithful TS port of `nisps/ml/jolt.hpp` (which itself ports upstream - * memllib InterfaceRL "jolts"). While a button/pedal is held, pick a - * handful of random weights scattered across the whole network and - * EMA-glide each toward a bounded random target; on arrival, re-roll the - * target so the motion never stops. Releasing freezes the weights where - * they landed (the change is permanent). - * - * This operates on the MLP's FLAT weight buffer (the same buffer reached - * via `mlStore.getWeights()` / `setWeights()` / weight_count), so it is - * architecture-agnostic. - * - * RNG note: the C++ owns a per-instance deterministic xoshiro256+ for - * firmware↔browser parity. This is a browser-only exploration aid that - * never round-trips through WASM, so we use `Math.random()` — the exact - * sequence does not need to match firmware. Constants are reproduced - * verbatim from JoltParams. - * - * DEFAULT STATE IS INERT: a freshly constructed Jolt is inactive and - * `step()` is a no-op until `press()` is called. - */ - -/** Upstream JoltParams defaults (kJolt* constants). */ -export interface JoltParams { - /** kJoltNumWeights — number of weights morphed at once. */ - numWeights: number; - /** kJoltMorphRate — EMA glide per tick (~1s @200Hz). */ - morphRate: number; - /** kJoltWeightMin — lower bound of a random target. */ - targetMin: number; - /** kJoltWeightMax — upper bound of a random target. */ - targetMax: number; - /** kJoltTargetEpsilon — re-roll target once within this distance. */ - targetEpsilon: number; -} - -export const DEFAULT_JOLT_PARAMS: JoltParams = { - numWeights: 40, - morphRate: 0.017, - targetMin: -1.2, - targetMax: 0.9, - targetEpsilon: 0.05, -}; - -export class Jolt { - private params_: JoltParams = { ...DEFAULT_JOLT_PARAMS }; - private active_ = false; - private n_ = 0; - private idx_: Int32Array = new Int32Array(0); - private target_: Float32Array = new Float32Array(0); - - constructor(params?: Partial) { - if (params) this.params_ = { ...this.params_, ...params }; - } - - setParams(p: Partial): void { - this.params_ = { ...this.params_, ...p }; - } - - get params(): Readonly { - return this.params_; - } - - active(): boolean { - return this.active_; - } - - /** - * Begin a jolt over a flat weight buffer of `weightCount` entries: pick - * `numWeights` random global indices and a bounded random target each. - */ - press(weightCount: number): void { - this.active_ = true; - this.n_ = this.params_.numWeights; - if (weightCount <= 0) { - this.n_ = 0; - return; - } - if (this.idx_.length < this.n_) this.idx_ = new Int32Array(this.n_); - if (this.target_.length < this.n_) this.target_ = new Float32Array(this.n_); - for (let i = 0; i < this.n_; ++i) { - this.idx_[i] = Math.floor(Math.random() * weightCount) % weightCount; - this.target_[i] = this.rollTarget_(); - } - } - - /** - * Per control tick while held: EMA-glide each selected weight toward its - * target, re-rolling targets that have been reached. No-op when inactive. - * Mutates `weights` in place. - */ - step(weights: Float32Array): void { - if (!this.active_) return; - const wc = weights.length; - const rate = this.params_.morphRate; - const eps = this.params_.targetEpsilon; - for (let i = 0; i < this.n_; ++i) { - const k = this.idx_[i]!; - if (k < 0 || k >= wc) continue; - let w = weights[k]!; - w += rate * (this.target_[i]! - w); - weights[k] = w; - if (Math.abs(this.target_[i]! - w) < eps) { - this.target_[i] = this.rollTarget_(); - } - } - } - - /** Release: freeze weights where they are (permanent). */ - release(): void { - this.active_ = false; - } - - private rollTarget_(): number { - return ( - this.params_.targetMin + - Math.random() * (this.params_.targetMax - this.params_.targetMin) - ); - } -} diff --git a/playground/src/ml/types.ts b/playground/src/ml/types.ts deleted file mode 100644 index 1d8b3de..0000000 --- a/playground/src/ml/types.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * TypeScript types matching the C API surface in `nisps/wasm/bindings.cpp`. - * - * These types are intentionally minimal: they describe the JS-visible shape - * of the Emscripten module, the heap views we read/write, and the per-layer - * stats record. They DO NOT mirror any internal C++ struct. - * - * The Emscripten glue produced by `scripts/build-wasm.sh` exposes a factory - * function, `createNispsModule(opts?) => Promise`, which we - * call from `wasm-iml.ts` and `wasm-worker.ts`. - */ - -/** - * Shape of the loaded WASM module — the subset we use. - * Emscripten generates more on it; we type only what we need. - * - * NOTE: `_*` prefixed methods are the raw exported C functions (Emscripten - * naming convention). They take/return numbers (pointers + primitives). - */ -export interface NispsModule { - // Memory views (re-bound after grow). - HEAP8: Int8Array; - HEAP16: Int16Array; - HEAP32: Int32Array; - HEAPU8: Uint8Array; - HEAPU16: Uint16Array; - HEAPU32: Uint32Array; - HEAPF32: Float32Array; - HEAPF64: Float64Array; - - _malloc(bytes: number): number; - _free(ptr: number): void; - - // ML lifecycle. - // Seed is a uint32_t (not 64-bit) — see bindings.cpp file comment. - _nisps_ml_create(input_size: number, output_size: number, hidden_ptr: number, n_hidden: number, seed: number): number; - _nisps_ml_destroy(ml: number): void; - _nisps_ml_reset(ml: number): void; - - // ML inference. - _nisps_ml_set_input(ml: number, idx: number, v: number): void; - _nisps_ml_process(ml: number): void; - _nisps_ml_outputs(ml: number): number; // returns float* into HEAPF32 - _nisps_ml_infer_batch(ml: number, points_ptr: number, n_points: number, out_ptr: number): void; - - // ML training. - _nisps_ml_add_example(ml: number, features_ptr: number, labels_ptr: number): void; - _nisps_ml_train(ml: number, lr: number, max_iter: number, min_err: number, sample_weights_ptr: number): number; - _nisps_ml_eval_loss(ml: number): number; - - // ML examples. - _nisps_ml_clear_examples(ml: number): void; - _nisps_ml_example_count(ml: number): number; - - // ML weights. - _nisps_ml_weight_count(ml: number): number; - _nisps_ml_get_weights(ml: number, out_ptr: number): void; - _nisps_ml_set_weights(ml: number, in_ptr: number): void; - _nisps_ml_draw_weights(ml: number, spread: number): void; - _nisps_ml_move_weights(ml: number, speed: number, spread: number, mask_ptr: number): void; - _nisps_ml_get_layer_stats(ml: number, out_ptr: number): void; - _nisps_ml_describe(out_ptr: number): void; - - // Engines. - _nisps_engine_create(id_ptr: number, sample_rate: number): number; - _nisps_engine_destroy(engine: number): void; - _nisps_engine_set_params(engine: number, params_ptr: number, n_params: number): void; - _nisps_engine_process_block( - engine: number, - in_l_ptr: number, in_r_ptr: number, - out_l_ptr: number, out_r_ptr: number, - n_samples: number, - ): void; -} - -/** Factory function exposed by the Emscripten glue. */ -export type NispsModuleFactory = (opts?: { - locateFile?: (path: string, prefix: string) => string; - wasmBinary?: ArrayBuffer | Uint8Array; - print?: (msg: string) => void; - printErr?: (msg: string) => void; -}) => Promise; - -/** Architecture descriptor returned from `nisps_ml_describe`. */ -export interface MLArchitecture { - inputSize: number; - hidden: [number, number, number]; - outputSize: number; - numLayers: number; -} - -/** Per-layer weight health record (one per layer). */ -export interface LayerStats { - meanAbs: number; - maxAbs: number; - deadFrac: number; - saturatingFrac: number; -} - -/** The `engine_id` strings the C++ side recognises. Anything else falls back to "thru". */ -export type EngineId = - | 'thru' - | 'paf_synth' - | 'channel_strip' - | 'xiasri' - | 'verb_fx' - | 'memlcelium' - | 'breakor' - | 'elysiamorf' - | 'analysis'; - -/** Message protocol between main thread and `wasm-worker.ts`. */ -export type WorkerRequest = - | { - kind: 'init'; - seed: number; - } - | { - kind: 'train'; - requestId: number; - // Flat features: nExamples * inputSize floats. - features: Float32Array; - // Flat labels: nExamples * outputSize floats. - labels: Float32Array; - // Optional per-example weights, sums to 1. Empty = uniform. - sampleWeights: Float32Array; - // Current weights to seed worker MLP. - weights: Float32Array; - lr: number; - maxIter: number; - minErr: number; - inputSize: number; - outputSize: number; - } - | { - kind: 'dispose'; - }; - -export type WorkerResponse = - | { - kind: 'ready'; - } - | { - kind: 'result'; - requestId: number; - loss: number; - weights: Float32Array; - // Loss curve (per-iteration). Currently always empty — the C++ MLP - // exposes loss_history but the WASM bridge does not yet plumb it. - lossHistory: Float32Array; - } - | { - kind: 'error'; - requestId: number; - message: string; - }; diff --git a/playground/src/ml/wasm-iml.ts b/playground/src/ml/wasm-iml.ts deleted file mode 100644 index 1f5b985..0000000 --- a/playground/src/ml/wasm-iml.ts +++ /dev/null @@ -1,650 +0,0 @@ -/** - * WasmIML — main-thread ML interface backed by `nisps.wasm`. - * - * Owns: - * - One `nisps.wasm` instance. - * - One MLP handle. - * - A JS-side `Dataset` (mirrors the C++ ring buffer; see dataset.ts). - * - Pre-allocated heap buffers for inputs/outputs/weights/etc. - * - A lazy `WasmTrainer` worker for off-thread async training. - * - * Threading model (architecture.md §6.4): - * - Inference + sync training run on this main-thread instance. - * - `trainAsync()` spawns/uses a worker which holds a SECOND wasm - * instance; weights round-trip through `getWeights()`/`setWeights()`. - * - * Side effects: every mutation that should be visible to the UI calls into - * `mlStore`. The store is the single source of truth for Solid components. - * - * Concept compatibility: this class re-implements the legacy WasmIML - * surface area documented in `recon/04-playground.md §6` so the existing - * Playwright debug-probe tests can keep passing once the new probe is - * wired up. - */ - -import { produce } from 'solid-js/store'; - -import { mlStore } from '../stores/ml-store'; -import { coreBus } from '../stores/bus'; -import { Dataset } from './dataset'; -import type { - LayerStats, - MLArchitecture, - NispsModule, - NispsModuleFactory, -} from './types'; -import { createTrainer, type WasmTrainer } from './wasm-worker'; - -const NISPS_JS_URL = '/nisps.js'; -const NISPS_WASM_URL = '/nisps.wasm'; - -/** Default architecture matches `nisps/wasm/bindings.cpp` instantiation. */ -const DEFAULT_INPUT_SIZE = 2; -const DEFAULT_OUTPUT_SIZE = 126; - -let cachedFactory: NispsModuleFactory | null = null; - -/** - * Load the Emscripten glue once, cache the factory. - * - * The glue is served from `playground/public/nisps.js` (committed). We - * dynamically import it so the WASM only loads when the ML system is first - * used — `/dev/primitives` doesn't pay for it. - */ -async function getFactory(): Promise { - if (cachedFactory) return cachedFactory; - // Vite serves `/nisps.js` as a normal asset; we use a dynamic-eval import - // to avoid Vite trying to resolve it at build time. - const url = new URL(NISPS_JS_URL, window.location.origin).toString(); - const mod = await import(/* @vite-ignore */ url); - // Emscripten MODULARIZE=1 default export key is `default`. - // tslint:disable-next-line:no-any - const factory = (mod as { default?: NispsModuleFactory; createNispsModule?: NispsModuleFactory }) - .default ?? (mod as { createNispsModule?: NispsModuleFactory }).createNispsModule; - if (!factory) throw new Error('[wasm-iml] nisps.js does not export a module factory'); - cachedFactory = factory; - return factory; -} - -/** Aligned float-array allocation helper. Returns ptr + a view. */ -class HeapBuffer { - readonly ptr: number; - readonly view: Float32Array; - constructor(private mod: NispsModule, public readonly count: number) { - this.ptr = mod._malloc(count * 4); - if (!this.ptr) throw new Error(`malloc(${count * 4}) failed`); - this.view = new Float32Array(mod.HEAPF32.buffer, this.ptr, count); - } - /** After memory growth, refresh the view onto the new ArrayBuffer. */ - rebind(): void { - // Re-create the view with the (possibly new) underlying buffer. - Object.defineProperty(this, 'view', { - value: new Float32Array(this.mod.HEAPF32.buffer, this.ptr, this.count), - writable: false, - }); - } - free(): void { - this.mod._free(this.ptr); - } -} - -class HeapU8 { - readonly ptr: number; - readonly view: Uint8Array; - constructor(private mod: NispsModule, public readonly count: number) { - this.ptr = mod._malloc(count); - if (!this.ptr) throw new Error(`malloc(${count}) failed`); - this.view = new Uint8Array(mod.HEAPU8.buffer, this.ptr, count); - } - rebind(): void { - Object.defineProperty(this, 'view', { - value: new Uint8Array(this.mod.HEAPU8.buffer, this.ptr, this.count), - writable: false, - }); - } - free(): void { - this.mod._free(this.ptr); - } -} - -export interface WasmIMLOptions { - inputSize?: number; - outputSize?: number; - hiddenLayers?: ReadonlyArray; - seed?: number; - /** localStorage key the loaded weights/dataset will be persisted under. */ - storageKey?: string; - maxExamples?: number; -} - -export class WasmIML { - // WASM binding state. - private module!: NispsModule; - private mlHandle = 0; - private weightCount_ = 0; - - // Architecture descriptor (resolved post-init from the WASM build). - private arch_: MLArchitecture = { - inputSize: DEFAULT_INPUT_SIZE, - hidden: [10, 14, 18], - outputSize: DEFAULT_OUTPUT_SIZE, - numLayers: 4, - }; - - // Pre-allocated heap buffers. - private featuresBuf!: HeapBuffer; - private labelsBuf!: HeapBuffer; - private weightsBuf!: HeapBuffer; - private statsBuf!: HeapBuffer; - private batchInBuf!: HeapBuffer; - private batchOutBuf!: HeapBuffer; - private pinMaskBuf!: HeapU8; - private describeBuf!: HeapBuffer; // 6 ints, reused as 6 floats on the heap is wrong; - // We use HEAP32 directly via a tiny scratch malloc: - private describePtr = 0; - - // JS-side state. - readonly dataset: Dataset; - private lastLoss_: number | null = null; - private trainer: WasmTrainer | null = null; - private storageKey: string; - private saveTimer: number | null = null; - private destroyed = false; - - static MAX_BATCH = 4096; - - private constructor(opts: WasmIMLOptions) { - this.dataset = new Dataset(opts.maxExamples ?? 100); - this.storageKey = opts.storageKey ?? 'nisps:wasm-iml'; - } - - /** - * Async factory. Loads the wasm, creates the MLP handle, allocates heap - * buffers, and rehydrates persisted state. - */ - static async create(opts: WasmIMLOptions = {}): Promise { - const inst = new WasmIML(opts); - await inst.init_(opts); - return inst; - } - - private async init_(opts: WasmIMLOptions): Promise { - const factory = await getFactory(); - this.module = await factory({ - // Vite serves /nisps.wasm at the root; the default locateFile would - // resolve relative to nisps.js (also at root) so this is the same - // result, but explicit is better. - locateFile: (path: string) => { - if (path.endsWith('.wasm')) return new URL(NISPS_WASM_URL, window.location.origin).toString(); - return path; - }, - }); - - // Resolve architecture via the WASM module (compile-time fixed; we - // stash the values for callers that need them). - this.describePtr = this.module._malloc(6 * 4); - this.module._nisps_ml_describe(this.describePtr); - const dims = new Int32Array(this.module.HEAP32.buffer, this.describePtr, 6); - this.arch_ = { - inputSize: dims[0], - hidden: [dims[1], dims[2], dims[3]], - outputSize: dims[4], - numLayers: dims[5], - }; - - // Caller-supplied dimensions are accepted but ignored; warn if mismatch. - const wantedIn = opts.inputSize ?? this.arch_.inputSize; - const wantedOut = opts.outputSize ?? this.arch_.outputSize; - if (wantedIn !== this.arch_.inputSize || wantedOut !== this.arch_.outputSize) { - console.warn( - `[wasm-iml] requested ${wantedIn}->${wantedOut} but WASM build is fixed at ` + - `${this.arch_.inputSize}->${this.arch_.outputSize}; extras are ignored.`, - ); - } - - // Create the MLP. We pass dummy hidden ptr/count — the binding ignores them. - const seed = (opts.seed ?? (Date.now() >>> 0)) >>> 0; - this.mlHandle = this.module._nisps_ml_create( - this.arch_.inputSize, - this.arch_.outputSize, - 0, // hidden ptr (unused) - 0, // n_hidden (unused) - seed, - ); - if (!this.mlHandle) throw new Error('[wasm-iml] nisps_ml_create returned null'); - - this.weightCount_ = this.module._nisps_ml_weight_count(this.mlHandle); - - // Heap buffers (created after we know the architecture). - this.featuresBuf = new HeapBuffer(this.module, this.arch_.inputSize); - this.labelsBuf = new HeapBuffer(this.module, this.arch_.outputSize); - this.weightsBuf = new HeapBuffer(this.module, this.weightCount_); - this.statsBuf = new HeapBuffer(this.module, this.arch_.numLayers * 4); - this.batchInBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.inputSize); - this.batchOutBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.outputSize); - this.pinMaskBuf = new HeapU8(this.module, this.arch_.outputSize); - - // Push initial state to the store. - mlStore.__setState(produce((s) => { - s.inputSize = this.arch_.inputSize; - s.outputSize = this.arch_.outputSize; - s.exampleCount = 0; - s.lastLoss = null; - s.lossHistory = []; - s.training = false; - s.ready = true; - })); - mlStore.__setOutputs(new Float32Array(this.arch_.outputSize)); - this.publishWeights_(); - - // Load persisted state if present (best-effort). - this.tryLoadFromStorage_(); - } - - // ------------------------------------------------------------------- - // Lifecycle - // ------------------------------------------------------------------- - - dispose(): void { - if (this.destroyed) return; - this.destroyed = true; - if (this.saveTimer !== null) { - clearTimeout(this.saveTimer); - this.saveTimer = null; - } - if (this.trainer) { - this.trainer.dispose(); - this.trainer = null; - } - if (this.module && this.mlHandle) { - this.module._nisps_ml_destroy(this.mlHandle); - this.mlHandle = 0; - } - if (this.featuresBuf) this.featuresBuf.free(); - if (this.labelsBuf) this.labelsBuf.free(); - if (this.weightsBuf) this.weightsBuf.free(); - if (this.statsBuf) this.statsBuf.free(); - if (this.batchInBuf) this.batchInBuf.free(); - if (this.batchOutBuf) this.batchOutBuf.free(); - if (this.pinMaskBuf) this.pinMaskBuf.free(); - if (this.describePtr) this.module._free(this.describePtr); - mlStore.__setState(produce((s) => { - s.ready = false; - })); - } - - get architecture(): MLArchitecture { - return this.arch_; - } - get weightCount(): number { - return this.weightCount_; - } - get exampleCount(): number { - return this.dataset.size; - } - get lastLoss(): number | null { - return this.lastLoss_; - } - - // ------------------------------------------------------------------- - // Inference - // ------------------------------------------------------------------- - - setInput(idx: number, value: number): void { - this.module._nisps_ml_set_input(this.mlHandle, idx, value); - } - - process(): Float32Array { - this.module._nisps_ml_process(this.mlHandle); - const ptr = this.module._nisps_ml_outputs(this.mlHandle); - // Copy out so the caller can hold onto it across memory growth. - const view = new Float32Array(this.module.HEAPF32.buffer, ptr, this.arch_.outputSize); - const out = new Float32Array(view); // copy - mlStore.__setOutputs(out); - return out; - } - - /** Convenience: setInput(0,x); setInput(1,y); process(). */ - inferXY(x: number, y: number): Float32Array { - this.setInput(0, x); - this.setInput(1, y); - return this.process(); - } - - /** - * Batch inference. `points` is an array of [x,y] tuples (or any vector - * length <= inputSize; trailing entries zero-padded). Returns a flat - * Float32Array of length n * outputSize. - * - * Larger requests than `MAX_BATCH` are chunked transparently. - */ - inferBatch(points: ReadonlyArray>): Float32Array { - const n = points.length; - const inSz = this.arch_.inputSize; - const outSz = this.arch_.outputSize; - const result = new Float32Array(n * outSz); - - let written = 0; - for (let offset = 0; offset < n; offset += WasmIML.MAX_BATCH) { - const chunk = Math.min(WasmIML.MAX_BATCH, n - offset); - // Pack into batchInBuf. - for (let i = 0; i < chunk; ++i) { - const src = points[offset + i]; - const base = i * inSz; - for (let j = 0; j < inSz; ++j) this.batchInBuf.view[base + j] = src[j] ?? 0; - } - this.module._nisps_ml_infer_batch( - this.mlHandle, - this.batchInBuf.ptr, - chunk, - this.batchOutBuf.ptr, - ); - // Copy out into result. - const slice = this.batchOutBuf.view.subarray(0, chunk * outSz); - result.set(slice, written); - written += chunk * outSz; - } - return result; - } - - // ------------------------------------------------------------------- - // Training - // ------------------------------------------------------------------- - - /** - * Add a feature/label pair to BOTH the JS dataset and the WASM ring - * buffer. The two stay in sync because every train() call pushes the - * full JS dataset back into WASM (in case of weight recompute, undo - * restore, etc.). For the sake of correctness, we re-sync on each add - * too — cheap relative to training. - */ - addExample(features: ReadonlyArray, labels: ReadonlyArray): boolean { - const ok = this.dataset.add(features, labels); - if (!ok) return false; - this.copyExampleToWasm_(features, labels); - mlStore.__setState(produce((s) => { - s.exampleCount = this.dataset.size; - })); - coreBus.emit('ml.example_added', { count: this.dataset.size }); - this.scheduleSave_(); - return true; - } - - private copyExampleToWasm_(features: ReadonlyArray, labels: ReadonlyArray): void { - const fv = this.featuresBuf.view; - const lv = this.labelsBuf.view; - const inSz = this.arch_.inputSize; - const outSz = this.arch_.outputSize; - for (let i = 0; i < inSz; ++i) fv[i] = features[i] ?? 0; - for (let i = 0; i < outSz; ++i) lv[i] = labels[i] ?? 0; - this.module._nisps_ml_add_example(this.mlHandle, this.featuresBuf.ptr, this.labelsBuf.ptr); - } - - /** - * Synchronous training. Returns the final loss (also stored in - * `lastLoss`). Updates `mlStore.lastLoss` and emits `ml.trained`. - * - * Caller can pass per-sample weights; if omitted the WASM side uses - * uniform 1/n weighting. - */ - train(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): number { - if (this.dataset.isEmpty()) { - this.lastLoss_ = 0; - mlStore.__setState(produce((s) => { s.lastLoss = 0; })); - return 0; - } - - let weightsPtr = 0; - let weightsHandle: HeapBuffer | null = null; - if (sampleWeights && sampleWeights.length === this.dataset.size) { - weightsHandle = new HeapBuffer(this.module, sampleWeights.length); - weightsHandle.view.set(sampleWeights); - weightsPtr = weightsHandle.ptr; - } - - mlStore.__setState(produce((s) => { s.training = true; })); - let loss = 0; - try { - loss = this.module._nisps_ml_train(this.mlHandle, lr, maxIter, minErr, weightsPtr); - } finally { - if (weightsHandle) weightsHandle.free(); - mlStore.__setState(produce((s) => { s.training = false; })); - } - - this.lastLoss_ = loss; - mlStore.__setState(produce((s) => { - s.lastLoss = loss; - // The C++ MLP stores per-iter history but we don't currently expose - // it via the WASM bindings. Stream 9 may add nisps_ml_loss_history. - s.lossHistory = [loss]; - })); - this.publishWeights_(); - coreBus.emit('ml.trained', { loss }); - this.scheduleSave_(); - return loss; - } - - /** - * Async training via worker. The worker holds a SECOND wasm instance, - * receives current weights + dataset, runs SGD, and returns updated - * weights. Main-thread weights are then `setWeights()`-restored. - */ - async trainAsync(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): Promise { - if (this.dataset.isEmpty()) { - this.lastLoss_ = 0; - return 0; - } - if (!this.trainer) this.trainer = await createTrainer(); - - const weights = this.getWeights(); - const features = new Float32Array(this.dataset.featuresFlat()); - const labels = new Float32Array(this.dataset.labelsFlat()); - const sw = sampleWeights ? new Float32Array(sampleWeights) : new Float32Array(0); - - mlStore.__setState(produce((s) => { s.training = true; })); - try { - const result = await this.trainer.train({ - weights, - features, - labels, - sampleWeights: sw, - lr, - maxIter, - minErr, - inputSize: this.arch_.inputSize, - outputSize: this.arch_.outputSize, - }); - this.setWeights(result.weights); - this.lastLoss_ = result.loss; - mlStore.__setState(produce((s) => { - s.lastLoss = result.loss; - s.lossHistory = Array.from(result.lossHistory); - })); - coreBus.emit('ml.trained', { loss: result.loss }); - this.scheduleSave_(); - return result.loss; - } finally { - mlStore.__setState(produce((s) => { s.training = false; })); - } - } - - /** Non-destructive loss query. */ - evalLoss(): number { - return this.module._nisps_ml_eval_loss(this.mlHandle); - } - - clearExamples(): void { - this.dataset.clear(); - this.module._nisps_ml_clear_examples(this.mlHandle); - mlStore.__setState(produce((s) => { s.exampleCount = 0; })); - coreBus.emit('ml.examples_cleared', undefined); - this.scheduleSave_(); - } - - // ------------------------------------------------------------------- - // RL ops - // ------------------------------------------------------------------- - - randomiseWeights(spread = 0.6): void { - this.module._nisps_ml_draw_weights(this.mlHandle, spread); - this.publishWeights_(); - coreBus.emit('ml.delta_update', { reason: 'randomize' }); - this.scheduleSave_(); - } - - moveWeights(speed: number, spread: number, pinMask?: Uint8Array): void { - let maskPtr = 0; - if (pinMask) { - const sz = Math.min(pinMask.length, this.arch_.outputSize); - for (let i = 0; i < sz; ++i) this.pinMaskBuf.view[i] = pinMask[i]; - for (let i = sz; i < this.arch_.outputSize; ++i) this.pinMaskBuf.view[i] = 0; - maskPtr = this.pinMaskBuf.ptr; - } - this.module._nisps_ml_move_weights(this.mlHandle, speed, spread, maskPtr); - this.publishWeights_(); - // The caller (RL handler) decides whether this is a thumbs-up/down; - // we emit a generic delta_update. - coreBus.emit('ml.delta_update', { reason: 'thumbs_down' }); - } - - // ------------------------------------------------------------------- - // Weights I/O - // ------------------------------------------------------------------- - - getWeights(): Float32Array { - this.module._nisps_ml_get_weights(this.mlHandle, this.weightsBuf.ptr); - // Copy out so caller can mutate freely. - return new Float32Array(this.weightsBuf.view); - } - - setWeights(w: Float32Array | Uint8Array): void { - if (w.length < this.weightCount_) { - throw new Error(`setWeights: expected ${this.weightCount_} floats, got ${w.length}`); - } - this.weightsBuf.view.set(w as Float32Array, 0); - this.module._nisps_ml_set_weights(this.mlHandle, this.weightsBuf.ptr); - this.publishWeights_(); - } - - /** Per-layer weight stats. Returns one record per layer. */ - getLayerStats(): LayerStats[] { - this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr); - const out: LayerStats[] = []; - for (let i = 0; i < this.arch_.numLayers; ++i) { - const base = i * 4; - out.push({ - meanAbs: this.statsBuf.view[base], - maxAbs: this.statsBuf.view[base + 1], - deadFrac: this.statsBuf.view[base + 2], - saturatingFrac: this.statsBuf.view[base + 3], - }); - } - return out; - } - - /** Flat layer-stats Float32Array (numLayers * 4). For probe API. */ - getLayerStatsFlat(): Float32Array { - this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr); - return new Float32Array(this.statsBuf.view); - } - - // ------------------------------------------------------------------- - // Misc - // ------------------------------------------------------------------- - - reset(): void { - this.module._nisps_ml_reset(this.mlHandle); - this.dataset.clear(); - this.lastLoss_ = null; - mlStore.__setState(produce((s) => { - s.exampleCount = 0; - s.lastLoss = null; - s.lossHistory = []; - })); - this.publishWeights_(); - coreBus.emit('ml.examples_cleared', undefined); - this.scheduleSave_(); - } - - // ------------------------------------------------------------------- - // Persistence - // ------------------------------------------------------------------- - - private scheduleSave_(): void { - if (this.saveTimer !== null) clearTimeout(this.saveTimer); - this.saveTimer = window.setTimeout(() => this.saveNow(), 500); - } - - saveNow(): void { - if (this.destroyed) return; - if (this.saveTimer !== null) { - clearTimeout(this.saveTimer); - this.saveTimer = null; - } - try { - const weights = this.getWeights(); - const payload = { - v: 1, - arch: this.arch_, - weights: Array.from(weights), - features: Array.from(this.dataset.featuresFlat()), - labels: Array.from(this.dataset.labelsFlat()), - size: this.dataset.size, - lastLoss: this.lastLoss_, - }; - localStorage.setItem(this.storageKey, JSON.stringify(payload)); - } catch (err) { - // localStorage might be full or unavailable; not fatal. - console.warn('[wasm-iml] saveNow failed:', err); - } - } - - private tryLoadFromStorage_(): void { - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) return; - const payload = JSON.parse(raw) as { - v: number; - weights: number[]; - features: number[]; - labels: number[]; - size: number; - lastLoss: number | null; - }; - if (payload.v !== 1) return; - // Restore dataset (rebuild via add()). - const inSz = this.arch_.inputSize; - const outSz = this.arch_.outputSize; - if (payload.size > 0 && payload.features.length === payload.size * inSz && - payload.labels.length === payload.size * outSz) { - for (let i = 0; i < payload.size; ++i) { - const f = payload.features.slice(i * inSz, (i + 1) * inSz); - const l = payload.labels.slice(i * outSz, (i + 1) * outSz); - this.dataset.add(f, l); - // Also push to the WASM ring buffer. - this.copyExampleToWasm_(f, l); - } - } - // Restore weights. - if (payload.weights.length === this.weightCount_) { - this.setWeights(new Float32Array(payload.weights)); - } - this.lastLoss_ = payload.lastLoss; - mlStore.__setState(produce((s) => { - s.exampleCount = this.dataset.size; - s.lastLoss = this.lastLoss_; - })); - } catch (err) { - console.warn('[wasm-iml] tryLoadFromStorage failed:', err); - } - } - - // ------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------- - - private publishWeights_(): void { - const w = this.getWeights(); - mlStore.__setWeights(w); - } -} diff --git a/playground/src/ml/wasm-worker.ts b/playground/src/ml/wasm-worker.ts deleted file mode 100644 index 5d3db63..0000000 --- a/playground/src/ml/wasm-worker.ts +++ /dev/null @@ -1,334 +0,0 @@ -/** - * Disposable Web Worker that runs SGD off the main thread. - * - * The worker holds its own `nisps.wasm` instance (architecture.md §6.4). - * The main thread sends: - * - current weights (so the worker is in the same state as the UI), - * - dataset (features + labels), - * - SGD hyperparameters, - * and receives updated weights + final loss. - * - * This module exposes: - * - `createTrainer()` - factory that spawns the worker, loads its WASM, - * and returns a `WasmTrainer` handle. - * - The worker entry-point itself (when this file runs in a Worker). - * - * The worker is implemented inline so a single TS file becomes both the - * main-thread API and the worker bundle. Vite's `new Worker(new URL(..., - * import.meta.url))` pattern packs it correctly. - * - * Lifecycle: each `WasmTrainer` is disposable via `.dispose()` which - * terminates the worker. Tests should always dispose to avoid leaks. - */ - -import type { NispsModule, NispsModuleFactory, WorkerRequest, WorkerResponse } from './types'; - -// --------------------------------------------------------------------------- -// Main-thread side -// --------------------------------------------------------------------------- - -export interface TrainArgs { - weights: Float32Array; - features: Float32Array; - labels: Float32Array; - /** Optional; pass empty for uniform weighting. */ - sampleWeights: Float32Array; - lr: number; - maxIter: number; - minErr: number; - inputSize: number; - outputSize: number; -} - -export interface TrainResult { - loss: number; - weights: Float32Array; - lossHistory: Float32Array; -} - -export class WasmTrainer { - private worker: Worker; - private nextId = 1; - private pending = new Map void; reject: (e: unknown) => void }>(); - private disposed = false; - - static async create(): Promise { - const trainer = new WasmTrainer(); - await trainer.init_(); - return trainer; - } - - private constructor() { - this.worker = new Worker(new URL('./wasm-worker.ts', import.meta.url), { type: 'module' }); - this.worker.onmessage = (ev) => this.onMessage_(ev.data as WorkerResponse); - this.worker.onerror = (ev) => { - // Fail any pending requests. - for (const { reject } of this.pending.values()) reject(ev.message ?? 'worker error'); - this.pending.clear(); - }; - } - - private init_(): Promise { - return new Promise((resolve, reject) => { - const handler = (ev: MessageEvent) => { - const msg = ev.data as WorkerResponse; - if (msg.kind === 'ready') { - this.worker.removeEventListener('message', handler); - resolve(); - } else if (msg.kind === 'error') { - this.worker.removeEventListener('message', handler); - reject(new Error(msg.message)); - } - }; - this.worker.addEventListener('message', handler); - const seed = (Date.now() ^ Math.floor(Math.random() * 0xffffffff)) >>> 0; - this.worker.postMessage({ kind: 'init', seed } satisfies WorkerRequest); - }); - } - - train(args: TrainArgs): Promise { - if (this.disposed) return Promise.reject(new Error('WasmTrainer disposed')); - const requestId = this.nextId++; - return new Promise((resolve, reject) => { - this.pending.set(requestId, { resolve, reject }); - const msg: WorkerRequest = { - kind: 'train', - requestId, - weights: args.weights, - features: args.features, - labels: args.labels, - sampleWeights: args.sampleWeights, - lr: args.lr, - maxIter: args.maxIter, - minErr: args.minErr, - inputSize: args.inputSize, - outputSize: args.outputSize, - }; - // Transfer all the typed-array buffers we no longer need on the - // main thread; faster than copying. Caller has already cloned. - this.worker.postMessage(msg, [ - args.weights.buffer, - args.features.buffer, - args.labels.buffer, - args.sampleWeights.buffer, - ]); - }); - } - - dispose(): void { - if (this.disposed) return; - this.disposed = true; - try { - this.worker.postMessage({ kind: 'dispose' } satisfies WorkerRequest); - } catch { - /* ignore */ - } - this.worker.terminate(); - for (const { reject } of this.pending.values()) reject(new Error('disposed')); - this.pending.clear(); - } - - private onMessage_(msg: WorkerResponse): void { - if (msg.kind === 'result') { - const p = this.pending.get(msg.requestId); - if (p) { - this.pending.delete(msg.requestId); - p.resolve({ loss: msg.loss, weights: msg.weights, lossHistory: msg.lossHistory }); - } - } else if (msg.kind === 'error') { - const p = this.pending.get(msg.requestId); - if (p) { - this.pending.delete(msg.requestId); - p.reject(new Error(msg.message)); - } - } - // 'ready' handled in init_(). - } -} - -export function createTrainer(): Promise { - return WasmTrainer.create(); -} - -// --------------------------------------------------------------------------- -// Worker-thread side -// --------------------------------------------------------------------------- -// -// When this module is loaded in a Worker, `self` is `WorkerGlobalScope` and -// `window` is undefined. We use that as the dispatch. -// -// We import the wasm via a same-origin fetch (no DOM available so we can't -// use a regular import URL — but Vite's bundler treats `new Worker(...)` -// specially and `?url` imports work for assets). - -// Inside a Worker, `self` is a `DedicatedWorkerGlobalScope`. To keep TS -// happy in both build contexts we use a structural cast. -declare const self: { - postMessage: (msg: unknown, transfer?: Transferable[]) => void; - addEventListener: (event: string, handler: (ev: MessageEvent) => void) => void; - location: { origin: string }; - importScripts?: unknown; -}; - -const isWorker = - typeof window === 'undefined' && - typeof self !== 'undefined' && - typeof (self as { importScripts?: unknown }).importScripts !== 'undefined'; - -if (isWorker) { - // Module-level state in worker scope. - let mod: NispsModule | null = null; - let mlHandle = 0; - let weightCount = 0; - - // Heap buffers (allocated on first train). - let weightsPtr = 0; - let weightsViewLen = 0; - let featuresPtr = 0; - let featuresLen = 0; - let labelsPtr = 0; - let labelsLen = 0; - let sampleWeightsPtr = 0; - let sampleWeightsLen = 0; - - async function loadModule(seed: number): Promise { - // Same-origin fetch to /nisps.js. The worker is served by the dev - // server with COOP/COEP set, so this works. - const factoryMod = await import(/* @vite-ignore */ new URL('/nisps.js', self.location.origin).toString()); - const factory: NispsModuleFactory = - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (factoryMod as any).default ?? (factoryMod as any).createNispsModule; - mod = await factory({ - locateFile: (path: string) => { - if (path.endsWith('.wasm')) return new URL('/nisps.wasm', self.location.origin).toString(); - return path; - }, - }); - mlHandle = mod._nisps_ml_create(0, 0, 0, 0, seed >>> 0); - weightCount = mod._nisps_ml_weight_count(mlHandle); - } - - function ensureBuffers(features: Float32Array, labels: Float32Array, sampleWeights: Float32Array, weights: Float32Array): void { - if (!mod) throw new Error('worker module not loaded'); - - if (weightsViewLen !== weightCount) { - if (weightsPtr) mod._free(weightsPtr); - weightsPtr = mod._malloc(weightCount * 4); - weightsViewLen = weightCount; - } - if (features.length !== featuresLen) { - if (featuresPtr) mod._free(featuresPtr); - featuresPtr = mod._malloc(features.length * 4); - featuresLen = features.length; - } - if (labels.length !== labelsLen) { - if (labelsPtr) mod._free(labelsPtr); - labelsPtr = mod._malloc(labels.length * 4); - labelsLen = labels.length; - } - if (sampleWeights.length !== sampleWeightsLen) { - if (sampleWeightsPtr) mod._free(sampleWeightsPtr); - sampleWeightsPtr = sampleWeights.length > 0 ? mod._malloc(sampleWeights.length * 4) : 0; - sampleWeightsLen = sampleWeights.length; - } - - new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount).set(weights); - new Float32Array(mod.HEAPF32.buffer, featuresPtr, features.length).set(features); - new Float32Array(mod.HEAPF32.buffer, labelsPtr, labels.length).set(labels); - if (sampleWeightsPtr) { - new Float32Array(mod.HEAPF32.buffer, sampleWeightsPtr, sampleWeights.length).set(sampleWeights); - } - } - - function trainOnce(req: Extract): WorkerResponse { - if (!mod) { - return { kind: 'error', requestId: req.requestId, message: 'worker not initialised' }; - } - try { - ensureBuffers(req.features, req.labels, req.sampleWeights, req.weights); - // Push current weights into our MLP. - mod._nisps_ml_set_weights(mlHandle, weightsPtr); - - // Seed the example ring buffer. We must clear first because past - // train calls may have left examples there. - mod._nisps_ml_clear_examples(mlHandle); - const inSz = req.inputSize; - const outSz = req.outputSize; - const n = req.features.length / inSz; - // Allocate small per-example scratch (re-used across iterations of - // this loop). - // We use stack-equivalents by allocating once, then shifting pointers. - for (let i = 0; i < n; ++i) { - const fPtr = featuresPtr + i * inSz * 4; - const lPtr = labelsPtr + i * outSz * 4; - mod._nisps_ml_add_example(mlHandle, fPtr, lPtr); - } - - // Run training. - const swPtr = req.sampleWeights.length > 0 ? sampleWeightsPtr : 0; - const loss = mod._nisps_ml_train(mlHandle, req.lr, req.maxIter, req.minErr, swPtr); - - // Read out final weights. - mod._nisps_ml_get_weights(mlHandle, weightsPtr); - const view = new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount); - const outWeights = new Float32Array(view); // copy - - // Loss history not yet plumbed via WASM; emit just final loss. - const lossHistory = new Float32Array([loss]); - - return { - kind: 'result', - requestId: req.requestId, - loss, - weights: outWeights, - lossHistory, - }; - } catch (err) { - return { - kind: 'error', - requestId: req.requestId, - message: err instanceof Error ? err.message : String(err), - }; - } - } - - function disposeModule(): void { - if (!mod) return; - if (mlHandle) { - mod._nisps_ml_destroy(mlHandle); - mlHandle = 0; - } - if (weightsPtr) { mod._free(weightsPtr); weightsPtr = 0; } - if (featuresPtr) { mod._free(featuresPtr); featuresPtr = 0; } - if (labelsPtr) { mod._free(labelsPtr); labelsPtr = 0; } - if (sampleWeightsPtr) { mod._free(sampleWeightsPtr); sampleWeightsPtr = 0; } - mod = null; - } - - self.addEventListener('message', async (ev: MessageEvent) => { - const req = ev.data; - if (req.kind === 'init') { - try { - await loadModule(req.seed); - self.postMessage({ kind: 'ready' } satisfies WorkerResponse); - } catch (err) { - self.postMessage({ - kind: 'error', - requestId: 0, - message: err instanceof Error ? err.message : String(err), - } satisfies WorkerResponse); - } - } else if (req.kind === 'train') { - const res = trainOnce(req); - // Transfer weights back to main thread to avoid copy. - if (res.kind === 'result') { - self.postMessage(res, [res.weights.buffer, res.lossHistory.buffer]); - } else { - self.postMessage(res); - } - } else if (req.kind === 'dispose') { - disposeModule(); - // Worker terminates from main thread side via .terminate(). - } - }); -} diff --git a/playground/src/modes/BreakOrMode.tsx b/playground/src/modes/BreakOrMode.tsx deleted file mode 100644 index 34a543c..0000000 --- a/playground/src/modes/BreakOrMode.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * BreakOrMode — drum/breakbeat synthesis (xy_pad input, 56 outputs). - */ - -import { Component } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { XYPad } from '../primitives/XYPad'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { BreakorSchema } from './generated/breakor_schema'; - -export const BreakOrMode: Component = () => { - const schema = BreakorSchema; - const runtime = useModeRuntime(schema); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Drag through drum space. - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default BreakOrMode; diff --git a/playground/src/modes/C15Mode.tsx b/playground/src/modes/C15Mode.tsx deleted file mode 100644 index 2aee72e..0000000 --- a/playground/src/modes/C15Mode.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/** - * C15Mode — browser-only stub. - * - * The C15 (Nonlinear Labs C15) WASM synthesizer is not yet ported into the - * SolidJS playground; only firmware modes have schemas + WASM engines so - * far. This file exists so the mode switcher can list all eight firmware - * modes plus a "C15" entry that surfaces a clear "TODO" rather than 404'ing. - * - * Stream 11+ is expected to add a `c15` schema and wire the existing - * `playground/c15` directory into `nisps/wasm/engines/`. - */ - -import { Component } from 'solid-js'; - -export const C15Mode: Component = () => { - return ( -
-

C15 mode — TODO

-

- The C15 WASM synthesizer hasn't been ported into the SolidJS rewrite - yet. The legacy bridge lives in playground/c15/ and - playground/js/synth/c15-bridge.js; stream 11+ will wrap - it as an AudioWorklet engine alongside the firmware-derived ones. -

-

- Until then, pick one of the firmware modes (PAFSynth, Channel Strip, - Verb FX, etc.) from the mode switcher above. -

-
- ); -}; - -export default C15Mode; diff --git a/playground/src/modes/ChannelStripMode.tsx b/playground/src/modes/ChannelStripMode.tsx deleted file mode 100644 index eee732f..0000000 --- a/playground/src/modes/ChannelStripMode.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** - * ChannelStripMode — channel-strip processor controlled by joystick. - * - * 24 outputs feed EQ / dynamics / gain. Settings drawer is the default - * SettingsDrawer (input/training/exploration/output/overrides/advanced). - */ - -import { Component } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { VirtualJoystick } from '../primitives/VirtualJoystick'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { ChannelStripSchema } from './generated/channel_strip_schema'; - -export const ChannelStripMode: Component = () => { - const schema = ChannelStripSchema; - const runtime = useModeRuntime(schema); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Mix EQ + dynamics by moving the joystick. - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default ChannelStripMode; diff --git a/playground/src/modes/ElysiamorfMode.tsx b/playground/src/modes/ElysiamorfMode.tsx deleted file mode 100644 index 4085019..0000000 --- a/playground/src/modes/ElysiamorfMode.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * ElysiamorfMode — granular / morphing synth (xy_pad input, 40 outputs). - */ - -import { Component } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { XYPad } from '../primitives/XYPad'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { ElysiamorfSchema } from './generated/elysiamorf_schema'; - -export const ElysiamorfMode: Component = () => { - const schema = ElysiamorfSchema; - const runtime = useModeRuntime(schema); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Drag to morph through grain space. - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default ElysiamorfMode; diff --git a/playground/src/modes/MEMLCeliumMode.tsx b/playground/src/modes/MEMLCeliumMode.tsx deleted file mode 100644 index 9ee74b7..0000000 --- a/playground/src/modes/MEMLCeliumMode.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** - * MEMLCeliumMode — uSEQ-Celium dual-MLP CV/gate output (56 outputs). - */ - -import { Component, createSignal } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { XYPad } from '../primitives/XYPad'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { MemlceliumSchema } from './generated/memlcelium_schema'; - -export const MEMLCeliumMode: Component = () => { - const schema = MemlceliumSchema; - const runtime = useModeRuntime(schema); - const [voiceSpace, setVoiceSpace] = createSignal(0); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Sculpt voice + CV/gate. CV/gate streaming over USB serial requires - firmware (browser preview only). - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default MEMLCeliumMode; diff --git a/playground/src/modes/ModeShell.module.css b/playground/src/modes/ModeShell.module.css deleted file mode 100644 index 11568a2..0000000 --- a/playground/src/modes/ModeShell.module.css +++ /dev/null @@ -1,162 +0,0 @@ -.shell { - display: grid; - grid-template-rows: auto 1fr auto auto; - gap: var(--sp-4); - min-height: 100%; - padding: var(--sp-4); - background: var(--bg); - color: var(--fg); -} - -.header { - display: flex; - align-items: center; - gap: var(--sp-3); - padding-bottom: var(--sp-3); - border-bottom: 1px solid var(--line); -} - -.title { - font-size: var(--fs-lg); - font-weight: 600; - color: var(--accent); - margin: 0; -} - -.subtitle { - font-size: var(--fs-sm); - color: var(--fg-mute); - margin: 0; -} - -.audioToggle { - margin-left: auto; - display: flex; - align-items: center; - gap: var(--sp-2); -} - -.audioBtn { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: var(--sp-2) var(--sp-3); - font-size: var(--fs-sm); - color: var(--fg); - cursor: pointer; -} - -.audioBtn.on { - border-color: var(--accent); - color: var(--accent); -} - -.audioBtn:hover { - background: var(--bg-3); -} - -.voiceSpaces { - display: flex; - align-items: center; - gap: var(--sp-2); -} - -.voiceSpacesLabel { - font-size: var(--fs-xs); - color: var(--fg-mute); - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.body { - display: grid; - grid-template-columns: minmax(320px, 1fr) minmax(280px, 380px); - gap: var(--sp-4); - align-items: start; -} - -@media (max-width: 900px) { - .body { - grid-template-columns: 1fr; - } -} - -.primaryArea { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--sp-3); - padding: var(--sp-4); - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--r-2); - min-height: 280px; -} - -.outputArea { - display: flex; - flex-direction: column; - gap: var(--sp-3); - padding: var(--sp-3); - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--r-2); - min-height: 200px; -} - -.controls { - display: flex; - gap: var(--sp-4); - flex-wrap: wrap; - align-items: stretch; - padding: var(--sp-3); - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--r-2); -} - -.controlAxes { - flex: 1 1 auto; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: var(--sp-3); -} - -.trainingPanel { - flex: 0 0 auto; -} - -.drawerToggleBar { - display: flex; - gap: var(--sp-2); - justify-content: flex-end; -} - -.drawerToggleBtn { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: var(--sp-2) var(--sp-3); - cursor: pointer; - font-size: var(--fs-sm); - color: var(--fg-mute); -} - -.drawerToggleBtn:hover { - color: var(--fg); - background: var(--bg-3); -} - -.statusLine { - font-size: var(--fs-xs); - color: var(--fg-dim); - font-family: var(--font-mono); -} - -.frozen { - color: var(--accent-2); -} - -.notReady { - color: var(--warn); -} diff --git a/playground/src/modes/ModeShell.tsx b/playground/src/modes/ModeShell.tsx deleted file mode 100644 index 0f6f9fd..0000000 --- a/playground/src/modes/ModeShell.tsx +++ /dev/null @@ -1,300 +0,0 @@ -/** - * ModeShell — common scaffolding shared by every concrete mode. - * - * Provides: - * - Header (mode name, voice space pill, audio start/stop, drawer button). - * - Primary input area (mode-supplied) + JoyMap navigator on its right. - * - Output area (mode-supplied). - * - Control axes bar (Boldness/Memory/Precision wired to controlStore). - * - Training controls — undo, A/B, region/param pin all wired through. - * - SettingsDrawer (collapsible sections, per-param overrides, advanced). - * - * Modes only have to author the primary input + output JSX. Everything - * else is owned here so behaviour stays consistent across modes. - */ - -import { Component, createSignal, JSX, Show, For, createMemo } from 'solid-js'; -import { TrainingControls } from '../primitives/TrainingControls'; -import { ControlAxis } from '../primitives/ControlAxis'; -import { PillToggle } from '../primitives/PillToggle'; -import { Drawer } from '../primitives/Drawer'; -import { JoyMap } from '../primitives/JoyMap'; -import { controlStore } from '../stores/control-store'; -import { inputStore } from '../stores/input-store'; -import { sessionStore } from '../stores/session-store'; -import { explorationStore } from '../stores/exploration-store'; -import type { ModeRuntime } from './mode-runtime'; -import type { ModeSchema } from './generated'; -import { SettingsDrawer } from './SettingsDrawer'; -import styles from './ModeShell.module.css'; - -export interface ModeShellProps { - schema: ModeSchema; - runtime: ModeRuntime; - /** Primary input renderer (joystick / xy-pad / etc.). */ - primaryInput: () => JSX.Element; - /** Output / visualisation area. */ - outputArea: () => JSX.Element; - /** Optional drawer body for mode-specific settings (replaces default). */ - drawerContent?: () => JSX.Element; - drawerTitle?: string; - /** Active voice space index (only used if schema.voice_spaces is non-empty). */ - activeVoiceSpace?: () => number; - onVoiceSpaceChange?: (idx: number) => void; -} - -export const ModeShell: Component = (props) => { - const [drawerOpen, setDrawerOpen] = createSignal(false); - const [snapshotPopupOpen, setSnapshotPopupOpen] = createSignal(false); - const showVoiceSpaces = () => - props.schema.ui.show_voice_space_selector && props.schema.voice_spaces.length > 0; - - const voiceSpaceOptions = () => - props.schema.voice_spaces.map((label, idx) => ({ - value: String(idx), - label, - })); - - const noiseRings = createMemo(() => { - // Outer = noiseCap, inner = noiseLevel — both as fractions of half-window. - const cap = explorationStore.state.noiseCap; - const cur = explorationStore.state.noiseLevel; - if (cap <= 0) return null; - return [cap * 0.4, cur * 0.4] as const; - }); - - return ( -
-
-
-

{formatModeName(props.schema.mode_id)}

-

- {props.schema.ml.input_size} in → {props.schema.ml.output_size} out - {' · '} - engine: {props.schema.engine_id} -

-
- - -
- Voice space - String(props.activeVoiceSpace?.() ?? 0)} - onChange={(v) => props.onVoiceSpaceChange?.(Number(v))} - ariaLabel="Voice space" - /> -
-
- -
- - void props.runtime.mic.start()} - aria-label="Enable microphone" - >🎤 Mic - } - > - - - - void props.runtime.audio.start()} - aria-label="Start audio engine" - >▶ Start audio - } - > - - - -
-
- -
-
- {props.primaryInput()} - inputStore.config.zoom} - anchor={() => [ - inputStore.config.anchorMode === 'center' ? 0.5 : inputStore.config.anchorX, - inputStore.config.anchorMode === 'center' ? 0.5 : inputStore.config.anchorY, - ]} - position={props.runtime.pipedInput} - trail={props.runtime.trail} - regionPins={() => sessionStore.state.regionPins} - noiseRings={noiseRings as () => readonly [number, number] | null} - frozen={props.runtime.frozen} - onTrailTap={(p) => props.runtime.snapToTrail(p)} - onLongPress={() => props.runtime.pinCurrentRegion()} - ariaLabel="Joy map navigator" - /> -
-
{props.outputArea()}
-
- -
-
- - {(axis) => ( - controlStore.state[axis.key]} - onChange={(v) => controlStore.setAxis(axis.key, v)} - preset={() => controlStore.state.presetId} - onDoubleTap={() => controlStore.clearOffsets(axis.key)} - /> - )} - -
-
- props.runtime.trainOnCurrent()} - onRandomize={() => props.runtime.randomize()} - onThumbsUp={() => props.runtime.thumbsUp()} - onThumbsDown={() => props.runtime.thumbsDown()} - onUndo={() => { - // Click = pop one snapshot. Long-press = open list popup. - props.runtime.undo(); - }} - exampleCount={() => props.runtime.training.examples()} - lastLoss={() => props.runtime.training.lastLoss()} - busy={() => props.runtime.training.busy()} - canUndo={() => props.runtime.canUndo()} - /> - 0}> - - -
-
- -

- - Loading WASM ML…{' '} - - - frozen{' '} - - - input ({props.runtime.pipedInput()[0].toFixed(2)}, - {' '} - {props.runtime.pipedInput()[1].toFixed(2)}) - {' · '} - noise {explorationStore.state.noiseLevel.toFixed(3)} - -

- - setDrawerOpen(false)} - side="right" - title={props.drawerTitle ?? 'Mode settings'} - width={460} - > - - } - > - {props.drawerContent!()} - - - - {/* Snapshot list popup (long-press undo equivalent) */} - setSnapshotPopupOpen(false)} - side="right" - title="Snapshot history" - width={320} - > -
    - {(s) => ( -
  • - -
  • - )}
    -
-
-
- ); -}; - -const AXES = [ - { key: 'boldness' as const, label: 'Boldness', endpoints: ['Caution', 'Bold'] as const }, - { key: 'memory' as const, label: 'Memory', endpoints: ['Amnesia', 'Elephant'] as const }, - { key: 'precision' as const, label: 'Precision', endpoints: ['Raw', 'Precise'] as const }, -]; - -function formatModeName(id: string): string { - return id - .split('_') - .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) - .join(' '); -} - -export default ModeShell; diff --git a/playground/src/modes/ModeSwitcher.module.css b/playground/src/modes/ModeSwitcher.module.css deleted file mode 100644 index c226fb7..0000000 --- a/playground/src/modes/ModeSwitcher.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.bar { - display: flex; - align-items: center; - gap: var(--sp-3); - padding: var(--sp-2) var(--sp-4); - background: var(--bg-1); - border-bottom: 1px solid var(--line); -} - -.label { - font-size: var(--fs-xs); - color: var(--fg-mute); - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.select { - background: var(--bg-2); - color: var(--fg); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: var(--sp-1) var(--sp-3); - font-family: var(--font-mono); - font-size: var(--fs-sm); - cursor: pointer; -} - -.select:hover { - border-color: var(--line-strong); -} - -.select:focus { - outline: 1px solid var(--accent); -} - -.description { - font-size: var(--fs-xs); - color: var(--fg-dim); -} - -.placeholder { - color: var(--warn); -} diff --git a/playground/src/modes/ModeSwitcher.tsx b/playground/src/modes/ModeSwitcher.tsx deleted file mode 100644 index 2f10235..0000000 --- a/playground/src/modes/ModeSwitcher.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** - * ModeSwitcher — top-level select that picks the active mode. - * - * Writes through `modeStore.switchMode(id)` so persistence + the bus event - * fire correctly. Reads the current selection back from the store so it - * stays in sync with persisted state on first paint. - * - * Audio engine switching is handled inside the mode runtime (each mode - * routes its own engine_id through `EngineHost.setEngine` when started). - */ - -import { Component, createMemo, For, Show } from 'solid-js'; -import { modeStore } from '../stores/mode-store'; -import { MODE_REGISTRY, getModeById } from './index'; -import styles from './ModeSwitcher.module.css'; - -export const ModeSwitcher: Component = () => { - const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id; - const active = createMemo(() => getModeById(activeId())); - - return ( -
- Mode - - - {active().description} - - - - -
- ); -}; - -export default ModeSwitcher; diff --git a/playground/src/modes/PAFSynthMode.tsx b/playground/src/modes/PAFSynthMode.tsx deleted file mode 100644 index bdeeaca..0000000 --- a/playground/src/modes/PAFSynthMode.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * PAFSynthMode — Phase Aligned Formant synth (XY pad input, 33 outputs). - * - * Uses the xy_pad as primary input (as per schema). Voice spaces are - * exposed via the shell header. A drawer renders the SliderBank for - * monitoring (and eventually editing) per-parameter values. - */ - -import { Component, createSignal } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { XYPad } from '../primitives/XYPad'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { PafSynthSchema } from './generated/paf_synth_schema'; - -export const PAFSynthMode: Component = () => { - const schema = PafSynthSchema; - const runtime = useModeRuntime(schema); - - const [voiceSpace, setVoiceSpace] = createSignal(0); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Drag to sculpt formants. Voice space:  - {schema.voice_spaces[voiceSpace()] ?? 'Default'} - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default PAFSynthMode; diff --git a/playground/src/modes/SLPWorkshopMode.tsx b/playground/src/modes/SLPWorkshopMode.tsx deleted file mode 100644 index 7541c5b..0000000 --- a/playground/src/modes/SLPWorkshopMode.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/** - * SLPWorkshopMode — the Synth Library Portland workshop instrument. - * - * Reuses the MEMLCelium engine and MLP shape verbatim (so the synth voice is - * identical), but foregrounds the adaptive-learning gestures ported from - * upstream InterfaceRL and now living in the shared core: - * - Jolt: hold to continuously morph the network's weights live, release to - * freeze (nisps/ml/jolt.hpp). - * - Explore: an Ornstein-Uhlenbeck random walk on the output that makes the - * sound slowly roam so likes/dislikes can steer it (nisps/ml/ou_noise.hpp). - */ - -import { Component, createSignal } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { XYPad } from '../primitives/XYPad'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { Slider } from '../primitives/Slider'; -import { SlpWorkshopSchema } from './generated/slp_workshop_schema'; - -export const SLPWorkshopMode: Component = () => { - const schema = SlpWorkshopSchema; - const runtime = useModeRuntime(schema); - const [voiceSpace, setVoiceSpace] = createSignal(0); - const [exploreLevel, setExploreLevel] = createSignal(0); - - const setExplore = (v: number): void => { - setExploreLevel(v); - runtime.explore.setIntensity(v); - }; - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Synth Library Portland workshop instrument — MEMLCelium voice with - live Jolt + Explore learning controls. - - - {/* Adaptive-learning gestures. */} -
- {/* Jolt — hold to morph weights live, release to freeze. */} - - - {/* Explore — OU random-walk exploration intensity. */} - -
- - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default SLPWorkshopMode; diff --git a/playground/src/modes/SettingsDrawer.module.css b/playground/src/modes/SettingsDrawer.module.css deleted file mode 100644 index 2777125..0000000 --- a/playground/src/modes/SettingsDrawer.module.css +++ /dev/null @@ -1,250 +0,0 @@ -.wrap { - display: flex; - flex-direction: column; - gap: var(--sp-2); -} - -.section { - border-top: 1px solid var(--line); - padding-top: var(--sp-2); -} - -.section:first-of-type { - border-top: none; - padding-top: 0; -} - -.sectionHeader { - display: flex; - align-items: center; - gap: var(--sp-2); - width: 100%; - background: transparent; - border: none; - padding: var(--sp-2) 0; - font-size: var(--fs-sm); - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--fg-mute); - cursor: pointer; - text-align: left; - font-family: var(--font-mono); -} - -.sectionHeader:hover { - color: var(--fg); -} - -.caret { - color: var(--fg-dim); - font-size: var(--fs-xs); - width: 12px; - display: inline-block; -} - -.badge { - margin-left: auto; - font-size: var(--fs-xs); - background: var(--bg-3); - color: var(--fg-mute); - padding: 1px 6px; - border-radius: var(--r-1); - font-family: var(--font-mono); -} - -.body { - display: flex; - flex-direction: column; - gap: var(--sp-3); - padding: var(--sp-2) 0 var(--sp-3); -} - -.row { - display: flex; - align-items: center; - gap: var(--sp-2); - flex-wrap: wrap; -} - -.fieldLabel { - font-size: var(--fs-xs); - color: var(--fg-mute); - text-transform: uppercase; - letter-spacing: 0.06em; - min-width: 90px; -} - -.checkbox { - display: flex; - align-items: center; - gap: var(--sp-1); - font-size: var(--fs-sm); - color: var(--fg); - cursor: pointer; -} - -.subhead { - font-size: var(--fs-xs); - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--fg-mute); - margin: var(--sp-2) 0 var(--sp-1); -} - -.muted { - color: var(--fg-dim); - font-size: var(--fs-xs); - font-family: var(--font-mono); -} - -.btnAlt { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: var(--sp-1) var(--sp-3); - font-size: var(--fs-sm); - color: var(--fg); - cursor: pointer; -} - -.btnAlt:hover:not(:disabled) { - background: var(--bg-3); -} - -.btnAlt:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btnTiny { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: 2px 8px; - font-size: var(--fs-xs); - color: var(--fg-mute); - cursor: pointer; -} - -.btnTiny:hover { - color: var(--fg); - background: var(--bg-3); -} - -.snapshotList, -.presetList { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 2px; - max-height: 220px; - overflow-y: auto; -} - -.snapshotBtn { - display: flex; - flex-direction: column; - width: 100%; - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - padding: var(--sp-1) var(--sp-2); - text-align: left; - cursor: pointer; - color: var(--fg); -} - -.snapshotBtn:hover { - background: var(--bg-3); -} - -.snapshotTag { - font-size: var(--fs-sm); - font-family: var(--font-mono); -} - -.snapshotMeta { - font-size: var(--fs-xs); - color: var(--fg-mute); - font-family: var(--font-mono); -} - -.presetRow { - display: flex; - align-items: center; - gap: var(--sp-2); - padding: var(--sp-1); - border-bottom: 1px dashed var(--line); -} - -.presetRow > span { - flex: 1; - font-size: var(--fs-sm); -} - -.paramList { - display: flex; - flex-direction: column; - gap: var(--sp-2); - max-height: 360px; - overflow-y: auto; -} - -.layerStats { - display: flex; - flex-direction: column; - gap: var(--sp-1); - font-family: var(--font-mono); - font-size: var(--fs-xs); -} - -.layerStatRow { - display: flex; - align-items: center; - gap: var(--sp-2); - padding: 2px var(--sp-2); - background: var(--bg-2); - border-radius: var(--r-1); -} - -.layerStatLabel { - color: var(--accent); - font-weight: 600; - min-width: 24px; -} - -.layerStatValue { - flex: 1; - color: var(--fg-mute); -} - -.layerStatStatus { - font-size: 10px; - text-transform: uppercase; - padding: 1px 6px; - border-radius: var(--r-1); - background: var(--bg-3); -} - -.layerStatStatus[data-status="dead"] { color: #999; } -.layerStatStatus[data-status="saturating"] { color: var(--warn); } -.layerStatStatus[data-status="healthy"] { color: var(--good); } - -.textInput { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-1); - color: var(--fg); - font-family: var(--font-mono); - font-size: var(--fs-sm); - padding: var(--sp-1) var(--sp-2); - flex: 1; -} - -.hr { - border: none; - border-top: 1px dashed var(--line); - margin: var(--sp-2) 0; -} diff --git a/playground/src/modes/SettingsDrawer.tsx b/playground/src/modes/SettingsDrawer.tsx deleted file mode 100644 index 1f409b0..0000000 --- a/playground/src/modes/SettingsDrawer.tsx +++ /dev/null @@ -1,656 +0,0 @@ -/** - * SettingsDrawer — settings panel rendered inside ModeShell's drawer. - * - * Sections: - * - Input: deadzone, curve, smoothing, momentum, anchor mode, invert - * - Training: learning rate, weight decay - * - Exploration: spread, noise floor/cap/growth/decay, - * auto-explore (toggle, interval, intensity) - * - Output: global curve, smoothing, slew rate, freeze - * - Per-param overrides: ParamEditor list - * - Advanced: weight health histogram, gradient flow, session presets - * - * Sections are collapsible. Per-param-override editor renders inline (also - * accessible via double-tap on slider in the live readout — wired in - * ModeShell). The drawer is a child of `ModeShell`'s `drawerContent` slot. - */ - -import { Component, createSignal, For, Show, createMemo, createEffect } from 'solid-js'; -import { ParamEditor, type ParamOverride as PrimitiveParamOverride } from '../primitives/ParamEditor'; -import { Slider } from '../primitives/Slider'; -import { PillToggle } from '../primitives/PillToggle'; -import { WeightHealth } from '../primitives/WeightHealth'; -import { GradientFlow } from '../primitives/GradientFlow'; -import { Heatmap } from '../primitives/Heatmap'; -import { ProgressRing } from '../primitives/ProgressRing'; -import { LossPlot } from '../primitives/LossPlot'; - -import { inputStore } from '../stores/input-store'; -import { outputStore } from '../stores/output-store'; -import { explorationStore } from '../stores/exploration-store'; -import { modeStore, defaultParamOverride, type ParamOverride } from '../stores/mode-store'; -import { sessionStore } from '../stores/session-store'; -import { mlStore } from '../stores/ml-store'; -import { coreBus } from '../stores/bus'; -import { - ZOOM_MIN, - ZOOM_MAX, - DEADZONE_MAX, - INPUT_CURVE_MIN, - INPUT_CURVE_MAX, - SMOOTHING_MAX, -} from '../input/pipeline'; -import { GLOBAL_CURVE_MIN, GLOBAL_CURVE_MAX } from '../output/pipeline'; -import { - layerNorms, - gradientStatuses, - weightHistogram, - weightStatus, - layerStatsToStatus, - layerWeightCounts, -} from '../features/weight-health'; -import { saveNamedPreset, loadNamedPreset, buildShareUrl } from '../features/session-preset'; -import { captureA, toggleAB, acceptB, revertToA } from '../features/snapshots'; -import type { ModeRuntime } from './mode-runtime'; -import type { ModeSchema } from './generated'; -import type { Param } from './generated/types'; -import type { CurveName } from '../output/curves'; -import type { HeatmapColorMode } from '../features/heatmap-sampler'; - -import styles from './SettingsDrawer.module.css'; - -export interface SettingsDrawerProps { - schema: ModeSchema; - runtime: ModeRuntime; - /** Show advanced section (weight health, gradient flow, session presets). */ - showAdvanced?: boolean; -} - -interface Section { - id: string; - title: string; - defaultOpen?: boolean; - render: () => any; -} - -export const SettingsDrawer: Component = (props) => { - const [open, setOpen] = createSignal>({ - input: false, - training: false, - exploration: false, - output: false, - overrides: false, - advanced: false, - snapshots: false, - }); - const toggle = (id: string) => setOpen((s) => ({ ...s, [id]: !s[id] })); - - // ----- Per-param overrides UI ---------------------------------------- - const overrides = () => modeStore.state.overrides[props.schema.mode_id] ?? {}; - - const getOverride = (param: Param): PrimitiveParamOverride => { - const cur = overrides()[param.name]; - if (cur) { - return { - min: cur.min, - max: cur.max, - curve: cur.curve as CurveName, - curveParam: cur.curveParam, - muted: cur.muted, - pinned: cur.pinned, - fixedValue: cur.fixedValue, - }; - } - return { - min: param.min, - max: param.max, - curve: param.curve as CurveName, - muted: false, - pinned: false, - fixedValue: param.default, - }; - }; - - const writeOverride = (param: Param, next: PrimitiveParamOverride) => { - const stored: ParamOverride = { - min: next.min, - max: next.max, - curve: next.curve, - curveParam: next.curveParam, - muted: next.muted, - pinned: next.pinned, - fixedValue: next.fixedValue, - // Frozen flag is local to the runtime (set via output-store freezeMask) - // — keep it false in the persistent override unless the user explicitly - // toggles it. - frozen: overrides()[param.name]?.frozen ?? false, - }; - modeStore.setOverride(props.schema.mode_id, param.name, stored); - }; - - // ----- Per-output health (advanced section) ----------------------------- - const [advancedExpanded, setAdvancedExpanded] = createSignal(props.showAdvanced ?? false); - const histogram = createMemo(() => { - const w = mlStore.weights(); - return weightHistogram(w); - }); - const status = createMemo(() => weightStatus(mlStore.weights())); - - // Gradient flow: capture before/after weights on training events. - const [normHistory, setNormHistory] = createSignal([]); - const [gradStatuses, setGradStatuses] = createSignal>([]); - let lastBeforeWeights: Float32Array | null = null; - - const offTrainStart = coreBus.on('snap.push', (e) => { - if (e.tag === 'before train' && mlStore.iml) { - lastBeforeWeights = mlStore.getWeights(); - } - }); - const offTrainEnd = coreBus.on('ml.trained', () => { - if (lastBeforeWeights && mlStore.iml) { - const after = mlStore.getWeights(); - const arch = mlStore.iml.architecture; - const sizes = layerWeightCounts({ - inputSize: arch.inputSize, - hidden: arch.hidden, - outputSize: arch.outputSize, - }); - const norms = layerNorms(lastBeforeWeights, after, sizes); - setNormHistory(norms); - setGradStatuses(gradientStatuses(norms)); - lastBeforeWeights = null; - } - }); - - // Session-preset save / share UI state - const [presetName, setPresetName] = createSignal(''); - const [shareUrl, setShareUrl] = createSignal(''); - - // Snapshot list (long-press-undo equivalent) - const snapshots = () => sessionStore.listSnapshots(); - - // Cleanup on unmount (Solid handles via owner; we only need to detach bus - // subs explicitly because they live across renders). - // We can't use onCleanup here because this component is recreated per-mode, - // but bus.on returns an unsubscribe. Defer cleanup to when the component - // unmounts via createEffect cleanup function: - createEffect(() => { - return () => { offTrainStart(); offTrainEnd(); }; - }); - - // ----- A/B compare visibility ------------------------------------------ - const ab = () => sessionStore.state.ab; - const live = () => ab().live; - - // --------------------------------------------------------------------- - // Render helpers - // --------------------------------------------------------------------- - - const SectionHeader = (h: { id: string; title: string; count?: number }) => ( - - ); - - return ( -
- {/* Snapshots / undo / A/B */} -
- - -
-
- - -
- 0}> -
    - {(s) => ( -
  • - -
  • - )}
    -
-
-
-

A / B compare

-
- active: {live()} - }> - no capture yet - -
-
- - - - -
-
-
-
- - {/* Input pipeline */} -
- - -
- inputStore.setZoom(v)} - /> - inputStore.setDeadzone(v)} - /> - inputStore.setInputCurve(v)} - /> - inputStore.setSmoothing(v)} - /> -
- - inputStore.config.anchorMode} - onChange={(v) => inputStore.setAnchorMode(v as 'auto' | 'sticky' | 'center')} - ariaLabel="Anchor mode" - /> -
-
- - inputStore.config.momentumZoom} - onChange={(v) => inputStore.setMomentumZoom(v as 'off' | 'gentle' | 'strong')} - ariaLabel="Momentum zoom" - /> -
-
- - -
-
-
-
- - {/* Training */} -
- - -
- explorationStore.setLearningRate(v)} - /> - explorationStore.setWeightDecay(v)} - /> - mlStore.state.lossHistory} width={320} height={70} /> -
-
-
- - {/* Exploration */} -
- - -
- explorationStore.setSpread(v)} - /> - explorationStore.setNoiseFloor(v)} - /> - explorationStore.setNoiseCap(v)} - /> - explorationStore.setNoiseGrowth(v)} - /> - explorationStore.setNoiseDecay(v)} - /> -
-

Auto-explore

-
- - - 1} - ariaLabel="Auto-explore active" - /> - -
- explorationStore.setAutoExploreInterval(v)} - /> - explorationStore.setAutoExploreIntensity(v)} - /> -
-
-
- - {/* Output */} -
- - -
- outputStore.setGlobalCurve(v)} - /> - outputStore.setSmoothing(v)} - /> - outputStore.setSlewRate(v)} - /> - -
-
-
- - {/* Per-param overrides */} -
- - -
-
- -
-
- {(p) => ( - getOverride(p)} - onChange={(next) => writeOverride(p, next)} - compact - /> - )} -
-
-
-
- - {/* Advanced: weight health, gradient flow, heatmap */} -
- - -
- - -

Weight health

- -

Gradient flow (last train)

- 0} - fallback={

No training run yet.

} - > - -
-

Per-layer stats

-
- {(stats, idx) => ( -
- L{idx()} - - mean|w| {stats.meanAbs.toFixed(3)} - · max|w| {stats.maxAbs.toFixed(3)} - · {(stats.deadFrac * 100).toFixed(0)}% dead - · {(stats.saturatingFrac * 100).toFixed(0)}% sat - - {layerStatsToStatus(stats)} -
- )}
-
-

Input heatmap

-
- - props.runtime.heatmap.colorMode()} - onChange={(v) => props.runtime.heatmap.setColorMode(v as HeatmapColorMode)} - ariaLabel="Heatmap color mode" - /> - -
- -
-
-

Session preset

-
- setPresetName(e.currentTarget.value)} - /> - -
- 0}> -
    - {(p) => ( -
  • - {p.name} - - -
  • - )}
    -
-
-
- - - e.currentTarget.select()} - /> - -
-
-
-
-
- ); -}; - -export default SettingsDrawer; diff --git a/playground/src/modes/SoundAnalysisMIDIMode.tsx b/playground/src/modes/SoundAnalysisMIDIMode.tsx deleted file mode 100644 index 5fc102c..0000000 --- a/playground/src/modes/SoundAnalysisMIDIMode.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/** - * SoundAnalysisMIDIMode — sound analysis → MIDI CC output (audio_in input). - * - * Schema declares `primary_input: 'audio_in'` and `engine_id: 'thru'` (no - * synthesis). Stream 10 wires the mic into channels 2..5 via ModeShell's - * Mic button + the runtime's MicInput. The joystick is used as a fallback - * for channels 0..1 and during testing without mic permissions. - * - * MIDI output routing is read-only here — the SettingsDrawer's per-param - * override editor lets users tighten the active range / mute params; the - * actual MIDI device wiring is left for the engine host to handle (or for - * a future stream that adds WebMIDI sender). For now CC values are - * visible in the live OutputDisplay. - */ - -import { Component, Show } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { VirtualJoystick } from '../primitives/VirtualJoystick'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { SoundAnalysisMidiSchema } from './generated/sound_analysis_midi_schema'; - -export const SoundAnalysisMIDIMode: Component = () => { - const schema = SoundAnalysisMidiSchema; - const runtime = useModeRuntime(schema); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - ariaLabel="Sound analysis joystick" - /> - } - > -
- - {runtime.mic.started() ? '🎤 Mic active' : 'Mic input'} - - - {runtime.mic.started() - ? 'Audio features → channels 2..5. Joystick below drives channels 0..1.' - : 'Tap "Mic" in the header to feed mic features into the model.'} - - runtime.setInput(x, y)} - position={runtime.pipedInput} - ariaLabel="Manual joystick fallback" - /> -
-
- - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default SoundAnalysisMIDIMode; diff --git a/playground/src/modes/VerbFXMode.tsx b/playground/src/modes/VerbFXMode.tsx deleted file mode 100644 index ac69ac7..0000000 --- a/playground/src/modes/VerbFXMode.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** - * VerbFXMode — verb / fx unit (joystick input, ~47 outputs). - */ - -import { Component, createSignal } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { VirtualJoystick } from '../primitives/VirtualJoystick'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { VerbFxSchema } from './generated/verb_fx_schema'; - -export const VerbFXMode: Component = () => { - const schema = VerbFxSchema; - const runtime = useModeRuntime(schema); - const [voiceSpace, setVoiceSpace] = createSignal(0); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Sweep through verb space. Voice space:  - {schema.voice_spaces[voiceSpace()] ?? 'Default'} - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default VerbFXMode; diff --git a/playground/src/modes/XIASRIMode.tsx b/playground/src/modes/XIASRIMode.tsx deleted file mode 100644 index e1dacbf..0000000 --- a/playground/src/modes/XIASRIMode.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** - * XIASRIMode — audio-reactive verb / pitch effects. - * - * Schema declares `primary_input: 'joystick'` and feeds the MLP from - * joy_x/joy_y plus optional mic features. ModeShell exposes a Mic toggle - * that opt-in feeds analysis features into channels 2..5. - */ - -import { Component } from 'solid-js'; -import { ModeShell } from './ModeShell'; -import { useModeRuntime } from './mode-runtime'; -import { VirtualJoystick } from '../primitives/VirtualJoystick'; -import { OutputDisplay } from '../primitives/OutputDisplay'; -import { LossPlot } from '../primitives/LossPlot'; -import { XiasriSchema } from './generated/xiasri_schema'; - -export const XIASRIMode: Component = () => { - const schema = XiasriSchema; - const runtime = useModeRuntime(schema); - - return ( - ( - <> - runtime.setInput(x, y)} - position={runtime.pipedInput} - /> - - Joystick → verb / pitch space. Enable mic for audio-reactive features. - - - )} - outputArea={() => ( - <> - - - - )} - /> - ); -}; - -export default XIASRIMode; diff --git a/playground/src/modes/generated/breakor_schema.ts b/playground/src/modes/generated/breakor_schema.ts deleted file mode 100644 index 0bfe8ab..0000000 --- a/playground/src/modes/generated/breakor_schema.ts +++ /dev/null @@ -1,596 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/breakor.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface BreakorParams { - readonly kick_ratio0: number; - readonly kick_ratio1: number; - readonly kick_ratio2: number; - readonly kick_pmul: number; - readonly kick_poff: number; - readonly kick_amp0: number; - readonly kick_amp1: number; - readonly snare_ratio0: number; - readonly snare_ratio1: number; - readonly snare_ratio2: number; - readonly snare_pmul: number; - readonly snare_poff: number; - readonly snare_amp0: number; - readonly snare_amp1: number; - readonly tom_ratio0: number; - readonly tom_ratio1: number; - readonly tom_ratio2: number; - readonly tom_pmul: number; - readonly tom_poff: number; - readonly tom_amp0: number; - readonly tom_amp1: number; - readonly lowtom_ratio0: number; - readonly lowtom_ratio1: number; - readonly lowtom_ratio2: number; - readonly lowtom_pmul: number; - readonly lowtom_poff: number; - readonly lowtom_amp0: number; - readonly lowtom_amp1: number; - readonly rim_ratio0: number; - readonly rim_ratio1: number; - readonly rim_ratio2: number; - readonly rim_pmul: number; - readonly rim_poff: number; - readonly rim_amp0: number; - readonly rim_amp1: number; - readonly hat_ratio0: number; - readonly hat_ratio1: number; - readonly hat_ratio2: number; - readonly hat_pmul: number; - readonly hat_poff: number; - readonly hat_amp0: number; - readonly hat_amp1: number; - readonly ophat_ratio0: number; - readonly ophat_ratio1: number; - readonly ophat_ratio2: number; - readonly ophat_pmul: number; - readonly ophat_poff: number; - readonly ophat_amp0: number; - readonly ophat_amp1: number; - readonly perc_ratio0: number; - readonly perc_ratio1: number; - readonly perc_ratio2: number; - readonly perc_pmul: number; - readonly perc_poff: number; - readonly perc_amp0: number; - readonly perc_amp1: number; -} - -export const BreakorSchema: ModeSchema = { - mode_id: 'breakor', - engine_id: 'breakor', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 14, - 18, - ], - output_size: 56, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'kick_ratio0', - label: 'Kick Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_ratio1', - label: 'Kick Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_ratio2', - label: 'Kick Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_pmul', - label: 'Kick Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_poff', - label: 'Kick Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_amp0', - label: 'Kick Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'kick', - }, - { - name: 'kick_amp1', - label: 'Kick Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'kick', - }, - { - name: 'snare_ratio0', - label: 'Snare Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_ratio1', - label: 'Snare Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_ratio2', - label: 'Snare Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_pmul', - label: 'Snare Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_poff', - label: 'Snare Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_amp0', - label: 'Snare Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'snare', - }, - { - name: 'snare_amp1', - label: 'Snare Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'snare', - }, - { - name: 'tom_ratio0', - label: 'Tom Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_ratio1', - label: 'Tom Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_ratio2', - label: 'Tom Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_pmul', - label: 'Tom Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_poff', - label: 'Tom Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_amp0', - label: 'Tom Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'tom', - }, - { - name: 'tom_amp1', - label: 'Tom Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'tom', - }, - { - name: 'lowtom_ratio0', - label: 'LowTom Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_ratio1', - label: 'LowTom Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_ratio2', - label: 'LowTom Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_pmul', - label: 'LowTom Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_poff', - label: 'LowTom Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_amp0', - label: 'LowTom Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'lowtom_amp1', - label: 'LowTom Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'lowtom', - }, - { - name: 'rim_ratio0', - label: 'Rim Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_ratio1', - label: 'Rim Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_ratio2', - label: 'Rim Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_pmul', - label: 'Rim Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_poff', - label: 'Rim Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_amp0', - label: 'Rim Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'rim', - }, - { - name: 'rim_amp1', - label: 'Rim Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'rim', - }, - { - name: 'hat_ratio0', - label: 'Hat Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_ratio1', - label: 'Hat Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_ratio2', - label: 'Hat Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_pmul', - label: 'Hat Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_poff', - label: 'Hat Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_amp0', - label: 'Hat Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'hat', - }, - { - name: 'hat_amp1', - label: 'Hat Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'hat', - }, - { - name: 'ophat_ratio0', - label: 'OpenHat Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_ratio1', - label: 'OpenHat Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_ratio2', - label: 'OpenHat Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_pmul', - label: 'OpenHat Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_poff', - label: 'OpenHat Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_amp0', - label: 'OpenHat Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'openhat', - }, - { - name: 'ophat_amp1', - label: 'OpenHat Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'openhat', - }, - { - name: 'perc_ratio0', - label: 'Perc Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_ratio1', - label: 'Perc Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_ratio2', - label: 'Perc Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_pmul', - label: 'Perc Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_poff', - label: 'Perc Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_amp0', - label: 'Perc Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'perc', - }, - { - name: 'perc_amp1', - label: 'Perc Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'perc', - }, - ], - voice_spaces: [], - ui: { - primary_input: 'xy_pad', - show_voice_space_selector: false, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/generated/channel_strip_schema.ts b/playground/src/modes/generated/channel_strip_schema.ts deleted file mode 100644 index 8bf61c8..0000000 --- a/playground/src/modes/generated/channel_strip_schema.ts +++ /dev/null @@ -1,283 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/channel_strip.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface ChannelStripParams { - readonly pre_gain: number; - readonly peak0_freq: number; - readonly p02: number; - readonly p03: number; - readonly peak1_freq: number; - readonly peak_q: number; - readonly peak_gain: number; - readonly in_lpf_cutoff: number; - readonly in_hpf_cutoff: number; - readonly p09: number; - readonly comp_threshold: number; - readonly comp_ratio: number; - readonly comp_attack: number; - readonly comp_release: number; - readonly lowshelf_freq: number; - readonly lowshelf_q: number; - readonly lowshelf_gain: number; - readonly highshelf_freq: number; - readonly highshelf_q: number; - readonly highshelf_gain: number; - readonly p20: number; - readonly p21: number; - readonly p22: number; - readonly post_gain: number; -} - -export const ChannelStripSchema: ModeSchema = { - mode_id: 'channel_strip', - engine_id: 'channel_strip', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 10, - 14, - ], - output_size: 24, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'pre_gain', - label: 'Pre Gain', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'gain', - }, - { - name: 'peak0_freq', - label: 'Peak0 Freq', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'eq', - }, - { - name: 'p02', - label: 'Param 02', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p03', - label: 'Param 03', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'peak1_freq', - label: 'Peak1 Freq', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'eq', - }, - { - name: 'peak_q', - label: 'Peak Q', - min: 0, - max: 1, - default: 0.3, - curve: 'linear', - group: 'eq', - }, - { - name: 'peak_gain', - label: 'Peak Gain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'eq', - }, - { - name: 'in_lpf_cutoff', - label: 'Input LPF', - min: 0, - max: 1, - default: 0.7, - curve: 'square', - group: 'filters', - }, - { - name: 'in_hpf_cutoff', - label: 'Input HPF', - min: 0, - max: 1, - default: 0.2, - curve: 'square', - group: 'filters', - }, - { - name: 'p09', - label: 'Param 09', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'comp_threshold', - label: 'Comp Threshold', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'comp', - }, - { - name: 'comp_ratio', - label: 'Comp Ratio', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'comp', - }, - { - name: 'comp_attack', - label: 'Comp Attack', - min: 0, - max: 1, - default: 0.1, - curve: 'linear', - group: 'comp', - }, - { - name: 'comp_release', - label: 'Comp Release', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'comp', - }, - { - name: 'lowshelf_freq', - label: 'Lowshelf Freq', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'eq', - }, - { - name: 'lowshelf_q', - label: 'Lowshelf Q', - min: 0, - max: 1, - default: 0.3, - curve: 'linear', - group: 'eq', - }, - { - name: 'lowshelf_gain', - label: 'Lowshelf Gain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'eq', - }, - { - name: 'highshelf_freq', - label: 'Highshelf Freq', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'eq', - }, - { - name: 'highshelf_q', - label: 'Highshelf Q', - min: 0, - max: 1, - default: 0.3, - curve: 'linear', - group: 'eq', - }, - { - name: 'highshelf_gain', - label: 'Highshelf Gain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'eq', - }, - { - name: 'p20', - label: 'Param 20', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p21', - label: 'Param 21', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p22', - label: 'Param 22', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'post_gain', - label: 'Post Gain', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'gain', - }, - ], - voice_spaces: [ - 'WannabeNeve66', - 'SSL 4K G-ist', - 'SSL 9K-inda', - 'MaleVox', - 'FemaleVox', - 'Neve 80', - ], - ui: { - primary_input: 'joystick', - show_voice_space_selector: true, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/generated/elysiamorf_schema.ts b/playground/src/modes/generated/elysiamorf_schema.ts deleted file mode 100644 index b08ec88..0000000 --- a/playground/src/modes/generated/elysiamorf_schema.ts +++ /dev/null @@ -1,436 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/elysiamorf.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface ElysiamorfParams { - readonly fm0_carrier: number; - readonly fm0_mod_freq: number; - readonly fm0_mod_index: number; - readonly fm0_phasor_mul: number; - readonly fm0_phase_off: number; - readonly fm1_carrier: number; - readonly fm1_mod_freq: number; - readonly fm1_mod_index: number; - readonly fm1_phasor_mul: number; - readonly fm1_phase_off: number; - readonly fm2_carrier: number; - readonly fm2_mod_freq: number; - readonly fm2_mod_index: number; - readonly fm2_phasor_mul: number; - readonly fm2_phase_off: number; - readonly fm3_carrier: number; - readonly fm3_mod_freq: number; - readonly fm3_mod_index: number; - readonly fm3_phasor_mul: number; - readonly fm3_phase_off: number; - readonly fm4_carrier: number; - readonly fm4_mod_freq: number; - readonly fm4_mod_index: number; - readonly fm4_phasor_mul: number; - readonly fm4_phase_off: number; - readonly fm5_carrier: number; - readonly fm5_mod_freq: number; - readonly fm5_mod_index: number; - readonly fm5_phasor_mul: number; - readonly fm5_phase_off: number; - readonly fm6_carrier: number; - readonly fm6_mod_freq: number; - readonly fm6_mod_index: number; - readonly fm6_phasor_mul: number; - readonly fm6_phase_off: number; - readonly fm7_carrier: number; - readonly fm7_mod_freq: number; - readonly fm7_mod_index: number; - readonly fm7_phasor_mul: number; - readonly fm7_phase_off: number; -} - -export const ElysiamorfSchema: ModeSchema = { - mode_id: 'elysiamorf', - engine_id: 'elysiamorf', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 14, - 18, - ], - output_size: 40, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'fm0_carrier', - label: 'FM0 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm0', - }, - { - name: 'fm0_mod_freq', - label: 'FM0 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm0', - }, - { - name: 'fm0_mod_index', - label: 'FM0 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm0', - }, - { - name: 'fm0_phasor_mul', - label: 'FM0 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm0', - }, - { - name: 'fm0_phase_off', - label: 'FM0 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm0', - }, - { - name: 'fm1_carrier', - label: 'FM1 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm1', - }, - { - name: 'fm1_mod_freq', - label: 'FM1 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm1', - }, - { - name: 'fm1_mod_index', - label: 'FM1 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm1', - }, - { - name: 'fm1_phasor_mul', - label: 'FM1 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm1', - }, - { - name: 'fm1_phase_off', - label: 'FM1 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm1', - }, - { - name: 'fm2_carrier', - label: 'FM2 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm2', - }, - { - name: 'fm2_mod_freq', - label: 'FM2 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm2', - }, - { - name: 'fm2_mod_index', - label: 'FM2 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm2', - }, - { - name: 'fm2_phasor_mul', - label: 'FM2 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm2', - }, - { - name: 'fm2_phase_off', - label: 'FM2 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm2', - }, - { - name: 'fm3_carrier', - label: 'FM3 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm3', - }, - { - name: 'fm3_mod_freq', - label: 'FM3 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm3', - }, - { - name: 'fm3_mod_index', - label: 'FM3 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm3', - }, - { - name: 'fm3_phasor_mul', - label: 'FM3 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm3', - }, - { - name: 'fm3_phase_off', - label: 'FM3 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm3', - }, - { - name: 'fm4_carrier', - label: 'FM4 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm4', - }, - { - name: 'fm4_mod_freq', - label: 'FM4 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm4', - }, - { - name: 'fm4_mod_index', - label: 'FM4 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm4', - }, - { - name: 'fm4_phasor_mul', - label: 'FM4 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm4', - }, - { - name: 'fm4_phase_off', - label: 'FM4 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm4', - }, - { - name: 'fm5_carrier', - label: 'FM5 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm5', - }, - { - name: 'fm5_mod_freq', - label: 'FM5 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm5', - }, - { - name: 'fm5_mod_index', - label: 'FM5 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm5', - }, - { - name: 'fm5_phasor_mul', - label: 'FM5 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm5', - }, - { - name: 'fm5_phase_off', - label: 'FM5 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm5', - }, - { - name: 'fm6_carrier', - label: 'FM6 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm6', - }, - { - name: 'fm6_mod_freq', - label: 'FM6 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm6', - }, - { - name: 'fm6_mod_index', - label: 'FM6 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm6', - }, - { - name: 'fm6_phasor_mul', - label: 'FM6 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm6', - }, - { - name: 'fm6_phase_off', - label: 'FM6 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm6', - }, - { - name: 'fm7_carrier', - label: 'FM7 Carrier', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm7', - }, - { - name: 'fm7_mod_freq', - label: 'FM7 Mod Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'fm7', - }, - { - name: 'fm7_mod_index', - label: 'FM7 Mod Index', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm7', - }, - { - name: 'fm7_phasor_mul', - label: 'FM7 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm7', - }, - { - name: 'fm7_phase_off', - label: 'FM7 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'fm7', - }, - ], - voice_spaces: [], - ui: { - primary_input: 'xy_pad', - show_voice_space_selector: false, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/generated/index.ts b/playground/src/modes/generated/index.ts deleted file mode 100644 index d33e40c..0000000 --- a/playground/src/modes/generated/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// AUTOGENERATED — do not edit. Run `bun run codegen/generate.ts` to regenerate. -// Re-exports every generated mode schema. - -export * from './types'; -export * from './breakor_schema'; -export * from './channel_strip_schema'; -export * from './elysiamorf_schema'; -export * from './memlcelium_schema'; -export * from './paf_synth_schema'; -export * from './slp_workshop_schema'; -export * from './sound_analysis_midi_schema'; -export * from './verb_fx_schema'; -export * from './xiasri_schema'; diff --git a/playground/src/modes/generated/memlcelium_schema.ts b/playground/src/modes/generated/memlcelium_schema.ts deleted file mode 100644 index 2f0f6d9..0000000 --- a/playground/src/modes/generated/memlcelium_schema.ts +++ /dev/null @@ -1,598 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/memlcelium.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface MemlceliumParams { - readonly seq0_ratio0: number; - readonly seq0_ratio1: number; - readonly seq0_ratio2: number; - readonly seq0_phasor_mul: number; - readonly seq0_phase_off: number; - readonly seq0_amp0: number; - readonly seq0_amp1: number; - readonly seq1_ratio0: number; - readonly seq1_ratio1: number; - readonly seq1_ratio2: number; - readonly seq1_phasor_mul: number; - readonly seq1_phase_off: number; - readonly seq1_amp0: number; - readonly seq1_amp1: number; - readonly v0_base_freq: number; - readonly v0_paf0_cf: number; - readonly v0_paf1_cf: number; - readonly v0_paf2_cf: number; - readonly v0_paf0_bw: number; - readonly v0_paf1_bw: number; - readonly v0_paf2_bw: number; - readonly v0_paf_vib: number; - readonly v0_paf_vfr: number; - readonly v0_paf0_shift: number; - readonly v0_paf1_shift: number; - readonly v0_paf2_shift: number; - readonly v0_amp_attack: number; - readonly v0_amp_decay: number; - readonly v0_amp_sustain: number; - readonly v0_amp_release: number; - readonly v0_pitch_attack: number; - readonly v0_pitch_decay: number; - readonly v0_pitch_emph: number; - readonly v0_shape_gain: number; - readonly v0_shape_asym: number; - readonly v0_shape_mix: number; - readonly v0_rm_gain: number; - readonly v1_base_freq: number; - readonly v1_detune1: number; - readonly v1_detune2: number; - readonly v1_paf0_cf: number; - readonly v1_paf1_cf: number; - readonly v1_paf2_cf: number; - readonly v1_paf0_bw: number; - readonly v1_paf1_bw: number; - readonly v1_paf2_bw: number; - readonly v1_paf0_shift: number; - readonly v1_paf1_shift: number; - readonly v1_paf2_shift: number; - readonly v1_amp_attack: number; - readonly v1_amp_decay: number; - readonly v1_amp_sustain: number; - readonly v1_amp_release: number; - readonly v1_pitch_attack: number; - readonly v1_pitch_decay: number; - readonly v1_pitch_emph: number; -} - -export const MemlceliumSchema: ModeSchema = { - mode_id: 'memlcelium', - engine_id: 'memlcelium', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 14, - 18, - ], - output_size: 56, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'seq0_ratio0', - label: 'Seq0 Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_ratio1', - label: 'Seq0 Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_ratio2', - label: 'Seq0 Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_phasor_mul', - label: 'Seq0 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_phase_off', - label: 'Seq0 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_amp0', - label: 'Seq0 Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_amp1', - label: 'Seq0 Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio0', - label: 'Seq1 Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio1', - label: 'Seq1 Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio2', - label: 'Seq1 Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_phasor_mul', - label: 'Seq1 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_phase_off', - label: 'Seq1 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_amp0', - label: 'Seq1 Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_amp1', - label: 'Seq1 Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'v0_base_freq', - label: 'V0 Base Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf0_cf', - label: 'V0 PAF0 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_cf', - label: 'V0 PAF1 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_cf', - label: 'V0 PAF2 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf0_bw', - label: 'V0 PAF0 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_bw', - label: 'V0 PAF1 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_bw', - label: 'V0 PAF2 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf_vib', - label: 'V0 PAF Vib', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'v0_synth', - }, - { - name: 'v0_paf_vfr', - label: 'V0 PAF Vib Rate', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'v0_synth', - }, - { - name: 'v0_paf0_shift', - label: 'V0 PAF0 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_shift', - label: 'V0 PAF1 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_shift', - label: 'V0 PAF2 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_amp_attack', - label: 'V0 Amp Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_amp_decay', - label: 'V0 Amp Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_amp_sustain', - label: 'V0 Amp Sustain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_amp_release', - label: 'V0 Amp Release', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_pitch_attack', - label: 'V0 Pitch Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_pitch_decay', - label: 'V0 Pitch Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_pitch_emph', - label: 'V0 Pitch Emph', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_shape_gain', - label: 'V0 Shape Gain', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_shape_asym', - label: 'V0 Shape Asym', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_shape_mix', - label: 'V0 Shape Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_rm_gain', - label: 'V0 RingMod Gain', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v1_base_freq', - label: 'V1 Base Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_detune1', - label: 'V1 Detune 1', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_detune2', - label: 'V1 Detune 2', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_cf', - label: 'V1 PAF0 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_cf', - label: 'V1 PAF1 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_cf', - label: 'V1 PAF2 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_bw', - label: 'V1 PAF0 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_bw', - label: 'V1 PAF1 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_bw', - label: 'V1 PAF2 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_shift', - label: 'V1 PAF0 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_shift', - label: 'V1 PAF1 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_shift', - label: 'V1 PAF2 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_amp_attack', - label: 'V1 Amp Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_amp_decay', - label: 'V1 Amp Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_amp_sustain', - label: 'V1 Amp Sustain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_amp_release', - label: 'V1 Amp Release', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_pitch_attack', - label: 'V1 Pitch Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_pitch_decay', - label: 'V1 Pitch Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_pitch_emph', - label: 'V1 Pitch Emph', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - ], - voice_spaces: [ - 'Direct', - ], - ui: { - primary_input: 'xy_pad', - show_voice_space_selector: false, - show_synth_visualizer: true, - }, -}; diff --git a/playground/src/modes/generated/paf_synth_schema.ts b/playground/src/modes/generated/paf_synth_schema.ts deleted file mode 100644 index 6b66bc0..0000000 --- a/playground/src/modes/generated/paf_synth_schema.ts +++ /dev/null @@ -1,374 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/paf_synth.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface PafSynthParams { - readonly p00: number; - readonly p01: number; - readonly paf0_cf: number; - readonly paf1_cf: number; - readonly p04: number; - readonly paf0_bw: number; - readonly paf1_bw: number; - readonly p07: number; - readonly paf0_vib: number; - readonly paf1_vib: number; - readonly p10: number; - readonly paf0_vfr: number; - readonly paf1_vfr: number; - readonly p13: number; - readonly paf0_shift: number; - readonly paf1_shift: number; - readonly p16: number; - readonly dl1mix: number; - readonly p18: number; - readonly dlfb: number; - readonly env_decay: number; - readonly p21: number; - readonly p22: number; - readonly p23: number; - readonly p24: number; - readonly p25: number; - readonly shape_gain: number; - readonly shape_asym: number; - readonly shape_mix: number; - readonly rm_gain: number; - readonly env_attack: number; - readonly env_sustain: number; - readonly env_release: number; -} - -export const PafSynthSchema: ModeSchema = { - mode_id: 'paf_synth', - engine_id: 'paf_synth', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 10, - 14, - ], - output_size: 33, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'p00', - label: 'Param 00', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p01', - label: 'Param 01', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'paf0_cf', - label: 'PAF0 Center Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'paf1_cf', - label: 'PAF1 Center Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'p04', - label: 'Param 04', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'paf0_bw', - label: 'PAF0 Bandwidth', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'paf1_bw', - label: 'PAF1 Bandwidth', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'p07', - label: 'Param 07', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'paf0_vib', - label: 'PAF0 Vibrato Depth', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'modulation', - }, - { - name: 'paf1_vib', - label: 'PAF1 Vibrato Depth', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'modulation', - }, - { - name: 'p10', - label: 'Param 10', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'paf0_vfr', - label: 'PAF0 Vibrato Rate', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'modulation', - }, - { - name: 'paf1_vfr', - label: 'PAF1 Vibrato Rate', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'modulation', - }, - { - name: 'p13', - label: 'Param 13', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'paf0_shift', - label: 'PAF0 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'paf1_shift', - label: 'PAF1 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'operators', - }, - { - name: 'p16', - label: 'Param 16', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'dl1mix', - label: 'Delay Mix', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'delay', - }, - { - name: 'p18', - label: 'Param 18', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'dlfb', - label: 'Delay Feedback', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delay', - }, - { - name: 'env_decay', - label: 'Env Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'envelope', - }, - { - name: 'p21', - label: 'Param 21', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p22', - label: 'Param 22', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p23', - label: 'Param 23', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p24', - label: 'Param 24', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'p25', - label: 'Param 25', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'shape_gain', - label: 'Sine Shape Gain', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'shaper', - }, - { - name: 'shape_asym', - label: 'Sine Shape Asym', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'shaper', - }, - { - name: 'shape_mix', - label: 'Sine Shape Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'shaper', - }, - { - name: 'rm_gain', - label: 'Ring Mod Gain', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'shaper', - }, - { - name: 'env_attack', - label: 'Env Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'envelope', - }, - { - name: 'env_sustain', - label: 'Env Sustain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'envelope', - }, - { - name: 'env_release', - label: 'Env Release', - min: 0, - max: 1, - default: 0.3, - curve: 'linear', - group: 'envelope', - }, - ], - voice_spaces: [ - 'Ellipticacacia', - 'Rowantares', - 'Neemeda', - 'Aquillow', - 'Magnetarch', - 'Elderstar', - 'Ipeleiades', - ], - ui: { - primary_input: 'xy_pad', - show_voice_space_selector: true, - show_synth_visualizer: true, - }, -}; diff --git a/playground/src/modes/generated/slp_workshop_schema.ts b/playground/src/modes/generated/slp_workshop_schema.ts deleted file mode 100644 index 4a74ad1..0000000 --- a/playground/src/modes/generated/slp_workshop_schema.ts +++ /dev/null @@ -1,598 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/slp_workshop.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface SlpWorkshopParams { - readonly seq0_ratio0: number; - readonly seq0_ratio1: number; - readonly seq0_ratio2: number; - readonly seq0_phasor_mul: number; - readonly seq0_phase_off: number; - readonly seq0_amp0: number; - readonly seq0_amp1: number; - readonly seq1_ratio0: number; - readonly seq1_ratio1: number; - readonly seq1_ratio2: number; - readonly seq1_phasor_mul: number; - readonly seq1_phase_off: number; - readonly seq1_amp0: number; - readonly seq1_amp1: number; - readonly v0_base_freq: number; - readonly v0_paf0_cf: number; - readonly v0_paf1_cf: number; - readonly v0_paf2_cf: number; - readonly v0_paf0_bw: number; - readonly v0_paf1_bw: number; - readonly v0_paf2_bw: number; - readonly v0_paf_vib: number; - readonly v0_paf_vfr: number; - readonly v0_paf0_shift: number; - readonly v0_paf1_shift: number; - readonly v0_paf2_shift: number; - readonly v0_amp_attack: number; - readonly v0_amp_decay: number; - readonly v0_amp_sustain: number; - readonly v0_amp_release: number; - readonly v0_pitch_attack: number; - readonly v0_pitch_decay: number; - readonly v0_pitch_emph: number; - readonly v0_shape_gain: number; - readonly v0_shape_asym: number; - readonly v0_shape_mix: number; - readonly v0_rm_gain: number; - readonly v1_base_freq: number; - readonly v1_detune1: number; - readonly v1_detune2: number; - readonly v1_paf0_cf: number; - readonly v1_paf1_cf: number; - readonly v1_paf2_cf: number; - readonly v1_paf0_bw: number; - readonly v1_paf1_bw: number; - readonly v1_paf2_bw: number; - readonly v1_paf0_shift: number; - readonly v1_paf1_shift: number; - readonly v1_paf2_shift: number; - readonly v1_amp_attack: number; - readonly v1_amp_decay: number; - readonly v1_amp_sustain: number; - readonly v1_amp_release: number; - readonly v1_pitch_attack: number; - readonly v1_pitch_decay: number; - readonly v1_pitch_emph: number; -} - -export const SlpWorkshopSchema: ModeSchema = { - mode_id: 'slp_workshop', - engine_id: 'memlcelium', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 14, - 18, - ], - output_size: 56, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'seq0_ratio0', - label: 'Seq0 Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_ratio1', - label: 'Seq0 Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_ratio2', - label: 'Seq0 Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_phasor_mul', - label: 'Seq0 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_phase_off', - label: 'Seq0 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_amp0', - label: 'Seq0 Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq0_amp1', - label: 'Seq0 Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio0', - label: 'Seq1 Ratio 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio1', - label: 'Seq1 Ratio 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_ratio2', - label: 'Seq1 Ratio 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_phasor_mul', - label: 'Seq1 Phasor Mul', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_phase_off', - label: 'Seq1 Phase Off', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_amp0', - label: 'Seq1 Amp 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'seq1_amp1', - label: 'Seq1 Amp 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'sequencer', - }, - { - name: 'v0_base_freq', - label: 'V0 Base Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf0_cf', - label: 'V0 PAF0 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_cf', - label: 'V0 PAF1 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_cf', - label: 'V0 PAF2 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf0_bw', - label: 'V0 PAF0 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_bw', - label: 'V0 PAF1 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_bw', - label: 'V0 PAF2 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf_vib', - label: 'V0 PAF Vib', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'v0_synth', - }, - { - name: 'v0_paf_vfr', - label: 'V0 PAF Vib Rate', - min: 0, - max: 1, - default: 0.5, - curve: 'square', - group: 'v0_synth', - }, - { - name: 'v0_paf0_shift', - label: 'V0 PAF0 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf1_shift', - label: 'V0 PAF1 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_paf2_shift', - label: 'V0 PAF2 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_amp_attack', - label: 'V0 Amp Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_amp_decay', - label: 'V0 Amp Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_amp_sustain', - label: 'V0 Amp Sustain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_amp_release', - label: 'V0 Amp Release', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_pitch_attack', - label: 'V0 Pitch Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_pitch_decay', - label: 'V0 Pitch Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v0_env', - }, - { - name: 'v0_pitch_emph', - label: 'V0 Pitch Emph', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_env', - }, - { - name: 'v0_shape_gain', - label: 'V0 Shape Gain', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_shape_asym', - label: 'V0 Shape Asym', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_shape_mix', - label: 'V0 Shape Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v0_rm_gain', - label: 'V0 RingMod Gain', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v0_synth', - }, - { - name: 'v1_base_freq', - label: 'V1 Base Freq', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_detune1', - label: 'V1 Detune 1', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_detune2', - label: 'V1 Detune 2', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_cf', - label: 'V1 PAF0 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_cf', - label: 'V1 PAF1 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_cf', - label: 'V1 PAF2 CF', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_bw', - label: 'V1 PAF0 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_bw', - label: 'V1 PAF1 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_bw', - label: 'V1 PAF2 BW', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf0_shift', - label: 'V1 PAF0 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf1_shift', - label: 'V1 PAF1 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_paf2_shift', - label: 'V1 PAF2 Shift', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_synth', - }, - { - name: 'v1_amp_attack', - label: 'V1 Amp Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_amp_decay', - label: 'V1 Amp Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_amp_sustain', - label: 'V1 Amp Sustain', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_amp_release', - label: 'V1 Amp Release', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_pitch_attack', - label: 'V1 Pitch Attack', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - { - name: 'v1_pitch_decay', - label: 'V1 Pitch Decay', - min: 0, - max: 1, - default: 0.3, - curve: 'square', - group: 'v1_env', - }, - { - name: 'v1_pitch_emph', - label: 'V1 Pitch Emph', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'v1_env', - }, - ], - voice_spaces: [ - 'Direct', - ], - ui: { - primary_input: 'xy_pad', - show_voice_space_selector: false, - show_synth_visualizer: true, - }, -}; diff --git a/playground/src/modes/generated/sound_analysis_midi_schema.ts b/playground/src/modes/generated/sound_analysis_midi_schema.ts deleted file mode 100644 index 67bd6cc..0000000 --- a/playground/src/modes/generated/sound_analysis_midi_schema.ts +++ /dev/null @@ -1,122 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/sound_analysis_midi.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface SoundAnalysisMidiParams { - readonly midi_cc0: number; - readonly midi_cc1: number; - readonly midi_cc2: number; - readonly midi_cc3: number; - readonly midi_cc4: number; - readonly midi_cc5: number; - readonly midi_cc6: number; - readonly midi_cc7: number; -} - -export const SoundAnalysisMidiSchema: ModeSchema = { - mode_id: 'sound_analysis_midi', - engine_id: 'thru', - ml: { - input_channels: [ - 'audio_pitch', - 'audio_aperiodicity', - 'audio_energy', - 'audio_attack', - 'audio_brightness', - 'audio_energy_crude', - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 10, - hidden_layers: [ - 10, - 10, - 14, - ], - output_size: 8, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'midi_cc0', - label: 'MIDI CC 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc1', - label: 'MIDI CC 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc2', - label: 'MIDI CC 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc3', - label: 'MIDI CC 3', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc4', - label: 'MIDI CC 4', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc5', - label: 'MIDI CC 5', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc6', - label: 'MIDI CC 6', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - { - name: 'midi_cc7', - label: 'MIDI CC 7', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'midi', - }, - ], - voice_spaces: [], - ui: { - primary_input: 'audio_in', - show_voice_space_selector: false, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/generated/types.ts b/playground/src/modes/generated/types.ts deleted file mode 100644 index 1aec060..0000000 --- a/playground/src/modes/generated/types.ts +++ /dev/null @@ -1,54 +0,0 @@ -// AUTOGENERATED — do not edit. Run `bun run codegen/generate.ts` to regenerate. -// Shared TypeScript types for generated mode schemas. - -export type Curve = - | 'linear' - | 'exp' - | 'log' - | 'square' - | 'sqrt' - | 'sigmoid' - | 'cubic'; - -export type PrimaryInput = - | 'xy_pad' - | 'joystick' - | 'sliders' - | 'audio_in' - | 'midi_in' - | 'none'; - -export interface Param { - readonly name: string; - readonly label: string; - readonly min: number; - readonly max: number; - readonly default: number; - readonly curve: Curve; - readonly group: string; -} - -export interface MLConfig { - readonly input_channels: readonly string[]; - readonly input_size: number; - readonly hidden_layers: readonly number[]; - readonly output_size: number; - readonly default_spread: number; - readonly default_learning_rate: number; - readonly default_max_iterations: number; -} - -export interface UIConfig { - readonly primary_input: PrimaryInput; - readonly show_voice_space_selector: boolean; - readonly show_synth_visualizer: boolean; -} - -export interface ModeSchema { - readonly mode_id: string; - readonly engine_id: string; - readonly ml: MLConfig; - readonly params: readonly Param[]; - readonly voice_spaces: readonly string[]; - readonly ui: UIConfig; -} diff --git a/playground/src/modes/generated/verb_fx_schema.ts b/playground/src/modes/generated/verb_fx_schema.ts deleted file mode 100644 index e24a80e..0000000 --- a/playground/src/modes/generated/verb_fx_schema.ts +++ /dev/null @@ -1,519 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/verb_fx.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface VerbFxParams { - readonly fb_delay_xfade: number; - readonly lp0_fb: number; - readonly lp0_cutoff: number; - readonly lp1_fb: number; - readonly lp1_cutoff: number; - readonly lp2_fb: number; - readonly lp2_cutoff: number; - readonly lp3_fb: number; - readonly lp3_cutoff: number; - readonly lp4_fb: number; - readonly lp4_cutoff: number; - readonly lp5_fb: number; - readonly lp5_cutoff: number; - readonly lp6_fb: number; - readonly lp6_cutoff: number; - readonly lp7_fb: number; - readonly lp7_cutoff: number; - readonly allp0_fb: number; - readonly allp1_fb: number; - readonly allp2_fb: number; - readonly allp3_fb: number; - readonly fbank_f0: number; - readonly fbank_f1: number; - readonly fbank_f2: number; - readonly fbank_f3: number; - readonly fbank_f4: number; - readonly fbank_f5: number; - readonly fbank_f6: number; - readonly fbank_f7: number; - readonly fbank_res0: number; - readonly fbank_res1: number; - readonly fbank_res2: number; - readonly fbank_res3: number; - readonly fbank_res4: number; - readonly fbank_res5: number; - readonly fbank_res6: number; - readonly fbank_res7: number; - readonly delay0_time: number; - readonly delay0_fb: number; - readonly delay1_time: number; - readonly delay1_fb: number; - readonly delay2_time: number; - readonly delay2_fb: number; - readonly verb_vs_delay: number; - readonly delay_to_verb: number; - readonly delay_morph: number; - readonly delay_blend: number; -} - -export const VerbFxSchema: ModeSchema = { - mode_id: 'verb_fx', - engine_id: 'verb_fx', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 14, - 18, - ], - output_size: 47, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'fb_delay_xfade', - label: 'Bank/Delay XFade', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'routing', - }, - { - name: 'lp0_fb', - label: 'LP Comb 0 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp0_cutoff', - label: 'LP Comb 0 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp1_fb', - label: 'LP Comb 1 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp1_cutoff', - label: 'LP Comb 1 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp2_fb', - label: 'LP Comb 2 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp2_cutoff', - label: 'LP Comb 2 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp3_fb', - label: 'LP Comb 3 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp3_cutoff', - label: 'LP Comb 3 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp4_fb', - label: 'LP Comb 4 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp4_cutoff', - label: 'LP Comb 4 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp5_fb', - label: 'LP Comb 5 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp5_cutoff', - label: 'LP Comb 5 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp6_fb', - label: 'LP Comb 6 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp6_cutoff', - label: 'LP Comb 6 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp7_fb', - label: 'LP Comb 7 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'lp7_cutoff', - label: 'LP Comb 7 Cutoff', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp0_fb', - label: 'Allpass 0 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp1_fb', - label: 'Allpass 1 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp2_fb', - label: 'Allpass 2 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp3_fb', - label: 'Allpass 3 FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'verb', - }, - { - name: 'fbank_f0', - label: 'Filterbank Freq 0', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f1', - label: 'Filterbank Freq 1', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f2', - label: 'Filterbank Freq 2', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f3', - label: 'Filterbank Freq 3', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f4', - label: 'Filterbank Freq 4', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f5', - label: 'Filterbank Freq 5', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f6', - label: 'Filterbank Freq 6', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_f7', - label: 'Filterbank Freq 7', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res0', - label: 'Filterbank Res 0', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res1', - label: 'Filterbank Res 1', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res2', - label: 'Filterbank Res 2', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res3', - label: 'Filterbank Res 3', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res4', - label: 'Filterbank Res 4', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res5', - label: 'Filterbank Res 5', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res6', - label: 'Filterbank Res 6', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'fbank_res7', - label: 'Filterbank Res 7', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'filterbank', - }, - { - name: 'delay0_time', - label: 'Long Delay Time', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay0_fb', - label: 'Long Delay FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay1_time', - label: 'Med Delay Time', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay1_fb', - label: 'Med Delay FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay2_time', - label: 'Short Delay Time', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay2_fb', - label: 'Short Delay FB', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'verb_vs_delay', - label: 'Verb vs Delay', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'routing', - }, - { - name: 'delay_to_verb', - label: 'Delay→Verb Send', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'routing', - }, - { - name: 'delay_morph', - label: 'Delay Morph', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'delays', - }, - { - name: 'delay_blend', - label: 'Delay Blend', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - ], - voice_spaces: [ - 'Default', - 'Resonant', - 'Soft', - 'Cathedral', - 'Shimmer', - 'Chamber', - 'Metallic', - 'Granular', - 'Diffuse', - 'Dark', - 'Bright', - 'Harmonic', - ], - ui: { - primary_input: 'joystick', - show_voice_space_selector: true, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/generated/xiasri_schema.ts b/playground/src/modes/generated/xiasri_schema.ts deleted file mode 100644 index e1b774a..0000000 --- a/playground/src/modes/generated/xiasri_schema.ts +++ /dev/null @@ -1,278 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/xiasri.json. Run `bun run codegen/generate.ts` to regenerate. -import type { ModeSchema } from './types'; - -export interface XiasriParams { - readonly dl1_mix: number; - readonly dl2_mix: number; - readonly dl3_mix: number; - readonly p03: number; - readonly allp1_fb: number; - readonly allp2_fb: number; - readonly comb1_fb: number; - readonly comb2_fb: number; - readonly dl1_fb: number; - readonly dl2_fb: number; - readonly dl3_fb: number; - readonly wetdry_mix: number; - readonly pitch_transp: number; - readonly pitch_mix: number; - readonly allp3_fb: number; - readonly allp4_fb: number; - readonly allp5_fb: number; - readonly allp6_fb: number; - readonly allp3_mix: number; - readonly allp4_mix: number; - readonly allp5_mix: number; - readonly allp6_mix: number; - readonly dl4_mix: number; - readonly dl4_fb: number; -} - -export const XiasriSchema: ModeSchema = { - mode_id: 'xiasri', - engine_id: 'xiasri', - ml: { - input_channels: [ - 'joy_x', - 'joy_y', - 'joy_z', - 'joy_w', - ], - input_size: 4, - hidden_layers: [ - 10, - 10, - 14, - ], - output_size: 24, - default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, - }, - params: [ - { - name: 'dl1_mix', - label: 'Delay 1 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'dl2_mix', - label: 'Delay 2 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'dl3_mix', - label: 'Delay 3 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'p03', - label: 'Param 03', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'general', - }, - { - name: 'allp1_fb', - label: 'Allpass 1 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp2_fb', - label: 'Allpass 2 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'comb1_fb', - label: 'Comb 1 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'comb2_fb', - label: 'Comb 2 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'dl1_fb', - label: 'Delay 1 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'dl2_fb', - label: 'Delay 2 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'dl3_fb', - label: 'Delay 3 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - { - name: 'wetdry_mix', - label: 'Wet/Dry Mix', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'mix', - }, - { - name: 'pitch_transp', - label: 'Pitch Transpose', - min: 0, - max: 1, - default: 0.5, - curve: 'linear', - group: 'pitch', - }, - { - name: 'pitch_mix', - label: 'Pitch Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'pitch', - }, - { - name: 'allp3_fb', - label: 'Allpass 3 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp4_fb', - label: 'Allpass 4 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp5_fb', - label: 'Allpass 5 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp6_fb', - label: 'Allpass 6 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp3_mix', - label: 'Allpass 3 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp4_mix', - label: 'Allpass 4 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp5_mix', - label: 'Allpass 5 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'allp6_mix', - label: 'Allpass 6 Mix', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'verb', - }, - { - name: 'dl4_mix', - label: 'Delay 4 Mix', - min: 0, - max: 1, - default: 0, - curve: 'square', - group: 'delays', - }, - { - name: 'dl4_fb', - label: 'Delay 4 Feedback', - min: 0, - max: 1, - default: 0, - curve: 'linear', - group: 'delays', - }, - ], - voice_spaces: [ - 'Direct', - ], - ui: { - primary_input: 'joystick', - show_voice_space_selector: false, - show_synth_visualizer: false, - }, -}; diff --git a/playground/src/modes/index.ts b/playground/src/modes/index.ts deleted file mode 100644 index 006653a..0000000 --- a/playground/src/modes/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Mode registry — maps mode_id (and the special "c15" placeholder) to the - * TSX component that renders it. The ModeSwitcher reads from this list to - * populate its dropdown, and `App.tsx` looks up the active mode here. - * - * Order matters — the switcher renders modes in this order. - */ - -import type { Component } from 'solid-js'; -import { PAFSynthMode } from './PAFSynthMode'; -import { ChannelStripMode } from './ChannelStripMode'; -import { XIASRIMode } from './XIASRIMode'; -import { VerbFXMode } from './VerbFXMode'; -import { MEMLCeliumMode } from './MEMLCeliumMode'; -import { SLPWorkshopMode } from './SLPWorkshopMode'; -import { BreakOrMode } from './BreakOrMode'; -import { ElysiamorfMode } from './ElysiamorfMode'; -import { SoundAnalysisMIDIMode } from './SoundAnalysisMIDIMode'; -import { C15Mode } from './C15Mode'; - -export interface ModeRegistration { - /** Stable id; matches the schema's `mode_id` for firmware modes. */ - id: string; - /** Human-readable label for the switcher. */ - label: string; - /** Short description for the switcher tooltip. */ - description: string; - /** TSX component rendering the mode. */ - Component: Component; - /** True for browser-only placeholders (currently just C15). */ - placeholder?: boolean; -} - -export const MODE_REGISTRY: ReadonlyArray = [ - { - id: 'paf_synth', - label: 'PAF Synth', - description: 'Phase-aligned formant synth (XY pad).', - Component: PAFSynthMode, - }, - { - id: 'channel_strip', - label: 'Channel Strip', - description: 'EQ + dynamics processing channel.', - Component: ChannelStripMode, - }, - { - id: 'xiasri', - label: 'XIASRI', - description: 'Audio-reactive verb / pitch engine.', - Component: XIASRIMode, - }, - { - id: 'verb_fx', - label: 'Verb FX', - description: 'Reverb / multi-effects unit.', - Component: VerbFXMode, - }, - { - id: 'memlcelium', - label: 'MEML Celium', - description: 'Voice + dual-MLP CV/gate via uSEQ.', - Component: MEMLCeliumMode, - }, - { - id: 'slp_workshop', - label: 'SLP-Workshop', - description: 'MEMLCelium voice + live Jolt / Explore learning (Synth Library Portland).', - Component: SLPWorkshopMode, - }, - { - id: 'breakor', - label: 'Breakor', - description: 'Drum / breakbeat synthesis.', - Component: BreakOrMode, - }, - { - id: 'elysiamorf', - label: 'Elysiamorf', - description: 'Granular morphing synth.', - Component: ElysiamorfMode, - }, - { - id: 'sound_analysis_midi', - label: 'Sound Analysis → MIDI', - description: 'Audio features → MIDI CC routing.', - Component: SoundAnalysisMIDIMode, - }, - { - id: 'c15', - label: 'C15 (browser-only)', - description: 'C15 WASM synth. Not yet ported.', - Component: C15Mode, - placeholder: true, - }, -]; - -/** Look up a mode by id, falling back to the first registration. */ -export function getModeById(id: string | null): ModeRegistration { - if (!id) return MODE_REGISTRY[0]!; - return MODE_REGISTRY.find((m) => m.id === id) ?? MODE_REGISTRY[0]!; -} - -export { ModeShell } from './ModeShell'; -export { useModeRuntime } from './mode-runtime'; -export type { ModeRuntime } from './mode-runtime'; diff --git a/playground/src/modes/mode-helpers.ts b/playground/src/modes/mode-helpers.ts deleted file mode 100644 index 785493e..0000000 --- a/playground/src/modes/mode-helpers.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Helpers shared across mode TSX files. Pure functions; no Solid state. - */ - -import type { Param } from './generated/types'; -import type { SliderConfig } from '../primitives/SliderBank'; -import type { CurveName } from '../output/curves'; - -/** - * Convert a schema param list into SliderConfig entries that the SliderBank - * primitive understands. Sliders are grouped by the schema's `group` field - * so the bank renders collapsible sections. - */ -export function paramsToSliderConfig(params: ReadonlyArray): SliderConfig[] { - let lastGroup: string | null = null; - return params.map((p) => { - const isNewGroup = p.group !== lastGroup; - lastGroup = p.group; - const cfg: SliderConfig = { - id: p.name, - label: p.label, - min: p.min, - max: p.max, - curve: p.curve as CurveName, - }; - if (isNewGroup) cfg.section = formatGroupName(p.group); - return cfg; - }); -} - -/** - * Map a Float32Array (length = N) of normalized values [0,1] to a flat - * array sized to match the slider config (already in min/max range). - * - * If `overrides` is supplied, per-param min/max overrides are honoured - * instead of the schema defaults — this is what the mode UI sliders - * display when the user has tightened the active range. - */ -export function outputsToSliderValues( - outputs: Float32Array, - params: ReadonlyArray, - overrides?: Record, -): number[] { - const out: number[] = []; - for (let i = 0; i < params.length; ++i) { - const v = outputs[i] ?? 0; - const p = params[i]!; - const ov = overrides?.[p.name]; - if (ov && ov.muted && ov.fixedValue !== undefined) { - out.push(ov.fixedValue); - continue; - } - const min = ov?.min ?? p.min; - const max = ov?.max ?? p.max; - out.push(min + v * (max - min)); - } - return out; -} - -function formatGroupName(group: string): string { - if (!group) return ''; - return group - .split(/[_\s]+/) - .map((p) => p.charAt(0).toUpperCase() + p.slice(1)) - .join(' '); -} diff --git a/playground/src/modes/mode-runtime.ts b/playground/src/modes/mode-runtime.ts deleted file mode 100644 index 31404be..0000000 --- a/playground/src/modes/mode-runtime.ts +++ /dev/null @@ -1,785 +0,0 @@ -/** - * Mode runtime — shared wiring between mode TSX components and the - * playground's stores / WASM ML / audio engine host. - * - * Every mode does the same dance: - * 1. Hold a primary 2D input position (joystick / xy-pad / external feed). - * 2. Push it through the input pipeline. - * 3. Forward the processed (x, y) to the WASM MLP as input channels [0..N]. - * Modes with input_size > 2 zero-pad the unused channels (or the mic - * analyser fills 0..3 if active). - * 4. Pull the WASM outputs (Float32Array of 126), slice to the schema's - * `output_size`, and run them through the output pipeline. - * 5. Apply per-param overrides (modeStore.activeOverrides). - * 6. Throttle + ship the processed slice to the AudioWorklet engine. - * - * Stream 10 wires: - * - Compound axes → underlying stores (input/output/exploration). - * - Per-param overrides between MLP outputs and engine. - * - Snapshot + undo + A/B compare on RL events. - * - Pin mask passed to moveWeights. - * - Auto-explore interval timer. - * - Trail tracking for JoyMap. - * - Heatmap sampler invalidated on weight-change events. - * - Mic features pushed into MLP for audio-input modes. - */ - -import { batch, createEffect, createMemo, createSignal, onCleanup, onMount, untrack } from 'solid-js'; - -import { mlStore, modeStore, controlStore, sessionStore, explorationStore } from '../stores'; -import { inputStore } from '../stores/input-store'; -import { outputStore } from '../stores/output-store'; -import { coreBus } from '../stores/bus'; -import { processInput, defaultInputState, type InputState } from '../input/pipeline'; -import { - processOutput, - defaultOutputState, - type OutputState, -} from '../output/pipeline'; -import { EngineHost } from '../audio/engine-host'; -import type { EngineId } from '../ml/types'; -import type { ModeSchema } from './generated'; - -import { applyOverrides, buildPinMask } from '../features/overrides'; -import { applyControlRouting } from '../features/control-routing'; -import { Jolt } from '../ml/jolt'; -import { OUExplore } from '../output/ou-explore'; -import { createTrailRing, type TrailPoint } from '../features/trail'; -import { autoSnapshot, undoLastSnapshot } from '../features/snapshots'; -import { HeatmapSampler, type HeatmapColorMode } from '../features/heatmap-sampler'; -import { MicInput } from '../features/mic-input'; - -/** - * Throttle interval for engine param updates (ms). 50ms ≈ 20fps which - * matches the legacy playground's C15 update cadence. - */ -const ENGINE_PARAM_THROTTLE_MS = 50; - -/** A single shared EngineHost. Audio only starts on user gesture. */ -let engineHost: EngineHost | null = null; -function getEngineHost(): EngineHost { - if (!engineHost) engineHost = new EngineHost(); - return engineHost; -} - -export interface ModeRuntime { - /** Driver — call from joystick/xy-pad/etc. */ - setInput: (x: number, y: number) => void; - - /** Most recent processed input (after pipeline). */ - pipedInput: () => readonly [number, number]; - - /** Whether the input is currently frozen by zoom. */ - frozen: () => boolean; - - /** Raw 126-output ML vector (live). */ - rawOutputs: () => Float32Array; - - /** Output-sliced + pipeline-processed vector (length = schema.output_size). */ - processedOutputs: () => Float32Array; - - /** - * Final per-param values after override application (length = params.length). - * These are what get shipped to the engine and visualised in sliders. - */ - paramOutputs: () => Float32Array; - - /** True iff WASM has loaded and the MLP is ready. */ - ready: () => boolean; - - /** Audio host control. */ - audio: { - started: () => boolean; - start: () => Promise; - stop: () => Promise; - setMuted: (muted: boolean) => void; - }; - - /** Mic input control (for audio_in modes; safe to call on others — no-op). */ - mic: { - started: () => boolean; - start: () => Promise; - stop: () => Promise; - }; - - /** Loss / training plumbing surfaced from mlStore. */ - training: { - busy: () => boolean; - examples: () => number; - lastLoss: () => number | null; - lossHistory: () => ReadonlyArray; - }; - - /** Trigger a sync train + push the current pipeline-processed sample. */ - trainOnCurrent: () => void; - - /** RL callbacks. */ - thumbsUp: () => void; - thumbsDown: () => void; - randomize: () => void; - /** Pop snapshot stack and restore weights. Returns true on success. */ - undo: () => boolean; - - /** Whether the snapshot stack has anything to undo. */ - canUndo: () => boolean; - - /** Trail ring for JoyMap binding. */ - trail: () => ReadonlyArray; - /** Snap input back to a trail point. */ - snapToTrail: (p: { x: number; y: number }) => void; - - /** Heatmap sampler. Returns the underlying cells; modes pass to . */ - heatmap: { - cells: () => Float32Array; - setColorMode: (m: HeatmapColorMode) => void; - colorMode: () => HeatmapColorMode; - refresh: (force?: boolean) => void; - resolution: () => number; - }; - - /** Region pin: pin the current zoom window (long-press handler). */ - pinCurrentRegion: () => void; - - /** - * Jolt — held-button continuous weight morph (port of nisps/ml/jolt.hpp). - * `press()` starts morphing the network's weights; a control-rate timer - * glides them while held; `release()` freezes them in place. Inert until - * pressed; modes that never call `press()` are unaffected. - */ - jolt: { - /** Begin the jolt (button press / toggle on). */ - press: () => void; - /** Freeze the jolt (button release / toggle off). */ - release: () => void; - /** Whether the jolt is currently active. */ - active: () => boolean; - }; - - /** - * Explore — OU random-walk exploration intensity in [0,1] (port of - * nisps/ml/ou_noise.hpp). Added to the mode's output vector before audio. - * Intensity 0 = disabled passthrough; modes that never set it are - * unaffected. - */ - explore: { - setIntensity: (level: number) => void; - intensity: () => number; - }; -} - -interface RuntimeOptions { - /** Override the engine id (defaults to schema.engine_id). */ - engineOverride?: EngineId; - /** Skip starting the audio engine even on user gesture. */ - audioDisabled?: boolean; -} - -/** - * Create the runtime for a given mode schema. Call from inside a Solid - * component (uses createSignal/onCleanup). - */ -export function useModeRuntime( - schema: ModeSchema, - opts: RuntimeOptions = {}, -): ModeRuntime { - // ----- WASM init --------------------------------------------------------- - const [ready, setReady] = createSignal(mlStore.state.ready); - - // Lazy initialise WASM. Idempotent across remounts. - void mlStore - .initialize(schema.ml.input_size, schema.ml.output_size) - .then(() => setReady(true)) - .catch((err) => { - // Best-effort; UI keeps running without ML. - // eslint-disable-next-line no-console - console.error('[mode-runtime] mlStore.initialize failed:', err); - }); - - // ----- Mode-store side effects ------------------------------------------ - // Make sure the active mode in the store matches what's actually rendered. - if (modeStore.state.activeModeId !== schema.mode_id) { - modeStore.switchMode(schema.mode_id); - } - - // ----- Compound axis routing ------------------------------------------- - // Whenever any axis or offset changes, re-derive the underlying store - // values. Track axis reads explicitly; do the writes inside untrack so - // we don't pick up stale dependencies on write-targets (input/output/ - // exploration stores). - createEffect(() => { - void controlStore.state.boldness; - void controlStore.state.memory; - void controlStore.state.precision; - void controlStore.state.offsets.boldness; - void controlStore.state.offsets.memory; - void controlStore.state.offsets.precision; - untrack(() => applyControlRouting()); - }); - - // ----- Input pipeline state -------------------------------------------- - const [pipedInput, setPipedInput] = createSignal([0.5, 0.5]); - const [frozen, setFrozen] = createSignal(false); - let inputState: InputState = defaultInputState(); - let lastFrameMs = performance.now(); - - // Trail ring - const trailRing = createTrailRing(); - - // Pressure tracking (set externally via setPressure on touch). - let pressDownAt: number | null = null; - - const setInput = (rawX: number, rawY: number): void => { - const now = performance.now(); - const dt = Math.max(0.001, (now - lastFrameMs) / 1000); - lastFrameMs = now; - - const result = processInput([rawX, rawY], inputStore.config, inputState, dt); - inputState = result.state; - inputStore.__setLiveState(result.state); - setPipedInput([result.x, result.y]); - setFrozen(result.frozen); - trailRing.push(result.x, result.y); - - if (!ready()) return; - // Push input to the MLP. Channels beyond [x,y] are zeroed out (or - // overridden with mic features in audio-input modes). - const inSz = schema.ml.input_size; - mlStore.setInput(0, result.x); - if (inSz > 1) mlStore.setInput(1, result.y); - // Mic features (if active) take channels 2..5. - if (mic.isRunning() && inSz > 2) { - const f = mic.getFeatures(); - const fv = [f.energy, f.brightness, f.pitch, f.aperiodicity]; - for (let i = 2; i < inSz; ++i) { - mlStore.setInput(i, fv[i - 2] ?? 0); - } - } else { - for (let i = 2; i < inSz; ++i) mlStore.setInput(i, 0); - } - mlStore.process(); - - // Update pressure-feedback hold timer. - if (pressDownAt !== null) { - explorationStore.setPressure( - explorationStore.state.pressureForce, - now - pressDownAt, - ); - } - }; - - // ----- Output pipeline + override application -------------------------- - let outputState: OutputState = defaultOutputState(); - const sliceLen = schema.ml.output_size; - const [processedOutputs, setProcessedOutputs] = createSignal( - new Float32Array(sliceLen), - { equals: false }, - ); - const paramCount = schema.params.length; - let prevParamOutputs: Float32Array | null = null; - const [paramOutputs, setParamOutputs] = createSignal( - new Float32Array(paramCount), - { equals: false }, - ); - - // ----- Explore (OU exploration noise) ---------------------------------- - // Inert by default (intensity 0). Applied to the processed slice below, - // after the output pipeline and before override application, so it rides - // on top of the mode's mapping output. Modes that never set an intensity - // leave the output untouched (parity-safe). - const ouExplore = new OUExplore(); - const [exploreIntensity, setExploreIntensitySig] = createSignal(0); - - // Run the output pipeline whenever raw outputs change. - const rawOutputsAccessor = mlStore.outputs; - let lastOutFrameMs = performance.now(); - - const recomputeOutputs = () => { - const raw = rawOutputsAccessor(); - if (raw.length === 0) return; - const now = performance.now(); - const dtMs = Math.max(1, now - lastOutFrameMs); - lastOutFrameMs = now; - // Slice to mode's output_size up front. - const slice = raw.length === sliceLen ? raw : raw.subarray(0, sliceLen); - const result = processOutput(slice as Float32Array, outputStore.config, outputState, dtMs); - outputState = result.state; - // Explore (OU) noise: temporally-correlated random walk added on top of - // the processed output, clamped to [0,1]. No-op when intensity is 0. - ouExplore.apply(result.processed); - setProcessedOutputs(result.processed); - - // Apply per-param overrides for the per-param consumer (engine, sliders). - // Only the first `params.length` outputs are user-visible parameters; the - // remainder is reserved for engine internals (none right now). - const paramSlice = result.processed.length === paramCount - ? result.processed - : result.processed.subarray(0, paramCount); - const overrides = modeStore.state.overrides[schema.mode_id] ?? {}; - const applied = applyOverrides( - paramSlice as Float32Array, - prevParamOutputs, - schema.params, - overrides, - ); - prevParamOutputs = applied.values; - setParamOutputs(applied.values); - }; - - // Trigger recompute on any raw-output change. Wrap in untrack so reading - // outputStore.config inside processOutput doesn't add a tracked dep here. - createEffect(() => { - rawOutputsAccessor(); - untrack(recomputeOutputs); - }); - - // Re-run override application when overrides change (no new ML output). - createEffect(() => { - void modeStore.state.overrides[schema.mode_id]; - untrack(() => { - const raw = rawOutputsAccessor(); - if (raw.length > 0) recomputeOutputs(); - }); - }); - - // Push the freeze mask into outputStore whenever overrides change. - createEffect(() => { - const ovs = modeStore.state.overrides[schema.mode_id] ?? {}; - let mask: Uint8Array | null = null; - for (let i = 0; i < schema.params.length; ++i) { - const ov = ovs[schema.params[i]!.name]; - if (ov && ov.frozen) { - if (!mask) mask = new Uint8Array(schema.params.length); - mask[i] = 1; - } - } - untrack(() => outputStore.setFreezeMask(mask)); - }); - - // ----- Engine wiring (audio) ------------------------------------------- - const host = getEngineHost(); - const [audioStarted, setAudioStarted] = createSignal(host.isStarted); - let pendingParams: Float32Array | null = null; - let throttleTimer: number | null = null; - - const flushParams = () => { - throttleTimer = null; - if (!pendingParams || !host.isStarted) { - pendingParams = null; - return; - } - const copy = new Float32Array(pendingParams); - pendingParams = null; - try { - host.setParams(copy); - } catch (err) { - // eslint-disable-next-line no-console - console.warn('[mode-runtime] setParams failed:', err); - } - }; - - const scheduleParamFlush = (params: Float32Array) => { - pendingParams = params; - if (throttleTimer === null) { - throttleTimer = window.setTimeout(flushParams, ENGINE_PARAM_THROTTLE_MS); - } - }; - - // Pipe paramOutputs into the engine host whenever they change. - createEffect(() => { - const out = paramOutputs(); - if (out.length === 0) return; - if (!host.isStarted) return; - scheduleParamFlush(out); - }); - - const engineId = (opts.engineOverride ?? (schema.engine_id as EngineId)); - - const startAudio = async (): Promise => { - if (opts.audioDisabled) return; - try { - await host.start(engineId); - setAudioStarted(true); - const out = paramOutputs(); - if (out.length > 0) host.setParams(new Float32Array(out)); - } catch (err) { - // eslint-disable-next-line no-console - console.error('[mode-runtime] audio start failed:', err); - } - }; - - const stopAudio = async (): Promise => { - try { - await host.stop(); - } finally { - setAudioStarted(false); - } - }; - - // Switch engine if mode changes engine_id (e.g. on remount). - onMount(() => { - if (host.isStarted) host.setEngine(engineId); - }); - - onCleanup(() => { - if (throttleTimer !== null) { - clearTimeout(throttleTimer); - throttleTimer = null; - } - pendingParams = null; - }); - - // ----- Mic input ------------------------------------------------------- - const mic = new MicInput(); - const [micStarted, setMicStarted] = createSignal(false); - const startMic = async () => { - try { - await mic.start(); - setMicStarted(true); - } catch (err) { - // eslint-disable-next-line no-console - console.error('[mode-runtime] mic start failed:', err); - } - }; - const stopMic = async () => { - try { - await mic.stop(); - } finally { - setMicStarted(false); - } - }; - onCleanup(() => { void stopMic(); }); - - // ----- Snapshot stack helpers ------------------------------------------ - const canUndoMemo = createMemo(() => sessionStore.state.snapshots.length > 0); - - // ----- Heatmap sampler ------------------------------------------------- - const sampler = new HeatmapSampler({ resolution: 16 }); - const [heatmapColorMode, setHeatmapColorMode] = createSignal('luminance'); - const [heatmapCells, setHeatmapCells] = createSignal(sampler.getCells(), { equals: false }); - const refreshHeatmap = (force = false): void => { - untrack(() => { - if (!ready()) return; - const cfg = inputStore.config; - const z = cfg.zoom; - const cx = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorX; - const cy = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorY; - const took = sampler.update({ cx, cy, zoom: z }, force); - if (took) { - setHeatmapCells(new Float32Array(sampler.getCells())); - } - }); - }; - - // Refresh heatmap on weight changes (training, RL feedback, randomize). - const offTrained = coreBus.on('ml.trained', () => refreshHeatmap()); - const offDelta = coreBus.on('ml.delta_update', () => refreshHeatmap()); - onCleanup(() => { offTrained(); offDelta(); }); - - // Initial heatmap refresh once WASM is ready. - createEffect(() => { - if (ready()) refreshHeatmap(true); - }); - - // ----- Auto-explore loop ----------------------------------------------- - let autoExploreTimer: number | null = null; - const tickAutoExplore = () => { - untrack(() => { - if (!ready()) return; - if (!explorationStore.state.autoExploreEnabled) return; - const intensity = explorationStore.state.autoExploreIntensity; - // Zoom-scaled intensity: smaller zoom = gentler. - const zoom = inputStore.config.zoom; - const scaledIntensity = intensity * (0.3 + 0.7 * zoom); - const cap = explorationStore.state.noiseCap * scaledIntensity; - autoSnapshot('before auto-explore'); - const spread = explorationStore.state.spread; - const overrides = modeStore.state.overrides[schema.mode_id] ?? {}; - const pinMask = buildPinMask( - mlStore.state.outputSize, - schema.mode_id, - schema.params, - overrides, - sessionStore.state.paramPins, - ); - mlStore.moveWeights(cap, spread, pinMask); - explorationStore.growNoise(0.5); - // Re-run inference to update the heatmap and visuals. - const [x, y] = pipedInput(); - setInput(x, y); - }); - }; - createEffect(() => { - const enabled = explorationStore.state.autoExploreEnabled; - const interval = explorationStore.state.autoExploreIntervalMs; - if (autoExploreTimer !== null) { - window.clearInterval(autoExploreTimer); - autoExploreTimer = null; - } - if (enabled) { - autoExploreTimer = window.setInterval(tickAutoExplore, interval); - } - }); - onCleanup(() => { - if (autoExploreTimer !== null) { - window.clearInterval(autoExploreTimer); - autoExploreTimer = null; - } - }); - - // ----- Jolt (held-button continuous weight morph) ---------------------- - // Inert until press(). While active, a ~200Hz control-rate timer glides a - // scatter of the network's weights toward random targets (port of - // nisps/ml/jolt.hpp). release() freezes them where they landed. - const jolt = new Jolt(); - let joltTimer: number | null = null; - // ~200Hz to match the upstream firmware control rate the constants assume. - const JOLT_TICK_MS = 5; - - const tickJolt = () => { - if (!ready() || !jolt.active()) return; - const w = mlStore.getWeights(); - if (w.length === 0) return; - jolt.step(w); - mlStore.setWeights(w); - coreBus.emit('ml.delta_update', { reason: 'jolt' }); - // Re-run inference so the audio + visuals reflect the morphed weights. - const [x, y] = pipedInput(); - setInput(x, y); - }; - - const joltPress = () => { - if (!ready()) return; - autoSnapshot('before jolt'); - jolt.press(mlStore.iml?.weightCount ?? mlStore.getWeights().length); - if (joltTimer === null) { - joltTimer = window.setInterval(tickJolt, JOLT_TICK_MS); - } - }; - - const joltRelease = () => { - jolt.release(); - if (joltTimer !== null) { - window.clearInterval(joltTimer); - joltTimer = null; - } - }; - onCleanup(() => { - if (joltTimer !== null) { - window.clearInterval(joltTimer); - joltTimer = null; - } - }); - - // ----- Explore (OU) control-rate driver -------------------------------- - // The OU walk advances inside recomputeOutputs (driven by setInput). When - // the input is static the walk would stall, so while Explore is active we - // keep ticking so the sound keeps roaming. Inert when intensity is 0. - let exploreTimer: number | null = null; - const EXPLORE_TICK_MS = 30; - const setExploreIntensity = (level: number) => { - ouExplore.setIntensity(level); - setExploreIntensitySig(ouExplore.intensity()); - if (ouExplore.enabled() && exploreTimer === null) { - exploreTimer = window.setInterval(() => { - untrack(() => { - if (!ready()) return; - // Re-run the output pipeline (advances the OU state) and reship. - recomputeOutputs(); - }); - }, EXPLORE_TICK_MS); - } else if (!ouExplore.enabled() && exploreTimer !== null) { - window.clearInterval(exploreTimer); - exploreTimer = null; - ouExplore.reset(); - } - }; - onCleanup(() => { - if (exploreTimer !== null) { - window.clearInterval(exploreTimer); - exploreTimer = null; - } - }); - - // ----- Touch / pressure feedback --------------------------------------- - const onPointerDown = (e: PointerEvent) => { - pressDownAt = performance.now(); - explorationStore.setPressure( - // Force is 0..1 on supported devices; default 0.5 elsewhere. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (e as any).pressure ?? 0.5, - 0, - ); - }; - const onPointerUp = () => { - pressDownAt = null; - explorationStore.setPressure(0, 0); - }; - if (typeof window !== 'undefined') { - window.addEventListener('pointerdown', onPointerDown, { passive: true }); - window.addEventListener('pointerup', onPointerUp); - window.addEventListener('pointercancel', onPointerUp); - onCleanup(() => { - window.removeEventListener('pointerdown', onPointerDown); - window.removeEventListener('pointerup', onPointerUp); - window.removeEventListener('pointercancel', onPointerUp); - }); - } - - // ----- Training helpers ------------------------------------------------- - const trainOnCurrent = () => { - if (!ready()) return; - autoSnapshot('before train'); - const lr = explorationStore.state.learningRate; - mlStore.train(lr, schema.ml.default_max_iterations, 0.001); - }; - - const thumbsUp = () => { - if (!ready()) return; - const [x, y] = pipedInput(); - const out = paramOutputs(); - if (out.length === 0) return; - const features = new Array(schema.ml.input_size).fill(0); - features[0] = x; - if (features.length > 1) features[1] = y; - // Mic features into the example too, if active. - if (mic.isRunning() && schema.ml.input_size > 2) { - const f = mic.getFeatures(); - const fv = [f.energy, f.brightness, f.pitch, f.aperiodicity]; - for (let i = 2; i < schema.ml.input_size; ++i) { - features[i] = fv[i - 2] ?? 0; - } - } - // Labels: the raw 126-vector targets (not the override-applied; the MLP - // doesn't know about overrides). - const labels = Array.from(processedOutputs()); - autoSnapshot('before thumbs-up'); - mlStore.addExample(features, labels); - explorationStore.decayNoise(explorationStore.state.pressureForce); - trainOnCurrent(); - }; - - const thumbsDown = () => { - if (!ready()) return; - autoSnapshot('before thumbs-down'); - const cap = explorationStore.state.noiseCap; - const spread = explorationStore.state.spread; - const overrides = modeStore.state.overrides[schema.mode_id] ?? {}; - const pinMask = buildPinMask( - mlStore.state.outputSize, - schema.mode_id, - schema.params, - overrides, - sessionStore.state.paramPins, - ); - mlStore.moveWeights(cap, spread, pinMask); - explorationStore.growNoise(explorationStore.state.pressureForce); - const [x, y] = pipedInput(); - setInput(x, y); - }; - - const randomize = () => { - if (!ready()) return; - autoSnapshot('before randomize'); - mlStore.drawWeights(explorationStore.state.spread); - const [x, y] = pipedInput(); - setInput(x, y); - }; - - const undo = (): boolean => { - const ok = undoLastSnapshot(); - if (ok) { - const [x, y] = pipedInput(); - setInput(x, y); - } - return ok; - }; - - // ----- Region pins ------------------------------------------------------ - const pinCurrentRegion = () => { - const cfg = inputStore.config; - const z = cfg.zoom; - const cx = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorX; - const cy = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorY; - const halfZ = z * 0.5; - sessionStore.addRegionPin({ - x: Math.max(0, cx - halfZ), - y: Math.max(0, cy - halfZ), - width: Math.min(z, 1), - height: Math.min(z, 1), - }); - autoSnapshot('pinned baseline'); - }; - - // Snap-to-trail - const snapToTrail = (p: { x: number; y: number }) => { - batch(() => { - setInput(p.x, p.y); - }); - }; - - return { - setInput, - pipedInput, - frozen, - rawOutputs: rawOutputsAccessor, - processedOutputs, - paramOutputs, - ready, - audio: { - started: audioStarted, - start: startAudio, - stop: stopAudio, - setMuted: (muted) => host.setMuted(muted), - }, - mic: { - started: micStarted, - start: startMic, - stop: stopMic, - }, - training: { - busy: () => mlStore.state.training, - examples: () => mlStore.state.exampleCount, - lastLoss: () => mlStore.state.lastLoss, - lossHistory: () => mlStore.state.lossHistory, - }, - trainOnCurrent, - thumbsUp, - thumbsDown, - randomize, - undo, - canUndo: () => canUndoMemo(), - trail: trailRing.points, - snapToTrail, - heatmap: { - cells: heatmapCells, - setColorMode: (m) => { - sampler.setColorMode(m); - setHeatmapColorMode(m); - refreshHeatmap(true); - }, - colorMode: heatmapColorMode, - refresh: refreshHeatmap, - resolution: () => sampler.resolution, - }, - pinCurrentRegion, - jolt: { - press: joltPress, - release: joltRelease, - active: () => jolt.active(), - }, - explore: { - setIntensity: setExploreIntensity, - intensity: exploreIntensity, - }, - }; -} - -/** - * Disposes the shared EngineHost. Test helper — production never calls this. - */ -export function __disposeEngineHost(): void { - if (engineHost) { - engineHost.dispose(); - engineHost = null; - } -} diff --git a/playground/src/output/curves.ts b/playground/src/output/curves.ts deleted file mode 100644 index a58bbcb..0000000 --- a/playground/src/output/curves.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Curve catalog — TypeScript mirror of the named curves from - * nisps/core/math.hpp (forthcoming, stream 1). All inputs and outputs are in - * [0, 1] unless noted otherwise. - * - * IMPORTANT: This file MUST stay in lockstep with the C++ side. The - * authoritative reference is `nisps/core/math.hpp`. Golden-vector tests - * (stream 11) compare WASM-computed vs TS-computed outputs and fail on - * any drift. - * - * Architecture §5.3: - * linear, exp, log, square, sqrt, sigmoid, cubic, centered_power - * - * The "centered_power" variant comes from the legacy input/output pipelines - * and shapes around 0.5 instead of 0.0. Kept as a named curve because both - * input and output pipelines use it. - */ - -export type CurveName = - | 'linear' - | 'exp' - | 'log' - | 'square' - | 'sqrt' - | 'sigmoid' - | 'cubic' - | 'centered_power'; - -/** Hard clamp to [0, 1]. */ -export function clamp01(v: number): number { - if (v < 0) return 0; - if (v > 1) return 1; - return v; -} - -/** Generic clamp. */ -export function clamp(v: number, lo: number, hi: number): number { - if (v < lo) return lo; - if (v > hi) return hi; - return v; -} - -/** Linear: identity. */ -export function curveLinear(x: number): number { - return clamp01(x); -} - -/** Exponential: e^(k*x) - 1, normalized to [0,1] over [0,1] input. */ -export function curveExp(x: number, k: number = 4.0): number { - if (x <= 0) return 0; - if (x >= 1) return 1; - const denom = Math.exp(k) - 1.0; - if (denom === 0) return x; - return (Math.exp(k * x) - 1.0) / denom; -} - -/** Inverse of curveExp. */ -export function curveLog(x: number, k: number = 4.0): number { - if (x <= 0) return 0; - if (x >= 1) return 1; - const denom = Math.exp(k) - 1.0; - if (denom === 0) return x; - return Math.log(1 + x * denom) / k; -} - -/** Square: x^2. */ -export function curveSquare(x: number): number { - const v = clamp01(x); - return v * v; -} - -/** Square-root. */ -export function curveSqrt(x: number): number { - return Math.sqrt(clamp01(x)); -} - -/** Logistic sigmoid mapped onto [0,1] domain (centered at x=0.5). */ -export function curveSigmoid(x: number, slope: number = 8.0): number { - // Sigmoid centered at 0.5 with given slope. Output is in (0, 1). - // Normalize so endpoints map exactly to 0 and 1. - const t = (x - 0.5) * slope; - const s = 1 / (1 + Math.exp(-t)); - // Anchor: when x=0, t=-slope/2; when x=1, t=+slope/2 - const sLo = 1 / (1 + Math.exp(slope / 2)); - const sHi = 1 / (1 + Math.exp(-slope / 2)); - return (s - sLo) / (sHi - sLo); -} - -/** Cubic ease-in-out. */ -export function curveCubic(x: number): number { - const v = clamp01(x); - // Smoothstep cubic: 3v^2 - 2v^3 - return v * v * (3 - 2 * v); -} - -/** - * Centered power curve. Pivots around 0.5. - * - * exponent < 1 → push toward extremes - * exponent = 1 → identity - * exponent > 1 → pull toward center - */ -export function curveCenteredPower(x: number, exponent: number): number { - if (exponent === 1) return clamp01(x); - const offset = x - 0.5; - const sign = offset < 0 ? -1 : 1; - // Range [-0.5, 0.5] -> [-1, 1] for the power op, then halve back. - const shaped = (sign * Math.pow(Math.abs(offset) * 2, exponent)) / 2; - return clamp01(shaped + 0.5); -} - -/** Apply by name. `param` interpretation depends on the curve. */ -export function applyCurve(name: CurveName, x: number, param?: number): number { - switch (name) { - case 'linear': return curveLinear(x); - case 'exp': return curveExp(x, param ?? 4.0); - case 'log': return curveLog(x, param ?? 4.0); - case 'square': return curveSquare(x); - case 'sqrt': return curveSqrt(x); - case 'sigmoid': return curveSigmoid(x, param ?? 8.0); - case 'cubic': return curveCubic(x); - case 'centered_power': return curveCenteredPower(x, param ?? 1.0); - } -} - -export const CURVE_NAMES: ReadonlyArray = [ - 'linear', 'exp', 'log', 'square', 'sqrt', 'sigmoid', 'cubic', 'centered_power', -]; diff --git a/playground/src/output/ou-explore.ts b/playground/src/output/ou-explore.ts deleted file mode 100644 index ccd6fc4..0000000 --- a/playground/src/output/ou-explore.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * ou-explore.ts — Ornstein-Uhlenbeck exploration noise on the output. - * - * Faithful TS port of `nisps/ml/ou_noise.hpp`. Adds a per-output-channel - * Ornstein-Uhlenbeck random walk to the network's action (output) vector - * before it reaches audio. Unlike i.i.d. per-frame noise, an OU process is - * temporally correlated: each output drifts in long, smooth sweeps and is - * gently pulled back toward the mapping output (mean reversion). Because - * learning stays active while the noise roams, "likes" registered during - * the wander steer the network toward sounds the player wants. - * - * Discrete Euler-Maruyama update, per output channel x (mu = 0): - * x += theta * (-x) * dt + noiseScale * N(0,1) - * out = clamp(out + x, 0, 1) - * where, to make the stationary std equal a requested `std`: - * noiseScale = std * sqrt(2 * theta * dt) - * and the exploration knob [0,1] maps std = level * kMaxAmplitude (0.65). - * - * RNG note: the C++ owns a per-instance deterministic xoshiro256+ for - * firmware↔browser parity. This is a browser-only exploration aid that - * never round-trips through WASM, so `Math.random()` is fine. The gaussian - * is reproduced as the same sum-of-three-uniforms shape the C++ `Rng` uses - * (`next_float_gaussian`). - * - * DEFAULT STATE IS INERT: intensity defaults to 0, `enabled()` is false, and - * `apply()` neither advances the RNG nor touches the output — so the output - * passes through bit-identically when intensity is 0 (parity-safe). - */ - -/** Upstream kMaxAmplitude: the exploration knob's full-scale stationary std. */ -export const OU_MAX_AMPLITUDE = 0.65; - -/** Sum-of-three-uniforms gaussian (matches nisps::Rng::next_float_gaussian). */ -function gaussian(stddev = 1): number { - // Three uniforms in [-1, 1): variance 1/3 each ⇒ sum has variance 1. - const a = Math.random() * 2 - 1; - const b = Math.random() * 2 - 1; - const c = Math.random() * 2 - 1; - return (a + b + c) * stddev; -} - -export class OUExplore { - private theta_ = 0.02; - private dt_ = 0.001; - private stationaryStd_ = 0; - private noiseScale_ = 0; - private state_: Float32Array = new Float32Array(0); - - constructor() { - this.recomputeScale_(); - } - - /** - * Exploration amount in [0,1]; 0 disables (inert). Maps to the OU - * stationary std = level * kMaxAmplitude. - */ - setIntensity(level: number): void { - let l = level; - if (l < 0) l = 0; - else if (l > 1) l = 1; - this.stationaryStd_ = l * OU_MAX_AMPLITUDE; - this.recomputeScale_(); - } - - intensity(): number { - return this.stationaryStd_ / OU_MAX_AMPLITUDE; - } - - enabled(): boolean { - return this.stationaryStd_ > 0; - } - - setTheta(theta: number): void { - this.theta_ = theta; - this.recomputeScale_(); - } - - setDt(dt: number): void { - this.dt_ = dt; - this.recomputeScale_(); - } - - /** - * Advance the per-channel OU state and add it (clamped) to `out`, mutating - * `out` in place. No-op (no state advance, no output change) when disabled. - * `out` is the post-inference parameter vector. - */ - apply(out: Float32Array): void { - if (!this.enabled()) return; - const n = out.length; - if (this.state_.length < n) { - // Grow the per-channel state, preserving existing drift. - const next = new Float32Array(n); - next.set(this.state_); - this.state_ = next; - } - const theta = this.theta_; - const dt = this.dt_; - const scale = this.noiseScale_; - for (let i = 0; i < n; ++i) { - // mu = 0: the walk is an offset that mean-reverts to zero, so the - // network's own mapping output stays the anchor. - let s = this.state_[i]!; - s += theta * -s * dt + scale * gaussian(1); - this.state_[i] = s; - let v = out[i]! + s; - if (v < 0) v = 0; - else if (v > 1) v = 1; - out[i] = v; - } - } - - reset(): void { - this.state_.fill(0); - } - - private recomputeScale_(): void { - let k = 2 * this.theta_ * this.dt_; - if (k < 0) k = 0; - this.noiseScale_ = this.stationaryStd_ * Math.sqrt(k); - } -} diff --git a/playground/src/output/pipeline.ts b/playground/src/output/pipeline.ts deleted file mode 100644 index bc0e2b6..0000000 --- a/playground/src/output/pipeline.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Output pipeline — pure TS port of legacy `js/ui/output-pipeline.js`. - * - * Stages (in order) for each output: - * 1. Global power curve (raw^exponent, exponent in [0.2, 5.0]) - * 2. Per-output EMA smoothing (frame-rate-independent) - * 3. Slew-rate limiting (max change per second per output) - * 4. Freeze gate (global) and per-output freeze mask - * - * `processOutput` is a pure function: takes the raw output vector, the prior - * processed vector (or null on first call), and config; returns a new - * Float32Array. Consumer (output-store) holds prev between frames. - * - * NOTE: Reuses an internal scratch buffer ONLY when a prev buffer of the - * exact same length is supplied AND `cfg.reuseBuffer === true`. Otherwise it - * allocates a fresh Float32Array (safer for cross-component sharing). - */ - -import { clamp, clamp01 } from './curves'; - -export const GLOBAL_CURVE_MIN = 0.2; -export const GLOBAL_CURVE_MAX = 5.0; -export const SMOOTHING_MAX = 0.95; -export const SLEW_RATE_MIN = 0.005; - -const REFERENCE_DT = 1 / 60; - -export interface OutputConfig { - /** Power curve exponent applied to ALL outputs. 1 = linear. */ - globalCurve: number; - /** EMA smoothing factor [0, 0.95]. */ - smoothing: number; - /** Max change per second per output. Infinity = unlimited. */ - slewRate: number; - /** Global freeze gate. */ - freezeOutput: boolean; - /** Per-output freeze mask (1 = frozen). Length must match output vector. */ - freezeMask: Uint8Array | null; - /** If true and prev buffer matches length, reuse it for processed output. */ - reuseBuffer: boolean; -} - -export interface OutputState { - /** Last processed output (kept here for slew/freeze logic). */ - prev: Float32Array | null; - /** Last EMA-smoothed values per output. */ - smoothed: Float32Array | null; -} - -export function defaultOutputConfig(): OutputConfig { - return { - globalCurve: 1.0, - smoothing: 0, - slewRate: Infinity, - freezeOutput: false, - freezeMask: null, - reuseBuffer: false, - }; -} - -export function defaultOutputState(): OutputState { - return { prev: null, smoothed: null }; -} - -function emaSmooth(prev: number, raw: number, smoothing: number, dt: number): number { - if (smoothing <= 0) return raw; - const effectiveDt = dt > 0 ? dt : REFERENCE_DT; - const alpha = 1 - smoothing; - const alphaEff = 1 - Math.pow(1 - alpha, effectiveDt / REFERENCE_DT); - return prev + alphaEff * (raw - prev); -} - -/** - * Process raw outputs through global curve → smoothing → slew → freeze gate. - * - * @param raw raw output vector (Float32Array of size N) - * @param cfg pipeline config - * @param state prior state (use {@link defaultOutputState} first call) - * @param dtMs time since last call in milliseconds - * @returns { processed, state } with the new outputs and updated state - */ -export function processOutput( - raw: Float32Array, - cfg: OutputConfig, - state: OutputState, - dtMs: number, -): { processed: Float32Array; state: OutputState } { - const n = raw.length; - const dt = Math.max(0, dtMs / 1000); - - let prev = state.prev; - let smoothed = state.smoothed; - if (!prev || prev.length !== n) { - prev = new Float32Array(n); - // Seed from raw on first call - for (let i = 0; i < n; i++) prev[i] = clamp01(raw[i] ?? 0); - } - if (!smoothed || smoothed.length !== n) { - smoothed = new Float32Array(n); - for (let i = 0; i < n; i++) smoothed[i] = clamp01(raw[i] ?? 0); - } - - let processed: Float32Array; - if (cfg.reuseBuffer && prev.length === n) { - processed = prev; - } else { - processed = new Float32Array(n); - } - - // Stage 1: global curve (mutates a working scratch via direct compute) - const exp = cfg.globalCurve; - - if (cfg.freezeOutput) { - // Output frozen: hold prior values. - if (processed !== prev) { - processed.set(prev); - } - return { processed, state: { prev: processed, smoothed } }; - } - - for (let i = 0; i < n; i++) { - const r = clamp01(raw[i] ?? 0); - const curved = exp === 1.0 ? r : Math.pow(r, exp); - - // Per-output freeze - if (cfg.freezeMask && cfg.freezeMask[i]) { - processed[i] = prev[i] ?? curved; - continue; - } - - // Stage 2: EMA smoothing - let value = emaSmooth(smoothed[i] ?? curved, curved, cfg.smoothing, dt); - smoothed[i] = value; - - // Stage 3: slew-rate limit - if (isFinite(cfg.slewRate) && cfg.slewRate > 0) { - const maxDelta = cfg.slewRate * dt; - const delta = value - (prev[i] ?? value); - if (Math.abs(delta) > maxDelta) { - value = (prev[i] ?? value) + Math.sign(delta) * maxDelta; - } - } - - processed[i] = clamp01(value); - } - - // Update prev for next call - if (processed !== prev) { - prev = new Float32Array(processed); // copy so caller can hold processed buffer freely - } - - return { processed, state: { prev, smoothed } }; -} diff --git a/playground/src/primitives/ControlAxis.demo.tsx b/playground/src/primitives/ControlAxis.demo.tsx deleted file mode 100644 index ab13e29..0000000 --- a/playground/src/primitives/ControlAxis.demo.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Component, createSignal } from 'solid-js'; -import { ControlAxis } from './ControlAxis'; - -export const ControlAxisDemo: Component = () => { - const [boldness, setBoldness] = createSignal(0.5); - const [memory, setMemory] = createSignal(0.7); - const [precision, setPrecision] = createSignal(0.3); - const [preset, setPreset] = createSignal('default'); - - const onAny = () => setPreset(null); - - return ( -
- { setBoldness(v); onAny(); }} - preset={preset} - endpoints={['Caution', 'Bold']} - accentColor="var(--accent)" - onDoubleTap={() => setBoldness(0.5)} - /> - { setMemory(v); onAny(); }} - preset={preset} - endpoints={['Amnesia', 'Elephant']} - accentColor="var(--accent-2)" - onDoubleTap={() => setMemory(0.5)} - /> - { setPrecision(v); onAny(); }} - preset={preset} - endpoints={['Raw', 'Precise']} - accentColor="var(--good)" - onDoubleTap={() => setPrecision(0.3)} - /> -
- ); -}; diff --git a/playground/src/primitives/ControlAxis.module.css b/playground/src/primitives/ControlAxis.module.css deleted file mode 100644 index 34f4c1e..0000000 --- a/playground/src/primitives/ControlAxis.module.css +++ /dev/null @@ -1,99 +0,0 @@ -.axis { - display: flex; - flex-direction: column; - gap: var(--sp-1); - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--r-2); - padding: var(--sp-2) var(--sp-3); - --axis-accent: var(--accent); -} - -.head { - display: flex; - align-items: center; - gap: var(--sp-2); - font-size: var(--fs-sm); -} - -.label { - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--fg); - flex: 1; -} - -.preset { - color: var(--axis-accent); - font-size: var(--fs-xs); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.value { - font-variant-numeric: tabular-nums; - color: var(--fg-mute); - font-size: var(--fs-xs); - min-width: 4ch; - text-align: right; -} - -.input { - appearance: none; - -webkit-appearance: none; - width: 100%; - height: 24px; - background: transparent; - margin: 0; - cursor: pointer; -} - -.input::-webkit-slider-runnable-track { - height: 6px; - border-radius: 999px; - background: var(--bg-3); -} - -.input::-moz-range-track { - height: 6px; - border-radius: 999px; - background: var(--bg-3); -} - -.input::-webkit-slider-thumb { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--axis-accent); - margin-top: -6px; - box-shadow: 0 0 10px var(--axis-accent); - cursor: pointer; -} - -.input::-moz-range-thumb { - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--axis-accent); - border: none; - box-shadow: 0 0 10px var(--axis-accent); -} - -.input:focus { outline: none; } - -.endpoints { - display: flex; - justify-content: space-between; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--fg-dim); -} - -.disabled { - opacity: 0.5; - pointer-events: none; -} diff --git a/playground/src/primitives/ControlAxis.tsx b/playground/src/primitives/ControlAxis.tsx deleted file mode 100644 index ba718f0..0000000 --- a/playground/src/primitives/ControlAxis.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Component, Show } from 'solid-js'; -import styles from './ControlAxis.module.css'; - -export interface ControlAxisProps { - label: string; - value: () => number; - onChange: (v: number) => void; - /** Active control preset id (rendered as subtitle when set). */ - preset?: () => string | null; - /** Endpoint labels (left, right). E.g. ['Caution', 'Bold']. */ - endpoints?: readonly [string, string]; - /** Called when user double-taps the axis (re-link offsets). */ - onDoubleTap?: () => void; - /** Disabled state. */ - disabled?: boolean; - /** Visual color hint (defaults to --accent). */ - accentColor?: string; -} - -export const ControlAxis: Component = (props) => { - let lastTap = 0; - - const onPointerDown = () => { - const now = performance.now(); - if (now - lastTap < 350 && props.onDoubleTap) { - props.onDoubleTap(); - lastTap = 0; - } else { - lastTap = now; - } - }; - - const onInput = (e: InputEvent & { currentTarget: HTMLInputElement }) => { - const v = parseFloat(e.currentTarget.value); - if (!Number.isNaN(v)) props.onChange(Math.max(0, Math.min(1, v))); - }; - - const accent = () => props.accentColor ?? 'var(--accent)'; - - return ( -
-
- {props.label} - - {props.preset!()} - - {props.value().toFixed(2)} -
- - -
- {props.endpoints![0]} - {props.endpoints![1]} -
-
-
- ); -}; diff --git a/playground/src/primitives/Drawer.demo.tsx b/playground/src/primitives/Drawer.demo.tsx deleted file mode 100644 index 3ac4b9c..0000000 --- a/playground/src/primitives/Drawer.demo.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, createSignal } from 'solid-js'; -import { Drawer } from './Drawer'; - -export const DrawerDemo: Component = () => { - const [openR, setOpenR] = createSignal(false); - const [openL, setOpenL] = createSignal(false); - return ( -
- - - setOpenL(false)} side="left" title="Left drawer"> -

Slides in from the left.

-

Press Escape, click outside, or click × to close.

-
- setOpenR(false)} side="right" title="Right drawer"> -

Slides in from the right.

-

Mode-specific drawers will use this primitive.

-
-
- ); -}; diff --git a/playground/src/primitives/Drawer.module.css b/playground/src/primitives/Drawer.module.css deleted file mode 100644 index f138d53..0000000 --- a/playground/src/primitives/Drawer.module.css +++ /dev/null @@ -1,92 +0,0 @@ -.root { - position: fixed; - inset: 0; - z-index: var(--z-drawer); - pointer-events: none; -} - -.scrim { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.45); - pointer-events: auto; - animation: fadeIn var(--dur-fast) var(--ease); -} - -.panel { - position: absolute; - top: 0; - bottom: 0; - background: var(--bg-1); - border-left: 1px solid var(--line); - pointer-events: auto; - display: flex; - flex-direction: column; - box-shadow: 0 0 24px rgba(0, 0, 0, 0.5); - animation: slideIn var(--dur-med) var(--ease); -} - -.left .panel { - left: 0; - border-right: 1px solid var(--line); - border-left: 0; - animation-name: slideInLeft; -} - -.right .panel { - right: 0; -} - -.header { - display: flex; - align-items: center; - gap: var(--sp-3); - padding: var(--sp-3) var(--sp-4); - border-bottom: 1px solid var(--line); - flex: 0 0 auto; -} - -.title { - flex: 1; - font-size: var(--fs-md); - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--fg-mute); -} - -.close { - background: transparent; - border: 0; - font-size: 22px; - color: var(--fg-mute); - width: 32px; - height: 32px; - border-radius: var(--r-1); - padding: 0; -} - -.close:hover { - background: var(--bg-2); - color: var(--fg); -} - -.body { - flex: 1; - overflow: auto; - padding: var(--sp-3) var(--sp-4); -} - -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes slideIn { - from { transform: translateX(100%); } - to { transform: translateX(0); } -} - -@keyframes slideInLeft { - from { transform: translateX(-100%); } - to { transform: translateX(0); } -} diff --git a/playground/src/primitives/Drawer.tsx b/playground/src/primitives/Drawer.tsx deleted file mode 100644 index 445e02a..0000000 --- a/playground/src/primitives/Drawer.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, JSX, onCleanup, onMount, Show } from 'solid-js'; -import { Portal } from 'solid-js/web'; -import styles from './Drawer.module.css'; - -export interface DrawerProps { - open: boolean; - onClose: () => void; - side: 'left' | 'right'; - title?: string; - children: JSX.Element; - /** Optional width in px (default 360). */ - width?: number; - /** Set to false to disable click-outside / Escape close behaviour. */ - dismissable?: boolean; -} - -export const Drawer: Component = (props) => { - const onKey = (e: KeyboardEvent) => { - if (!props.open) return; - if (props.dismissable === false) return; - if (e.key === 'Escape') { - e.stopPropagation(); - props.onClose(); - } - }; - - onMount(() => { - document.addEventListener('keydown', onKey); - }); - onCleanup(() => { - document.removeEventListener('keydown', onKey); - }); - - return ( - - -