diff --git a/docs/vst3.md b/docs/vst3.md index a5ef5413..df08998b 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -7,6 +7,8 @@ TODO: Link to `src/common/serialization/vst3/README.md` TODO: Mention the new `Ya::supports()` mechanism for monolithic interfaces through multiple inheritance +TODO: Explain the monolith. + The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a dynamic casting interface through `queryInterface()` and a reference counting diff --git a/meson.build b/meson.build index ef56acd9..99c9644e 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', + 'src/common/serialization/vst3/plugin-monolith.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', @@ -126,6 +127,7 @@ if with_vst3 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', + 'src/common/serialization/vst3/plugin-monolith.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 18229a1f..51140247 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -37,7 +37,30 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetBusArrangements& request) { + const YaPluginMonolith::Construct&) { + log_request_base(is_host_vst, [&](auto& message) { + // TODO: Log the CID on verbosity level 2, and then also report all CIDs + // in the plugin factory + // TODO: When adding the enum class for instantiating different types, + // make sure to reflect those in the constructor and destructor + // logging + message << "IPluginFactory::createComponent(cid = ..., _iid = " + "IComponent::iid, " + "&obj)"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginMonolith::Destruct& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::~IComponent()"; + }); +} + +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::SetBusArrangements& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::setBusArrangements(inputs = [SpeakerArrangement; " @@ -48,7 +71,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetBusArrangement& request) { + const YaAudioProcessor::GetBusArrangement& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getBusArrangement(dir = " << request.dir @@ -57,7 +80,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::CanProcessSampleSize& request) { + const YaAudioProcessor::CanProcessSampleSize& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::canProcessSampleSize(symbolicSampleSize = " @@ -65,8 +88,9 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetLatencySamples& request) { +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::GetLatencySamples& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getLatencySamples()"; @@ -74,7 +98,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetupProcessing& request) { + const YaAudioProcessor::SetupProcessing& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::setupProcessing(setup = ::setProcessing(state = " @@ -95,7 +119,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Process& request) { + const YaAudioProcessor::Process& request) { // TODO: Only log this on log level 2 log_request_base(is_host_vst, [&](auto& message) { // TODO: Log about the process data @@ -105,31 +129,13 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetTailSamples& request) { + const YaAudioProcessor::GetTailSamples& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getTailSamples()"; }); } -void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { - log_request_base(is_host_vst, [&](auto& message) { - // TODO: Log the CID on verbosity level 2, and then also report all CIDs - // in the plugin factory - message << "IPluginFactory::createComponent(cid = ..., _iid = " - "IComponent::iid, " - "&obj)"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Destruct& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::~IComponent()"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -246,6 +252,26 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } +void Vst3Logger::log_response(bool is_host_vst, const Ack&) { + log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const std::variant& + result) { + log_response_base(is_host_vst, [&](auto& message) { + std::visit(overload{[&](const YaPluginMonolith::ConstructArgs& args) { + message << ""; + }, + [&](const UniversalTResult& code) { + message << code.string(); + }}, + result); + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { @@ -268,25 +294,6 @@ void Vst3Logger::log_response( }); } -void Vst3Logger::log_response(bool is_host_vst, const Ack&) { - log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); -} - -void Vst3Logger::log_response( - bool is_host_vst, - const std::variant& result) { - log_response_base(is_host_vst, [&](auto& message) { - std::visit(overload{[&](const YaComponent::ConstructArgs& args) { - message << ""; - }, - [&](const UniversalTResult& code) { - message << code.string(); - }}, - result); - }); -} - void Vst3Logger::log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 53e92a65..a45b2e41 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -56,6 +56,8 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + void log_request(bool is_host_vst, const YaPluginMonolith::Construct&); + void log_request(bool is_host_vst, const YaPluginMonolith::Destruct&); void log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); void log_request(bool is_host_vst, @@ -69,8 +71,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); void log_request(bool is_host_vst, const YaAudioProcessor::Process&); void log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); - void log_request(bool is_host_vst, const YaComponent::Construct&); - void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); @@ -86,13 +86,13 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsConfiguration&); void log_response(bool is_host_vst, const Ack&); + void log_response( + bool is_host_vst, + const std::variant&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::ProcessResponse&); - void log_response( - bool is_host_vst, - const std::variant&); void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 304899fd..d8aa9a54 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,8 +23,8 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" -#include "vst3/component.h" #include "vst3/plugin-factory.h" +#include "vst3/plugin-monolith.h" // Event handling for our VST3 plugins works slightly different from how we // handle VST2 plugins. VST3 does not have a centralized event dispatching @@ -57,8 +57,16 @@ struct WantsConfiguration { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant component) + Steinberg::IPtr object) : supported( - Steinberg::FUnknownPtr(component)) {} + Steinberg::FUnknownPtr(object)) {} YaAudioProcessor::YaAudioProcessor(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/audio-processor.h b/src/common/serialization/vst3/audio-processor.h index 140c014a..f3874fd7 100644 --- a/src/common/serialization/vst3/audio-processor.h +++ b/src/common/serialization/vst3/audio-processor.h @@ -29,7 +29,7 @@ /** * Wraps around `IAudioProcessor` for serialization purposes. This is - * instantiated as part of `YaComponent`. + * instantiated as part of `YaPluginMonolith`. */ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { public: @@ -40,8 +40,8 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { ConstructArgs(); /** - * Check whether an existing implementation implements `IPluginBase` and - * read arguments from it. + * Check whether an existing implementation implements `IAudioProcessor` + * and read arguments from it. */ ConstructArgs(Steinberg::IPtr object); diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 1ae2c548..5d121d66 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -19,57 +19,22 @@ YaComponent::ConstructArgs::ConstructArgs() {} YaComponent::ConstructArgs::ConstructArgs( - Steinberg::IPtr component, - size_t instance_id) - : instance_id(instance_id), - audio_processor_args(component), - plugin_base_args(component) { - // `IComponent::getControllerClassId` - Steinberg::TUID cid; - if (component->getControllerClassId(cid) == Steinberg::kResultOk) { - edit_controller_cid = std::to_array(cid); + Steinberg::IPtr object) { + auto component = Steinberg::FUnknownPtr(object); + + if (component) { + supported = true; + + // `IComponent::getControllerClassId` + Steinberg::TUID cid; + if (component->getControllerClassId(cid) == Steinberg::kResultOk) { + edit_controller_cid = std::to_array(cid); + } } } YaComponent::YaComponent(const ConstructArgs&& args) - : YaAudioProcessor(std::move(args.audio_processor_args)), - YaPluginBase(std::move(args.plugin_base_args)), - arguments(std::move(args)){FUNKNOWN_CTOR} - - YaComponent::~YaComponent() { - FUNKNOWN_DTOR -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" -IMPLEMENT_REFCOUNT(YaComponent) -#pragma GCC diagnostic pop - -tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, - void** obj) { - QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, - Steinberg::Vst::IComponent) - if (YaPluginBase::supported()) { - // We had to expand the macro here because we need to cast through - // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` - if (Steinberg::FUnknownPrivate ::iidEqual( - _iid, Steinberg::IPluginBase::iid)) { - addRef(); - *obj = static_cast( - static_cast(this)); - return ::Steinberg ::kResultOk; - } - } - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, - Steinberg::Vst::IComponent) - if (YaAudioProcessor::supported()) { - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, - Steinberg::Vst::IAudioProcessor) - } - - *obj = nullptr; - return Steinberg::kNoInterface; -} + : arguments(std::move(args)) {} tresult PLUGIN_API YaComponent::getControllerClassId(Steinberg::TUID classId) { if (arguments.edit_controller_cid) { diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 09c4f714..f0579990 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -16,68 +16,41 @@ #pragma once -#include -#include -#include - -#include #include -#include -#include #include #include #include "../../bitsery/ext/vst3.h" #include "../common.h" -#include "audio-processor.h" #include "base.h" -#include "host-application.h" -#include "plugin-base.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IComponent` for serialization purposes. See `README.md` for - * more information on how this works. On the Wine plugin host side this is only - * used for serialization, and on the plugin side have an implementation that - * can send control messages. - * - * This implements all interfaces that an `IComponent` might also implement. - * - * We might be able to do some caching here with the buss infos, but since that - * sounds like a huge potential source of errors we'll just do pure callbacks - * for everything other than the edit controller's class ID. - * - * TODO: Rework this into `YaPluginMonolith` - * TODO: Eventually this should (optionally) implement everything supported by - * the SDK's `AudioEffect` component. + * Wraps around `IComponent` for serialization purposes. This is instantiated as + * part of `YaPluginMonolith`. Event though `IComponent` inherits from + * `IPlguinBase`, we'll implement that separately in `YaPluginBase` because + * `IEditController` also inherits from `IPluginBase`. */ -class YaComponent : public Steinberg::Vst::IComponent, - public YaAudioProcessor, - public YaPluginBase { +class YaComponent : public Steinberg::Vst::IComponent { public: /** - * These are the arguments for creating a `YaComponentPluginImpl`. + * These are the arguments for creating a `YaComponent`. */ struct ConstructArgs { ConstructArgs(); /** - * Read arguments from an existing implementation. Depending on the - * supported interface function more or less of this struct will be left - * empty, and `known_iids` will be set accordingly. + * Check whether an existing implementation implements `IComponent` and + * read arguments from it. */ - ConstructArgs(Steinberg::IPtr component, - size_t instance_id); + ConstructArgs(Steinberg::IPtr object); /** - * The unique identifier for this specific instance. + * Whether the object supported this interface. */ - native_size_t instance_id; - - YaAudioProcessor::ConstructArgs audio_processor_args; - YaPluginBase::ConstructArgs plugin_base_args; + bool supported; /** * The class ID of this component's corresponding editor controller. You @@ -87,59 +60,19 @@ class YaComponent : public Steinberg::Vst::IComponent, template void serialize(S& s) { - s.value8b(instance_id); - s.object(audio_processor_args); - s.object(plugin_base_args); + s.value1b(supported); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } }; - /** - * Message to request the Wine plugin host to instantiate a new IComponent - * to pass through a call to `IComponent::createInstance(cid, - * IComponent::iid, ...)`. - */ - struct Construct { - using Response = std::variant; - - ArrayUID cid; - - template - void serialize(S& s) { - s.container1b(cid); - } - }; - /** * Instantiate this instance with arguments read from another interface * implementation. */ YaComponent(const ConstructArgs&& args); - /** - * Message to request the Wine plugin host to destroy the IComponent - * instance with the given instance ID. Sent from the destructor of - * `YaComponentPluginImpl`. - */ - struct Destruct { - using Response = Ack; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - /** - * @remark The plugin side implementation should send a control message to - * clean up the instance on the Wine side in its destructor. - */ - virtual ~YaComponent() = 0; - - DECLARE_FUNKNOWN_METHODS + inline bool supported() { return arguments.supported; } tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; @@ -381,10 +314,3 @@ class YaComponent : public Steinberg::Vst::IComponent, }; #pragma GCC diagnostic pop - -template -void serialize( - S& s, - std::variant& result) { - s.ext(result, bitsery::ext::StdVariant{}); -} diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 80845748..f2a9056b 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -34,6 +34,9 @@ * application context passed during `IPluginBase::intialize()` as well as for * `IPluginFactory3::setHostContext()`. This interface is thus implemented on * both the native plugin side as well as the Wine plugin host side. + * + * TODO: When implementing more host interfaces, also rework this into a + * monolith class like with the plugin. */ class YaHostApplication : public Steinberg::Vst::IHostApplication { public: diff --git a/src/common/serialization/vst3/plugin-base.cpp b/src/common/serialization/vst3/plugin-base.cpp index f595b6ab..e2abb0fd 100644 --- a/src/common/serialization/vst3/plugin-base.cpp +++ b/src/common/serialization/vst3/plugin-base.cpp @@ -19,8 +19,8 @@ YaPluginBase::ConstructArgs::ConstructArgs() {} YaPluginBase::ConstructArgs::ConstructArgs( - Steinberg::IPtr component) - : supported(Steinberg::FUnknownPtr(component)) {} + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} YaPluginBase::YaPluginBase(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin-base.h b/src/common/serialization/vst3/plugin-base.h index 503fe2f4..a4d7e556 100644 --- a/src/common/serialization/vst3/plugin-base.h +++ b/src/common/serialization/vst3/plugin-base.h @@ -29,7 +29,7 @@ /** * Wraps around `IPluginBase` for serialization purposes. Both components and * edit controllers inherit from this. This is instantiated as part of - * `YaComponent` or `YaEditController`. + * `YaPluginMonolith`. */ class YaPluginBase : public Steinberg::IPluginBase { public: diff --git a/src/common/serialization/vst3/plugin-monolith.cpp b/src/common/serialization/vst3/plugin-monolith.cpp new file mode 100644 index 00000000..814185a3 --- /dev/null +++ b/src/common/serialization/vst3/plugin-monolith.cpp @@ -0,0 +1,75 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plugin-monolith.h" + +YaPluginMonolith::ConstructArgs::ConstructArgs() {} + +YaPluginMonolith::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t instance_id) + : instance_id(instance_id), + audio_processor_args(object), + component_args(object), + plugin_base_args(object) {} + +YaPluginMonolith::YaPluginMonolith(const ConstructArgs&& args) + : YaAudioProcessor(std::move(args.audio_processor_args)), + YaComponent(std::move(args.component_args)), + YaPluginBase(std::move(args.plugin_base_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + YaPluginMonolith::~YaPluginMonolith() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaPluginMonolith) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaPluginMonolith::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaPluginBase::supported()) { + // We had to expand the macro here because we need to cast through + // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` + if (Steinberg::FUnknownPrivate ::iidEqual(_iid, + Steinberg::FUnknown::iid)) { + addRef(); + *obj = static_cast( + static_cast(this)); + return ::Steinberg ::kResultOk; + } + if (Steinberg::FUnknownPrivate ::iidEqual( + _iid, Steinberg::IPluginBase::iid)) { + addRef(); + *obj = static_cast( + static_cast(this)); + return ::Steinberg ::kResultOk; + } + } + if (YaComponent::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + } + if (YaAudioProcessor::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, + Steinberg::Vst::IAudioProcessor) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/plugin-monolith.h b/src/common/serialization/vst3/plugin-monolith.h new file mode 100644 index 00000000..dc94f142 --- /dev/null +++ b/src/common/serialization/vst3/plugin-monolith.h @@ -0,0 +1,151 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../common.h" +#include "audio-processor.h" +#include "base.h" +#include "component.h" +#include "host-application.h" +#include "plugin-base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that optionally implements all VST3 interfaces a plugin + * object could implement. A more in depth explanation can be found in + * `docs/vst3.md`, but the way this works is that we begin with an `FUnknown` + * pointer from the Windows VST3 plugin obtained by a call to + * `IPluginFactory::createInstance()` (with an interface decided by the host). + * We then go through all the plugin interfaces and check whether that object + * supports them one by one. For each supported interface we remember that the + * plugin supports it, and we'll optionally write down some static data (such as + * the edit controller cid) that can't change over the lifetime of the + * application. On the plugin side we then return a `YaPluginMonolith` + * implementation that contains all of this information about interfaces the + * object we're proxying might support. This way we can allow casts to all of + * those object types in `queryInterface()`, essentially perfectly mimicing the + * original object. + * + * This monolith approach is also important when it comes to `IConnectionPoint`. + * The host should be able to connect arbitrary objects together, and the plugin + * can then use the query interface smart pointer casting system to cast those + * objects to the types they want. By having a huge monolithic class that + * implements any interface such an object might also implement, we can allow + * perfect proxying behaviour for connecting components. + */ +class YaPluginMonolith : public YaAudioProcessor, + public YaComponent, + public YaPluginBase { + public: + /** + * These are the arguments for creating a `YaPluginMonolithImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, size_t instance_id); + + /** + * The unique identifier for this specific object instance. + */ + native_size_t instance_id; + + YaAudioProcessor::ConstructArgs audio_processor_args; + YaComponent::ConstructArgs component_args; + YaPluginBase::ConstructArgs plugin_base_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(audio_processor_args); + s.object(component_args); + s.object(plugin_base_args); + } + }; + + /** + * Message to request the Wine plugin host to instantiate a new IComponent + * to pass through a call to `IComponent::createInstance(cid, + * IComponent::iid, ...)`. + */ + struct Construct { + using Response = std::variant; + + ArrayUID cid; + + // TODO: Add an enum class to reify the type of object we want to + // instantiate so we can initialize things other than + // `IComponent`, like `IEditController.` + + template + void serialize(S& s) { + s.container1b(cid); + } + }; + + /** + * Instantiate this object instance with arguments read from another + * interface implementation. + */ + YaPluginMonolith(const ConstructArgs&& args); + + /** + * Message to request the Wine plugin host to destroy this object instance + * with the given instance ID. Sent from the destructor of + * `YaPluginMonolithImpl`. This will cause all smart pointers to the actual + * object in the Wine plugin host to be dropped. + */ + struct Destruct { + using Response = Ack; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + /** + * @remark The plugin side implementation should send a control message to + * clean up the instance on the Wine side in its destructor. + */ + virtual ~YaPluginMonolith() = 0; + + DECLARE_FUNKNOWN_METHODS + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop + +template +void serialize( + S& s, + std::variant& result) { + s.ext(result, bitsery::ext::StdVariant{}); +} diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 92329b35..8f802e5a 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -16,22 +16,23 @@ #include "component.h" -YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::ConstructArgs&& args) - : YaComponent(std::move(args)), bridge(bridge) { +YaPluginMonolithImpl::YaPluginMonolithImpl( + Vst3PluginBridge& bridge, + YaPluginMonolith::ConstructArgs&& args) + : YaPluginMonolith(std::move(args)), bridge(bridge) { bridge.register_component(arguments.instance_id, *this); } -YaComponentPluginImpl::~YaComponentPluginImpl() { +YaPluginMonolithImpl::~YaPluginMonolithImpl() { bridge.send_message( - YaComponent::Destruct{.instance_id = arguments.instance_id}); + YaPluginMonolith::Destruct{.instance_id = arguments.instance_id}); bridge.unregister_component(arguments.instance_id); } tresult PLUGIN_API -YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { +YaPluginMonolithImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Successful queries should also be logged - const tresult result = YaComponent::queryInterface(_iid, obj); + const tresult result = YaPluginMonolith::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { bridge.logger.log_unknown_interface("In IComponent::queryInterface()", Steinberg::FUID::fromTUID(_iid)); @@ -40,7 +41,7 @@ YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( +tresult PLUGIN_API YaPluginMonolithImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, @@ -57,7 +58,7 @@ tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( }); } -tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( +tresult PLUGIN_API YaPluginMonolithImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { @@ -74,30 +75,30 @@ tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( } tresult PLUGIN_API -YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { +YaPluginMonolithImpl::canProcessSampleSize(int32 symbolicSampleSize) { return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ .instance_id = arguments.instance_id, .symbolic_sample_size = symbolicSampleSize}); } -uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { +uint32 PLUGIN_API YaPluginMonolithImpl::getLatencySamples() { return bridge.send_message(YaAudioProcessor::GetLatencySamples{ .instance_id = arguments.instance_id}); } tresult PLUGIN_API -YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { +YaPluginMonolithImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { return bridge.send_message(YaAudioProcessor::SetupProcessing{ .instance_id = arguments.instance_id, .setup = setup}); } -tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { +tresult PLUGIN_API YaPluginMonolithImpl::setProcessing(TBool state) { return bridge.send_message(YaAudioProcessor::SetProcessing{ .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API -YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { +YaPluginMonolithImpl::process(Steinberg::Vst::ProcessData& data) { ProcessResponse response = bridge.send_message(YaAudioProcessor::Process{ .instance_id = arguments.instance_id, .data = data}); @@ -106,29 +107,29 @@ YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { return response.result; } -uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { +uint32 PLUGIN_API YaPluginMonolithImpl::getTailSamples() { return bridge.send_message( YaAudioProcessor::GetTailSamples{.instance_id = arguments.instance_id}); } tresult PLUGIN_API -YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { +YaPluginMonolithImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ .instance_id = arguments.instance_id, .mode = mode}); } int32 PLUGIN_API -YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir) { +YaPluginMonolithImpl::getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) { return bridge.send_message(YaComponent::GetBusCount{ .instance_id = arguments.instance_id, .type = type, .dir = dir}); } tresult PLUGIN_API -YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::BusInfo& bus /*out*/) { +YaPluginMonolithImpl::getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) { const GetBusInfoResponse response = bridge.send_message( YaComponent::GetBusInfo{.instance_id = arguments.instance_id, .type = type, @@ -140,7 +141,7 @@ YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, return response.result; } -tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( +tresult PLUGIN_API YaPluginMonolithImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { const GetRoutingInfoResponse response = bridge.send_message( @@ -154,10 +155,10 @@ tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( } tresult PLUGIN_API -YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - TBool state) { +YaPluginMonolithImpl::activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) { return bridge.send_message( YaComponent::ActivateBus{.instance_id = arguments.instance_id, .type = type, @@ -166,17 +167,17 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { +tresult PLUGIN_API YaPluginMonolithImpl::setActive(TBool state) { return bridge.send_message(YaComponent::SetActive{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { +tresult PLUGIN_API YaPluginMonolithImpl::setState(Steinberg::IBStream* state) { return bridge.send_message(YaComponent::SetState{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { +tresult PLUGIN_API YaPluginMonolithImpl::getState(Steinberg::IBStream* state) { const GetStateResponse response = bridge.send_message( YaComponent::GetState{.instance_id = arguments.instance_id}); @@ -185,7 +186,7 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { return response.result; } -tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { +tresult PLUGIN_API YaPluginMonolithImpl::initialize(FUnknown* context) { // This `context` will likely be an `IHostApplication`. If it is, we will // store it here, and we'll proxy through all calls to it made from the Wine // side. Otherwise we'll still call `IPluginBase::initialize()` but with a @@ -209,7 +210,7 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { std::move(host_application_context_args)}); } -tresult PLUGIN_API YaComponentPluginImpl::terminate() { +tresult PLUGIN_API YaPluginMonolithImpl::terminate() { return bridge.send_message( YaPluginBase::Terminate{.instance_id = arguments.instance_id}); } diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 8d2a293c..03ecdbd7 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -20,17 +20,17 @@ #include "../vst3.h" -class YaComponentPluginImpl : public YaComponent { +class YaPluginMonolithImpl : public YaPluginMonolith { public: - YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::ConstructArgs&& args); + YaPluginMonolithImpl(Vst3PluginBridge& bridge, + YaPluginMonolith::ConstructArgs&& args); /** * When the reference count reaches zero and this destructor is called, * we'll send a request to the Wine plugin host to destroy the corresponding * object. */ - ~YaComponentPluginImpl(); + ~YaPluginMonolithImpl(); /** * We'll override the query interface to log queries for interfaces we do diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index a39ac230..6b65b2e1 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -38,13 +38,13 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::variant result = - bridge.send_message(YaComponent::Construct{.cid = cid_array}); + std::variant result = + bridge.send_message(YaPluginMonolith::Construct{.cid = cid_array}); return std::visit( overload{ - [&](YaComponent::ConstructArgs&& args) -> tresult { + [&](YaPluginMonolith::ConstructArgs&& args) -> tresult { *obj = static_cast( - new YaComponentPluginImpl(bridge, std::move(args))); + new YaPluginMonolithImpl(bridge, std::move(args))); return Steinberg::kResultOk; }, [&](const UniversalTResult& code) -> tresult { return code; }}, diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 963ff26e..30a2f093 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -18,6 +18,7 @@ #include "../vst3.h" +// TODO Rename to YaPluginFactoryImpl class YaPluginFactoryPluginImpl : public YaPluginFactory { public: YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge, diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index c8e45e95..61ae85a1 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -115,10 +115,10 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { } void Vst3PluginBridge::register_component(size_t instance_id, - YaComponentPluginImpl& component) { + YaPluginMonolithImpl& component) { std::lock_guard lock(component_instances_mutex); component_instances.emplace(instance_id, - std::ref(component)); + std::ref(component)); } void Vst3PluginBridge::unregister_component(size_t instance_id) { diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 402ad38b..341b402c 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -24,7 +24,7 @@ #include "common.h" // Forward declaration -class YaComponentPluginImpl; +class YaPluginMonolithImpl; /** * This handles the communication between the native host and a VST3 plugin @@ -83,9 +83,11 @@ class Vst3PluginBridge : PluginBridge> { * context. * * @see component_instances + * + * TODO: REname to `register_instance` or `register_object` */ void register_component(size_t instance_id, - YaComponentPluginImpl& component); + YaPluginMonolithImpl& component); /** * Remove a previously registered `YaComponentPluginImpl` from the list of @@ -149,7 +151,7 @@ class Vst3PluginBridge : PluginBridge> { * `register_component()` in the constractor, and an instance is then * removed through a call to `unregister_component()` in the destructor. */ - std::map> + std::map> component_instances; std::mutex component_instances_mutex; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1c0e9c36..57dfa2b6 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,6 +60,34 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](const YaPluginMonolith::Construct& args) + -> YaPluginMonolith::Construct::Response { + Steinberg::TUID cid; + std::copy(args.cid.begin(), args.cid.end(), cid); + Steinberg::IPtr component = + module->getFactory() + .createInstance(cid); + if (component) { + std::lock_guard lock(component_instances_mutex); + + const size_t instance_id = generate_instance_id(); + component_instances[instance_id] = std::move(component); + + return YaPluginMonolith::ConstructArgs( + component_instances[instance_id].component, + instance_id); + } else { + // The actual result is lost here + return UniversalTResult(Steinberg::kNotImplemented); + } + }, + [&](const YaPluginMonolith::Destruct& request) + -> YaPluginMonolith::Destruct::Response { + std::lock_guard lock(component_instances_mutex); + component_instances.erase(request.instance_id); + + return Ack{}; + }, [&](YaAudioProcessor::SetBusArrangements& request) -> YaAudioProcessor::SetBusArrangements::Response { return component_instances[request.instance_id] @@ -113,34 +141,6 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .audio_processor->getTailSamples(); }, - [&](const YaComponent::Construct& args) - -> YaComponent::Construct::Response { - Steinberg::TUID cid; - std::copy(args.cid.begin(), args.cid.end(), cid); - Steinberg::IPtr component = - module->getFactory() - .createInstance(cid); - if (component) { - std::lock_guard lock(component_instances_mutex); - - const size_t instance_id = generate_instance_id(); - component_instances[instance_id] = std::move(component); - - return YaComponent::ConstructArgs( - component_instances[instance_id].component, - instance_id); - } else { - // The actual result is lost here - return UniversalTResult(Steinberg::kNotImplemented); - } - }, - [&](const YaComponent::Destruct& request) - -> YaComponent::Destruct::Response { - std::lock_guard lock(component_instances_mutex); - component_instances.erase(request.instance_id); - - return Ack{}; - }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { return component_instances[request.instance_id] @@ -202,7 +202,8 @@ void Vst3Bridge::run() { -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object // and pass that to the initialize function. This object should - // be cleaned up again during `YaComponent::Destruct`. + // be cleaned up again during `YaPluginMonolith::Destruct`. + // TOOD: This needs changing when we get to `YaHostMonolith` Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { component_instances[request.instance_id]