From 2a61b2e0dff952e590863aeabb5b3e07de7e6a4f Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 12:56:03 -0700
Subject: [PATCH 1/6] Derive project version from header

---
 CMakeLists.txt               | 16 ++++++++++++++--
 README.md                    |  2 +-
 docs/conf.py                 | 22 ++++++++++++++++++++--
 docs/wavenet_walkthrough.rst |  4 ++--
 4 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e9fee8..d21f36a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,19 @@
 cmake_minimum_required(VERSION 3.10)
 
-# Make sure this matches ./NAM/version.h!
-project(NAM VERSION 0.4.0)
+file(READ "${CMAKE_CURRENT_LIST_DIR}/NAM/version.h" _nam_version_header)
+foreach(_component MAJOR MINOR PATCH)
+	string(REGEX MATCH
+		"#define[ \t]+NEURAL_AMP_MODELER_DSP_VERSION_${_component}[ \t]+([0-9]+)"
+		_nam_version_match "${_nam_version_header}"
+	)
+	if(NOT _nam_version_match)
+		message(FATAL_ERROR "Could not parse NEURAL_AMP_MODELER_DSP_VERSION_${_component} from NAM/version.h")
+	endif()
+	set(_nam_version_${_component} "${CMAKE_MATCH_1}")
+endforeach()
+
+set(_nam_project_version "${_nam_version_MAJOR}.${_nam_version_MINOR}.${_nam_version_PATCH}")
+project(NAM VERSION "${_nam_project_version}")
 
 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
 
