mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
💥 Encapsulate and rework all socket logic
This is a pretty huge change that will be important for being able to handle nested or mutually recursive `dispatch()` and `audioMaster()` calls. This sadly all had to be done in a single commit, so here's a summary: - `src/common/sockets.h:Sockets` contains all sockets on both the plugin and the Wine host side, and is used to both listen on and connect to the sockets. - Sockets and other temporary files respect `$XDG_RUNTIME_DIR` instead of being dumped in `/tmp`. - All sockets now have a unique endpoint in `/run/user/<uid>/yabridge-<plugin_name>-<random_id>/`. This is important for when we want to have multiple socket connections for handling `dispatch()` and `audioMaster()`. - Because of the above, we no longer clean up the socket endpoint files after the connection gets established during initialization. Instead we'll remove the socket base directory when shutting down.
This commit is contained in:
@@ -209,10 +209,12 @@ void GroupBridge::accept_requests() {
|
||||
// this has to be done on the same thread that's handling messages,
|
||||
// and all window messages have to be handled from the same thread.
|
||||
logger.log("Received request to host '" + request.plugin_path +
|
||||
"' using socket '" + request.socket_path + "'");
|
||||
"' using socket endpoint base directory '" +
|
||||
request.endpoint_base_dir + "'");
|
||||
try {
|
||||
auto bridge = std::make_unique<Vst2Bridge>(
|
||||
plugin_context, request.plugin_path, request.socket_path);
|
||||
plugin_context, request.plugin_path,
|
||||
request.endpoint_base_dir);
|
||||
logger.log("Finished initializing '" + request.plugin_path +
|
||||
"'");
|
||||
|
||||
|
||||
@@ -67,16 +67,10 @@ Vst2Bridge& get_bridge_instance(const AEffect* plugin) {
|
||||
|
||||
Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
|
||||
std::string plugin_dll_path,
|
||||
std::string socket_endpoint_path)
|
||||
std::string endpoint_base_dir)
|
||||
: io_context(main_context),
|
||||
plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
|
||||
socket_endpoint(socket_endpoint_path),
|
||||
host_vst_dispatch(io_context),
|
||||
host_vst_dispatch_midi_events(io_context),
|
||||
vst_host_callback(io_context),
|
||||
host_vst_parameters(io_context),
|
||||
host_vst_process_replacing(io_context),
|
||||
host_vst_control(io_context) {
|
||||
sockets(io_context, endpoint_base_dir, false) {
|
||||
// Got to love these C APIs
|
||||
if (!plugin_handle) {
|
||||
throw std::runtime_error("Could not load the Windows .dll file at '" +
|
||||
@@ -101,14 +95,7 @@ Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
|
||||
"'.");
|
||||
}
|
||||
|
||||
// It's very important that these sockets are accepted to in the same order
|
||||
// in the Linux plugin
|
||||
host_vst_dispatch.connect(socket_endpoint);
|
||||
host_vst_dispatch_midi_events.connect(socket_endpoint);
|
||||
vst_host_callback.connect(socket_endpoint);
|
||||
host_vst_parameters.connect(socket_endpoint);
|
||||
host_vst_process_replacing.connect(socket_endpoint);
|
||||
host_vst_control.connect(socket_endpoint);
|
||||
sockets.connect();
|
||||
|
||||
// Initialize after communication has been set up
|
||||
// We'll try to do the same `get_bridge_isntance` trick as in
|
||||
@@ -132,13 +119,14 @@ Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
|
||||
// of this object will be sent over the `dispatcher()` socket. This would be
|
||||
// done after the host calls `effOpen()`, and when the plugin calls
|
||||
// `audioMasterIOChanged()`.
|
||||
write_object(host_vst_control, EventResult{.return_value = 0,
|
||||
.payload = *plugin,
|
||||
.value_payload = std::nullopt});
|
||||
write_object(sockets.host_vst_control,
|
||||
EventResult{.return_value = 0,
|
||||
.payload = *plugin,
|
||||
.value_payload = std::nullopt});
|
||||
|
||||
// After sending the AEffect struct we'll receive this instance's
|
||||
// configuration as a response
|
||||
config = read_object<Configuration>(host_vst_control);
|
||||
config = read_object<Configuration>(sockets.host_vst_control);
|
||||
|
||||
// This works functionally identically to the `handle_dispatch()` function,
|
||||
// but this socket will only handle MIDI events and it will handle them
|
||||
@@ -160,7 +148,7 @@ void Vst2Bridge::handle_dispatch() {
|
||||
while (true) {
|
||||
try {
|
||||
receive_event(
|
||||
host_vst_dispatch, std::nullopt,
|
||||
sockets.host_vst_dispatch, std::nullopt,
|
||||
passthrough_event(
|
||||
plugin,
|
||||
[&](AEffect* plugin, int opcode, int index, intptr_t value,
|
||||
@@ -194,7 +182,8 @@ void Vst2Bridge::handle_dispatch_midi_events() {
|
||||
while (true) {
|
||||
try {
|
||||
receive_event(
|
||||
host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) {
|
||||
sockets.host_vst_dispatch_midi_events, std::nullopt,
|
||||
[&](Event& event) {
|
||||
if (BOOST_LIKELY(event.opcode == effProcessEvents)) {
|
||||
// For 99% of the plugins we can just call
|
||||
// `effProcessReplacing()` and be done with it, but a
|
||||
@@ -255,19 +244,19 @@ void Vst2Bridge::handle_parameters() {
|
||||
// through on this socket since they have a lot of overlap. The
|
||||
// presence of the `value` field tells us which one we're dealing
|
||||
// with.
|
||||
auto request = read_object<Parameter>(host_vst_parameters);
|
||||
auto request = read_object<Parameter>(sockets.host_vst_parameters);
|
||||
if (request.value) {
|
||||
// `setParameter`
|
||||
plugin->setParameter(plugin, request.index, *request.value);
|
||||
|
||||
ParameterResult response{std::nullopt};
|
||||
write_object(host_vst_parameters, response);
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
} else {
|
||||
// `getParameter`
|
||||
float value = plugin->getParameter(plugin, request.index);
|
||||
|
||||
ParameterResult response{value};
|
||||
write_object(host_vst_parameters, response);
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this
|
||||
@@ -288,8 +277,8 @@ void Vst2Bridge::handle_process_replacing() {
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
auto request = read_object<AudioBuffers>(host_vst_process_replacing,
|
||||
process_buffer);
|
||||
auto request = read_object<AudioBuffers>(
|
||||
sockets.host_vst_process_replacing, process_buffer);
|
||||
// Let the plugin process the MIDI events that were received since
|
||||
// the last buffer, and then clean up those events. This approach
|
||||
// should not be needed but Kontakt only stores pointers to rather
|
||||
@@ -348,8 +337,8 @@ void Vst2Bridge::handle_process_replacing() {
|
||||
|
||||
AudioBuffers response{output_buffers_single_precision,
|
||||
request.sample_frames};
|
||||
write_object(host_vst_process_replacing, response,
|
||||
process_buffer);
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
},
|
||||
[&](std::vector<std::vector<double>>& input_buffers) {
|
||||
// Exactly the same as the above, but for double
|
||||
@@ -373,8 +362,8 @@ void Vst2Bridge::handle_process_replacing() {
|
||||
|
||||
AudioBuffers response{output_buffers_double_precision,
|
||||
request.sample_frames};
|
||||
write_object(host_vst_process_replacing, response,
|
||||
process_buffer);
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
}},
|
||||
request.buffers);
|
||||
|
||||
@@ -419,7 +408,7 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
|
||||
// When hosting multiple plugins in a group process, all plugins
|
||||
// should get a unique window class
|
||||
const std::string window_class =
|
||||
"yabridge plugin " + socket_endpoint.path();
|
||||
"yabridge plugin " + sockets.base_dir.string();
|
||||
Editor& editor_instance = editor.emplace<Editor>(
|
||||
config, window_class, x11_handle, plugin);
|
||||
|
||||
@@ -581,7 +570,7 @@ intptr_t Vst2Bridge::host_callback(AEffect* effect,
|
||||
}
|
||||
|
||||
HostCallbackDataConverter converter(effect, time_info);
|
||||
return send_event(vst_host_callback, host_callback_mutex, converter,
|
||||
return send_event(sockets.vst_host_callback, host_callback_mutex, converter,
|
||||
std::nullopt, opcode, index, value, data, option);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "../../common/configuration.h"
|
||||
#include "../../common/logging.h"
|
||||
#include "../../common/communication.h"
|
||||
#include "../editor.h"
|
||||
#include "../utils.h"
|
||||
|
||||
@@ -46,7 +47,7 @@ struct EditorOpening {};
|
||||
* plugin and provides host callback function for the plugin to talk back.
|
||||
*
|
||||
* @remark Because of Win32 API limitations, all window handling has to be done
|
||||
* from the same thread. Most plugins won't have any issues when using
|
||||
* from a single thread. Most plugins won't have any issues when using
|
||||
* multiple message loops, but the Melda plugins for instance will only update
|
||||
* their GUIs from the message loop of the thread that created the first
|
||||
* instance. This is why we pass an IO context to this class so everything
|
||||
@@ -64,8 +65,8 @@ class Vst2Bridge {
|
||||
* also be run from this context.
|
||||
* @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to
|
||||
* load.
|
||||
* @param socket_endpoint_path A (Unix style) path to the Unix socket
|
||||
* endpoint the native VST plugin created to communicate over.
|
||||
* @param endpoint_base_dir The base directory used for the socket
|
||||
* endpoints. See `Sockets` for more information.
|
||||
*
|
||||
* @note The object has to be constructed from the same thread that calls
|
||||
* `main_context.run()`.
|
||||
@@ -75,7 +76,7 @@ class Vst2Bridge {
|
||||
*/
|
||||
Vst2Bridge(boost::asio::io_context& main_context,
|
||||
std::string plugin_dll_path,
|
||||
std::string socket_endpoint_path);
|
||||
std::string endpoint_base_dir);
|
||||
|
||||
/**
|
||||
* Returns true if the message loop should be skipped. This happens when the
|
||||
@@ -189,47 +190,9 @@ class Vst2Bridge {
|
||||
AEffect* plugin;
|
||||
|
||||
/**
|
||||
* The UNIX domain socket endpoint used for communicating to this specific
|
||||
* bridged plugin.
|
||||
* All sockets used for communicating with this specific plugin.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::endpoint socket_endpoint;
|
||||
|
||||
// The naming convention for these sockets is `<from>_<to>_<event>`. For
|
||||
// instance the socket named `host_vst_dispatch` forwards
|
||||
// `AEffect.dispatch()` calls from the native VST host to the Windows VST
|
||||
// plugin (through the Wine VST host).
|
||||
|
||||
/**
|
||||
* The socket that forwards all `dispatcher()` calls from the VST host to
|
||||
* the plugin.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::socket host_vst_dispatch;
|
||||
/**
|
||||
* Used specifically for the `effProcessEvents` opcode. This is needed
|
||||
* because the Win32 API is designed to block during certain GUI
|
||||
* interactions such as resizing a window or opening a dropdown. Without
|
||||
* this MIDI input would just stop working at times.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events;
|
||||
/**
|
||||
* The socket that forwards all `audioMaster()` calls from the Windows VST
|
||||
* plugin to the host.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::socket vst_host_callback;
|
||||
/**
|
||||
* Used for both `getParameter` and `setParameter` since they mostly
|
||||
* overlap.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::socket host_vst_parameters;
|
||||
boost::asio::local::stream_protocol::socket host_vst_process_replacing;
|
||||
|
||||
/**
|
||||
* A control socket that sends data that is not suitable for the other
|
||||
* sockets. At the moment this is only used to, on startup, send the Windows
|
||||
* VST plugin's `AEffect` object to the native VST plugin, and to then send
|
||||
* the configuration (from `config`) back to the Wine host.
|
||||
*/
|
||||
boost::asio::local::stream_protocol::socket host_vst_control;
|
||||
Sockets sockets;
|
||||
|
||||
/**
|
||||
* The thread that specifically handles `effProcessEvents` opcodes so the
|
||||
|
||||
Reference in New Issue
Block a user