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:
Robbert van der Helm
2020-05-22 23:20:06 +02:00
parent a246ddf344
commit 3a9d902c72
2 changed files with 134 additions and 111 deletions
+53 -30
View File
@@ -186,21 +186,23 @@ void Vst2Bridge::handle_dispatch() {
} }
} }
[[noreturn]] void Vst2Bridge::handle_dispatch_midi_events() { void Vst2Bridge::handle_dispatch_midi_events() {
try {
while (true) { while (true) {
receive_event( receive_event(
host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) { host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) {
if (BOOST_LIKELY(event.opcode == effProcessEvents)) { if (BOOST_LIKELY(event.opcode == effProcessEvents)) {
// For 99% of the plugins we can just call // For 99% of the plugins we can just call
// `effProcessReplacing()` and be done with it, but a select // `effProcessReplacing()` and be done with it, but a
// few plugins (I could only find Kontakt that does this) // select few plugins (I could only find Kontakt that
// don't actually make copies of the events they receive and // does this) don't actually make copies of the events
// only store pointers, meaning that they have to live at // they receive and only store pointers, meaning that
// least until the next audio buffer gets processed. We're // they have to live at least until the next audio
// not using `passhtourhg_events()` here directly because we // buffer gets processed. We're not using
// need to store a copy of the `DynamicVstEvents` struct // `passhtourhg_events()` here directly because we need
// before passing the generated `VstEvents` object to the // to store a copy of the `DynamicVstEvents` struct
// plugin. // before passing the generated `VstEvents` object to
// the plugin.
std::lock_guard lock(next_buffer_midi_events_mutex); std::lock_guard lock(next_buffer_midi_events_mutex);
next_audio_buffer_midi_events.push_back( next_audio_buffer_midi_events.push_back(
@@ -208,13 +210,14 @@ void Vst2Bridge::handle_dispatch() {
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 {
@@ -224,17 +227,23 @@ void Vst2Bridge::handle_dispatch() {
"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);
} }
}); });
} }
} catch (const boost::system::system_error&) {
// The plugin has cut off communications, so we can shut down this host
// application
}
} }
[[noreturn]] void Vst2Bridge::handle_parameters() { void Vst2Bridge::handle_parameters() {
try {
while (true) { while (true) {
// Both `getParameter` and `setParameter` functions are passed // Both `getParameter` and `setParameter` functions are passed
// through on this socket since they have a lot of overlap. The // through on this socket since they have a lot of overlap. The
@@ -243,7 +252,8 @@ void Vst2Bridge::handle_dispatch() {
auto request = read_object<Parameter>(host_vst_parameters); auto request = read_object<Parameter>(host_vst_parameters);
if (request.value.has_value()) { if (request.value.has_value()) {
// `setParameter` // `setParameter`
plugin->setParameter(plugin, request.index, request.value.value()); plugin->setParameter(plugin, request.index,
request.value.value());
ParameterResult response{std::nullopt}; ParameterResult response{std::nullopt};
write_object(host_vst_parameters, response); write_object(host_vst_parameters, response);
@@ -255,11 +265,16 @@ void Vst2Bridge::handle_dispatch() {
write_object(host_vst_parameters, response); 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_process_replacing() {
std::vector<std::vector<float>> output_buffers(plugin->numOutputs); std::vector<std::vector<float>> output_buffers(plugin->numOutputs);
try {
while (true) { while (true) {
auto request = read_object<AudioBuffers>(host_vst_process_replacing, auto request = read_object<AudioBuffers>(host_vst_process_replacing,
process_buffer); process_buffer);
@@ -281,22 +296,23 @@ void Vst2Bridge::handle_dispatch() {
outputs.push_back(buffer.data()); outputs.push_back(buffer.data());
} }
// Let the plugin process the MIDI events that were received since the // Let the plugin process the MIDI events that were received since
// last buffer, and then clean up those events. This approach should not // the last buffer, and then clean up those events. This approach
// be needed but Kontakt only stores pointers to rather than copies of // should not be needed but Kontakt only stores pointers to rather
// the events. // than copies of the events.
{ {
std::lock_guard lock(next_buffer_midi_events_mutex); std::lock_guard lock(next_buffer_midi_events_mutex);
// Any plugin made in the last fifteen years or so should support // Any plugin made in the last fifteen years or so should
// `processReplacing`. In the off chance it does not we can just // support `processReplacing`. In the off chance it does not we
// emulate this behavior ourselves. // can just emulate this behavior ourselves.
if (plugin->processReplacing != nullptr) { if (plugin->processReplacing != nullptr) {
plugin->processReplacing(plugin, inputs.data(), outputs.data(), plugin->processReplacing(plugin, inputs.data(),
outputs.data(),
request.sample_frames); request.sample_frames);
} else { } else {
// If we zero out this buffer then the behavior is the same as // If we zero out this buffer then the behavior is the same
// `processReplacing`` // as `processReplacing``
for (std::vector<float>& buffer : output_buffers) { for (std::vector<float>& buffer : output_buffers) {
std::fill(buffer.begin(), buffer.end(), 0.0); std::fill(buffer.begin(), buffer.end(), 0.0);
} }
@@ -311,6 +327,10 @@ void Vst2Bridge::handle_dispatch() {
AudioBuffers response{output_buffers, request.sample_frames}; AudioBuffers response{output_buffers, request.sample_frames};
write_object(host_vst_process_replacing, response, process_buffer); 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
}
} }
intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
@@ -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;
} }
+3 -3
View File
@@ -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