Allow changing the event loop tick rate

This also changes the refresh rate for most plugins. You can now lower
this setting if your computer is struggling to keep up with rendering a
certain heavy plugin.
This commit is contained in:
Robbert van der Helm
2021-01-11 23:38:21 +01:00
parent b86df213fb
commit bf3a4e7296
10 changed files with 84 additions and 13 deletions
+6
View File
@@ -30,6 +30,12 @@ TODO: Add an updated screenshot with some fancy VST3-only plugins to the readme
Wine's XEmbed implementation instead of yabridge's normal window embedding
method. Some plugins have will have redrawing issues when using XEmbed or the
editor might not show up at all, so your mileage may very much vary.
- Added a frame rate
[option](https://github.com/robbert-vdh/yabridge#compatibility-options) to
change the rate at which events are being handled. This usually also controls
the refresh rate of a plugin's editor GUI. The default 60 updates per second
may be too high if your computer's cannot keep up, or if you're using a host
that never closes the editor such as Ardour.
### Changed
+1
View File
@@ -282,6 +282,7 @@ plugin._
| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. |
| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. |
| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't always work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ |
| `frame_rate` | `<number>` | The rate at which Win32 events are being handled and usually also the refresh rate of a plugin's editor GUI. When using plugin groups all plugins share the same event handling loop, so in those the last loaded plugin will set the refresh rate. Defaults to `60`. _This option is only available on the master branch._ |
These options are workarounds for issues mentioned in the [known
issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts
+11
View File
@@ -96,6 +96,12 @@ Configuration::Configuration(const fs::path& config_path,
} else {
invalid_options.push_back(key);
}
} else if (key == "frame_rate") {
if (const auto parsed_value = value.as_floating_point()) {
frame_rate = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "group") {
if (const auto parsed_value = value.as_string()) {
group = parsed_value->get();
@@ -110,3 +116,8 @@ Configuration::Configuration(const fs::path& config_path,
break;
}
}
std::chrono::steady_clock::duration Configuration::event_loop_interval() const {
return std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::milliseconds(1000) / frame_rate.value_or(60.0));
}
+22
View File
@@ -22,6 +22,7 @@
#include <boost/filesystem.hpp>
#include <bitsery/ext/std_optional.h>
#include <chrono>
#include <optional>
#include "bitsery/ext/boost-path.h"
@@ -109,6 +110,18 @@ class Configuration {
*/
bool editor_xembed = false;
/**
* The number of times per second we'll handle the event loop. In most
* plugins this also controls the plugin editor GUI's refresh rate.
*
* This defaults to 60 fps, but we'll store it in an optional as we only
* want to show it in the startup message if this setting has explicitly
* been set.
*
* @relates event_loop_interval
*/
std::optional<float> frame_rate;
/**
* The name of the plugin group that should be used for the plugin this
* configuration object was created for. If not set, then the plugin should
@@ -139,13 +152,22 @@ class Configuration {
*/
std::vector<std::string> unknown_options;
/**
* The delay in milliseconds between calls to the event loop and to
* `effEditIdle` for VST2 plugins. This is based on `frame_rate`.
*/
std::chrono::steady_clock::duration event_loop_interval() const;
template <typename S>
void serialize(S& s) {
s.value1b(cache_time_info);
s.value1b(editor_double_embed);
s.value1b(editor_xembed);
s.ext(frame_rate, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.value4b(v); });
s.ext(group, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.text1b(v, 4096); });
s.ext(matched_file, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.ext(v, bitsery::ext::BoostPath{}); });
s.ext(matched_pattern, bitsery::ext::StdOptional(),
+8
View File
@@ -16,6 +16,8 @@
#pragma once
#include <iomanip>
// Generated inside of the build directory
#include <src/common/config/config.h>
#include <src/common/config/version.h>
@@ -163,6 +165,12 @@ class PluginBridge {
if (config.editor_xembed) {
other_options.push_back("editor: XEmbed");
}
if (config.frame_rate) {
std::ostringstream option;
option << "frame rate: " << std::setprecision(2)
<< *config.frame_rate << " fps";
other_options.push_back(option.str());
}
if (!other_options.empty()) {
init_msg << join_quoted_strings(other_options) << std::endl;
} else {
+3
View File
@@ -130,6 +130,9 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
// configuration as a response
config = sockets.host_vst_control.receive_single<Configuration>();
// Allow this plugin to configure the main context's tick rate
main_context.update_timer_interval(config.event_loop_interval());
parameters_handler = Win32Thread([&]() {
sockets.host_vst_parameters.receive_multi<Parameter>(
[&](Parameter request, std::vector<uint8_t>& buffer) {
+3
View File
@@ -75,6 +75,9 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context,
// process
config = sockets.vst_host_callback.send_message(WantsConfiguration{},
std::nullopt);
// Allow this plugin to configure the main context's tick rate
main_context.update_timer_interval(config.event_loop_interval());
}
void Vst3Bridge::run() {
+1 -1
View File
@@ -145,7 +145,7 @@ Editor::Editor(const Configuration& config,
win32_handle.get(),
idle_timer_id,
std::chrono::duration_cast<std::chrono::milliseconds>(
event_loop_interval)
config.event_loop_interval())
.count())
: Win32Timer()),
idle_timer_proc(std::move(timer_proc)),
+5
View File
@@ -26,6 +26,11 @@ void MainContext::stop() {
context.stop();
}
void MainContext::update_timer_interval(
std::chrono::steady_clock::duration new_interval) {
timer_interval = new_interval;
}
uint32_t WINAPI
win32_thread_trampoline(fu2::unique_function<void()>* entry_point) {
(*entry_point)();
+24 -12
View File
@@ -34,14 +34,6 @@
#include "../common/utils.h"
/**
* The delay between calls to the event loop so we can keep a nice 60 fps. We
* could bump this up to the monitor's refresh rate, but I'm afraid that it will
* start to noticeably take up resources in plugin groups.
*/
constexpr std::chrono::duration event_loop_interval =
std::chrono::milliseconds(1000) / 60;
/**
* A wrapper around `boost::asio::io_context()` to serve as the application's
* main IO context. A single instance is shared for all plugins in a plugin
@@ -66,6 +58,14 @@ class MainContext {
*/
void stop();
/**
* Set a new timer interval. We'll do this whenever a new plugin loads,
* because we can't know in advance what the plugin's frame rate option is
* set to.
*/
void update_timer_interval(
std::chrono::steady_clock::duration new_interval);
/**
* Asynchronously execute a function inside of this main IO context and
* return the results as a future. This is used to make sure that operations
@@ -91,7 +91,9 @@ class MainContext {
}
/**
* Start a timer to handle events every `event_loop_interval` milliseconds.
* Start a timer to handle events on a user configurable interval. The
* interval is controllable through the `frame_rate` option and defaults to
* 60 updates per second.
*
* @param handler The function that should be executed in the IO context
* when the timer ticks. This should be a function that handles both the
@@ -101,9 +103,9 @@ class MainContext {
void async_handle_events(F handler) {
// Try to keep a steady framerate, but add in delays to let other events
// get handled if the GUI message handling somehow takes very long.
events_timer.expires_at(std::max(
events_timer.expiry() + event_loop_interval,
std::chrono::steady_clock::now() + std::chrono::milliseconds(5)));
events_timer.expires_at(
std::max(events_timer.expiry() + timer_interval,
std::chrono::steady_clock::now() + timer_interval / 4));
events_timer.async_wait(
[&, handler](const boost::system::error_code& error) {
if (error.failed()) {
@@ -137,6 +139,16 @@ class MainContext {
* The timer used to periodically handle X11 events and Win32 messages.
*/
boost::asio::steady_timer events_timer;
/**
* The time between timer ticks in `async_handle_events`. This gets
* initialized at 60 ticks per second, and when a plugin load we'll update
* this value based on the plugin's `frame_rate` option.
*
* @see update_timer_interval
*/
std::chrono::steady_clock::duration timer_interval =
std::chrono::milliseconds(1000) / 60;
};
/**