mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 12:10:09 +02:00
Extend the lifetime of MIDI events, fixing Kontakt
This commit is contained in:
@@ -20,7 +20,6 @@ There are a few things that should be done before releasing this, including:
|
||||
that do this. Not sure if this is fixable in yabridge.
|
||||
- Serum crashed and audio engine froze while browsing through Serum presets in
|
||||
the browser?
|
||||
- No midi input in Kontakt.
|
||||
- Polish GUIs even further. There are some todos left in
|
||||
`src/wine-host/editor.{h,cpp}`.
|
||||
- Add missing details if any to the architecture section.
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user