BogaudioModules

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

commit 37d7005d546d008393055d519231e375b76ec8bd
parent 28fce5b7cf9f2cc014889b9c44a8da3adb2bb4cb
Author: Matt Demanett <matt@demanett.net>
Date:   Fri, 17 Apr 2020 18:25:49 -0400

Split up and templatize MultimodeFilter; this realizes some minor memory savings; is preparation for speed improvements.

Diffstat:
Msrc/FFB.cpp | 47+++++++++++++++++++++++------------------------
Msrc/FFB.hpp | 4+++-
Msrc/dsp/filters/equalizer.cpp | 28+++-------------------------
Msrc/dsp/filters/equalizer.hpp | 6+++---
Msrc/dsp/filters/multimode.cpp | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/dsp/filters/multimode.hpp | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
6 files changed, 291 insertions(+), 126 deletions(-)

diff --git a/src/FFB.cpp b/src/FFB.cpp @@ -8,30 +8,27 @@ void FFB::Engine::sampleRateChange() { } auto bp = [this, sr](int i, float cutoff) { - _filters[i].setParams( + _bandPasses[i].setParams( sr, - MultimodeFilter::BUTTERWORTH_TYPE, - 4.0f, - MultimodeFilter::BANDPASS_MODE, cutoff, 0.22f / BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWPITCH, MultimodeFilter::PITCH_BANDWIDTH_MODE ); }; - _filters[ 0].setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::LOWPASS_MODE, 95.0f, 0.0f); - bp(1, 125.0f); - bp(2, 175.0f); - bp(3, 250.0f); - bp(4, 350.0f); - bp(5, 500.0f); - bp(6, 700.0f); - bp(7, 1000.0f); - bp(8, 1400.0f); - bp(9, 2000.0f); - bp(10, 2800.0f); - bp(11, 4000.0f); - bp(12, 5600.0f); - _filters[13].setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::HIGHPASS_MODE, 6900.0f, 0.0f); + _lowPass.setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::LOWPASS_MODE, 95.0f, 0.0f); + bp(0, 125.0f); + bp(1, 175.0f); + bp(2, 250.0f); + bp(3, 350.0f); + bp(4, 500.0f); + bp(5, 700.0f); + bp(6, 1000.0f); + bp(7, 1400.0f); + bp(8, 2000.0f); + bp(9, 2800.0f); + bp(10, 4000.0f); + bp(11, 5600.0f); + _highPass.setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::HIGHPASS_MODE, 6900.0f, 0.0f); } void FFB::sampleRateChange() { @@ -85,13 +82,15 @@ void FFB::processChannel(const ProcessArgs& args, int c) { float in = inputs[IN_INPUT].getVoltage(c); float outAll = 0.0f; - float outOdd = 0.0f; - float outEven = 0.0f; - for (int i = 0; i < 14; ++i) { - float out = e._amplifiers[i].next(e._filters[i].next(in)); + outAll += e._amplifiers[0].next(e._lowPass.next(in)); + outAll += e._amplifiers[13].next(e._highPass.next(in)); + float outOdd = outAll; + float outEven = outAll; + for (int i = 1; i <= 12; ++i) { + float out = e._amplifiers[i].next(e._bandPasses[i - 1].next(in)); outAll += out; - outOdd += (i == 0 || i == 13 || i % 2 == 1) * out; - outEven += (i == 0 || i == 13 || i % 2 == 0) * out; + outOdd += (i % 2 == 1) * out; + outEven += (i % 2 == 0) * out; } outputs[ALL_OUTPUT].setChannels(_channels); diff --git a/src/FFB.hpp b/src/FFB.hpp @@ -47,7 +47,9 @@ struct FFB : BGModule { }; struct Engine { - MultimodeFilter _filters[14]; + MultimodeFilter _lowPass; + FourPoleButtworthBandpassFilter _bandPasses[12]; + MultimodeFilter _highPass; Amplifier _amplifiers[14]; bogaudio::dsp::SlewLimiter _slews[14]; diff --git a/src/dsp/filters/equalizer.cpp b/src/dsp/filters/equalizer.cpp @@ -16,35 +16,13 @@ void Equalizer::setParams( assert(highDb >= cutDb && highDb <= gainDb); _lowAmp.setLevel(lowDb); - _lowFilter.setParams( - sampleRate, - MultimodeFilter::BUTTERWORTH_TYPE, - 4, - MultimodeFilter::LOWPASS_MODE, - 100.0f, - 0.0f - ); + _lowFilter.setParams(sampleRate, 100.0f, 0.0f); _midAmp.setLevel(midDb); - _midFilter.setParams( - sampleRate, - MultimodeFilter::BUTTERWORTH_TYPE, - 2, - MultimodeFilter::BANDPASS_MODE, - 350.0f, - 0.55f, - MultimodeFilter::PITCH_BANDWIDTH_MODE - ); + _midFilter.setParams(sampleRate, 350.0f, 0.55f, MultimodeFilter::PITCH_BANDWIDTH_MODE); _highAmp.setLevel(highDb); - _highFilter.setParams( - sampleRate, - MultimodeFilter::BUTTERWORTH_TYPE, - 4, - MultimodeFilter::HIGHPASS_MODE, - 1000.0f, - 0.0f - ); + _highFilter.setParams(sampleRate, 1000.0f, 0.0f); } float Equalizer::next(float sample) { diff --git a/src/dsp/filters/equalizer.hpp b/src/dsp/filters/equalizer.hpp @@ -12,9 +12,9 @@ struct Equalizer : Filter { Amplifier _lowAmp; Amplifier _midAmp; Amplifier _highAmp; - MultimodeFilter _lowFilter; - MultimodeFilter _midFilter; - MultimodeFilter _highFilter; + FourPoleButtworthLowpassFilter _lowFilter; + TowPoleButtworthBandpassFilter _midFilter; + FourPoleButtworthHighpassFilter _highFilter; void setParams( float sampleRate, diff --git a/src/dsp/filters/multimode.cpp b/src/dsp/filters/multimode.cpp @@ -7,7 +7,42 @@ using namespace bogaudio::dsp; -void MultimodeFilter::setParams( +template<typename T, int N> void BiquadBank<T, N>::setParams(int i, T a0, T a1, T a2, T b0, T b1, T b2) { + assert(i >= 0 && i < N); + _biquads[i].setParams(a0, a1, a2, b0, b1, b2); +} + +template<typename T, int N> void BiquadBank<T, N>::reset(int from) { + assert(from >= 0); + for (; from < N; ++from) { + _biquads[from].reset(); + } +} + +template<> float BiquadBank<double, 4>::next(float sample) { + assert(_n <= 4); + for (int i = 0; i < _n; ++i) { + sample = _biquads[i].next(sample); + } + return sample; +} + +template<> float BiquadBank<double, 16>::next(float sample) { + assert(_n <= 16); + for (int i = 0; i < _n; ++i) { + sample = _biquads[i].next(sample); + } + return sample; +} + +template struct bogaudio::dsp::BiquadBank<double, 4>; +template struct bogaudio::dsp::BiquadBank<double, 16>; + + +template<int N> void MultimodeDesigner<N>::setParams( + bool& changed, + BiquadBank<T, N>& biquads, + float& outGain, float sampleRate, Type type, int poles, @@ -16,13 +51,15 @@ void MultimodeFilter::setParams( float qbw, BandwidthMode bwm ) { - assert(poles >= minPoles && poles <= maxPoles); + assert(N >= minPoles && N <= maxPoles); + assert(poles >= minPoles && poles <= N); assert(poles % modPoles == 0); assert(frequency >= minFrequency && frequency <= maxFrequency); assert(qbw >= minQbw && qbw <= maxQbw); bool repole = _type != type || _mode != mode || _nPoles != poles || (type == CHEBYSHEV_TYPE && (mode == LOWPASS_MODE || mode == HIGHPASS_MODE) && _qbw != qbw); bool redesign = repole || _frequency != frequency || _qbw != qbw || _sampleRate != sampleRate || _bandwidthMode != bwm; + changed = redesign; _sampleRate = sampleRate; _half2PiST = M_PI * (1.0f / sampleRate); _type = type; @@ -43,7 +80,7 @@ void MultimodeFilter::setParams( _poles[j] = Pole(-re, im, re + re, re * re + im * im); } - _outGain = 1.0f; + outGain = 1.0f; break; } @@ -68,8 +105,8 @@ void MultimodeFilter::setParams( _poles[j] = Pole(-re, im, re + re, re * re + im * im); } - _outGain = 1.0 / (e * std::pow(2.0, (T)(_nPoles - 1))); - // _outGain = 1.0f / std::pow(2.0f, (T)(_nPoles - 1)); + outGain = 1.0 / (e * std::pow(2.0, (T)(_nPoles - 1))); + // outGain = 1.0f / std::pow(2.0f, (T)(_nPoles - 1)); break; } @@ -83,11 +120,9 @@ void MultimodeFilter::setParams( switch (_mode) { case LOWPASS_MODE: case HIGHPASS_MODE: { - int nf = _nPoles / 2 + _nPoles % 2; - for (int i = _nFilters; i < nf; ++i) { - _filters[i].reset(); - } - _nFilters = nf; + biquads.reset(_nBiquads); + _nBiquads = _nPoles / 2 + _nPoles % 2; + biquads.setSectionsInUse(_nBiquads); // T iq = (1.0 / std::sqrt(2.0)) - 0.65 * _qbw; T iq = (T)0.8 - (T)0.6 * _qbw; @@ -96,49 +131,49 @@ void MultimodeFilter::setParams( if (_mode == LOWPASS_MODE) { int ni = 0; - int nf = _nFilters; + int nb = _nBiquads; if (_nPoles % 2 == 1) { ++ni; - --nf; + --nb; T wap = wa * std::real(_poles[0].p); - _filters[0].setParams(wa, wa, 0.0, wap + (T)1.0, wap - (T)1.0, (T)0.0); + biquads.setParams(0, wa, wa, 0.0, wap + (T)1.0, wap - (T)1.0, (T)0.0); } T a0 = wa2; T a1 = wa2 + wa2; T a2 = wa2; - for (int i = 0; i < nf; ++i) { + for (int i = 0; i < nb; ++i) { Pole& pole = _poles[ni + i]; T ywa2 = pole.y * wa2; T ywa21 = ywa2 + (T)1.0; - T x = (((T)(i == nf / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x; + T x = (((T)(i == nb / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x; T xwa = x * wa; T b0 = ywa21 - xwa; T b1 = (T)-2.0 + (ywa2 + ywa2); T b2 = ywa21 + xwa; - _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i, a0, a1, a2, b0, b1, b2); } } else { int ni = 0; - int nf = _nFilters; + int nb = _nBiquads; if (_nPoles % 2 == 1) { ++ni; - --nf; + --nb; T rp = std::real(_poles[0].p); - _filters[0].setParams(1.0, -1.0, 0.0, wa + rp, wa - rp, 0.0); + biquads.setParams(0, 1.0, -1.0, 0.0, wa + rp, wa - rp, 0.0); } T a0 = 1.0; T a1 = -2.0f; T a2 = 1.0; - for (int i = 0; i < nf; ++i) { + for (int i = 0; i < nb; ++i) { Pole& pole = _poles[ni + i]; T wa2y = wa2 + pole.y; - T x = (((T)(i == nf / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x; + T x = (((T)(i == nb / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x; T xwa = x * wa; T b0 = wa2y - xwa; T b1 = (wa2 + wa2) - (pole.y + pole.y); T b2 = wa2y + xwa; - _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i, a0, a1, a2, b0, b1, b2); } } break; @@ -146,11 +181,9 @@ void MultimodeFilter::setParams( case BANDPASS_MODE: case BANDREJECT_MODE: { - int nf = ((_nPoles / 2) * 2) + (_nPoles % 2); - for (int i = _nFilters; i < nf; ++i) { - _filters[i].reset(); - } - _nFilters = nf; + biquads.reset(_nBiquads); + _nBiquads = ((_nPoles / 2) * 2) + (_nPoles % 2); + biquads.setSectionsInUse(_nBiquads); T wdl = 0.0; T wdh = 0.0; @@ -183,12 +216,13 @@ void MultimodeFilter::setParams( T a2 = -w; int ni = 0; - int nf = _nFilters; + int nb = _nBiquads; if (_nPoles % 2 == 1) { ++ni; - --nf; + --nb; T wp = w * std::real(_poles[0].p); - _filters[0].setParams( + biquads.setParams( + 0, a0, a1, a2, @@ -197,7 +231,7 @@ void MultimodeFilter::setParams( (T)1.0 - wp + w02 ); } - for (int i = 0; i < nf; i += 2) { + for (int i = 0; i < nb; i += 2) { Pole& pole = _poles[ni + i / 2]; TC x = pole.p2; x *= w2; @@ -223,13 +257,13 @@ void MultimodeFilter::setParams( T b0 = (T)1.0 + f1a + f2a; T b1 = (T)-2.0 + (f2a + f2a); T b2 = (T)1.0 - f1a + f2a; - _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i, a0, a1, a2, b0, b1, b2); } { T b0 = (T)1.0 + f1b + f2b; T b1 = (T)-2.0 + (f2b + f2b); T b2 = (T)1.0 - f1b + f2b; - _filters[ni + i + 1].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i + 1, a0, a1, a2, b0, b1, b2); } } } @@ -239,13 +273,14 @@ void MultimodeFilter::setParams( T a2 = a0; int ni = 0; - int nf = _nFilters; + int nb = _nBiquads; if (_nPoles % 2 == 1) { ++ni; - --nf; + --nb; T rp = std::real(_poles[0].p); T rpw02 = rp * w02; - _filters[0].setParams( + biquads.setParams( + 0, a0, a1, a2, @@ -254,7 +289,7 @@ void MultimodeFilter::setParams( rp - w + rpw02 ); } - for (int i = 0; i < nf; i += 2) { + for (int i = 0; i < nb; i += 2) { Pole& pole = _poles[ni + i / 2]; TC x = pole.p2; x *= (T)-4.0 * w02; @@ -278,13 +313,13 @@ void MultimodeFilter::setParams( T b0 = pole.r + f1a + f2a; T b1 = (T)-2.0 * pole.r + (f2a + f2a); T b2 = pole.r - f1a + f2a; - _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i, a0, a1, a2, b0, b1, b2); } { T b0 = pole.r + f1b + f2b; T b1 = (T)-2.0 * pole.r + (f2b + f2b); T b2 = pole.r - f1b + f2b; - _filters[ni + i + 1].setParams(a0, a1, a2, b0, b1, b2); + biquads.setParams(ni + i + 1, a0, a1, a2, b0, b1, b2); } } } @@ -298,15 +333,41 @@ void MultimodeFilter::setParams( } } -float MultimodeFilter::next(float sample) { - for (int i = 0; i < _nFilters; ++i) { - sample = _filters[i].next(sample); - } - return _outGain * sample; +template struct bogaudio::dsp::MultimodeDesigner<4>; +template struct bogaudio::dsp::MultimodeDesigner<16>; + + +template<int N> void MultimodeBase<N>::design( + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm +) { + bool changed = false; + _designer.setParams( + changed, + _biquads, + _outGain, + sampleRate, + type, + poles, + mode, + frequency, + qbw, + bwm + ); } -void MultimodeFilter::reset() { - for (int i = 0; i < _nFilters; ++i) { - _filters[i].reset(); - } +template<int N> float MultimodeBase<N>::next(float sample) { + return _outGain * _biquads.next(sample); } + +template<int N> void MultimodeBase<N>::reset() { + _biquads.reset(); +} + +template struct bogaudio::dsp::MultimodeBase<4>; +template struct bogaudio::dsp::MultimodeBase<16>; diff --git a/src/dsp/filters/multimode.hpp b/src/dsp/filters/multimode.hpp @@ -7,6 +7,17 @@ namespace bogaudio { namespace dsp { +template<typename T, int N> +struct BiquadBank : Filter { + BiquadFilter<T> _biquads[N]; + int _n = N; + + void setParams(int i, T a0, T a1, T a2, T b0, T b1, T b2); + void reset(int from = 0); + inline void setSectionsInUse(int n) { _n = n; } + float next(float sample) override; +}; + // hacky workaround for certain linkers here; see https://github.com/bogaudio/BogaudioModules/issues/104 #define BOGAUDIO_DSP_MULTIMODEFILTER_MINFREQUENCY 1.0f #define BOGAUDIO_DSP_MULTIMODEFILTER_MAXFREQUENCY 21000.0f @@ -17,30 +28,10 @@ namespace dsp { #define BOGAUDIO_DSP_MULTIMODEFILTER_MINBWPITCH (1.0f / (1.0f * 12.0f * 100.0f / 25.0f)) #define BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWPITCH 2.0f -struct MultimodeFilter : Filter { +struct MultimodeTypes { typedef double T; typedef std::complex<T> TC; - struct Pole { - TC p; - T x = 0.0; - T y = 0.0; - TC pc; - TC p2; - TC i2p; - TC i2pc; - T r = 0.0; - - Pole() {} - Pole(T re, T im, T x, T y) : p(TC(re, im)), x(x), y(y) { - pc = std::conj(p); - p2 = p * p; - i2p = (T)1.0 / ((T)2.0 * p); - i2pc = (T)1.0 / ((T)2.0 * pc); - r = std::abs(p); - } - }; - enum Type { UNKNOWN_TYPE, BUTTERWORTH_TYPE, @@ -60,6 +51,29 @@ struct MultimodeFilter : Filter { LINEAR_BANDWIDTH_MODE, PITCH_BANDWIDTH_MODE }; +}; + +template<int N> +struct MultimodeDesigner : MultimodeTypes { + struct Pole { + TC p; + T x = 0.0; + T y = 0.0; + TC pc; + TC p2; + TC i2p; + TC i2pc; + T r = 0.0; + + Pole() {} + Pole(T re, T im, T x, T y) : p(TC(re, im)), x(x), y(y) { + pc = std::conj(p); + p2 = p * p; + i2p = (T)1.0 / ((T)2.0 * p); + i2pc = (T)1.0 / ((T)2.0 * pc); + r = std::abs(p); + } + }; static constexpr int minPoles = 1; static constexpr int maxPoles = 16; @@ -81,13 +95,30 @@ struct MultimodeFilter : Filter { float _frequency = -1.0f; float _qbw = -1.0f; BandwidthMode _bandwidthMode = UNKNOWN_BANDWIDTH_MODE; - Pole _poles[maxPoles / 2]; - BiquadFilter<T> _filters[maxPoles] {}; - int _nFilters = 1; - float _outGain = 1.0f; + int _nBiquads = 0; void setParams( + bool& changed, + BiquadBank<T, N>& biquads, + float& outGain, + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE + ); +}; + +template<int N> +struct MultimodeBase : MultimodeTypes, Filter { + MultimodeDesigner<N> _designer; + BiquadBank<T, N> _biquads; + float _outGain = 1.0f; + + void design( float sampleRate, Type type, int poles, @@ -100,5 +131,99 @@ struct MultimodeFilter : Filter { void reset(); }; +struct MultimodeFilter : MultimodeBase<16> { + inline void setParams( + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE + ) { + design( + sampleRate, + type, + poles, + mode, + frequency, + qbw, + bwm + ); + } +}; + +struct FourPoleButtworthLowpassFilter : MultimodeBase<4> { + inline void setParams( + float sampleRate, + float frequency, + float q + ) { + design( + sampleRate, + BUTTERWORTH_TYPE, + 4, + LOWPASS_MODE, + frequency, + q + ); + } +}; + +struct FourPoleButtworthHighpassFilter : MultimodeBase<4> { + inline void setParams( + float sampleRate, + float frequency, + float q + ) { + design( + sampleRate, + BUTTERWORTH_TYPE, + 4, + HIGHPASS_MODE, + frequency, + q + ); + } +}; + +struct TowPoleButtworthBandpassFilter : MultimodeBase<4> { + inline void setParams( + float sampleRate, + float frequency, + float bw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE + ) { + design( + sampleRate, + BUTTERWORTH_TYPE, + 2, + BANDPASS_MODE, + frequency, + bw, + bwm + ); + } +}; + +struct FourPoleButtworthBandpassFilter : MultimodeBase<4> { + inline void setParams( + float sampleRate, + float frequency, + float bw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE + ) { + design( + sampleRate, + BUTTERWORTH_TYPE, + 4, + BANDPASS_MODE, + frequency, + bw, + bwm + ); + } +}; + } // namespace dsp } // namespace bogaudio