diff --git a/README.md b/README.md
index f94ea3f..57ff429 100644
--- a/README.md
+++ b/README.md
@@ -24,5 +24,5 @@ This library uses [Eigen](http://eigen.tuxfamily.org) to do the linear algebra r
   <img src="media/tone3000-logo.svg" alt="Tone3000 logo">
 </div>
 
-Development of version 0.4.0 of this library has been generously supported by [TONE3000](https://tone3000.com). 
+Early development of this library was generously supported by [TONE3000](https://tone3000.com).
 **Thank you!**
diff --git a/docs/conf.py b/docs/conf.py
index 305152c..28638cd 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -4,6 +4,7 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html
 
 import os
+import re
 import sys
 from pathlib import Path
 
@@ -19,8 +20,25 @@
 project = 'NeuralAmpModelerCore'
 copyright = '2023-present Steven Atkinson'
 author = 'Neural Amp Modeler Contributors'
-release = '0.4.0'
-version = '0.4.0'
+
+
+def _get_project_version():
+    version_header = Path(__file__).resolve().parent.parent / 'NAM' / 'version.h'
+    contents = version_header.read_text(encoding='utf-8')
+    version_parts = {}
+    for component in ('MAJOR', 'MINOR', 'PATCH'):
+        match = re.search(
+            rf'#define\s+NEURAL_AMP_MODELER_DSP_VERSION_{component}\s+(\d+)',
+            contents,
+        )
+        if match is None:
+            raise RuntimeError(f'Could not parse {component.lower()} version from {version_header}')
+        version_parts[component.lower()] = match.group(1)
+    return f"{version_parts['major']}.{version_parts['minor']}.{version_parts['patch']}"
+
+
+release = _get_project_version()
+version = release
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
diff --git a/docs/wavenet_walkthrough.rst b/docs/wavenet_walkthrough.rst
index 9dd90e6..c3cee7e 100644
--- a/docs/wavenet_walkthrough.rst
+++ b/docs/wavenet_walkthrough.rst
@@ -27,8 +27,8 @@ Here's a rundown of what's not exactly the same at an informal level:
   gated activation.
   Here, the gated activation is optional (and is frequently not used, like in the popular 
   A1 standard/lite/feather/nano configurations).
-* In v0.4.0, even more modifications have been added in--FiLMs, a bottlneck, and an
-  arbitrary "conditioning DSP" module that can be used to embed the input signal in a more
+* NAM adds further modifications: FiLMs, a bottleneck, and an arbitrary
+  "conditioning DSP" module that can be used to embed the input signal in a more
   effective way to modulate the layers in the main model.
   It doesn't need to be a WaveNet, but if it were then this feels more like a "cascading 
   (stacked) WaveNet".

From f7bf9af3c4536bbf6858ae12f2c09e8be1a72f92 Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 12:59:28 -0700
Subject: [PATCH 2/6] Restore README version reference

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 57ff429..8e57596 100644
--- a/README.md
+++ b/README.md
@@ -24,5 +24,5 @@ This library uses [Eigen](http://eigen.tuxfamily.org) to do the linear algebra r
   <img src="media/tone3000-logo.svg" alt="Tone3000 logo">
 </div>
 
-Early development of this library was generously supported by [TONE3000](https://tone3000.com).
+Development of version 0.4.0 of this library has been generously supported by [TONE3000](https://tone3000.com).
 **Thank you!**

From 4f72acd53d2885d6274e9fa66a334cac01ff2a10 Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 13:00:16 -0700
Subject: [PATCH 3/6] Restore WaveNet walkthrough version reference

---
 docs/wavenet_walkthrough.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/wavenet_walkthrough.rst b/docs/wavenet_walkthrough.rst
index c3cee7e..9dd90e6 100644
--- a/docs/wavenet_walkthrough.rst
+++ b/docs/wavenet_walkthrough.rst
@@ -27,8 +27,8 @@ Here's a rundown of what's not exactly the same at an informal level:
   gated activation.
   Here, the gated activation is optional (and is frequently not used, like in the popular 
   A1 standard/lite/feather/nano configurations).
-* NAM adds further modifications: FiLMs, a bottleneck, and an arbitrary
-  "conditioning DSP" module that can be used to embed the input signal in a more
+* In v0.4.0, even more modifications have been added in--FiLMs, a bottlneck, and an
+  arbitrary "conditioning DSP" module that can be used to embed the input signal in a more
   effective way to modulate the layers in the main model.
   It doesn't need to be a WaveNet, but if it were then this feels more like a "cascading 
   (stacked) WaveNet".

From c136943a123ebe599f1e724f860f8eb01f5e4ebf Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 13:02:11 -0700
Subject: [PATCH 4/6] Restore README to main

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 8e57596..f94ea3f 100644
--- a/README.md
+++ b/README.md
@@ -24,5 +24,5 @@ This library uses [Eigen](http://eigen.tuxfamily.org) to do the linear algebra r
   <img src="media/tone3000-logo.svg" alt="Tone3000 logo">
 </div>
 
-Development of version 0.4.0 of this library has been generously supported by [TONE3000](https://tone3000.com).
+Development of version 0.4.0 of this library has been generously supported by [TONE3000](https://tone3000.com). 
 **Thank you!**

From b0191082e3dd6c1b9beb0c6c1e4998d60a4cc727 Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 14:49:50 -0700
Subject: [PATCH 5/6] Add load option to suppress prewarm

---
 NAM/container.cpp             |  7 +++
 NAM/container.h               |  1 +
 NAM/dsp.cpp                   | 32 ++++++++++++-
 NAM/dsp.h                     | 40 ++++++++++++----
 NAM/get_dsp.cpp               | 70 +++++++++++++++++----------
 NAM/get_dsp.h                 | 27 +++++++++--
 NAM/model_config.h            |  2 +-
 NAM/wavenet/model.cpp         |  7 +++
 NAM/wavenet/model.h           |  2 +
 NAM/wavenet/slimmable.cpp     | 15 +++++-
 NAM/wavenet/slimmable.h       |  1 +
 tools/run_tests.cpp           |  6 +++
 tools/test/test_container.cpp |  4 +-
 tools/test/test_dsp.cpp       | 54 +++++++++++++++++++++
 tools/test/test_get_dsp.cpp   | 90 ++++++++++++++++++++++++++++++++++-
 15 files changed, 310 insertions(+), 48 deletions(-)

diff --git a/NAM/container.cpp b/NAM/container.cpp
index ee7d9f1..2d25f04 100644
--- a/NAM/container.cpp
+++ b/NAM/container.cpp
@@ -61,6 +61,13 @@ void ContainerModel::prewarm()
   _submodels[active_index].model->prewarm();
 }
 
+void ContainerModel::SetPrewarmOnReset(const bool prewarmOnReset)
+{
+  DSP::SetPrewarmOnReset(prewarmOnReset);
+  for (auto& submodel : _submodels)
+    submodel.model->SetPrewarmOnReset(prewarmOnReset);
+}
+
 void ContainerModel::Reset(const double sampleRate, const int maxBufferSize)
 {
   std::lock_guard<std::mutex> lock(_slim_set_mutex);
diff --git a/NAM/container.h b/NAM/container.h
index dccc914..36aee5d 100644
--- a/NAM/container.h
+++ b/NAM/container.h
@@ -37,6 +37,7 @@ class ContainerModel : public DSP, public SlimmableModel
   void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override;
   void prewarm() override;
   void Reset(const double sampleRate, const int maxBufferSize) override;
+  void SetPrewarmOnReset(const bool prewarmOnReset) override;
   void SetSlimmableSize(const double val) override;
 
 protected:
diff --git a/NAM/dsp.cpp b/NAM/dsp.cpp
index a4040c3..57d370f 100644
--- a/NAM/dsp.cpp
+++ b/NAM/dsp.cpp
@@ -14,8 +14,27 @@
 
 constexpr const long _INPUT_BUFFER_SAFETY_FACTOR = 32;
 
+namespace
+{
+
+thread_local bool gPrewarmOnResetDefault = true;
+
+} // namespace
+
+nam::ScopedPrewarmOnResetDefault::ScopedPrewarmOnResetDefault(const bool prewarmOnReset)
+: mPreviousPrewarmOnReset(gPrewarmOnResetDefault)
+{
+  gPrewarmOnResetDefault = prewarmOnReset;
+}
+
+nam::ScopedPrewarmOnResetDefault::~ScopedPrewarmOnResetDefault()
+{
+  gPrewarmOnResetDefault = mPreviousPrewarmOnReset;
+}
+
 nam::DSP::DSP(const int in_channels, const int out_channels, const double expected_sample_rate)
 : mExpectedSampleRate(expected_sample_rate)
+, mPrewarmOnReset(gPrewarmOnResetDefault)
 , mInChannels(in_channels)
 , mOutChannels(out_channels)
 {
@@ -96,7 +115,18 @@ void nam::DSP::Reset(const double sampleRate, const int maxBufferSize)
   mHaveExternalSampleRate = true;
   SetMaxBufferSize(maxBufferSize);
 
-  prewarm();
+  if (GetPrewarmOnReset())
+    prewarm();
+}
+
+void nam::DSP::SetPrewarmOnReset(const bool prewarmOnReset)
+{
+  mPrewarmOnReset.store(prewarmOnReset, std::memory_order_release);
+}
+
+bool nam::DSP::GetPrewarmOnReset() const
+{
+  return mPrewarmOnReset.load(std::memory_order_acquire);
 }
 
 void nam::DSP::SetLoudness(const double loudness)
diff --git a/NAM/dsp.h b/NAM/dsp.h
index c714a19..16b6fb0 100644
--- a/NAM/dsp.h
+++ b/NAM/dsp.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <atomic>
 #include <filesystem>
 #include <iterator>
 #include <memory>
@@ -39,6 +40,25 @@ namespace wavenet
 class WaveNet;
 } // namespace wavenet
 
+/// \brief Temporarily change the thread-local prewarm-on-reset default for newly constructed DSP objects
+///
+/// Existing DSP objects are not affected. DSP instances constructed while this object is alive on the current thread
+/// copy the scoped default into their instance-level prewarm-on-reset setting.
+class ScopedPrewarmOnResetDefault
+{
+public:
+  explicit ScopedPrewarmOnResetDefault(const bool prewarmOnReset);
+  ~ScopedPrewarmOnResetDefault();
+
+  ScopedPrewarmOnResetDefault(const ScopedPrewarmOnResetDefault&) = delete;
+  ScopedPrewarmOnResetDefault& operator=(const ScopedPrewarmOnResetDefault&) = delete;
+
+  bool PreviousPrewarmOnReset() const { return mPreviousPrewarmOnReset; }
+
+private:
+  bool mPreviousPrewarmOnReset;
+};
+
 /// \brief Base class for all DSP models
 ///
 /// DSP provides the common interface for all neural network-based audio processing models.
@@ -133,20 +153,19 @@ class DSP
 
   /// \brief General function for resetting the DSP unit
   ///
-  /// This doesn't call prewarm(). If you want to do that, then you might want to use ResetAndPrewarm().
-  /// See https://github.com/sdatkinson/NeuralAmpModelerCore/issues/96 for the reasoning.
+  /// By default, this calls prewarm() after updating the sample rate and buffer size. Use SetPrewarmOnReset() to
+  /// disable or re-enable that behavior for a DSP instance.
   /// \param sampleRate Current sample rate
   /// \param maxBufferSize Maximum buffer size to process
   virtual void Reset(const double sampleRate, const int maxBufferSize);
 
-  /// \brief Reset the DSP unit, then prewarm
-  /// \param sampleRate Current sample rate
-  /// \param maxBufferSize Maximum buffer size to process
-  void ResetAndPrewarm(const double sampleRate, const int maxBufferSize)
-  {
-    Reset(sampleRate, maxBufferSize);
-    prewarm();
-  }
+  /// \brief Control whether Reset() calls prewarm()
+  /// \param prewarmOnReset true for Reset() to call prewarm(), false to skip prewarm()
+  virtual void SetPrewarmOnReset(const bool prewarmOnReset);
+
+  /// \brief Check whether Reset() calls prewarm()
+  /// \return true if Reset() calls prewarm()
+  bool GetPrewarmOnReset() const;
 
   /// \brief Set the input level
   /// \param inputLevel Input level in dBu
@@ -177,6 +196,7 @@ class DSP
   double mExternalSampleRate = -1.0;
   // The largest buffer I expect to be told to process:
   int mMaxBufferSize = 0;
+  std::atomic<bool> mPrewarmOnReset;
 
   /// \brief Get how many samples should be processed for the model to be considered "warmed up"
   ///
diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp
index 3aa8592..462d34f 100644
--- a/NAM/get_dsp.cpp
+++ b/NAM/get_dsp.cpp
@@ -139,26 +139,42 @@ std::vector<float> GetWeights(nlohmann::json const& j)
     throw std::runtime_error("Corrupted model file is missing weights.");
 }
 
-std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename)
+void populate_dsp_data(const nlohmann::json& config, dspData& returnedConfig)
+{
+  verify_config_version(config["version"].get<std::string>());
+
+  nlohmann::json config_json = config["config"];
+  std::vector<float> weights = GetWeights(config);
+
+  returnedConfig.version = config["version"].get<std::string>();
+  returnedConfig.architecture = config["architecture"].get<std::string>();
+  returnedConfig.config = config_json;
+  returnedConfig.metadata = config.value("metadata", nlohmann::json());
+  returnedConfig.weights = weights;
+  returnedConfig.expected_sample_rate = nam::get_sample_rate_from_nam_file(config);
+}
+
+std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, DspLoadOptions options)
 {
   dspData temp;
-  return get_dsp(config_filename, temp);
+  return get_dsp(config_filename, temp, options);
 }
 
-std::unique_ptr<DSP> get_dsp(const nlohmann::json& config)
+std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, DspLoadOptions options)
 {
   dspData temp;
-  return get_dsp(config, temp);
+  return get_dsp(config, temp, options);
 }
 
-std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, dspData& returnedConfig)
+std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, dspData& returnedConfig,
+                             DspLoadOptions options)
 {
   if (!std::filesystem::exists(config_filename))
     throw std::runtime_error("Config file doesn't exist!\n");
   std::ifstream i(config_filename);
   nlohmann::json j;
   i >> j;
-  get_dsp(j, returnedConfig);
+  populate_dsp_data(j, returnedConfig);
 
   /*Copy to a new dsp_config object for get_dsp below,
    since not sure if weights actually get modified as being non-const references on some
@@ -166,24 +182,12 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, dspDat
    We need to return unmodified version of dsp_config via returnedConfig.*/
   dspData conf = returnedConfig;
 
-  return get_dsp(conf);
+  return get_dsp(conf, options);
 }
 
-std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, dspData& returnedConfig)
+std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, dspData& returnedConfig, DspLoadOptions options)
 {
-  verify_config_version(config["version"].get<std::string>());
-
-  auto architecture = config["architecture"];
-  nlohmann::json config_json = config["config"];
-  std::vector<float> weights = GetWeights(config);
-
-  // Assign values to returnedConfig
-  returnedConfig.version = config["version"].get<std::string>();
-  returnedConfig.architecture = config["architecture"].get<std::string>();
-  returnedConfig.config = config_json;
-  returnedConfig.metadata = config.value("metadata", nlohmann::json());
-  returnedConfig.weights = weights;
-  returnedConfig.expected_sample_rate = nam::get_sample_rate_from_nam_file(config);
+  populate_dsp_data(config, returnedConfig);
 
   /*Copy to a new dsp_config object for get_dsp below,
    since not sure if weights actually get modified as being non-const references on some
@@ -191,7 +195,7 @@ std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, dspData& returnedConf
    We need to return unmodified version of dsp_config via returnedConfig.*/
   dspData conf = returnedConfig;
 
-  return get_dsp(conf);
+  return get_dsp(conf, options);
 }
 
 // =============================================================================
