diff --git a/eurorack b/eurorack index e9aac2a..2bd5cdf 160000 --- a/eurorack +++ b/eurorack @@ -1 +1 @@ -Subproject commit e9aac2ab705e7b7e902b43a1bbd9e01d25cd42a2 +Subproject commit 2bd5cdf9598ebf720da33b60e0bc5e2dad4215d3 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)"> addModel(modelVeils); p->addModel(modelFrames); // p->addModel(modelPeaks); - // p->addModel(modelStages); p->addModel(modelMarbles); + p->addModel(modelStages); } diff --git a/src/Stages.cpp b/src/Stages.cpp index b7ca955..154eb07 100644 --- a/src/Stages.cpp +++ b/src/Stages.cpp @@ -1,6 +1,5 @@ #include "AudibleInstruments.hpp" #include "dsp/digital.hpp" -// #include "stages/chain_state.h" #include "stages/segment_generator.h" #include "stages/oscillator.h" @@ -9,6 +8,114 @@ static const int NUM_CHANNELS = 6; static const int BLOCK_SIZE = 8; +struct LongPressButton { + enum Events { + NO_PRESS, + SHORT_PRESS, + LONG_PRESS + }; + + float pressedTime = 0.f; + BooleanTrigger trigger; + + Events step(Param ¶m) { + Events 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 GroupInfo { + int first_segment = 0; + int segment_count = 0; + bool gated = false; +}; + +struct GroupBuilder { + + GroupInfo groups[NUM_CHANNELS]; + int groupCount = 0; + + bool buildGroups(std::vector *gateInputs, size_t first, size_t count) { + bool any_gates = false; + + GroupInfo nextGroups[NUM_CHANNELS]; + + 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 (!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++; + } + } + } + + bool changed = false; + + if (currentGroup != groupCount) { + changed = true; + groupCount = currentGroup; + } + + 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; + } + + groups[i].first_segment = nextGroups[i].first_segment; + groups[i].segment_count = nextGroups[i].segment_count; + groups[i].gated = nextGroups[i].gated; + } + + return changed; + } +}; struct Stages : Module { enum ParamIds { @@ -27,40 +134,41 @@ 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 }; stages::segment::Configuration configurations[NUM_CHANNELS]; + bool configuration_changed[NUM_CHANNELS]; stages::SegmentGenerator segment_generator[NUM_CHANNELS]; - stages::Oscillator oscillator[NUM_CHANNELS]; - // stages::ChainState chain_state; - // stages::Settings settings; + float lightOscillatorPhase; // Buttons - BooleanTrigger typeTriggers[NUM_CHANNELS]; - float pressedTime = 0.f; + LongPressButton typeButtons[NUM_CHANNELS]; // Buffers float envelopeBuffer[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); onReset(); } void onReset() override { 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; } + + lightOscillatorPhase = 0.f; + onSampleRateChange(); } json_t *toJson() override { @@ -94,49 +202,127 @@ struct Stages : Module { } } + void onSampleRateChange() override { + for (int i = 0; i < NUM_CHANNELS; i++) { + segment_generator[i].SetSampleRate(engineGetSampleRate()); + } + } + void stepBlock() { // Get parameters 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; } - // // 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 + 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; i++) { - bool led_state = segment_generator[i].Process(gate_flags[i], out, BLOCK_SIZE); + 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 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 (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; + } + } + + 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 < 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[group.first_segment], out, BLOCK_SIZE); + for (int j = 0; j < BLOCK_SIZE; j++) { - envelopeBuffer[i][j] = out[j].value; + 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; + } + } + // First group segment gets the actual output + envelopeBuffer[group.first_segment][j] = out[j].value; + } + } + } + + void toggleMode(int i) { + configurations[i].type = (stages::segment::Type) ((configurations[i].type + 1) % 3); + configuration_changed[i] = true; + } + + void toggleLoop(int segment) { + configuration_changed[segment] = true; + configurations[segment].loop = !configurations[segment].loop; + + // 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; + + if (segment_count > segment) { + GroupInfo &group = groupBuilder.groups[i]; + + // See how many loop items we have + int numberOfLoopsInGroup = 0; + + 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; + } } } } 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++) { - bool pressed = params[TYPE_PARAMS + i].value > 0.f; - if (pressed) { - pressedTime += engineGetSampleTime(); - } - // Check if released - if (typeTriggers[i].process(!pressed)) { - if (pressedTime >= 1.f) { - configurations[i].loop = !configurations[i].loop; - } - else { - configurations[i].type = (stages::segment::Type) ((configurations[i].type + 1) % 3); - } - pressedTime = 0.f; + 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; } } @@ -154,13 +340,34 @@ struct Stages : Module { } // Output - 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); + for (int i = 0; i < groupBuilder.groupCount; i++) { + GroupInfo &group = groupBuilder.groups[i]; + + int numberOfLoopsInGroup = 0; + for (int j = 0; j < group.segment_count; j++) { + int segment = group.first_segment + j; + + float envelope = envelopeBuffer[segment][blockIndex]; + outputs[ENVELOPE_OUTPUTS + segment].value = envelope * 8.f; + lights[ENVELOPE_LIGHTS + segment].setBrightnessSmooth(envelope); + + numberOfLoopsInGroup += configurations[segment].loop ? 1 : 0; + float flashlevel = 1.f; - 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 (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 + 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); + } } } }; @@ -214,12 +421,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));