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);
+
}
+
+
+
};