From e52b800a927cf0fe936aca1d73999872d094704f Mon Sep 17 00:00:00 2001 From: w1n5t0n Date: Sat, 28 Mar 2026 00:38:58 +0200 Subject: [PATCH] =?UTF-8?q?feat(vcv):=20complete=20Phase=202=20=E2=80=94?= =?UTF-8?q?=20core=20engine=20wired=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IML with [16,24,16] hidden layers as module member - CV inputs read, clamped 0-10V, normalized to [0,1] - MLP inference in process(), 12 outputs scaled to 0-10V - SPREAD knob (0-1, default 0.6) controls weight initialization - RAND button randomizes weights using current spread value - Panel: knob + button + 2 inputs + 12 outputs in 2x6 grid --- vcv/src/MEMLNaut.cpp | 57 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/vcv/src/MEMLNaut.cpp b/vcv/src/MEMLNaut.cpp index fe5c9a2..0c812c5 100644 --- a/vcv/src/MEMLNaut.cpp +++ b/vcv/src/MEMLNaut.cpp @@ -1,8 +1,13 @@ #include "plugin.hpp" #include +static constexpr int NUM_ML_INPUTS = 2; +static constexpr int NUM_ML_OUTPUTS = 12; + struct MEMLNaut : Module { enum ParamId { + PARAM_SPREAD, + PARAM_RAND, PARAMS_LEN }; enum InputId { @@ -20,21 +25,47 @@ struct MEMLNaut : Module { LIGHTS_LEN }; + nisps::IML iml{NUM_ML_INPUTS, NUM_ML_OUTPUTS, {16, 24, 16}}; + dsp::BooleanTrigger randTrigger; + MEMLNaut() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); + configParam(PARAM_SPREAD, 0.f, 1.f, 0.6f, "Spread", "%", 0.f, 100.f); + configButton(PARAM_RAND, "Randomize weights"); configInput(INPUT_X, "X"); configInput(INPUT_Y, "Y"); - for (int i = 0; i < 12; i++) { + for (int i = 0; i < NUM_ML_OUTPUTS; i++) { configOutput(OUTPUT_1 + i, string::f("Out %d", i + 1)); } - // Verify nisps-core headers integrate correctly - // (actual IML instance will be added in Phase 2) - static_assert(sizeof(nisps::IML) > 0, "nisps::IML must be a complete type"); + // Initial randomization with default spread + iml.set_mode(nisps::IML::Mode::Training); + iml.randomise_weights(0.6f); + iml.set_mode(nisps::IML::Mode::Inference); } void process(const ProcessArgs& args) override { - // Empty — Phase 2 will wire up IML inference + // Handle RAND button + if (randTrigger.process(params[PARAM_RAND].getValue() > 0.f)) { + float spread = params[PARAM_SPREAD].getValue(); + iml.set_mode(nisps::IML::Mode::Training); + iml.randomise_weights(spread); + iml.set_mode(nisps::IML::Mode::Inference); + } + + // Read inputs, normalize 0-10V → [0,1], clamp + float x = clamp(inputs[INPUT_X].getVoltage() / 10.f, 0.f, 1.f); + float y = clamp(inputs[INPUT_Y].getVoltage() / 10.f, 0.f, 1.f); + + iml.set_input(0, x); + iml.set_input(1, y); + iml.process(); + + // Write outputs: sigmoid [0,1] → 0-10V + const float* outs = iml.get_outputs(); + for (int i = 0; i < NUM_ML_OUTPUTS; i++) { + outputs[OUTPUT_1 + i].setVoltage(outs[i] * 10.f); + } } }; @@ -43,14 +74,18 @@ struct MEMLNautWidget : ModuleWidget { setModule(module); setPanel(createPanel(asset::plugin(pluginInstance, "res/MEMLNaut.svg"))); - // Inputs (left side) - addInput(createInputCentered(mm2px(Vec(8.0, 20.0)), module, MEMLNaut::INPUT_X)); - addInput(createInputCentered(mm2px(Vec(8.0, 32.0)), module, MEMLNaut::INPUT_Y)); + // Knobs + addParam(createParamCentered(mm2px(Vec(12.0, 20.0)), module, MEMLNaut::PARAM_SPREAD)); + addParam(createParamCentered(mm2px(Vec(28.0, 20.0)), module, MEMLNaut::PARAM_RAND)); - // Outputs (right side, 2 columns of 6) + // Inputs (left side) + addInput(createInputCentered(mm2px(Vec(8.0, 38.0)), module, MEMLNaut::INPUT_X)); + addInput(createInputCentered(mm2px(Vec(8.0, 50.0)), module, MEMLNaut::INPUT_Y)); + + // Outputs (2 columns of 6, below inputs) for (int i = 0; i < 6; i++) { - addOutput(createOutputCentered(mm2px(Vec(20.0, 20.0 + i * 12.0)), module, MEMLNaut::OUTPUT_1 + i)); - addOutput(createOutputCentered(mm2px(Vec(32.0, 20.0 + i * 12.0)), module, MEMLNaut::OUTPUT_1 + 6 + i)); + addOutput(createOutputCentered(mm2px(Vec(12.0, 62.0 + i * 10.0)), module, MEMLNaut::OUTPUT_1 + i)); + addOutput(createOutputCentered(mm2px(Vec(28.0, 62.0 + i * 10.0)), module, MEMLNaut::OUTPUT_1 + 6 + i)); } } };