Extend the lifetime of MIDI events, fixing Kontakt

This commit is contained in:
Robbert van der Helm
2020-04-26 18:17:09 +02:00
parent aeac8e87fa
commit 85afd4107e
3 changed files with 76 additions and 16 deletions
+62 -15
View File
@@ -16,6 +16,8 @@
#include "plugin-bridge.h"
#include <iostream>
#include <boost/filesystem.hpp>
#include "../common/communication.h"
@@ -157,8 +159,43 @@ void PluginBridge::handle_dispatch() {
[[noreturn]] void PluginBridge::handle_dispatch_midi_events() {
while (true) {
passthrough_event(host_vst_dispatch_midi_events, std::nullopt, plugin,
plugin->dispatcher);
// TODO: Refactor `passthrough_event()` to factor out the data
// conversion specifically for this case so we don't have to do
// this `DynamicVstEvents -> VstEvents -> void* ->
// DynamicVstEvents -> VstEvents -> void*` conversion dance
passthrough_event(
host_vst_dispatch_midi_events, std::nullopt, plugin,
[&](AEffect* plugin, int opcode, int index, intptr_t value,
void* data, float option) {
if (BOOST_LIKELY(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. This
// does mean that we have to reconstruct the
// `DynamicVstEvents` object first.
// HACK: Is there a cleaner way to do this, or a way to
// avoid having to store temporary copies of this?
DynamicVstEvents* events;
{
std::lock_guard lock(next_buffer_midi_events_mutex);
events = &next_audio_buffer_midi_events.emplace_back(
*static_cast<const VstEvents*>(data));
}
return plugin->dispatcher(plugin, opcode, index, value,
&events->as_c_events(), option);
} else {
std::cerr << "[Warning] Received non-midi "
"event on midi processing thread"
<< std::endl;
return dispatch_wrapper(plugin, opcode, index, value, data,
option);
}
});
}
}
@@ -210,21 +247,31 @@ void PluginBridge::handle_dispatch() {
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 != 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);
// 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);
}
plugin->process(plugin, inputs.data(), outputs.data(),
request.sample_frames);
next_audio_buffer_midi_events.clear();
}
AudioBuffers response{output_buffers, request.sample_frames};
+14
View File
@@ -168,6 +168,20 @@ class PluginBridge {
*/
std::vector<uint8_t> process_buffer;
/**
* The MIDI events that have been received **and processed** since the last
* call to `processReplacing()`. 99% of plugins make a copy of the MIDI
* events they receive but some plugins such as Kontakt only store pointers
* to these events, which means that the actual `VstEvent` objects must live
* at least until the next audio buffer gets processed.
*/
std::vector<DynamicVstEvents> next_audio_buffer_midi_events;
/**
* Mutex for locking the above event queue, since recieving and processing
* now happens in two different threads.
*/
std::mutex next_buffer_midi_events_mutex;
/**
* The plugin editor window. Allows embedding the plugin's editor into a
* Wine window, and embedding that Wine window into a window provided by the