diff --git a/docs/vst3.md b/docs/vst3.md index 3b3cbcfa..11253864 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -2,6 +2,8 @@ TODO: Flesh this out further +TODO: Link to `src/common/serialization/vst3/README.md` + 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 93443962..90ce16a3 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/host-application.cpp', '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-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', @@ -122,6 +123,7 @@ if with_vst3 'src/common/serialization/vst3/host-application.cpp', '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-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 b3a11c15..26bd558d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -54,27 +54,6 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Initialize& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::initialize(context = "; - if (request.host_application_context_args) { - message << ""; - } else { - message << ""; - } - message << ")"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Terminate& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::terminate()"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -227,6 +206,27 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginBase::Initialize& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::initialize(context = "; + if (request.host_application_context_args) { + message << ""; + } else { + message << ""; + } + message << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginBase::Terminate& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::terminate()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9cab41f7..67912a6d 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -58,8 +58,6 @@ class Vst3Logger { 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::Initialize&); - void log_request(bool is_host_vst, const YaComponent::Terminate&); 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&); @@ -77,6 +75,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetProcessing&); void log_request(bool is_host_vst, const YaComponent::Process&); void log_request(bool is_host_vst, const YaComponent::GetTailSamples&); + void log_request(bool is_host_vst, const YaPluginBase::Initialize&); + void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index b9049583..304899fd 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -59,8 +59,6 @@ struct WantsConfiguration { */ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index e93902ec..da95009c 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -7,11 +7,12 @@ serialization works. VST3 interfaces are implemented as follows: -| Yabridge class | Interfaces | Notes | -| ------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | -| `YaComponent` | `IComponent`, `IPluginBase`, `IAudioProcessor` | | -| `YaHostApplication` | `iHostAPplication` | Used as a 'context' to allow the plugin to maek callbacks. | -| `YaPluginFactory` | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | | +| Yabridge class | Included in | Interfaces | +| ------------------- | ------------- | ------------------------------------------------------ | +| `YaComponent` | | `IComponent`, `IAudioProcessor` | +| `YaHostApplication` | | `iHostAPplication` | +| `YaPluginBase` | `YaComponent` | `IPluginBase` | +| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | The following interfaces are implemented purely fur serialization purposes: diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index cbc9b2e5..754168ab 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -21,30 +21,21 @@ YaComponent::ConstructArgs::ConstructArgs() {} YaComponent::ConstructArgs::ConstructArgs( Steinberg::IPtr component, size_t instance_id) - : instance_id(instance_id) { - known_iids.insert(component->iid); + : instance_id(instance_id), + audio_processor_supported( + Steinberg::FUnknownPtr(component)) { // `IComponent::getControllerClassId` Steinberg::TUID cid; if (component->getControllerClassId(cid) == Steinberg::kResultOk) { edit_controller_cid = std::to_array(cid); } - - // There's no static data we can copy from the audio processor - if (auto audio_processor = - Steinberg::FUnknownPtr( - component)) { - known_iids.insert(Steinberg::Vst::IAudioProcessor::iid); - } } -YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) { - FUNKNOWN_CTOR +YaComponent::YaComponent(const ConstructArgs&& args) + : YaPluginBase(std::move(args.plugin_base_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} - // Everything else is handled directly through callbacks to minimize the - // potential for errors -} - -YaComponent::~YaComponent() { + YaComponent::~YaComponent() { FUNKNOWN_DTOR } @@ -55,14 +46,22 @@ IMPLEMENT_REFCOUNT(YaComponent) tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, void** obj) { - QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginBase) - if (arguments.known_iids.contains(Steinberg::Vst::IComponent::iid)) { - QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, - Steinberg::IPluginBase) - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, - Steinberg::Vst::IComponent) + 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; + } } - if (arguments.known_iids.contains(Steinberg::Vst::IAudioProcessor::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + if (arguments.audio_processor_supported) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, Steinberg::Vst::IAudioProcessor) } diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index a018af1a..3899a4fd 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -32,6 +32,7 @@ #include "../common.h" #include "base.h" #include "host-application.h" +#include "plugin-base.h" #include "process-data.h" #pragma GCC diagnostic push @@ -43,18 +44,18 @@ * 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: Amplement IConnectionPoint - * TODO: How should we support IComponents without a seperate edit controller? - * Can we just use a separate `YaEditController` that just points to the - * same implementation (with the same CID)? Check the reference - * implementation in the framework to see how this is initialized, make - * sure we support the reference w workflow. + * TODO: Rework this into `YaPluginMonolith` + * TODO: Eventually this should (optionally) implement everything supported by + * the SDK's `AudioEffect` component. */ class YaComponent : public Steinberg::Vst::IComponent, + public YaPluginBase, public Steinberg::Vst::IAudioProcessor { public: /** @@ -76,10 +77,10 @@ class YaComponent : public Steinberg::Vst::IComponent, */ native_size_t instance_id; - /** - * The IIDs that the interface we serialized supports. - */ - std::set known_iids; + YaPluginBase::ConstructArgs plugin_base_args; + + // TODO: Remove, transitional + bool audio_processor_supported; /** * The class ID of this component's corresponding editor controller. You @@ -90,10 +91,8 @@ class YaComponent : public Steinberg::Vst::IComponent, template void serialize(S& s) { s.value8b(instance_id); - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); + s.object(plugin_base_args); + s.value1b(audio_processor_supported); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } @@ -146,49 +145,6 @@ class YaComponent : public Steinberg::Vst::IComponent, DECLARE_FUNKNOWN_METHODS - // From `IPluginBase` - - /** - * Message to pass through a call to `IPluginBase::initialize()` to the Wine - * plugin host. if we pass an `IHostApplication` instance, then a proxy - * `YaHostApplication` should be created and passed as an argument to - * `IPluginBase::initialize()`. If this is absent a null pointer should be - * passed. The lifetime of this `YaHostApplication` object should be bound - * to the `IComponent` we are proxying. - */ - struct Initialize { - using Response = UniversalTResult; - - native_size_t instance_id; - std::optional - host_application_context_args; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.ext(host_application_context_args, bitsery::ext::StdOptional{}); - } - }; - - virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; - - /** - * Message to pass through a call to `IPluginBase::terminate()` to the Wine - * plugin host. - */ - struct Terminate { - using Response = UniversalTResult; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - virtual tresult PLUGIN_API terminate() override = 0; - // From `IComponent` tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; diff --git a/src/common/serialization/vst3/plugin-base.cpp b/src/common/serialization/vst3/plugin-base.cpp new file mode 100644 index 00000000..f595b6ab --- /dev/null +++ b/src/common/serialization/vst3/plugin-base.cpp @@ -0,0 +1,26 @@ +// 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-base.h" + +YaPluginBase::ConstructArgs::ConstructArgs() {} + +YaPluginBase::ConstructArgs::ConstructArgs( + Steinberg::IPtr component) + : supported(Steinberg::FUnknownPtr(component)) {} + +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 new file mode 100644 index 00000000..47ee27c6 --- /dev/null +++ b/src/common/serialization/vst3/plugin-base.h @@ -0,0 +1,113 @@ +// 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 "base.h" +#include "host-application.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPluginBase` for serialization purposes. Both components and + * edit controllers inherit from this. This is instantiated as part of + * `YaComponent` or `YaEditController`. + */ +class YaPluginBase : public Steinberg::IPluginBase { + public: + /** + * These are the arguments for creating a `YaPluginBase`. + */ + 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. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaPluginBase(const ConstructArgs&& args); + + inline bool supported() { return arguments.supported; } + + /** + * Message to pass through a call to `IPluginBase::initialize()` to the Wine + * plugin host. if we pass an `IHostApplication` instance, then a proxy + * `YaHostApplication` should be created and passed as an argument to + * `IPluginBase::initialize()`. If this is absent a null pointer should be + * passed. The lifetime of this `YaHostApplication` object should be bound + * to the `IComponent` we are proxying. + */ + struct Initialize { + using Response = UniversalTResult; + + native_size_t instance_id; + std::optional + host_application_context_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.ext(host_application_context_args, bitsery::ext::StdOptional{}); + } + }; + + virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; + + /** + * Message to pass through a call to `IPluginBase::terminate()` to the Wine + * plugin host. + */ + struct Terminate { + using Response = UniversalTResult; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + virtual tresult PLUGIN_API terminate() override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 2a5a4c7f..4a96aa50 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -40,35 +40,6 @@ YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaComponentPluginImpl::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 - // null pointer instead. - host_application_context = context; - - std::optional - host_application_context_args = std::nullopt; - if (host_application_context) { - host_application_context_args = YaHostApplication::ConstructArgs( - host_application_context, arguments.instance_id); - } else { - bridge.logger.log_unknown_interface( - "In IPluginBase::initialize()", - context ? std::optional(context->iid) : std::nullopt); - } - - return bridge.send_message( - YaComponent::Initialize{.instance_id = arguments.instance_id, - .host_application_context_args = - std::move(host_application_context_args)}); -} - -tresult PLUGIN_API YaComponentPluginImpl::terminate() { - return bridge.send_message( - YaComponent::Terminate{.instance_id = arguments.instance_id}); -} - tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ @@ -143,6 +114,35 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { return response.result; } +tresult PLUGIN_API YaComponentPluginImpl::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 + // null pointer instead. + host_application_context = context; + + std::optional + host_application_context_args = std::nullopt; + if (host_application_context) { + host_application_context_args = YaHostApplication::ConstructArgs( + host_application_context, arguments.instance_id); + } else { + bridge.logger.log_unknown_interface( + "In IPluginBase::initialize()", + context ? std::optional(context->iid) : std::nullopt); + } + + return bridge.send_message( + YaPluginBase::Initialize{.instance_id = arguments.instance_id, + .host_application_context_args = + std::move(host_application_context_args)}); +} + +tresult PLUGIN_API YaComponentPluginImpl::terminate() { + return bridge.send_message( + YaPluginBase::Terminate{.instance_id = arguments.instance_id}); +} + tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 1c20d118..fd9a4ca5 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -39,9 +39,7 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; - tresult PLUGIN_API initialize(FUnknown* context) override; - tresult PLUGIN_API terminate() override; - + // From `IComponent` tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) override; @@ -61,6 +59,10 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API setState(Steinberg::IBStream* state) override; tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + // From `IPluginBase` + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + tresult PLUGIN_API setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 25c6d0af..6d60c3ce 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -26,7 +26,9 @@ ComponentInstance::ComponentInstance() {} ComponentInstance::ComponentInstance( Steinberg::IPtr component) - : component(component), audio_processor(component) {} + : component(component), + plugin_base(component), + audio_processor(component) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, @@ -86,8 +88,8 @@ void Vst3Bridge::run() { return Ack{}; }, - [&](YaComponent::Initialize& request) - -> YaComponent::Initialize::Response { + [&](YaPluginBase::Initialize& request) + -> 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`. @@ -103,12 +105,12 @@ void Vst3Bridge::run() { } return component_instances[request.instance_id] - .component->initialize(context); + .plugin_base->initialize(context); }, - [&](const YaComponent::Terminate& request) - -> YaComponent::Terminate::Response { + [&](const YaPluginBase::Terminate& request) + -> YaPluginBase::Terminate::Response { return component_instances[request.instance_id] - .component->terminate(); + .plugin_base->terminate(); }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 5e968efa..2dfa2c9a 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -28,6 +28,9 @@ * A holder for an `IComponent` instance created from the factory along with any * host context proxy objects belonging to it, and several predefined * `FUnknownPtrs` so we don't have to do these dynamic casts all the times.. + * + * TODO: When implementing `IEditController`, change this to use `IPluginBase` + * as the base interface. */ struct ComponentInstance { ComponentInstance(); @@ -37,7 +40,7 @@ struct ComponentInstance { /** * If the host passes an `IHostApplication` during * `IPluginBase::initialize()`, we'll store a proxy object here and then - * pass it to `component->initialize()`. + * pass it to `plugin_base->initialize()`. */ Steinberg::IPtr hsot_application_context; @@ -49,6 +52,7 @@ struct ComponentInstance { // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. + Steinberg::FUnknownPtr plugin_base; Steinberg::FUnknownPtr audio_processor; };