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.
This commit is contained in:
Robbert van der Helm
2020-12-17 12:48:10 +01:00
parent d6c28f48d9
commit d8b2646563
22 changed files with 422 additions and 285 deletions
+2
View File
@@ -7,6 +7,8 @@ TODO: Link to `src/common/serialization/vst3/README.md`
TODO: Mention the new `Ya<Base>::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
+2
View File
@@ -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',
+53 -46
View File
@@ -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* #" << request.instance_id
<< ">::~IComponent()";
});
}
void Vst3Logger::log_request(
bool is_host_vst,
const YaAudioProcessor::SetBusArrangements& request) {
log_request_base(is_host_vst, [&](auto& message) {
message << "<IAudioProcessor* #" << request.instance_id
<< ">::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 << "<IAudioProcessor* #" << request.instance_id
<< ">::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 << "<IAudioProcessor* #" << request.instance_id
<< ">::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 << "<IAudioProcessor* #" << request.instance_id
<< ">::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 << "<IAudioProcessor* #" << request.instance_id
<< ">::setupProcessing(setup = <SetupProcessing with mode = "
@@ -86,7 +110,7 @@ void Vst3Logger::log_request(bool is_host_vst,
}
void Vst3Logger::log_request(bool is_host_vst,
const YaComponent::SetProcessing& request) {
const YaAudioProcessor::SetProcessing& request) {
log_request_base(is_host_vst, [&](auto& message) {
message << "<IAudioProcessor* #" << request.instance_id
<< ">::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 << "<IAudioProcessor* #" << request.instance_id
<< ">::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* #" << request.instance_id
<< ">::~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<YaPluginMonolith::ConstructArgs, UniversalTResult>&
result) {
log_response_base(is_host_vst, [&](auto& message) {
std::visit(overload{[&](const YaPluginMonolith::ConstructArgs& args) {
message << "<IComponent* #" << args.instance_id
<< ">";
},
[&](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<YaComponent::ConstructArgs, UniversalTResult>& result) {
log_response_base(is_host_vst, [&](auto& message) {
std::visit(overload{[&](const YaComponent::ConstructArgs& args) {
message << "<IComponent* #" << args.instance_id
<< ">";
},
[&](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) {
+5 -5
View File
@@ -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<YaPluginMonolith::ConstructArgs, UniversalTResult>&);
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<YaComponent::ConstructArgs, UniversalTResult>&);
void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&);
void log_response(bool is_host_vst,
const YaComponent::GetRoutingInfoResponse&);
+11 -11
View File
@@ -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<YaComponent::Construct,
YaComponent::Destruct,
using ControlRequest = std::variant<YaPluginMonolith::Construct,
YaPluginMonolith::Destruct,
YaAudioProcessor::SetBusArrangements,
YaAudioProcessor::GetBusArrangement,
YaAudioProcessor::CanProcessSampleSize,
YaAudioProcessor::GetLatencySamples,
YaAudioProcessor::SetupProcessing,
YaAudioProcessor::SetProcessing,
YaAudioProcessor::Process,
YaAudioProcessor::GetTailSamples,
YaComponent::SetIoMode,
YaComponent::GetBusCount,
YaComponent::GetBusInfo,
@@ -67,14 +75,6 @@ using ControlRequest = std::variant<YaComponent::Construct,
YaComponent::SetActive,
YaComponent::SetState,
YaComponent::GetState,
YaComponent::SetBusArrangements,
YaComponent::GetBusArrangement,
YaComponent::CanProcessSampleSize,
YaComponent::GetLatencySamples,
YaComponent::SetupProcessing,
YaComponent::SetProcessing,
YaComponent::Process,
YaComponent::GetTailSamples,
YaPluginBase::Initialize,
YaPluginBase::Terminate,
YaPluginFactory::Construct,
+8 -7
View File
@@ -7,13 +7,14 @@ serialization works.
VST3 interfaces are implemented as follows:
| Yabridge class | Included in | Interfaces |
| ------------------- | ------------- | ------------------------------------------------------ |
| `YaComponent` | | `IComponent` |
| `YaAudioProcessor` | `YaComponent` | `IAudioProcessor` |
| `YaPluginBase` | `YaComponent` | `IPluginBase` |
| `YaHostApplication` | | `iHostAPplication` |
| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` |
| Yabridge class | Included in | Interfaces |
| ------------------- | ------------------ | ------------------------------------------------------ |
| `YaPluginMonolith` | | All of the below |
| `YaAudioProcessor` | `YaPluginMonolith` | `IAudioProcessor` |
| `YaComponent` | `YaPluginMonolith` | `IComponent` |
| `YaPluginBase` | `YaPluginMonolith` | `IPluginBase` |
| `YaHostApplication` | | `iHostAPplication` |
| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` |
The following interfaces are implemented purely fur serialization purposes:
@@ -19,9 +19,9 @@
YaAudioProcessor::ConstructArgs::ConstructArgs() {}
YaAudioProcessor::ConstructArgs::ConstructArgs(
Steinberg::IPtr<Steinberg::FUnknown> component)
Steinberg::IPtr<Steinberg::FUnknown> object)
: supported(
Steinberg::FUnknownPtr<Steinberg::Vst::IAudioProcessor>(component)) {}
Steinberg::FUnknownPtr<Steinberg::Vst::IAudioProcessor>(object)) {}
YaAudioProcessor::YaAudioProcessor(const ConstructArgs&& args)
: arguments(std::move(args)) {}
@@ -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<Steinberg::FUnknown> object);
+12 -47
View File
@@ -19,57 +19,22 @@
YaComponent::ConstructArgs::ConstructArgs() {}
YaComponent::ConstructArgs::ConstructArgs(
Steinberg::IPtr<Steinberg::Vst::IComponent> 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<Steinberg::FUnknown> object) {
auto component = Steinberg::FUnknownPtr<Steinberg::Vst::IComponent>(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<Steinberg ::IPluginBase*>(
static_cast<YaPluginBase*>(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) {
+13 -87
View File
@@ -16,68 +16,41 @@
#pragma once
#include <optional>
#include <set>
#include <variant>
#include <bitsery/ext/pointer.h>
#include <bitsery/ext/std_optional.h>
#include <bitsery/ext/std_set.h>
#include <bitsery/ext/std_variant.h>
#include <bitsery/traits/array.h>
#include <pluginterfaces/vst/ivstcomponent.h>
#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<Steinberg::Vst::IComponent> component,
size_t instance_id);
ConstructArgs(Steinberg::IPtr<Steinberg::FUnknown> 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 <typename S>
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<ConstructArgs, UniversalTResult>;
ArrayUID cid;
template <typename S>
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 <typename S>
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 <typename S>
void serialize(
S& s,
std::variant<YaComponent::ConstructArgs, UniversalTResult>& result) {
s.ext(result, bitsery::ext::StdVariant{});
}
@@ -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:
@@ -19,8 +19,8 @@
YaPluginBase::ConstructArgs::ConstructArgs() {}
YaPluginBase::ConstructArgs::ConstructArgs(
Steinberg::IPtr<Steinberg::FUnknown> component)
: supported(Steinberg::FUnknownPtr<Steinberg::IPluginBase>(component)) {}
Steinberg::IPtr<Steinberg::FUnknown> object)
: supported(Steinberg::FUnknownPtr<Steinberg::IPluginBase>(object)) {}
YaPluginBase::YaPluginBase(const ConstructArgs&& args)
: arguments(std::move(args)) {}
+1 -1
View File
@@ -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:
@@ -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 <https://www.gnu.org/licenses/>.
#include "plugin-monolith.h"
YaPluginMonolith::ConstructArgs::ConstructArgs() {}
YaPluginMonolith::ConstructArgs::ConstructArgs(
Steinberg::IPtr<Steinberg::FUnknown> 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<Steinberg ::IPluginBase*>(
static_cast<YaPluginBase*>(this));
return ::Steinberg ::kResultOk;
}
if (Steinberg::FUnknownPrivate ::iidEqual(
_iid, Steinberg::IPluginBase::iid)) {
addRef();
*obj = static_cast<Steinberg ::IPluginBase*>(
static_cast<YaPluginBase*>(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;
}
@@ -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 <https://www.gnu.org/licenses/>.
#pragma once
#include <bitsery/ext/std_variant.h>
#include <pluginterfaces/vst/ivstcomponent.h>
#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<FUnknown> 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 <typename S>
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<ConstructArgs, UniversalTResult>;
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 <typename S>
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 <typename S>
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 <typename S>
void serialize(
S& s,
std::variant<YaPluginMonolith::ConstructArgs, UniversalTResult>& result) {
s.ext(result, bitsery::ext::StdVariant{});
}
+33 -32
View File
@@ -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});
}
+4 -4
View File
@@ -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
@@ -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<YaComponent::ConstructArgs, UniversalTResult> result =
bridge.send_message(YaComponent::Construct{.cid = cid_array});
std::variant<YaPluginMonolith::ConstructArgs, UniversalTResult> result =
bridge.send_message(YaPluginMonolith::Construct{.cid = cid_array});
return std::visit(
overload{
[&](YaComponent::ConstructArgs&& args) -> tresult {
[&](YaPluginMonolith::ConstructArgs&& args) -> tresult {
*obj = static_cast<Steinberg::Vst::IComponent*>(
new YaComponentPluginImpl(bridge, std::move(args)));
new YaPluginMonolithImpl(bridge, std::move(args)));
return Steinberg::kResultOk;
},
[&](const UniversalTResult& code) -> tresult { return code; }},
@@ -18,6 +18,7 @@
#include "../vst3.h"
// TODO Rename to YaPluginFactoryImpl
class YaPluginFactoryPluginImpl : public YaPluginFactory {
public:
YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge,
+2 -2
View File
@@ -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<YaComponentPluginImpl>(component));
std::ref<YaPluginMonolithImpl>(component));
}
void Vst3PluginBridge::unregister_component(size_t instance_id) {
+5 -3
View File
@@ -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<Vst3Sockets<std::jthread>> {
* 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<Vst3Sockets<std::jthread>> {
* `register_component()` in the constractor, and an instance is then
* removed through a call to `unregister_component()` in the destructor.
*/
std::map<size_t, std::reference_wrapper<YaComponentPluginImpl>>
std::map<size_t, std::reference_wrapper<YaPluginMonolithImpl>>
component_instances;
std::mutex component_instances_mutex;
};
+30 -29
View File
@@ -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<Steinberg::Vst::IComponent> component =
module->getFactory()
.createInstance<Steinberg::Vst::IComponent>(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<Steinberg::Vst::IComponent> component =
module->getFactory()
.createInstance<Steinberg::Vst::IComponent>(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]