mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Allow all threads to return when sockets close
This was not a problem with individually hosted plugins because the entire process got terminated at once, but here we all threads to shut down gracefully when a plugin's sockets get closed. I wish this wouldn't need all these try-catches, but we're not writing Haskell here. The only issue remaining is that for some reason only the first instance's editor works, at least for Serum. This might be because of the message loop.
This commit is contained in:
+131
-108
@@ -186,130 +186,150 @@ void Vst2Bridge::handle_dispatch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void Vst2Bridge::handle_dispatch_midi_events() {
|
void Vst2Bridge::handle_dispatch_midi_events() {
|
||||||
while (true) {
|
try {
|
||||||
receive_event(
|
while (true) {
|
||||||
host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) {
|
receive_event(
|
||||||
if (BOOST_LIKELY(event.opcode == effProcessEvents)) {
|
host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) {
|
||||||
// For 99% of the plugins we can just call
|
if (BOOST_LIKELY(event.opcode == effProcessEvents)) {
|
||||||
// `effProcessReplacing()` and be done with it, but a select
|
// For 99% of the plugins we can just call
|
||||||
// few plugins (I could only find Kontakt that does this)
|
// `effProcessReplacing()` and be done with it, but a
|
||||||
// don't actually make copies of the events they receive and
|
// select few plugins (I could only find Kontakt that
|
||||||
// only store pointers, meaning that they have to live at
|
// does this) don't actually make copies of the events
|
||||||
// least until the next audio buffer gets processed. We're
|
// they receive and only store pointers, meaning that
|
||||||
// not using `passhtourhg_events()` here directly because we
|
// they have to live at least until the next audio
|
||||||
// need to store a copy of the `DynamicVstEvents` struct
|
// buffer gets processed. We're not using
|
||||||
// before passing the generated `VstEvents` object to the
|
// `passhtourhg_events()` here directly because we need
|
||||||
// plugin.
|
// to store a copy of the `DynamicVstEvents` struct
|
||||||
std::lock_guard lock(next_buffer_midi_events_mutex);
|
// 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(
|
next_audio_buffer_midi_events.push_back(
|
||||||
std::get<DynamicVstEvents>(event.payload));
|
std::get<DynamicVstEvents>(event.payload));
|
||||||
DynamicVstEvents& events =
|
DynamicVstEvents& events =
|
||||||
next_audio_buffer_midi_events.back();
|
next_audio_buffer_midi_events.back();
|
||||||
|
|
||||||
// Exact same handling as in `passthrough_event`, apart from
|
// Exact same handling as in `passthrough_event`, apart
|
||||||
// making a copy of the events first
|
// from making a copy of the events first
|
||||||
const intptr_t return_value = plugin->dispatcher(
|
const intptr_t return_value = plugin->dispatcher(
|
||||||
plugin, event.opcode, event.index, event.value,
|
plugin, event.opcode, event.index, event.value,
|
||||||
&events.as_c_events(), event.option);
|
&events.as_c_events(), event.option);
|
||||||
|
|
||||||
EventResult response{return_value, nullptr, std::nullopt};
|
EventResult response{return_value, nullptr,
|
||||||
|
std::nullopt};
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
|
|
||||||
std::cerr << "[Warning] Received non-MIDI "
|
std::cerr << "[Warning] Received non-MIDI "
|
||||||
"event on MIDI processing thread"
|
"event on MIDI processing thread"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
// Maybe this should just be a hard error instead, since it
|
// Maybe this should just be a hard error instead, since
|
||||||
// should never happen
|
// it should never happen
|
||||||
return passthrough_event(
|
return passthrough_event(
|
||||||
plugin, std::bind(&Vst2Bridge::dispatch_wrapper, this,
|
plugin,
|
||||||
_1, _2, _3, _4, _5, _6))(event);
|
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);
|
|
||||||
}
|
}
|
||||||
|
} catch (const boost::system::system_error&) {
|
||||||
|
// The plugin has cut off communications, so we can shut down this host
|
||||||
|
// application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void Vst2Bridge::handle_process_replacing() {
|
void Vst2Bridge::handle_parameters() {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const boost::system::system_error&) {
|
||||||
|
// The plugin has cut off communications, so we can shut down this host
|
||||||
|
// application
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Vst2Bridge::handle_process_replacing() {
|
||||||
std::vector<std::vector<float>> output_buffers(plugin->numOutputs);
|
std::vector<std::vector<float>> output_buffers(plugin->numOutputs);
|
||||||
|
|
||||||
while (true) {
|
try {
|
||||||
auto request = read_object<AudioBuffers>(host_vst_process_replacing,
|
while (true) {
|
||||||
process_buffer);
|
auto request = read_object<AudioBuffers>(host_vst_process_replacing,
|
||||||
|
process_buffer);
|
||||||
|
|
||||||
// The process functions expect a `float**` for their inputs and
|
// The process functions expect a `float**` for their inputs and
|
||||||
// their outputs
|
// their outputs
|
||||||
std::vector<float*> inputs;
|
std::vector<float*> inputs;
|
||||||
for (auto& buffer : request.buffers) {
|
for (auto& buffer : request.buffers) {
|
||||||
inputs.push_back(buffer.data());
|
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();
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
AudioBuffers response{output_buffers, request.sample_frames};
|
// Let the plugin process the MIDI events that were received since
|
||||||
write_object(host_vst_process_replacing, response, process_buffer);
|
// 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);
|
||||||
|
}
|
||||||
|
} catch (const boost::system::system_error&) {
|
||||||
|
// The plugin has cut off communications, so we can shut down this host
|
||||||
|
// application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,12 +497,15 @@ intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect,
|
|||||||
|
|
||||||
uint32_t WINAPI handle_dispatch_midi_events_proxy(void* instance) {
|
uint32_t WINAPI handle_dispatch_midi_events_proxy(void* instance) {
|
||||||
static_cast<Vst2Bridge*>(instance)->handle_dispatch_midi_events();
|
static_cast<Vst2Bridge*>(instance)->handle_dispatch_midi_events();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t WINAPI handle_parameters_proxy(void* instance) {
|
uint32_t WINAPI handle_parameters_proxy(void* instance) {
|
||||||
static_cast<Vst2Bridge*>(instance)->handle_parameters();
|
static_cast<Vst2Bridge*>(instance)->handle_parameters();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t WINAPI handle_process_replacing_proxy(void* instance) {
|
uint32_t WINAPI handle_process_replacing_proxy(void* instance) {
|
||||||
static_cast<Vst2Bridge*>(instance)->handle_process_replacing();
|
static_cast<Vst2Bridge*>(instance)->handle_process_replacing();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ class Vst2Bridge {
|
|||||||
// below. They're defined here because we can't use lambdas with WinAPI's
|
// 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
|
// `CreateThread` which is needed to support the proper call conventions the
|
||||||
// VST plugins expect.
|
// VST plugins expect.
|
||||||
[[noreturn]] void handle_dispatch_midi_events();
|
void handle_dispatch_midi_events();
|
||||||
[[noreturn]] void handle_parameters();
|
void handle_parameters();
|
||||||
[[noreturn]] void handle_process_replacing();
|
void handle_process_replacing();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forward the host callback made by the plugin to the host and return the
|
* Forward the host callback made by the plugin to the host and return the
|
||||||
|
|||||||
Reference in New Issue
Block a user