commit 1188f4bd42cdcff5dfa5cfc4316cc24ef92d383d
parent 4df3d8b82b616b214e7b05e584b66a00da298e50
Author: Matt Demanett <matt@demanett.net>
Date: Sun, 22 Apr 2018 18:42:18 -0400
Slew limiter dsp; use to slighlty smooth abrupt CV changes in oscillators, avoid pops.
Diffstat:
13 files changed, 197 insertions(+), 30 deletions(-)
diff --git a/benchmarks/signal_benchmark.cpp b/benchmarks/signal_benchmark.cpp
@@ -0,0 +1,42 @@
+
+#include <benchmark/benchmark.h>
+
+#include "dsp/noise.hpp"
+#include "dsp/signal.hpp"
+
+using namespace bogaudio::dsp;
+
+static void BM_SlewLimiter_Fast(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;
+ for (auto _ : state) {
+ i = ++i % n;
+ benchmark::DoNotOptimize(sl.next(buf[i]));
+ }
+}
+BENCHMARK(BM_SlewLimiter_Fast);
+
+static void BM_SlewLimiter_Slow(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;
+ for (auto _ : state) {
+ i = ++i % n;
+ if (i == 0) {
+ j = ++j % n;
+ }
+ benchmark::DoNotOptimize(sl.next(buf[j]));
+ }
+}
+BENCHMARK(BM_SlewLimiter_Slow);
diff --git a/src/Additator.cpp b/src/Additator.cpp
@@ -8,10 +8,18 @@ void Additator::onReset() {
}
void Additator::onSampleRateChange() {
- _oscillator.setSampleRate(engineGetSampleRate());
- _maxFrequency = 0.47f * _oscillator._sampleRate;
+ float sampleRate = engineGetSampleRate();
+ _oscillator.setSampleRate(sampleRate);
+ _maxFrequency = 0.47f * sampleRate;
_steps = modulationSteps;
_phase = PHASE_RESET;
+ _widthSL.setParams(sampleRate, slewLimitTime);;
+ _oddSkewSL.setParams(sampleRate, slewLimitTime);;
+ _evenSkewSL.setParams(sampleRate, slewLimitTime);;
+ _amplitudeNormalizationSL.setParams(sampleRate, slewLimitTime);;
+ _decaySL.setParams(sampleRate, slewLimitTime);;
+ _balanceSL.setParams(sampleRate, slewLimitTime);;
+ _filterSL.setParams(sampleRate, slewLimitTime);;
}
float Additator::cvValue(Input& cv, bool dc) {
@@ -38,9 +46,9 @@ void Additator::step() {
if (_steps >= modulationSteps) {
_steps = 0;
- float width = clamp(params[WIDTH_PARAM].value + (maxWidth / 2.0f) * cvValue(inputs[WIDTH_INPUT]), 0.0f, maxWidth);
- float oddSkew = clamp(params[ODD_SKEW_PARAM].value + cvValue(inputs[ODD_SKEW_INPUT]), -maxSkew, maxSkew);
- float evenSkew = clamp(params[EVEN_SKEW_PARAM].value + cvValue(inputs[EVEN_SKEW_INPUT]), -maxSkew, maxSkew);
+ float width = _widthSL.next(clamp(params[WIDTH_PARAM].value + (maxWidth / 2.0f) * cvValue(inputs[WIDTH_INPUT]), 0.0f, maxWidth));
+ float oddSkew = _oddSkewSL.next(clamp(params[ODD_SKEW_PARAM].value + cvValue(inputs[ODD_SKEW_INPUT]), -maxSkew, maxSkew));
+ float evenSkew = _evenSkewSL.next(clamp(params[EVEN_SKEW_PARAM].value + cvValue(inputs[EVEN_SKEW_INPUT]), -maxSkew, maxSkew));
if (
_width != width ||
_oddSkew != oddSkew ||
@@ -65,10 +73,10 @@ void Additator::step() {
}
int partials = clamp((int)roundf(params[PARTIALS_PARAM].value * cvValue(inputs[PARTIALS_INPUT], true)), 0, maxPartials);
- float amplitudeNormalization = clamp(params[GAIN_PARAM].value + ((maxAmplitudeNormalization - minAmplitudeNormalization) / 2.0f) * cvValue(inputs[GAIN_INPUT]), minAmplitudeNormalization, maxAmplitudeNormalization);
- float decay = clamp(params[DECAY_PARAM].value + ((maxDecay - minDecay) / 2.0f) * cvValue(inputs[DECAY_INPUT]), minDecay, maxDecay);
- float balance = clamp(params[BALANCE_PARAM].value + cvValue(inputs[BALANCE_INPUT]), -1.0f, 1.0f);
- float filter = clamp(params[FILTER_PARAM].value + cvValue(inputs[FILTER_INPUT]), minFilter, maxFilter);
+ float amplitudeNormalization = _amplitudeNormalizationSL.next(clamp(params[GAIN_PARAM].value + ((maxAmplitudeNormalization - minAmplitudeNormalization) / 2.0f) * cvValue(inputs[GAIN_INPUT]), minAmplitudeNormalization, maxAmplitudeNormalization));
+ float decay = _decaySL.next(clamp(params[DECAY_PARAM].value + ((maxDecay - minDecay) / 2.0f) * cvValue(inputs[DECAY_INPUT]), minDecay, maxDecay));
+ float balance = _balanceSL.next(clamp(params[BALANCE_PARAM].value + cvValue(inputs[BALANCE_INPUT]), -1.0f, 1.0f));
+ float filter = _filterSL.next(clamp(params[FILTER_PARAM].value + cvValue(inputs[FILTER_INPUT]), minFilter, maxFilter));
if (
_partials != partials ||
_amplitudeNormalization != amplitudeNormalization ||
diff --git a/src/Additator.hpp b/src/Additator.hpp
@@ -68,6 +68,7 @@ struct Additator : Module {
const float maxDecay = 3.0f;
const float minFilter = 0.1;
const float maxFilter = 1.9;
+ const float slewLimitTime = 1.0f;
int _steps = 0;
int _partials = 0;
@@ -82,6 +83,13 @@ struct Additator : Module {
float _maxFrequency = 0.0f;
SineBankOscillator _oscillator;
PositiveZeroCrossing _syncTrigger;
+ SlewLimiter _widthSL;
+ SlewLimiter _oddSkewSL;
+ SlewLimiter _evenSkewSL;
+ SlewLimiter _amplitudeNormalizationSL;
+ SlewLimiter _decaySL;
+ SlewLimiter _balanceSL;
+ SlewLimiter _filterSL;
Additator()
: Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS)
diff --git a/src/FMOp.cpp b/src/FMOp.cpp
@@ -16,6 +16,10 @@ void FMOp::onSampleRateChange() {
_sineTable.setSampleRate(sampleRate);
_decimator.setParams(sampleRate, oversample);
_maxFrequency = 0.47f * sampleRate;
+ _feedbackSL.setParams(sampleRate, slewLimitTime);
+ _depthSL.setParams(sampleRate, slewLimitTime);
+ _levelSL.setParams(sampleRate, slewLimitTime);
+ _sustainSL.setParams(sampleRate, slewLimitTime / 10.0f);
}
void FMOp::step() {
@@ -73,7 +77,7 @@ void FMOp::step() {
}
_envelope.setAttack(powf(params[ATTACK_PARAM].value, 2.0f) * 10.f);
_envelope.setDecay(powf(params[DECAY_PARAM].value, 2.0f) * 10.f);
- _envelope.setSustain(sustain);
+ _envelope.setSustain(_sustainSL.next(sustain));
_envelope.setRelease(powf(params[RELEASE_PARAM].value, 2.0f) * 10.f);
}
@@ -100,7 +104,7 @@ void FMOp::step() {
envelope = _envelope.next();
}
- float feedback = _feedback;
+ float feedback = _feedbackSL.next(_feedback);
if (_feedbackEnvelopeOn) {
feedback *= envelope;
}
@@ -110,13 +114,13 @@ void FMOp::step() {
offset = feedback * _feedbackDelayedSample;
}
if (inputs[FM_INPUT].active) {
- offset += inputs[FM_INPUT].value * _depth * 2.0f;
+ offset += inputs[FM_INPUT].value * _depthSL.next(_depth) * 2.0f;
}
for (int i = 0; i < oversample; ++i) {
_phasor.advancePhase();
_buffer[i] = _sineTable.nextFromPhasor(_phasor, Phasor::radiansToPhase(offset));
}
- float out = _level;
+ float out = _levelSL.next(_level);
if (_levelEnvelopeOn) {
out *= envelope;
}
diff --git a/src/FMOp.hpp b/src/FMOp.hpp
@@ -4,6 +4,7 @@
#include "dsp/envelope.hpp"
#include "dsp/filter.hpp"
#include "dsp/oscillator.hpp"
+#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -52,6 +53,7 @@ struct FMOp : Module {
const float amplitude = 5.0f;
const int modulationSteps = 100;
static constexpr int oversample = 8;
+ const float slewLimitTime = 1.0f;
int _steps = 0;
float _feedback = 0.0f;
float _feedbackDelayedSample = 0.0f;
@@ -67,6 +69,10 @@ struct FMOp : Module {
SineTableOscillator _sineTable;
LPFDecimator _decimator;
SchmittTrigger _gateTrigger;
+ SlewLimiter _feedbackSL;
+ SlewLimiter _depthSL;
+ SlewLimiter _levelSL;
+ SlewLimiter _sustainSL;
FMOp()
: Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS)
diff --git a/src/Test.cpp b/src/Test.cpp
@@ -339,6 +339,14 @@ void Test::step() {
_table.setSampleRate(engineGetSampleRate());
_table.setFrequency(oscillatorPitch());
outputs[OUT2_OUTPUT].value = _table.next() * 5.0f;
+
+#elif SLEW
+ float ms = params[PARAM1_PARAM].value;
+ 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);
+ outputs[OUT_OUTPUT].value = _slew.next(inputs[IN_INPUT].value);
#endif
}
diff --git a/src/Test.hpp b/src/Test.hpp
@@ -16,12 +16,13 @@ extern Model* modelTest;
// #define OVERSAMPLING 1
// #define OVERSAMPLED_BL 1
// #define ANTIALIASING 1
-#define DECIMATORS 1
+// #define DECIMATORS 1
// #define FM 1
// #define PM 1
// #define FEEDBACK_PM 1
// #define EG 1
// #define TABLES 1
+#define SLEW 1
#include "pitch.hpp"
#ifdef LPF
@@ -69,6 +70,8 @@ extern Model* modelTest;
#include "dsp/envelope.hpp"
#elif TABLES
#include "dsp/oscillator.hpp"
+#elif SLEW
+#include "dsp/signal.hpp"
#else
#error what
#endif
@@ -178,6 +181,8 @@ struct Test : Module {
#elif TABLES
SineTableOscillator _sine;
TablePhasor _table;
+#elif SLEW
+ SlewLimiter _slew;
#endif
Test()
diff --git a/src/VCO.cpp b/src/VCO.cpp
@@ -46,7 +46,7 @@ void VCO::step() {
pw *= 1.0f - 2.0f * _square.minPulseWidth;
pw *= 0.5f;
pw += 0.5f;
- _square.setPulseWidth(pw);
+ _square.setPulseWidth(_squarePulseWidthSL.next(pw));
_fmDepth = params[FM_PARAM].value;
}
@@ -140,6 +140,7 @@ void VCO::setSampleRate(float sampleRate) {
_squareDecimator.setParams(sampleRate, oversample);
_sawDecimator.setParams(sampleRate, oversample);
_triangleDecimator.setParams(sampleRate, oversample);
+ _squarePulseWidthSL.setParams(sampleRate, slewLimitTime / 10.0f);
}
void VCO::setFrequency(float frequency) {
diff --git a/src/VCO.hpp b/src/VCO.hpp
@@ -46,6 +46,7 @@ struct VCO : Module {
const int modulationSteps = 100;
const float amplitude = 5.0f;
static constexpr int oversample = 8;
+ const float slewLimitTime = 1.0f;
int _modulationStep = 0;
float _oversampleThreshold = 0.0f;
float _frequency = 0.0f;
@@ -54,7 +55,6 @@ struct VCO : Module {
bool _slowMode = false;
float _fmDepth = 0.0f;
bool _fmLinearMode = false;
- PositiveZeroCrossing _syncTrigger;
Phasor _phasor;
BandLimitedSquareOscillator _square;
@@ -67,6 +67,8 @@ struct VCO : Module {
float _squareBuffer[oversample];
float _sawBuffer[oversample];
float _triangleBuffer[oversample];
+ PositiveZeroCrossing _syncTrigger;
+ SlewLimiter _squarePulseWidthSL;
VCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
onReset();
diff --git a/src/XCO.cpp b/src/XCO.cpp
@@ -47,34 +47,36 @@ void XCO::step() {
pw *= 1.0f - 2.0f * _square.minPulseWidth;
pw *= 0.5f;
pw += 0.5f;
- _square.setPulseWidth(pw);
+ _square.setPulseWidth(_squarePulseWidthSL.next(pw));
float saturation = params[SAW_SATURATION_PARAM].value;
if (inputs[SAW_SATURATION_INPUT].active) {
saturation *= clamp(inputs[SAW_SATURATION_INPUT].value / 10.0f, 0.0f, 1.0f);
}
- _saw.setSaturation(saturation * 10.f);
+ _saw.setSaturation(_sawSaturationSL.next(saturation) * 10.f);
- _triangleSampleWidth = params[TRIANGLE_SAMPLE_PARAM].value * Phasor::maxSampleWidth;
+ float tsw = params[TRIANGLE_SAMPLE_PARAM].value * Phasor::maxSampleWidth;
if (inputs[TRIANGLE_SAMPLE_INPUT].active) {
- _triangleSampleWidth *= clamp(inputs[TRIANGLE_SAMPLE_INPUT].value / 10.0f, 0.0f, 1.0f);
+ tsw *= clamp(inputs[TRIANGLE_SAMPLE_INPUT].value / 10.0f, 0.0f, 1.0f);
}
+ _triangleSampleWidth = _triangleSampleWidthSL.next(tsw);
_triangle.setSampleWidth(_triangleSampleWidth);
- _sineFeedback = params[SINE_FEEDBACK_PARAM].value;
+ float sfb = params[SINE_FEEDBACK_PARAM].value;
if (inputs[SINE_FEEDBACK_INPUT].active) {
- _sineFeedback *= clamp(inputs[SINE_FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f);
+ sfb *= clamp(inputs[SINE_FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f);
}
+ _sineFeedback = _sineFeedbackSL.next(sfb);
_fmDepth = params[FM_DEPTH_PARAM].value;
if (inputs[FM_DEPTH_INPUT].active) {
_fmDepth *= clamp(inputs[FM_DEPTH_INPUT].value / 10.0f, 0.0f, 1.0f);
}
- _squarePhaseOffset = phaseOffset(params[SQUARE_PHASE_PARAM], inputs[SQUARE_PHASE_INPUT]);
- _sawPhaseOffset = phaseOffset(params[SAW_PHASE_PARAM], inputs[SAW_PHASE_INPUT]);
- _trianglePhaseOffset = phaseOffset(params[TRIANGLE_PHASE_PARAM], inputs[TRIANGLE_PHASE_INPUT]);
- _sinePhaseOffset = phaseOffset(params[SINE_PHASE_PARAM], inputs[SINE_PHASE_INPUT]);
+ _squarePhaseOffset = _squarePhaseOffsetSL.next(phaseOffset(params[SQUARE_PHASE_PARAM], inputs[SQUARE_PHASE_INPUT]));
+ _sawPhaseOffset = _sawPhaseOffsetSL.next(phaseOffset(params[SAW_PHASE_PARAM], inputs[SAW_PHASE_INPUT]));
+ _trianglePhaseOffset = _trianglePhaseOffsetSL.next(phaseOffset(params[TRIANGLE_PHASE_PARAM], inputs[TRIANGLE_PHASE_INPUT]));
+ _sinePhaseOffset = _sinePhaseOffsetSL.next(phaseOffset(params[SINE_PHASE_PARAM], inputs[SINE_PHASE_INPUT]));
_squareMix = level(params[SQUARE_MIX_PARAM], inputs[SQUARE_MIX_INPUT]);
_sawMix = level(params[SAW_MIX_PARAM], inputs[SAW_MIX_INPUT]);
@@ -88,8 +90,9 @@ void XCO::step() {
float frequency = _baseHz;
Phasor::phase_delta_t phaseOffset = 0;
- if (inputs[FM_INPUT].active && _fmDepth > 0.01f) {
- float fm = inputs[FM_INPUT].value * _fmDepth;
+ float fmd = _fmDepthSL.next(_fmDepth);
+ if (inputs[FM_INPUT].active && fmd > 0.01f) {
+ float fm = inputs[FM_INPUT].value * fmd;
if (_fmLinearMode) {
phaseOffset = Phasor::radiansToPhase(2.0f * fm);
}
@@ -189,7 +192,9 @@ void XCO::step() {
outputs[SAW_OUTPUT].value = sawOut;
outputs[TRIANGLE_OUTPUT].value = triangleOut;
outputs[SINE_OUTPUT].value = _sineFeedbackDelayedSample = sineOut;
- outputs[MIX_OUTPUT].value = _squareMix * squareOut + _sawMix * sawOut + _triangleMix * triangleOut + _sineMix * sineOut;
+ if (outputs[MIX_OUTPUT].active) {
+ outputs[MIX_OUTPUT].value = _squareMixSL.next(_squareMix) * squareOut + _sawMixSL.next(_sawMix) * sawOut + _triangleMixSL.next(_triangleMix) * triangleOut + _sineMixSL.next(_sineMix) * sineOut;
+ }
}
Phasor::phase_delta_t XCO::phaseOffset(Param& param, Input& input) {
@@ -210,13 +215,29 @@ float XCO::level(Param& param, Input& input) {
void XCO::setSampleRate(float sampleRate) {
_oversampleThreshold = 0.06f * sampleRate;
+
_phasor.setSampleRate(sampleRate);
_square.setSampleRate(sampleRate);
_saw.setSampleRate(sampleRate);
+
_squareDecimator.setParams(sampleRate, oversample);
_sawDecimator.setParams(sampleRate, oversample);
_triangleDecimator.setParams(sampleRate, oversample);
_sineDecimator.setParams(sampleRate, oversample);
+
+ _fmDepthSL.setParams(sampleRate, slewLimitTime);
+ _squarePulseWidthSL.setParams(sampleRate, slewLimitTime / 10.0f);
+ _sawSaturationSL.setParams(sampleRate, slewLimitTime / 10.0f);
+ _triangleSampleWidthSL.setParams(sampleRate, slewLimitTime / 10.0f);
+ _sineFeedbackSL.setParams(sampleRate, slewLimitTime / 10.0f);
+ _squarePhaseOffsetSL.setParams(sampleRate, slewLimitTime / 2.0f);
+ _sawPhaseOffsetSL.setParams(sampleRate, slewLimitTime / 2.0f);
+ _trianglePhaseOffsetSL.setParams(sampleRate, slewLimitTime / 2.0f);
+ _sinePhaseOffsetSL.setParams(sampleRate, slewLimitTime / 2.0f);
+ _squareMixSL.setParams(sampleRate, slewLimitTime);
+ _sawMixSL.setParams(sampleRate, slewLimitTime);
+ _triangleMixSL.setParams(sampleRate, slewLimitTime);
+ _sineMixSL.setParams(sampleRate, slewLimitTime);
}
void XCO::setFrequency(float frequency) {
diff --git a/src/XCO.hpp b/src/XCO.hpp
@@ -70,6 +70,7 @@ struct XCO : Module {
const int modulationSteps = 100;
const float amplitude = 5.0f;
static constexpr int oversample = 8;
+ const float slewLimitTime = 1.0f;
int _modulationStep = 0;
float _oversampleThreshold = 0.0f;
float _frequency = 0.0f;
@@ -89,7 +90,6 @@ struct XCO : Module {
float _sawMix = 1.0f;
float _triangleMix = 1.0f;
float _sineMix = 1.0f;
- PositiveZeroCrossing _syncTrigger;
Phasor _phasor;
BandLimitedSquareOscillator _square;
@@ -104,6 +104,21 @@ struct XCO : Module {
float _sawBuffer[oversample];
float _triangleBuffer[oversample];
float _sineBuffer[oversample];
+ PositiveZeroCrossing _syncTrigger;
+
+ SlewLimiter _fmDepthSL;
+ SlewLimiter _squarePulseWidthSL;
+ SlewLimiter _sawSaturationSL;
+ SlewLimiter _triangleSampleWidthSL;
+ SlewLimiter _sineFeedbackSL;
+ SlewLimiter _squarePhaseOffsetSL;
+ SlewLimiter _sawPhaseOffsetSL;
+ SlewLimiter _trianglePhaseOffsetSL;
+ SlewLimiter _sinePhaseOffsetSL;
+ SlewLimiter _squareMixSL;
+ SlewLimiter _sawMixSL;
+ SlewLimiter _triangleMixSL;
+ SlewLimiter _sineMixSL;
XCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
onReset();
diff --git a/src/dsp/signal.cpp b/src/dsp/signal.cpp
@@ -1,4 +1,6 @@
+#include <assert.h>
+
#include "signal.hpp"
using namespace bogaudio::dsp;
@@ -40,3 +42,31 @@ bool PositiveZeroCrossing::next(float sample) {
void PositiveZeroCrossing::reset() {
_state = NEGATIVE_STATE;
}
+
+
+void SlewLimiter::setParams(float sampleRate, float milliseconds) {
+ assert(sampleRate > 0.0f);
+ assert(milliseconds >= 0.0f);
+ _sampleRate = sampleRate;
+ _milliseconds = milliseconds;
+ _samples = (_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 (_target != sample) {
+ _target = sample;
+ _delta = (sample - _current) / _samples;
+ _steps = _samples;
+ }
+ else if (_steps <= 1) {
+ _current = sample;
+ return sample;
+ }
+ _current += _delta;
+ --_steps;
+ return _current;
+}
diff --git a/src/dsp/signal.hpp b/src/dsp/signal.hpp
@@ -28,5 +28,22 @@ struct PositiveZeroCrossing {
void reset();
};
+struct SlewLimiter {
+ float _sampleRate;
+ float _milliseconds;
+ float _samples;
+ float _current = 0.0f;
+ float _target = 0.0f;
+ int _steps = 0;
+ float _delta = 0.0f;
+
+ SlewLimiter(float sampleRate = 1000.0f, float milliseconds = 1.0f) {
+ setParams(sampleRate, milliseconds);
+ }
+
+ void setParams(float sampleRate, float milliseconds);
+ float next(float sample);
+};
+
} // namespace dsp
} // namespace bogaudio