@@ -224,9 +228,6 @@ std::unique_ptr<DSP> create_dsp(std::unique_ptr<ModelConfig> config, std::vector
 {
   auto out = config->create(std::move(weights), metadata.sample_rate);
   apply_metadata(*out, metadata);
-  // "pre-warm" the model to settle initial conditions
-  // Can this be removed now that it's part of Reset()?
-  out->prewarm();
   return out;
 }
 
@@ -234,7 +235,10 @@ std::unique_ptr<DSP> create_dsp(std::unique_ptr<ModelConfig> config, std::vector
 // get_dsp(dspData&) — now uses unified path
 // =============================================================================
 
-std::unique_ptr<DSP> get_dsp(dspData& conf)
+namespace
+{
+
+std::unique_ptr<DSP> get_dsp_with_current_prewarm_default(dspData& conf)
 {
   verify_config_version(conf.version);
 
@@ -259,6 +263,20 @@ std::unique_ptr<DSP> get_dsp(dspData& conf)
   return create_dsp(std::move(model_config), std::move(conf.weights), metadata);
 }
 
+} // anonymous namespace
+
+std::unique_ptr<DSP> get_dsp(dspData& conf, DspLoadOptions options)
+{
+  if (options.prewarm)
+    return get_dsp_with_current_prewarm_default(conf);
+
+  ScopedPrewarmOnResetDefault scoped_prewarm_default(false);
+  auto dsp = get_dsp_with_current_prewarm_default(conf);
+  if (dsp != nullptr)
+    dsp->SetPrewarmOnReset(scoped_prewarm_default.PreviousPrewarmOnReset());
+  return dsp;
+}
+
 double get_sample_rate_from_nam_file(const nlohmann::json& j)
 {
   if (j.find("sample_rate") != j.end())
diff --git a/NAM/get_dsp.h b/NAM/get_dsp.h
index da874fe..90f01b0 100644
--- a/NAM/get_dsp.h
+++ b/NAM/get_dsp.h
@@ -65,34 +65,51 @@ void verify_config_version(const std::string versionStr);
 const std::string LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION = "0.7.0";
 const std::string EARLIEST_SUPPORTED_NAM_FILE_VERSION = "0.5.0";
 
+/// \brief Options that control DSP loading behavior
+struct DspLoadOptions
+{
+  /// \brief Whether Reset() calls made during loading may prewarm constructed models
+  ///
+  /// Set this to false to avoid expensive prewarm work during get_dsp(). The returned model is restored to the
+  /// caller's previous prewarm-on-reset default before get_dsp() returns.
+  bool prewarm = true;
+};
+
 /// \brief Get NAM from a .nam file at the provided location
 /// \param config_filename Path to the .nam model file
+/// \param options Loading options
 /// \return Unique pointer to a DSP object
-std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename);
+std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, DspLoadOptions options = DspLoadOptions());
 
 /// \brief Get NAM from a provided configuration struct
 /// \param conf DSP data structure containing model configuration and weights
+/// \param options Loading options
 /// \return Unique pointer to a DSP object
-std::unique_ptr<DSP> get_dsp(dspData& conf);
+std::unique_ptr<DSP> get_dsp(dspData& conf, DspLoadOptions options = DspLoadOptions());
 
 /// \brief Get NAM from a .nam file and store its configuration
 ///
 /// Creates an instance of DSP and also returns a dspData struct that holds the data of the model.
 /// \param config_filename Path to the .nam model file
 /// \param returnedConfig Output parameter that will be filled with the model data
+/// \param options Loading options
 /// \return Unique pointer to a DSP object
-std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, dspData& returnedConfig);
+std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename, dspData& returnedConfig,
+                             DspLoadOptions options = DspLoadOptions());
 
 /// \brief Get NAM from a provided configuration JSON object
 /// \param config JSON configuration object
 /// \param returnedConfig Output parameter that will be filled with the model data
