mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-06 19:40:10 +02:00
a1c9d0fbf5
Over the last 2048 processing cycles. Should give a better idea of the spread.
1436 lines
55 KiB
C++
1436 lines
55 KiB
C++
// yabridge: a Wine VST bridge
|
|
// Copyright (C) 2020-2021 Robbert van der Helm
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include "plugin-proxy.h"
|
|
|
|
#include "plug-view-proxy.h"
|
|
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
/**
|
|
* When the host tries to connect two plugin instances with connection proxies,
|
|
* we'll first try to bypass that proxy. This goes against the idea of yabridge,
|
|
* but these proxies can make things very difficult when plugins start sending
|
|
* messages from the GUI thread. If we cannot figure out what we're connected
|
|
* to, we'll still proxy the host's connection proxy.
|
|
*/
|
|
constexpr char other_instance_message_id[] = "yabridge_other_instance";
|
|
/**
|
|
* In the message described above we'll use this attribute to pass through a
|
|
* pointer to the sender of the message. This will the other side set the
|
|
* `connected_instance_id` field on the other object to the instance ID of that
|
|
* connected object. This will let us bypass the connection proxy since we can
|
|
* then just connect the two objects directly.
|
|
*/
|
|
constexpr char other_instance_pointer_attribute[] = "other_proxy_ptr";
|
|
|
|
/**
|
|
* The time between reports for the mean processing time.
|
|
*/
|
|
constexpr std::chrono::high_resolution_clock::duration report_interval = 5s;
|
|
|
|
Vst3PluginProxyImpl::ContextMenu::ContextMenu(
|
|
Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu)
|
|
: menu(menu) {}
|
|
|
|
Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
|
|
Vst3PluginProxy::ConstructArgs&& args)
|
|
: Vst3PluginProxy(std::move(args)),
|
|
bridge(bridge),
|
|
last_report(std::chrono::high_resolution_clock::now()) {
|
|
bridge.register_plugin_proxy(*this);
|
|
}
|
|
|
|
Vst3PluginProxyImpl::~Vst3PluginProxyImpl() noexcept {
|
|
// NOTE: This can actually throw (e.g. out of memory or the socket got
|
|
// closed). But if that were to happen, then we wouldn't be able to
|
|
// recover from it anyways.
|
|
bridge.send_message(
|
|
Vst3PluginProxy::Destruct{.instance_id = instance_id()});
|
|
bridge.unregister_plugin_proxy(*this);
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) {
|
|
const tresult result = Vst3PluginProxy::queryInterface(_iid, obj);
|
|
bridge.logger.log_query_interface("In FUnknown::queryInterface()", result,
|
|
Steinberg::FUID::fromTUID(_iid));
|
|
|
|
return result;
|
|
}
|
|
|
|
size_t Vst3PluginProxyImpl::register_context_menu(
|
|
Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu) {
|
|
std::lock_guard lock(context_menus_mutex);
|
|
|
|
const size_t context_menu_id = current_context_menu_id.fetch_add(1);
|
|
context_menus.emplace(context_menu_id, menu);
|
|
|
|
return context_menu_id;
|
|
}
|
|
|
|
/**
|
|
* Unregister a context menu using the ID generated by a previous call to
|
|
* `register_context_menu()`. This will release the context menu object
|
|
* returned by the host.
|
|
*/
|
|
bool Vst3PluginProxyImpl::unregister_context_menu(size_t context_menu_id) {
|
|
std::lock_guard lock(context_menus_mutex);
|
|
return context_menus.erase(context_menu_id);
|
|
}
|
|
|
|
void Vst3PluginProxyImpl::clear_caches() noexcept {
|
|
clear_bus_cache();
|
|
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
function_result_cache = FunctionResultCache{};
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setAudioPresentationLatencySamples(
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 busIndex,
|
|
uint32 latencyInSamples) {
|
|
return bridge.send_message(
|
|
YaAudioPresentationLatency::SetAudioPresentationLatencySamples{
|
|
.instance_id = instance_id(),
|
|
.dir = dir,
|
|
.bus_index = busIndex,
|
|
.latency_in_samples = latencyInSamples});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements(
|
|
Steinberg::Vst::SpeakerArrangement* inputs,
|
|
int32 numIns,
|
|
Steinberg::Vst::SpeakerArrangement* outputs,
|
|
int32 numOuts) {
|
|
clear_bus_cache();
|
|
|
|
// NOTE: Ardour passes a null pointer when `numIns` or `numOuts` is 0, so we
|
|
// need to work around that
|
|
return bridge.send_audio_processor_message(
|
|
YaAudioProcessor::SetBusArrangements{
|
|
.instance_id = instance_id(),
|
|
.inputs =
|
|
(inputs ? std::vector<Steinberg::Vst::SpeakerArrangement>(
|
|
inputs, &inputs[numIns])
|
|
: std::vector<Steinberg::Vst::SpeakerArrangement>()),
|
|
.num_ins = numIns,
|
|
.outputs =
|
|
(outputs ? std::vector<Steinberg::Vst::SpeakerArrangement>(
|
|
outputs, &outputs[numOuts])
|
|
: std::vector<Steinberg::Vst::SpeakerArrangement>()),
|
|
.num_outs = numOuts,
|
|
});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement(
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
Steinberg::Vst::SpeakerArrangement& arr) {
|
|
const GetBusArrangementResponse response =
|
|
bridge.send_audio_processor_message(YaAudioProcessor::GetBusArrangement{
|
|
.instance_id = instance_id(), .dir = dir, .index = index});
|
|
|
|
arr = response.arr;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::canProcessSampleSize(int32 symbolicSampleSize) {
|
|
const auto request = YaAudioProcessor::CanProcessSampleSize{
|
|
.instance_id = instance_id(),
|
|
.symbolic_sample_size = symbolicSampleSize};
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
if (auto it = function_result_cache.can_process_sample_size.find(
|
|
symbolicSampleSize);
|
|
it != function_result_cache.can_process_sample_size.end()) {
|
|
const bool log_response = bridge.logger.log_request(true, request);
|
|
if (log_response) {
|
|
bridge.logger.log_response(
|
|
true,
|
|
YaAudioProcessor::CanProcessSampleSize::Response(
|
|
it->second),
|
|
true);
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
const tresult result = bridge.send_audio_processor_message(request);
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
function_result_cache.can_process_sample_size[symbolicSampleSize] =
|
|
result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
uint32 PLUGIN_API Vst3PluginProxyImpl::getLatencySamples() {
|
|
return bridge.send_audio_processor_message(
|
|
YaAudioProcessor::GetLatencySamples{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) {
|
|
const YaAudioProcessor::SetupProcessingResponse response =
|
|
bridge.send_audio_processor_message(YaAudioProcessor::SetupProcessing{
|
|
.instance_id = instance_id(), .setup = setup});
|
|
|
|
// We have now set up the shared audio buffers on the Wine side, and we'll
|
|
// be able to able to connect to them by using the same audio configuration
|
|
if (!process_buffers) {
|
|
process_buffers.emplace(response.audio_buffers_config);
|
|
} else {
|
|
process_buffers->resize(response.audio_buffers_config);
|
|
}
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) {
|
|
// REAPER used to repeatedly query the plugin for its bus information on
|
|
// every processing cycle. Because this really adds up in terms of latency
|
|
// we sadly have to deviate from yabridge's principles and implement a
|
|
// cache. We keep this in because it can still help performance a little in
|
|
// some DAWs.
|
|
{
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (state) {
|
|
processing_bus_cache.emplace();
|
|
} else {
|
|
processing_bus_cache.reset();
|
|
}
|
|
}
|
|
|
|
return bridge.send_audio_processor_message(YaAudioProcessor::SetProcessing{
|
|
.instance_id = instance_id(), .state = state});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) {
|
|
if (const auto& now = std::chrono::high_resolution_clock::now();
|
|
now - last_report >= report_interval) {
|
|
bridge.logger.log(
|
|
"Mean processing time: " +
|
|
std::to_string(
|
|
std::chrono::duration_cast<
|
|
std::chrono::duration<float, std::micro>>(mean_process_time)
|
|
.count()) +
|
|
" us");
|
|
|
|
if (process_time_ring_buffer_wrapped_around) {
|
|
// We only report these values every few seconds, so we don't need
|
|
// to be clever with keeping rolling minima and maxima and we can
|
|
// just linearly iterate over everything every now and then
|
|
std::chrono::high_resolution_clock::duration min_process_time =
|
|
process_time_ring_buffer[0];
|
|
std::chrono::high_resolution_clock::duration max_process_time =
|
|
process_time_ring_buffer[0];
|
|
for (size_t i = 1; i < process_time_ring_buffer.size(); i++) {
|
|
if (process_time_ring_buffer[i] < min_process_time) {
|
|
min_process_time = process_time_ring_buffer[i];
|
|
} else if (process_time_ring_buffer[i] > max_process_time) {
|
|
max_process_time = process_time_ring_buffer[i];
|
|
}
|
|
}
|
|
|
|
bridge.logger.log(
|
|
"Min processing time: " +
|
|
std::to_string(std::chrono::duration_cast<
|
|
std::chrono::duration<float, std::micro>>(
|
|
min_process_time)
|
|
.count()) +
|
|
" us");
|
|
bridge.logger.log(
|
|
"Max processing time: " +
|
|
std::to_string(std::chrono::duration_cast<
|
|
std::chrono::duration<float, std::micro>>(
|
|
max_process_time)
|
|
.count()) +
|
|
" us");
|
|
} else {
|
|
bridge.logger.log("Min processing time: <still warming up>");
|
|
bridge.logger.log("Max processing time: <still warming up>");
|
|
}
|
|
|
|
last_report = now;
|
|
}
|
|
|
|
// Doing this twice is a bit of a waste, but we don't want to measure IO
|
|
const auto& process_start = std::chrono::high_resolution_clock::now();
|
|
|
|
// 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 = 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;
|
|
}
|
|
|
|
// We reuse this existing object to avoid allocations.
|
|
// `YaProcessData::repopulate()` will write the input audio to the shared
|
|
// audio buffers, so they're not stored within the request object itself.
|
|
assert(process_buffers);
|
|
process_request.instance_id = instance_id();
|
|
process_request.data.repopulate(data, *process_buffers);
|
|
process_request.new_realtime_priority = new_realtime_priority;
|
|
|
|
// HACK: This is a bit ugly. This `YaProcessData::Response` object actually
|
|
// contains pointers to the corresponding `YaProcessData` fields in
|
|
// this object, so we can only send back the fields that are actually
|
|
// relevant. This is necessary to avoid allocating copies or moves on
|
|
// the Wine side. This `create_response()` function creates a response
|
|
// object that points to the fields in `process_request.data`, so when
|
|
// we deserialize into `process_response` we end up actually writing
|
|
// to the actual `process_request.data` object. Thus we can also call
|
|
// `process_request.data.write_back_outputs()` later.
|
|
//
|
|
// `YaProcessData::Response::serialize()` should make this a lot
|
|
// clearer.
|
|
process_response.output_data = process_request.data.create_response();
|
|
|
|
// We'll also receive the response into an existing object so we can also
|
|
// avoid heap allocations there
|
|
bridge.receive_audio_processor_message_into(
|
|
MessageReference<YaAudioProcessor::Process>(process_request),
|
|
process_response);
|
|
|
|
// At this point the shared audio buffers should contain the output audio,
|
|
// so we'll write that back to the host along with any metadata (which in
|
|
// practice are only the silence flags), as well as any output parameter
|
|
// changes and events
|
|
process_request.data.write_back_outputs(data, *process_buffers);
|
|
|
|
const auto& process_end = std::chrono::high_resolution_clock::now();
|
|
const auto& process_time = process_end - process_start;
|
|
mean_process_time = std::chrono::duration_cast<
|
|
std::chrono::high_resolution_clock::duration>(
|
|
(process_time * 0.05) + (mean_process_time * 0.95));
|
|
|
|
// For the minma and maxima we keep the last `process_time_ring_buffer_size`
|
|
// timings around so we can compute these during the report
|
|
process_time_ring_buffer[process_time_ring_buffer_pos] = process_time;
|
|
process_time_ring_buffer_pos += 1;
|
|
if (process_time_ring_buffer_pos >= process_time_ring_buffer.size()) {
|
|
process_time_ring_buffer_pos = 0;
|
|
process_time_ring_buffer_wrapped_around = true;
|
|
}
|
|
|
|
return process_response.result;
|
|
}
|
|
|
|
uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() {
|
|
return bridge.send_audio_processor_message(
|
|
YaAudioProcessor::GetTailSamples{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setAutomationState(int32 state) {
|
|
return bridge.send_message(YaAutomationState::SetAutomationState{
|
|
.instance_id = instance_id(), .state = state});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getControllerClassId(Steinberg::TUID classId) {
|
|
if (classId) {
|
|
const GetControllerClassIdResponse response =
|
|
bridge.send_audio_processor_message(
|
|
YaComponent::GetControllerClassId{.instance_id =
|
|
instance_id()});
|
|
|
|
ArrayUID native_uid = response.editor_cid.get_native_uid();
|
|
std::copy(native_uid.begin(), native_uid.end(), classId);
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IComponent::getControllerClassId()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setIoMode(Steinberg::Vst::IoMode mode) {
|
|
return bridge.send_audio_processor_message(
|
|
YaComponent::SetIoMode{.instance_id = instance_id(), .mode = mode});
|
|
}
|
|
|
|
int32 PLUGIN_API
|
|
Vst3PluginProxyImpl::getBusCount(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir) {
|
|
const auto request = YaComponent::GetBusCount{
|
|
.instance_id = instance_id(), .type = type, .dir = dir};
|
|
|
|
std::tuple<Steinberg::Vst::MediaType, Steinberg::Vst::BusDirection> args{
|
|
type, dir};
|
|
{
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (processing_bus_cache) {
|
|
if (auto it = processing_bus_cache->bus_count.find(args);
|
|
it != processing_bus_cache->bus_count.end()) {
|
|
const bool log_response =
|
|
bridge.logger.log_request(true, request);
|
|
if (log_response) {
|
|
bridge.logger.log_response(
|
|
true, YaComponent::GetBusCount::Response(it->second),
|
|
true);
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 result = bridge.send_audio_processor_message(request);
|
|
|
|
{
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (processing_bus_cache) {
|
|
processing_bus_cache->bus_count[args] = result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
Steinberg::Vst::BusInfo& bus /*out*/) {
|
|
const auto request = YaComponent::GetBusInfo{
|
|
.instance_id = instance_id(), .type = type, .dir = dir, .index = index};
|
|
|
|
std::tuple<Steinberg::Vst::MediaType, Steinberg::Vst::BusDirection, int32>
|
|
args{type, dir, index};
|
|
{
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (processing_bus_cache) {
|
|
if (auto it = processing_bus_cache->bus_info.find(args);
|
|
it != processing_bus_cache->bus_info.end()) {
|
|
const bool log_response =
|
|
bridge.logger.log_request(true, request);
|
|
if (log_response) {
|
|
bridge.logger.log_response(
|
|
false,
|
|
YaComponent::GetBusInfo::Response{
|
|
.result = Steinberg::kResultOk, .bus = it->second},
|
|
true);
|
|
}
|
|
|
|
bus = it->second;
|
|
|
|
return Steinberg::kResultOk;
|
|
}
|
|
}
|
|
}
|
|
|
|
const GetBusInfoResponse response =
|
|
bridge.send_audio_processor_message(request);
|
|
|
|
bus = response.bus;
|
|
|
|
{
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (processing_bus_cache) {
|
|
processing_bus_cache->bus_info[args] = response.bus;
|
|
}
|
|
}
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getRoutingInfo(
|
|
Steinberg::Vst::RoutingInfo& inInfo,
|
|
Steinberg::Vst::RoutingInfo& outInfo /*out*/) {
|
|
const GetRoutingInfoResponse response =
|
|
bridge.send_audio_processor_message(YaComponent::GetRoutingInfo{
|
|
.instance_id = instance_id(), .in_info = inInfo});
|
|
|
|
outInfo = response.out_info;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
TBool state) {
|
|
return bridge.send_audio_processor_message(
|
|
YaComponent::ActivateBus{.instance_id = instance_id(),
|
|
.type = type,
|
|
.dir = dir,
|
|
.index = index,
|
|
.state = state});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) {
|
|
// HACK: Even though we initially implemented this cache specifically for
|
|
// REAPER, REAPER doesn't use `IComponent::setProcessing` properly and
|
|
// calls it before doing setting up input and output busses. So now
|
|
// our workaround to get acceptable performance in REAPER needs a
|
|
// workaround of its ownn. Great!
|
|
clear_bus_cache();
|
|
|
|
return bridge.send_audio_processor_message(
|
|
YaComponent::SetActive{.instance_id = instance_id(), .state = state});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) {
|
|
if (state) {
|
|
// Since both interfaces contain this function, this is used for both
|
|
// `IComponent::setState()` as well as `IEditController::setState()`
|
|
// NOTE: This will likely be called from the GUI thread, and some
|
|
// plugins will try to resize as part of setting their new state.
|
|
// That `IPlugFrame::resizeView()` _also_ has to be handled on the
|
|
// GUI thread. So if the GUI is active, we'll use the mutual
|
|
// recursion mechanism to allow this resize call to also be
|
|
// performed from the GUI thread.
|
|
return bridge.send_mutually_recursive_message(Vst3PluginProxy::SetState{
|
|
.instance_id = instance_id(), .state = state});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'I{Component,EditController}::setState()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) {
|
|
if (state) {
|
|
// Since both interfaces contain this function, this is used for both
|
|
// `IComponent::getState()` as well as `IEditController::getState()`
|
|
// NOTE: This will likely be called from the GUI thread, if the plugin
|
|
// somehow ends up sending a resize while this happens, we should
|
|
// end up in a deadlock. Normally this wouldn't be an issue, but
|
|
// REAPER will fetch the complete plugin state from time to time,
|
|
// so when changing a parameter also resizes the GUI we can run
|
|
// into a situation where we need mutually recursive function
|
|
// calls.
|
|
const GetStateResponse response =
|
|
bridge.send_mutually_recursive_message(Vst3PluginProxy::GetState{
|
|
.instance_id = instance_id(), .state = state});
|
|
|
|
assert(response.state.write_back(state) == Steinberg::kResultOk);
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'I{Component,EditController}::getState()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) {
|
|
if (!other) {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IConnectionPointProxy::connect()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
|
|
// When the host is trying to connect two plugin proxy objects, we can just
|
|
// identify the other object by its instance IDs and then connect the
|
|
// objects in the Wine plugin host directly. If this is not possible, we'll
|
|
// still try to bypass the proxy and connect the object directly. That goes
|
|
// against the principles of yabridge, but the alternative is nearly
|
|
// impossible to pull off correctly because FabFilter VST3 plugins will call
|
|
// `IConnectionPoint::notify()` from the GUI thread to communicate between
|
|
// the processor and the edit controller. If we try to handle those mutually
|
|
// recursive function calls from the GUI thread, then we'll still run into
|
|
// issues when using multiple instances of the plugin. If we cannot figure
|
|
// out which object the plugins are connected to, we'll still proxy the
|
|
// host's connection proxy.
|
|
if (auto other_instance = dynamic_cast<Vst3PluginProxy*>(other)) {
|
|
connected_instance_id = other_instance->instance_id();
|
|
|
|
return bridge.send_message(
|
|
YaConnectionPoint::Connect{.instance_id = instance_id(),
|
|
.other = other_instance->instance_id()});
|
|
}
|
|
|
|
// As mentioned above, we'll first try to bypass the connection point proxy
|
|
// and connect the objects directly
|
|
if (host_application) {
|
|
Steinberg::IPtr<Steinberg::Vst::IMessage> message =
|
|
Steinberg::owned(Steinberg::Vst::allocateMessage(host_application));
|
|
if (message) {
|
|
message->setMessageID(other_instance_message_id);
|
|
|
|
Steinberg::IPtr<Steinberg::Vst::IAttributeList> attributes =
|
|
message->getAttributes();
|
|
if (attributes) {
|
|
attributes->setInt(
|
|
other_instance_pointer_attribute,
|
|
static_cast<int64>(reinterpret_cast<size_t>(this)));
|
|
}
|
|
|
|
// If we are connected with another object instance from this
|
|
// plugin, `connected_instance_id` should now be set
|
|
other->notify(message);
|
|
if (connected_instance_id) {
|
|
return bridge.send_message(YaConnectionPoint::Connect{
|
|
.instance_id = instance_id(),
|
|
.other = *connected_instance_id});
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we cannot bypass the proxy, we'll just proxy the host's proxy
|
|
connection_point_proxy = other;
|
|
|
|
return bridge.send_message(YaConnectionPoint::Connect{
|
|
.instance_id = instance_id(),
|
|
.other =
|
|
Vst3ConnectionPointProxy::ConstructArgs(other, instance_id())});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::disconnect(IConnectionPoint* /*other*/) {
|
|
// See `Vst3PluginProxyImpl::connect()`, if we directly connected two
|
|
// instances we'll also disconnect them again
|
|
if (connected_instance_id) {
|
|
return bridge.send_message(YaConnectionPoint::Disconnect{
|
|
.instance_id = instance_id(),
|
|
.other_instance_id = *connected_instance_id});
|
|
} else {
|
|
const tresult result = bridge.send_message(
|
|
YaConnectionPoint::Disconnect{.instance_id = instance_id(),
|
|
.other_instance_id = std::nullopt});
|
|
connection_point_proxy.reset();
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) {
|
|
// Since there is no way to enumerate over all values in an
|
|
// `IAttributeList`, we can only support relaying messages that were sent by
|
|
// our own objects. Additionally, the `IMessage*` we end up passing to the
|
|
// plugin needs to have the same lifetime as the original object, because
|
|
// some plugins are being a bit naughty. That's why we pass around a pointer
|
|
// to the original message object.
|
|
// All of this is only needed to support hosts that place a connection proxy
|
|
// between two objects instead of connecting them directly. If the objects
|
|
// are connected directly we also connected them directly on the Wine side,
|
|
// so we don't have to do any additional when those objects pass through
|
|
// messages.
|
|
if (auto message_ptr = dynamic_cast<YaMessagePtr*>(message)) {
|
|
return bridge.send_message(YaConnectionPoint::Notify{
|
|
.instance_id = instance_id(), .message_ptr = *message_ptr});
|
|
}
|
|
|
|
// NOTE: As mentioned above, when the host (or specifically, Ardour or
|
|
// Mixbus) calls `IConnectionPoint::connect()`, we'll try to bypass
|
|
// the connection proxy since this creates some difficult situations
|
|
// when plugins start calling `IConnectionPoint::notify()` from the
|
|
// GUI thread
|
|
if (message &&
|
|
strcmp(message->getMessageID(), other_instance_message_id) == 0) {
|
|
Steinberg::IPtr<Steinberg::Vst::IAttributeList> attributes =
|
|
message->getAttributes();
|
|
if (attributes) {
|
|
int64 other_object_ptr;
|
|
if (attributes->getInt(other_instance_pointer_attribute,
|
|
other_object_ptr) == Steinberg::kResultOk &&
|
|
other_object_ptr != 0) {
|
|
Vst3PluginProxyImpl& other_object =
|
|
*reinterpret_cast<Vst3PluginProxyImpl*>(
|
|
static_cast<size_t>(other_object_ptr));
|
|
|
|
other_object.connected_instance_id = instance_id();
|
|
|
|
return Steinberg::kResultOk;
|
|
}
|
|
}
|
|
}
|
|
|
|
bridge.logger.log(
|
|
"WARNING: Unknown message type passed to "
|
|
"'IConnectionPoint::notify()', ignoring");
|
|
return Steinberg::kNotImplemented;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) {
|
|
if (state) {
|
|
return bridge.send_message(YaEditController::SetComponentState{
|
|
.instance_id = instance_id(), .state = state});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IEditController::setComponentState()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
|
const auto request =
|
|
YaEditController::GetParameterCount{.instance_id = instance_id()};
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
if (function_result_cache.parameter_count) {
|
|
const bool log_response = bridge.logger.log_request(true, request);
|
|
if (log_response) {
|
|
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);
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
function_result_cache.parameter_count = result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo(
|
|
int32 paramIndex,
|
|
Steinberg::Vst::ParameterInfo& info /*out*/) {
|
|
const auto request = YaEditController::GetParameterInfo{
|
|
.instance_id = instance_id(), .param_index = paramIndex};
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
if (auto it = function_result_cache.parameter_info.find(paramIndex);
|
|
it != function_result_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, .info = it->second},
|
|
true);
|
|
}
|
|
|
|
info = it->second;
|
|
|
|
return Steinberg::kResultOk;
|
|
}
|
|
}
|
|
|
|
const GetParameterInfoResponse response = bridge.send_message(request);
|
|
|
|
info = response.info;
|
|
|
|
{
|
|
std::lock_guard lock(function_result_cache_mutex);
|
|
function_result_cache.parameter_info[paramIndex] = response.info;
|
|
}
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue(
|
|
Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue valueNormalized /*in*/,
|
|
Steinberg::Vst::String128 string /*out*/) {
|
|
if (string) {
|
|
const GetParamStringByValueResponse response =
|
|
bridge.send_message(YaEditController::GetParamStringByValue{
|
|
.instance_id = instance_id(),
|
|
.id = id,
|
|
.value_normalized = valueNormalized});
|
|
|
|
std::copy(response.string.begin(), response.string.end(), string);
|
|
string[response.string.size()] = 0;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IEditController::getParamStringByValue()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParamValueByString(
|
|
Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::TChar* string /*in*/,
|
|
Steinberg::Vst::ParamValue& valueNormalized /*out*/) {
|
|
if (string) {
|
|
const GetParamValueByStringResponse response =
|
|
bridge.send_message(YaEditController::GetParamValueByString{
|
|
.instance_id = instance_id(), .id = id, .string = string});
|
|
|
|
valueNormalized = response.value_normalized;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IEditController::getParamValueByString()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
Vst3PluginProxyImpl::normalizedParamToPlain(
|
|
Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue valueNormalized) {
|
|
return bridge.send_message(YaEditController::NormalizedParamToPlain{
|
|
.instance_id = instance_id(),
|
|
.id = id,
|
|
.value_normalized = valueNormalized});
|
|
}
|
|
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
Vst3PluginProxyImpl::plainParamToNormalized(
|
|
Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue plainValue) {
|
|
return bridge.send_message(YaEditController::PlainParamToNormalized{
|
|
.instance_id = instance_id(), .id = id, .plain_value = plainValue});
|
|
}
|
|
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
Vst3PluginProxyImpl::getParamNormalized(Steinberg::Vst::ParamID id) {
|
|
return bridge.send_message(YaEditController::GetParamNormalized{
|
|
.instance_id = instance_id(), .id = id});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue value) {
|
|
return bridge.send_message(YaEditController::SetParamNormalized{
|
|
.instance_id = instance_id(), .id = id, .value = value});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler(
|
|
Steinberg::Vst::IComponentHandler* handler) {
|
|
// Null pointers are valid here going from the reference implementations in
|
|
// the SDK
|
|
if (handler) {
|
|
// We'll store the pointer for when the plugin later makes a callback to
|
|
// this component handler
|
|
component_handler = handler;
|
|
|
|
// Automatically converted smart pointers for when the plugin performs a
|
|
// callback later
|
|
component_handler_2 = component_handler;
|
|
component_handler_3 = component_handler;
|
|
component_handler_bus_activation = component_handler;
|
|
progress = component_handler;
|
|
unit_handler = component_handler;
|
|
unit_handler_2 = component_handler;
|
|
|
|
return bridge.send_message(YaEditController::SetComponentHandler{
|
|
.instance_id = instance_id(),
|
|
.component_handler_proxy_args =
|
|
Vst3ComponentHandlerProxy::ConstructArgs(component_handler,
|
|
instance_id())});
|
|
} else {
|
|
component_handler = nullptr;
|
|
|
|
component_handler_2 = nullptr;
|
|
component_handler_3 = nullptr;
|
|
component_handler_bus_activation = nullptr;
|
|
progress = nullptr;
|
|
unit_handler = nullptr;
|
|
unit_handler_2 = nullptr;
|
|
|
|
return bridge.send_message(YaEditController::SetComponentHandler{
|
|
.instance_id = instance_id(),
|
|
.component_handler_proxy_args = std::nullopt});
|
|
}
|
|
}
|
|
|
|
Steinberg::IPlugView* PLUGIN_API
|
|
Vst3PluginProxyImpl::createView(Steinberg::FIDString name) {
|
|
if (name) {
|
|
CreateViewResponse response =
|
|
bridge.send_message(YaEditController::CreateView{
|
|
.instance_id = instance_id(), .name = name});
|
|
|
|
if (response.plug_view_args) {
|
|
// The host should manage this. Returning raw pointers feels scary.
|
|
auto plug_view_proxy = new Vst3PlugViewProxyImpl(
|
|
bridge, std::move(*response.plug_view_args));
|
|
|
|
// We also need to store an (unmanaged, since we don't want to
|
|
// affect the reference counting) pointer to this to be able to
|
|
// handle calls to `IPlugFrame::resizeView()` in the future
|
|
last_created_plug_view = plug_view_proxy;
|
|
|
|
return plug_view_proxy;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IEditController::createView()'");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) {
|
|
return bridge.send_message(YaEditController2::SetKnobMode{
|
|
.instance_id = instance_id(), .mode = mode});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) {
|
|
return bridge.send_message(YaEditController2::OpenHelp{
|
|
.instance_id = instance_id(), .only_check = onlyCheck});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) {
|
|
return bridge.send_message(YaEditController2::OpenAboutBox{
|
|
.instance_id = instance_id(), .only_check = onlyCheck});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::beginEditFromHost(Steinberg::Vst::ParamID paramID) {
|
|
return bridge.send_message(YaEditControllerHostEditing::BeginEditFromHost{
|
|
.instance_id = instance_id(), .param_id = paramID});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::endEditFromHost(Steinberg::Vst::ParamID paramID) {
|
|
return bridge.send_message(YaEditControllerHostEditing::EndEditFromHost{
|
|
.instance_id = instance_id(), .param_id = paramID});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::setChannelContextInfos(
|
|
Steinberg::Vst::IAttributeList* list) {
|
|
if (list) {
|
|
// NOTE: After getting and setting state, the plugin may want to resize
|
|
// to match its old size. The DMG plugins tend to delay this
|
|
// request until after the state has been set, but at that point
|
|
// in time REAPER will also send channel context info. Both of
|
|
// these things need to be handled on the GUI thread on their
|
|
// receiving sides, resulting in a deadlock without this mutual
|
|
// recursion.
|
|
return bridge.send_mutually_recursive_message(
|
|
YaInfoListener::SetChannelContextInfos{
|
|
.instance_id = instance_id(),
|
|
.list = YaAttributeList::read_channel_context(list)});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IInfoListener::setChannelContextInfos()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
int32 PLUGIN_API Vst3PluginProxyImpl::getKeyswitchCount(int32 busIndex,
|
|
int16 channel) {
|
|
return bridge.send_message(
|
|
YaKeyswitchController::GetKeyswitchCount{.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getKeyswitchInfo(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
int32 keySwitchIndex,
|
|
Steinberg::Vst::KeyswitchInfo& info /*out*/) {
|
|
const GetKeyswitchInfoResponse response =
|
|
bridge.send_message(YaKeyswitchController::GetKeyswitchInfo{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.key_switch_index = keySwitchIndex});
|
|
|
|
info = response.info;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::onLiveMIDIControllerInput(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::CtrlNumber midiCC) {
|
|
return bridge.send_message(
|
|
YaMidiLearn::OnLiveMIDIControllerInput{.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.midi_cc = midiCC});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getMidiControllerAssignment(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::CtrlNumber midiControllerNumber,
|
|
Steinberg::Vst::ParamID& id /*out*/) {
|
|
const GetMidiControllerAssignmentResponse response =
|
|
bridge.send_message(YaMidiMapping::GetMidiControllerAssignment{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.midi_controller_number = midiControllerNumber});
|
|
|
|
id = response.id;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
int32 PLUGIN_API Vst3PluginProxyImpl::getNoteExpressionCount(int32 busIndex,
|
|
int16 channel) {
|
|
return bridge.send_message(
|
|
YaNoteExpressionController::GetNoteExpressionCount{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getNoteExpressionInfo(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
int32 noteExpressionIndex,
|
|
Steinberg::Vst::NoteExpressionTypeInfo& info /*out*/) {
|
|
const GetNoteExpressionInfoResponse response =
|
|
bridge.send_message(YaNoteExpressionController::GetNoteExpressionInfo{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.note_expression_index = noteExpressionIndex});
|
|
|
|
info = response.info;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getNoteExpressionStringByValue(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::NoteExpressionTypeID id,
|
|
Steinberg::Vst::NoteExpressionValue valueNormalized /*in*/,
|
|
Steinberg::Vst::String128 string /*out*/) {
|
|
if (string) {
|
|
const GetNoteExpressionStringByValueResponse response =
|
|
bridge.send_message(
|
|
YaNoteExpressionController::GetNoteExpressionStringByValue{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.id = id,
|
|
.value_normalized = valueNormalized});
|
|
|
|
std::copy(response.string.begin(), response.string.end(), string);
|
|
string[response.string.size()] = 0;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'INoteExpressionController::getNoteExpressionStringByValue()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getNoteExpressionValueByString(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::NoteExpressionTypeID id,
|
|
const Steinberg::Vst::TChar* string /*in*/,
|
|
Steinberg::Vst::NoteExpressionValue& valueNormalized /*out*/) {
|
|
if (string) {
|
|
const GetNoteExpressionValueByStringResponse response =
|
|
bridge.send_message(
|
|
YaNoteExpressionController::GetNoteExpressionValueByString{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.id = id,
|
|
.string = string});
|
|
|
|
valueNormalized = response.value_normalized;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'INoteExpressionController::getNoteExpressionValueByString()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getPhysicalUIMapping(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::PhysicalUIMapList& list) {
|
|
const GetNotePhysicalUIMappingResponse response = bridge.send_message(
|
|
YaNoteExpressionPhysicalUIMapping::GetNotePhysicalUIMapping{
|
|
.instance_id = instance_id(),
|
|
.bus_index = busIndex,
|
|
.channel = channel,
|
|
.list = list});
|
|
|
|
response.list.write_back(list);
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getParameterIDFromFunctionName(
|
|
Steinberg::Vst::UnitID unitID,
|
|
Steinberg::FIDString functionName,
|
|
Steinberg::Vst::ParamID& paramID) {
|
|
if (functionName) {
|
|
const GetParameterIDFromFunctionNameResponse response =
|
|
bridge.send_message(
|
|
YaParameterFunctionName::GetParameterIDFromFunctionName{
|
|
.instance_id = instance_id(),
|
|
.unit_id = unitID,
|
|
.function_name = functionName});
|
|
|
|
paramID = response.param_id;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IParameterFunctionName::getParameterIDFromFunctionName()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) {
|
|
if (context) {
|
|
// We will create a proxy object that that supports all the same
|
|
// interfaces as `context`, and then we'll store `context` in this
|
|
// object. We can then use it to handle callbacks made by the Windows
|
|
// VST3 plugin to this context.
|
|
host_context = context;
|
|
|
|
// Automatically converted smart pointers for when the plugin performs a
|
|
// callback later
|
|
host_application = host_context;
|
|
plug_interface_support = host_context;
|
|
|
|
return bridge.send_message(YaPluginBase::Initialize{
|
|
.instance_id = instance_id(),
|
|
.host_context_args = Vst3HostContextProxy::ConstructArgs(
|
|
host_context, instance_id())});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to 'IPluginBase::initialize()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::terminate() {
|
|
return bridge.send_message(
|
|
YaPluginBase::Terminate{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getPrefetchableSupport(
|
|
Steinberg::Vst::PrefetchableSupport& prefetchable /*out*/) {
|
|
const GetPrefetchableSupportResponse response =
|
|
bridge.send_audio_processor_message(
|
|
YaPrefetchableSupport::GetPrefetchableSupport{.instance_id =
|
|
instance_id()});
|
|
|
|
prefetchable = response.prefetchable;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
uint32 PLUGIN_API Vst3PluginProxyImpl::getProcessContextRequirements() {
|
|
return bridge.send_message(
|
|
YaProcessContextRequirements::GetProcessContextRequirements{
|
|
.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::programDataSupported(
|
|
Steinberg::Vst::ProgramListID listId) {
|
|
return bridge.send_message(YaProgramListData::ProgramDataSupported{
|
|
.instance_id = instance_id(), .list_id = listId});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getProgramData(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) {
|
|
if (data) {
|
|
const GetProgramDataResponse response = bridge.send_message(
|
|
YaProgramListData::GetProgramData{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex,
|
|
.data = data});
|
|
|
|
assert(response.data.write_back(data) == Steinberg::kResultOk);
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IProgramListData::getProgramData()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setProgramData(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) {
|
|
if (data) {
|
|
return bridge.send_message(
|
|
YaProgramListData::SetProgramData{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex,
|
|
.data = data});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IProgramListData::setProgramData()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::unitDataSupported(Steinberg::Vst::UnitID unitId) {
|
|
return bridge.send_message(YaUnitData::UnitDataSupported{
|
|
.instance_id = instance_id(), .unit_id = unitId});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getUnitData(Steinberg::Vst::UnitID unitId,
|
|
Steinberg::IBStream* data) {
|
|
if (data) {
|
|
const GetUnitDataResponse response =
|
|
bridge.send_message(YaUnitData::GetUnitData{
|
|
.instance_id = instance_id(), .unit_id = unitId, .data = data});
|
|
|
|
assert(response.data.write_back(data) == Steinberg::kResultOk);
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to 'IUnitData::getUnitData()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setUnitData(Steinberg::Vst::UnitID unitId,
|
|
Steinberg::IBStream* data) {
|
|
if (data) {
|
|
return bridge.send_message(YaUnitData::SetUnitData{
|
|
.instance_id = instance_id(), .unit_id = unitId, .data = data});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to 'IUnitData::setUnitData()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() {
|
|
return bridge.send_message(
|
|
YaUnitInfo::GetUnitCount{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getUnitInfo(int32 unitIndex,
|
|
Steinberg::Vst::UnitInfo& info /*out*/) {
|
|
const GetUnitInfoResponse response =
|
|
bridge.send_message(YaUnitInfo::GetUnitInfo{
|
|
.instance_id = instance_id(), .unit_index = unitIndex});
|
|
|
|
info = response.info;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
int32 PLUGIN_API Vst3PluginProxyImpl::getProgramListCount() {
|
|
return bridge.send_message(
|
|
YaUnitInfo::GetProgramListCount{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getProgramListInfo(
|
|
int32 listIndex,
|
|
Steinberg::Vst::ProgramListInfo& info /*out*/) {
|
|
const GetProgramListInfoResponse response =
|
|
bridge.send_message(YaUnitInfo::GetProgramListInfo{
|
|
.instance_id = instance_id(), .list_index = listIndex});
|
|
|
|
info = response.info;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getProgramName(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::Vst::String128 name /*out*/) {
|
|
if (name) {
|
|
const GetProgramNameResponse response = bridge.send_message(
|
|
YaUnitInfo::GetProgramName{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex});
|
|
|
|
std::copy(response.name.begin(), response.name.end(), name);
|
|
name[response.name.size()] = 0;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to 'IUnitInfo::getProgramName()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getProgramInfo(
|
|
Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::Vst::CString attributeId /*in*/,
|
|
Steinberg::Vst::String128 attributeValue /*out*/) {
|
|
if (attributeId && attributeValue) {
|
|
const GetProgramInfoResponse response = bridge.send_message(
|
|
YaUnitInfo::GetProgramInfo{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex,
|
|
.attribute_id = attributeId});
|
|
|
|
std::copy(response.attribute_value.begin(),
|
|
response.attribute_value.end(), attributeValue);
|
|
attributeValue[response.attribute_value.size()] = 0;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to 'IUnitInfo::getProgramInfo()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::hasProgramPitchNames(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex) {
|
|
return bridge.send_message(
|
|
YaUnitInfo::HasProgramPitchNames{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex});
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName(
|
|
Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
int16 midiPitch,
|
|
Steinberg::Vst::String128 name /*out*/) {
|
|
if (name) {
|
|
const GetProgramPitchNameResponse response = bridge.send_message(
|
|
YaUnitInfo::GetProgramPitchName{.instance_id = instance_id(),
|
|
.list_id = listId,
|
|
.program_index = programIndex,
|
|
.midi_pitch = midiPitch});
|
|
|
|
std::copy(response.name.begin(), response.name.end(), name);
|
|
name[response.name.size()] = 0;
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IUnitInfo::getProgramPitchName()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
Steinberg::Vst::UnitID PLUGIN_API Vst3PluginProxyImpl::getSelectedUnit() {
|
|
return bridge.send_message(
|
|
YaUnitInfo::GetSelectedUnit{.instance_id = instance_id()});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::selectUnit(Steinberg::Vst::UnitID unitId) {
|
|
return bridge.send_message(YaUnitInfo::SelectUnit{
|
|
.instance_id = instance_id(), .unit_id = unitId});
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::getUnitByBus(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 busIndex,
|
|
int32 channel,
|
|
Steinberg::Vst::UnitID& unitId /*out*/) {
|
|
const GetUnitByBusResponse response = bridge.send_message(
|
|
YaUnitInfo::GetUnitByBus{.instance_id = instance_id(),
|
|
.type = type,
|
|
.dir = dir,
|
|
.bus_index = busIndex,
|
|
.channel = channel});
|
|
|
|
unitId = response.unit_id;
|
|
|
|
return response.result;
|
|
}
|
|
|
|
tresult PLUGIN_API
|
|
Vst3PluginProxyImpl::setUnitProgramData(int32 listOrUnitId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) {
|
|
if (data) {
|
|
return bridge.send_message(
|
|
YaUnitInfo::SetUnitProgramData{.instance_id = instance_id(),
|
|
.list_or_unit_id = listOrUnitId,
|
|
.program_index = programIndex,
|
|
.data = data});
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IUnitInfo::setUnitProgramData()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API Vst3PluginProxyImpl::getXmlRepresentationStream(
|
|
Steinberg::Vst::RepresentationInfo& info /*in*/,
|
|
Steinberg::IBStream* stream /*out*/) {
|
|
if (stream) {
|
|
const GetXmlRepresentationStreamResponse response = bridge.send_message(
|
|
YaXmlRepresentationController::GetXmlRepresentationStream{
|
|
.instance_id = instance_id(), .info = info, .stream = stream});
|
|
|
|
response.stream.write_back(stream);
|
|
|
|
return response.result;
|
|
} else {
|
|
bridge.logger.log(
|
|
"WARNING: Null pointer passed to "
|
|
"'IXmlRepresentationController::getXmlRepresentationStream()'");
|
|
return Steinberg::kInvalidArgument;
|
|
}
|
|
}
|
|
|
|
void Vst3PluginProxyImpl::clear_bus_cache() noexcept {
|
|
std::lock_guard lock(processing_bus_cache_mutex);
|
|
if (processing_bus_cache) {
|
|
processing_bus_cache.emplace();
|
|
}
|
|
}
|