#include "cf.hpp" #include "dsp/digital.hpp" namespace rack_plugin_cf { struct trSEQ : Module { enum ParamIds { CLOCK_PARAM, RUN_PARAM, RESET_PARAM, NOTESIN_PARAM, CLEAR_PARAM, STEPS_PARAM, GATE_PARAM = STEPS_PARAM + 16, NUM_PARAMS = GATE_PARAM + 16 }; enum InputIds { CLOCK_INPUT, EXT_CLOCK_INPUT, RESET_INPUT, NOTESIN_INPUT, CLEAR_INPUT, STEPS_INPUT, GATE_INPUT, NUM_INPUTS = GATE_INPUT+16 }; enum OutputIds { GATES_OUTPUT, NUM_OUTPUTS }; enum LightIds { RUNNING_LIGHT, RESET_LIGHT, GATES_LIGHT, GATE_LIGHTS, NUM_LIGHTS = GATE_LIGHTS + 16 }; bool running = true; SchmittTrigger clockTrigger; // for external clock // For buttons SchmittTrigger runningTrigger; SchmittTrigger resetTrigger; SchmittTrigger gateTriggers[32]; float phase = 0.0; int index = 0; bool gateState[16] = {}; float resetLight = 0.0; float stepLights[16] = {}; enum GateMode { TRIGGER, RETRIGGER, CONTINUOUS, }; GateMode gateMode = TRIGGER; PulseGenerator gatePulse; trSEQ() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {onReset();} void step() override; json_t *toJson() override { json_t *rootJ = json_object(); // running json_object_set_new(rootJ, "running", json_boolean(running)); // gates json_t *gatesJ = json_array(); for (int i = 0; i < 16; i++) { json_t *gateJ = json_integer((int) gateState[i]); json_array_append_new(gatesJ, gateJ); } json_object_set_new(rootJ, "gates", gatesJ); // gateMode json_t *gateModeJ = json_integer((int) gateMode); json_object_set_new(rootJ, "gateMode", gateModeJ); return rootJ; } void fromJson(json_t *rootJ) override { // running json_t *runningJ = json_object_get(rootJ, "running"); if (runningJ) running = json_is_true(runningJ); // gates json_t *gatesJ = json_object_get(rootJ, "gates"); if (gatesJ) { for (int i = 0; i < 16; i++) { json_t *gateJ = json_array_get(gatesJ, i); if (gateJ) gateState[i] = !!json_integer_value(gateJ); } } // gateMode json_t *gateModeJ = json_object_get(rootJ, "gateMode"); if (gateModeJ) gateMode = (GateMode)json_integer_value(gateModeJ); } void onReset() override { for (int i = 0; i < 16; i++) { gateState[i] = false; } } void onRandomize() override { for (int i = 0; i < 16; i++) { gateState[i] = (randomUniform() > 0.5); } } }; void trSEQ::step() { const float lightLambda = 0.075; // Run if (runningTrigger.process(params[RUN_PARAM].value)) { running = !running; } lights[RUNNING_LIGHT].value = running ? 1.0 : 0.0; bool nextStep = false; if (running) { if (inputs[EXT_CLOCK_INPUT].active) { // External clock if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { phase = 0.0; nextStep = true; } } else { // Internal clock float clockTime = powf(2.0, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); phase += clockTime / engineGetSampleRate(); if (phase >= 1.0) { phase -= 1.0; nextStep = true; } } } // Reset if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { phase = 0.0; index = 16; nextStep = true; resetLight = 1.0; } if (nextStep) { // Advance step int numSteps = clamp(static_cast(round(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value)), 1, 16); index += 1; if (index >= numSteps) { index = 0; } if (!inputs[NOTESIN_INPUT].active) inputs[NOTESIN_INPUT].value = 0; if (!inputs[CLEAR_INPUT].active) inputs[CLEAR_INPUT].value = 0; // // #define or || if (params[NOTESIN_PARAM].value || inputs[NOTESIN_INPUT].value>0) gateState[index] = true; if (params[CLEAR_PARAM].value || inputs[CLEAR_INPUT].value>0) gateState[index] = false; // // #undef or stepLights[index] = 1.0; gatePulse.trigger(1e-3); } resetLight -= resetLight / lightLambda / engineGetSampleRate(); bool pulse = gatePulse.process(1.0 / engineGetSampleRate()); // Gate buttons for (int i = 0; i < 16; i++) { if (gateTriggers[i+16].process(inputs[GATE_INPUT + i].value)) { gateState[i] = !gateState[i]; } if (gateTriggers[i].process(params[GATE_PARAM + i].value)) { gateState[i] = !gateState[i]; } bool gateOn = (running && i == index && gateState[i]); if (gateMode == TRIGGER) gateOn = gateOn && pulse; else if (gateMode == RETRIGGER) gateOn = gateOn && !pulse; //outputs[GATE_OUTPUT + i].value = gateOn ? 10.0 : 0.0; stepLights[i] -= stepLights[i] / lightLambda / engineGetSampleRate(); lights[GATE_LIGHTS + i].value = gateState[i] ? 1.0 - stepLights[i] : stepLights[i]; } // Rows bool gatesOn = (running && gateState[index]); if (gateMode == TRIGGER) gatesOn = gatesOn && pulse; else if (gateMode == RETRIGGER) gatesOn = gatesOn && !pulse; // Outputs outputs[GATES_OUTPUT].value = gatesOn ? 10.0 : 0.0; lights[RESET_LIGHT].value = resetLight; lights[GATES_LIGHT].value = gatesOn ? 1.0 : 0.0; } struct PadButton : SVGSwitch, MomentarySwitch { PadButton() { addFrame(SVG::load(assetPlugin(plugin, "res/PadButton.svg"))); addFrame(SVG::load(assetPlugin(plugin, "res/PadButtonDown.svg"))); sw->wrap(); box.size = sw->box.size; } }; struct trSEQWidget : ModuleWidget { trSEQWidget(trSEQ *module); Menu *createContextMenu() override; }; trSEQWidget::trSEQWidget(trSEQ *module) : ModuleWidget(module) { setPanel(SVG::load(assetPlugin(plugin, "res/trSEQ.svg"))); addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x-30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x-30, 365))); addParam(ParamWidget::create(Vec(18, 56), module, trSEQ::CLOCK_PARAM, -2.0f, 6.0f, 2.0f)); addParam(ParamWidget::create(Vec(60, 61-1), module, trSEQ::RUN_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(64.4, 64.4), module, trSEQ::RUNNING_LIGHT)); addParam(ParamWidget::create(Vec(99, 61-1), module, trSEQ::RESET_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(103.4, 64.4), module, trSEQ::RESET_LIGHT)); addParam(ParamWidget::create(Vec(132, 56), module, trSEQ::STEPS_PARAM, 1.0f, 16.0f, 16.0f)); addChild(ModuleLightWidget::create>(Vec(289.4, 64.4), module, trSEQ::GATES_LIGHT)); static const float portX[8] = {20, 58, 96, 135, 173, 212, 250, 289}; addParam(ParamWidget::create(Vec(portX[5]-26, 56), module, trSEQ::NOTESIN_PARAM, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(portX[6]-26, 56), module, trSEQ::CLEAR_PARAM, 0.0f, 1.0f, 0.0f)); addInput(Port::create(Vec(portX[5]-24, 98), Port::INPUT, module, trSEQ::NOTESIN_INPUT)); addInput(Port::create(Vec(portX[6]-24, 98), Port::INPUT, module, trSEQ::CLEAR_INPUT)); addInput(Port::create(Vec(portX[0]-1, 98), Port::INPUT, module, trSEQ::CLOCK_INPUT)); addInput(Port::create(Vec(portX[1]-1, 98), Port::INPUT, module, trSEQ::EXT_CLOCK_INPUT)); addInput(Port::create(Vec(portX[2]-1, 98), Port::INPUT, module, trSEQ::RESET_INPUT)); addInput(Port::create(Vec(portX[3]-1, 98), Port::INPUT, module, trSEQ::STEPS_INPUT)); addOutput(Port::create(Vec(portX[7]-6.5, 98), Port::OUTPUT, module, trSEQ::GATES_OUTPUT)); for (int i = 0; i < 16; i++) { addParam(ParamWidget::create(Vec(i*19+12, 203-1), module, trSEQ::GATE_PARAM + i, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(i*19+16.4, 206.4), module, trSEQ::GATE_LIGHTS + i)); addInput(Port::create(Vec(i*19+9, 247+ 40*(i%2)), Port::INPUT, module, trSEQ::GATE_INPUT + i)); } } struct trSEQGateModeItem : MenuItem { trSEQ *trseq; trSEQ::GateMode gateMode; void onAction(EventAction &e) override { trseq->gateMode = gateMode; } void step() override { rightText = (trseq->gateMode == gateMode) ? "✔" : ""; } }; Menu *trSEQWidget::createContextMenu() { Menu *menu = ModuleWidget::createContextMenu(); MenuLabel *spacerLabel = new MenuLabel(); menu->addChild(spacerLabel); trSEQ *trseq = dynamic_cast(module); assert(trseq); MenuLabel *modeLabel = new MenuLabel(); modeLabel->text = "Gate Mode"; menu->addChild(modeLabel); trSEQGateModeItem *triggerItem = new trSEQGateModeItem(); triggerItem->text = "Trigger"; triggerItem->trseq = trseq; triggerItem->gateMode = trSEQ::TRIGGER; menu->addChild(triggerItem); trSEQGateModeItem *retriggerItem = new trSEQGateModeItem(); retriggerItem->text = "Retrigger"; retriggerItem->trseq = trseq; retriggerItem->gateMode = trSEQ::RETRIGGER; menu->addChild(retriggerItem); trSEQGateModeItem *continuousItem = new trSEQGateModeItem(); continuousItem->text = "Continuous"; continuousItem->trseq = trseq; continuousItem->gateMode = trSEQ::CONTINUOUS; menu->addChild(continuousItem); return menu; } } // namespace rack_plugin_cf using namespace rack_plugin_cf; RACK_PLUGIN_MODEL_INIT(cf, trSEQ) { Model *modeltrSEQ = Model::create("cf", "trSEQ", "trSEQ", SEQUENCER_TAG); return modeltrSEQ; }