Reload supported interfaces after IPluginBase::initialize()

This is needed as a workaround to support Waves VST3 plugins.

Right now does does not actually fix the issue because the arguments are
not updated in the subclasses. The next commit will fix this.
This commit is contained in:
Robbert van der Helm
2021-07-05 16:03:39 +02:00
parent 3fbb01f225
commit 1397400155
8 changed files with 119 additions and 48 deletions
+4
View File
@@ -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.
+7 -1
View File
@@ -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 = <FUnknown*>)";
@@ -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) {
+3 -1
View File
@@ -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<Vst3PluginProxy::ConstructArgs, UniversalTResult>&);
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,
+5 -1
View File
@@ -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,
+48 -1
View File
@@ -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 <typename S>
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 <typename S>
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;
};
@@ -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 <typename S>
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;
/**
+12 -4
View File
@@ -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()'");
+37 -18
View File
@@ -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<YaHostApplication*>(
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<YaHostApplication*>(
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)