mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
Cache VST3 parameter information
This is in some cases needed to get decent performance in REAPER, as REAPER seems to query this information (which cannot change without the plugin requesting a restart) four times per second.
This commit is contained in:
@@ -543,11 +543,6 @@ include:
|
|||||||
section for instructions on how to set this up. This is not necessary for VST3
|
section for instructions on how to set this up. This is not necessary for VST3
|
||||||
plugins, as multiple instances of those plugins will always be hosted in a
|
plugins, as multiple instances of those plugins will always be hosted in a
|
||||||
single process by design.
|
single process by design.
|
||||||
- When using **REAPER**, make sure that REAPER's plugin bridges are disabled.
|
|
||||||
When this option is enabled REAPER will constantly query all of a plugin's
|
|
||||||
parameters four times per second. This can amount to multiple tens of
|
|
||||||
thousands of extra, unnecessary function calls per second, which increases
|
|
||||||
yabridge's CPU usage considerably.
|
|
||||||
- **Drag-and-drop** from applications running under Wine to X11 does not yet
|
- **Drag-and-drop** from applications running under Wine to X11 does not yet
|
||||||
work, so you won't be able to drag samples and MIDI files from a plugin to the
|
work, so you won't be able to drag samples and MIDI files from a plugin to the
|
||||||
host. At least, not directly. Because Windows applications have to create
|
host. At least, not directly. Because Windows applications have to create
|
||||||
|
|||||||
@@ -1411,13 +1411,17 @@ void Vst3Logger::log_response(
|
|||||||
|
|
||||||
void Vst3Logger::log_response(
|
void Vst3Logger::log_response(
|
||||||
bool is_host_vst,
|
bool is_host_vst,
|
||||||
const YaEditController::GetParameterInfoResponse& response) {
|
const YaEditController::GetParameterInfoResponse& response,
|
||||||
|
bool from_cache) {
|
||||||
log_response_base(is_host_vst, [&](auto& message) {
|
log_response_base(is_host_vst, [&](auto& message) {
|
||||||
message << response.result.string();
|
message << response.result.string();
|
||||||
if (response.result == Steinberg::kResultOk) {
|
if (response.result == Steinberg::kResultOk) {
|
||||||
std::string param_title =
|
std::string param_title =
|
||||||
VST3::StringConvert::convert(response.updated_info.title);
|
VST3::StringConvert::convert(response.updated_info.title);
|
||||||
message << ", <ParameterInfo for '" << param_title << "'>";
|
message << ", <ParameterInfo for '" << param_title << "'>";
|
||||||
|
if (from_cache) {
|
||||||
|
message << " (from cache)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,8 @@ class Vst3Logger {
|
|||||||
void log_response(bool is_host_vst,
|
void log_response(bool is_host_vst,
|
||||||
const Vst3PluginProxy::GetStateResponse&);
|
const Vst3PluginProxy::GetStateResponse&);
|
||||||
void log_response(bool is_host_vst,
|
void log_response(bool is_host_vst,
|
||||||
const YaEditController::GetParameterInfoResponse&);
|
const YaEditController::GetParameterInfoResponse&,
|
||||||
|
bool from_cache = false);
|
||||||
void log_response(bool is_host_vst,
|
void log_response(bool is_host_vst,
|
||||||
const YaEditController::GetParamStringByValueResponse&);
|
const YaEditController::GetParamStringByValueResponse&);
|
||||||
void log_response(bool is_host_vst,
|
void log_response(bool is_host_vst,
|
||||||
|
|||||||
@@ -63,10 +63,9 @@ bool Vst3PluginProxyImpl::unregister_context_menu(size_t context_menu_id) {
|
|||||||
return context_menus.erase(context_menu_id);
|
return context_menus.erase(context_menu_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vst3PluginProxyImpl::clear_bus_cache() {
|
void Vst3PluginProxyImpl::clear_caches() {
|
||||||
if (processing_bus_cache) {
|
clear_bus_cache();
|
||||||
processing_bus_cache.emplace();
|
clear_parameter_cache();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tresult PLUGIN_API Vst3PluginProxyImpl::setAudioPresentationLatencySamples(
|
tresult PLUGIN_API Vst3PluginProxyImpl::setAudioPresentationLatencySamples(
|
||||||
@@ -231,8 +230,8 @@ Vst3PluginProxyImpl::getBusCount(Steinberg::Vst::MediaType type,
|
|||||||
it != processing_bus_cache->bus_count.end()) {
|
it != processing_bus_cache->bus_count.end()) {
|
||||||
const bool log_response = bridge.logger.log_request(true, request);
|
const bool log_response = bridge.logger.log_request(true, request);
|
||||||
if (log_response) {
|
if (log_response) {
|
||||||
bridge.logger.log_response(true, PrimitiveWrapper(it->second),
|
bridge.logger.log_response(
|
||||||
true);
|
true, YaComponent::GetBusCount::Response(it->second), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second;
|
return it->second;
|
||||||
@@ -268,11 +267,11 @@ Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type,
|
|||||||
it != processing_bus_cache->bus_info.end()) {
|
it != processing_bus_cache->bus_info.end()) {
|
||||||
const bool log_response = bridge.logger.log_request(true, request);
|
const bool log_response = bridge.logger.log_request(true, request);
|
||||||
if (log_response) {
|
if (log_response) {
|
||||||
bridge.logger.log_response(
|
bridge.logger.log_response(true,
|
||||||
true,
|
YaComponent::GetBusInfo::Response{
|
||||||
GetBusInfoResponse{.result = Steinberg::kResultOk,
|
.result = Steinberg::kResultOk,
|
||||||
.updated_bus = it->second},
|
.updated_bus = it->second},
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bus = it->second;
|
bus = it->second;
|
||||||
@@ -436,19 +435,59 @@ Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
||||||
return bridge.send_message(
|
const auto request =
|
||||||
YaEditController::GetParameterCount{.instance_id = instance_id()});
|
YaEditController::GetParameterCount{.instance_id = instance_id()};
|
||||||
|
|
||||||
|
// We'll cache this information to work around an issue in REAPER, see
|
||||||
|
// `parameter_info_cache`
|
||||||
|
if (parameter_info_cache.parameter_count) {
|
||||||
|
const bool log_response = bridge.logger.log_request(true, request);
|
||||||
|
if (log_response) {
|
||||||
|
bridge.logger.log_response(
|
||||||
|
true,
|
||||||
|
YaEditController::GetParameterCount::Response(
|
||||||
|
*parameter_info_cache.parameter_count),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *parameter_info_cache.parameter_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 result = bridge.send_message(request);
|
||||||
|
|
||||||
|
parameter_info_cache.parameter_count = result;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo(
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo(
|
||||||
int32 paramIndex,
|
int32 paramIndex,
|
||||||
Steinberg::Vst::ParameterInfo& info /*out*/) {
|
Steinberg::Vst::ParameterInfo& info /*out*/) {
|
||||||
const GetParameterInfoResponse response = bridge.send_message(
|
const auto request = YaEditController::GetParameterInfo{
|
||||||
YaEditController::GetParameterInfo{.instance_id = instance_id(),
|
.instance_id = instance_id(), .param_index = paramIndex, .info = info};
|
||||||
.param_index = paramIndex,
|
|
||||||
.info = info});
|
// We'll cache this information to work around an issue in REAPER, see
|
||||||
|
// `parameter_info_cache`
|
||||||
|
if (auto it = parameter_info_cache.parameter_info.find(paramIndex);
|
||||||
|
it != parameter_info_cache.parameter_info.end()) {
|
||||||
|
const bool log_response = bridge.logger.log_request(true, request);
|
||||||
|
if (log_response) {
|
||||||
|
bridge.logger.log_response(
|
||||||
|
true,
|
||||||
|
YaEditController::GetParameterInfo::Response{
|
||||||
|
.result = Steinberg::kResultOk, .updated_info = it->second},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
info = it->second;
|
||||||
|
|
||||||
|
return Steinberg::kResultOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetParameterInfoResponse response = bridge.send_message(request);
|
||||||
|
|
||||||
info = response.updated_info;
|
info = response.updated_info;
|
||||||
|
parameter_info_cache.parameter_info[paramIndex] = response.updated_info;
|
||||||
|
|
||||||
return response.result;
|
return response.result;
|
||||||
}
|
}
|
||||||
@@ -1117,3 +1156,13 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getXmlRepresentationStream(
|
|||||||
return Steinberg::kInvalidArgument;
|
return Steinberg::kInvalidArgument;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Vst3PluginProxyImpl::clear_bus_cache() {
|
||||||
|
if (processing_bus_cache) {
|
||||||
|
processing_bus_cache.emplace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Vst3PluginProxyImpl::clear_parameter_cache() {
|
||||||
|
parameter_info_cache = ParameterInfoCache{};
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,24 @@
|
|||||||
#include "../vst3.h"
|
#include "../vst3.h"
|
||||||
#include "plug-view-proxy.h"
|
#include "plug-view-proxy.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here we pass though all function calls made by the host to the Windows VST3
|
||||||
|
* plugin. We sadly had to deviate from yabridge's 'one-to-one passthrough'
|
||||||
|
* philosphy in two places:
|
||||||
|
*
|
||||||
|
* 1. REAPER requests the plugin's input and output bus count and information
|
||||||
|
* every processing cycle, even though this information cannot change during
|
||||||
|
* processing. With plugins with many outputs this can lead a lot of extra
|
||||||
|
* back and forth before the plugin gets to process audio, which thus leads
|
||||||
|
* to very inflated DSP usage.
|
||||||
|
* 2. REAPER in some situations (because it doesn't always seem to happen)
|
||||||
|
* fetches the plugin's parameter information exactly four times per second.
|
||||||
|
* When plugins have thousands of parameters, this unnecessarily uses up a
|
||||||
|
* lot of CPU time.
|
||||||
|
*
|
||||||
|
* Both of these caches were necessary to get decent performance in REAPER, and
|
||||||
|
* they should be removed as soon as REAPER no longer needs this.
|
||||||
|
*/
|
||||||
class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
||||||
public:
|
public:
|
||||||
Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
|
Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
|
||||||
@@ -55,23 +73,19 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|||||||
bool unregister_context_menu(size_t context_menu_id);
|
bool unregister_context_menu(size_t context_menu_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the bus count and information cache. We need this cache for REAPER
|
* Clear the bus and parameter caches. We'll call this on
|
||||||
* as it makes `num_inputs + num_outputs + 2` function calls to retrieve
|
* `IComponentHandler::restartComponent`. These caching layers are necessary
|
||||||
* this information every single processing cycle. For plugins with a lot of
|
* to get decent performance in REAPER as REAPER repeatedly calls these
|
||||||
* outputs this really adds up. According to the VST3 workflow diagrams bus
|
* functions many times per second, even though their values will never
|
||||||
* information cannot change anymore once `IAudioProcessor::setProcessing()`
|
* change.
|
||||||
* has been called, but REAPER doesn't quite follow the spec here and it
|
|
||||||
* will set bus arrangements and activate the plugin only after it's called
|
|
||||||
* `IAudioProcessor::setProcessing()`. Because of that we'll have to
|
|
||||||
* manually flush this cache when the stores information potentially becomes
|
|
||||||
* invalid.
|
|
||||||
*
|
*
|
||||||
* HACK: Once REAPER stops calling these functions, we should remove this
|
* HACK: Once REAPER stops calling these functions, we should remove this
|
||||||
* caching layer ASAP as it can only cause issues
|
* caching layer ASAP as it can only cause issues
|
||||||
*
|
*
|
||||||
* @see processing_bus_cache
|
* @see clear_bus_cache
|
||||||
|
* @see clear_parameter_cache
|
||||||
*/
|
*/
|
||||||
void clear_bus_cache();
|
void clear_caches();
|
||||||
|
|
||||||
// From `IAudioPresentationLatency`
|
// From `IAudioPresentationLatency`
|
||||||
tresult PLUGIN_API
|
tresult PLUGIN_API
|
||||||
@@ -383,6 +397,33 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|||||||
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2;
|
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Clear the bus count and information cache. We need this cache for REAPER
|
||||||
|
* as it makes `num_inputs + num_outputs + 2` function calls to retrieve
|
||||||
|
* this information every single processing cycle. For plugins with a lot of
|
||||||
|
* outputs this really adds up. According to the VST3 workflow diagrams bus
|
||||||
|
* information cannot change anymore once `IAudioProcessor::setProcessing()`
|
||||||
|
* has been called, but REAPER doesn't quite follow the spec here and it
|
||||||
|
* will set bus arrangements and activate the plugin only after it's called
|
||||||
|
* `IAudioProcessor::setProcessing()`. Because of that we'll have to
|
||||||
|
* manually flush this cache when the stores information potentially becomes
|
||||||
|
* invalid.
|
||||||
|
*
|
||||||
|
* @see processing_bus_cache
|
||||||
|
*/
|
||||||
|
void clear_bus_cache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the parameter information cache. Normally hosts only have to
|
||||||
|
* request this once, since the information never changes. REAPER however in
|
||||||
|
* some situations asks for this information four times per second. This
|
||||||
|
* extra back and forth can really add up once plugins start having
|
||||||
|
* thousands of parameters.
|
||||||
|
*
|
||||||
|
* @see parameter_info_cache
|
||||||
|
*/
|
||||||
|
void clear_parameter_cache();
|
||||||
|
|
||||||
Vst3PluginBridge& bridge;
|
Vst3PluginBridge& bridge;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -431,15 +472,45 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HACK: To work around some behaviour in REAPER where it will repeatedly
|
* To work around some behaviour in REAPER where it will repeatedly query
|
||||||
* query the same bus information for bus during every processing
|
* the same bus information for bus during every processing cycle, we'll
|
||||||
* cycle, we'll cache this information during processing. Otherwise
|
* cache this information during processing. Otherwise this will cause
|
||||||
* this will cause `input_busses + output_busses + 2` extra
|
* `input_busses + output_busses + 2` extra unnecessary back and forths for
|
||||||
* unnecessary back and forths for every processing cycle. This can
|
* every processing cycle. This can really add up for plugins with 16, or
|
||||||
* really add up for plugins with 16, or even 32 outputs.
|
* even 32 outputs.
|
||||||
*
|
*
|
||||||
* Since this information cannot change during processing, this will not
|
* Since this information cannot change during processing, this will not
|
||||||
* contain a value while the plugin is not processing audio.
|
* contain a value while the plugin is not processing audio.
|
||||||
|
*
|
||||||
|
* HACK: This is only necessary because REAPER requests this information
|
||||||
|
* once per procession cycle. We can get rid of this once it no longer
|
||||||
|
* does that.
|
||||||
*/
|
*/
|
||||||
std::optional<BusInfoCache> processing_bus_cache;
|
std::optional<BusInfoCache> processing_bus_cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache for `IEditController::getParameterCount()` and
|
||||||
|
* `IEditController::getParameterInfo()` to work around an implementation
|
||||||
|
* issue in REAPER. In some situations REAPER will query this information
|
||||||
|
* four times a second, and all of this back and forth communication really
|
||||||
|
* adds up when a plugin starts having thousands of parameters.
|
||||||
|
*
|
||||||
|
* @see parameter_cache
|
||||||
|
*/
|
||||||
|
struct ParameterInfoCache {
|
||||||
|
std::optional<int32> parameter_count;
|
||||||
|
std::map<int32, Steinberg::Vst::ParameterInfo> parameter_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache for the parameter count and infos. This is necessary because in
|
||||||
|
* some situations REAPER queries this information four times per second
|
||||||
|
* even though it cannot change. This happens when using the plugin bridges,
|
||||||
|
* but it can also happen in some other cases so I'm not quite sure what the
|
||||||
|
* trigger is.
|
||||||
|
*
|
||||||
|
* HACK: This is only necessary because REAPER sometimes requests this
|
||||||
|
* information four times per second.
|
||||||
|
*/
|
||||||
|
ParameterInfoCache parameter_info_cache;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ Vst3PluginBridge::Vst3PluginBridge()
|
|||||||
Vst3PluginProxyImpl& proxy_object =
|
Vst3PluginProxyImpl& proxy_object =
|
||||||
plugin_proxies.at(request.owner_instance_id).get();
|
plugin_proxies.at(request.owner_instance_id).get();
|
||||||
|
|
||||||
// To err on the safe side, we'll just always clear out bus
|
// To err on the safe side, we'll just always clear out all
|
||||||
// info cache whenever a plugin requests a restart
|
// of our caches whenever a plugin requests a restart
|
||||||
proxy_object.clear_bus_cache();
|
proxy_object.clear_caches();
|
||||||
|
|
||||||
return proxy_object.component_handler->restartComponent(
|
return proxy_object.component_handler->restartComponent(
|
||||||
request.flags);
|
request.flags);
|
||||||
|
|||||||
@@ -324,7 +324,9 @@ void Editor::handle_x11_events() const {
|
|||||||
xcb_generic_event_t* generic_event;
|
xcb_generic_event_t* generic_event;
|
||||||
while ((generic_event = xcb_poll_for_event(x11_connection.get())) !=
|
while ((generic_event = xcb_poll_for_event(x11_connection.get())) !=
|
||||||
nullptr) {
|
nullptr) {
|
||||||
switch (generic_event->response_type & event_type_mask) {
|
const uint8_t event_type =
|
||||||
|
generic_event->response_type & event_type_mask;
|
||||||
|
switch (event_type) {
|
||||||
// We're listening for `ConfigureNotify` events on the topmost
|
// We're listening for `ConfigureNotify` events on the topmost
|
||||||
// window before the root window, i.e. the window that's actually
|
// window before the root window, i.e. the window that's actually
|
||||||
// going to get dragged around the by the user. In most cases this
|
// going to get dragged around the by the user. In most cases this
|
||||||
@@ -334,6 +336,7 @@ void Editor::handle_x11_events() const {
|
|||||||
// check is sometimes necessary for using multiple editor windows
|
// check is sometimes necessary for using multiple editor windows
|
||||||
// within a single plugin group.
|
// within a single plugin group.
|
||||||
case XCB_CONFIGURE_NOTIFY:
|
case XCB_CONFIGURE_NOTIFY:
|
||||||
|
std::cerr << "XCB_CONFIGURE_NOTIFY" << std::endl;
|
||||||
if (!use_xembed) {
|
if (!use_xembed) {
|
||||||
fix_local_coordinates();
|
fix_local_coordinates();
|
||||||
}
|
}
|
||||||
@@ -342,6 +345,7 @@ void Editor::handle_x11_events() const {
|
|||||||
// most hosts will only show the window after the plugin has
|
// most hosts will only show the window after the plugin has
|
||||||
// embedded itself into it.
|
// embedded itself into it.
|
||||||
case XCB_VISIBILITY_NOTIFY:
|
case XCB_VISIBILITY_NOTIFY:
|
||||||
|
std::cerr << "XCB_VISIBILITY_NOTIFY" << std::endl;
|
||||||
if (use_xembed) {
|
if (use_xembed) {
|
||||||
do_xembed();
|
do_xembed();
|
||||||
}
|
}
|
||||||
@@ -357,6 +361,12 @@ void Editor::handle_x11_events() const {
|
|||||||
// `EnterNotify'.
|
// `EnterNotify'.
|
||||||
case XCB_ENTER_NOTIFY:
|
case XCB_ENTER_NOTIFY:
|
||||||
case XCB_FOCUS_IN:
|
case XCB_FOCUS_IN:
|
||||||
|
if (event_type == XCB_ENTER_NOTIFY) {
|
||||||
|
std::cerr << "XCB_ENTER_NOTIFY" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cerr << "XCB_FOCUS_IN" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (!use_xembed) {
|
if (!use_xembed) {
|
||||||
fix_local_coordinates();
|
fix_local_coordinates();
|
||||||
}
|
}
|
||||||
@@ -376,6 +386,7 @@ void Editor::handle_x11_events() const {
|
|||||||
// to mess with keyboard focus when hovering over the window while
|
// to mess with keyboard focus when hovering over the window while
|
||||||
// for instance a dialog is open.
|
// for instance a dialog is open.
|
||||||
case XCB_LEAVE_NOTIFY: {
|
case XCB_LEAVE_NOTIFY: {
|
||||||
|
std::cerr << "XCB_LEAVE_NOTIFY" << std::endl;
|
||||||
const auto event =
|
const auto event =
|
||||||
reinterpret_cast<xcb_leave_notify_event_t*>(generic_event);
|
reinterpret_cast<xcb_leave_notify_event_t*>(generic_event);
|
||||||
|
|
||||||
@@ -393,6 +404,10 @@ void Editor::handle_x11_events() const {
|
|||||||
set_input_focus(false);
|
set_input_focus(false);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
default: {
|
||||||
|
std::cerr << "X11 event " << event_type << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(generic_event);
|
free(generic_event);
|
||||||
|
|||||||
Reference in New Issue
Block a user