// 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 . #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 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 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( inputs, &inputs[numIns]) : std::vector()), .num_ins = numIns, .outputs = (outputs ? std::vector( outputs, &outputs[numOuts]) : std::vector()), .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>(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>( min_process_time) .count()) + " us"); bridge.logger.log( "Max processing time: " + std::to_string(std::chrono::duration_cast< std::chrono::duration>( max_process_time) .count()) + " us"); } else { bridge.logger.log("Min processing time: "); bridge.logger.log("Max processing time: "); } 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 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(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 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 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(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 message = Steinberg::owned(Steinberg::Vst::allocateMessage(host_application)); if (message) { message->setMessageID(other_instance_message_id); Steinberg::IPtr attributes = message->getAttributes(); if (attributes) { attributes->setInt( other_instance_pointer_attribute, static_cast(reinterpret_cast(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(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 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( static_cast(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(); } }