diff --git a/CHANGELOG.md b/CHANGELOG.md index 00eab412..47bb6cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,10 @@ Versioning](https://semver.org/spec/v2.0.0.html). happened often with _JUCE_ based plugins like Sonic Academy's _Kick 2_ and _Anaglyph_. While this is technically a workaround for a bad interaction with JUCE and Wine, it should make these plugins a lot more pleasant to use. +- Fixed **Waves** VST3 plugins not being able to initialize correctly. These + plugins would at runtime change their query interface to support more VST3 + interfaces, including the required edit controller interface. Yabridge now + requeries the supported interfaces at a later stage to work around this. - Fixed missing transport information for VST2 plugins in **Ardour**, breaking host sync and LFOs in certain plugins. This was a regression from yabridge 3.2.0. diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 1b9d1800..06e6e55c 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -699,7 +699,7 @@ bool Vst3Logger::log_request( } bool Vst3Logger::log_request(bool is_host_vst, - const YaPluginBase::Initialize& request) { + const Vst3PluginProxy::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IPluginBase::initialize(context = )"; @@ -1468,6 +1468,12 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response( + bool is_host_vst, + const Vst3PluginProxy::InitializeResponse& response) { + log_response(is_host_vst, response.result); +} + void Vst3Logger::log_response( bool is_host_vst, const Vst3PluginProxy::GetStateResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 6d1ca160..eeb1b5c1 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -67,6 +67,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const Vst3PlugViewProxy::Destruct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); + bool log_request(bool is_host_vst, const Vst3PluginProxy::Initialize&); bool log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); bool log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); bool log_request( @@ -150,7 +151,6 @@ class Vst3Logger { bool log_request( bool is_host_vst, const YaPlugViewContentScaleSupport::SetContentScaleFactor&); - bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory3::SetHostContext&); bool log_request( @@ -252,6 +252,8 @@ class Vst3Logger { void log_response( bool is_host_vst, const std::variant&); + void log_response(bool is_host_vst, + const Vst3PluginProxy::InitializeResponse&); void log_response(bool is_host_vst, const Vst3PluginProxy::GetStateResponse&); void log_response(bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 66d6a639..09779134 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -70,6 +70,11 @@ using ControlRequest = Vst3PlugViewProxy::Destruct, Vst3PluginProxy::Construct, Vst3PluginProxy::Destruct, + // This is actually part of `YaPluginBase`, but thanks to Waves + // we had to move this message to the main `Vst3PluginProxy` + // class + Vst3PluginProxy::Initialize, + // These are defined in both `IComponent` and `IEditController` Vst3PluginProxy::SetState, Vst3PluginProxy::GetState, YaAudioPresentationLatency::SetAudioPresentationLatencySamples, @@ -119,7 +124,6 @@ using ControlRequest = YaPlugView::CanResize, YaPlugView::CheckSizeConstraint, YaPlugViewContentScaleSupport::SetContentScaleFactor, - YaPluginBase::Initialize, YaPluginBase::Terminate, YaPluginFactory3::SetHostContext, YaProcessContextRequirements::GetProcessContextRequirements, diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 0fc350bc..653e1369 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -232,6 +232,53 @@ class Vst3PluginProxy : public YaAudioPresentationLatency, */ inline size_t instance_id() const noexcept { return arguments.instance_id; } + // These have to be defined here instead of in `YaPluginBase` because we + // need to reference the `ConstructArgs` + + /** + * The response code and updated supported interface list after a call to + * `IPluginBase::initialize()`. + * + * HACK: This is needed to support Waves VST3 plugins because they only + * expose the edit controller interface after this point + */ + struct InitializeResponse { + UniversalTResult result; + + // This is a very ugly hack, but we'll just have to requery all + // supported interfaces and replace the original constructargs in the + // plugin-side proxy object + Vst3PluginProxy::ConstructArgs updated_plugin_interfaces; + + template + void serialize(S& s) { + s.object(result); + s.object(updated_plugin_interfaces); + } + }; + + /** + * Message to pass through a call to `IPluginBase::initialize()` to the Wine + * plugin host. We will read what interfaces the passed context object + * implements so we can then create a proxy object on the Wine side that the + * plugin can use to make callbacks with. The lifetime of this + * `Vst3HostContextProxy` object should be bound to the `IComponent` we are + * proxying. + */ + struct Initialize { + using Response = InitializeResponse; + + native_size_t instance_id; + + Vst3HostContextProxy::ConstructArgs host_context_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(host_context_args); + } + }; + // We'll define messages for functions that have identical definitions in // multiple interfaces below. When the Wine plugin host process handles // these it should check which of the interfaces is supported on the host. @@ -287,7 +334,7 @@ class Vst3PluginProxy : public YaAudioPresentationLatency, } }; - private: + protected: ConstructArgs arguments; }; diff --git a/src/common/serialization/vst3/plugin/plugin-base.h b/src/common/serialization/vst3/plugin/plugin-base.h index 177b8b7b..62995ce5 100644 --- a/src/common/serialization/vst3/plugin/plugin-base.h +++ b/src/common/serialization/vst3/plugin/plugin-base.h @@ -63,28 +63,9 @@ class YaPluginBase : public Steinberg::IPluginBase { inline bool supported() const noexcept { return arguments.supported; } - /** - * Message to pass through a call to `IPluginBase::initialize()` to the Wine - * plugin host. We will read what interfaces the passed context object - * implements so we can then create a proxy object on the Wine side that the - * plugin can use to make callbacks with. The lifetime of this - * `Vst3HostContextProxy` object should be bound to the `IComponent` we are - * proxying. - */ - struct Initialize { - using Response = UniversalTResult; - - native_size_t instance_id; - - Vst3HostContextProxy::ConstructArgs host_context_args; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.object(host_context_args); - } - }; - + // The request and response for `IPluginBase::initialize()` is defined + // within `Vst3PluginProxy` because it (thanks to Waves) requires all + // supported interfaces to be queried again virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; /** diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 29cb176a..b1dad9c7 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -1057,10 +1057,18 @@ tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { host_application = host_context; plug_interface_support = host_context; - return bridge.send_message(YaPluginBase::Initialize{ - .instance_id = instance_id(), - .host_context_args = Vst3HostContextProxy::ConstructArgs( - host_context, instance_id())}); + const InitializeResponse response = + bridge.send_message(Vst3PluginProxy::Initialize{ + .instance_id = instance_id(), + .host_context_args = Vst3HostContextProxy::ConstructArgs( + host_context, instance_id())}); + + // HACK: For some reason, Waves plugins will only allow querying the + // `IEditController` interface after this point, so we need to + // update the list of interfaces we support for this object. + arguments = response.updated_plugin_interfaces; + + return response.result; } else { bridge.logger.log( "WARNING: Null pointer passed to 'IPluginBase::initialize()'"); diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 70c7aee3..8531e30f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -906,8 +906,8 @@ void Vst3Bridge::run() { .get(); } }, - [&](YaPluginBase::Initialize& request) - -> YaPluginBase::Initialize::Response { + [&](Vst3PluginProxy::Initialize& request) + -> Vst3PluginProxy::Initialize::Response { Vst3PluginInstance& instance = object_instances.at(request.instance_id); @@ -923,24 +923,43 @@ void Vst3Bridge::run() { // `IPlugView::{initialize,terminate}`, we'll run these // functions from the main GUI thread return main_context - .run_in_context([&]() -> tresult { - // The plugin may try to spawn audio worker threads - // during its initialization - set_realtime_priority(true); - // This static cast is required to upcast to `FUnknown*` - const tresult result = - instance.interfaces.plugin_base->initialize( - static_cast( - instance.host_context_proxy)); - set_realtime_priority(false); + .run_in_context( + [&]() -> Vst3PluginProxy::InitializeResponse { + // The plugin may try to spawn audio worker threads + // during its initialization + set_realtime_priority(true); + // This static cast is required to upcast to + // `FUnknown*` + const tresult result = + instance.interfaces.plugin_base->initialize( + static_cast( + instance.host_context_proxy)); + set_realtime_priority(false); - // The Win32 message loop will not be run up to this - // point to prevent plugins with partially initialized - // states from misbehaving - instance.is_initialized = true; + // HACK: Waves plugins for some reason only add + // `IEditController` to their query interface + // after `IPluginBase::initialize()` has been + // called, so we need to update the list of + // supported interfaces at this point. This + // needs to be done on both the Wine and the + // plugin since, so we also need to return an + // updated list of supported interfaces. + instance.interfaces = + Vst3PluginInterfaces(instance.object); - return result; - }) + Vst3PluginProxy::ConstructArgs updated_interfaces( + instance.object, request.instance_id); + + // The Win32 message loop will not be run up to this + // point to prevent plugins with partially + // initialized states from misbehaving + instance.is_initialized = true; + + return Vst3PluginProxy::InitializeResponse{ + .result = result, + .updated_plugin_interfaces = + updated_interfaces}; + }) .get(); }, [&](const YaPluginBase::Terminate& request)