// 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 #include #include "../common/logging.h" #include "configuration.h" #include "host-process.h" /** * This handles the communication between the Linux native VST plugin and the * Wine VST host. The functions below should be used as callback functions in an * `AEffect` object. */ class PluginBridge { public: /** * Initializes the Wine VST bridge. This sets up the sockets for event * handling. * * @param host_callback The callback function passed to the VST plugin by * the host. * * @throw std::runtime_error Thrown when the VST host could not be found, or * if it could not locate and load a VST .dll file. */ PluginBridge(audioMasterCallback host_callback); // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. /** * Handle an event sent by the VST host. Most of these opcodes will be * passed through to the winelib VST host. */ intptr_t dispatch(AEffect* plugin, int opcode, int index, intptr_t value, void* data, float option); /** * Ask the VST plugin to process audio for us. If the plugin somehow does * not support `processReplacing()` and only supports the old `process()` * function, then this will be handled implicitely in * `Vst2Bridge::handle_process_replacing()`. */ void process_replacing(AEffect* plugin, float** inputs, float** outputs, int sample_frames); float get_parameter(AEffect* plugin, int index); void set_parameter(AEffect* plugin, int index, float value); /** * The configuration for this instance of yabridge. Set based on the values * from a `yabridge.toml`, if it exists. * * @see Configuration::load_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 * native VST host from the Linux VST plugin's entry point. */ AEffect plugin; private: /** * Format and log all relevant debug information during initialization. */ void log_init_message(); boost::asio::io_context io_context; boost::asio::local::stream_protocol::endpoint socket_endpoint; boost::asio::local::stream_protocol::acceptor socket_acceptor; // 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 handles host callbacks. */ std::jthread host_callback_handler; /** * A binary semaphore to prevent race conditions from the dispatch function * being called by two threads at once. See `send_event()` for more * information. */ std::mutex dispatch_mutex; std::mutex dispatch_midi_events_mutex; /** * A similar semaphore as the `dispatch_*` semaphores in the rare case that * `getParameter()` and `setParameter()` are being called at the same time * since they use the same socket. */ std::mutex parameters_mutex; /** * The callback function passed by the host to the VST plugin instance. */ 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 */ Logger 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 VST plugin. * * @see launch_vst_host */ std::unique_ptr vst_host; /** * 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` and * `processReplacing` calls. */ std::vector process_buffer; /** * The VST host can query a plugin for arbitrary binary data such as * presets. It will expect the plugin to write back a pointer that points to * that data. This vector is where we store the chunk data for the last * `effGetChunk` event. */ std::vector chunk_data; /** * The VST host will expect to be returned a pointer to a struct that stores * the dimensions of the editor window. */ VstRect editor_rectangle; /** * Sending MIDI events sent to the host by the plugin using * `audioMasterProcessEvents` function has to be done during the processing * function. If they are sent during any other time or from another thread, * then the host will just discard them. Because we're receiving our host * callbacks on a separate thread, we have to temporarily store any events * we receive so we can send them to the host at the end of * `process_replacing()`. */ std::vector incoming_midi_events; /** * Mutex for locking the above event queue, since recieving and processing * now happens in two different threads. */ std::mutex incoming_midi_events_mutex; };