From dd49e033308b8a29c430472a38c3b6806d6e313a Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 28 Jul 2021 22:24:39 -0400 Subject: [PATCH] Add a DC Blocker for non-0-0 waveshapers (#4785) Waveshapers which don't map 0 to 0 can leave a nasty click on end of note if note results in silence. For those, we have a simple dc blocker which kills vlf dc signals and which we apply to the appropriate wavetables. Addresses #1964 --- src/common/dsp/QuadFilterWaveshapers.cpp | 40 +++++++++++++++++------- src/gui/widgets/WaveShaperSelector.cpp | 9 ++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/common/dsp/QuadFilterWaveshapers.cpp b/src/common/dsp/QuadFilterWaveshapers.cpp index dbd62e5b31c..1aae11147a5 100644 --- a/src/common/dsp/QuadFilterWaveshapers.cpp +++ b/src/common/dsp/QuadFilterWaveshapers.cpp @@ -193,6 +193,19 @@ __m128 WS_LUT(QuadFilterWaveshaperState *__restrict s, const float *table, __m12 return x; } +template inline __m128 dcBlock(QuadFilterWaveshaperState *__restrict s, __m128 x) +{ + // https://www.dsprelated.com/freebooks/filters/DC_Blocker.html + // y_n = x_n - x_n-1 + R y_n-1 + const auto fac = _mm_set1_ps(0.9999); + auto dx = _mm_sub_ps(x, s->R[0]); + auto filtval = _mm_add_ps(dx, _mm_mul_ps(fac, s->R[1])); + s->R[R1] = x; + s->R[R2] = filtval; + s->init = _mm_setzero_ps(); + return filtval; +} + // Given a table of size N+1, N a power of 2, representing data between -1 and 1, interp template __m128 WS_PM1_LUT(const float *table, __m128 in) { @@ -259,7 +272,7 @@ template > table; - return WS_PM1_LUT<1024>(table.data, C(s, x, drive)); + return dcBlock<0, 1>(s, WS_PM1_LUT<1024>(table.data, C(s, x, drive))); } float FuzzCtrTable(const float x) @@ -278,16 +291,21 @@ float FuzzCtrTable(const float x) __m128 FuzzCtr(QuadFilterWaveshaperState *__restrict s, __m128 x, __m128 drive) { static LUTBase<2048, FuzzCtrTable> table; - return WS_PM1_LUT<2048>(table.data, TANH(s, x, drive)); + return dcBlock<0, 1>(s, WS_PM1_LUT<2048>(table.data, TANH(s, x, drive))); } -template <__m128 (*K)(__m128)> +template <__m128 (*K)(__m128), bool useDCBlock> __m128 CHEBY_CORE(QuadFilterWaveshaperState *__restrict s, __m128 x, __m128 drive) { static const auto m1 = _mm_set1_ps(-1.0f); static const auto p1 = _mm_set1_ps(1.0f); auto bound = K(_mm_max_ps(_mm_min_ps(x, p1), m1)); + + if (useDCBlock) + { + bound = dcBlock<0, 1>(s, bound); + } return TANH(s, bound, drive); } @@ -369,7 +387,7 @@ __m128 Plus12(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) { static const auto ser = ChebSeries<3>({0, 0.5, 0.5}); static const auto scale = _mm_set1_ps(0.66); - return ser.eval(TANH(s, _mm_mul_ps(in, scale), drive)); + return dcBlock<0, 1>(s, ser.eval(TANH(s, _mm_mul_ps(in, scale), drive))); } __m128 Plus13(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) @@ -383,7 +401,7 @@ __m128 Plus14(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) { static const auto ser = ChebSeries<5>({0, 0.5, 0, 0, 0.5}); static const auto scale = _mm_set1_ps(0.66); - return ser.eval(TANH(s, _mm_mul_ps(in, scale), drive)); + return dcBlock<0, 1>(s, ser.eval(TANH(s, _mm_mul_ps(in, scale), drive))); } __m128 Plus15(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) @@ -397,7 +415,7 @@ __m128 Plus12345(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 driv { static const auto ser = ChebSeries<6>({0, 0.2, 0.2, 0.2, 0.2, 0.2}); static const auto scale = _mm_set1_ps(0.66); - return ser.eval(TANH(s, _mm_mul_ps(in, scale), drive)); + return dcBlock<0, 1>(s, ser.eval(TANH(s, _mm_mul_ps(in, scale), drive))); } __m128 PlusSaw3(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) @@ -405,7 +423,7 @@ __m128 PlusSaw3(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive static const float fac = 0.9f / (1.f + 0.5 + 0.25); static const auto ser = ChebSeries<4>({0, -fac, fac * 0.5f, -fac * 0.25f}); static const auto scale = _mm_set1_ps(-0.66); // flip direction - return ser.eval(TANH(s, _mm_mul_ps(in, scale), drive)); + return dcBlock<0, 1>(s, ser.eval(TANH(s, _mm_mul_ps(in, scale), drive))); } __m128 PlusSqr3(QuadFilterWaveshaperState *__restrict s, __m128 in, __m128 drive) @@ -634,13 +652,13 @@ WaveshaperQFPtr GetQFPtrWaveshaper(int type) case wst_digital: return DIGI_SSE2; case wst_cheby2: - return CHEBY_CORE; + return CHEBY_CORE; case wst_cheby3: - return CHEBY_CORE; + return CHEBY_CORE; case wst_cheby4: - return CHEBY_CORE; + return CHEBY_CORE; case wst_cheby5: - return CHEBY_CORE; + return CHEBY_CORE; case wst_fwrectify: return ADAA_FULL_WAVE; case wst_poswav: diff --git a/src/gui/widgets/WaveShaperSelector.cpp b/src/gui/widgets/WaveShaperSelector.cpp index 324648becd2..30bdd74ca99 100644 --- a/src/gui/widgets/WaveShaperSelector.cpp +++ b/src/gui/widgets/WaveShaperSelector.cpp @@ -167,6 +167,7 @@ struct WaveShaperAnalysisWidget : public juce::Component, public juce::Slider::L { wss.R[i] = _mm_set1_ps(R[i]); } + wss.init = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps()); // better way? auto wsop = GetQFPtrWaveshaper(wstype); @@ -212,6 +213,9 @@ struct WaveShaperAnalysisWidget : public juce::Component, public juce::Slider::L ws2.R[i] = _mm_set1_ps(R[i]); } + ws1.init = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps()); // better way? + ws2.init = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps()); // better way? + auto wsop = GetQFPtrWaveshaper(wstype); for (int i = 0; i < npts; i++) @@ -286,6 +290,7 @@ void WaveShaperSelector::paint(juce::Graphics &g) initializeWaveshaperRegister(iValue, R); for (int i = 0; i < 4; ++i) s.R[i] = _mm_load_ps(R); + s.init = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps()); float dx = 0.05; // Give a few warmup pixels for the ADAAs @@ -296,6 +301,10 @@ void WaveShaperSelector::paint(juce::Graphics &g) vals[0] = x; auto in = _mm_load_ps(vals); auto r = wsop(&s, in, drive); + for (int i = 0; i < 4; ++i) + s.R[i] = _mm_load_ps(R); + s.init = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps()); + _mm_store_ps(vals, r); if (x >= -2) wsCurves[iValue].emplace_back(x, vals[0]);