BogaudioModules

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

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:
Mbenchmarks/signal_benchmark.cpp | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Test.cpp | 9+++++++++
Msrc/Test.hpp | 7++++++-
Msrc/VCAmp.cpp | 34+++++++++++++++++++++-------------
Msrc/VCAmp.hpp | 5+++++
Msrc/dsp/signal.cpp | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/dsp/signal.hpp | 29+++++++++++++++++++++++++++++
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;