diff --git a/meson.build b/meson.build index 20e7a306..54162984 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', @@ -105,7 +106,8 @@ host_sources = [ if with_vst3 host_sources += [ - 'src/wine-host/bridges/vst3.cpp' + 'src/common/serialization/vst3/plugin-factory.cpp', + 'src/wine-host/bridges/vst3.cpp', ] endif diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 065bcb1b..27dfb589 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,6 +23,7 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" +#include "vst3/plugin-factory.h" // Event handling for our VST3 plugins works slightly different from how we // handle VST2 plugins. VST3 does not have a centralized event dispatching diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md new file mode 100644 index 00000000..e25d87db --- /dev/null +++ b/src/common/serialization/vst3/README.md @@ -0,0 +1,54 @@ +# VST3 serialization + +TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps +replace this readme with a link to that document. + +The VST3 SDK uses an architecture where every object inherits from an interface, +and every interface inherits from `FUnknown` which offers a sort of query +interface. Newer versions of an interface with added functionality then inherit +from the previous version of that interface. Every interface (and thus also +newer versions of an old interface) get a unique identifier. It then uses a +smart pointer system (`FUnknownPtr`) that queries whether the `FUnknown` +matches a certain interface by checking whether the IDs match up, allowing casts +to that interface if the `FUnkonwn` matches. This means that an +`IPluginFactory*` may also be an `IPluginFactory2*` or an `IPluginFactory3*`. +For yabridge we need to be able to pass concrete serializable objects that +implement these interfaces around. + +## Serializing simple objects + +TODO: Think of a better naming scheme + +Serializing an object that implements `ISimple` that only stores data and can't +perform any callbacks works as follows: + +1. We define a class called `YaSimple` that inherits from `ISimple`. +2. We fetch all data from `ISimple` and store it in `YaSimple`. +3. `YaSimpl` can then be serialized with bitsery and transmitted like any other + object. + +Our +solution approach for serializing Our solution here is to build an object that's compatible with +`IPluginFactory3` that copies all data from the original object + +## Serializing versioned interfaces + +For serializing versioned interfaces, such as `IPluginFactory3`, we'll do +something similar: + +1. As with simple object, we define a class called `YaPluginFactory` that + inherits from `IPluginFactory3`. +2. Now we start copying data starting with `IPluginFactory`, then moving on to + `IPluginFactory2`, and then finally `IPluginFactory3`. When at some point our + `FUnknownPtr` returns a null pointer we know that the object doesn't + implement that version of the interface and we can stop. +3. During the copying process we'll also copy over the `iid`. This allows our + object to appear as the highest version of the interface we were able to copy + from. Doing this avoids complicated inheritance chains in our own + implemetnation. +4. `YaPluginFactory` can then be serialized with bitsery and transmitted like + any other object. + +## Processors and controllers + +TODO: Not sure how this will work yet. diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp new file mode 100644 index 00000000..08fd4195 --- /dev/null +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -0,0 +1,97 @@ +// 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-factory.h" + +YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} + +YaPluginFactory::YaPluginFactory(Steinberg::IPluginFactory* /*factory*/){ + FUNKNOWN_CTOR + + // TODO: Copy everything from `factory` +} + +YaPluginFactory::~YaPluginFactory() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaPluginFactory) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::IPluginFactory) + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, + Steinberg::IPluginFactory) + if (actual_iid == Steinberg::IPluginFactory2::iid || + actual_iid == Steinberg::IPluginFactory3::iid) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid, + Steinberg::IPluginFactory2) + } + if (actual_iid == Steinberg::IPluginFactory3::iid) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid, + Steinberg::IPluginFactory3) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} + +tresult PLUGIN_API +YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { + // TODO: Implement + return 0; +} + +int32 PLUGIN_API YaPluginFactory::countClasses() { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, + Steinberg::PClassInfo* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API YaPluginFactory::createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::getClassInfoUnicode(int32 index, + Steinberg::PClassInfoW* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::setHostContext(Steinberg::FUnknown* context) { + // TODO: Implement + return 0; +} diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h new file mode 100644 index 00000000..0ce61703 --- /dev/null +++ b/src/common/serialization/vst3/plugin-factory.h @@ -0,0 +1,81 @@ +// 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 "../../bitsery/ext/vst3.h" + +namespace { +using Steinberg::int32, Steinberg::tresult; +} // namespace + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPluginFactory{1,2,3}` for serialization purposes. See + * `README.md` for more information on how this works. + */ +class YaPluginFactory : public Steinberg::IPluginFactory3 { + public: + YaPluginFactory(); + + /** + * Create a copy of an existing plugin factory. Depending on the + supported + * interface function more or less of this struct will be left empty, and + * `iid` will be set accordingly. + * + * TODO: Check if we don't need a custom query interface, we probably do. + */ + explicit YaPluginFactory(Steinberg::IPluginFactory* factory); + + ~YaPluginFactory(); + + DECLARE_FUNKNOWN_METHODS + + // From `IPluginFactory` + tresult PLUGIN_API getFactoryInfo(Steinberg::PFactoryInfo* info) override; + int32 PLUGIN_API countClasses() override; + tresult PLUGIN_API getClassInfo(Steinberg::int32 index, + Steinberg::PClassInfo* info) override; + tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override; + + // From `IPluginFactory2` + tresult PLUGIN_API getClassInfo2(int32 index, + Steinberg::PClassInfo2* info) override; + + // From `IPluginFactory3` + tresult PLUGIN_API + getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; + tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + + /** + * The IID of the interface we should report as. + */ + Steinberg::FUID actual_iid; + + template + void serialize(S& s) { + s.ext(actual_iid, bitsery::ext::FUID()); + } +}; + +#pragma GCC diagnostic pop