From 13a16aea60acd3fc546ef9feddf92f86d3fc1d0a Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Mon, 1 Oct 2018 23:30:47 +0100 Subject: [PATCH 01/14] Essential working Stages --- src/AudibleInstruments.cpp | 2 +- src/Stages.cpp | 188 ++++++++++++++++++++++++++++++++++--- 2 files changed, 174 insertions(+), 16 deletions(-) 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); + } }; From 32aa9a15eb8fa40242018353c3c3e5114b00f4aa Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 2 Oct 2018 00:48:46 +0100 Subject: [PATCH 02/14] Allow varying sample rate --- eurorack | 2 +- src/Stages.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/eurorack b/eurorack index e9aac2a..041b005 160000 --- a/eurorack +++ b/eurorack @@ -1 +1 @@ -Subproject commit e9aac2ab705e7b7e902b43a1bbd9e01d25cd42a2 +Subproject commit 041b00526bbffd8fa59d4b08f8a43238e8437b16 diff --git a/src/Stages.cpp b/src/Stages.cpp index 3c8f2b5..e653fbc 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -110,7 +110,10 @@ struct Stages : Module { } void onReset() override { + stages::kSampleRate = engineGetSampleRate(); + abloop = false; + for (size_t i = 0; i < NUM_CHANNELS; ++i) { segment_generator[i].Init(); oscillator[i].Init(); @@ -152,6 +155,13 @@ struct Stages : Module { } } + void onSampleRateChange() override { + stages::kSampleRate = engineGetSampleRate(); + for (int i = 0; i < NUM_CHANNELS; i++) { + // segment_generator[i].InitRamps(); + } + } + void stepBlock() { // Get parameters float primaries[NUM_CHANNELS]; @@ -282,7 +292,7 @@ struct Stages : Module { int loopcount = 0; for (int i = 0; i < NUM_CHANNELS; i++) { float envelope = envelopeBuffer[i][blockIndex]; - outputs[ENVELOPE_OUTPUTS + i].value = envelope * 10.f; + outputs[ENVELOPE_OUTPUTS + i].value = envelope * 8.f; lights[ENVELOPE_LIGHTS + i].setBrightnessSmooth(envelope); if (currentGroupSize <= 0) { From d9434e41c97ead49a58a54799a8c69d8a72401de Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 2 Oct 2018 00:51:11 +0100 Subject: [PATCH 03/14] Sending input CV to the correct segments --- src/Stages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index e653fbc..71910c2 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -167,7 +167,7 @@ struct Stages : Module { float primaries[NUM_CHANNELS]; float secondaries[NUM_CHANNELS]; for (int i = 0; i < NUM_CHANNELS; i++) { - primaries[i] = clamp(params[LEVEL_PARAMS + i].value + inputs[LEVEL_INPUTS].value / 8.f, 0.f, 1.f); + 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; } From f29754229deadd311ac6987248f707dd6f44d951 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 2 Oct 2018 22:25:29 +0100 Subject: [PATCH 04/14] Breaking long press button functionality out Using simpler sine oscillators for flashing lights that can render with a phase offset. Cleaning up warnings. --- src/Stages.cpp | 182 ++++++++++++++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 69 deletions(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index 71910c2..d20094e 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -4,11 +4,66 @@ #include "stages/segment_generator.h" #include "stages/oscillator.h" - // Must match io_buffer.h static const int NUM_CHANNELS = 6; static const int BLOCK_SIZE = 8; +struct SineOscillator { + float phase = 0.f; + + float step(float offset) { + + // Implement a simple sine oscillator + float deltaTime = engineGetSampleTime(); + + float freq = 0.5f; + + // Accumulate the phase + phase += freq * deltaTime; + if (phase >= 1.0f) + phase -= 1.0f; + + // Compute the sine output + float sine = sinf(2.0f * M_PI * (phase + offset)); + return sine; + } +}; + +struct LongPressButton { + + enum Events { + NO_PRESS, + SHORT_PRESS, + LONG_PRESS + }; + + float pressedTime = 0.f; + BooleanTrigger trigger; + + Events step(Param& param) { + auto result = NO_PRESS; + + bool pressed = param.value > 0.f; + if (pressed && pressedTime >= 0.f) { + pressedTime += engineGetSampleTime(); + if (pressedTime >= 1.f) { + pressedTime = -1.f; + result = LONG_PRESS; + } + } + + // Check if released + if (trigger.process(!pressed)) { + if (pressedTime >= 0.f) { + result = SHORT_PRESS; + } + pressedTime = 0.f; + } + + return result; + } +}; + struct GroupBuilder { GroupBuilder() { @@ -59,6 +114,8 @@ struct GroupBuilder { currentGroupSize -= 1; } + + return segment; } }; @@ -87,18 +144,16 @@ 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]; + SineOscillator oscillator[NUM_CHANNELS]; bool abloop; // stages::ChainState chain_state; // stages::Settings settings; // Buttons - BooleanTrigger typeTriggers[NUM_CHANNELS]; - float pressedTime = 0.f; + LongPressButton typeButtons[NUM_CHANNELS]; // 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; @@ -116,7 +171,6 @@ struct Stages : Module { 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; @@ -181,7 +235,7 @@ struct Stages : Module { // 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) { + for (int 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; @@ -189,7 +243,7 @@ struct Stages : Module { if (segment_changed) { if (numberOfLoops > 2) { - for (size_t j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) { + for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) { configurations[i + j].loop = false; } } @@ -197,15 +251,11 @@ struct Stages : Module { } // 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) { + for (int 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); + 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 @@ -220,56 +270,49 @@ struct Stages : Module { } } - void step() override { - // Buttons - for (int i = 0; i < NUM_CHANNELS; i++) { - bool pressed = params[TYPE_PARAMS + i].value > 0.f; - if (pressed && pressedTime >= 0.f) { - pressedTime += engineGetSampleTime(); - 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; - } - } - } - } + void toggleMode(int i) { + configurations[i].type = (stages::segment::Type) ((configurations[i].type + 1) % 3); + configuration_changed[i] = true; + } + + void toggleLoop(int i) { + 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); + + // See how many loop items we have + int loopitems = 0; + + for (size_t j = 0; j < groupBuilder.groupSize[group]; j += 1) { + loopitems += configurations[group + j].loop ? 1 : 0; } - // 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; + // If we've got too many loop items, clear down to the one loop + if ((abloop && loopitems > 2) || (!abloop && loopitems > 1)) { + for (size_t j = 0; j < groupBuilder.groupSize[group]; j += 1) { + configurations[group + j].loop = (group + (int)j) == i; } - pressedTime = 0.f; + loopitems = 1; + } + + // Turn abloop off if we've got 2 or more loops + if (loopitems >= 2) { + abloop = false; + } + } + } + + void step() override { + // Buttons + for (int i = 0; i < NUM_CHANNELS; i++) { + switch (typeButtons[i].step(params[TYPE_PARAMS + i])) { + default: + case LongPressButton::NO_PRESS: break; + case LongPressButton::SHORT_PRESS: toggleMode(i); break; + case LongPressButton::LONG_PRESS: toggleLoop(i); break; } } @@ -287,7 +330,6 @@ struct Stages : Module { } // Output - int group = 0; int currentGroupSize = 0; int loopcount = 0; for (int i = 0; i < NUM_CHANNELS; i++) { @@ -297,22 +339,24 @@ struct Stages : Module { if (currentGroupSize <= 0) { currentGroupSize = max(1, groupBuilder.groupSize[i]); - group = i; loopcount = 0; } currentGroupSize -= 1; loopcount += configurations[i].loop ? 1 : 0; + auto flashlevel = 1.f; + 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); + oscillator[i].step(0.f); // move the oscillator on to keep the lights in sync } 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])); + flashlevel = abs(oscillator[i].step(0.f)); } 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]))); + flashlevel = 1 - abs(oscillator[i].step(0.0625f)); } + + lights[TYPE_LIGHTS + i*2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); + lights[TYPE_LIGHTS + i*2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * flashlevel); + } } }; From 55d7087daaa321262f15fd9e9f67cdb873835ff8 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 2 Oct 2018 22:26:32 +0100 Subject: [PATCH 05/14] Fixing panel size to 14hp or 71.12mm --- res/Stages.svg | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/res/Stages.svg b/res/Stages.svg index 8e40025..4518a1a 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="70.887665mm" + width="71.120003mm" height="128.5889mm" - viewBox="0 0 70.887665 128.5889" + viewBox="0 0 71.120003 128.5889" version="1.1" id="svg1428" inkscape:version="0.92.2 2405546, 2018-03-11" @@ -1173,11 +1173,11 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="191.99827" - inkscape:cy="232.47187" + inkscape:zoom="5.6" + inkscape:cx="149.63939" + inkscape:cy="511.43702" inkscape:document-units="mm" - inkscape:current-layer="layer2" + inkscape:current-layer="layer1" showgrid="false" fit-margin-top="0" fit-margin-left="0" @@ -1186,7 +1186,7 @@ inkscape:window-width="1600" inkscape:window-height="882" inkscape:window-x="0" - inkscape:window-y="18" + inkscape:window-y="27" inkscape:window-maximized="0" inkscape:snap-bbox="true" inkscape:snap-bbox-midpoints="false" @@ -1194,7 +1194,8 @@ inkscape:snap-page="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" - inkscape:bbox-paths="true" /> + inkscape:bbox-paths="true" + units="mm" /> @@ -1213,11 +1214,11 @@ id="layer1" transform="translate(0.04409722,-168.45588)"> Date: Tue, 2 Oct 2018 22:52:44 +0100 Subject: [PATCH 06/14] Re-initializing ramp generators when sample rate changes --- eurorack | 2 +- src/Stages.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eurorack b/eurorack index 041b005..f8d34c2 160000 --- a/eurorack +++ b/eurorack @@ -1 +1 @@ -Subproject commit 041b00526bbffd8fa59d4b08f8a43238e8437b16 +Subproject commit f8d34c206da458e4d8708df2cc94064826d975ff diff --git a/src/Stages.cpp b/src/Stages.cpp index d20094e..a1545a6 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -212,7 +212,7 @@ struct Stages : Module { void onSampleRateChange() override { stages::kSampleRate = engineGetSampleRate(); for (int i = 0; i < NUM_CHANNELS; i++) { - // segment_generator[i].InitRamps(); + segment_generator[i].InitRamps(); } } From 7279f7391dbdb7eb7bffc16c8b0d66d20ca2dbee Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 2 Oct 2018 23:07:41 +0100 Subject: [PATCH 07/14] Fixing up alternate light flashing lights Was abusing legacy match from when using a triangle oscillator for the light levels --- src/Stages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index a1545a6..d30751e 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -351,7 +351,7 @@ struct Stages : Module { } else if (configurations[i].loop && loopcount == 1) { flashlevel = abs(oscillator[i].step(0.f)); } else { - flashlevel = 1 - abs(oscillator[i].step(0.0625f)); + flashlevel = abs(oscillator[i].step(0.25f)); } lights[TYPE_LIGHTS + i*2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); From bcc460848a55a401ccc2709c484b04afaecad4b3 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Wed, 3 Oct 2018 19:49:24 +0100 Subject: [PATCH 08/14] Changing individual segment output to be the inverse of the phase, which seems to match up more with the Stages manual --- src/Stages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index d30751e..9f86ded 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -261,7 +261,7 @@ struct Stages : Module { // 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 + k][j] = k == out[j].segment ? 1- out[j].phase : 0.f; } envelopeBuffer[i][j] = out[j].value; } From 5ff5bfcab4dbc2045d95d7242d9198f2d828e824 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Tue, 9 Oct 2018 01:56:51 -0400 Subject: [PATCH 09/14] Code style --- src/Stages.cpp | 97 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index 9f86ded..eefbf88 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -1,13 +1,14 @@ #include "AudibleInstruments.hpp" #include "dsp/digital.hpp" -// #include "stages/chain_state.h" #include "stages/segment_generator.h" #include "stages/oscillator.h" + // Must match io_buffer.h static const int NUM_CHANNELS = 6; static const int BLOCK_SIZE = 8; + struct SineOscillator { float phase = 0.f; @@ -29,8 +30,8 @@ struct SineOscillator { } }; -struct LongPressButton { +struct LongPressButton { enum Events { NO_PRESS, SHORT_PRESS, @@ -40,8 +41,8 @@ struct LongPressButton { float pressedTime = 0.f; BooleanTrigger trigger; - Events step(Param& param) { - auto result = NO_PRESS; + Events step(Param ¶m) { + Events result = NO_PRESS; bool pressed = param.value > 0.f; if (pressed && pressedTime >= 0.f) { @@ -64,10 +65,10 @@ struct LongPressButton { } }; -struct GroupBuilder { +struct GroupBuilder { GroupBuilder() { - for (size_t i = 0; i < NUM_CHANNELS; i += 1) { + for (size_t i = 0; i < NUM_CHANNELS; i++) { groupSize[i] = 0; } } @@ -80,16 +81,17 @@ struct GroupBuilder { isPatched = false; size_t activeGroup = 0; - for (int i = count-1; i >= 0; i -= 1) { - auto patched = (*inputs)[first + i].active; + for (int i = count - 1; i >= 0; i -= 1) { + bool patched = (*inputs)[first + i].active; - activeGroup += 1; + activeGroup++; if (!patched) { - changed = changed || groupSize[i] != 0; + changed = changed || (groupSize[i] != 0); groupSize[i] = 0; - } else if (patched) { + } + else if (patched) { isPatched = true; - changed = changed || groupSize[i] != activeGroup; + changed = changed || (groupSize[i] != activeGroup); groupSize[i] = activeGroup; activeGroup = 0; } @@ -102,7 +104,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; i++) { if (currentGroupSize <= 0) { currentGroupSize = max(1, groupSize[i]); group = i; @@ -136,7 +138,7 @@ struct Stages : Module { NUM_OUTPUTS }; enum LightIds { - ENUMS(TYPE_LIGHTS, NUM_CHANNELS*2), + ENUMS(TYPE_LIGHTS, NUM_CHANNELS * 2), ENUMS(ENVELOPE_LIGHTS, NUM_CHANNELS), NUM_LIGHTS }; @@ -145,9 +147,7 @@ struct Stages : Module { bool configuration_changed[NUM_CHANNELS]; stages::SegmentGenerator segment_generator[NUM_CHANNELS]; SineOscillator oscillator[NUM_CHANNELS]; - bool abloop; - // stages::ChainState chain_state; - // stages::Settings settings; + bool abLoop; // Buttons LongPressButton typeButtons[NUM_CHANNELS]; @@ -160,14 +160,11 @@ struct Stages : Module { GroupBuilder groupBuilder; Stages() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { - // chain_state.Init(NULL, NULL); onReset(); } void onReset() override { - stages::kSampleRate = engineGetSampleRate(); - - abloop = false; + abLoop = false; for (size_t i = 0; i < NUM_CHANNELS; ++i) { segment_generator[i].Init(); @@ -176,6 +173,8 @@ struct Stages : Module { configurations[i].loop = false; configuration_changed[i] = true; } + + onSampleRateChange(); } json_t *toJson() override { @@ -226,16 +225,15 @@ struct Stages : Module { } // See if the group associations have changed since the last group - auto groups_changed = groupBuilder.buildGroups(&inputs, GATE_INPUTS, NUM_CHANNELS); + bool groups_changed = groupBuilder.buildGroups(&inputs, GATE_INPUTS, NUM_CHANNELS); // Process block stages::SegmentGenerator::Output out[BLOCK_SIZE] = {}; 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 (int j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) { + for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j++) { numberOfLoops += configurations[i + j].loop ? 1 : 0; segment_changed |= configuration_changed[i + j]; configuration_changed[i + j] = false; @@ -243,7 +241,7 @@ struct Stages : Module { if (segment_changed) { if (numberOfLoops > 2) { - for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j += 1) { + for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j++) { configurations[i + j].loop = false; } } @@ -251,7 +249,7 @@ struct Stages : Module { } // Set the segment parameters on the generator we're about to process - for (int j = 0; j < segment_generator[i].num_segments(); j += 1) { + for (int j = 0; j < segment_generator[i].num_segments(); j++) { segment_generator[i].set_segment_parameters(j, primaries[i + j], secondaries[i + j]); } @@ -260,8 +258,8 @@ struct Stages : Module { // 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 ? 1- out[j].phase : 0.f; + for (int k = 1; k < segment_generator[i].num_segments(); k++) { + envelopeBuffer[i + k][j] = k == out[j].segment ? 1 - out[j].phase : 0.f; } envelopeBuffer[i][j] = out[j].value; } @@ -286,21 +284,21 @@ struct Stages : Module { // See how many loop items we have int loopitems = 0; - for (size_t j = 0; j < groupBuilder.groupSize[group]; j += 1) { + for (size_t j = 0; j < groupBuilder.groupSize[group]; j++) { loopitems += configurations[group + j].loop ? 1 : 0; } // If we've got too many loop items, clear down to the one loop - if ((abloop && loopitems > 2) || (!abloop && loopitems > 1)) { - for (size_t j = 0; j < groupBuilder.groupSize[group]; j += 1) { + if ((abLoop && loopitems > 2) || (!abLoop && loopitems > 1)) { + for (size_t j = 0; j < groupBuilder.groupSize[group]; j++) { configurations[group + j].loop = (group + (int)j) == i; } loopitems = 1; } - // Turn abloop off if we've got 2 or more loops + // Turn abLoop off if we've got 2 or more loops if (loopitems >= 2) { - abloop = false; + abLoop = false; } } } @@ -344,19 +342,20 @@ struct Stages : Module { currentGroupSize -= 1; loopcount += configurations[i].loop ? 1 : 0; - auto flashlevel = 1.f; + float flashlevel = 1.f; if (!configurations[i].loop) { oscillator[i].step(0.f); // move the oscillator on to keep the lights in sync - } else if (configurations[i].loop && loopcount == 1) { + } + else if (configurations[i].loop && loopcount == 1) { flashlevel = abs(oscillator[i].step(0.f)); - } else { + } + else { flashlevel = abs(oscillator[i].step(0.25f)); } - lights[TYPE_LIGHTS + i*2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); - lights[TYPE_LIGHTS + i*2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * flashlevel); - + lights[TYPE_LIGHTS + i * 2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); + lights[TYPE_LIGHTS + i * 2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * flashlevel); } } }; @@ -410,12 +409,12 @@ struct StagesWidget : ModuleWidget { 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(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)); @@ -430,14 +429,14 @@ struct StagesWidget : ModuleWidget { struct ABLoopItem : MenuItem { Stages *module; void onAction(EventAction &e) override { - module->abloop = true; + module->abLoop = true; } }; menu->addChild(MenuEntry::create()); - ABLoopItem *abloopItem = MenuItem::create("Set A/B Loop", CHECKMARK(module->abloop)); - abloopItem->module = module; - menu->addChild(abloopItem); + ABLoopItem *abLoopItem = MenuItem::create("Set A/B Loop", CHECKMARK(module->abLoop)); + abLoopItem->module = module; + menu->addChild(abLoopItem); } }; From d4933932dcaa7b101401bda1ede2bd4a5f3f94c4 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 9 Oct 2018 21:20:56 +0100 Subject: [PATCH 10/14] Simplifying light oscillators --- .vscode/settings.json | 10 ++++++++++ src/Stages.cpp | 46 ++++++++++++++----------------------------- 2 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b377f0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "files.associations": { + "istream": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "system_error": "cpp", + "type_traits": "cpp" + } +} \ No newline at end of file diff --git a/src/Stages.cpp b/src/Stages.cpp index eefbf88..9ebddf1 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -8,29 +8,6 @@ static const int NUM_CHANNELS = 6; static const int BLOCK_SIZE = 8; - -struct SineOscillator { - float phase = 0.f; - - float step(float offset) { - - // Implement a simple sine oscillator - float deltaTime = engineGetSampleTime(); - - float freq = 0.5f; - - // Accumulate the phase - phase += freq * deltaTime; - if (phase >= 1.0f) - phase -= 1.0f; - - // Compute the sine output - float sine = sinf(2.0f * M_PI * (phase + offset)); - return sine; - } -}; - - struct LongPressButton { enum Events { NO_PRESS, @@ -146,7 +123,7 @@ struct Stages : Module { stages::segment::Configuration configurations[NUM_CHANNELS]; bool configuration_changed[NUM_CHANNELS]; stages::SegmentGenerator segment_generator[NUM_CHANNELS]; - SineOscillator oscillator[NUM_CHANNELS]; + float lightOscillatorPhase; bool abLoop; // Buttons @@ -174,6 +151,7 @@ struct Stages : Module { configuration_changed[i] = true; } + lightOscillatorPhase = 0.f; onSampleRateChange(); } @@ -304,6 +282,11 @@ struct Stages : Module { } void step() override { + // Oscillate flashing the type lights + lightOscillatorPhase += 0.5f * engineGetSampleTime(); + if (lightOscillatorPhase >= 1.0f) + lightOscillatorPhase -= 1.0f; + // Buttons for (int i = 0; i < NUM_CHANNELS; i++) { switch (typeButtons[i].step(params[TYPE_PARAMS + i])) { @@ -344,14 +327,15 @@ struct Stages : Module { loopcount += configurations[i].loop ? 1 : 0; float flashlevel = 1.f; - if (!configurations[i].loop) { - oscillator[i].step(0.f); // move the oscillator on to keep the lights in sync - } - else if (configurations[i].loop && loopcount == 1) { - flashlevel = abs(oscillator[i].step(0.f)); + if (configurations[i].loop && loopcount == 1) { + flashlevel = abs(sinf(2.0f * M_PI * lightOscillatorPhase)); } - else { - flashlevel = abs(oscillator[i].step(0.25f)); + else if (configurations[i].loop && loopcount > 1) { + float advancedPhase = lightOscillatorPhase + 0.25f; + if (advancedPhase > 1.0f) + advancedPhase -= 1.0f; + + flashlevel = abs(sinf(2.0f * M_PI * advancedPhase)); } lights[TYPE_LIGHTS + i * 2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); From 92e161460f1fc1c46e649ae1c46cbcdbe75c4d3a Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 9 Oct 2018 21:46:24 +0100 Subject: [PATCH 11/14] Removing explicit AB Loop Mode Diverging from hardware, allowing a group to have 2 active loop segments by holding each segment button individually rather than both together (or using abLoop mode) --- src/Stages.cpp | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index 9ebddf1..be264fe 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -124,7 +124,6 @@ struct Stages : Module { bool configuration_changed[NUM_CHANNELS]; stages::SegmentGenerator segment_generator[NUM_CHANNELS]; float lightOscillatorPhase; - bool abLoop; // Buttons LongPressButton typeButtons[NUM_CHANNELS]; @@ -141,8 +140,6 @@ struct Stages : Module { } void onReset() override { - abLoop = false; - for (size_t i = 0; i < NUM_CHANNELS; ++i) { segment_generator[i].Init(); @@ -266,18 +263,13 @@ struct Stages : Module { loopitems += configurations[group + j].loop ? 1 : 0; } - // If we've got too many loop items, clear down to the one loop - if ((abLoop && loopitems > 2) || (!abLoop && loopitems > 1)) { + // If we've got too many loop items, clear down to the one looping segment + if (loopitems > 2) { for (size_t j = 0; j < groupBuilder.groupSize[group]; j++) { configurations[group + j].loop = (group + (int)j) == i; } loopitems = 1; } - - // Turn abLoop off if we've got 2 or more loops - if (loopitems >= 2) { - abLoop = false; - } } } @@ -406,22 +398,6 @@ 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); - } }; From d1525bd8c732a60438b941fa9d307cf97bb572e2 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 9 Oct 2018 22:02:37 +0100 Subject: [PATCH 12/14] Using segment_generator's explicit sample rate interface --- eurorack | 2 +- src/Stages.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/eurorack b/eurorack index f8d34c2..2bd5cdf 160000 --- a/eurorack +++ b/eurorack @@ -1 +1 @@ -Subproject commit f8d34c206da458e4d8708df2cc94064826d975ff +Subproject commit 2bd5cdf9598ebf720da33b60e0bc5e2dad4215d3 diff --git a/src/Stages.cpp b/src/Stages.cpp index be264fe..26960a6 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -184,9 +184,8 @@ struct Stages : Module { } void onSampleRateChange() override { - stages::kSampleRate = engineGetSampleRate(); for (int i = 0; i < NUM_CHANNELS; i++) { - segment_generator[i].InitRamps(); + segment_generator[i].SetSampleRate(engineGetSampleRate()); } } From 23a6d00e62f6b393ada97fcbb349ad5617f14f26 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Tue, 9 Oct 2018 22:27:24 +0100 Subject: [PATCH 13/14] Removing vscode settings file --- .vscode/settings.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7b377f0..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "files.associations": { - "istream": "cpp", - "optional": "cpp", - "ostream": "cpp", - "ratio": "cpp", - "system_error": "cpp", - "type_traits": "cpp" - } -} \ No newline at end of file From dc94cd5d58d169de05a0d5a4e1e9880bbe6c9e54 Mon Sep 17 00:00:00 2001 From: Jim Tupper Date: Wed, 10 Oct 2018 00:55:20 +0100 Subject: [PATCH 14/14] Reworking group builder Group builder algorithm works forwards, builds more static info about each group. Algorithms now iterate over groups first, then segments within the groups as necessary. --- src/Stages.cpp | 237 ++++++++++++++++++++++++++++--------------------- 1 file changed, 137 insertions(+), 100 deletions(-) diff --git a/src/Stages.cpp b/src/Stages.cpp index 26960a6..154eb07 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -42,59 +42,78 @@ struct LongPressButton { } }; +struct GroupInfo { + int first_segment = 0; + int segment_count = 0; + bool gated = false; +}; struct GroupBuilder { - GroupBuilder() { - for (size_t i = 0; i < NUM_CHANNELS; i++) { - groupSize[i] = 0; - } - } - size_t groupSize[NUM_CHANNELS]; - bool isPatched = false; + GroupInfo groups[NUM_CHANNELS]; + int groupCount = 0; - bool buildGroups(std::vector *inputs, size_t first, size_t count) { - bool changed = false; - isPatched = false; - size_t activeGroup = 0; + bool buildGroups(std::vector *gateInputs, size_t first, size_t count) { + bool any_gates = false; - for (int i = count - 1; i >= 0; i -= 1) { - bool patched = (*inputs)[first + i].active; + GroupInfo nextGroups[NUM_CHANNELS]; - activeGroup++; - if (!patched) { - changed = changed || (groupSize[i] != 0); - groupSize[i] = 0; + int currentGroup = 0; + for (int i = 0; i < NUM_CHANNELS; i++) { + bool gated = (*gateInputs)[first + i].active; + + if (!any_gates) { + if (!gated) { + // No gates at all yet, segments are all single segment groups + nextGroups[currentGroup].first_segment = i; + nextGroups[currentGroup].segment_count = 1; + nextGroups[currentGroup].gated = false; + currentGroup++; + } + else { + // first gate, current group is start of a segment group + any_gates = true; + nextGroups[currentGroup].first_segment = i; + nextGroups[currentGroup].segment_count = 1; + nextGroups[currentGroup].gated = true; + currentGroup++; + } } - else if (patched) { - isPatched = true; - changed = changed || (groupSize[i] != activeGroup); - groupSize[i] = activeGroup; - activeGroup = 0; + else { + if (!gated) { + // We've had a gate, this ungated segment is part of the previous group + nextGroups[currentGroup-1].segment_count++; + } + else { + // This gated input indicates the start of the next group + nextGroups[currentGroup].first_segment = i; + nextGroups[currentGroup].segment_count = 1; + nextGroups[currentGroup].gated = true; + currentGroup++; + } } } - return changed; - } - - int groupForSegment(int segment) { - int group = 0; - int currentGroupSize = 0; + bool changed = false; - for (int i = 0; i < NUM_CHANNELS; i++) { - if (currentGroupSize <= 0) { - currentGroupSize = max(1, groupSize[i]); - group = i; - } + if (currentGroup != groupCount) { + changed = true; + groupCount = currentGroup; + } - if (segment == i) { - return group; + for (int i = 0; i < groupCount; i++) { + if (nextGroups[i].segment_count != groups[i].segment_count || + nextGroups[i].gated != groups[i].gated || + nextGroups[i].first_segment != groups[i].first_segment) { + changed = true; } - currentGroupSize -= 1; + groups[i].first_segment = nextGroups[i].first_segment; + groups[i].segment_count = nextGroups[i].segment_count; + groups[i].gated = nextGroups[i].gated; } - return segment; + return changed; } }; @@ -203,42 +222,53 @@ struct Stages : Module { // Process block stages::SegmentGenerator::Output out[BLOCK_SIZE] = {}; - for (int i = 0; i < NUM_CHANNELS;) { + for (int i = 0; i < groupBuilder.groupCount; i++) { + GroupInfo &group = groupBuilder.groups[i]; + // Check if the config needs applying to the segment generator for this group - bool segment_changed = groups_changed; - int numberOfLoops = 0; - for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j++) { - numberOfLoops += configurations[i + j].loop ? 1 : 0; - segment_changed |= configuration_changed[i + j]; - configuration_changed[i + j] = false; + bool apply_config = groups_changed; + int numberOfLoopsInGroup = 0; + for (int j = 0; j < group.segment_count; j++) { + int segment = group.first_segment + j; + numberOfLoopsInGroup += configurations[segment].loop ? 1 : 0; + apply_config |= configuration_changed[segment]; + configuration_changed[segment] = false; } - if (segment_changed) { - if (numberOfLoops > 2) { - for (int j = 0; j < max(1, groupBuilder.groupSize[i]); j++) { - configurations[i + j].loop = false; - } + if (numberOfLoopsInGroup > 2) { + // Too many segments are looping, turn them all off + apply_config = true; + for (int j = 0; j < group.segment_count; j++) { + configurations[group.first_segment + j].loop = false; } - segment_generator[i].Configure(groupBuilder.groupSize[i] > 0, &configurations[i], max(1, groupBuilder.groupSize[i])); + } + + if (apply_config) { + segment_generator[i].Configure(group.gated, &configurations[group.first_segment], group.segment_count); } // Set the segment parameters on the generator we're about to process - for (int j = 0; j < segment_generator[i].num_segments(); j++) { - segment_generator[i].set_segment_parameters(j, primaries[i + j], secondaries[i + j]); + for (int j = 0; j < group.segment_count; j++) { + segment_generator[i].set_segment_parameters(j, primaries[group.first_segment + j], secondaries[group.first_segment + j]); } - segment_generator[i].Process(gate_flags[i], out, BLOCK_SIZE); + segment_generator[i].Process(gate_flags[group.first_segment], 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++) { - envelopeBuffer[i + k][j] = k == out[j].segment ? 1 - out[j].phase : 0.f; + for (int k = 1; k < group.segment_count; k++) { + int segment = group.first_segment + k; + if (k == out[j].segment) { + // Set the phase output for the active segment + envelopeBuffer[segment][j] = 1.f - out[j].phase; + } + else { + // Non active segments have 0.f output + envelopeBuffer[segment][j] = 0.f; + } } - envelopeBuffer[i][j] = out[j].value; + // First group segment gets the actual output + envelopeBuffer[group.first_segment][j] = out[j].value; } - - i += segment_generator[i].num_segments(); } } @@ -247,27 +277,35 @@ struct Stages : Module { configuration_changed[i] = true; } - void toggleLoop(int i) { - configuration_changed[i] = true; - configurations[i].loop = !configurations[i].loop; + void toggleLoop(int segment) { + configuration_changed[segment] = true; + configurations[segment].loop = !configurations[segment].loop; - // ensure that we're the only loop item in the group - if (configurations[i].loop) { - int group = groupBuilder.groupForSegment(i); + // ensure that we don't have too many looping segments in the group + if (configurations[segment].loop) { + int segment_count = 0; + for (int i = 0; i < groupBuilder.groupCount; i++) { + segment_count += groupBuilder.groups[i].segment_count; - // See how many loop items we have - int loopitems = 0; + if (segment_count > segment) { + GroupInfo &group = groupBuilder.groups[i]; - for (size_t j = 0; j < groupBuilder.groupSize[group]; j++) { - loopitems += configurations[group + j].loop ? 1 : 0; - } + // See how many loop items we have + int numberOfLoopsInGroup = 0; - // If we've got too many loop items, clear down to the one looping segment - if (loopitems > 2) { - for (size_t j = 0; j < groupBuilder.groupSize[group]; j++) { - configurations[group + j].loop = (group + (int)j) == i; + for (int j = 0; j < group.segment_count; j++) { + numberOfLoopsInGroup += configurations[group.first_segment + j].loop ? 1 : 0; + } + + // If we've got too many loop items, clear down to the one looping segment + if (numberOfLoopsInGroup > 2) { + for (int j = 0; j < group.segment_count; j++) { + configurations[group.first_segment + j].loop = (group.first_segment + j) == segment; + } + } + + break; } - loopitems = 1; } } } @@ -302,35 +340,34 @@ struct Stages : Module { } // Output - 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 * 8.f; - lights[ENVELOPE_LIGHTS + i].setBrightnessSmooth(envelope); + for (int i = 0; i < groupBuilder.groupCount; i++) { + GroupInfo &group = groupBuilder.groups[i]; - if (currentGroupSize <= 0) { - currentGroupSize = max(1, groupBuilder.groupSize[i]); - loopcount = 0; - } - currentGroupSize -= 1; + int numberOfLoopsInGroup = 0; + for (int j = 0; j < group.segment_count; j++) { + int segment = group.first_segment + j; - loopcount += configurations[i].loop ? 1 : 0; - float flashlevel = 1.f; + float envelope = envelopeBuffer[segment][blockIndex]; + outputs[ENVELOPE_OUTPUTS + segment].value = envelope * 8.f; + lights[ENVELOPE_LIGHTS + segment].setBrightnessSmooth(envelope); - if (configurations[i].loop && loopcount == 1) { - flashlevel = abs(sinf(2.0f * M_PI * lightOscillatorPhase)); - } - else if (configurations[i].loop && loopcount > 1) { - float advancedPhase = lightOscillatorPhase + 0.25f; - if (advancedPhase > 1.0f) - advancedPhase -= 1.0f; + numberOfLoopsInGroup += configurations[segment].loop ? 1 : 0; + float flashlevel = 1.f; - flashlevel = abs(sinf(2.0f * M_PI * advancedPhase)); - } + if (configurations[segment].loop && numberOfLoopsInGroup == 1) { + flashlevel = abs(sinf(2.0f * M_PI * lightOscillatorPhase)); + } + else if (configurations[segment].loop && numberOfLoopsInGroup > 1) { + float advancedPhase = lightOscillatorPhase + 0.25f; + if (advancedPhase > 1.0f) + advancedPhase -= 1.0f; + + flashlevel = abs(sinf(2.0f * M_PI * advancedPhase)); + } - lights[TYPE_LIGHTS + i * 2 + 0].setBrightness((configurations[i].type == 0 || configurations[i].type == 1) * flashlevel); - lights[TYPE_LIGHTS + i * 2 + 1].setBrightness((configurations[i].type == 1 || configurations[i].type == 2) * flashlevel); + lights[TYPE_LIGHTS + segment * 2 + 0].setBrightness((configurations[segment].type == 0 || configurations[segment].type == 1) * flashlevel); + lights[TYPE_LIGHTS + segment * 2 + 1].setBrightness((configurations[segment].type == 1 || configurations[segment].type == 2) * flashlevel); + } } } };