From 2a272801d3c05822253f15916ee83e3b46ee1c9c Mon Sep 17 00:00:00 2001
From: abscisys <vincent_gauche@hotmail.com>
Date: Sat, 4 Feb 2023 03:38:07 +0100
Subject: [PATCH] Fixing the Shimmer Reverb unstability

---
 src/fx_components.h               |   4 +-
 src/fx_engine.hpp                 |   6 +-
 src/fx_rack.cpp                   |   5 +-
 src/fx_shimmer_reverb.cpp         |   6 +-
 src/test/Makefile                 |   6 +-
 src/test/beta_lowlevel.cpp        | 441 ++++++++++++++++++++++++++++++
 src/test/lowlevel.cpp             | 157 -----------
 src/test/test_cpp_performance.cpp |   2 +-
 src/test/test_fx_helper.h         |   2 +-
 9 files changed, 457 insertions(+), 172 deletions(-)
 create mode 100644 src/test/beta_lowlevel.cpp
 delete mode 100644 src/test/lowlevel.cpp

diff --git a/src/fx_components.h b/src/fx_components.h
index 03e8a8a..81837c5 100644
--- a/src/fx_components.h
+++ b/src/fx_components.h
@@ -150,7 +150,7 @@ public:
 
 private:
     static bool ClassInitializer();
-    static const size_t DataPointSize = 352800;
+    static const size_t DataPointSize = 176400;
     static const float32_t DeltaTime; 
     static float32_t DataPoints[];
 
@@ -311,7 +311,7 @@ private:
 };
 
 
-typedef ComplexLFO LFO;
+typedef InterpolatedSineOscillator LFO;
 
 
 class JitterGenerator : public FXBase
diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp
index a3f2f91..1c2d9ee 100644
--- a/src/fx_engine.hpp
+++ b/src/fx_engine.hpp
@@ -274,6 +274,7 @@ public:
         inline void read(D& d, int32_t offset, float32_t scale)
         {
             assert((D::base + D::length) <= size);
+
             T r;
             if(offset == -1)
             {
@@ -314,8 +315,9 @@ public:
             int32_t offset_integral = static_cast<int32_t>(offset);
             float32_t offset_fractional = offset - static_cast<float32_t>(offset_integral);
 
-            float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]);
-            float32_t b = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]);
+            int32_t index = this->write_ptr_ + offset_integral + D::base;
+            float32_t a = DataType<format>::decompress(this->buffer_[index & MASK]);
+            float32_t b = DataType<format>::decompress(this->buffer_[(index + 1) & MASK]);
             float32_t x = a + (b - a) * offset_fractional;
 
             this->previous_read_ = x;
