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 713c38f843..7c573e8a2d 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -125,10 +125,7 @@ public: tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Steinberg::Linux::IEventHandler) - - *obj = nullptr; - return kNoInterface; + return testFor (*this, targetIID, UniqueBase{}).extract (obj); } void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override @@ -280,6 +277,45 @@ private: ThreadLocalValue& toSet; }; +template +static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor, + const TUID targetIID, + Member&& member) +{ + if (processor == nullptr) + return {}; + + void* obj = nullptr; + + if (auto* extensions = dynamic_cast (processor)) + { + const auto result = (extensions->*member) (targetIID, &obj); + return { result, obj }; + } + + return {}; +} + +tresult extractResult (const QueryInterfaceResult& userInterface, + const InterfaceResultWithDeferredAddRef& juceInterface, + void** obj) +{ + if (userInterface.isOk() && juceInterface.isOk()) + { + // If you hit this assertion, you've provided a custom implementation of an interface + // that JUCE implements already. As a result, your plugin may not behave correctly. + // Consider removing your custom implementation. + jassertfalse; + + return userInterface.extract (obj); + } + + if (userInterface.isOk()) + return userInterface.extract (obj); + + return juceInterface.extract (obj); +} + //============================================================================== class JuceAudioProcessor : public Vst::IUnitInfo { @@ -629,27 +665,13 @@ public: tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (targetIID, FObject) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, JuceVST3EditController) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController2) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IMidiMapping) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::ChannelContext::IInfoListener) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IPluginBase, Vst::IEditController) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IDependent, Vst::IEditController) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IEditController) + const auto userProvidedInterface = queryAdditionalInterfaces (getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIEditController); - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - { - audioProcessor->addRef(); - *obj = audioProcessor; - return kResultOk; - } + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - *obj = nullptr; - return kNoInterface; + return extractResult (userProvidedInterface, juceProvidedInterface, obj); } //============================================================================== @@ -1151,7 +1173,7 @@ public: && (pluginInstance->getActiveEditor() == nullptr || getHostType().isAdobeAudition()); if (mayCreateEditor) - return new JuceVST3Editor (*this, *pluginInstance); + return new JuceVST3Editor (*this, *audioProcessor); } return nullptr; @@ -1328,10 +1350,41 @@ private: float lastScaleFactorReceived = 1.0f; #endif + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + SharedBase{}, + UniqueBase{}, + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, audioProcessor.get() }; + + return {}; + } + void installAudioProcessor (const VSTComSmartPtr& newAudioProcessor) { audioProcessor = newAudioProcessor; + if (auto* extensions = dynamic_cast (audioProcessor->get())) + { + extensions->setIComponentHandler (componentHandler); + extensions->setIHostApplication (hostContext.get()); + } + if (auto* pluginInstance = getPluginInstance()) { lastLatencySamples = pluginInstance->getLatencySamples(); @@ -1420,10 +1473,10 @@ private: private Timer { public: - JuceVST3Editor (JuceVST3EditController& ec, AudioProcessor& p) - : Vst::EditorView (&ec, nullptr), - owner (&ec), - pluginInstance (p) + JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) + : EditorView (&ec, nullptr), + owner (&ec), + pluginInstance (*p.get()) { createContentWrapperComponentIfNeeded(); @@ -1438,7 +1491,11 @@ private: tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Steinberg::IPlugViewContentScaleSupport) + const auto result = testFor (*this, targetIID, UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + return Vst::EditorView::queryInterface (targetIID, obj); } @@ -2059,23 +2116,13 @@ public: tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (targetIID, IPluginBase) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, JuceVST3Component) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IComponent) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IAudioProcessor) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IComponent) + const auto userProvidedInterface = queryAdditionalInterfaces (&getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIAudioProcessor); - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - { - comPluginInstance->addRef(); - *obj = comPluginInstance; - return kResultOk; - } + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - *obj = nullptr; - return kNoInterface; + return extractResult (userProvidedInterface, juceProvidedInterface, obj); } //============================================================================== @@ -3006,6 +3053,27 @@ public: } private: + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, comPluginInstance.get() }; + + return {}; + } + //============================================================================== struct ScopedInSetupProcessingSetter { @@ -3287,6 +3355,7 @@ private: } T* operator->() { return ptr.operator->(); } + T* get() const noexcept { return ptr.get(); } operator T*() const noexcept { return ptr.get(); } template @@ -3523,10 +3592,15 @@ struct JucePluginFactory : public IPluginFactory3 tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (targetIID, IPluginFactory3) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, IPluginFactory2) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, IPluginFactory) - TEST_FOR_AND_RETURN_IF_VALID (targetIID, FUnknown) + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); jassertfalse; // Something new? *obj = nullptr; 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 7b3ecff06b..ced253fac7 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client.h +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client.h @@ -127,4 +127,3 @@ #endif #include "utility/juce_CreatePluginFilter.h" -#include "VST/juce_VSTCallbackHandler.h" diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index da03acdfdb..89e6c3d84a 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -46,22 +46,106 @@ static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexc return std::memcmp (a, b, sizeof (Steinberg::TUID)) == 0; } -#define TEST_FOR_AND_RETURN_IF_VALID(iidToTest, ClassType) \ - if (doUIDsMatch (iidToTest, ClassType::iid)) \ - { \ - addRef(); \ - *obj = dynamic_cast (this); \ - return Steinberg::kResultOk; \ +/* Holds a tresult and a pointer to an object. + + Useful for holding intermediate results of calls to queryInterface. +*/ +class QueryInterfaceResult +{ +public: + QueryInterfaceResult() = default; + + QueryInterfaceResult (Steinberg::tresult resultIn, void* ptrIn) + : result (resultIn), ptr (ptrIn) {} + + bool isOk() const noexcept { return result == Steinberg::kResultOk; } + + Steinberg::tresult extract (void** obj) const + { + *obj = result == Steinberg::kResultOk ? ptr : nullptr; + return result; } -#define TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(iidToTest, CommonClassType, SourceClassType) \ - if (doUIDsMatch (iidToTest, CommonClassType::iid)) \ - { \ - addRef(); \ - *obj = (CommonClassType*) static_cast (this); \ - return Steinberg::kResultOk; \ +private: + Steinberg::tresult result = Steinberg::kResultFalse; + void* ptr = nullptr; +}; + +/* Holds a tresult and a pointer to an object. + + Calling InterfaceResultWithDeferredAddRef::extract() will also call addRef + on the pointed-to object. It is expected that users will use + InterfaceResultWithDeferredAddRef to hold intermediate results of a queryInterface + call. When a suitable interface is found, the function can be exited with + `return suitableInterface.extract (obj)`, which will set the obj pointer, + add a reference to the interface, and return the appropriate result code. +*/ +class InterfaceResultWithDeferredAddRef +{ +public: + InterfaceResultWithDeferredAddRef() = default; + + template + InterfaceResultWithDeferredAddRef (Steinberg::tresult resultIn, Ptr* ptrIn) + : result (resultIn, ptrIn), + addRefFn (doAddRef) {} + + bool isOk() const noexcept { return result.isOk(); } + + Steinberg::tresult extract (void** obj) const + { + const auto toReturn = result.extract (obj); + + if (result.isOk() && addRefFn != nullptr && *obj != nullptr) + addRefFn (*obj); + + return toReturn; } +private: + template + static void doAddRef (void* obj) { static_cast (obj)->addRef(); } + + QueryInterfaceResult result; + void (*addRefFn) (void*) = nullptr; +}; + +template struct UniqueBase {}; +template struct SharedBase {}; + +template +InterfaceResultWithDeferredAddRef testFor (ToTest& toTest, + const Steinberg::TUID targetIID, + SharedBase) +{ + if (! doUIDsMatch (targetIID, CommonClassType::iid)) + return {}; + + return { Steinberg::kResultOk, static_cast (static_cast (std::addressof (toTest))) }; +} + +template +InterfaceResultWithDeferredAddRef testFor (ToTest& toTest, + const Steinberg::TUID targetIID, + UniqueBase) +{ + return testFor (toTest, targetIID, SharedBase{}); +} + +template +InterfaceResultWithDeferredAddRef testForMultiple (ToTest&, const Steinberg::TUID) { return {}; } + +template +InterfaceResultWithDeferredAddRef testForMultiple (ToTest& toTest, const Steinberg::TUID targetIID, Head head, Tail... tail) +{ + const auto result = testFor (toTest, targetIID, head); + + if (result.isOk()) + return result; + + return testForMultiple (toTest, targetIID, tail...); +} + //============================================================================== #if VST_VERSION < 0x030608 #define kAmbi1stOrderACN kBFormat diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 4a41729a44..3cfc943b90 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -552,16 +552,15 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 return kResultOk; } - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler) - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler2) - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler3) - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IContextMenuTarget) - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IHostApplication) - TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IUnitHandler) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (iid, FUnknown, Vst::IComponentHandler) - - *obj = nullptr; - return kNotImplemented; + return testForMultiple (*this, + iid, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + SharedBase{}).extract (obj); } private: @@ -2643,11 +2642,10 @@ public: tresult PLUGIN_API queryInterface (const TUID queryIid, void** obj) override { - TEST_FOR_AND_RETURN_IF_VALID (queryIid, Vst::IAttributeList) - TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (queryIid, FUnknown, Vst::IAttributeList) - - *obj = nullptr; - return kNotImplemented; + return testForMultiple (*this, + queryIid, + UniqueBase{}, + SharedBase{}).extract (obj); } tresult PLUGIN_API setInt (AttrID, Steinberg::int64) override { return kOutOfMemory; } diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index 97cd93bf76..cbce4e90a6 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -115,6 +115,8 @@ #endif //============================================================================== +#include "utilities/juce_VSTCallbackHandler.h" +#include "utilities/juce_VST3ClientExtensions.h" #include "utilities/juce_ExtensionsVisitor.h" #include "processors/juce_AudioProcessorEditor.h" #include "processors/juce_AudioProcessorListener.h" diff --git a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h new file mode 100644 index 0000000000..32afeeb2d6 --- /dev/null +++ b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h @@ -0,0 +1,87 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +// Forward declaration to avoid leaking implementation details. +namespace Steinberg +{ + class FUnknown; + using TUID = char[16]; +} + +namespace juce +{ + +/** An interface to allow an AudioProcessor to implement extended VST3-specific + functionality. + + To use this class, ensure that your AudioProcessor publicly inherits + from VST3ClientExtensions. + + @see VSTCallbackHandler + + @tags{Audio} +*/ +struct VST3ClientExtensions +{ + virtual ~VST3ClientExtensions() = default; + + /** This function may be used by implementations of queryInterface() + in the VST3's implementation of IEditController to return + additional supported interfaces. + */ + virtual int32_t queryIEditController (const Steinberg::TUID, void** obj) + { + *obj = nullptr; + return -1; + } + + /** This function may be used by implementations of queryInterface() + in the VST3's implementation of IAudioProcessor to return + additional supported interfaces. + */ + virtual int32_t queryIAudioProcessor (const Steinberg::TUID, void** obj) + { + *obj = nullptr; + return -1; + } + + /** This may be called by the VST3 wrapper when the host sets an + IComponentHandler for the plugin to use. + + You should not make any assumptions about how and when this will be + called - this function may not be called at all! + */ + virtual void setIComponentHandler (Steinberg::FUnknown*) {} + + /** This may be called shortly after the AudioProcessor is constructed + with the current IHostApplication. + + You should not make any assumptions about how and when this will be + called - this function may not be called at all! + */ + virtual void setIHostApplication (Steinberg::FUnknown*) {} +}; + +} // namespace juce diff --git a/modules/juce_audio_plugin_client/VST/juce_VSTCallbackHandler.h b/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h similarity index 92% rename from modules/juce_audio_plugin_client/VST/juce_VSTCallbackHandler.h rename to modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h index 8c61618600..ffc18bd298 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VSTCallbackHandler.h +++ b/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h @@ -29,6 +29,11 @@ namespace juce /** An interface to allow an AudioProcessor to send and receive VST specific calls from the host. + To use this class, ensure that your AudioProcessor publicly inherits + from VSTCallbackHandler. + + @see VST3ClientExtensions + @tags{Audio} */ struct VSTCallbackHandler