From ff2807c9398a6c0b2e65ed4486d60ddba09f634e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 22:41:06 +0100 Subject: [PATCH] Add all the boilerplate for the Vst3PluginBridge And now that I also have an idea of what the communication model will look like, this can server as a base for instantiating plugins. --- README.md | 3 + meson.build | 7 ++ src/common/communication/vst3.h | 9 +++ src/common/logging/vst2.h | 3 + src/common/logging/vst3.h | 27 +++++++ src/plugin/bridges/vst2.h | 3 +- src/plugin/bridges/vst3.cpp | 61 +++++++++++++++ src/plugin/bridges/vst3.h | 129 ++++++++++++++++++++++++++++++++ src/plugin/vst3-plugin.cpp | 72 +++++++++++++++--- 9 files changed, 303 insertions(+), 11 deletions(-) create mode 100644 src/common/logging/vst3.h create mode 100644 src/plugin/bridges/vst3.cpp create mode 100644 src/plugin/bridges/vst3.h diff --git a/README.md b/README.md index 72fcb93b..86786e49 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ imcomplete list of things that still have to be done before this can be used: figure out how to do this in the least confusing way possible. - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. +- Pay close attention to the plugin groups section, since VST3 plugins by design + cannot be hosted completely individually (as in, each plugin is basically in + its own group). - Update all the AUR packages. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? diff --git a/meson.build b/meson.build index f25f8565..7a19cfa9 100644 --- a/meson.build +++ b/meson.build @@ -74,8 +74,15 @@ vst2_plugin_sources = [ ] vst3_plugin_sources = [ + 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst2.cpp', + 'src/common/configuration.cpp', + 'src/common/plugins.cpp', + 'src/common/utils.cpp', + 'src/plugin/bridges/vst3.cpp', + 'src/plugin/host-process.cpp', + 'src/plugin/utils.cpp', 'src/plugin/vst3-plugin.cpp', version_header, ] diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index bab15217..06252170 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -29,6 +29,9 @@ * sockets, and the call to `connect()` will then accept any incoming * connections. * + * TODO: I have no idea what the best approach here is yet, so this is very much + * subject to change + * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ @@ -62,4 +65,10 @@ class Vst3Sockets : public Sockets { // Manually close all sockets so we break out of any blocking operations // that may still be active } + + // TODO: I still don't know if recursive callbacks are a thing in VST3. If + // not, then we should probably have two `AdHocSocketHandler`s per + // plugin instance (one for each direction, as with `dispatcher()` and + // `audioMaster()` in VST2). Using fewer probably also works, but we + // wouldn't want to have to spawn new sockets during audio processing. }; diff --git a/src/common/logging/vst2.h b/src/common/logging/vst2.h index 841b46bf..e02b1f16 100644 --- a/src/common/logging/vst2.h +++ b/src/common/logging/vst2.h @@ -32,6 +32,9 @@ */ std::optional opcode_to_string(bool is_dispatch, int opcode); +/** + * Provides VST2 specific logging functionality for debugging plugins. + */ class Vst2Logger : public Logger { public: // The following functions are for logging specific events, they are only diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h new file mode 100644 index 00000000..8a22a848 --- /dev/null +++ b/src/common/logging/vst3.h @@ -0,0 +1,27 @@ +// 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 "common.h" + +/** + * Provides VST3-specific logging functionality for debugging plugins. + */ +class Vst3Logger : public Logger { + public: + // TODO: Logging interface for VST3 plugins +}; diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index ce28a7aa..09f15d56 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -122,7 +121,7 @@ class Vst2PluginBridge { * The configuration for this instance of yabridge. Set based on the values * from a `yabridge.toml`, if it exists. * - * @see ./utils.h:load_config_for + * @see ../utils.h:load_config_for */ Configuration config; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp new file mode 100644 index 00000000..a02e2aae --- /dev/null +++ b/src/plugin/bridges/vst3.cpp @@ -0,0 +1,61 @@ +// 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 "vst3.h" + +#include "../../common/utils.h" + +// TODO: Do all of the initialization stuff from `Vst2PluginBridge` +Vst3PluginBridge::Vst3PluginBridge() + : // TODO: This is technically correct because we can configure the entire + // directory at once + config(load_config_for(get_this_file_location())), + // TODO: This is incorrect for VST3 modules + plugin_module_path(find_vst_plugin()), + io_context(), + sockets(io_context, + // TODO: This is incorrect + generate_endpoint_base( + plugin_module_path.filename().replace_extension("").string()), + true), + logger(Logger::create_from_environment( + create_logger_prefix(sockets.base_dir))), + wine_version(get_wine_version()), + vst_host( + config.group + ? std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{.plugin_type = PluginType::vst2, + .plugin_path = plugin_module_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}, + sockets, + *config.group)) + : std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{ + .plugin_type = PluginType::vst2, + .plugin_path = plugin_module_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}))), + has_realtime_priority(set_realtime_priority()), + wine_io_handler([&]() { io_context.run(); }) { + log_init_message(); +} + +void Vst3PluginBridge::log_init_message() { + // TODO: Move `Vst2PluginBridge::log_init_message()` to utils and call that +} diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h new file mode 100644 index 00000000..50cd70ec --- /dev/null +++ b/src/plugin/bridges/vst3.h @@ -0,0 +1,129 @@ +// 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 +#include + +#include "../../common/communication/vst3.h" +#include "../../common/configuration.h" +#include "../../common/logging/vst3.h" +#include "../host-process.h" + +/** + * This handles the communication between the native host and a VST3 plugin + * hosted in our Wine plugin host. VST3 is handled very differently from VST2 + * because a plugin is no longer its own entity, but rather a definition of + * objects that the host can create and interconnect. This `Vst3PluginBridge` + * will be instantiated when the plugin first gets loaded, and it will survive + * until the last instance of the plugin gets removed. The Wine host process + * will thus also have the same lifetime, and even with yabridge's 'individual' + * plugin hosting other instances of the same plugin will be handled by a single + * process. + * + * @remark See the comments at the top of `vst3-plugin.cpp` for more + * information. + * + * The naming scheme of all of these 'bridge' classes is `{,Plugin}Bridge` + * for greppability reasons. The `Plugin` infix is added on the native plugin + * side. + */ +class Vst3PluginBridge { + public: + /** + * Initializes the VST3 module by starting and setting up communicating with + * the Wine plugin host. + * + * @throw std::runtime_error Thrown when the Wine plugin host could not be + * found, or if it could not locate and load a VST3 module. + */ + Vst3PluginBridge(); + + /** + * The configuration for this instance of yabridge. Set based on the values + * from a `yabridge.toml`, if it exists. + * + * @see ../utils.h:load_config_for + */ + Configuration config; + + /** + * The path to the VST3 module being loaded in the Wine VST host. This is + * normally a directory called `MyPlugin.vst3` that contains + * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older + * deprecated (but still ubiquitous) format where the top level + * `MyPlugin.vst3` is not a directory but a .dll file. This points to either + * of those things, and then `VST3::Hosting::Win32Module::create()` will be + * able to load it. + * + * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 + */ + const boost::filesystem::path plugin_module_path; + + private: + /** + * Format and log all relevant debug information during initialization. + */ + void log_init_message(); + + boost::asio::io_context io_context; + Vst3Sockets sockets; + + /** + * The logging facility used for this instance of yabridge. See + * `Logger::create_from_env()` for how this is configured. + * + * @see Logger::create_from_env + */ + Vst3Logger logger; + + /** + * The version of Wine currently in use. Used in the debug output on plugin + * startup. + */ + const std::string wine_version; + + /** + * The Wine process hosting the Windows VST3 plugin. + * + * @see launch_vst_host + */ + std::unique_ptr vst_host; + + /** + * A thread used during the initialisation process to terminate listening on + * the sockets if the Wine process cannot start for whatever reason. This + * has to be defined here instead of in the constructor we can't simply + * detach the thread as it has to check whether the Wine plugin host is + * still running. + */ + std::jthread host_guard_handler; + + /** + * Whether this process runs with realtime priority. We'll set this _after_ + * spawning the Wine process because from my testing running wineserver with + * realtime priority can actually increase latency. + */ + bool has_realtime_priority; + + /** + * Runs the Boost.Asio `io_context` thread for logging the Wine process + * STDOUT and STDERR messages. + */ + std::jthread wine_io_handler; +}; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index bb55be74..cd12d108 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -1,17 +1,66 @@ +// 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 -// TODO: Should you include this implementation file or copy everything over? #include +#include "bridges/vst3.h" + using Steinberg::gPluginFactory; -// TODO: What should we do here? Also, note to self, don't forget to call these -// on the Wine host side if the host SDK doesn't already do that for us. +// Because VST3 plugins consist of completely independent components that have +// to be initialized and connected by the host, hosting a VST3 plugin through +// yabridge works very differently from hosting VST2 plugin. Even with +// individually hosted plugins, all instances of the plugin will be handled by a +// single dynamic library (that VST3 calls a 'module'). Because of this, we'll +// spawn our host process when the first instance of a plugin gets initialized, +// and when the last instance exits so will the host process. +// +// Even though the new VST3 module format where everything's inside of a bundle +// is not particularly common, it is the only standard for Linux and that's what +// we'll use. The installation format for yabridge will thus have the Windows +// plugin symlinked to either the `x86_64-win` or the `x86-win` directory inside +// of the bundle, even if it does not come in a bundle itself. + +Vst3PluginBridge* bridge = nullptr; + bool InitModule() { - return true; + assert(bridge == nullptr); + + try { + // This is the only place where we have to use manual memory management. + // The bridge's destructor is called when the `effClose` opcode is + // received. + bridge = new Vst3PluginBridge(); + + return true; + } catch (const std::exception& error) { + Logger logger = Logger::create_from_environment(); + logger.log("Error during initialization:"); + logger.log(error.what()); + + return false; + } } bool DeinitModule() { + assert(bridge != nullptr); + + delete bridge; return true; } @@ -21,9 +70,11 @@ bool DeinitModule() { * classes, and then recreate it here. */ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { - // TODO: So from this I can imagine that the host is supposed to keep this - // module loaded into memory and reuse it for multiple plugins? How - // should Wine host instances be tied to native plugin instances? + // TODO: Check the VST3::Hosting module loading source to see if + // gPluginFactory is used directly by the host or not. + // TODO: We can do all of our allocations and things indie of + // `Vst3PluginBridge`, so this function should call some function on + // Vst3Bridge that does the initialization if (!gPluginFactory) { // TODO: Here we want to: // 1) Load the plugin on the Wine host @@ -64,8 +115,11 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // also installed there. // // The second one sounds much better, but it will still need some - // more consideration. Also, yabridgectl will need to do some - // extra work there to detect removed plugins. + // more consideration. Aside from that VST3 plugins also have a + // centralized preset location, even though barely anyone uses it, + // yabridgectl will also have to make a symlink of that. Also, + // yabridgectl will need to do some extra work there to detect + // removed plugins. // TODO: Also symlink presets, and allow pruning broken symlinks there // as well // TODO: And how do we choose between 32-bit and 64-bit versions of a