#include "plugin.hpp" using namespace simd; struct MuDi : Module { enum ParamId { PARAMS_LEN }; enum InputId { CLOCK_INPUT, RESET_INPUT, INPUTS_LEN }; enum OutputId { F_1_OUTPUT, F_2_OUTPUT, F_4_OUTPUT, F_8_OUTPUT, F_16_OUTPUT, OUTPUTS_LEN }; enum LightId { ENUMS(F_1_LIGHT, 3), ENUMS(F_2_LIGHT, 3), ENUMS(F_4_LIGHT, 3), ENUMS(F_8_LIGHT, 3), ENUMS(F_16_LIGHT, 3), LIGHTS_LEN }; dsp::TSchmittTrigger clockTrigger_1[4]; dsp::TSchmittTrigger clockTrigger_2[4]; dsp::TSchmittTrigger clockTrigger_4[4]; dsp::TSchmittTrigger clockTrigger_8[4]; float_4 clockState_1[4] = {}; float_4 clockState_2[4] = {}; float_4 clockState_4[4] = {}; float_4 clockState_8[4] = {}; float_4 clockState_16[4] = {}; dsp::TSchmittTrigger resetTrigger[4]; dsp::ClockDivider lightDivider; bool removeClockDC = false; MuDi() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); configInput(CLOCK_INPUT, "Clock"); configInput(RESET_INPUT, "Reset"); configOutput(F_1_OUTPUT, "F"); configOutput(F_2_OUTPUT, "1/2 F"); configOutput(F_4_OUTPUT, "1/4 F"); configOutput(F_8_OUTPUT, "1/8 F"); configOutput(F_16_OUTPUT, "1/16 F"); lightDivider.setDivision(32); } void process(const ProcessArgs& args) override { const int numPolyphonyEngines = inputs[CLOCK_INPUT].getChannels(); for (int c = 0; c < numPolyphonyEngines; c += 4) { // reset float_4 reset = resetTrigger[c / 4].process(inputs[RESET_INPUT].getPolyVoltageSimd(c)); clockState_2[c / 4] = ifelse(reset, 0.f, clockState_2[c / 4]); clockState_4[c / 4] = ifelse(reset, 0.f, clockState_4[c / 4]); clockState_8[c / 4] = ifelse(reset, 0.f, clockState_8[c / 4]); clockState_16[c / 4] = ifelse(reset, 0.f, clockState_16[c / 4]); // base derived clock float_4 triggered = clockTrigger_1[c / 4].process(inputs[CLOCK_INPUT].getVoltageSimd(c)); clockState_1[c / 4] = clockTrigger_1[c / 4].isHigh(); // 1/2 derived clock changes state on every rising edge of the base clock clockState_2[c / 4] = ifelse(triggered, ~clockState_2[c / 4], clockState_2[c / 4]); float_4 clockTriggered_2 = clockTrigger_2[c / 4].process(ifelse(clockState_2[c / 4], 10.f, 0.f)); // 1/4 derived clock changes state on every rising edge of the 1/2 derived clock clockState_4[c / 4] = ifelse(clockTriggered_2, ~clockState_4[c / 4], clockState_4[c / 4]); float_4 clockTriggered_4 = clockTrigger_4[c / 4].process(ifelse(clockState_4[c / 4], 10.f, 0.f)); // 1/8 derived clock changes state on every rising edge of the 1/4 derived clock clockState_8[c / 4] = ifelse(clockTriggered_4, ~clockState_8[c / 4], clockState_8[c / 4]); float_4 clockTriggered_8 = clockTrigger_8[c / 4].process(ifelse(clockState_8[c / 4], 10.f, 0.f)); // 1/16 derived clock changes state on every rising edge of the 1/8 derived clock clockState_16[c / 4] = ifelse(clockTriggered_8, ~clockState_16[c / 4], clockState_16[c / 4]); // Set outputs outputs[F_1_OUTPUT].setVoltageSimd(ifelse(clockState_1[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c); outputs[F_2_OUTPUT].setVoltageSimd(ifelse(clockState_2[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c); outputs[F_4_OUTPUT].setVoltageSimd(ifelse(clockState_4[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c); outputs[F_8_OUTPUT].setVoltageSimd(ifelse(clockState_8[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c); outputs[F_16_OUTPUT].setVoltageSimd(ifelse(clockState_16[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c); } outputs[F_1_OUTPUT].setChannels(numPolyphonyEngines); outputs[F_2_OUTPUT].setChannels(numPolyphonyEngines); outputs[F_4_OUTPUT].setChannels(numPolyphonyEngines); outputs[F_8_OUTPUT].setChannels(numPolyphonyEngines); outputs[F_16_OUTPUT].setChannels(numPolyphonyEngines); bool anyState[5] = {}; for (int c = 0; c < numPolyphonyEngines; c++) { anyState[0] |= ifelse(clockState_1[c / 4], 1.f, 0.f)[c % 4] > 0.f; anyState[1] |= ifelse(clockState_2[c / 4], 1.f, 0.f)[c % 4] > 0.f; anyState[2] |= ifelse(clockState_4[c / 4], 1.f, 0.f)[c % 4] > 0.f; anyState[3] |= ifelse(clockState_8[c / 4], 1.f, 0.f)[c % 4] > 0.f; anyState[4] |= ifelse(clockState_16[c / 4], 1.f, 0.f)[c % 4] > 0.f; } // Set lights if (lightDivider.process()) { float lightTime = args.sampleTime * lightDivider.getDivision(); for (int i = 0; i < 5; i++) { lights[F_1_LIGHT + 3 * i + 0].setBrightnessSmooth(anyState[i] && numPolyphonyEngines == 1, lightTime); lights[F_1_LIGHT + 3 * i + 1].setBrightness(0.f); lights[F_1_LIGHT + 3 * i + 2].setBrightnessSmooth(anyState[i] && numPolyphonyEngines > 1, lightTime); } } } void dataFromJson(json_t* rootJ) override { json_t* removeClockDCJ = json_object_get(rootJ, "removeClockDC"); if (removeClockDCJ) removeClockDC = json_boolean_value(removeClockDCJ); } json_t* dataToJson() override { json_t* rootJ = json_object(); json_object_set_new(rootJ, "removeClockDC", json_boolean(removeClockDC)); return rootJ; } }; struct MuDiWidget : ModuleWidget { MuDiWidget(MuDi* module) { setModule(module); setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MuDi.svg"))); addChild(createWidget(Vec(box.size.x - RACK_GRID_WIDTH, 0))); addChild(createWidget(Vec(box.size.x - RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addInput(createInputCentered(mm2px(Vec(5.0, 15.138)), module, MuDi::CLOCK_INPUT)); addInput(createInputCentered(mm2px(Vec(5.0, 30.245)), module, MuDi::RESET_INPUT)); addOutput(createOutputCentered(mm2px(Vec(5.0, 56.695)), module, MuDi::F_1_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(5.0, 70.45)), module, MuDi::F_2_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(5.0, 84.204)), module, MuDi::F_4_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(5.0, 97.959)), module, MuDi::F_8_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(5.0, 111.713)), module, MuDi::F_16_OUTPUT)); addChild(createLightCentered>(mm2px(Vec(1.95, 62.74)), module, MuDi::F_1_LIGHT)); addChild(createLightCentered>(mm2px(Vec(1.95, 76.325)), module, MuDi::F_2_LIGHT)); addChild(createLightCentered>(mm2px(Vec(1.95, 90.1)), module, MuDi::F_4_LIGHT)); addChild(createLightCentered>(mm2px(Vec(1.95, 103.874)), module, MuDi::F_8_LIGHT)); addChild(createLightCentered>(mm2px(Vec(1.95, 117.648)), module, MuDi::F_16_LIGHT)); } void appendContextMenu(Menu* menu) override { MuDi* module = dynamic_cast(this->module); assert(module); menu->addChild(new MenuSeparator()); menu->addChild(createSubmenuItem("Hardware compatibility", "", [ = ](Menu * menu) { menu->addChild(createBoolPtrMenuItem("Remove DC from clock outs", "", &module->removeClockDC)); })); } }; Model* modelMuDi = createModel("MuDi");