mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-13 07:42:15 +02:00
e0ab24e645
Happy new year!
217 lines
8.7 KiB
C++
217 lines
8.7 KiB
C++
// yabridge: a Wine VST bridge
|
|
// Copyright (C) 2020-2022 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#pragma once
|
|
|
|
#include <vestige/aeffectx.h>
|
|
|
|
#include <boost/asio/io_context.hpp>
|
|
#include <thread>
|
|
|
|
#include "../../common/communication/vst2.h"
|
|
#include "../../common/logging/vst2.h"
|
|
#include "common.h"
|
|
|
|
/**
|
|
* This handles the communication between the Linux native VST2 plugin and the
|
|
* Wine VST host. The functions below should be used as callback functions in an
|
|
* `AEffect` object.
|
|
*
|
|
* The naming scheme of all of these 'bridge' classes is `<type>{,Plugin}Bridge`
|
|
* for greppability reasons. The `Plugin` infix is added on the native plugin
|
|
* side.
|
|
*/
|
|
class Vst2PluginBridge : PluginBridge<Vst2Sockets<std::jthread>> {
|
|
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.
|
|
*/
|
|
Vst2PluginBridge(audioMasterCallback host_callback);
|
|
|
|
/**
|
|
* Terminate the Wine plugin host process and drop all work when the module
|
|
* gets unloaded.
|
|
*/
|
|
~Vst2PluginBridge() noexcept override;
|
|
|
|
// 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);
|
|
/**
|
|
* This is the old, accumulative version of `processReplacing()`. As far as
|
|
* I'm aware no host from the last 20 years will use this (since it's not
|
|
* very practical), but we have to support this anyways. Because this is not
|
|
* used, we'll just reuse your `process_replacing()` implementation (which
|
|
* actually falls back to `process()` if the plugin somehow does not support
|
|
* the former).
|
|
*/
|
|
void process(AEffect* plugin,
|
|
float** inputs,
|
|
float** outputs,
|
|
int sample_frames);
|
|
/**
|
|
* 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);
|
|
/**
|
|
* The same as `Vst2PluginBridge::process_replacing`, but for double
|
|
* precision audio. Support for this on both the plugin and host side is
|
|
* pretty rare, but REAPER supports it. This reuses the same infrastructure
|
|
* as `process_replacing` is using since the host will only call one or the
|
|
* other.
|
|
*/
|
|
void process_double_replacing(AEffect* plugin,
|
|
double** inputs,
|
|
double** outputs,
|
|
int sample_frames);
|
|
float get_parameter(AEffect* plugin, int index);
|
|
void set_parameter(AEffect* plugin, int index, float value);
|
|
|
|
/**
|
|
* Process audio and handle plugin-generated MIDI events afterwards.
|
|
*
|
|
* @tparam T The sample type. Should be either `float` for single preceision
|
|
* audio processing called through `processReplacing`, or `double` for
|
|
* double precision audio through `processDoubleReplacing`.
|
|
* @tparam replacing Whether or not `outputs` should be replaced by the new
|
|
* processed audio. This is the normal behaviour for `processReplacing()`.
|
|
* If this is set to `false` then the results are added to the existing
|
|
* values in `outputs`. No host will use this last behaviour anymore, but
|
|
* it's part of the VST2.4 spec so we have to support it.
|
|
*
|
|
* @see Vst2PluginBridge::process_replacing
|
|
* @see Vst2PluginBridge::process_double_replacing
|
|
*/
|
|
template <typename T, bool replacing>
|
|
void do_process(T** inputs, T** outputs, int sample_frames);
|
|
|
|
/**
|
|
* 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:
|
|
/**
|
|
* The thread that handles host callbacks.
|
|
*/
|
|
std::jthread host_callback_handler;
|
|
|
|
/**
|
|
* A mutex to prevent multiple simultaneous calls to `getParameter()` and
|
|
* `setParameter()`. This likely won't happen, but better safe than sorry.
|
|
* For `dispatch()` and `audioMaster()` there's some more complex logic for
|
|
* this in `Vst2EventHandler`.
|
|
*/
|
|
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. Wraps around
|
|
* `PluginBridge::generic_logger`.
|
|
*/
|
|
Vst2Logger logger;
|
|
|
|
/**
|
|
* A shared memory object that contains both the input and output audio
|
|
* buffers. This is first configured on the Wine plugin host side during
|
|
* `effMainsChanged` and then replicated on the plugin side. This way we
|
|
* reduce the amount of copying during audio processing to only two copies.
|
|
* We'll write the input audio to this buffer and send the process request
|
|
* to the Wine plugin host. There the Windows VST2 plugin will then read
|
|
* from the buffer and write its results to the same buffer. We can then
|
|
* write those results back to the host.
|
|
*
|
|
* This will be a nullopt until `effMainsChanged` has been called.
|
|
*/
|
|
std::optional<AudioShmBuffer> process_buffers;
|
|
|
|
/**
|
|
* We'll periodically synchronize the Wine host's audio thread priority with
|
|
* that of the host. Since the overhead from doing so does add up, we'll
|
|
* only do this every once in a while.
|
|
*/
|
|
time_t last_audio_thread_priority_synchronization = 0;
|
|
|
|
/**
|
|
* 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<uint8_t> 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 host on the audio thread at the end of
|
|
* `process_replacing()`.
|
|
*/
|
|
boost::container::small_vector<DynamicVstEvents, 1> 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;
|
|
|
|
/**
|
|
* REAPER requires us to call `audioMasterSizeWidnow()` from the same thread
|
|
* that's calling `effEditIdle()`. If we call this from any other thread,
|
|
* then the FX window won't be resized. To accommodate for this, we'll store
|
|
* the width and the height passed to the last call to
|
|
* `audioMasterSizeWindow`. If this contains a value, we'll then call
|
|
* `audioMasterSizeWindow()` with the new size during `effEditIdle()`.
|
|
*/
|
|
std::optional<std::pair<int, int>> incoming_resize;
|
|
std::mutex incoming_resize_mutex;
|
|
};
|