// 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 "../boost-fix.h"
#define NOMINMAX
#define NOSERVICE
#define NOMCX
#define NOIMM
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
#include "../../common/logging.h"
#include "../editor.h"
#include "../utils.h"
/**
* A marker struct to indicate that the editor is about to be opened.
*
* @see Vst2Bridge::editor
*/
struct EditorOpening {};
/**
* This hosts a Windows VST2 plugin, forwards messages sent by the Linux VST
* 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
* 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
* that's not performance critical (audio and midi event handling) is handled
* on the same thread, even when hosting multiple plugins.
*/
class Vst2Bridge {
public:
/**
* Initializes the Windows VST plugin and set up communication with the
* native Linux VST plugin.
*
* @param main_context The main IO context for this application. Most events
* will be dispatched to this context, and the event handling loop should
* 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.
*
* @note The object has to be constructed from the same thread that calls
* `main_context.run()`.
*
* @throw std::runtime_error Thrown when the VST plugin could not be loaded,
* or if communication could not be set up.
*/
Vst2Bridge(boost::asio::io_context& main_context,
std::string plugin_dll_path,
std::string socket_endpoint_path);
/**
* Returns true if the message loop should be skipped. This happens when the
* editor is in the process of being opened. In VST hosts on Windows
* `effEditOpen()` and `effEditGetRect()` will always be called in sequence,
* but in our approach there will be an opportunity to handle events in
* between these two calls. Most plugins will handle this just fine, but
* some plugins end up blocking indefinitely while waiting for the other
* function to be called, hence why this function is needed. For
* individually hosted plugins this check is done implicitely in
* `Vst2Bridge::handle_win32_events()`.
*/
bool should_skip_message_loop();
/**
* Handle events until the plugin exits. The actual events are posted to
* `main_context` to ensure that all operations to could potentially
* interact with Win32 code are run from a single thread, even when hosting
* multiple plugins. The message loop should be run on a timer within the
* same IO context.
*
* @note Because of the reasons mentioned above, for this to work the plugin
* should be initialized within the same thread that calls
* `main_context.run()`.
*/
void handle_dispatch();
/**
* Handle X11 events for the editor window if it is open. This can be run
* safely from any thread.
*/
void handle_x11_events();
/**
* Run the message loop for this plugin. This is only used for the
* individual plugin host. When hosting multiple plugins, a simple central
* message loop with a check to `should_skip_message_loop()` should be used
* instead. This is run on a timer in the same IO context as the one that
* handles the events, i.e. `main_context`.
*
* Because of the way the Win32 API works we have to process events on the
* same thread as the one the window was created on, and that thread is the
* thread that's handling dispatcher calls. Some plugins will also rely on
* the Win32 message loop to run tasks on a timer and to defer loading, so
* we have to make sure to always run this loop. The only exception is a in
* specific situation that can cause a race condition in some plugins
* because of incorrect assumptions made by the plugin. See the dostring for
* `Vst2Bridge::editor` for more information.
*/
void handle_win32_events();
// These functions are the entry points for the `*_handler` threads
// defined below. They're defined here because we can't use lambdas with
// WinAPI's `CreateThread` which is needed to support the proper call
// conventions the VST plugins expect.
void handle_dispatch_midi_events();
void handle_parameters();
void handle_process_replacing();
/**
* Forward the host callback made by the plugin to the host and return the
* results.
*/
intptr_t host_callback(AEffect*, int, int, intptr_t, void*, float);
/**
* With the `audioMasterGetTime` host callback the plugin expects the return
* value from the calblack to be a pointer to a VstTimeInfo struct. If the
* host did not support a certain time info query, than we'll store the
* returned null pointer as a nullopt.
*/
std::optional time_info;
private:
/**
* A wrapper around `plugin->dispatcher` that handles the opening and
* closing of GUIs.
*/
intptr_t dispatch_wrapper(AEffect* plugin,
int opcode,
int index,
intptr_t value,
void* data,
float option);
/**
* The IO context used for event handling so that all events and window
* message handling can be performed from a single thread, even when hosting
* multiple plugins.
*/
boost::asio::io_context& io_context;
/**
* The shared library handle of the VST plugin. I sadly could not get
* Boost.DLL to work here, so we'll just load the VST plugins by hand.
*
* FIXME: I don't know why, but `FreeLibrary()` seems to corrupt memory.
* This leads to a lot of weird behavior, such as plugins crashing as
* soon as other plugins get loaded or calls to `LoadLibrary()`
* returning null pointers while they would otherwise load fine
* without the prior call to `FreeLibrary`. We are leaking memory
* here until this is fixed, but it should not be a huge issue since
* this leak only exists for plugin groups.
*/
// std::unique_ptr, decltype(&FreeLibrary)>
// plugin_handle;
HMODULE plugin_handle;
/**
* The loaded plugin's `AEffect` struct, obtained using the above library
* handle.
*/
AEffect* plugin;
/**
* The UNIX domain socket endpoint used for communicating to this specific
* bridged plugin.
*/
boost::asio::local::stream_protocol::endpoint socket_endpoint;
// The naming convention for these sockets is `__`. 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. This is also used once at startup to populate the values of
* the `AEffect` object.
*/
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;
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;
/**
* The thread that specifically handles `effProcessEvents` opcodes so the
* plugin can still receive MIDI during GUI interaction to work around Win32
* API limitations.
*/
Win32Thread dispatch_midi_events_handler;
/**
* The thread that responds to `getParameter` and `setParameter` requests.
*/
Win32Thread parameters_handler;
/**
* The thread that handles calls to `processReplacing` (and `process`).
*/
Win32Thread process_replacing_handler;
/**
* A binary semaphore to prevent race conditions from the host callback
* function being called by two threads at once. See `send_event()` for more
* information.
*/
std::mutex host_callback_mutex;
/**
* A scratch buffer for sending and receiving data during `process` and
* `processReplacing` calls.
*/
std::vector process_buffer;
/**
* The MIDI events that have been received **and processed** since the last
* call to `processReplacing()`. 99% of plugins make a copy of the MIDI
* events they receive but some plugins such as Kontakt only store pointers
* to these events, which means that the actual `VstEvent` objects must live
* at least until the next audio buffer gets processed.
*/
std::vector next_audio_buffer_midi_events;
/**
* Mutex for locking the above event queue, since recieving and processing
* now happens in two different threads.
*/
std::mutex next_buffer_midi_events_mutex;
/**
* The plugin editor window. Allows embedding the plugin's editor into a
* Wine window, and embedding that Wine window into a window provided by the
* host. Should be empty when the editor is not open.
*
* This field can have three possible states:
*
* - `std::nullopt` when the editor is closed.
* - An `Editor` object when the editor is open.
* - `EditorOpening` when the editor is not yet open, but the host has
* already called `effEditGetRect()` and is about to call `effEditOpen()`.
* This is needed because there is a race condition in some bugs that
* cause them to crash or enter an infinite Win32 message loop when
* `effEditGetRect()` gets dispatched and we then enter the message loop
* loop before `effEditOpen()` gets called. Most plugins will handle this
* just fine, but a select few plugins make the assumption that the editor
* is already open once `effEditGetRect()` has been called, even if
* `effEditOpen` has not yet been dispatched. VST hsots on Windows will
* call these two events in sequence, so the bug would never occur there.
* To work around this we'll use this third state to temporarily stop
* processing Windows events in the one or two ticks between these two
* events.
*
* @see should_postpone_message_loop
*/
std::variant editor;
};