fix: avoid audio-thread stalls on model eviction and AU bypass toggle#627
Open
Ahorix wants to merge 1 commit into
Open
fix: avoid audio-thread stalls on model eviction and AU bypass toggle#627Ahorix wants to merge 1 commit into
Ahorix wants to merge 1 commit into
Conversation
Two real-time safety fixes that eliminate audible dropouts when disabling the plugin in Ableton Live (and likely other AU hosts): 1. Deferred model deletion (ProcessBlock / OnIdle) _ApplyDSPStaging() is called from ProcessBlock (audio thread). When a model is removed or replaced, the outgoing unique_ptr destructor was freeing several MB of Eigen weight matrices inline on the audio thread. On macOS, free() is non-deterministic on large blocks and can stall the audio callback long enough to miss the buffer deadline. Fix: evict the old model into mModelPendingDeletion (atomic raw ptr) instead of destroying it in place. OnIdle() (UI thread) performs the actual delete on the next tick (~20 ms later). 2. Skip _ResetModelAndIR() on AU bypass toggles (OnReset) iPlug2 calls OnReset() in response to kAudioUnitProperty_BypassEffect (the AU power-button toggle). This triggered _ResetModelAndIR() which calls DSP::Reset() -> SetMaxBufferSize() + prewarm(). prewarm() runs the model forward for thousands of samples to settle LSTM state — all on the audio thread — causing a guaranteed dropout on every toggle. Fix: cache the last sample rate and block size seen by OnReset(). Skip _ResetModelAndIR() when neither has changed, which is always the case for a bypass toggle. Legitimate format changes (sample rate, buffer size) still trigger a full reset. Note: the iPlug2 AUv2 handler for kAudioUnitProperty_BypassEffect already has a "TODO: should the following be called here?" comment on the OnReset() call, suggesting this was a known uncertainty upstream.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Disabling the NAM plugin in Ableton Live (and likely other AU hosts) via the device power button causes an audible dropout/crackle, even on completely silent tracks. This was reproducible and isolated to NAM — simpler plugins toggle cleanly.
Two separate real-time safety violations were found and fixed.
Fix 1: Deferred model deletion
_ApplyDSPStaging()is called fromProcessBlock(audio thread). When a model is removed or replaced, the outgoingunique_ptrdestructor was freeing several MB of Eigen weight matrices inline on the audio thread. On macOS,free()is non-deterministic on large blocks and can stall the audio callback long enough to miss the buffer deadline.Fix: evict the old model into
mModelPendingDeletion(anstd::atomic<ResamplingNAM*>) instead of destroying it in place.OnIdle()(UI thread) performs the actualdeleteon the next tick (~20 ms later).Fix 2: Skip
_ResetModelAndIR()on AU bypass togglesiPlug2 calls
OnReset()in response tokAudioUnitProperty_BypassEffect(the AU power-button toggle). This triggered_ResetModelAndIR()which callsDSP::Reset()→SetMaxBufferSize()+prewarm().prewarm()runs the model forward for thousands of samples to settle LSTM state — all on the audio thread — causing a guaranteed dropout on every toggle.Fix: cache the last sample rate and block size seen by
OnReset(). Skip_ResetModelAndIR()when neither has changed, which is always the case for a bypass toggle. Legitimate format changes (sample rate, buffer size) still trigger a full reset.Note: the iPlug2 AUv2 handler for
kAudioUnitProperty_BypassEffectalready has a// TODO: should the following be called here?comment on theOnReset()call, suggesting this was a known uncertainty upstream. The answer for NAM is: no, it should not.Testing