diff --git a/.gitignore b/.gitignore index a66be7a..7de4ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,7 @@ sdcard .vscode/ # temporary tests -src/test/*.bin +src/test/bin/ src/test/objects/ src/test/results/ -src/test/*.csv *xtleak.kcg* diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp index a8b50a9..a3f2f91 100644 --- a/src/fx_engine.hpp +++ b/src/fx_engine.hpp @@ -11,6 +11,8 @@ #include "fx_components.h" +#define STEP_UP(x) x + enum Format { FORMAT_12_BIT, @@ -369,7 +371,7 @@ public: { for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) { - c->lfo_value_[i] = this->lfo_[i]->process(); + c->lfo_value_[i] = STEP_UP(this->lfo_[i]->process()); } } } diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp index ce13c56..9c0d490 100644 --- a/src/fx_shimmer_reverb.cpp +++ b/src/fx_shimmer_reverb.cpp @@ -10,7 +10,9 @@ ShimmerReverb::ShimmerReverb(float32_t sampling_rate) : engine_(sampling_rate), input_gain_(-1.0f), diffusion_(-1.0f), - lp_(-1.0f) + lp_(-1.0f), + lp_decay_1_(0.0f), + lp_decay_2_(0.0f) { this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f); this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f); @@ -27,6 +29,8 @@ ShimmerReverb::~ShimmerReverb() void ShimmerReverb::reset() { this->engine_.reset(); + this->lp_decay_1_ = 0.0f; + this->lp_decay_2_ = 0.0f; } void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) @@ -96,7 +100,7 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, c.write(del1, 2.0f); c.write(wet, 0.0f); - outR = wet; + outL = wet; c.load(apout); c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt); diff --git a/src/test/Makefile b/src/test/Makefile index 58ab5ac..0df43b0 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -1,7 +1,9 @@ +BINDIR := bin OBJDIR := objects -OUTPUT_FOLDER = results -EXE := all_test.bin -BETA := beta.bin +OUTPUT_FOLDER := results + +EXE := $(BINDIR)/all_test.bin +BETA := $(BINDIR)/beta.bin CXX := g++ CXXFLAGS = -g -std=c++20 -MMD -MP @@ -44,7 +46,7 @@ BETAOBJS = $(BETASRCS:%.cpp=$(OBJDIR)/%.o) all: $(EXE) test -bin: $(EXE) +build: $(EXE) test: $(EXE) $(OUTPUT_FOLDER) rm -rf $(OUTPUT_FOLDER)/* @@ -54,6 +56,9 @@ test-debug: $(EXE) $(OUTPUT_FOLDER) rm -rf $(OUTPUT_FOLDER)/* valgrind --leak-check=full --leak-resolution=high --show-leak-kinds=all --xtree-leak=yes --show-mismatched-frees=yes --error-limit=no --log-file=$(OUTPUT_FOLDER)/valgrind-analysis-results.txt ./$(EXE) +$(BINDIR): + mkdir -p $@ + $(OBJDIR): mkdir -p $@ @@ -66,11 +71,11 @@ $(OBJDIR)/%.o: %.cpp $(OBJDIR) $(OBJDIR)/%.o: ../%.cpp $(OBJDIR) $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ -$(EXE): $(TST_OBJS) $(FX__OBJS) +$(EXE): $(BINDIR) $(TST_OBJS) $(FX__OBJS) $(LD) $(CXXFLAGS) $(call wildcard,$(TST_OBJS)) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS) -$(BETA): $(BETAOBJS) beta.cpp +$(BETA): $(BINDIR) $(BETAOBJS) $(FX__OBJS) $(LD) $(CXXFLAGS) $(BETAOBJS) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS) clean: - rm -rf *.o $(OBJDIR) $(EXE) $(OUTPUT_FOLDER) + rm -rf $(OBJDIR) $(BINDIR) $(OUTPUT_FOLDER) diff --git a/src/test/lowlevel.cpp b/src/test/lowlevel.cpp new file mode 100644 index 0000000..1ba89ce --- /dev/null +++ b/src/test/lowlevel.cpp @@ -0,0 +1,157 @@ +#include + +#include + +#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 ap1; + Engine::DelayLine ap2; + Engine::DelayLine ap3; + Engine::DelayLine ap4; + Engine::DelayLine dap1a; + Engine::DelayLine dap1b; + Engine::DelayLine del1; + Engine::DelayLine dap2a; + Engine::DelayLine dap2b; + Engine::DelayLine 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(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; +}