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:
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);
};