From 2615302cc268a1aefcea4de68837165d176cfe75 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Fri, 15 May 2026 13:13:08 -0700 Subject: [PATCH] Update slimmable container reset behavior --- NAM/container.cpp | 23 +++++--- tools/run_tests.cpp | 2 + tools/test/test_container.cpp | 101 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/NAM/container.cpp b/NAM/container.cpp index 2a103f4b..ee7d9f16 100644 --- a/NAM/container.cpp +++ b/NAM/container.cpp @@ -57,15 +57,22 @@ void ContainerModel::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int void ContainerModel::prewarm() { - for (auto& sm : _submodels) - sm.model->prewarm(); + const size_t active_index = _active_index.load(std::memory_order_acquire); + _submodels[active_index].model->prewarm(); } void ContainerModel::Reset(const double sampleRate, const int maxBufferSize) { - DSP::Reset(sampleRate, maxBufferSize); - for (auto& sm : _submodels) - sm.model->Reset(sampleRate, maxBufferSize); + std::lock_guard lock(_slim_set_mutex); + + // Update this container's reset state without dispatching through DSP::Reset(), + // which would prewarm the active child before the child receives these settings. + mExternalSampleRate = sampleRate; + mHaveExternalSampleRate = true; + SetMaxBufferSize(maxBufferSize); + + const size_t active_index = _active_index.load(std::memory_order_acquire); + _submodels[active_index].model->Reset(sampleRate, maxBufferSize); } void ContainerModel::SetSlimmableSize(const double val) @@ -87,15 +94,15 @@ void ContainerModel::SetSlimmableSize(const double val) } // 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. + // Serialize reset 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 + // Setting _active_index puts the model in the RT path, so reset before doing that. const double sr = mHaveExternalSampleRate ? mExternalSampleRate : mExpectedSampleRate; - _submodels[active_index].model->ResetAndPrewarm(sr, GetMaxBufferSize()); + _submodels[active_index].model->Reset(sr, GetMaxBufferSize()); // Finally set when we're ready: _active_index.store(active_index, std::memory_order_release); diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp index a4cfc4f8..6b18df94 100644 --- a/tools/run_tests.cpp +++ b/tools/run_tests.cpp @@ -293,6 +293,8 @@ int main() test_container::test_container_sample_rate_mismatch_throws(); test_container::test_container_load_from_file(); test_container::test_container_default_is_max_size(); + test_container::test_container_reset_only_resets_active_submodel(); + test_container::test_container_switch_resets_before_activation(); // Render --slim tests test_render_slim::test_slim_changes_output(); diff --git a/tools/test/test_container.cpp b/tools/test/test_container.cpp index ea18c62e..a89e8a5c 100644 --- a/tools/test/test_container.cpp +++ b/tools/test/test_container.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,70 @@ namespace test_container { +class CountingDSP : public nam::DSP +{ +public: + explicit CountingDSP(NAM_SAMPLE process_value) + : DSP(1, 1, 48000.0) + , process_value(process_value) + { + } + + void Reset(const double sampleRate, const int maxBufferSize) override + { + if (on_reset) + on_reset(); + + reset_count++; + reset_sample_rate = sampleRate; + reset_buffer_size = maxBufferSize; + DSP::Reset(sampleRate, maxBufferSize); + } + + void prewarm() override { prewarm_count++; } + + void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override + { + (void)input; + process_count++; + for (int i = 0; i < num_frames; i++) + output[0][i] = process_value; + } + + const NAM_SAMPLE process_value; + int reset_count = 0; + int prewarm_count = 0; + int process_count = 0; + double reset_sample_rate = 0.0; + int reset_buffer_size = 0; + std::function on_reset; +}; + +std::unique_ptr build_counting_container(CountingDSP*& small, CountingDSP*& large) +{ + auto small_model = std::make_unique((NAM_SAMPLE)1.0); + auto large_model = std::make_unique((NAM_SAMPLE)2.0); + + small = small_model.get(); + large = large_model.get(); + + std::vector submodels; + submodels.push_back({0.5, std::move(small_model)}); + submodels.push_back({1.0, std::move(large_model)}); + + return std::make_unique(std::move(submodels), 48000.0); +} + +NAM_SAMPLE process_one_sample(nam::DSP* dsp) +{ + NAM_SAMPLE input = (NAM_SAMPLE)0.0; + NAM_SAMPLE output = (NAM_SAMPLE)-1.0; + NAM_SAMPLE* in_ptr = &input; + NAM_SAMPLE* out_ptr = &output; + dsp->process(&in_ptr, &out_ptr, 1); + return output; +} + // Helper: load a .nam file as JSON nlohmann::json load_nam_json(const std::string& path) { @@ -333,4 +398,40 @@ void test_container_default_is_max_size() assert(std::abs(out_default[i] - out_max[i]) < 1e-6); } +void test_container_reset_only_resets_active_submodel() +{ + CountingDSP* small = nullptr; + CountingDSP* large = nullptr; + auto dsp = build_counting_container(small, large); + + dsp->Reset(44100.0, 128); + + assert(small->reset_count == 0); + assert(small->prewarm_count == 0); + + assert(large->reset_count == 1); + assert(large->prewarm_count == 1); + assert(large->reset_sample_rate == 44100.0); + assert(large->reset_buffer_size == 128); +} + +void test_container_switch_resets_before_activation() +{ + CountingDSP* small = nullptr; + CountingDSP* large = nullptr; + auto dsp = build_counting_container(small, large); + dsp->Reset(48000.0, 64); + + NAM_SAMPLE value_during_reset = (NAM_SAMPLE)-1.0; + small->on_reset = [&]() { value_during_reset = process_one_sample(dsp.get()); }; + + auto* slimmable = dynamic_cast(dsp.get()); + assert(slimmable != nullptr); + slimmable->SetSlimmableSize(0.0); + + assert(small->reset_count == 1); + assert(value_during_reset == large->process_value); + assert(process_one_sample(dsp.get()) == small->process_value); +} + } // namespace test_container