mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Move wine-bridge.h -> bridges/vst2.h
This way we can structure the group handling and a potential future VST3 bridge in the same way.
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "vst2.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../../common/communication.h"
|
||||
#include "../../common/events.h"
|
||||
|
||||
/**
|
||||
* A function pointer to what should be the entry point of a VST plugin.
|
||||
*/
|
||||
using VstEntryPoint = AEffect*(VST_CALL_CONV*)(audioMasterCallback);
|
||||
|
||||
/**
|
||||
* This ugly global is needed so we can get the instance of a `Brdige` class
|
||||
* from an `AEffect` when it performs a host callback during its initialization.
|
||||
*/
|
||||
Vst2Bridge* current_bridge_instance = nullptr;
|
||||
|
||||
intptr_t VST_CALL_CONV
|
||||
host_callback_proxy(AEffect*, int, int, intptr_t, void*, float);
|
||||
|
||||
// We need to use the `CreateThread` WinAPI functions instead of `std::thread`
|
||||
// to use the correct calling conventions within threads. Otherwise we'll get
|
||||
// some rare and impossible to debug data races in some particular plugins.
|
||||
uint32_t WINAPI handle_dispatch_midi_events_proxy(void*);
|
||||
uint32_t WINAPI handle_parameters_proxy(void*);
|
||||
uint32_t WINAPI handle_process_replacing_proxy(void*);
|
||||
|
||||
/**
|
||||
* Fetch the Vst2Bridge instance stored in one of the two pointers reserved
|
||||
* for the host of the hosted VST plugin. This is sadly needed as a workaround
|
||||
* to avoid using globals since we need free function pointers to interface with
|
||||
* the VST C API.
|
||||
*/
|
||||
Vst2Bridge& get_bridge_instance(const AEffect* plugin) {
|
||||
// This is needed during the initialization of the plugin since we can only
|
||||
// add our own pointer after it's done initializing
|
||||
if (current_bridge_instance != nullptr) {
|
||||
// This should only be used during initialization
|
||||
assert(plugin == nullptr || plugin->ptr1 == nullptr);
|
||||
return *current_bridge_instance;
|
||||
}
|
||||
|
||||
return *static_cast<Vst2Bridge*>(plugin->ptr1);
|
||||
}
|
||||
|
||||
Vst2Bridge::Vst2Bridge(std::string plugin_dll_path,
|
||||
std::string socket_endpoint_path)
|
||||
: plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
|
||||
io_context(),
|
||||
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) {
|
||||
// Got to love these C APIs
|
||||
if (plugin_handle == nullptr) {
|
||||
throw std::runtime_error("Could not load the Windows .dll file at '" +
|
||||
plugin_dll_path + "'");
|
||||
}
|
||||
|
||||
// VST plugin entry point functions should be called `VSTPluginMain`, but
|
||||
// there are some older deprecated names that legacy plugins may still use
|
||||
VstEntryPoint vst_entry_point = nullptr;
|
||||
for (auto name : {"VSTPluginMain", "main_plugin", "main"}) {
|
||||
vst_entry_point =
|
||||
reinterpret_cast<VstEntryPoint>(reinterpret_cast<size_t>(
|
||||
GetProcAddress(plugin_handle.get(), name)));
|
||||
|
||||
if (vst_entry_point != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (vst_entry_point == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Could not find a valid VST entry point for '" + plugin_dll_path +
|
||||
"'.");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Initialize after communication has been set up
|
||||
// We'll try to do the same `get_bridge_isntance` trick as in
|
||||
// `plugin/plugin.cpp`, but since the plugin will probably call the host
|
||||
// callback while it's initializing we sadly have to use a global here.
|
||||
current_bridge_instance = this;
|
||||
plugin = vst_entry_point(host_callback_proxy);
|
||||
if (plugin == nullptr) {
|
||||
throw std::runtime_error("VST plugin at '" + plugin_dll_path +
|
||||
"' failed to initialize.");
|
||||
}
|
||||
|
||||
// We only needed this little hack during initialization
|
||||
current_bridge_instance = nullptr;
|
||||
plugin->ptr1 = this;
|
||||
|
||||
// Send the plugin's information to the Linux VST plugin. This is done over
|
||||
// the `dispatch()` socket since this has to be done only once during
|
||||
// initialization. Any updates during runtime are handled using the
|
||||
// `audioMasterIOChanged` host callback.
|
||||
write_object(host_vst_dispatch, EventResult{0, *plugin, std::nullopt});
|
||||
|
||||
// This works functionally identically to the `handle_dispatch()` function
|
||||
// below, but this socket will only handle MIDI events. This is needed
|
||||
// because of Win32 API limitations.
|
||||
dispatch_midi_events_handler =
|
||||
Win32Thread(handle_dispatch_midi_events_proxy, this);
|
||||
|
||||
parameters_handler = Win32Thread(handle_parameters_proxy, this);
|
||||
|
||||
process_replacing_handler =
|
||||
Win32Thread(handle_process_replacing_proxy, this);
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_dispatch() {
|
||||
using namespace std::placeholders;
|
||||
|
||||
// For our communication we use simple threads and blocking operations
|
||||
// instead of asynchronous IO since communication has to be handled in
|
||||
// lockstep anyway
|
||||
try {
|
||||
while (true) {
|
||||
receive_event(host_vst_dispatch, std::nullopt,
|
||||
passthrough_event(
|
||||
plugin, std::bind(&Vst2Bridge::dispatch_wrapper,
|
||||
this, _1, _2, _3, _4, _5, _6)));
|
||||
|
||||
// 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.
|
||||
std::visit(overload{[](Editor& editor) { editor.handle_events(); },
|
||||
[](std::monostate&) {
|
||||
MSG msg;
|
||||
|
||||
while (PeekMessage(&msg, nullptr, 0, 0,
|
||||
PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
},
|
||||
[](EditorOpening&) {
|
||||
// Don't handle any events in this
|
||||
// particular case as explained in
|
||||
// `Vst2Bridge::editor`
|
||||
}},
|
||||
editor);
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this host
|
||||
// application
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void Vst2Bridge::handle_dispatch_midi_events() {
|
||||
while (true) {
|
||||
receive_event(
|
||||
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 select
|
||||
// few plugins (I could only find Kontakt that does this)
|
||||
// don't actually make copies of the events they receive and
|
||||
// only store pointers, meaning that they have to live at
|
||||
// least until the next audio buffer gets processed. We're
|
||||
// not using `passhtourhg_events()` here directly because we
|
||||
// need to store a copy of the `DynamicVstEvents` struct
|
||||
// before passing the generated `VstEvents` object to the
|
||||
// plugin.
|
||||
std::lock_guard lock(next_buffer_midi_events_mutex);
|
||||
|
||||
next_audio_buffer_midi_events.push_back(
|
||||
std::get<DynamicVstEvents>(event.payload));
|
||||
DynamicVstEvents& events =
|
||||
next_audio_buffer_midi_events.back();
|
||||
|
||||
// Exact same handling as in `passthrough_event`, apart from
|
||||
// making a copy of the events first
|
||||
const intptr_t return_value = plugin->dispatcher(
|
||||
plugin, event.opcode, event.index, event.value,
|
||||
&events.as_c_events(), event.option);
|
||||
|
||||
EventResult response{return_value, nullptr, std::nullopt};
|
||||
|
||||
return response;
|
||||
} else {
|
||||
using namespace std::placeholders;
|
||||
|
||||
std::cerr << "[Warning] Received non-MIDI "
|
||||
"event on MIDI processing thread"
|
||||
<< std::endl;
|
||||
|
||||
// Maybe this should just be a hard error instead, since it
|
||||
// should never happen
|
||||
return passthrough_event(
|
||||
plugin, std::bind(&Vst2Bridge::dispatch_wrapper, this,
|
||||
_1, _2, _3, _4, _5, _6))(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void Vst2Bridge::handle_parameters() {
|
||||
while (true) {
|
||||
// Both `getParameter` and `setParameter` functions are passed
|
||||
// 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);
|
||||
if (request.value.has_value()) {
|
||||
// `setParameter`
|
||||
plugin->setParameter(plugin, request.index, request.value.value());
|
||||
|
||||
ParameterResult response{std::nullopt};
|
||||
write_object(host_vst_parameters, response);
|
||||
} else {
|
||||
// `getParameter`
|
||||
float value = plugin->getParameter(plugin, request.index);
|
||||
|
||||
ParameterResult response{value};
|
||||
write_object(host_vst_parameters, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void Vst2Bridge::handle_process_replacing() {
|
||||
std::vector<std::vector<float>> output_buffers(plugin->numOutputs);
|
||||
|
||||
while (true) {
|
||||
auto request = read_object<AudioBuffers>(host_vst_process_replacing,
|
||||
process_buffer);
|
||||
|
||||
// The process functions expect a `float**` for their inputs and
|
||||
// their outputs
|
||||
std::vector<float*> inputs;
|
||||
for (auto& buffer : request.buffers) {
|
||||
inputs.push_back(buffer.data());
|
||||
}
|
||||
|
||||
// We reuse the buffers to avoid some unnecessary heap allocations,
|
||||
// so we need to make sure the buffers are large enough since
|
||||
// plugins can change their output configuration
|
||||
std::vector<float*> outputs;
|
||||
output_buffers.resize(plugin->numOutputs);
|
||||
for (auto& buffer : output_buffers) {
|
||||
buffer.resize(request.sample_frames);
|
||||
outputs.push_back(buffer.data());
|
||||
}
|
||||
|
||||
// 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 than copies of
|
||||
// the events.
|
||||
{
|
||||
std::lock_guard lock(next_buffer_midi_events_mutex);
|
||||
|
||||
// Any plugin made in the last fifteen years or so should support
|
||||
// `processReplacing`. In the off chance it does not we can just
|
||||
// emulate this behavior ourselves.
|
||||
if (plugin->processReplacing != nullptr) {
|
||||
plugin->processReplacing(plugin, inputs.data(), outputs.data(),
|
||||
request.sample_frames);
|
||||
} else {
|
||||
// If we zero out this buffer then the behavior is the same as
|
||||
// `processReplacing``
|
||||
for (std::vector<float>& buffer : output_buffers) {
|
||||
std::fill(buffer.begin(), buffer.end(), 0.0);
|
||||
}
|
||||
|
||||
plugin->process(plugin, inputs.data(), outputs.data(),
|
||||
request.sample_frames);
|
||||
}
|
||||
|
||||
next_audio_buffer_midi_events.clear();
|
||||
}
|
||||
|
||||
AudioBuffers response{output_buffers, request.sample_frames};
|
||||
write_object(host_vst_process_replacing, response, process_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
void* data,
|
||||
float option) {
|
||||
// We have to intercept GUI open calls since we can't use
|
||||
// the X11 window handle passed by the host
|
||||
switch (opcode) {
|
||||
case effEditGetRect: {
|
||||
// Some plugins will have a race condition if the message loops gets
|
||||
// handled between the call to `effEditGetRect()` and
|
||||
// `effEditOpen()`, although this behavior never appears on Windows
|
||||
// as hosts will always either call these functions in sequence or
|
||||
// in reverse. We need to temporarily stop handling messages when
|
||||
// this happens.
|
||||
if (!std::holds_alternative<Editor>(editor)) {
|
||||
editor = EditorOpening{};
|
||||
}
|
||||
|
||||
return plugin->dispatcher(plugin, opcode, index, value, data,
|
||||
option);
|
||||
} break;
|
||||
case effEditOpen: {
|
||||
// Create a Win32 window through Wine, embed it into the window
|
||||
// provided by the host, and let the plugin embed itself into
|
||||
// the Wine window
|
||||
const auto x11_handle = reinterpret_cast<size_t>(data);
|
||||
Editor& editor_instance =
|
||||
editor.emplace<Editor>("yabridge plugin", plugin, x11_handle);
|
||||
|
||||
return plugin->dispatcher(plugin, opcode, index, value,
|
||||
editor_instance.win32_handle.get(),
|
||||
option);
|
||||
} break;
|
||||
case effEditClose: {
|
||||
const intptr_t return_value =
|
||||
plugin->dispatcher(plugin, opcode, index, value, data, option);
|
||||
|
||||
// Cleanup is handled through RAII
|
||||
editor = std::monostate();
|
||||
|
||||
return return_value;
|
||||
} break;
|
||||
default:
|
||||
return plugin->dispatcher(plugin, opcode, index, value, data,
|
||||
option);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class HostCallbackDataConverter : DefaultDataConverter {
|
||||
public:
|
||||
HostCallbackDataConverter(AEffect* plugin,
|
||||
std::optional<VstTimeInfo>& time_info)
|
||||
: plugin(plugin), time_info(time_info) {}
|
||||
|
||||
EventPayload read(const int opcode,
|
||||
const int index,
|
||||
const intptr_t value,
|
||||
const void* data) {
|
||||
switch (opcode) {
|
||||
case audioMasterGetTime:
|
||||
return WantsVstTimeInfo{};
|
||||
break;
|
||||
case audioMasterIOChanged:
|
||||
// This is a helpful event that indicates that the VST plugin's
|
||||
// `AEffect` struct has changed. Writing these results back is
|
||||
// done inside of `passthrough_event`.
|
||||
return AEffect(*plugin);
|
||||
break;
|
||||
case audioMasterProcessEvents:
|
||||
return DynamicVstEvents(*static_cast<const VstEvents*>(data));
|
||||
break;
|
||||
// We detect whether an opcode should return a string by checking
|
||||
// whether there's a zeroed out buffer behind the void pointer. This
|
||||
// works for any host, but not all plugins zero out their buffers.
|
||||
case audioMasterGetVendorString:
|
||||
case audioMasterGetProductString:
|
||||
return WantsString{};
|
||||
break;
|
||||
default:
|
||||
return DefaultDataConverter::read(opcode, index, value, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<EventPayload> read_value(const int opcode,
|
||||
const intptr_t value) {
|
||||
return DefaultDataConverter::read_value(opcode, value);
|
||||
}
|
||||
|
||||
void write(const int opcode, void* data, const EventResult& response) {
|
||||
switch (opcode) {
|
||||
case audioMasterGetTime:
|
||||
// Write the returned `VstTimeInfo` struct into a field and make
|
||||
// the function return a poitner to it in the function below.
|
||||
// Depending on whether the host supported the requested time
|
||||
// information this operations returns either a null pointer or
|
||||
// a pointer to a `VstTimeInfo` object.
|
||||
if (std::holds_alternative<std::nullptr_t>(response.payload)) {
|
||||
time_info = std::nullopt;
|
||||
} else {
|
||||
time_info = std::get<VstTimeInfo>(response.payload);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DefaultDataConverter::write(opcode, data, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t return_value(const int opcode, const intptr_t original) {
|
||||
switch (opcode) {
|
||||
case audioMasterGetTime: {
|
||||
// Return a pointer to the `VstTimeInfo` object written in the
|
||||
// function above
|
||||
VstTimeInfo* time_info_pointer = nullptr;
|
||||
if (time_info.has_value()) {
|
||||
time_info_pointer = &time_info.value();
|
||||
}
|
||||
|
||||
return reinterpret_cast<intptr_t>(time_info_pointer);
|
||||
} break;
|
||||
default:
|
||||
return DefaultDataConverter::return_value(opcode, original);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void write_value(const int opcode,
|
||||
intptr_t value,
|
||||
const EventResult& response) {
|
||||
return DefaultDataConverter::write_value(opcode, value, response);
|
||||
}
|
||||
|
||||
private:
|
||||
AEffect* plugin;
|
||||
std::optional<VstTimeInfo>& time_info;
|
||||
};
|
||||
|
||||
intptr_t Vst2Bridge::host_callback(AEffect* effect,
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
void* data,
|
||||
float option) {
|
||||
HostCallbackDataConverter converter(effect, time_info);
|
||||
return send_event(vst_host_callback, host_callback_mutex, converter,
|
||||
std::nullopt, opcode, index, value, data, option);
|
||||
}
|
||||
|
||||
intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect,
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
void* data,
|
||||
float option) {
|
||||
return get_bridge_instance(effect).host_callback(effect, opcode, index,
|
||||
value, data, option);
|
||||
}
|
||||
|
||||
uint32_t WINAPI handle_dispatch_midi_events_proxy(void* instance) {
|
||||
static_cast<Vst2Bridge*>(instance)->handle_dispatch_midi_events();
|
||||
}
|
||||
|
||||
uint32_t WINAPI handle_parameters_proxy(void* instance) {
|
||||
static_cast<Vst2Bridge*>(instance)->handle_parameters();
|
||||
}
|
||||
|
||||
uint32_t WINAPI handle_process_replacing_proxy(void* instance) {
|
||||
static_cast<Vst2Bridge*>(instance)->handle_process_replacing();
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../boost-fix.h"
|
||||
|
||||
#define NOMINMAX
|
||||
#define NOSERVICE
|
||||
#define NOMCX
|
||||
#define NOIMM
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <vestige/aeffectx.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <mutex>
|
||||
|
||||
#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 handles the communication between the Linux native VST plugin and the
|
||||
* Wine VST host when hosting VST2 plugins. The functions below should be used
|
||||
* as callback functions in an `AEffect` object.
|
||||
*/
|
||||
class Vst2Bridge {
|
||||
public:
|
||||
/**
|
||||
* Initializes the Windows VST plugin and set up communication with the
|
||||
* native Linux VST plugin.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @throw std::runtime_error Thrown when the VST plugin could not be loaded,
|
||||
* or if communication could not be set up.
|
||||
*/
|
||||
Vst2Bridge(std::string plugin_dll_path, std::string socket_endpoint_path);
|
||||
|
||||
/**
|
||||
* Handle events on the main thread until the plugin quits. This can't be
|
||||
* done on another thread since some plugins (e.g. Melda) expect certain
|
||||
* (but for some reason not all) events to be passed from the same thread it
|
||||
* was initiated from. This is then also the same thread that should handle
|
||||
* Win32 GUI events.
|
||||
*/
|
||||
void handle_dispatch();
|
||||
|
||||
// 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.
|
||||
[[noreturn]] void handle_dispatch_midi_events();
|
||||
[[noreturn]] void handle_parameters();
|
||||
[[noreturn]] 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<VstTimeInfo> 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 shared library handle of the VST plugin. I sadly could not get
|
||||
* Boost.DLL to work here, so we'll just load the VST plugisn by hand.
|
||||
*/
|
||||
std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&FreeLibrary)>
|
||||
plugin_handle;
|
||||
|
||||
/**
|
||||
* The loaded plugin's `AEffect` struct, obtained using the above library
|
||||
* handle.
|
||||
*/
|
||||
AEffect* plugin;
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
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. 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<uint8_t> 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<DynamicVstEvents> 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.
|
||||
*/
|
||||
std::variant<std::monostate, Editor, EditorOpening> editor;
|
||||
};
|
||||
Reference in New Issue
Block a user