// yabridge: a Wine VST bridge
// Copyright (C) 2020-2021 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 .
#include "vst2.h"
#include
#include
#include "../../common/communication/vst2.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 `Vst2Bridge` class
* from an `AEffect` when it performs a host callback during its initialization.
*/
Vst2Bridge* current_bridge_instance = nullptr;
/**
* Needed for the rare event that two plugins are getting initialized at the
* same time.
*/
std::mutex current_bridge_instance_mutex;
/**
* Opcodes that should always be handled on the main thread because they may
* involve GUI operations.
*
* NOTE: `effMainsChanged` is the odd one here. EZdrummer interacts with the
* Win32 message loop while handling this function. If we don't execute
* this from the main GUI thread, then EZdrummer won't produce any sound.
*/
const std::set unsafe_opcodes{effOpen, effClose, effEditGetRect,
effEditOpen, effEditClose, effEditIdle,
effEditTop, effMainsChanged};
intptr_t VST_CALL_CONV
host_callback_proxy(AEffect*, int, int, intptr_t, void*, float);
/**
* 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) {
return *current_bridge_instance;
}
return *static_cast(plugin->ptr1);
}
Vst2Bridge::Vst2Bridge(MainContext& main_context,
std::string plugin_dll_path,
std::string endpoint_base_dir)
: HostBridge(plugin_dll_path),
main_context(main_context),
plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
sockets(main_context.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 '" +
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(reinterpret_cast(
GetProcAddress(plugin_handle.get(), name)));
if (vst_entry_point) {
break;
}
}
if (!vst_entry_point) {
throw std::runtime_error(
"Could not find a valid VST entry point for '" + plugin_dll_path +
"'.");
}
sockets.connect();
// Initialize after communication has been set up
// We'll try to do the same `get_bridge_instance` 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.
{
std::lock_guard lock(current_bridge_instance_mutex);
current_bridge_instance = this;
plugin = vst_entry_point(host_callback_proxy);
if (!plugin) {
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. Any other updates
// 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()`.
sockets.host_vst_control.send(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 = sockets.host_vst_control.receive_single();
// Allow this plugin to configure the main context's tick rate
main_context.update_timer_interval(config.event_loop_interval());
parameters_handler = Win32Thread([&]() {
sockets.host_vst_parameters.receive_multi(
[&](Parameter request, std::vector& buffer) {
// 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.
if (request.value) {
// `setParameter`
plugin->setParameter(plugin, request.index, *request.value);
ParameterResult response{std::nullopt};
sockets.host_vst_parameters.send(response, buffer);
} else {
// `getParameter`
float value = plugin->getParameter(plugin, request.index);
ParameterResult response{value};
sockets.host_vst_parameters.send(response, buffer);
}
});
});
process_replacing_handler = Win32Thread([&]() {
// These are used as scratch buffers to prevent unnecessary allocations.
// Since don't know in advance whether the host will call
// `processReplacing` or `processDoubleReplacing` we'll just create
// both.
std::vector> output_buffers_single_precision(
plugin->numOutputs);
std::vector> output_buffers_double_precision(
plugin->numOutputs);
sockets.host_vst_process_replacing.receive_multi(
[&](AudioBuffers request, std::vector& 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 than copies of the events.
std::lock_guard lock(next_buffer_midi_events_mutex);
// HACK: Workaround for a bug in SWAM Cello where it would call
// `audioMasterGetTime()` once for every sample. The first
// value returned by this function during an audio
// processing cycle will be reused for the rest of the
// cycle.
if (config.cache_time_info) {
time_info.reset();
}
// Since the host should only be calling one of `process()`,
// processReplacing()` or `processDoubleReplacing()`, we can all
// handle them over the same socket. We pick which one to call
// depending on the type of data we got sent and the plugin's
// reported support for these functions.
std::visit(
overload{
[&](std::vector>& input_buffers) {
// The process functions expect a `float**` for
// their inputs and their outputs
std::vector inputs;
for (auto& buffer : input_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. The type we're using
// here (single precision floats vs double
// precisioon doubles) should be the same as the one
// we're sending in our response.
std::vector outputs;
output_buffers_single_precision.resize(
plugin->numOutputs);
for (auto& buffer :
output_buffers_single_precision) {
buffer.resize(request.sample_frames);
outputs.push_back(buffer.data());
}
// 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) {
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& buffer :
output_buffers_single_precision) {
std::fill(buffer.begin(), buffer.end(),
0.0);
}
plugin->process(plugin, inputs.data(),
outputs.data(),
request.sample_frames);
}
AudioBuffers response{
output_buffers_single_precision,
request.sample_frames};
sockets.host_vst_process_replacing.send(response,
buffer);
},
[&](std::vector>& input_buffers) {
// Exactly the same as the above, but for double
// precision audio
std::vector inputs;
for (auto& buffer : input_buffers) {
inputs.push_back(buffer.data());
}
std::vector outputs;
output_buffers_double_precision.resize(
plugin->numOutputs);
for (auto& buffer :
output_buffers_double_precision) {
buffer.resize(request.sample_frames);
outputs.push_back(buffer.data());
}
plugin->processDoubleReplacing(
plugin, inputs.data(), outputs.data(),
request.sample_frames);
AudioBuffers response{
output_buffers_double_precision,
request.sample_frames};
sockets.host_vst_process_replacing.send(response,
buffer);
}},
request.buffers);
next_audio_buffer_midi_events.clear();
});
});
}
void Vst2Bridge::run() {
sockets.host_vst_dispatch.receive_events(
std::nullopt, [&](Event& event, bool /*on_main_thread*/) {
if (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 to those events, meaning that they have to
// live at least until the next audio buffer gets processed.
// We're not using `passthrough_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(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 = return_value,
.payload = nullptr,
.value_payload = std::nullopt};
return response;
} else {
return passthrough_event(
plugin,
[&](AEffect* plugin, int opcode, int index, intptr_t value,
void* data, float option) -> intptr_t {
// HACK: Ardour 6.3 will call `effEditIdle` before
// `effEditOpen`, which causes some plugins to
// crash. This has been fixed as of
// https://github.com/Ardour/ardour/commit/f7cb1b0b481eeda755bdf8eb9fc5f90a81d2aa01.
// We should keep this in until Ardour 6.3 is no
// longer in distro repositories.
//
// Note that now that we run `effEditIdle`
// entirely off of a Win32 timer this will never
// get hit, but we'll keep it in for the sake of
// preserving correct behaviour.
if (opcode == effEditIdle && !editor) {
std::cerr << "WARNING: The host is calling "
"`effEditIdle()` while the "
"plugin's editor is closed, "
"filtering the request (is "
"this Ardour?). This bug should "
"be reported to the host."
<< std::endl;
return 0;
}
// Certain functions will most definitely involve the
// GUI or the Win32 message loop. These functions have
// to be performed on the thread that is running the IO
// context, since this is also where the plugins were
// instantiated and where the Win32 message loop is
// handled.
if (unsafe_opcodes.contains(opcode)) {
return main_context
.run_in_context([&]() {
return dispatch_wrapper(plugin, opcode,
index, value, data,
option);
})
.get();
} else {
return dispatch_wrapper(plugin, opcode, index,
value, data, option);
}
},
event);
}
});
}
void Vst2Bridge::handle_x11_events() {
if (editor) {
editor->handle_x11_events();
}
}
void Vst2Bridge::handle_win32_events() {
MSG msg;
for (int i = 0;
i < max_win32_messages && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
i++) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
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 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(data);
// NOTE: Just like in the event loop, we want to run this with lower
// priority to prevent whatever operation the plugin does
// while it's loading its editor from preempting the audio
// thread.
set_realtime_priority(false);
Editor& editor_instance =
editor.emplace(config, x11_handle, [plugin = this->plugin]() {
plugin->dispatcher(plugin, effEditIdle, 0, 0, nullptr, 0.0);
});
const intptr_t result =
plugin->dispatcher(plugin, opcode, index, value,
editor_instance.get_win32_handle(), option);
set_realtime_priority(true);
return result;
} break;
case effEditClose: {
// Cleanup is handled through RAII
set_realtime_priority(false);
const intptr_t return_value =
plugin->dispatcher(plugin, opcode, index, value, data, option);
editor.reset();
set_realtime_priority(true);
return return_value;
} break;
case effEditGetRect: {
set_realtime_priority(false);
const intptr_t return_value =
plugin->dispatcher(plugin, opcode, index, value, data, option);
set_realtime_priority(true);
return return_value;
} break;
default:
return plugin->dispatcher(plugin, opcode, index, value, data,
option);
break;
}
}
class HostCallbackDataConverter : DefaultDataConverter {
public:
HostCallbackDataConverter(AEffect* plugin,
std::optional& time_info)
: plugin(plugin), time_info(time_info) {}
EventPayload read(const int opcode,
const int index,
const intptr_t value,
const void* data) const override {
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(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 read_value(
const int opcode,
const intptr_t value) const override {
return DefaultDataConverter::read_value(opcode, value);
}
void write(const int opcode,
void* data,
const EventResult& response) const override {
switch (opcode) {
case audioMasterGetTime:
// Write the returned `VstTimeInfo` struct into a field and
// make the function return a pointer 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(response.payload)) {
time_info = std::nullopt;
} else {
time_info = std::get(response.payload);
}
break;
default:
DefaultDataConverter::write(opcode, data, response);
break;
}
}
intptr_t return_value(const int opcode,
const intptr_t original) const override {
switch (opcode) {
case audioMasterGetTime: {
// Return a pointer to the `VstTimeInfo` object written in
// the function above
VstTimeInfo* time_info_pointer = nullptr;
if (time_info) {
time_info_pointer = &*time_info;
}
return reinterpret_cast(time_info_pointer);
} break;
default:
return DefaultDataConverter::return_value(opcode, original);
break;
}
}
void write_value(const int opcode,
intptr_t value,
const EventResult& response) const override {
return DefaultDataConverter::write_value(opcode, value, response);
}
private:
AEffect* plugin;
std::optional& time_info;
};
intptr_t Vst2Bridge::host_callback(AEffect* effect,
int opcode,
int index,
intptr_t value,
void* data,
float option) {
// HACK: Workaround for a bug in SWAM Cello where it would call
// `audioMasterGetTime()` once for every sample. When this option is
// enabled `time_info` should be reset in the process function. The
// `time_info` value is assigned inside of
// `HostCallbackDataConverter::write()`.
if (config.cache_time_info && time_info) {
return reinterpret_cast(&*time_info);
}
HostCallbackDataConverter converter(effect, time_info);
return sockets.vst_host_callback.send_event(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);
}