From bd2b008438ec95f5e0858cf1272ef49f2d2007c9 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 15 Nov 2024 01:01:01 +0100 Subject: [PATCH 1/5] AudioUnitBackend: Load music effects too --- .../backends/audiounit/audiounitbackend.mm | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitbackend.mm b/src/effects/backends/audiounit/audiounitbackend.mm index 8e7b839b259..fa58e80f432 100644 --- a/src/effects/backends/audiounit/audiounitbackend.mm +++ b/src/effects/backends/audiounit/audiounitbackend.mm @@ -18,7 +18,7 @@ /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AudioUnitBackend : public EffectsBackend { public: - AudioUnitBackend() : m_componentsById([[NSDictionary alloc] init]) { + AudioUnitBackend() : m_componentsById([[NSMutableDictionary alloc] init]) { loadAudioUnits(); } @@ -59,18 +59,23 @@ bool canInstantiateEffect(const QString& effectId) const override { } private: - NSDictionary* m_componentsById; + NSMutableDictionary* m_componentsById; QHash m_manifestsById; void loadAudioUnits() { qDebug() << "Loading audio units..."; + loadAudioUnitsOfType(kAudioUnitType_Effect); + loadAudioUnitsOfType(kAudioUnitType_MusicEffect); + } + + void loadAudioUnitsOfType(OSType componentType) { // See // https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/incorporating_audio_effects_and_instruments?language=objc // Create a query for audio components AudioComponentDescription description = { - .componentType = kAudioUnitType_Effect, + .componentType = componentType, .componentSubType = 0, .componentManufacturer = 0, .componentFlags = 0, @@ -85,10 +90,6 @@ void loadAudioUnits() { auto components = [manager componentsMatchingDescription:description]; // Assign ids to the components - NSMutableDictionary* componentsById = - [[NSMutableDictionary alloc] init]; - QHash manifestsById; - for (AVAudioUnitComponent* component in components) { qDebug() << "Found audio unit" << [component name]; @@ -97,13 +98,10 @@ void loadAudioUnits() { [component manufacturerName], [component name], [component versionString]]); - componentsById[effectId.toNSString()] = component; - manifestsById[effectId] = EffectManifestPointer( + m_componentsById[effectId.toNSString()] = component; + m_manifestsById[effectId] = EffectManifestPointer( new AudioUnitManifest(effectId, component)); } - - m_componentsById = componentsById; - m_manifestsById = manifestsById; } }; From 0d4c5321936b63f417b1fcd75d68120803feb8df Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 15 Nov 2024 01:33:06 +0100 Subject: [PATCH 2/5] AudioUnitManifest: Instantiate audio units out-of-process --- .../backends/audiounit/audiounitmanifest.mm | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 1ee8fd2c8f2..0a7d4e56f21 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -1,6 +1,8 @@ #import #include "effects/backends/effectmanifestparameter.h" +#include +#include #include #include "effects/backends/audiounit/audiounitmanager.h" @@ -18,9 +20,25 @@ setDescription(QString::fromNSString([component typeName])); setAuthor(QString::fromNSString([component manufacturerName])); - // Try instantiating the unit in-process to fetch its properties quickly + // Instantiate audio unit (out-of-process) to load parameters + AudioUnitManager manager{component}; + + const int TIMEOUT_MS = 5000; + + QElapsedTimer timer; + timer.start(); + + while (manager.getAudioUnit() == nil) { + if (timer.elapsed() > TIMEOUT_MS) { + qWarning() << name() << "took more than" << TIMEOUT_MS + << "ms to initialize, skipping manifest initialization " + "for this effect. This means this effect will not " + "display any parameters and likely not be useful!"; + return; + } + QThread::msleep(10); + } - AudioUnitManager manager{component, AudioUnitInstantiationType::Sync}; AudioUnit audioUnit = manager.getAudioUnit(); if (audioUnit) { From 5587e935aba5848d73425322c2c1f55fe784a270 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 15 Nov 2024 01:58:07 +0100 Subject: [PATCH 3/5] AudioUnitBackend: Load audio unit manifests concurrently --- .../backends/audiounit/audiounitbackend.mm | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitbackend.mm b/src/effects/backends/audiounit/audiounitbackend.mm index fa58e80f432..fc8a4f6823f 100644 --- a/src/effects/backends/audiounit/audiounitbackend.mm +++ b/src/effects/backends/audiounit/audiounitbackend.mm @@ -3,10 +3,12 @@ #import #import #import +#import #include #include #include +#include #include #include @@ -14,6 +16,7 @@ #include "effects/backends/audiounit/audiouniteffectprocessor.h" #include "effects/backends/audiounit/audiounitmanifest.h" #include "effects/defs.h" +#include "util/compatibility/qmutex.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AudioUnitBackend : public EffectsBackend { @@ -61,6 +64,7 @@ bool canInstantiateEffect(const QString& effectId) const override { private: NSMutableDictionary* m_componentsById; QHash m_manifestsById; + QMutex m_mutex; void loadAudioUnits() { qDebug() << "Loading audio units..."; @@ -83,24 +87,54 @@ void loadAudioUnitsOfType(OSType componentType) { }; // Find the audio units - // TODO: Should we perform this asynchronously (e.g. using Qt's - // threading or GCD)? auto manager = [AVAudioUnitComponentManager sharedAudioUnitComponentManager]; auto components = [manager componentsMatchingDescription:description]; - // Assign ids to the components + // Load component manifests (parameters etc.) concurrently since this + // requires instantiating the corresponding Audio Units. We use Grand + // Central Dispatch (GCD) for this instead of Qt's threading facilities + // since GCD is a bit more lightweight and generally preferred for + // Apple API-related stuff. + + dispatch_group_t group = dispatch_group_create(); + for (AVAudioUnitComponent* component in components) { qDebug() << "Found audio unit" << [component name]; QString effectId = QString::fromNSString( [NSString stringWithFormat:@"%@~%@~%@", - [component manufacturerName], - [component name], - [component versionString]]); + [component manufacturerName], + [component name], + [component versionString]]); + + // Register component m_componentsById[effectId.toNSString()] = component; - m_manifestsById[effectId] = EffectManifestPointer( - new AudioUnitManifest(effectId, component)); + + // Use a concurrent background GCD queue to load manifest + dispatch_queue_t queue = dispatch_get_global_queue( + DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + dispatch_group_async(group, queue, ^{ + // Load manifest (potentially slow blocking operation) + auto manifest = EffectManifestPointer( + new AudioUnitManifest(effectId, component)); + + // Register manifest + auto locker = lockMutex(&m_mutex); + m_manifestsById[effectId] = manifest; + }); + } + + int64_t timeoutMs = 10000; + + qDebug() << "Waiting for audio unit manifests to be loaded..."; + if (dispatch_group_wait(group, + dispatch_time(DISPATCH_TIME_NOW, timeoutMs * 1000000)) == + 0) { + qDebug() << "Successfully loaded audio unit manifests"; + } else { + qWarning() << "Timed out while loading audio unit manifests"; } } }; From b0fd89b511376ef75b3e00b5bb31314daeaf031d Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 17 Nov 2024 19:48:45 +0100 Subject: [PATCH 4/5] AudioUnitBackend: Reduce load timeout to 6 seconds --- src/effects/backends/audiounit/audiounitbackend.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitbackend.mm b/src/effects/backends/audiounit/audiounitbackend.mm index fc8a4f6823f..a06e5491db9 100644 --- a/src/effects/backends/audiounit/audiounitbackend.mm +++ b/src/effects/backends/audiounit/audiounitbackend.mm @@ -126,11 +126,11 @@ void loadAudioUnitsOfType(OSType componentType) { }); } - int64_t timeoutMs = 10000; + const int64_t TIMEOUT_MS = 6000; qDebug() << "Waiting for audio unit manifests to be loaded..."; if (dispatch_group_wait(group, - dispatch_time(DISPATCH_TIME_NOW, timeoutMs * 1000000)) == + dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_MS * 1000000)) == 0) { qDebug() << "Successfully loaded audio unit manifests"; } else { From 6d79c2a020b29e4a06d928d1362f8b7227a46b04 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 17 Nov 2024 19:49:15 +0100 Subject: [PATCH 5/5] AudioUnitManifest: Reduce timeout to 2 seconds --- src/effects/backends/audiounit/audiounitmanifest.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 0a7d4e56f21..dda59e053ba 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -23,7 +23,7 @@ // Instantiate audio unit (out-of-process) to load parameters AudioUnitManager manager{component}; - const int TIMEOUT_MS = 5000; + const int TIMEOUT_MS = 2000; QElapsedTimer timer; timer.start();