diff --git a/res/Stages.svg b/res/Stages.svg index 4518a1a..2bcb9e9 100644 --- a/res/Stages.svg +++ b/res/Stages.svg @@ -10,9 +10,9 @@ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="71.120003mm" + width="426.72mm" height="128.5889mm" - viewBox="0 0 71.120003 128.5889" + viewBox="0 0 426.72 128.5889" version="1.1" id="svg1428" inkscape:version="0.92.2 2405546, 2018-03-11" @@ -1165,6 +1165,3444 @@ width="179.20599" y="23.478001" id="rect11797" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id="background-0" /> + id="g8152-3" + transform="matrix(0.33866667,0,0,0.33866667,436.95688,169.57609)"> + id="defs7957-8"> + id="rect5791" /> + id="clipPath10802-5"> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> + clip-path="url(#SVGID_2_-4)" /> *inputs, size_t first, size_t count) { @@ -102,7 +103,7 @@ struct GroupBuilder { int group = 0; int currentGroupSize = 0; - for (int i = 0; i < NUM_CHANNELS; i += 1) { + for (int i = 0; i < NUM_CHANNELS * MAX_MODULES; i += 1) { if (currentGroupSize <= 0) { currentGroupSize = max(1, groupSize[i]); group = i; @@ -121,41 +122,43 @@ struct GroupBuilder { struct Stages : Module { enum ParamIds { - ENUMS(SHAPE_PARAMS, NUM_CHANNELS), - ENUMS(TYPE_PARAMS, NUM_CHANNELS), - ENUMS(LEVEL_PARAMS, NUM_CHANNELS), + ENUMS(SHAPE_PARAMS, NUM_CHANNELS*MAX_MODULES), + ENUMS(TYPE_PARAMS, NUM_CHANNELS*MAX_MODULES), + ENUMS(LEVEL_PARAMS, NUM_CHANNELS*MAX_MODULES), NUM_PARAMS }; enum InputIds { - ENUMS(LEVEL_INPUTS, NUM_CHANNELS), - ENUMS(GATE_INPUTS, NUM_CHANNELS), + ENUMS(LEVEL_INPUTS, NUM_CHANNELS*MAX_MODULES), + ENUMS(GATE_INPUTS, NUM_CHANNELS*MAX_MODULES), NUM_INPUTS }; enum OutputIds { - ENUMS(ENVELOPE_OUTPUTS, NUM_CHANNELS), + ENUMS(ENVELOPE_OUTPUTS, NUM_CHANNELS*MAX_MODULES), NUM_OUTPUTS }; enum LightIds { - ENUMS(TYPE_LIGHTS, NUM_CHANNELS*2), - ENUMS(ENVELOPE_LIGHTS, NUM_CHANNELS), + ENUMS(TYPE_LIGHTS, NUM_CHANNELS*2*MAX_MODULES), + ENUMS(ENVELOPE_LIGHTS, NUM_CHANNELS*MAX_MODULES), NUM_LIGHTS }; - stages::segment::Configuration configurations[NUM_CHANNELS]; - bool configuration_changed[NUM_CHANNELS]; - stages::SegmentGenerator segment_generator[NUM_CHANNELS]; - SineOscillator oscillator[NUM_CHANNELS]; + int numModules = 1; + + stages::segment::Configuration configurations[NUM_CHANNELS*MAX_MODULES]; + bool configuration_changed[NUM_CHANNELS*MAX_MODULES]; + stages::SegmentGenerator segment_generator[NUM_CHANNELS*MAX_MODULES]; + SineOscillator oscillator[NUM_CHANNELS*MAX_MODULES]; bool abloop; // stages::ChainState chain_state; // stages::Settings settings; // Buttons - LongPressButton typeButtons[NUM_CHANNELS]; + LongPressButton typeButtons[NUM_CHANNELS*MAX_MODULES]; // Buffers - float envelopeBuffer[NUM_CHANNELS][BLOCK_SIZE] = {}; - stmlib::GateFlags last_gate_flags[NUM_CHANNELS] = {}; - stmlib::GateFlags gate_flags[NUM_CHANNELS][BLOCK_SIZE] = {}; + float envelopeBuffer[NUM_CHANNELS*MAX_MODULES][BLOCK_SIZE] = {}; + stmlib::GateFlags last_gate_flags[NUM_CHANNELS*MAX_MODULES] = {}; + stmlib::GateFlags gate_flags[NUM_CHANNELS*MAX_MODULES][BLOCK_SIZE] = {}; int blockIndex = 0; GroupBuilder groupBuilder; @@ -169,7 +172,7 @@ struct Stages : Module { abloop = false; - for (size_t i = 0; i < NUM_CHANNELS; ++i) { + for (size_t i = 0; i < NUM_CHANNELS*MAX_MODULES; ++i) { segment_generator[i].Init(); configurations[i].type = stages::segment::TYPE_RAMP; @@ -181,8 +184,10 @@ struct Stages : Module { json_t *toJson() override { json_t *rootJ = json_object(); + json_object_set_new(rootJ, "numModules", json_integer(numModules)); + json_t *configurationsJ = json_array(); - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS*MAX_MODULES; i++) { json_t *configurationJ = json_object(); json_object_set_new(configurationJ, "type", json_integer(configurations[i].type)); json_object_set_new(configurationJ, "loop", json_boolean(configurations[i].loop)); @@ -194,8 +199,12 @@ struct Stages : Module { } void fromJson(json_t *rootJ) override { + json_t *numModulesJ = json_object_get(rootJ, "numModules"); + if (numModulesJ) + numModules = json_integer_value(numModulesJ); + json_t *configurationsJ = json_object_get(rootJ, "configurations"); - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS*MAX_MODULES; i++) { json_t *configurationJ = json_array_get(configurationsJ, i); if (configurationJ) { json_t *typeJ = json_object_get(configurationJ, "type"); @@ -211,26 +220,26 @@ struct Stages : Module { void onSampleRateChange() override { stages::kSampleRate = engineGetSampleRate(); - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS*MAX_MODULES; i++) { segment_generator[i].InitRamps(); } } void stepBlock() { // Get parameters - float primaries[NUM_CHANNELS]; - float secondaries[NUM_CHANNELS]; - for (int i = 0; i < NUM_CHANNELS; i++) { + float primaries[NUM_CHANNELS*numModules]; + float secondaries[NUM_CHANNELS*numModules]; + for (int i = 0; i < NUM_CHANNELS*numModules; i++) { primaries[i] = clamp(params[LEVEL_PARAMS + i].value + inputs[LEVEL_INPUTS + i].value / 8.f, 0.f, 1.f); secondaries[i] = params[SHAPE_PARAMS + i].value; } // See if the group associations have changed since the last group - auto groups_changed = groupBuilder.buildGroups(&inputs, GATE_INPUTS, NUM_CHANNELS); + auto groups_changed = groupBuilder.buildGroups(&inputs, GATE_INPUTS, NUM_CHANNELS * numModules); // Process block stages::SegmentGenerator::Output out[BLOCK_SIZE] = {}; - for (int i = 0; i < NUM_CHANNELS;) { + for (int i = 0; i < NUM_CHANNELS*numModules;) { // Check if the config needs applying to the segment generator for this group bool segment_changed = groups_changed; @@ -307,7 +316,7 @@ struct Stages : Module { void step() override { // Buttons - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS * numModules; i++) { switch (typeButtons[i].step(params[TYPE_PARAMS + i])) { default: case LongPressButton::NO_PRESS: break; @@ -317,7 +326,7 @@ struct Stages : Module { } // Input - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS * numModules; i++) { bool gate = (inputs[GATE_INPUTS + i].value >= 1.7f); last_gate_flags[i] = stmlib::ExtractGateFlags(last_gate_flags[i], gate); gate_flags[i][blockIndex] = last_gate_flags[i]; @@ -332,7 +341,7 @@ struct Stages : Module { // Output int currentGroupSize = 0; int loopcount = 0; - for (int i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS * numModules; i++) { float envelope = envelopeBuffer[i][blockIndex]; outputs[ENVELOPE_OUTPUTS + i].value = envelope * 8.f; lights[ENVELOPE_LIGHTS + i].setBrightnessSmooth(envelope); @@ -363,65 +372,118 @@ struct Stages : Module { struct StagesWidget : ModuleWidget { + + Stages* module; + int lastNumModules = 1; + + Widget* topLeftScrew; + Widget* topRightScrew; + Widget* bottomLeftScrew; + Widget* bottomRightScrew; + StagesWidget(Stages *module) : ModuleWidget(module) { + this->module = module; setPanel(SVG::load(assetPlugin(plugin, "res/Stages.svg"))); - addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); - addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); - addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); - addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); - - addParam(ParamWidget::create(mm2px(Vec(3.72965, 13.98158)), module, Stages::SHAPE_PARAMS + 0, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(15.17012, 13.98158)), module, Stages::SHAPE_PARAMS + 1, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(26.6099, 13.98158)), module, Stages::SHAPE_PARAMS + 2, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(38.07174, 13.98158)), module, Stages::SHAPE_PARAMS + 3, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(49.51152, 13.98158)), module, Stages::SHAPE_PARAMS + 4, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(60.95199, 13.98158)), module, Stages::SHAPE_PARAMS + 5, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create(mm2px(Vec(4.17259, 32.37248)), module, Stages::TYPE_PARAMS + 0, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(15.61237, 32.37248)), module, Stages::TYPE_PARAMS + 1, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(27.05284, 32.37248)), module, Stages::TYPE_PARAMS + 2, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(38.51399, 32.37248)), module, Stages::TYPE_PARAMS + 3, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(49.95446, 32.37248)), module, Stages::TYPE_PARAMS + 4, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(61.39424, 32.37248)), module, Stages::TYPE_PARAMS + 5, 0.0, 1.0, 0.0)); - addParam(ParamWidget::create(mm2px(Vec(3.36193, 43.06508)), module, Stages::LEVEL_PARAMS + 0, 0.0, 1.0, 1.0)); - addParam(ParamWidget::create(mm2px(Vec(14.81619, 43.06508)), module, Stages::LEVEL_PARAMS + 1, 0.0, 1.0, 1.0)); - addParam(ParamWidget::create(mm2px(Vec(26.26975, 43.06508)), module, Stages::LEVEL_PARAMS + 2, 0.0, 1.0, 1.0)); - addParam(ParamWidget::create(mm2px(Vec(37.70265, 43.06508)), module, Stages::LEVEL_PARAMS + 3, 0.0, 1.0, 1.0)); - addParam(ParamWidget::create(mm2px(Vec(49.15759, 43.06508)), module, Stages::LEVEL_PARAMS + 4, 0.0, 1.0, 1.0)); - addParam(ParamWidget::create(mm2px(Vec(60.61184, 43.06508)), module, Stages::LEVEL_PARAMS + 5, 0.0, 1.0, 1.0)); - - addInput(Port::create(mm2px(Vec(2.70756, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 0)); - addInput(Port::create(mm2px(Vec(14.14734, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 1)); - addInput(Port::create(mm2px(Vec(25.58781, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 2)); - addInput(Port::create(mm2px(Vec(37.04896, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 3)); - addInput(Port::create(mm2px(Vec(48.48943, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 4)); - addInput(Port::create(mm2px(Vec(59.92921, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 5)); - addInput(Port::create(mm2px(Vec(2.70756, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 0)); - addInput(Port::create(mm2px(Vec(14.14734, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 1)); - addInput(Port::create(mm2px(Vec(25.58781, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 2)); - addInput(Port::create(mm2px(Vec(37.04896, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 3)); - addInput(Port::create(mm2px(Vec(48.48943, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 4)); - addInput(Port::create(mm2px(Vec(59.92921, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 5)); - - addOutput(Port::create(mm2px(Vec(2.70756, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 0)); - addOutput(Port::create(mm2px(Vec(14.14734, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 1)); - addOutput(Port::create(mm2px(Vec(25.58781, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 2)); - addOutput(Port::create(mm2px(Vec(37.04896, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 3)); - addOutput(Port::create(mm2px(Vec(48.48943, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 4)); - addOutput(Port::create(mm2px(Vec(59.92921, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 5)); - - addChild(ModuleLightWidget::create>(mm2px(Vec(5.27737, 26.74447)), module, Stages::TYPE_LIGHTS + 0*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(16.73784, 26.74447)), module, Stages::TYPE_LIGHTS + 1*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(28.1783, 26.74447)), module, Stages::TYPE_LIGHTS + 2*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(39.61877, 26.74447)), module, Stages::TYPE_LIGHTS + 3*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(51.07923, 26.74447)), module, Stages::TYPE_LIGHTS + 4*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(62.51971, 26.74447)), module, Stages::TYPE_LIGHTS + 5*2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(2.29462, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 0)); - addChild(ModuleLightWidget::create>(mm2px(Vec(13.73509, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 1)); - addChild(ModuleLightWidget::create>(mm2px(Vec(25.17556, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 2)); - addChild(ModuleLightWidget::create>(mm2px(Vec(36.63671, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 3)); - addChild(ModuleLightWidget::create>(mm2px(Vec(48.07649, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 4)); - addChild(ModuleLightWidget::create>(mm2px(Vec(59.51696, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 5)); + this->box.size.x = 210 * module->numModules; + this->panel->box.size = this->box.size; + + topLeftScrew = Widget::create(Vec(RACK_GRID_WIDTH, 0)); + topRightScrew = Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)); + bottomLeftScrew = Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)); + bottomRightScrew = Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)); + addChild(topLeftScrew); + addChild(topRightScrew); + addChild(bottomLeftScrew); + addChild(bottomRightScrew); + + for(auto i = 0; i < MAX_MODULES; i += 1) { + float offset = 71.12 * i; + addParam(ParamWidget::create(mm2px(Vec(offset + 3.72965, 13.98158)), module, Stages::SHAPE_PARAMS + 0 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 15.17012, 13.98158)), module, Stages::SHAPE_PARAMS + 1 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 26.6099, 13.98158)), module, Stages::SHAPE_PARAMS + 2 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 38.07174, 13.98158)), module, Stages::SHAPE_PARAMS + 3 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 49.51152, 13.98158)), module, Stages::SHAPE_PARAMS + 4 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 60.95199, 13.98158)), module, Stages::SHAPE_PARAMS + 5 + (NUM_CHANNELS*i), 0.0, 1.0, 0.5)); + addParam(ParamWidget::create(mm2px(Vec(offset + 4.17259, 32.37248)), module, Stages::TYPE_PARAMS + 0 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 15.61237, 32.37248)), module, Stages::TYPE_PARAMS + 1 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 27.05284, 32.37248)), module, Stages::TYPE_PARAMS + 2 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 38.51399, 32.37248)), module, Stages::TYPE_PARAMS + 3 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 49.95446, 32.37248)), module, Stages::TYPE_PARAMS + 4 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 61.39424, 32.37248)), module, Stages::TYPE_PARAMS + 5 + (NUM_CHANNELS*i), 0.0, 1.0, 0.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 3.36193, 43.06508)), module, Stages::LEVEL_PARAMS + 0 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 14.81619, 43.06508)), module, Stages::LEVEL_PARAMS + 1 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 26.26975, 43.06508)), module, Stages::LEVEL_PARAMS + 2 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 37.70265, 43.06508)), module, Stages::LEVEL_PARAMS + 3 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 49.15759, 43.06508)), module, Stages::LEVEL_PARAMS + 4 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + addParam(ParamWidget::create(mm2px(Vec(offset + 60.61184, 43.06508)), module, Stages::LEVEL_PARAMS + 5 + (NUM_CHANNELS*i), 0.0, 1.0, 1.0)); + + addInput(Port::create(mm2px(Vec(offset + 2.70756, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 0 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 14.14734, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 1 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 25.58781, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 2 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 37.04896, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 3 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 48.48943, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 4 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 59.92921, 77.75277)), Port::INPUT, module, Stages::LEVEL_INPUTS + 5 + (NUM_CHANNELS*i))); + + addInput(Port::create(mm2px(Vec(offset + 2.70756, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 0 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 14.14734, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 1 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 25.58781, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 2 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 37.04896, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 3 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 48.48943, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 4 + (NUM_CHANNELS*i))); + addInput(Port::create(mm2px(Vec(offset + 59.92921, 92.35239)), Port::INPUT, module, Stages::GATE_INPUTS + 5 + (NUM_CHANNELS*i))); + + addOutput(Port::create(mm2px(Vec(offset + 2.70756, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 0 + (NUM_CHANNELS*i))); + addOutput(Port::create(mm2px(Vec(offset + 14.14734, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 1 + (NUM_CHANNELS*i))); + addOutput(Port::create(mm2px(Vec(offset + 25.58781, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 2 + (NUM_CHANNELS*i))); + addOutput(Port::create(mm2px(Vec(offset + 37.04896, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 3 + (NUM_CHANNELS*i))); + addOutput(Port::create(mm2px(Vec(offset + 48.48943, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 4 + (NUM_CHANNELS*i))); + addOutput(Port::create(mm2px(Vec(offset + 59.92921, 106.95203)), Port::OUTPUT, module, Stages::ENVELOPE_OUTPUTS + 5 + (NUM_CHANNELS*i))); + + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 5.27737, 26.74447)), module, Stages::TYPE_LIGHTS + (0*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 16.73784, 26.74447)), module, Stages::TYPE_LIGHTS + (1*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 28.1783, 26.74447)), module, Stages::TYPE_LIGHTS + (2*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 39.61877, 26.74447)), module, Stages::TYPE_LIGHTS + (3*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 51.07923, 26.74447)), module, Stages::TYPE_LIGHTS + (4*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 62.51971, 26.74447)), module, Stages::TYPE_LIGHTS + (5*2) + (NUM_CHANNELS*2*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 2.29462, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 0 + (NUM_CHANNELS*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 13.73509, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 1 + (NUM_CHANNELS*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 25.17556, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 2 + (NUM_CHANNELS*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 36.63671, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 3 + (NUM_CHANNELS*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 48.07649, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 4 + (NUM_CHANNELS*i))); + addChild(ModuleLightWidget::create>(mm2px(Vec(offset + 59.51696, 103.19253)), module, Stages::ENVELOPE_LIGHTS + 5 + (NUM_CHANNELS*i))); + } + } + + void step() override { + if (module->numModules != lastNumModules) { + resize(module->numModules); + + // Remove wires which no longer have a port + for (int i = 0; i < inputs.size(); i++) { + if (inputs[i]->portId >= Stages::LEVEL_INPUTS + (module->numModules * NUM_CHANNELS) && inputs[i]->portId < Stages::LEVEL_INPUTS + (NUM_CHANNELS * MAX_MODULES)) { + gRackWidget->wireContainer->removeAllWires(inputs[i]); + } + if (inputs[i]->portId >= Stages::GATE_INPUTS + (module->numModules * NUM_CHANNELS) && inputs[i]->portId < Stages::GATE_INPUTS + (NUM_CHANNELS * MAX_MODULES)) { + gRackWidget->wireContainer->removeAllWires(inputs[i]); + } + } + for (int i = 0; i < outputs.size(); i++) { + if (outputs[i]->portId >= Stages::ENVELOPE_OUTPUTS + (module->numModules * NUM_CHANNELS) && outputs[i]->portId < Stages::ENVELOPE_OUTPUTS + (NUM_CHANNELS * MAX_MODULES)) { + gRackWidget->wireContainer->removeAllWires(outputs[i]); + } + } + } + + ModuleWidget::step(); + } + + void resize(int width) { + lastNumModules = width; + this->box.size.x = width * 210; + this->panel->box.size = this->box.size; + + topRightScrew->box.pos.x = box.size.x - 2 * RACK_GRID_WIDTH; + bottomRightScrew->box.pos.x = box.size.x - 2 * RACK_GRID_WIDTH; } void appendContextMenu(Menu *menu) override { @@ -438,7 +500,39 @@ struct StagesWidget : ModuleWidget { ABLoopItem *abloopItem = MenuItem::create("Set A/B Loop", CHECKMARK(module->abloop)); abloopItem->module = module; menu->addChild(abloopItem); + + struct ExpandoItem : MenuItem { + Stages *module; + int width = 1; + void onAction(EventAction &e) override { + module->numModules = width; + } + }; + + struct ModuleLinkingItem : MenuItem { + Stages* module; + + Menu *createChildMenu() override { + Menu *menu = new Menu(); + for (auto i = 1; i <= MAX_MODULES; i += 1) { + ExpandoItem *expandoItem = MenuItem::create(stringf("%dx", i), CHECKMARK(module->numModules == i)); + expandoItem->module = module; + expandoItem->width = i; + menu->addChild(expandoItem); + } + return menu; + } + }; + + menu->addChild(MenuEntry::create()); + ModuleLinkingItem *moduleLinkingItem = MenuItem::create("Linked Modules"); + moduleLinkingItem->module = module; + menu->addChild(moduleLinkingItem); + } + + + };