BogaudioModules

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

commit d204c00c6b918a84d3d54aa4d1419dd8e6e17378
parent 4d22573c18ac69771ff4a3bb8123807b8aceab2a
Author: Matt Demanett <matt@demanett.net>
Date:   Thu,  9 Jul 2020 01:26:40 -0400

FOLLOW: general fixup, with new better EF model; fixed tooltips; changed SCALE to GAIN.  PEQ6XF, PEQ14XF, PEQ14XV: switched to new EF model; performance improvements.  Changed all RMS calculations to be slightly faster and to ignore DC offsets.

Diffstat:
Mbenchmarks/filter_benchmark.cpp | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbenchmarks/signal_benchmark.cpp | 71+----------------------------------------------------------------------
Mres-src/Follow-src.svg | 10+++++-----
Mres/Follow.svg | 0
Msrc/Blank3.hpp | 2+-
Msrc/Blank6.hpp | 2+-
Msrc/Follow.cpp | 41+++++++++++++++--------------------------
Msrc/Follow.hpp | 26++++++++++++++++----------
Msrc/Lmtr.hpp | 1+
Msrc/Mono.hpp | 1+
Msrc/Nsgt.hpp | 1+
Msrc/PEQ14XF.cpp | 33++++-----------------------------
Msrc/PEQ14XF.hpp | 5++---
Msrc/PEQ14XR.cpp | 21++++-----------------
Msrc/PEQ14XR.hpp | 4++--
Msrc/PEQ14XV.cpp | 36+++++-------------------------------
Msrc/PEQ14XV.hpp | 8++------
Msrc/PEQ6XF.cpp | 27++++-----------------------
Msrc/PEQ6XF.hpp | 5++---
Msrc/Pressor.hpp | 1+
Msrc/Test.cpp | 14+++++++++++++-
Msrc/Test.hpp | 12++++++++++--
Msrc/VCAmp.hpp | 1+
Msrc/VU.hpp | 1+
Msrc/dsp/filters/experiments.cpp | 8--------
Msrc/dsp/filters/experiments.hpp | 7-------
Asrc/dsp/filters/utility.cpp | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/utility.hpp | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/dsp/signal.cpp | 26++------------------------
Msrc/dsp/signal.hpp | 23+----------------------
Asrc/follower_base.cpp | 27+++++++++++++++++++++++++++
Asrc/follower_base.hpp | 20++++++++++++++++++++
Msrc/mixer.hpp | 1+
Msrc/parametric_eq.hpp | 12+++++-------
34 files changed, 338 insertions(+), 298 deletions(-)

