#include "Southpole.hpp" #include "dsp/digital.hpp" namespace rack_plugin_Southpole { struct Fuse : Module { enum ParamIds { SWITCH1_PARAM, SWITCH2_PARAM, SWITCH3_PARAM, SWITCH4_PARAM, NUM_PARAMS }; enum InputIds { ARM1_INPUT, ARM2_INPUT, ARM3_INPUT, ARM4_INPUT, CLK_INPUT, RESET_INPUT, NUM_INPUTS }; enum OutputIds { OUT1_OUTPUT, OUT2_OUTPUT, OUT3_OUTPUT, OUT4_OUTPUT, NUM_OUTPUTS }; enum LightIds { ARM1_LIGHT, ARM2_LIGHT, ARM3_LIGHT, ARM4_LIGHT, NUM_LIGHTS }; bool gateMode; Fuse() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); lights.resize(NUM_LIGHTS); } void step() override; SchmittTrigger clockTrigger; SchmittTrigger resetTrigger; SchmittTrigger armTrigger[4]; PulseGenerator pulse[4]; bool armed[4]; bool gateOn[4]; const unsigned maxsteps = 16; unsigned curstep = 0; json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "gateMode", json_boolean( gateMode )); return rootJ; } void fromJson(json_t *rootJ) override { json_t *gateModeJ = json_object_get(rootJ, "gateMode"); if (gateModeJ) { gateMode = json_boolean_value(gateModeJ); } } }; void Fuse::step() { bool nextStep = false; if (inputs[RESET_INPUT].active) { if (resetTrigger.process(inputs[RESET_INPUT].value)) { curstep = maxsteps; for (unsigned int i=0; i<4; i++) { armTrigger[i].reset(); armed[i] = false; gateOn[i] = false; } } } if (inputs[CLK_INPUT].active) { if (clockTrigger.process(inputs[CLK_INPUT].value)) { nextStep = true; } } if ( nextStep ) { curstep++; if ( curstep >= maxsteps ) curstep = 0; if ( curstep % 4 == 0 ) { unsigned int i = curstep/4; gateOn[(i-1)%4] = false; if ( armed[i] ) { pulse[i].trigger(1e-3); if ( gateMode ) gateOn[i] = true; armed[i] = false; } } //printf("%d %d\n",curstep,gateOn[curstep/4]); } for (unsigned int i=0; i<4; i++) { if ( params[SWITCH1_PARAM + i].value > 0. ) armed[i] = true; if ( armTrigger[i].process(inputs[ARM1_INPUT + i].normalize(0.))) armed[i] = true; lights[ARM1_LIGHT + i].setBrightness( armed[i] ? 1.0 : 0.0 ); bool p = pulse[i].process(1.0 / engineGetSampleRate()); if (gateOn[i]) p = true; outputs[OUT1_OUTPUT + i].value = p ? 10.0 : 0.0; } }; struct FuseDisplay : TransparentWidget { Fuse *module; FuseDisplay() {} void draw(NVGcontext *vg) override { // Background NVGcolor backgroundColor = nvgRGB(0x30, 0x00, 0x10); NVGcolor borderColor = nvgRGB(0xd0, 0xd0, 0xd0); nvgBeginPath(vg); nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 5.0); nvgFillColor(vg, backgroundColor); nvgFill(vg); nvgStrokeWidth(vg, 1.5); nvgStrokeColor(vg, borderColor); nvgStroke(vg); // Lights nvgStrokeColor(vg, nvgRGBA(0x7f, 0x00, 0x00, 0xff)); nvgFillColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); for ( unsigned y_ = 0; y_ < 16; y_++ ) { unsigned y = 15 - y_; nvgBeginPath(vg); nvgStrokeWidth(vg, 1.); nvgRect(vg, 3., y*box.size.y/18.+7.*floor(y/4.)+9., box.size.x-6., box.size.y/18.-6.); if (y_ <= module->curstep) nvgFill(vg); nvgStroke(vg); } } }; struct FuseWidget : ModuleWidget { Menu *createContextMenu() override; FuseWidget(Fuse *module) : ModuleWidget(module) { box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/Fuse.svg"))); addChild(panel); } { FuseDisplay *display = new FuseDisplay(); display->module = module; display->box.pos = Vec( 32, 25.); display->box.size = Vec( 24., box.size.y-85. ); addChild(display); } float y1 = 76; float yh = 73; float x1 = 5; float x2 = 35; for(int i = 0; i < 4; i++) { addParam(ParamWidget::create(Vec(x1+1, y1 + i*yh-22), module, Fuse::SWITCH1_PARAM + 3 - i, 0.0, 1.0, 0.0)); addChild(ModuleLightWidget::create>(Vec(x1+5, y1+ i*yh-18), module, Fuse::ARM1_LIGHT + 3 - i)); addInput(Port::create(Vec(x1, y1 + i*yh-45), Port::INPUT, module, Fuse::ARM1_INPUT + 3 - i)); addOutput(Port::create(Vec(x1, y1 + i*yh), Port::OUTPUT, module, Fuse::OUT1_OUTPUT + 3 - i)); } addInput(Port::create(Vec(x1, 330), Port::INPUT, module, Fuse::CLK_INPUT)); addInput(Port::create(Vec(x2, 330), Port::INPUT, module, Fuse::RESET_INPUT)); } }; struct FuseGateModeItem : MenuItem { Fuse *fuse; bool gateMode; void onAction(EventAction &e) override { fuse->gateMode = gateMode; } void step() override { rightText = (fuse->gateMode == gateMode) ? "✔" : ""; } }; Menu *FuseWidget::createContextMenu() { Menu *menu = ModuleWidget::createContextMenu(); MenuLabel *spacerLabel = new MenuLabel(); menu->addChild(spacerLabel); Fuse *fuse = dynamic_cast(module); assert(fuse); MenuLabel *modeLabel = new MenuLabel(); modeLabel->text = "Gate Mode"; menu->addChild(modeLabel); FuseGateModeItem *triggerItem = new FuseGateModeItem(); triggerItem->text = "Trigger"; triggerItem->fuse = fuse; triggerItem->gateMode = false; menu->addChild(triggerItem); FuseGateModeItem *gateItem = new FuseGateModeItem(); gateItem->text = "Gate"; gateItem->fuse = fuse; gateItem->gateMode = true; menu->addChild(gateItem); return menu; } } // namespace rack_plugin_Southpole using namespace rack_plugin_Southpole; RACK_PLUGIN_MODEL_INIT(Southpole, Fuse) { Model *modelFuse = Model::create( "Southpole", "Fuse", "Fuse - next pattern", SEQUENCER_TAG); return modelFuse; }