+/// \param options Loading options
 /// \return Unique pointer to a DSP object
-std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, dspData& returnedConfig);
+std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, dspData& returnedConfig,
+                             DspLoadOptions options = DspLoadOptions());
 
 /// \brief Get NAM from a provided configuration JSON object (convenience overload)
 /// \param config JSON configuration object
+/// \param options Loading options
 /// \return Unique pointer to a DSP object
-std::unique_ptr<DSP> get_dsp(const nlohmann::json& config);
+std::unique_ptr<DSP> get_dsp(const nlohmann::json& config, DspLoadOptions options = DspLoadOptions());
 
 /// \brief Get sample rate from a .nam file
 /// \param j JSON object from the .nam file
diff --git a/NAM/model_config.h b/NAM/model_config.h
index 32825a5..d9653da 100644
--- a/NAM/model_config.h
+++ b/NAM/model_config.h
@@ -106,7 +106,7 @@ struct ConfigParserHelper
 /// \brief Construct a DSP object from a typed config, weights, and metadata
 ///
 /// This is the single construction path used by both JSON and binary loaders.
-/// Handles construction, metadata application, and prewarm.
+/// Handles construction and metadata application.
 /// \param config Architecture-specific configuration (abstract base)
 /// \param weights Model weights (taken by value to allow move for WaveNet)
 /// \param metadata Model metadata (version, sample rate, loudness, levels)
