diff --git a/src/AudibleInstruments.cpp b/src/AudibleInstruments.cpp
index b6f9ccb..1eabd8d 100644
--- a/src/AudibleInstruments.cpp
+++ b/src/AudibleInstruments.cpp
@@ -23,5 +23,5 @@ void init(rack::Plugin *p) {
p->addModel(modelVeils);
p->addModel(modelFrames);
// p->addModel(modelPeaks);
- // p->addModel(modelStages);
+ p->addModel(modelStages);
}
diff --git a/src/Stages.cpp b/src/Stages.cpp
index b7ca955..3c8f2b5 100644
--- a/src/Stages.cpp
+++ b/src/Stages.cpp
@@ -9,6 +9,58 @@
static const int NUM_CHANNELS = 6;
static const int BLOCK_SIZE = 8;
+struct GroupBuilder {
+
+ GroupBuilder() {
+ for (size_t i = 0; i < NUM_CHANNELS; i += 1) {
+ groupSize[i] = 0;
+ }
+ }
+
+ size_t groupSize[NUM_CHANNELS];
+ bool isPatched = false;
+
+ bool buildGroups(std::vector *inputs, size_t first, size_t count) {
+ bool changed = false;
+ isPatched = false;
+ size_t activeGroup = 0;
+
+ for (int i = count-1; i >= 0; i -= 1) {
+ auto patched = (*inputs)[first + i].active;
+
+ activeGroup += 1;
+ if (!patched) {
+ changed = changed || groupSize[i] != 0;
+ groupSize[i] = 0;
+ } else if (patched) {
+ isPatched = true;
+ changed = changed || groupSize[i] != activeGroup;
+ groupSize[i] = activeGroup;
+ activeGroup = 0;
+ }
+ }
+
+ return changed;
+ }
+
+ int groupForSegment(int segment) {
+ int group = 0;
+ int currentGroupSize = 0;
+
+ for (int i = 0; i < NUM_CHANNELS; i += 1) {
+ if (currentGroupSize <= 0) {
+ currentGroupSize = max(1, groupSize[i]);
+ group = i;
+ }
+
+ if (segment == i) {
+ return group;
+ }
+
+ currentGroupSize -= 1;
+ }
+ }
+};
struct Stages : Module {
enum ParamIds {
@@ -33,8 +85,10 @@ struct Stages : Module {
};
stages::segment::Configuration configurations[NUM_CHANNELS];
+ bool configuration_changed[NUM_CHANNELS];
stages::SegmentGenerator segment_generator[NUM_CHANNELS];
stages::Oscillator oscillator[NUM_CHANNELS];
+ bool abloop;
// stages::ChainState chain_state;
// stages::Settings settings;
@@ -44,9 +98,11 @@ struct Stages : Module {
// Buffers
float envelopeBuffer[NUM_CHANNELS][BLOCK_SIZE] = {};
+ float typeLightsBuffer[NUM_CHANNELS][BLOCK_SIZE] = {};
stmlib::GateFlags last_gate_flags[NUM_CHANNELS] = {};
stmlib::GateFlags gate_flags[NUM_CHANNELS][BLOCK_SIZE] = {};
int blockIndex = 0;
+ GroupBuilder groupBuilder;
Stages() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
// chain_state.Init(NULL, NULL);
@@ -54,12 +110,14 @@ struct Stages : Module {
}
void onReset() override {
+ abloop = false;
for (size_t i = 0; i < NUM_CHANNELS; ++i) {
segment_generator[i].Init();
oscillator[i].Init();
configurations[i].type = stages::segment::TYPE_RAMP;
configurations[i].loop = false;
+ configuration_changed[i] = true;
}
}
@@ -103,21 +161,52 @@ struct Stages : Module {
secondaries[i] = params[SHAPE_PARAMS + i].value;
}
- // // Get patched state
- // for (int i = 0; i < NUM_CHANNELS; i++) {
- // bool p = inputs[GATE_INPUTS + i].active;
- // if (p != patched[i])
- // dirty_configurations[i] = true;
- // patched[i] = p;
- // }
+ // See if the group associations have changed since the last group
+ auto groups_changed = groupBuilder.buildGroups(&inputs, GATE_INPUTS, NUM_CHANNELS);
// Process block
stages::SegmentGenerator::Output out[BLOCK_SIZE] = {};
- for (int i = 0; i < NUM_CHANNELS; i++) {
+ for (int i = 0; i < NUM_CHANNELS;) {
+
+ // Check if the config needs applying to the segment generator for this group
+ bool segment_changed = groups_changed;
+ int numberOfLoops = 0;
+ for (size_t j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) {
+ numberOfLoops += configurations[i + j].loop ? 1 : 0;
+ segment_changed |= configuration_changed[i + j];
+ configuration_changed[i + j] = false;
+ }
+
+ if (segment_changed) {
+ if (numberOfLoops > 2) {
+ for (size_t j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) {
+ configurations[i + j].loop = false;
+ }
+ }
+ segment_generator[i].Configure(groupBuilder.groupSize[i] > 0, &configurations[i], max(1, groupBuilder.groupSize[i]));
+ }
+
+ // Set the segment parameters on the generator we're about to process
+ for (size_t j = 0; j < segment_generator[i].num_segments(); j += 1) {
+ segment_generator[i].set_segment_parameters(j, primaries[i + j], secondaries[i + j]);
+
+ oscillator[i + j].Render(
+ 0.00001f, 0.5f, typeLightsBuffer[i + j], BLOCK_SIZE
+ );
+ }
+
bool led_state = segment_generator[i].Process(gate_flags[i], out, BLOCK_SIZE);
+
+ // Set the outputs for the active segment in each output sample
+ // All outputs also go to the first segment
for (int j = 0; j < BLOCK_SIZE; j++) {
+ for (int k = 1; k < segment_generator[i].num_segments(); k += 1) {
+ envelopeBuffer[i + k][j] = k == out[j].segment ? out[j].value : 0.f;
+ }
envelopeBuffer[i][j] = out[j].value;
}
+
+ i += segment_generator[i].num_segments();
}
}
@@ -125,16 +214,50 @@ struct Stages : Module {
// Buttons
for (int i = 0; i < NUM_CHANNELS; i++) {
bool pressed = params[TYPE_PARAMS + i].value > 0.f;
- if (pressed) {
+ if (pressed && pressedTime >= 0.f) {
pressedTime += engineGetSampleTime();
- }
- // Check if released
- if (typeTriggers[i].process(!pressed)) {
if (pressedTime >= 1.f) {
+ pressedTime = -1.f;
+ configuration_changed[i] = true;
configurations[i].loop = !configurations[i].loop;
+
+ // ensure that we're the only loop item in the group
+ if (configurations[i].loop) {
+ int group = groupBuilder.groupForSegment(i);
+
+ if (abloop) {
+ // See how many loop items we have
+ int loopitems = 0;
+
+ for (int j = 0; j < groupBuilder.groupSize[group]; j += 1) {
+ loopitems += configurations[group + j].loop ? 1 : 0;
+ }
+
+ // Turn abloop off if we've got 2 or more loops
+ if (loopitems >= 2) {
+ abloop = false;
+ }
+
+ // If we've got >2 loops, clear down to the one loop
+ if (loopitems > 2) {
+ for (int j = 0; j < groupBuilder.groupSize[group]; j += 1) {
+ configurations[group + j].loop = (group + j) == i;
+ }
+ }
+ } else {
+ for (int j = 0; j < groupBuilder.groupSize[group]; j += 1) {
+ configurations[group + j].loop = (group + j) == i;
+ }
+ }
+ }
}
- else {
+ }
+
+ // Check if released
+ if (typeTriggers[i].process(!pressed)) {
+ if (pressedTime >= 0.f) {
configurations[i].type = (stages::segment::Type) ((configurations[i].type + 1) % 3);
+ configuration_changed[i] = true;
}
pressedTime = 0.f;
}
@@ -154,13 +277,32 @@ struct Stages : Module {
}
// Output
+ int group = 0;
+ int currentGroupSize = 0;
+ int loopcount = 0;
for (int i = 0; i < NUM_CHANNELS; i++) {
float envelope = envelopeBuffer[i][blockIndex];
outputs[ENVELOPE_OUTPUTS + i].value = envelope * 10.f;
lights[ENVELOPE_LIGHTS + i].setBrightnessSmooth(envelope);
- lights[TYPE_LIGHTS + i*2 + 0].setBrightness(configurations[i].type == 0 || configurations[i].type == 1);
- lights[TYPE_LIGHTS + i*2 + 1].setBrightness(configurations[i].type == 1 || configurations[i].type == 2);
+ if (currentGroupSize <= 0) {
+ currentGroupSize = max(1, groupBuilder.groupSize[i]);
+ group = i;
+ loopcount = 0;
+ }
+ currentGroupSize -= 1;
+
+ loopcount += configurations[i].loop ? 1 : 0;
+ if (!configurations[i].loop) {
+ lights[TYPE_LIGHTS + i*2 + 0].setBrightness(configurations[i].type == 0 || configurations[i].type == 1);
+ lights[TYPE_LIGHTS + i*2 + 1].setBrightness(configurations[i].type == 1 || configurations[i].type == 2);
+ } else if (configurations[i].loop && loopcount == 1) {
+ lights[TYPE_LIGHTS + i*2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * abs(typeLightsBuffer[i][blockIndex]));
+ lights[TYPE_LIGHTS + i*2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * abs(typeLightsBuffer[i][blockIndex]));
+ } else {
+ lights[TYPE_LIGHTS + i*2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * (1.f - abs(typeLightsBuffer[i][blockIndex])));
+ lights[TYPE_LIGHTS + i*2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * (1.f - abs(typeLightsBuffer[i][blockIndex])));
+ }
}
}
};
@@ -227,6 +369,22 @@ struct StagesWidget : ModuleWidget {
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));
}
+
+ void appendContextMenu(Menu *menu) override {
+ Stages *module = dynamic_cast(this->module);
+
+ struct ABLoopItem : MenuItem {
+ Stages *module;
+ void onAction(EventAction &e) override {
+ module->abloop = true;
+ }
+ };
+
+ menu->addChild(MenuEntry::create());
+ ABLoopItem *abloopItem = MenuItem::create("Set A/B Loop", CHECKMARK(module->abloop));
+ abloopItem->module = module;
+ menu->addChild(abloopItem);
+ }
};