memlnaut-nisps/nisps/wasm
monkey-w1n5t0n b6819fd26f feat(wasm)!: P2.2 — nisps_ml_create honours dims; runtime-shaped browser MLP + reshape
Operator-approved ABI change (P2 stop-point). The WASM MLP is now
MLPCore<DynamicStorage>:

- nisps_ml_create(input, output, hidden[3], n, seed) honours its args;
  non-positive/null fall back to the historical 32→[10,14,18]→126, so
  every pre-P2 caller (manifold, worker, parity harness) stays
  bit-identical. Invalid/oversized dims (>4096) → null.
- NEW nisps_ml_reshape(ml, in, out, hidden, n, spread): fresh net at the
  new dims, warm-started via nisps/ml/warm_start.hpp (overlapping region
  copied; rest keeps spread init); feedback controller re-created (state
  resets — reset-on-reshape modal is the front-end contract). Failure
  leaves the old net untouched.
- nisps_ml_describe(ml, out): takes the handle; null reports defaults.
- FeedbackController got the same storage split: algorithms in
  FeedbackControllerCore<FbStorage>; FixedFeedbackStorage keeps firmware/
  tests source-identical via the old alias; DynamicFeedbackStorage (one
  arena) sizes to the runtime net. Firmware .text unchanged (122692).
- MLHandle: per-instance scratch vectors; dropped the dead 2MB
  batch_out_scratch.
- TS: types.ts decls (+_nisps_ml_reshape), wasm-iml re-describes the
  created instance, worker carries a shape-contract note for P2.3.

Verified: ctest 4/4 incl. new warm-start grow/shrink test; reshape ABI
smoke (dims honoured, overlap survives, invalid rejected, outputs
bounded); parity PASS unchanged (2.4e-7); lint clean; manifold 9 unit +
20 e2e green; firmware .text 122692 (+0.30% vs pre-P2 baseline).
2026-07-14 03:38:06 +02:00
..
bindings.cpp feat(wasm)!: P2.2 — nisps_ml_create honours dims; runtime-shaped browser MLP + reshape 2026-07-14 03:38:06 +02:00
README.md chore(build): P0 plumbing — WASM build/parity retarget to manifold/public 2026-07-13 23:14:23 +02:00

nisps/wasm

Emscripten target that exposes nisps/ml (MLP) and nisps/engines (audio engines) to the browser apps via a flat C ABI.

This directory is a leaf — it does not export headers for inclusion by other C++ code. The only artifact is bindings.cpp plus the build script that turns it into manifold/public/nisps.{wasm,js} (with a transitional copy to playground/public/ until P1 of docs/specs/plans/one-core-engine-refactor.md retires the playground).

Building

scripts/build-wasm.sh

Requires emcc (Emscripten). The script defaults to /usr/lib/emscripten/emcc and respects an EMCC env var override.

Output:

  • manifold/public/nisps.wasm — the compiled module.
  • manifold/public/nisps.js — Emscripten glue (factory function createNispsModule, MODULARIZE=1).

Both files are committed (so the browser apps work from a fresh clone without a C++ toolchain). Re-run build-wasm.sh after changes to nisps/{core,ml,engines,wasm}.

Architecture limit (read this)

The MLP class template is parametrised on (input_size, hidden1, hidden2, hidden3, output_size). WASM cannot recompile templates at runtime, so this build instantiates exactly ONE configuration:

nisps::ml::MLP<32, 10, 14, 18, 126>

That serves the browser use case (up to 32 input axes → up to 126 synth parameters). nisps_ml_create() accepts caller-supplied dimensions for forward compatibility but currently ignores them — see comment at the top of bindings.cpp. The schemas in schemas/modes/*.json use up to output_size=126; modes whose output_size is < 126 simply ignore the trailing entries.

To support additional architectures, either:

  1. Compile multiple wasm modules (nisps_small.wasm, nisps_default.wasm, …) and let the playground load the right one based on the active mode.
  2. Add a runtime-shape MLP variant to nisps/ml (heap allocation only at create(); no impact on hot paths).

Both options are deferred to a future stream.

C API surface

See bindings.cpp for the full list. Summary:

Group Functions
ML life nisps_ml_create, nisps_ml_destroy, nisps_ml_reset
ML I/O nisps_ml_set_input, nisps_ml_process, nisps_ml_outputs, nisps_ml_infer_batch
Training nisps_ml_add_example, nisps_ml_train, nisps_ml_eval_loss, nisps_ml_clear_examples, nisps_ml_example_count
Weights nisps_ml_weight_count, nisps_ml_get_weights, nisps_ml_set_weights, nisps_ml_draw_weights, nisps_ml_move_weights
Diag nisps_ml_get_layer_stats, nisps_ml_describe
Engines nisps_engine_create, nisps_engine_destroy, nisps_engine_set_params, nisps_engine_process_block

Engine-id strings follow the C++ engine_id() constexpr accessors: thru, paf_synth, channel_strip, xiasri, verb_fx, memlcelium, breakor, elysiamorf, analysis. Unknown ids fall back to thru (silent passthrough).