Skip to content

AUv2: carry the parameter backlog across a deactivate - #558

Merged
defiantnerd merged 1 commit into
fix-passive-parameter-syncfrom
auv2-drain-adapter-on-deactivate
Sep 14, 2026
Merged

defiantnerd merged 1 commit into
fix-passive-parameter-syncfrom
auv2-drain-adapter-on-deactivate

Conversation

@defiantnerd

@defiantnerd defiantnerd commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #556, which lists this under Not addressed:

The mirror image on the AUv2 side: events queued on the process adapter between the last render and Cleanup or a restart still die with the adapter. AUv3 drains those after deactivate(); AUv2 could do the same. Separate change.

Stacked on #556 — it needs ensureFlushAdapter(), which that PR introduces. Base it on next once #556 lands and the diff is unchanged.

The gap

SetParameter queues onto _processAdapter; deactivateCLAP() resets it. Whatever the host set since the last render or flush is in that queue and is destroyed with it — after AUBase::SetParameter has 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 of clap_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 the deactivate() 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 first process() cycle.

Details worth noting:

  • Only parameter events move. Anything else queued belongs to a render that is not going to happen, and a flush may not carry it in any case.
  • Offsets are dropped, and that part is load-bearing rather than tidiness. 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.
  • _requestedFlush is set when something actually moved. Nothing else would ask: the transfer is not a SetParameter, 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.

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
defiantnerd force-pushed the auv2-drain-adapter-on-deactivate branch from ae3f887 to 1df2279 Compare September 14, 2026 13:07

@baconpaul baconpaul left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

@defiantnerd
defiantnerd merged commit 18c9831 into fix-passive-parameter-sync Sep 14, 2026
2 checks passed
@defiantnerd
defiantnerd deleted the auv2-drain-adapter-on-deactivate branch September 14, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants