From d7d46688f2b04435b69d191fe5e9f89bfa6b3a49 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 15 Jan 2025 21:08:33 -0500 Subject: [PATCH] MOve to libsamplerate sinc fastest downsampling (#127) * MOve to libsamplerate sinc fastest downsampling From the constant rates * Runtime selectable resampler settings as well as internal SR --- .gitmodules | 3 + CMakeLists.txt | 1 + libs/CMakeLists.txt | 2 + libs/libsamplerate | 1 + src/configuration.h | 8 +++ src/synth/patch.h | 17 +++++- src/synth/synth.cpp | 103 ++++++++++++++++++++++++++++++---- src/synth/synth.h | 10 +++- src/ui/playmode-sub-panel.cpp | 4 ++ src/ui/playmode-sub-panel.h | 2 + 10 files changed, 136 insertions(+), 15 deletions(-) create mode 160000 libs/libsamplerate diff --git a/.gitmodules b/.gitmodules index cfc080d..b932d36 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "libs/sst/sst-plugininfra"] path = libs/sst/sst-plugininfra url = https://github.com/surge-synthesizer/sst-plugininfra +[submodule "libs/libsamplerate"] + path = libs/libsamplerate + url = https://github.com/libsndfile/libsamplerate.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 548c57d..9288acb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ target_link_libraries(${PROJECT_NAME}-impl PRIVATE sst-plugininfra sst-plugininfra::filesystem sst-plugininfra::tinyxml sst-plugininfra::strnatcmp sst::clap_juce_shim sst::clap_juce_shim_headers ${PROJECT_NAME}-patches + samplerate ) # Hmm - why do I need this? diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 29e457c..8f56cc2 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -28,3 +28,5 @@ if (APPLE) endif() add_subdirectory(clap-libs/clap-wrapper) + +add_subdirectory(libsamplerate) \ No newline at end of file diff --git a/libs/libsamplerate b/libs/libsamplerate new file mode 160000 index 0000000..4858fb0 --- /dev/null +++ b/libs/libsamplerate @@ -0,0 +1 @@ +Subproject commit 4858fb016550d677de2356486bcceda5aed85a72 diff --git a/src/configuration.h b/src/configuration.h index 0a7a9d4..c889a01 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -43,6 +43,14 @@ enum SampleRateStrategy SR_220240 = 3 // or 5x }; +enum ResamplerEngine +{ + SRC_FAST, + SRC_MEDIUM, + SRC_BEST, + LANCZOS +}; + } // namespace baconpaul::six_sines inline std::string fileTrunc(const std::string &f) diff --git a/src/synth/patch.h b/src/synth/patch.h index b72087f..919b51b 100644 --- a/src/synth/patch.h +++ b/src/synth/patch.h @@ -1147,6 +1147,18 @@ struct Patch {SampleRateStrategy::SR_176192, "176.4/192 kHz"}, {SampleRateStrategy::SR_220240, "220.5/240 kHz"}, })), + resampleEngine(intMd() + .withName(name() + " Resampler Engine") + .withGroupName(name()) + .withDefault(ResamplerEngine::SRC_FAST) + .withRange(ResamplerEngine::SRC_FAST, ResamplerEngine::LANCZOS) + .withID(id(41)) + .withUnorderedMapFormatting({ + {ResamplerEngine::SRC_FAST, "SRC Fast (rec)"}, + {ResamplerEngine::SRC_MEDIUM, "SRC Medium"}, + {ResamplerEngine::SRC_BEST, "SRC Expensive"}, + {ResamplerEngine::LANCZOS, "Lanczos A=4"}, + })), ModulationMixin(name(), id(120)), modtarget(scpu::make_array_lambda( [this](int i) @@ -1177,7 +1189,7 @@ struct Patch Param mpeActive, mpeBendRange; Param octTranspose, fineTune, pan, lfoDepth; Param attackFloorOnRetrig, rephaseOnRetrigger; - Param sampleRateStrategy; + Param sampleRateStrategy, resampleEngine; std::array modtarget; @@ -1203,7 +1215,8 @@ struct Patch &lfoDepth, &attackFloorOnRetrig, &rephaseOnRetrigger, - &sampleRateStrategy}; + &sampleRateStrategy, + &resampleEngine}; appendDAHDSRParams(res); for (int i = 0; i < numModsPer; ++i) diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp index 4f2e820..f57d257 100644 --- a/src/synth/synth.cpp +++ b/src/synth/synth.cpp @@ -97,9 +97,45 @@ void Synth::setSampleRate(double sampleRate) lagHandler.setRate(60, blockSize, monoValues.sr.sampleRate); vuPeak.setSampleRate(monoValues.sr.sampleRate); + sampleRateRatio = hostSampleRate / engineSampleRate; - resampler = - std::make_unique((float)monoValues.sr.sampleRate, (float)hostSampleRate); + if (resamplerEngine == LANCZOS) + { + SXSNLOG("Setting Lanczos Resampler"); + resampler = + std::make_unique((float)monoValues.sr.sampleRate, (float)hostSampleRate); + } + else + { + if (lState) + { + src_delete(lState); + } + if (rState) + { + src_delete(rState); + } + int ec; + auto mode = SRC_SINC_FASTEST; + if (resamplerEngine == SRC_MEDIUM) + { + SXSNLOG("Setting SRC Resampler - SRC_SINC_MEDIUM_QUALITY"); + mode = SRC_SINC_MEDIUM_QUALITY; + } + if (resamplerEngine == SRC_BEST) + { + SXSNLOG("Setting SRC Resampler - SRC_SINC_BEST_QUALITY"); + mode = SRC_SINC_BEST_QUALITY; + } + else + { + SXSNLOG("Setting SRC Resampler - SRC_SINC_FASTEST"); + } + lState = src_new(mode, 1, &ec); + rState = src_new(mode, 1, &ec); + src_set_ratio(lState, sampleRateRatio); + src_set_ratio(rState, sampleRateRatio); + } } void Synth::process(const clap_output_events_t *outq) @@ -107,8 +143,6 @@ void Synth::process(const clap_output_events_t *outq) if (!SinTable::staticsInitialized) SinTable::initializeStatics(); - assert(resampler); - processUIQueue(outq); if (!audioRunning) @@ -119,8 +153,18 @@ void Synth::process(const clap_output_events_t *outq) monoValues.attackFloorOnRetrig = patch.output.attackFloorOnRetrig > 0.5; - while (resampler->inputsRequiredToGenerateOutputs(blockSize) > 0) + int loops{0}; + + SRC_DATA d; + + int generated{0}; + + if (resamplerEngine == LANCZOS) + generated = (resampler->inputsRequiredToGenerateOutputs(blockSize) > 0 ? 0 : blockSize); + + while (generated < blockSize) { + loops++; lagHandler.process(); float lOutput alignas(16)[2][blockSize]; @@ -159,9 +203,40 @@ void Synth::process(const clap_output_events_t *outq) assert(!v->next && !v->prior); } - for (int i = 0; i < blockSize; ++i) + if (resamplerEngine == LANCZOS) + { + for (int i = 0; i < blockSize; ++i) + { + resampler->push(lOutput[0][i], lOutput[1][i]); + } + generated = (resampler->inputsRequiredToGenerateOutputs(blockSize) > 0 ? 0 : blockSize); + } + else { - resampler->push(lOutput[0][i], lOutput[1][i]); + d.data_in = lOutput[0]; + d.data_out = output[0] + generated; + d.input_frames = blockSize; + d.output_frames = blockSize - generated; + d.end_of_input = 0; + d.src_ratio = sampleRateRatio; + + assert(d.input_frames_used == blockSize); + src_process(lState, &d); + auto lgen = d.output_frames_gen; + + d.data_in = lOutput[1]; + d.data_out = output[1] + generated; + d.input_frames = blockSize; + d.output_frames = blockSize - generated; + d.end_of_input = 0; + d.src_ratio = sampleRateRatio; + + assert(d.input_frames_used == blockSize); + src_process(rState, &d); + auto rgen = d.output_frames_gen; + + assert(lgen == rgen); + generated += lgen; } if (isEditorAttached) @@ -187,8 +262,11 @@ void Synth::process(const clap_output_events_t *outq) } } - resampler->populateNextBlockSize(output[0], output[1]); - resampler->renormalizePhases(); + if (resamplerEngine == LANCZOS) + { + resampler->populateNextBlockSize(output[0], output[1]); + resampler->renormalizePhases(); + } } void Synth::addToVoiceList(Voice *v) @@ -295,7 +373,8 @@ void Synth::processUIQueue(const clap_output_events_t *outq) dest->meta.id == patch.output.polyLimit.meta.id || dest->meta.id == patch.output.pianoModeActive.meta.id || dest->meta.id == patch.output.mpeActive.meta.id || - dest->meta.id == patch.output.sampleRateStrategy.meta.id) + dest->meta.id == patch.output.sampleRateStrategy.meta.id || + dest->meta.id == patch.output.resampleEngine.meta.id) { reapplyControlSettings(); } @@ -364,9 +443,11 @@ void Synth::processUIQueue(const clap_output_events_t *outq) void Synth::reapplyControlSettings() { - if (sampleRateStrategy != (SampleRateStrategy)patch.output.sampleRateStrategy.value) + if (sampleRateStrategy != (SampleRateStrategy)patch.output.sampleRateStrategy.value || + resamplerEngine != (ResamplerEngine)patch.output.resampleEngine.value) { sampleRateStrategy = (SampleRateStrategy)patch.output.sampleRateStrategy.value; + resamplerEngine = (ResamplerEngine)patch.output.resampleEngine.value; if (hostSampleRate > 0) { diff --git a/src/synth/synth.h b/src/synth/synth.h index 8ee3682..f9c6518 100644 --- a/src/synth/synth.h +++ b/src/synth/synth.h @@ -19,8 +19,10 @@ #include #include -#include #include "sst/basic-blocks/dsp/LanczosResampler.h" +#include "samplerate.h" + +#include #include "sst/basic-blocks/dsp/Lag.h" #include "sst/basic-blocks/dsp/VUPeak.h" #include "sst/basic-blocks/tables/EqualTuningProvider.h" @@ -45,9 +47,13 @@ struct Synth float output alignas(16)[2][blockSize]; SampleRateStrategy sampleRateStrategy{SampleRateStrategy::SR_110120}; + ResamplerEngine resamplerEngine{ResamplerEngine::SRC_FAST}; + using resampler_t = sst::basic_blocks::dsp::LanczosResampler; std::unique_ptr resampler; + SRC_STATE *lState{nullptr}, *rState{nullptr}; + Patch patch; MonoValues monoValues; @@ -233,7 +239,7 @@ struct Synth bool audioRunning{true}; - double hostSampleRate{0}, engineSampleRate{0}; + double hostSampleRate{0}, engineSampleRate{0}, sampleRateRatio{0}; void setSampleRate(double sampleRate); void process(const clap_output_events_t *); diff --git a/src/ui/playmode-sub-panel.cpp b/src/ui/playmode-sub-panel.cpp index e2776cb..741efe5 100644 --- a/src/ui/playmode-sub-panel.cpp +++ b/src/ui/playmode-sub-panel.cpp @@ -159,6 +159,8 @@ PlayModeSubPanel::PlayModeSubPanel(SixSinesEditor &e) : HasEditor(e) createComponent(editor, *this, editor.patchCopy.output.sampleRateStrategy, srStrat, srStratD); addAndMakeVisible(*srStrat); + createComponent(editor, *this, editor.patchCopy.output.resampleEngine, rsEng, rsEngD); + addAndMakeVisible(*rsEng); srStratLab = std::make_unique(); srStratLab->setText("Oversampling"); addAndMakeVisible(*srStratLab); @@ -240,6 +242,8 @@ void PlayModeSubPanel::resized() positionTitleLabelAt(depx, depy, bbx.getWidth(), srStratLab); bbx = juce::Rectangle(depx, depy + uicTitleLabelHeight, bbx.getWidth(), uicLabelHeight); srStrat->setBounds(bbx); + bbx = bbx.translated(0, uicMargin + uicLabelHeight); + rsEng->setBounds(bbx); } void PlayModeSubPanel::setTriggerButtonLabel() diff --git a/src/ui/playmode-sub-panel.h b/src/ui/playmode-sub-panel.h index c7562c0..a60edde 100644 --- a/src/ui/playmode-sub-panel.h +++ b/src/ui/playmode-sub-panel.h @@ -84,6 +84,8 @@ struct PlayModeSubPanel : juce::Component, HasEditor std::unique_ptr srStratLab; std::unique_ptr srStrat; std::unique_ptr srStratD; + std::unique_ptr rsEng; + std::unique_ptr rsEngD; void showPolyLimitMenu(); int getPolyLimit();