diff --git a/Makefile b/Makefile
index cc7d1a3..f4e0c43 100644
--- a/Makefile
+++ b/Makefile
@@ -74,6 +74,11 @@ SOURCES += eurorack/peaks/pulse_processor/pulse_shaper.cc
SOURCES += eurorack/peaks/pulse_processor/pulse_randomizer.cc
SOURCES += eurorack/peaks/number_station/number_station.cc
+SOURCES += eurorack/stages/segment_generator.cc
+SOURCES += eurorack/stages/ramp_extractor.cc
+# SOURCES += eurorack/stages/chain_state.cc
+SOURCES += eurorack/stages/resources.cc
+
DISTRIBUTABLES += $(wildcard LICENSE*) res
RACK_DIR ?= ../..
diff --git a/eurorack b/eurorack
index 0bcabd2..e9aac2a 160000
--- a/eurorack
+++ b/eurorack
@@ -1 +1 @@
-Subproject commit 0bcabd2baa131576c38780972aee0ef1b30163f8
+Subproject commit e9aac2ab705e7b7e902b43a1bbd9e01d25cd42a2
diff --git a/res/Stages.svg b/res/Stages.svg
new file mode 100644
index 0000000..8e40025
--- /dev/null
+++ b/res/Stages.svg
@@ -0,0 +1,3042 @@
+
+
+
+
diff --git a/src/AudibleInstruments.cpp b/src/AudibleInstruments.cpp
index fe068f4..b6f9ccb 100644
--- a/src/AudibleInstruments.cpp
+++ b/src/AudibleInstruments.cpp
@@ -23,4 +23,5 @@ void init(rack::Plugin *p) {
p->addModel(modelVeils);
p->addModel(modelFrames);
// p->addModel(modelPeaks);
+ // p->addModel(modelStages);
}
diff --git a/src/AudibleInstruments.hpp b/src/AudibleInstruments.hpp
index 3568ad2..0383a0a 100644
--- a/src/AudibleInstruments.hpp
+++ b/src/AudibleInstruments.hpp
@@ -21,3 +21,4 @@ extern Model *modelBlinds;
extern Model *modelVeils;
extern Model *modelFrames;
extern Model *modelPeaks;
+extern Model *modelStages;
diff --git a/src/Stages.cpp b/src/Stages.cpp
new file mode 100644
index 0000000..b7ca955
--- /dev/null
+++ b/src/Stages.cpp
@@ -0,0 +1,233 @@
+#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 Stages : Module {
+ enum ParamIds {
+ ENUMS(SHAPE_PARAMS, NUM_CHANNELS),
+ ENUMS(TYPE_PARAMS, NUM_CHANNELS),
+ ENUMS(LEVEL_PARAMS, NUM_CHANNELS),
+ NUM_PARAMS
+ };
+ enum InputIds {
+ ENUMS(LEVEL_INPUTS, NUM_CHANNELS),
+ ENUMS(GATE_INPUTS, NUM_CHANNELS),
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ ENUMS(ENVELOPE_OUTPUTS, NUM_CHANNELS),
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ ENUMS(TYPE_LIGHTS, NUM_CHANNELS*2),
+ ENUMS(ENVELOPE_LIGHTS, NUM_CHANNELS),
+ NUM_LIGHTS
+ };
+
+ stages::segment::Configuration configurations[NUM_CHANNELS];
+ stages::SegmentGenerator segment_generator[NUM_CHANNELS];
+ stages::Oscillator oscillator[NUM_CHANNELS];
+ // stages::ChainState chain_state;
+ // stages::Settings settings;
+
+ // Buttons
+ BooleanTrigger typeTriggers[NUM_CHANNELS];
+ float pressedTime = 0.f;
+
+ // 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;
+
+ 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;
+ }
+ }
+
+ json_t *toJson() override {
+ json_t *rootJ = json_object();
+
+ json_t *configurationsJ = json_array();
+ for (int i = 0; i < NUM_CHANNELS; 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));
+ json_array_insert_new(configurationsJ, i, configurationJ);
+ }
+ json_object_set_new(rootJ, "configurations", configurationsJ);
+
+ return rootJ;
+ }
+
+ void fromJson(json_t *rootJ) override {
+ json_t *configurationsJ = json_object_get(rootJ, "configurations");
+ for (int i = 0; i < NUM_CHANNELS; i++) {
+ json_t *configurationJ = json_array_get(configurationsJ, i);
+ if (configurationJ) {
+ json_t *typeJ = json_object_get(configurationJ, "type");
+ if (typeJ)
+ configurations[i].type = (stages::segment::Type) json_integer_value(typeJ);
+
+ json_t *loopJ = json_object_get(configurationJ, "loop");
+ if (loopJ)
+ configurations[i].loop = json_boolean_value(loopJ);
+ }
+ }
+ }
+
+ 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);
+ 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;
+ // }
+
+ // 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 j = 0; j < BLOCK_SIZE; j++) {
+ envelopeBuffer[i][j] = out[j].value;
+ }
+ }
+ }
+
+ void step() override {
+ // 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;
+ }
+ }
+
+ // Input
+ for (int i = 0; i < NUM_CHANNELS; 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];
+ }
+
+ // Process block
+ if (++blockIndex >= BLOCK_SIZE) {
+ blockIndex = 0;
+ stepBlock();
+ }
+
+ // 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);
+
+ 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);
+ }
+ }
+};
+
+
+struct StagesWidget : ModuleWidget {
+ StagesWidget(Stages *module) : ModuleWidget(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));
+ }
+};
+
+
+Model *modelStages = Model::create("Audible Instruments", "Stages", "Segment Generator", FUNCTION_GENERATOR_TAG, ENVELOPE_GENERATOR_TAG);