diff --git a/src/fx_rack.cpp b/src/fx_rack.cpp
index b08e964..7d00652 100644
--- a/src/fx_rack.cpp
+++ b/src/fx_rack.cpp
@@ -50,10 +50,9 @@ inline void FXRack::reset()
 
 inline void FXRack::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
 {
-    FXChain::iterator end = this->fx_chain_.end();
-    for(FXChain::iterator it = this->fx_chain_.begin(); it != end; it++)
+    for(FXElement* fx : this->fx_chain_)
     {
-        (*it)->processSample(inL, inR, outL, outR);
+        fx->processSample(inL, inR, outL, outR);
 
         inL = outL;
         inR = outR;
diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp
index 9c0d490..d9267bf 100644
--- a/src/fx_shimmer_reverb.cpp
+++ b/src/fx_shimmer_reverb.cpp
@@ -97,20 +97,20 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
     c.writeAllPass(dap1a, kap);
     c.read(dap1b TAIL, kap);
     c.writeAllPass(dap1b, -kap);
-    c.write(del1, 2.0f);
+    c.write(del1, 1.5f);
     c.write(wet, 0.0f);
 
     outL = wet;
 
     c.load(apout);
-    c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt);
+    // c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt);
     c.read(del1 TAIL, krt);
     c.lp(lp_2, klp);
     c.read(dap2a TAIL, kap);
     c.writeAllPass(dap2a, -kap);
     c.read(dap2b TAIL, -kap);
     c.writeAllPass(dap2b, kap);
-    c.write(del2, 2.0f);
+    c.write(del2, 1.5f);
     c.write(wet, 0.0f);
 
     outR = wet;
diff --git a/src/test/Makefile b/src/test/Makefile
index 0df43b0..88a71b1 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -2,7 +2,7 @@ BINDIR := bin
 OBJDIR := objects
 OUTPUT_FOLDER := results
 
-EXE := $(BINDIR)/all_test.bin
+EXE := $(BINDIR)/all_tests.bin
 BETA := $(BINDIR)/beta.bin
 
 CXX := g++
@@ -32,9 +32,9 @@ FX__SRCS += ../fx_shimmer_reverb.cpp
 FX__SRCS += ../fx_dry.cpp
 FX__SRCS += ../fx_rack.cpp
 
-TST_SRCS := $(filter-out waveplay.cpp, $(filter-out beta.cpp, $(wildcard *.cpp)))
+TST_SRCS := $(filter-out waveplay.cpp $(wildcard beta*.cpp), $(wildcard *.cpp))
 
-BETASRCS := beta.cpp
+BETASRCS := $(wildcard beta*.cpp)
 BETASRCS += arm_functions.cpp
 BETASRCS += wavein.cpp
 BETASRCS += waveout.cpp
diff --git a/src/test/beta_lowlevel.cpp b/src/test/beta_lowlevel.cpp
new file mode 100644
index 0000000..f53c820
--- /dev/null
+++ b/src/test/beta_lowlevel.cpp
@@ -0,0 +1,441 @@
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <iomanip>
+
+#include "test_fx_helper.h"
+#include "../fx_engine.hpp"
+
+#define PRINT_EXEC(ctx, x) \
+    std::cout.fill(' '); \
+    std::cout.width(80); \
+    std::cout << std::left; \
+    std::cout.precision(6); \
+    std::cout << std::fixed; \
+    std::cout << #x; \
+    x \
+    { \
+        float32_t v = 0.0f; \
+        ctx.write(v); \
+        std::cout << " // accumulator_: " << showpos << v; \
+    } \
+    std::cout << std::endl
+
+#define TAIL , -1
+
+typedef FxEngine<16384, Format::FORMAT_FLOAT32, true> Engine;
+
+void processDebugShimmerSample(
+    Engine& engine_, size_t index, 
+    float32_t& lp_decay_1_, float32_t& lp_decay_2_, 
+    float32_t inL, float32_t inR, 
+    float32_t& outL, float32_t& outR)
+{
+    // This is the Griesinger topology described in the Dattorro paper
+    // (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay).
+    // Modulation is applied in the loop of the first diffuser AP for additional
+    // smearing; and to the two long delays for a slow shimmer/chorus effect.
+    typedef Engine::Reserve< 113,
+            Engine::Reserve< 162,
+            Engine::Reserve< 241,
+            Engine::Reserve< 399,
+            Engine::Reserve<1653,
+            Engine::Reserve<2038,
+            Engine::Reserve<3411,
+            Engine::Reserve<1913,
+            Engine::Reserve<1663,
+            Engine::Reserve<4782> > > > > > > > > > Memory;
+    Engine::DelayLine<Memory, 0> ap1;
+    Engine::DelayLine<Memory, 1> ap2;
+    Engine::DelayLine<Memory, 2> ap3;
+    Engine::DelayLine<Memory, 3> ap4;
+    Engine::DelayLine<Memory, 4> dap1a;
+    Engine::DelayLine<Memory, 5> dap1b;
+    Engine::DelayLine<Memory, 6> del1;
+    Engine::DelayLine<Memory, 7> dap2a;
+    Engine::DelayLine<Memory, 8> dap2b;
+    Engine::DelayLine<Memory, 9> del2;
+    Engine::Context c;
+
+    const float32_t kap = 0.8f;
+    const float32_t klp = 0.7f;
+    const float32_t krt = 0.75f;
+    const float32_t gain = 0.55f;
+
+    float32_t lp_1 = lp_decay_1_;
+    float32_t lp_2 = lp_decay_2_;
+
+    float32_t wet = 0.0f;
+    float32_t apout = 0.0f;
+    engine_.start(&c);
+
+    // Smear AP1 inside the loop.
+    PRINT_EXEC(c, c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f););
+    PRINT_EXEC(c, c.write(ap1, 100, 0.0f););
+    PRINT_EXEC(c, c.read(inL + inR, gain););
+
+    // Diffuse through 4 allpasses.
+    PRINT_EXEC(c, c.read(ap1 TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(ap1, -kap););
+    PRINT_EXEC(c, c.read(ap2 TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(ap2, -kap););
+    PRINT_EXEC(c, c.read(ap3 TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(ap3, -kap););
+    PRINT_EXEC(c, c.read(ap4 TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(ap4, -kap););
+    PRINT_EXEC(c, c.write(apout););
+      
+    // Main reverb loop.
+    PRINT_EXEC(c, c.load(apout););
+    PRINT_EXEC(c, c.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt););
+    PRINT_EXEC(c, c.lp(lp_1, klp););
+    PRINT_EXEC(c, c.read(dap1a TAIL, -kap););
+    PRINT_EXEC(c, c.writeAllPass(dap1a, kap););
+    PRINT_EXEC(c, c.read(dap1b TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(dap1b, -kap););
+    PRINT_EXEC(c, c.write(del1, 1.5f););
+    PRINT_EXEC(c, c.write(wet, 0.0f););
+
+    outL = wet;
+
+
+    PRINT_EXEC(c, c.load(apout););
+    // PRINT_EXEC(c, c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt););
+    PRINT_EXEC(c, c.read(del1 TAIL, krt););
+    PRINT_EXEC(c, c.lp(lp_2, klp););
+    PRINT_EXEC(c, c.read(dap2a TAIL, kap););
+    PRINT_EXEC(c, c.writeAllPass(dap2a, -kap););
+    PRINT_EXEC(c, c.read(dap2b TAIL, -kap););
+    PRINT_EXEC(c, c.writeAllPass(dap2b, kap););
+    PRINT_EXEC(c, c.write(del2, 1.5f););
+    PRINT_EXEC(c, c.write(wet, 0.0f););
+
+    outR = wet;
+
+
+    lp_decay_1_ = lp_1;
+    lp_decay_2_ = lp_2;
+
+    std::cout << "Index # " << index << " - ( " << inL << ", " << inR << " ) ==> ( " << outL << ", " << outR << " )" << std::endl;
+    std::cout << std::endl << "***********************************************************************************************************" << std::endl << std::endl;
+}
+
+TEST(LowLevel, TestDiracShimmerAlgo)
+{
+    const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info();
+    std::string full_test_name = test_info->test_case_name();
+    full_test_name += ".";
+    full_test_name += test_info->name();
+
+    Engine engine_(SAMPLING_FREQUENCY);
+    engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
+    engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
+    engine_.reset();
+
+    float32_t lp1 = 0.0f;
+    float32_t lp2 = 0.0f;
+
+    const size_t size = static_cast<float32_t>(SAMPLING_FREQUENCY) * 4;
+    float32_t* inSamples = new float32_t[size];
+    memset(inSamples, 0, size * sizeof(float32_t));
+    inSamples[0] = 1.0f;
+
+    float32_t* outSamplesL = new float32_t[size];
+    float32_t* outSamplesR = new float32_t[size];
+    memset(outSamplesL, 0, size * sizeof(float32_t));
+    memset(outSamplesR, 0, size * sizeof(float32_t));
+
+    for(size_t i = 0; i < size; ++i)
+    {
+        processDebugShimmerSample(engine_, i, lp1, lp2, inSamples[i], inSamples[i], outSamplesL[i], outSamplesR[i]);
+    }
+
+    saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16);
+
+    delete[] outSamplesL;
+    delete[] outSamplesR;
+
+    delete[] inSamples;
+}
+
+void processShimmerSample(
+    Engine& engine_L_, Engine& engine_R_, size_t index, 
+    float32_t& lp_decay_1_, float32_t& lp_decay_2_, 
+    float32_t inL, float32_t inR, 
+    float32_t& outL, float32_t& outR)
+{
+    // This is the Griesinger topology described in the Dattorro paper
+    // (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay).
+    // Modulation is applied in the loop of the first diffuser AP for additional
+    // smearing; and to the two long delays for a slow shimmer/chorus effect.
+    typedef Engine::Reserve< 113,
+            Engine::Reserve< 162,
+            Engine::Reserve< 241,
+            Engine::Reserve< 399,
+            Engine::Reserve<1653,
+            Engine::Reserve<2038,
+            Engine::Reserve<3411,
+            Engine::Reserve<1913,
+            Engine::Reserve<1663,
+            Engine::Reserve<4782> > > > > > > > > > Memory;
+    Engine::DelayLine<Memory, 0> ap1;
+    Engine::DelayLine<Memory, 1> ap2;
+    Engine::DelayLine<Memory, 2> ap3;
+    Engine::DelayLine<Memory, 3> ap4;
+    Engine::DelayLine<Memory, 4> dap1a;
+    Engine::DelayLine<Memory, 5> dap1b;
+    Engine::DelayLine<Memory, 6> del1;
+    Engine::DelayLine<Memory, 7> dap2a;
+    Engine::DelayLine<Memory, 8> dap2b;
+    Engine::DelayLine<Memory, 9> del2;
+    Engine::Context cL;
+    Engine::Context cR;
+
+    const float32_t kap = 0.8f;
+    const float32_t klp = 0.7f;
+    const float32_t krt = 0.75f;
+    const float32_t gain = 0.55f;
+
+    float32_t lp_1 = lp_decay_1_;
+    float32_t lp_2 = lp_decay_2_;
+
+    float32_t wet = 0.0f;
+    float32_t apout = 0.0f;
+    engine_L_.start(&cL);
+    engine_R_.start(&cR);
+
+    // Smear AP1 inside the loop.
+    cL.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f);
+    cL.write(ap1, 100, 0.0f);
+    cL.read(inL, gain);
+
+    // Diffuse through 4 allpasses.
+    cL.read(ap1 TAIL, kap);
+    cL.writeAllPass(ap1, -kap);
+    cL.read(ap2 TAIL, kap);
+    cL.writeAllPass(ap2, -kap);
+    cL.read(ap3 TAIL, kap);
+    cL.writeAllPass(ap3, -kap);
+    cL.read(ap4 TAIL, kap);
+    cL.writeAllPass(ap4, -kap);
+    cL.write(apout);
+      
+    // Main reverb loop.
+    cL.load(apout);
+    cL.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt);
+    cL.lp(lp_1, klp);
+    cL.read(dap1a TAIL, -kap);
+    cL.writeAllPass(dap1a, kap);
+    cL.read(dap1b TAIL, kap);
+    cL.writeAllPass(dap1b, -kap);
+    cL.write(del1, 1.5f);
+    cL.write(wet, 0.0f);
+
+    outL = wet;
+
+
+    // Smear AP1 inside the loop.
+    cR.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f);
+    cR.write(ap1, 100, 0.0f);
+    cR.read(inL + inR, gain);
+
+    // Diffuse through 4 allpasses.
+    cR.read(ap1 TAIL, kap);
+    cR.writeAllPass(ap1, -kap);
+    cR.read(ap2 TAIL, kap);
+    cR.writeAllPass(ap2, -kap);
+    cR.read(ap3 TAIL, kap);
+    cR.writeAllPass(ap3, -kap);
+    cR.read(ap4 TAIL, kap);
+    cR.writeAllPass(ap4, -kap);
+    cR.write(apout);
+      
+    // Main reverb loop.
+    cR.load(apout);
+    // cR.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt);
+    cR.read(del1 TAIL, krt);
+    cR.lp(lp_2, klp);
+    cR.read(dap2a TAIL, kap);
+    cR.writeAllPass(dap2a, -kap);
+    cR.read(dap2b TAIL, -kap);
+    cR.writeAllPass(dap2b, kap);
+    cR.write(del2, 1.5f);
+    cR.write(wet, 0.0f);
+
+    outR = wet;
+
+
+    lp_decay_1_ = lp_1;
+    lp_decay_2_ = lp_2;
+}
+
+TEST(LowLevel, TestStereoShimmerAlgo)
+{
+    const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info();
+    std::string full_test_name = test_info->test_case_name();
+    full_test_name += ".";
+    full_test_name += test_info->name();
+
+    Engine engine_L_(SAMPLING_FREQUENCY);
+    Engine engine_R_(SAMPLING_FREQUENCY);
+    engine_L_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
+    engine_L_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
+    engine_L_.reset();
+    engine_R_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
+    engine_R_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
+    engine_R_.reset();
+
+    float32_t lp1 = 0.0f;
+    float32_t lp2 = 0.0f;
+
+    size_t size = 0;
+    float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size);
+
+    float32_t* outSamplesL = new float32_t[size];
+    float32_t* outSamplesR = new float32_t[size];
+    memset(outSamplesL, 0, size * sizeof(float32_t));
+    memset(outSamplesR, 0, size * sizeof(float32_t));
+
+    for(size_t i = 0; i < size; ++i)
+    {
+        processShimmerSample(engine_L_, engine_R_, i, lp1, lp2, inSamples[0][i], inSamples[1][i], outSamplesL[i], outSamplesR[i]);
+    }
+
+    saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16);
+
+    delete[] outSamplesL;
+    delete[] outSamplesR;
+
+    delete[] inSamples[0];
+    delete[] inSamples[1];
+    delete[] inSamples;
+}
+
+void processShimmerSample(
+    Engine& engine_, size_t index, 
+    float32_t& lp_decay_1_, float32_t& lp_decay_2_, 
+    float32_t inL, float32_t inR, 
+    float32_t& outL, float32_t& outR)
+{
+    // This is the Griesinger topology described in the Dattorro paper
+    // (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay).
+    // Modulation is applied in the loop of the first diffuser AP for additional
+    // smearing; and to the two long delays for a slow shimmer/chorus effect.
+    typedef Engine::Reserve< 113,
+            Engine::Reserve< 162,
+            Engine::Reserve< 241,
+            Engine::Reserve< 399,
+            Engine::Reserve<1653,
+            Engine::Reserve<2038,
+            Engine::Reserve<3411,
+            Engine::Reserve<1913,
+            Engine::Reserve<1663,
+            Engine::Reserve<4782> > > > > > > > > > Memory;
+    Engine::DelayLine<Memory, 0> ap1;
+    Engine::DelayLine<Memory, 1> ap2;
+    Engine::DelayLine<Memory, 2> ap3;
+    Engine::DelayLine<Memory, 3> ap4;
+    Engine::DelayLine<Memory, 4> dap1a;
+    Engine::DelayLine<Memory, 5> dap1b;
+    Engine::DelayLine<Memory, 6> del1;
+    Engine::DelayLine<Memory, 7> dap2a;
+    Engine::DelayLine<Memory, 8> dap2b;
+    Engine::DelayLine<Memory, 9> del2;
+    Engine::Context c;
+
+    const float32_t kap = 0.8f;
+    const float32_t klp = 0.7f;
+    const float32_t krt = 0.75f;
+    const float32_t gain = 0.55f;
+
+    float32_t lp_1 = lp_decay_1_;
+    float32_t lp_2 = lp_decay_2_;
+
+    float32_t wet = 0.0f;
+    float32_t apout = 0.0f;
+    engine_.start(&c);
+
+    // Smear AP1 inside the loop.
+    c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f);
+    c.write(ap1, 100, 0.0f);
+    c.read(inL + inR, gain);
+
+    // Diffuse through 4 allpasses.
+    c.read(ap1 TAIL, kap);
+    c.writeAllPass(ap1, -kap);
+    c.read(ap2 TAIL, kap);
+    c.writeAllPass(ap2, -kap);
+    c.read(ap3 TAIL, kap);
+    c.writeAllPass(ap3, -kap);
+    c.read(ap4 TAIL, kap);
+    c.writeAllPass(ap4, -kap);
+    c.write(apout);
+      
+    // Main reverb loop.
+    c.load(apout);
+    c.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt);
+    c.lp(lp_1, klp);
+    c.read(dap1a TAIL, -kap);
+    c.writeAllPass(dap1a, kap);
+    c.read(dap1b TAIL, kap);
+    c.writeAllPass(dap1b, -kap);
+    c.write(del1, 1.5f);
+    c.write(wet, 0.0f);
+
+    outL = wet;
+
+
+    c.load(apout);
+    c.read(del1 TAIL, krt);
+    c.lp(lp_2, klp);
+    c.read(dap2a TAIL, kap);
+    c.writeAllPass(dap2a, -kap);
+    c.read(dap2b TAIL, -kap);
+    c.writeAllPass(dap2b, kap);
+    c.write(del2, 1.5f);
+    c.write(wet, 0.0f);
+
+    outR = wet;
+
+
+    lp_decay_1_ = lp_1;
+    lp_decay_2_ = lp_2;
+}
+
+TEST(LowLevel, TestMonoShimmerAlgo)
+{
+    const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info();
+    std::string full_test_name = test_info->test_case_name();
+    full_test_name += ".";
+    full_test_name += test_info->name();
+
+    Engine engine_(SAMPLING_FREQUENCY);
+    engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
+    engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
+    engine_.reset();
+
+    float32_t lp1 = 0.0f;
+    float32_t lp2 = 0.0f;
+
+    size_t size = 0;
+    float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size);
+
+    float32_t* outSamplesL = new float32_t[size];
+    float32_t* outSamplesR = new float32_t[size];
+    memset(outSamplesL, 0, size * sizeof(float32_t));
+    memset(outSamplesR, 0, size * sizeof(float32_t));
+
+    for(size_t i = 0; i < size; ++i)
+    {
+        processShimmerSample(engine_, i, lp1, lp2, inSamples[0][i], inSamples[1][i], outSamplesL[i], outSamplesR[i]);
+    }
+
+    saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16);
+
+    delete[] outSamplesL;
+    delete[] outSamplesR;
+
+    delete[] inSamples[0];
+    delete[] inSamples[1];
+    delete[] inSamples;
+}
diff --git a/src/test/lowlevel.cpp b/src/test/lowlevel.cpp
deleted file mode 100644
index 1ba89ce..0000000
--- a/src/test/lowlevel.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <iostream>
-
-#include "test_fx_helper.h"
-#include "../fx_engine.hpp"
-
-#define EXEC_PRINT(ctx, x) \
-    std::cout.fill(' '); \
-    std::cout.width(80); \
-    std::cout << std::left; \
-    std::cout.precision(6); \
-    std::cout << std::fixed; \
-    std::cout << #x; \
-    x \
-    { \
-        float32_t v = 0.0f; \
-        ctx.write(v); \
-        std::cout << " // accumulator_: " << v; \
-    } \
-    std::cout << std::endl
-
-#define TAIL , -1
-
-typedef FxEngine<16384, Format::FORMAT_FLOAT32, true> Engine;
-
-void processShimmerSample(
-    Engine& engine_, size_t index, 
-    float32_t& lp_decay_1_, float32_t& lp_decay_2_, 
-    float32_t inL, float32_t inR, 
-    float32_t& outL, float32_t& outR)
-{
-    // This is the Griesinger topology described in the Dattorro paper
-    // (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay).
-    // Modulation is applied in the loop of the first diffuser AP for additional
-    // smearing; and to the two long delays for a slow shimmer/chorus effect.
-    typedef Engine::Reserve< 113,
-            Engine::Reserve< 162,
-            Engine::Reserve< 241,
-            Engine::Reserve< 399,
-            Engine::Reserve<1653,
-            Engine::Reserve<2038,
-            Engine::Reserve<3411,
-            Engine::Reserve<1913,
-            Engine::Reserve<1663,
-            Engine::Reserve<4782> > > > > > > > > > Memory;
-    Engine::DelayLine<Memory, 0> ap1;
-    Engine::DelayLine<Memory, 1> ap2;
-    Engine::DelayLine<Memory, 2> ap3;
-    Engine::DelayLine<Memory, 3> ap4;
-    Engine::DelayLine<Memory, 4> dap1a;
-    Engine::DelayLine<Memory, 5> dap1b;
-    Engine::DelayLine<Memory, 6> del1;
-    Engine::DelayLine<Memory, 7> dap2a;
-    Engine::DelayLine<Memory, 8> dap2b;
-    Engine::DelayLine<Memory, 9> del2;
-    Engine::Context c;
-
-    const float32_t kap = 0.8f;
-    const float32_t klp = 0.7f;
-    const float32_t krt = 0.75f;
-    const float32_t gain = 0.55f;
-
-    float32_t lp_1 = lp_decay_1_;
-    float32_t lp_2 = lp_decay_2_;
-
-    float32_t wet = 0.0f;
-    float32_t apout = 0.0f;
-    engine_.start(&c);
-
-    // Smear AP1 inside the loop.
-    EXEC_PRINT(c, c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f););
-    EXEC_PRINT(c, c.write(ap1, 100, 0.0f););
-    EXEC_PRINT(c, c.read(inL, gain););
-
-    // Diffuse through 4 allpasses.
-    EXEC_PRINT(c, c.read(ap1 TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(ap1, -kap););
-    EXEC_PRINT(c, c.read(ap2 TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(ap2, -kap););
-    EXEC_PRINT(c, c.read(ap3 TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(ap3, -kap););
-    EXEC_PRINT(c, c.read(ap4 TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(ap4, -kap););
-    EXEC_PRINT(c, c.write(apout););
-      
-    // Main reverb loop.
-    EXEC_PRINT(c, c.load(apout););
-    EXEC_PRINT(c, c.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt););
-    EXEC_PRINT(c, c.lp(lp_1, klp););
-    EXEC_PRINT(c, c.read(dap1a TAIL, -kap););
-    EXEC_PRINT(c, c.writeAllPass(dap1a, kap););
-    EXEC_PRINT(c, c.read(dap1b TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(dap1b, -kap););
-    EXEC_PRINT(c, c.write(del1, 2.0f););
-    EXEC_PRINT(c, c.write(wet, 0.0f););
-
-    outL = wet;
-
-
-    EXEC_PRINT(c, c.load(apout););
-    EXEC_PRINT(c, c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt););
-    EXEC_PRINT(c, c.read(del1 TAIL, krt););
-    EXEC_PRINT(c, c.lp(lp_2, klp););
-    EXEC_PRINT(c, c.read(dap2a TAIL, kap););
-    EXEC_PRINT(c, c.writeAllPass(dap2a, -kap););
-    EXEC_PRINT(c, c.read(dap2b TAIL, -kap););
-    EXEC_PRINT(c, c.writeAllPass(dap2b, kap););
-    EXEC_PRINT(c, c.write(del2, 2.0f););
-    EXEC_PRINT(c, c.write(wet, 0.0f););
-
-    outR = wet;
-
-    std::cout << "Index # " << index << " - ( " << inL << ", " << inR << " ) ==> ( " << outL << ", " << outR << " )" << std::endl;
-    std::cout << std::endl << "**********************************************************************************************************" << std::endl << std::endl;
-
-    lp_decay_1_ = lp_1;
-    lp_decay_2_ = lp_2;
-}
-
-TEST(LowLevel, TestShimmerAlgo)
-{
-    const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info();
-    std::string full_test_name = test_info->test_case_name();
-    full_test_name += ".";
-    full_test_name += test_info->name();
-
-    Engine engine_(SAMPLING_FREQUENCY);
-    engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
-    engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
-    engine_.reset();
-
-    float32_t lp1 = 0.0f;
-    float32_t lp2 = 0.0f;
-
-    const size_t size = static_cast<float32_t>(SAMPLING_FREQUENCY);
-    float32_t* inSamples = new float32_t[size];
-    memset(inSamples, 0, size * sizeof(float32_t));
-    inSamples[0] = 1.0f;
-
-    float32_t* outSamplesL = new float32_t[size];
-    float32_t* outSamplesR = new float32_t[size];
-    memset(outSamplesL, 0, size * sizeof(float32_t));
-    memset(outSamplesR, 0, size * sizeof(float32_t));
-
-    for(size_t i = 0; i < size; ++i)
-    {
-        processShimmerSample(engine_, i, lp1, lp2, inSamples[i], inSamples[i], outSamplesL[i], outSamplesR[i]);
-    }
-
-    saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16);
-
-    delete[] outSamplesL;
-    delete[] outSamplesR;
-
-    delete[] inSamples;
-}
diff --git a/src/test/test_cpp_performance.cpp b/src/test/test_cpp_performance.cpp
index d1d3e3e..99ab2ee 100644
--- a/src/test/test_cpp_performance.cpp
+++ b/src/test/test_cpp_performance.cpp
@@ -29,7 +29,7 @@ TEST(CppPerformance, LFOPerformance_ComplexLFO_InterpolatedSineOscillator)
     }
     auto d2 = LAP_TIME("lfo2");
 
-    EXPECT_LE(d1, d2 + 100);
+    EXPECT_GE(d1, d2);
 }
 
 TEST(CppPerformance, LFOPerformance_ComplexLFO_FastLFO)
diff --git a/src/test/test_fx_helper.h b/src/test/test_fx_helper.h
index 4f6f2c5..22098a1 100644
--- a/src/test/test_fx_helper.h
+++ b/src/test/test_fx_helper.h
@@ -7,7 +7,7 @@
 #include "wave.h"
 #include "../fx.h"
 
-#define AUDIO_SOURCE_FILE "test.wav"
+#define AUDIO_SOURCE_FILE "test2.wav"
 
 #define SAMPLING_FREQUENCY 44100.0f