diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h
new file mode 100644
index 00000000..c845a2c9
--- /dev/null
+++ b/src/plugin/bridges/common.h
@@ -0,0 +1,247 @@
+// 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
+
+// Generated inside of the build directory
+#include
+#include
+
+#include "../../common/configuration.h"
+#include "../../common/utils.h"
+#include "../host-process.h"
+
+/**
+ * Handles all common operations for hosting plugins such as setting up the
+ * plugin host process, the logger, and logging debug information on startup.
+ *
+ * @tparam Sockets the `Sockets` implementation to use. We have to initialize it
+ * here because we need to pass it to our `HostProcess`.
+ */
+template TSockets>
+class PluginBridge {
+ public:
+ /**
+ * Sets up everything needed to start the host process. Classes deriving
+ * from this should call `log_init_message()` themselves after their
+ * initialization list.
+ *
+ * @param plugin_type The type of the plugin we're handling.
+ * @param plugin_path The path to the plugin. For VST2 plugins this is the
+ * path to the `.dll` file, and for VST3 plugins this is the path to the
+ * module (either a `.vst3` DLL file or a bundle).
+ * @param create_socket_instance A function to create a socket instance.
+ * Using a lambda here feels wrong, but I can't think of a better
+ * solution right now.
+ */
+ template
+ PluginBridge(PluginType plugin_type,
+ const boost::filesystem::path& plugin_path,
+ F create_socket_instance)
+ : plugin_type(plugin_type),
+ plugin_path(plugin_path),
+ io_context(),
+ sockets(create_socket_instance(io_context)),
+ config(load_config_for(get_this_file_location())),
+ generic_logger(Logger::create_from_environment(
+ create_logger_prefix(sockets.base_dir))),
+ plugin_host(
+ config.group
+ ? std::unique_ptr(std::make_unique(
+ io_context,
+ generic_logger,
+ HostRequest{
+ .plugin_type = plugin_type,
+ .plugin_path = plugin_path.string(),
+ .endpoint_base_dir = sockets.base_dir.string()},
+ sockets,
+ *config.group))
+ : std::unique_ptr(
+ std::make_unique(
+ io_context,
+ generic_logger,
+ HostRequest{.plugin_type = plugin_type,
+ .plugin_path = plugin_path.string(),
+ .endpoint_base_dir =
+ sockets.base_dir.string()}))),
+ has_realtime_priority(set_realtime_priority()),
+ wine_io_handler([&]() { io_context.run(); }) {}
+
+ virtual ~PluginBridge(){};
+
+ protected:
+ /**
+ * Format and log all relevant debug information during initialization.
+ */
+ void log_init_message() {
+ std::stringstream init_msg;
+
+ init_msg << "Initializing yabridge version " << yabridge_git_version
+ << std::endl;
+ init_msg << "host: '" << plugin_host->path().string() << "'"
+ << std::endl;
+ init_msg << "plugin: '" << plugin_path.string() << "'"
+ << std::endl;
+ init_msg << "plugin type: '" << plugin_type_to_string(plugin_type)
+ << "'" << std::endl;
+ init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no")
+ << "'" << std::endl;
+ init_msg << "sockets: '" << sockets.base_dir.string() << "'"
+ << std::endl;
+ init_msg << "wine prefix: '";
+
+ // If the Wine prefix is manually overridden, then this should be made
+ // clear. This follows the behaviour of `set_wineprefix()`.
+ boost::process::environment env = boost::this_process::environment();
+ if (!env["WINEPREFIX"].empty()) {
+ init_msg << env["WINEPREFIX"].to_string() << " ";
+ } else {
+ init_msg << find_wineprefix().value_or("").string();
+ }
+ init_msg << "'" << std::endl;
+
+ init_msg << "wine version: '" << get_wine_version() << "'" << std::endl;
+ init_msg << std::endl;
+
+ // Print the path to the currently loaded configuration file and all
+ // settings in use. Printing the matched glob pattern could also be
+ // useful but it'll be very noisy and it's likely going to be clear from
+ // the shown values anyways.
+ init_msg << "config from: '"
+ << config.matched_file.value_or("").string() << "'"
+ << std::endl;
+
+ init_msg << "hosting mode: '";
+ if (config.group) {
+ init_msg << "plugin group \"" << *config.group << "\"";
+ } else {
+ init_msg << "individually";
+ }
+ if (plugin_host->architecture() == LibArchitecture::dll_32) {
+ init_msg << ", 32-bit";
+ } else {
+ init_msg << ", 64-bit";
+ }
+ init_msg << "'" << std::endl;
+
+ init_msg << "other options: ";
+ std::vector other_options;
+ if (config.cache_time_info) {
+ other_options.push_back("hack: time info cache");
+ }
+ if (config.editor_double_embed) {
+ other_options.push_back("editor: double embed");
+ }
+ if (!other_options.empty()) {
+ init_msg << join_quoted_strings(other_options) << std::endl;
+ } else {
+ init_msg << "''" << std::endl;
+ }
+
+ // To make debugging easier, we'll print both unrecognized options (that
+ // might be left over when an option gets removed) as well as options
+ // have the wrong argument types
+ if (!config.invalid_options.empty()) {
+ init_msg << "invalid arguments: "
+ << join_quoted_strings(config.invalid_options)
+ << " (check the readme for more information)" << std::endl;
+ }
+ if (!config.unknown_options.empty()) {
+ init_msg << "unrecognized options: "
+ << join_quoted_strings(config.unknown_options)
+ << std::endl;
+ }
+ init_msg << std::endl;
+
+ // Include a list of enabled compile-tiem features, mostly to make debug
+ // logs more useful
+ init_msg << "Enabled features:" << std::endl;
+#ifdef WITH_BITBRIDGE
+ init_msg << "- bitbridge support" << std::endl;
+#endif
+#ifdef WITH_WINEDBG
+ init_msg << "- winedbg" << std::endl;
+#endif
+#ifdef WITH_VST3
+ init_msg << "- VST3 support" << std::endl;
+#endif
+#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG) || defined(WITH_VST3))
+ init_msg << " " << std::endl;
+#endif
+ init_msg << std::endl;
+
+ for (std::string line = ""; std::getline(init_msg, line);) {
+ generic_logger.log(line);
+ }
+ }
+
+ /**
+ * The type of the plugin we're dealing with. Passed to the host process and
+ * printed in the initialisation message.
+ */
+ const PluginType plugin_type;
+
+ /**
+ * The path to the plugin (`.dll` or module) being loaded in the Wine plugin
+ * host.
+ */
+ const boost::filesystem::path plugin_path;
+
+ boost::asio::io_context io_context;
+
+ /**
+ * The sockets used for communication with the Wine process.
+ *
+ * @see PluginBridge::log_init_message
+ */
+ TSockets sockets;
+
+ /**
+ * 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 logging facility used for this instance of yabridge. See
+ * `Logger::create_from_env()` for how this is configured.
+ *
+ * @see Logger::create_from_env
+ */
+ Logger generic_logger;
+
+ /**
+ * The Wine process hosting our plugins. In the case of group hosts a
+ * `PluginBridge` instance doesn't actually own a process, but rather either
+ * spawns a new detached process or it connects to an existing one.
+ */
+ std::unique_ptr plugin_host;
+
+ /**
+ * 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/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp
index c716d8ed..c52213bd 100644
--- a/src/plugin/bridges/vst2.cpp
+++ b/src/plugin/bridges/vst2.cpp
@@ -16,15 +16,9 @@
#include "vst2.h"
-// Generated inside of the build directory
-#include
-#include
-
#include "../../common/communication/vst2.h"
-#include "../../common/utils.h"
#include "../utils.h"
-namespace bp = boost::process;
// I'd rather use std::filesystem instead, but Boost.Process depends on
// boost::filesystem
namespace fs = boost::filesystem;
@@ -46,42 +40,28 @@ Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) {
}
Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback)
- : config(load_config_for(get_this_file_location())),
- vst_plugin_path(find_vst_plugin()),
+ : PluginBridge(PluginType::vst2,
+ find_vst_plugin(),
+ [](boost::asio::io_context& io_context) {
+ return Vst2Sockets(
+ io_context,
+ generate_endpoint_base(find_vst_plugin()
+ .filename()
+ .replace_extension("")
+ .string()),
+ true);
+ }),
// All the fields should be zero initialized because
// `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin
// bridge will crash otherwise
plugin(),
- io_context(),
- sockets(io_context,
- generate_endpoint_base(
- vst_plugin_path.filename().replace_extension("").string()),
- true),
host_callback_function(host_callback),
- // This weird cast is not needed, but without it clang/ccls won't shut up
+ // TODO: This is UB, use composition with `generic_logger` instead
logger(static_cast(Logger::create_from_environment(
- create_logger_prefix(sockets.base_dir)))),
- vst_host(
- config.group
- ? std::unique_ptr(std::make_unique(
- io_context,
- logger,
- HostRequest{.plugin_type = plugin_type,
- .plugin_path = vst_plugin_path.string(),
- .endpoint_base_dir = sockets.base_dir.string()},
- sockets,
- *config.group))
- : std::unique_ptr(std::make_unique(
- io_context,
- logger,
- HostRequest{
- .plugin_type = plugin_type,
- .plugin_path = vst_plugin_path.string(),
- .endpoint_base_dir = sockets.base_dir.string()}))),
- has_realtime_priority(set_realtime_priority()),
- wine_io_handler([&]() { io_context.run(); }) {
+ create_logger_prefix(sockets.base_dir)))) {
log_init_message();
+ // TODO: Also move his to `PluginHost`
#ifndef WITH_WINEDBG
// If the Wine process fails to start, then nothing will connect to the
// sockets and we'll be hanging here indefinitely. To prevent this, we'll
@@ -93,7 +73,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback)
using namespace std::literals::chrono_literals;
while (!st.stop_requested()) {
- if (!vst_host->running()) {
+ if (!plugin_host->running()) {
logger.log(
"The Wine host process has exited unexpectedly. Check the "
"output above for more information.");
@@ -439,7 +419,7 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/,
logger.log("The plugin crashed during shutdown, ignoring");
}
- vst_host->terminate();
+ plugin_host->terminate();
// The `stop()` method will cause the IO context to just drop all of
// its work immediately and not throw any exceptions that would have
@@ -619,107 +599,6 @@ void Vst2PluginBridge::set_parameter(AEffect* /*plugin*/,
assert(!response.value);
}
-void Vst2PluginBridge::log_init_message() {
- std::stringstream init_msg;
-
- init_msg << "Initializing yabridge version " << yabridge_git_version
- << std::endl;
- init_msg << "host: '" << vst_host->path().string() << "'"
- << std::endl;
- init_msg << "plugin: '" << vst_plugin_path.string() << "'"
- << std::endl;
- init_msg << "plugin type: '" << plugin_type_to_string(plugin_type) << "'"
- << std::endl;
- init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no")
- << "'" << std::endl;
- init_msg << "sockets: '" << sockets.base_dir.string() << "'"
- << std::endl;
- init_msg << "wine prefix: '";
-
- // If the Wine prefix is manually overridden, then this should be made
- // clear. This follows the behaviour of `set_wineprefix()`.
- bp::environment env = boost::this_process::environment();
- if (!env["WINEPREFIX"].empty()) {
- init_msg << env["WINEPREFIX"].to_string() << " ";
- } else {
- init_msg << find_wineprefix().value_or("").string();
- }
- init_msg << "'" << std::endl;
-
- init_msg << "wine version: '" << get_wine_version() << "'" << std::endl;
- init_msg << std::endl;
-
- // Print the path to the currently loaded configuration file and all
- // settings in use. Printing the matched glob pattern could also be useful
- // but it'll be very noisy and it's likely going to be clear from the shown
- // values anyways.
- init_msg << "config from: '"
- << config.matched_file.value_or("").string() << "'"
- << std::endl;
-
- init_msg << "hosting mode: '";
- if (config.group) {
- init_msg << "plugin group \"" << *config.group << "\"";
- } else {
- init_msg << "individually";
- }
- if (vst_host->architecture() == LibArchitecture::dll_32) {
- init_msg << ", 32-bit";
- } else {
- init_msg << ", 64-bit";
- }
- init_msg << "'" << std::endl;
-
- init_msg << "other options: ";
- std::vector other_options;
- if (config.cache_time_info) {
- other_options.push_back("hack: time info cache");
- }
- if (config.editor_double_embed) {
- other_options.push_back("editor: double embed");
- }
- if (!other_options.empty()) {
- init_msg << join_quoted_strings(other_options) << std::endl;
- } else {
- init_msg << "''" << std::endl;
- }
-
- // To make debugging easier, we'll print both unrecognized options (that
- // might be left over when an option gets removed) as well as options have
- // the wrong argument types
- if (!config.invalid_options.empty()) {
- init_msg << "invalid arguments: "
- << join_quoted_strings(config.invalid_options)
- << " (check the readme for more information)" << std::endl;
- }
- if (!config.unknown_options.empty()) {
- init_msg << "unrecognized options: "
- << join_quoted_strings(config.unknown_options) << std::endl;
- }
- init_msg << std::endl;
-
- // Include a list of enabled compile-tiem features, mostly to make debug
- // logs more useful
- init_msg << "Enabled features:" << std::endl;
-#ifdef WITH_BITBRIDGE
- init_msg << "- bitbridge support" << std::endl;
-#endif
-#ifdef WITH_WINEDBG
- init_msg << "- winedbg" << std::endl;
-#endif
-#ifdef WITH_VST3
- init_msg << "- VST3 support" << std::endl;
-#endif
-#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG) || defined(WITH_VST3))
- init_msg << " " << std::endl;
-#endif
- init_msg << std::endl;
-
- for (std::string line = ""; std::getline(init_msg, line);) {
- logger.log(line);
- }
-}
-
// The below functions are proxy functions for the methods defined in
// `Bridge.cpp`
diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h
index 6a2fcd1a..19d6e3de 100644
--- a/src/plugin/bridges/vst2.h
+++ b/src/plugin/bridges/vst2.h
@@ -23,9 +23,8 @@
#include
#include "../../common/communication/vst2.h"
-#include "../../common/configuration.h"
#include "../../common/logging/vst2.h"
-#include "../host-process.h"
+#include "common.h"
/**
* This handles the communication between the Linux native VST2 plugin and the
@@ -36,7 +35,7 @@
* for greppability reasons. The `Plugin` infix is added on the native plugin
* side.
*/
-class Vst2PluginBridge {
+class Vst2PluginBridge : PluginBridge> {
public:
/**
* Initializes the Wine VST bridge. This sets up the sockets for event
@@ -50,12 +49,6 @@ class Vst2PluginBridge {
*/
Vst2PluginBridge(audioMasterCallback host_callback);
- /**
- * The type of the plugin we're dealing with. Passed to the host process and
- * printed in the initialisation message.
- */
- static constexpr PluginType plugin_type = PluginType::vst2;
-
// The four below functions are the handlers from the VST2 API. They are
// called through proxy functions in `plugin.cpp`.
@@ -123,19 +116,6 @@ class Vst2PluginBridge {
template
void do_process(T** inputs, T** outputs, int sample_frames);
- /**
- * 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 .dll being loaded in the Wine VST host.
- */
- const boost::filesystem::path vst_plugin_path;
-
/**
* This AEffect struct will be populated using the data passed by the Wine
* VST host during initialization and then passed as a pointer to the Linux
@@ -144,14 +124,6 @@ class Vst2PluginBridge {
AEffect plugin;
private:
- /**
- * Format and log all relevant debug information during initialization.
- */
- void log_init_message();
-
- boost::asio::io_context io_context;
- Vst2Sockets sockets;
-
/**
* The thread that handles host callbacks.
*/
@@ -171,20 +143,11 @@ class Vst2PluginBridge {
audioMasterCallback host_callback_function;
/**
- * The logging facility used for this instance of yabridge. See
- * `Logger::create_from_env()` for how this is configured.
- *
- * @see Logger::create_from_env
+ * The logging facility used for this instance of yabridge. Wraps around
+ * `PluginBridge::generic_logger`.
*/
Vst2Logger logger;
- /**
- * The Wine process hosting the Windows VST 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
@@ -194,19 +157,6 @@ class Vst2PluginBridge {
*/
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;
-
/**
* A scratch buffer for sending and receiving data during `process`,
* `processReplacing` and `processDoubleReplacing` calls.
diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h
index 50cd70ec..106201d2 100644
--- a/src/plugin/bridges/vst3.h
+++ b/src/plugin/bridges/vst3.h
@@ -42,6 +42,8 @@
* 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.
+ *
+ * TODO: Also inherit this from PluginBridge
*/
class Vst3PluginBridge {
public:
@@ -85,10 +87,8 @@ class Vst3PluginBridge {
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
+ * The logging facility used for this instance of yabridge. Wraps around
+ * `PluginBridge::generic_logger`.
*/
Vst3Logger logger;
diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp
index a9e7d894..0693b39c 100644
--- a/src/plugin/utils.cpp
+++ b/src/plugin/utils.cpp
@@ -81,6 +81,8 @@ fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) {
}
fs::path find_vst_plugin() {
+ // TODO: This has to be able to differentiate between VST2 plugins and VST3
+ // modules
const fs::path this_plugin_path = get_this_file_location();
fs::path plugin_path(this_plugin_path);