AUv2: carry the parameter backlog across a deactivate - #558
Merged
defiantnerd merged 1 commit intoSep 14, 2026
Merged
Conversation
SetParameter queues onto the process adapter, and deactivateCLAP() destroys it. Anything the host set since the last render or flush is still in that queue and goes with it -- silently, and after the host has been told the value took. The window is small but it is not rare: a host sets a parameter and then deactivates the unit on a restart the plugin asked for, on a sample-rate or block-size change, or on the way through a project close. In every one of those the value is simply gone, and on the restart cases the plugin comes back up in a state the host UI disagrees with. This is the same loss the deactivated-state adapter already covers at the other end of the window -- the span when there is no process adapter -- so the backlog goes to the same place. The events move to the flush adapter as the process adapter is dropped, and the deactivated path delivers them: the next idle tick, or activateCLAP() ahead of clap_plugin.activate(), whichever comes first. Both run on the main thread with the plugin inactive, which is where clap_plugin_params.flush() is legal, so nothing is handed to the plugin during teardown while it is still active. Only parameter events move. Whatever else is queued belongs to a render that is not going to happen, and a flush may not carry it in any case. Their offsets go with the block they were offsets into, and dropping those is not tidiness. The event sort keys on time first and only breaks ties on the insertion index, so an offset left over from an earlier block would sort behind a value the deactivated state queues later at time 0 -- the stale value applied last, inverting exactly the last-write-wins this is meant to preserve. Zeroed, they tie, and the tiebreak orders them as they arrived.
defiantnerd
force-pushed
the
auv2-drain-adapter-on-deactivate
branch
from
September 14, 2026 13:07
ae3f887 to
1df2279
Compare
baconpaul
approved these changes
Sep 14, 2026
Collaborator
There was a problem hiding this comment.
I'm happy to merge this since the code seems correct, with my comment caveat, but does this ever actually happen? oh ignore that. I now see how it can happen in a deactivated clap via the powered-off logic strip with events. But we should try and do that check!
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.
Follow-up to #556, which lists this under Not addressed:
Stacked on #556 — it needs
ensureFlushAdapter(), which that PR introduces. Base it onnextonce #556 lands and the diff is unchanged.The gap
SetParameterqueues onto_processAdapter;deactivateCLAP()resets it. Whatever the host set since the last render or flush is in that queue and is destroyed with it — afterAUBase::SetParameterhas already returned success and stored the value in the AU element, so the host believes it took.#556 closed the same loss for the span when there is no process adapter. This is the other end of the same window: the moment the adapter goes away.
It is reachable on every deactivate that is not a teardown — a restart the plugin asked for, a sample-rate or block-size change, a project close-and-reopen. On the restart paths the plugin comes back up in a state the host UI disagrees with, which is the same symptom as #551.
The fix
The backlog moves to the adapter the deactivated state already flushes from, instead of dying with the one being dropped. Delivery is then the deactivated path's job — the next idle tick, or
activateCLAP()ahead ofclap_plugin.activate(), whichever comes first.Both of those run on the main thread with the plugin inactive, which is where
clap_plugin_params.flush()is legal. Nothing is handed to the plugin during teardown: it is still active until thedeactivate()below the lock, and flushing there would mean claiming the audio thread while tearing down.Ordering it ahead of
activate()also means a restart brings the plugin back already holding the value, rather than being handed it in its firstprocess()cycle.Details worth noting:
sortEventIndices()keys on time first and only breaks ties on the insertion index. An offset left over from a block that never ran would sort behind a value the deactivated state queues later at time 0 — the stale value applied last, inverting the last-write-wins this exists to preserve. Zeroed, they tie, and the tiebreak orders them as they arrived: these first, anything queued afterwards over the top._requestedFlushis set when something actually moved. Nothing else would ask: the transfer is not aSetParameter, and the idle tick only flushes on request.ensureFlushAdapter()now runs before the reset rather than after, so the transfer has a target. Both adapters are briefly alive; the flush one carries no audio buffers.Verification
Built and linked clean (AUv2 target, the wrapper's own
-Werror), clang-formatted.Not yet exercised in a host — the useful test is a restart-on-parameter-change plugin in Logic: move a parameter, have the plugin request a restart in the same breath, and confirm the value survives rather than reverting. Worth doing before merge, along with the #556 checks it stacks on.