diff --git a/benchmarks/filter_benchmark.cpp b/benchmarks/filter_benchmark.cpp @@ -4,6 +4,8 @@ #include "dsp/noise.hpp" #include "dsp/filters/multimode.hpp" #include "dsp/filters/resample.hpp" +#include "dsp/filters/utility.hpp" +#include "dsp/oscillator.hpp" // #include "dsp/decimator.hpp" // rack using namespace bogaudio::dsp; @@ -192,3 +194,88 @@ static void BM_Filter_BiquadBank16_2Pole(benchmark::State& state) { } } BENCHMARK(BM_Filter_BiquadBank16_2Pole); + +static void BM_Filter_RMS_Short(benchmark::State& state) { + SineOscillator o(500.0, 100.0); + const int n = 256; + float buf[n]; + for (int i = 0; i < n; ++i) { + buf[i] = o.next() * 5.0f; + } + RootMeanSquare rms(44100.0, 0.05); + int i = 0; + for (auto _ : state) { + i = ++i % n; + rms.next(buf[i]); + } +} +BENCHMARK(BM_Filter_RMS_Short); + +static void BM_Filter_RMS_Long(benchmark::State& state) { + SineOscillator o(500.0, 100.0); + const int n = 256; + float buf[n]; + for (int i = 0; i < n; ++i) { + buf[i] = o.next() * 5.0f; + } + RootMeanSquare rms(44100.0, 1.0); + int i = 0; + for (auto _ : state) { + i = ++i % n; + rms.next(buf[i]); + } +} +BENCHMARK(BM_Filter_RMS_Long); + +static void BM_Filter_RMS_Modulating(benchmark::State& state) { + SineOscillator o(500.0, 100.0); + const int n = 256; + float buf[n]; + for (int i = 0; i < n; ++i) { + buf[i] = o.next() * 5.0f; + } + std::minstd_rand g; + std::uniform_real_distribution<float> r(0.0f, 1.0f); + RootMeanSquare rms(44100.0, 1.0); + int i = 0; + for (auto _ : state) { + i = ++i % n; + if (i % 50 == 0) { + rms.setSensitivity(r(g)); + } + rms.next(buf[i]); + } +} +BENCHMARK(BM_Filter_RMS_Modulating); + +static void BM_Filter_PureRMS_Short(benchmark::State& state) { + SineOscillator o(500.0, 100.0); + const int n = 256; + float buf[n]; + for (int i = 0; i < n; ++i) { + buf[i] = o.next() * 5.0f; + } + PureRootMeanSquare rms(44100.0, 0.05); + int i = 0; + for (auto _ : state) { + i = ++i % n; + rms.next(buf[i]); + } +} +BENCHMARK(BM_Filter_PureRMS_Short); + +static void BM_Filter_PucketteEnvelopeFollower(benchmark::State& state) { + SineOscillator o(500.0, 100.0); + const int n = 256; + float buf[n]; + for (int i = 0; i < n; ++i) { + buf[i] = o.next() * 5.0f; + } + PucketteEnvelopeFollower pef; + int i = 0; + for (auto _ : state) { + i = ++i % n; + pef.next(buf[i]); + } +} +BENCHMARK(BM_Filter_PucketteEnvelopeFollower); diff --git a/benchmarks/signal_benchmark.cpp b/benchmarks/signal_benchmark.cpp @@ -3,8 +3,8 @@ #include <benchmark/benchmark.h> -#include "dsp/oscillator.hpp" #include "dsp/noise.hpp" +#include "dsp/oscillator.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; @@ -49,75 +49,6 @@ static void BM_Signal_Amplifier(benchmark::State& state) { } BENCHMARK(BM_Signal_Amplifier); -static void BM_Signal_RMS_Short(benchmark::State& state) { - SineOscillator o(500.0, 100.0); - const int n = 256; - float buf[n]; - for (int i = 0; i < n; ++i) { - buf[i] = o.next() * 5.0f; - } - RootMeanSquare rms(44100.0, 0.05); - int i = 0; - for (auto _ : state) { - i = ++i % n; - benchmark::DoNotOptimize(rms.next(buf[i])); - } -} -BENCHMARK(BM_Signal_RMS_Short); - -static void BM_Signal_RMS_Long(benchmark::State& state) { - SineOscillator o(500.0, 100.0); - const int n = 256; - float buf[n]; - for (int i = 0; i < n; ++i) { - buf[i] = o.next() * 5.0f; - } - RootMeanSquare rms(44100.0, 1.0); - int i = 0; - for (auto _ : state) { - i = ++i % n; - benchmark::DoNotOptimize(rms.next(buf[i])); - } -} -BENCHMARK(BM_Signal_RMS_Long); - -static void BM_Signal_RMS_Modulating(benchmark::State& state) { - SineOscillator o(500.0, 100.0); - const int n = 256; - float buf[n]; - for (int i = 0; i < n; ++i) { - buf[i] = o.next() * 5.0f; - } - std::minstd_rand g; - std::uniform_real_distribution<float> r(0.0f, 1.0f); - RootMeanSquare rms(44100.0, 1.0); - int i = 0; - for (auto _ : state) { - i = ++i % n; - if (i % 50 == 0) { - rms.setSensitivity(r(g)); - } - benchmark::DoNotOptimize(rms.next(buf[i])); - } -} -BENCHMARK(BM_Signal_RMS_Modulating); - -static void BM_Signal_PucketteEnvelopeFollower(benchmark::State& state) { - SineOscillator o(500.0, 100.0); - const int n = 256; - float buf[n]; - for (int i = 0; i < n; ++i) { - buf[i] = o.next() * 5.0f; - } - PucketteEnvelopeFollower pef; - int i = 0; - for (auto _ : state) { - i = ++i % n; - benchmark::DoNotOptimize(pef.next(buf[i])); - } -} -BENCHMARK(BM_Signal_PucketteEnvelopeFollower); - static void BM_Signal_SlewLimiter(benchmark::State& state) { WhiteNoiseGenerator r; const int n = 256; diff --git a/res-src/Follow-src.svg b/res-src/Follow-src.svg @@ -49,7 +49,7 @@ <symbol id="knobguide-scale" 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> + <text font-size="8pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-2 3)">-</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)" /> @@ -59,7 +59,7 @@ <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> + <text font-size="6pt" transform="rotate(60) translate(20 0) rotate(-60) translate(-2 2)">+</text> </g> </symbol> @@ -105,12 +105,12 @@ </g> <g transform="translate(0 131)"> - <text font-size="6pt" letter-spacing="1px" transform="translate(8 0)">SCALE</text> - <use id="SCALE_PARAM" xlink:href="#knob" transform="translate(0 3)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(11.5 0)">GAIN</text> + <use id="GAIN_PARAM" xlink:href="#knob" transform="translate(0 3)" /> <use xlink:href="#knobguide-scale" transform="scale(1) translate(0 3)" /> <g transform="translate(5.5 49)"> <rect width="34" height="38" rx="5" fill="#fafafa" /> - <use id="SCALE_INPUT" xlink:href="#input" transform="translate(5 3)" /> + <use id="GAIN_INPUT" xlink:href="#input" transform="translate(5 3)" /> <text font-size="5pt" letter-spacing="2px" transform="translate(11 35)">CV</text> </g> </g> diff --git a/res/Follow.svg b/res/Follow.svg Binary files differ. diff --git a/src/Blank3.hpp b/src/Blank3.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/signal.hpp" +#include "dsp/filters/utility.hpp" using namespace bogaudio::dsp; diff --git a/src/Blank6.hpp b/src/Blank6.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/signal.hpp" +#include "dsp/filters/utility.hpp" using namespace bogaudio::dsp; diff --git a/src/Follow.cpp b/src/Follow.cpp @@ -1,12 +1,6 @@ #include "Follow.hpp" -void Follow::sampleRateChange() { - for (int c = 0; c < _channels; ++c) { - _rms[c]->setSampleRate(APP->engine->getSampleRate()); - } -} - bool Follow::active() { return inputs[IN_INPUT].isConnected() && outputs[OUT_OUTPUT].isConnected(); } @@ -16,29 +10,24 @@ int Follow::channels() { } void Follow::addChannel(int c) { - _rms[c] = new RootMeanSquare(1000.0f, 1.0f, 500.0f); - _rms[c]->setSampleRate(APP->engine->getSampleRate()); + _engines[c] = new Engine; } void Follow::removeChannel(int c) { - delete _rms[c]; - _rms[c] = NULL; + delete _engines[c]; + _engines[c] = NULL; } -void Follow::processChannel(const ProcessArgs& args, int c) { - float response = params[RESPONSE_PARAM].getValue(); - if (inputs[RESPONSE_INPUT].isConnected()) { - response *= clamp(inputs[RESPONSE_INPUT].getPolyVoltage(c) / 10.f, 0.0f, 1.0f); - } - _rms[c]->setSensitivity(response); - - float scale = params[SCALE_PARAM].getValue(); - if (inputs[SCALE_INPUT].isConnected()) { - scale *= clamp(inputs[SCALE_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); - } +void Follow::modulateChannel(int c) { + Engine& e = *_engines[c]; + e.follower.setParams(APP->engine->getSampleRate(), sensitivity(params[RESPONSE_PARAM], &inputs[RESPONSE_INPUT], c)); + e.gain.setLevel(gain(params[GAIN_PARAM], &inputs[GAIN_INPUT], c)); +} +void Follow::processChannel(const ProcessArgs& args, int c) { + Engine& e = *_engines[c]; outputs[OUT_OUTPUT].setChannels(_channels); - outputs[OUT_OUTPUT].setVoltage(scale * 2.0f * _rms[c]->next(inputs[IN_INPUT].getVoltage(c)), c); + outputs[OUT_OUTPUT].setVoltage(_saturator.next(e.gain.next(e.follower.next(inputs[IN_INPUT].getVoltage(c)))), c); } struct FollowWidget : ModuleWidget { @@ -60,20 +49,20 @@ struct FollowWidget : ModuleWidget { // generated by svg_widgets.rb auto responseParamPosition = Vec(8.0, 36.0); - auto scaleParamPosition = Vec(8.0, 142.0); + auto gainParamPosition = Vec(8.0, 142.0); auto responseInputPosition = Vec(10.5, 77.0); - auto scaleInputPosition = Vec(10.5, 183.0); + auto gainInputPosition = Vec(10.5, 183.0); auto inInputPosition = Vec(10.5, 233.0); auto outOutputPosition = Vec(10.5, 271.0); // end generated by svg_widgets.rb addParam(createParam<Knob29>(responseParamPosition, module, Follow::RESPONSE_PARAM)); - addParam(createParam<Knob29>(scaleParamPosition, module, Follow::SCALE_PARAM)); + addParam(createParam<Knob29>(gainParamPosition, module, Follow::GAIN_PARAM)); addInput(createInput<Port24>(responseInputPosition, module, Follow::RESPONSE_INPUT)); - addInput(createInput<Port24>(scaleInputPosition, module, Follow::SCALE_INPUT)); + addInput(createInput<Port24>(gainInputPosition, module, Follow::GAIN_INPUT)); addInput(createInput<Port24>(inInputPosition, module, Follow::IN_INPUT)); addOutput(createOutput<Port24>(outOutputPosition, module, Follow::OUT_OUTPUT)); diff --git a/src/Follow.hpp b/src/Follow.hpp @@ -1,7 +1,9 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/signal.hpp" +#include "follower_base.hpp" +#include "dsp/filters/equalizer.hpp" +#include "dsp/filters/utility.hpp" using namespace bogaudio::dsp; @@ -9,16 +11,16 @@ extern Model* modelFollow; namespace bogaudio { -struct Follow : BGModule { +struct Follow : FollowerBase { enum ParamsIds { RESPONSE_PARAM, - SCALE_PARAM, + GAIN_PARAM, NUM_PARAMS }; enum InputsIds { RESPONSE_INPUT, - SCALE_INPUT, + GAIN_INPUT, IN_INPUT, NUM_INPUTS }; @@ -28,21 +30,25 @@ struct Follow : BGModule { NUM_OUTPUTS }; - RootMeanSquare* _rms[maxChannels] {}; + struct Engine { + EnvelopeFollower follower; + Amplifier gain; + }; + + Engine* _engines[maxChannels] {}; + Saturator _saturator; Follow() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - configParam(RESPONSE_PARAM, 0.0f, 1.0f, 0.3f, "Sensitivity", "%", 0.0f, 100.0f); - configParam(SCALE_PARAM, 0.0f, 1.0f, 1.0f, "Scale", "%", 0.0f, 100.0f); - - sampleRateChange(); + configParam(RESPONSE_PARAM, 0.0f, 1.0f, 0.3f, "Smoothing", "%", 0.0f, 100.0f); + configParam<EFGainParamQuantity>(GAIN_PARAM, -1.0f, 1.0f, 0.0f, "Gain", " dB"); } bool active() override; int channels() override; void addChannel(int c) override; void removeChannel(int c) override; - void sampleRateChange() override; + void modulateChannel(int c) override; void processChannel(const ProcessArgs& args, int c) override; }; diff --git a/src/Lmtr.hpp b/src/Lmtr.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/Mono.hpp b/src/Mono.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/Nsgt.hpp b/src/Nsgt.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/PEQ14XF.cpp b/src/PEQ14XF.cpp @@ -1,21 +1,8 @@ #include "PEQ14XF.hpp" -void PEQ14XF::sampleRateChange() { - float sr = APP->engine->getSampleRate(); - for (int c = 0; c < _channels; ++c) { - for (int i = 0; i < 14; ++i) { - _engines[c]->efs[i].setSampleRate(sr); - } - } -} - void PEQ14XF::addChannel(int c) { _engines[c] = new Engine(); - float sr = APP->engine->getSampleRate(); - for (int i = 0; i < 14; ++i) { - _engines[c]->efs[i].setSampleRate(sr); - } } void PEQ14XF::removeChannel(int c) { @@ -26,28 +13,16 @@ void PEQ14XF::removeChannel(int c) { void PEQ14XF::modulateChannel(int c) { Engine& e = *_engines[c]; - float response = params[DAMP_PARAM].getValue(); - if (inputs[DAMP_INPUT].isConnected()) { - response *= clamp(inputs[DAMP_INPUT].getPolyVoltage(c) / 10.f, 0.0f, 1.0f); - } + float sr = APP->engine->getSampleRate(); + float response = sensitivity(params[DAMP_PARAM], &inputs[DAMP_INPUT], c); if (e.response != response) { e.response = response; for (int i = 0; i < 14; ++i) { - _engines[c]->efs[i].setSensitivity(e.response); + e.efs[i].setParams(sr, e.response); } } - float db = clamp(params[GAIN_PARAM].getValue(), -1.0f, 1.0f); - if (inputs[GAIN_INPUT].isConnected()) { - db *= clamp(inputs[GAIN_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); - } - if (db < 0.0f) { - db = -db * efGainMinDecibels; - } - else { - db *= std::min(12.0f, efGainMaxDecibels); - } - e.gain.setLevel(db); + e.gain.setLevel(gain(params[GAIN_PARAM], &inputs[GAIN_INPUT], c)); } void PEQ14XF::processAll(const ProcessArgs& args) { diff --git a/src/PEQ14XF.hpp b/src/PEQ14XF.hpp @@ -36,7 +36,7 @@ struct PEQ14XF : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa }; struct Engine { - RootMeanSquare efs[14]; + EnvelopeFollower efs[14]; float response = -1.0f; Amplifier gain; }; @@ -47,14 +47,13 @@ struct PEQ14XF : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa PEQ14XF() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower response", "%", 0.0f, 100.0f); + configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower smoothing", "%", 0.0f, 100.0f); configParam<EFGainParamQuantity>(GAIN_PARAM, -1.0f, 1.0f, 0.0f, "Envelope follower gain", " dB"); setBaseModelPredicate([](Model* m) { return m == modelPEQ14 || m == modelPEQ14XF || m == modelPEQ14XR || m == modelPEQ14XV; }); setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XF || m == modelPEQ14XR || m == modelPEQ14XV; }); } - void sampleRateChange() override; void addChannel(int c) override; void removeChannel(int c) override; void modulateChannel(int c) override; diff --git a/src/PEQ14XR.cpp b/src/PEQ14XR.cpp @@ -4,7 +4,6 @@ void PEQ14XR::Engine::setSampleRate(float sr) { for (int i = 0; i < 14; ++i) { oscillators[i].setSampleRate(sr); - efs[i].setSampleRate(sr); } } @@ -28,28 +27,16 @@ void PEQ14XR::removeChannel(int c) { void PEQ14XR::modulateChannel(int c) { Engine& e = *_engines[c]; - float response = params[DAMP_PARAM].getValue(); - if (inputs[DAMP_INPUT].isConnected()) { - response *= clamp(inputs[DAMP_INPUT].getPolyVoltage(c) / 10.f, 0.0f, 1.0f); - } + float sr = APP->engine->getSampleRate(); + float response = sensitivity(params[DAMP_PARAM], &inputs[DAMP_INPUT], c); if (e.response != response) { e.response = response; for (int i = 0; i < 14; ++i) { - _engines[c]->efs[i].setSensitivity(e.response); + e.efs[i].setParams(sr, e.response); } } - float db = clamp(params[GAIN_PARAM].getValue(), -1.0f, 1.0f); - if (inputs[GAIN_INPUT].isConnected()) { - db *= clamp(inputs[GAIN_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); - } - if (db < 0.0f) { - db = -db * efGainMinDecibels; - } - else { - db *= std::min(12.0f, efGainMaxDecibels); - } - e.efGain.setLevel(db); + e.efGain.setLevel(gain(params[GAIN_PARAM], &inputs[GAIN_INPUT], c)); } void PEQ14XR::processAlways(const ProcessArgs& args) { diff --git a/src/PEQ14XR.hpp b/src/PEQ14XR.hpp @@ -40,7 +40,7 @@ struct PEQ14XR : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa BandOscillator oscillators[14]; Amplifier amplifiers[14]; - RootMeanSquare efs[14]; + EnvelopeFollower efs[14]; float response = -1.0f; Amplifier efGain; @@ -53,7 +53,7 @@ struct PEQ14XR : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa PEQ14XR() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower response", "%", 0.0f, 100.0f); + configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower smoothing", "%", 0.0f, 100.0f); configParam<EFGainParamQuantity>(GAIN_PARAM, -1.0f, 1.0f, 0.0f, "Envelope follower gain", " dB"); setBaseModelPredicate([](Model* m) { return m == modelPEQ14 || m == modelPEQ14XF || m == modelPEQ14XR || m == modelPEQ14XV; }); diff --git a/src/PEQ14XV.cpp b/src/PEQ14XV.cpp @@ -16,22 +16,8 @@ PEQ14XV::Engine::~Engine() { } } -void PEQ14XV::Engine::setSampleRate(float sr) { - for (int i = 0; i < 14; ++i) { - efs[i].setSampleRate(sr); - } -} - -void PEQ14XV::sampleRateChange() { - _sampleRate = APP->engine->getSampleRate(); - for (int c = 0; c < _channels; ++c) { - _engines[c]->setSampleRate(_sampleRate); - } -} - void PEQ14XV::addChannel(int c) { _engines[c] = new Engine(); - _engines[c]->setSampleRate(APP->engine->getSampleRate()); } void PEQ14XV::removeChannel(int c) { @@ -56,28 +42,16 @@ void PEQ14XV::modulate() { void PEQ14XV::modulateChannel(int c) { Engine& e = *_engines[c]; - float response = params[EF_DAMP_PARAM].getValue(); - if (inputs[EF_DAMP_INPUT].isConnected()) { - response *= clamp(inputs[EF_DAMP_INPUT].getPolyVoltage(c) / 10.f, 0.0f, 1.0f); - } + float sr = APP->engine->getSampleRate(); + float response = sensitivity(params[EF_DAMP_PARAM], &inputs[EF_DAMP_INPUT], c); if (e.response != response) { e.response = response; for (int i = 0; i < 14; ++i) { - _engines[c]->efs[i].setSensitivity(e.response); + e.efs[i].setParams(sr, e.response); } } - float db = clamp(params[EF_GAIN_PARAM].getValue(), -1.0f, 1.0f); - if (inputs[EF_GAIN_INPUT].isConnected()) { - db *= clamp(inputs[EF_GAIN_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); - } - if (db < 0.0f) { - db = -db * efGainMinDecibels; - } - else { - db *= std::min(12.0f, efGainMaxDecibels); - } - e.efGain.setLevel(db); + e.efGain.setLevel(gain(params[EF_GAIN_PARAM], &inputs[EF_GAIN_INPUT], c)); float transpose = clamp(params[TRANSPOSE_PARAM].getValue(), -1.0f, 1.0f); if (inputs[TRANSPOSE_INPUT].isConnected()) { @@ -139,7 +113,7 @@ void PEQ14XV::processChannel(const ProcessArgs& args, int c) { } e.filters[i]->setParams( - _sampleRate, + APP->engine->getSampleRate(), MultimodeFilter::BUTTERWORTH_TYPE, poles, mode, diff --git a/src/PEQ14XV.hpp b/src/PEQ14XV.hpp @@ -34,20 +34,17 @@ struct PEQ14XV : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa struct Engine { MultimodeFilter* filters[14] {}; Amplifier amplifiers[14]; - RootMeanSquare efs[14]; + EnvelopeFollower efs[14]; float response = -1.0f; Amplifier efGain; float transposeSemitones = 0.0f; Engine(); ~Engine(); - - void setSampleRate(float sr); }; static constexpr float maxOutputGain = 24.0f; - float _sampleRate = 1000.0f; Engine* _engines[maxChannels] {}; Amplifier _outputGain; Amplifier _band14Mix; @@ -58,7 +55,7 @@ struct PEQ14XV : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa PEQ14XV() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - configParam(EF_DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower response", "%", 0.0f, 100.0f); + configParam(EF_DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower smoothing", "%", 0.0f, 100.0f); configParam<EFGainParamQuantity>(EF_GAIN_PARAM, -1.0f, 1.0f, 0.0f, "Envelope follower gain", " dB"); configParam(TRANSPOSE_PARAM, -1.0f, 1.0f, 0.0f, "Transpose", " semitones", 0.0f, 24.0f); configParam(OUTPUT_GAIN_PARAM, 0.0f, 1.0f, 0.0f, "Output gain", " dB", 0.0f, maxOutputGain); @@ -70,7 +67,6 @@ struct PEQ14XV : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa setExpanderModelPredicate([](Model* m) { return m == modelPEQ14XF || m == modelPEQ14XR || m == modelPEQ14XV; }); } - void sampleRateChange() override; void addChannel(int c) override; void removeChannel(int c) override; void modulate() override; diff --git a/src/PEQ6XF.cpp b/src/PEQ6XF.cpp @@ -1,21 +1,8 @@ #include "PEQ6XF.hpp" -void PEQ6XF::sampleRateChange() { - float sr = APP->engine->getSampleRate(); - for (int c = 0; c < _channels; ++c) { - for (int i = 0; i < 6; ++i) { - _engines[c]->efs[i].setSampleRate(sr); - } - } -} - void PEQ6XF::addChannel(int c) { _engines[c] = new Engine(); - float sr = APP->engine->getSampleRate(); - for (int i = 0; i < 6; ++i) { - _engines[c]->efs[i].setSampleRate(sr); - } } void PEQ6XF::removeChannel(int c) { @@ -24,24 +11,18 @@ void PEQ6XF::removeChannel(int c) { } void PEQ6XF::modulate() { - float response = params[DAMP_PARAM].getValue(); + float sr = APP->engine->getSampleRate(); + float response = sensitivity(params[DAMP_PARAM], NULL, 0); if (_response != response) { _response = response; for (int c = 0; c < _channels; ++c) { for (int i = 0; i < 6; ++i) { - _engines[c]->efs[i].setSensitivity(_response); + _engines[c]->efs[i].setParams(sr, _response); } } } - float db = clamp(params[GAIN_PARAM].getValue(), -1.0f, 1.0f); - if (db < 0.0f) { - db = -db * efGainMinDecibels; - } - else { - db *= std::min(12.0f, efGainMaxDecibels); - } - _gain.setLevel(db); + _gain.setLevel(gain(params[GAIN_PARAM], NULL, 0)); } void PEQ6XF::processAll(const ProcessArgs& args) { diff --git a/src/PEQ6XF.hpp b/src/PEQ6XF.hpp @@ -26,7 +26,7 @@ struct PEQ6XF : ExpanderModule<PEQ6ExpanderMessage, PEQXFBase> { }; struct Engine { - RootMeanSquare efs[6]; + EnvelopeFollower efs[6]; }; Engine* _engines[maxChannels] {}; @@ -37,13 +37,12 @@ struct PEQ6XF : ExpanderModule<PEQ6ExpanderMessage, PEQXFBase> { PEQ6XF() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower response", "%", 0.0f, 100.0f); + configParam(DAMP_PARAM, 0.0f, 1.0f, 0.3f, "Envelope follower smoothing", "%", 0.0f, 100.0f); configParam<EFGainParamQuantity>(GAIN_PARAM, -1.0f, 1.0f, 0.0f, "Envelope follower gain", " dB"); setBaseModelPredicate([](Model* m) { return m == modelPEQ6; }); } - void sampleRateChange() override; void addChannel(int c) override; void removeChannel(int c) override; void modulate() override; diff --git a/src/Pressor.hpp b/src/Pressor.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/Test.cpp b/src/Test.cpp @@ -397,9 +397,21 @@ void Test::processChannel(const ProcessArgs& args, int _c) { _rms.setSampleRate(APP->engine->getSampleRate()); _rms.setSensitivity(sensitivity); outputs[OUT_OUTPUT].setVoltage(_rms.next(inputs[IN_INPUT].getVoltage())); - _pef.setSensitivity(sensitivity); + _pef.setParams(APP->engine->getSampleRate(), sensitivity); outputs[OUT2_OUTPUT].setVoltage(_pef.next(inputs[IN_INPUT].getVoltage())); +#elif FASTRMS + float sensitivity = params[PARAM2_PARAM].getValue(); + if (inputs[CV2_INPUT].isConnected()) { + sensitivity *= clamp(inputs[CV2_INPUT].getVoltage(), 0.0f, 10.0f) / 10.0f; + } + _pure.setSampleRate(APP->engine->getSampleRate()); + _pure.setSensitivity(sensitivity); + outputs[OUT_OUTPUT].setVoltage(_pure.next(inputs[IN_INPUT].getVoltage())); + _fast.setSampleRate(APP->engine->getSampleRate()); + _fast.setSensitivity(sensitivity); + outputs[OUT2_OUTPUT].setVoltage(_fast.next(inputs[IN_INPUT].getVoltage())); + #elif RAVG if (_reset.process(inputs[CV1_INPUT].getVoltage())) { _average.reset(); diff --git a/src/Test.hpp b/src/Test.hpp @@ -4,7 +4,7 @@ extern Model* modelTest; -#define LPF 1 +//#define LPF 1 // #define LPFNOISE 1 // #define SINE 1 // #define SQUARE 1 @@ -24,7 +24,8 @@ extern Model* modelTest; // #define EG 1 // #define TABLES 1 // #define SLEW 1 -// #define RMS 1 +#define RMS 1 +// #define FASTRMS 1 // #define RAVG 1 // #define SATURATOR 1 // #define BROWNIAN 1 @@ -84,6 +85,10 @@ extern Model* modelTest; #include "dsp/signal.hpp" #elif RMS #include "dsp/signal.hpp" +#include "dsp/filters/utility.hpp" +#elif FASTRMS +#include "dsp/signal.hpp" +#include "dsp/filters/utility.hpp" #elif RAVG #include "dsp/signal.hpp" #elif SATURATOR @@ -215,6 +220,9 @@ struct Test : BGModule { #elif RMS RootMeanSquare _rms; PucketteEnvelopeFollower _pef; +#elif FASTRMS + PureRootMeanSquare _pure; + FastRootMeanSquare _fast; #elif RAVG RunningAverage _average; Trigger _reset; diff --git a/src/VCAmp.hpp b/src/VCAmp.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/VU.hpp b/src/VU.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/dsp/filters/experiments.cpp b/src/dsp/filters/experiments.cpp @@ -46,14 +46,6 @@ void ComplexBiquadFilter::updateParams() { } -float DCBlocker::next(float sample) { - const float r = 0.999f; - _lastOut = sample - _lastIn + r * _lastOut; - _lastIn = sample; - return _lastOut; -} - - // Adapted from Smith 1997, "The Scientist and Engineer's Guide to DSP" void MultipoleFilter::setParams( Type type, diff --git a/src/dsp/filters/experiments.hpp b/src/dsp/filters/experiments.hpp @@ -26,13 +26,6 @@ struct ComplexBiquadFilter : BiquadFilter<float> { void updateParams(); }; -struct DCBlocker : Filter { - float _lastIn = 0.0f; - float _lastOut = 0.0f; - - float next(float sample) override; -}; - struct MultipoleFilter : Filter { enum Type { LP_TYPE, diff --git a/src/dsp/filters/utility.cpp b/src/dsp/filters/utility.cpp @@ -0,0 +1,43 @@ + +#include "filters/utility.hpp" + +using namespace bogaudio::dsp; + +float DCBlocker::next(float sample) { + const float r = 0.999f; + _lastOut = sample - _lastIn + r * _lastOut; + _lastIn = sample; + return _lastOut; +} + + +float AverageRectifiedValue::next(float sample) { + return RunningAverage::next(std::abs(sample)); +} + + +float FastRootMeanSquare::next(float sample) { + return AverageRectifiedValue::next(_dcBlocker.next(sample)); +} + + +float PureRootMeanSquare::next(float sample) { + float a = RunningAverage::next(sample * sample); + if (_sum <= 0.0) { + return 0.0f; + } + return sqrtf(a); +} + + +void PucketteEnvelopeFollower::setParams(float sampleRate, float sensitivity) { + const float maxCutoff = 10000.0f; + const float minCutoff = 100.0f; + assert(sensitivity >= 0.0f && sensitivity <= 1.0f); + float cutoff = minCutoff + sensitivity * (maxCutoff - minCutoff); + _filter.setParams(sampleRate, cutoff); +} + +float PucketteEnvelopeFollower::next(float sample) { + return _filter.next(std::abs(_dcBlocker.next(sample))); +} diff --git a/src/dsp/filters/utility.hpp b/src/dsp/filters/utility.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "filters/filter.hpp" +#include "signal.hpp" + +namespace bogaudio { +namespace dsp { + +struct DCBlocker : Filter { + float _lastIn = 0.0f; + float _lastOut = 0.0f; + + float next(float sample) override; +}; + +struct AverageRectifiedValue : RunningAverage { + AverageRectifiedValue(float sampleRate = 1000.0f, float sensitivity = 1.0f, float maxDelayMS = 300.0f) + : RunningAverage(sampleRate, sensitivity, maxDelayMS) + { + } + + float next(float sample) override; +}; + +struct FastRootMeanSquare : AverageRectifiedValue { + DCBlocker _dcBlocker; + + FastRootMeanSquare(float sampleRate = 1000.0f, float sensitivity = 1.0f, float maxDelayMS = 300.0f) + : AverageRectifiedValue(sampleRate, sensitivity, maxDelayMS) + { + } + + float next(float sample) override; +}; + +struct PureRootMeanSquare : RunningAverage { + PureRootMeanSquare(float sampleRate = 1000.0f, float sensitivity = 1.0f, float maxDelayMS = 300.0f) + : RunningAverage(sampleRate, sensitivity, maxDelayMS) + { + } + + float next(float sample) override; +}; + +typedef FastRootMeanSquare RootMeanSquare; + +// Puckette 2007, "Theory and Technique" +struct PucketteEnvelopeFollower { + DCBlocker _dcBlocker; + LowPassFilter _filter; + + void setParams(float sampleRate, float sensitivity); + float next(float sample); +}; + +typedef PucketteEnvelopeFollower EnvelopeFollower; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/signal.cpp b/src/dsp/signal.cpp @@ -98,6 +98,7 @@ void RunningAverage::setSensitivity(float sensitivity) { _trailI = _bufferN - _sumN; _sum = 0.0; } + _invSumN = 1.0f / (float)_sumN; } void RunningAverage::reset() { @@ -112,30 +113,7 @@ float RunningAverage::next(float sample) { _sum += _buffer[_leadI] = sample; ++_leadI; _leadI %= _bufferN; - return (float)_sum / (float)_sumN; -} - - -float RootMeanSquare::next(float sample) { - float a = RunningAverage::next(sample * sample); - if (_sum <= 0.0) { - return 0.0f; - } - return sqrtf(a); -} - - -void PucketteEnvelopeFollower::setSensitivity(float sensitivity) { - assert(sensitivity >= 0.0f); - assert(sensitivity <= 1.0f); - _sensitivity = std::min(sensitivity, 0.97f); -} - -float PucketteEnvelopeFollower::next(float sample) { - const float norm = 5.0f; - sample /= norm; - _lastOutput = _sensitivity * _lastOutput + (1 - _sensitivity) * sample * sample; - return _lastOutput * norm; + return (float)_sum * _invSumN; } diff --git a/src/dsp/signal.hpp b/src/dsp/signal.hpp @@ -48,6 +48,7 @@ struct RunningAverage { float* _buffer = NULL; int _bufferN = 0; int _sumN = 0; + float _invSumN = 0.0f; int _leadI = 0; int _trailI = 0; double _sum = 0; @@ -68,28 +69,6 @@ struct RunningAverage { virtual float next(float sample); }; -struct RootMeanSquare : RunningAverage { - RootMeanSquare(float sampleRate = 1000.0f, float sensitivity = 1.0f, float maxDelayMS = 300.0f) - : RunningAverage(sampleRate, sensitivity, maxDelayMS) - { - } - - float next(float sample) override; -}; - -// Puckette 2007, "Theory and Technique" -struct PucketteEnvelopeFollower { - float _sensitivity = -1.0f; - float _lastOutput = 0.0f; - - PucketteEnvelopeFollower(float sensitivity = 1.0f) { - setSensitivity(sensitivity); - } - - void setSensitivity(float sensitivity); - float next(float sample); -}; - struct PositiveZeroCrossing { const float positiveThreshold = 0.01f; const float negativeThreshold = -positiveThreshold; diff --git a/src/follower_base.cpp b/src/follower_base.cpp @@ -0,0 +1,27 @@ + +#include "follower_base.hpp" + +float FollowerBase::sensitivity(Param& dampParam, Input* dampInput, int c) { + float response = clamp(dampParam.getValue(), 0.0f, 1.0f); + if (dampInput && dampInput->isConnected()) { + response *= clamp(dampInput->getPolyVoltage(c) / 10.f, 0.0f, 1.0f); + } + response = 1.0f - response; + response *= response; + response *= response; + return response; +} + +float FollowerBase::gain(Param& gainParam, Input* gainInput, int c) { + float db = clamp(gainParam.getValue(), -1.0f, 1.0f); + if (gainInput && gainInput->isConnected()) { + db *= clamp(gainInput->getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); + } + if (db < 0.0f) { + db = -db * efGainMinDecibels; + } + else { + db *= std::min(12.0f, efGainMaxDecibels); + } + return db; +} diff --git a/src/follower_base.hpp b/src/follower_base.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "bogaudio.hpp" +#include "dsp/filters/equalizer.hpp" + +using namespace bogaudio::dsp; + +namespace bogaudio { + +struct FollowerBase : BGModule { + typedef EQParamQuantity EFGainParamQuantity; + + static constexpr float efGainMinDecibels = Equalizer::cutDb; + static constexpr float efGainMaxDecibels = Equalizer::gainDb; + + float sensitivity(Param& dampParam, Input* dampInput, int c); + float gain(Param& gainParam, Input* gainInput, int c); +}; + +} // namespace bogaudio diff --git a/src/mixer.hpp b/src/mixer.hpp @@ -1,6 +1,7 @@ #pragma once #include "bogaudio.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/parametric_eq.hpp b/src/parametric_eq.hpp @@ -1,8 +1,10 @@ #pragma once #include "bogaudio.hpp" -#include "filters/equalizer.hpp" -#include "filters/multimode.hpp" +#include "follower_base.hpp" +#include "dsp/filters/equalizer.hpp" +#include "dsp/filters/multimode.hpp" +#include "dsp/filters/utility.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; @@ -143,11 +145,7 @@ struct PEQEngine { float next(float sample, float* rmsSums); }; -struct PEQXFBase : BGModule { - typedef EQParamQuantity EFGainParamQuantity; - static constexpr float efGainMinDecibels = Equalizer::cutDb; - static constexpr float efGainMaxDecibels = Equalizer::gainDb; - +struct PEQXFBase : FollowerBase { float scaleEF(float ef, float frequency, float bandwidth); };