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
+131 -108
View File
@@ -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;
} }
+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