1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

AudioProcessor: Add extensions API for VST3 clients

This commit is contained in:
reuk 2021-06-09 18:35:09 +01:00
parent 442369bd6b
commit 63a40188d9
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
7 changed files with 325 additions and 76 deletions

View file

@ -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<Steinberg::Linux::IEventHandler>{}).extract (obj);
}
void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override
@ -280,6 +277,45 @@ private:
ThreadLocalValue<bool>& toSet;
};
template <typename Member>
static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor,
const TUID targetIID,
Member&& member)
{
if (processor == nullptr)
return {};
void* obj = nullptr;
if (auto* extensions = dynamic_cast<VST3ClientExtensions*> (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<FObject>{},
UniqueBase<JuceVST3EditController>{},
UniqueBase<Vst::IEditController>{},
UniqueBase<Vst::IEditController2>{},
UniqueBase<Vst::IConnectionPoint>{},
UniqueBase<Vst::IMidiMapping>{},
UniqueBase<Vst::IUnitInfo>{},
UniqueBase<Vst::ChannelContext::IInfoListener>{},
SharedBase<IPluginBase, Vst::IEditController>{},
UniqueBase<IDependent>{},
SharedBase<FUnknown, Vst::IEditController>{});
if (result.isOk())
return result;
if (doUIDsMatch (targetIID, JuceAudioProcessor::iid))
return { kResultOk, audioProcessor.get() };
return {};
}
void installAudioProcessor (const VSTComSmartPtr<JuceAudioProcessor>& newAudioProcessor)
{
audioProcessor = newAudioProcessor;
if (auto* extensions = dynamic_cast<VST3ClientExtensions*> (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<IPlugViewContentScaleSupport>{});
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<IPluginBase>{},
UniqueBase<JuceVST3Component>{},
UniqueBase<Vst::IComponent>{},
UniqueBase<Vst::IAudioProcessor>{},
UniqueBase<Vst::IUnitInfo>{},
UniqueBase<Vst::IConnectionPoint>{},
SharedBase<FUnknown, Vst::IComponent>{});
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 <typename... Args>
@ -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<IPluginFactory3>{},
UniqueBase<IPluginFactory2>{},
UniqueBase<IPluginFactory>{},
UniqueBase<FUnknown>{});
if (result.isOk())
return result.extract (obj);
jassertfalse; // Something new?
*obj = nullptr;

View file

@ -127,4 +127,3 @@
#endif
#include "utility/juce_CreatePluginFilter.h"
#include "VST/juce_VSTCallbackHandler.h"

View file

@ -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<ClassType*> (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<SourceClassType*> (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 <typename Ptr>
InterfaceResultWithDeferredAddRef (Steinberg::tresult resultIn, Ptr* ptrIn)
: result (resultIn, ptrIn),
addRefFn (doAddRef<Ptr>) {}
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 <typename Ptr>
static void doAddRef (void* obj) { static_cast<Ptr*> (obj)->addRef(); }
QueryInterfaceResult result;
void (*addRefFn) (void*) = nullptr;
};
template <typename ClassType> struct UniqueBase {};
template <typename CommonClassType, typename SourceClassType> struct SharedBase {};
template <typename ToTest, typename CommonClassType, typename SourceClassType>
InterfaceResultWithDeferredAddRef testFor (ToTest& toTest,
const Steinberg::TUID targetIID,
SharedBase<CommonClassType, SourceClassType>)
{
if (! doUIDsMatch (targetIID, CommonClassType::iid))
return {};
return { Steinberg::kResultOk, static_cast<CommonClassType*> (static_cast<SourceClassType*> (std::addressof (toTest))) };
}
template <typename ToTest, typename ClassType>
InterfaceResultWithDeferredAddRef testFor (ToTest& toTest,
const Steinberg::TUID targetIID,
UniqueBase<ClassType>)
{
return testFor (toTest, targetIID, SharedBase<ClassType, ClassType>{});
}
template <typename ToTest>
InterfaceResultWithDeferredAddRef testForMultiple (ToTest&, const Steinberg::TUID) { return {}; }
template <typename ToTest, typename Head, typename... Tail>
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

View file

@ -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<Vst::IComponentHandler>{},
UniqueBase<Vst::IComponentHandler2>{},
UniqueBase<Vst::IComponentHandler3>{},
UniqueBase<Vst::IContextMenuTarget>{},
UniqueBase<Vst::IHostApplication>{},
UniqueBase<Vst::IUnitHandler>{},
SharedBase<FUnknown, Vst::IComponentHandler>{}).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<Vst::IAttributeList>{},
SharedBase<FUnknown, Vst::IAttributeList>{}).extract (obj);
}
tresult PLUGIN_API setInt (AttrID, Steinberg::int64) override { return kOutOfMemory; }

View file

@ -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"

View file

@ -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

View file

@ -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