diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a44176..fd3dca38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). objects on both sides. This greatly reduces the overhead of our VST3 bridging by getting rid of all memory allocations during audio processing. - VST2 audio processing also received the same optimizations. In a few places - yabridge would still reallocate heap data during audio processing. + yabridge would still reallocate heap data during audio processing. We now make + sure to always reuse all buffers used in the audio processing process. - Considerably optimized both VST2 and VST3 audio processing by preventing unnecessary memory operations. As it turns out, the underlying binary serialization library used by yabridge would always reinitialize the type-safe diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 37966ead..49b29453 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -569,57 +569,71 @@ template void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { // To prevent unnecessary bridging overhead, we'll send the time information // together with the buffers because basically every plugin needs this - std::optional current_time_info; const VstTimeInfo* returned_time_info = reinterpret_cast(host_callback_function( &plugin, audioMasterGetTime, 0, 0, nullptr, 0.0)); if (returned_time_info) { - current_time_info = *returned_time_info; + process_input_buffers.current_time_info = *returned_time_info; + } else { + process_input_buffers.current_time_info.reset(); } // Some plugisn also ask for the current process level, so we'll prefetch // that information as well - const int current_process_level = static_cast(host_callback_function( - &plugin, audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0)); + process_input_buffers.current_process_level = + static_cast(host_callback_function( + &plugin, audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0)); // We'll synchronize the scheduling priority of the audio thread on the Wine // plugin host with that of the host's audio thread every once in a while - std::optional new_realtime_priority; const time_t now = time(nullptr); if (now > last_audio_thread_priority_synchronization + audio_thread_priority_synchronization_interval) { - new_realtime_priority = get_realtime_priority(); + process_input_buffers.new_realtime_priority = get_realtime_priority(); last_audio_thread_priority_synchronization = now; + } else { + process_input_buffers.new_realtime_priority.reset(); } - // The inputs and outputs arrays should be `[num_inputs][sample_frames]` and - // `[num_outputs][sample_frames]` floats large respectfully - std::vector> input_buffers(plugin.numInputs, - std::vector(sample_frames)); + // We reuse this audio buffers object both for the request and the response + // to avoid unnecessary allocations. The inputs and outputs arrays should be + // `[num_inputs][sample_frames]` and `[num_outputs][sample_frames]` floats + // large respectfully. + process_input_buffers.sample_frames = sample_frames; + if (!std::holds_alternative>>( + process_input_buffers.buffers)) { + process_input_buffers.buffers.emplace>>(); + } + + std::vector>& input_audio_buffers = + std::get>>(process_input_buffers.buffers); + input_audio_buffers.resize(plugin.numInputs); for (int channel = 0; channel < plugin.numInputs; channel++) { + input_audio_buffers[channel].resize(sample_frames); std::copy_n(inputs[channel], sample_frames, - input_buffers[channel].begin()); + input_audio_buffers[channel].begin()); } - AudioBuffers request{.buffers = input_buffers, - .sample_frames = sample_frames, - .current_time_info = current_time_info, - .current_process_level = current_process_level, - .new_realtime_priority = new_realtime_priority}; - sockets.host_vst_process_replacing.send(request, process_buffer); + // After sending these buffers to the Wine plugin host, we'll receive the + // results back in the same object so we can write back the outputs + sockets.host_vst_process_replacing.send(process_input_buffers, + process_buffer); - // Write the results back to the `outputs` arrays - const auto& response = - sockets.host_vst_process_replacing.receive_single( - request, process_buffer); - const auto& response_buffers = - std::get>>(response.buffers); + // NOTE: We use a different object for this, because otherwise + // mono-to-stereo plugins or any other configuration where the number + // of input channels does not match the number of output channels + // would still result in constant reallocations + sockets.host_vst_process_replacing.receive_single( + process_output_buffers, process_buffer); - assert(response_buffers.size() == static_cast(plugin.numOutputs)); + std::vector>& output_audio_buffers = + std::get>>(process_output_buffers.buffers); + assert(output_audio_buffers.size() == + static_cast(plugin.numOutputs)); for (int channel = 0; channel < plugin.numOutputs; channel++) { if constexpr (replacing) { - std::copy(response_buffers[channel].begin(), - response_buffers[channel].end(), outputs[channel]); + std::copy(output_audio_buffers[channel].begin(), + output_audio_buffers[channel].end(), outputs[channel]); } else { // The old `process()` function expects the plugin to add its output // to the accumulated values in `outputs`. Since no host is ever @@ -629,9 +643,9 @@ void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { // We could use `std::execution::unseq` here but that would require // linking to TBB and since this probably won't ever be used anyways // that's a bit of a waste. - std::transform(response_buffers[channel].begin(), - response_buffers[channel].end(), outputs[channel], - outputs[channel], + std::transform(output_audio_buffers[channel].begin(), + output_audio_buffers[channel].end(), + outputs[channel], outputs[channel], [](const T& new_value, T& current_value) -> T { return new_value + current_value; }); diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 11f29b31..bcc71680 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -153,6 +153,22 @@ class Vst2PluginBridge : PluginBridge> { */ Vst2Logger logger; + /** + * The object we'll serialize the audio buffers and any auxiliary + * information into when processing audio. We need to reuse this object to + * avoid reallocations since it contains pointers to heap data. + */ + AudioBuffers process_input_buffers; + + /** + * The object we'll serialize the response into after the plugin has + * finished processing audio. We cannot reuse `process_input_buffers` + * because for instance a mono-to-stereo plugin would cause us to constantly + * reallocate the sample buffer for the last channel. We need to reuse this + * object to avoid reallocations since it contains pointers to heap data. + */ + AudioBuffers process_output_buffers; + /** * A scratch buffer for sending and receiving binary data during the * `process()`, `processReplacing()` and `processDoubleReplacing()` calls.