mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Replace the Win32Thread proxy functions
Now that we can use lambdas instead.
This commit is contained in:
+192
-212
@@ -50,13 +50,6 @@ const std::set<int> unsafe_opcodes{effOpen, effClose, effEditGetRect,
|
||||
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
|
||||
@@ -140,13 +133,200 @@ Vst2Bridge::Vst2Bridge(PluginContext& main_context,
|
||||
// This works functionally identically to the `handle_dispatch()` function,
|
||||
// but this socket will only handle MIDI events and it will handle them
|
||||
// eagerly. This is needed because of Win32 API limitations.
|
||||
dispatch_midi_events_handler =
|
||||
Win32Thread(handle_dispatch_midi_events_proxy, this);
|
||||
dispatch_midi_events_handler = Win32Thread([&]() {
|
||||
sockets.host_vst_dispatch_midi_events.receive(
|
||||
std::nullopt, [&](Event& event, bool /*on_main_thread*/) {
|
||||
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 `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);
|
||||
|
||||
parameters_handler = Win32Thread(handle_parameters_proxy, this);
|
||||
next_audio_buffer_midi_events.push_back(
|
||||
std::get<DynamicVstEvents>(event.payload));
|
||||
DynamicVstEvents& events =
|
||||
next_audio_buffer_midi_events.back();
|
||||
|
||||
process_replacing_handler =
|
||||
Win32Thread(handle_process_replacing_proxy, this);
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
parameters_handler = Win32Thread([&]() {
|
||||
while (true) {
|
||||
try {
|
||||
// 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>(sockets.host_vst_parameters);
|
||||
if (request.value) {
|
||||
// `setParameter`
|
||||
plugin->setParameter(plugin, request.index, *request.value);
|
||||
|
||||
ParameterResult response{std::nullopt};
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
} else {
|
||||
// `getParameter`
|
||||
float value = plugin->getParameter(plugin, request.index);
|
||||
|
||||
ParameterResult response{value};
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down
|
||||
// this host application
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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<std::vector<float>> output_buffers_single_precision(
|
||||
plugin->numOutputs);
|
||||
std::vector<std::vector<double>> output_buffers_double_precision(
|
||||
plugin->numOutputs);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
auto request = read_object<AudioBuffers>(
|
||||
sockets.host_vst_process_replacing, process_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);
|
||||
|
||||
// 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<std::vector<float>>& input_buffers) {
|
||||
// The process functions expect a `float**` for
|
||||
// their inputs and their outputs
|
||||
std::vector<float*> 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<float*> 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<float>& 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};
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
},
|
||||
[&](std::vector<std::vector<double>>& input_buffers) {
|
||||
// Exactly the same as the above, but for double
|
||||
// precision audio
|
||||
std::vector<double*> inputs;
|
||||
for (auto& buffer : input_buffers) {
|
||||
inputs.push_back(buffer.data());
|
||||
}
|
||||
|
||||
std::vector<double*> 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};
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
}},
|
||||
request.buffers);
|
||||
|
||||
next_audio_buffer_midi_events.clear();
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down
|
||||
// this host application
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Vst2Bridge::should_skip_message_loop() const {
|
||||
@@ -191,191 +371,6 @@ void Vst2Bridge::handle_dispatch() {
|
||||
});
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_dispatch_midi_events() {
|
||||
sockets.host_vst_dispatch_midi_events.receive(
|
||||
std::nullopt, [&](Event& event, bool /*on_main_thread*/) {
|
||||
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
|
||||
// `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<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 = return_value,
|
||||
.payload = nullptr,
|
||||
.value_payload = 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_parameters() {
|
||||
while (true) {
|
||||
try {
|
||||
// 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>(sockets.host_vst_parameters);
|
||||
if (request.value) {
|
||||
// `setParameter`
|
||||
plugin->setParameter(plugin, request.index, *request.value);
|
||||
|
||||
ParameterResult response{std::nullopt};
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
} else {
|
||||
// `getParameter`
|
||||
float value = plugin->getParameter(plugin, request.index);
|
||||
|
||||
ParameterResult response{value};
|
||||
write_object(sockets.host_vst_parameters, response);
|
||||
}
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this
|
||||
// host application
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Bridge::handle_process_replacing() {
|
||||
// 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<std::vector<float>> output_buffers_single_precision(
|
||||
plugin->numOutputs);
|
||||
std::vector<std::vector<double>> output_buffers_double_precision(
|
||||
plugin->numOutputs);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
auto request = read_object<AudioBuffers>(
|
||||
sockets.host_vst_process_replacing, process_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);
|
||||
|
||||
// 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<std::vector<float>>& input_buffers) {
|
||||
// The process functions expect a `float**` for their
|
||||
// inputs and their outputs
|
||||
std::vector<float*> 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<float*> 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<float>& 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};
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
},
|
||||
[&](std::vector<std::vector<double>>& input_buffers) {
|
||||
// Exactly the same as the above, but for double
|
||||
// precision audio
|
||||
std::vector<double*> inputs;
|
||||
for (auto& buffer : input_buffers) {
|
||||
inputs.push_back(buffer.data());
|
||||
}
|
||||
|
||||
std::vector<double*> 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};
|
||||
write_object(sockets.host_vst_process_replacing,
|
||||
response, process_buffer);
|
||||
}},
|
||||
request.buffers);
|
||||
|
||||
next_audio_buffer_midi_events.clear();
|
||||
} catch (const boost::system::system_error&) {
|
||||
// The plugin has cut off communications, so we can shut down this
|
||||
// host application
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
|
||||
int opcode,
|
||||
int index,
|
||||
@@ -573,18 +568,3 @@ intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect,
|
||||
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();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t WINAPI handle_parameters_proxy(void* instance) {
|
||||
static_cast<Vst2Bridge*>(instance)->handle_parameters();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t WINAPI handle_process_replacing_proxy(void* instance) {
|
||||
static_cast<Vst2Bridge*>(instance)->handle_process_replacing();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -128,14 +128,6 @@ class Vst2Bridge {
|
||||
*/
|
||||
void handle_win32_events();
|
||||
|
||||
// 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.
|
||||
void handle_dispatch_midi_events();
|
||||
void handle_parameters();
|
||||
void handle_process_replacing();
|
||||
|
||||
/**
|
||||
* Forward the host callback made by the plugin to the host and return the
|
||||
* results.
|
||||
|
||||
Reference in New Issue
Block a user