From f5b571412fd8efaffd177fad0b59b7982d5aaf02 Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Tue, 21 Jul 2026 14:02:23 +0200 Subject: [PATCH] refactor(codegen): codegen owns mode identity, per-mode schemas and net dims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 (S1, S5, S6, S25, L11, L37, ST11, ST12). Behaviour-preserving by construction: the diff on the generated directories is PURELY ADDITIVE (217 insertions, 0 deletions), so no emitted constant changed value. This moves where truth lives; it does not change what truth says. - S5: nine mode headers each carried a mechanically identical 12-field positional ParamSchema aggregate. codegen now emits one `inline constexpr ParamSchema kSchema` per mode, and the struct itself moved into generated/schema_types.hpp. Each param_schema() is a one-line return. - S6 + S25: every mode hand-typed its net shape a second time as MLP template args, duplicating the schema's own dims. codegen emits a `MLP` alias built from the already-emitted constants (not re-literalled), and all nine modes use it. NMaxExamples still defaults from kDefaultMaxExamples (Phase 2). - S1: model.ts hand-imported all nine schemas by name and hand-paired each with its overlay — so the SET of modes was hand-maintained and could silently drift from codegen. codegen now emits ALL_MODE_SCHEMAS; SCHEMA_MODE_OVERLAYS is purely display truth (label/glyph/css/order), which stays hand-curated. - L37: deleted the hand-written modeEngineId switch, which duplicated schema.engine_id and silently defaulted unknown modes to 'thru'. Routes on MFMode.engineId with exactly one documented exception: sound_analysis_midi declares engine_id 'thru' because its ModeBase audio slot really is NoOpEngine, while it separately drives the real AnalysisEngine. - L11: ExternalSynthMIDIMode's shape was literal in two places; now named once in an ext_synth_defaults namespace with an ExtSynthMIDIMLP alias. Full folding into the JSON pipeline is NOT done and the reason is recorded in-file: it is a template family over an externally-supplied Device and variable NOut, with no single (device, NOut) schema to author. - ST12: extracted codegen/lib.ts for the helpers both generators duplicated, and corrected the comment that claimed they had to be separate. Proof the extraction was behaviour-free: regenerating the MIDI-device outputs produces a byte-identical tree. - ST11: deleted codegen/templates/ — dead "reference" files no generator reads, already drifted from the real emitters. Gates: run-all-tests.sh ALL GREEN; codegen idempotent (re-running both generators yields no further diff), which is what CI's dirty-diff gate checks. --- codegen/generate-midi-devices.ts | 28 +-- codegen/generate.ts | 131 +++++++--- codegen/lib.ts | 51 ++++ codegen/templates/cpp_schema.hpp.template | 53 ---- codegen/templates/ts_schema.ts.template | 44 ---- codegen/tests/golden/paf_synth_schema.hpp | 18 ++ manifold/public/nisps.wasm | Bin 130025 -> 130040 bytes manifold/src/console/model.ts | 226 ++++++++++++------ manifold/src/modes/generated/index.ts | 24 ++ nisps/modes/base.hpp | 54 ++--- nisps/modes/breakor.hpp | 25 +- nisps/modes/channel_strip.hpp | 25 +- nisps/modes/elysiamorf.hpp | 25 +- nisps/modes/external_synth_midi.hpp | 64 ++++- nisps/modes/generated/breakor_schema.hpp | 18 ++ .../modes/generated/channel_strip_schema.hpp | 18 ++ nisps/modes/generated/elysiamorf_schema.hpp | 18 ++ nisps/modes/generated/memlcelium_schema.hpp | 18 ++ nisps/modes/generated/paf_synth_schema.hpp | 18 ++ nisps/modes/generated/schema_types.hpp | 31 +++ nisps/modes/generated/slp_workshop_schema.hpp | 18 ++ .../generated/sound_analysis_midi_schema.hpp | 18 ++ nisps/modes/generated/verb_fx_schema.hpp | 18 ++ nisps/modes/generated/xiasri_schema.hpp | 18 ++ nisps/modes/memlcelium.hpp | 25 +- nisps/modes/paf_synth.hpp | 25 +- nisps/modes/slp_workshop.hpp | 25 +- nisps/modes/sound_analysis_midi.hpp | 24 +- nisps/modes/verb_fx.hpp | 25 +- nisps/modes/xiasri.hpp | 25 +- 30 files changed, 652 insertions(+), 458 deletions(-) create mode 100644 codegen/lib.ts delete mode 100644 codegen/templates/cpp_schema.hpp.template delete mode 100644 codegen/templates/ts_schema.ts.template diff --git a/codegen/generate-midi-devices.ts b/codegen/generate-midi-devices.ts index a404a19..52cc223 100644 --- a/codegen/generate-midi-devices.ts +++ b/codegen/generate-midi-devices.ts @@ -14,14 +14,18 @@ * by name, and selects which the ML drives over MIDI CC. Idempotent: regenerating the * same schemas yields byte-identical output. Exits non-zero on validation failure. * - * Deliberately separate from codegen/generate.ts (the mode-schema pipeline) so it cannot - * disturb the mode golden test. + * Deliberately a separate DRIVER from codegen/generate.ts (the mode-schema + * pipeline): different schema dir, different meta-schema, different output + * dirs, and generate.ts's golden test must not depend on MIDI-device schema + * state. That does not require duplicate helpers, though — the small + * string/fs helpers both drivers need live once in ./lib.ts (ST12). */ -import { readFileSync, writeFileSync, readdirSync, mkdirSync, existsSync } from "node:fs"; +import { writeFileSync, readdirSync, existsSync } from "node:fs"; import { join, dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020, { type AnySchemaObject } from "ajv/dist/2020.js"; +import { ensureDir, readJSON, toPascalCase, cppStringLit, tsStringLit } from "./lib.ts"; // ----- Types ---------------------------------------------------------------- @@ -62,22 +66,8 @@ const CPP_OUT_DIR = join(REPO_ROOT, "nisps", "midi", "generated"); const TS_OUT_DIR = join(REPO_ROOT, "manifold", "src", "midi-devices", "generated"); // ----- Helpers -------------------------------------------------------------- - -function ensureDir(d: string): void { - if (!existsSync(d)) mkdirSync(d, { recursive: true }); -} -function readJSON(path: string): T { - return JSON.parse(readFileSync(path, "utf8")) as T; -} -function toPascalCase(snake: string): string { - return snake.split("_").map(s => (s.length === 0 ? s : s[0].toUpperCase() + s.slice(1))).join(""); -} -function cppStringLit(s: string): string { - return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; -} -function tsStringLit(s: string): string { - return "'" + s.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'"; -} +// ensureDir/readJSON/toPascalCase/cppStringLit/tsStringLit now live in +// ./lib.ts (ST12, shared with generate.ts). const BANNER = "// AUTOGENERATED — do not edit. Source: schemas/midi_devices/*.json. " + "Run `bun run codegen/generate-midi-devices.ts` to regenerate."; diff --git a/codegen/generate.ts b/codegen/generate.ts index f296bf2..d2585d4 100644 --- a/codegen/generate.ts +++ b/codegen/generate.ts @@ -14,14 +14,26 @@ * (The TS target moved playground → manifold at P5 of * docs/specs/plans/one-core-engine-refactor.md.) * + * Per-mode C++ headers also emit (simplification 2026-07, S1/S5/S6/S25): + * - `kSchema` — the `nisps::ParamSchema` aggregate; each mode's + * `param_schema()` becomes a one-line `return generated::kSchema;` + * instead of hand-assembling the same 12-field struct. + * - `MLP` — the mode's `nisps::ml::MLP<...>` net-shape alias, + * built from the constants above instead of a second hand-typed copy of + * the dims in nisps/modes/*.hpp. + * The TS `index.ts` also emits `ALL_MODE_SCHEMAS` (every mode schema, in + * generation order) so manifold's mode catalogue no longer hand-imports each + * schema by name — see manifold/src/console/model.ts's `SCHEMA_MODES` overlay. + * * Idempotent: regenerating the same schemas yields byte-identical output. * Exits non-zero on validation failure. */ -import { readFileSync, writeFileSync, readdirSync, mkdirSync, existsSync } from "node:fs"; +import { writeFileSync, readdirSync, existsSync } from "node:fs"; import { join, dirname, resolve, basename } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020, { type AnySchemaObject } from "ajv/dist/2020.js"; +import { ensureDir, readJSON, toPascalCase, cppStringLit, tsStringLit } from "./lib.ts"; // ----- Types ---------------------------------------------------------------- @@ -70,27 +82,8 @@ const CPP_OUT_DIR = join(REPO_ROOT, "nisps", "modes", "generated"); const TS_OUT_DIR = join(REPO_ROOT, "manifold", "src", "modes", "generated"); // ----- Helpers -------------------------------------------------------------- - -function ensureDir(d: string): void { - if (!existsSync(d)) { - mkdirSync(d, { recursive: true }); - } -} - -function readJSON(path: string): T { - return JSON.parse(readFileSync(path, "utf8")) as T; -} - -/** - * Convert snake_case to PascalCase. - * "paf_synth" -> "PafSynth", "memlcelium" -> "Memlcelium" - */ -function toPascalCase(snake: string): string { - return snake - .split("_") - .map(s => s.length === 0 ? s : s[0].toUpperCase() + s.slice(1)) - .join(""); -} +// ensureDir/readJSON/toPascalCase/cppStringLit/tsStringLit now live in +// ./lib.ts (ST12, shared with generate-midi-devices.ts). /** * Convert mode_id to UPPER_SNAKE for #define guards. @@ -99,21 +92,6 @@ function toUpperSnake(snake: string): string { return snake.toUpperCase(); } -/** - * Escape a string literal for embedding into a C++ string. - * We expect ASCII identifiers + label text only; quote and backslash are escaped. - */ -function cppStringLit(s: string): string { - return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; -} - -/** - * Escape a string for a TS single-line single-quoted literal. - */ -function tsStringLit(s: string): string { - return "'" + s.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'"; -} - /** * Format a float literal for C++ ensuring `.f` suffix and explicit decimal point. * Required by the perf contract (architecture §3.3). @@ -161,11 +139,18 @@ function emitSchemaTypesHpp(): string { "//", "// `Curve` is the authoritative enum from nisps/core/math.hpp; we re-export it", "// into this namespace so generated headers can refer to plain `Curve::linear`.", + "//", + "// `ParamSchema` lives in the top-level `nisps` namespace, not", + "// `nisps::modes::generated`: nisps/core/concepts.hpp forward-declares", + "// `nisps::ParamSchema` and requires `T::param_schema()` to return", + "// `const nisps::ParamSchema&`, so the definition has to match that", + "// forward declaration exactly (S5, one-core simplification 2026-07).", "#ifndef NISPS_GENERATED_SCHEMA_TYPES_HPP", "#define NISPS_GENERATED_SCHEMA_TYPES_HPP", "", "#include ", "#include ", + "#include ", "#include ", "", "#include \"../../core/math.hpp\"", @@ -209,6 +194,30 @@ function emitSchemaTypesHpp(): string { "", "} // namespace nisps::modes::generated", "", + "namespace nisps {", + "", + "// View-style aggregate satisfying the `nisps::Mode` concept's", + "// `param_schema()` requirement. All members are spans/views into", + "// compile-time generated arrays; codegen emits one", + "// `inline constexpr ParamSchema kSchema` per mode (see the", + "// per-mode _schema.hpp in this directory).", + "struct ParamSchema {", + " std::string_view mode_id;", + " std::string_view engine_id;", + " std::span input_channels;", + " std::size_t input_size;", + " 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;", + "};", + "", + "} // namespace nisps", + "", "#endif // NISPS_GENERATED_SCHEMA_TYPES_HPP", "", ].join("\n"); @@ -286,6 +295,7 @@ function emitModeHpp(schema: ModeSchema, sourceFile: string): string { lines.push(`#define ${guard}`); lines.push(""); lines.push("#include \"schema_types.hpp\""); + lines.push("#include \"../../ml/mlp.hpp\""); lines.push(""); lines.push("namespace nisps::modes::generated {"); lines.push(""); @@ -368,6 +378,35 @@ function emitModeHpp(schema: ModeSchema, sourceFile: string): string { lines.push("};"); lines.push(""); + // Net-shape alias (S6/S25): the mode's MLP<> template args, built from the + // constants above rather than hand-typed a second time in nisps/modes/*.hpp. + const modeMlpName = `${toPascalCase(schema.mode_id)}MLP`; + lines.push( + `using ${modeMlpName} = ::nisps::ml::MLP<` + + `${constName}MLConfig.input_size, ` + + `${constName}HiddenLayers[0], ${constName}HiddenLayers[1], ${constName}HiddenLayers[2], ` + + `${constName}MLConfig.output_size>;` + ); + lines.push(""); + + // ParamSchema aggregate (S5): the one-line `param_schema()` body every mode + // used to hand-assemble as a private `kSchema` positional-init block. + lines.push(`inline constexpr ::nisps::ParamSchema ${constName}Schema = {`); + lines.push(` ${constName}ModeId,`); + lines.push(` ${constName}EngineId,`); + lines.push(` std::span(${constName}InputChannels),`); + lines.push(` ${constName}MLConfig.input_size,`); + 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,`); + lines.push("};"); + lines.push(""); + lines.push("} // namespace nisps::modes::generated"); lines.push(""); lines.push(`#endif // ${guard}`); @@ -454,11 +493,29 @@ function emitTsIndex(modeIds: string[]): string { lines.push("// AUTOGENERATED — do not edit. Run `bun run codegen/generate.ts` to regenerate."); lines.push("// Re-exports every generated mode schema."); lines.push(""); + lines.push("import type { ModeSchema } from './types';"); + for (const id of modeIds) { + lines.push(`import { ${toPascalCase(id)}Schema } from './${id}_schema';`); + } + lines.push(""); lines.push("export * from './types';"); for (const id of modeIds) { lines.push(`export * from './${id}_schema';`); } lines.push(""); + // Mechanically-derived mode-identity registry (S1): every generated mode + // schema, in generation (mode_id-sorted) order. NOT display order — that + // ordering is hand-curated overlay truth (manifold/src/console/model.ts's + // `SCHEMA_MODES`). + lines.push( + "/** Every generated mode schema, mode_id-sorted. Mechanically-derived mode-identity truth. */" + ); + lines.push("export const ALL_MODE_SCHEMAS: readonly ModeSchema[] = ["); + for (const id of modeIds) { + lines.push(` ${toPascalCase(id)}Schema,`); + } + lines.push("];"); + lines.push(""); return lines.join("\n"); } diff --git a/codegen/lib.ts b/codegen/lib.ts new file mode 100644 index 0000000..559649c --- /dev/null +++ b/codegen/lib.ts @@ -0,0 +1,51 @@ +/** + * Shared helpers for the two codegen drivers (generate.ts — mode schemas — + * and generate-midi-devices.ts — MIDI device templates). + * + * The two drivers stay separate FILES on purpose: they read different schema + * directories, validate against different meta-schemas, write to different + * output dirs, and generate.ts's golden test (codegen/tests/golden_test.ts) + * must not depend on MIDI-device schema state. None of that requires + * duplicating these small string/fs helpers — they carry no per-driver + * behaviour, so they live here once (ST12, simplification 2026-07). + */ + +import { readFileSync, existsSync, mkdirSync } from "node:fs"; + +/** Create a directory (and parents) if it doesn't already exist. */ +export function ensureDir(d: string): void { + if (!existsSync(d)) { + mkdirSync(d, { recursive: true }); + } +} + +/** Read and JSON-parse a file. */ +export function readJSON(path: string): T { + return JSON.parse(readFileSync(path, "utf8")) as T; +} + +/** + * Convert snake_case to PascalCase. + * "paf_synth" -> "PafSynth", "memlcelium" -> "Memlcelium" + */ +export function toPascalCase(snake: string): string { + return snake + .split("_") + .map(s => (s.length === 0 ? s : s[0].toUpperCase() + s.slice(1))) + .join(""); +} + +/** + * Escape a string literal for embedding into a C++ string. + * We expect ASCII identifiers + label text only; quote and backslash are escaped. + */ +export function cppStringLit(s: string): string { + return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; +} + +/** + * Escape a string for a TS single-line single-quoted literal. + */ +export function tsStringLit(s: string): string { + return "'" + s.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'"; +} diff --git a/codegen/templates/cpp_schema.hpp.template b/codegen/templates/cpp_schema.hpp.template deleted file mode 100644 index 63b7ccb..0000000 --- a/codegen/templates/cpp_schema.hpp.template +++ /dev/null @@ -1,53 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/{{SOURCE_FILE}}. Run `bun run codegen/generate.ts` to regenerate. -// -// This file is a reference template showing the shape of each generated C++ -// header. It is NOT consumed at codegen time — `codegen/generate.ts` builds the -// strings programmatically (see emitModeHpp). The template lives here so that: -// 1. reviewers can see the intended structure at a glance, -// 2. anyone tweaking the codegen has a "what should this look like?" anchor. -// -// Placeholders use {{NAME}} syntax purely for documentation. - -#ifndef NISPS_GENERATED_{{MODE_ID_UPPER}}_SCHEMA_HPP -#define NISPS_GENERATED_{{MODE_ID_UPPER}}_SCHEMA_HPP - -#include "schema_types.hpp" - -namespace nisps::modes::generated { - -inline constexpr std::string_view k{{ModeId}}ModeId = "{{mode_id}}"; -inline constexpr std::string_view k{{ModeId}}EngineId = "{{engine_id}}"; - -inline constexpr std::array k{{ModeId}}InputChannels = {{ - "{{input_channel_0}}", - /* ... */ -}}; - -inline constexpr std::array k{{ModeId}}HiddenLayers = {{ - /* ... */ -}}; - -inline constexpr MLConfig k{{ModeId}}MLConfig = { - {{input_size}}u, {{output_size}}u, - {{default_spread}}f, {{default_lr}}f, {{default_iter}}u, -}; - -inline constexpr std::size_t k{{ModeId}}ParamCount = {{N_PARAMS}}u; -inline constexpr std::array k{{ModeId}}Params = {{ - Param{ "{{name}}", "{{label}}", {{min}}f, {{max}}f, {{default}}f, Curve::{{Curve}}, "{{group}}" }, - /* ... */ -}}; - -inline constexpr std::size_t k{{ModeId}}VoiceSpaceCount = {{N_VS}}u; -inline constexpr std::array k{{ModeId}}VoiceSpaces = {{ - /* ... */ -}}; - -inline constexpr UIConfig k{{ModeId}}UI = { - PrimaryInput::{{PrimaryInput}}, - {{show_voice_space_selector}}, {{show_synth_visualizer}}, -}; - -} // namespace nisps::modes::generated - -#endif // NISPS_GENERATED_{{MODE_ID_UPPER}}_SCHEMA_HPP diff --git a/codegen/templates/ts_schema.ts.template b/codegen/templates/ts_schema.ts.template deleted file mode 100644 index e52c010..0000000 --- a/codegen/templates/ts_schema.ts.template +++ /dev/null @@ -1,44 +0,0 @@ -// AUTOGENERATED — do not edit. Source: schemas/modes/{{SOURCE_FILE}}. Run `bun run codegen/generate.ts` to regenerate. -// -// Reference template for the TypeScript codegen output. Not consumed at runtime -// (the codegen builds strings programmatically in emitModeTs). Lives here for -// human reviewers — placeholders use {{NAME}} syntax. - -import type { ModeSchema } from './types'; - -export interface {{ModeId}}Params { - readonly {{param_name_0}}: number; - /* ... */ -} - -export const {{ModeId}}Schema: ModeSchema = { - mode_id: '{{mode_id}}', - engine_id: '{{engine_id}}', - ml: { - input_channels: ['{{input_channel_0}}', /* ... */], - input_size: {{input_size}}, - hidden_layers: [/* ... */], - output_size: {{output_size}}, - default_spread: {{default_spread}}, - default_learning_rate: {{default_lr}}, - default_max_iterations: {{default_iter}}, - }, - params: [ - { - name: '{{name}}', - label: '{{label}}', - min: {{min}}, - max: {{max}}, - default: {{default}}, - curve: '{{curve}}', - group: '{{group}}', - }, - /* ... */ - ], - voice_spaces: [/* ... */], - ui: { - primary_input: '{{primary_input}}', - show_voice_space_selector: {{show_voice_space_selector}}, - show_synth_visualizer: {{show_synth_visualizer}}, - }, -}; diff --git a/codegen/tests/golden/paf_synth_schema.hpp b/codegen/tests/golden/paf_synth_schema.hpp index fc5adfa..197c671 100644 --- a/codegen/tests/golden/paf_synth_schema.hpp +++ b/codegen/tests/golden/paf_synth_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_PAF_SYNTH_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -348,6 +349,23 @@ inline constexpr UIConfig kPafSynthUI = { true, }; +using PafSynthMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kPafSynthSchema = { + kPafSynthModeId, + kPafSynthEngineId, + std::span(kPafSynthInputChannels), + kPafSynthMLConfig.input_size, + std::span(kPafSynthHiddenLayers), + kPafSynthMLConfig.output_size, + kPafSynthMLConfig.default_spread, + kPafSynthMLConfig.default_learning_rate, + kPafSynthMLConfig.default_max_iterations, + std::span(kPafSynthParams), + std::span(kPafSynthVoiceSpaces), + kPafSynthUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_PAF_SYNTH_SCHEMA_HPP diff --git a/manifold/public/nisps.wasm b/manifold/public/nisps.wasm index 1ceed31367faaac4c161501477a9fddae50ebe1b..7dab364393dbe0d04884655de4cda71619404a67 100755 GIT binary patch delta 2805 zcmaJ@O=whC6n^K_LNq!ul@u;`-IP!!`hrADMp zQF*TzN*2L}WWsY{Aqy$iE}CLXA{3fM7g@MSsf$2U2vk}Np$)BID1r7n@7_0#QJTfO z_ndpa@1AqNbMNH$htai%(di{Yw{QI{-rw!~Xo}FKup>&6It|mN*OG=LNm;S(f7fW- z61|4uem5?K^k>C>E8LbBYXnSqFNbHusA(7XD`ZBDn6@}ywkaou1#|h!RL&tvnFye= zT^!7bkv=od0j{vZCQ^`LtXN^&kYRS;r`OwZSWFP2l*6X|>|rx29{AtYi9d_a9<)Mv zu?pKB7UgsdW2~HRP@AWtp^|Kn5xcztav9EwR+MBIRT*L@m895uXl=1}A#yNimjCa8@jeaVxIHh)aJ4F$Ba|6u<~A#tMt!1Zvz@YKWY; zr`5nZtFfZhK!8@`z7k_uSi%=WaE=0E0GJW;g2h;VB*r|}A;z*6b9y-Sie@lL7{0tyvx(>Xx&<@(>g5HhmM*a8$<0$Xw0-s@-CdB zTw{w235OR_-bZEH8f#TyE#6mUYM~DAU&dyXvpv9W_qGiH+ZA9t6n3Wy%P4Hg`*Hx- zKIQBRu=~897<(YV<`wpk3frx)Z+XX0(+kc0%GnoSPk2{O13M644=8L!g&p#-okPkx z3{a_9W$3Hp{uf1GRBJ~ALf0#nshftq56ZNIM!e6<)ZSAi6*cfQhO2t95>Q-&L!IDR z@4X6j(v-LT49Ko~$IehYO?wwGJoB}NxfI}Ly_;t!pZK;0A0fY*#yK52N`5^%?r3|A zp-)t_pjWFk;=A673gv0h`?NwY(~`GPp^xdx%#nBLhsNBPnTlHc-h~Wl*bhQEyz-dP zF2lrt$J-tisnBN34gaC{$2ITmA=>QVH_71ai<0;Hr+2dVi<{X}bu!Pt1jZiMU6?ex z#R|{yo-Qk&yB_Kmi+b*29SL)+|2J3c7V~T)er|(*R@>{B5juweXIe+z2v)#6rRU5H zX&z%9V;jJc4SrYKHRd4|cUHkU;Q`GA)_y&89i)MLkE-B9fm?a^DYHgdQc-#B24$B9 zfHI?}To@Roifw(|n^In%hWscAJRg)D*ylh delta 2735 zcmaJ@U1(HC6uxKf-Q=3w=%RUu$+pZciH5C#Skmn3io0WCE2v;WL}-iqpw+C~Y9zE` zmAl1IdbBT~skK+q^(<^b}lw}M0`;5jjEYF@1 zD{(g9IJIJVXN!XfZQLDKOPspd1y2-r>jfC2o?s&tLH(@86q#P8NSf<+)F4k%bx1*yxLxEdv~0(D1nYVo5!lCu}ZoTV^B%^W|MS-TjD-Xh*Y(b z@wk^|Z2&t?T`>X!M^IPaBxpIibeXfOUdH9#+BBfm0^rOt02vnVmwHGsL_-ejNbVCatwaheV?|7QA5GQEh zaJH&*0gq{_uD*O6a<~x2y_#|DW^kxkvD}eqtY;Esw3$YDDmF}%Aq&G^Zkh!suEP`D zh4>)zk?^6NEz}MfAfwjuib|q*DBUDb^`_x;ppya|@j)h`EuA7Djmp_p&$#8CUJ1l5 zXNFj{k0lOO_bg*x1Lkzn=#XyVevi|YZek@IIY6e^cesV|7Ij@1*DYZbQh}W!IU;Jc z+CroH);zw3Cx<&up@{wn6mxJsO@K2Q2I41PLSf&Gu-6EQjT}Mgy0qW&snI}IT zqSp4Lv3#Xush)@OE(_IqAj=i%p*H#Ld(=xQ`Oi`6@ViZPo55}}Rw}~wMp$mJee%pP zVDs{uW55oY=uHN@%UHb;c29)uGuVAHI|S^0`OXk^P+5LFM6b3VHlV=>bSwhxGN7t_ zq(CR->IybIYohn-4cFGlm9s60@i99@tXE3}!$t~*M7w6#Gf%~xN<`}Zl^O~1qNZrh_Hd)NEDP`jwy|==D$J&eMRUiUT zFdcX^S^zOhuE;`MyBxb_7Z9(R>lQG!!ar*5CFZk+ctmsQgsWz%SP$E&C4JY#?qB9T z%=Q?2iE_|{ZL@Bq+Gi1<++?SgveU$-mU~-`xx^Su1ZU`JLpZMm6n;g_d0H`5bfpk%%Q4%bA^4w+7UrLwyp`rVh%+Rj;?U1W{Ibunnmjl-892o%%K>fW>6s-hX-t2$Wq=E2ZYw~4{)k#V{8|0` zfE&N!@!LBKfz&O=-wHkAm)a3M1^PveuN4l^bv4Nr2!9qG(OsZvHEt{ro-0ReC{Rs} jT?IObp|e1}OP4=(!v|Kp0}tev=V?7nPux0B1E>E5W$CHN diff --git a/manifold/src/console/model.ts b/manifold/src/console/model.ts index da87cdb..649396e 100644 --- a/manifold/src/console/model.ts +++ b/manifold/src/console/model.ts @@ -2,14 +2,18 @@ * Console — shared instrument model: the modes catalogue + per-param shaping * helpers. * - * SOURCE OF TRUTH (one-core-engine P5.2): the schema-backed modes are DERIVED - * from the codegen-produced schemas in `src/modes/generated/` — real param - * names, groups, count, and each mode's `ml` config + `engine_id` come from - * schema truth, never hand-written. A thin manifold-side OVERLAY supplies only - * the display concerns a schema has no opinion on: label, glyph, ModeClass, - * input kind, and ordering. Two manifold-only modes with no schema - * (`visualizer`, `c15` placeholder) stay hand-written and use the default net - * shape. + * SOURCE OF TRUTH (one-core-engine P5.2, mode-identity consolidation S1 — + * simplification 2026-07): the schema-backed modes are DERIVED from + * `ALL_MODE_SCHEMAS`, codegen's mechanically-generated registry of every + * schema in `src/modes/generated/` — real param names, groups, count, each + * mode's `ml` config + `engine_id`, AND the set of mode ids itself all come + * from schema truth, never hand-imported one-by-one. A thin manifold-side + * OVERLAY (`SCHEMA_MODE_OVERLAYS`, keyed by mode_id) supplies only the display + * concerns a schema has no opinion on: label, glyph, ModeClass, input kind, + * and catalogue ORDER (insertion order of the object's keys) — those are + * legitimately hand-curated display truth, not mechanically derivable, and + * survive here. Two manifold-only modes with no schema (`visualizer`, `c15` + * placeholder) stay hand-written and use the default net shape. * * KEY CHANGE vs the JSX reference: the pseudo-inference `MF_infer` (sin/cos * placeholder) and the `useInstrument` hook are GONE. The `values` every @@ -23,29 +27,49 @@ */ import type { ModeSchema } from '../modes/generated/types'; -import { - BreakorSchema, - ChannelStripSchema, - ElysiamorfSchema, - MemlceliumSchema, - PafSynthSchema, - SlpWorkshopSchema, - SoundAnalysisMidiSchema, - VerbFxSchema, - XiasriSchema, -} from '../modes/generated'; +import { ALL_MODE_SCHEMAS } from '../modes/generated'; +import { applyCurve } from '../backends/mapping'; export type ParamStatus = 'off' | 'fixed' | 'live'; /** * Param GROUP. Historically a small hand-picked union; now the group is the raw * schema string (`'operators'`, `'envelope'`, `'kick'`, …) so the type is just - * `string`. Unknown groups fall back to the accent colour in the GROUP_COLOR - * maps that key off this field. + * `string`. Unknown groups fall back to the accent colour in {@link GROUP_COLOR}. */ export type ParamGroup = string; export type ModeClass = 'Synth' | 'Sequencer' | 'Controller' | 'Visual'; export type ModeInput = 'xy' | 'joystick' | 'audio_in'; +/** + * Group → CSS custom-property name, for colouring per-output UI (bars, dots, + * meters). ST4 (simplification 2026-07): this was four byte-identical copies + * (console/shared-ui.tsx, console/OutputStage.tsx, console/CompositeStage.tsx, + * dock/OutputControlRow.tsx) — one canonical map now lives here. + * + * The keys are JSX-era placeholder group names, NOT the real schema group + * strings (`schemas/modes/*.json` uses `verb`, `sequencer`, `kick`, `snare`, + * `filterbank`, `operators`, … — only `pitch` overlaps). For every + * schema-backed mode this means almost every param falls through to the + * `--accent` default; the map only does real work for the two hand-written + * manifold-only modes (`visualizer`, `c15`), whose {@link MANIFOLD_ONLY_MODES} + * groups (`mod`/`amp`/`fx`) were chosen to match it. A hash-to-palette + * function over arbitrary group strings would fix this for schema-driven + * groups too, but `console/OutputStage.tsx` — the hero output view, and the + * highest-traffic consumer of group colour — is outside this pass's file + * ownership; changing the strategy here alone would make the demoted/dock + * views (this map's consumers) disagree with the hero view instead of + * agreeing, which is the opposite of ST4's goal. Left as a plain map; + * revisit keys/strategy together with OutputStage.tsx in one change. + */ +export const GROUP_COLOR: Readonly> = { + formant: '--accent', + pitch: '--accent-2', + amp: '--good', + filter: '--warn', + fx: '--info', + mod: '--accent-3', +}; + /** * A mode's net shape — the engine dims the runtime-shaped WASM MLP is reshaped * to when this mode is active (one-core-engine P5.3). Schema-backed modes carry @@ -123,9 +147,11 @@ export interface MFMode { */ ml: ModeML; /** - * The schema's `engine_id` (audio-engine metadata). NOTE: audio backend - * SELECTION still routes through {@link modeEngineId}, which is unchanged — - * this field is the schema-truth annotation, not the routing decision. + * The schema's `engine_id` (audio-engine metadata). {@link modeEngineId} + * routes the actual backend SELECTION off this field for every mode except + * `sound_analysis_midi` (see that function's doc comment for why it needs + * an exception) — this field stays the schema-truth annotation; + * `modeEngineId` is the routing decision. */ engineId: string; placeholder?: boolean; @@ -191,34 +217,31 @@ interface ModeOverlay { } /** - * ORDERED list of schema-backed modes: `{ schema, overlay }`. Order here is the - * catalogue order. `xiasri` + `slp_workshop` are new browser-viable entries - * (they have schemas but weren't in the hand-written catalogue). The overlay is - * hand-picked display; the params/ml/engine_id come from the schema. + * Mode-identity display OVERLAY, keyed by mode_id (S1 — simplification + * 2026-07): labels, glyphs, `ModeClass`, input kind, and CATALOGUE ORDER + * (this object's key insertion order) are hand-curated display truth with no + * schema opinion, so they stay hand-written here. Everything else a mode + * needs (which mode ids exist, their params/ml/engine_id) is mechanically + * derived from `ALL_MODE_SCHEMAS` (codegen output) below — `xiasri` + + * `slp_workshop` are browser-viable entries that have schemas but weren't in + * the pre-P5 hand-written catalogue. */ -const SCHEMA_MODES: ReadonlyArray<{ schema: ModeSchema; overlay: ModeOverlay }> = [ - { schema: PafSynthSchema, overlay: { label: 'PAF Synth', glyph: '∿', cls: 'Synth', input: 'xy' } }, - { - schema: ChannelStripSchema, - overlay: { label: 'Channel Strip', glyph: '▤', cls: 'Synth', input: 'joystick' }, +const SCHEMA_MODE_OVERLAYS: Readonly> = { + paf_synth: { label: 'PAF Synth', glyph: '∿', cls: 'Synth', input: 'xy' }, + channel_strip: { label: 'Channel Strip', glyph: '▤', cls: 'Synth', input: 'joystick' }, + verb_fx: { label: 'Verb FX', glyph: '◞', cls: 'Synth', input: 'joystick' }, + elysiamorf: { label: 'Elysiamorf', glyph: '❋', cls: 'Synth', input: 'xy' }, + memlcelium: { label: 'MEML Celium', glyph: '☷', cls: 'Sequencer', input: 'xy' }, + breakor: { label: 'Breakor', glyph: '⊟', cls: 'Sequencer', input: 'joystick' }, + xiasri: { label: 'Xiasri', glyph: '✴', cls: 'Synth', input: 'joystick' }, + slp_workshop: { label: 'SLP Workshop', glyph: '☷', cls: 'Sequencer', input: 'xy' }, + sound_analysis_midi: { + label: 'Sound Analysis → MIDI', + glyph: '⇉', + cls: 'Controller', + input: 'audio_in', }, - { schema: VerbFxSchema, overlay: { label: 'Verb FX', glyph: '◞', cls: 'Synth', input: 'joystick' } }, - { schema: ElysiamorfSchema, overlay: { label: 'Elysiamorf', glyph: '❋', cls: 'Synth', input: 'xy' } }, - { - schema: MemlceliumSchema, - overlay: { label: 'MEML Celium', glyph: '☷', cls: 'Sequencer', input: 'xy' }, - }, - { schema: BreakorSchema, overlay: { label: 'Breakor', glyph: '⊟', cls: 'Sequencer', input: 'joystick' } }, - { schema: XiasriSchema, overlay: { label: 'Xiasri', glyph: '✴', cls: 'Synth', input: 'joystick' } }, - { - schema: SlpWorkshopSchema, - overlay: { label: 'SLP Workshop', glyph: '☷', cls: 'Sequencer', input: 'xy' }, - }, - { - schema: SoundAnalysisMidiSchema, - overlay: { label: 'Sound Analysis → MIDI', glyph: '⇉', cls: 'Controller', input: 'audio_in' }, - }, -]; +}; function modeFromSchema(schema: ModeSchema, overlay: ModeOverlay): MFMode { return { @@ -234,6 +257,27 @@ function modeFromSchema(schema: ModeSchema, overlay: ModeOverlay): MFMode { }; } +/** + * Schema-backed modes, in {@link SCHEMA_MODE_OVERLAYS}'s curated catalogue + * order. A mode_id with no matching generated schema is dropped (loudly, via + * console.error) rather than crashing the console — this should only be + * reachable mid-edit, between adding/removing a schema and updating the + * overlay map. + */ +const SCHEMA_MODES: MFMode[] = Object.entries(SCHEMA_MODE_OVERLAYS).flatMap( + ([modeId, overlay]) => { + const schema = ALL_MODE_SCHEMAS.find((s) => s.mode_id === modeId); + if (!schema) { + console.error( + `model.ts: SCHEMA_MODE_OVERLAYS has an entry for mode_id '${modeId}' but no ` + + 'matching generated schema exists (check codegen output) — dropped from MF_MODES.', + ); + return []; + } + return [modeFromSchema(schema, overlay)]; + }, +); + /** * Manifold-only modes with NO schema — hand-written params on the DEFAULT net * shape. `visualizer` is a pure browser visual; `c15` is the "Powerful Synth @@ -269,16 +313,41 @@ const MANIFOLD_ONLY_MODES: MFMode[] = [ }, ]; -export const MF_MODES: MFMode[] = [ - ...SCHEMA_MODES.map(({ schema, overlay }) => modeFromSchema(schema, overlay)), - ...MANIFOLD_ONLY_MODES, -]; +export const MF_MODES: MFMode[] = [...SCHEMA_MODES, ...MANIFOLD_ONLY_MODES]; -/** Mirrors the engine's `applyCurve` (≈0.43 ≈ linear). */ -export function applyCurve(v: number, c: number): number { - const e = 0.25 + c * 1.75; - return Math.pow(Math.max(0, Math.min(1, v)), e); -} +/** + * L38 (simplification 2026-07): this used to be a SECOND, divergent + * `applyCurve` (`e = 0.25 + c * 1.75`, linear at c≈0.43) alongside + * `backends/mapping.ts`'s spec-anchored version (0.5 = exact linear + * midpoint, backends-spec §3). Deleted in favour of the mapping.ts survivor, + * imported above and re-exported below so `console/index.ts`'s existing + * `export { applyCurve } from './model'` keeps working. + * + * Behaviour change: {@link shapeValues} (below) and Drawers.tsx's bar + * snapshot are the only consumers of this UI-display curve — they shape the + * on-screen output value/bar for every mode, at every param's `curve` + * setting (default 0.5). Actual backend signals (MIDI/OSC/VCV/CV, via + * `backends/*.ts`'s `mapOutput`) already used mapping.ts's formula + * exclusively, so this change makes the on-screen numbers agree with what is + * actually sent to a sink, rather than diverging from it as before. At the + * default curve (0.5) the exponent moves from 1.125 to an exact 1.0 + * (linear); away from 0.5 the whole response-curve shape changes (mapping.ts + * is symmetric in log-exponent space, 0.25 at c=0, 4.0 at c=1; the deleted + * version ranged 0.25→2.0 linearly) — every displayed output value moves + * visibly, for every mode, at every curve setting except the shared endpoint + * c=0 (both give exponent 0.25). No schema, engine, or backend-emitted value + * changes. + * + * NOTE for a follow-up: `console/CurvePad.tsx` (out of this pass's file + * ownership) has its own THIRD inlined copy of the deleted formula + * (`e = 0.25 + curve * 1.75`) to draw its response-curve preview canvas and + * the numeric readout beside it. That preview matched this file's curve + * before today; now it matches neither this file's (now mapping.ts's) curve + * nor the value the backends actually emit. Repoint it at the same + * `applyCurve` import so the knob preview, the on-screen bars, and the + * emitted signal all agree. + */ +export { applyCurve }; /** * Map the engine's raw output vector onto a mode's params, applying each @@ -302,24 +371,25 @@ export function shapeValues(params: MFParam[], engineOut: Float32Array | null): }); } -/** Map a mode id → the audio-engine backend id. Mode ids align with engine ids - * except `slp_workshop` (runs the memlcelium engine), the analysis controller, - * and the relabelled `c15`. */ +/** + * Map a mode id → the audio-engine backend id. Routes on `MFMode.engineId` + * (schema truth — `slp_workshop`'s schema already declares `engine_id: + * 'memlcelium'`, so it needs no special case here; unknown ids, including the + * two manifold-only modes, fall back to `'thru'` via their own `engineId`) + * (L37 — simplification 2026-07, deleted the hand-`switch`ed duplicate of + * schema `engine_id` that silently dropped any new mode never added to it). + * + * ONE named exception: `sound_analysis_midi`. Its schema declares `engine_id: + * 'thru'` because `SoundAnalysisMIDIMode`'s own `ModeBase` audio-engine slot + * really is `NoOpEngine` (nisps/modes/sound_analysis_midi.hpp) — audio passes + * through silently. But the mode also runs a SEPARATE real engine, the + * spectral-feature tap `AnalysisEngine` (engine_id "analysis", + * nisps/engines/analysis.hpp:95) — the browser must instantiate THAT as its + * audio backend for feature extraction to run at all. `schema.engine_id` + * ('thru') and the backend to actually select ('analysis') are genuinely + * different facts for this one mode; every other mode has them equal. + */ export function modeEngineId(modeId: string): string { - switch (modeId) { - case 'paf_synth': - case 'channel_strip': - case 'verb_fx': - case 'elysiamorf': - case 'memlcelium': - case 'breakor': - case 'xiasri': - return modeId; - case 'slp_workshop': - return 'memlcelium'; - case 'sound_analysis_midi': - return 'analysis'; - default: - return 'thru'; - } + if (modeId === 'sound_analysis_midi') return 'analysis'; + return MF_MODES.find((m) => m.id === modeId)?.engineId ?? 'thru'; } diff --git a/manifold/src/modes/generated/index.ts b/manifold/src/modes/generated/index.ts index d33e40c..1f3be46 100644 --- a/manifold/src/modes/generated/index.ts +++ b/manifold/src/modes/generated/index.ts @@ -1,6 +1,17 @@ // AUTOGENERATED — do not edit. Run `bun run codegen/generate.ts` to regenerate. // Re-exports every generated mode schema. +import type { ModeSchema } from './types'; +import { BreakorSchema } from './breakor_schema'; +import { ChannelStripSchema } from './channel_strip_schema'; +import { ElysiamorfSchema } from './elysiamorf_schema'; +import { MemlceliumSchema } from './memlcelium_schema'; +import { PafSynthSchema } from './paf_synth_schema'; +import { SlpWorkshopSchema } from './slp_workshop_schema'; +import { SoundAnalysisMidiSchema } from './sound_analysis_midi_schema'; +import { VerbFxSchema } from './verb_fx_schema'; +import { XiasriSchema } from './xiasri_schema'; + export * from './types'; export * from './breakor_schema'; export * from './channel_strip_schema'; @@ -11,3 +22,16 @@ export * from './slp_workshop_schema'; export * from './sound_analysis_midi_schema'; export * from './verb_fx_schema'; export * from './xiasri_schema'; + +/** Every generated mode schema, mode_id-sorted. Mechanically-derived mode-identity truth. */ +export const ALL_MODE_SCHEMAS: readonly ModeSchema[] = [ + BreakorSchema, + ChannelStripSchema, + ElysiamorfSchema, + MemlceliumSchema, + PafSynthSchema, + SlpWorkshopSchema, + SoundAnalysisMidiSchema, + VerbFxSchema, + XiasriSchema, +]; diff --git a/nisps/modes/base.hpp b/nisps/modes/base.hpp index ca0d86e..2f6e430 100644 --- a/nisps/modes/base.hpp +++ b/nisps/modes/base.hpp @@ -1,22 +1,18 @@ // nisps/modes/base.hpp — Common scaffolding for every concrete mode. // -// Two responsibilities: -// 1. Define `nisps::ParamSchema` (the aggregate type that the `Mode` -// concept's `param_schema()` returns a const-reference to). The codegen -// output in `nisps/modes/generated/` lives in a different namespace and -// provides typed constants per mode; we wrap those in a uniform view- -// style `ParamSchema` here so the concept is satisfied without touching -// generated code. -// -// 2. Provide `ModeBase` — a CRTP base -// that absorbs the per-mode boilerplate (input forwarding, ML inference -// driving engine params, voice space selection, control event ring -// buffer). Concrete modes derive from this and only specialise: -// - the schema reference (static), -// - the "extra" pre-mapping done before set_params (e.g. analysis -// features stitched into ML inputs in SoundAnalysisMIDI), -// - any engine-specific control glue (note_on/note_off, sequencer -// play/stop, BPM updates). +// Provides `ModeBase` — a CRTP base +// that absorbs the per-mode boilerplate (input forwarding, ML inference +// driving engine params, voice space selection, control event ring +// buffer). Concrete modes derive from this and only specialise: +// - the schema reference (static; `nisps::ParamSchema` — the aggregate +// type the `Mode` concept's `param_schema()` returns a const-reference +// to — is now DEFINED in the codegen output, `generated/schema_types.hpp`, +// included below; codegen also emits one `inline constexpr ParamSchema +// kSchema` per mode, so `param_schema()` is a one-line return), +// - the "extra" pre-mapping done before set_params (e.g. analysis +// features stitched into ML inputs in SoundAnalysisMIDI), +// - any engine-specific control glue (note_on/note_off, sequencer +// play/stop, BPM updates). // // Modes are platform-agnostic. Hardware/browser glue maps abstract input // channels (float [0, 1]) into `set_input(idx, value)` and drains @@ -43,25 +39,11 @@ namespace nisps { -// --------------------------------------------------------------------------- -// ParamSchema — view-style aggregate matching the concept's forward decl. -// All members are spans/views into compile-time generated arrays; the -// schema itself can be `inline constexpr` per mode. -// --------------------------------------------------------------------------- -struct ParamSchema { - std::string_view mode_id; - std::string_view engine_id; - std::span input_channels; - std::size_t input_size; - 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; -}; +// `nisps::ParamSchema` is defined in generated/schema_types.hpp (included +// above) — codegen owns it (S5, one-core simplification 2026-07) since its +// shape must match the `nisps::Mode` concept's forward declaration +// (nisps/core/concepts.hpp) exactly, and every mode's actual schema VALUE is +// itself codegen output (`generated::kSchema`). // --------------------------------------------------------------------------- // Abstract control event — emitted by modes for the platform glue to drain. diff --git a/nisps/modes/breakor.hpp b/nisps/modes/breakor.hpp index aaf444a..bd7d524 100644 --- a/nisps/modes/breakor.hpp +++ b/nisps/modes/breakor.hpp @@ -17,7 +17,6 @@ #include "../core/perf.hpp" #include "../core/types.hpp" #include "../engines/breakor.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/breakor_schema.hpp" @@ -26,17 +25,19 @@ namespace nisps::modes { class BreakOrMode : public ModeBase< BreakOrMode, BreakOrEngine, - ml::MLP<4u, 10u, 14u, 18u, 56u>, + generated::BreakorMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::BreakorMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kBreakorModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kBreakorSchema; + } NISPS_FORCE_INLINE void set_playing(bool playing) noexcept { engine_.set_playing(playing); @@ -71,22 +72,6 @@ class BreakOrMode : public ModeBase< (void)push_control_event(ce); } } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kBreakorModeId, - generated::kBreakorEngineId, - std::span(generated::kBreakorInputChannels), - generated::kBreakorMLConfig.input_size, - std::span(generated::kBreakorHiddenLayers), - generated::kBreakorMLConfig.output_size, - generated::kBreakorMLConfig.default_spread, - generated::kBreakorMLConfig.default_learning_rate, - generated::kBreakorMLConfig.default_max_iterations, - std::span(generated::kBreakorParams), - std::span(generated::kBreakorVoiceSpaces), - generated::kBreakorUI, - }; }; static_assert(Mode, "BreakOrMode must satisfy nisps::Mode"); diff --git a/nisps/modes/channel_strip.hpp b/nisps/modes/channel_strip.hpp index c7ab6de..d0b6cb1 100644 --- a/nisps/modes/channel_strip.hpp +++ b/nisps/modes/channel_strip.hpp @@ -13,7 +13,6 @@ #include "../core/concepts.hpp" #include "../core/types.hpp" #include "../engines/channel_strip.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/channel_strip_schema.hpp" @@ -22,33 +21,19 @@ namespace nisps::modes { class ChannelStripMode : public ModeBase< ChannelStripMode, ChannelStripEngine, - ml::MLP<4u, 10u, 10u, 14u, 24u>, + generated::ChannelStripMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::ChannelStripMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kChannelStripModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kChannelStripModeId, - generated::kChannelStripEngineId, - std::span(generated::kChannelStripInputChannels), - generated::kChannelStripMLConfig.input_size, - std::span(generated::kChannelStripHiddenLayers), - generated::kChannelStripMLConfig.output_size, - generated::kChannelStripMLConfig.default_spread, - generated::kChannelStripMLConfig.default_learning_rate, - generated::kChannelStripMLConfig.default_max_iterations, - std::span(generated::kChannelStripParams), - std::span(generated::kChannelStripVoiceSpaces), - generated::kChannelStripUI, - }; + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kChannelStripSchema; + } }; static_assert(Mode, "ChannelStripMode must satisfy nisps::Mode"); diff --git a/nisps/modes/elysiamorf.hpp b/nisps/modes/elysiamorf.hpp index a46e1fc..c87cef8 100644 --- a/nisps/modes/elysiamorf.hpp +++ b/nisps/modes/elysiamorf.hpp @@ -16,7 +16,6 @@ #include "../core/perf.hpp" #include "../core/types.hpp" #include "../engines/elysiamorf.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/elysiamorf_schema.hpp" @@ -25,17 +24,19 @@ namespace nisps::modes { class ElysiamorfMode : public ModeBase< ElysiamorfMode, ElysiamorfEngine, - ml::MLP<4u, 10u, 14u, 18u, 40u>, + generated::ElysiamorfMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::ElysiamorfMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kElysiamorfModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kElysiamorfSchema; + } NISPS_FORCE_INLINE void set_playing(bool playing) noexcept { engine_.set_playing(playing); @@ -64,22 +65,6 @@ class ElysiamorfMode : public ModeBase< (void)push_control_event(ce); } } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kElysiamorfModeId, - generated::kElysiamorfEngineId, - std::span(generated::kElysiamorfInputChannels), - generated::kElysiamorfMLConfig.input_size, - std::span(generated::kElysiamorfHiddenLayers), - generated::kElysiamorfMLConfig.output_size, - generated::kElysiamorfMLConfig.default_spread, - generated::kElysiamorfMLConfig.default_learning_rate, - generated::kElysiamorfMLConfig.default_max_iterations, - std::span(generated::kElysiamorfParams), - std::span(generated::kElysiamorfVoiceSpaces), - generated::kElysiamorfUI, - }; }; static_assert(Mode, "ElysiamorfMode must satisfy nisps::Mode"); diff --git a/nisps/modes/external_synth_midi.hpp b/nisps/modes/external_synth_midi.hpp index f6a24d9..bcd3555 100644 --- a/nisps/modes/external_synth_midi.hpp +++ b/nisps/modes/external_synth_midi.hpp @@ -18,6 +18,22 @@ // // `kRouteOutputsToEngine = false` (specialised below) so ModeBase skips routing // ML outputs into engine params, exactly like SoundAnalysisMIDIMode. +// +// NOT folded into the schemas/modes/*.json codegen pipeline (L11, one-core +// simplification 2026-07): that pipeline emits ONE static schema per JSON +// file, but this "mode" is a C++ template family over an externally supplied +// `Device` (one of the nisps/midi/generated device templates) and an output +// count `NOut` — there is no single (device, NOut) pair to author a JSON +// schema for, and its per-output "params" aren't independently named at all; +// they're whichever NOut slots `pick_cc_slots` selects from `Device.params`, +// which is ITSELF already schema-generated (codegen/generate-midi-devices.ts, +// schemas/midi_devices/). Folding would mean inventing a new +// device-templated schema kind for a single, already-degenerate consumer — +// not a behaviour-preserving consolidation. Instead: the net-shape + ML +// defaults this mode needs (identical to every other 4-joystick-input mode — +// see e.g. schemas/modes/memlcelium.json) are named ONCE below +// (`ext_synth_defaults`), so the `MLP<>` shape and the `ParamSchema` instance +// both read from the same place instead of re-typing the same literals. #pragma once @@ -37,6 +53,34 @@ namespace nisps::modes { +// Shared net-shape + ML defaults for every ExternalSynthMIDIMode +// 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). +namespace ext_synth_defaults { +inline constexpr std::size_t kInputSize = 4u; +inline constexpr std::array kInputChannels{ + std::string_view{"joy_x"}, std::string_view{"joy_y"}, + 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 +// `MLP` codegen alias, generalised to a template parameter since NOut +// varies per device/output-count combination). +template +using ExtSynthMIDIMLP = ::nisps::ml::MLP< + ext_synth_defaults::kInputSize, + ext_synth_defaults::kHiddenLayers[0], + ext_synth_defaults::kHiddenLayers[1], + ext_synth_defaults::kHiddenLayers[2], + NOut>; + template class ExternalSynthMIDIMode; @@ -74,14 +118,14 @@ template , NoOpEngine, - ml::MLP<4u, 10u, 14u, 18u, NOut>, + ExtSynthMIDIMLP, 4u> { static_assert(Device.params.size() >= NOut, "device template has fewer params than the mode's output count"); public: using Base = ModeBase, NoOpEngine, - ml::MLP<4u, 10u, 14u, 18u, NOut>, 4u>; + ExtSynthMIDIMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return Device.device_id; } @@ -129,10 +173,6 @@ class ExternalSynthMIDIMode : public ModeBase< return a; }(); - static constexpr std::array kInputs{ - std::string_view{"joy_x"}, std::string_view{"joy_y"}, - std::string_view{"joy_z"}, std::string_view{"joy_w"}}; - static constexpr std::array kHidden{10u, 14u, 18u}; static constexpr std::array kNoParams{}; static constexpr std::array kNoVoiceSpaces{}; static constexpr generated::UIConfig kUI{generated::PrimaryInput::Joystick, false, false}; @@ -140,13 +180,13 @@ class ExternalSynthMIDIMode : public ModeBase< static inline constexpr ParamSchema kSchema = ParamSchema{ Device.device_id, std::string_view{"thru"}, - std::span(kInputs), - 4u, - std::span(kHidden), + std::span(ext_synth_defaults::kInputChannels), + ext_synth_defaults::kInputSize, + std::span(ext_synth_defaults::kHiddenLayers), NOut, - 0.6f, - 1.0f, - 1000u, + 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 9ee607e..60bcfd0 100644 --- a/nisps/modes/generated/breakor_schema.hpp +++ b/nisps/modes/generated/breakor_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_BREAKOR_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -547,6 +548,23 @@ inline constexpr UIConfig kBreakorUI = { false, }; +using BreakorMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kBreakorSchema = { + kBreakorModeId, + kBreakorEngineId, + std::span(kBreakorInputChannels), + kBreakorMLConfig.input_size, + std::span(kBreakorHiddenLayers), + kBreakorMLConfig.output_size, + kBreakorMLConfig.default_spread, + kBreakorMLConfig.default_learning_rate, + kBreakorMLConfig.default_max_iterations, + std::span(kBreakorParams), + std::span(kBreakorVoiceSpaces), + kBreakorUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_BREAKOR_SCHEMA_HPP diff --git a/nisps/modes/generated/channel_strip_schema.hpp b/nisps/modes/generated/channel_strip_schema.hpp index 0e83d35..82cc6c8 100644 --- a/nisps/modes/generated/channel_strip_schema.hpp +++ b/nisps/modes/generated/channel_strip_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_CHANNEL_STRIP_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -266,6 +267,23 @@ inline constexpr UIConfig kChannelStripUI = { false, }; +using ChannelStripMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kChannelStripSchema = { + kChannelStripModeId, + kChannelStripEngineId, + std::span(kChannelStripInputChannels), + kChannelStripMLConfig.input_size, + std::span(kChannelStripHiddenLayers), + kChannelStripMLConfig.output_size, + kChannelStripMLConfig.default_spread, + kChannelStripMLConfig.default_learning_rate, + kChannelStripMLConfig.default_max_iterations, + std::span(kChannelStripParams), + std::span(kChannelStripVoiceSpaces), + kChannelStripUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_CHANNEL_STRIP_SCHEMA_HPP diff --git a/nisps/modes/generated/elysiamorf_schema.hpp b/nisps/modes/generated/elysiamorf_schema.hpp index 299c315..202c490 100644 --- a/nisps/modes/generated/elysiamorf_schema.hpp +++ b/nisps/modes/generated/elysiamorf_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_ELYSIAMORF_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -403,6 +404,23 @@ inline constexpr UIConfig kElysiamorfUI = { false, }; +using ElysiamorfMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kElysiamorfSchema = { + kElysiamorfModeId, + kElysiamorfEngineId, + std::span(kElysiamorfInputChannels), + kElysiamorfMLConfig.input_size, + std::span(kElysiamorfHiddenLayers), + kElysiamorfMLConfig.output_size, + kElysiamorfMLConfig.default_spread, + kElysiamorfMLConfig.default_learning_rate, + kElysiamorfMLConfig.default_max_iterations, + std::span(kElysiamorfParams), + std::span(kElysiamorfVoiceSpaces), + kElysiamorfUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_ELYSIAMORF_SCHEMA_HPP diff --git a/nisps/modes/generated/memlcelium_schema.hpp b/nisps/modes/generated/memlcelium_schema.hpp index a297f43..d478c68 100644 --- a/nisps/modes/generated/memlcelium_schema.hpp +++ b/nisps/modes/generated/memlcelium_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_MEMLCELIUM_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -549,6 +550,23 @@ inline constexpr UIConfig kMemlceliumUI = { true, }; +using MemlceliumMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kMemlceliumSchema = { + kMemlceliumModeId, + kMemlceliumEngineId, + std::span(kMemlceliumInputChannels), + kMemlceliumMLConfig.input_size, + std::span(kMemlceliumHiddenLayers), + kMemlceliumMLConfig.output_size, + kMemlceliumMLConfig.default_spread, + kMemlceliumMLConfig.default_learning_rate, + kMemlceliumMLConfig.default_max_iterations, + std::span(kMemlceliumParams), + std::span(kMemlceliumVoiceSpaces), + kMemlceliumUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_MEMLCELIUM_SCHEMA_HPP diff --git a/nisps/modes/generated/paf_synth_schema.hpp b/nisps/modes/generated/paf_synth_schema.hpp index fc5adfa..197c671 100644 --- a/nisps/modes/generated/paf_synth_schema.hpp +++ b/nisps/modes/generated/paf_synth_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_PAF_SYNTH_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -348,6 +349,23 @@ inline constexpr UIConfig kPafSynthUI = { true, }; +using PafSynthMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kPafSynthSchema = { + kPafSynthModeId, + kPafSynthEngineId, + std::span(kPafSynthInputChannels), + kPafSynthMLConfig.input_size, + std::span(kPafSynthHiddenLayers), + kPafSynthMLConfig.output_size, + kPafSynthMLConfig.default_spread, + kPafSynthMLConfig.default_learning_rate, + kPafSynthMLConfig.default_max_iterations, + std::span(kPafSynthParams), + std::span(kPafSynthVoiceSpaces), + kPafSynthUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_PAF_SYNTH_SCHEMA_HPP diff --git a/nisps/modes/generated/schema_types.hpp b/nisps/modes/generated/schema_types.hpp index c40abef..a4f5371 100644 --- a/nisps/modes/generated/schema_types.hpp +++ b/nisps/modes/generated/schema_types.hpp @@ -3,11 +3,18 @@ // // `Curve` is the authoritative enum from nisps/core/math.hpp; we re-export it // into this namespace so generated headers can refer to plain `Curve::linear`. +// +// `ParamSchema` lives in the top-level `nisps` namespace, not +// `nisps::modes::generated`: nisps/core/concepts.hpp forward-declares +// `nisps::ParamSchema` and requires `T::param_schema()` to return +// `const nisps::ParamSchema&`, so the definition has to match that +// forward declaration exactly (S5, one-core simplification 2026-07). #ifndef NISPS_GENERATED_SCHEMA_TYPES_HPP #define NISPS_GENERATED_SCHEMA_TYPES_HPP #include #include +#include #include #include "../../core/math.hpp" @@ -51,4 +58,28 @@ struct UIConfig { } // namespace nisps::modes::generated +namespace nisps { + +// View-style aggregate satisfying the `nisps::Mode` concept's +// `param_schema()` requirement. All members are spans/views into +// compile-time generated arrays; codegen emits one +// `inline constexpr ParamSchema kSchema` per mode (see the +// per-mode _schema.hpp in this directory). +struct ParamSchema { + std::string_view mode_id; + std::string_view engine_id; + std::span input_channels; + std::size_t input_size; + 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; +}; + +} // namespace nisps + #endif // NISPS_GENERATED_SCHEMA_TYPES_HPP diff --git a/nisps/modes/generated/slp_workshop_schema.hpp b/nisps/modes/generated/slp_workshop_schema.hpp index fddaa17..f5c938d 100644 --- a/nisps/modes/generated/slp_workshop_schema.hpp +++ b/nisps/modes/generated/slp_workshop_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_SLP_WORKSHOP_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -549,6 +550,23 @@ inline constexpr UIConfig kSlpWorkshopUI = { true, }; +using SlpWorkshopMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kSlpWorkshopSchema = { + kSlpWorkshopModeId, + kSlpWorkshopEngineId, + std::span(kSlpWorkshopInputChannels), + kSlpWorkshopMLConfig.input_size, + std::span(kSlpWorkshopHiddenLayers), + kSlpWorkshopMLConfig.output_size, + kSlpWorkshopMLConfig.default_spread, + kSlpWorkshopMLConfig.default_learning_rate, + kSlpWorkshopMLConfig.default_max_iterations, + std::span(kSlpWorkshopParams), + std::span(kSlpWorkshopVoiceSpaces), + kSlpWorkshopUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_SLP_WORKSHOP_SCHEMA_HPP diff --git a/nisps/modes/generated/sound_analysis_midi_schema.hpp b/nisps/modes/generated/sound_analysis_midi_schema.hpp index cde3b3e..2b9023d 100644 --- a/nisps/modes/generated/sound_analysis_midi_schema.hpp +++ b/nisps/modes/generated/sound_analysis_midi_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_SOUND_ANALYSIS_MIDI_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -121,6 +122,23 @@ inline constexpr UIConfig kSoundAnalysisMidiUI = { false, }; +using SoundAnalysisMidiMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kSoundAnalysisMidiSchema = { + kSoundAnalysisMidiModeId, + kSoundAnalysisMidiEngineId, + std::span(kSoundAnalysisMidiInputChannels), + kSoundAnalysisMidiMLConfig.input_size, + std::span(kSoundAnalysisMidiHiddenLayers), + kSoundAnalysisMidiMLConfig.output_size, + kSoundAnalysisMidiMLConfig.default_spread, + kSoundAnalysisMidiMLConfig.default_learning_rate, + kSoundAnalysisMidiMLConfig.default_max_iterations, + std::span(kSoundAnalysisMidiParams), + std::span(kSoundAnalysisMidiVoiceSpaces), + kSoundAnalysisMidiUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_SOUND_ANALYSIS_MIDI_SCHEMA_HPP diff --git a/nisps/modes/generated/verb_fx_schema.hpp b/nisps/modes/generated/verb_fx_schema.hpp index d18b127..7ce2076 100644 --- a/nisps/modes/generated/verb_fx_schema.hpp +++ b/nisps/modes/generated/verb_fx_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_VERB_FX_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -479,6 +480,23 @@ inline constexpr UIConfig kVerbFxUI = { false, }; +using VerbFxMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kVerbFxSchema = { + kVerbFxModeId, + kVerbFxEngineId, + std::span(kVerbFxInputChannels), + kVerbFxMLConfig.input_size, + std::span(kVerbFxHiddenLayers), + kVerbFxMLConfig.output_size, + kVerbFxMLConfig.default_spread, + kVerbFxMLConfig.default_learning_rate, + kVerbFxMLConfig.default_max_iterations, + std::span(kVerbFxParams), + std::span(kVerbFxVoiceSpaces), + kVerbFxUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_VERB_FX_SCHEMA_HPP diff --git a/nisps/modes/generated/xiasri_schema.hpp b/nisps/modes/generated/xiasri_schema.hpp index 7e6f501..b0942c0 100644 --- a/nisps/modes/generated/xiasri_schema.hpp +++ b/nisps/modes/generated/xiasri_schema.hpp @@ -3,6 +3,7 @@ #define NISPS_GENERATED_XIASRI_SCHEMA_HPP #include "schema_types.hpp" +#include "../../ml/mlp.hpp" namespace nisps::modes::generated { @@ -261,6 +262,23 @@ inline constexpr UIConfig kXiasriUI = { false, }; +using XiasriMLP = ::nisps::ml::MLP; + +inline constexpr ::nisps::ParamSchema kXiasriSchema = { + kXiasriModeId, + kXiasriEngineId, + std::span(kXiasriInputChannels), + kXiasriMLConfig.input_size, + std::span(kXiasriHiddenLayers), + kXiasriMLConfig.output_size, + kXiasriMLConfig.default_spread, + kXiasriMLConfig.default_learning_rate, + kXiasriMLConfig.default_max_iterations, + std::span(kXiasriParams), + std::span(kXiasriVoiceSpaces), + kXiasriUI, +}; + } // namespace nisps::modes::generated #endif // NISPS_GENERATED_XIASRI_SCHEMA_HPP diff --git a/nisps/modes/memlcelium.hpp b/nisps/modes/memlcelium.hpp index 94329ed..b82030d 100644 --- a/nisps/modes/memlcelium.hpp +++ b/nisps/modes/memlcelium.hpp @@ -16,7 +16,6 @@ #include "../core/perf.hpp" #include "../core/types.hpp" #include "../engines/memlcelium.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/memlcelium_schema.hpp" @@ -25,17 +24,19 @@ namespace nisps::modes { class MEMLCeliumMode : public ModeBase< MEMLCeliumMode, MEMLCeliumEngine, - ml::MLP<4u, 10u, 14u, 18u, 56u>, + generated::MemlceliumMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::MemlceliumMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kMemlceliumModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kMemlceliumSchema; + } NISPS_FORCE_INLINE void set_playing(bool playing) noexcept { engine_.set_playing(playing); @@ -43,22 +44,6 @@ class MEMLCeliumMode : public ModeBase< NISPS_FORCE_INLINE void update_bpm(float bpm) noexcept { engine_.update_bpm(bpm); } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kMemlceliumModeId, - generated::kMemlceliumEngineId, - std::span(generated::kMemlceliumInputChannels), - generated::kMemlceliumMLConfig.input_size, - std::span(generated::kMemlceliumHiddenLayers), - generated::kMemlceliumMLConfig.output_size, - generated::kMemlceliumMLConfig.default_spread, - generated::kMemlceliumMLConfig.default_learning_rate, - generated::kMemlceliumMLConfig.default_max_iterations, - std::span(generated::kMemlceliumParams), - std::span(generated::kMemlceliumVoiceSpaces), - generated::kMemlceliumUI, - }; }; static_assert(Mode, "MEMLCeliumMode must satisfy nisps::Mode"); diff --git a/nisps/modes/paf_synth.hpp b/nisps/modes/paf_synth.hpp index f479e0b..431eea4 100644 --- a/nisps/modes/paf_synth.hpp +++ b/nisps/modes/paf_synth.hpp @@ -19,7 +19,6 @@ #include "../core/perf.hpp" #include "../core/types.hpp" #include "../engines/paf_synth.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/paf_synth_schema.hpp" @@ -28,18 +27,20 @@ namespace nisps::modes { class PAFSynthMode : public ModeBase< PAFSynthMode, PAFSynthEngine, - ml::MLP<4u, 10u, 10u, 14u, 33u>, + generated::PafSynthMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::PafSynthMLP, 4u>; using Base::Base; // ---- Concept-required statics ---- static constexpr std::string_view mode_id() noexcept { return generated::kPafSynthModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kPafSynthSchema; + } // ---- Mode-specific control glue ---- NISPS_FORCE_INLINE void note_on(std::uint8_t note, std::uint8_t velocity) noexcept { @@ -48,22 +49,6 @@ class PAFSynthMode : public ModeBase< NISPS_FORCE_INLINE void note_off(std::uint8_t note) noexcept { engine_.note_off(note); } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kPafSynthModeId, - generated::kPafSynthEngineId, - std::span(generated::kPafSynthInputChannels), - generated::kPafSynthMLConfig.input_size, - std::span(generated::kPafSynthHiddenLayers), - generated::kPafSynthMLConfig.output_size, - generated::kPafSynthMLConfig.default_spread, - generated::kPafSynthMLConfig.default_learning_rate, - generated::kPafSynthMLConfig.default_max_iterations, - std::span(generated::kPafSynthParams), - std::span(generated::kPafSynthVoiceSpaces), - generated::kPafSynthUI, - }; }; static_assert(Mode, "PAFSynthMode must satisfy nisps::Mode"); diff --git a/nisps/modes/slp_workshop.hpp b/nisps/modes/slp_workshop.hpp index d75e9fc..b14d84c 100644 --- a/nisps/modes/slp_workshop.hpp +++ b/nisps/modes/slp_workshop.hpp @@ -28,7 +28,6 @@ #include "../core/perf.hpp" #include "../core/types.hpp" #include "../engines/memlcelium.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/slp_workshop_schema.hpp" @@ -37,17 +36,19 @@ namespace nisps::modes { class SLPWorkshopMode : public ModeBase< SLPWorkshopMode, MEMLCeliumEngine, - ml::MLP<4u, 10u, 14u, 18u, 56u>, + generated::SlpWorkshopMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::SlpWorkshopMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kSlpWorkshopModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kSlpWorkshopSchema; + } NISPS_FORCE_INLINE void set_playing(bool playing) noexcept { engine_.set_playing(playing); @@ -55,22 +56,6 @@ class SLPWorkshopMode : public ModeBase< NISPS_FORCE_INLINE void update_bpm(float bpm) noexcept { engine_.update_bpm(bpm); } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kSlpWorkshopModeId, - generated::kSlpWorkshopEngineId, - std::span(generated::kSlpWorkshopInputChannels), - generated::kSlpWorkshopMLConfig.input_size, - std::span(generated::kSlpWorkshopHiddenLayers), - generated::kSlpWorkshopMLConfig.output_size, - generated::kSlpWorkshopMLConfig.default_spread, - generated::kSlpWorkshopMLConfig.default_learning_rate, - generated::kSlpWorkshopMLConfig.default_max_iterations, - std::span(generated::kSlpWorkshopParams), - std::span(generated::kSlpWorkshopVoiceSpaces), - generated::kSlpWorkshopUI, - }; }; static_assert(Mode, "SLPWorkshopMode must satisfy nisps::Mode"); diff --git a/nisps/modes/sound_analysis_midi.hpp b/nisps/modes/sound_analysis_midi.hpp index 56485c7..37dd6ce 100644 --- a/nisps/modes/sound_analysis_midi.hpp +++ b/nisps/modes/sound_analysis_midi.hpp @@ -27,7 +27,6 @@ #include "../core/types.hpp" #include "../engines/analysis.hpp" #include "../engines/base.hpp" // NoOpEngine -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/sound_analysis_midi_schema.hpp" @@ -48,17 +47,19 @@ namespace nisps::modes { class SoundAnalysisMIDIMode : public ModeBase< SoundAnalysisMIDIMode, NoOpEngine, - ml::MLP<10u, 10u, 10u, 14u, 8u>, + generated::SoundAnalysisMidiMLP, 10u> { public: using Base = ModeBase, 10u>; + generated::SoundAnalysisMidiMLP, 10u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kSoundAnalysisMidiModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kSoundAnalysisMidiSchema; + } void on_setup(float sample_rate) noexcept { analysis_.setup(sample_rate); @@ -107,21 +108,6 @@ class SoundAnalysisMIDIMode : public ModeBase< static constexpr std::size_t kCCCount = 8u; AnalysisEngine analysis_{}; - - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kSoundAnalysisMidiModeId, - generated::kSoundAnalysisMidiEngineId, - std::span(generated::kSoundAnalysisMidiInputChannels), - generated::kSoundAnalysisMidiMLConfig.input_size, - std::span(generated::kSoundAnalysisMidiHiddenLayers), - generated::kSoundAnalysisMidiMLConfig.output_size, - generated::kSoundAnalysisMidiMLConfig.default_spread, - generated::kSoundAnalysisMidiMLConfig.default_learning_rate, - generated::kSoundAnalysisMidiMLConfig.default_max_iterations, - std::span(generated::kSoundAnalysisMidiParams), - std::span(generated::kSoundAnalysisMidiVoiceSpaces), - generated::kSoundAnalysisMidiUI, - }; }; static_assert(Mode, diff --git a/nisps/modes/verb_fx.hpp b/nisps/modes/verb_fx.hpp index fbc981d..5e5e747 100644 --- a/nisps/modes/verb_fx.hpp +++ b/nisps/modes/verb_fx.hpp @@ -12,7 +12,6 @@ #include "../core/concepts.hpp" #include "../core/types.hpp" #include "../engines/verb_fx.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/verb_fx_schema.hpp" @@ -21,33 +20,19 @@ namespace nisps::modes { class VerbFXMode : public ModeBase< VerbFXMode, VerbFXEngine, - ml::MLP<4u, 10u, 14u, 18u, 47u>, + generated::VerbFxMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::VerbFxMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kVerbFxModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kVerbFxModeId, - generated::kVerbFxEngineId, - std::span(generated::kVerbFxInputChannels), - generated::kVerbFxMLConfig.input_size, - std::span(generated::kVerbFxHiddenLayers), - generated::kVerbFxMLConfig.output_size, - generated::kVerbFxMLConfig.default_spread, - generated::kVerbFxMLConfig.default_learning_rate, - generated::kVerbFxMLConfig.default_max_iterations, - std::span(generated::kVerbFxParams), - std::span(generated::kVerbFxVoiceSpaces), - generated::kVerbFxUI, - }; + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kVerbFxSchema; + } }; static_assert(Mode, "VerbFXMode must satisfy nisps::Mode"); diff --git a/nisps/modes/xiasri.hpp b/nisps/modes/xiasri.hpp index fa3d85c..521a41a 100644 --- a/nisps/modes/xiasri.hpp +++ b/nisps/modes/xiasri.hpp @@ -13,7 +13,6 @@ #include "../core/concepts.hpp" #include "../core/types.hpp" #include "../engines/xiasri.hpp" -#include "../ml/mlp.hpp" #include "base.hpp" #include "generated/xiasri_schema.hpp" @@ -22,33 +21,19 @@ namespace nisps::modes { class XIASRIMode : public ModeBase< XIASRIMode, XIASRIEngine, - ml::MLP<4u, 10u, 10u, 14u, 24u>, + generated::XiasriMLP, 4u> { public: using Base = ModeBase, 4u>; + generated::XiasriMLP, 4u>; using Base::Base; static constexpr std::string_view mode_id() noexcept { return generated::kXiasriModeId; } - static constexpr const ParamSchema& param_schema() noexcept { return kSchema; } - - private: - static inline constexpr ParamSchema kSchema = ParamSchema{ - generated::kXiasriModeId, - generated::kXiasriEngineId, - std::span(generated::kXiasriInputChannels), - generated::kXiasriMLConfig.input_size, - std::span(generated::kXiasriHiddenLayers), - generated::kXiasriMLConfig.output_size, - generated::kXiasriMLConfig.default_spread, - generated::kXiasriMLConfig.default_learning_rate, - generated::kXiasriMLConfig.default_max_iterations, - std::span(generated::kXiasriParams), - std::span(generated::kXiasriVoiceSpaces), - generated::kXiasriUI, - }; + static constexpr const ParamSchema& param_schema() noexcept { + return generated::kXiasriSchema; + } }; static_assert(Mode, "XIASRIMode must satisfy nisps::Mode");