mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 12:10:09 +02:00
f5b4a28bd0
This works around an issue with REAPER. During every processing cycle REAPER would query how many input and output busses we have, and it would then enumerate over all of those busses. This meant that if a VST3 plugin has 32 output busses, then REAPER will do 34 extra function calls before processing audio.
427 lines
18 KiB
C++
427 lines
18 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/>.
|
|
|
|
#pragma once
|
|
|
|
#include "../vst3.h"
|
|
#include "plug-view-proxy.h"
|
|
|
|
class Vst3PluginProxyImpl : public Vst3PluginProxy {
|
|
public:
|
|
Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
|
|
Vst3PluginProxy::ConstructArgs&& args);
|
|
|
|
/**
|
|
* When the reference count reaches zero and this destructor is called,
|
|
* we'll send a request to the Wine plugin host to destroy the corresponding
|
|
* object.
|
|
*/
|
|
~Vst3PluginProxyImpl();
|
|
|
|
/**
|
|
* We'll override the query interface to log queries for interfaces we do
|
|
* not (yet) support.
|
|
*/
|
|
tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid,
|
|
void** obj) override;
|
|
|
|
/**
|
|
* Add a context menu created by a call to
|
|
* `IComponentHandler3::createContextMenu` to our list of registered cotnext
|
|
* menus. This way we can refer to it later when the plugin calls a function
|
|
* on the proxy object we'll create for it.
|
|
*/
|
|
size_t register_context_menu(
|
|
Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu);
|
|
|
|
/**
|
|
* 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 unregister_context_menu(size_t context_menu_id);
|
|
|
|
// From `IAudioPresentationLatency`
|
|
tresult PLUGIN_API
|
|
setAudioPresentationLatencySamples(Steinberg::Vst::BusDirection dir,
|
|
int32 busIndex,
|
|
uint32 latencyInSamples) override;
|
|
|
|
// From `IAudioProcessor`
|
|
tresult PLUGIN_API
|
|
setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs,
|
|
int32 numIns,
|
|
Steinberg::Vst::SpeakerArrangement* outputs,
|
|
int32 numOuts) override;
|
|
tresult PLUGIN_API
|
|
getBusArrangement(Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
Steinberg::Vst::SpeakerArrangement& arr) override;
|
|
tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override;
|
|
uint32 PLUGIN_API getLatencySamples() override;
|
|
tresult PLUGIN_API
|
|
setupProcessing(Steinberg::Vst::ProcessSetup& setup) override;
|
|
tresult PLUGIN_API setProcessing(TBool state) override;
|
|
tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override;
|
|
uint32 PLUGIN_API getTailSamples() override;
|
|
|
|
// From `IAutomationState`
|
|
tresult PLUGIN_API setAutomationState(int32 state) override;
|
|
|
|
// From `IComponent`
|
|
tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override;
|
|
tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override;
|
|
int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir) override;
|
|
tresult PLUGIN_API
|
|
getBusInfo(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
Steinberg::Vst::BusInfo& bus /*out*/) override;
|
|
tresult PLUGIN_API
|
|
getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo,
|
|
Steinberg::Vst::RoutingInfo& outInfo /*out*/) override;
|
|
tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 index,
|
|
TBool state) override;
|
|
tresult PLUGIN_API setActive(TBool state) override;
|
|
tresult PLUGIN_API setState(Steinberg::IBStream* state) override;
|
|
tresult PLUGIN_API getState(Steinberg::IBStream* state) override;
|
|
|
|
// From `IConnectionPoint`
|
|
tresult PLUGIN_API connect(IConnectionPoint* other) override;
|
|
tresult PLUGIN_API disconnect(IConnectionPoint* other) override;
|
|
tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override;
|
|
|
|
// From `IEditController`
|
|
tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override;
|
|
// `IEditController` also contains `getState()` and `setState()` functions.
|
|
// These are identical to those defiend in `IComponent` and they're thus
|
|
// handled in in the same function.
|
|
int32 PLUGIN_API getParameterCount() override;
|
|
tresult PLUGIN_API
|
|
getParameterInfo(int32 paramIndex,
|
|
Steinberg::Vst::ParameterInfo& info /*out*/) override;
|
|
tresult PLUGIN_API
|
|
getParamStringByValue(Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue valueNormalized /*in*/,
|
|
Steinberg::Vst::String128 string /*out*/) override;
|
|
tresult PLUGIN_API getParamValueByString(
|
|
Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::TChar* string /*in*/,
|
|
Steinberg::Vst::ParamValue& valueNormalized /*out*/) override;
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
normalizedParamToPlain(Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue valueNormalized) override;
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
plainParamToNormalized(Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue plainValue) override;
|
|
Steinberg::Vst::ParamValue PLUGIN_API
|
|
getParamNormalized(Steinberg::Vst::ParamID id) override;
|
|
tresult PLUGIN_API
|
|
setParamNormalized(Steinberg::Vst::ParamID id,
|
|
Steinberg::Vst::ParamValue value) override;
|
|
tresult PLUGIN_API
|
|
setComponentHandler(Steinberg::Vst::IComponentHandler* handler) override;
|
|
Steinberg::IPlugView* PLUGIN_API
|
|
createView(Steinberg::FIDString name) override;
|
|
|
|
// From `IEditController2`
|
|
tresult PLUGIN_API setKnobMode(Steinberg::Vst::KnobMode mode) override;
|
|
tresult PLUGIN_API openHelp(TBool onlyCheck) override;
|
|
tresult PLUGIN_API openAboutBox(TBool onlyCheck) override;
|
|
|
|
// From `IEditControllerHostEditing`
|
|
tresult PLUGIN_API
|
|
beginEditFromHost(Steinberg::Vst::ParamID paramID) override;
|
|
tresult PLUGIN_API
|
|
endEditFromHost(Steinberg::Vst::ParamID paramID) override;
|
|
|
|
// From `IInfoListener`
|
|
tresult PLUGIN_API
|
|
setChannelContextInfos(Steinberg::Vst::IAttributeList* list) override;
|
|
|
|
// From `IKeyswitchController`
|
|
int32 PLUGIN_API getKeyswitchCount(int32 busIndex, int16 channel) override;
|
|
tresult PLUGIN_API
|
|
getKeyswitchInfo(int32 busIndex,
|
|
int16 channel,
|
|
int32 keySwitchIndex,
|
|
Steinberg::Vst::KeyswitchInfo& info /*out*/) override;
|
|
|
|
// From `IMidiLearn`
|
|
tresult PLUGIN_API
|
|
onLiveMIDIControllerInput(int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::CtrlNumber midiCC) override;
|
|
|
|
// From `IMidiMapping`
|
|
tresult PLUGIN_API
|
|
getMidiControllerAssignment(int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::CtrlNumber midiControllerNumber,
|
|
Steinberg::Vst::ParamID& id /*out*/) override;
|
|
|
|
// From `INoteExpressionController`
|
|
int32 PLUGIN_API getNoteExpressionCount(int32 busIndex,
|
|
int16 channel) override;
|
|
tresult PLUGIN_API getNoteExpressionInfo(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
int32 noteExpressionIndex,
|
|
Steinberg::Vst::NoteExpressionTypeInfo& info /*out*/) override;
|
|
tresult PLUGIN_API getNoteExpressionStringByValue(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::NoteExpressionTypeID id,
|
|
Steinberg::Vst::NoteExpressionValue valueNormalized /*in*/,
|
|
Steinberg::Vst::String128 string /*out*/) override;
|
|
tresult PLUGIN_API getNoteExpressionValueByString(
|
|
int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::NoteExpressionTypeID id,
|
|
const Steinberg::Vst::TChar* string /*in*/,
|
|
Steinberg::Vst::NoteExpressionValue& valueNormalized /*out*/) override;
|
|
|
|
// From `INoteExpressionPhysicalUIMapping`
|
|
tresult PLUGIN_API
|
|
getPhysicalUIMapping(int32 busIndex,
|
|
int16 channel,
|
|
Steinberg::Vst::PhysicalUIMapList& list) override;
|
|
|
|
// From `IParameterFunctionName`
|
|
tresult PLUGIN_API
|
|
getParameterIDFromFunctionName(Steinberg::Vst::UnitID unitID,
|
|
Steinberg::FIDString functionName,
|
|
Steinberg::Vst::ParamID& paramID) override;
|
|
|
|
// From `IPluginBase`
|
|
tresult PLUGIN_API initialize(FUnknown* context) override;
|
|
tresult PLUGIN_API terminate() override;
|
|
|
|
// From `IPrefetchableSupport`
|
|
tresult PLUGIN_API getPrefetchableSupport(
|
|
Steinberg::Vst::PrefetchableSupport& prefetchable /*out*/) override;
|
|
|
|
// From `IProcessContextRequirements`
|
|
uint32 PLUGIN_API getProcessContextRequirements() override;
|
|
|
|
// From `IProgramListData`
|
|
tresult PLUGIN_API
|
|
programDataSupported(Steinberg::Vst::ProgramListID listId) override;
|
|
tresult PLUGIN_API getProgramData(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) override;
|
|
tresult PLUGIN_API setProgramData(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) override;
|
|
|
|
// From `IUnitData`
|
|
tresult PLUGIN_API
|
|
unitDataSupported(Steinberg::Vst::UnitID unitId) override;
|
|
tresult PLUGIN_API getUnitData(Steinberg::Vst::UnitID unitId,
|
|
Steinberg::IBStream* data) override;
|
|
tresult PLUGIN_API setUnitData(Steinberg::Vst::UnitID unitId,
|
|
Steinberg::IBStream* data) override;
|
|
|
|
// From `IUnitInfo`
|
|
int32 PLUGIN_API getUnitCount() override;
|
|
tresult PLUGIN_API
|
|
getUnitInfo(int32 unitIndex,
|
|
Steinberg::Vst::UnitInfo& info /*out*/) override;
|
|
int32 PLUGIN_API getProgramListCount() override;
|
|
tresult PLUGIN_API
|
|
getProgramListInfo(int32 listIndex,
|
|
Steinberg::Vst::ProgramListInfo& info /*out*/) override;
|
|
tresult PLUGIN_API
|
|
getProgramName(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::Vst::String128 name /*out*/) override;
|
|
tresult PLUGIN_API
|
|
getProgramInfo(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
Steinberg::Vst::CString attributeId /*in*/,
|
|
Steinberg::Vst::String128 attributeValue /*out*/) override;
|
|
tresult PLUGIN_API
|
|
hasProgramPitchNames(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex) override;
|
|
tresult PLUGIN_API
|
|
getProgramPitchName(Steinberg::Vst::ProgramListID listId,
|
|
int32 programIndex,
|
|
int16 midiPitch,
|
|
Steinberg::Vst::String128 name /*out*/) override;
|
|
Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override;
|
|
tresult PLUGIN_API selectUnit(Steinberg::Vst::UnitID unitId) override;
|
|
tresult PLUGIN_API
|
|
getUnitByBus(Steinberg::Vst::MediaType type,
|
|
Steinberg::Vst::BusDirection dir,
|
|
int32 busIndex,
|
|
int32 channel,
|
|
Steinberg::Vst::UnitID& unitId /*out*/) override;
|
|
tresult PLUGIN_API setUnitProgramData(int32 listOrUnitId,
|
|
int32 programIndex,
|
|
Steinberg::IBStream* data) override;
|
|
|
|
// From `IXmlRepresentationController`
|
|
tresult PLUGIN_API
|
|
getXmlRepresentationStream(Steinberg::Vst::RepresentationInfo& info /*in*/,
|
|
Steinberg::IBStream* stream /*out*/);
|
|
|
|
/**
|
|
* The component handler the host passed to us during
|
|
* `IEditController::setComponentHandler()`. When the plugin makes a
|
|
* callback on a component handler proxy object, we'll pass the call through
|
|
* to this object.
|
|
*/
|
|
Steinberg::IPtr<Steinberg::Vst::IComponentHandler> component_handler;
|
|
|
|
/**
|
|
* If the host doesn't connect two objects directly in
|
|
* `IConnectionPoint::connect` but instead connects them through a proxy,
|
|
* we'll store that proxy here. This way we can then route messages sent by
|
|
* the plugin through this proxy. So far this is only needed for Ardour.
|
|
*/
|
|
Steinberg::IPtr<Steinberg::Vst::IConnectionPoint> connection_point_proxy;
|
|
|
|
/**
|
|
* An unmanaged, raw pointer to the `IPlugView` instance returned in our
|
|
* implementation of `IEditController::createView()`. We need this to handle
|
|
* `IPlugFrame::resizeView()`, since that expects a pointer to the view that
|
|
* gets resized.
|
|
*
|
|
* XXX: This approach of course won't work with multiple views, but the SDK
|
|
* currently only defines a single type of view so that shouldn't be an
|
|
* issue
|
|
*/
|
|
Vst3PlugViewProxyImpl* last_created_plug_view = nullptr;
|
|
|
|
/**
|
|
* A pointer to a context menu returned by the host as a response to a call
|
|
* to `IComponentHandler3::createContextMenu`, as well as all targets we've
|
|
* created for it. This way we can drop both all at once.
|
|
*/
|
|
struct ContextMenu {
|
|
ContextMenu(Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu);
|
|
|
|
Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu;
|
|
|
|
/**
|
|
* All targets we pass to `IContextMenu::addItem`. We'll store them per
|
|
* item tag, so we can drop them together with the menu. We probably
|
|
* don't have to use smart pointers for this, but the docs are missing a
|
|
* lot of details o how this should be implemented and there's no
|
|
* example implementation around.
|
|
*/
|
|
std::map<int32, Steinberg::IPtr<YaContextMenuTarget>> targets;
|
|
};
|
|
|
|
/**
|
|
* All context menus created by this object through
|
|
* `IComponentHandler3::createContextMenu()`. We'll generate a unique
|
|
* identifier for each context menu just like we do for plugin objects. When
|
|
* the plugin drops the context menu object, we'll also remove the
|
|
* corresponding entry for this map causing the original pointer returned by
|
|
* the host to get dropped a well.
|
|
*
|
|
* @see Vst3PluginProxyImpl::register_context_menu
|
|
* @see Vst3PluginProxyImpl::unregister_context_menu
|
|
*/
|
|
std::map<size_t, ContextMenu> context_menus;
|
|
std::mutex context_menus_mutex;
|
|
|
|
// The following pointers are cast from `host_context` if
|
|
// `IPluginBase::initialize()` has been called
|
|
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IHostApplication> host_application;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IPlugInterfaceSupport>
|
|
plug_interface_support;
|
|
|
|
// The following pointers are cast from `component_handler` if
|
|
// `IEditController::setComponentHandler()` has been called
|
|
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler2>
|
|
component_handler_2;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler3>
|
|
component_handler_3;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandlerBusActivation>
|
|
component_handler_bus_activation;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IProgress> progress;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler> unit_handler;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2;
|
|
|
|
private:
|
|
Vst3PluginBridge& bridge;
|
|
|
|
/**
|
|
* An host context if we get passed one through `IPluginBase::initialize()`.
|
|
* We'll read which interfaces it supports and we'll then create a proxy
|
|
* object that supports those same interfaces. This should be the same for
|
|
* all plugin instances so we should not have to store it here separately,
|
|
* but for the sake of correctness we will.
|
|
*/
|
|
Steinberg::IPtr<Steinberg::FUnknown> host_context;
|
|
|
|
/**
|
|
* We'll periodically synchronize the Wine host's audio thread priority with
|
|
* that of the host. Since the overhead from doing so does add up, we'll
|
|
* only do this every once in a while.
|
|
*/
|
|
time_t last_audio_thread_priority_synchronization = 0;
|
|
|
|
/**
|
|
* Used to assign unique identifiers to context menus created by
|
|
* `IComponentHandler3::CreateContextMenu`.
|
|
*
|
|
* @related Vst3PluginProxyImpl::register_context_menu
|
|
*/
|
|
std::atomic_size_t current_context_menu_id;
|
|
|
|
/**
|
|
* A cache for `IAudioProcessor::getBusCount()` and
|
|
* `IAudioProcessor::getBusInfo()` to work around an implementation issue in
|
|
* REAPER. If during processing a plugin returns a value for one of these
|
|
* function calls, we'll memoize the function call using the maps defined
|
|
* below.
|
|
*
|
|
* @see processing_bus_cache
|
|
*/
|
|
struct BusInfoCache {
|
|
std::map<
|
|
std::tuple<Steinberg::Vst::MediaType, Steinberg::Vst::BusDirection>,
|
|
int32>
|
|
bus_count;
|
|
std::map<std::tuple<Steinberg::Vst::MediaType,
|
|
Steinberg::Vst::BusDirection,
|
|
int32>,
|
|
Steinberg::Vst::BusInfo>
|
|
bus_info;
|
|
};
|
|
|
|
/**
|
|
* HACK: To work around some behaviour in REAPER where it will repeatedly
|
|
* query the same bus information for bus during every processing
|
|
* cycle, we'll cache this information during processing. Otherwise
|
|
* this will cause `input_busses + output_busses + 2` extra
|
|
* unnecessary back and forths for every processing cycle. This can
|
|
* really add up for plugins with 16, or even 32 outputs.
|
|
*
|
|
* Since this information cannot change during processing, this will not
|
|
* contain a value while the plugin is not processing audio.
|
|
*/
|
|
std::optional<BusInfoCache> processing_bus_cache;
|
|
};
|