diff --git a/README.md b/README.md index 1ef5ae76..1911c947 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index 21c42828..3e953a44 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -16,6 +16,8 @@ #include "plugin-bridge.h" +#include + #include #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(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& 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& 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}; diff --git a/src/wine-host/plugin-bridge.h b/src/wine-host/plugin-bridge.h index bf2cc78f..784983ae 100644 --- a/src/wine-host/plugin-bridge.h +++ b/src/wine-host/plugin-bridge.h @@ -168,6 +168,20 @@ class PluginBridge { */ std::vector 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 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