From 207530bbb5b8394c4c968622d6a0e8815fa0c420 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 18:58:53 -0700 Subject: [PATCH 1/3] [BUGFIX] Avoid prewarming container slimmable model when SetSlimmableSize doesn't change which model is active. --- NAM/container.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/NAM/container.cpp b/NAM/container.cpp index 8442e173..8d6b6fe2 100644 --- a/NAM/container.cpp +++ b/NAM/container.cpp @@ -69,18 +69,25 @@ void ContainerModel::Reset(const double sampleRate, const int maxBufferSize) void ContainerModel::SetSlimmableSize(const double val) { - _active_index = _submodels.size() - 1; + auto active_index = _submodels.size() - 1; for (size_t i = 0; i < _submodels.size(); ++i) { if (val < _submodels[i].max_value) { - _active_index = i; + active_index = i; break; } } - + if (active_index == _active_index) // No change to active model, so nothing to do + { + return; + } + // Setting _active_index puts the model in the RT path, so prewarm before doing that const double sr = mHaveExternalSampleRate ? mExternalSampleRate : mExpectedSampleRate; - _active_model().ResetAndPrewarm(sr, GetMaxBufferSize()); + _submodels[active_index].model->ResetAndPrewarm(sr, GetMaxBufferSize()); + + // Finally set when we're ready: + _active_index = active_index; } // ============================================================================= From ddcbc55bd20f660160aa4e286368aa034e6bd24a Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 19:16:21 -0700 Subject: [PATCH 2/3] [TEST] Ensure consistent model state before processing in test_container Added calls to ResetAndPrewarm in test_container_default_is_max_size to guarantee that both predictions start from the same model state, enhancing test reliability. --- tools/test/test_container.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/test/test_container.cpp b/tools/test/test_container.cpp index 993c9065..ea18c62e 100644 --- a/tools/test/test_container.cpp +++ b/tools/test/test_container.cpp @@ -314,6 +314,8 @@ void test_container_default_is_max_size() NAM_SAMPLE* in_ptr = input.data(); NAM_SAMPLE* out_ptr; + // Ensure both predictions start from identical model state. + dsp->ResetAndPrewarm(sample_rate, buffer_size); // Process with default (should be max size) out_ptr = out_default.data(); dsp->process(&in_ptr, &out_ptr, buffer_size); @@ -322,6 +324,7 @@ void test_container_default_is_max_size() auto* slimmable = dynamic_cast(dsp.get()); assert(slimmable != nullptr); slimmable->SetSlimmableSize(1.0); + dsp->ResetAndPrewarm(sample_rate, buffer_size); out_ptr = out_max.data(); dsp->process(&in_ptr, &out_ptr, buffer_size); From 9c3e2f49e17eb118763fa3205292b4b347afabbe Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 22:01:56 -0700 Subject: [PATCH 3/3] [BUGFIX] Make container model activation thread-safe. Use atomic active-index loads/stores and a mutex-protected activation path so concurrent SetSlimmableSize calls cannot race with RT processing. Made-with: Cursor --- NAM/container.cpp | 20 ++++++++++++++++---- NAM/container.h | 7 ++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/NAM/container.cpp b/NAM/container.cpp index 8d6b6fe2..d14cb6b8 100644 --- a/NAM/container.cpp +++ b/NAM/container.cpp @@ -51,7 +51,8 @@ ContainerModel::ContainerModel(std::vector submodels, const double exp void ContainerModel::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) { - _active_model().process(input, output, num_frames); + const size_t active_index = _active_index.load(std::memory_order_acquire); + _submodels[active_index].model->process(input, output, num_frames); } void ContainerModel::prewarm() @@ -69,7 +70,7 @@ void ContainerModel::Reset(const double sampleRate, const int maxBufferSize) void ContainerModel::SetSlimmableSize(const double val) { - auto active_index = _submodels.size() - 1; + size_t active_index = _submodels.size() - 1; for (size_t i = 0; i < _submodels.size(); ++i) { if (val < _submodels[i].max_value) @@ -78,16 +79,27 @@ void ContainerModel::SetSlimmableSize(const double val) break; } } - if (active_index == _active_index) // No change to active model, so nothing to do + + // Fast path: no change to active model. + if (active_index == _active_index.load(std::memory_order_acquire)) + { + return; + } + + // Plugin host can deliver param changes from both UI/controller and processor paths. + // Serialize reset+prewarm so only one thread can perform model activation at a time. + std::lock_guard lock(_slim_set_mutex); + if (active_index == _active_index.load(std::memory_order_acquire)) { return; } + // Setting _active_index puts the model in the RT path, so prewarm before doing that const double sr = mHaveExternalSampleRate ? mExternalSampleRate : mExpectedSampleRate; _submodels[active_index].model->ResetAndPrewarm(sr, GetMaxBufferSize()); // Finally set when we're ready: - _active_index = active_index; + _active_index.store(active_index, std::memory_order_release); } // ============================================================================= diff --git a/NAM/container.h b/NAM/container.h index d69f03d0..dccc9149 100644 --- a/NAM/container.h +++ b/NAM/container.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -42,9 +44,8 @@ class ContainerModel : public DSP, public SlimmableModel private: std::vector _submodels; - size_t _active_index = 0; - - DSP& _active_model() { return *_submodels[_active_index].model; } + std::atomic _active_index{0}; + std::mutex _slim_set_mutex; }; // Config / registration