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
5 changes: 5 additions & 0 deletions src/detail/auv2/auv2_base_classes.h
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,11 @@ class WrapAsAUV2 : public ausdk::AUBase,
// flushes so gestures pair up; see ensureFlushAdapter().
std::unique_ptr<Clap::AUv2::ProcessAdapter> _flushAdapter;
std::atomic<bool> _initialized = false;
// Whether clap_plugin.activate() succeeded and has not been matched by a
// deactivate() yet -- not the same fact as _initialized, which also goes
// false while the AU keeps running but nothing may enter the plugin. Main
// thread only (activateCLAP()/deactivateCLAP()).
bool _clapActive = false;

// some info about the wrapped clap
// audio-port layout captured at PostConstructor. Scanning the CLAP
Expand Down
7 changes: 7 additions & 0 deletions src/detail/shared/spinlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ struct SpinLock
}
}

// for a caller that must not wait -- with lock()/unlock() this also makes
// std::unique_lock<SpinLock>(lock, std::try_to_lock) work
bool try_lock()
{
return !locked_.exchange(true, std::memory_order_acquire);
}

void unlock()
{
locked_.store(false, std::memory_order_release);
Expand Down
83 changes: 60 additions & 23 deletions src/wrapasauv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ OSStatus WrapAsAUV2::Initialize()
auto guarantee_mainthread = _plugin->AlwaysMainThread();
if (!activateCLAP())
{
// The host settled on a main-bus format pair the plugin does not accept
// (see activateCLAP). Refuse the initialization rather than render with
// buffers sized differently from the plugin's ports.
// The plugin refused the host-chosen main-bus format pair, or refused to
// activate at all (see activateCLAP). Refuse the initialization rather
// than render with buffers sized differently from the plugin's ports.
return kAudioUnitErr_FormatNotSupported;
}

Expand Down Expand Up @@ -362,10 +362,13 @@ void WrapAsAUV2::rebuildPresetCache() const
return;
}

// Only a completed crawl is worth caching. A host asking during one - AU
// asks early - would otherwise pin whatever partial list existed at that
// moment, and an empty one would stay empty until the completion tick.
_presetCacheBuilt = _presetIndex->isComplete();
// Only a completed crawl is publishable, so stay empty until there is one:
// the index sorts itself when the crawl finishes, so a host that read a
// partial list - AU asks early - would show names against numbers that
// resolve to other presets once the sort lands. The completion tick rebuilds
// and notifies the host.
if (!_presetIndex->isComplete()) return;
_presetCacheBuilt = true;

const auto presets = _presetIndex->presets();
_presetCache.reserve(presets.size());
Expand Down Expand Up @@ -1481,7 +1484,7 @@ bool WrapAsAUV2::activateCLAP()
{
if (_plugin)
{
assert(!_initialized);
assert(!_clapActive);
// Reconcile the host-chosen bus formats with the plugin's port layout
// before anything reads the ports: main thread, plugin deactivated. A
// failure here must fail the activation: ValidFormat can only vet each
Expand Down Expand Up @@ -1556,7 +1559,15 @@ bool WrapAsAUV2::activateCLAP()
_flushAdapter.reset();
}

_plugin->activate();
if (!_plugin->activate())
{
// Take the process adapter built above back down: with no active plugin
// behind it, nothing would ever drain what SetParameter queues on it.
deactivateCLAP();
return false;
}
_clapActive = true;

_plugin->start_processing();
_initialized = true;
}
Expand Down Expand Up @@ -1607,8 +1618,15 @@ void WrapAsAUV2::deactivateCLAP()

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

// CLAP forbids either call on a plugin that is not active, and a
// reactivation that failed leaves it exactly that way.
if (_clapActive)
{
_clapActive = false;
_plugin->stop_processing();
_plugin->deactivate();
}
}
}

Expand All @@ -1632,12 +1650,26 @@ void WrapAsAUV2::releaseHostMIDIOutput()
OSStatus WrapAsAUV2::Render(AudioUnitRenderActionFlags &inFlags, const AudioTimeStamp &inTimeStamp,
UInt32 inFrames)
{
assert(inFlags == 0);
ClapWrapper::detail::shared::SpinLockGuard processGuard(_processLock);
if (_initialized && (inFlags == 0))
// None of the flags a host may set on the way in mean "do not process":
// DoNotCheckRenderArgs only tells AUBase to skip its argument checks, and an
// input the upstream unit marked silent still has to reach a plugin that may
// have a tail. Only the offline phases that are not a render pass are refused,
// and this is not an offline unit, so they should never arrive at all.
constexpr AudioUnitRenderActionFlags cannotProcess =
kAudioOfflineUnitRenderAction_Preflight | kAudioOfflineUnitRenderAction_Complete;

// try_lock, not lock: onIdle() can hold this across clap_plugin_params.flush(),
// which is plugin code of unbounded duration. A block the render thread would
// have had to wait for is a block it renders silent instead.
std::unique_lock<ClapWrapper::detail::shared::SpinLock> processGuard(_processLock, std::try_to_lock);
if (processGuard.owns_lock() && _initialized && !(inFlags & cannotProcess))
{
// do the render dance
Clap::AUv2::ProcessData data{inFlags, inTimeStamp, inFrames, this};
// The adapter hands these to PullInput, which ORs in whatever the upstream
// unit reports -- including its own OutputIsSilence. Ours is set below, on
// purpose; inheriting it would tell the host this plugin rendered silence.
AudioUnitRenderActionFlags pullFlags = inFlags;
Clap::AUv2::ProcessData data{pullFlags, inTimeStamp, inFrames, this};

// retrieve musical information for this render block

Expand Down Expand Up @@ -1712,17 +1744,22 @@ OSStatus WrapAsAUV2::Render(AudioUnitRenderActionFlags &inFlags, const AudioTime
// ()
// {}
// );

// The plugin rendered: whatever silence the host or an upstream unit
// claimed on the way in does not describe this output.
inFlags &= static_cast<AudioUnitRenderActionFlags>(~kAudioUnitRenderAction_OutputIsSilence);
}
else
{
// Nothing rendered this cycle (the CLAP is down while onIdle() cycles it
// for a restart), and AUBase will not silence anything for us: the AU stays
// initialized throughout, so DoRenderBus copies the output element's cache
// into the host's buffer after every noErr Render and returning without
// writing replays the last block. Zero every output element, not just the
// bus being rendered - RenderBus answers the others from their caches - and
// the silence flag on top is only a hint. _renderedSinceIdle is left alone:
// the idle tick's flush still has to make up for this block.
// Nothing rendered this cycle (the CLAP is down while onIdle() cycles it for
// a restart, or that tick still holds the process lock), and AUBase will not
// silence anything for us: the AU stays initialized throughout, so
// DoRenderBus copies the output element's cache into the host's buffer after
// every noErr Render and returning without writing replays the last block.
// Zero every output element, not just the bus being rendered - RenderBus
// answers the others from their caches - and the silence flag on top is only
// a hint. _renderedSinceIdle is left alone: the idle tick's flush still has
// to make up for this block.
const auto numOutputs = Outputs().GetNumberOfElements();
for (UInt32 i = 0; i < numOutputs; ++i)
{
Expand Down
Loading