diff --git a/NAM/wavenet/model.cpp b/NAM/wavenet/model.cpp
index eaf74ad..9437e1e 100644
--- a/NAM/wavenet/model.cpp
+++ b/NAM/wavenet/model.cpp
@@ -689,6 +689,13 @@ void nam::wavenet::WaveNet::SetMaxBufferSize(const int maxBufferSize)
   }
 }
 
+void nam::wavenet::WaveNet::SetPrewarmOnReset(const bool prewarmOnReset)
+{
+  DSP::SetPrewarmOnReset(prewarmOnReset);
+  if (this->_condition_dsp != nullptr)
+    this->_condition_dsp->SetPrewarmOnReset(prewarmOnReset);
+}
+
 void nam::wavenet::WaveNet::_process_condition(const int num_frames)
 {
   if (this->_condition_dsp == nullptr)
diff --git a/NAM/wavenet/model.h b/NAM/wavenet/model.h
index 968baf8..508f909 100644
--- a/NAM/wavenet/model.h
+++ b/NAM/wavenet/model.h
@@ -58,6 +58,8 @@ class WaveNet : public DSP
   /// \param num_frames Number of frames to process
   void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override;
 
+  void SetPrewarmOnReset(const bool prewarmOnReset) override;
+
   /// \brief Set model weights from a vector
   /// \param weights Vector containing all model weights
   void set_weights_(std::vector<float>& weights);
diff --git a/NAM/wavenet/slimmable.cpp b/NAM/wavenet/slimmable.cpp
index 19b019e..69f077b 100644
--- a/NAM/wavenet/slimmable.cpp
+++ b/NAM/wavenet/slimmable.cpp
@@ -410,8 +410,10 @@ std::unique_ptr<DSP> SlimmableWavenet::_create_wavenet_for_channels(const std::v
     condition_dsp = get_dsp(_condition_dsp_json);
 
   double sampleRate = _current_sample_rate > 0 ? _current_sample_rate : GetExpectedSampleRate();
-  return std::make_unique<wavenet::WaveNet>(_in_channels, *params_ptr, _head_scale, _with_head, std::nullopt,
-                                            std::move(weights), std::move(condition_dsp), sampleRate);
+  auto model = std::make_unique<wavenet::WaveNet>(_in_channels, *params_ptr, _head_scale, _with_head, std::nullopt,
+                                                  std::move(weights), std::move(condition_dsp), sampleRate);
+  model->SetPrewarmOnReset(GetPrewarmOnReset());
+  return model;
 }
 
 void SlimmableWavenet::_rebuild_model(const std::vector<int>& target_channels)
@@ -481,6 +483,15 @@ void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize)
     pending->model->Reset(sampleRate, maxBufferSize);
 }
 
+void SlimmableWavenet::SetPrewarmOnReset(const bool prewarmOnReset)
+{
+  DSP::SetPrewarmOnReset(prewarmOnReset);
+  if (_active_model)
+    _active_model->SetPrewarmOnReset(prewarmOnReset);
+  if (auto pending = _pending_load_acquire())
+    pending->model->SetPrewarmOnReset(prewarmOnReset);
+}
+
 void SlimmableWavenet::SetSlimmableSize(const double val)
 {
   const size_t num_arrays = _original_params.size();
diff --git a/NAM/wavenet/slimmable.h b/NAM/wavenet/slimmable.h
index f0687c0..bc416ac 100644
--- a/NAM/wavenet/slimmable.h
+++ b/NAM/wavenet/slimmable.h
@@ -48,6 +48,7 @@ class SlimmableWavenet : public DSP, public SlimmableModel
   void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override;
   void prewarm() override;
   void Reset(const double sampleRate, const int maxBufferSize) override;
+  void SetPrewarmOnReset(const bool prewarmOnReset) override;
   void SetSlimmableSize(const double val) override;
 
 protected:
diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp
index 0f9d50a..0f92745 100644
--- a/tools/run_tests.cpp
+++ b/tools/run_tests.cpp
@@ -84,6 +84,9 @@ int main()
   test_dsp::test_has_output_level();
   test_dsp::test_set_input_level();
   test_dsp::test_set_output_level();
+  test_dsp::test_reset_prewarm_on_reset_default();
+  test_dsp::test_set_prewarm_on_reset();
+  test_dsp::test_scoped_prewarm_on_reset_default();
 
   test_linear::test_direct_known_values();
   test_linear::test_fft_matches_direct_irregular_chunks();
@@ -285,6 +288,9 @@ int main()
   test_get_dsp::test_version_too_early();
   test_get_dsp::test_is_version_supported_core_behavior();
   test_get_dsp::test_register_custom_version_support_checker();
+  test_get_dsp::test_get_dsp_default_allows_constructor_reset_prewarm();
+  test_get_dsp::test_get_dsp_prewarm_option_suppresses_constructor_reset_prewarm();
+  test_get_dsp::test_get_dsp_with_returned_config_constructs_once();
 
   // Finally, some end-to-end tests.
   test_get_dsp::test_load_and_process_nam_files();
diff --git a/tools/test/test_container.cpp b/tools/test/test_container.cpp
index a89e8a5..46b1752 100644
--- a/tools/test/test_container.cpp
+++ b/tools/test/test_container.cpp
@@ -380,7 +380,7 @@ void test_container_default_is_max_size()
   NAM_SAMPLE* out_ptr;
 
   // Ensure both predictions start from identical model state.
-  dsp->ResetAndPrewarm(sample_rate, buffer_size);
+  dsp->Reset(sample_rate, buffer_size);
   // Process with default (should be max size)
   out_ptr = out_default.data();
   dsp->process(&in_ptr, &out_ptr, buffer_size);
@@ -389,7 +389,7 @@ void test_container_default_is_max_size()
   auto* slimmable = dynamic_cast<nam::SlimmableModel*>(dsp.get());
   assert(slimmable != nullptr);
   slimmable->SetSlimmableSize(1.0);
-  dsp->ResetAndPrewarm(sample_rate, buffer_size);
+  dsp->Reset(sample_rate, buffer_size);
   out_ptr = out_max.data();
   dsp->process(&in_ptr, &out_ptr, buffer_size);
 
diff --git a/tools/test/test_dsp.cpp b/tools/test/test_dsp.cpp
index d019a87..334461d 100644
--- a/tools/test/test_dsp.cpp
+++ b/tools/test/test_dsp.cpp
@@ -1,10 +1,24 @@
 // Tests for dsp
 
 #include "NAM/dsp.h"
+#include <cassert>
 #include <vector>
 
 namespace test_dsp
 {
+class PrewarmCountingDSP : public nam::DSP
+{
+public:
+  PrewarmCountingDSP()
+  : nam::DSP(1, 1, 48000.0)
+  {
+  }
+
+  void prewarm() override { prewarm_count++; }
+
+  int prewarm_count = 0;
+};
+
 // Simplest test: can I construct something!
 void test_construct()
 {
@@ -91,6 +105,46 @@ void test_set_output_level()
   myDsp.SetOutputLevel(19.0);
 }
 
+void test_reset_prewarm_on_reset_default()
+{
+  PrewarmCountingDSP dsp;
+  assert(dsp.GetPrewarmOnReset());
+
+  dsp.Reset(48000.0, 64);
+
+  assert(dsp.prewarm_count == 1);
+}
+
+void test_set_prewarm_on_reset()
+{
+  PrewarmCountingDSP dsp;
+
+  dsp.SetPrewarmOnReset(false);
+  dsp.Reset(48000.0, 64);
+  assert(dsp.prewarm_count == 0);
+
+  dsp.SetPrewarmOnReset(true);
+  dsp.Reset(48000.0, 64);
+  assert(dsp.prewarm_count == 1);
+}
+
+void test_scoped_prewarm_on_reset_default()
+{
+  PrewarmCountingDSP before_scope;
+  assert(before_scope.GetPrewarmOnReset());
+
+  {
+    nam::ScopedPrewarmOnResetDefault scoped_default(false);
+    PrewarmCountingDSP in_scope;
+    assert(!in_scope.GetPrewarmOnReset());
+    in_scope.Reset(48000.0, 64);
+    assert(in_scope.prewarm_count == 0);
+  }
+
+  PrewarmCountingDSP after_scope;
+  assert(after_scope.GetPrewarmOnReset());
+}
+
 void test_process_multi_channel()
 {
   const int in_channels = 2;
diff --git a/tools/test/test_get_dsp.cpp b/tools/test/test_get_dsp.cpp
index 150f50f..7f97c6a 100644
--- a/tools/test/test_get_dsp.cpp
+++ b/tools/test/test_get_dsp.cpp
@@ -10,6 +10,7 @@
 #include "json.hpp"
 
 #include "NAM/get_dsp.h"
+#include "NAM/registry.h"
 
 namespace test_get_dsp
 {
@@ -46,6 +47,50 @@ nam::dspData _GetConfig(const std::string& configStr = basicConfigStr)
   return returnedConfig;
 }
 
+namespace
+{
+
+constexpr const char* kConstructorResetArchitecture = "ConstructorResetPrewarmPolicyTest";
+int gConstructorResetConstructCount = 0;
+
+class ConstructorResetDSP : public nam::DSP
+{
+public:
+  explicit ConstructorResetDSP(const double expected_sample_rate)
+  : nam::DSP(1, 1, expected_sample_rate)
+  {
+    gConstructorResetConstructCount++;
+    Reset(expected_sample_rate, 16);
+  }
+
+  void prewarm() override { prewarm_count++; }
+
+  int prewarm_count = 0;
+};
+
+std::unique_ptr<nam::DSP> ConstructorResetFactory(const nlohmann::json& config, std::vector<float>& weights,
+                                                  const double expectedSampleRate)
+{
+  (void)config;
+  (void)weights;
+  return std::make_unique<ConstructorResetDSP>(expectedSampleRate);
+}
+
+static nam::factory::Helper _register_ConstructorResetPrewarmPolicyTest(kConstructorResetArchitecture,
+                                                                        ConstructorResetFactory);
+
+nlohmann::json build_constructor_reset_config()
+{
+  return nlohmann::json{{"version", "0.7.0"},
+                        {"metadata", nlohmann::json::object()},
+                        {"architecture", kConstructorResetArchitecture},
+                        {"config", nlohmann::json::object()},
+                        {"weights", nlohmann::json::array()},
+                        {"sample_rate", 48000}};
+}
+
+} // namespace
+
 void test_gets_input_level()
 {
   nam::dspData config = _GetConfig();
@@ -273,4 +318,47 @@ void test_register_custom_version_support_checker()
   assert(nam::is_version_supported("DEMO::1.0.3") == nam::Supported::PARTIAL);
   assert(nam::is_version_supported("DEMO::2.0.0") == nam::Supported::NO);
 }
-}; // namespace test_get_dsp
\ No newline at end of file
+
+void test_get_dsp_default_allows_constructor_reset_prewarm()
+{
+  gConstructorResetConstructCount = 0;
+
+  auto dsp = nam::get_dsp(build_constructor_reset_config());
+  auto* typed = dynamic_cast<ConstructorResetDSP*>(dsp.get());
+
+  assert(typed != nullptr);
+  assert(gConstructorResetConstructCount == 1);
+  assert(typed->prewarm_count == 1);
+  assert(typed->GetPrewarmOnReset());
+}
+
+void test_get_dsp_prewarm_option_suppresses_constructor_reset_prewarm()
+{
+  gConstructorResetConstructCount = 0;
+
+  nam::DspLoadOptions options;
+  options.prewarm = false;
+  auto dsp = nam::get_dsp(build_constructor_reset_config(), options);
+  auto* typed = dynamic_cast<ConstructorResetDSP*>(dsp.get());
+
+  assert(typed != nullptr);
+  assert(gConstructorResetConstructCount == 1);
+  assert(typed->prewarm_count == 0);
+  assert(typed->GetPrewarmOnReset());
+
+  typed->Reset(48000.0, 16);
+  assert(typed->prewarm_count == 1);
+}
+
+void test_get_dsp_with_returned_config_constructs_once()
+{
+  gConstructorResetConstructCount = 0;
+
+  nam::dspData returned_config;
+  auto dsp = nam::get_dsp(build_constructor_reset_config(), returned_config);
+
+  assert(dsp != nullptr);
+  assert(gConstructorResetConstructCount == 1);
+  assert(returned_config.architecture == kConstructorResetArchitecture);
+}
+}; // namespace test_get_dsp

From c09d49d2759a80d13f1ebe0b70747a9f7f69b9e5 Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
Date: Wed, 10 Jun 2026 15:23:16 -0700
Subject: [PATCH 6/6] Clarify prewarm load option semantics

---
 NAM/get_dsp.cpp             |  4 ++--
 NAM/get_dsp.h               | 10 ++++++----
 tools/run_tests.cpp         |  2 ++
 tools/test/test_get_dsp.cpp | 33 +++++++++++++++++++++++++++++++++
 4 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp
index 462d34f..6439364 100644
--- a/NAM/get_dsp.cpp
+++ b/NAM/get_dsp.cpp
@@ -267,10 +267,10 @@ std::unique_ptr<DSP> get_dsp_with_current_prewarm_default(dspData& conf)
 
 std::unique_ptr<DSP> get_dsp(dspData& conf, DspLoadOptions options)
 {
-  if (options.prewarm)
+  if (!options.prewarm.has_value())
     return get_dsp_with_current_prewarm_default(conf);
 
-  ScopedPrewarmOnResetDefault scoped_prewarm_default(false);
+  ScopedPrewarmOnResetDefault scoped_prewarm_default(*options.prewarm);
   auto dsp = get_dsp_with_current_prewarm_default(conf);
   if (dsp != nullptr)
     dsp->SetPrewarmOnReset(scoped_prewarm_default.PreviousPrewarmOnReset());
diff --git a/NAM/get_dsp.h b/NAM/get_dsp.h
index 90f01b0..c9e7941 100644
--- a/NAM/get_dsp.h
+++ b/NAM/get_dsp.h
@@ -2,6 +2,7 @@
 
 #include <fstream>
 #include <memory>
+#include <optional>
 #include <vector>
 
 #include "dsp.h"
@@ -68,11 +69,12 @@ const std::string EARLIEST_SUPPORTED_NAM_FILE_VERSION = "0.5.0";
 /// \brief Options that control DSP loading behavior
 struct DspLoadOptions
 {
-  /// \brief Whether Reset() calls made during loading may prewarm constructed models
+  /// \brief Whether to override the current prewarm-on-reset context during loading
   ///
-  /// Set this to false to avoid expensive prewarm work during get_dsp(). The returned model is restored to the
-  /// caller's previous prewarm-on-reset default before get_dsp() returns.
-  bool prewarm = true;
+  /// std::nullopt leaves the current thread-local context unchanged. Set this to false to avoid expensive prewarm work
+  /// during get_dsp(), or true to force prewarm during get_dsp(). When an override is provided, the returned model is
+  /// restored to the caller's previous prewarm-on-reset default before get_dsp() returns.
+  std::optional<bool> prewarm = std::nullopt;
 };
 
 /// \brief Get NAM from a .nam file at the provided location
diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp
index 0f92745..e765ddf 100644
--- a/tools/run_tests.cpp
+++ b/tools/run_tests.cpp
@@ -289,7 +289,9 @@ int main()
   test_get_dsp::test_is_version_supported_core_behavior();
   test_get_dsp::test_register_custom_version_support_checker();
   test_get_dsp::test_get_dsp_default_allows_constructor_reset_prewarm();
+  test_get_dsp::test_get_dsp_default_inherits_scoped_prewarm_default();
   test_get_dsp::test_get_dsp_prewarm_option_suppresses_constructor_reset_prewarm();
+  test_get_dsp::test_get_dsp_prewarm_option_forces_constructor_reset_prewarm();
   test_get_dsp::test_get_dsp_with_returned_config_constructs_once();
 
   // Finally, some end-to-end tests.
diff --git a/tools/test/test_get_dsp.cpp b/tools/test/test_get_dsp.cpp
index 7f97c6a..3cfd368 100644
--- a/tools/test/test_get_dsp.cpp
+++ b/tools/test/test_get_dsp.cpp
@@ -332,6 +332,20 @@ void test_get_dsp_default_allows_constructor_reset_prewarm()
   assert(typed->GetPrewarmOnReset());
 }
 
+void test_get_dsp_default_inherits_scoped_prewarm_default()
+{
+  gConstructorResetConstructCount = 0;
+
+  nam::ScopedPrewarmOnResetDefault scoped_default(false);
+  auto dsp = nam::get_dsp(build_constructor_reset_config());
+  auto* typed = dynamic_cast<ConstructorResetDSP*>(dsp.get());
+
+  assert(typed != nullptr);
+  assert(gConstructorResetConstructCount == 1);
+  assert(typed->prewarm_count == 0);
+  assert(!typed->GetPrewarmOnReset());
+}
+
 void test_get_dsp_prewarm_option_suppresses_constructor_reset_prewarm()
 {
   gConstructorResetConstructCount = 0;
@@ -350,6 +364,25 @@ void test_get_dsp_prewarm_option_suppresses_constructor_reset_prewarm()
   assert(typed->prewarm_count == 1);
 }
 
+void test_get_dsp_prewarm_option_forces_constructor_reset_prewarm()
+{
+  gConstructorResetConstructCount = 0;
+
+  nam::ScopedPrewarmOnResetDefault scoped_default(false);
+  nam::DspLoadOptions options;
+  options.prewarm = true;
+  auto dsp = nam::get_dsp(build_constructor_reset_config(), options);
+  auto* typed = dynamic_cast<ConstructorResetDSP*>(dsp.get());
+
+  assert(typed != nullptr);
+  assert(gConstructorResetConstructCount == 1);
+  assert(typed->prewarm_count == 1);
+  assert(!typed->GetPrewarmOnReset());
+
+  typed->Reset(48000.0, 16);
+  assert(typed->prewarm_count == 1);
+}
+
 void test_get_dsp_with_returned_config_constructs_once()
 {
   gConstructorResetConstructCount = 0;
