mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user