The operator's call: "there should be one default learning rate and one
default max iterations and they should both be configurable at runtime."
There were SIX copies, not the four the audit described, and they did not
agree:
nisps/ml/mlp.hpp no-arg train() hardcoding 1.f / 1000u / 0.001f —
and firmware's ONLY training path calls exactly
this, so firmware had no runtime knob at all
wasm-iml.ts train() and trainAsync() TS default params (x2)
engine-api.ts learningRate ?? 1.0, with no maxIterations knob
vcv/src/iml.hpp 200 / 0.1 / 0.00001 — silently divergent
external_synth_midi.hpp its own kDefaultLearningRate/kDefaultMaxIterations
schemas/modes/*.json x9, identical, read by nobody at runtime
Now: schemas/ml_defaults.json is the single declaration (validated against a
sibling meta-schema, matching the midi_device.schema.json convention), codegen
emits it to C++ and TS in the same run, and MLPCore carries a TrainConfig whose
default member initialisers read the generated constant.
set_train_config()/nisps_ml_set_train_config() make it runtime-overridable on
every target; the explicit-argument train() overload is untouched. min_error
joins the tuple — it was duplicated identically and belongs with the other two.
The per-mode ml block loses default_learning_rate/default_max_iterations.
default_spread stays (genuinely wired on both targets) and input_channels stays
(codegen-time validated, real information for sound_analysis_midi).
VCV BEHAVIOUR CHANGE, deliberate: MEMLNaut.cpp constructs IML positionally and
relies on those defaults, so the module moves to 1000/1.0/0.001 — 5x the max
iterations, 10x the learning rate, and a 100x looser early-stop threshold. The
old values were never justified anywhere; they arrived with
|
||
|---|---|---|
| .. | ||
| tests | ||
| .gitignore | ||
| generate-midi-devices.ts | ||
| generate.ts | ||
| lib.ts | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
codegen — schemas → C++ + TypeScript
Bun scripts that turn the JSON schemas under schemas/ into generated code for both
targets. The per-file inventory lives in MAP.md (§ codegen/) — this README only
covers how to run it.
generate.ts— validatesschemas/modes/*.jsonagainstschemas/schema.json, emitsnisps/modes/generated/<mode>_schema.hpp(+schema_types.hpp) andmanifold/src/modes/generated/<mode>_schema.ts(+types.ts,index.ts).generate-midi-devices.ts— validatesschemas/midi_devices/*.jsonagainstschemas/midi_device.schema.json, emitsnisps/midi/generated/midi_devices.hppandmanifold/src/midi-devices/generated/{types,devices,index}.ts.
Both validate with ajv (Draft 2020-12), exit non-zero on failure, and are idempotent (re-running with unchanged schemas is byte-identical).
Run
cd codegen
bun install
bun run generate.ts # mode schemas
bun run generate-midi-devices.ts # MIDI device templates
Schema or codegen changes ship with the regenerated C++ and TypeScript in the same commit — CI re-runs both generators and fails on any diff.
Golden test
tests/golden_test.ts re-runs generate.ts against the live output dirs, diffs
paf_synth_schema.{hpp,ts} against the snapshots in tests/golden/, then re-runs to
prove idempotency:
bun run test
After an intentional emission change, refresh the goldens:
bun run generate.ts
cp ../nisps/modes/generated/paf_synth_schema.hpp tests/golden/
cp ../manifold/src/modes/generated/paf_synth_schema.ts tests/golden/
Adding a mode / device
Drop the new JSON into schemas/modes/ (or schemas/midi_devices/), run the matching
generator, and commit the JSON plus the regenerated outputs together.