mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Allow indirect IConnectionPoint connections
This is needed to support Ardour. These extra hops and serialization steps will probably hurt performance, but outside of some huge hacks (to connect the components directly anyways) there's not much else we can do.
This commit is contained in:
@@ -16,8 +16,6 @@ This branch is still very far removed from being in a usable state. Below is an
|
||||
incomplete list of things that still have to be done before this can be used:
|
||||
|
||||
- Interfaces left to implement:
|
||||
- `IConnectionPoint::notify()`, and support for indirectly connecting objects
|
||||
through connction proxies
|
||||
- `IEditController2`
|
||||
- All other mandatory interfaces
|
||||
- All other optional interfaces
|
||||
|
||||
@@ -94,8 +94,17 @@ bool Vst3Logger::log_request(bool is_host_vst,
|
||||
const YaConnectionPoint::Connect& request) {
|
||||
return log_request_base(is_host_vst, [&](auto& message) {
|
||||
message << request.instance_id
|
||||
<< ": IConnectionPoint::connect(other = <IConnectionPoint* #"
|
||||
<< request.other_instance_id << ">)";
|
||||
<< ": IConnectionPoint::connect(other = ";
|
||||
std::visit(
|
||||
overload{[&](const native_size_t& other_instance_id) {
|
||||
message << "<IConnectionPoint* #" << other_instance_id
|
||||
<< ">";
|
||||
},
|
||||
[&](const Vst3ConnectionPointProxy::ConstructArgs&) {
|
||||
message << "<IConnectionPoint* proxy>";
|
||||
}},
|
||||
request.other);
|
||||
message << ")";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,8 +112,14 @@ bool Vst3Logger::log_request(bool is_host_vst,
|
||||
const YaConnectionPoint::Disconnect& request) {
|
||||
return log_request_base(is_host_vst, [&](auto& message) {
|
||||
message << request.instance_id
|
||||
<< ": IConnectionPoint::disconnect(other = <IConnectionPoint* #"
|
||||
<< request.other_instance_id << ">)";
|
||||
<< ": IConnectionPoint::disconnect(other = ";
|
||||
if (request.other_instance_id) {
|
||||
message << "<IConnectionPoint* #" << *request.other_instance_id
|
||||
<< ">";
|
||||
} else {
|
||||
message << "<IConnectionPoint* proxy>";
|
||||
}
|
||||
message << ")";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
|
||||
#include "connection-point-proxy.h"
|
||||
|
||||
Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object,
|
||||
size_t owner_instance_id)
|
||||
: owner_instance_id(owner_instance_id), connection_point_args(object) {}
|
||||
|
||||
Vst3ConnectionPointProxy::Vst3ConnectionPointProxy(const ConstructArgs&& args)
|
||||
: YaConnectionPoint(std::move(args.connection_point_args)),
|
||||
arguments(std::move(args)){FUNKNOWN_CTOR}
|
||||
|
||||
@@ -38,40 +38,10 @@
|
||||
*/
|
||||
class Vst3ConnectionPointProxy : public YaConnectionPoint {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for constructing a
|
||||
* `Vst3ConnectionPointProxyImpl`.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This is not necessary in this case since the object has to support
|
||||
* `IConnectionPoint`, but let's stay consistent with the overall style
|
||||
* here.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<FUnknown> object,
|
||||
size_t owner_instance_id);
|
||||
|
||||
/**
|
||||
* The unique instance identifier of the proxy object instance this
|
||||
* connection proxy has been passed to and thus belongs to. This way we
|
||||
* can refer to the correct 'actual' `IConnectionPoint` instance when
|
||||
* the plugin calls `notify()` on this proxy object.
|
||||
*/
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
YaConnectionPoint::ConstructArgs connection_point_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(connection_point_args);
|
||||
}
|
||||
};
|
||||
// We had to define this in `YaConnectionPoint` to work around circular
|
||||
// includes
|
||||
using ConstructArgs =
|
||||
YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs;
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from an actual
|
||||
|
||||
@@ -23,5 +23,14 @@ YaConnectionPoint::ConstructArgs::ConstructArgs(
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IConnectionPoint>(object)) {}
|
||||
|
||||
YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs::
|
||||
Vst3ConnectionPointProxyConstructArgs() {}
|
||||
|
||||
YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs::
|
||||
Vst3ConnectionPointProxyConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object,
|
||||
size_t owner_instance_id)
|
||||
: owner_instance_id(owner_instance_id), connection_point_args(object) {}
|
||||
|
||||
YaConnectionPoint::YaConnectionPoint(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <pluginterfaces/vst/ivstmessage.h>
|
||||
|
||||
#include "../../common.h"
|
||||
@@ -32,8 +35,6 @@
|
||||
* monolithic proxy class we can easily directly connect different objects by
|
||||
* checking if they're a `Vst3PluginProxy` and then fetching that object's
|
||||
* instance ID (if the host doesn't place a proxy object here).
|
||||
*
|
||||
* TODO: Make sure we somehow handle proxies created by the host here.
|
||||
*/
|
||||
class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint {
|
||||
public:
|
||||
@@ -60,6 +61,45 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint {
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* These are the arguments for constructing a
|
||||
* `Vst3ConnectionPointProxyImpl`.
|
||||
*
|
||||
* It's defined here to work around circular includes.
|
||||
*/
|
||||
struct Vst3ConnectionPointProxyConstructArgs {
|
||||
Vst3ConnectionPointProxyConstructArgs();
|
||||
|
||||
/**
|
||||
* Read from an existing object. We will try to mimic this object, so
|
||||
* we'll support any interfaces this object also supports.
|
||||
*
|
||||
* This is not necessary in this case since the object has to support
|
||||
* `IConnectionPoint`, but let's stay consistent with the overall style
|
||||
* here.
|
||||
*/
|
||||
Vst3ConnectionPointProxyConstructArgs(Steinberg::IPtr<FUnknown> object,
|
||||
size_t owner_instance_id);
|
||||
|
||||
/**
|
||||
* The unique instance identifier of the proxy object instance this
|
||||
* connection proxy has been passed to and thus belongs to. This way we
|
||||
* can refer to the correct 'actual' `IConnectionPoint` instance when
|
||||
* the plugin calls `notify()` on this proxy object.
|
||||
*/
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
ConstructArgs connection_point_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(connection_point_args);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
@@ -69,10 +109,10 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint {
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IConnectionPoint::connect(other_instance_id)` to the Wine plugin host.
|
||||
* At the moment this is only implemented for directly connecting objects
|
||||
* created by the plugin without any proxies in between them.
|
||||
* Message to pass through a call to `IConnectionPoint::connect(other)` to
|
||||
* the Wine plugin host. If the host directly connects two objects, then
|
||||
* we'll connect them directly as well. Otherwise all messages have to be
|
||||
* routed through the host.
|
||||
*/
|
||||
struct Connect {
|
||||
using Response = UniversalTResult;
|
||||
@@ -82,24 +122,32 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint {
|
||||
/**
|
||||
* The other object this object should be connected to. When connecting
|
||||
* two `Vst3PluginProxy` objects, we can directly connect the underlying
|
||||
* objects on the Wine side.
|
||||
* objects on the Wine side using their instance IDs. Otherwise we'll
|
||||
* create a proxy object for the connection proxy provided by the host
|
||||
* that the plugin can use to send messages to.
|
||||
*/
|
||||
native_size_t other_instance_id;
|
||||
std::variant<native_size_t, Vst3ConnectionPointProxyConstructArgs>
|
||||
other;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value8b(other_instance_id);
|
||||
s.ext(other,
|
||||
bitsery::ext::StdVariant{
|
||||
[](S& s, native_size_t& other_instance_id) {
|
||||
s.value8b(other_instance_id);
|
||||
},
|
||||
[](S& s, Vst3ConnectionPointProxyConstructArgs& args) {
|
||||
s.object(args);
|
||||
}});
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API connect(IConnectionPoint* other) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IConnectionPoint::disconnect(other_instance_id)` to the Wine plugin
|
||||
* host. At the moment this is only implemented for directly connecting
|
||||
* objects created by the plugin without any proxies in between them.
|
||||
* Message to pass through a call to `IConnectionPoint::disconnect(other)`
|
||||
* to the Wine plugin host.
|
||||
*/
|
||||
struct Disconnect {
|
||||
using Response = UniversalTResult;
|
||||
@@ -107,15 +155,19 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint {
|
||||
native_size_t instance_id;
|
||||
|
||||
/**
|
||||
* The other object backed by a `Vst3PluginProxy` this object was
|
||||
* connected to and should be disconnected from. When connecting.
|
||||
* If we connected two objects directly, then this is the instance ID of
|
||||
* that object. Otherwise we'll just destroy the smart pointer pointing
|
||||
* to our `IConnectionPoint` proxy object.
|
||||
*/
|
||||
native_size_t other_instance_id;
|
||||
std::optional<native_size_t> other_instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value8b(other_instance_id);
|
||||
s.ext(other_instance_id, bitsery::ext::StdOptional{},
|
||||
[](S& s, native_size_t& instance_id) {
|
||||
s.value8b(instance_id);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -214,17 +214,19 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) {
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) {
|
||||
// When the host is trying to connect two plugin proxy objects, we can just
|
||||
// identify the other object by its instance IDs and then connect the
|
||||
// objects in the Wine plugin host directly
|
||||
// objects in the Wine plugin host directly. Otherwise we'll have to set up
|
||||
// a proxy for the host's connection proxy so the messages can be routed
|
||||
// through that.
|
||||
if (auto other_proxy = dynamic_cast<Vst3PluginProxy*>(other)) {
|
||||
return bridge.send_message(YaConnectionPoint::Connect{
|
||||
.instance_id = instance_id(),
|
||||
.other_instance_id = other_proxy->instance_id()});
|
||||
.instance_id = instance_id(), .other = other_proxy->instance_id()});
|
||||
} else {
|
||||
// TODO: Add support for `ConnectionProxy` and similar objects
|
||||
bridge.logger.log(
|
||||
"WARNING: The host passed a proxy proxy object to "
|
||||
"'IConnectionPoint::connect()'. This is currently not supported.");
|
||||
return Steinberg::kNotImplemented;
|
||||
connection_point_proxy = other;
|
||||
|
||||
return bridge.send_message(YaConnectionPoint::Connect{
|
||||
.instance_id = instance_id(),
|
||||
.other =
|
||||
Vst3ConnectionPointProxy::ConstructArgs(other, instance_id())});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +237,12 @@ tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) {
|
||||
.instance_id = instance_id(),
|
||||
.other_instance_id = other_proxy->instance_id()});
|
||||
} else {
|
||||
// TODO: Add support for `ConnectionProxy` and similar objects
|
||||
bridge.logger.log(
|
||||
"WARNING: The host passed a proxy proxy object to "
|
||||
"'IConnectionPoint::disconnect()'. This is currently not "
|
||||
"supported.");
|
||||
return Steinberg::kNotImplemented;
|
||||
const tresult result = bridge.send_message(
|
||||
YaConnectionPoint::Disconnect{.instance_id = instance_id(),
|
||||
.other_instance_id = std::nullopt});
|
||||
connection_point_proxy.reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <public.sdk/source/vst/hosting/module_win32.cpp>
|
||||
|
||||
#include "vst3-impls/component-handler-proxy.h"
|
||||
#include "vst3-impls/connection-point-proxy.h"
|
||||
#include "vst3-impls/host-context-proxy.h"
|
||||
#include "vst3-impls/plug-frame-proxy.h"
|
||||
|
||||
@@ -168,24 +169,55 @@ void Vst3Bridge::run() {
|
||||
return Vst3PluginProxy::GetStateResponse{
|
||||
.result = result, .updated_state = std::move(stream)};
|
||||
},
|
||||
[&](const YaConnectionPoint::Connect& request)
|
||||
[&](YaConnectionPoint::Connect& request)
|
||||
-> YaConnectionPoint::Connect::Response {
|
||||
// We can directly connect the underlying objects
|
||||
// TODO: Add support for connecting objects through a proxy
|
||||
// object provided by the host
|
||||
return object_instances[request.instance_id]
|
||||
.connection_point->connect(
|
||||
object_instances[request.other_instance_id]
|
||||
.connection_point);
|
||||
// If the host directly connected the underlying objects then we
|
||||
// can directly connect them as well. Otherwise we'll have to go
|
||||
// through a connection proxy (to proxy the host's connection
|
||||
// proxy).
|
||||
return std::visit(
|
||||
overload{
|
||||
[&](const native_size_t& other_instance_id) -> tresult {
|
||||
return object_instances[request.instance_id]
|
||||
.connection_point->connect(
|
||||
object_instances[other_instance_id]
|
||||
.connection_point);
|
||||
},
|
||||
[&](Vst3ConnectionPointProxy::ConstructArgs& args)
|
||||
-> tresult {
|
||||
object_instances[request.instance_id]
|
||||
.connection_point_proxy = Steinberg::owned(
|
||||
new Vst3ConnectionPointProxyImpl(
|
||||
*this, std::move(args)));
|
||||
|
||||
return object_instances[request.instance_id]
|
||||
.connection_point->connect(
|
||||
object_instances[request.instance_id]
|
||||
.connection_point_proxy);
|
||||
}},
|
||||
request.other);
|
||||
},
|
||||
[&](const YaConnectionPoint::Disconnect& request)
|
||||
-> YaConnectionPoint::Disconnect::Response {
|
||||
// TODO: Add support for connecting objects through a proxy
|
||||
// object provided by the host
|
||||
return object_instances[request.instance_id]
|
||||
.connection_point->disconnect(
|
||||
object_instances[request.other_instance_id]
|
||||
.connection_point);
|
||||
// If the objects were connected directly we can also disconnect
|
||||
// them directly. Otherwise we'll disconnect them from our proxy
|
||||
// object and then destroy that proxy object.
|
||||
if (request.other_instance_id) {
|
||||
return object_instances[request.instance_id]
|
||||
.connection_point->disconnect(
|
||||
object_instances[*request.other_instance_id]
|
||||
.connection_point);
|
||||
} else {
|
||||
const tresult result =
|
||||
object_instances[request.instance_id]
|
||||
.connection_point->disconnect(
|
||||
object_instances[*request.other_instance_id]
|
||||
.connection_point_proxy);
|
||||
object_instances[*request.other_instance_id]
|
||||
.connection_point_proxy.reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
[&](YaConnectionPoint::Notify& request)
|
||||
-> YaConnectionPoint::Notify::Response {
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
#include "../editor.h"
|
||||
#include "common.h"
|
||||
|
||||
// Forward declarations
|
||||
class Vst3PlugFrameProxyImpl;
|
||||
|
||||
/**
|
||||
* A holder for plugin object instance created from the factory. This stores all
|
||||
* relevant interface smart pointers to that object so we can handle control
|
||||
@@ -57,6 +54,18 @@ struct InstanceInterfaces {
|
||||
*/
|
||||
Steinberg::IPtr<Vst3HostContextProxy> host_context_proxy;
|
||||
|
||||
/**
|
||||
* If the host connects two objects indirectly using a connection proxy (as
|
||||
* allowed by the VST3 specification), then we also can't connect the
|
||||
* objects directly on the Wine side. In that case we'll have to create this
|
||||
* proxy object, pass it to the plugin, and if the plugin then calls
|
||||
* `IConnectionPoint::notify()` on it we'll pass that call through to the
|
||||
* `IConnectionPoint` instance passed to us by the host (which will then in
|
||||
* turn call `IConnectionPoint::notify()` on our plugin proxy object).
|
||||
* Proxies for days.
|
||||
*/
|
||||
Steinberg::IPtr<Vst3ConnectionPointProxy> connection_point_proxy;
|
||||
|
||||
/**
|
||||
* After a call to `IEditController::setComponentHandler()`, we'll create a
|
||||
* proxy of that component handler just like we did for the plugin object.
|
||||
|
||||
Reference in New Issue
Block a user