diff --git a/res/Rampage.svg b/res/Rampage.svg
new file mode 100644
index 0000000..4f39c55
--- /dev/null
+++ b/res/Rampage.svg
@@ -0,0 +1,2953 @@
+
+
+
+
diff --git a/src/Befaco.cpp b/src/Befaco.cpp
index bdab107..58fc153 100644
--- a/src/Befaco.cpp
+++ b/src/Befaco.cpp
@@ -9,7 +9,7 @@ void init(rack::Plugin *p) {
plugin->name = "Befaco";
plugin->homepageUrl = "https://github.com/VCVRack/Befaco";
createModel(plugin, "EvenVCO", "EvenVCO");
- // createModel(plugin, "Rampage", "Rampage");
+ createModel(plugin, "Rampage", "Rampage");
createModel(plugin, "ABC", "A*B+C");
createModel(plugin, "SpringReverb", "Spring Reverb");
createModel(plugin, "Mixer", "Mixer");
diff --git a/src/Rampage.cpp b/src/Rampage.cpp
index 8447cac..4fe5394 100644
--- a/src/Rampage.cpp
+++ b/src/Rampage.cpp
@@ -1,109 +1,168 @@
#include "Befaco.hpp"
+#include "dsp/digital.hpp"
struct Rampage : Module {
enum ParamIds {
RANGE_A_PARAM,
- SHAPE_A_PARAM,
- TRIGG_A_PARAM,
- RISE_A_PARAM,
- FALL_A_PARAM,
- CYCLE_A_PARAM,
RANGE_B_PARAM,
+ SHAPE_A_PARAM,
SHAPE_B_PARAM,
+ TRIGG_A_PARAM,
TRIGG_B_PARAM,
+ RISE_A_PARAM,
RISE_B_PARAM,
+ FALL_A_PARAM,
FALL_B_PARAM,
+ CYCLE_A_PARAM,
CYCLE_B_PARAM,
BALANCE_PARAM,
NUM_PARAMS
};
enum InputIds {
IN_A_INPUT,
- TRIGG_A_INPUT,
- RISE_CV_A_INPUT,
- FALL_CV_A_INPUT,
- EXP_CV_A_INPUT,
- CYCLE_A_INPUT,
IN_B_INPUT,
+ TRIGG_A_INPUT,
TRIGG_B_INPUT,
+ RISE_CV_A_INPUT,
RISE_CV_B_INPUT,
+ FALL_CV_A_INPUT,
FALL_CV_B_INPUT,
+ EXP_CV_A_INPUT,
EXP_CV_B_INPUT,
+ CYCLE_A_INPUT,
CYCLE_B_INPUT,
NUM_INPUTS
};
enum OutputIds {
RISING_A_OUTPUT,
- FALLING_A_OUTPUT,
- EOC_A_OUTPUT,
- OUT_A_OUTPUT,
RISING_B_OUTPUT,
+ FALLING_A_OUTPUT,
FALLING_B_OUTPUT,
+ EOC_A_OUTPUT,
EOC_B_OUTPUT,
+ OUT_A_OUTPUT,
OUT_B_OUTPUT,
COMPARATOR_OUTPUT,
MIN_OUTPUT,
MAX_OUTPUT,
- NUM_OUTPUTS,
+ COMPARATOR_LIGHT,
+ MIN_LIGHT,
+ MAX_LIGHT,
+ OUT_A_LIGHT,
+ OUT_B_LIGHT,
+ RISING_A_LIGHT,
+ RISING_B_LIGHT,
+ FALLING_A_LIGHT,
+ FALLING_B_LIGHT,
+ NUM_OUTPUTS
};
- float lastOutA = 0.0;
- float lastOutB = 0.0;
+ float out[2] = {};
+ bool gate[2] = {};
+ SchmittTrigger trigger[2];
+ PulseGenerator endOfCyclePulse[2];
- float comparatorLight = 0.0;
- float minLight = 0.0;
- float maxLight = 0.0;
- float outALight = 0.0;
- float outBLight = 0.0;
- float risingALight = 0.0;
- float fallingALight = 0.0;
- float risingBLight = 0.0;
- float fallingBLight = 0.0;
-
- Rampage() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
+ Rampage() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
+ for (int c = 0; c < 2; c++) {
+ trigger[c].setThresholds(0.0, 4.0);
+ }
+ }
void step();
};
-void Rampage::step() {
- // TEMP
- float outA = inputs[IN_A_INPUT].value;
- float outB = inputs[IN_B_INPUT].value;
- outputs[OUT_A_OUTPUT].value = outA;
- outputs[OUT_B_OUTPUT].value = outB;
- outALight = outA / 10.0;
- outBLight = outB / 10.0;
-
- // Slope detector
- const float slopeThreshold = 1.0; // volts per second
-#define SLOPE(out, lastOut, rising, falling, risingLight, fallingLight) { \
- float slope = (out - lastOut) / gSampleRate; \
- lastOut = out; \
- float rising = slope > slopeThreshold ? 10.0 : 0.0; \
- float falling = slope < -slopeThreshold ? 10.0 : 0.0; \
- outputs[rising].value = rising; \
- outputs[falling].value = falling; \
- risingLight = rising / 10.0; \
- fallingLight = falling / 10.0; \
+static float shapeDelta(float delta, float tau, float shape) {
+ float lin = sgnf(delta) * 10.0 / tau;
+ if (shape < 0.0) {
+ float log = sgnf(delta) * 40.0 / tau / (fabsf(delta) + 1.0);
+ return crossf(lin, log, -shape * 0.95);
+ }
+ else {
+ float exp = M_E * delta / tau;
+ return crossf(lin, exp, shape * 0.90);
+ }
}
- SLOPE(outA, lastOutA, RISING_A_OUTPUT, FALLING_A_OUTPUT, risingALight, fallingALight)
- SLOPE(outB, lastOutB, RISING_B_OUTPUT, FALLING_B_OUTPUT, risingBLight, fallingBLight)
- // Analog logic processor
+void Rampage::step() {
+ for (int c = 0; c < 2; c++) {
+ float in = inputs[IN_A_INPUT + c].value;
+ if (trigger[c].process(params[TRIGG_A_PARAM + c].value * 10.0 + inputs[TRIGG_A_INPUT + c].value)) {
+ gate[c] = true;
+ }
+ if (gate[c]) {
+ in = 10.0;
+ }
+
+ float shape = params[SHAPE_A_PARAM + c].value;
+ float delta = in - out[c];
+
+ // Integrator
+ float minTime;
+ switch ((int) params[RANGE_A_PARAM + c].value) {
+ case 0: minTime = 1e-2; break;
+ case 1: minTime = 1e-3; break;
+ default: minTime = 1e-1; break;
+ }
+
+ bool rising = false;
+ bool falling = false;
+
+ if (delta > 0) {
+ // Rise
+ float riseCv = params[RISE_A_PARAM + c].value - inputs[EXP_CV_A_INPUT + c].value + inputs[RISE_CV_A_INPUT + c].value / 10.0;
+ riseCv = clampf(riseCv, 0.0, 1.0);
+ float rise = minTime * powf(2.0, riseCv * 10.0);
+ out[c] += shapeDelta(delta, rise, shape) / gSampleRate;
+ rising = (in - out[c] > 1e-3);
+ if (!rising) {
+ gate[c] = false;
+ }
+ }
+ else if (delta < 0) {
+ // Fall
+ float fallCv = params[FALL_A_PARAM + c].value - inputs[EXP_CV_A_INPUT + c].value + inputs[FALL_CV_A_INPUT + c].value / 10.0;
+ fallCv = clampf(fallCv, 0.0, 1.0);
+ float fall = minTime * powf(2.0, fallCv * 10.0);
+ out[c] += shapeDelta(delta, fall, shape) / gSampleRate;
+ falling = (in - out[c] < -1e-3);
+ if (!falling) {
+ // End of cycle, check if we should turn the gate back on (cycle mode)
+ endOfCyclePulse[c].trigger(1e-3);
+ if (params[CYCLE_A_PARAM + c].value * 10.0 + inputs[CYCLE_A_INPUT + c].value >= 4.0) {
+ gate[c] = true;
+ }
+ }
+ }
+
+ if (!rising && !falling) {
+ out[c] = in;
+ }
+
+ outputs[RISING_A_OUTPUT + c].value = (rising ? 10.0 : 0.0);
+ outputs[FALLING_A_OUTPUT + c].value = (falling ? 10.0 : 0.0);
+ outputs[RISING_A_LIGHT + c].value = (rising ? 1.0 : 0.0);
+ outputs[FALLING_A_LIGHT + c].value = (falling ? 1.0 : 0.0);
+ outputs[EOC_A_OUTPUT + c].value = (endOfCyclePulse[c].process(1.0 / gSampleRate) ? 10.0 : 0.0);
+ outputs[OUT_A_OUTPUT + c].value = out[c];
+ outputs[OUT_A_LIGHT + c].value = out[c] / 10.0;
+ }
+
+ // Logic
float balance = params[BALANCE_PARAM].value;
- const float balancePower = 0.5;
- outA *= powf(1.0 - balance, balancePower);
- outB *= powf(balance, balancePower);
- float max = fmaxf(outA, outB);
- float min = fminf(outA, outB);
- float comparator = outB > outA ? 10.0 : 0.0;
- outputs[MAX_OUTPUT].value = max;
- outputs[MIN_OUTPUT].value = min;
- outputs[COMPARATOR_OUTPUT].value = comparator;
- maxLight = max / 10.0;
- minLight = min / 10.0;
- comparatorLight = comparator / 20.0;
+ float a = out[0];
+ float b = out[1];
+ if (balance < 0.5)
+ b *= 2.0 * balance;
+ else if (balance > 0.5)
+ a *= 2.0 * (1.0 - balance);
+ outputs[COMPARATOR_OUTPUT].value = (b > a ? 10.0 : 0.0);
+ outputs[MIN_OUTPUT].value = fminf(a, b);
+ outputs[MAX_OUTPUT].value = fmaxf(a, b);
+ // Lights
+ outputs[COMPARATOR_LIGHT].value = outputs[COMPARATOR_OUTPUT].value / 10.0;
+ outputs[MIN_LIGHT].value = outputs[MIN_OUTPUT].value / 10.0;
+ outputs[MAX_LIGHT].value = outputs[MAX_OUTPUT].value / 10.0;
}
@@ -113,9 +172,9 @@ RampageWidget::RampageWidget() {
box.size = Vec(15*18, 380);
{
- Panel *panel = new DarkPanel();
+ SVGPanel *panel = new SVGPanel();
panel->box.size = box.size;
- panel->backgroundImage = Image::load(assetPlugin(plugin, "res/Rampage.png"));
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/Rampage.svg")));
addChild(panel);
}
@@ -124,52 +183,52 @@ RampageWidget::RampageWidget() {
addChild(createScrew(Vec(15, 365)));
addChild(createScrew(Vec(box.size.x-30, 365)));
- addInput(createInput(Vec(14-3, 30-3), module, Rampage::IN_A_INPUT));
- addInput(createInput(Vec(52-3, 37-3), module, Rampage::TRIGG_A_INPUT));
- addInput(createInput(Vec(8-3, 268-3), module, Rampage::RISE_CV_A_INPUT));
- addInput(createInput(Vec(67-3, 268-3), module, Rampage::FALL_CV_A_INPUT));
- addInput(createInput(Vec(38-3, 297-3), module, Rampage::EXP_CV_A_INPUT));
- addInput(createInput(Vec(102-3, 290-3), module, Rampage::CYCLE_A_INPUT));
- addInput(createInput(Vec(230-3, 30-3), module, Rampage::IN_B_INPUT));
- addInput(createInput(Vec(192-3, 37-3), module, Rampage::TRIGG_B_INPUT));
- addInput(createInput(Vec(176-3, 268-3), module, Rampage::RISE_CV_B_INPUT));
- addInput(createInput(Vec(237-3, 268-3), module, Rampage::FALL_CV_B_INPUT));
- addInput(createInput(Vec(207-3, 297-3), module, Rampage::EXP_CV_B_INPUT));
- addInput(createInput(Vec(142-3, 290-3), module, Rampage::CYCLE_B_INPUT));
-
- addParam(createParam(Vec(96-2, 35-3), module, Rampage::RANGE_A_PARAM, 0.0, 2.0, 0.0));
+ addInput(createInput(Vec(11, 27), module, Rampage::IN_A_INPUT));
+ addInput(createInput(Vec(49, 34), module, Rampage::TRIGG_A_INPUT));
+ addInput(createInput(Vec(5, 265), module, Rampage::RISE_CV_A_INPUT));
+ addInput(createInput(Vec(64, 265), module, Rampage::FALL_CV_A_INPUT));
+ addInput(createInput(Vec(35, 294), module, Rampage::EXP_CV_A_INPUT));
+ addInput(createInput(Vec(99, 287), module, Rampage::CYCLE_A_INPUT));
+ addInput(createInput(Vec(227, 27), module, Rampage::IN_B_INPUT));
+ addInput(createInput(Vec(189, 34), module, Rampage::TRIGG_B_INPUT));
+ addInput(createInput(Vec(173, 265), module, Rampage::RISE_CV_B_INPUT));
+ addInput(createInput(Vec(234, 265), module, Rampage::FALL_CV_B_INPUT));
+ addInput(createInput(Vec(204, 294), module, Rampage::EXP_CV_B_INPUT));
+ addInput(createInput(Vec(139, 287), module, Rampage::CYCLE_B_INPUT));
+
+ addParam(createParam(Vec(94, 32), module, Rampage::RANGE_A_PARAM, 0.0, 2.0, 0.0));
addParam(createParam(Vec(27, 90), module, Rampage::SHAPE_A_PARAM, -1.0, 1.0, 0.0));
addParam(createParam(Vec(72, 82), module, Rampage::TRIGG_A_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(21-5, 140-5), module, Rampage::RISE_A_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(62-5, 140-5), module, Rampage::FALL_A_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(101, 240-2), module, Rampage::CYCLE_A_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(149-2, 35-3), module, Rampage::RANGE_B_PARAM, 0.0, 2.0, 0.0));
+ addParam(createParam(Vec(16, 135), module, Rampage::RISE_A_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(57, 135), module, Rampage::FALL_A_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(101, 238), module, Rampage::CYCLE_A_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(147, 32), module, Rampage::RANGE_B_PARAM, 0.0, 2.0, 0.0));
addParam(createParam(Vec(217, 90), module, Rampage::SHAPE_B_PARAM, -1.0, 1.0, 0.0));
addParam(createParam(Vec(170, 82), module, Rampage::TRIGG_B_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(202-5, 140-5), module, Rampage::RISE_B_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(243-5, 140-5), module, Rampage::FALL_B_PARAM, 0.0, 1.0, 0.0));
- addParam(createParam(Vec(141, 240-2), module, Rampage::CYCLE_B_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(197, 135), module, Rampage::RISE_B_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(238, 135), module, Rampage::FALL_B_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam(Vec(141, 238), module, Rampage::CYCLE_B_PARAM, 0.0, 1.0, 0.0));
addParam(createParam(Vec(117, 76), module, Rampage::BALANCE_PARAM, 0.0, 1.0, 0.5));
- addOutput(createOutput(Vec(8-3, 326-3), module, Rampage::RISING_A_OUTPUT));
- addOutput(createOutput(Vec(67-3, 326-3), module, Rampage::FALLING_A_OUTPUT));
- addOutput(createOutput(Vec(104-3, 326-3), module, Rampage::EOC_A_OUTPUT));
- addOutput(createOutput(Vec(102-3, 195-3), module, Rampage::OUT_A_OUTPUT));
- addOutput(createOutput(Vec(176-3, 326-3), module, Rampage::RISING_B_OUTPUT));
- addOutput(createOutput(Vec(237-3, 326-3), module, Rampage::FALLING_B_OUTPUT));
- addOutput(createOutput(Vec(140-3, 326-3), module, Rampage::EOC_B_OUTPUT));
- addOutput(createOutput(Vec(142-3, 195-3), module, Rampage::OUT_B_OUTPUT));
- addOutput(createOutput(Vec(122-3, 133-3), module, Rampage::COMPARATOR_OUTPUT));
- addOutput(createOutput(Vec(89-3, 156-3), module, Rampage::MIN_OUTPUT));
- addOutput(createOutput(Vec(155-3, 156-3), module, Rampage::MAX_OUTPUT));
-
- addChild(createValueLight>(Vec(131, 167), &module->comparatorLight));
- addChild(createValueLight>(Vec(122, 174), &module->minLight));
- addChild(createValueLight>(Vec(140, 174), &module->maxLight));
- addChild(createValueLight>(Vec(125, 185), &module->outALight));
- addChild(createValueLight>(Vec(137, 185), &module->outBLight));
- addChild(createValueLight>(Vec(17, 312), &module->risingALight));
- addChild(createValueLight>(Vec(77, 312), &module->fallingALight));
- addChild(createValueLight>(Vec(186, 312), &module->risingBLight));
- addChild(createValueLight>(Vec(245, 312), &module->fallingBLight));
+ addOutput(createOutput(Vec(5, 323), module, Rampage::RISING_A_OUTPUT));
+ addOutput(createOutput(Vec(65, 323), module, Rampage::FALLING_A_OUTPUT));
+ addOutput(createOutput(Vec(101, 323), module, Rampage::EOC_A_OUTPUT));
+ addOutput(createOutput(Vec(99, 192), module, Rampage::OUT_A_OUTPUT));
+ addOutput(createOutput(Vec(173, 323), module, Rampage::RISING_B_OUTPUT));
+ addOutput(createOutput(Vec(234, 323), module, Rampage::FALLING_B_OUTPUT));
+ addOutput(createOutput(Vec(137, 323), module, Rampage::EOC_B_OUTPUT));
+ addOutput(createOutput(Vec(139, 192), module, Rampage::OUT_B_OUTPUT));
+ addOutput(createOutput(Vec(119, 130), module, Rampage::COMPARATOR_OUTPUT));
+ addOutput(createOutput(Vec(86, 153), module, Rampage::MIN_OUTPUT));
+ addOutput(createOutput(Vec(152, 153), module, Rampage::MAX_OUTPUT));
+
+ addChild(createValueLight>(Vec(131, 167), &module->outputs[Rampage::COMPARATOR_LIGHT].value));
+ addChild(createValueLight>(Vec(122, 174), &module->outputs[Rampage::MIN_LIGHT].value));
+ addChild(createValueLight>(Vec(140, 174), &module->outputs[Rampage::MAX_LIGHT].value));
+ addChild(createValueLight>(Vec(125, 185), &module->outputs[Rampage::OUT_A_LIGHT].value));
+ addChild(createValueLight>(Vec(137, 185), &module->outputs[Rampage::OUT_B_LIGHT].value));
+ addChild(createValueLight>(Vec(17, 312), &module->outputs[Rampage::RISING_A_LIGHT].value));
+ addChild(createValueLight>(Vec(77, 312), &module->outputs[Rampage::FALLING_A_LIGHT].value));
+ addChild(createValueLight>(Vec(186, 312), &module->outputs[Rampage::RISING_B_LIGHT].value));
+ addChild(createValueLight>(Vec(246, 312), &module->outputs[Rampage::FALLING_B_LIGHT].value));
}