diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ce74f..534e3f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,7 +116,7 @@ jobs: bun run generate-midi-devices.ts bun run tests/golden_test.ts cd .. - GEN_DIRS="nisps/modes/generated nisps/midi/generated \ + GEN_DIRS="nisps/modes/generated nisps/midi/generated nisps/ml/generated \ manifold/src/modes/generated manifold/src/midi-devices/generated" git add -N $GEN_DIRS if ! git diff --exit-code -- $GEN_DIRS; then diff --git a/MAP.md b/MAP.md index 9729905..b99ced5 100644 --- a/MAP.md +++ b/MAP.md @@ -6,7 +6,7 @@ MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 cod ### `nisps/` — platform-agnostic C++20 library (the only ML/DSP/engine code) - `nisps/core/` — `perf.hpp` (hot-path/inlining attrs), `types.hpp`, `concepts.hpp` (`MLEngine`, `AudioEngine`, `Mode`), `ring_buffer.hpp` (SPSC lock-free cross-core channel, replaces pico/util/queue), `event_queue.hpp` (single-threaded in-engine event FIFO — deliberately NOT RingBuffer, which is an atomics-based cross-thread channel), `rng.hpp` (xoshiro256+ deterministic), `math.hpp` (fast_sigmoid, `Curve` enum + `apply_curve`). -- `nisps/ml/` — the MLP core, written once against a storage policy (`mlp.hpp` `MLPCore`): `storage.hpp` (`FixedStorage` — template-sized `std::array`, zero heap; `MLP` alias preserves the classic compile-time surface) and `dynamic_storage.hpp` (`DynamicStorage` — runtime dims, single arena alloc at construction; `#error`s on RP2350 builds, sole lint heap-allowlist entry). Fixed↔dynamic bit-parity enforced by `tests/cpp/test_mlp_storage_parity.cpp`. Files: `mlp.hpp`, `activations.hpp`, `loss.hpp` (MSE, no double-scaling), `training.hpp` (SGD + grad clipping), `init.hpp` (spread-aware uniform↔Xavier), `rl.hpp` (`move_weights` with output pin mask + per-layer scaling + weight decay), `jolt.hpp` (`Jolt` — held continuous weight-morph over the flat weight buffer + post-release LR ramp; ported from upstream InterfaceRL), `ou_noise.hpp` (`OUNoise` — Ornstein-Uhlenbeck exploration walk on the output vector; ported from upstream InterfaceRL), `feedback.hpp` (`FeedbackControllerCore` — the "Down Action" state machine: Avoid (geometric push-away default / Diffuse legacy) / RandomiseOutputs / RandomiseMlp / ExploreAndPlace; storage-policied like the MLP, own deterministic RNG, exposed via `nisps_ml_feedback_*` C API), `replay.hpp` (`ReplayView` — reward-tagged memory: dedup/deepen, k-NN positive centroid with deterministic tie-break, proportional decay+eviction), `geo_push.hpp` (push-away target computation, upstream InterfaceRL @ 0a541cc), `warm_start.hpp` (overlapping-weights copy for reshape), `stats.hpp`. Jolt + OU are inert by default and wired into `ModeBase`, so every mode exposes `jolt_press/jolt_release`, `jolt_lr_scale`, and `set_explore_intensity`. +- `nisps/ml/` — the MLP core, written once against a storage policy (`mlp.hpp` `MLPCore`): `storage.hpp` (`FixedStorage` — template-sized `std::array`, zero heap; `MLP` alias preserves the classic compile-time surface) and `dynamic_storage.hpp` (`DynamicStorage` — runtime dims, single arena alloc at construction; `#error`s on RP2350 builds, sole lint heap-allowlist entry). Fixed↔dynamic bit-parity enforced by `tests/cpp/test_mlp_storage_parity.cpp`. Files: `mlp.hpp`, `activations.hpp`, `loss.hpp` (MSE, no double-scaling), `training.hpp` (SGD + grad clipping), `init.hpp` (spread-aware uniform↔Xavier), `rl.hpp` (`move_weights` with output pin mask + per-layer scaling + weight decay), `jolt.hpp` (`Jolt` — held continuous weight-morph over the flat weight buffer + post-release LR ramp; ported from upstream InterfaceRL), `ou_noise.hpp` (`OUNoise` — Ornstein-Uhlenbeck exploration walk on the output vector; ported from upstream InterfaceRL), `feedback.hpp` (`FeedbackControllerCore` — the "Down Action" state machine: Avoid (geometric push-away default / Diffuse legacy) / RandomiseOutputs / RandomiseMlp / ExploreAndPlace; storage-policied like the MLP, own deterministic RNG, exposed via `nisps_ml_feedback_*` C API), `replay.hpp` (`ReplayView` — reward-tagged memory: dedup/deepen, k-NN positive centroid with deterministic tie-break, proportional decay+eviction), `geo_push.hpp` (push-away target computation, upstream InterfaceRL @ 0a541cc), `warm_start.hpp` (overlapping-weights copy for reshape), `stats.hpp`. `generated/ml_defaults.hpp` is codegen output (do not edit): `nisps::ml::generated::kMlTrainDefaults`, the ONE learning-rate / max-iterations / min-error default shared by firmware, WASM and VCV (source `schemas/ml_defaults.json`); `MLPCore::set_train_config()` and `nisps_ml_set_train_config()` override it at runtime. It lives under `ml/` rather than `modes/generated/` because `nisps/ml` sits below `nisps/modes` — mlp.hpp must not include upward. Jolt + OU are inert by default and wired into `ModeBase`, so every mode exposes `jolt_press/jolt_release`, `jolt_lr_scale`, and `set_explore_intensity`. - `nisps/pipeline/` — the control-rate input/output processing chains (P4): `input_chain.hpp` (`InputChain` — invert→deadzone→circular clamp→momentum-modulated zoom→centred power→EMA→momentum; caller-supplied dt, internal clock, fixed velocity ring, serialisable state) and `output_chain.hpp` (`OutputChain` — curve→EMA→slew→freeze(+mask), capacity-templated). Behaviour contract = the retired manifold TS pipelines, pinned by `manifold/tests/fixtures/` and parity stage 7. - `nisps/dsp/` — `biquad.hpp`, `delay.hpp`, `reverb.hpp`, `filter.hpp`, `env.hpp`, `osc.hpp`, `pitch_shift.hpp`, `dc_blocker.hpp`, plus the sequencer primitives shared by the sequencer engines: `ratio_seq.hpp` and `seq_clock.hpp` (bar phasor + MIDI clock + bpm). Lean primitives extracted from maximilian; daisysp PitchShifter replaced with custom granular impl. - `nisps/engines/` — eight audio engines, each satisfying `AudioEngine`: `paf_synth.hpp`, `channel_strip.hpp`, `xiasri.hpp`, `verb_fx.hpp`, `memlcelium.hpp`, `breakor.hpp` (sequencer, NoOp audio), `elysiamorf.hpp` (sequencer, NoOp audio), `analysis.hpp` (input-side spectral features). Plus `base.hpp` (`NoOpEngine`, engine_id "thru"). @@ -141,7 +141,7 @@ includes; no `nisps-core`. - **Parity check**: `bash scripts/parity-check.sh`. - **All tests**: `bash scripts/run-all-tests.sh`. - **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 both `nisps/modes/generated/` C++ and `manifold/src/modes/generated/` TS). +- **Codegen**: `cd codegen && bun run generate.ts` (regenerates `nisps/modes/generated/` + `nisps/ml/generated/` C++ and `manifold/src/modes/generated/` TS). ## Conventions diff --git a/codegen/generate.ts b/codegen/generate.ts index d2585d4..ed70851 100644 --- a/codegen/generate.ts +++ b/codegen/generate.ts @@ -50,8 +50,6 @@ interface ModeSchema { hidden_layers: number[]; output_size: number; default_spread: number; - default_learning_rate: number; - default_max_iterations: number; }; params: Array<{ name: string; @@ -71,6 +69,24 @@ interface ModeSchema { }; } +/** + * The ONE global training-hyperparameter default (schemas/ml_defaults.json, + * validated against schemas/ml_defaults.schema.json) — NOT per-mode, unlike + * everything else in ModeSchema. See S26 (docs/specs/recon/ + * simplification-audit-2026-07.md): learning_rate/max_iterations/min_error + * used to be duplicated per-mode (identically, unread at runtime) plus + * hardcoded separately in nisps/ml/mlp.hpp, manifold/src/engine/wasm-iml.ts, + * and vcv/src/iml.hpp. Now declared once here and consumed by + * `nisps::ml::MLPCore`'s `TrainConfig` default member initialisers. + */ +interface MlTrainDefaults { + $schema?: string; + _note?: string; + learning_rate: number; + max_iterations: number; + min_error: number; +} + // ----- Path resolution ------------------------------------------------------ const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -78,7 +94,15 @@ const REPO_ROOT = resolve(__dirname, ".."); const SCHEMAS_DIR = join(REPO_ROOT, "schemas"); const MODES_DIR = join(SCHEMAS_DIR, "modes"); const META_SCHEMA_PATH = join(SCHEMAS_DIR, "schema.json"); +const ML_DEFAULTS_PATH = join(SCHEMAS_DIR, "ml_defaults.json"); +const ML_DEFAULTS_SCHEMA_PATH = join(SCHEMAS_DIR, "ml_defaults.schema.json"); const CPP_OUT_DIR = join(REPO_ROOT, "nisps", "modes", "generated"); +// The training defaults are an ML fact, not a mode fact, and `nisps/ml` sits +// BELOW `nisps/modes` in the layering MAP.md documents — emitting them into +// modes/generated/ would make mlp.hpp include upward. They get their own +// output dir on the C++ side. The TS side has no such layering to respect, so +// it keeps everything generated under one directory. +const CPP_ML_OUT_DIR = join(REPO_ROOT, "nisps", "ml", "generated"); const TS_OUT_DIR = join(REPO_ROOT, "manifold", "src", "modes", "generated"); // ----- Helpers -------------------------------------------------------------- @@ -173,8 +197,6 @@ function emitSchemaTypesHpp(): string { " std::size_t input_size;", " std::size_t output_size;", " float default_spread;", - " float default_learning_rate;", - " std::size_t default_max_iterations;", "};", "", "enum class PrimaryInput : unsigned char {", @@ -209,8 +231,6 @@ function emitSchemaTypesHpp(): string { " std::span hidden_layers;", " std::size_t output_size;", " float default_spread;", - " float default_learning_rate;", - " std::size_t default_max_iterations;", " std::span params;", " std::span voice_spaces;", " ::nisps::modes::generated::UIConfig ui;", @@ -261,8 +281,6 @@ function emitSharedTsTypes(): string { " 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 {", @@ -325,8 +343,6 @@ function emitModeHpp(schema: ModeSchema, sourceFile: string): string { lines.push(` ${schema.ml.input_size}u,`); lines.push(` ${schema.ml.output_size}u,`); lines.push(` ${cppFloatLit(schema.ml.default_spread)},`); - lines.push(` ${cppFloatLit(schema.ml.default_learning_rate)},`); - lines.push(` ${schema.ml.default_max_iterations}u,`); lines.push("};"); lines.push(""); @@ -399,8 +415,6 @@ function emitModeHpp(schema: ModeSchema, sourceFile: string): string { lines.push(` std::span(${constName}HiddenLayers),`); lines.push(` ${constName}MLConfig.output_size,`); lines.push(` ${constName}MLConfig.default_spread,`); - lines.push(` ${constName}MLConfig.default_learning_rate,`); - lines.push(` ${constName}MLConfig.default_max_iterations,`); lines.push(` std::span(${constName}Params),`); lines.push(` std::span(${constName}VoiceSpaces),`); lines.push(` ${constName}UI,`); @@ -452,8 +466,6 @@ function emitModeTs(schema: ModeSchema, sourceFile: string): string { lines.push(" ],"); lines.push(` output_size: ${schema.ml.output_size},`); lines.push(` default_spread: ${schema.ml.default_spread},`); - lines.push(` default_learning_rate: ${schema.ml.default_learning_rate},`); - lines.push(` default_max_iterations: ${schema.ml.default_max_iterations},`); lines.push(" },"); lines.push(" params: ["); for (const p of schema.params) { @@ -519,6 +531,73 @@ function emitTsIndex(modeIds: string[]): string { return lines.join("\n"); } +// ----- Global ML-defaults emission (S26) ------------------------------------ +// The ONE learning_rate/max_iterations/min_error default (schemas/ml_defaults. +// json), NOT per-mode — emitted once, alongside schema_types.hpp/types.ts, +// rather than once per mode file like everything else in this module. + +const AUTOGEN_BANNER_ML_DEFAULTS = (lang: "C++" | "TS"): string => + `// AUTOGENERATED (${lang}) — do not edit. Source: schemas/ml_defaults.json. ` + + "Run `bun run codegen/generate.ts` to regenerate."; + +function emitMlDefaultsHpp(d: MlTrainDefaults): string { + return [ + AUTOGEN_BANNER_ML_DEFAULTS("C++"), + "// The ONE global training-hyperparameter default, shared by every mode on", + "// every platform (firmware/WASM/VCV) — see docs/specs/recon/", + "// simplification-audit-2026-07.md S26. Consumed by", + "// nisps::ml::MLPCore::TrainConfig's default member initialisers", + "// (nisps/ml/mlp.hpp); nisps_ml_set_train_config() and", + "// nisps::ml::MLPCore::set_train_config() make it runtime-overridable.", + "#ifndef NISPS_ML_GENERATED_ML_DEFAULTS_HPP", + "#define NISPS_ML_GENERATED_ML_DEFAULTS_HPP", + "", + "#include ", + "", + "namespace nisps::ml::generated {", + "", + "struct MlTrainDefaults {", + " float learning_rate;", + " std::size_t max_iterations;", + " float min_error;", + "};", + "", + "inline constexpr MlTrainDefaults kMlTrainDefaults = {", + ` ${cppFloatLit(d.learning_rate)},`, + ` ${d.max_iterations}u,`, + ` ${cppFloatLit(d.min_error)},`, + "};", + "", + "} // namespace nisps::ml::generated", + "", + "#endif // NISPS_ML_GENERATED_ML_DEFAULTS_HPP", + "", + ].join("\n"); +} + +function emitMlDefaultsTs(d: MlTrainDefaults): string { + return [ + AUTOGEN_BANNER_ML_DEFAULTS("TS"), + "// The ONE global training-hyperparameter default, shared by every mode on", + "// every platform — see docs/specs/recon/simplification-audit-2026-07.md S26.", + "// Consumed by WasmIML's train()/trainAsync() default parameters and", + "// EngineApi's learningRate/maxIterations options (manifold/src/engine/).", + "", + "export interface MlTrainDefaults {", + " readonly learningRate: number;", + " readonly maxIterations: number;", + " readonly minError: number;", + "}", + "", + "export const ML_TRAIN_DEFAULTS: MlTrainDefaults = {", + ` learningRate: ${d.learning_rate},`, + ` maxIterations: ${d.max_iterations},`, + ` minError: ${d.min_error},`, + "};", + "", + ].join("\n"); +} + // ----- Driver -------------------------------------------------------------- function main(): number { @@ -539,6 +618,29 @@ function main(): number { }); const validate = ajv.compile(metaSchema); + // 1b. Load, compile, and validate the ONE global ML training default + // (schemas/ml_defaults.json against schemas/ml_defaults.schema.json — S26, + // NOT per-mode, so it lives outside the modeFiles loop below). + if (!existsSync(ML_DEFAULTS_SCHEMA_PATH)) { + console.error(`error: ml-defaults meta-schema not found at ${ML_DEFAULTS_SCHEMA_PATH}`); + return 1; + } + if (!existsSync(ML_DEFAULTS_PATH)) { + console.error(`error: ml-defaults data not found at ${ML_DEFAULTS_PATH}`); + return 1; + } + const mlDefaultsSchema = readJSON(ML_DEFAULTS_SCHEMA_PATH); + const validateMlDefaults = ajv.compile(mlDefaultsSchema); + const mlDefaultsRaw = readJSON(ML_DEFAULTS_PATH); + if (!validateMlDefaults(mlDefaultsRaw)) { + console.error(`error: ${ML_DEFAULTS_PATH}: schema validation failed:`); + for (const err of validateMlDefaults.errors ?? []) { + console.error(` ${err.instancePath || ""} ${err.message}`); + } + return 1; + } + const mlDefaults = mlDefaultsRaw as MlTrainDefaults; + // 2. Discover all mode schemas if (!existsSync(MODES_DIR)) { console.error(`error: modes directory not found at ${MODES_DIR}`); @@ -620,6 +722,8 @@ function main(): number { // 4. Emit C++ outputs ensureDir(CPP_OUT_DIR); writeFileSync(join(CPP_OUT_DIR, "schema_types.hpp"), emitSchemaTypesHpp()); + ensureDir(CPP_ML_OUT_DIR); + writeFileSync(join(CPP_ML_OUT_DIR, "ml_defaults.hpp"), emitMlDefaultsHpp(mlDefaults)); for (const { source, schema } of schemas) { const out = join(CPP_OUT_DIR, `${schema.mode_id}_schema.hpp`); writeFileSync(out, emitModeHpp(schema, source)); @@ -628,6 +732,7 @@ function main(): number { // 5. Emit TS outputs ensureDir(TS_OUT_DIR); writeFileSync(join(TS_OUT_DIR, "types.ts"), emitSharedTsTypes()); + writeFileSync(join(TS_OUT_DIR, "ml_defaults.ts"), emitMlDefaultsTs(mlDefaults)); for (const { source, schema } of schemas) { const out = join(TS_OUT_DIR, `${schema.mode_id}_schema.ts`); writeFileSync(out, emitModeTs(schema, source)); diff --git a/codegen/tests/golden/paf_synth_schema.hpp b/codegen/tests/golden/paf_synth_schema.hpp index 197c671..a202a12 100644 --- a/codegen/tests/golden/paf_synth_schema.hpp +++ b/codegen/tests/golden/paf_synth_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kPafSynthMLConfig = { 4u, 33u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kPafSynthParamCount = 33u; @@ -359,8 +357,6 @@ inline constexpr ::nisps::ParamSchema kPafSynthSchema = { std::span(kPafSynthHiddenLayers), kPafSynthMLConfig.output_size, kPafSynthMLConfig.default_spread, - kPafSynthMLConfig.default_learning_rate, - kPafSynthMLConfig.default_max_iterations, std::span(kPafSynthParams), std::span(kPafSynthVoiceSpaces), kPafSynthUI, diff --git a/codegen/tests/golden/paf_synth_schema.ts b/codegen/tests/golden/paf_synth_schema.ts index 6b66bc0..5890052 100644 --- a/codegen/tests/golden/paf_synth_schema.ts +++ b/codegen/tests/golden/paf_synth_schema.ts @@ -55,8 +55,6 @@ export const PafSynthSchema: ModeSchema = { ], output_size: 33, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/public/nisps.js b/manifold/public/nisps.js index 794bc3c..f47f693 100644 --- a/manifold/public/nisps.js +++ b/manifold/public/nisps.js @@ -6,7 +6,7 @@ var createNispsModule = (() => { 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={c:__abort_js,b:__emscripten_memcpy_js,a:_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_reshape=Module["_nisps_ml_reshape"]=(a0,a1,a2,a3,a4,a5)=>(_nisps_ml_reshape=Module["_nisps_ml_reshape"]=wasmExports["g"])(a0,a1,a2,a3,a4,a5);var _nisps_ml_destroy=Module["_nisps_ml_destroy"]=a0=>(_nisps_ml_destroy=Module["_nisps_ml_destroy"]=wasmExports["h"])(a0);var _nisps_ml_set_input=Module["_nisps_ml_set_input"]=(a0,a1,a2)=>(_nisps_ml_set_input=Module["_nisps_ml_set_input"]=wasmExports["i"])(a0,a1,a2);var _nisps_ml_process=Module["_nisps_ml_process"]=a0=>(_nisps_ml_process=Module["_nisps_ml_process"]=wasmExports["j"])(a0);var _nisps_ml_outputs=Module["_nisps_ml_outputs"]=a0=>(_nisps_ml_outputs=Module["_nisps_ml_outputs"]=wasmExports["k"])(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["l"])(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["m"])(a0,a1,a2);var _nisps_ml_train=Module["_nisps_ml_train"]=(a0,a1,a2,a3,a4)=>(_nisps_ml_train=Module["_nisps_ml_train"]=wasmExports["n"])(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["o"])(a0);var _nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=a0=>(_nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=wasmExports["p"])(a0);var _nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=(a0,a1)=>(_nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=wasmExports["q"])(a0,a1);var _nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=(a0,a1)=>(_nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=wasmExports["r"])(a0,a1);var _nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=(a0,a1)=>(_nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=wasmExports["s"])(a0,a1);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_set_focus=Module["_nisps_ml_feedback_set_focus"]=(a0,a1,a2)=>(_nisps_ml_feedback_set_focus=Module["_nisps_ml_feedback_set_focus"]=wasmExports["w"])(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["x"])(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["y"])(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["z"])(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["A"])(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["B"])(a0);var _nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=(a0,a1)=>(_nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=wasmExports["C"])(a0,a1);var _nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=(a0,a1)=>(_nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=wasmExports["D"])(a0,a1);var _nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=a0=>(_nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=wasmExports["E"])(a0);var _nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=a0=>(_nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=wasmExports["F"])(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["G"])(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["H"])(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["I"])(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["J"])(a0,a1);var _nisps_ml_feedback_dislike_geometric=Module["_nisps_ml_feedback_dislike_geometric"]=(a0,a1,a2)=>(_nisps_ml_feedback_dislike_geometric=Module["_nisps_ml_feedback_dislike_geometric"]=wasmExports["K"])(a0,a1,a2);var _nisps_ml_feedback_store_positive=Module["_nisps_ml_feedback_store_positive"]=(a0,a1)=>(_nisps_ml_feedback_store_positive=Module["_nisps_ml_feedback_store_positive"]=wasmExports["L"])(a0,a1);var _nisps_ml_feedback_positive_count=Module["_nisps_ml_feedback_positive_count"]=a0=>(_nisps_ml_feedback_positive_count=Module["_nisps_ml_feedback_positive_count"]=wasmExports["M"])(a0);var _nisps_ml_feedback_negative_count=Module["_nisps_ml_feedback_negative_count"]=a0=>(_nisps_ml_feedback_negative_count=Module["_nisps_ml_feedback_negative_count"]=wasmExports["N"])(a0);var _nisps_ml_feedback_set_avoid_style=Module["_nisps_ml_feedback_set_avoid_style"]=(a0,a1)=>(_nisps_ml_feedback_set_avoid_style=Module["_nisps_ml_feedback_set_avoid_style"]=wasmExports["O"])(a0,a1);var _nisps_ml_jolt_press=Module["_nisps_ml_jolt_press"]=a0=>(_nisps_ml_jolt_press=Module["_nisps_ml_jolt_press"]=wasmExports["P"])(a0);var _nisps_ml_jolt_step=Module["_nisps_ml_jolt_step"]=a0=>(_nisps_ml_jolt_step=Module["_nisps_ml_jolt_step"]=wasmExports["Q"])(a0);var _nisps_ml_jolt_release=Module["_nisps_ml_jolt_release"]=a0=>(_nisps_ml_jolt_release=Module["_nisps_ml_jolt_release"]=wasmExports["R"])(a0);var _nisps_ml_jolt_active=Module["_nisps_ml_jolt_active"]=a0=>(_nisps_ml_jolt_active=Module["_nisps_ml_jolt_active"]=wasmExports["S"])(a0);var _nisps_ml_explore_intensity=Module["_nisps_ml_explore_intensity"]=(a0,a1)=>(_nisps_ml_explore_intensity=Module["_nisps_ml_explore_intensity"]=wasmExports["T"])(a0,a1);var _nisps_ml_explore_get_intensity=Module["_nisps_ml_explore_get_intensity"]=a0=>(_nisps_ml_explore_get_intensity=Module["_nisps_ml_explore_get_intensity"]=wasmExports["U"])(a0);var _nisps_ml_explore_apply=Module["_nisps_ml_explore_apply"]=(a0,a1,a2)=>(_nisps_ml_explore_apply=Module["_nisps_ml_explore_apply"]=wasmExports["V"])(a0,a1,a2);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["W"])(a0,a1);var _nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=a0=>(_nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=wasmExports["X"])(a0);var _nisps_ml_describe=Module["_nisps_ml_describe"]=(a0,a1)=>(_nisps_ml_describe=Module["_nisps_ml_describe"]=wasmExports["Y"])(a0,a1);var _nisps_pipeline_create=Module["_nisps_pipeline_create"]=()=>(_nisps_pipeline_create=Module["_nisps_pipeline_create"]=wasmExports["Z"])();var _nisps_pipeline_destroy=Module["_nisps_pipeline_destroy"]=a0=>(_nisps_pipeline_destroy=Module["_nisps_pipeline_destroy"]=wasmExports["_"])(a0);var _nisps_input_set_config=Module["_nisps_input_set_config"]=(a0,a1,a2)=>(_nisps_input_set_config=Module["_nisps_input_set_config"]=wasmExports["$"])(a0,a1,a2);var _nisps_input_process=Module["_nisps_input_process"]=(a0,a1,a2,a3,a4)=>(_nisps_input_process=Module["_nisps_input_process"]=wasmExports["aa"])(a0,a1,a2,a3,a4);var _nisps_input_reset=Module["_nisps_input_reset"]=a0=>(_nisps_input_reset=Module["_nisps_input_reset"]=wasmExports["ba"])(a0);var _nisps_output_set_config=Module["_nisps_output_set_config"]=(a0,a1,a2,a3,a4)=>(_nisps_output_set_config=Module["_nisps_output_set_config"]=wasmExports["ca"])(a0,a1,a2,a3,a4);var _nisps_output_set_freeze_mask=Module["_nisps_output_set_freeze_mask"]=(a0,a1,a2)=>(_nisps_output_set_freeze_mask=Module["_nisps_output_set_freeze_mask"]=wasmExports["da"])(a0,a1,a2);var _nisps_output_process=Module["_nisps_output_process"]=(a0,a1,a2,a3)=>(_nisps_output_process=Module["_nisps_output_process"]=wasmExports["ea"])(a0,a1,a2,a3);var _nisps_output_reset=Module["_nisps_output_reset"]=a0=>(_nisps_output_reset=Module["_nisps_output_reset"]=wasmExports["fa"])(a0);var _nisps_curve_apply=Module["_nisps_curve_apply"]=(a0,a1,a2)=>(_nisps_curve_apply=Module["_nisps_curve_apply"]=wasmExports["ga"])(a0,a1,a2);var _nisps_curve_apply_batch=Module["_nisps_curve_apply_batch"]=(a0,a1,a2,a3,a4)=>(_nisps_curve_apply_batch=Module["_nisps_curve_apply_batch"]=wasmExports["ha"])(a0,a1,a2,a3,a4);var _nisps_engine_create=Module["_nisps_engine_create"]=(a0,a1)=>(_nisps_engine_create=Module["_nisps_engine_create"]=wasmExports["ia"])(a0,a1);var _nisps_engine_destroy=Module["_nisps_engine_destroy"]=a0=>(_nisps_engine_destroy=Module["_nisps_engine_destroy"]=wasmExports["ja"])(a0);var _nisps_engine_set_params=Module["_nisps_engine_set_params"]=(a0,a1,a2)=>(_nisps_engine_set_params=Module["_nisps_engine_set_params"]=wasmExports["ka"])(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["la"])(a0,a1,a2,a3,a4,a5);var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["na"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["oa"])(a0);var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["pa"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["qa"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["ra"])();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; +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={c:__abort_js,b:__emscripten_memcpy_js,a:_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_reshape=Module["_nisps_ml_reshape"]=(a0,a1,a2,a3,a4,a5)=>(_nisps_ml_reshape=Module["_nisps_ml_reshape"]=wasmExports["g"])(a0,a1,a2,a3,a4,a5);var _nisps_ml_destroy=Module["_nisps_ml_destroy"]=a0=>(_nisps_ml_destroy=Module["_nisps_ml_destroy"]=wasmExports["h"])(a0);var _nisps_ml_set_input=Module["_nisps_ml_set_input"]=(a0,a1,a2)=>(_nisps_ml_set_input=Module["_nisps_ml_set_input"]=wasmExports["i"])(a0,a1,a2);var _nisps_ml_process=Module["_nisps_ml_process"]=a0=>(_nisps_ml_process=Module["_nisps_ml_process"]=wasmExports["j"])(a0);var _nisps_ml_outputs=Module["_nisps_ml_outputs"]=a0=>(_nisps_ml_outputs=Module["_nisps_ml_outputs"]=wasmExports["k"])(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["l"])(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["m"])(a0,a1,a2);var _nisps_ml_train=Module["_nisps_ml_train"]=(a0,a1,a2,a3,a4)=>(_nisps_ml_train=Module["_nisps_ml_train"]=wasmExports["n"])(a0,a1,a2,a3,a4);var _nisps_ml_set_train_config=Module["_nisps_ml_set_train_config"]=(a0,a1,a2,a3)=>(_nisps_ml_set_train_config=Module["_nisps_ml_set_train_config"]=wasmExports["o"])(a0,a1,a2,a3);var _nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=a0=>(_nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=wasmExports["p"])(a0);var _nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=a0=>(_nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=wasmExports["q"])(a0);var _nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=(a0,a1)=>(_nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=wasmExports["r"])(a0,a1);var _nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=(a0,a1)=>(_nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=wasmExports["s"])(a0,a1);var _nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=(a0,a1)=>(_nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=wasmExports["t"])(a0,a1);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["u"])(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["v"])(a0);var _nisps_ml_feedback_exploring=Module["_nisps_ml_feedback_exploring"]=a0=>(_nisps_ml_feedback_exploring=Module["_nisps_ml_feedback_exploring"]=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_static_output=Module["_nisps_ml_feedback_static_output"]=(a0,a1)=>(_nisps_ml_feedback_static_output=Module["_nisps_ml_feedback_static_output"]=wasmExports["A"])(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["B"])(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["C"])(a0);var _nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=(a0,a1)=>(_nisps_ml_feedback_reroll=Module["_nisps_ml_feedback_reroll"]=wasmExports["D"])(a0,a1);var _nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=(a0,a1)=>(_nisps_ml_feedback_nudge=Module["_nisps_ml_feedback_nudge"]=wasmExports["E"])(a0,a1);var _nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=a0=>(_nisps_ml_feedback_undo=Module["_nisps_ml_feedback_undo"]=wasmExports["F"])(a0);var _nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=a0=>(_nisps_ml_feedback_like=Module["_nisps_ml_feedback_like"]=wasmExports["G"])(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["H"])(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["I"])(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["J"])(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["K"])(a0,a1);var _nisps_ml_feedback_dislike_geometric=Module["_nisps_ml_feedback_dislike_geometric"]=(a0,a1,a2)=>(_nisps_ml_feedback_dislike_geometric=Module["_nisps_ml_feedback_dislike_geometric"]=wasmExports["L"])(a0,a1,a2);var _nisps_ml_feedback_store_positive=Module["_nisps_ml_feedback_store_positive"]=(a0,a1)=>(_nisps_ml_feedback_store_positive=Module["_nisps_ml_feedback_store_positive"]=wasmExports["M"])(a0,a1);var _nisps_ml_feedback_positive_count=Module["_nisps_ml_feedback_positive_count"]=a0=>(_nisps_ml_feedback_positive_count=Module["_nisps_ml_feedback_positive_count"]=wasmExports["N"])(a0);var _nisps_ml_feedback_negative_count=Module["_nisps_ml_feedback_negative_count"]=a0=>(_nisps_ml_feedback_negative_count=Module["_nisps_ml_feedback_negative_count"]=wasmExports["O"])(a0);var _nisps_ml_feedback_set_avoid_style=Module["_nisps_ml_feedback_set_avoid_style"]=(a0,a1)=>(_nisps_ml_feedback_set_avoid_style=Module["_nisps_ml_feedback_set_avoid_style"]=wasmExports["P"])(a0,a1);var _nisps_ml_jolt_press=Module["_nisps_ml_jolt_press"]=a0=>(_nisps_ml_jolt_press=Module["_nisps_ml_jolt_press"]=wasmExports["Q"])(a0);var _nisps_ml_jolt_step=Module["_nisps_ml_jolt_step"]=a0=>(_nisps_ml_jolt_step=Module["_nisps_ml_jolt_step"]=wasmExports["R"])(a0);var _nisps_ml_jolt_release=Module["_nisps_ml_jolt_release"]=a0=>(_nisps_ml_jolt_release=Module["_nisps_ml_jolt_release"]=wasmExports["S"])(a0);var _nisps_ml_jolt_active=Module["_nisps_ml_jolt_active"]=a0=>(_nisps_ml_jolt_active=Module["_nisps_ml_jolt_active"]=wasmExports["T"])(a0);var _nisps_ml_explore_intensity=Module["_nisps_ml_explore_intensity"]=(a0,a1)=>(_nisps_ml_explore_intensity=Module["_nisps_ml_explore_intensity"]=wasmExports["U"])(a0,a1);var _nisps_ml_explore_get_intensity=Module["_nisps_ml_explore_get_intensity"]=a0=>(_nisps_ml_explore_get_intensity=Module["_nisps_ml_explore_get_intensity"]=wasmExports["V"])(a0);var _nisps_ml_explore_apply=Module["_nisps_ml_explore_apply"]=(a0,a1,a2)=>(_nisps_ml_explore_apply=Module["_nisps_ml_explore_apply"]=wasmExports["W"])(a0,a1,a2);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["X"])(a0,a1);var _nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=a0=>(_nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=wasmExports["Y"])(a0);var _nisps_ml_describe=Module["_nisps_ml_describe"]=(a0,a1)=>(_nisps_ml_describe=Module["_nisps_ml_describe"]=wasmExports["Z"])(a0,a1);var _nisps_pipeline_create=Module["_nisps_pipeline_create"]=()=>(_nisps_pipeline_create=Module["_nisps_pipeline_create"]=wasmExports["_"])();var _nisps_pipeline_destroy=Module["_nisps_pipeline_destroy"]=a0=>(_nisps_pipeline_destroy=Module["_nisps_pipeline_destroy"]=wasmExports["$"])(a0);var _nisps_input_set_config=Module["_nisps_input_set_config"]=(a0,a1,a2)=>(_nisps_input_set_config=Module["_nisps_input_set_config"]=wasmExports["aa"])(a0,a1,a2);var _nisps_input_process=Module["_nisps_input_process"]=(a0,a1,a2,a3,a4)=>(_nisps_input_process=Module["_nisps_input_process"]=wasmExports["ba"])(a0,a1,a2,a3,a4);var _nisps_input_reset=Module["_nisps_input_reset"]=a0=>(_nisps_input_reset=Module["_nisps_input_reset"]=wasmExports["ca"])(a0);var _nisps_output_set_config=Module["_nisps_output_set_config"]=(a0,a1,a2,a3,a4)=>(_nisps_output_set_config=Module["_nisps_output_set_config"]=wasmExports["da"])(a0,a1,a2,a3,a4);var _nisps_output_set_freeze_mask=Module["_nisps_output_set_freeze_mask"]=(a0,a1,a2)=>(_nisps_output_set_freeze_mask=Module["_nisps_output_set_freeze_mask"]=wasmExports["ea"])(a0,a1,a2);var _nisps_output_process=Module["_nisps_output_process"]=(a0,a1,a2,a3)=>(_nisps_output_process=Module["_nisps_output_process"]=wasmExports["fa"])(a0,a1,a2,a3);var _nisps_output_reset=Module["_nisps_output_reset"]=a0=>(_nisps_output_reset=Module["_nisps_output_reset"]=wasmExports["ga"])(a0);var _nisps_curve_apply=Module["_nisps_curve_apply"]=(a0,a1,a2)=>(_nisps_curve_apply=Module["_nisps_curve_apply"]=wasmExports["ha"])(a0,a1,a2);var _nisps_curve_apply_batch=Module["_nisps_curve_apply_batch"]=(a0,a1,a2,a3,a4)=>(_nisps_curve_apply_batch=Module["_nisps_curve_apply_batch"]=wasmExports["ia"])(a0,a1,a2,a3,a4);var _nisps_engine_create=Module["_nisps_engine_create"]=(a0,a1)=>(_nisps_engine_create=Module["_nisps_engine_create"]=wasmExports["ja"])(a0,a1);var _nisps_engine_destroy=Module["_nisps_engine_destroy"]=a0=>(_nisps_engine_destroy=Module["_nisps_engine_destroy"]=wasmExports["ka"])(a0);var _nisps_engine_set_params=Module["_nisps_engine_set_params"]=(a0,a1,a2)=>(_nisps_engine_set_params=Module["_nisps_engine_set_params"]=wasmExports["la"])(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["ma"])(a0,a1,a2,a3,a4,a5);var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["oa"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["pa"])(a0);var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["qa"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["ra"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["sa"])();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; diff --git a/manifold/public/nisps.wasm b/manifold/public/nisps.wasm index 9eb04a8..31cacfd 100755 Binary files a/manifold/public/nisps.wasm and b/manifold/public/nisps.wasm differ diff --git a/manifold/src/engine/engine-api.ts b/manifold/src/engine/engine-api.ts index 811e1f4..ed33070 100644 --- a/manifold/src/engine/engine-api.ts +++ b/manifold/src/engine/engine-api.ts @@ -16,6 +16,7 @@ */ import { EngineHost } from './engine-host'; +import { ML_TRAIN_DEFAULTS } from '../modes/generated/ml_defaults'; import type { InputConfig, OutputConfig } from './pipeline-types'; import { Spine, type BackendSend } from './spine'; import type { EngineId, FeedbackMode, LayerStats } from './types'; @@ -106,6 +107,8 @@ export interface EngineApiOptions { maxExamples?: number; /** Default learning rate for thumbsUp/train. */ learningRate?: number; + /** Default max training iterations for train/trainAsync. */ + maxIterations?: number; /** Default RL move speed / spread for thumbsDown. */ noiseCap?: number; spread?: number; @@ -123,6 +126,7 @@ export class EngineApi { private host: EngineHost; private learningRate: number; + private maxIterations: number; private noiseCap: number; private spread_: number; @@ -134,9 +138,15 @@ export class EngineApi { this.iml = iml; this.spine = spine; this.host = host; - this.learningRate = opts.learningRate ?? 1.0; + this.learningRate = opts.learningRate ?? ML_TRAIN_DEFAULTS.learningRate; + this.maxIterations = opts.maxIterations ?? ML_TRAIN_DEFAULTS.maxIterations; this.noiseCap = opts.noiseCap ?? 0.3; this.spread_ = opts.spread ?? 0.6; + // Persist the configured default on the underlying MLP too (S26) — makes + // the WASM engine's OWN training config match EngineApi's knobs, the same + // real runtime-configurability firmware/VCV get for free from + // MLPCore::TrainConfig's default member initialisers. + this.iml.setTrainConfig(this.learningRate, this.maxIterations, ML_TRAIN_DEFAULTS.minError); if (opts.debugClockDt !== undefined) this.spine.setFixedDt(opts.debugClockDt); // Wire the spine's backend.send to push routed params into the worklet. @@ -291,11 +301,11 @@ export class EngineApi { } train(): number { - return this.iml.train(this.learningRate); + return this.iml.train(this.learningRate, this.maxIterations); } trainAsync(): Promise { - return this.iml.trainAsync(this.learningRate); + return this.iml.trainAsync(this.learningRate, this.maxIterations); } randomise(spread = this.spread_): void { diff --git a/manifold/src/engine/types.ts b/manifold/src/engine/types.ts index ea180c0..6a3b34e 100644 --- a/manifold/src/engine/types.ts +++ b/manifold/src/engine/types.ts @@ -45,6 +45,9 @@ export interface NispsModule { // 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; + // Persist a new training-hyperparameter default on the handle (S26) — mirrors + // nisps::ml::MLPCore::set_train_config. Does not train. + _nisps_ml_set_train_config(ml: number, lr: number, max_iter: number, min_err: number): void; _nisps_ml_eval_loss(ml: number): number; // ML examples. diff --git a/manifold/src/engine/wasm-iml.ts b/manifold/src/engine/wasm-iml.ts index df46160..887b364 100644 --- a/manifold/src/engine/wasm-iml.ts +++ b/manifold/src/engine/wasm-iml.ts @@ -19,6 +19,7 @@ */ import { Dataset } from './dataset'; +import { ML_TRAIN_DEFAULTS } from '../modes/generated/ml_defaults'; import { anchorModeToInt, momentumModeToInt, @@ -180,6 +181,13 @@ export class WasmIML { dataset!: Dataset; private readonly sink: EngineSink; private lastLoss_: number | null = null; + // JS-side mirror of the training-hyperparameter default, seeded from the + // ONE generated constant (S26, docs/specs/recon/simplification-audit-2026-07 + // .md) rather than hardcoded literals. `setTrainConfig` updates this AND the + // WASM handle's own copy (nisps_ml_set_train_config) so train()/trainAsync() + // fall back to a genuinely runtime-configurable default, not just a JS + // literal. + private trainConfig = { ...ML_TRAIN_DEFAULTS }; private trainer: WasmTrainer | null = null; private storageKey: string; private saveTimer: number | null = null; @@ -620,7 +628,22 @@ export class WasmIML { this.module._nisps_ml_add_example(this.mlHandle, this.featuresBuf.ptr, this.labelsBuf.ptr); } - train(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): number { + /** Persist a new training-hyperparameter default (S26): updates the JS-side + * mirror used as the train()/trainAsync() fallback AND the WASM handle's + * own copy via the C API, so the underlying MLP is genuinely + * runtime-configurable rather than just remembering a number to pass on + * each call. */ + setTrainConfig(lr: number, maxIter: number, minErr: number): void { + this.trainConfig = { learningRate: lr, maxIterations: maxIter, minError: minErr }; + this.module._nisps_ml_set_train_config(this.mlHandle, lr, maxIter, minErr); + } + + train( + lr = this.trainConfig.learningRate, + maxIter = this.trainConfig.maxIterations, + minErr = this.trainConfig.minError, + sampleWeights?: Float32Array, + ): number { if (this.dataset.isEmpty()) { this.lastLoss_ = 0; this.sink.setState({ lastLoss: 0 }); @@ -653,7 +676,12 @@ export class WasmIML { return loss; } - async trainAsync(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): Promise { + async trainAsync( + lr = this.trainConfig.learningRate, + maxIter = this.trainConfig.maxIterations, + minErr = this.trainConfig.minError, + sampleWeights?: Float32Array, + ): Promise { if (this.dataset.isEmpty()) { this.lastLoss_ = 0; return 0; diff --git a/manifold/src/modes/generated/breakor_schema.ts b/manifold/src/modes/generated/breakor_schema.ts index 0bfe8ab..e719246 100644 --- a/manifold/src/modes/generated/breakor_schema.ts +++ b/manifold/src/modes/generated/breakor_schema.ts @@ -78,8 +78,6 @@ export const BreakorSchema: ModeSchema = { ], output_size: 56, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/channel_strip_schema.ts b/manifold/src/modes/generated/channel_strip_schema.ts index 8bf61c8..e2791a3 100644 --- a/manifold/src/modes/generated/channel_strip_schema.ts +++ b/manifold/src/modes/generated/channel_strip_schema.ts @@ -46,8 +46,6 @@ export const ChannelStripSchema: ModeSchema = { ], output_size: 24, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/elysiamorf_schema.ts b/manifold/src/modes/generated/elysiamorf_schema.ts index b08ec88..a66ebb9 100644 --- a/manifold/src/modes/generated/elysiamorf_schema.ts +++ b/manifold/src/modes/generated/elysiamorf_schema.ts @@ -62,8 +62,6 @@ export const ElysiamorfSchema: ModeSchema = { ], output_size: 40, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/memlcelium_schema.ts b/manifold/src/modes/generated/memlcelium_schema.ts index 2f0f6d9..39c573f 100644 --- a/manifold/src/modes/generated/memlcelium_schema.ts +++ b/manifold/src/modes/generated/memlcelium_schema.ts @@ -78,8 +78,6 @@ export const MemlceliumSchema: ModeSchema = { ], output_size: 56, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/ml_defaults.ts b/manifold/src/modes/generated/ml_defaults.ts new file mode 100644 index 0000000..d1f721d --- /dev/null +++ b/manifold/src/modes/generated/ml_defaults.ts @@ -0,0 +1,17 @@ +// AUTOGENERATED (TS) — do not edit. Source: schemas/ml_defaults.json. Run `bun run codegen/generate.ts` to regenerate. +// The ONE global training-hyperparameter default, shared by every mode on +// every platform — see docs/specs/recon/simplification-audit-2026-07.md S26. +// Consumed by WasmIML's train()/trainAsync() default parameters and +// EngineApi's learningRate/maxIterations options (manifold/src/engine/). + +export interface MlTrainDefaults { + readonly learningRate: number; + readonly maxIterations: number; + readonly minError: number; +} + +export const ML_TRAIN_DEFAULTS: MlTrainDefaults = { + learningRate: 1, + maxIterations: 1000, + minError: 0.001, +}; diff --git a/manifold/src/modes/generated/paf_synth_schema.ts b/manifold/src/modes/generated/paf_synth_schema.ts index 6b66bc0..5890052 100644 --- a/manifold/src/modes/generated/paf_synth_schema.ts +++ b/manifold/src/modes/generated/paf_synth_schema.ts @@ -55,8 +55,6 @@ export const PafSynthSchema: ModeSchema = { ], output_size: 33, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/slp_workshop_schema.ts b/manifold/src/modes/generated/slp_workshop_schema.ts index 4a74ad1..9ced1e1 100644 --- a/manifold/src/modes/generated/slp_workshop_schema.ts +++ b/manifold/src/modes/generated/slp_workshop_schema.ts @@ -78,8 +78,6 @@ export const SlpWorkshopSchema: ModeSchema = { ], output_size: 56, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/sound_analysis_midi_schema.ts b/manifold/src/modes/generated/sound_analysis_midi_schema.ts index 67bd6cc..d40ea06 100644 --- a/manifold/src/modes/generated/sound_analysis_midi_schema.ts +++ b/manifold/src/modes/generated/sound_analysis_midi_schema.ts @@ -36,8 +36,6 @@ export const SoundAnalysisMidiSchema: ModeSchema = { ], output_size: 8, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/types.ts b/manifold/src/modes/generated/types.ts index 1aec060..e095a7e 100644 --- a/manifold/src/modes/generated/types.ts +++ b/manifold/src/modes/generated/types.ts @@ -34,8 +34,6 @@ export interface MLConfig { 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 { diff --git a/manifold/src/modes/generated/verb_fx_schema.ts b/manifold/src/modes/generated/verb_fx_schema.ts index e24a80e..fae4bd3 100644 --- a/manifold/src/modes/generated/verb_fx_schema.ts +++ b/manifold/src/modes/generated/verb_fx_schema.ts @@ -69,8 +69,6 @@ export const VerbFxSchema: ModeSchema = { ], output_size: 47, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/manifold/src/modes/generated/xiasri_schema.ts b/manifold/src/modes/generated/xiasri_schema.ts index e1b774a..1ebd04e 100644 --- a/manifold/src/modes/generated/xiasri_schema.ts +++ b/manifold/src/modes/generated/xiasri_schema.ts @@ -46,8 +46,6 @@ export const XiasriSchema: ModeSchema = { ], output_size: 24, default_spread: 0.6, - default_learning_rate: 1, - default_max_iterations: 1000, }, params: [ { diff --git a/nisps/ml/generated/ml_defaults.hpp b/nisps/ml/generated/ml_defaults.hpp new file mode 100644 index 0000000..f07cd24 --- /dev/null +++ b/nisps/ml/generated/ml_defaults.hpp @@ -0,0 +1,29 @@ +// AUTOGENERATED (C++) — do not edit. Source: schemas/ml_defaults.json. Run `bun run codegen/generate.ts` to regenerate. +// The ONE global training-hyperparameter default, shared by every mode on +// every platform (firmware/WASM/VCV) — see docs/specs/recon/ +// simplification-audit-2026-07.md S26. Consumed by +// nisps::ml::MLPCore::TrainConfig's default member initialisers +// (nisps/ml/mlp.hpp); nisps_ml_set_train_config() and +// nisps::ml::MLPCore::set_train_config() make it runtime-overridable. +#ifndef NISPS_ML_GENERATED_ML_DEFAULTS_HPP +#define NISPS_ML_GENERATED_ML_DEFAULTS_HPP + +#include + +namespace nisps::ml::generated { + +struct MlTrainDefaults { + float learning_rate; + std::size_t max_iterations; + float min_error; +}; + +inline constexpr MlTrainDefaults kMlTrainDefaults = { + 1.0f, + 1000u, + 0.001f, +}; + +} // namespace nisps::ml::generated + +#endif // NISPS_ML_GENERATED_ML_DEFAULTS_HPP diff --git a/nisps/ml/mlp.hpp b/nisps/ml/mlp.hpp index 030e8a2..245f36a 100644 --- a/nisps/ml/mlp.hpp +++ b/nisps/ml/mlp.hpp @@ -48,6 +48,7 @@ #include "../core/concepts.hpp" #include "../core/perf.hpp" #include "../core/rng.hpp" +#include "generated/ml_defaults.hpp" #include "activations.hpp" #include "init.hpp" #include "loss.hpp" @@ -58,6 +59,30 @@ namespace nisps::ml { +// Per-instance training-hyperparameter config (S26, docs/specs/recon/ +// simplification-audit-2026-07.md): the ONE learning_rate/max_iterations/ +// min_error default used to be duplicated identically across all nine +// schemas/modes/*.json (unread at runtime), hardcoded again in this file's +// no-arg train() overload, again in manifold's wasm-iml.ts TS default +// parameters, and a FOURTH time (diverging: 0.1/200/0.00001) in vcv/src/ +// iml.hpp. Default member initialisers below pull the single generated +// constant (schemas/ml_defaults.json -> nisps/ml/generated/ml_defaults.hpp) +// so every MLPCore instance — firmware, WASM handle, VCV adapter — starts +// pre-configured identically; `set_train_config` makes it runtime-overridable, +// same as the codebase-wide decision requires. +// +// Note on layering: ml_defaults.hpp is generated into nisps/ml/generated/, NOT +// alongside schema_types.hpp in nisps/modes/generated/ where the rest of the +// codegen output lives. Training hyperparameters are an ML fact, not a mode +// fact, and nisps/ml sits below nisps/modes — emitting them there would make +// this file include upward. The TS side has no equivalent layering to respect +// and keeps all generated output in one directory. +struct TrainConfig { + float learning_rate = ::nisps::ml::generated::kMlTrainDefaults.learning_rate; + std::size_t max_iterations = ::nisps::ml::generated::kMlTrainDefaults.max_iterations; + float min_error = ::nisps::ml::generated::kMlTrainDefaults.min_error; +}; + // Activation of layer L in the fixed 4-layer topology. template inline constexpr Activation kLayerActivation = @@ -139,11 +164,24 @@ class MLPCore : public Storage { for (std::size_t i = 0; i < n_out; ++i) dsl[l_off + i] = labels[i]; } - // Concept-required no-arg overload. + // Concept-required no-arg overload. Reads the runtime-configurable + // `train_config_` (default-initialised from the single generated default; + // see `TrainConfig` above) rather than hardcoding numbers here. float train() noexcept { - return train(1.f, 1000u, 0.001f, std::span{}); + return train(train_config_.learning_rate, train_config_.max_iterations, + train_config_.min_error, std::span{}); } + // Runtime knob for the no-arg train() overload (S26). Does not affect the + // explicit-argument train() below, which stays the always-available + // explicit path. + void set_train_config(float lr, std::size_t max_iter, float min_err) noexcept { + train_config_.learning_rate = lr; + train_config_.max_iterations = max_iter; + train_config_.min_error = min_err; + } + const TrainConfig& train_config() const noexcept { return train_config_; } + // Full SGD training. `sample_weights`, if non-empty, must size to the // current example count and sum to 1.0 (caller's responsibility — we // do NOT renormalize). @@ -556,6 +594,7 @@ class MLPCore : public Storage { std::size_t dataset_count_ = 0u; std::size_t dataset_head_ = 0u; std::size_t loss_history_count_ = 0u; + TrainConfig train_config_{}; Rng rng_; }; diff --git a/nisps/modes/external_synth_midi.hpp b/nisps/modes/external_synth_midi.hpp index bcd3555..4fcdd7a 100644 --- a/nisps/modes/external_synth_midi.hpp +++ b/nisps/modes/external_synth_midi.hpp @@ -57,8 +57,12 @@ namespace nisps::modes { // instantiation (see the file-header comment for why these are hand-named // rather than codegen'd): 4 joystick inputs -> [10,14,18] hidden -> NOut // (device-CC-count-driven) outputs, matching every other 4-joystick-input -// mode's schema defaults (default_spread 0.6, default_learning_rate 1.0, -// default_max_iterations 1000). +// mode's schema default_spread (0.6). The training hyperparameter defaults +// (learning_rate/max_iterations/min_error) are no longer part of +// `ParamSchema` at all (S26) — they live once, globally, in +// `nisps::ml::generated::kMlTrainDefaults` (nisps/ml/generated/ +// ml_defaults.hpp) and are wired via `nisps::ml::MLPCore::TrainConfig`'s +// default member initialisers instead. namespace ext_synth_defaults { inline constexpr std::size_t kInputSize = 4u; inline constexpr std::array kInputChannels{ @@ -66,8 +70,6 @@ inline constexpr std::array kInputChannels{ std::string_view{"joy_z"}, std::string_view{"joy_w"}}; inline constexpr std::array kHiddenLayers{10u, 14u, 18u}; inline constexpr float kDefaultSpread = 0.6f; -inline constexpr float kDefaultLearningRate = 1.0f; -inline constexpr std::size_t kDefaultMaxIterations = 1000u; } // namespace ext_synth_defaults // The net-shape alias every instantiation uses (mirrors S6/S25's per-mode @@ -185,8 +187,6 @@ class ExternalSynthMIDIMode : public ModeBase< std::span(ext_synth_defaults::kHiddenLayers), NOut, ext_synth_defaults::kDefaultSpread, - ext_synth_defaults::kDefaultLearningRate, - ext_synth_defaults::kDefaultMaxIterations, std::span(kNoParams), std::span(kNoVoiceSpaces), kUI, diff --git a/nisps/modes/generated/breakor_schema.hpp b/nisps/modes/generated/breakor_schema.hpp index 60bcfd0..444dea1 100644 --- a/nisps/modes/generated/breakor_schema.hpp +++ b/nisps/modes/generated/breakor_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kBreakorMLConfig = { 4u, 56u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kBreakorParamCount = 56u; @@ -558,8 +556,6 @@ inline constexpr ::nisps::ParamSchema kBreakorSchema = { std::span(kBreakorHiddenLayers), kBreakorMLConfig.output_size, kBreakorMLConfig.default_spread, - kBreakorMLConfig.default_learning_rate, - kBreakorMLConfig.default_max_iterations, std::span(kBreakorParams), std::span(kBreakorVoiceSpaces), kBreakorUI, diff --git a/nisps/modes/generated/channel_strip_schema.hpp b/nisps/modes/generated/channel_strip_schema.hpp index 82cc6c8..57ca4c2 100644 --- a/nisps/modes/generated/channel_strip_schema.hpp +++ b/nisps/modes/generated/channel_strip_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kChannelStripMLConfig = { 4u, 24u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kChannelStripParamCount = 24u; @@ -277,8 +275,6 @@ inline constexpr ::nisps::ParamSchema kChannelStripSchema = { std::span(kChannelStripHiddenLayers), kChannelStripMLConfig.output_size, kChannelStripMLConfig.default_spread, - kChannelStripMLConfig.default_learning_rate, - kChannelStripMLConfig.default_max_iterations, std::span(kChannelStripParams), std::span(kChannelStripVoiceSpaces), kChannelStripUI, diff --git a/nisps/modes/generated/elysiamorf_schema.hpp b/nisps/modes/generated/elysiamorf_schema.hpp index 202c490..1e1a1e9 100644 --- a/nisps/modes/generated/elysiamorf_schema.hpp +++ b/nisps/modes/generated/elysiamorf_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kElysiamorfMLConfig = { 4u, 40u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kElysiamorfParamCount = 40u; @@ -414,8 +412,6 @@ inline constexpr ::nisps::ParamSchema kElysiamorfSchema = { std::span(kElysiamorfHiddenLayers), kElysiamorfMLConfig.output_size, kElysiamorfMLConfig.default_spread, - kElysiamorfMLConfig.default_learning_rate, - kElysiamorfMLConfig.default_max_iterations, std::span(kElysiamorfParams), std::span(kElysiamorfVoiceSpaces), kElysiamorfUI, diff --git a/nisps/modes/generated/memlcelium_schema.hpp b/nisps/modes/generated/memlcelium_schema.hpp index d478c68..c232a15 100644 --- a/nisps/modes/generated/memlcelium_schema.hpp +++ b/nisps/modes/generated/memlcelium_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kMemlceliumMLConfig = { 4u, 56u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kMemlceliumParamCount = 56u; @@ -560,8 +558,6 @@ inline constexpr ::nisps::ParamSchema kMemlceliumSchema = { std::span(kMemlceliumHiddenLayers), kMemlceliumMLConfig.output_size, kMemlceliumMLConfig.default_spread, - kMemlceliumMLConfig.default_learning_rate, - kMemlceliumMLConfig.default_max_iterations, std::span(kMemlceliumParams), std::span(kMemlceliumVoiceSpaces), kMemlceliumUI, diff --git a/nisps/modes/generated/paf_synth_schema.hpp b/nisps/modes/generated/paf_synth_schema.hpp index 197c671..a202a12 100644 --- a/nisps/modes/generated/paf_synth_schema.hpp +++ b/nisps/modes/generated/paf_synth_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kPafSynthMLConfig = { 4u, 33u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kPafSynthParamCount = 33u; @@ -359,8 +357,6 @@ inline constexpr ::nisps::ParamSchema kPafSynthSchema = { std::span(kPafSynthHiddenLayers), kPafSynthMLConfig.output_size, kPafSynthMLConfig.default_spread, - kPafSynthMLConfig.default_learning_rate, - kPafSynthMLConfig.default_max_iterations, std::span(kPafSynthParams), std::span(kPafSynthVoiceSpaces), kPafSynthUI, diff --git a/nisps/modes/generated/schema_types.hpp b/nisps/modes/generated/schema_types.hpp index a4f5371..1dbe2c0 100644 --- a/nisps/modes/generated/schema_types.hpp +++ b/nisps/modes/generated/schema_types.hpp @@ -37,8 +37,6 @@ struct MLConfig { std::size_t input_size; std::size_t output_size; float default_spread; - float default_learning_rate; - std::size_t default_max_iterations; }; enum class PrimaryInput : unsigned char { @@ -73,8 +71,6 @@ struct ParamSchema { std::span hidden_layers; std::size_t output_size; float default_spread; - float default_learning_rate; - std::size_t default_max_iterations; std::span params; std::span voice_spaces; ::nisps::modes::generated::UIConfig ui; diff --git a/nisps/modes/generated/slp_workshop_schema.hpp b/nisps/modes/generated/slp_workshop_schema.hpp index f5c938d..941c9c3 100644 --- a/nisps/modes/generated/slp_workshop_schema.hpp +++ b/nisps/modes/generated/slp_workshop_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kSlpWorkshopMLConfig = { 4u, 56u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kSlpWorkshopParamCount = 56u; @@ -560,8 +558,6 @@ inline constexpr ::nisps::ParamSchema kSlpWorkshopSchema = { std::span(kSlpWorkshopHiddenLayers), kSlpWorkshopMLConfig.output_size, kSlpWorkshopMLConfig.default_spread, - kSlpWorkshopMLConfig.default_learning_rate, - kSlpWorkshopMLConfig.default_max_iterations, std::span(kSlpWorkshopParams), std::span(kSlpWorkshopVoiceSpaces), kSlpWorkshopUI, diff --git a/nisps/modes/generated/sound_analysis_midi_schema.hpp b/nisps/modes/generated/sound_analysis_midi_schema.hpp index 2b9023d..44b62dc 100644 --- a/nisps/modes/generated/sound_analysis_midi_schema.hpp +++ b/nisps/modes/generated/sound_analysis_midi_schema.hpp @@ -33,8 +33,6 @@ inline constexpr MLConfig kSoundAnalysisMidiMLConfig = { 10u, 8u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kSoundAnalysisMidiParamCount = 8u; @@ -132,8 +130,6 @@ inline constexpr ::nisps::ParamSchema kSoundAnalysisMidiSchema = { std::span(kSoundAnalysisMidiHiddenLayers), kSoundAnalysisMidiMLConfig.output_size, kSoundAnalysisMidiMLConfig.default_spread, - kSoundAnalysisMidiMLConfig.default_learning_rate, - kSoundAnalysisMidiMLConfig.default_max_iterations, std::span(kSoundAnalysisMidiParams), std::span(kSoundAnalysisMidiVoiceSpaces), kSoundAnalysisMidiUI, diff --git a/nisps/modes/generated/verb_fx_schema.hpp b/nisps/modes/generated/verb_fx_schema.hpp index 7ce2076..6604ea6 100644 --- a/nisps/modes/generated/verb_fx_schema.hpp +++ b/nisps/modes/generated/verb_fx_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kVerbFxMLConfig = { 4u, 47u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kVerbFxParamCount = 47u; @@ -490,8 +488,6 @@ inline constexpr ::nisps::ParamSchema kVerbFxSchema = { std::span(kVerbFxHiddenLayers), kVerbFxMLConfig.output_size, kVerbFxMLConfig.default_spread, - kVerbFxMLConfig.default_learning_rate, - kVerbFxMLConfig.default_max_iterations, std::span(kVerbFxParams), std::span(kVerbFxVoiceSpaces), kVerbFxUI, diff --git a/nisps/modes/generated/xiasri_schema.hpp b/nisps/modes/generated/xiasri_schema.hpp index b0942c0..d300045 100644 --- a/nisps/modes/generated/xiasri_schema.hpp +++ b/nisps/modes/generated/xiasri_schema.hpp @@ -27,8 +27,6 @@ inline constexpr MLConfig kXiasriMLConfig = { 4u, 24u, 0.6f, - 1.0f, - 1000u, }; inline constexpr std::size_t kXiasriParamCount = 24u; @@ -272,8 +270,6 @@ inline constexpr ::nisps::ParamSchema kXiasriSchema = { std::span(kXiasriHiddenLayers), kXiasriMLConfig.output_size, kXiasriMLConfig.default_spread, - kXiasriMLConfig.default_learning_rate, - kXiasriMLConfig.default_max_iterations, std::span(kXiasriParams), std::span(kXiasriVoiceSpaces), kXiasriUI, diff --git a/nisps/wasm/bindings.cpp b/nisps/wasm/bindings.cpp index 814837d..379293a 100644 --- a/nisps/wasm/bindings.cpp +++ b/nisps/wasm/bindings.cpp @@ -502,6 +502,20 @@ float nisps_ml_train(void* ml, float lr, int max_iter, float min_err, return h->mlp.train(lr, static_cast(max_iter), min_err, weights); } +// Persist a new training-hyperparameter default on the handle itself (S26): +// mirrors nisps::ml::MLPCore::set_train_config so the browser MLP is +// genuinely runtime-configurable, not just JS remembering a number to pass on +// each nisps_ml_train() call. Does not train; only reconfigures the no-arg +// train() fallback (unused WASM-side today, but keeps the handle's own state +// consistent with firmware/VCV, which carry the same knob). +EMSCRIPTEN_KEEPALIVE +void nisps_ml_set_train_config(void* ml, float lr, int max_iter, float min_err) { + if (!ml) return; + auto* h = static_cast(ml); + if (max_iter <= 0) max_iter = 1; + h->mlp.set_train_config(lr, static_cast(max_iter), min_err); +} + EMSCRIPTEN_KEEPALIVE float nisps_ml_eval_loss(void* ml) { if (!ml) return 0.f; diff --git a/schemas/ml_defaults.json b/schemas/ml_defaults.json new file mode 100644 index 0000000..8405d1e --- /dev/null +++ b/schemas/ml_defaults.json @@ -0,0 +1,7 @@ +{ + "$schema": "ml_defaults.schema.json", + "_note": "The ONE default learning rate / max iterations / min-error for training, shared by every mode on every platform (audit S26, docs/specs/recon/simplification-audit-2026-07.md). Values preserve the numbers previously hardcoded in nisps/ml/mlp.hpp's no-arg train() and manifold/src/engine/wasm-iml.ts's TS default parameters, so wiring this up is numerically a no-op for firmware and WASM. It is NOT a no-op for vcv/src/iml.hpp, whose own private defaults (200 / 0.1 / 0.00001) diverged and have been deleted in the same change (see vcv/src/iml.hpp's IML constructor comment) — a real behaviour change to the VCV module.", + "learning_rate": 1.0, + "max_iterations": 1000, + "min_error": 0.001 +} diff --git a/schemas/ml_defaults.schema.json b/schemas/ml_defaults.schema.json new file mode 100644 index 0000000..5ecd115 --- /dev/null +++ b/schemas/ml_defaults.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://memlnaut/schemas/ml_defaults.schema.json", + "title": "MEMLNaut ML Training Defaults", + "description": "Single, global declaration of the MLP training-hyperparameter defaults (learning rate, max iterations, min-error/convergence threshold) — ONE value per field, shared by every mode on every platform (firmware, WASM/manifold, VCV). This is only the DEFAULT: codegen/generate.ts emits it to nisps/modes/generated/ml_defaults.hpp (C++) and manifold/src/modes/generated/ml_defaults.ts (TS), and nisps::ml::MLPCore::set_train_config()/train_config() (nisps/ml/mlp.hpp) makes it overridable at runtime on every target. Per-mode ml.default_spread stays separate — it is genuinely per-mode-tuned, unlike these.", + "type": "object", + "required": ["learning_rate", "max_iterations", "min_error"], + "additionalProperties": false, + "properties": { + "$schema": { "type": "string" }, + "_note": { + "type": "string", + "description": "Free-text provenance note from the schema author." + }, + "learning_rate": { + "type": "number", + "exclusiveMinimum": 0.0, + "description": "SGD learning rate the no-arg train() falls back to." + }, + "max_iterations": { + "type": "integer", + "minimum": 1, + "description": "Epoch cap for the no-arg train() fallback." + }, + "min_error": { + "type": "number", + "minimum": 0.0, + "description": "Early-stop threshold: training stops once epoch loss drops below this value." + } + } +} diff --git a/schemas/modes/breakor.json b/schemas/modes/breakor.json index 7b569b1..983ec93 100644 --- a/schemas/modes/breakor.json +++ b/schemas/modes/breakor.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 14, 18], "output_size": 56, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "kick_ratio0", "label": "Kick Ratio 0", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "kick" }, diff --git a/schemas/modes/channel_strip.json b/schemas/modes/channel_strip.json index 822c5d3..fb87164 100644 --- a/schemas/modes/channel_strip.json +++ b/schemas/modes/channel_strip.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 10, 14], "output_size": 24, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "pre_gain", "label": "Pre Gain", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "square", "group": "gain" }, diff --git a/schemas/modes/elysiamorf.json b/schemas/modes/elysiamorf.json index 25ba5b9..a395c05 100644 --- a/schemas/modes/elysiamorf.json +++ b/schemas/modes/elysiamorf.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 14, 18], "output_size": 40, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "fm0_carrier", "label": "FM0 Carrier", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "fm0" }, diff --git a/schemas/modes/memlcelium.json b/schemas/modes/memlcelium.json index aa1666c..194f9cb 100644 --- a/schemas/modes/memlcelium.json +++ b/schemas/modes/memlcelium.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 14, 18], "output_size": 56, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "seq0_ratio0", "label": "Seq0 Ratio 0", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "sequencer" }, diff --git a/schemas/modes/paf_synth.json b/schemas/modes/paf_synth.json index dd41712..57f33a4 100644 --- a/schemas/modes/paf_synth.json +++ b/schemas/modes/paf_synth.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 10, 14], "output_size": 33, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "p00", "label": "Param 00", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "general" }, diff --git a/schemas/modes/slp_workshop.json b/schemas/modes/slp_workshop.json index e1ce169..e63a7a4 100644 --- a/schemas/modes/slp_workshop.json +++ b/schemas/modes/slp_workshop.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 14, 18], "output_size": 56, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "seq0_ratio0", "label": "Seq0 Ratio 0", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "sequencer" }, diff --git a/schemas/modes/sound_analysis_midi.json b/schemas/modes/sound_analysis_midi.json index 0e0ae07..0d71dbb 100644 --- a/schemas/modes/sound_analysis_midi.json +++ b/schemas/modes/sound_analysis_midi.json @@ -19,9 +19,7 @@ "input_size": 10, "hidden_layers": [10, 10, 14], "output_size": 8, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "midi_cc0", "label": "MIDI CC 0", "min": 0.0, "max": 1.0, "default": 0.5, "curve": "linear", "group": "midi" }, diff --git a/schemas/modes/verb_fx.json b/schemas/modes/verb_fx.json index bb908f4..07a1f3a 100644 --- a/schemas/modes/verb_fx.json +++ b/schemas/modes/verb_fx.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 14, 18], "output_size": 47, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "fb_delay_xfade", "label": "Bank/Delay XFade", "min": 0.0, "max": 1.0, "default": 0.0, "curve": "linear", "group": "routing" }, diff --git a/schemas/modes/xiasri.json b/schemas/modes/xiasri.json index 57678a5..45008b3 100644 --- a/schemas/modes/xiasri.json +++ b/schemas/modes/xiasri.json @@ -8,9 +8,7 @@ "input_size": 4, "hidden_layers": [10, 10, 14], "output_size": 24, - "default_spread": 0.6, - "default_learning_rate": 1.0, - "default_max_iterations": 1000 + "default_spread": 0.6 }, "params": [ { "name": "dl1_mix", "label": "Delay 1 Mix", "min": 0.0, "max": 1.0, "default": 0.0, "curve": "linear", "group": "delays" }, diff --git a/schemas/schema.json b/schemas/schema.json index ed69eef..f5ed170 100644 --- a/schemas/schema.json +++ b/schemas/schema.json @@ -36,9 +36,7 @@ "input_size", "hidden_layers", "output_size", - "default_spread", - "default_learning_rate", - "default_max_iterations" + "default_spread" ], "additionalProperties": false, "properties": { @@ -58,9 +56,7 @@ "minItems": 1 }, "output_size": { "type": "integer", "minimum": 1 }, - "default_spread": { "type": "number", "minimum": 0.0, "maximum": 1.0 }, - "default_learning_rate": { "type": "number", "exclusiveMinimum": 0.0 }, - "default_max_iterations": { "type": "integer", "minimum": 1 } + "default_spread": { "type": "number", "minimum": 0.0, "maximum": 1.0 } } }, "params": { diff --git a/scripts/build-wasm.sh b/scripts/build-wasm.sh index a4448ab..7472bdb 100755 --- a/scripts/build-wasm.sh +++ b/scripts/build-wasm.sh @@ -41,7 +41,7 @@ EXPORTED_FUNCS='[ "_malloc","_free", "_nisps_ml_create","_nisps_ml_destroy","_nisps_ml_reshape", "_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_add_example","_nisps_ml_train","_nisps_ml_set_train_config","_nisps_ml_eval_loss", "_nisps_ml_clear_examples", "_nisps_ml_weight_count","_nisps_ml_get_weights","_nisps_ml_set_weights", "_nisps_ml_draw_weights", diff --git a/vcv/src/iml.hpp b/vcv/src/iml.hpp index 41479e0..8b4378d 100644 --- a/vcv/src/iml.hpp +++ b/vcv/src/iml.hpp @@ -45,6 +45,7 @@ #include "../../nisps/core/rng.hpp" #include "../../nisps/ml/dynamic_storage.hpp" #include "../../nisps/ml/mlp.hpp" +#include "../../nisps/ml/generated/ml_defaults.hpp" namespace nisps { @@ -77,11 +78,23 @@ class IML { // are unchanged. `hidden` MUST carry exactly three sizes (the core topology // is fixed at three hidden layers); fewer are padded from the default, // extras ignored. + // BEHAVIOUR CHANGE (S26, docs/specs/recon/simplification-audit-2026-07.md): + // these three defaults used to be private to this adapter (200 / 0.1 / + // 0.00001) and disagreed with the firmware/WASM default (1000 / 1.0 / + // 0.001) that every other target already used. They now come from the + // ONE shared generated constant (schemas/ml_defaults.json -> + // nisps::ml::generated::kMlTrainDefaults) instead of a private copy. + // `MEMLNaut.cpp` constructs `IML` with only 3 positional args, so this is + // a REAL runtime behaviour change for the module: 5x more max iterations, + // 10x the learning rate, and a 100x looser (larger) early-stop threshold. explicit IML(std::size_t n_inputs, std::size_t n_outputs, std::vector hidden = {16u, 24u, 16u}, - std::size_t max_iterations = 200u, - Float learning_rate = static_cast(0.1), - Float convergence_threshold = static_cast(0.00001), + std::size_t max_iterations = + ::nisps::ml::generated::kMlTrainDefaults.max_iterations, + Float learning_rate = static_cast( + ::nisps::ml::generated::kMlTrainDefaults.learning_rate), + Float convergence_threshold = static_cast( + ::nisps::ml::generated::kMlTrainDefaults.min_error), std::uint64_t seed = 0xC0FFEEu) : n_inputs_(n_inputs), n_outputs_(n_outputs),