diff --git a/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp b/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp new file mode 100644 index 0000000000..f89650bd2d --- /dev/null +++ b/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp @@ -0,0 +1,61 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include +#include "../utility/juce_CheckSettingMacros.h" + +#if JucePlugin_Enable_ARA + +#include "../utility/juce_IncludeSystemHeaders.h" +#include "../utility/juce_IncludeModuleHeaders.h" + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", "-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) + +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +namespace juce +{ + +#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || JUCE_LOG_ASSERTIONS +JUCE_API void JUCE_CALLTYPE handleARAAssertion (const char* file, const int line, const char* diagnosis) noexcept +{ + #if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) + DBG (diagnosis); + #endif + + logAssertion (file, line); + + #if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) + if (juce_isRunningUnderDebugger()) + JUCE_BREAK_IN_DEBUGGER; + JUCE_ANALYZER_NORETURN + #endif +} +#endif + +ARA_SETUP_DEBUG_MESSAGE_PREFIX(JucePlugin_Name); + +} // namespace juce + +#endif diff --git a/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.h b/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.h new file mode 100644 index 0000000000..b369c9c257 --- /dev/null +++ b/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.h @@ -0,0 +1,48 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#if JucePlugin_Enable_ARA +// Configure ARA debug support prior to including ARA SDK headers +namespace juce +{ + +#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || JUCE_LOG_ASSERTIONS + +#define ARA_ENABLE_INTERNAL_ASSERTS 1 + +extern JUCE_API void JUCE_CALLTYPE handleARAAssertion (const char* file, const int line, const char* diagnosis) noexcept; + +#if !defined(ARA_HANDLE_ASSERT) +#define ARA_HANDLE_ASSERT(file, line, diagnosis) juce::handleARAAssertion (file, line, diagnosis) +#endif + +#if JUCE_LOG_ASSERTIONS +#define ARA_ENABLE_DEBUG_OUTPUT 1 +#endif + +#else + +#define ARA_ENABLE_INTERNAL_ASSERTS 0 + +#endif // (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || JUCE_LOG_ASSERTIONS + +} // namespace juce + +#endif diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index effde86ecc..272cfe8232 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -58,6 +58,14 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE #include #include +#if JucePlugin_Enable_ARA + #include + #include + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current JUCE ARA implementation" + #endif +#endif + //============================================================================== using namespace juce; @@ -425,6 +433,17 @@ public: outWritable = false; return noErr; + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitFactory); + return noErr; + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitPlugInExtensionBinding); + return noErr; + #endif + default: break; } } @@ -466,6 +485,33 @@ public: jassertfalse; break; + //============================================================================== + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + { + auto auFactory = static_cast (outData); + if (auFactory->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + auFactory->outFactory = createARAFactory(); + return noErr; + } + + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + { + auto binding = static_cast (outData); + if (binding->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (juceFilter.get()); + binding->outPlugInExtension = araAudioProcessorExtension->bindToARA (binding->inDocumentControllerRef, binding->knownRoles, binding->assignedRoles); + if (binding->outPlugInExtension == nullptr) + return kAudioUnitErr_CannotDoInCurrentContext; // bindToARA() returns null if binding is already established + + return noErr; + } + #endif + case juceFilterObjectPropertyID: ((void**) outData)[0] = (void*) static_cast (juceFilter.get()); ((void**) outData)[1] = (void*) this; @@ -1030,6 +1076,9 @@ public: { const double rate = getSampleRate(); jassert (rate > 0); + #if JucePlugin_Enable_ARA + jassert (juceFilter->getLatencySamples() == 0 || ! dynamic_cast (juceFilter.get())->isBoundToARA()); + #endif return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; } @@ -1698,7 +1747,14 @@ public: { if (AudioProcessor* filter = static_cast (pointers[0])) if (AudioProcessorEditor* editorComp = filter->createEditorIfNeeded()) + { + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (editorComp) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (editorComp->isResizable()); + #endif return EditorCompHolder::createViewFor (filter, static_cast (pointers[1]), editorComp); + } } return nil; diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index a9fbe1d5ce..01cf2f99f5 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -79,6 +79,21 @@ JUCE_BEGIN_NO_SANITIZE ("vptr") #include #endif +//============================================================================== +#if JucePlugin_Enable_ARA + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wpragma-pack") + #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current implementation" + #endif + + DEF_CLASS_IID(ARA::IPlugInEntryPoint) + DEF_CLASS_IID(ARA::IPlugInEntryPoint2) + DEF_CLASS_IID(ARA::IMainFactory) +#endif + namespace juce { @@ -644,6 +659,9 @@ class JuceVST3EditController : public Vst::EditController, public Vst::IMidiMapping, public Vst::IUnitInfo, public Vst::ChannelContext::IInfoListener, + #if JucePlugin_Enable_ARA + public Presonus::IPlugInViewEmbedding, + #endif public AudioProcessorListener, private ComponentRestarter::Listener { @@ -925,6 +943,21 @@ public: return kResultOk; } + //============================================================================== + #if JucePlugin_Enable_ARA + Steinberg::TBool PLUGIN_API isViewEmbeddingSupported() override + { + if (auto* pluginInstance = getPluginInstance()) + return (Steinberg::TBool) dynamic_cast (pluginInstance)->isEditorView(); + return (Steinberg::TBool) false; + } + + Steinberg::tresult PLUGIN_API setViewIsEmbedded (Steinberg::IPlugView* /*view*/, Steinberg::TBool /*embedded*/) override + { + return kResultOk; + } + #endif + //============================================================================== tresult PLUGIN_API setComponentState (IBStream* stream) override { @@ -1266,6 +1299,10 @@ public: auto latencySamples = pluginInstance->getLatencySamples(); + #if JucePlugin_Enable_ARA + jassert (latencySamples == 0 || ! dynamic_cast (pluginInstance)->isBoundToARA()); + #endif + if (details.latencyChanged && latencySamples != lastLatencySamples) { flags |= Vst::kLatencyChanged; @@ -1410,6 +1447,9 @@ private: UniqueBase{}, SharedBase{}, UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + #endif SharedBase{}); if (result.isOk()) @@ -1989,6 +2029,12 @@ private: { pluginEditor.reset (plugin.createEditorIfNeeded()); + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (pluginEditor.get()) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (pluginEditor->isResizable()); + #endif + if (pluginEditor != nullptr) { editorHostContext = std::make_unique (*owner.owner->audioProcessor, @@ -2231,12 +2277,61 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) }; + +//============================================================================== +#if JucePlugin_Enable_ARA + class JuceARAFactory : public ARA::IMainFactory + { + public: + JuceARAFactory() = default; + virtual ~JuceARAFactory() = default; + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const ::Steinberg::TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + if (doUIDsMatch (targetIID, JuceARAFactory::iid)) + { + addRef(); + *obj = this; + return kResultOk; + } + + *obj = nullptr; + return kNoInterface; + } + + //---from ARA::IMainFactory------- + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + static const FUID iid; + + private: + //============================================================================== + std::atomic refCount { 1 }; + }; +#endif + //============================================================================== class JuceVST3Component : public Vst::IComponent, public Vst::IAudioProcessor, public Vst::IUnitInfo, public Vst::IConnectionPoint, public Vst::IProcessContextRequirements, + #if JucePlugin_Enable_ARA + public ARA::IPlugInEntryPoint, + public ARA::IPlugInEntryPoint2, + #endif public AudioPlayHead { public: @@ -3272,6 +3367,10 @@ private: UniqueBase{}, UniqueBase{}, UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + UniqueBase{}, + #endif SharedBase{}); if (result.isOk()) @@ -3385,6 +3484,27 @@ private: bufferMapper.prepare (p, bufferSize); } + //============================================================================== + #if JucePlugin_Enable_ARA + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentController (ARA::ARADocumentControllerRef /*controllerRef*/) SMTG_OVERRIDE + { + ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); + return nullptr; + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, + ARA::ARAPlugInInstanceRoleFlags knownRoles, ARA::ARAPlugInInstanceRoleFlags assignedRoles) SMTG_OVERRIDE + { + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (pluginInstance); + return araAudioProcessorExtension->bindToARA (documentControllerRef, knownRoles, assignedRoles); + } + #endif + //============================================================================== ScopedJuceInitialiser_GUI libraryInitialiser; @@ -3483,6 +3603,11 @@ DEF_CLASS_IID (JuceAudioProcessor) DEF_CLASS_IID (JuceVST3Component) #endif +#if JucePlugin_Enable_ARA + DECLARE_CLASS_IID (JuceARAFactory, 0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) + DEF_CLASS_IID (JuceARAFactory) +#endif + JUCE_END_IGNORE_WARNINGS_MSVC JUCE_END_IGNORE_WARNINGS_GCC_LIKE @@ -3611,6 +3736,13 @@ static FUnknown* createControllerInstance (Vst::IHostApplication* host) return static_cast (new JuceVST3EditController (host)); } +#if JucePlugin_Enable_ARA + static FUnknown* createARAFactoryInstance (Vst::IHostApplication* /*host*/) + { + return static_cast (new JuceARAFactory()); + } +#endif + //============================================================================== struct JucePluginFactory; static JucePluginFactory* globalFactory = nullptr; @@ -3883,6 +4015,20 @@ extern "C" SMTG_EXPORT_SYMBOL IPluginFactory* PLUGIN_API GetPluginFactory() kVstVersionString); globalFactory->registerClass (controllerClass, createControllerInstance); + + #if JucePlugin_Enable_ARA + static const PClassInfo2 araFactoryClass (JuceARAFactory::iid, + PClassInfo::kManyInstances, + kARAMainFactoryClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString); + + globalFactory->registerClass (araFactoryClass, createARAFactoryInstance); + #endif } else { diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client.h b/modules/juce_audio_plugin_client/juce_audio_plugin_client.h index 5c75f55e5c..c57e0fe83a 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client.h +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client.h @@ -121,3 +121,7 @@ #endif #include "utility/juce_CreatePluginFilter.h" + +#if JucePlugin_Enable_ARA + #include "ARA/juce_ARA_Wrapper.h" +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp new file mode 100644 index 0000000000..33ecfb8152 --- /dev/null +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp @@ -0,0 +1,19 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "ARA/juce_ARA_Wrapper.cpp" diff --git a/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h b/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h index a41dc2496b..bb5e6570d1 100644 --- a/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h +++ b/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h @@ -30,6 +30,10 @@ inline AudioProcessor* JUCE_API JUCE_CALLTYPE createPluginFilterOfType (AudioPro // your createPluginFilter() method must return an object! jassert (pluginInstance != nullptr && pluginInstance->wrapperType == type); + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (pluginInstance) != nullptr); + #endif + return pluginInstance; } diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index 9d5b62a255..be1d5cfe27 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -162,7 +162,16 @@ #include "utilities/juce_PluginHostType.h" #include "utilities/ARA/juce_ARA_utils.h" -// This is here to avoid missing-prototype warnings in user code. +//============================================================================== +// These declarations are here to avoid missing-prototype warnings in user code. + // If you're implementing a plugin, you should supply a body for // this function in your own code. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter(); + +// If you are implementing an ARA enabled plugin, you need to +// implement this function somewhere in the codebase by returning +// SubclassOfARADocumentControllerSpecialisation::createARAFactory(); +#if JucePlugin_Enable_ARA + const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory(); +#endif