mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 20:10:13 +02:00
Sync VST2 audio thread scheduling priorities
We'll periodically copy the scheduling priorities from the host's audio threads to the Wine plugin host's audio threads. The overhead of doing this is about 1 microsecond on my system, so doing this every cycle really adds up. But getting the Unix epoch time and comparing some timestamps has a neglegible overhead, so this should give you the best of both worlds. Next we'll do the same thing for VST3 plugins. As suggested by @jhernberg
This commit is contained in:
+3
-1
@@ -78,7 +78,9 @@ TODO: Add an updated screenshot with some fancy VST3-only plugins to the readme
|
||||
to realtime priority. This prevents changing the scheduling policy of your
|
||||
host's GUI thread if your host instantiates plugins from its GUI thread like
|
||||
REAPER does.
|
||||
- TODO: Next up is periodically synchronizing audio thread priorities.
|
||||
- The realtime scheduling priorities of all audio threads on the Wine plugin
|
||||
host are now periodically synchronized with those of the host's audio
|
||||
threads.
|
||||
|
||||
- Opening and closing plugin editors is now also no longer done with realtime
|
||||
priority. This should get rid of any latency spikes during those operations,
|
||||
|
||||
@@ -555,6 +555,15 @@ struct AudioBuffers {
|
||||
*/
|
||||
int sample_frames;
|
||||
|
||||
/**
|
||||
* We'll periodically synchronize the realtime priority setting of the
|
||||
* host's audio thread with the Wine plugin host. We'll do this
|
||||
* approximately every ten seconds, as doing this getting and setting
|
||||
* scheduler information has a non trivial amount of overhead (even if it's
|
||||
* only a single microsoecond).
|
||||
*/
|
||||
std::optional<int> new_realtime_priority;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(
|
||||
@@ -572,5 +581,8 @@ struct AudioBuffers {
|
||||
},
|
||||
});
|
||||
s.value4b(sample_frames);
|
||||
|
||||
s.ext(new_realtime_priority, bitsery::ext::StdOptional{},
|
||||
[](S& s, int& priority) { s.value4b(priority); });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ fs::path get_temporary_directory() {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> get_scheduling_priority() {
|
||||
std::optional<int> get_realtime_priority() {
|
||||
sched_param current_params{};
|
||||
if (sched_getparam(0, ¤t_params) == 0 &&
|
||||
current_params.sched_priority > 0) {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ boost::filesystem::path get_temporary_directory();
|
||||
* `SCHED_FIFO`. Returns a nullopt of the calling thread is not under realtime
|
||||
* scheduling.
|
||||
*/
|
||||
std::optional<int> get_scheduling_priority();
|
||||
std::optional<int> get_realtime_priority();
|
||||
|
||||
/**
|
||||
* Set the scheduling policy to `SCHED_FIFO` with priority 5 for this process.
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
#include "../../common/communication/vst2.h"
|
||||
#include "../utils.h"
|
||||
|
||||
/**
|
||||
* The interval in seconds between synchronizing the Wine plugin host's audio
|
||||
* thread scheduling priority with the host's audio thread.
|
||||
*
|
||||
* @relates Vst2Bridge::last_audio_thread_priority_synchronization
|
||||
*/
|
||||
constexpr time_t audio_thread_priority_synchronization_interval = 10;
|
||||
|
||||
intptr_t dispatch_proxy(AEffect*, int, int, intptr_t, void*, float);
|
||||
void process_proxy(AEffect*, float**, float**, int);
|
||||
void process_replacing_proxy(AEffect*, float**, float**, int);
|
||||
@@ -497,7 +505,19 @@ void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
|
||||
input_buffers[channel].begin());
|
||||
}
|
||||
|
||||
const AudioBuffers request{input_buffers, sample_frames};
|
||||
// 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 = std::nullopt;
|
||||
time_t now = std::time(nullptr);
|
||||
if (now > last_audio_thread_priority_synchronization +
|
||||
audio_thread_priority_synchronization_interval) {
|
||||
new_realtime_priority = get_realtime_priority();
|
||||
last_audio_thread_priority_synchronization = now;
|
||||
}
|
||||
|
||||
const AudioBuffers request{.buffers = input_buffers,
|
||||
.sample_frames = sample_frames,
|
||||
.new_realtime_priority = new_realtime_priority};
|
||||
sockets.host_vst_process_replacing.send(request, process_buffer);
|
||||
|
||||
// Write the results back to the `outputs` arrays
|
||||
|
||||
@@ -159,6 +159,13 @@ class Vst2PluginBridge : PluginBridge<Vst2Sockets<std::jthread>> {
|
||||
*/
|
||||
std::vector<uint8_t> process_buffer;
|
||||
|
||||
/**
|
||||
* We'll periodically synchronize the Wine host's audio thread priority with
|
||||
* that of the host. Since the overhead from doing so does add up, we'll
|
||||
* only do this every once in a while.
|
||||
*/
|
||||
time_t last_audio_thread_priority_synchronization = 0;
|
||||
|
||||
/**
|
||||
* The VST host can query a plugin for arbitrary binary data such as
|
||||
* presets. It will expect the plugin to write back a pointer that points to
|
||||
|
||||
@@ -168,6 +168,13 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
||||
|
||||
sockets.host_vst_process_replacing.receive_multi<AudioBuffers>(
|
||||
[&](AudioBuffers request, std::vector<uint8_t>& buffer) {
|
||||
// As suggested by Jack Winter, we'll synchronize this thread's
|
||||
// audio processing priority with that of the host's audio
|
||||
// thread every once in a while
|
||||
if (request.new_realtime_priority) {
|
||||
set_realtime_priority(true, *request.new_realtime_priority);
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -237,8 +244,9 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
||||
}
|
||||
|
||||
AudioBuffers response{
|
||||
output_buffers_single_precision,
|
||||
request.sample_frames};
|
||||
.buffers = output_buffers_single_precision,
|
||||
.sample_frames = request.sample_frames,
|
||||
.new_realtime_priority = std::nullopt};
|
||||
sockets.host_vst_process_replacing.send(response,
|
||||
buffer);
|
||||
},
|
||||
@@ -264,8 +272,9 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
||||
request.sample_frames);
|
||||
|
||||
AudioBuffers response{
|
||||
output_buffers_double_precision,
|
||||
request.sample_frames};
|
||||
.buffers = output_buffers_double_precision,
|
||||
.sample_frames = request.sample_frames,
|
||||
.new_realtime_priority = std::nullopt};
|
||||
sockets.host_vst_process_replacing.send(response,
|
||||
buffer);
|
||||
}},
|
||||
|
||||
Reference in New Issue
Block a user