Skip to content
Open
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
9 changes: 8 additions & 1 deletion src/detail/auv2/auv2_base_classes.h
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ class WrapAsAUV2 : public ausdk::AUBase,
// buffer sizes).
bool applyConfigurationFromBusFormats();

// Returns false when the plugin rejects the host-chosen bus formats; the
// Returns false when the plugin rejects the bus formats or fails to start;
// caller must treat that as a failed initialization.
bool activateCLAP();
void deactivateCLAP();
Expand All @@ -832,6 +832,9 @@ class WrapAsAUV2 : public ausdk::AUBase,

// --------------- internals

/// Shares the SDK's non-realtime entry lock with idle callbacks and the Cocoa editor.
std::shared_ptr<ausdk::AUMutex> _mainThreadMutex = std::make_shared<ausdk::AUMutex>();

// the wrapped CLAP:
std::string _clapname;
std::string _clapid;
Expand All @@ -846,6 +849,10 @@ class WrapAsAUV2 : public ausdk::AUBase,
// exist. Lives across flushes so gestures pair up; see flushParameters().
std::unique_ptr<Clap::AUv2::ProcessAdapter> _flushAdapter;
std::atomic<bool> _initialized = false;
/// Whether CLAP activation succeeded and requires a matching deactivation.
bool _clapActivated = false;
/// Whether CLAP processing started and requires a matching stop notification.
bool _clapProcessing = false;

// some info about the wrapped clap
// audio-port layout captured at PostConstructor. Scanning the CLAP
Expand Down
6 changes: 5 additions & 1 deletion src/detail/auv2/auv2_shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include <iostream>
#include <functional>
#include <memory>
#include <AudioUnitSDK/AUUtility.h>
#include "clap_proxy.h"
#include <AudioToolbox/AudioUnitProperties.h>

