| @@ -8,11 +8,11 @@ struct SEQ3 : Module { | |||
| RUN_PARAM, | |||
| RESET_PARAM, | |||
| STEPS_PARAM, | |||
| ROW1_PARAM, | |||
| ROW2_PARAM = ROW1_PARAM + 8, | |||
| ROW3_PARAM = ROW2_PARAM + 8, | |||
| GATE_PARAM = ROW3_PARAM + 8, | |||
| NUM_PARAMS = GATE_PARAM + 8 | |||
| ENUMS(ROW1_PARAM, 8), | |||
| ENUMS(ROW2_PARAM, 8), | |||
| ENUMS(ROW3_PARAM, 8), | |||
| ENUMS(GATE_PARAM, 8), | |||
| NUM_PARAMS | |||
| }; | |||
| enum InputIds { | |||
| CLOCK_INPUT, | |||
| @@ -26,42 +26,43 @@ struct SEQ3 : Module { | |||
| ROW1_OUTPUT, | |||
| ROW2_OUTPUT, | |||
| ROW3_OUTPUT, | |||
| GATE_OUTPUT, | |||
| NUM_OUTPUTS = GATE_OUTPUT + 8 | |||
| ENUMS(GATE_OUTPUT, 8), | |||
| NUM_OUTPUTS | |||
| }; | |||
| enum LightIds { | |||
| RUNNING_LIGHT, | |||
| RESET_LIGHT, | |||
| GATES_LIGHT, | |||
| ROW_LIGHTS, | |||
| GATE_LIGHTS = ROW_LIGHTS + 3, | |||
| NUM_LIGHTS = GATE_LIGHTS + 8 | |||
| ENUMS(ROW_LIGHTS, 3), | |||
| ENUMS(GATE_LIGHTS, 8), | |||
| NUM_LIGHTS | |||
| }; | |||
| bool running = true; | |||
| SchmittTrigger clockTrigger; // for external clock | |||
| // For buttons | |||
| SchmittTrigger clockTrigger; | |||
| SchmittTrigger runningTrigger; | |||
| SchmittTrigger resetTrigger; | |||
| SchmittTrigger gateTriggers[8]; | |||
| float phase = 0.0f; | |||
| /** Phase of internal LFO */ | |||
| float phase = 0.f; | |||
| int index = 0; | |||
| bool gateState[8] = {}; | |||
| float resetLight = 0.0f; | |||
| float stepLights[8] = {}; | |||
| enum GateMode { | |||
| TRIGGER, | |||
| RETRIGGER, | |||
| CONTINUOUS, | |||
| }; | |||
| GateMode gateMode = TRIGGER; | |||
| PulseGenerator gatePulse; | |||
| bool gates[8] = {}; | |||
| SEQ3() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
| onReset(); | |||
| } | |||
| void step() override; | |||
| void onReset() override { | |||
| for (int i = 0; i < 8; i++) { | |||
| gates[i] = true; | |||
| } | |||
| } | |||
| void onRandomize() override { | |||
| for (int i = 0; i < 8; i++) { | |||
| gates[i] = (randomUniform() > 0.5f); | |||
| } | |||
| } | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -72,15 +73,10 @@ struct SEQ3 : Module { | |||
| // gates | |||
| json_t *gatesJ = json_array(); | |||
| for (int i = 0; i < 8; i++) { | |||
| json_t *gateJ = json_integer((int) gateState[i]); | |||
| json_array_append_new(gatesJ, gateJ); | |||
| json_array_insert_new(gatesJ, i, json_integer((int) gates[i])); | |||
| } | |||
| json_object_set_new(rootJ, "gates", gatesJ); | |||
| // gateMode | |||
| json_t *gateModeJ = json_integer((int) gateMode); | |||
| json_object_set_new(rootJ, "gateMode", gateModeJ); | |||
| return rootJ; | |||
| } | |||
| @@ -96,199 +92,115 @@ struct SEQ3 : Module { | |||
| for (int i = 0; i < 8; i++) { | |||
| json_t *gateJ = json_array_get(gatesJ, i); | |||
| if (gateJ) | |||
| gateState[i] = !!json_integer_value(gateJ); | |||
| gates[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 < 8; i++) { | |||
| gateState[i] = true; | |||
| } | |||
| void setIndex(int index) { | |||
| int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1.0f, 8.0f); | |||
| phase = 0.f; | |||
| this->index = index; | |||
| if (this->index >= numSteps) | |||
| this->index = 0; | |||
| } | |||
| void onRandomize() override { | |||
| for (int i = 0; i < 8; i++) { | |||
| gateState[i] = (randomUniform() > 0.5f); | |||
| void step() override { | |||
| // Run | |||
| if (runningTrigger.process(params[RUN_PARAM].value)) { | |||
| running = !running; | |||
| } | |||
| } | |||
| }; | |||
| void SEQ3::step() { | |||
| const float lightLambda = 0.075f; | |||
| // Run | |||
| if (runningTrigger.process(params[RUN_PARAM].value)) { | |||
| running = !running; | |||
| } | |||
| lights[RUNNING_LIGHT].value = running ? 1.0f : 0.0f; | |||
| bool nextStep = false; | |||
| if (running) { | |||
| if (inputs[EXT_CLOCK_INPUT].active) { | |||
| // External clock | |||
| if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { | |||
| phase = 0.0f; | |||
| nextStep = true; | |||
| bool gateIn = false; | |||
| if (running) { | |||
| if (inputs[EXT_CLOCK_INPUT].active) { | |||
| // External clock | |||
| if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { | |||
| setIndex(index + 1); | |||
| } | |||
| gateIn = clockTrigger.isHigh(); | |||
| } | |||
| } | |||
| else { | |||
| // Internal clock | |||
| float clockTime = powf(2.0f, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); | |||
| phase += clockTime * engineGetSampleTime(); | |||
| if (phase >= 1.0f) { | |||
| phase -= 1.0f; | |||
| nextStep = true; | |||
| else { | |||
| // Internal clock | |||
| float clockTime = powf(2.0f, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); | |||
| phase += clockTime * engineGetSampleTime(); | |||
| if (phase >= 1.0f) { | |||
| setIndex(index + 1); | |||
| } | |||
| gateIn = (phase < 0.5f); | |||
| } | |||
| } | |||
| } | |||
| // Reset | |||
| if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { | |||
| phase = 0.0f; | |||
| index = 8; | |||
| nextStep = true; | |||
| resetLight = 1.0f; | |||
| } | |||
| if (nextStep) { | |||
| // Advance step | |||
| int numSteps = clamp(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1.0f, 8.0f); | |||
| index += 1; | |||
| if (index >= numSteps) { | |||
| index = 0; | |||
| // Reset | |||
| if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { | |||
| setIndex(0); | |||
| } | |||
| stepLights[index] = 1.0f; | |||
| gatePulse.trigger(1e-3); | |||
| } | |||
| resetLight -= resetLight / lightLambda * engineGetSampleTime(); | |||
| bool pulse = gatePulse.process(engineGetSampleTime()); | |||
| // Gate buttons | |||
| for (int i = 0; i < 8; i++) { | |||
| if (gateTriggers[i].process(params[GATE_PARAM + i].value)) { | |||
| gateState[i] = !gateState[i]; | |||
| // Gate buttons | |||
| for (int i = 0; i < 8; i++) { | |||
| if (gateTriggers[i].process(params[GATE_PARAM + i].value)) { | |||
| gates[i] = !gates[i]; | |||
| } | |||
| outputs[GATE_OUTPUT + i].value = (running && gateIn && i == index && gates[i]) ? 10.0f : 0.0f; | |||
| lights[GATE_LIGHTS + i].setBrightnessSmooth((gateIn && i == index) ? (gates[i] ? 1.f : 0.33) : (gates[i] ? 0.66 : 0.0)); | |||
| } | |||
| 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.0f : 0.0f; | |||
| stepLights[i] -= stepLights[i] / lightLambda * engineGetSampleTime(); | |||
| lights[GATE_LIGHTS + i].value = gateState[i] ? 1.0f - stepLights[i] : stepLights[i]; | |||
| // Outputs | |||
| outputs[ROW1_OUTPUT].value = params[ROW1_PARAM + index].value; | |||
| outputs[ROW2_OUTPUT].value = params[ROW2_PARAM + index].value; | |||
| outputs[ROW3_OUTPUT].value = params[ROW3_PARAM + index].value; | |||
| outputs[GATES_OUTPUT].value = (gateIn && gates[index]) ? 10.0f : 0.0f; | |||
| lights[RUNNING_LIGHT].value = (running); | |||
| lights[RESET_LIGHT].setBrightnessSmooth(resetTrigger.isHigh()); | |||
| lights[GATES_LIGHT].setBrightnessSmooth(gateIn); | |||
| lights[ROW_LIGHTS].value = outputs[ROW1_OUTPUT].value / 10.0f; | |||
| lights[ROW_LIGHTS + 1].value = outputs[ROW2_OUTPUT].value / 10.0f; | |||
| lights[ROW_LIGHTS + 2].value = outputs[ROW3_OUTPUT].value / 10.0f; | |||
| } | |||
| // Rows | |||
| float row1 = params[ROW1_PARAM + index].value; | |||
| float row2 = params[ROW2_PARAM + index].value; | |||
| float row3 = params[ROW3_PARAM + index].value; | |||
| bool gatesOn = (running && gateState[index]); | |||
| if (gateMode == TRIGGER) | |||
| gatesOn = gatesOn && pulse; | |||
| else if (gateMode == RETRIGGER) | |||
| gatesOn = gatesOn && !pulse; | |||
| // Outputs | |||
| outputs[ROW1_OUTPUT].value = row1; | |||
| outputs[ROW2_OUTPUT].value = row2; | |||
| outputs[ROW3_OUTPUT].value = row3; | |||
| outputs[GATES_OUTPUT].value = gatesOn ? 10.0f : 0.0f; | |||
| lights[RESET_LIGHT].value = resetLight; | |||
| lights[GATES_LIGHT].value = gatesOn ? 1.0f : 0.0f; | |||
| lights[ROW_LIGHTS].value = row1 / 10.0f; | |||
| lights[ROW_LIGHTS + 1].value = row2 / 10.0f; | |||
| lights[ROW_LIGHTS + 2].value = row3 / 10.0f; | |||
| } | |||
| struct SEQ3Widget : ModuleWidget { | |||
| SEQ3Widget(SEQ3 *module); | |||
| void appendContextMenu(Menu *menu) override; | |||
| }; | |||
| SEQ3Widget::SEQ3Widget(SEQ3 *module) : ModuleWidget(module) { | |||
| setPanel(SVG::load(assetPlugin(plugin, "res/SEQ3.svg"))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365))); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(18, 56), module, SEQ3::CLOCK_PARAM, -2.0f, 6.0f, 2.0f)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(60, 61-1), module, SEQ3::RUN_PARAM, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(64.4f, 64.4f), module, SEQ3::RUNNING_LIGHT)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(99, 61-1), module, SEQ3::RESET_PARAM, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(103.4f, 64.4f), module, SEQ3::RESET_LIGHT)); | |||
| addParam(ParamWidget::create<RoundSmallBlackSnapKnob>(Vec(132, 56), module, SEQ3::STEPS_PARAM, 1.0f, 8.0f, 8.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(179.4f, 64.4f), module, SEQ3::GATES_LIGHT)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(218.4f, 64.4f), module, SEQ3::ROW_LIGHTS)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(256.4f, 64.4f), module, SEQ3::ROW_LIGHTS + 1)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(295.4f, 64.4f), module, SEQ3::ROW_LIGHTS + 2)); | |||
| static const float portX[8] = {20, 58, 96, 135, 173, 212, 250, 289}; | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[0]-1, 98), Port::INPUT, module, SEQ3::CLOCK_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[1]-1, 98), Port::INPUT, module, SEQ3::EXT_CLOCK_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[2]-1, 98), Port::INPUT, module, SEQ3::RESET_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[3]-1, 98), Port::INPUT, module, SEQ3::STEPS_INPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[4]-1, 98), Port::OUTPUT, module, SEQ3::GATES_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[5]-1, 98), Port::OUTPUT, module, SEQ3::ROW1_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[6]-1, 98), Port::OUTPUT, module, SEQ3::ROW2_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[7]-1, 98), Port::OUTPUT, module, SEQ3::ROW3_OUTPUT)); | |||
| for (int i = 0; i < 8; i++) { | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 157), module, SEQ3::ROW1_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 198), module, SEQ3::ROW2_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 240), module, SEQ3::ROW3_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(portX[i]+2, 278-1), module, SEQ3::GATE_PARAM + i, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(portX[i]+6.4f, 281.4f), module, SEQ3::GATE_LIGHTS + i)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[i]-1, 307), Port::OUTPUT, module, SEQ3::GATE_OUTPUT + i)); | |||
| } | |||
| } | |||
| struct SEQ3Widget : ModuleWidget { | |||
| SEQ3Widget(SEQ3 *module) : ModuleWidget(module) { | |||
| setPanel(SVG::load(assetPlugin(plugin, "res/SEQ3.svg"))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365))); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(18, 56), module, SEQ3::CLOCK_PARAM, -2.0f, 6.0f, 2.0f)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(60, 61-1), module, SEQ3::RUN_PARAM, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(64.4f, 64.4f), module, SEQ3::RUNNING_LIGHT)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(99, 61-1), module, SEQ3::RESET_PARAM, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(103.4f, 64.4f), module, SEQ3::RESET_LIGHT)); | |||
| addParam(ParamWidget::create<RoundSmallBlackSnapKnob>(Vec(132, 56), module, SEQ3::STEPS_PARAM, 1.0f, 8.0f, 8.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(179.4f, 64.4f), module, SEQ3::GATES_LIGHT)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(218.4f, 64.4f), module, SEQ3::ROW_LIGHTS)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(256.4f, 64.4f), module, SEQ3::ROW_LIGHTS + 1)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(295.4f, 64.4f), module, SEQ3::ROW_LIGHTS + 2)); | |||
| static const float portX[8] = {20, 58, 96, 135, 173, 212, 250, 289}; | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[0]-1, 98), Port::INPUT, module, SEQ3::CLOCK_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[1]-1, 98), Port::INPUT, module, SEQ3::EXT_CLOCK_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[2]-1, 98), Port::INPUT, module, SEQ3::RESET_INPUT)); | |||
| addInput(Port::create<PJ301MPort>(Vec(portX[3]-1, 98), Port::INPUT, module, SEQ3::STEPS_INPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[4]-1, 98), Port::OUTPUT, module, SEQ3::GATES_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[5]-1, 98), Port::OUTPUT, module, SEQ3::ROW1_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[6]-1, 98), Port::OUTPUT, module, SEQ3::ROW2_OUTPUT)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[7]-1, 98), Port::OUTPUT, module, SEQ3::ROW3_OUTPUT)); | |||
| struct SEQ3GateModeItem : MenuItem { | |||
| SEQ3 *seq3; | |||
| SEQ3::GateMode gateMode; | |||
| void onAction(EventAction &e) override { | |||
| seq3->gateMode = gateMode; | |||
| } | |||
| void step() override { | |||
| rightText = CHECKMARK(seq3->gateMode == gateMode); | |||
| for (int i = 0; i < 8; i++) { | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 157), module, SEQ3::ROW1_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 198), module, SEQ3::ROW2_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(portX[i]-2, 240), module, SEQ3::ROW3_PARAM + i, 0.0f, 10.0f, 0.0f)); | |||
| addParam(ParamWidget::create<LEDButton>(Vec(portX[i]+2, 278-1), module, SEQ3::GATE_PARAM + i, 0.0f, 1.0f, 0.0f)); | |||
| addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(portX[i]+6.4f, 281.4f), module, SEQ3::GATE_LIGHTS + i)); | |||
| addOutput(Port::create<PJ301MPort>(Vec(portX[i]-1, 307), Port::OUTPUT, module, SEQ3::GATE_OUTPUT + i)); | |||
| } | |||
| } | |||
| }; | |||
| void SEQ3Widget::appendContextMenu(Menu *menu) { | |||
| menu->addChild(MenuEntry::create()); | |||
| SEQ3 *seq3 = dynamic_cast<SEQ3*>(module); | |||
| assert(seq3); | |||
| menu->addChild(MenuLabel::create("Gate Mode")); | |||
| SEQ3GateModeItem *triggerItem = MenuItem::create<SEQ3GateModeItem>("Trigger"); | |||
| triggerItem->seq3 = seq3; | |||
| triggerItem->gateMode = SEQ3::TRIGGER; | |||
| menu->addChild(triggerItem); | |||
| SEQ3GateModeItem *retriggerItem = MenuItem::create<SEQ3GateModeItem>("Retrigger"); | |||
| retriggerItem->seq3 = seq3; | |||
| retriggerItem->gateMode = SEQ3::RETRIGGER; | |||
| menu->addChild(retriggerItem); | |||
| SEQ3GateModeItem *continuousItem = MenuItem::create<SEQ3GateModeItem>("Continuous"); | |||
| continuousItem->seq3 = seq3; | |||
| continuousItem->gateMode = SEQ3::CONTINUOUS; | |||
| menu->addChild(continuousItem); | |||
| } | |||
| Model *modelSEQ3 = Model::create<SEQ3, SEQ3Widget>("Fundamental", "SEQ3", "SEQ-3", SEQUENCER_TAG); | |||