mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
Reuse VST2 audio processing buffers on plugin side
This commit is contained in:
+2
-1
@@ -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
|
objects on both sides. This greatly reduces the overhead of our VST3 bridging
|
||||||
by getting rid of all memory allocations during audio processing.
|
by getting rid of all memory allocations during audio processing.
|
||||||
- VST2 audio processing also received the same optimizations. In a few places
|
- 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
|
- Considerably optimized both VST2 and VST3 audio processing by preventing
|
||||||
unnecessary memory operations. As it turns out, the underlying binary
|
unnecessary memory operations. As it turns out, the underlying binary
|
||||||
serialization library used by yabridge would always reinitialize the type-safe
|
serialization library used by yabridge would always reinitialize the type-safe
|
||||||
|
|||||||
+43
-29
@@ -569,57 +569,71 @@ template <typename T, bool replacing>
|
|||||||
void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
|
void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
|
||||||
// To prevent unnecessary bridging overhead, we'll send the time information
|
// To prevent unnecessary bridging overhead, we'll send the time information
|
||||||
// together with the buffers because basically every plugin needs this
|
// together with the buffers because basically every plugin needs this
|
||||||
std::optional<VstTimeInfo> current_time_info;
|
|
||||||
const VstTimeInfo* returned_time_info =
|
const VstTimeInfo* returned_time_info =
|
||||||
reinterpret_cast<const VstTimeInfo*>(host_callback_function(
|
reinterpret_cast<const VstTimeInfo*>(host_callback_function(
|
||||||
&plugin, audioMasterGetTime, 0, 0, nullptr, 0.0));
|
&plugin, audioMasterGetTime, 0, 0, nullptr, 0.0));
|
||||||
if (returned_time_info) {
|
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
|
// Some plugisn also ask for the current process level, so we'll prefetch
|
||||||
// that information as well
|
// that information as well
|
||||||
const int current_process_level = static_cast<int>(host_callback_function(
|
process_input_buffers.current_process_level =
|
||||||
&plugin, audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0));
|
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
|
// 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
|
// 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);
|
const time_t now = time(nullptr);
|
||||||
if (now > last_audio_thread_priority_synchronization +
|
if (now > last_audio_thread_priority_synchronization +
|
||||||
audio_thread_priority_synchronization_interval) {
|
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;
|
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
|
// We reuse this audio buffers object both for the request and the response
|
||||||
// `[num_outputs][sample_frames]` floats large respectfully
|
// to avoid unnecessary allocations. The inputs and outputs arrays should be
|
||||||
std::vector<std::vector<T>> input_buffers(plugin.numInputs,
|
// `[num_inputs][sample_frames]` and `[num_outputs][sample_frames]` floats
|
||||||
std::vector<T>(sample_frames));
|
// 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++) {
|
for (int channel = 0; channel < plugin.numInputs; channel++) {
|
||||||
|
input_audio_buffers[channel].resize(sample_frames);
|
||||||
std::copy_n(inputs[channel], sample_frames,
|
std::copy_n(inputs[channel], sample_frames,
|
||||||
input_buffers[channel].begin());
|
input_audio_buffers[channel].begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioBuffers request{.buffers = input_buffers,
|
// After sending these buffers to the Wine plugin host, we'll receive the
|
||||||
.sample_frames = sample_frames,
|
// results back in the same object so we can write back the outputs
|
||||||
.current_time_info = current_time_info,
|
sockets.host_vst_process_replacing.send(process_input_buffers,
|
||||||
.current_process_level = current_process_level,
|
process_buffer);
|
||||||
.new_realtime_priority = new_realtime_priority};
|
|
||||||
sockets.host_vst_process_replacing.send(request, process_buffer);
|
|
||||||
|
|
||||||
// Write the results back to the `outputs` arrays
|
// NOTE: We use a different object for this, because otherwise
|
||||||
const auto& response =
|
// mono-to-stereo plugins or any other configuration where the number
|
||||||
sockets.host_vst_process_replacing.receive_single<AudioBuffers>(
|
// of input channels does not match the number of output channels
|
||||||
request, process_buffer);
|
// would still result in constant reallocations
|
||||||
const auto& response_buffers =
|
sockets.host_vst_process_replacing.receive_single<AudioBuffers>(
|
||||||
std::get<std::vector<std::vector<T>>>(response.buffers);
|
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++) {
|
for (int channel = 0; channel < plugin.numOutputs; channel++) {
|
||||||
if constexpr (replacing) {
|
if constexpr (replacing) {
|
||||||
std::copy(response_buffers[channel].begin(),
|
std::copy(output_audio_buffers[channel].begin(),
|
||||||
response_buffers[channel].end(), outputs[channel]);
|
output_audio_buffers[channel].end(), outputs[channel]);
|
||||||
} else {
|
} else {
|
||||||
// The old `process()` function expects the plugin to add its output
|
// The old `process()` function expects the plugin to add its output
|
||||||
// to the accumulated values in `outputs`. Since no host is ever
|
// 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
|
// We could use `std::execution::unseq` here but that would require
|
||||||
// linking to TBB and since this probably won't ever be used anyways
|
// linking to TBB and since this probably won't ever be used anyways
|
||||||
// that's a bit of a waste.
|
// that's a bit of a waste.
|
||||||
std::transform(response_buffers[channel].begin(),
|
std::transform(output_audio_buffers[channel].begin(),
|
||||||
response_buffers[channel].end(), outputs[channel],
|
output_audio_buffers[channel].end(),
|
||||||
outputs[channel],
|
outputs[channel], outputs[channel],
|
||||||
[](const T& new_value, T& current_value) -> T {
|
[](const T& new_value, T& current_value) -> T {
|
||||||
return new_value + current_value;
|
return new_value + current_value;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -153,6 +153,22 @@ class Vst2PluginBridge : PluginBridge<Vst2Sockets<std::jthread>> {
|
|||||||
*/
|
*/
|
||||||
Vst2Logger logger;
|
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
|
* A scratch buffer for sending and receiving binary data during the
|
||||||
* `process()`, `processReplacing()` and `processDoubleReplacing()` calls.
|
* `process()`, `processReplacing()` and `processDoubleReplacing()` calls.
|
||||||
|
|||||||
Reference in New Issue
Block a user