mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
Batch VST3 parameter info querying #236
To hopefully work mitigate the Kontakt bug that causes the host to rescan thousands of parameters hundreds of times when using certain VST3 Kontakt patches in REAPER.
This commit is contained in:
@@ -10,6 +10,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Parameter information for VST3 plugins is now queried all at once. This should
|
||||||
|
work around a bug in _Kontakt_ that would cause loading patches with lots of
|
||||||
|
exposed parameters to become very slow in **REAPER**
|
||||||
|
([#236](https://github.com/robbert-vdh/yabridge/issues/236)).
|
||||||
- When dragging plugin windows around, yabridge now waits for the mouse buttons
|
- When dragging plugin windows around, yabridge now waits for the mouse buttons
|
||||||
to be released before informing Wine about the window's new screen
|
to be released before informing Wine about the window's new screen
|
||||||
coordinates. This prevents constant flickering when dragging plugin windows
|
coordinates. This prevents constant flickering when dragging plugin windows
|
||||||
|
|||||||
@@ -233,20 +233,10 @@ bool Vst3Logger::log_request(
|
|||||||
|
|
||||||
bool Vst3Logger::log_request(
|
bool Vst3Logger::log_request(
|
||||||
bool is_host_plugin,
|
bool is_host_plugin,
|
||||||
const YaEditController::GetParameterCount& request) {
|
const YaEditController::GetParameterInfos& request) {
|
||||||
return log_request_base(is_host_plugin, [&](auto& message) {
|
return log_request_base(is_host_plugin, [&](auto& message) {
|
||||||
message << request.instance_id
|
message << request.instance_id
|
||||||
<< ": IEditController::getParameterCount()";
|
<< ": IEditController::getParameterInfo(..., &info) (batched)";
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vst3Logger::log_request(
|
|
||||||
bool is_host_plugin,
|
|
||||||
const YaEditController::GetParameterInfo& request) {
|
|
||||||
return log_request_base(is_host_plugin, [&](auto& message) {
|
|
||||||
message << request.instance_id
|
|
||||||
<< ": IEditController::getParameterInfo(paramIndex = "
|
|
||||||
<< request.param_index << ", &info)";
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1478,17 +1468,13 @@ void Vst3Logger::log_response(
|
|||||||
|
|
||||||
void Vst3Logger::log_response(
|
void Vst3Logger::log_response(
|
||||||
bool is_host_plugin,
|
bool is_host_plugin,
|
||||||
const YaEditController::GetParameterInfoResponse& response,
|
const YaEditController::GetParameterInfosResponse& response,
|
||||||
bool from_cache) {
|
bool from_cache) {
|
||||||
log_response_base(is_host_plugin, [&](auto& message) {
|
log_response_base(is_host_plugin, [&](auto& message) {
|
||||||
message << response.result.string();
|
message << "<ParameterInfo> for " << response.infos.size()
|
||||||
if (response.result == Steinberg::kResultOk) {
|
<< " parameters";
|
||||||
std::string param_title =
|
if (from_cache) {
|
||||||
VST3::StringConvert::convert(response.info.title);
|
message << " (from cache)";
|
||||||
message << ", <ParameterInfo for '" << param_title << "'>";
|
|
||||||
if (from_cache) {
|
|
||||||
message << " (from cache)";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ class Vst3Logger {
|
|||||||
bool log_request(bool is_host_plugin,
|
bool log_request(bool is_host_plugin,
|
||||||
const YaEditController::SetComponentState&);
|
const YaEditController::SetComponentState&);
|
||||||
bool log_request(bool is_host_plugin,
|
bool log_request(bool is_host_plugin,
|
||||||
const YaEditController::GetParameterCount&);
|
const YaEditController::GetParameterInfos&);
|
||||||
bool log_request(bool is_host_plugin,
|
|
||||||
const YaEditController::GetParameterInfo&);
|
|
||||||
bool log_request(bool is_host_plugin,
|
bool log_request(bool is_host_plugin,
|
||||||
const YaEditController::GetParamStringByValue&);
|
const YaEditController::GetParamStringByValue&);
|
||||||
bool log_request(bool is_host_plugin,
|
bool log_request(bool is_host_plugin,
|
||||||
@@ -272,7 +270,7 @@ class Vst3Logger {
|
|||||||
void log_response(bool is_host_plugin,
|
void log_response(bool is_host_plugin,
|
||||||
const Vst3PluginProxy::GetStateResponse&);
|
const Vst3PluginProxy::GetStateResponse&);
|
||||||
void log_response(bool is_host_plugin,
|
void log_response(bool is_host_plugin,
|
||||||
const YaEditController::GetParameterInfoResponse&,
|
const YaEditController::GetParameterInfosResponse&,
|
||||||
bool from_cache = false);
|
bool from_cache = false);
|
||||||
void log_response(bool is_host_plugin,
|
void log_response(bool is_host_plugin,
|
||||||
const YaEditController::GetParamStringByValueResponse&);
|
const YaEditController::GetParamStringByValueResponse&);
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ using Vst3ControlRequest =
|
|||||||
YaConnectionPoint::Notify,
|
YaConnectionPoint::Notify,
|
||||||
YaContextMenuTarget::ExecuteMenuItem,
|
YaContextMenuTarget::ExecuteMenuItem,
|
||||||
YaEditController::SetComponentState,
|
YaEditController::SetComponentState,
|
||||||
YaEditController::GetParameterCount,
|
YaEditController::GetParameterInfos,
|
||||||
YaEditController::GetParameterInfo,
|
|
||||||
YaEditController::GetParamStringByValue,
|
YaEditController::GetParamStringByValue,
|
||||||
YaEditController::GetParamValueByString,
|
YaEditController::GetParamValueByString,
|
||||||
YaEditController::NormalizedParamToPlain,
|
YaEditController::NormalizedParamToPlain,
|
||||||
|
|||||||
@@ -98,11 +98,37 @@ class YaEditController : public Steinberg::Vst::IEditController {
|
|||||||
getState(Steinberg::IBStream* state) override = 0;
|
getState(Steinberg::IBStream* state) override = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to pass through a call to `IEditController::getParameterCount()`
|
* All of a plugin's parameter infos.
|
||||||
* to the Wine plugin host.
|
*
|
||||||
|
* @see GetParameterInfos
|
||||||
*/
|
*/
|
||||||
struct GetParameterCount {
|
struct GetParameterInfosResponse {
|
||||||
using Response = PrimitiveResponse<int32>;
|
/**
|
||||||
|
* All of the plugin's parameter infos. If the plugin somehow returned
|
||||||
|
* an error for a parameter that should be in range, then this contains
|
||||||
|
* a nullopt value.
|
||||||
|
*/
|
||||||
|
std::vector<std::optional<Steinberg::Vst::ParameterInfo>> infos;
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
void serialize(S& s) {
|
||||||
|
s.container(infos, 1 << 16, [](S& s, auto& v) {
|
||||||
|
s.ext(v, bitsery::ext::InPlaceOptional{});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the plugin's parameter information using both
|
||||||
|
* `IEditController::getParameterCount()` and
|
||||||
|
* `IEditController::getParameterInfo()`. This is queried all at once and
|
||||||
|
* then cached until the plugin asks for a rescan to speed up loading for
|
||||||
|
* plugins with huge amounts of parameters, and plugins like Kontakt that
|
||||||
|
* may tell the host to rescan for parameters hundreds of times in a row
|
||||||
|
* (https://github.com/robbert-vdh/yabridge/issues/236).
|
||||||
|
*/
|
||||||
|
struct GetParameterInfos {
|
||||||
|
using Response = GetParameterInfosResponse;
|
||||||
|
|
||||||
native_size_t instance_id;
|
native_size_t instance_id;
|
||||||
|
|
||||||
@@ -113,41 +139,6 @@ class YaEditController : public Steinberg::Vst::IEditController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
virtual int32 PLUGIN_API getParameterCount() override = 0;
|
virtual int32 PLUGIN_API getParameterCount() override = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* The response code and returned parameter information for a call to
|
|
||||||
* `IEditController::getParameterInfo(param_index, &info)`.
|
|
||||||
*/
|
|
||||||
struct GetParameterInfoResponse {
|
|
||||||
UniversalTResult result;
|
|
||||||
Steinberg::Vst::ParameterInfo info;
|
|
||||||
|
|
||||||
template <typename S>
|
|
||||||
void serialize(S& s) {
|
|
||||||
s.object(result);
|
|
||||||
s.object(info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message to pass through a call to
|
|
||||||
* `IEditController::getParameterInfo(param_index, &info)` to the Wine
|
|
||||||
* plugin host.
|
|
||||||
*/
|
|
||||||
struct GetParameterInfo {
|
|
||||||
using Response = GetParameterInfoResponse;
|
|
||||||
|
|
||||||
native_size_t instance_id;
|
|
||||||
|
|
||||||
int32 param_index;
|
|
||||||
|
|
||||||
template <typename S>
|
|
||||||
void serialize(S& s) {
|
|
||||||
s.value8b(instance_id);
|
|
||||||
s.value4b(param_index);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual tresult PLUGIN_API
|
virtual tresult PLUGIN_API
|
||||||
getParameterInfo(int32 paramIndex,
|
getParameterInfo(int32 paramIndex,
|
||||||
Steinberg::Vst::ParameterInfo& info /*out*/) override = 0;
|
Steinberg::Vst::ParameterInfo& info /*out*/) override = 0;
|
||||||
|
|||||||
@@ -623,72 +623,75 @@ Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
||||||
const auto request =
|
// Parameter information is queried all at once to work around a Kontakt
|
||||||
YaEditController::GetParameterCount{.instance_id = instance_id()};
|
// bug, see https://github.com/robbert-vdh/yabridge/issues/236
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// We'll assume that the plugin has at least one parameter. If it does
|
||||||
|
// not have any parameters then everything will work as expected, except
|
||||||
|
// that the parameter count is not cached.
|
||||||
std::lock_guard lock(function_result_cache_mutex_);
|
std::lock_guard lock(function_result_cache_mutex_);
|
||||||
if (function_result_cache_.parameter_count) {
|
if (!function_result_cache_.parameter_info.empty()) {
|
||||||
const bool log_response =
|
// We can't cleanly log here, but it also doesn't really matter
|
||||||
bridge_.logger_.log_request(true, request);
|
return static_cast<int32>(
|
||||||
if (log_response) {
|
function_result_cache_.parameter_info.size());
|
||||||
bridge_.logger_.log_response(
|
|
||||||
true,
|
|
||||||
YaEditController::GetParameterCount::Response(
|
|
||||||
*function_result_cache_.parameter_count),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *function_result_cache_.parameter_count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int32 result = bridge_.send_message(request);
|
// The first time either of these two functions is called we'll fetch the
|
||||||
|
// infos for all parameters. These are cleared when the plugin triggers a
|
||||||
|
// component restart.
|
||||||
|
query_parameter_info();
|
||||||
|
|
||||||
{
|
std::lock_guard lock(function_result_cache_mutex_);
|
||||||
std::lock_guard lock(function_result_cache_mutex_);
|
return static_cast<int32>(function_result_cache_.parameter_info.size());
|
||||||
function_result_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 auto request = YaEditController::GetParameterInfo{
|
// The integer parameter indices are _fun_
|
||||||
.instance_id = instance_id(), .param_index = paramIndex};
|
if (paramIndex < 0) {
|
||||||
|
return Steinberg::kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See above
|
||||||
{
|
{
|
||||||
std::lock_guard lock(function_result_cache_mutex_);
|
std::lock_guard lock(function_result_cache_mutex_);
|
||||||
if (auto it = function_result_cache_.parameter_info.find(paramIndex);
|
if (!function_result_cache_.parameter_info.empty()) {
|
||||||
it != function_result_cache_.parameter_info.end()) {
|
if (paramIndex <
|
||||||
const bool log_response =
|
static_cast<int32>(
|
||||||
bridge_.logger_.log_request(true, request);
|
function_result_cache_.parameter_info.size())) {
|
||||||
if (log_response) {
|
if (const auto& result =
|
||||||
bridge_.logger_.log_response(
|
function_result_cache_.parameter_info[paramIndex]) {
|
||||||
true,
|
info = *result;
|
||||||
YaEditController::GetParameterInfo::Response{
|
return Steinberg::kResultOk;
|
||||||
.result = Steinberg::kResultOk, .info = it->second},
|
} else {
|
||||||
true);
|
return Steinberg::kResultFalse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Steinberg::kInvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
info = it->second;
|
|
||||||
|
|
||||||
return Steinberg::kResultOk;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetParameterInfoResponse response = bridge_.send_message(request);
|
// The first time either of these two functions is called we'll fetch the
|
||||||
|
// infos for all parameters. These are cleared when the plugin triggers a
|
||||||
|
// component restart.
|
||||||
|
query_parameter_info();
|
||||||
|
|
||||||
info = response.info;
|
std::lock_guard lock(function_result_cache_mutex_);
|
||||||
|
if (paramIndex <
|
||||||
{
|
static_cast<int32>(function_result_cache_.parameter_info.size())) {
|
||||||
std::lock_guard lock(function_result_cache_mutex_);
|
if (const auto& result =
|
||||||
function_result_cache_.parameter_info[paramIndex] = response.info;
|
function_result_cache_.parameter_info[paramIndex]) {
|
||||||
|
info = *result;
|
||||||
|
return Steinberg::kResultOk;
|
||||||
|
} else {
|
||||||
|
return Steinberg::kResultFalse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Steinberg::kInvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue(
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue(
|
||||||
@@ -1376,6 +1379,14 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getXmlRepresentationStream(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Vst3PluginProxyImpl::query_parameter_info() {
|
||||||
|
std::lock_guard lock(function_result_cache_mutex_);
|
||||||
|
|
||||||
|
const GetParameterInfosResponse response = bridge_.send_message(
|
||||||
|
YaEditController::GetParameterInfos{.instance_id = instance_id()});
|
||||||
|
function_result_cache_.parameter_info = std::move(response.infos);
|
||||||
|
}
|
||||||
|
|
||||||
void Vst3PluginProxyImpl::clear_bus_cache() noexcept {
|
void Vst3PluginProxyImpl::clear_bus_cache() noexcept {
|
||||||
std::lock_guard lock(processing_bus_cache_mutex_);
|
std::lock_guard lock(processing_bus_cache_mutex_);
|
||||||
if (processing_bus_cache_) {
|
if (processing_bus_cache_) {
|
||||||
|
|||||||
@@ -398,6 +398,14 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|||||||
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2_;
|
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Query information for all of the plugin's parameters and writes the
|
||||||
|
* results to `function_result_cache_`. Acquires a lock on the struct in the
|
||||||
|
* process, so it must not be locked before calling this function (thanks
|
||||||
|
* STL).
|
||||||
|
*/
|
||||||
|
void query_parameter_info();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the bus count and information cache. We need this cache for REAPER
|
* 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
|
* as it makes `num_inputs + num_outputs + 2` function calls to retrieve
|
||||||
@@ -529,13 +537,19 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|||||||
*/
|
*/
|
||||||
std::map<int32, tresult> can_process_sample_size;
|
std::map<int32, tresult> can_process_sample_size;
|
||||||
/**
|
/**
|
||||||
* Memoizes `IEditController::getParameterCount()`.
|
* Memoizes `IEditController::getParameterCount()` and
|
||||||
|
* `IEditController::getParameterInfo()`. This information is queried
|
||||||
|
* all at to work around a Kontakt bug where they tell the host to
|
||||||
|
* rescan the parameters hundreds of times in a row when loading a patch
|
||||||
|
* that has hundreds of custom parameters instead of doing it only once
|
||||||
|
* at the end.
|
||||||
|
*
|
||||||
|
* Because the plugin _can_ return an error when fetching the info for a
|
||||||
|
* parameter that should be in range, this array stores `std::optional`s
|
||||||
|
* so we can do the same thing here.
|
||||||
*/
|
*/
|
||||||
std::optional<int32> parameter_count;
|
std::vector<std::optional<Steinberg::Vst::ParameterInfo>>
|
||||||
/**
|
parameter_info;
|
||||||
* Memoizes `IEditController::getParameterInfo()`.
|
|
||||||
*/
|
|
||||||
std::unordered_map<int32, Steinberg::Vst::ParameterInfo> parameter_info;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -400,23 +400,34 @@ void Vst3Bridge::run() {
|
|||||||
return instance.interfaces.edit_controller->setComponentState(
|
return instance.interfaces.edit_controller->setComponentState(
|
||||||
&request.state);
|
&request.state);
|
||||||
},
|
},
|
||||||
[&](const YaEditController::GetParameterCount& request)
|
[&](const YaEditController::GetParameterInfos& request)
|
||||||
-> YaEditController::GetParameterCount::Response {
|
-> YaEditController::GetParameterInfos::Response {
|
||||||
const auto& [instance, _] = get_instance(request.instance_id);
|
const auto& [instance, _] = get_instance(request.instance_id);
|
||||||
|
|
||||||
return instance.interfaces.edit_controller->getParameterCount();
|
// This is an optimization mostly for Kontakt, which may tell
|
||||||
},
|
// tell the host to rescan its 3000 parameters hundreds of times
|
||||||
[&](YaEditController::GetParameterInfo& request)
|
// in rapid succession. Querying all parameters at once can save
|
||||||
-> YaEditController::GetParameterInfo::Response {
|
// minutes of waiting around on slower machines.
|
||||||
Steinberg::Vst::ParameterInfo info{};
|
const int num_parameters =
|
||||||
const auto& [instance, _] = get_instance(request.instance_id);
|
instance.interfaces.edit_controller->getParameterCount();
|
||||||
|
|
||||||
const tresult result =
|
std::vector<std::optional<Steinberg::Vst::ParameterInfo>> infos;
|
||||||
instance.interfaces.edit_controller->getParameterInfo(
|
infos.reserve(num_parameters);
|
||||||
request.param_index, info);
|
for (int i = 0; i < num_parameters; i++) {
|
||||||
|
// This should never fail, but we can't make things up and
|
||||||
|
// we don't want to change parameter orders around so we'll
|
||||||
|
// store a nullopt if the plugin returns an error here
|
||||||
|
Steinberg::Vst::ParameterInfo info{};
|
||||||
|
if (instance.interfaces.edit_controller->getParameterInfo(
|
||||||
|
i, info) == Steinberg::kResultOk) {
|
||||||
|
infos.push_back(std::move(info));
|
||||||
|
} else {
|
||||||
|
infos.push_back(std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return YaEditController::GetParameterInfoResponse{
|
return YaEditController::GetParameterInfosResponse{
|
||||||
.result = result, .info = std::move(info)};
|
.infos = std::move(infos)};
|
||||||
},
|
},
|
||||||
[&](const YaEditController::GetParamStringByValue& request)
|
[&](const YaEditController::GetParamStringByValue& request)
|
||||||
-> YaEditController::GetParamStringByValue::Response {
|
-> YaEditController::GetParamStringByValue::Response {
|
||||||
@@ -793,8 +804,7 @@ void Vst3Bridge::run() {
|
|||||||
x11_handle);
|
x11_handle);
|
||||||
const tresult result =
|
const tresult result =
|
||||||
instance.plug_view_instance->plug_view->attached(
|
instance.plug_view_instance->plug_view->attached(
|
||||||
editor_instance.win32_handle(),
|
editor_instance.win32_handle(), type.c_str());
|
||||||
type.c_str());
|
|
||||||
|
|
||||||
// Set the window's initial size according to what the
|
// Set the window's initial size according to what the
|
||||||
// plugin reports. Otherwise get rid of the editor again
|
// plugin reports. Otherwise get rid of the editor again
|
||||||
@@ -1315,7 +1325,7 @@ void Vst3Bridge::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Vst3Bridge::resize_editor(size_t instance_id,
|
bool Vst3Bridge::resize_editor(size_t instance_id,
|
||||||
const Steinberg::ViewRect& new_size) {
|
const Steinberg::ViewRect& new_size) {
|
||||||
const auto& [instance, _] = get_instance(instance_id);
|
const auto& [instance, _] = get_instance(instance_id);
|
||||||
|
|
||||||
if (instance.editor) {
|
if (instance.editor) {
|
||||||
|
|||||||
@@ -308,8 +308,7 @@ class Vst3Bridge : public HostBridge {
|
|||||||
* the new size. This is called from `IPlugFrame::resizeView()` to make sure
|
* the new size. This is called from `IPlugFrame::resizeView()` to make sure
|
||||||
* we do the resize before the request gets sent to the host.
|
* we do the resize before the request gets sent to the host.
|
||||||
*/
|
*/
|
||||||
bool resize_editor(size_t instance_id,
|
bool resize_editor(size_t instance_id, const Steinberg::ViewRect& new_size);
|
||||||
const Steinberg::ViewRect& new_size);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a context with with `context_menu`'s ID and owner in
|
* Register a context with with `context_menu`'s ID and owner in
|
||||||
|
|||||||
Reference in New Issue
Block a user