commit bdf9a7fc2677e2f63d7376e80f384f8684d44a3e
parent 56fca49a79dd66ca9050839c72eef9beb83a8242
Author: Matt Demanett <matt@demanett.net>
Date: Mon, 7 May 2018 22:24:02 -0400
DSP for RMS; add output indication colored LED to VCAmp slider.
Diffstat:
7 files changed, 187 insertions(+), 14 deletions(-)
diff --git a/benchmarks/signal_benchmark.cpp b/benchmarks/signal_benchmark.cpp
@@ -3,6 +3,7 @@
#include <benchmark/benchmark.h>
+#include "dsp/oscillator.hpp"
#include "dsp/noise.hpp"
#include "dsp/signal.hpp"
@@ -48,6 +49,59 @@ static void BM_Amplifier(benchmark::State& state) {
}
BENCHMARK(BM_Amplifier);
+static void BM_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_RMS_Short);
+
+static void BM_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_RMS_Long);
+
+static void BM_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_RMS_Modulating);
+
static void BM_SlewLimiter_Fast(benchmark::State& state) {
WhiteNoiseGenerator r;
const int n = 256;
diff --git a/src/Test.cpp b/src/Test.cpp
@@ -347,6 +347,15 @@ void Test::step() {
}
_slew.setParams(engineGetSampleRate(), powf(ms, 2.0f) * 100.0f);
outputs[OUT_OUTPUT].value = _slew.next(inputs[IN_INPUT].value);
+
+#elif RMS
+ float sensitivity = params[PARAM2_PARAM].value;
+ if (inputs[CV2_INPUT].active) {
+ sensitivity *= clamp(inputs[CV2_INPUT].value, 0.0f, 10.0f) / 10.0f;
+ }
+ _rms.setSampleRate(engineGetSampleRate());
+ _rms.setSensitivity(sensitivity);
+ outputs[OUT_OUTPUT].value = _rms.next(inputs[IN_INPUT].value);
#endif
}
diff --git a/src/Test.hpp b/src/Test.hpp
@@ -22,7 +22,8 @@ extern Model* modelTest;
// #define FEEDBACK_PM 1
// #define EG 1
// #define TABLES 1
-#define SLEW 1
+// #define SLEW 1
+#define RMS 1
#include "pitch.hpp"
#ifdef LPF
@@ -72,6 +73,8 @@ extern Model* modelTest;
#include "dsp/oscillator.hpp"
#elif SLEW
#include "dsp/signal.hpp"
+#elif RMS
+#include "dsp/signal.hpp"
#else
#error what
#endif
@@ -183,6 +186,8 @@ struct Test : Module {
TablePhasor _table;
#elif SLEW
SlewLimiter _slew;
+#elif RMS
+ RootMeanSquare _rms;
#endif
Test()
diff --git a/src/VCAmp.cpp b/src/VCAmp.cpp
@@ -1,6 +1,10 @@
#include "VCAmp.hpp"
+void VCAmp::onSampleRateChange() {
+ _rms.setSampleRate(engineGetSampleRate());
+}
+
void VCAmp::step() {
if (inputs[IN_INPUT].active && outputs[OUT_OUTPUT].active) {
float level = params[LEVEL_PARAM].value;
@@ -11,6 +15,7 @@ void VCAmp::step() {
level += minDecibels;
_amplifier.setLevel(level);
outputs[OUT_OUTPUT].value = _amplifier.next(inputs[IN_INPUT].value);
+ _rmsLevel = _rms.next(outputs[OUT_OUTPUT].value) / 5.0f;
}
}
@@ -51,20 +56,23 @@ struct VUSlider : Knob {
nvgFillColor(vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0xff));
nvgFill(vg);
- float db = -60.0f + value * 72.0f;
- if (db > -60.0f) {
- nvgBeginPath(vg);
- nvgRoundedRect(vg, 2, 4, 14, 5, 1.0);
- if (db < -12.0f) {
- nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, (1.0f - (db + 12.0f) / -48.0f) * (float)0xff));
- }
- else if (db < 0.0f) {
- nvgFillColor(vg, nvgRGBA((1.0f - db / -12.0f) * 0xff, 0xff, 0x00, 0xff));
- }
- else {
- nvgFillColor(vg, nvgRGBA(0xff, (1.0f - db / 12.0f) * 0xff, 0x00, 0xff));
+ float db = dynamic_cast<VCAmp*>(module)->_rmsLevel;
+ if (db > 0.0f) {
+ db = amplitudeToDecibels(db);
+ if (db > -60.0f) {
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, 2, 4, 14, 5, 1.0);
+ if (db < -24.0f) {
+ nvgFillColor(vg, nvgRGBA(0x55, 0xff, 0x00, (1.0f - (db + 24.0f) / -36.0f) * (float)0xff));
+ }
+ else if (db < 0.0f) {
+ nvgFillColor(vg, nvgRGBA((1.0f - db / -24.0f) * 0xff, 0xff, 0x00, 0xff));
+ }
+ else {
+ nvgFillColor(vg, nvgRGBA(0xff, (1.0f - db / 18.0f) * 0xff, 0x00, 0xff));
+ }
+ nvgFill(vg);
}
- nvgFill(vg);
}
}
nvgRestore(vg);
diff --git a/src/VCAmp.hpp b/src/VCAmp.hpp
@@ -33,10 +33,15 @@ struct VCAmp : Module {
const float maxDecibels = 12.0f;
const float minDecibels = Amplifier::minDecibels;
Amplifier _amplifier;
+ RootMeanSquare _rms;
+ float _rmsLevel = 0.0f;
VCAmp() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
+ onSampleRateChange();
+ _rms.setSensitivity(0.05f);
}
+ virtual void onSampleRateChange() override;
virtual void step() override;
};
diff --git a/src/dsp/signal.cpp b/src/dsp/signal.cpp
@@ -48,6 +48,69 @@ float Amplifier::next(float s) {
}
+void RootMeanSquare::setSampleRate(float sampleRate) {
+ assert(sampleRate > 0.0f);
+ if (_sampleRate != sampleRate) {
+ _sampleRate = sampleRate;
+ if (_buffer) {
+ delete[] _buffer;
+ }
+ _bufferN = (maxDelayMS / 1000.0f) * _sampleRate;
+ _buffer = new float[_bufferN] {};
+ if (_initialized) {
+ _initialized = false;
+ setSensitivity(_sensitivity);
+ }
+ }
+}
+
+void RootMeanSquare::setSensitivity(float sensitivity) {
+ assert(sensitivity >= 0.0f);
+ assert(sensitivity <= 1.0f);
+ if (_initialized) {
+ if (_sensitivity != sensitivity) {
+ _sensitivity = sensitivity;
+ int newSumN = std::max(_sensitivity * _bufferN, 1.0f);
+ int i = newSumN;
+ while (i > _sumN) {
+ _trailI = --_trailI;
+ if (_trailI < 0) {
+ _trailI = _bufferN - 1;
+ }
+ _sum += _buffer[_trailI];
+ --i;
+ }
+ while (i < _sumN) {
+ _sum -= _buffer[_trailI];
+ _trailI = ++_trailI % _bufferN;
+ ++i;
+ }
+ _sumN = newSumN;
+ }
+ }
+ else {
+ _initialized = true;
+ _sensitivity = sensitivity;
+ _sumN = std::max(_sensitivity * _bufferN, 1.0f);
+ _leadI = 0;
+ _trailI = _bufferN - _sumN;
+ _sum = 0.0f;
+ }
+ assert(_sumN > 0);
+}
+
+float RootMeanSquare::next(float sample) {
+ _sum -= _buffer[_trailI];
+ _trailI = ++_trailI % _bufferN;
+ _sum += _buffer[_leadI] = sample * sample;
+ _leadI = ++_leadI % _bufferN;
+ if (_sum <= 0.0) {
+ return 0.0f;
+ }
+ return sqrtf((float)_sum / (float)_sumN);
+}
+
+
bool PositiveZeroCrossing::next(float sample) {
switch (_state) {
case NEGATIVE_STATE: {
diff --git a/src/dsp/signal.hpp b/src/dsp/signal.hpp
@@ -38,6 +38,35 @@ struct Amplifier {
float next(float s);
};
+struct RootMeanSquare {
+ const float maxDelayMS = 300.0;
+ const float minDelayMS = 0.2;
+ float _sampleRate = -1.0f;
+ float _sensitivity = -1.0f;
+
+ bool _initialized = false;
+ float* _buffer = NULL;
+ int _bufferN = 0;
+ int _sumN = 0;
+ int _leadI = 0;
+ int _trailI = 0;
+ double _sum = 0;
+
+ RootMeanSquare(float sampleRate = 1000.0f, float sensitivity = 1.0f) {
+ setSampleRate(sampleRate);
+ setSensitivity(sensitivity);
+ }
+ ~RootMeanSquare() {
+ if (_buffer) {
+ delete[] _buffer;
+ }
+ }
+
+ void setSampleRate(float sampleRate);
+ void setSensitivity(float sensitivity);
+ float next(float sample);
+};
+
struct PositiveZeroCrossing {
const float positiveThreshold = 0.01f;
const float negativeThreshold = -positiveThreshold;