BogaudioModules

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

commit 28fce5b7cf9f2cc014889b9c44a8da3adb2bb4cb
parent 044b7b2b785a1fe8f74034b0d5e2fc4b16fb0226
Author: Matt Demanett <matt@demanett.net>
Date:   Fri, 17 Apr 2020 00:23:01 -0400

Break up dsp/filter.* into multiple files.

Diffstat:
MMakefile | 11++++++-----
Mbenchmarks/filter_benchmark.cpp | 2+-
Msrc/EQ.hpp | 2+-
Msrc/EQS.hpp | 2+-
Msrc/FFB.hpp | 3+--
Msrc/FMOp.hpp | 2+-
Msrc/LVCF.hpp | 2+-
Msrc/Test.hpp | 16++++++++--------
Msrc/Test2.hpp | 4++--
Msrc/TestVCF.hpp | 3++-
Msrc/VCF.hpp | 2+-
Msrc/XCO.hpp | 2+-
Dsrc/dsp/filter.cpp | 646-------------------------------------------------------------------------------
Dsrc/dsp/filter.hpp | 309-------------------------------------------------------------------------------
Asrc/dsp/filters/equalizer.cpp | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/equalizer.hpp | 29+++++++++++++++++++++++++++++
Asrc/dsp/filters/experiments.cpp | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/experiments.hpp | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/filter.cpp | 30++++++++++++++++++++++++++++++
Asrc/dsp/filters/filter.hpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/multimode.cpp | 312+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/multimode.hpp | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/resample.cpp | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dsp/filters/resample.hpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/dsp/noise.hpp | 2+-
Msrc/mixer.hpp | 2+-
Msrc/param_quantities.cpp | 2+-
Msrc/vco_base.hpp | 2+-
28 files changed, 1044 insertions(+), 984 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,7 +7,8 @@ ifdef TEST FLAGS += -DTEST=1 endif -SOURCES = $(wildcard src/*.cpp src/dsp/*cpp) +DSP_SOURCES = $(wildcard src/dsp/*cpp src/dsp/filters/*cpp) +SOURCES = $(wildcard src/*.cpp) $(DSP_SOURCES) DISTRIBUTABLES += $(wildcard LICENSE* README*) res @@ -16,7 +17,7 @@ include $(RACK_DIR)/plugin.mk CXXFLAGS += -Isrc -Isrc/dsp -Ilib -BENCHMARK_SOURCES = $(wildcard benchmarks/*.cpp src/dsp/*cpp) +BENCHMARK_SOURCES = $(wildcard benchmarks/*.cpp) $(DSP_SOURCES) BENCHMARK_OBJECTS = $(patsubst %, build/%.o, $(BENCHMARK_SOURCES)) BENCHMARK_DEPS = $(patsubst %, build/%.d, $(BENCHMARK_SOURCES)) -include $(BENCHMARK_DEPS) @@ -25,7 +26,7 @@ benchmark: $(BENCHMARK_OBJECTS) benchmark_clean: rm -f benchmark $(BENCHMARK_OBJECTS) -TESTMAIN_SOURCES = $(wildcard test/testmain.cpp src/dsp/*cpp) +TESTMAIN_SOURCES = test/testmain.cpp $(DSP_SOURCES) TESTMAIN_OBJECTS = $(patsubst %, build/%.o, $(TESTMAIN_SOURCES)) TESTMAIN_DEPS = $(patsubst %, build/%.d, $(TESTMAIN_SOURCES)) -include $(TESTMAIN_DEPS) @@ -34,7 +35,7 @@ testmain: $(TESTMAIN_OBJECTS) testmain_clean: rm -f testmain $(TESTMAIN_OBJECTS) -PLOT_SOURCES = $(wildcard test/plot.cpp src/dsp/*cpp) +PLOT_SOURCES = test/plot.cpp $(DSP_SOURCES) PLOT_OBJECTS = $(patsubst %, build/%.o, $(PLOT_SOURCES)) PLOT_DEPS = $(patsubst %, build/%.d, $(PLOT_SOURCES)) -include $(PLOT_DEPS) @@ -47,7 +48,7 @@ plotrungp: plot plot_clean: rm -f plot plot.tmp $(PLOT_OBJECTS) -SCATTER_SOURCES = $(wildcard test/scatter.cpp src/dsp/*cpp) +SCATTER_SOURCES = test/scatter.cpp $(DSP_SOURCES) SCATTER_OBJECTS = $(patsubst %, build/%.o, $(SCATTER_SOURCES)) SCATTER_DEPS = $(patsubst %, build/%.d, $(SCATTER_SOURCES)) -include $(SCATTER_DEPS) diff --git a/benchmarks/filter_benchmark.cpp b/benchmarks/filter_benchmark.cpp @@ -2,7 +2,7 @@ #include <benchmark/benchmark.h> #include "dsp/noise.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" // #include "dsp/decimator.hpp" // rack using namespace bogaudio::dsp; diff --git a/src/EQ.hpp b/src/EQ.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "filter.hpp" +#include "dsp/filters/equalizer.hpp" extern Model* modelEQ; diff --git a/src/EQS.hpp b/src/EQS.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "filter.hpp" +#include "dsp/filters/equalizer.hpp" extern Model* modelEQS; diff --git a/src/FFB.hpp b/src/FFB.hpp @@ -1,8 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "filter.hpp" -#include "signal.hpp" +#include "filters/multimode.hpp" using namespace bogaudio::dsp; diff --git a/src/FMOp.hpp b/src/FMOp.hpp @@ -2,7 +2,7 @@ #include "bogaudio.hpp" #include "dsp/envelope.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #include "dsp/oscillator.hpp" #include "dsp/signal.hpp" diff --git a/src/LVCF.hpp b/src/LVCF.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/multimode.hpp" using namespace bogaudio::dsp; diff --git a/src/Test.hpp b/src/Test.hpp @@ -33,9 +33,9 @@ extern Model* modelTest; #include "pitch.hpp" #ifdef LPF -#include "dsp/filter.hpp" +#include "dsp/filters/filter.hpp" #elif LPFNOISE -#include "dsp/filter.hpp" +#include "dsp/filters/filter.hpp" #include "dsp/noise.hpp" #elif SINE #include "dsp/oscillator.hpp" @@ -54,22 +54,22 @@ extern Model* modelTest; #elif OVERSAMPLING #include "dsp/oscillator.hpp" #include "dsp/decimator.hpp" // rack -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #define OVERSAMPLEN 16 #elif OVERSAMPLED_BL #include "dsp/oscillator.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #elif ANTIALIASING #include "dsp/oscillator.hpp" #include "dsp/decimator.hpp" // rack -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #elif DECIMATORS #include "dsp/oscillator.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #include "dsp/decimator.hpp" // rack #elif INTERPOLATOR #include "dsp/oscillator.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #elif FM #include "dsp/oscillator.hpp" #elif PM @@ -94,7 +94,7 @@ extern Model* modelTest; #elif RANDOMWALK #include "dsp/noise.hpp" #elif DCBLOCKER -#include "dsp/filter.hpp" +#include "dsp/filters/experiments.hpp" #else #error what #endif diff --git a/src/Test2.hpp b/src/Test2.hpp @@ -10,9 +10,9 @@ extern Model* modelTest2; #define LIMITER 1 #ifdef COMPLEX_BIQUAD -#include "dsp/filter.hpp" +#include "dsp/filters/filter.hpp" #elif MULTIPOLE -#include "dsp/filter.hpp" +#include "dsp/filters/experiments.hpp" #elif ADSR_ENVELOPE #include "dsp/envelope.hpp" #elif LIMITER diff --git a/src/TestVCF.hpp b/src/TestVCF.hpp @@ -1,7 +1,8 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/experiments.hpp" +#include "dsp/filters/resample.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/VCF.hpp b/src/VCF.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/multimode.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/XCO.hpp b/src/XCO.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #include "dsp/oscillator.hpp" #include "dsp/signal.hpp" diff --git a/src/dsp/filter.cpp b/src/dsp/filter.cpp @@ -1,646 +0,0 @@ - -#include <assert.h> -#include <algorithm> - -#include "filter.hpp" - -using namespace bogaudio::dsp; - -void ComplexBiquadFilter::setComplexParams( - float gain, - float zeroRadius, - float zeroTheta, - float poleRadius, - float poleTheta -) { - if ( - _gain != gain || - _zeroRadius != zeroRadius || - _zeroTheta != zeroTheta || - _poleRadius != poleRadius || - _poleTheta != poleTheta - ) { - assert(gain >= 0.0f && gain <= 1.0f); - assert(zeroRadius >= 0.0f && zeroRadius <= 1.0f); - assert(zeroTheta >= 0.0f && zeroTheta <= 2.0f*M_PI); - assert(poleRadius >= 0.0f && poleRadius <= 1.0f); - assert(poleTheta >= 0.0f && poleTheta <= 2.0f*M_PI); - _gain = gain; - _zeroRadius = zeroRadius; - _zeroTheta = zeroTheta; - _poleRadius = poleRadius; - _poleTheta = poleTheta; - updateParams(); - } -} - -void ComplexBiquadFilter::updateParams() { - setParams( - _gain, - -2.0f * _zeroRadius * cosf(_zeroTheta) * _gain, - _zeroRadius * _zeroRadius * _gain, - 1.0f, - -2.0f * _poleRadius * cosf(_poleTheta), - _poleRadius * _poleRadius - ); -} - - -// See: http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt -void LowPassFilter::setParams(float sampleRate, float cutoff, float q) { - if (_sampleRate == sampleRate && _cutoff == cutoff && _q == q) { - return; - } - _sampleRate = sampleRate; - _cutoff = cutoff; - _q = q; - // printf("\nLPF set param: sr=%f c=%f q=%f\n", _sampleRate, _cutoff, _q); - - float w0 = 2.0 * M_PI * _cutoff / _sampleRate; - float cosw0 = cosf(w0); - float alpha = sinf(w0) / (2.0 * _q); - - _biquad.setParams( - (1.0 - cosw0) / 2.0, - 1.0 - cosw0, - (1.0 - cosw0) / 2.0, - 1.0 + alpha, - -2.0 * cosw0, - 1.0 - alpha - ); -} - - -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, - int poles, - float sampleRate, - float cutoff, - float ripple -) { - if ( - _type == type && - _poles == poles && - _sampleRate == sampleRate && - _cutoff == cutoff && - _ripple == ripple - ) { - return; - } - assert(poles >= 1 && poles <= maxPoles); - assert(poles % 2 == 0); // relax this later? - assert(sampleRate >= 0.0f); - assert(cutoff >= 0.0f && cutoff <= sampleRate / 2.0f); - assert(ripple >= 0.0f && ripple <= maxRipple); - _type = type; - _poles = poles; - _sampleRate = sampleRate; - _cutoff = cutoff; - _ripple = ripple; - - for (int p = 0, pn = _poles / 2; p < pn; ++p) { - double a0 = 0.0; - double a1 = 0.0; - double a2 = 0.0; - double b1 = 0.0; - double b2 = 0.0; - { - double angle = M_PI/(_poles*2.0) + p*M_PI/_poles; - double rp = -cos(angle); - double ip = sin(angle); - - if (_ripple > 0.01f) { - double es = sqrt(pow(1.0 / (1.0 - _ripple), 2.0) - 1.0); - double esi = 1.0 / es; - double esis = esi * esi; - double polesi = 1.0 / (float)_poles; - double vx = polesi * log(esi + sqrt(esis + 1.0)); - double kx = polesi * log(esi + sqrt(esis - 1.0)); - kx = (exp(kx) + exp(-kx)) / 2.0; - rp *= ((exp(vx) - exp(-vx)) / 2.0) / kx; - ip *= ((exp(vx) + exp(-vx)) / 2.0) / kx; - // printf("\n\n\ncheb p=%d pn=%d rip=%f rp=%f ip=%f es=%f vx=%f kx=%f\n", p, pn, _ripple, rp, ip, es, vx, kx); - } - - const double t = 2.0 * tan(0.5); - const double ts = t * t; - // printf("\n\nco=%f sr=%f fc=%f\n", _cutoff, _sampleRate, _cutoff / _sampleRate); - double m = rp*rp + ip*ip; - double mts = m * ts; - double d = 4.0 - 4.0*rp*t + mts; - double x0 = ts / d; - double x1 = 2.0 * x0; - double x2 = x0; - double y1 = (8.0 - 2.0*mts) / d; - double y2 = (-4.0 - 4.0*rp*t - mts) / d; - // printf("vs p=%d t=%f w=%f m=%f d=%f x0=%f x1=%f x2=%f y1=%f y2=%f\n", p, t, w, m, d, x0, x1, x2, y1, y2); - - // FIXME: optimization: everything above here only depends on type, poles and ripple. - - double w = 2.0 * M_PI * (_cutoff / _sampleRate); - double w2 = w / 2.0; - double k = 0.0; - switch (_type) { - case LP_TYPE: { - k = sin(0.5 - w2) / sin(0.5 + w2); - break; - } - case HP_TYPE: { - k = -cos(w2 + 0.5) / cos(w2 - 0.5); - break; - } - } - double ks = k * k; - d = 1.0 + y1*k - y2*ks; - a0 = (x0 - x1*k + x2*ks) / d; - a1 = (-2.0*x0*k + x1 + x1*ks - 2.0*x2*k) / d; - a2 = (x0*ks - x1*k + x2) / d; - b1 = (2.0*k + y1 + y1*ks - 2.0*y2*k) / d; - b2 = (-ks - y1*k + y2) / d; - if (_type == HP_TYPE) { - a1 = -a1; - b1 = -b1; - } - - // printf("pole %d: rp=%f ip=%f a0=%f a1=%f a2=%f b1=%f b2=%f\n\n\n", p, rp, ip, a0, a1, a2, b1, b2); - _biquads[p].setParams(a0, a1, a2, 1.0, -b1, -b2); - } - } -} - -float MultipoleFilter::next(float sample) { - for (int p = 0, pn = _poles / 2; p < pn; ++p) { - sample = _biquads[p].next(sample); - } - return sample; -} - - -void MultimodeFilter::setParams( - float sampleRate, - Type type, - int poles, - Mode mode, - float frequency, - float qbw, - BandwidthMode bwm -) { - assert(poles >= minPoles && poles <= maxPoles); - 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; - _sampleRate = sampleRate; - _half2PiST = M_PI * (1.0f / sampleRate); - _type = type; - _nPoles = poles; - _mode = mode; - _frequency = frequency; - _qbw = qbw; - _bandwidthMode = bwm; - - if (repole) { - switch (_type) { - case BUTTERWORTH_TYPE: { - int np = _nPoles / 2 + (_nPoles % 2 == 1); - for (int k = 1, j = np - 1; k <= np; ++k, --j) { - T a = (T)(2 * k + _nPoles - 1) * M_PI / (T)(2 * _nPoles); - T re = std::cos(a); - T im = std::sin(a); - _poles[j] = Pole(-re, im, re + re, re * re + im * im); - } - - _outGain = 1.0f; - break; - } - - case CHEBYSHEV_TYPE: { - T ripple = 3.0; - if (mode == LOWPASS_MODE || mode == HIGHPASS_MODE) { - ripple += std::max(0.0f, 6.0f * qbw); - } - T e = ripple / (T)10.0; - e = std::pow((T)10.0, e); - e -= (T)1.0; - e = std::sqrt(e); - T ef = std::asinh((T)1.0 / e) / (float)_nPoles; - T efr = -std::sinh(ef); - T efi = std::cosh(ef); - - int np = _nPoles / 2 + (_nPoles % 2 == 1); - for (int k = 1, j = np - 1; k <= np; ++k, --j) { - T a = (T)(2 * k - 1) * M_PI / (T)(2 * _nPoles); - T re = efr * std::sin(a); - T im = efi * std::cos(a); - _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)); - break; - } - - default: { - assert(false); - } - } - } - - if (redesign) { - 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; - - // T iq = (1.0 / std::sqrt(2.0)) - 0.65 * _qbw; - T iq = (T)0.8 - (T)0.6 * _qbw; - T wa = std::tan(_frequency * _half2PiST); - T wa2 = wa * wa; - - if (_mode == LOWPASS_MODE) { - int ni = 0; - int nf = _nFilters; - if (_nPoles % 2 == 1) { - ++ni; - --nf; - 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); - } - T a0 = wa2; - T a1 = wa2 + wa2; - T a2 = wa2; - for (int i = 0; i < nf; ++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 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); - } - } - else { - int ni = 0; - int nf = _nFilters; - if (_nPoles % 2 == 1) { - ++ni; - --nf; - T rp = std::real(_poles[0].p); - _filters[0].setParams(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) { - 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 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); - } - } - break; - } - - 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; - - T wdl = 0.0; - T wdh = 0.0; - switch (_bandwidthMode) { - case LINEAR_BANDWIDTH_MODE: { - float bandwidth = std::max(minBWLinear, maxBWLinear * _qbw); - wdl = std::max(minFrequency, _frequency - 0.5f * bandwidth); - wdh = std::min(maxFrequency, std::max((float)wdl + 10.0f, _frequency + 0.5f * bandwidth)); - break; - } - case PITCH_BANDWIDTH_MODE: { - float bandwidth = std::max(minBWPitch, maxBWPitch * _qbw); - wdl = std::max(minFrequency, powf(2.0f, -bandwidth) * _frequency); - wdh = std::min(maxFrequency, std::max((float)wdl + 10.0f, powf(2.0f, bandwidth) * _frequency)); - break; - } - default: { - assert(false); - } - } - T wal = std::tan(wdl * _half2PiST); - T wah = std::tan(wdh * _half2PiST); - T w = wah - wal; - T w2 = w * w; - T w02 = wah * wal; - - if (_mode == BANDPASS_MODE) { - T a0 = w; - T a1 = 0.0; - T a2 = -w; - - int ni = 0; - int nf = _nFilters; - if (_nPoles % 2 == 1) { - ++ni; - --nf; - T wp = w * std::real(_poles[0].p); - _filters[0].setParams( - a0, - a1, - a2, - (T)1.0 + wp + w02, - (T)-2.0 + (w02 + w02), - (T)1.0 - wp + w02 - ); - } - for (int i = 0; i < nf; i += 2) { - Pole& pole = _poles[ni + i / 2]; - TC x = pole.p2; - x *= w2; - x -= (T)4.0 * w02; - x = std::sqrt(x); - TC xc = std::conj(x); - TC wp = w * pole.p; - TC wpc = w * pole.pc; - TC y1 = (x - wp) * (T)0.5; - TC y1c = (xc - wpc) * (T)0.5; - TC y2 = (-x - wp) * (T)0.5; - TC y2c = (-xc - wpc) * (T)0.5; - TC cf1a = -(y1 + y1c); - TC cf2a = y1 * y1c; - TC cf1b = -(y2 + y2c); - TC cf2b = y2 * y2c; - T f1a = std::real(cf1a); - T f1b = std::real(cf1b); - T f2a = std::real(cf2a); - T f2b = std::real(cf2b); - - { - 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); - } - { - 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); - } - } - } - else { - T a0 = (T)1.0 + w02; - T a1 = (T)-2.0 + (w02 + w02); - T a2 = a0; - - int ni = 0; - int nf = _nFilters; - if (_nPoles % 2 == 1) { - ++ni; - --nf; - T rp = std::real(_poles[0].p); - T rpw02 = rp * w02; - _filters[0].setParams( - a0, - a1, - a2, - rp + w + rpw02, - (T)-2.0 * rp + (rpw02 + rpw02), - rp - w + rpw02 - ); - } - for (int i = 0; i < nf; i += 2) { - Pole& pole = _poles[ni + i / 2]; - TC x = pole.p2; - x *= (T)-4.0 * w02; - x += w2; - x = std::sqrt(x); - TC xc = std::conj(x); - TC y1 = (x - w) * pole.i2p; - TC y1c = (xc - w) * pole.i2pc; - TC y2 = (-x - w) * pole.i2p; - TC y2c = (-xc - w) * pole.i2pc; - TC cf1a = -pole.r * (y1 + y1c); - TC cf2a = pole.r * y1 * y1c; - TC cf1b = -pole.r * (y2 + y2c); - TC cf2b = pole.r * y2 * y2c; - T f1a = std::real(cf1a); - T f1b = std::real(cf1b); - T f2a = std::real(cf2a); - T f2b = std::real(cf2b); - - { - 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); - } - { - 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); - } - } - } - break; - } - - default: { - assert(false); - } - } - } -} - -float MultimodeFilter::next(float sample) { - for (int i = 0; i < _nFilters; ++i) { - sample = _filters[i].next(sample); - } - return _outGain * sample; -} - -void MultimodeFilter::reset() { - for (int i = 0; i < _nFilters; ++i) { - _filters[i].reset(); - } -} - - -void Equalizer::setParams( - float sampleRate, - float lowDb, - float midDb, - float highDb -) { - assert(lowDb >= cutDb && lowDb <= gainDb); - assert(midDb >= cutDb && midDb <= gainDb); - assert(highDb >= cutDb && highDb <= gainDb); - - _lowAmp.setLevel(lowDb); - _lowFilter.setParams( - sampleRate, - MultimodeFilter::BUTTERWORTH_TYPE, - 4, - MultimodeFilter::LOWPASS_MODE, - 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 - ); - - _highAmp.setLevel(highDb); - _highFilter.setParams( - sampleRate, - MultimodeFilter::BUTTERWORTH_TYPE, - 4, - MultimodeFilter::HIGHPASS_MODE, - 1000.0f, - 0.0f - ); -} - -float Equalizer::next(float sample) { - float low = _lowAmp.next(_lowFilter.next(sample)); - float mid = _midAmp.next(_midFilter.next(sample)); - float high = _highAmp.next(_highFilter.next(sample)); - return low + mid + high; -} - - -void LPFDecimator::setParams(float sampleRate, int factor) { - _factor = factor; - _filter.setParams( - MultipoleFilter::LP_TYPE, - 4, - factor * sampleRate, - 0.45f * sampleRate, - 0 - ); -} - -float LPFDecimator::next(const float* buf) { - float s = 0.0f; - for (int i = 0; i < _factor; ++i) { - s = _filter.next(buf[i]); - } - return s; -} - - -// cascaded integrator–comb decimator: https://en.wikipedia.org/wiki/Cascaded_integrator%E2%80%93comb_filter -CICDecimator::CICDecimator(int stages, int factor) { - assert(stages > 0); - _stages = stages; - _integrators = new T[_stages + 1] {}; - _combs = new T[_stages] {}; - setParams(0.0f, factor); -} - -CICDecimator::~CICDecimator() { - delete[] _integrators; - delete[] _combs; -} - -void CICDecimator::setParams(float _sampleRate, int factor) { - assert(factor > 0); - if (_factor != factor) { - _factor = factor; - _gainCorrection = 1.0f / (float)(pow(_factor, _stages)); - } -} - -float CICDecimator::next(const float* buf) { - for (int i = 0; i < _factor; ++i) { - _integrators[0] = buf[i] * scale; - for (int j = 1; j <= _stages; ++j) { - _integrators[j] += _integrators[j - 1]; - } - } - T s = _integrators[_stages]; - for (int i = 0; i < _stages; ++i) { - T t = s; - s -= _combs[i]; - _combs[i] = t; - } - return _gainCorrection * (s / (float)scale); -} - - -CICInterpolator::CICInterpolator(int stages, int factor) { - assert(stages > 0); - _stages = stages; - _integrators = new T[_stages + 1] {}; - _combs = new T[_stages] {}; - _buffer = NULL; - setParams(0.0f, factor); -} - -CICInterpolator::~CICInterpolator() { - delete[] _integrators; - delete[] _combs; - delete[] _buffer; -} - -void CICInterpolator::setParams(float _sampleRate, int factor) { - assert(factor > 0); - if (_factor != factor) { - _factor = factor; - _gainCorrection = 1.0f / 512.0f; // (float)(pow(_factor, _stages / 2)); - if (_buffer) { - delete[] _buffer; - } - _buffer = new T[_factor] {}; - } -} - -void CICInterpolator::next(float sample, float* buf) { - T s = sample * scale; - for (int i = 0; i < _stages; ++i) { - T t = s; - s -= _combs[i]; - _combs[i] = t; - } - - std::fill(_buffer, _buffer + _factor, (T)0); - _buffer[0] = s; - for (int i = 0; i < _factor; ++i) { - _integrators[0] = _buffer[i]; - for (int j = 1; j <= _stages; ++j) { - _integrators[j] += _integrators[j - 1]; - } - buf[i] = _gainCorrection * (_integrators[_stages] / (float)scale); - } -} diff --git a/src/dsp/filter.hpp b/src/dsp/filter.hpp @@ -1,309 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <math.h> -#include <complex> - -#include "buffer.hpp" -#include "signal.hpp" - -namespace bogaudio { -namespace dsp { - -struct Filter { - Filter() {} - virtual ~Filter() {} - - virtual float next(float sample) = 0; -}; - -template<typename T> -struct BiquadFilter : Filter { - T _a0 = 0.0; - T _a1 = 0.0; - T _a2 = 0.0; - T _b1 = 0.0; - T _b2 = 0.0 ; - - T _x[3] {}; - T _y[3] {}; - - void setParams(T a0, T a1, T a2, T b0, T b1, T b2) { - T ib0 = 1.0 / b0; - _a0 = a0 * ib0; - _a1 = a1 * ib0; - _a2 = a2 * ib0; - _b1 = b1 * ib0; - _b2 = b2 * ib0; - } - - void reset() { - _x[0] = _x[1] = _x[2] = 0.0; - _y[0] = _y[1] = _y[2] = 0.0; - } - - float next(float sample) override { - _x[2] = _x[1]; - _x[1] = _x[0]; - _x[0] = sample; - - _y[2] = _y[1]; - _y[1] = _y[0]; - _y[0] = _a0 * _x[0]; - _y[0] += _a1 * _x[1]; - _y[0] += _a2 * _x[2]; - _y[0] -= _b1 * _y[1]; - _y[0] -= _b2 * _y[2]; - - return _y[0]; - } -}; - -struct ComplexBiquadFilter : BiquadFilter<float> { - float _gain = 1.0f; - float _zeroRadius = 1.0f; - float _zeroTheta = M_PI; - float _poleRadius = 0.9f; - float _poleTheta = 0.0f; - - ComplexBiquadFilter() { - updateParams(); - } - - void setComplexParams( - float gain, - float zeroRadius, - float zeroTheta, - float poleRadius, - float poleTheta - ); - void updateParams(); -}; - -struct LowPassFilter : Filter { - float _sampleRate = 0.0f; - float _cutoff = 0.0f; - float _q = 0.0f; - - BiquadFilter<float> _biquad; - - LowPassFilter(float sampleRate = 1000.0f, float cutoff = 100.0f, float q = 0.001f) { - setParams(sampleRate, cutoff, q); - } - - void setParams(float sampleRate, float cutoff, float q = 0.001f); - void reset() { _biquad.reset(); } - float next(float sample) override { - return _biquad.next(sample); - } -}; - -struct DCBlocker : Filter { - float _lastIn = 0.0f; - float _lastOut = 0.0f; - - float next(float sample) override; -}; - -struct MultipoleFilter : Filter { - enum Type { - LP_TYPE, - HP_TYPE - }; - - static constexpr int maxPoles = 20; - static constexpr float maxRipple = 0.29f; - Type _type = LP_TYPE; - int _poles = 1; - float _sampleRate = 0.0f; - float _cutoff = 0.0f; - float _ripple = 0.0f; - BiquadFilter<double> _biquads[maxPoles / 2]; - - MultipoleFilter() {} - - void setParams( - Type type, - int poles, - float sampleRate, - float cutoff, - float ripple // FIXME: using this with more than two poles creates large gain, need compensation. - ); - 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 -#define BOGAUDIO_DSP_MULTIMODEFILTER_MINQBW 0.0f -#define BOGAUDIO_DSP_MULTIMODEFILTER_MAXQBW 1.0f -#define BOGAUDIO_DSP_MULTIMODEFILTER_MINBWLINEAR 10.0f -#define BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWLINEAR 5000.0f -#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 { - 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, - CHEBYSHEV_TYPE - }; - - enum Mode { - UNKNOWN_MODE, - LOWPASS_MODE, - HIGHPASS_MODE, - BANDPASS_MODE, - BANDREJECT_MODE - }; - - enum BandwidthMode { - UNKNOWN_BANDWIDTH_MODE, - LINEAR_BANDWIDTH_MODE, - PITCH_BANDWIDTH_MODE - }; - - static constexpr int minPoles = 1; - static constexpr int maxPoles = 16; - static constexpr int modPoles = 1; - const float minFrequency = BOGAUDIO_DSP_MULTIMODEFILTER_MINFREQUENCY; - const float maxFrequency = BOGAUDIO_DSP_MULTIMODEFILTER_MAXFREQUENCY; - const float minQbw = BOGAUDIO_DSP_MULTIMODEFILTER_MINQBW; - const float maxQbw = BOGAUDIO_DSP_MULTIMODEFILTER_MAXQBW; - const float minBWLinear = BOGAUDIO_DSP_MULTIMODEFILTER_MINBWLINEAR; - const float maxBWLinear = BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWLINEAR; - const float minBWPitch = BOGAUDIO_DSP_MULTIMODEFILTER_MINBWPITCH; - const float maxBWPitch = BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWPITCH; - - float _sampleRate = 44100.0f; - float _half2PiST = 0.0f; - Type _type = UNKNOWN_TYPE; - Mode _mode = UNKNOWN_MODE; - int _nPoles = 0; - 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; - - void setParams( - float sampleRate, - Type type, - int poles, - Mode mode, - float frequency, - float qbw, - BandwidthMode bwm = PITCH_BANDWIDTH_MODE - ); - float next(float sample) override; - void reset(); -}; - -struct Equalizer : Filter { - static constexpr float gainDb = 12.0f; - static constexpr float cutDb = -36.0f; - - Amplifier _lowAmp; - Amplifier _midAmp; - Amplifier _highAmp; - MultimodeFilter _lowFilter; - MultimodeFilter _midFilter; - MultimodeFilter _highFilter; - - void setParams( - float sampleRate, - float lowDb, - float midDb, - float highDb - ); - float next(float sample) override; -}; - -struct Decimator { - Decimator() {} - virtual ~Decimator() {} - - virtual void setParams(float sampleRate, int factor) = 0; - virtual float next(const float* buf) = 0; -}; - -struct LPFDecimator : Decimator { - int _factor; - MultipoleFilter _filter; - - LPFDecimator(float sampleRate = 1000.0f, int factor = 8) { - setParams(sampleRate, factor); - } - void setParams(float sampleRate, int factor) override; - float next(const float* buf) override; -}; - -struct CICDecimator : Decimator { - typedef int64_t T; - static constexpr T scale = ((T)1) << 32; - int _stages; - T* _integrators; - T* _combs; - int _factor = 0; - float _gainCorrection; - - CICDecimator(int stages = 4, int factor = 8); - virtual ~CICDecimator(); - - void setParams(float sampleRate, int factor) override; - float next(const float* buf) override; -}; - -struct Interpolator { - Interpolator() {} - virtual ~Interpolator() {} - - virtual void setParams(float sampleRate, int factor) = 0; - virtual void next(float sample, float* buf) = 0; -}; - -struct CICInterpolator : Interpolator { - typedef int64_t T; - static constexpr T scale = ((T)1) << 32; - int _stages; - T* _integrators; - T* _combs; - T* _buffer; - int _factor = 0; - float _gainCorrection; - - CICInterpolator(int stages = 4, int factor = 8); - virtual ~CICInterpolator(); - - void setParams(float sampleRate, int factor) override; - void next(float sample, float* buf) override; -}; - -} // namespace dsp -} // namespace bogaudio diff --git a/src/dsp/filters/equalizer.cpp b/src/dsp/filters/equalizer.cpp @@ -0,0 +1,55 @@ + +#include <assert.h> + +#include "filters/equalizer.hpp" + +using namespace bogaudio::dsp; + +void Equalizer::setParams( + float sampleRate, + float lowDb, + float midDb, + float highDb +) { + assert(lowDb >= cutDb && lowDb <= gainDb); + assert(midDb >= cutDb && midDb <= gainDb); + assert(highDb >= cutDb && highDb <= gainDb); + + _lowAmp.setLevel(lowDb); + _lowFilter.setParams( + sampleRate, + MultimodeFilter::BUTTERWORTH_TYPE, + 4, + MultimodeFilter::LOWPASS_MODE, + 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 + ); + + _highAmp.setLevel(highDb); + _highFilter.setParams( + sampleRate, + MultimodeFilter::BUTTERWORTH_TYPE, + 4, + MultimodeFilter::HIGHPASS_MODE, + 1000.0f, + 0.0f + ); +} + +float Equalizer::next(float sample) { + float low = _lowAmp.next(_lowFilter.next(sample)); + float mid = _midAmp.next(_midFilter.next(sample)); + float high = _highAmp.next(_highFilter.next(sample)); + return low + mid + high; +} diff --git a/src/dsp/filters/equalizer.hpp b/src/dsp/filters/equalizer.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "filters/multimode.hpp" + +namespace bogaudio { +namespace dsp { + +struct Equalizer : Filter { + static constexpr float gainDb = 12.0f; + static constexpr float cutDb = -36.0f; + + Amplifier _lowAmp; + Amplifier _midAmp; + Amplifier _highAmp; + MultimodeFilter _lowFilter; + MultimodeFilter _midFilter; + MultimodeFilter _highFilter; + + void setParams( + float sampleRate, + float lowDb, + float midDb, + float highDb + ); + float next(float sample) override; +}; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/filters/experiments.cpp b/src/dsp/filters/experiments.cpp @@ -0,0 +1,160 @@ + +#include <assert.h> +#include <math.h> + +#include "filters/experiments.hpp" + +using namespace bogaudio::dsp; + +void ComplexBiquadFilter::setComplexParams( + float gain, + float zeroRadius, + float zeroTheta, + float poleRadius, + float poleTheta +) { + if ( + _gain != gain || + _zeroRadius != zeroRadius || + _zeroTheta != zeroTheta || + _poleRadius != poleRadius || + _poleTheta != poleTheta + ) { + assert(gain >= 0.0f && gain <= 1.0f); + assert(zeroRadius >= 0.0f && zeroRadius <= 1.0f); + assert(zeroTheta >= 0.0f && zeroTheta <= 2.0f*M_PI); + assert(poleRadius >= 0.0f && poleRadius <= 1.0f); + assert(poleTheta >= 0.0f && poleTheta <= 2.0f*M_PI); + _gain = gain; + _zeroRadius = zeroRadius; + _zeroTheta = zeroTheta; + _poleRadius = poleRadius; + _poleTheta = poleTheta; + updateParams(); + } +} + +void ComplexBiquadFilter::updateParams() { + setParams( + _gain, + -2.0f * _zeroRadius * cosf(_zeroTheta) * _gain, + _zeroRadius * _zeroRadius * _gain, + 1.0f, + -2.0f * _poleRadius * cosf(_poleTheta), + _poleRadius * _poleRadius + ); +} + + +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, + int poles, + float sampleRate, + float cutoff, + float ripple +) { + if ( + _type == type && + _poles == poles && + _sampleRate == sampleRate && + _cutoff == cutoff && + _ripple == ripple + ) { + return; + } + assert(poles >= 1 && poles <= maxPoles); + assert(poles % 2 == 0); // relax this later? + assert(sampleRate >= 0.0f); + assert(cutoff >= 0.0f && cutoff <= sampleRate / 2.0f); + assert(ripple >= 0.0f && ripple <= maxRipple); + _type = type; + _poles = poles; + _sampleRate = sampleRate; + _cutoff = cutoff; + _ripple = ripple; + + for (int p = 0, pn = _poles / 2; p < pn; ++p) { + double a0 = 0.0; + double a1 = 0.0; + double a2 = 0.0; + double b1 = 0.0; + double b2 = 0.0; + { + double angle = M_PI/(_poles*2.0) + p*M_PI/_poles; + double rp = -cos(angle); + double ip = sin(angle); + + if (_ripple > 0.01f) { + double es = sqrt(pow(1.0 / (1.0 - _ripple), 2.0) - 1.0); + double esi = 1.0 / es; + double esis = esi * esi; + double polesi = 1.0 / (float)_poles; + double vx = polesi * log(esi + sqrt(esis + 1.0)); + double kx = polesi * log(esi + sqrt(esis - 1.0)); + kx = (exp(kx) + exp(-kx)) / 2.0; + rp *= ((exp(vx) - exp(-vx)) / 2.0) / kx; + ip *= ((exp(vx) + exp(-vx)) / 2.0) / kx; + // printf("\n\n\ncheb p=%d pn=%d rip=%f rp=%f ip=%f es=%f vx=%f kx=%f\n", p, pn, _ripple, rp, ip, es, vx, kx); + } + + const double t = 2.0 * tan(0.5); + const double ts = t * t; + // printf("\n\nco=%f sr=%f fc=%f\n", _cutoff, _sampleRate, _cutoff / _sampleRate); + double m = rp*rp + ip*ip; + double mts = m * ts; + double d = 4.0 - 4.0*rp*t + mts; + double x0 = ts / d; + double x1 = 2.0 * x0; + double x2 = x0; + double y1 = (8.0 - 2.0*mts) / d; + double y2 = (-4.0 - 4.0*rp*t - mts) / d; + // printf("vs p=%d t=%f w=%f m=%f d=%f x0=%f x1=%f x2=%f y1=%f y2=%f\n", p, t, w, m, d, x0, x1, x2, y1, y2); + + // FIXME: optimization: everything above here only depends on type, poles and ripple. + + double w = 2.0 * M_PI * (_cutoff / _sampleRate); + double w2 = w / 2.0; + double k = 0.0; + switch (_type) { + case LP_TYPE: { + k = sin(0.5 - w2) / sin(0.5 + w2); + break; + } + case HP_TYPE: { + k = -cos(w2 + 0.5) / cos(w2 - 0.5); + break; + } + } + double ks = k * k; + d = 1.0 + y1*k - y2*ks; + a0 = (x0 - x1*k + x2*ks) / d; + a1 = (-2.0*x0*k + x1 + x1*ks - 2.0*x2*k) / d; + a2 = (x0*ks - x1*k + x2) / d; + b1 = (2.0*k + y1 + y1*ks - 2.0*y2*k) / d; + b2 = (-ks - y1*k + y2) / d; + if (_type == HP_TYPE) { + a1 = -a1; + b1 = -b1; + } + + // printf("pole %d: rp=%f ip=%f a0=%f a1=%f a2=%f b1=%f b2=%f\n\n\n", p, rp, ip, a0, a1, a2, b1, b2); + _biquads[p].setParams(a0, a1, a2, 1.0, -b1, -b2); + } + } +} + +float MultipoleFilter::next(float sample) { + for (int p = 0, pn = _poles / 2; p < pn; ++p) { + sample = _biquads[p].next(sample); + } + return sample; +} diff --git a/src/dsp/filters/experiments.hpp b/src/dsp/filters/experiments.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "filters/filter.hpp" + +namespace bogaudio { +namespace dsp { + +struct ComplexBiquadFilter : BiquadFilter<float> { + float _gain = 1.0f; + float _zeroRadius = 1.0f; + float _zeroTheta = M_PI; + float _poleRadius = 0.9f; + float _poleTheta = 0.0f; + + ComplexBiquadFilter() { + updateParams(); + } + + void setComplexParams( + float gain, + float zeroRadius, + float zeroTheta, + float poleRadius, + float poleTheta + ); + 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, + HP_TYPE + }; + + static constexpr int maxPoles = 20; + static constexpr float maxRipple = 0.29f; + Type _type = LP_TYPE; + int _poles = 1; + float _sampleRate = 0.0f; + float _cutoff = 0.0f; + float _ripple = 0.0f; + BiquadFilter<double> _biquads[maxPoles / 2]; + + MultipoleFilter() {} + + void setParams( + Type type, + int poles, + float sampleRate, + float cutoff, + float ripple // FIXME: using this with more than two poles creates large gain, need compensation. + ); + float next(float sample) override; +}; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/filters/filter.cpp b/src/dsp/filters/filter.cpp @@ -0,0 +1,30 @@ + +#include <math.h> + +#include "filters/filter.hpp" + +using namespace bogaudio::dsp; + +// See: http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt +void LowPassFilter::setParams(float sampleRate, float cutoff, float q) { + if (_sampleRate == sampleRate && _cutoff == cutoff && _q == q) { + return; + } + _sampleRate = sampleRate; + _cutoff = cutoff; + _q = q; + // printf("\nLPF set param: sr=%f c=%f q=%f\n", _sampleRate, _cutoff, _q); + + float w0 = 2.0 * M_PI * _cutoff / _sampleRate; + float cosw0 = cosf(w0); + float alpha = sinf(w0) / (2.0 * _q); + + _biquad.setParams( + (1.0 - cosw0) / 2.0, + 1.0 - cosw0, + (1.0 - cosw0) / 2.0, + 1.0 + alpha, + -2.0 * cosw0, + 1.0 - alpha + ); +} diff --git a/src/dsp/filters/filter.hpp b/src/dsp/filters/filter.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "buffer.hpp" +#include "signal.hpp" + +namespace bogaudio { +namespace dsp { + +struct Filter { + Filter() {} + virtual ~Filter() {} + + virtual float next(float sample) = 0; +}; + +template<typename T> +struct BiquadFilter : Filter { + T _a0 = 0.0; + T _a1 = 0.0; + T _a2 = 0.0; + T _b1 = 0.0; + T _b2 = 0.0 ; + + T _x[3] {}; + T _y[3] {}; + + void setParams(T a0, T a1, T a2, T b0, T b1, T b2) { + T ib0 = 1.0 / b0; + _a0 = a0 * ib0; + _a1 = a1 * ib0; + _a2 = a2 * ib0; + _b1 = b1 * ib0; + _b2 = b2 * ib0; + } + + void reset() { + _x[0] = _x[1] = _x[2] = 0.0; + _y[0] = _y[1] = _y[2] = 0.0; + } + + float next(float sample) override { + _x[2] = _x[1]; + _x[1] = _x[0]; + _x[0] = sample; + + _y[2] = _y[1]; + _y[1] = _y[0]; + _y[0] = _a0 * _x[0]; + _y[0] += _a1 * _x[1]; + _y[0] += _a2 * _x[2]; + _y[0] -= _b1 * _y[1]; + _y[0] -= _b2 * _y[2]; + + return _y[0]; + } +}; + +struct LowPassFilter : Filter { + float _sampleRate = 0.0f; + float _cutoff = 0.0f; + float _q = 0.0f; + + BiquadFilter<float> _biquad; + + LowPassFilter(float sampleRate = 1000.0f, float cutoff = 100.0f, float q = 0.001f) { + setParams(sampleRate, cutoff, q); + } + + void setParams(float sampleRate, float cutoff, float q = 0.001f); + void reset() { _biquad.reset(); } + float next(float sample) override { + return _biquad.next(sample); + } +}; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/filters/multimode.cpp b/src/dsp/filters/multimode.cpp @@ -0,0 +1,312 @@ + +#include <assert.h> +#include <math.h> +#include <algorithm> + +#include "filters/multimode.hpp" + +using namespace bogaudio::dsp; + +void MultimodeFilter::setParams( + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm +) { + assert(poles >= minPoles && poles <= maxPoles); + 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; + _sampleRate = sampleRate; + _half2PiST = M_PI * (1.0f / sampleRate); + _type = type; + _nPoles = poles; + _mode = mode; + _frequency = frequency; + _qbw = qbw; + _bandwidthMode = bwm; + + if (repole) { + switch (_type) { + case BUTTERWORTH_TYPE: { + int np = _nPoles / 2 + (_nPoles % 2 == 1); + for (int k = 1, j = np - 1; k <= np; ++k, --j) { + T a = (T)(2 * k + _nPoles - 1) * M_PI / (T)(2 * _nPoles); + T re = std::cos(a); + T im = std::sin(a); + _poles[j] = Pole(-re, im, re + re, re * re + im * im); + } + + _outGain = 1.0f; + break; + } + + case CHEBYSHEV_TYPE: { + T ripple = 3.0; + if (mode == LOWPASS_MODE || mode == HIGHPASS_MODE) { + ripple += std::max(0.0f, 6.0f * qbw); + } + T e = ripple / (T)10.0; + e = std::pow((T)10.0, e); + e -= (T)1.0; + e = std::sqrt(e); + T ef = std::asinh((T)1.0 / e) / (float)_nPoles; + T efr = -std::sinh(ef); + T efi = std::cosh(ef); + + int np = _nPoles / 2 + (_nPoles % 2 == 1); + for (int k = 1, j = np - 1; k <= np; ++k, --j) { + T a = (T)(2 * k - 1) * M_PI / (T)(2 * _nPoles); + T re = efr * std::sin(a); + T im = efi * std::cos(a); + _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)); + break; + } + + default: { + assert(false); + } + } + } + + if (redesign) { + 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; + + // T iq = (1.0 / std::sqrt(2.0)) - 0.65 * _qbw; + T iq = (T)0.8 - (T)0.6 * _qbw; + T wa = std::tan(_frequency * _half2PiST); + T wa2 = wa * wa; + + if (_mode == LOWPASS_MODE) { + int ni = 0; + int nf = _nFilters; + if (_nPoles % 2 == 1) { + ++ni; + --nf; + 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); + } + T a0 = wa2; + T a1 = wa2 + wa2; + T a2 = wa2; + for (int i = 0; i < nf; ++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 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); + } + } + else { + int ni = 0; + int nf = _nFilters; + if (_nPoles % 2 == 1) { + ++ni; + --nf; + T rp = std::real(_poles[0].p); + _filters[0].setParams(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) { + 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 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); + } + } + break; + } + + 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; + + T wdl = 0.0; + T wdh = 0.0; + switch (_bandwidthMode) { + case LINEAR_BANDWIDTH_MODE: { + float bandwidth = std::max(minBWLinear, maxBWLinear * _qbw); + wdl = std::max(minFrequency, _frequency - 0.5f * bandwidth); + wdh = std::min(maxFrequency, std::max((float)wdl + 10.0f, _frequency + 0.5f * bandwidth)); + break; + } + case PITCH_BANDWIDTH_MODE: { + float bandwidth = std::max(minBWPitch, maxBWPitch * _qbw); + wdl = std::max(minFrequency, powf(2.0f, -bandwidth) * _frequency); + wdh = std::min(maxFrequency, std::max((float)wdl + 10.0f, powf(2.0f, bandwidth) * _frequency)); + break; + } + default: { + assert(false); + } + } + T wal = std::tan(wdl * _half2PiST); + T wah = std::tan(wdh * _half2PiST); + T w = wah - wal; + T w2 = w * w; + T w02 = wah * wal; + + if (_mode == BANDPASS_MODE) { + T a0 = w; + T a1 = 0.0; + T a2 = -w; + + int ni = 0; + int nf = _nFilters; + if (_nPoles % 2 == 1) { + ++ni; + --nf; + T wp = w * std::real(_poles[0].p); + _filters[0].setParams( + a0, + a1, + a2, + (T)1.0 + wp + w02, + (T)-2.0 + (w02 + w02), + (T)1.0 - wp + w02 + ); + } + for (int i = 0; i < nf; i += 2) { + Pole& pole = _poles[ni + i / 2]; + TC x = pole.p2; + x *= w2; + x -= (T)4.0 * w02; + x = std::sqrt(x); + TC xc = std::conj(x); + TC wp = w * pole.p; + TC wpc = w * pole.pc; + TC y1 = (x - wp) * (T)0.5; + TC y1c = (xc - wpc) * (T)0.5; + TC y2 = (-x - wp) * (T)0.5; + TC y2c = (-xc - wpc) * (T)0.5; + TC cf1a = -(y1 + y1c); + TC cf2a = y1 * y1c; + TC cf1b = -(y2 + y2c); + TC cf2b = y2 * y2c; + T f1a = std::real(cf1a); + T f1b = std::real(cf1b); + T f2a = std::real(cf2a); + T f2b = std::real(cf2b); + + { + 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); + } + { + 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); + } + } + } + else { + T a0 = (T)1.0 + w02; + T a1 = (T)-2.0 + (w02 + w02); + T a2 = a0; + + int ni = 0; + int nf = _nFilters; + if (_nPoles % 2 == 1) { + ++ni; + --nf; + T rp = std::real(_poles[0].p); + T rpw02 = rp * w02; + _filters[0].setParams( + a0, + a1, + a2, + rp + w + rpw02, + (T)-2.0 * rp + (rpw02 + rpw02), + rp - w + rpw02 + ); + } + for (int i = 0; i < nf; i += 2) { + Pole& pole = _poles[ni + i / 2]; + TC x = pole.p2; + x *= (T)-4.0 * w02; + x += w2; + x = std::sqrt(x); + TC xc = std::conj(x); + TC y1 = (x - w) * pole.i2p; + TC y1c = (xc - w) * pole.i2pc; + TC y2 = (-x - w) * pole.i2p; + TC y2c = (-xc - w) * pole.i2pc; + TC cf1a = -pole.r * (y1 + y1c); + TC cf2a = pole.r * y1 * y1c; + TC cf1b = -pole.r * (y2 + y2c); + TC cf2b = pole.r * y2 * y2c; + T f1a = std::real(cf1a); + T f1b = std::real(cf1b); + T f2a = std::real(cf2a); + T f2b = std::real(cf2b); + + { + 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); + } + { + 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); + } + } + } + break; + } + + default: { + assert(false); + } + } + } +} + +float MultimodeFilter::next(float sample) { + for (int i = 0; i < _nFilters; ++i) { + sample = _filters[i].next(sample); + } + return _outGain * sample; +} + +void MultimodeFilter::reset() { + for (int i = 0; i < _nFilters; ++i) { + _filters[i].reset(); + } +} diff --git a/src/dsp/filters/multimode.hpp b/src/dsp/filters/multimode.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include <complex> + +#include "filters/filter.hpp" + +namespace bogaudio { +namespace dsp { + +// 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 +#define BOGAUDIO_DSP_MULTIMODEFILTER_MINQBW 0.0f +#define BOGAUDIO_DSP_MULTIMODEFILTER_MAXQBW 1.0f +#define BOGAUDIO_DSP_MULTIMODEFILTER_MINBWLINEAR 10.0f +#define BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWLINEAR 5000.0f +#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 { + 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, + CHEBYSHEV_TYPE + }; + + enum Mode { + UNKNOWN_MODE, + LOWPASS_MODE, + HIGHPASS_MODE, + BANDPASS_MODE, + BANDREJECT_MODE + }; + + enum BandwidthMode { + UNKNOWN_BANDWIDTH_MODE, + LINEAR_BANDWIDTH_MODE, + PITCH_BANDWIDTH_MODE + }; + + static constexpr int minPoles = 1; + static constexpr int maxPoles = 16; + static constexpr int modPoles = 1; + const float minFrequency = BOGAUDIO_DSP_MULTIMODEFILTER_MINFREQUENCY; + const float maxFrequency = BOGAUDIO_DSP_MULTIMODEFILTER_MAXFREQUENCY; + const float minQbw = BOGAUDIO_DSP_MULTIMODEFILTER_MINQBW; + const float maxQbw = BOGAUDIO_DSP_MULTIMODEFILTER_MAXQBW; + const float minBWLinear = BOGAUDIO_DSP_MULTIMODEFILTER_MINBWLINEAR; + const float maxBWLinear = BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWLINEAR; + const float minBWPitch = BOGAUDIO_DSP_MULTIMODEFILTER_MINBWPITCH; + const float maxBWPitch = BOGAUDIO_DSP_MULTIMODEFILTER_MAXBWPITCH; + + float _sampleRate = 44100.0f; + float _half2PiST = 0.0f; + Type _type = UNKNOWN_TYPE; + Mode _mode = UNKNOWN_MODE; + int _nPoles = 0; + 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; + + void setParams( + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE + ); + float next(float sample) override; + void reset(); +}; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/filters/resample.cpp b/src/dsp/filters/resample.cpp @@ -0,0 +1,113 @@ + +#include <assert.h> +#include <math.h> +#include <algorithm> + +#include "filters/resample.hpp" + +using namespace bogaudio::dsp; + +void LPFDecimator::setParams(float sampleRate, int factor) { + _factor = factor; + _filter.setParams( + MultipoleFilter::LP_TYPE, + 4, + factor * sampleRate, + 0.45f * sampleRate, + 0 + ); +} + +float LPFDecimator::next(const float* buf) { + float s = 0.0f; + for (int i = 0; i < _factor; ++i) { + s = _filter.next(buf[i]); + } + return s; +} + + +// cascaded integrator–comb decimator: https://en.wikipedia.org/wiki/Cascaded_integrator%E2%80%93comb_filter +CICDecimator::CICDecimator(int stages, int factor) { + assert(stages > 0); + _stages = stages; + _integrators = new T[_stages + 1] {}; + _combs = new T[_stages] {}; + setParams(0.0f, factor); +} + +CICDecimator::~CICDecimator() { + delete[] _integrators; + delete[] _combs; +} + +void CICDecimator::setParams(float _sampleRate, int factor) { + assert(factor > 0); + if (_factor != factor) { + _factor = factor; + _gainCorrection = 1.0f / (float)(pow(_factor, _stages)); + } +} + +float CICDecimator::next(const float* buf) { + for (int i = 0; i < _factor; ++i) { + _integrators[0] = buf[i] * scale; + for (int j = 1; j <= _stages; ++j) { + _integrators[j] += _integrators[j - 1]; + } + } + T s = _integrators[_stages]; + for (int i = 0; i < _stages; ++i) { + T t = s; + s -= _combs[i]; + _combs[i] = t; + } + return _gainCorrection * (s / (float)scale); +} + + +CICInterpolator::CICInterpolator(int stages, int factor) { + assert(stages > 0); + _stages = stages; + _integrators = new T[_stages + 1] {}; + _combs = new T[_stages] {}; + _buffer = NULL; + setParams(0.0f, factor); +} + +CICInterpolator::~CICInterpolator() { + delete[] _integrators; + delete[] _combs; + delete[] _buffer; +} + +void CICInterpolator::setParams(float _sampleRate, int factor) { + assert(factor > 0); + if (_factor != factor) { + _factor = factor; + _gainCorrection = 1.0f / 512.0f; // (float)(pow(_factor, _stages / 2)); + if (_buffer) { + delete[] _buffer; + } + _buffer = new T[_factor] {}; + } +} + +void CICInterpolator::next(float sample, float* buf) { + T s = sample * scale; + for (int i = 0; i < _stages; ++i) { + T t = s; + s -= _combs[i]; + _combs[i] = t; + } + + std::fill(_buffer, _buffer + _factor, (T)0); + _buffer[0] = s; + for (int i = 0; i < _factor; ++i) { + _integrators[0] = _buffer[i]; + for (int j = 1; j <= _stages; ++j) { + _integrators[j] += _integrators[j - 1]; + } + buf[i] = _gainCorrection * (_integrators[_stages] / (float)scale); + } +} diff --git a/src/dsp/filters/resample.hpp b/src/dsp/filters/resample.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "filters/filter.hpp" +#include "filters/experiments.hpp" + +namespace bogaudio { +namespace dsp { + +struct Decimator { + Decimator() {} + virtual ~Decimator() {} + + virtual void setParams(float sampleRate, int factor) = 0; + virtual float next(const float* buf) = 0; +}; + +struct LPFDecimator : Decimator { + int _factor; + MultipoleFilter _filter; + + LPFDecimator(float sampleRate = 1000.0f, int factor = 8) { + setParams(sampleRate, factor); + } + void setParams(float sampleRate, int factor) override; + float next(const float* buf) override; +}; + +struct CICDecimator : Decimator { + typedef int64_t T; + static constexpr T scale = ((T)1) << 32; + int _stages; + T* _integrators; + T* _combs; + int _factor = 0; + float _gainCorrection; + + CICDecimator(int stages = 4, int factor = 8); + virtual ~CICDecimator(); + + void setParams(float sampleRate, int factor) override; + float next(const float* buf) override; +}; + +struct Interpolator { + Interpolator() {} + virtual ~Interpolator() {} + + virtual void setParams(float sampleRate, int factor) = 0; + virtual void next(float sample, float* buf) = 0; +}; + +struct CICInterpolator : Interpolator { + typedef int64_t T; + static constexpr T scale = ((T)1) << 32; + int _stages; + T* _integrators; + T* _combs; + T* _buffer; + int _factor = 0; + float _gainCorrection; + + CICInterpolator(int stages = 4, int factor = 8); + virtual ~CICInterpolator(); + + void setParams(float sampleRate, int factor) override; + void next(float sample, float* buf) override; +}; + +} // namespace dsp +} // namespace bogaudio diff --git a/src/dsp/noise.hpp b/src/dsp/noise.hpp @@ -3,7 +3,7 @@ #include <random> #include "base.hpp" -#include "filter.hpp" +#include "filters/filter.hpp" namespace bogaudio { namespace dsp { diff --git a/src/mixer.hpp b/src/mixer.hpp @@ -2,7 +2,7 @@ #include "bogaudio.hpp" #include "expanders.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/equalizer.hpp" #include "dsp/signal.hpp" using namespace bogaudio::dsp; diff --git a/src/param_quantities.cpp b/src/param_quantities.cpp @@ -1,5 +1,5 @@ #include "param_quantities.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/equalizer.hpp" #include "dsp/pitch.hpp" #include "dsp/signal.hpp" diff --git a/src/vco_base.hpp b/src/vco_base.hpp @@ -1,7 +1,7 @@ #pragma once #include "bogaudio.hpp" -#include "dsp/filter.hpp" +#include "dsp/filters/resample.hpp" #include "dsp/oscillator.hpp" #include "dsp/signal.hpp"