From d8b26465639d777ce87311e7a89cb14b8d012513 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 12:48:10 +0100 Subject: [PATCH] Split off IComponent and create a monolithic class We can now use implement all VST3 plugin interfaces through this class, check whether the object from the plugin also supports these classes, and then conditionally allow casting to the supported classes. This should give us a one-to-one proxy of the original object. --- docs/vst3.md | 2 + meson.build | 2 + src/common/logging/vst3.cpp | 99 ++++++------ src/common/logging/vst3.h | 10 +- src/common/serialization/vst3.h | 22 +-- src/common/serialization/vst3/README.md | 15 +- .../serialization/vst3/audio-processor.cpp | 4 +- .../serialization/vst3/audio-processor.h | 6 +- src/common/serialization/vst3/component.cpp | 59 ++----- src/common/serialization/vst3/component.h | 100 ++---------- .../serialization/vst3/host-application.h | 3 + src/common/serialization/vst3/plugin-base.cpp | 4 +- src/common/serialization/vst3/plugin-base.h | 2 +- .../serialization/vst3/plugin-monolith.cpp | 75 +++++++++ .../serialization/vst3/plugin-monolith.h | 151 ++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 65 ++++---- src/plugin/bridges/vst3-impls/component.h | 8 +- .../bridges/vst3-impls/plugin-factory.cpp | 8 +- .../bridges/vst3-impls/plugin-factory.h | 1 + src/plugin/bridges/vst3.cpp | 4 +- src/plugin/bridges/vst3.h | 8 +- src/wine-host/bridges/vst3.cpp | 59 +++---- 22 files changed, 422 insertions(+), 285 deletions(-) create mode 100644 src/common/serialization/vst3/plugin-monolith.cpp create mode 100644 src/common/serialization/vst3/plugin-monolith.h 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]