Files
yabridge/src/plugin/bridges/vst3-impls/plugin-proxy.cpp
T
Robbert van der Helm b5dd806b2d 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.
2021-01-30 22:24:05 +01:00

1169 lines
43 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"
Vst3PluginProxyImpl::ContextMenu::ContextMenu(
Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu)
: menu(menu) {}
Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
Vst3PluginProxy::ConstructArgs&& args)
: Vst3PluginProxy(std::move(args)), bridge(bridge) {
bridge.register_plugin_proxy(*this);
}
Vst3PluginProxyImpl::~Vst3PluginProxyImpl() {
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, std::move(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() {
clear_bus_cache();
clear_parameter_cache();
}
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 = arr});
arr = response.updated_arr;
return response.result;
}
tresult PLUGIN_API
Vst3PluginProxyImpl::canProcessSampleSize(int32 symbolicSampleSize) {
return bridge.send_audio_processor_message(
YaAudioProcessor::CanProcessSampleSize{
.instance_id = instance_id(),
.symbolic_sample_size = symbolicSampleSize});
}
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) {
return bridge.send_audio_processor_message(
YaAudioProcessor::SetupProcessing{.instance_id = instance_id(),
.setup = setup});
}
tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) {
// REAPER will 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
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) {
// 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 = std::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;
}
// TODO: Check whether reusing a `YaProcessData` object make a difference in
// terms of performance
ProcessResponse response =
bridge.send_audio_processor_message(YaAudioProcessor::Process{
.instance_id = instance_id(),
.data = data,
.new_realtime_priority = new_realtime_priority});
response.output_data.write_back_outputs(data);
return 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};
// During processing we'll cache this info to work around an implementation
// issue in REAPER
std::tuple<Steinberg::Vst::MediaType, Steinberg::Vst::BusDirection> args{
type, dir};
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);
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,
.bus = bus};
// During processing we'll cache this info to work around an implementation
// issue in REAPER
std::tuple<Steinberg::Vst::MediaType, Steinberg::Vst::BusDirection, int32>
args{type, dir, index};
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(true,
YaComponent::GetBusInfo::Response{
.result = Steinberg::kResultOk,
.updated_bus = it->second},
true);
}
bus = it->second;
return Steinberg::kResultOk;
}
}
const GetBusInfoResponse response =
bridge.send_audio_processor_message(request);
bus = response.updated_bus;
if (processing_bus_cache) {
processing_bus_cache->bus_info[args] = response.updated_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,
.out_info = outInfo});
inInfo = response.updated_in_info;
outInfo = response.updated_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 have 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()`
return bridge.send_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()`
const GetStateResponse response =
bridge.send_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) {
// 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. Otherwise we'll have to set up
// a proxy for the host's connection proxy so the messages can be routed
// through that.
if (auto other_proxy = dynamic_cast<Vst3PluginProxy*>(other)) {
return bridge.send_message(YaConnectionPoint::Connect{
.instance_id = instance_id(), .other = other_proxy->instance_id()});
} else {
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 (auto other_proxy = dynamic_cast<Vst3PluginProxy*>(other)) {
return bridge.send_message(YaConnectionPoint::Disconnect{
.instance_id = instance_id(),
.other_instance_id = other_proxy->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});
} else {
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()};
// 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(
int32 paramIndex,
Steinberg::Vst::ParameterInfo& info /*out*/) {
const auto request = YaEditController::GetParameterInfo{
.instance_id = instance_id(), .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;
parameter_info_cache.parameter_info[paramIndex] = response.updated_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) {
return bridge.send_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() {
if (processing_bus_cache) {
processing_bus_cache.emplace();
}
}
void Vst3PluginProxyImpl::clear_parameter_cache() {
parameter_info_cache = ParameterInfoCache{};
}