diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h index c154ab0843..cef4de1077 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h @@ -43,35 +43,9 @@ namespace juce @tags{Audio} */ -class JUCE_API AudioUnitPluginFormat : public AudioPluginFormat +class JUCE_API AudioUnitPluginFormat : public AudioUnitPluginFormatHeadless { -public: - //============================================================================== - AudioUnitPluginFormat(); - ~AudioUnitPluginFormat() override; - - //============================================================================== - static String getFormatName() { return "AudioUnit"; } - String getName() const override { return getFormatName(); } - bool canScanForPlugins() const override { return true; } - bool isTrivialToScan() const override { return false; } - - void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; - bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; - String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; - bool pluginNeedsRescanning (const PluginDescription&) override; - StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; - bool doesPluginStillExist (const PluginDescription&) override; - FileSearchPath getDefaultLocationsToSearch() override; - void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override; - -private: - //============================================================================== - void createPluginInstance (const PluginDescription&, double initialSampleRate, - int initialBufferSize, PluginCreationCallback) override; - bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const override; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) + void createPluginInstance (const PluginDescription&, double, int, PluginCreationCallback) override; }; #endif diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 9cbe7835b9..c2815bc72c 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -34,228 +34,20 @@ #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) -#if JUCE_MAC -#include -#include -#include - -#if JUCE_PLUGINHOST_ARA - #include -#endif - -#endif - -#include - -#include - -#include -#include -#include -#include +#include namespace juce { -namespace AudioUnitFormatHelpers +class AudioUnitPluginWindowCocoa final : public AudioProcessorEditor { - #if JUCE_DEBUG - static ThreadLocalValue insideCallback; - #endif - - static String osTypeToString (OSType type) noexcept - { - const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), - (juce_wchar) ((type >> 16) & 0xff), - (juce_wchar) ((type >> 8) & 0xff), - (juce_wchar) (type & 0xff) }; - return String (s, 4); - } - - static OSType stringToOSType (String s) - { - if (s.trim().length() >= 4) // (to avoid trimming leading spaces) - s = s.trim(); - - s += " "; - - return (((OSType) (unsigned char) s[0]) << 24) - | (((OSType) (unsigned char) s[1]) << 16) - | (((OSType) (unsigned char) s[2]) << 8) - | ((OSType) (unsigned char) s[3]); - } - - static const char* auIdentifierPrefix = "AudioUnit:"; - - static String createPluginIdentifier (const AudioComponentDescription& desc) - { - String s (auIdentifierPrefix); - - if (desc.componentType == kAudioUnitType_MusicDevice) - s << "Synths/"; - else if (desc.componentType == kAudioUnitType_MusicEffect - || desc.componentType == kAudioUnitType_Effect) - s << "Effects/"; - else if (desc.componentType == kAudioUnitType_Generator) - s << "Generators/"; - else if (desc.componentType == kAudioUnitType_Panner) - s << "Panners/"; - else if (desc.componentType == kAudioUnitType_Mixer) - s << "Mixers/"; - else if (desc.componentType == kAudioUnitType_MIDIProcessor) - s << "MidiEffects/"; - - s << osTypeToString (desc.componentType) << "," - << osTypeToString (desc.componentSubType) << "," - << osTypeToString (desc.componentManufacturer); - - return s; - } - - static void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) - { - CFObjectHolder cfName; - - if (AudioComponentCopyName (comp, &cfName.object) == noErr) - name = String::fromCFString (cfName.object); - - if (name.containsChar (':')) - { - manufacturer = name.upToFirstOccurrenceOf (":", false, false).trim(); - name = name.fromFirstOccurrenceOf (":", false, false).trim(); - } - - if (name.isEmpty()) - name = ""; - } - - static bool getComponentDescFromIdentifier (const String& fileOrIdentifier, AudioComponentDescription& desc, - String& name, String& version, String& manufacturer) - { - if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) - { - String s (fileOrIdentifier.substring (jmax (fileOrIdentifier.lastIndexOfChar (':'), - fileOrIdentifier.lastIndexOfChar ('/')) + 1)); - - StringArray tokens; - tokens.addTokens (s, ",", StringRef()); - tokens.removeEmptyStrings(); - - if (tokens.size() == 3) - { - zerostruct (desc); - desc.componentType = stringToOSType (tokens[0]); - desc.componentSubType = stringToOSType (tokens[1]); - desc.componentManufacturer = stringToOSType (tokens[2]); - - if (AudioComponent comp = AudioComponentFindNext (nullptr, &desc)) - { - getNameAndManufacturer (comp, name, manufacturer); - - if (manufacturer.isEmpty()) - manufacturer = tokens[2]; - - if (version.isEmpty()) - { - UInt32 versionNum; - - if (AudioComponentGetVersion (comp, &versionNum) == noErr) - { - version << (int) (versionNum >> 16) << "." - << (int) ((versionNum >> 8) & 0xff) << "." - << (int) (versionNum & 0xff); - } - } - - return true; - } - } - } - - return false; - } - - static bool getComponentDescFromFile ([[maybe_unused]] const String& fileOrIdentifier, - [[maybe_unused]] AudioComponentDescription& desc, - [[maybe_unused]] String& name, - [[maybe_unused]] String& version, - [[maybe_unused]] String& manufacturer) - { - zerostruct (desc); - - #if JUCE_IOS - return false; - #else - const File file (fileOrIdentifier); - if (! file.hasFileExtension (".component") && ! file.hasFileExtension (".appex")) - return false; - - const char* const utf8 = fileOrIdentifier.toUTF8(); - - if (auto url = CFUniquePtr (CFURLCreateFromFileSystemRepresentation (nullptr, (const UInt8*) utf8, - (CFIndex) strlen (utf8), file.isDirectory()))) - { - if (auto bundleRef = CFUniquePtr (CFBundleCreate (kCFAllocatorDefault, url.get()))) - { - CFTypeRef bundleName = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleName")); - - if (bundleName != nullptr && CFGetTypeID (bundleName) == CFStringGetTypeID()) - name = String::fromCFString ((CFStringRef) bundleName); - - if (name.isEmpty()) - name = file.getFileNameWithoutExtension(); - - CFTypeRef versionString = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleVersion")); - - if (versionString != nullptr && CFGetTypeID (versionString) == CFStringGetTypeID()) - version = String::fromCFString ((CFStringRef) versionString); - - CFTypeRef manuString = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleGetInfoString")); - - if (manuString != nullptr && CFGetTypeID (manuString) == CFStringGetTypeID()) - manufacturer = String::fromCFString ((CFStringRef) manuString); - - NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; - - NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; - NSDictionary* dict = [audioComponents objectAtIndex: 0]; - - desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); - desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); - desc.componentSubType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"subtype"])); - - [bundle release]; - } - } - - return desc.componentType != 0 && desc.componentSubType != 0; - #endif - } - - static const char* getCategory (OSType type) noexcept - { - switch (type) - { - case kAudioUnitType_Effect: - case kAudioUnitType_MusicEffect: return "Effect"; - case kAudioUnitType_MusicDevice: return "Synth"; - case kAudioUnitType_Generator: return "Generator"; - case kAudioUnitType_Panner: return "Panner"; - case kAudioUnitType_Mixer: return "Mixer"; - case kAudioUnitType_MIDIProcessor: return "MidiEffects"; - default: break; - } - - return nullptr; - } - // Audio Unit plugins expect hosts to listen to their view bounds, and to resize // the plugin window/view appropriately. #if JUCE_IOS - #define JUCE_IOS_MAC_VIEW UIView + using JUCE_IOS_MAC_VIEW = UIView; using ViewComponentBaseClass = UIViewComponent; #else - #define JUCE_IOS_MAC_VIEW NSView + using JUCE_IOS_MAC_VIEW = NSView; using ViewComponentBaseClass = NSViewComponent; #endif @@ -266,2319 +58,8 @@ namespace AudioUnitFormatHelpers void handleAsyncUpdate() override { resizeToFitView(); } }; - template - static std::optional tryGetProperty (AudioUnit inUnit, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement) - { - PropertyType property{}; - auto size = (UInt32) sizeof (property); - - if (AudioUnitGetProperty (inUnit, inID, inScope, inElement, &property, &size) != noErr) - return {}; - - // This may lead to a memory corruption as the plugin has written more data than was provided! - jassert (size <= sizeof (PropertyType)); - - return property; - } - - template - class PropertyData - { - public: - explicit PropertyData (MemoryBlock&& inData) : data (std::move (inData)) {} - - Type& get() { return *static_cast< Type*> (data.getData()); } - const Type& get() const { return *static_cast (data.getData()); } - UInt32 size() const { return (UInt32) data.getSize(); } - - private: - MemoryBlock data; - }; - - template - static std::optional> tryGetPropertyData (AudioUnit inUnit, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement) - { - UInt32 size{}; - - if (AudioUnitGetPropertyInfo (inUnit, inID, inScope, inElement, &size, nullptr) != noErr) - return {}; - - constexpr auto minimumSize = sizeof (Type); - size = jmax ((UInt32) minimumSize, size); - - MemoryBlock data; - data.setSize (size, true); - - if (AudioUnitGetProperty (inUnit, inID, inScope, inElement, data.getData(), &size) != noErr) - return {}; - - // This may lead to a memory corruption as the plugin has written more data than was provided! - jassert (size <= data.getSize()); - - return PropertyData { std::move (data) }; - } - - template - static bool trySetProperty (AudioUnit inUnit, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - const Type& newValue) - { - return AudioUnitSetProperty (inUnit, inID, inScope, inElement, &newValue, sizeof (Type)); - } - - template - static bool trySetProperty (AudioUnit inUnit, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - const PropertyData& newValue) - { - return AudioUnitSetProperty (inUnit, inID, inScope, inElement, newValue.get(), newValue.size()); - } - - static UInt32 getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept - { - const auto count = tryGetProperty (comp, kAudioUnitProperty_ElementCount, scope, 0); - jassert (count); - return *count; - } - - /* The plugin may expect its channels in a particular order, reported to the host - using kAudioUnitProperty_AudioChannelLayout. - This remapper allows us to respect the channel order requested by the plugin, - while still using the JUCE channel ordering for the AudioBuffer argument - of AudioProcessor::processBlock. - */ - class SingleDirectionChannelMapping - { - public: - void setUpMapping (AudioUnit comp, bool isInput) - { - channels.clear(); - busOffset.clear(); - - const auto scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; - const auto n = getElementCount (comp, scope); - - for (UInt32 busIndex = 0; busIndex < n; ++busIndex) - { - std::vector busMap; - - if (const auto layout = tryGetPropertyData (comp, kAudioUnitProperty_AudioChannelLayout, scope, busIndex)) - { - const auto juceChannelOrder = CoreAudioLayouts::fromCoreAudio (layout->get()); - const auto auChannelOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (layout->get()); - - for (auto juceChannelIndex = 0; juceChannelIndex < juceChannelOrder.size(); ++juceChannelIndex) - busMap.push_back ((size_t) auChannelOrder.indexOf (juceChannelOrder.getTypeOfChannel (juceChannelIndex))); - } - - busOffset.push_back (busMap.empty() ? unknownChannelCount : channels.size()); - channels.insert (channels.end(), busMap.begin(), busMap.end()); - } - } - - size_t getAuIndexForJuceChannel (size_t bus, size_t channel) const noexcept - { - const auto baseOffset = busOffset[bus]; - return baseOffset != unknownChannelCount ? channels[baseOffset + channel] - : channel; - } - - private: - static constexpr size_t unknownChannelCount = std::numeric_limits::max(); - - /* The index (in the channels vector) of the first channel in each bus. - e.g the index of the first channel in the second bus can be found at busOffset[1]. - It's possible for a bus not to report its channel layout, and in this case a value - of unknownChannelCount will be stored for that bus. - */ - std::vector busOffset; - - /* The index in a collection of JUCE channels of the AU channel with a matching channel - type. The mappings for all buses are stored in bus order. To find the start offset for a - particular bus, use the busOffset vector. - e.g. the index of the AU channel with the same type as the fifth channel of the third bus - in JUCE layout is found at channels[busOffset[2] + 4]. - If the busOffset for the bus is unknownChannelCount, then assume there is no mapping - between JUCE/AU channel layouts. - */ - std::vector channels; - }; - - static bool isPluginAUv3 (const AudioComponentDescription& desc) - { - return (desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0; - } -} - -static bool hasARAExtension ([[maybe_unused]] AudioUnit audioUnit) -{ - #if JUCE_PLUGINHOST_ARA - UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); - Boolean isWriteable = FALSE; - - OSStatus status = AudioUnitGetPropertyInfo (audioUnit, - ARA::kAudioUnitProperty_ARAFactory, - kAudioUnitScope_Global, - 0, - &propertySize, - &isWriteable); - - if ((status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable) - return true; - #endif - - return false; -} - -struct AudioUnitDeleter -{ - void operator() (AudioUnit au) const { AudioComponentInstanceDispose (au); } -}; - -using AudioUnitUniquePtr = std::unique_ptr, AudioUnitDeleter>; -using AudioUnitSharedPtr = std::shared_ptr>; -using AudioUnitWeakPtr = std::weak_ptr>; - -static std::shared_ptr getARAFactory ([[maybe_unused]] AudioUnitSharedPtr audioUnit) -{ - #if JUCE_PLUGINHOST_ARA - jassert (audioUnit != nullptr); - - UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); - ARA::ARAAudioUnitFactory audioUnitFactory { ARA::kARAAudioUnitMagic, nullptr }; - - if (hasARAExtension (audioUnit.get())) - { - OSStatus status = AudioUnitGetProperty (audioUnit.get(), - ARA::kAudioUnitProperty_ARAFactory, - kAudioUnitScope_Global, - 0, - &audioUnitFactory, - &propertySize); - - if ((status == noErr) - && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) - && (audioUnitFactory.inOutMagicNumber == ARA::kARAAudioUnitMagic)) - { - jassert (audioUnitFactory.outFactory != nullptr); - return getOrCreateARAFactory (audioUnitFactory.outFactory, - [owningAuPtr = std::move (audioUnit)]() {}); - } - } - #endif - - return {}; -} - -struct VersionedAudioComponent -{ - AudioComponent audioComponent = nullptr; - bool isAUv3 = false; - - bool operator< (const VersionedAudioComponent& other) const { return audioComponent < other.audioComponent; } -}; - -using AudioUnitCreationCallback = std::function; - -static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback) -{ - if (versionedComponent.isAUv3) - { - AudioComponentInstantiate (versionedComponent.audioComponent, - kAudioComponentInstantiation_LoadOutOfProcess, - ^(AudioComponentInstance audioUnit, OSStatus err) - { - callback (audioUnit, err); - }); - - return; - } - - AudioComponentInstance audioUnit; - auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit); - callback (err != noErr ? nullptr : audioUnit, err); -} - -struct AudioComponentResult -{ - explicit AudioComponentResult (String error) : errorMessage (std::move (error)) {} - - explicit AudioComponentResult (VersionedAudioComponent auComponent) : component (std::move (auComponent)) {} - - bool isValid() const { return component.audioComponent != nullptr; } - - VersionedAudioComponent component; - String errorMessage; -}; - -static AudioComponentResult getAudioComponent (AudioUnitPluginFormat& format, const PluginDescription& desc) -{ - using namespace AudioUnitFormatHelpers; - - AudioUnitPluginFormat audioUnitPluginFormat; - - if (! format.fileMightContainThisPluginType (desc.fileOrIdentifier)) - return AudioComponentResult { NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in") }; - - String pluginName, version, manufacturer; - AudioComponentDescription componentDesc; - AudioComponent auComponent; - String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description"); - - if (! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer) - && ! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) - { - return AudioComponentResult { errMessage }; - } - - if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr) - { - return AudioComponentResult { errMessage }; - } - - if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr) - { - return AudioComponentResult { errMessage }; - } - - const bool isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); - - return AudioComponentResult { { auComponent, isAUv3 } }; -} - -static void getOrCreateARAAudioUnit (VersionedAudioComponent auComponent, std::function callback) -{ - static std::map audioUnitARACache; - - if (auto audioUnit = audioUnitARACache[auComponent].lock()) - { - callback (std::move (audioUnit)); - return; - } - - createAudioUnit (auComponent, [auComponent, cb = std::move (callback)] (AudioUnit audioUnit, OSStatus err) - { - cb ([auComponent, audioUnit, err]() -> AudioUnitSharedPtr - { - if (err != noErr) - return nullptr; - - AudioUnitSharedPtr auPtr { AudioUnitUniquePtr { audioUnit } }; - audioUnitARACache[auComponent] = auPtr; - return auPtr; - }()); - }); -} - -//============================================================================== -class AudioUnitPluginWindowCocoa; - -//============================================================================== -class AudioUnitPluginInstance final : public AudioPluginInstance -{ public: - struct AUInstanceParameter final : public Parameter - { - AUInstanceParameter (AudioUnitPluginInstance& parent, - UInt32 parameterID, - const String& parameterName, - AudioUnitParameterValue minParameterValue, - AudioUnitParameterValue maxParameterValue, - AudioUnitParameterValue defaultParameterValue, - bool parameterIsAutomatable, - bool parameterIsDiscrete, - int numParameterSteps, - bool isBoolean, - const String& label, - bool parameterValuesHaveStrings) - : pluginInstance (parent), - paramID (parameterID), - name (parameterName), - minValue (minParameterValue), - maxValue (maxParameterValue), - range (maxValue - minValue), - automatable (parameterIsAutomatable), - discrete (parameterIsDiscrete), - numSteps (numParameterSteps), - valuesHaveStrings (parameterValuesHaveStrings), - isSwitch (isBoolean), - valueLabel (label), - defaultValue (normaliseParamValue (defaultParameterValue)) - { - if (getNumSteps() <= maxStringsToCache) - auValueStrings = getDiscreteValueStrings(); - } - - float getValue() const override - { - const ScopedLock sl (pluginInstance.lock); - - AudioUnitParameterValue value = 0; - - if (auto* au = pluginInstance.audioUnit) - { - AudioUnitGetParameter (au, paramID, kAudioUnitScope_Global, 0, &value); - value = normaliseParamValue (value); - } - - return value; - } - - void setValue (float newValue) override - { - const ScopedLock sl (pluginInstance.lock); - - if (auto* au = pluginInstance.audioUnit) - { - AudioUnitSetParameter (au, paramID, kAudioUnitScope_Global, - 0, scaleParamValue (newValue), 0); - - sendParameterChangeEvent(); - } - } - - float getDefaultValue() const override - { - return defaultValue; - } - - String getName (int /*maximumStringLength*/) const override { return name; } - String getLabel() const override { return valueLabel; } - - String getText (float value, int maximumLength) const override - { - if (! auValueStrings.isEmpty()) - { - auto index = roundToInt (jlimit (0.0f, 1.0f, value) * (float) (auValueStrings.size() - 1)); - return auValueStrings[index]; - } - - auto scaledValue = scaleParamValue (value); - - if (valuesHaveStrings) - { - if (auto* au = pluginInstance.audioUnit) - { - AudioUnitParameterStringFromValue stringValue; - stringValue.inParamID = paramID; - stringValue.inValue = &scaledValue; - stringValue.outString = nullptr; - - UInt32 propertySize = sizeof (stringValue); - - auto err = AudioUnitGetProperty (au, - kAudioUnitProperty_ParameterStringFromValue, - kAudioUnitScope_Global, - 0, - &stringValue, - &propertySize); - - if (! err && stringValue.outString != nullptr) - { - const CFUniquePtr ownedString { stringValue.outString }; - return String::fromCFString (stringValue.outString).substring (0, maximumLength); - } - } - } - - return Parameter::getText (scaledValue, maximumLength); - } - - float getValueForText (const String& text) const override - { - if (! auValueStrings.isEmpty()) - { - auto index = auValueStrings.indexOf (text); - - if (index != -1) - return ((float) index) / (float) (auValueStrings.size() - 1); - } - - if (valuesHaveStrings) - { - if (auto* au = pluginInstance.audioUnit) - { - AudioUnitParameterValueFromString pvfs; - pvfs.inParamID = paramID; - CFUniquePtr cfString (text.toCFString()); - pvfs.inString = cfString.get(); - UInt32 propertySize = sizeof (pvfs); - - auto err = AudioUnitGetProperty (au, - kAudioUnitProperty_ParameterValueFromString, - kAudioUnitScope_Global, - 0, - &pvfs, - &propertySize); - - if (! err) - return normaliseParamValue (pvfs.outValue); - } - } - - return Parameter::getValueForText (text); - } - - bool isAutomatable() const override { return automatable; } - bool isDiscrete() const override { return discrete; } - bool isBoolean() const override { return isSwitch; } - int getNumSteps() const override { return numSteps; } - - StringArray getAllValueStrings() const override - { - if (! auValueStrings.isEmpty()) - return auValueStrings; - - // If this is hit, the returned array of strings is going to be very large! - // Consider alternative approaches - perhaps you could fetch value strings - // one-at-a-time to avoid needing to have all the strings in memory simultaneously. - jassert (getNumSteps() <= maxStringsToCache); - - return getDiscreteValueStrings(); - } - - String getParameterID() const override - { - return String (paramID); - } - - void sendParameterChangeEvent() - { - #if JUCE_MAC - jassert (pluginInstance.audioUnit != nullptr); - - AudioUnitEvent ev; - ev.mEventType = kAudioUnitEvent_ParameterValueChange; - ev.mArgument.mParameter.mAudioUnit = pluginInstance.audioUnit; - ev.mArgument.mParameter.mParameterID = paramID; - ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; - ev.mArgument.mParameter.mElement = 0; - - AUEventListenerNotify (pluginInstance.eventListenerRef, nullptr, &ev); - #endif - } - - float normaliseParamValue (float scaledValue) const noexcept - { - return (scaledValue - minValue) / range; - } - - float scaleParamValue (float normalisedValue) const noexcept - { - return minValue + (range * normalisedValue); - } - - UInt32 getRawParamID() const { return paramID; } - - void setName (String&& newName) { name = std::move (newName); } - void setLabel (String&& newLabel) { valueLabel = std::move (newLabel); } - - private: - static constexpr auto maxStringsToCache = 10'000; - - StringArray getDiscreteValueStrings() const - { - if (! isDiscrete()) - return {}; - - StringArray result; - - const auto maxIndex = getNumSteps() - 1; - - for (int i = 0; i < getNumSteps(); ++i) - result.add (getText ((float) i / (float) maxIndex, 1024)); - - return result; - } - - AudioUnitPluginInstance& pluginInstance; - const UInt32 paramID; - String name; - const AudioUnitParameterValue minValue, maxValue, range; - const bool automatable, discrete; - const int numSteps; - const bool valuesHaveStrings, isSwitch; - String valueLabel; - const AudioUnitParameterValue defaultValue; - StringArray auValueStrings; - }; - - AudioUnitPluginInstance (AudioComponentInstance au) - : AudioPluginInstance (getBusesProperties (au)), - auComponent (AudioComponentInstanceGetComponent (au)), - audioUnit (au), - #if JUCE_MAC - eventListenerRef (nullptr), - #endif - midiConcatenator (2048) - { - using namespace AudioUnitFormatHelpers; - - AudioComponentGetDescription (auComponent, &componentDesc); - - isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); - - wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice - || componentDesc.componentType == kAudioUnitType_MusicEffect - || componentDesc.componentType == kAudioUnitType_MIDIProcessor; - - isMidiEffectPlugin = (componentDesc.componentType == kAudioUnitType_MIDIProcessor); - AudioComponentDescription ignore; - getComponentDescFromIdentifier (createPluginIdentifier (componentDesc), ignore, pluginName, version, manufacturer); - updateSupportedLayouts(); - } - - ~AudioUnitPluginInstance() override - { - const ScopedLock sl (lock); - - #if JUCE_DEBUG - // this indicates that some kind of recursive call is getting triggered that's - // deleting this plugin while it's still under construction. - jassert (AudioUnitFormatHelpers::insideCallback.get() == 0); - #endif - - if (audioUnit != nullptr) - { - struct AUDeleter final : public CallbackMessage - { - AUDeleter (AudioUnitPluginInstance& inInstance, WaitableEvent& inEvent) - : auInstance (inInstance), completionSignal (inEvent) - {} - - void messageCallback() override - { - auInstance.cleanup(); - completionSignal.signal(); - } - - AudioUnitPluginInstance& auInstance; - WaitableEvent& completionSignal; - }; - - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - cleanup(); - } - else - { - WaitableEvent completionEvent; - (new AUDeleter (*this, completionEvent))->post(); - completionEvent.wait(); - } - } - } - - // called from the destructor above - void cleanup() - { - #if JUCE_MAC - disposeEventListener(); - #endif - - if (prepared) - releaseResources(); - - AudioComponentInstanceDispose (audioUnit); - audioUnit = nullptr; - } - - bool initialise (double rate, int blockSize) - { - producesMidiMessages = canProduceMidiOutput(); - setRateAndBufferSizeDetails (rate, blockSize); - setLatencySamples (0); - refreshParameterList(); - setPluginCallbacks(); - - #if JUCE_MAC - createEventListener(); - #endif - - return true; - } - - //============================================================================== - bool canAddBus (bool isInput) const override { return isBusCountWritable (isInput); } - bool canRemoveBus (bool isInput) const override { return isBusCountWritable (isInput); } - - bool canApplyBusCountChange (bool isInput, bool isAdding, BusProperties& outProperties) override - { - auto currentCount = (UInt32) getBusCount (isInput); - auto newCount = (UInt32) ((int) currentCount + (isAdding ? 1 : -1)); - AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; - - if (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)) == noErr) - { - getBusProperties (isInput, currentCount, outProperties.busName, outProperties.defaultLayout); - outProperties.isActivatedByDefault = true; - updateSupportedLayouts(); - - return true; - } - - return false; - } - - //============================================================================== - bool isBusesLayoutSupported (const BusesLayout& layouts) const override - { - if (layouts == getBusesLayout()) - return true; - - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - auto& requestedLayouts = (isInput ? layouts.inputBuses : layouts.outputBuses); - auto& oppositeRequestedLayouts = (isInput ? layouts.outputBuses : layouts.inputBuses); - auto& supported = (isInput ? supportedInLayouts : supportedOutLayouts); - const int n = getBusCount (isInput); - - for (int busIdx = 0; busIdx < n; ++busIdx) - { - auto& requested = requestedLayouts.getReference (busIdx); - const int oppositeBusIdx = jmin (getBusCount (! isInput) - 1, busIdx); - const bool hasOppositeBus = (oppositeBusIdx >= 0); - auto oppositeRequested = (hasOppositeBus ? oppositeRequestedLayouts.getReference (oppositeBusIdx) : AudioChannelSet()); - auto& possible = supported.getReference (busIdx); - - if (requested.isDisabled()) - return false; - - if (possible.size() > 0 && ! possible.contains (requested)) - return false; - - int i; - for (i = 0; i < numChannelInfos; ++i) - { - auto& info = channelInfos[i]; - auto& thisChannels = (isInput ? info.inChannels : info.outChannels); - auto& opChannels = (isInput ? info.outChannels : info.inChannels); - - // this bus - if (thisChannels == 0) continue; - else if (thisChannels > 0 && requested.size() != thisChannels) continue; - else if (thisChannels < -2 && requested.size() > (thisChannels * -1)) continue; - - // opposite bus - if (opChannels == 0 && hasOppositeBus) continue; - else if (opChannels > 0 && oppositeRequested.size() != opChannels) continue; - else if (opChannels < -2 && oppositeRequested.size() > (opChannels * -1)) continue; - - // both buses - if (thisChannels == -2 && opChannels == -2) continue; - if (thisChannels == -1 && opChannels == -1) - { - int numOppositeBuses = getBusCount (! isInput); - int j; - - for (j = 0; j < numOppositeBuses; ++j) - if (requested.size() != oppositeRequestedLayouts.getReference (j).size()) - break; - - if (j < numOppositeBuses) - continue; - } - - break; - } - - if (i >= numChannelInfos) - return false; - } - } - - return true; - } - - bool syncBusLayouts (const BusesLayout& layouts, bool isInitialized, bool& layoutHasChanged) const - { - layoutHasChanged = false; - - for (const auto scope : { kAudioUnitScope_Input, kAudioUnitScope_Output }) - { - const auto isInput = scope == kAudioUnitScope_Input; - const auto busCount = (UInt32) getBusCount (isInput); - - if (AudioUnitFormatHelpers::getElementCount (audioUnit, scope) != busCount && isBusCountWritable (isInput)) - { - layoutHasChanged = true; - - if (! AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, busCount)) - jassertfalse; - } - - for (UInt32 busIndex = 0; busIndex < busCount; ++busIndex) - { - const auto& set = layouts.getChannelSet (isInput, (int) busIndex); - const auto currentStream = AudioUnitFormatHelpers::tryGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIndex); - - if (! currentStream.has_value()) - return false; - - const auto actualNumChannels = static_cast (currentStream->mChannelsPerFrame); - const auto requestedNumChannels = set.size(); - - if (actualNumChannels != requestedNumChannels) - { - const auto sampleRate = AudioUnitFormatHelpers::tryGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, busIndex); - jassert (sampleRate.has_value()); - - layoutHasChanged = true; - AudioStreamBasicDescription newStream{}; - newStream.mSampleRate = *sampleRate; - newStream.mFormatID = kAudioFormatLinearPCM; - newStream.mFormatFlags = (int) kAudioFormatFlagsNativeFloatPacked | (int) kAudioFormatFlagIsNonInterleaved | (int) kAudioFormatFlagsNativeEndian; - newStream.mFramesPerPacket = 1; - newStream.mBytesPerPacket = 4; - newStream.mBytesPerFrame = 4; - newStream.mBitsPerChannel = 32; - newStream.mChannelsPerFrame = static_cast (requestedNumChannels); - - if (AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIndex, newStream)) - return false; - } - - if (isInitialized && ! set.isDiscreteLayout()) - { - if (const auto layout = AudioUnitFormatHelpers::tryGetPropertyData (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIndex)) - { - // This is likely a fault with the plugin. Not enough data has been allocated - // to support the AudioChannelLayout being described - jassert (layout->get().mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions - || layout->size() >= offsetof (AudioChannelLayout, mChannelDescriptions) + layout->get().mNumberChannelDescriptions * sizeof (AudioChannelDescription)); - - const auto requestedTag = CoreAudioLayouts::toCoreAudio (set); - const auto actualTag = CoreAudioLayouts::toCoreAudio (CoreAudioLayouts::fromCoreAudio (layout->get())); - - if (actualTag != requestedTag) - { - AudioChannelLayout newLayout{}; - newLayout.mChannelLayoutTag = requestedTag; - - // We'll likely need to fill more data in the newLayout for these tags. - // Please notify the JUCE team if you hit this assertion and provide clear - // instructions regarding the plugin being loaded and any other relevant - // details required to hit this assertion. Preferably if you can recreate - // the assertion using the AudioPluginHost that would be extremely helpful. - - jassert (requestedTag != kAudioChannelLayoutTag_UseChannelBitmap - && requestedTag != kAudioChannelLayoutTag_UseChannelDescriptions); - - if (AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIndex, newLayout)) - return false; - } - } - } - } - } - - return true; - } - - bool canApplyBusesLayout (const BusesLayout& layouts) const override - { - // You cannot call setBusesLayout when the AudioProcessor is processing. - // Call releaseResources first! - jassert (! prepared); - - bool layoutHasChanged = false; - - if (! syncBusLayouts (layouts, false, layoutHasChanged)) - return false; - - // did anything actually change - if (! layoutHasChanged) - return true; - - // Some plug-ins require the LayoutTag to be set after initialization - const auto success = (AudioUnitInitialize (audioUnit) == noErr) - && syncBusLayouts (layouts, true, layoutHasChanged); - - AudioUnitUninitialize (audioUnit); - - if (! success) - // make sure that the layout is back to its original state - syncBusLayouts (getBusesLayout(), false, layoutHasChanged); - - return success; - } - - //============================================================================== - // AudioPluginInstance methods: - - void fillInPluginDescription (PluginDescription& desc) const override - { - desc.name = pluginName; - desc.descriptiveName = pluginName; - desc.fileOrIdentifier = AudioUnitFormatHelpers::createPluginIdentifier (componentDesc); - desc.uniqueId = desc.deprecatedUid = ((int) componentDesc.componentType) - ^ ((int) componentDesc.componentSubType) - ^ ((int) componentDesc.componentManufacturer); - desc.lastFileModTime = Time(); - desc.lastInfoUpdateTime = Time::getCurrentTime(); - desc.pluginFormatName = "AudioUnit"; - desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); - desc.manufacturerName = manufacturer; - desc.version = version; - desc.numInputChannels = getTotalNumInputChannels(); - desc.numOutputChannels = getTotalNumOutputChannels(); - desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); - - #if JUCE_PLUGINHOST_ARA - desc.hasARAExtension = [&] - { - UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); - Boolean isWriteable = FALSE; - - OSStatus status = AudioUnitGetPropertyInfo (audioUnit, - ARA::kAudioUnitProperty_ARAFactory, - kAudioUnitScope_Global, - 0, - &propertySize, - &isWriteable); - - return (status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable; - }(); - #endif - } - - void getExtensions (ExtensionsVisitor& visitor) const override - { - struct Extensions final : public ExtensionsVisitor::AudioUnitClient - { - explicit Extensions (const AudioUnitPluginInstance* instanceIn) : instance (instanceIn) {} - - AudioUnit getAudioUnitHandle() const noexcept override { return instance->audioUnit; } - - const AudioUnitPluginInstance* instance = nullptr; - }; - - visitor.visitAudioUnitClient (Extensions { this }); - - #ifdef JUCE_PLUGINHOST_ARA - struct ARAExtensions final : public ExtensionsVisitor::ARAClient - { - explicit ARAExtensions (const AudioUnitPluginInstance* instanceIn) : instance (instanceIn) {} - - void createARAFactoryAsync (std::function cb) const override - { - getOrCreateARAAudioUnit ({ instance->auComponent, instance->isAUv3 }, - [origCb = std::move (cb)] (auto dylibKeepAliveAudioUnit) - { - origCb ([&]() -> ARAFactoryWrapper - { - if (dylibKeepAliveAudioUnit != nullptr) - return ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }; - - return ARAFactoryWrapper { nullptr }; - }()); - }); - } - - const AudioUnitPluginInstance* instance = nullptr; - }; - - if (hasARAExtension (audioUnit)) - visitor.visitARAClient (ARAExtensions (this)); - #endif - } - - void* getPlatformSpecificData() override { return audioUnit; } - const String getName() const override { return pluginName; } - - double getTailLengthSeconds() const override - { - Float64 tail = 0; - UInt32 tailSize = sizeof (tail); - - if (audioUnit != nullptr) - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_TailTime, kAudioUnitScope_Global, - 0, &tail, &tailSize); - - return tail; - } - - bool acceptsMidi() const override { return wantsMidiMessages; } - bool producesMidi() const override { return producesMidiMessages; } - - //============================================================================== - // AudioProcessor methods: - - void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override - { - if (audioUnit != nullptr) - { - releaseResources(); - setPluginCallbacks(); - - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; - const int n = getBusCount (isInput); - - for (int i = 0; i < n; ++i) - { - Float64 sampleRate; - UInt32 sampleRateSize = sizeof (sampleRate); - const Float64 sr = newSampleRate; - - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); - - if (! approximatelyEqual (sampleRate, sr)) - { - if (isAUv3) // setting kAudioUnitProperty_SampleRate fails on AUv3s - { - AudioStreamBasicDescription stream; - UInt32 dataSize = sizeof (stream); - auto err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, &dataSize); - - if (err == noErr && dataSize == sizeof (stream)) - { - stream.mSampleRate = sr; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, sizeof (stream)); - } - } - else - { - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sr, sizeof (sr)); - } - } - - if (isInput) - { - AURenderCallbackStruct info; - zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) - - info.inputProcRefCon = this; - info.inputProc = renderGetInputCallback; - - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, - static_cast (i), &info, sizeof (info)); - } - else - { - outputBufferList.add (new AUBuffer (static_cast (getChannelCountOfBus (false, i)))); - } - } - } - - UInt32 frameSize = (UInt32) estimatedSamplesPerBlock; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, - &frameSize, sizeof (frameSize)); - - setRateAndBufferSizeDetails ((double) newSampleRate, estimatedSamplesPerBlock); - - zerostruct (timeStamp); - timeStamp.mSampleTime = 0; - timeStamp.mHostTime = mach_absolute_time(); - timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; - - wasPlaying = false; - - resetBuses(); - - bool ignore; - - if (! syncBusLayouts (getBusesLayout(), false, ignore)) - return; - - prepared = [&] - { - if (AudioUnitInitialize (audioUnit) != noErr) - return false; - - if (! haveParameterList) - refreshParameterList(); - - if (! syncBusLayouts (getBusesLayout(), true, ignore)) - { - AudioUnitUninitialize (audioUnit); - return false; - } - - updateLatency(); - return true; - }(); - - inMapping .setUpMapping (audioUnit, true); - outMapping.setUpMapping (audioUnit, false); - - preparedChannels = jmax (getTotalNumInputChannels(), getTotalNumOutputChannels()); - preparedSamples = estimatedSamplesPerBlock; - inputBuffer.setSize (preparedChannels, preparedSamples); - } - } - - void releaseResources() override - { - if (prepared) - { - resetBuses(); - AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); - AudioUnitUninitialize (audioUnit); - - outputBufferList.clear(); - prepared = false; - } - - incomingMidi.clear(); - } - - void resetBuses() - { - for (int i = 0; i < getBusCount (true); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Input, static_cast (i)); - for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast (i)); - } - - void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) - { - auto* playhead = getPlayHead(); - const auto position = playhead != nullptr ? playhead->getPosition() : nullopt; - - if (const auto hostTimeNs = position.hasValue() ? position->getHostTimeNs() : nullopt) - { - timeStamp.mHostTime = *hostTimeNs; - timeStamp.mFlags |= kAudioTimeStampHostTimeValid; - } - else - { - timeStamp.mHostTime = 0; - timeStamp.mFlags &= ~kAudioTimeStampHostTimeValid; - } - - // If these are hit, we might allocate in the process block! - jassert (buffer.getNumChannels() <= preparedChannels); - jassert (buffer.getNumSamples() <= preparedSamples); - // Copy the input buffer to guard against the case where a bus has more output channels - // than input channels, so rendering the output for that bus might stamp over the input - // to the following bus. - inputBuffer.makeCopyOf (buffer, true); - - const auto numSamples = buffer.getNumSamples(); - - if (auSupportsBypass) - { - updateBypass (processBlockBypassedCalled); - } - else if (processBlockBypassedCalled) - { - AudioProcessor::processBlockBypassed (buffer, midiMessages); - return; - } - - if (prepared) - { - const auto numOutputBuses = getBusCount (false); - - for (int i = 0; i < numOutputBuses; ++i) - { - if (AUBuffer* buf = outputBufferList[i]) - { - AudioBufferList& abl = *buf; - const auto* bus = getBus (false, i); - const auto channelCount = bus != nullptr ? bus->getNumberOfChannels() : 0; - - for (auto juceChannel = 0; juceChannel < channelCount; ++juceChannel) - { - const auto auChannel = outMapping.getAuIndexForJuceChannel ((size_t) i, (size_t) juceChannel); - abl.mBuffers[auChannel].mNumberChannels = 1; - abl.mBuffers[auChannel].mDataByteSize = (UInt32) ((size_t) numSamples * sizeof (float)); - abl.mBuffers[auChannel].mData = buffer.getWritePointer (bus->getChannelIndexInProcessBlockBuffer (juceChannel)); - } - } - } - - if (wantsMidiMessages) - { - for (const auto metadata : midiMessages) - { - if (metadata.numBytes <= 3) - { - const auto getByteOrZero = [&metadata] (int index) - { - return index < metadata.numBytes ? metadata.data[index] : (uint8) 0; - }; - - MusicDeviceMIDIEvent (audioUnit, - getByteOrZero (0), - getByteOrZero (1), - getByteOrZero (2), - (UInt32) metadata.samplePosition); - } - else - { - MusicDeviceSysEx (audioUnit, metadata.data, (UInt32) metadata.numBytes); - } - } - - midiMessages.clear(); - } - - for (int i = 0; i < numOutputBuses; ++i) - { - AudioUnitRenderActionFlags flags = 0; - - if (AUBuffer* buf = outputBufferList[i]) - AudioUnitRender (audioUnit, &flags, &timeStamp, static_cast (i), (UInt32) numSamples, buf->bufferList.get()); - } - - timeStamp.mSampleTime += numSamples; - } - else - { - // Plugin not working correctly, so just bypass.. - for (int i = getTotalNumOutputChannels(); --i >= 0;) - buffer.clear (i, 0, buffer.getNumSamples()); - } - - if (producesMidiMessages) - { - const ScopedLock sl (midiInLock); - midiMessages.swapWith (incomingMidi); - incomingMidi.clear(); - } - } - - void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override - { - processAudio (buffer, midiMessages, false); - } - - void processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midiMessages) override - { - processAudio (buffer, midiMessages, true); - } - - //============================================================================== - AudioProcessorParameter* getBypassParameter() const override { return auSupportsBypass ? bypassParam.get() : nullptr; } - - bool hasEditor() const override - { - #if JUCE_MAC - return true; - #else - UInt32 dataSize; - Boolean isWritable; - - return (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_RequestViewController, - kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr - && dataSize == sizeof (uintptr_t) && isWritable != 0); - #endif - } - - AudioProcessorEditor* createEditor() override; - - static AudioProcessor::BusesProperties getBusesProperties (AudioComponentInstance comp) - { - AudioProcessor::BusesProperties busProperties; - - for (int dir = 0; dir < 2; ++dir) - { - const auto isInput = (dir == 0); - const auto n = AudioUnitFormatHelpers::getElementCount (comp, isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); - - for (UInt32 i = 0; i < n; ++i) - { - String busName; - AudioChannelSet currentLayout; - - getBusProperties (comp, isInput, i, busName, currentLayout); - jassert (! currentLayout.isDisabled()); - - busProperties.addBus (isInput, busName, currentLayout, true); - } - } - - return busProperties; - } - - //============================================================================== - const String getInputChannelName (int index) const override - { - if (isPositiveAndBelow (index, getTotalNumInputChannels())) - return "Input " + String (index + 1); - - return {}; - } - - const String getOutputChannelName (int index) const override - { - if (isPositiveAndBelow (index, getTotalNumOutputChannels())) - return "Output " + String (index + 1); - - return {}; - } - - bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumInputChannels()); } - bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } - - //============================================================================== - void sendAllParametersChangedEvents() - { - #if JUCE_MAC - jassert (audioUnit != nullptr); - - AudioUnitParameter param; - param.mAudioUnit = audioUnit; - param.mParameterID = kAUParameterListener_AnyParameter; - - AUParameterListenerNotify (nullptr, nullptr, ¶m); - #endif - } - - //============================================================================== - class ScopedFactoryPresets - { - public: - ScopedFactoryPresets (AudioUnit& au) - { - UInt32 sz = sizeof (CFArrayRef); - AudioUnitGetProperty (au, kAudioUnitProperty_FactoryPresets, - kAudioUnitScope_Global, 0, &presets.object, &sz); - } - - CFArrayRef get() const noexcept - { - return presets.object; - } - - private: - CFObjectHolder presets; - }; - - int getNumPrograms() override - { - ScopedFactoryPresets factoryPresets { audioUnit }; - - if (factoryPresets.get() != nullptr) - return (int) CFArrayGetCount (factoryPresets.get()); - - return 0; - } - - int getCurrentProgram() override - { - AUPreset current{}; - UInt32 sz = sizeof (AUPreset); - - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_PresentPreset, - kAudioUnitScope_Global, 0, ¤t, &sz); - - const CFUniquePtr ownedString { current.presetName }; - return current.presetNumber; - } - - void setCurrentProgram (int newIndex) override - { - ScopedFactoryPresets factoryPresets { audioUnit }; - - if (factoryPresets.get() != nullptr - && newIndex < (int) CFArrayGetCount (factoryPresets.get())) - { - AUPreset current{}; - current.presetNumber = newIndex; - - if (auto* p = static_cast (CFArrayGetValueAtIndex (factoryPresets.get(), newIndex))) - current.presetName = p->presetName; - - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_PresentPreset, - kAudioUnitScope_Global, 0, ¤t, sizeof (AUPreset)); - - sendAllParametersChangedEvents(); - } - } - - const String getProgramName (int index) override - { - if (index == -1) - { - AUPreset current{}; - current.presetNumber = -1; - - UInt32 prstsz = sizeof (AUPreset); - - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_PresentPreset, - kAudioUnitScope_Global, 0, ¤t, &prstsz); - - const CFUniquePtr ownedString { current.presetName }; - return current.presetName != nullptr ? String::fromCFString (current.presetName) : String{}; - } - - ScopedFactoryPresets factoryPresets { audioUnit }; - - if (factoryPresets.get() != nullptr) - { - for (CFIndex i = 0; i < CFArrayGetCount (factoryPresets.get()); ++i) - if (auto* p = static_cast (CFArrayGetValueAtIndex (factoryPresets.get(), i))) - if (p->presetNumber == index) - return String::fromCFString (p->presetName); - } - - return {}; - } - - void changeProgramName (int /*index*/, const String& /*newName*/) override - { - jassertfalse; // xxx not implemented! - } - - //============================================================================== - void updateTrackProperties (const TrackProperties& properties) override - { - if (properties.name.has_value()) - { - CFObjectHolder contextName { properties.name->toCFString() }; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ContextName, kAudioUnitScope_Global, - 0, &contextName.object, sizeof (contextName.object)); - } - } - - //============================================================================== - void getStateInformation (MemoryBlock& destData) override - { - getCurrentProgramStateInformation (destData); - } - - void getCurrentProgramStateInformation (MemoryBlock& destData) override - { - CFObjectHolder propertyList; - UInt32 sz = sizeof (propertyList.object); - - if (AudioUnitGetProperty (audioUnit, - kAudioUnitProperty_ClassInfo, - kAudioUnitScope_Global, - 0, &propertyList.object, &sz) == noErr) - { - CFUniquePtr stream (CFWriteStreamCreateWithAllocatedBuffers (kCFAllocatorDefault, kCFAllocatorDefault)); - CFWriteStreamOpen (stream.get()); - - const auto bytesWritten = CFPropertyListWrite (propertyList.object, stream.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr); - CFWriteStreamClose (stream.get()); - - CFUniquePtr data ((CFDataRef) CFWriteStreamCopyProperty (stream.get(), kCFStreamPropertyDataWritten)); - - destData.setSize ((size_t) bytesWritten); - destData.copyFrom (CFDataGetBytePtr (data.get()), 0, destData.getSize()); - } - } - - void setStateInformation (const void* data, int sizeInBytes) override - { - setCurrentProgramStateInformation (data, sizeInBytes); - } - - void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override - { - CFUniquePtr stream (CFReadStreamCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, - sizeInBytes, kCFAllocatorNull)); - CFReadStreamOpen (stream.get()); - - CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0; - CFObjectHolder propertyList { CFPropertyListCreateWithStream (kCFAllocatorDefault, - stream.get(), - 0, - kCFPropertyListImmutable, - &format, - nullptr) }; - - if (propertyList.object != nullptr) - { - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, - 0, &propertyList.object, sizeof (propertyList.object)); - - sendAllParametersChangedEvents(); - } - } - - void refreshParameterList() override - { - paramIDToParameter.clear(); - AudioProcessorParameterGroup newParameterTree; - - if (audioUnit != nullptr) - { - UInt32 paramListSize = 0; - auto err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, - 0, ¶mListSize, nullptr); - - haveParameterList = (paramListSize > 0 && err == noErr); - - if (! haveParameterList) - return; - - if (paramListSize > 0) - { - const size_t numParams = paramListSize / sizeof (int); - - std::vector ids (numParams, 0); - - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, - 0, ids.data(), ¶mListSize); - - std::map groupIDMap; - - for (size_t i = 0; i < numParams; ++i) - { - const ScopedAudioUnitParameterInfo info { audioUnit, ids[i] }; - - if (! info.isValid()) - continue; - - const auto paramName = getParamName (info.get()); - const auto label = getParamLabel (info.get()); - const auto isDiscrete = (info.get().unit == kAudioUnitParameterUnit_Indexed - || info.get().unit == kAudioUnitParameterUnit_Boolean); - const auto isBoolean = info.get().unit == kAudioUnitParameterUnit_Boolean; - - auto parameter = std::make_unique (*this, - ids[i], - paramName, - info.get().minValue, - info.get().maxValue, - info.get().defaultValue, - (info.get().flags & kAudioUnitParameterFlag_NonRealTime) == 0, - isDiscrete, - isDiscrete ? (int) (info.get().maxValue - info.get().minValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), - isBoolean, - label, - (info.get().flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0); - - paramIDToParameter.emplace (ids[i], parameter.get()); - - if (info.get().flags & kAudioUnitParameterFlag_HasClump) - { - auto groupInfo = groupIDMap.find (info.get().clumpID); - - if (groupInfo == groupIDMap.end()) - { - const auto clumpName = [this, &info] - { - AudioUnitParameterNameInfo clumpNameInfo; - UInt32 clumpSz = sizeof (clumpNameInfo); - zerostruct (clumpNameInfo); - clumpNameInfo.inID = info.get().clumpID; - clumpNameInfo.inDesiredLength = (SInt32) 256; - - if (AudioUnitGetProperty (audioUnit, - kAudioUnitProperty_ParameterClumpName, - kAudioUnitScope_Global, - 0, - &clumpNameInfo, - &clumpSz) == noErr) - { - const CFUniquePtr ownedString { clumpNameInfo.outName }; - return String::fromCFString (clumpNameInfo.outName); - } - - return String (info.get().clumpID); - }(); - - auto group = std::make_unique (String (info.get().clumpID), - clumpName, String()); - group->addChild (std::move (parameter)); - groupIDMap[info.get().clumpID] = group.get(); - newParameterTree.addChild (std::move (group)); - } - else - { - groupInfo->second->addChild (std::move (parameter)); - } - } - else - { - newParameterTree.addChild (std::move (parameter)); - } - } - } - } - - setHostedParameterTree (std::move (newParameterTree)); - - UInt32 propertySize = 0; - Boolean writable = false; - - auSupportsBypass = (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_BypassEffect, - kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr - && propertySize >= sizeof (UInt32) && writable); - bypassParam.reset (new AUBypassParameter (*this)); - } - - void updateLatency() - { - Float64 latencySecs = 0.0; - UInt32 latencySize = sizeof (latencySecs); - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, - 0, &latencySecs, &latencySize); - - setLatencySamples (roundToInt (latencySecs * getSampleRate())); - } - - void handleIncomingMidiMessage (void*, const MidiMessage& message) - { - const ScopedLock sl (midiInLock); - incomingMidi.addEvent (message, 0); - } - - void handlePartialSysexMessage (void*, const uint8*, int, double) {} - - bool isMidiEffect() const override { return isMidiEffectPlugin; } - -private: - //============================================================================== - friend class AudioUnitPluginWindowCocoa; - friend class AudioUnitPluginFormat; - - CoreAudioTimeConversions timeConversions; - - AudioComponentDescription componentDesc; - AudioComponent auComponent; - String pluginName, manufacturer, version; - String fileOrIdentifier; - CriticalSection lock; - - bool wantsMidiMessages = false, producesMidiMessages = false, - wasPlaying = false, prepared = false, - isAUv3 = false, isMidiEffectPlugin = false; - - struct AUBuffer - { - AUBuffer (size_t numBuffers) - { - bufferList.calloc (1, (sizeof (AudioBufferList) - sizeof (::AudioBuffer)) + (sizeof (::AudioBuffer) * numBuffers)); - AudioBufferList& buffer = *bufferList.get(); - - buffer.mNumberBuffers = static_cast (numBuffers); - } - - operator AudioBufferList&() - { - return *bufferList.get(); - } - - HeapBlock bufferList; - }; - - //============================================================================== - struct AUBypassParameter final : public Parameter - { - AUBypassParameter (AudioUnitPluginInstance& effectToUse) - : parent (effectToUse), currentValue (getCurrentHostValue()) - {} - - bool getCurrentHostValue() - { - if (parent.auSupportsBypass) - { - UInt32 dataSize = sizeof (UInt32); - UInt32 value = 0; - - if (AudioUnitGetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect, - kAudioUnitScope_Global, 0, &value, &dataSize) == noErr - && dataSize == sizeof (UInt32)) - return value != 0; - } - - return false; - } - - float getValue() const override - { - return currentValue ? 1.0f : 0.0f; - } - - void setValue (float newValue) override - { - auto newBypassValue = (newValue != 0.0f); - - const ScopedLock sl (parent.lock); - - if (newBypassValue != currentValue) - { - currentValue = newBypassValue; - - if (parent.auSupportsBypass) - { - UInt32 value = (newValue != 0.0f ? 1 : 0); - AudioUnitSetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect, - kAudioUnitScope_Global, 0, &value, sizeof (UInt32)); - - #if JUCE_MAC - jassert (parent.audioUnit != nullptr); - - AudioUnitEvent ev; - ev.mEventType = kAudioUnitEvent_PropertyChange; - ev.mArgument.mProperty.mAudioUnit = parent.audioUnit; - ev.mArgument.mProperty.mPropertyID = kAudioUnitProperty_BypassEffect; - ev.mArgument.mProperty.mScope = kAudioUnitScope_Global; - ev.mArgument.mProperty.mElement = 0; - - AUEventListenerNotify (parent.eventListenerRef, nullptr, &ev); - #endif - } - } - } - - float getValueForText (const String& text) const override - { - String lowercaseText (text.toLowerCase()); - - for (auto& testText : auOnStrings) - if (lowercaseText == testText) - return 1.0f; - - for (auto& testText : auOffStrings) - if (lowercaseText == testText) - return 0.0f; - - return text.getIntValue() != 0 ? 1.0f : 0.0f; - } - - float getDefaultValue() const override { return 0.0f; } - String getName (int /*maximumStringLength*/) const override { return "Bypass"; } - String getText (float value, int) const override { return (value != 0.0f ? TRANS ("On") : TRANS ("Off")); } - bool isAutomatable() const override { return true; } - bool isDiscrete() const override { return true; } - bool isBoolean() const override { return true; } - int getNumSteps() const override { return 2; } - StringArray getAllValueStrings() const override { return values; } - String getLabel() const override { return {}; } - - String getParameterID() const override { return {}; } - - AudioUnitPluginInstance& parent; - const StringArray auOnStrings { TRANS ("on"), TRANS ("yes"), TRANS ("true") }; - const StringArray auOffStrings { TRANS ("off"), TRANS ("no"), TRANS ("false") }; - const StringArray values { TRANS ("Off"), TRANS ("On") }; - - bool currentValue = false; - }; - - OwnedArray outputBufferList; - AudioTimeStamp timeStamp; - AudioBuffer inputBuffer; - Array> supportedInLayouts, supportedOutLayouts; - - int numChannelInfos, preparedChannels = 0, preparedSamples = 0; - HeapBlock channelInfos; - - AudioUnit audioUnit; - #if JUCE_MAC - AUEventListenerRef eventListenerRef; - #endif - - std::map paramIDToParameter; - - AudioUnitFormatHelpers::SingleDirectionChannelMapping inMapping, outMapping; - MidiDataConcatenator midiConcatenator; - CriticalSection midiInLock; - MidiBuffer incomingMidi; - std::unique_ptr bypassParam; - bool lastProcessBlockCallWasBypass = false, auSupportsBypass = false; - bool haveParameterList = false; - - void setPluginCallbacks() - { - if (audioUnit != nullptr) - { - #if JUCE_MAC - if (producesMidiMessages) - { - AUMIDIOutputCallbackStruct info; - zerostruct (info); - - info.userData = this; - info.midiOutputCallback = renderMidiOutputCallback; - - producesMidiMessages = (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallback, - kAudioUnitScope_Global, 0, &info, sizeof (info)) == noErr); - } - #endif - - HostCallbackInfo info; - zerostruct (info); - - info.hostUserData = this; - info.beatAndTempoProc = getBeatAndTempoCallback; - info.musicalTimeLocationProc = getMusicalTimeLocationCallback; - info.transportStateProc = getTransportStateCallback; - - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, - kAudioUnitScope_Global, 0, &info, sizeof (info)); - } - } - - #if JUCE_MAC - void disposeEventListener() - { - if (eventListenerRef != nullptr) - { - AUListenerDispose (eventListenerRef); - eventListenerRef = nullptr; - } - } - - void createEventListener() - { - if (audioUnit == nullptr) - return; - - disposeEventListener(); - - AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), - kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); - - for (auto* param : getParameters()) - { - jassert (dynamic_cast (param) != nullptr); - - AudioUnitEvent event; - event.mArgument.mParameter.mAudioUnit = audioUnit; - event.mArgument.mParameter.mParameterID = static_cast (param)->getRawParamID(); - event.mArgument.mParameter.mScope = kAudioUnitScope_Global; - event.mArgument.mParameter.mElement = 0; - - event.mEventType = kAudioUnitEvent_ParameterValueChange; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - - event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - - event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - } - - addPropertyChangeListener (kAudioUnitProperty_PresentPreset); - addPropertyChangeListener (kAudioUnitProperty_ParameterList); - addPropertyChangeListener (kAudioUnitProperty_Latency); - addPropertyChangeListener (kAudioUnitProperty_BypassEffect); - } - - void addPropertyChangeListener (AudioUnitPropertyID type) const - { - AudioUnitEvent event; - event.mEventType = kAudioUnitEvent_PropertyChange; - event.mArgument.mProperty.mPropertyID = type; - event.mArgument.mProperty.mAudioUnit = audioUnit; - event.mArgument.mProperty.mScope = kAudioUnitScope_Global; - event.mArgument.mProperty.mElement = 0; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - } - - void eventCallback (const AudioUnitEvent& event, AudioUnitParameterValue newValue) - { - if (event.mEventType == kAudioUnitEvent_PropertyChange) - { - respondToPropertyChange (event.mArgument.mProperty); - return; - } - - const auto iter = paramIDToParameter.find (event.mArgument.mParameter.mParameterID); - auto* param = iter != paramIDToParameter.end() ? iter->second : nullptr; - jassert (param != nullptr); // Invalid parameter index - - if (param == nullptr) - return; - - if (event.mEventType == kAudioUnitEvent_ParameterValueChange) - param->sendValueChangedMessageToListeners (param->normaliseParamValue (newValue)); - else if (event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture) - param->beginChangeGesture(); - else if (event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) - param->endChangeGesture(); - } - - void respondToPropertyChange (const AudioUnitProperty& prop) - { - switch (prop.mPropertyID) - { - case kAudioUnitProperty_ParameterList: - updateParameterInfo(); - updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); - break; - - case kAudioUnitProperty_PresentPreset: - sendAllParametersChangedEvents(); - updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); - break; - - case kAudioUnitProperty_Latency: - updateLatency(); - break; - - case kAudioUnitProperty_BypassEffect: - if (bypassParam != nullptr) - bypassParam->setValueNotifyingHost (bypassParam->getValue()); - - break; - } - } - - static void eventListenerCallback (void* userRef, void*, const AudioUnitEvent* event, - UInt64, AudioUnitParameterValue value) - { - JUCE_ASSERT_MESSAGE_THREAD - jassert (event != nullptr); - static_cast (userRef)->eventCallback (*event, value); - } - - void updateParameterInfo() - { - for (const auto& idAndParam : paramIDToParameter) - { - const auto& id = idAndParam.first; - const auto& param = idAndParam.second; - - const ScopedAudioUnitParameterInfo info { audioUnit, id }; - - if (! info.isValid()) - continue; - - param->setName (getParamName (info.get())); - param->setLabel (getParamLabel (info.get())); - } - } - #endif - - /* Some fields in the AudioUnitParameterInfo may need to be released after use, - so we'll do that using RAII. - */ - class ScopedAudioUnitParameterInfo - { - public: - ScopedAudioUnitParameterInfo (AudioUnit au, UInt32 paramId) - { - auto sz = (UInt32) sizeof (info); - valid = noErr == AudioUnitGetProperty (au, - kAudioUnitProperty_ParameterInfo, - kAudioUnitScope_Global, - paramId, - &info, - &sz); - } - - ScopedAudioUnitParameterInfo (const ScopedAudioUnitParameterInfo&) = delete; - ScopedAudioUnitParameterInfo (ScopedAudioUnitParameterInfo&&) = delete; - ScopedAudioUnitParameterInfo& operator= (const ScopedAudioUnitParameterInfo&) = delete; - ScopedAudioUnitParameterInfo& operator= (ScopedAudioUnitParameterInfo&&) = delete; - - ~ScopedAudioUnitParameterInfo() noexcept - { - if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) == 0) - return; - - if (info.cfNameString != nullptr) - CFRelease (info.cfNameString); - - if (info.unit == kAudioUnitParameterUnit_CustomUnit && info.unitName != nullptr) - CFRelease (info.unitName); - } - - bool isValid() const { return valid; } - - const AudioUnitParameterInfo& get() const noexcept { return info; } - - private: - AudioUnitParameterInfo info; - bool valid = false; - }; - - static String getParamName (const AudioUnitParameterInfo& info) - { - if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) == 0) - return { info.name, sizeof (info.name) }; - - return String::fromCFString (info.cfNameString); - } - - static String getParamLabel (const AudioUnitParameterInfo& info) - { - if (info.unit == kAudioUnitParameterUnit_CustomUnit) return String::fromCFString (info.unitName); - if (info.unit == kAudioUnitParameterUnit_Percent) return "%"; - if (info.unit == kAudioUnitParameterUnit_Seconds) return "s"; - if (info.unit == kAudioUnitParameterUnit_Hertz) return "Hz"; - if (info.unit == kAudioUnitParameterUnit_Decibels) return "dB"; - if (info.unit == kAudioUnitParameterUnit_Milliseconds) return "ms"; - - return {}; - } - - //============================================================================== - OSStatus renderGetInput (AudioUnitRenderActionFlags*, - const AudioTimeStamp*, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList* ioData) - { - if (inputBuffer.getNumChannels() <= 0) - { - jassertfalse; - return noErr; - } - - // if this ever happens, might need to add extra handling - if (inputBuffer.getNumSamples() != (int) inNumberFrames) - { - jassertfalse; - return noErr; - } - - const auto buffer = static_cast (inBusNumber) < getBusCount (true) - ? getBusBuffer (inputBuffer, true, static_cast (inBusNumber)) - : AudioBuffer(); - - for (int juceChannel = 0; juceChannel < buffer.getNumChannels(); ++juceChannel) - { - const auto auChannel = (int) inMapping.getAuIndexForJuceChannel (inBusNumber, (size_t) juceChannel); - - if (auChannel < buffer.getNumChannels()) - memcpy (ioData->mBuffers[auChannel].mData, buffer.getReadPointer (juceChannel), sizeof (float) * inNumberFrames); - else - zeromem (ioData->mBuffers[auChannel].mData, sizeof (float) * inNumberFrames); - } - - return noErr; - } - - OSStatus renderMidiOutput (const MIDIPacketList* pktlist) - { - if (pktlist != nullptr && pktlist->numPackets) - { - auto time = Time::getMillisecondCounterHiRes() * 0.001; - const MIDIPacket* packet = &pktlist->packet[0]; - - for (UInt32 i = 0; i < pktlist->numPackets; ++i) - { - midiConcatenator.pushMidiData (packet->data, (int) packet->length, time, (void*) nullptr, *this); - packet = MIDIPacketNext (packet); - } - } - - return noErr; - } - - template - static void setIfNotNull (Type1* p, Type2 value) noexcept - { - if (p != nullptr) *p = value; - } - - /* If the AudioPlayHead is available, and has valid PositionInfo, this will return the result - of calling the specified getter on that PositionInfo. Otherwise, this will return a - default-constructed instance of the same type. - - For getters that return an Optional, this function will return a nullopt if the playhead or - position info is invalid. - - For getters that return a bool, this function will return false if the playhead or position - info is invalid. - */ - template - Result getFromPlayHead (Result (AudioPlayHead::PositionInfo::* member)() const) const - { - if (auto* ph = getPlayHead()) - if (const auto pos = ph->getPosition()) - return ((*pos).*member)(); - - return {}; - } - - OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const - { - setIfNotNull (outCurrentBeat, getFromPlayHead (&AudioPlayHead::PositionInfo::getPpqPosition).orFallback (0)); - setIfNotNull (outCurrentTempo, getFromPlayHead (&AudioPlayHead::PositionInfo::getBpm).orFallback (120.0)); - return noErr; - } - - OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, - UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const - { - setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx - setIfNotNull (outCurrentMeasureDownBeat, getFromPlayHead (&AudioPlayHead::PositionInfo::getPpqPositionOfLastBarStart).orFallback (0.0)); - - const auto signature = getFromPlayHead (&AudioPlayHead::PositionInfo::getTimeSignature).orFallback (AudioPlayHead::TimeSignature{}); - setIfNotNull (outTimeSig_Numerator, (Float32) signature.numerator); - setIfNotNull (outTimeSig_Denominator, (UInt32) signature.denominator); - - return noErr; - } - - OSStatus getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, - Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, - Float64* outCycleStartBeat, Float64* outCycleEndBeat) - { - const auto nowPlaying = getFromPlayHead (&AudioPlayHead::PositionInfo::getIsPlaying); - setIfNotNull (outIsPlaying, nowPlaying); - setIfNotNull (outTransportStateChanged, std::exchange (wasPlaying, nowPlaying) != nowPlaying); - setIfNotNull (outCurrentSampleInTimeLine, (double) getFromPlayHead (&AudioPlayHead::PositionInfo::getTimeInSamples).orFallback (0)); - setIfNotNull (outIsCycling, getFromPlayHead (&AudioPlayHead::PositionInfo::getIsLooping)); - - const auto loopPoints = getFromPlayHead (&AudioPlayHead::PositionInfo::getLoopPoints).orFallback (AudioPlayHead::LoopPoints{}); - setIfNotNull (outCycleStartBeat, loopPoints.ppqStart); - setIfNotNull (outCycleEndBeat, loopPoints.ppqEnd); - - return noErr; - } - - //============================================================================== - static OSStatus renderGetInputCallback (void* hostRef, AudioUnitRenderActionFlags* ioActionFlags, - const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, - UInt32 inNumberFrames, AudioBufferList* ioData) - { - return static_cast (hostRef) - ->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); - } - - static OSStatus renderMidiOutputCallback (void* hostRef, const AudioTimeStamp*, UInt32 /*midiOutNum*/, - const MIDIPacketList* pktlist) - { - return static_cast (hostRef)->renderMidiOutput (pktlist); - } - - static OSStatus getBeatAndTempoCallback (void* hostRef, Float64* outCurrentBeat, Float64* outCurrentTempo) - { - return static_cast (hostRef)->getBeatAndTempo (outCurrentBeat, outCurrentTempo); - } - - static OSStatus getMusicalTimeLocationCallback (void* hostRef, UInt32* outDeltaSampleOffsetToNextBeat, - Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, - Float64* outCurrentMeasureDownBeat) - { - return static_cast (hostRef) - ->getMusicalTimeLocation (outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, - outTimeSig_Denominator, outCurrentMeasureDownBeat); - } - - static OSStatus getTransportStateCallback (void* hostRef, Boolean* outIsPlaying, Boolean* outTransportStateChanged, - Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, - Float64* outCycleStartBeat, Float64* outCycleEndBeat) - { - return static_cast (hostRef) - ->getTransportState (outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, - outIsCycling, outCycleStartBeat, outCycleEndBeat); - } - - //============================================================================== - bool isBusCountWritable (bool isInput) const noexcept - { - UInt32 countSize; - Boolean writable; - AudioUnitScope scope = (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); - - auto err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &countSize, &writable); - - return (err == noErr && writable != 0 && countSize == sizeof (UInt32)); - } - - //============================================================================== - void getBusProperties (bool isInput, UInt32 busIdx, String& busName, AudioChannelSet& currentLayout) const - { - getBusProperties (audioUnit, isInput, busIdx, busName, currentLayout); - } - - static void getBusProperties (AudioUnit comp, bool isInput, UInt32 busIdx, String& busName, AudioChannelSet& currentLayout) - { - const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; - busName = (isInput ? "Input #" : "Output #") + String (busIdx + 1); - - { - CFObjectHolder busNameCF; - UInt32 propertySize = sizeof (busNameCF.object); - - if (AudioUnitGetProperty (comp, kAudioUnitProperty_ElementName, scope, busIdx, &busNameCF.object, &propertySize) == noErr) - if (busNameCF.object != nullptr) - busName = nsStringToJuce ((NSString*) busNameCF.object); - - if (const auto auLayout = AudioUnitFormatHelpers::tryGetPropertyData (comp, kAudioUnitProperty_AudioChannelLayout, scope, busIdx)) - currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout->get()); - - if (currentLayout.isDisabled()) - { - AudioStreamBasicDescription descr; - propertySize = sizeof (descr); - - if (AudioUnitGetProperty (comp, kAudioUnitProperty_StreamFormat, scope, busIdx, &descr, &propertySize) == noErr) - currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); - } - } - } - - //============================================================================== - void numBusesChanged() override - { - updateSupportedLayouts(); - } - - void updateSupportedLayouts() - { - supportedInLayouts.clear(); - supportedOutLayouts.clear(); - numChannelInfos = 0; - channelInfos.free(); - - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; - const auto n = AudioUnitFormatHelpers::getElementCount (audioUnit, scope); - - for (UInt32 busIdx = 0; busIdx < n; ++busIdx) - { - Array supported; - AudioChannelSet currentLayout; - - if (const auto auLayout = AudioUnitFormatHelpers::tryGetPropertyData (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIdx)) - currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout->get()); - - if (currentLayout.isDisabled()) - { - AudioStreamBasicDescription descr; - UInt32 propertySize = sizeof (descr); - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIdx, &descr, &propertySize) == noErr) - currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); - } - - supported.clear(); - { - UInt32 propertySize = 0; - Boolean writable; - - if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, busIdx, &propertySize, &writable) == noErr - && propertySize > 0) - { - const size_t numElements = propertySize / sizeof (AudioChannelLayoutTag); - HeapBlock layoutTags (numElements); - propertySize = static_cast (sizeof (AudioChannelLayoutTag) * numElements); - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, - static_cast (busIdx), layoutTags.get(), &propertySize) == noErr) - { - for (int j = 0; j < static_cast (numElements); ++j) - { - const AudioChannelLayoutTag tag = layoutTags[j]; - - if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) - { - AudioChannelLayout caLayout; - - caLayout.mChannelLayoutTag = tag; - supported.addIfNotAlreadyThere (CoreAudioLayouts::fromCoreAudio (caLayout)); - } - } - - if (supported.size() > 0) - supported.addIfNotAlreadyThere (currentLayout); - } - } - } - - (isInput ? supportedInLayouts : supportedOutLayouts).add (supported); - } - } - - { - UInt32 propertySize = 0; - Boolean writable; - - if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr - && propertySize > 0) - { - numChannelInfos = propertySize / sizeof (AUChannelInfo); - channelInfos.malloc (static_cast (numChannelInfos)); - propertySize = static_cast (sizeof (AUChannelInfo) * static_cast (numChannelInfos)); - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, channelInfos.get(), &propertySize) != noErr) - numChannelInfos = 0; - } - else - { - numChannelInfos = 1; - channelInfos.malloc (static_cast (numChannelInfos)); - channelInfos.get()->inChannels = -1; - channelInfos.get()->outChannels = -1; - } - } - } - - bool canProduceMidiOutput() - { - #if JUCE_MAC - UInt32 dataSize = 0; - Boolean isWritable = false; - - if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, - kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr - && dataSize != 0) - { - CFObjectHolder midiArray; - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, - kAudioUnitScope_Global, 0, &midiArray.object, &dataSize) == noErr) - return (CFArrayGetCount (midiArray.object) > 0); - } - #endif - - return false; - } - - bool supportsMPE() const override - { - UInt32 dataSize = 0; - Boolean isWritable = false; - - if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportsMPE, - kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr - && dataSize == sizeof (UInt32)) - { - UInt32 result = 0; - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportsMPE, - kAudioUnitScope_Global, 0, &result, &dataSize) == noErr) - { - return result > 0; - } - } - - return false; - } - - //============================================================================== - void updateBypass (bool processBlockBypassedCalled) - { - if (processBlockBypassedCalled && bypassParam != nullptr) - { - if (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass) - bypassParam->setValue (1.0f); - } - else - { - if (lastProcessBlockCallWasBypass && bypassParam != nullptr) - bypassParam->setValue (0.0f); - } - - lastProcessBlockCallWasBypass = processBlockBypassedCalled; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance) -}; - -//============================================================================== -class AudioUnitPluginWindowCocoa final : public AudioProcessorEditor -{ -public: - AudioUnitPluginWindowCocoa (AudioUnitPluginInstance& p, bool createGenericViewIfNeeded) + AudioUnitPluginWindowCocoa (AudioUnitPluginInstanceHeadless& p, bool createGenericViewIfNeeded) : AudioProcessorEditor (&p), plugin (p) { @@ -2634,9 +115,8 @@ public: } private: - - AudioUnitPluginInstance& plugin; - AudioUnitFormatHelpers::AutoResizingNSViewComponent wrapper; + AudioUnitPluginInstanceHeadless& plugin; + AutoResizingNSViewComponent wrapper; typedef void (^ViewControllerCallbackBlock)(AUViewControllerBase *); @@ -2649,16 +129,16 @@ private: Boolean isWritable = false; #if JUCE_MAC - if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + if (AudioUnitGetPropertyInfo (plugin.getAudioUnitHandle(), kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize != 0 - && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + && AudioUnitGetPropertyInfo (plugin.getAudioUnitHandle(), kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr) { HeapBlock info; info.calloc (dataSize, 1); - if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + if (AudioUnitGetProperty (plugin.getAudioUnitHandle(), kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, info, &dataSize) == noErr) { NSString* viewClassName = (NSString*) (info->mCocoaAUViewClass[0]); @@ -2672,7 +152,7 @@ private: && [viewClass instancesRespondToSelector: @selector (uiViewForAudioUnit: withSize:)]) { id factory = [[[viewClass alloc] init] autorelease]; - pluginView = [factory uiViewForAudioUnit: plugin.audioUnit + pluginView = [factory uiViewForAudioUnit: plugin.getAudioUnitHandle() withSize: NSMakeSize (getWidth(), getHeight())]; } @@ -2687,14 +167,14 @@ private: dataSize = 0; isWritable = false; - if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, + if (AudioUnitGetPropertyInfo (plugin.getAudioUnitHandle(), kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize == sizeof (ViewControllerCallbackBlock)) { waitingForViewCallback = true; auto callback = ^(AUViewControllerBase* controller) { this->requestViewControllerCallback (controller); }; - if (noErr == AudioUnitSetProperty (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &callback, dataSize)) + if (noErr == AudioUnitSetProperty (plugin.getAudioUnitHandle(), kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &callback, dataSize)) return true; waitingForViewCallback = false; @@ -2711,7 +191,7 @@ private: desc, name, version, manufacturer); } - pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.audioUnit]; + pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.getAudioUnitHandle()]; } #endif @@ -2767,208 +247,42 @@ private: } }; -//============================================================================== -AudioProcessorEditor* AudioUnitPluginInstance::createEditor() +class AudioUnitPluginInstance final : public AudioUnitPluginInstanceHeadless { - std::unique_ptr w (new AudioUnitPluginWindowCocoa (*this, false)); +public: + using AudioUnitPluginInstanceHeadless::AudioUnitPluginInstanceHeadless; - if (! static_cast (w.get())->isValid()) - w.reset (new AudioUnitPluginWindowCocoa (*this, true)); // use AUGenericView as a fallback - - return w.release(); -} - -//============================================================================== -AudioUnitPluginFormat::AudioUnitPluginFormat() -{ -} - -AudioUnitPluginFormat::~AudioUnitPluginFormat() -{ -} - -void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray& results, - const String& fileOrIdentifier) -{ - if (! fileMightContainThisPluginType (fileOrIdentifier)) - return; - - PluginDescription desc; - desc.fileOrIdentifier = fileOrIdentifier; - desc.uniqueId = desc.deprecatedUid = 0; - - if (MessageManager::getInstance()->isThisTheMessageThread() - && requiresUnblockedMessageThreadDuringCreation (desc)) - return; - - try + bool hasEditor() const override { - auto createdInstance = createInstanceFromDescription (desc, 44100.0, 512); + #if JUCE_MAC + return true; + #else + UInt32 dataSize; + Boolean isWritable; - if (auto auInstance = dynamic_cast (createdInstance.get())) - results.add (new PluginDescription (auInstance->getPluginDescription())); + return (AudioUnitGetPropertyInfo (getAudioUnitHandle(), kAudioUnitProperty_RequestViewController, + kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr + && dataSize == sizeof (uintptr_t) && isWritable != 0); + #endif } - catch (...) + + AudioProcessorEditor* createEditor() override { - // crashed while loading... + std::unique_ptr w (new AudioUnitPluginWindowCocoa (*this, false)); + + if (! static_cast (w.get())->isValid()) + w.reset (new AudioUnitPluginWindowCocoa (*this, true)); // use AUGenericView as a fallback + + return w.release(); } -} +}; void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, - double rate, int blockSize, + double rate, + int blockSize, PluginCreationCallback callback) { - auto auComponentResult = getAudioComponent (*this, desc); - - if (! auComponentResult.isValid()) - { - callback (nullptr, std::move (auComponentResult.errorMessage)); - return; - } - - createAudioUnit (auComponentResult.component, - [rate, blockSize, origCallback = std::move (callback)] (AudioUnit audioUnit, OSStatus err) - { - if (err == noErr) - { - auto instance = std::make_unique (audioUnit); - - if (instance->initialise (rate, blockSize)) - origCallback (std::move (instance), {}); - else - origCallback (nullptr, NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in")); - } - else - { - auto errMsg = TRANS ("An OS error occurred during initialisation of the plug-in (XXX)"); - origCallback (nullptr, errMsg.replace ("XXX", String (err))); - } - }); -} - -void AudioUnitPluginFormat::createARAFactoryAsync (const PluginDescription& desc, ARAFactoryCreationCallback callback) -{ - auto auComponentResult = getAudioComponent (*this, desc); - - if (! auComponentResult.isValid()) - { - callback ({ {}, "Failed to create AudioComponent for " + desc.descriptiveName }); - return; - } - - getOrCreateARAAudioUnit (auComponentResult.component, [cb = std::move (callback)] (auto dylibKeepAliveAudioUnit) - { - cb ([&]() -> ARAFactoryResult - { - if (dylibKeepAliveAudioUnit != nullptr) - return { ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }, "" }; - - return { {}, "Failed to create ARAFactory from the provided AudioUnit" }; - }()); - }); -} - -bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const -{ - String pluginName, version, manufacturer; - AudioComponentDescription componentDesc; - - if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, - pluginName, version, manufacturer) - || AudioUnitFormatHelpers::getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, - pluginName, version, manufacturer)) - { - if (AudioComponent auComp = AudioComponentFindNext (nullptr, &componentDesc)) - { - if (AudioComponentGetDescription (auComp, &componentDesc) == noErr) - return AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); - } - } - - return false; -} - -StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, bool /*recursive*/, bool allowPluginsWhichRequireAsynchronousInstantiation) -{ - StringArray result; - AudioComponent comp = nullptr; - - for (;;) - { - AudioComponentDescription desc; - zerostruct (desc); - - comp = AudioComponentFindNext (comp, &desc); - - if (comp == nullptr) - break; - - if (AudioComponentGetDescription (comp, &desc) != noErr) - continue; - - if (desc.componentType == kAudioUnitType_MusicDevice - || desc.componentType == kAudioUnitType_MusicEffect - || desc.componentType == kAudioUnitType_Effect - || desc.componentType == kAudioUnitType_Generator - || desc.componentType == kAudioUnitType_Panner - || desc.componentType == kAudioUnitType_Mixer - || desc.componentType == kAudioUnitType_MIDIProcessor) - { - if (allowPluginsWhichRequireAsynchronousInstantiation || ! AudioUnitFormatHelpers::isPluginAUv3 (desc)) - result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); - } - } - - return result; -} - -bool AudioUnitPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) -{ - AudioComponentDescription desc; - String name, version, manufacturer; - - if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer)) - return AudioComponentFindNext (nullptr, &desc) != nullptr; - - auto f = File::createFileWithoutCheckingPath (fileOrIdentifier); - - return (f.hasFileExtension (".component") || f.hasFileExtension (".appex")) - && f.isDirectory(); -} - -String AudioUnitPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) -{ - AudioComponentDescription desc; - String name, version, manufacturer; - AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer); - - if (name.isEmpty()) - name = fileOrIdentifier; - - return name; -} - -bool AudioUnitPluginFormat::pluginNeedsRescanning (const PluginDescription& desc) -{ - AudioComponentDescription newDesc; - String name, version, manufacturer; - - return ! (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, newDesc, - name, version, manufacturer) - && version == desc.version); -} - -bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) -{ - if (desc.fileOrIdentifier.startsWithIgnoreCase (AudioUnitFormatHelpers::auIdentifierPrefix)) - return fileMightContainThisPluginType (desc.fileOrIdentifier); - - return File (desc.fileOrIdentifier).exists(); -} - -FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() -{ - return {}; + createAudioUnitPluginInstance (*this, desc, rate, blockSize, callback); } } // namespace juce diff --git a/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.h b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.h new file mode 100644 index 0000000000..66dc02259c --- /dev/null +++ b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN + +//============================================================================== +/** + Implements a plugin format manager for AudioUnits. + + @tags{Audio} +*/ +class JUCE_API AudioUnitPluginFormatHeadless : public AudioPluginFormat +{ +public: + //============================================================================== + AudioUnitPluginFormatHeadless() = default; + + //============================================================================== + static String getFormatName() { return "AudioUnit"; } + String getName() const override { return getFormatName(); } + bool canScanForPlugins() const override { return true; } + bool isTrivialToScan() const override { return false; } + + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; + FileSearchPath getDefaultLocationsToSearch() override; + void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override; + +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, PluginCreationCallback) override; + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormatHeadless) +}; + +#endif + +} diff --git a/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.mm b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.mm new file mode 100644 index 0000000000..68ef5da102 --- /dev/null +++ b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatHeadless.mm @@ -0,0 +1,204 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + +#include + +namespace juce +{ + +void AudioUnitPluginFormatHeadless::findAllTypesForFile (OwnedArray& results, + const String& fileOrIdentifier) +{ + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + PluginDescription desc; + desc.fileOrIdentifier = fileOrIdentifier; + desc.uniqueId = desc.deprecatedUid = 0; + + if (MessageManager::getInstance()->isThisTheMessageThread() + && requiresUnblockedMessageThreadDuringCreation (desc)) + return; + + try + { + auto createdInstance = createInstanceFromDescription (desc, 44100.0, 512); + + if (auto auInstance = dynamic_cast (createdInstance.get())) + results.add (new PluginDescription (auInstance->getPluginDescription())); + } + catch (...) + { + // crashed while loading... + } +} + +void AudioUnitPluginFormatHeadless::createPluginInstance (const PluginDescription& desc, + double rate, + int blockSize, + PluginCreationCallback callback) +{ + createAudioUnitPluginInstance (*this, desc, rate, blockSize, callback); +} + +void AudioUnitPluginFormatHeadless::createARAFactoryAsync (const PluginDescription& desc, ARAFactoryCreationCallback callback) +{ + auto auComponentResult = getAudioComponent (*this, desc); + + if (! auComponentResult.isValid()) + { + callback ({ {}, "Failed to create AudioComponent for " + desc.descriptiveName }); + return; + } + + getOrCreateARAAudioUnit (auComponentResult.component, [cb = std::move (callback)] (auto dylibKeepAliveAudioUnit) + { + cb ([&]() -> ARAFactoryResult + { + if (dylibKeepAliveAudioUnit != nullptr) + return { ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }, "" }; + + return { {}, "Failed to create ARAFactory from the provided AudioUnit" }; + }()); + }); +} + +bool AudioUnitPluginFormatHeadless::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const +{ + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; + + if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer) + || AudioUnitFormatHelpers::getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer)) + { + if (AudioComponent auComp = AudioComponentFindNext (nullptr, &componentDesc)) + { + if (AudioComponentGetDescription (auComp, &componentDesc) == noErr) + return AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); + } + } + + return false; +} + +StringArray AudioUnitPluginFormatHeadless::searchPathsForPlugins (const FileSearchPath&, bool /*recursive*/, bool allowPluginsWhichRequireAsynchronousInstantiation) +{ + StringArray result; + AudioComponent comp = nullptr; + + for (;;) + { + AudioComponentDescription desc; + zerostruct (desc); + + comp = AudioComponentFindNext (comp, &desc); + + if (comp == nullptr) + break; + + if (AudioComponentGetDescription (comp, &desc) != noErr) + continue; + + if (desc.componentType == kAudioUnitType_MusicDevice + || desc.componentType == kAudioUnitType_MusicEffect + || desc.componentType == kAudioUnitType_Effect + || desc.componentType == kAudioUnitType_Generator + || desc.componentType == kAudioUnitType_Panner + || desc.componentType == kAudioUnitType_Mixer + || desc.componentType == kAudioUnitType_MIDIProcessor) + { + if (allowPluginsWhichRequireAsynchronousInstantiation || ! AudioUnitFormatHelpers::isPluginAUv3 (desc)) + result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); + } + } + + return result; +} + +bool AudioUnitPluginFormatHeadless::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + AudioComponentDescription desc; + String name, version, manufacturer; + + if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer)) + return AudioComponentFindNext (nullptr, &desc) != nullptr; + + auto f = File::createFileWithoutCheckingPath (fileOrIdentifier); + + return (f.hasFileExtension (".component") || f.hasFileExtension (".appex")) + && f.isDirectory(); +} + +String AudioUnitPluginFormatHeadless::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + AudioComponentDescription desc; + String name, version, manufacturer; + AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer); + + if (name.isEmpty()) + name = fileOrIdentifier; + + return name; +} + +bool AudioUnitPluginFormatHeadless::pluginNeedsRescanning (const PluginDescription& desc) +{ + AudioComponentDescription newDesc; + String name, version, manufacturer; + + return ! (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, newDesc, + name, version, manufacturer) + && version == desc.version); +} + +bool AudioUnitPluginFormatHeadless::doesPluginStillExist (const PluginDescription& desc) +{ + if (desc.fileOrIdentifier.startsWithIgnoreCase (AudioUnitFormatHelpers::auIdentifierPrefix)) + return fileMightContainThisPluginType (desc.fileOrIdentifier); + + return File (desc.fileOrIdentifier).exists(); +} + +FileSearchPath AudioUnitPluginFormatHeadless::getDefaultLocationsToSearch() +{ + return {}; +} + +} // namespace juce + +#endif diff --git a/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatImpl.h b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatImpl.h new file mode 100644 index 0000000000..3ddaf01f27 --- /dev/null +++ b/modules/juce_audio_processors_headless/format_types/juce_AudioUnitPluginFormatImpl.h @@ -0,0 +1,2591 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + +#if JUCE_MAC +#include +#include +#include + +#if JUCE_PLUGINHOST_ARA + #include +#endif + +#endif + +#include + +#include + +#include +#include +#include +#include + +namespace juce +{ + +// Change this to disable logging of various activities +#ifndef AU_LOGGING + #define AU_LOGGING 1 +#endif + +namespace AudioUnitFormatHelpers +{ + #if JUCE_DEBUG + static ThreadLocalValue insideCallback; + #endif + + static String osTypeToString (OSType type) noexcept + { + const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), + (juce_wchar) ((type >> 16) & 0xff), + (juce_wchar) ((type >> 8) & 0xff), + (juce_wchar) (type & 0xff) }; + return String (s, 4); + } + + static OSType stringToOSType (String s) + { + if (s.trim().length() >= 4) // (to avoid trimming leading spaces) + s = s.trim(); + + s += " "; + + return (((OSType) (unsigned char) s[0]) << 24) + | (((OSType) (unsigned char) s[1]) << 16) + | (((OSType) (unsigned char) s[2]) << 8) + | ((OSType) (unsigned char) s[3]); + } + + static const char* auIdentifierPrefix = "AudioUnit:"; + + static String createPluginIdentifier (const AudioComponentDescription& desc) + { + String s (auIdentifierPrefix); + + if (desc.componentType == kAudioUnitType_MusicDevice) + s << "Synths/"; + else if (desc.componentType == kAudioUnitType_MusicEffect + || desc.componentType == kAudioUnitType_Effect) + s << "Effects/"; + else if (desc.componentType == kAudioUnitType_Generator) + s << "Generators/"; + else if (desc.componentType == kAudioUnitType_Panner) + s << "Panners/"; + else if (desc.componentType == kAudioUnitType_Mixer) + s << "Mixers/"; + else if (desc.componentType == kAudioUnitType_MIDIProcessor) + s << "MidiEffects/"; + + s << osTypeToString (desc.componentType) << "," + << osTypeToString (desc.componentSubType) << "," + << osTypeToString (desc.componentManufacturer); + + return s; + } + + static void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) + { + CFObjectHolder cfName; + + if (AudioComponentCopyName (comp, &cfName.object) == noErr) + name = String::fromCFString (cfName.object); + + if (name.containsChar (':')) + { + manufacturer = name.upToFirstOccurrenceOf (":", false, false).trim(); + name = name.fromFirstOccurrenceOf (":", false, false).trim(); + } + + if (name.isEmpty()) + name = ""; + } + + static bool getComponentDescFromIdentifier (const String& fileOrIdentifier, AudioComponentDescription& desc, + String& name, String& version, String& manufacturer) + { + if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) + { + String s (fileOrIdentifier.substring (jmax (fileOrIdentifier.lastIndexOfChar (':'), + fileOrIdentifier.lastIndexOfChar ('/')) + 1)); + + StringArray tokens; + tokens.addTokens (s, ",", StringRef()); + tokens.removeEmptyStrings(); + + if (tokens.size() == 3) + { + zerostruct (desc); + desc.componentType = stringToOSType (tokens[0]); + desc.componentSubType = stringToOSType (tokens[1]); + desc.componentManufacturer = stringToOSType (tokens[2]); + + if (AudioComponent comp = AudioComponentFindNext (nullptr, &desc)) + { + getNameAndManufacturer (comp, name, manufacturer); + + if (manufacturer.isEmpty()) + manufacturer = tokens[2]; + + if (version.isEmpty()) + { + UInt32 versionNum; + + if (AudioComponentGetVersion (comp, &versionNum) == noErr) + { + version << (int) (versionNum >> 16) << "." + << (int) ((versionNum >> 8) & 0xff) << "." + << (int) (versionNum & 0xff); + } + } + + return true; + } + } + } + + return false; + } + + static bool getComponentDescFromFile ([[maybe_unused]] const String& fileOrIdentifier, + [[maybe_unused]] AudioComponentDescription& desc, + [[maybe_unused]] String& name, + [[maybe_unused]] String& version, + [[maybe_unused]] String& manufacturer) + { + zerostruct (desc); + + #if JUCE_IOS + return false; + #else + const File file (fileOrIdentifier); + if (! file.hasFileExtension (".component") && ! file.hasFileExtension (".appex")) + return false; + + const char* const utf8 = fileOrIdentifier.toUTF8(); + + if (auto url = CFUniquePtr (CFURLCreateFromFileSystemRepresentation (nullptr, (const UInt8*) utf8, + (CFIndex) strlen (utf8), file.isDirectory()))) + { + if (auto bundleRef = CFUniquePtr (CFBundleCreate (kCFAllocatorDefault, url.get()))) + { + CFTypeRef bundleName = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleName")); + + if (bundleName != nullptr && CFGetTypeID (bundleName) == CFStringGetTypeID()) + name = String::fromCFString ((CFStringRef) bundleName); + + if (name.isEmpty()) + name = file.getFileNameWithoutExtension(); + + CFTypeRef versionString = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleVersion")); + + if (versionString != nullptr && CFGetTypeID (versionString) == CFStringGetTypeID()) + version = String::fromCFString ((CFStringRef) versionString); + + CFTypeRef manuString = CFBundleGetValueForInfoDictionaryKey (bundleRef.get(), CFSTR ("CFBundleGetInfoString")); + + if (manuString != nullptr && CFGetTypeID (manuString) == CFStringGetTypeID()) + manufacturer = String::fromCFString ((CFStringRef) manuString); + + NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; + + NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; + NSDictionary* dict = [audioComponents objectAtIndex: 0]; + + desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); + desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); + desc.componentSubType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"subtype"])); + + [bundle release]; + } + } + + return desc.componentType != 0 && desc.componentSubType != 0; + #endif + } + + static const char* getCategory (OSType type) noexcept + { + switch (type) + { + case kAudioUnitType_Effect: + case kAudioUnitType_MusicEffect: return "Effect"; + case kAudioUnitType_MusicDevice: return "Synth"; + case kAudioUnitType_Generator: return "Generator"; + case kAudioUnitType_Panner: return "Panner"; + case kAudioUnitType_Mixer: return "Mixer"; + case kAudioUnitType_MIDIProcessor: return "MidiEffects"; + default: break; + } + + return nullptr; + } + + template + static std::optional tryGetProperty (AudioUnit inUnit, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement) + { + PropertyType property{}; + auto size = (UInt32) sizeof (property); + + if (AudioUnitGetProperty (inUnit, inID, inScope, inElement, &property, &size) != noErr) + return {}; + + // This may lead to a memory corruption as the plugin has written more data than was provided! + jassert (size <= sizeof (PropertyType)); + + return property; + } + + template + class PropertyData + { + public: + explicit PropertyData (MemoryBlock&& inData) : data (std::move (inData)) {} + + Type& get() { return *static_cast< Type*> (data.getData()); } + const Type& get() const { return *static_cast (data.getData()); } + UInt32 size() const { return (UInt32) data.getSize(); } + + private: + MemoryBlock data; + }; + + template + static std::optional> tryGetPropertyData (AudioUnit inUnit, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement) + { + UInt32 size{}; + + if (AudioUnitGetPropertyInfo (inUnit, inID, inScope, inElement, &size, nullptr) != noErr) + return {}; + + constexpr auto minimumSize = sizeof (Type); + size = jmax ((UInt32) minimumSize, size); + + MemoryBlock data; + data.setSize (size, true); + + if (AudioUnitGetProperty (inUnit, inID, inScope, inElement, data.getData(), &size) != noErr) + return {}; + + // This may lead to a memory corruption as the plugin has written more data than was provided! + jassert (size <= data.getSize()); + + return PropertyData { std::move (data) }; + } + + template + static bool trySetProperty (AudioUnit inUnit, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + const Type& newValue) + { + return AudioUnitSetProperty (inUnit, inID, inScope, inElement, &newValue, sizeof (Type)); + } + + template + static bool trySetProperty (AudioUnit inUnit, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + const PropertyData& newValue) + { + return AudioUnitSetProperty (inUnit, inID, inScope, inElement, newValue.get(), newValue.size()); + } + + static UInt32 getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept + { + const auto count = tryGetProperty (comp, kAudioUnitProperty_ElementCount, scope, 0); + jassert (count); + return *count; + } + + /* The plugin may expect its channels in a particular order, reported to the host + using kAudioUnitProperty_AudioChannelLayout. + This remapper allows us to respect the channel order requested by the plugin, + while still using the JUCE channel ordering for the AudioBuffer argument + of AudioProcessor::processBlock. + */ + class SingleDirectionChannelMapping + { + public: + void setUpMapping (AudioUnit comp, bool isInput) + { + channels.clear(); + busOffset.clear(); + + const auto scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const auto n = getElementCount (comp, scope); + + for (UInt32 busIndex = 0; busIndex < n; ++busIndex) + { + std::vector busMap; + + if (const auto layout = tryGetPropertyData (comp, kAudioUnitProperty_AudioChannelLayout, scope, busIndex)) + { + const auto juceChannelOrder = CoreAudioLayouts::fromCoreAudio (layout->get()); + const auto auChannelOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (layout->get()); + + for (auto juceChannelIndex = 0; juceChannelIndex < juceChannelOrder.size(); ++juceChannelIndex) + busMap.push_back ((size_t) auChannelOrder.indexOf (juceChannelOrder.getTypeOfChannel (juceChannelIndex))); + } + + busOffset.push_back (busMap.empty() ? unknownChannelCount : channels.size()); + channels.insert (channels.end(), busMap.begin(), busMap.end()); + } + } + + size_t getAuIndexForJuceChannel (size_t bus, size_t channel) const noexcept + { + const auto baseOffset = busOffset[bus]; + return baseOffset != unknownChannelCount ? channels[baseOffset + channel] + : channel; + } + + private: + static constexpr size_t unknownChannelCount = std::numeric_limits::max(); + + /* The index (in the channels vector) of the first channel in each bus. + e.g the index of the first channel in the second bus can be found at busOffset[1]. + It's possible for a bus not to report its channel layout, and in this case a value + of unknownChannelCount will be stored for that bus. + */ + std::vector busOffset; + + /* The index in a collection of JUCE channels of the AU channel with a matching channel + type. The mappings for all buses are stored in bus order. To find the start offset for a + particular bus, use the busOffset vector. + e.g. the index of the AU channel with the same type as the fifth channel of the third bus + in JUCE layout is found at channels[busOffset[2] + 4]. + If the busOffset for the bus is unknownChannelCount, then assume there is no mapping + between JUCE/AU channel layouts. + */ + std::vector channels; + }; + + static bool isPluginAUv3 (const AudioComponentDescription& desc) + { + return (desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0; + } +} + +static inline bool hasARAExtension ([[maybe_unused]] AudioUnit audioUnit) +{ + #if JUCE_PLUGINHOST_ARA + UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); + Boolean isWriteable = FALSE; + + OSStatus status = AudioUnitGetPropertyInfo (audioUnit, + ARA::kAudioUnitProperty_ARAFactory, + kAudioUnitScope_Global, + 0, + &propertySize, + &isWriteable); + + if ((status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable) + return true; + #endif + + return false; +} + +struct AudioUnitDeleter +{ + void operator() (AudioUnit au) const { AudioComponentInstanceDispose (au); } +}; + +using AudioUnitUniquePtr = std::unique_ptr, AudioUnitDeleter>; +using AudioUnitSharedPtr = std::shared_ptr>; +using AudioUnitWeakPtr = std::weak_ptr>; + +static std::shared_ptr getARAFactory ([[maybe_unused]] AudioUnitSharedPtr audioUnit) +{ + #if JUCE_PLUGINHOST_ARA + jassert (audioUnit != nullptr); + + UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); + ARA::ARAAudioUnitFactory audioUnitFactory { ARA::kARAAudioUnitMagic, nullptr }; + + if (hasARAExtension (audioUnit.get())) + { + OSStatus status = AudioUnitGetProperty (audioUnit.get(), + ARA::kAudioUnitProperty_ARAFactory, + kAudioUnitScope_Global, + 0, + &audioUnitFactory, + &propertySize); + + if ((status == noErr) + && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) + && (audioUnitFactory.inOutMagicNumber == ARA::kARAAudioUnitMagic)) + { + jassert (audioUnitFactory.outFactory != nullptr); + return getOrCreateARAFactory (audioUnitFactory.outFactory, + [owningAuPtr = std::move (audioUnit)]() {}); + } + } + #endif + + return {}; +} + +struct VersionedAudioComponent +{ + AudioComponent audioComponent = nullptr; + bool isAUv3 = false; + + bool operator< (const VersionedAudioComponent& other) const { return audioComponent < other.audioComponent; } +}; + +using AudioUnitCreationCallback = std::function; + +static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback) +{ + if (versionedComponent.isAUv3) + { + AudioComponentInstantiate (versionedComponent.audioComponent, + kAudioComponentInstantiation_LoadOutOfProcess, + ^(AudioComponentInstance audioUnit, OSStatus err) + { + callback (audioUnit, err); + }); + + return; + } + + AudioComponentInstance audioUnit; + auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit); + callback (err != noErr ? nullptr : audioUnit, err); +} + +struct AudioComponentResult +{ + explicit AudioComponentResult (String error) : errorMessage (std::move (error)) {} + + explicit AudioComponentResult (VersionedAudioComponent auComponent) : component (std::move (auComponent)) {} + + bool isValid() const { return component.audioComponent != nullptr; } + + VersionedAudioComponent component; + String errorMessage; +}; + +static AudioComponentResult getAudioComponent (AudioUnitPluginFormatHeadless& format, const PluginDescription& desc) +{ + using namespace AudioUnitFormatHelpers; + + if (! format.fileMightContainThisPluginType (desc.fileOrIdentifier)) + return AudioComponentResult { NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in") }; + + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; + AudioComponent auComponent; + String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description"); + + if (! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer) + && ! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) + { + return AudioComponentResult { errMessage }; + } + + if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr) + { + return AudioComponentResult { errMessage }; + } + + if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr) + { + return AudioComponentResult { errMessage }; + } + + const bool isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); + + return AudioComponentResult { { auComponent, isAUv3 } }; +} + +static void getOrCreateARAAudioUnit (VersionedAudioComponent auComponent, std::function callback) +{ + static std::map audioUnitARACache; + + if (auto audioUnit = audioUnitARACache[auComponent].lock()) + { + callback (std::move (audioUnit)); + return; + } + + createAudioUnit (auComponent, [auComponent, cb = std::move (callback)] (AudioUnit audioUnit, OSStatus err) + { + cb ([auComponent, audioUnit, err]() -> AudioUnitSharedPtr + { + if (err != noErr) + return nullptr; + + AudioUnitSharedPtr auPtr { AudioUnitUniquePtr { audioUnit } }; + audioUnitARACache[auComponent] = auPtr; + return auPtr; + }()); + }); +} + +//============================================================================== +class AudioUnitPluginWindowCocoa; + +//============================================================================== +class AudioUnitPluginInstanceHeadless : public AudioPluginInstance +{ +public: + struct AUInstanceParameter final : public Parameter + { + AUInstanceParameter (AudioUnitPluginInstanceHeadless& parent, + UInt32 parameterID, + const String& parameterName, + AudioUnitParameterValue minParameterValue, + AudioUnitParameterValue maxParameterValue, + AudioUnitParameterValue defaultParameterValue, + bool parameterIsAutomatable, + bool parameterIsDiscrete, + int numParameterSteps, + bool isBoolean, + const String& label, + bool parameterValuesHaveStrings) + : pluginInstance (parent), + paramID (parameterID), + name (parameterName), + minValue (minParameterValue), + maxValue (maxParameterValue), + range (maxValue - minValue), + automatable (parameterIsAutomatable), + discrete (parameterIsDiscrete), + numSteps (numParameterSteps), + valuesHaveStrings (parameterValuesHaveStrings), + isSwitch (isBoolean), + valueLabel (label), + defaultValue (normaliseParamValue (defaultParameterValue)) + { + if (getNumSteps() <= maxStringsToCache) + auValueStrings = getDiscreteValueStrings(); + } + + float getValue() const override + { + const ScopedLock sl (pluginInstance.lock); + + AudioUnitParameterValue value = 0; + + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitGetParameter (au, paramID, kAudioUnitScope_Global, 0, &value); + value = normaliseParamValue (value); + } + + return value; + } + + void setValue (float newValue) override + { + const ScopedLock sl (pluginInstance.lock); + + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitSetParameter (au, paramID, kAudioUnitScope_Global, + 0, scaleParamValue (newValue), 0); + + sendParameterChangeEvent(); + } + } + + float getDefaultValue() const override + { + return defaultValue; + } + + String getName (int /*maximumStringLength*/) const override { return name; } + String getLabel() const override { return valueLabel; } + + String getText (float value, int maximumLength) const override + { + if (! auValueStrings.isEmpty()) + { + auto index = roundToInt (jlimit (0.0f, 1.0f, value) * (float) (auValueStrings.size() - 1)); + return auValueStrings[index]; + } + + auto scaledValue = scaleParamValue (value); + + if (valuesHaveStrings) + { + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitParameterStringFromValue stringValue; + stringValue.inParamID = paramID; + stringValue.inValue = &scaledValue; + stringValue.outString = nullptr; + + UInt32 propertySize = sizeof (stringValue); + + auto err = AudioUnitGetProperty (au, + kAudioUnitProperty_ParameterStringFromValue, + kAudioUnitScope_Global, + 0, + &stringValue, + &propertySize); + + if (! err && stringValue.outString != nullptr) + { + const CFUniquePtr ownedString { stringValue.outString }; + return String::fromCFString (stringValue.outString).substring (0, maximumLength); + } + } + } + + return Parameter::getText (scaledValue, maximumLength); + } + + float getValueForText (const String& text) const override + { + if (! auValueStrings.isEmpty()) + { + auto index = auValueStrings.indexOf (text); + + if (index != -1) + return ((float) index) / (float) (auValueStrings.size() - 1); + } + + if (valuesHaveStrings) + { + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitParameterValueFromString pvfs; + pvfs.inParamID = paramID; + CFUniquePtr cfString (text.toCFString()); + pvfs.inString = cfString.get(); + UInt32 propertySize = sizeof (pvfs); + + auto err = AudioUnitGetProperty (au, + kAudioUnitProperty_ParameterValueFromString, + kAudioUnitScope_Global, + 0, + &pvfs, + &propertySize); + + if (! err) + return normaliseParamValue (pvfs.outValue); + } + } + + return Parameter::getValueForText (text); + } + + bool isAutomatable() const override { return automatable; } + bool isDiscrete() const override { return discrete; } + bool isBoolean() const override { return isSwitch; } + int getNumSteps() const override { return numSteps; } + + StringArray getAllValueStrings() const override + { + if (! auValueStrings.isEmpty()) + return auValueStrings; + + // If this is hit, the returned array of strings is going to be very large! + // Consider alternative approaches - perhaps you could fetch value strings + // one-at-a-time to avoid needing to have all the strings in memory simultaneously. + jassert (getNumSteps() <= maxStringsToCache); + + return getDiscreteValueStrings(); + } + + String getParameterID() const override + { + return String (paramID); + } + + void sendParameterChangeEvent() + { + #if JUCE_MAC + jassert (pluginInstance.audioUnit != nullptr); + + AudioUnitEvent ev; + ev.mEventType = kAudioUnitEvent_ParameterValueChange; + ev.mArgument.mParameter.mAudioUnit = pluginInstance.audioUnit; + ev.mArgument.mParameter.mParameterID = paramID; + ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; + ev.mArgument.mParameter.mElement = 0; + + AUEventListenerNotify (pluginInstance.eventListenerRef, nullptr, &ev); + #endif + } + + float normaliseParamValue (float scaledValue) const noexcept + { + return (scaledValue - minValue) / range; + } + + float scaleParamValue (float normalisedValue) const noexcept + { + return minValue + (range * normalisedValue); + } + + UInt32 getRawParamID() const { return paramID; } + + void setName (String&& newName) { name = std::move (newName); } + void setLabel (String&& newLabel) { valueLabel = std::move (newLabel); } + + private: + static constexpr auto maxStringsToCache = 10'000; + + StringArray getDiscreteValueStrings() const + { + if (! isDiscrete()) + return {}; + + StringArray result; + + const auto maxIndex = getNumSteps() - 1; + + for (int i = 0; i < getNumSteps(); ++i) + result.add (getText ((float) i / (float) maxIndex, 1024)); + + return result; + } + + AudioUnitPluginInstanceHeadless& pluginInstance; + const UInt32 paramID; + String name; + const AudioUnitParameterValue minValue, maxValue, range; + const bool automatable, discrete; + const int numSteps; + const bool valuesHaveStrings, isSwitch; + String valueLabel; + const AudioUnitParameterValue defaultValue; + StringArray auValueStrings; + }; + + explicit AudioUnitPluginInstanceHeadless (AudioComponentInstance au) + : AudioPluginInstance (getBusesProperties (au)), + auComponent (AudioComponentInstanceGetComponent (au)), + audioUnit (au), + #if JUCE_MAC + eventListenerRef (nullptr), + #endif + midiConcatenator (2048) + { + using namespace AudioUnitFormatHelpers; + + AudioComponentGetDescription (auComponent, &componentDesc); + + isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); + + wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice + || componentDesc.componentType == kAudioUnitType_MusicEffect + || componentDesc.componentType == kAudioUnitType_MIDIProcessor; + + isMidiEffectPlugin = (componentDesc.componentType == kAudioUnitType_MIDIProcessor); + AudioComponentDescription ignore; + getComponentDescFromIdentifier (createPluginIdentifier (componentDesc), ignore, pluginName, version, manufacturer); + updateSupportedLayouts(); + } + + ~AudioUnitPluginInstanceHeadless() override + { + const ScopedLock sl (lock); + + #if JUCE_DEBUG + // this indicates that some kind of recursive call is getting triggered that's + // deleting this plugin while it's still under construction. + jassert (AudioUnitFormatHelpers::insideCallback.get() == 0); + #endif + + if (audioUnit != nullptr) + { + struct AUDeleter final : public CallbackMessage + { + AUDeleter (AudioUnitPluginInstanceHeadless& inInstance, WaitableEvent& inEvent) + : auInstance (inInstance), completionSignal (inEvent) + {} + + void messageCallback() override + { + auInstance.cleanup(); + completionSignal.signal(); + } + + AudioUnitPluginInstanceHeadless& auInstance; + WaitableEvent& completionSignal; + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cleanup(); + } + else + { + WaitableEvent completionEvent; + (new AUDeleter (*this, completionEvent))->post(); + completionEvent.wait(); + } + } + } + + // called from the destructor above + void cleanup() + { + #if JUCE_MAC + disposeEventListener(); + #endif + + if (prepared) + releaseResources(); + + AudioComponentInstanceDispose (audioUnit); + audioUnit = nullptr; + } + + bool initialise (double rate, int blockSize) + { + producesMidiMessages = canProduceMidiOutput(); + setRateAndBufferSizeDetails (rate, blockSize); + setLatencySamples (0); + refreshParameterList(); + setPluginCallbacks(); + + #if JUCE_MAC + createEventListener(); + #endif + + return true; + } + + //============================================================================== + bool canAddBus (bool isInput) const override { return isBusCountWritable (isInput); } + bool canRemoveBus (bool isInput) const override { return isBusCountWritable (isInput); } + + bool canApplyBusCountChange (bool isInput, bool isAdding, BusProperties& outProperties) override + { + auto currentCount = (UInt32) getBusCount (isInput); + auto newCount = (UInt32) ((int) currentCount + (isAdding ? 1 : -1)); + AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + + if (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)) == noErr) + { + getBusProperties (isInput, currentCount, outProperties.busName, outProperties.defaultLayout); + outProperties.isActivatedByDefault = true; + updateSupportedLayouts(); + + return true; + } + + return false; + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + if (layouts == getBusesLayout()) + return true; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + auto& requestedLayouts = (isInput ? layouts.inputBuses : layouts.outputBuses); + auto& oppositeRequestedLayouts = (isInput ? layouts.outputBuses : layouts.inputBuses); + auto& supported = (isInput ? supportedInLayouts : supportedOutLayouts); + const int n = getBusCount (isInput); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + auto& requested = requestedLayouts.getReference (busIdx); + const int oppositeBusIdx = jmin (getBusCount (! isInput) - 1, busIdx); + const bool hasOppositeBus = (oppositeBusIdx >= 0); + auto oppositeRequested = (hasOppositeBus ? oppositeRequestedLayouts.getReference (oppositeBusIdx) : AudioChannelSet()); + auto& possible = supported.getReference (busIdx); + + if (requested.isDisabled()) + return false; + + if (possible.size() > 0 && ! possible.contains (requested)) + return false; + + int i; + for (i = 0; i < numChannelInfos; ++i) + { + auto& info = channelInfos[i]; + auto& thisChannels = (isInput ? info.inChannels : info.outChannels); + auto& opChannels = (isInput ? info.outChannels : info.inChannels); + + // this bus + if (thisChannels == 0) continue; + else if (thisChannels > 0 && requested.size() != thisChannels) continue; + else if (thisChannels < -2 && requested.size() > (thisChannels * -1)) continue; + + // opposite bus + if (opChannels == 0 && hasOppositeBus) continue; + else if (opChannels > 0 && oppositeRequested.size() != opChannels) continue; + else if (opChannels < -2 && oppositeRequested.size() > (opChannels * -1)) continue; + + // both buses + if (thisChannels == -2 && opChannels == -2) continue; + if (thisChannels == -1 && opChannels == -1) + { + int numOppositeBuses = getBusCount (! isInput); + int j; + + for (j = 0; j < numOppositeBuses; ++j) + if (requested.size() != oppositeRequestedLayouts.getReference (j).size()) + break; + + if (j < numOppositeBuses) + continue; + } + + break; + } + + if (i >= numChannelInfos) + return false; + } + } + + return true; + } + + bool syncBusLayouts (const BusesLayout& layouts, bool isInitialized, bool& layoutHasChanged) const + { + layoutHasChanged = false; + + for (const auto scope : { kAudioUnitScope_Input, kAudioUnitScope_Output }) + { + const auto isInput = scope == kAudioUnitScope_Input; + const auto busCount = (UInt32) getBusCount (isInput); + + if (AudioUnitFormatHelpers::getElementCount (audioUnit, scope) != busCount && isBusCountWritable (isInput)) + { + layoutHasChanged = true; + + if (! AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, busCount)) + jassertfalse; + } + + for (UInt32 busIndex = 0; busIndex < busCount; ++busIndex) + { + const auto& set = layouts.getChannelSet (isInput, (int) busIndex); + const auto currentStream = AudioUnitFormatHelpers::tryGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIndex); + + if (! currentStream.has_value()) + return false; + + const auto actualNumChannels = static_cast (currentStream->mChannelsPerFrame); + const auto requestedNumChannels = set.size(); + + if (actualNumChannels != requestedNumChannels) + { + const auto sampleRate = AudioUnitFormatHelpers::tryGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, busIndex); + jassert (sampleRate.has_value()); + + layoutHasChanged = true; + AudioStreamBasicDescription newStream{}; + newStream.mSampleRate = *sampleRate; + newStream.mFormatID = kAudioFormatLinearPCM; + newStream.mFormatFlags = (int) kAudioFormatFlagsNativeFloatPacked | (int) kAudioFormatFlagIsNonInterleaved | (int) kAudioFormatFlagsNativeEndian; + newStream.mFramesPerPacket = 1; + newStream.mBytesPerPacket = 4; + newStream.mBytesPerFrame = 4; + newStream.mBitsPerChannel = 32; + newStream.mChannelsPerFrame = static_cast (requestedNumChannels); + + if (AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIndex, newStream)) + return false; + } + + if (isInitialized && ! set.isDiscreteLayout()) + { + if (const auto layout = AudioUnitFormatHelpers::tryGetPropertyData (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIndex)) + { + // This is likely a fault with the plugin. Not enough data has been allocated + // to support the AudioChannelLayout being described + jassert (layout->get().mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions + || layout->size() >= offsetof (AudioChannelLayout, mChannelDescriptions) + layout->get().mNumberChannelDescriptions * sizeof (AudioChannelDescription)); + + const auto requestedTag = CoreAudioLayouts::toCoreAudio (set); + const auto actualTag = CoreAudioLayouts::toCoreAudio (CoreAudioLayouts::fromCoreAudio (layout->get())); + + if (actualTag != requestedTag) + { + AudioChannelLayout newLayout{}; + newLayout.mChannelLayoutTag = requestedTag; + + // We'll likely need to fill more data in the newLayout for these tags. + // Please notify the JUCE team if you hit this assertion and provide clear + // instructions regarding the plugin being loaded and any other relevant + // details required to hit this assertion. Preferably if you can recreate + // the assertion using the AudioPluginHost that would be extremely helpful. + + jassert (requestedTag != kAudioChannelLayoutTag_UseChannelBitmap + && requestedTag != kAudioChannelLayoutTag_UseChannelDescriptions); + + if (AudioUnitFormatHelpers::trySetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIndex, newLayout)) + return false; + } + } + } + } + } + + return true; + } + + bool canApplyBusesLayout (const BusesLayout& layouts) const override + { + // You cannot call setBusesLayout when the AudioProcessor is processing. + // Call releaseResources first! + jassert (! prepared); + + bool layoutHasChanged = false; + + if (! syncBusLayouts (layouts, false, layoutHasChanged)) + return false; + + // did anything actually change + if (! layoutHasChanged) + return true; + + // Some plug-ins require the LayoutTag to be set after initialization + const auto success = (AudioUnitInitialize (audioUnit) == noErr) + && syncBusLayouts (layouts, true, layoutHasChanged); + + AudioUnitUninitialize (audioUnit); + + if (! success) + // make sure that the layout is back to its original state + syncBusLayouts (getBusesLayout(), false, layoutHasChanged); + + return success; + } + + //============================================================================== + // AudioPluginInstance methods: + + void fillInPluginDescription (PluginDescription& desc) const override + { + desc.name = pluginName; + desc.descriptiveName = pluginName; + desc.fileOrIdentifier = AudioUnitFormatHelpers::createPluginIdentifier (componentDesc); + desc.uniqueId = desc.deprecatedUid = ((int) componentDesc.componentType) + ^ ((int) componentDesc.componentSubType) + ^ ((int) componentDesc.componentManufacturer); + desc.lastFileModTime = Time(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); + desc.pluginFormatName = "AudioUnit"; + desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); + desc.manufacturerName = manufacturer; + desc.version = version; + desc.numInputChannels = getTotalNumInputChannels(); + desc.numOutputChannels = getTotalNumOutputChannels(); + desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); + + #if JUCE_PLUGINHOST_ARA + desc.hasARAExtension = [&] + { + UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); + Boolean isWriteable = FALSE; + + OSStatus status = AudioUnitGetPropertyInfo (audioUnit, + ARA::kAudioUnitProperty_ARAFactory, + kAudioUnitScope_Global, + 0, + &propertySize, + &isWriteable); + + return (status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable; + }(); + #endif + } + + void getExtensions (ExtensionsVisitor& visitor) const override + { + struct Extensions final : public ExtensionsVisitor::AudioUnitClient + { + explicit Extensions (const AudioUnitPluginInstanceHeadless* instanceIn) : instance (instanceIn) {} + + AudioUnit getAudioUnitHandle() const noexcept override { return instance->audioUnit; } + + const AudioUnitPluginInstanceHeadless* instance = nullptr; + }; + + visitor.visitAudioUnitClient (Extensions { this }); + + #ifdef JUCE_PLUGINHOST_ARA + struct ARAExtensions final : public ExtensionsVisitor::ARAClient + { + explicit ARAExtensions (const AudioUnitPluginInstanceHeadless* instanceIn) : instance (instanceIn) {} + + void createARAFactoryAsync (std::function cb) const override + { + getOrCreateARAAudioUnit ({ instance->auComponent, instance->isAUv3 }, + [origCb = std::move (cb)] (auto dylibKeepAliveAudioUnit) + { + origCb ([&]() -> ARAFactoryWrapper + { + if (dylibKeepAliveAudioUnit != nullptr) + return ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }; + + return ARAFactoryWrapper { nullptr }; + }()); + }); + } + + const AudioUnitPluginInstanceHeadless* instance = nullptr; + }; + + if (hasARAExtension (audioUnit)) + visitor.visitARAClient (ARAExtensions (this)); + #endif + } + + void* getPlatformSpecificData() override { return audioUnit; } + const String getName() const override { return pluginName; } + + double getTailLengthSeconds() const override + { + Float64 tail = 0; + UInt32 tailSize = sizeof (tail); + + if (audioUnit != nullptr) + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_TailTime, kAudioUnitScope_Global, + 0, &tail, &tailSize); + + return tail; + } + + bool acceptsMidi() const override { return wantsMidiMessages; } + bool producesMidi() const override { return producesMidiMessages; } + + AudioUnit getAudioUnitHandle() const { return audioUnit; } + + //============================================================================== + // AudioProcessor methods: + + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override + { + if (audioUnit != nullptr) + { + releaseResources(); + setPluginCallbacks(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getBusCount (isInput); + + for (int i = 0; i < n; ++i) + { + Float64 sampleRate; + UInt32 sampleRateSize = sizeof (sampleRate); + const Float64 sr = newSampleRate; + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); + + if (! approximatelyEqual (sampleRate, sr)) + { + if (isAUv3) // setting kAudioUnitProperty_SampleRate fails on AUv3s + { + AudioStreamBasicDescription stream; + UInt32 dataSize = sizeof (stream); + auto err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, &dataSize); + + if (err == noErr && dataSize == sizeof (stream)) + { + stream.mSampleRate = sr; + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, sizeof (stream)); + } + } + else + { + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sr, sizeof (sr)); + } + } + + if (isInput) + { + AURenderCallbackStruct info; + zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + + info.inputProcRefCon = this; + info.inputProc = renderGetInputCallback; + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, + static_cast (i), &info, sizeof (info)); + } + else + { + outputBufferList.add (new AUBuffer (static_cast (getChannelCountOfBus (false, i)))); + } + } + } + + UInt32 frameSize = (UInt32) estimatedSamplesPerBlock; + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, + &frameSize, sizeof (frameSize)); + + setRateAndBufferSizeDetails ((double) newSampleRate, estimatedSamplesPerBlock); + + zerostruct (timeStamp); + timeStamp.mSampleTime = 0; + timeStamp.mHostTime = mach_absolute_time(); + timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; + + wasPlaying = false; + + resetBuses(); + + bool ignore; + + if (! syncBusLayouts (getBusesLayout(), false, ignore)) + return; + + prepared = [&] + { + if (AudioUnitInitialize (audioUnit) != noErr) + return false; + + if (! haveParameterList) + refreshParameterList(); + + if (! syncBusLayouts (getBusesLayout(), true, ignore)) + { + AudioUnitUninitialize (audioUnit); + return false; + } + + updateLatency(); + return true; + }(); + + inMapping .setUpMapping (audioUnit, true); + outMapping.setUpMapping (audioUnit, false); + + preparedChannels = jmax (getTotalNumInputChannels(), getTotalNumOutputChannels()); + preparedSamples = estimatedSamplesPerBlock; + inputBuffer.setSize (preparedChannels, preparedSamples); + } + } + + void releaseResources() override + { + if (prepared) + { + resetBuses(); + AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); + AudioUnitUninitialize (audioUnit); + + outputBufferList.clear(); + prepared = false; + } + + incomingMidi.clear(); + } + + void resetBuses() + { + for (int i = 0; i < getBusCount (true); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Input, static_cast (i)); + for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast (i)); + } + + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) + { + auto* playhead = getPlayHead(); + const auto position = playhead != nullptr ? playhead->getPosition() : nullopt; + + if (const auto hostTimeNs = position.hasValue() ? position->getHostTimeNs() : nullopt) + { + timeStamp.mHostTime = *hostTimeNs; + timeStamp.mFlags |= kAudioTimeStampHostTimeValid; + } + else + { + timeStamp.mHostTime = 0; + timeStamp.mFlags &= ~kAudioTimeStampHostTimeValid; + } + + // If these are hit, we might allocate in the process block! + jassert (buffer.getNumChannels() <= preparedChannels); + jassert (buffer.getNumSamples() <= preparedSamples); + // Copy the input buffer to guard against the case where a bus has more output channels + // than input channels, so rendering the output for that bus might stamp over the input + // to the following bus. + inputBuffer.makeCopyOf (buffer, true); + + const auto numSamples = buffer.getNumSamples(); + + if (auSupportsBypass) + { + updateBypass (processBlockBypassedCalled); + } + else if (processBlockBypassedCalled) + { + AudioProcessor::processBlockBypassed (buffer, midiMessages); + return; + } + + if (prepared) + { + const auto numOutputBuses = getBusCount (false); + + for (int i = 0; i < numOutputBuses; ++i) + { + if (AUBuffer* buf = outputBufferList[i]) + { + AudioBufferList& abl = *buf; + const auto* bus = getBus (false, i); + const auto channelCount = bus != nullptr ? bus->getNumberOfChannels() : 0; + + for (auto juceChannel = 0; juceChannel < channelCount; ++juceChannel) + { + const auto auChannel = outMapping.getAuIndexForJuceChannel ((size_t) i, (size_t) juceChannel); + abl.mBuffers[auChannel].mNumberChannels = 1; + abl.mBuffers[auChannel].mDataByteSize = (UInt32) ((size_t) numSamples * sizeof (float)); + abl.mBuffers[auChannel].mData = buffer.getWritePointer (bus->getChannelIndexInProcessBlockBuffer (juceChannel)); + } + } + } + + if (wantsMidiMessages) + { + for (const auto metadata : midiMessages) + { + if (metadata.numBytes <= 3) + { + const auto getByteOrZero = [&metadata] (int index) + { + return index < metadata.numBytes ? metadata.data[index] : (uint8) 0; + }; + + MusicDeviceMIDIEvent (audioUnit, + getByteOrZero (0), + getByteOrZero (1), + getByteOrZero (2), + (UInt32) metadata.samplePosition); + } + else + { + MusicDeviceSysEx (audioUnit, metadata.data, (UInt32) metadata.numBytes); + } + } + + midiMessages.clear(); + } + + for (int i = 0; i < numOutputBuses; ++i) + { + AudioUnitRenderActionFlags flags = 0; + + if (AUBuffer* buf = outputBufferList[i]) + AudioUnitRender (audioUnit, &flags, &timeStamp, static_cast (i), (UInt32) numSamples, buf->bufferList.get()); + } + + timeStamp.mSampleTime += numSamples; + } + else + { + // Plugin not working correctly, so just bypass.. + for (int i = getTotalNumOutputChannels(); --i >= 0;) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + if (producesMidiMessages) + { + const ScopedLock sl (midiInLock); + midiMessages.swapWith (incomingMidi); + incomingMidi.clear(); + } + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + processAudio (buffer, midiMessages, false); + } + + void processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + processAudio (buffer, midiMessages, true); + } + + //============================================================================== + AudioProcessorParameter* getBypassParameter() const override { return auSupportsBypass ? bypassParam.get() : nullptr; } + + bool hasEditor() const override { return false; } + AudioProcessorEditor* createEditor() override { return nullptr; } + + static AudioProcessor::BusesProperties getBusesProperties (AudioComponentInstance comp) + { + AudioProcessor::BusesProperties busProperties; + + for (int dir = 0; dir < 2; ++dir) + { + const auto isInput = (dir == 0); + const auto n = AudioUnitFormatHelpers::getElementCount (comp, isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + for (UInt32 i = 0; i < n; ++i) + { + String busName; + AudioChannelSet currentLayout; + + getBusProperties (comp, isInput, i, busName, currentLayout); + jassert (! currentLayout.isDisabled()); + + busProperties.addBus (isInput, busName, currentLayout, true); + } + } + + return busProperties; + } + + //============================================================================== + const String getInputChannelName (int index) const override + { + if (isPositiveAndBelow (index, getTotalNumInputChannels())) + return "Input " + String (index + 1); + + return {}; + } + + const String getOutputChannelName (int index) const override + { + if (isPositiveAndBelow (index, getTotalNumOutputChannels())) + return "Output " + String (index + 1); + + return {}; + } + + bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumInputChannels()); } + bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } + + //============================================================================== + void sendAllParametersChangedEvents() + { + #if JUCE_MAC + jassert (audioUnit != nullptr); + + AudioUnitParameter param; + param.mAudioUnit = audioUnit; + param.mParameterID = kAUParameterListener_AnyParameter; + + AUParameterListenerNotify (nullptr, nullptr, ¶m); + #endif + } + + //============================================================================== + class ScopedFactoryPresets + { + public: + ScopedFactoryPresets (AudioUnit& au) + { + UInt32 sz = sizeof (CFArrayRef); + AudioUnitGetProperty (au, kAudioUnitProperty_FactoryPresets, + kAudioUnitScope_Global, 0, &presets.object, &sz); + } + + CFArrayRef get() const noexcept + { + return presets.object; + } + + private: + CFObjectHolder presets; + }; + + int getNumPrograms() override + { + ScopedFactoryPresets factoryPresets { audioUnit }; + + if (factoryPresets.get() != nullptr) + return (int) CFArrayGetCount (factoryPresets.get()); + + return 0; + } + + int getCurrentProgram() override + { + AUPreset current{}; + UInt32 sz = sizeof (AUPreset); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, 0, ¤t, &sz); + + const CFUniquePtr ownedString { current.presetName }; + return current.presetNumber; + } + + void setCurrentProgram (int newIndex) override + { + ScopedFactoryPresets factoryPresets { audioUnit }; + + if (factoryPresets.get() != nullptr + && newIndex < (int) CFArrayGetCount (factoryPresets.get())) + { + AUPreset current{}; + current.presetNumber = newIndex; + + if (auto* p = static_cast (CFArrayGetValueAtIndex (factoryPresets.get(), newIndex))) + current.presetName = p->presetName; + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, 0, ¤t, sizeof (AUPreset)); + + sendAllParametersChangedEvents(); + } + } + + const String getProgramName (int index) override + { + if (index == -1) + { + AUPreset current{}; + current.presetNumber = -1; + + UInt32 prstsz = sizeof (AUPreset); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, 0, ¤t, &prstsz); + + const CFUniquePtr ownedString { current.presetName }; + return current.presetName != nullptr ? String::fromCFString (current.presetName) : String{}; + } + + ScopedFactoryPresets factoryPresets { audioUnit }; + + if (factoryPresets.get() != nullptr) + { + for (CFIndex i = 0; i < CFArrayGetCount (factoryPresets.get()); ++i) + if (auto* p = static_cast (CFArrayGetValueAtIndex (factoryPresets.get(), i))) + if (p->presetNumber == index) + return String::fromCFString (p->presetName); + } + + return {}; + } + + void changeProgramName (int /*index*/, const String& /*newName*/) override + { + jassertfalse; // xxx not implemented! + } + + //============================================================================== + void updateTrackProperties (const TrackProperties& properties) override + { + if (properties.name.has_value()) + { + CFObjectHolder contextName { properties.name->toCFString() }; + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ContextName, kAudioUnitScope_Global, + 0, &contextName.object, sizeof (contextName.object)); + } + } + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + getCurrentProgramStateInformation (destData); + } + + void getCurrentProgramStateInformation (MemoryBlock& destData) override + { + CFObjectHolder propertyList; + UInt32 sz = sizeof (propertyList.object); + + if (AudioUnitGetProperty (audioUnit, + kAudioUnitProperty_ClassInfo, + kAudioUnitScope_Global, + 0, &propertyList.object, &sz) == noErr) + { + CFUniquePtr stream (CFWriteStreamCreateWithAllocatedBuffers (kCFAllocatorDefault, kCFAllocatorDefault)); + CFWriteStreamOpen (stream.get()); + + const auto bytesWritten = CFPropertyListWrite (propertyList.object, stream.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr); + CFWriteStreamClose (stream.get()); + + CFUniquePtr data ((CFDataRef) CFWriteStreamCopyProperty (stream.get(), kCFStreamPropertyDataWritten)); + + destData.setSize ((size_t) bytesWritten); + destData.copyFrom (CFDataGetBytePtr (data.get()), 0, destData.getSize()); + } + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + setCurrentProgramStateInformation (data, sizeInBytes); + } + + void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override + { + CFUniquePtr stream (CFReadStreamCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, + sizeInBytes, kCFAllocatorNull)); + CFReadStreamOpen (stream.get()); + + CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0; + CFObjectHolder propertyList { CFPropertyListCreateWithStream (kCFAllocatorDefault, + stream.get(), + 0, + kCFPropertyListImmutable, + &format, + nullptr) }; + + if (propertyList.object != nullptr) + { + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, + 0, &propertyList.object, sizeof (propertyList.object)); + + sendAllParametersChangedEvents(); + } + } + + void refreshParameterList() override + { + paramIDToParameter.clear(); + AudioProcessorParameterGroup newParameterTree; + + if (audioUnit != nullptr) + { + UInt32 paramListSize = 0; + auto err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, + 0, ¶mListSize, nullptr); + + haveParameterList = (paramListSize > 0 && err == noErr); + + if (! haveParameterList) + return; + + if (paramListSize > 0) + { + const size_t numParams = paramListSize / sizeof (int); + + std::vector ids (numParams, 0); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, + 0, ids.data(), ¶mListSize); + + std::map groupIDMap; + + for (size_t i = 0; i < numParams; ++i) + { + const ScopedAudioUnitParameterInfo info { audioUnit, ids[i] }; + + if (! info.isValid()) + continue; + + const auto paramName = getParamName (info.get()); + const auto label = getParamLabel (info.get()); + const auto isDiscrete = (info.get().unit == kAudioUnitParameterUnit_Indexed + || info.get().unit == kAudioUnitParameterUnit_Boolean); + const auto isBoolean = info.get().unit == kAudioUnitParameterUnit_Boolean; + + auto parameter = std::make_unique (*this, + ids[i], + paramName, + info.get().minValue, + info.get().maxValue, + info.get().defaultValue, + (info.get().flags & kAudioUnitParameterFlag_NonRealTime) == 0, + isDiscrete, + isDiscrete ? (int) (info.get().maxValue - info.get().minValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), + isBoolean, + label, + (info.get().flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0); + + paramIDToParameter.emplace (ids[i], parameter.get()); + + if (info.get().flags & kAudioUnitParameterFlag_HasClump) + { + auto groupInfo = groupIDMap.find (info.get().clumpID); + + if (groupInfo == groupIDMap.end()) + { + const auto clumpName = [this, &info] + { + AudioUnitParameterNameInfo clumpNameInfo; + UInt32 clumpSz = sizeof (clumpNameInfo); + zerostruct (clumpNameInfo); + clumpNameInfo.inID = info.get().clumpID; + clumpNameInfo.inDesiredLength = (SInt32) 256; + + if (AudioUnitGetProperty (audioUnit, + kAudioUnitProperty_ParameterClumpName, + kAudioUnitScope_Global, + 0, + &clumpNameInfo, + &clumpSz) == noErr) + { + const CFUniquePtr ownedString { clumpNameInfo.outName }; + return String::fromCFString (clumpNameInfo.outName); + } + + return String (info.get().clumpID); + }(); + + auto group = std::make_unique (String (info.get().clumpID), + clumpName, String()); + group->addChild (std::move (parameter)); + groupIDMap[info.get().clumpID] = group.get(); + newParameterTree.addChild (std::move (group)); + } + else + { + groupInfo->second->addChild (std::move (parameter)); + } + } + else + { + newParameterTree.addChild (std::move (parameter)); + } + } + } + } + + setHostedParameterTree (std::move (newParameterTree)); + + UInt32 propertySize = 0; + Boolean writable = false; + + auSupportsBypass = (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_BypassEffect, + kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr + && propertySize >= sizeof (UInt32) && writable); + bypassParam.reset (new AUBypassParameter (*this)); + } + + void updateLatency() + { + Float64 latencySecs = 0.0; + UInt32 latencySize = sizeof (latencySecs); + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, + 0, &latencySecs, &latencySize); + + setLatencySamples (roundToInt (latencySecs * getSampleRate())); + } + + void handleIncomingMidiMessage (void*, const MidiMessage& message) + { + const ScopedLock sl (midiInLock); + incomingMidi.addEvent (message, 0); + } + + void handlePartialSysexMessage (void*, const uint8*, int, double) {} + + bool isMidiEffect() const override { return isMidiEffectPlugin; } + +private: + //============================================================================== + friend class AudioUnitPluginFormat; + + CoreAudioTimeConversions timeConversions; + + AudioComponentDescription componentDesc; + AudioComponent auComponent; + String pluginName, manufacturer, version; + String fileOrIdentifier; + CriticalSection lock; + + bool wantsMidiMessages = false, producesMidiMessages = false, + wasPlaying = false, prepared = false, + isAUv3 = false, isMidiEffectPlugin = false; + + struct AUBuffer + { + AUBuffer (size_t numBuffers) + { + bufferList.calloc (1, (sizeof (AudioBufferList) - sizeof (::AudioBuffer)) + (sizeof (::AudioBuffer) * numBuffers)); + AudioBufferList& buffer = *bufferList.get(); + + buffer.mNumberBuffers = static_cast (numBuffers); + } + + operator AudioBufferList&() + { + return *bufferList.get(); + } + + HeapBlock bufferList; + }; + + //============================================================================== + struct AUBypassParameter final : public Parameter + { + AUBypassParameter (AudioUnitPluginInstanceHeadless& effectToUse) + : parent (effectToUse), currentValue (getCurrentHostValue()) + {} + + bool getCurrentHostValue() + { + if (parent.auSupportsBypass) + { + UInt32 dataSize = sizeof (UInt32); + UInt32 value = 0; + + if (AudioUnitGetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect, + kAudioUnitScope_Global, 0, &value, &dataSize) == noErr + && dataSize == sizeof (UInt32)) + return value != 0; + } + + return false; + } + + float getValue() const override + { + return currentValue ? 1.0f : 0.0f; + } + + void setValue (float newValue) override + { + auto newBypassValue = (newValue != 0.0f); + + const ScopedLock sl (parent.lock); + + if (newBypassValue != currentValue) + { + currentValue = newBypassValue; + + if (parent.auSupportsBypass) + { + UInt32 value = (newValue != 0.0f ? 1 : 0); + AudioUnitSetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect, + kAudioUnitScope_Global, 0, &value, sizeof (UInt32)); + + #if JUCE_MAC + jassert (parent.audioUnit != nullptr); + + AudioUnitEvent ev; + ev.mEventType = kAudioUnitEvent_PropertyChange; + ev.mArgument.mProperty.mAudioUnit = parent.audioUnit; + ev.mArgument.mProperty.mPropertyID = kAudioUnitProperty_BypassEffect; + ev.mArgument.mProperty.mScope = kAudioUnitScope_Global; + ev.mArgument.mProperty.mElement = 0; + + AUEventListenerNotify (parent.eventListenerRef, nullptr, &ev); + #endif + } + } + } + + float getValueForText (const String& text) const override + { + String lowercaseText (text.toLowerCase()); + + for (auto& testText : auOnStrings) + if (lowercaseText == testText) + return 1.0f; + + for (auto& testText : auOffStrings) + if (lowercaseText == testText) + return 0.0f; + + return text.getIntValue() != 0 ? 1.0f : 0.0f; + } + + float getDefaultValue() const override { return 0.0f; } + String getName (int /*maximumStringLength*/) const override { return "Bypass"; } + String getText (float value, int) const override { return (value != 0.0f ? TRANS ("On") : TRANS ("Off")); } + bool isAutomatable() const override { return true; } + bool isDiscrete() const override { return true; } + bool isBoolean() const override { return true; } + int getNumSteps() const override { return 2; } + StringArray getAllValueStrings() const override { return values; } + String getLabel() const override { return {}; } + + String getParameterID() const override { return {}; } + + AudioUnitPluginInstanceHeadless& parent; + const StringArray auOnStrings { TRANS ("on"), TRANS ("yes"), TRANS ("true") }; + const StringArray auOffStrings { TRANS ("off"), TRANS ("no"), TRANS ("false") }; + const StringArray values { TRANS ("Off"), TRANS ("On") }; + + bool currentValue = false; + }; + + OwnedArray outputBufferList; + AudioTimeStamp timeStamp; + AudioBuffer inputBuffer; + Array> supportedInLayouts, supportedOutLayouts; + + int numChannelInfos, preparedChannels = 0, preparedSamples = 0; + HeapBlock channelInfos; + + AudioUnit audioUnit; + #if JUCE_MAC + AUEventListenerRef eventListenerRef; + #endif + + std::map paramIDToParameter; + + AudioUnitFormatHelpers::SingleDirectionChannelMapping inMapping, outMapping; + MidiDataConcatenator midiConcatenator; + CriticalSection midiInLock; + MidiBuffer incomingMidi; + std::unique_ptr bypassParam; + bool lastProcessBlockCallWasBypass = false, auSupportsBypass = false; + bool haveParameterList = false; + + void setPluginCallbacks() + { + if (audioUnit != nullptr) + { + #if JUCE_MAC + if (producesMidiMessages) + { + AUMIDIOutputCallbackStruct info; + zerostruct (info); + + info.userData = this; + info.midiOutputCallback = renderMidiOutputCallback; + + producesMidiMessages = (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallback, + kAudioUnitScope_Global, 0, &info, sizeof (info)) == noErr); + } + #endif + + HostCallbackInfo info; + zerostruct (info); + + info.hostUserData = this; + info.beatAndTempoProc = getBeatAndTempoCallback; + info.musicalTimeLocationProc = getMusicalTimeLocationCallback; + info.transportStateProc = getTransportStateCallback; + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, + kAudioUnitScope_Global, 0, &info, sizeof (info)); + } + } + + #if JUCE_MAC + void disposeEventListener() + { + if (eventListenerRef != nullptr) + { + AUListenerDispose (eventListenerRef); + eventListenerRef = nullptr; + } + } + + void createEventListener() + { + if (audioUnit == nullptr) + return; + + disposeEventListener(); + + AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), + kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); + + for (auto* param : getParameters()) + { + jassert (dynamic_cast (param) != nullptr); + + AudioUnitEvent event; + event.mArgument.mParameter.mAudioUnit = audioUnit; + event.mArgument.mParameter.mParameterID = static_cast (param)->getRawParamID(); + event.mArgument.mParameter.mScope = kAudioUnitScope_Global; + event.mArgument.mParameter.mElement = 0; + + event.mEventType = kAudioUnitEvent_ParameterValueChange; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + + event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + + event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + } + + addPropertyChangeListener (kAudioUnitProperty_PresentPreset); + addPropertyChangeListener (kAudioUnitProperty_ParameterList); + addPropertyChangeListener (kAudioUnitProperty_Latency); + addPropertyChangeListener (kAudioUnitProperty_BypassEffect); + } + + void addPropertyChangeListener (AudioUnitPropertyID type) const + { + AudioUnitEvent event; + event.mEventType = kAudioUnitEvent_PropertyChange; + event.mArgument.mProperty.mPropertyID = type; + event.mArgument.mProperty.mAudioUnit = audioUnit; + event.mArgument.mProperty.mScope = kAudioUnitScope_Global; + event.mArgument.mProperty.mElement = 0; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + } + + void eventCallback (const AudioUnitEvent& event, AudioUnitParameterValue newValue) + { + if (event.mEventType == kAudioUnitEvent_PropertyChange) + { + respondToPropertyChange (event.mArgument.mProperty); + return; + } + + const auto iter = paramIDToParameter.find (event.mArgument.mParameter.mParameterID); + auto* param = iter != paramIDToParameter.end() ? iter->second : nullptr; + jassert (param != nullptr); // Invalid parameter index + + if (param == nullptr) + return; + + if (event.mEventType == kAudioUnitEvent_ParameterValueChange) + param->sendValueChangedMessageToListeners (param->normaliseParamValue (newValue)); + else if (event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture) + param->beginChangeGesture(); + else if (event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) + param->endChangeGesture(); + } + + void respondToPropertyChange (const AudioUnitProperty& prop) + { + switch (prop.mPropertyID) + { + case kAudioUnitProperty_ParameterList: + updateParameterInfo(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); + break; + + case kAudioUnitProperty_PresentPreset: + sendAllParametersChangedEvents(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); + break; + + case kAudioUnitProperty_Latency: + updateLatency(); + break; + + case kAudioUnitProperty_BypassEffect: + if (bypassParam != nullptr) + bypassParam->setValueNotifyingHost (bypassParam->getValue()); + + break; + } + } + + static void eventListenerCallback (void* userRef, void*, const AudioUnitEvent* event, + UInt64, AudioUnitParameterValue value) + { + JUCE_ASSERT_MESSAGE_THREAD + jassert (event != nullptr); + static_cast (userRef)->eventCallback (*event, value); + } + + void updateParameterInfo() + { + for (const auto& idAndParam : paramIDToParameter) + { + const auto& id = idAndParam.first; + const auto& param = idAndParam.second; + + const ScopedAudioUnitParameterInfo info { audioUnit, id }; + + if (! info.isValid()) + continue; + + param->setName (getParamName (info.get())); + param->setLabel (getParamLabel (info.get())); + } + } + #endif + + /* Some fields in the AudioUnitParameterInfo may need to be released after use, + so we'll do that using RAII. + */ + class ScopedAudioUnitParameterInfo + { + public: + ScopedAudioUnitParameterInfo (AudioUnit au, UInt32 paramId) + { + auto sz = (UInt32) sizeof (info); + valid = noErr == AudioUnitGetProperty (au, + kAudioUnitProperty_ParameterInfo, + kAudioUnitScope_Global, + paramId, + &info, + &sz); + } + + ScopedAudioUnitParameterInfo (const ScopedAudioUnitParameterInfo&) = delete; + ScopedAudioUnitParameterInfo (ScopedAudioUnitParameterInfo&&) = delete; + ScopedAudioUnitParameterInfo& operator= (const ScopedAudioUnitParameterInfo&) = delete; + ScopedAudioUnitParameterInfo& operator= (ScopedAudioUnitParameterInfo&&) = delete; + + ~ScopedAudioUnitParameterInfo() noexcept + { + if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) == 0) + return; + + if (info.cfNameString != nullptr) + CFRelease (info.cfNameString); + + if (info.unit == kAudioUnitParameterUnit_CustomUnit && info.unitName != nullptr) + CFRelease (info.unitName); + } + + bool isValid() const { return valid; } + + const AudioUnitParameterInfo& get() const noexcept { return info; } + + private: + AudioUnitParameterInfo info; + bool valid = false; + }; + + static String getParamName (const AudioUnitParameterInfo& info) + { + if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) == 0) + return { info.name, sizeof (info.name) }; + + return String::fromCFString (info.cfNameString); + } + + static String getParamLabel (const AudioUnitParameterInfo& info) + { + if (info.unit == kAudioUnitParameterUnit_CustomUnit) return String::fromCFString (info.unitName); + if (info.unit == kAudioUnitParameterUnit_Percent) return "%"; + if (info.unit == kAudioUnitParameterUnit_Seconds) return "s"; + if (info.unit == kAudioUnitParameterUnit_Hertz) return "Hz"; + if (info.unit == kAudioUnitParameterUnit_Decibels) return "dB"; + if (info.unit == kAudioUnitParameterUnit_Milliseconds) return "ms"; + + return {}; + } + + //============================================================================== + OSStatus renderGetInput (AudioUnitRenderActionFlags*, + const AudioTimeStamp*, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) + { + if (inputBuffer.getNumChannels() <= 0) + { + jassertfalse; + return noErr; + } + + // if this ever happens, might need to add extra handling + if (inputBuffer.getNumSamples() != (int) inNumberFrames) + { + jassertfalse; + return noErr; + } + + const auto buffer = static_cast (inBusNumber) < getBusCount (true) + ? getBusBuffer (inputBuffer, true, static_cast (inBusNumber)) + : AudioBuffer(); + + for (int juceChannel = 0; juceChannel < buffer.getNumChannels(); ++juceChannel) + { + const auto auChannel = (int) inMapping.getAuIndexForJuceChannel (inBusNumber, (size_t) juceChannel); + + if (auChannel < buffer.getNumChannels()) + memcpy (ioData->mBuffers[auChannel].mData, buffer.getReadPointer (juceChannel), sizeof (float) * inNumberFrames); + else + zeromem (ioData->mBuffers[auChannel].mData, sizeof (float) * inNumberFrames); + } + + return noErr; + } + + OSStatus renderMidiOutput (const MIDIPacketList* pktlist) + { + if (pktlist != nullptr && pktlist->numPackets) + { + auto time = Time::getMillisecondCounterHiRes() * 0.001; + const MIDIPacket* packet = &pktlist->packet[0]; + + for (UInt32 i = 0; i < pktlist->numPackets; ++i) + { + midiConcatenator.pushMidiData (packet->data, (int) packet->length, time, (void*) nullptr, *this); + packet = MIDIPacketNext (packet); + } + } + + return noErr; + } + + template + static void setIfNotNull (Type1* p, Type2 value) noexcept + { + if (p != nullptr) *p = value; + } + + /* If the AudioPlayHead is available, and has valid PositionInfo, this will return the result + of calling the specified getter on that PositionInfo. Otherwise, this will return a + default-constructed instance of the same type. + + For getters that return an Optional, this function will return a nullopt if the playhead or + position info is invalid. + + For getters that return a bool, this function will return false if the playhead or position + info is invalid. + */ + template + Result getFromPlayHead (Result (AudioPlayHead::PositionInfo::* member)() const) const + { + if (auto* ph = getPlayHead()) + if (const auto pos = ph->getPosition()) + return ((*pos).*member)(); + + return {}; + } + + OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const + { + setIfNotNull (outCurrentBeat, getFromPlayHead (&AudioPlayHead::PositionInfo::getPpqPosition).orFallback (0)); + setIfNotNull (outCurrentTempo, getFromPlayHead (&AudioPlayHead::PositionInfo::getBpm).orFallback (120.0)); + return noErr; + } + + OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, + UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const + { + setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx + setIfNotNull (outCurrentMeasureDownBeat, getFromPlayHead (&AudioPlayHead::PositionInfo::getPpqPositionOfLastBarStart).orFallback (0.0)); + + const auto signature = getFromPlayHead (&AudioPlayHead::PositionInfo::getTimeSignature).orFallback (AudioPlayHead::TimeSignature{}); + setIfNotNull (outTimeSig_Numerator, (Float32) signature.numerator); + setIfNotNull (outTimeSig_Denominator, (UInt32) signature.denominator); + + return noErr; + } + + OSStatus getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, + Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, + Float64* outCycleStartBeat, Float64* outCycleEndBeat) + { + const auto nowPlaying = getFromPlayHead (&AudioPlayHead::PositionInfo::getIsPlaying); + setIfNotNull (outIsPlaying, nowPlaying); + setIfNotNull (outTransportStateChanged, std::exchange (wasPlaying, nowPlaying) != nowPlaying); + setIfNotNull (outCurrentSampleInTimeLine, (double) getFromPlayHead (&AudioPlayHead::PositionInfo::getTimeInSamples).orFallback (0)); + setIfNotNull (outIsCycling, getFromPlayHead (&AudioPlayHead::PositionInfo::getIsLooping)); + + const auto loopPoints = getFromPlayHead (&AudioPlayHead::PositionInfo::getLoopPoints).orFallback (AudioPlayHead::LoopPoints{}); + setIfNotNull (outCycleStartBeat, loopPoints.ppqStart); + setIfNotNull (outCycleEndBeat, loopPoints.ppqEnd); + + return noErr; + } + + //============================================================================== + static OSStatus renderGetInputCallback (void* hostRef, AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, + UInt32 inNumberFrames, AudioBufferList* ioData) + { + return static_cast (hostRef) + ->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); + } + + static OSStatus renderMidiOutputCallback (void* hostRef, const AudioTimeStamp*, UInt32 /*midiOutNum*/, + const MIDIPacketList* pktlist) + { + return static_cast (hostRef)->renderMidiOutput (pktlist); + } + + static OSStatus getBeatAndTempoCallback (void* hostRef, Float64* outCurrentBeat, Float64* outCurrentTempo) + { + return static_cast (hostRef)->getBeatAndTempo (outCurrentBeat, outCurrentTempo); + } + + static OSStatus getMusicalTimeLocationCallback (void* hostRef, UInt32* outDeltaSampleOffsetToNextBeat, + Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, + Float64* outCurrentMeasureDownBeat) + { + return static_cast (hostRef) + ->getMusicalTimeLocation (outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, + outTimeSig_Denominator, outCurrentMeasureDownBeat); + } + + static OSStatus getTransportStateCallback (void* hostRef, Boolean* outIsPlaying, Boolean* outTransportStateChanged, + Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, + Float64* outCycleStartBeat, Float64* outCycleEndBeat) + { + return static_cast (hostRef) + ->getTransportState (outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, + outIsCycling, outCycleStartBeat, outCycleEndBeat); + } + + //============================================================================== + bool isBusCountWritable (bool isInput) const noexcept + { + UInt32 countSize; + Boolean writable; + AudioUnitScope scope = (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + auto err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &countSize, &writable); + + return (err == noErr && writable != 0 && countSize == sizeof (UInt32)); + } + + //============================================================================== + void getBusProperties (bool isInput, UInt32 busIdx, String& busName, AudioChannelSet& currentLayout) const + { + getBusProperties (audioUnit, isInput, busIdx, busName, currentLayout); + } + + static void getBusProperties (AudioUnit comp, bool isInput, UInt32 busIdx, String& busName, AudioChannelSet& currentLayout) + { + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + busName = (isInput ? "Input #" : "Output #") + String (busIdx + 1); + + { + CFObjectHolder busNameCF; + UInt32 propertySize = sizeof (busNameCF.object); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_ElementName, scope, busIdx, &busNameCF.object, &propertySize) == noErr) + if (busNameCF.object != nullptr) + busName = nsStringToJuce ((NSString*) busNameCF.object); + + if (const auto auLayout = AudioUnitFormatHelpers::tryGetPropertyData (comp, kAudioUnitProperty_AudioChannelLayout, scope, busIdx)) + currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout->get()); + + if (currentLayout.isDisabled()) + { + AudioStreamBasicDescription descr; + propertySize = sizeof (descr); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_StreamFormat, scope, busIdx, &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); + } + } + } + + //============================================================================== + void numBusesChanged() override + { + updateSupportedLayouts(); + } + + void updateSupportedLayouts() + { + supportedInLayouts.clear(); + supportedOutLayouts.clear(); + numChannelInfos = 0; + channelInfos.free(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const auto n = AudioUnitFormatHelpers::getElementCount (audioUnit, scope); + + for (UInt32 busIdx = 0; busIdx < n; ++busIdx) + { + Array supported; + AudioChannelSet currentLayout; + + if (const auto auLayout = AudioUnitFormatHelpers::tryGetPropertyData (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, busIdx)) + currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout->get()); + + if (currentLayout.isDisabled()) + { + AudioStreamBasicDescription descr; + UInt32 propertySize = sizeof (descr); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, busIdx, &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); + } + + supported.clear(); + { + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, busIdx, &propertySize, &writable) == noErr + && propertySize > 0) + { + const size_t numElements = propertySize / sizeof (AudioChannelLayoutTag); + HeapBlock layoutTags (numElements); + propertySize = static_cast (sizeof (AudioChannelLayoutTag) * numElements); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, + static_cast (busIdx), layoutTags.get(), &propertySize) == noErr) + { + for (int j = 0; j < static_cast (numElements); ++j) + { + const AudioChannelLayoutTag tag = layoutTags[j]; + + if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) + { + AudioChannelLayout caLayout; + + caLayout.mChannelLayoutTag = tag; + supported.addIfNotAlreadyThere (CoreAudioLayouts::fromCoreAudio (caLayout)); + } + } + + if (supported.size() > 0) + supported.addIfNotAlreadyThere (currentLayout); + } + } + } + + (isInput ? supportedInLayouts : supportedOutLayouts).add (supported); + } + } + + { + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr + && propertySize > 0) + { + numChannelInfos = propertySize / sizeof (AUChannelInfo); + channelInfos.malloc (static_cast (numChannelInfos)); + propertySize = static_cast (sizeof (AUChannelInfo) * static_cast (numChannelInfos)); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, channelInfos.get(), &propertySize) != noErr) + numChannelInfos = 0; + } + else + { + numChannelInfos = 1; + channelInfos.malloc (static_cast (numChannelInfos)); + channelInfos.get()->inChannels = -1; + channelInfos.get()->outChannels = -1; + } + } + } + + bool canProduceMidiOutput() + { + #if JUCE_MAC + UInt32 dataSize = 0; + Boolean isWritable = false; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, + kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr + && dataSize != 0) + { + CFObjectHolder midiArray; + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, + kAudioUnitScope_Global, 0, &midiArray.object, &dataSize) == noErr) + return (CFArrayGetCount (midiArray.object) > 0); + } + #endif + + return false; + } + + bool supportsMPE() const override + { + UInt32 dataSize = 0; + Boolean isWritable = false; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportsMPE, + kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr + && dataSize == sizeof (UInt32)) + { + UInt32 result = 0; + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportsMPE, + kAudioUnitScope_Global, 0, &result, &dataSize) == noErr) + { + return result > 0; + } + } + + return false; + } + + //============================================================================== + void updateBypass (bool processBlockBypassedCalled) + { + if (processBlockBypassedCalled && bypassParam != nullptr) + { + if (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass) + bypassParam->setValue (1.0f); + } + else + { + if (lastProcessBlockCallWasBypass && bypassParam != nullptr) + bypassParam->setValue (0.0f); + } + + lastProcessBlockCallWasBypass = processBlockBypassedCalled; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstanceHeadless) +}; + +//============================================================================== +template +static void createAudioUnitPluginInstance (AudioUnitPluginFormatHeadless& format, + const PluginDescription& desc, + double rate, + int blockSize, + AudioPluginFormat::PluginCreationCallback callback) +{ + auto auComponentResult = getAudioComponent (format, desc); + + if (! auComponentResult.isValid()) + { + callback (nullptr, std::move (auComponentResult.errorMessage)); + return; + } + + createAudioUnit (auComponentResult.component, + [rate, blockSize, origCallback = std::move (callback)] (AudioUnit audioUnit, OSStatus err) + { + if (err == noErr) + { + auto instance = std::make_unique (audioUnit); + + if (instance->initialise (rate, blockSize)) + origCallback (std::move (instance), {}); + else + origCallback (nullptr, NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in")); + } + else + { + auto errMsg = TRANS ("An OS error occurred during initialisation of the plug-in (XXX)"); + origCallback (nullptr, errMsg.replace ("XXX", String (err))); + } + }); +} + +} // namespace juce + +#endif diff --git a/modules/juce_audio_processors_headless/juce_audio_processors_headless.cpp b/modules/juce_audio_processors_headless/juce_audio_processors_headless.cpp index cc78d70a47..2612a17c8e 100644 --- a/modules/juce_audio_processors_headless/juce_audio_processors_headless.cpp +++ b/modules/juce_audio_processors_headless/juce_audio_processors_headless.cpp @@ -41,6 +41,9 @@ #error "Incorrect use of JUCE cpp file" #endif +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + #include "juce_audio_processors_headless.h" #include @@ -61,3 +64,4 @@ #include #include #include +#include diff --git a/modules/juce_audio_processors_headless/juce_audio_processors_headless.h b/modules/juce_audio_processors_headless/juce_audio_processors_headless.h index 4ac93ad83f..9fb95143dc 100644 --- a/modules/juce_audio_processors_headless/juce_audio_processors_headless.h +++ b/modules/juce_audio_processors_headless/juce_audio_processors_headless.h @@ -89,3 +89,4 @@ #include #include #include +#include