Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/detail/auv2/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,4 +852,55 @@ void ProcessAdapter::addParameterEvent(const clap_param_info_t &info, double val
this->_eventindices.emplace_back(this->_events.size());
this->_events.emplace_back(n);
}

// Moves the queued parameter events onto another adapter. This exists for one
// moment: deactivateCLAP() drops the process adapter, and anything the host set
// since the last render or flush is still sitting in it. Those values are as
// real as any other -- a host that sets a parameter and then deactivates the
// unit (a restart, a format change, a project close-and-reopen) has been told
// nothing went wrong -- so they move to the adapter the deactivated state
// flushes from rather than dying with this one.
//
// Only parameter events move. Everything else queued here belongs to a render
// that is not going to happen now, and a flush may not carry it anyway.
//
// The offsets go with the block they were offsets into, and dropping them is
// not tidiness: sortEventIndices() sorts on time first, and only breaks ties on
// the insertion index. Left as they were, an event stamped into some earlier
// block would sort *after* a value the deactivated state queues later at time
// 0 -- the stale value applied last, which is the inversion this transfer
// exists to avoid. At 0 they all tie, and the tiebreak then orders them the way
// they arrived: these first, anything queued afterwards over the top.
//
// Order follows position in _events, because flush() rebuilds _eventindices
// over it before sorting. Keeping the index list in step here is for a
// process() that never comes on the adapter these are going to.
size_t ProcessAdapter::transferPendingParametersTo(ProcessAdapter &other)
{
if (&other == this) return 0;

size_t moved = 0;
size_t kept = 0;
for (size_t i = 0; i < _events.size(); ++i)
{
if (isParameterEvent(_events[i].header.type))
{
clap_multi_event_t n = _events[i];
n.header.time = 0;
other._eventindices.emplace_back(other._events.size());
other._events.emplace_back(n);
++moved;
}
else
{
_events[kept++] = _events[i];
}
}

_events.resize(kept);
_eventindices.clear();
for (size_t i = 0; i < kept; ++i) _eventindices.emplace_back(i);

return moved;
}
} // namespace Clap::AUv2
4 changes: 4 additions & 0 deletions src/detail/auv2/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ class ProcessAdapter
UInt32 inOffsetSampleFrame);
void stopNote(int32_t note_id, int16_t channel, UInt32 inOffsetSampleFrame);
void addParameterEvent(const clap_param_info_t &info, double value, uint32_t inOffsetSampleFrame);
// Hands the parameter events still queued here to another adapter, so they
// survive this one being destroyed. Returns how many moved. See
// WrapAsAUV2::deactivateCLAP().
size_t transferPendingParametersTo(ProcessAdapter &other);
// void startNote()
~ProcessAdapter();

Expand Down
27 changes: 25 additions & 2 deletions src/wrapasauv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,6 @@ void WrapAsAUV2::deactivateCLAP()
// pointer and then use it, and the idle tick may be inside a flush on it.
ClapWrapper::detail::shared::SpinLockGuard processGuard(_processLock);
_initialized = false;
_processAdapter.reset();

// Stand the deactivated-state adapter up here, on the main thread, rather
// than leaving SetParameter to build it on demand. AUBase calls
Expand All @@ -1580,7 +1579,31 @@ void WrapAsAUV2::deactivateCLAP()
// rebuild its DSP. A render thread that had to allocate the replacement
// would be allocating inside a real-time callback; finding one already
// here makes it a queue push and nothing more.
ensureFlushAdapter();
//
// It is also where the process adapter's own backlog goes. Whatever the
// host set since the last render or flush is still queued there, and
// dropping the adapter would drop it: the host was told the value took,
// and the plugin would never hear it. This is the same loss SetParameter
// avoids while the CLAP is deactivated, at the other end of the window --
// the moment the adapter goes away rather than the span when there is
// none. Delivery is the deactivated path's job from here: the next idle
// tick, or activateCLAP() ahead of clap_plugin.activate(), whichever
// comes first, both on the main thread with the plugin inactive, which is
// where clap_plugin_params.flush() is legal. Nothing is handed to the
// plugin here -- it is still active until the deactivate() below, and a
// flush at this point would have to claim the audio thread during
// teardown.
if (auto *flushAdapter = ensureFlushAdapter())
{
if (_processAdapter && _processAdapter->transferPendingParametersTo(*flushAdapter) > 0)
{
// Nothing else would ask: the transfer is not a SetParameter, and the
// idle tick only flushes when something has asked it to.
_requestedFlush = true;
}
}

_processAdapter.reset();
}
_plugin->stop_processing();
_plugin->deactivate();
Expand Down
Loading