diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp index 4a08266ac..9d0992b46 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.cpp +++ b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -409,14 +409,24 @@ void NeuralAmpModeler::OnReset() SetTailSize(tailCycles * (int)(sampleRate / kDCBlockerFrequency)); mInputSender.Reset(sampleRate); mOutputSender.Reset(sampleRate); - // If there is a model or IR loaded, they need to be checked for resampling. - _ResetModelAndIR(sampleRate, GetBlockSize()); + // Only reset the model when the format actually changed. iPlug2 also calls OnReset() in + // response to kAudioUnitProperty_BypassEffect (the AU bypass / power-button toggle), which + // would trigger prewarm() on the audio thread and cause an audible dropout. Sample rate and + // block size are unchanged on a bypass toggle, so skipping _ResetModelAndIR() is safe there. + if (sampleRate != mLastResetSampleRate || maxBlockSize != mLastResetBlockSize) + { + _ResetModelAndIR(sampleRate, maxBlockSize); + mLastResetSampleRate = sampleRate; + mLastResetBlockSize = maxBlockSize; + } mToneStack->Reset(sampleRate, maxBlockSize); _UpdateLatency(); } void NeuralAmpModeler::OnIdle() { + delete mModelPendingDeletion.exchange(nullptr, std::memory_order_acquire); + mInputSender.TransmitData(*this); mOutputSender.TransmitData(*this); @@ -597,7 +607,7 @@ void NeuralAmpModeler::_ApplyDSPStaging() // Remove marked modules if (mShouldRemoveModel) { - mModel = nullptr; + mModelPendingDeletion.store(mModel.release(), std::memory_order_release); mNAMPath.Set(""); mShouldRemoveModel = false; mModelCleared = true; @@ -614,8 +624,9 @@ void NeuralAmpModeler::_ApplyDSPStaging() // Move things from staged to live if (mStagedModel != nullptr) { + ResamplingNAM* old = mModel.release(); mModel = std::move(mStagedModel); - mStagedModel = nullptr; + mModelPendingDeletion.store(old, std::memory_order_release); mNewModelLoadedInDSP = true; _UpdateLatency(); _SetInputGain(); diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h index f5dae839d..677433d15 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.h +++ b/NeuralAmpModeler/NeuralAmpModeler.h @@ -306,6 +306,14 @@ class NeuralAmpModeler final : public iplug::Plugin std::atomic mNewModelLoadedInDSP = false; std::atomic mModelCleared = false; + // Holds a model evicted from mModel that must be deleted on the UI thread (OnIdle) rather + // than the audio thread, to avoid freeing large Eigen weight matrices inside ProcessBlock + // and causing a buffer-deadline miss. + std::atomic mModelPendingDeletion{nullptr}; + + // Last format seen by OnReset(). Used to guard _ResetModelAndIR() — see OnReset() for rationale. + double mLastResetSampleRate{0.0}; + int mLastResetBlockSize{0}; // Tone stack modules std::unique_ptr mToneStack;