Expand All @@ -27,7 +29,9 @@ namespace free_audio::auv2_wrapper
typedef struct ui_connection
{
uint32_t identifier = kAudioUnitProperty_ClapWrapper_UIConnection_id;
Clap::Plugin *_plugin = nullptr; // points to the plugin instance
Clap::Plugin *_plugin = nullptr; // points to the plugin instance
/// Serializes host lifecycle calls with editor calls and outlives either connection endpoint.
std::shared_ptr<ausdk::AUMutex> _mainThreadMutex;
Comment thread
defiantnerd marked this conversation as resolved.
clap_window_t *_window = nullptr; // points to a window handle, actually ptr to wrapping NSView class
uint32_t *_canary = nullptr; // a canary in the Windows class
std::function<void(clap_window_t *, uint32_t *)> _registerWindow = nullptr;
Expand Down
7 changes: 6 additions & 1 deletion src/detail/auv2/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ void ProcessAdapter::process(ProcessData &data)
for (uint32_t i = 0; i < _numInputs; ++i)
{
auto &m = static_cast<ausdk::AUInputElement &>(*_audioInputScope->SafeGetElement(i));
if (m.PullInput(data.flags, data.timestamp, i, data.numSamples) == noErr)
// Silence reported by one input must not describe another input or the plugin output.
auto inputFlags = data.flags & ~kAudioUnitRenderAction_OutputIsSilence;
if (m.PullInput(inputFlags, data.timestamp, i, data.numSamples) == noErr)
{
AudioBufferList &myInBuffers = m.GetBufferList();
auto num = myInBuffers.mNumberBuffers;
Expand Down Expand Up @@ -353,6 +355,9 @@ void ProcessAdapter::process(ProcessData &data)

_plugin->process(_plugin, &_processData);

// A CLAP plugin may generate audible output even when its inputs are silent.
data.flags &= ~kAudioUnitRenderAction_OutputIsSilence;

processOutputEvents();

// clean up and prepare the events for the next cycle
Expand Down
12 changes: 11 additions & 1 deletion src/detail/auv2/wrappedview.asinclude.mm
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ @implementation CLAP_WRAPPER_COCOA_CLASS

- (NSView *)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize
{
static free_audio::auv2_wrapper::ui_connection uiconn;
free_audio::auv2_wrapper::ui_connection uiconn;

// free_audio::auv2_wrapper::ui_connection connection;
// Remember we end up being called here because that's what AUCocoaUIView does in the initiation
Expand Down Expand Up @@ -89,6 +89,9 @@ - (id)initWithAUv2:(free_audio::auv2_wrapper::ui_connection *)cont preferredSize
LOGINFO("[clap-wrapper] creating NSView");

ui = *cont;
// The property lookup has returned, so editor calls need their own SDK entry guard.
const auto mainThreadMutex = ui._mainThreadMutex;
const ausdk::AUEntryGuard mainThreadGuard(mainThreadMutex.get());
canary = 0xbeebbeeb;

if (ui._registerWindow)
Expand Down Expand Up @@ -151,6 +154,8 @@ - (void)doIdle
}
- (void)viewDidMoveToWindow
{
const auto mainThreadMutex = ui._mainThreadMutex;
const ausdk::AUEntryGuard mainThreadGuard(mainThreadMutex.get());
if ([self window] == nil)
{
LOGINFO("[clap-wrapper] - view removed from a window");
Expand All @@ -171,6 +176,9 @@ - (void)viewDidMoveToWindow

- (void)dealloc
{
// Super deallocation releases the C++ ivars before this scope unlocks the mutex.
const auto mainThreadMutex = ui._mainThreadMutex;
const ausdk::AUEntryGuard mainThreadGuard(mainThreadMutex.get());
LOGINFO("[clap-wrapper] NS View dealloc");
if (idleTimer)
{
Expand All @@ -186,6 +194,8 @@ - (void)dealloc
- (void)setFrame:(NSRect)newSize
{
[super setFrame:newSize];
const auto mainThreadMutex = ui._mainThreadMutex;
const ausdk::AUEntryGuard mainThreadGuard(mainThreadMutex.get());
if (canary)
{
auto gui = ui._plugin->_ext._gui;
Expand Down
61 changes: 45 additions & 16 deletions src/wrapasauv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cassert>
#include <algorithm>
#include <cmath>
#include <mutex>
#include <Block.h>

extern bool fillAudioUnitCocoaView(AudioUnitCocoaViewInfo *viewInfo, std::shared_ptr<Clap::Plugin>);
Expand Down Expand Up @@ -157,6 +158,10 @@ WrapAsAUV2::WrapAsAUV2(const std::string &clapname, const std::string &clapid, i
, _idx{idx}
, _os_attached([this] { os::attach(this); }, [this] { os::detach(this); })
{
// Logic may initialize an AU on a worker while its editor and idle timer run.
// The SDK holds this lock across its complete non-realtime dispatch, including
// changes to AUBase::IsInitialized() before and after the virtual callbacks.
SetMutex(_mainThreadMutex.get());
_uiIsOpened = false;
if (!_desc)
{
Expand All @@ -173,7 +178,6 @@ WrapAsAUV2::WrapAsAUV2(const std::string &clapname, const std::string &clapid, i
if (_plugin)
{
_plugin->initialize();
_os_attached.on();
Comment thread
defiantnerd marked this conversation as resolved.
}
else
{
Expand All @@ -189,6 +193,7 @@ WrapAsAUV2::WrapAsAUV2(const std::string &clapname, const std::string &clapid, i

WrapAsAUV2::~WrapAsAUV2()
{
const std::lock_guard<ausdk::AUMutex> mainThreadGuard(*_mainThreadMutex);
#if AUSDK_MIDI2_AVAILABLE
if (auto blk = _midioutput_hosteventlistblock.exchange(nullptr)) Block_release(blk);
for (auto blk : _retiredEventListBlocks) Block_release(blk);
Expand All @@ -214,6 +219,8 @@ WrapAsAUV2::~WrapAsAUV2()
{
CFRelease(_current_program_name);
}
// AUBase must not retain a pointer to a member-owned lock during base destruction.
SetMutex(nullptr);
}

// the very very reduced state machine
Expand All @@ -233,9 +240,8 @@ 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.
// A rejected configuration or lifecycle call leaves the CLAP deactivated.
// Report the failure so the host does not render an unavailable processor.
return kAudioUnitErr_FormatNotSupported;
}

Expand Down Expand Up @@ -718,7 +724,6 @@ void WrapAsAUV2::Cleanup()
if (_plugin->_plugin && _plugin->_ext._gui)
{
this->_uiconn._destroyWindow();
this->_plugin->_ext._gui->destroy(_plugin->_plugin);
}
}
}
Expand Down Expand Up @@ -884,6 +889,7 @@ OSStatus WrapAsAUV2::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScop
// return noErr;
case kAudioUnitProperty_ClapWrapper_UIConnection_id:
_uiconn._plugin = _plugin.get();
_uiconn._mainThreadMutex = _mainThreadMutex;
_uiconn._window = nullptr;
_uiconn._registerWindow = [this](auto *x, auto *y)
{
Expand Down Expand Up @@ -1339,11 +1345,22 @@ bool WrapAsAUV2::activateCLAP()
_flushAdapter.reset();
}

_plugin->activate();
_plugin->start_processing();
_clapActivated = _plugin->activate();
if (!_clapActivated)
{
deactivateCLAP();
return false;
}
_clapProcessing = _plugin->start_processing();
if (!_clapProcessing)
{
deactivateCLAP();
return false;
}
_initialized = true;
return true;
}
return true;
return false;
}

void WrapAsAUV2::deactivateCLAP()
Expand All @@ -1357,8 +1374,16 @@ void WrapAsAUV2::deactivateCLAP()
_initialized = false;
_processAdapter.reset();
}
_plugin->stop_processing();
_plugin->deactivate();
if (_clapProcessing)
{
_plugin->stop_processing();
_clapProcessing = false;
}
if (_clapActivated)
{
_plugin->deactivate();
_clapActivated = false;
}
}
}

Expand All @@ -1382,9 +1407,8 @@ 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))
if (_initialized)
{
// do the render dance
Clap::AUv2::ProcessData data{inFlags, inTimeStamp, inFrames, this};
Expand Down Expand Up @@ -1590,6 +1614,10 @@ void WrapAsAUV2::pushQueuedEventsToHost()

void WrapAsAUV2::onIdle()
{
// Preserve pending work when a host lifecycle call owns the CLAP main-thread state.
// Waiting here could block the real UI thread while host initialization waits for it.
const std::unique_lock<ausdk::AUMutex> mainThreadGuard(*_mainThreadMutex, std::try_to_lock);
if (!mainThreadGuard.owns_lock()) return;
if (!_plugin) return;

pushQueuedEventsToHost();
Expand Down Expand Up @@ -1634,10 +1662,8 @@ void WrapAsAUV2::onIdle()
if (wasInitialized)
{
deactivateCLAP();
// Cannot fail for the format-pair reason Initialize guards against:
// the formats have not changed since the last successful activation.
// If it fails anyway, _initialized stays false and renders return
// silence, the same state as before Initialize.
// Even unchanged formats can encounter a rejected CLAP lifecycle call.
// A failed restart leaves processing unavailable for a later retry.
if (!activateCLAP())
{
LOGINFO("[clap-wrapper] restart: could not reactivate the plugin");
Expand Down Expand Up @@ -2247,6 +2273,9 @@ void WrapAsAUV2::PostConstructor()
Inputs().GetElement(0)->SetName(CFSTR("Input"));
LOGINFO("[clap-wrapper] PostConstructor: added placeholder silent input bus");
}

// The timer must not enter the CLAP while its initial port configuration is being queried.
_os_attached.on();
}

UInt32 WrapAsAUV2::GetAudioChannelLayout(AudioUnitScope scope, AudioUnitElement element,
Expand Down