commit 0a8d3750a690c1715bfeeb0a01f16ddb55f7dc57
parent 4a4fb03d33bfd9abbea207032ae8dc1c886e46f9
Author: Matt Demanett <matt@demanett.net>
Date: Wed, 30 May 2018 22:46:17 -0400
Simplify basic dsp slew limiter; add a more complicated one with variable shape; add Lag module; CVD fix.
Diffstat:
11 files changed, 389 insertions(+), 37 deletions(-)
diff --git a/benchmarks/signal_benchmark.cpp b/benchmarks/signal_benchmark.cpp
@@ -118,7 +118,7 @@ static void BM_PucketteEnvelopeFollower(benchmark::State& state) {
}
BENCHMARK(BM_PucketteEnvelopeFollower);
-static void BM_SlewLimiter_Fast(benchmark::State& state) {
+static void BM_SlewLimiter(benchmark::State& state) {
WhiteNoiseGenerator r;
const int n = 256;
float buf[n];
@@ -132,26 +132,23 @@ static void BM_SlewLimiter_Fast(benchmark::State& state) {
benchmark::DoNotOptimize(sl.next(buf[i]));
}
}
-BENCHMARK(BM_SlewLimiter_Fast);
+BENCHMARK(BM_SlewLimiter);
-static void BM_SlewLimiter_Slow(benchmark::State& state) {
+static void BM_ShapedSlewLimiter(benchmark::State& state) {
WhiteNoiseGenerator r;
const int n = 256;
float buf[n];
for (int i = 0; i < n; ++i) {
buf[i] = r.next();
}
- SlewLimiter sl(44100.0, 1.0f);
- int i = 0, j = 0;
+ ShapedSlewLimiter sl(44100.0, 1.0f, 0.5f);
+ int i = 0;
for (auto _ : state) {
i = ++i % n;
- if (i == 0) {
- j = ++j % n;
- }
- benchmark::DoNotOptimize(sl.next(buf[j]));
+ benchmark::DoNotOptimize(sl.next(buf[i]));
}
}
-BENCHMARK(BM_SlewLimiter_Slow);
+BENCHMARK(BM_ShapedSlewLimiter);
static void BM_Panner(benchmark::State& state) {
SineOscillator o(500.0, 100.0);
diff --git a/res-src/Lag-src.svg b/res-src/Lag-src.svg
@@ -0,0 +1,153 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="45"
+ height="380"
+ viewBox="0 0 45 380"
+>
+ <style>
+ text {
+ fill: #333;
+ font-family: 'Roboto', sans-serif;
+ font-weight: bold;
+ }
+ text.title {
+ font-family: 'Comfortaa', sans-serif;
+ font-weight: normal;
+ }
+ text.brand {
+ font-family: 'Audiowide', sans-serif;
+ font-weight: bold;
+ }
+ </style>
+
+ <defs>
+ <symbol id="knob" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <polyline points="-5,0 5,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-5 0,5" stroke-width="1" stroke="#00f" />
+ <circle cx="0" cy="0" r="14" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knob-smallest" viewBox="0 0 16px 16px">
+ <g transform="translate(8 8)">
+ <polyline points="-3,0 3,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-3 0,3" stroke-width="1" stroke="#00f" />
+ <circle r="7.5" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-time" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <text font-size="5.0pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-2 2)">0</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(17 0)" />
+ <text font-size="5.0pt" transform="rotate(60) translate(20 0) rotate(-60) translate(-2 2)">1</text>
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-scale" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <text font-size="5.0pt" transform="rotate(-135) translate(15 0) rotate(135) translate(-8 2)">0.1</text>
+ <polyline points="0,0 2,0" stroke-width="1" stroke="#333" transform="rotate(-135) translate(9.5 0)" />
+ <text font-size="5.0pt" transform="rotate(-90) translate(15 0) rotate(90) translate(-2 2)">1</text>
+ <polyline points="0,0 2,0" stroke-width="1" stroke="#333" transform="rotate(-90) translate(9.5 0)" />
+ <text font-size="5.0pt" transform="rotate(-45) translate(15 0) rotate(45) translate(-2 2)">10</text>
+ <polyline points="0,0 2,0" stroke-width="1" stroke="#333" transform="rotate(-45) translate(9.5 0)" />
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-shape" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <text font-size="5.0pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-10 2)">LOG</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(17 0)" />
+ <polyline points="0,0 3.5,0" stroke-width="1" stroke="#333" transform="rotate(-90) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(17 0)" />
+ <text font-size="5.0pt" transform="rotate(60) translate(20 0) rotate(-60) translate(-2.5 2)">EXP</text>
+ </g>
+ </symbol>
+
+ <symbol id="input" viewBox="0 0 24px 24px">
+ <g transform="translate(12 12)">
+ <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" />
+ <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#0f0" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="output" viewBox="0 0 24px 24px">
+ <g transform="translate(12 12)">
+ <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#f00" fill="#f00" />
+ <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#f00" fill="none" />
+ </g>
+ </symbol>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 44,1 44,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 44.5,0.5 44.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 45,0 45,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <!-- <polyline points="0,0 0,300" stroke="#0f0" stroke-width="1" fill="none" transform="translate(22.5 0)" /> -->
+
+ <g transform="rotate(-90) translate(-376 13)">
+ <text class="title" font-size="7pt" letter-spacing="2.5px">LAG</text>
+ <g transform="translate(0 12)">
+ <text class="brand" font-size="7pt" letter-spacing="2px">BGA</text>
+ <rect width="3.0" height="3" fill="#ddd" transform="translate(11.5 -5)" />
+ </g>
+ </g>
+
+ <g transform="translate(0 25)">
+ <text font-size="6pt" letter-spacing="2.0px" transform="translate(9.5 0)">TIME</text>
+ <use id="TIME_PARAM" xlink:href="#knob" transform="translate(0 3)" />
+ <use xlink:href="#knobguide-time" transform="translate(0 3)" />
+ <use id="TIME_SCALE_PARAM" xlink:href="#knob-smallest" transform="translate(14.5 59)" />
+ <use xlink:href="#knobguide-scale" transform="translate(0 44.5)" />
+ <g transform="translate(5.5 79)">
+ <rect width="34" height="39" rx="5" fill="#fafafa" />
+ <use id="TIME_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(11 35)">CV</text>
+ </g>
+ </g>
+
+ <g transform="translate(0 165)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(7.5 0)">SHAPE</text>
+ <use id="SHAPE_PARAM" xlink:href="#knob" transform="translate(0 3)" />
+ <use xlink:href="#knobguide-shape" transform="translate(0 3)" />
+ <g transform="translate(5.5 49)">
+ <rect width="34" height="38" rx="5" fill="#fafafa" />
+ <use id="SHAPE_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(11 35)">CV</text>
+ </g>
+ </g>
+
+ <g transform="translate(0 264)">
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 28)" />
+ <rect width="34" height="35" rx="5" fill="#fafafa" />
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(12.5 35)">IN</text>
+ </g>
+ <g transform="translate(5.5 41)">
+ <rect width="34" height="10" fill="#bbb" transform="translate(0 -3)" />
+ <rect width="34" height="35" rx="5" fill="#bbb" />
+ <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(5 0)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(8.3 32)">OUT</text>
+ </g>
+ </g>
+</svg>
diff --git a/res/Lag.svg b/res/Lag.svg
Binary files differ.
diff --git a/src/CVD.cpp b/src/CVD.cpp
@@ -8,7 +8,7 @@ void CVD::onSampleRateChange() {
void CVD::step() {
float time = params[TIME_PARAM].value;
if (inputs[TIME_INPUT].active) {
- time *= clamp(params[TIME_INPUT].value / 10.0f, 0.0f, 1.0f);
+ time *= clamp(inputs[TIME_INPUT].value / 10.0f, 0.0f, 1.0f);
}
switch ((int)params[TIME_SCALE_PARAM].value) {
case 0: {
diff --git a/src/Lag.cpp b/src/Lag.cpp
@@ -0,0 +1,98 @@
+
+#include "Lag.hpp"
+
+void Lag::onReset() {
+ _modulationStep = modulationSteps;
+}
+
+void Lag::step() {
+ if (!(inputs[IN_INPUT].active && outputs[OUT_OUTPUT].active)) {
+ return;
+ }
+
+ ++_modulationStep;
+ if (_modulationStep >= modulationSteps) {
+ _modulationStep = 0;
+
+ float time = params[TIME_PARAM].value;
+ if (inputs[TIME_INPUT].active) {
+ time *= clamp(inputs[TIME_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+ switch ((int)params[TIME_SCALE_PARAM].value) {
+ case 0: {
+ time /= 10.f;
+ break;
+ }
+ case 2: {
+ time *= 10.f;
+ break;
+ }
+ }
+ time *= 1000.0f;
+
+ float shape = params[SHAPE_PARAM].value;
+ if (inputs[SHAPE_INPUT].active) {
+ shape *= clamp(inputs[SHAPE_INPUT].value / 5.0f, -1.0f, 1.0f);
+ }
+ if (shape < 0.0) {
+ shape = 1.0f + shape;
+ shape = _slew.minShape + shape * (1.0f - _slew.minShape);
+ }
+ else {
+ shape *= _slew.maxShape - 1.0f;
+ shape += 1.0f;
+ }
+
+ _slew.setParams(engineGetSampleRate(), time, shape);
+ }
+
+ outputs[OUT_OUTPUT].value = _slew.next(inputs[IN_INPUT].value);
+}
+
+struct LagWidget : ModuleWidget {
+ LagWidget(Lag* module) : ModuleWidget(module) {
+ box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/Lag.svg")));
+ addChild(panel);
+ }
+
+ addChild(Widget::create<ScrewSilver>(Vec(0, 0)));
+ addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto timeParamPosition = Vec(8.0, 36.0);
+ auto timeScaleParamPosition = Vec(14.5, 84.0);
+ auto shapeParamPosition = Vec(8.0, 176.0);
+
+ auto timeInputPosition = Vec(10.5, 107.0);
+ auto shapeInputPosition = Vec(10.5, 217.0);
+ auto inInputPosition = Vec(10.5, 267.0);
+
+ auto outOutputPosition = Vec(10.5, 305.0);
+ // end generated by svg_widgets.rb
+
+ addParam(ParamWidget::create<Knob29>(timeParamPosition, module, Lag::TIME_PARAM, 0.0, 1.0, 0.5));
+ {
+ auto w = ParamWidget::create<Knob16>(timeScaleParamPosition, module, Lag::TIME_SCALE_PARAM, 0.0, 2.0, 1.0);
+ auto k = dynamic_cast<SVGKnob*>(w);
+ k->snap = true;
+ k->minAngle = -M_PI / 4.0f;
+ k->maxAngle = M_PI / 4.0f;
+ k->speed = 3.0;
+ addParam(w);
+ }
+ addParam(ParamWidget::create<Knob29>(shapeParamPosition, module, Lag::SHAPE_PARAM, -1.0, 1.0, 0.0));
+
+ addInput(Port::create<Port24>(timeInputPosition, Port::INPUT, module, Lag::TIME_INPUT));
+ addInput(Port::create<Port24>(shapeInputPosition, Port::INPUT, module, Lag::SHAPE_INPUT));
+ addInput(Port::create<Port24>(inInputPosition, Port::INPUT, module, Lag::IN_INPUT));
+
+ addOutput(Port::create<Port24>(outOutputPosition, Port::OUTPUT, module, Lag::OUT_OUTPUT));
+ }
+};
+
+Model* modelLag = Model::create<Lag, LagWidget>("Bogaudio", "Bogaudio-Lag", "Lag", SLEW_LIMITER_TAG, UTILITY_TAG);
diff --git a/src/Lag.hpp b/src/Lag.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "dsp/signal.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelLag;
+
+namespace bogaudio {
+
+struct Lag : Module {
+ enum ParamsIds {
+ TIME_PARAM,
+ TIME_SCALE_PARAM,
+ SHAPE_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ TIME_INPUT,
+ SHAPE_INPUT,
+ IN_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ NUM_LIGHTS
+ };
+
+ const int modulationSteps = 100;
+ int _modulationStep = 0;
+ ShapedSlewLimiter _slew;
+
+ Lag() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
+ onReset();
+ }
+
+ virtual void onReset() override;
+ virtual void step() override;
+};
+
+} // namespace bogaudio
diff --git a/src/Test.cpp b/src/Test.cpp
@@ -345,9 +345,28 @@ void Test::step() {
if (inputs[CV1_INPUT].active) {
ms *= clamp(inputs[CV2_INPUT].value, 0.0f, 10.0f) / 10.0f;
}
- _slew.setParams(engineGetSampleRate(), powf(ms, 2.0f) * 100.0f);
+ ms = powf(ms, 2.0f);
+ ms *= 10000.0f;
+ _slew.setParams(engineGetSampleRate(), ms);
outputs[OUT_OUTPUT].value = _slew.next(inputs[IN_INPUT].value);
+ float shape = params[PARAM2_PARAM].value;
+ if (inputs[CV2_INPUT].active) {
+ shape *= clamp(inputs[CV2_INPUT].value / 5.0f, -1.0f, 1.0f);
+ }
+ if (shape < 0.5) {
+ shape /= 0.5;
+ shape = _slew2.minShape + shape * (1.0f - _slew2.minShape);
+ }
+ else {
+ shape -= 0.5f;
+ shape /= 0.5f;
+ shape *= (_slew2.maxShape - 1.0f);
+ shape += 1.0f;
+ }
+ _slew2.setParams(engineGetSampleRate(), ms, shape);
+ outputs[OUT2_OUTPUT].value = _slew2.next(inputs[IN_INPUT].value);
+
#elif RMS
float sensitivity = params[PARAM2_PARAM].value;
if (inputs[CV2_INPUT].active) {
diff --git a/src/Test.hpp b/src/Test.hpp
@@ -22,8 +22,8 @@ extern Model* modelTest;
// #define FEEDBACK_PM 1
// #define EG 1
// #define TABLES 1
-// #define SLEW 1
-#define RMS 1
+#define SLEW 1
+// #define RMS 1
#include "pitch.hpp"
#ifdef LPF
@@ -186,6 +186,7 @@ struct Test : Module {
TablePhasor _table;
#elif SLEW
SlewLimiter _slew;
+ ShapedSlewLimiter _slew2;
#elif RMS
RootMeanSquare _rms;
PucketteEnvelopeFollower _pef;
diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp
@@ -26,6 +26,7 @@
#include "DGate.hpp"
#include "FlipFlop.hpp"
#include "Follow.hpp"
+#include "Lag.hpp"
#include "Manual.hpp"
#include "Mult.hpp"
#include "Noise.hpp"
@@ -87,6 +88,7 @@ void init(rack::Plugin *p) {
#ifdef EXPERIMENTAL
p->addModel(modelFlipFlop);
p->addModel(modelFollow);
+ p->addModel(modelLag);
#endif
p->addModel(modelManual);
#ifdef EXPERIMENTAL
diff --git a/src/dsp/signal.cpp b/src/dsp/signal.cpp
@@ -169,28 +169,48 @@ void PositiveZeroCrossing::reset() {
void SlewLimiter::setParams(float sampleRate, float milliseconds) {
assert(sampleRate > 0.0f);
assert(milliseconds >= 0.0f);
- _sampleRate = sampleRate;
- _milliseconds = milliseconds;
- _samples = (_milliseconds / 1000.0f) * _sampleRate;
+ _delta = range / ((milliseconds / 1000.0f) * sampleRate);
}
float SlewLimiter::next(float sample) {
- if (_samples < 2 || (sample > _current - 0.01f && sample < _current + 0.01f)) {
- _current = sample;
- return sample;
+ if (sample > _last) {
+ return _last = std::min(_last + _delta, sample);
}
- if (_target != sample) {
- _target = sample;
- _delta = (sample - _current) / _samples;
- _steps = _samples;
+ return _last = std::max(_last - _delta, sample);
+}
+
+
+void ShapedSlewLimiter::setParams(float sampleRate, float milliseconds, float shape) {
+ assert(sampleRate > 0.0f);
+ assert(milliseconds >= 0.0f);
+ assert(shape >= minShape);
+ assert(shape <= maxShape);
+ _sampleTime = 1.0f / sampleRate;
+ _time = milliseconds / 1000.0f;
+ _shapeExponent = (shape > -0.05f && shape < 0.05f) ? 0.0f : shape;
+ _inverseShapeExponent = 1.0f / _shapeExponent;
+}
+
+float ShapedSlewLimiter::next(float sample) {
+ float difference = sample - _last;
+ float ttg = abs(difference) / range;
+ if (_time < 0.0001f || ttg < 0.0001f) {
+ return _last = sample;
+ }
+ if (_shapeExponent != 0.0f) {
+ ttg = powf(ttg, _shapeExponent);
+ }
+ ttg *= _time;
+ ttg = std::max(0.0f, ttg - _sampleTime);
+ ttg /= _time;
+ if (_shapeExponent != 0.0f) {
+ ttg = powf(ttg, _inverseShapeExponent);
}
- else if (_steps <= 1) {
- _current = sample;
- return sample;
+ float y = abs(difference) - ttg * range;
+ if (difference < 0.0f) {
+ return _last = std::max(_last - y, sample);
}
- _current += _delta;
- --_steps;
- return _current;
+ return _last = std::min(_last + y, sample);
}
diff --git a/src/dsp/signal.hpp b/src/dsp/signal.hpp
@@ -105,13 +105,9 @@ struct PositiveZeroCrossing {
};
struct SlewLimiter {
- float _sampleRate;
- float _milliseconds;
- float _samples;
- float _current = 0.0f;
- float _target = 0.0f;
- int _steps = 0;
- float _delta = 0.0f;
+ const float range = 10.0f;
+ float _delta;
+ float _last = 0.0f;
SlewLimiter(float sampleRate = 1000.0f, float milliseconds = 1.0f) {
setParams(sampleRate, milliseconds);
@@ -121,6 +117,24 @@ struct SlewLimiter {
float next(float sample);
};
+struct ShapedSlewLimiter {
+ const float range = 10.0f;
+ const float minShape = 0.1f;
+ const float maxShape = 5.0f;
+ float _sampleTime;
+ float _time;
+ float _shapeExponent;
+ float _inverseShapeExponent;
+ float _last = 0.0;
+
+ ShapedSlewLimiter(float sampleRate = 1000.0f, float milliseconds = 1.0f, float shape = 1.0f) {
+ setParams(sampleRate, milliseconds, shape);
+ }
+
+ void setParams(float sampleRate, float milliseconds, float shape);
+ float next(float sample);
+};
+
struct CrossFader {
float _mix = 2.0f;
float _curve = 1.0f;