BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

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:
Mbenchmarks/signal_benchmark.cpp | 17+++++++----------
Ares-src/Lag-src.svg | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/Lag.svg | 0
Msrc/CVD.cpp | 2+-
Asrc/Lag.cpp | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Lag.hpp | 48++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Test.cpp | 21++++++++++++++++++++-
Msrc/Test.hpp | 5+++--
Msrc/bogaudio.cpp | 2++
Msrc/dsp/signal.cpp | 52++++++++++++++++++++++++++++++++++++----------------
Msrc/dsp/signal.hpp | 28+++++++++++++++++++++-------
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;