From eaaa43de1e3903c8ca5d62836773b7dca05d45bb Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Sat, 1 Apr 2023 12:40:32 -0400 Subject: [PATCH] Nimbus Effect at very high SampleRates or small Blocks Nimbus at very high sample rates or small blocks would have the internal oversampler mis-align and glitch or in some cases produce silence. I think this fixes it and also adds an assertive regtest. Addresses #6834 --- src/common/dsp/effects/NimbusEffect.cpp | 53 +++++++++++++++---------- src/common/dsp/effects/NimbusEffect.h | 2 +- src/surge-testrunner/UnitTestsFX.cpp | 52 +++++++++++++++++++++++- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/common/dsp/effects/NimbusEffect.cpp b/src/common/dsp/effects/NimbusEffect.cpp index 2575d4f37c7..e56408a021e 100644 --- a/src/common/dsp/effects/NimbusEffect.cpp +++ b/src/common/dsp/effects/NimbusEffect.cpp @@ -175,38 +175,47 @@ void NimbusEffect::process(float *dataL, float *dataR) if (frames_to_go > 0) { - numStubs = frames_to_go; - for (int i = 0; i < numStubs; ++i) + int startSub = numStubs; + int addStub = frames_to_go; + numStubs += frames_to_go; + + for (int i = 0; i < addStub; ++i) { - stub_input[0][i] = resample_into[consume_ptr][0]; - stub_input[1][i] = resample_into[consume_ptr][1]; + stub_input[0][i + startSub] = resample_into[consume_ptr][0]; + stub_input[1][i + startSub] = resample_into[consume_ptr][1]; consume_ptr++; } } - SRC_DATA odata; - odata.end_of_input = 0; - odata.src_ratio = processor_sr_inv * storage->samplerate; - odata.data_in = &(resample_this[0][0]); - odata.data_out = &(resample_into[0][0]); - odata.input_frames = outpos; - odata.output_frames = BLOCK_SIZE << 3; - auto reso = src_process(euroSR_to_surgeSR, &odata); - if (!builtBuffer) - created += odata.output_frames_gen; - - size_t w = resampWritePtr; - for (int i = 0; i < odata.output_frames_gen; ++i) + if (outpos > 0) { - resampled_output[w][0] = resample_into[i][0]; - resampled_output[w][1] = resample_into[i][1]; + SRC_DATA odata; + odata.end_of_input = 0; + odata.src_ratio = processor_sr_inv * storage->samplerate; + odata.data_in = &(resample_this[0][0]); + odata.data_out = &(resample_into[0][0]); + odata.input_frames = outpos; + odata.output_frames = BLOCK_SIZE << 3; + auto reso = src_process(euroSR_to_surgeSR, &odata); + if (!builtBuffer) + created += odata.output_frames_gen; + + size_t w = resampWritePtr; + for (int i = 0; i < odata.output_frames_gen; ++i) + { + resampled_output[w][0] = resample_into[i][0]; + resampled_output[w][1] = resample_into[i][1]; - w = (w + 1U) & (raw_out_sz - 1U); + w = (w + 1U) & (raw_out_sz - 1U); + } + resampWritePtr = w; } - resampWritePtr = w; } - bool rpi = (created) > (BLOCK_SIZE + 8); // leave some buffer + // If you hit this you need to adjust this gapping ratio probably. + static_assert(BLOCK_SIZE >= nimbusprocess_blocksize); + int ratio = std::max((int)std::ceil(processor_sr_inv * storage->samplerate) - 2, 0); + bool rpi = (created) > (BLOCK_SIZE * (1 + ratio) + 8); // leave some buffer if (rpi) builtBuffer = true; diff --git a/src/common/dsp/effects/NimbusEffect.h b/src/common/dsp/effects/NimbusEffect.h index 019276c0a66..e4e1430a891 100644 --- a/src/common/dsp/effects/NimbusEffect.h +++ b/src/common/dsp/effects/NimbusEffect.h @@ -79,7 +79,7 @@ class NimbusEffect : public Effect SRC_STATE_tag *surgeSR_to_euroSR, *euroSR_to_surgeSR; - static constexpr int raw_out_sz = BLOCK_SIZE_OS << 4; // power of 2 pls + static constexpr int raw_out_sz = BLOCK_SIZE_OS << 5; // power of 2 pls float resampled_output[raw_out_sz][2]; // at sr size_t resampReadPtr = 0, resampWritePtr = 1; // see comment in init diff --git a/src/surge-testrunner/UnitTestsFX.cpp b/src/surge-testrunner/UnitTestsFX.cpp index 95dd0a34e79..00613815c12 100644 --- a/src/surge-testrunner/UnitTestsFX.cpp +++ b/src/surge-testrunner/UnitTestsFX.cpp @@ -315,4 +315,54 @@ TEST_CASE("Waveshaper Pop", "[fx]") } } } -} \ No newline at end of file +} + +TEST_CASE("High SampleRate Nimbus", "[fx]") +{ + for (auto base : {44100, 48000}) + { + for (int m = 1; m <= 8; m *= 2) + { + DYNAMIC_SECTION("High Sample Rate " + std::to_string(base) + " * " + std::to_string(m)) + { + auto surge = Surge::Headless::createSurge(base * m); + REQUIRE(surge); + + for (int i = 0; i < 100; ++i) + surge->process(); + + auto *pt = &(surge->storage.getPatch().fx[0].type); + auto awv = 1.f * fxt_nimbus / (pt->val_max.i - pt->val_min.i); + + auto did = surge->idForParameter(pt); + surge->setParameter01(did, awv, false); + surge->process(); + + auto sp = [&](auto id, auto val) { + auto *pawt = &(surge->storage.getPatch().fx[0].p[id]); + auto did = surge->idForParameter(pawt); + surge->setParameter01(did, val, false); + }; + sp(2, 0.5f); // position + sp(5, 0.75f); // density + sp(11, 1.f); // mix + + surge->playNote(0, 60, 127, 0); + auto maxAmp = -10.f; + for (int i = 0; i < 5000 * m; ++i) + { + surge->process(); + + for (int s = 0; s < BLOCK_SIZE; ++s) + maxAmp = std::max(maxAmp, surge->output[0][s]); + } + REQUIRE(maxAmp > 0.1); + surge->releaseNote(0, 60, 0); + for (int i = 0; i < 500; ++i) + { + surge->process(); + } + } + } + } +}