diff --git a/CMakeLists.txt b/CMakeLists.txt index d46c664f..195d2720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ option(CLAP_WRAPPER_BUILD_TESTS "Build test CLAP wrappers" OFF) project(clap-wrapper LANGUAGES C CXX - VERSION 0.9.1 + VERSION 0.9.2 DESCRIPTION "CLAP-as-X wrappers" ) set(CLAP_WRAPPER_VERSION "${CMAKE_PROJECT_VERSION}" CACHE STRING "Version of the wrapper project") diff --git a/cmake/wrap_vst3.cmake b/cmake/wrap_vst3.cmake index 8850fedf..2164bc84 100644 --- a/cmake/wrap_vst3.cmake +++ b/cmake/wrap_vst3.cmake @@ -17,6 +17,7 @@ function(private_add_vst3_wrapper_sources) ${sd}/src/wrapasvst3.h ${sd}/src/wrapasvst3.cpp ${sd}/src/wrapasvst3_entry.cpp + ${sd}/src/detail/ara/ara.h ${sd}/src/detail/vst3/parameter.h ${sd}/src/detail/vst3/parameter.cpp ${sd}/src/detail/vst3/plugview.h @@ -26,6 +27,7 @@ function(private_add_vst3_wrapper_sources) ${sd}/src/detail/vst3/process.cpp ${sd}/src/detail/vst3/categories.h ${sd}/src/detail/vst3/categories.cpp + ${sd}/src/detail/vst3/aravst3.h ) target_include_directories(${V3_TARGET}-clap-wrapper-vst3-lib PRIVATE "${sd}/include") diff --git a/src/clap_proxy.cpp b/src/clap_proxy.cpp index f0729057..b34f4c16 100644 --- a/src/clap_proxy.cpp +++ b/src/clap_proxy.cpp @@ -212,6 +212,7 @@ void Plugin::connectClap(const clap_plugin_t* clap) getExtension(_plugin, _ext._tail, CLAP_EXT_TAIL); getExtension(_plugin, _ext._gui, CLAP_EXT_GUI); getExtension(_plugin, _ext._timer, CLAP_EXT_TIMER_SUPPORT); + getExtension(_plugin, _ext._ara, CLAP_EXT_ARA_PLUGINEXTENSION); getExtension(_plugin, _ext._contextmenu, CLAP_EXT_CONTEXT_MENU); if (_ext._contextmenu == nullptr) diff --git a/src/clap_proxy.h b/src/clap_proxy.h index 2d6b3a52..37e5fd21 100644 --- a/src/clap_proxy.h +++ b/src/clap_proxy.h @@ -112,6 +112,7 @@ struct ClapPluginExtensions const clap_plugin_tail_t* _tail = nullptr; const clap_plugin_timer_support_t* _timer = nullptr; const clap_plugin_context_menu_t* _contextmenu = nullptr; + const clap_ara_plugin_extension_t* _ara = nullptr; #if LIN const clap_plugin_posix_fd_support* _posixfd = nullptr; #endif diff --git a/src/detail/ara/ara.h b/src/detail/ara/ara.h new file mode 100644 index 00000000..ef5c450e --- /dev/null +++ b/src/detail/ara/ara.h @@ -0,0 +1,109 @@ +#pragma once + +#include + +// ARA support for clap-wrapper +// +// the structs and identifiers are a copy from the ARA SDK (file ARA_API/ARACLAP.h from https://github.com/Celemony/ARA_API). +// ARA is Copyright (c) 2022-2024, Celemony Software GmbH, All Rights Reserved. +// +// The ARA SDK is available under Apache-2.0 license here: +// +// https://github.com/Celemony/ARA_SDK +// +// More information about ARA can be found here: https://vwww.celemony.com/ara +// +// the clap-wrapper itself does not require the ARA SDK because CLAP is a shell API only and +// just passes interface pointers through extensions between the foreign host and the ARA plugin core. +// +// do not use this file in your clap plugin, use the ARA SDK! +// + +//! Factory ID for retrieving the clap_ara_factory_t extension from clap_plugin_entry_t.get_factory() +static CLAP_CONSTEXPR const char CLAP_EXT_ARA_FACTORY[] = "org.ara-audio.ara.factory/2"; + +//! Extension ID for retrieving the clap_ara_plugin_extension_t from clap_plugin_t.get_extension() +static CLAP_CONSTEXPR const char CLAP_EXT_ARA_PLUGINEXTENSION[] = "org.ara-audio.ara.pluginextension/2"; + +//! Add this feature if your plugin supports ARA. +//! This allows hosts to detect ARA early on in the setup phase. +#define CLAP_PLUGIN_FEATURE_ARA_SUPPORTED "ara:supported" + +//! Add this feature if your plugin requires ARA to operate (will not work as normal insert plug-in). +//! This allows non-ARA CLAP hosts to suppress the plug-in since it cannot be used there. +#define CLAP_PLUGIN_FEATURE_ARA_REQUIRED "ara:required" + +// VST3 class category name +#define kARAMainFactoryClass "ARA Main Factory Class" + +#ifdef __cplusplus +extern "C" +{ +#endif + + // substitute types so we don't need to include the ARA SDK in full + typedef void *ARAFactoryPtr; + typedef void *ARAPlugInExtensionInstancePtr; + typedef void *ARADocumentControllerRef; + typedef int32_t ARAPlugInInstanceRoleFlags; + + /***************************************************************************************************/ + //! Extension interface to connect to ARA at CLAP factory level. + //! The host can pass CLAP_EXT_ARA_FACTORY to clap_plugin_entry_t.get_factory() to directly obtain an + //! ARAFactory, which allows for creating and maintaining the model independently of any clap_plugin_t + //! instances, enabling tasks such as automatic tempo detection or audio-to-MIDI conversion. + //! For rendering and editing the model however, there must be an associated clap_plugin_t provided in + //! the same binary - the descriptor for which is returned at the same index as the related ARAFactory. + + typedef struct clap_ara_factory + { + //! Get the number of ARA factories (i.e. ARA-capable plug.-ins) available. + //! Note that the regular clap_plugin_factory can contain more plug-ins if these do not support + //! ARA - make no assumption about items returned here being related to the items returned there + //! in terms of count or order. + uint32_t(CLAP_ABI *get_factory_count)(const struct clap_ara_factory *factory); + + //! Get the ARA factory for the plug-in at the given index. + //! The returned pointer must remain valid until clap_plugin_entry_t.deinit() is called. + //! The returned ARAFactory must be equal to the ARAFactory returned from instances of the + //! associated CLAP plug-in through their clap_ara_plugin_extension_t.get_factory(). + ARAFactoryPtr(CLAP_ABI *get_ara_factory)(const struct clap_ara_factory *factory, uint32_t index); + + //! Get the ID of the CLAP plug-in associated with the ARA factory for the given index. + //! The plug-in must be in the same binary. + //! The returned pointer must remain valid until clap_plugin_entry_t.deinit is called. + const char *(CLAP_ABI *get_plugin_id)(const struct clap_ara_factory *factory, uint32_t index); + } clap_ara_factory_t; + + /***************************************************************************************************/ + //! Extension interface to connect to ARA at CLAP plug-in level. + //! This interface provides access to the ARA specific extension of a CLAP plug-in. + //! Return an pointer to a clap_ara_plugin_extension_t when clap_plugin_t.get_extension() is called + //! with CLAP_EXT_ARA_PLUGINEXTENSION. + + typedef struct clap_ara_plugin_extension + { + //! Access the ARAFactory associated with this plug-in. + ARAFactoryPtr(CLAP_ABI *get_factory)(const clap_plugin_t *plugin); + + //! Bind the CLAP instance to an ARA document controller, switching it from "normal" operation + //! to ARA mode with the assigned roles, and exposing the ARA plug-in extension. + //! \p knownRoles encodes all roles that the host considered in its implementation and will explicitly + //! assign to some plug-in instance(s), while \p assignedRoles describes the roles that this specific + //! instance will fulfill. + //! This may be called only once during the lifetime of the CLAP plug-in, before the first call + //! to clap_plugin_t.activate() or clap_host_state_t.load() or other processing related extensions + //! or the creation of the GUI. + //! The ARA document controller must remain valid as long as the plug-in is in use - rendering, + //! showing its UI, etc. However, when tearing down the plug-in, the actual order for deleting + //! the clap_plugin_t instance and for deleting ARA document controller is undefined. + //! Plug-ins must handle both potential destruction orders to allow for a simpler reference + //! counting implementation on the host side. + ARAPlugInExtensionInstancePtr(CLAP_ABI *bind_to_document_controller)( + const clap_plugin_t *plugin, ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles); + } clap_ara_plugin_extension_t; + +#ifdef __cplusplus +} +#endif diff --git a/src/detail/clap/fsutil.cpp b/src/detail/clap/fsutil.cpp index c03fa613..a5b4cd0c 100644 --- a/src/detail/clap/fsutil.cpp +++ b/src/detail/clap/fsutil.cpp @@ -244,12 +244,16 @@ void Library::setupPluginsFromPluginEntry(const char *path) _pluginEntry->get_factory(CLAP_PLUGIN_FACTORY_INFO_VST3)); _pluginFactoryAUv2Info = static_cast( _pluginEntry->get_factory(CLAP_PLUGIN_FACTORY_INFO_AUV2)); + _pluginFactoryARAInfo = + static_cast(_pluginEntry->get_factory(CLAP_EXT_ARA_FACTORY)); // detect plugins that do not check the CLAP_PLUGIN_FACTORY_ID if ((void *)_pluginFactory == (void *)_pluginFactoryVst3Info) { + // in this case, don't trust anything from there _pluginFactoryVst3Info = nullptr; _pluginFactoryAUv2Info = nullptr; + _pluginFactoryARAInfo = nullptr; } auto count = _pluginFactory->get_plugin_count(_pluginFactory); diff --git a/src/detail/clap/fsutil.h b/src/detail/clap/fsutil.h index 77b016e7..5bf37987 100644 --- a/src/detail/clap/fsutil.h +++ b/src/detail/clap/fsutil.h @@ -32,6 +32,7 @@ namespace fs = std::filesystem; #include "clapwrapper/vst3.h" #include "clapwrapper/auv2.h" +#include "../ara/ara.h" #if MAC #include @@ -55,6 +56,7 @@ class Library const clap_plugin_factory_t* _pluginFactory = nullptr; const clap_plugin_factory_as_vst3* _pluginFactoryVst3Info = nullptr; const clap_plugin_factory_as_auv2* _pluginFactoryAUv2Info = nullptr; + const clap_ara_factory_t* _pluginFactoryARAInfo = nullptr; std::vector plugins; const clap_plugin_info_as_vst3_t* get_vst3_info(uint32_t index) const; diff --git a/src/detail/vst3/aravst3.h b/src/detail/vst3/aravst3.h new file mode 100644 index 00000000..68aaa7d0 --- /dev/null +++ b/src/detail/vst3/aravst3.h @@ -0,0 +1,112 @@ +#pragma once + +#include "../ara/ara.h" + +#include "pluginterfaces/base/funknown.h" +#include "pluginterfaces/base/falignpush.h" + +// these interfaces are copied from the ARA SDK to maintain compatibility +// without the need of the full ARA SDK +// +// the types handled here are just void* for the intercommunication between +// VST3 Host and CLAP plugin. + +#define ARA_DEPRECATED(x) +#define ARA_ADDENDUM(x) + +namespace ARA +{ +/***************************************************************************************************/ +//! Interface class to be implemented by an object provided by the VST3 factory. +//! The host can use the VST3 factory to directly obtain the ARA factory, which allows for creating +//! and maintaining the model independently of any IAudioProcessor instances, enabling tasks such as +//! automatic tempo detection or audio-to-MIDI conversion. +//! For rendering and editing the model however, there must be an associated IAudioProcessor class +//! provided in the same binary. +//! This match is usually trivial because there typically is only one such class in the binary, but +//! there are cases such as WaveShell where multiple plug-ins live in the same binary, and only a +//! subset of those plug-ins support ARA. In this scenario, the plug-in must use the same class name +//! for the matching pair of ARA::IMainFactory and IAudioProcessor classes - this enables the host +//! to quickly identify the matching pairs without having to create instances of all the +//! IAudioProcessor classes to query their IPlugInEntryPoint::getFactory ()->factoryID to perform +//! the matching. +class IMainFactory : public Steinberg::FUnknown +{ + public: + //! Get the ARA factory. + //! The returned pointer must remain valid throughout the lifetime of the object that provided it. + //! The returned ARAFactory must be equal to the ARAFactory provided by the associated + //! IAudioProcessor class through its IPlugInEntryPoint. + virtual ARAFactoryPtr PLUGIN_API getFactory() = 0; + virtual ~IMainFactory() + { + } + static const Steinberg::FUID iid; +}; + +//! Class category name for the ARA::IMainFactory. +#if !defined(kARAMainFactoryClass) +#define kARAMainFactoryClass "ARA Main Factory Class" +#endif + +DECLARE_CLASS_IID(IMainFactory, 0xDB2A1669, 0xFAFD42A5, 0xA82F864F, 0x7B6872EA) + +/***************************************************************************************************/ +//! Interface class to be implemented by the VST3 IAudioProcessor component (kVstAudioEffectClass). +class IPlugInEntryPoint : public Steinberg::FUnknown +{ + public: + //! Get the ARA factory. + //! The returned pointer must remain valid throughout the lifetime of the object that provided it. + //! The returned ARAFactory must be equal to the ARAFactory provided by the associated IMainFactory. + //! To prevent ambiguities, the name of the plug-in as stored in the PClassInfo.name of this + //! class must match the ARAFactory.plugInName returned here. + virtual ARAFactoryPtr PLUGIN_API getFactory() = 0; + + //! Bind the VST3 instance to an ARA document controller, switching it from "normal" operation + //! to ARA mode, and exposing the ARA plug-in extension. + //! Note that since ARA 2.0, this call has been deprecated and replaced with + //! bindToDocumentControllerWithRoles (). + //! This deprecated call is equivalent to the new call with no known roles set, however all + //! ARA 1.x hosts are in fact using all instances with playback renderer, edit renderer and + //! editor view role enabled, so plug-ins implementing ARA 1 backwards compatibility can + //! safely assume those three roles to be enabled if this call was made. + //! Same call order rules as bindToDocumentControllerWithRoles () apply. + ARA_DEPRECATED(2_0_Draft) + virtual ARAPlugInExtensionInstancePtr PLUGIN_API + bindToDocumentController(ARADocumentControllerRef documentControllerRef) = 0; + + static const Steinberg::FUID iid; +}; + +DECLARE_CLASS_IID(IPlugInEntryPoint, 0x12814E54, 0xA1CE4076, 0x82B96813, 0x16950BD6) + +//! ARA 2 extension of IPlugInEntryPoint, from the ARA SDK +class ARA_ADDENDUM(2_0_Draft) IPlugInEntryPoint2 : public Steinberg::FUnknown +{ + public: + //! Extended version of bindToDocumentController (): + //! bind the VST3 instance to an ARA document controller, switching it from "normal" operation + //! to ARA mode with the assigned roles, and exposing the ARA plug-in extension. + //! \p knownRoles encodes all roles that the host considered in its implementation and will explicitly + //! assign to some plug-in instance(s), while \p assignedRoles describes the roles that this specific + //! instance will fulfill. + //! This may be called only once during the lifetime of the IAudioProcessor component, before + //! the first call to setActive () or setState () or getProcessContextRequirements () or the + //! creation of the GUI (see IPlugView). + //! The ARA document controller must remain valid as long as the plug-in is in use - rendering, + //! showing its UI, etc. However, when tearing down the plug-in, the actual order for deleting + //! the IAudioProcessor instance and for deleting ARA document controller is undefined. + //! Plug-ins must handle both potential destruction orders to allow for a simpler reference + //! counting implementation on the host side. + virtual ARAPlugInExtensionInstancePtr PLUGIN_API bindToDocumentControllerWithRoles( + ARADocumentControllerRef documentControllerRef, ARAPlugInInstanceRoleFlags knownRoles, + ARAPlugInInstanceRoleFlags assignedRoles) = 0; + static const Steinberg::FUID iid; +}; + +DECLARE_CLASS_IID(IPlugInEntryPoint2, 0xCD9A5913, 0xC9EB46D7, 0x96CA53AD, 0xD1DB89F5) + +} // namespace ARA + +#include "pluginterfaces/base/falignpop.h" diff --git a/src/detail/vst3/categories.cpp b/src/detail/vst3/categories.cpp index 40fd431b..a0fd8d29 100644 --- a/src/detail/vst3/categories.cpp +++ b/src/detail/vst3/categories.cpp @@ -44,6 +44,7 @@ #include #include #include +#include "../ara/ara.h" using namespace Steinberg; using namespace Steinberg::Vst; @@ -101,6 +102,9 @@ static const struct _translate { CLAP_PLUGIN_FEATURE_MIXING , "Mixing"}, { CLAP_PLUGIN_FEATURE_MASTERING , "Mastering"}, + { CLAP_PLUGIN_FEATURE_ARA_SUPPORTED , "OnlyARA" }, + { CLAP_PLUGIN_FEATURE_ARA_REQUIRED , "OnlyARA" }, + { "external" , "External"}, {nullptr, nullptr} diff --git a/src/detail/vst3/process.cpp b/src/detail/vst3/process.cpp index 23f5ba89..2427afab 100644 --- a/src/detail/vst3/process.cpp +++ b/src/detail/vst3/process.cpp @@ -223,7 +223,10 @@ void ProcessAdapter::process(Steinberg::Vst::ProcessData& data) ; _transport.song_pos_beats = 0; - _transport.song_pos_seconds = 0; + + // samplerate and projectTimeSamples are always valid + _transport.song_pos_seconds = doubleToSecTime(_vstdata->processContext->projectTimeSamples / + _vstdata->processContext->sampleRate); if ((_vstdata->processContext->state & Vst::ProcessContext::kProjectTimeMusicValid)) { diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index 43b18f8f..9bf4dbd0 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -23,6 +23,9 @@ #define S16(x) u##x #endif +DEF_CLASS_IID(ARA::IPlugInEntryPoint) +DEF_CLASS_IID(ARA::IPlugInEntryPoint2) + #if 0 --- 8< --- struct ClapHostExtensions @@ -479,6 +482,37 @@ tresult ClapAsVst3::executeMenuItem(int32 tag) return kResultOk; } +ARAFactoryPtr PLUGIN_API ClapAsVst3::getFactory() +{ + LOGDETAIL("-> ARA::IPlugInEntryPoint::getFactory"); + if (_plugin->_ext._ara) + { + return _plugin->_ext._ara->get_factory(_plugin->_plugin); + } + return nullptr; +} + +ARAPlugInExtensionInstancePtr PLUGIN_API +ClapAsVst3::bindToDocumentController(ARADocumentControllerRef documentControllerRef) +{ + LOGDETAIL("-> ARA::IPlugInEntryPoint::bindToDocumentController (!!! DEPRECATED !!!)"); + // "call is deprecated in ARA 2, host must not call this" + return nullptr; +} + +ARAPlugInExtensionInstancePtr PLUGIN_API ClapAsVst3::bindToDocumentControllerWithRoles( + ARADocumentControllerRef documentControllerRef, ARAPlugInInstanceRoleFlags knownRoles, + ARAPlugInInstanceRoleFlags assignedRoles) +{ + LOGDETAIL("-> ARA::IPlugInEntryPoint2::bindToDocumentControllerWithRoles"); + if (_plugin->_ext._ara) + { + return _plugin->_ext._ara->bind_to_document_controller(_plugin->_plugin, documentControllerRef, + knownRoles, assignedRoles); + } + return nullptr; +} + static Vst::SpeakerArrangement speakerArrFromPortType(const char* port_type) { static const std::pair arrangementmap[] = { diff --git a/src/wrapasvst3.h b/src/wrapasvst3.h index 4ec78248..251d9f3b 100644 --- a/src/wrapasvst3.h +++ b/src/wrapasvst3.h @@ -34,6 +34,8 @@ #include "detail/vst3/plugview.h" #include "detail/clap/automation.h" #include "detail/shared/fixedqueue.h" +#include "detail/ara/ara.h" +#include "detail/vst3/aravst3.h" #include using namespace Steinberg; @@ -110,6 +112,8 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, public Steinberg::Vst::IMidiMapping, public Steinberg::Vst::INoteExpressionController, public Steinberg::Vst::IContextMenuTarget, + public ARA::IPlugInEntryPoint, + public ARA::IPlugInEntryPoint2, public Clap::IHost, public Clap::IAutomation, public os::IPlugObject @@ -123,6 +127,8 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, : super() , Steinberg::Vst::IMidiMapping() , Steinberg::Vst::INoteExpressionController() + , ARA::IPlugInEntryPoint() + , ARA::IPlugInEntryPoint2() , _library(lib) , _libraryIndex(number) , _creationcontext(context) @@ -206,6 +212,16 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, //---IContextMenuTarget ---------------------------------------------------------------- tresult PLUGIN_API executeMenuItem(int32 tag) override; + //----from ARA::IPlugInEntryPoint + ARAFactoryPtr PLUGIN_API getFactory() override; + ARAPlugInExtensionInstancePtr PLUGIN_API + bindToDocumentController(ARADocumentControllerRef documentControllerRef) override; + + //----from ARA::IPlugInEntryPoint2--------------------------- + ARAPlugInExtensionInstancePtr PLUGIN_API bindToDocumentControllerWithRoles( + ARADocumentControllerRef documentControllerRef, ARAPlugInInstanceRoleFlags knownRoles, + ARAPlugInInstanceRoleFlags assignedRoles) override; + //---Interface-------------------------------------------------------------------------- OBJ_METHODS(ClapAsVst3, SingleComponentEffect) DEFINE_INTERFACES @@ -225,6 +241,21 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, DEF_INTERFACE(IContextMenuTarget); } } + if (::Steinberg::FUnknownPrivate::iidEqual(iid, IPlugInEntryPoint::iid)) + { + if (_plugin->_ext._ara) + { + DEF_INTERFACE(IPlugInEntryPoint) + } + } + if (::Steinberg::FUnknownPrivate::iidEqual(iid, IPlugInEntryPoint2::iid)) + { + if (_plugin->_ext._ara) + { + DEF_INTERFACE(IPlugInEntryPoint2) + } + } + // add any other interfaces here: //if (::Steinberg::FUnknownPrivate::iidEqual(iid, IExampleSomething::iid)) //{ diff --git a/src/wrapasvst3_entry.cpp b/src/wrapasvst3_entry.cpp index c057ea62..8f8bfdb2 100644 --- a/src/wrapasvst3_entry.cpp +++ b/src/wrapasvst3_entry.cpp @@ -132,7 +132,7 @@ IPluginFactory* GetPluginFactoryEntryPoint() // static IPtr gPluginFactory = nullptr; static Clap::Library gClapLibrary; - static std::vector gCreationContexts; + static std::vector> gCreationContexts; // if there is no ClapLibrary yet if (!gClapLibrary._pluginFactory) @@ -275,15 +275,56 @@ IPluginFactory* GetPluginFactoryEntryPoint() LOGDETAIL("plugin id: {} -> {}", clapdescr->id, x); } #endif + auto ptr = std::make_shared(); + *ptr = {&gClapLibrary, ctr, + PClassInfo2( + lcid, PClassInfo::kManyInstances, kVstAudioEffectClass, plugname, + 0 /* the only flag is usually Vst:kDistributable, but CLAPs aren't distributable */, + features.c_str(), pluginvendor, clapdescr->version, kVstVersionString)}; + gCreationContexts.push_back(ptr); + gPluginFactory->registerClass(&gCreationContexts.back()->classinfo, ClapAsVst3::createInstance, + gCreationContexts.back().get()); + } - gCreationContexts.push_back( - {&gClapLibrary, ctr, - PClassInfo2( - lcid, PClassInfo::kManyInstances, kVstAudioEffectClass, plugname, - 0 /* the only flag is usually Vst:kDistributable, but CLAPs aren't distributable */, - features.c_str(), pluginvendor, clapdescr->version, kVstVersionString)}); - gPluginFactory->registerClass(&gCreationContexts.back().classinfo, ClapAsVst3::createInstance, - &gCreationContexts.back()); + if (gClapLibrary._pluginFactoryARAInfo) + { + LOGINFO("creating ARA companion factories"); + auto factory = gClapLibrary._pluginFactoryARAInfo; + auto count = factory->get_factory_count(factory); + for (decltype(count) i = 0; i < count; ++i) + { + auto matching_plugin = factory->get_plugin_id(factory, i); + LOGDETAIL("number of ARA plugins: {}", numPlugins); + for (int ctr = 0; ctr < numPlugins; ++ctr) + { + auto& clapdescr = gClapLibrary.plugins[ctr]; + if (!strcmp(clapdescr->id, matching_plugin)) + { + std::string extended_id(matching_plugin); + extended_id.append("-ARA"); + auto g = Crypto::create_sha1_guid_from_name(extended_id.c_str(), extended_id.size()); + TUID lcid; + memcpy(&lcid, &g, sizeof(TUID)); + + std::string n(clapdescr->name); +#ifdef _DEBUG + n.append(" (CLAP->VST3)"); +#endif + auto plugname = n.c_str(); // clapdescr->name; + auto ptr = std::make_shared(); + *ptr = {&gClapLibrary, (int)i, + PClassInfo2(lcid, PClassInfo::kManyInstances, kARAMainFactoryClass, plugname, 0, + "", /* not used in this context */ + "", /* not used in this context */ + clapdescr->version, kVstVersionString)}; + gCreationContexts.push_back(ptr); + gPluginFactory->registerClass(&gCreationContexts.back()->classinfo, + ClapAsVst3::createInstance, gCreationContexts.back().get()); + + break; + } + } + } } } else @@ -292,6 +333,34 @@ IPluginFactory* GetPluginFactoryEntryPoint() return gPluginFactory; } +class ARAMainFactory : public ARA::IMainFactory +{ + public: + ARAMainFactory(ARAFactoryPtr factory, Steinberg::FUID uid) + : ARA::IMainFactory() + , _arafactory(factory) + , _uid(uid){FUNKNOWN_CTOR} ARAFactoryPtr PLUGIN_API getFactory() override + { + return _arafactory; + } + //---Interface-------------------------------------------------------------------------- + DECLARE_FUNKNOWN_METHODS + + // Class ID + const Steinberg::FUID getClassFUID() + { + return _uid; + } + + private: + ARAFactoryPtr _arafactory = nullptr; + Steinberg::FUID _uid; +}; + +IMPLEMENT_FUNKNOWN_METHODS(ARAMainFactory, ARA::IMainFactory, ARA::IMainFactory::iid) + +DEF_CLASS_IID(ARA::IMainFactory) + /* creates an Instance from the creationContext. actually, there is always a valid entrypoint, otherwise no factory would have been provided. @@ -300,11 +369,26 @@ FUnknown* ClapAsVst3::createInstance(void* context) { auto ctx = static_cast(context); - LOGINFO("creating plugin {} (#{})", ctx->classinfo.name, ctx->index); - if (ctx->lib->hasEntryPoint()) + if (!strcmp(ctx->classinfo.category, kVstAudioEffectClass)) { - // MessageBoxA(NULL, "halt", "create", MB_OK); - return (IAudioProcessor*)new ClapAsVst3(ctx->lib, ctx->index, context); + LOGINFO("creating plugin {} (#{})", ctx->classinfo.name, ctx->index); + if (ctx->lib->hasEntryPoint()) + { + // MessageBoxA(NULL, "create ClapAsVst3", "create", MB_OK); + return (IAudioProcessor*)new ClapAsVst3(ctx->lib, ctx->index, context); + } } + + if (!strcmp(ctx->classinfo.category, kARAMainFactoryClass)) + { + LOGINFO("creating ARAMainFactory {} (#{})", ctx->classinfo.name, ctx->index); + if (ctx->lib->hasEntryPoint()) + { + const auto ara_factory = ctx->lib->_pluginFactoryARAInfo; + return static_cast(new ARAMainFactory( + ara_factory->get_ara_factory(ara_factory, ctx->index), Steinberg::FUID(ctx->classinfo.cid))); + } + } + return nullptr; // this should never happen. }