// 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 .
#include "vst2.h"
#include
#include
// Generated inside of the build directory
#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);
/**
* If `plugin->ptr2` is set to this value, then we'll know that `plugin->ptr1`
* is a valid pointer to a `Vst2Bridge` instance. This is needed for when one
* instance of a plugin in a plugin group processes audio while another instance
* of that plugin in the same plugin group is being initialized. In that
* situation we cannot rely on just `current_bridge_instance`, and some plugins
* don't zero initialize these pointers like they should so we also can't rely
* on that.
*/
constexpr size_t yabridge_ptr2_magic = 0xdeadbeef + 420;
/**
* 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.
*
* We don't need any locking here because we can only initialize `Vst2Bridge`
* from the main thread anyways.
*/
Vst2Bridge* current_bridge_instance = nullptr;
/**
* Callbacks (presumably made from the GUI thread) that may receive responses
* that have to be handled from the same thread. If we don't do this, then those
* respones might either cause a deadlock when the plugin uses recursive
* mutexes, or it may result in some other thread safety issues.
*
* NOTE: This is needed for Voxengo VST2 plugins in Renoise. When
* `effSetChunk()` is called from the GUI thread, Voxengo VST2 plugins
* will (wrongly) call `audioMasterUpdateDisplay()` while handling that
* call. Renoise then calls `effGetProgram()` while handling that which
* shouldn't cause any issues, but the Voxengo plugins try to lock
* recursive mutexes on both functions so `effGetProgram()` _has_ to be
* called on the same thread that is currently calling
* `audioMasterUpdateDisplay()`.
* NOTE: Similarly, REAPER calls `effProgramName()` in response to
* `audioMasterUpdateDisplay()`, and PG-8X also requires that to be called
* from the same thread that called `audioMasterUpdateDisplay()`.
*/
static const std::unordered_set mutually_recursive_callbacks{
audioMasterUpdateDisplay};
/**
* Opcodes that, when called on this plugin's dispatcher, have to be handled
* mutually recursively, if possible. This means that the plugin makes a
* callback using one of the functions defined in
* `mutually_recursive_callbacks`, and when the host responds by calling one of
* these functions, then that function should be handled on the same thread
* where the plugin originally called the request on. If no mutually recursive
* calling sequence is active while one of these functions is called, then we'll
* just execute the function directly on the calling thread. See above for a
* list of situations where this may be necessary.
*/
static const std::unordered_set safe_mutually_recursive_requests{
effGetProgram, effGetProgramName};
/**
* 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.
* NOTE: `effSetChunk` and `effGetChunk` should be callable from any thread, but
* Algonaut Atlas doesn't restore chunk data unless `effSetChunk` is run
* from the GUI thread
* NOTE: `effSetSampleRate` and `effSetBlockSize` really shouldn't be here, but
* New Sonic Arts' Vice plugin spawns a new thread and calls drawing code
* while changing sample rate and block size. We'll need to see if doing
* this on the main thread introduces any regressions.
*/
static const std::unordered_set unsafe_requests{
effOpen, effClose, effEditGetRect, effEditOpen,
effEditClose, effEditIdle, effEditTop, effMainsChanged,
effGetChunk, effSetChunk, effSetSampleRate, effSetBlockSize};
/**
* These opcodes from `unsafe_requests` should be run under realtime scheduling
* so that if they spawn audio worker threads, those threads will also be run
* with `SCHED_FIFO`. This is needed because unpatched Wine still does not
* implement thread priorities. Normally these unsafe requests are run on the
* main thread, which doesn't use realtime scheduling.
*/
static const std::unordered_set unsafe_requests_realtime{effOpen,
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) {
if (plugin && reinterpret_cast(plugin->ptr2) == yabridge_ptr2_magic)
[[likely]] {
return *static_cast(plugin->ptr1);
}
// We can only set this pointer after the plugin has initialized, so when
// the plugin performs a callback during its initialization we'll use the
// current bridge instance set during the Vst2Bridge constructor. This is
// thread safe because VST2 plugins have to be initialized on the main
// thread.
assert(current_bridge_instance);
return *current_bridge_instance;
}
Vst2Bridge::Vst2Bridge(MainContext& main_context,
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
std::string plugin_dll_path,
std::string endpoint_base_dir,
pid_t parent_pid)
: HostBridge(main_context, plugin_dll_path, parent_pid),
logger_(generic_logger_),
plugin_handle_(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
sockets_(main_context.context_, endpoint_base_dir, false) {
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
// pre-VST2.4 `main` was also a valid name
VstEntryPoint vst_entry_point = nullptr;
for (auto name : {"VSTPluginMain", "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();
// We'll try to do the same `get_bridge_instance()` trick as in
//`plugin/bridges/vst2.cpp`, but since the plugin will probably call the
// host callback while it's initializing we sadly have to use a global here.
// Note that this reinterpret cast is not needed at all since the function
// pointer types are exactly the same, but clangd will complain otherwise
current_bridge_instance = this;
// We'll also need to make sure that any audio worker threads created by the
// plugin are running using realtime scheduling, since Wine doesn't fully
// implement the Win32 process priority API yet.
set_realtime_priority(true);
plugin_ = vst_entry_point(
reinterpret_cast(host_callback_proxy));
set_realtime_priority(false);
if (!plugin_) {
throw std::runtime_error("VST plugin at '" + plugin_dll_path +
"' failed to initialize.");
}
// We use `plugin->ptr2` to identify plugins that have already been
// initialized. Otherwise we can run into thread safety issues when a plugin
// is processing audio while another plugin is being initialized.
current_bridge_instance = nullptr;
plugin_->ptr1 = this;
plugin_->ptr2 = reinterpret_cast(yabridge_ptr2_magic);
// 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()`. We will also send along this host's version so
// we can show a warning when the plugin's version doesn't match.
sockets_.host_vst_control_.send(
Vst2EventResult{.return_value = 0,
.payload = *plugin_,
.value_payload = yabridge_git_version});
// 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([&]() {
set_realtime_priority(true);
pthread_setname_np(pthread_self(), "parameters");
sockets_.host_vst_parameters_.receive_multi(
[&](Parameter& request, SerializationBufferBase& 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([&]() {
set_realtime_priority(true);
pthread_setname_np(pthread_self(), "audio");
// Most plugins will already enable FTZ, but there are a handful of
// plugins that don't that suffer from extreme DSP load increases when
// they start producing denormals
ScopedFlushToZero ftz_guard;
sockets_.host_vst_process_replacing_.receive_multi<
Vst2ProcessRequest>([&](Vst2ProcessRequest& process_request,
SerializationBufferBase& buffer) {
// Since the value cannot change during this processing cycle,
// we'll send the current transport information as part of the
// request so we prefetch it to avoid unnecessary callbacks from
// the audio thread
std::optional
time_info_cache_guard =
process_request.current_time_info
? std::optional(time_info_cache_.set(
*process_request.current_time_info))
: std::nullopt;
// We'll also prefetch the process level, since some plugins
// will ask for this during every processing cycle
decltype(process_level_cache_)::Guard process_level_cache_guard =
process_level_cache_.set(process_request.current_process_level);
// As suggested by Jack Winter, we'll synchronize this thread's
// audio processing priority with that of the host's audio
// thread every once in a while
if (process_request.new_realtime_priority) {
set_realtime_priority(true,
*process_request.new_realtime_priority);
}
// 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_);
// As an optimization we no don't pass the input audio along
// with `Vst2ProcessRequest`, and instead we'll write it to a
// shared memory object on the plugin side. We can then write
// the output audio to the same shared memory object. Since the
// host should only be calling one of `process()`,
// processReplacing()` or `processDoubleReplacing()`, we can all
// handle them all at once. We pick which one to call depending
// on the type of data we got sent and the plugin's reported
// support for these functions.
auto do_process = [&](T) {
// These were set up after the host called
// `effMainsChanged()` with the correct size, so this
// reinterpret cast is safe even if the host suddenly starts
// sending 32-bit single precision audio after it set up
// audio processing for double precision (not that the
// Windows VST2 plugin would be able to handle that,
// presumably)
T** input_channel_pointers = reinterpret_cast(
process_buffers_input_pointers_.data());
T** output_channel_pointers = reinterpret_cast(
process_buffers_output_pointers_.data());
if constexpr (std::is_same_v) {
// 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_, input_channel_pointers,
output_channel_pointers,
process_request.sample_frames);
} else {
// If we zero out this buffer then the behavior is
// the same as `processReplacing`
for (int channel = 0; channel < plugin_->numOutputs;
channel++) {
std::fill(output_channel_pointers[channel],
output_channel_pointers[channel] +
process_request.sample_frames,
static_cast(0.0));
}
plugin_->process(plugin_, input_channel_pointers,
output_channel_pointers,
process_request.sample_frames);
}
} else if (std::is_same_v) {
plugin_->processDoubleReplacing(
plugin_, input_channel_pointers,
output_channel_pointers, process_request.sample_frames);
} else {
static_assert(
std::is_same_v || std::is_same_v,
"Audio processing only works with single and "
"double precision floating point numbers");
}
};
assert(process_buffers_);
if (process_request.double_precision) {
// XXX: Clangd doesn't let you specify template parameters
// for templated lambdas. This argument should get
// optimized out
do_process(double());
} else {
do_process(float());
}
// We modified the buffers within the `process_response` object,
// so we can just send that object back. Like on the plugin side
// we cannot reuse the request object because a plugin may have
// a different number of input and output channels
sockets_.host_vst_process_replacing_.send(Ack{}, buffer);
// See the docstrong on `should_clear_midi_events` for why we
// don't just clear `next_buffer_midi_events` here
should_clear_midi_events_ = true;
});
});
}
bool Vst2Bridge::inhibits_event_loop() noexcept {
return !is_initialized_;
}
void Vst2Bridge::run() {
set_realtime_priority(true);
sockets_.host_vst_dispatch_.receive_events(
std::nullopt,
[&](Vst2Event& event, bool /*on_main_thread*/) -> Vst2EventResult {
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_);
// See the docstring on `should_clear_midi_events` for why we
// only deallocate old MIDI events here instead of a at the end
// of every processing cycle
if (should_clear_midi_events_) {
next_audio_buffer_midi_events_.clear();
should_clear_midi_events_ = false;
}
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);
return Vst2EventResult{.return_value = return_value,
.payload = nullptr,
.value_payload = std::nullopt};
}
Vst2EventResult result = passthrough_event(
plugin_,
[&](AEffect* plugin, int opcode, int index, intptr_t value,
void* data, float option) -> intptr_t {
// 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_requests.contains(opcode)) {
// Requests that potentially spawn an audio worker
// thread should be run with `SCHED_FIFO` until Wine
// implements the corresponding Windows API
const bool is_realtime_request =
unsafe_requests_realtime.contains(opcode);
return main_context_
.run_in_context([&]() -> intptr_t {
if (is_realtime_request) {
set_realtime_priority(true);
}
const intptr_t result = dispatch_wrapper(
plugin, opcode, index, value, data, option);
if (is_realtime_request) {
set_realtime_priority(false);
}
// The Win32 message loop will not be run up to
// this point to prevent plugins with partially
// initialized states from misbehaving
if (opcode == effOpen) {
is_initialized_ = true;
}
return result;
})
.get();
} else if (safe_mutually_recursive_requests.contains(
opcode)) {
// If this function call is potentially in response to a
// callback contained in `mutually_recursive_callbacks`,
// then we should call it on the same thread that called
// that callback if possible. This may be needed when
// plugins use recursive mutexes, thus causing deadlocks
// when the function is called from any other thread.
return mutual_recursion_.handle([&]() {
return dispatch_wrapper(plugin, opcode, index,
value, data, option);
});
} else {
return dispatch_wrapper(plugin, opcode, index, value,
data, option);
}
},
event);
// We also need some special handling to set up audio processing.
// After the plugin has finished setting up audio processing, we'll
// initialize our shared audio buffers on this side and send the
// configuration back to the native plugin so it can also connect to
// the same buffers. We cannot use `Vst2Bridge::dispatch_wrapper()`
// for this because we need to directly return payload data that
// won't be visible to the plugin at all.
// NOTE: Ardour will call `effMainsChanged()` with a value of 1
// unconditionally when unloading a plugin, even when audio
// playback has never been initialized (and `effSetBlockSize`
// has never been called)
if (event.opcode == effMainsChanged && event.value == 1) {
// Returning another result this way is a bit ugly, but sadly
// optimizations have never made code nicer to read
return Vst2EventResult{.return_value = result.return_value,
.payload = setup_shared_audio_buffers(),
.value_payload = std::nullopt};
}
return result;
});
}
void Vst2Bridge::close_sockets() {
sockets_.close();
}
class HostCallbackDataConverter : public DefaultDataConverter {
public:
HostCallbackDataConverter(
AEffect* plugin,
VstTimeInfo& last_time_info,
MutualRecursionHelper& mutual_recursion) noexcept
: plugin_(plugin),
last_time_info_(last_time_info),
mutual_recursion_(mutual_recursion) {}
Vst2Event::Payload read_data(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;
// NOTE: DefaultDataConverter::read() should be able to handle all
// of these 'simple' opcodes, but Plugsound Free by UVI passes
// random garbage for their data argument. Because of that
// `audioMasterWantMidi()` will segfault because when we'll
// try to read that data as a string we'll start reading
// unallocated memory. Even though no other plugins seem to do
// this< we'll list all of these data-less opcodes just to be
// sure. We're leaving out a few opcodes here, because I have
// no clue whether some of the more obscure ones are supposed
// to have an data argument or not.
case audioMasterAutomate:
case audioMasterVersion:
case audioMasterCurrentId:
case audioMasterIdle:
case audioMasterWantMidi:
case audioMasterSizeWindow:
case audioMasterGetSampleRate:
case audioMasterGetBlockSize:
case audioMasterGetInputLatency:
case audioMasterGetOutputLatency:
case audioMasterGetCurrentProcessLevel:
case audioMasterGetAutomationState:
case audioMasterGetVendorVersion:
case audioMasterGetLanguage:
case audioMasterUpdateDisplay:
case audioMasterBeginEdit:
case audioMasterEndEdit:
// NOTE: REAPER abuses the dispatcher to add their own opcodes
// outside of `audioMasterVendorSpecific`
case audioMasterDeadBeef:
return nullptr;
break;
default:
return DefaultDataConverter::read_data(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_data(const int opcode,
void* data,
const Vst2EventResult& response) const override {
switch (opcode) {
case audioMasterGetTime:
// If the host returned a valid `VstTimeInfo` object, then we'll
// keep track of it so we can return a pointer to it in the
// function below
if (std::holds_alternative(response.payload)) {
last_time_info_ = std::get(response.payload);
}
break;
default:
DefaultDataConverter::write_data(opcode, data, response);
break;
}
}
intptr_t return_value(const int opcode,
const intptr_t original) const override {
switch (opcode) {
case audioMasterGetTime: {
// If the host returned a null pointer, then we'll do the same
// thing here
if (original == 0) {
return 0;
} else {
return reinterpret_cast(&last_time_info_);
}
} break;
default:
return DefaultDataConverter::return_value(opcode, original);
break;
}
}
void write_value(const int opcode,
intptr_t value,
const Vst2EventResult& response) const override {
return DefaultDataConverter::write_value(opcode, value, response);
}
Vst2EventResult send_event(
boost::asio::local::stream_protocol::socket& socket,
const Vst2Event& event,
SerializationBufferBase& buffer) const override {
if (mutually_recursive_callbacks.contains(event.opcode)) {
return mutual_recursion_.fork([&]() {
return DefaultDataConverter::send_event(socket, event, buffer);
});
} else {
return DefaultDataConverter::send_event(socket, event, buffer);
}
}
private:
AEffect* plugin_;
VstTimeInfo& last_time_info_;
MutualRecursionHelper& mutual_recursion_;
};
intptr_t Vst2Bridge::host_callback(AEffect* effect,
int opcode,
int index,
intptr_t value,
void* data,
float option) {
switch (opcode) {
// During a processing call we'll have already sent the current
// transport information from the plugin side to avoid an unnecessary
// callback
case audioMasterGetTime: {
const VstTimeInfo* cached_time_info = time_info_cache_.get();
if (cached_time_info) {
// This cached value is temporary, so we'll still use the
// regular time info storing mechanism
last_time_info_ = *cached_time_info;
const intptr_t result =
reinterpret_cast(&last_time_info_);
// Make sure that these cached events don't get lost in the logs
logger_.log_event(false, opcode, index, value,
WantsVstTimeInfo{}, option, std::nullopt);
logger_.log_event_response(false, opcode, result,
last_time_info_, std::nullopt, true);
return result;
}
} break;
// We also send the current process level for similar reasons
case audioMasterGetCurrentProcessLevel: {
const int* current_process_level = process_level_cache_.get();
if (current_process_level) {
logger_.log_event(false, opcode, index, value, nullptr, option,
std::nullopt);
logger_.log_event_response(false, opcode,
*current_process_level, nullptr,
std::nullopt, true);
return *current_process_level;
}
} break;
// If the plugin changes its window size, we'll also resize the wrapper
// window accordingly.
case audioMasterSizeWindow: {
if (editor_) {
editor_->resize(index, value);
}
} break;
}
HostCallbackDataConverter converter(effect, last_time_info_,
mutual_recursion_);
return sockets_.vst_host_callback_.send_event(
converter, std::nullopt, opcode, index, value, data, option);
}
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. Keep in mind that in our `run()` function
// above some of these events will be called on some arbitrary thread (where
// we're running with realtime scheduling) and some might be called on the
// main thread using `main_context.run_in_context()` (where we don't use
// realtime scheduling).
switch (opcode) {
case effSetBlockSize: {
// Used to initialize the shared audio buffers when handling
// `effMainsChanged` in `Vst2Bridge::run()`
max_samples_per_block_ = value;
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(data);
Editor& editor_instance = editor_.emplace(
main_context_, config_, generic_logger_, x11_handle,
[plugin = 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);
// Make sure the wrapper window has the correct initial size. The
// plugin can later change this size using `audioMasterSizeWindow`.
// NOTE: Every single plugin handles `effEditGetRect` before
// `effEditOpen` fine. Except for this one single plugin:
// https://codefn42.com/randarp/index.html
VstRect* editor_rect = nullptr;
plugin->dispatcher(plugin, effEditGetRect, 0, 0, &editor_rect, 0.0);
if (editor_rect) {
std::cerr << editor_rect->right << std::endl;
std::cerr << editor_rect->bottom << std::endl;
editor_->resize(editor_rect->right - editor_rect->left,
editor_rect->bottom - editor_rect->top);
}
// NOTE: There's zero reason why the window couldn't already be
// visible from the start, but Waves V13 VST3 plugins think it
// would be a splendid idea to randomly dereference null
// pointers when the window is already visible. Thanks Waves.
editor_instance.show();
return result;
} break;
case effEditClose: {
// Cleanup is handled through RAII
const intptr_t return_value =
plugin->dispatcher(plugin, opcode, index, value, data, option);
editor_.reset();
return return_value;
} break;
case effSetProcessPrecision: {
// Used to initialize the shared audio buffers when handling
// `effMainsChanged` in `Vst2Bridge::run()`
double_precision_ = value == kVstProcessPrecision64;
return plugin->dispatcher(plugin, opcode, index, value, data,
option);
break;
}
default: {
return plugin->dispatcher(plugin, opcode, index, value, data,
option);
break;
}
}
}
AudioShmBuffer::Config Vst2Bridge::setup_shared_audio_buffers() {
// We'll first compute the size and channel offsets for our buffer based on
// the information already passed to us by the host. The offsets for each
// audio channel are in samples (since they'll be used with pointer
// arithmetic in `AudioShmBuffer`), and we'll only use the first bus (since
// VST2 plugins don't have multiple audio busses).
assert(max_samples_per_block_);
uint32_t current_offset = 0;
std::vector input_channel_offsets(plugin_->numInputs);
for (int channel = 0; channel < plugin_->numInputs; channel++) {
input_channel_offsets[channel] = current_offset;
current_offset += *max_samples_per_block_;
}
std::vector output_channel_offsets(plugin_->numOutputs);
for (int channel = 0; channel < plugin_->numOutputs; channel++) {
output_channel_offsets[channel] = current_offset;
current_offset += *max_samples_per_block_;
}
// The size of the buffer is in bytes, and it will depend on whether the
// host is going to pass 32-bit or 64-bit audio to the plugin
const uint32_t buffer_size =
current_offset * (double_precision_ ? sizeof(double) : sizeof(float));
// We'll set up these shared memory buffers on the Wine side first, and then
// when this request returns we'll do the same thing on the native plugin
// side
AudioShmBuffer::Config buffer_config{
.name = sockets_.base_dir_.filename().string(),
.size = buffer_size,
.input_offsets = {std::move(input_channel_offsets)},
.output_offsets = {std::move(output_channel_offsets)}};
if (!process_buffers_) {
process_buffers_.emplace(buffer_config);
} else {
process_buffers_->resize(buffer_config);
}
// The process functions expect a `T**` for their inputs and outputs, so
// we'll also set those up right now
process_buffers_input_pointers_.resize(plugin_->numInputs);
for (int channel = 0; channel < plugin_->numInputs; channel++) {
if (double_precision_) {
process_buffers_input_pointers_[channel] =
process_buffers_->input_channel_ptr(0, channel);
} else {
process_buffers_input_pointers_[channel] =
process_buffers_->input_channel_ptr(0, channel);
}
}
process_buffers_output_pointers_.resize(plugin_->numOutputs);
for (int channel = 0; channel < plugin_->numOutputs; channel++) {
if (double_precision_) {
process_buffers_output_pointers_[channel] =
process_buffers_->output_channel_ptr(0, channel);
} else {
process_buffers_output_pointers_[channel] =
process_buffers_->output_channel_ptr(0, channel);
}
}
return buffer_config;
}
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);
}