Reuse VST2 audio processing buffers on plugin side

This commit is contained in:
Robbert van der Helm
2021-05-23 15:25:36 +02:00
parent 4e81c1c2b3
commit 8429919dfe
3 changed files with 61 additions and 30 deletions
+2 -1
View File
@@ -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
+43 -29
View File
@@ -569,57 +569,71 @@ template <typename T, bool replacing>
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<VstTimeInfo> current_time_info;
const VstTimeInfo* returned_time_info =
reinterpret_cast<const VstTimeInfo*>(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<int>(host_callback_function(
&plugin, audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0));
process_input_buffers.current_process_level =
static_cast<int>(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<int> 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<std::vector<T>> input_buffers(plugin.numInputs,
std::vector<T>(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<std::vector<std::vector<T>>>(
process_input_buffers.buffers)) {
process_input_buffers.buffers.emplace<std::vector<std::vector<T>>>();
}
std::vector<std::vector<T>>& input_audio_buffers =
std::get<std::vector<std::vector<T>>>(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<AudioBuffers>(
request, process_buffer);
const auto& response_buffers =
std::get<std::vector<std::vector<T>>>(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<AudioBuffers>(
process_output_buffers, process_buffer);
assert(response_buffers.size() == static_cast<size_t>(plugin.numOutputs));
std::vector<std::vector<T>>& output_audio_buffers =
std::get<std::vector<std::vector<T>>>(process_output_buffers.buffers);
assert(output_audio_buffers.size() ==
static_cast<size_t>(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;
});
+16
View File
@@ -153,6 +153,22 @@ class Vst2PluginBridge : PluginBridge<Vst2Sockets<std::jthread>> {
*/
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.