Files
op-pedal/src/Vst3Effect.cpp
T
2026-06-02 12:59:41 -04:00

894 lines
24 KiB
C++

/*
* MIT License
*
* Copyright (c) Robin E.R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "ss.hpp"
#include <assert.h>
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp"
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <filesystem>
#include "RingBufferReader.hpp"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/common/memorystream.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstunits.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
#include <array>
#include <sstream>
#include "LiteralVersion.hpp"
#include "Vst3MidiToEvent.hpp"
#include "vst3/Vst3EffectImpl.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
std::unique_ptr<Vst3Effect> Vst3Effect::CreateInstance(uint64_t instanceId, const Vst3PluginInfo &info, IHost *pHost)
{
std::unique_ptr<Vst3EffectImpl> result = std::make_unique<Vst3EffectImpl>();
result->Load(instanceId, info, pHost);
return std::move(result);
}
static double NaNGuard(double value)
{
if (std::isnan(value)) return 0;
if (std::isinf(value)) return 0;
return value;
}
static uint32_t GetRefCount(FUnknown *p)
{
p->addRef();
return p->release();
}
//------------------------------------------------------------------------
// From Vst2Wrapper
static MidiCCMapping initMidiCtrlerAssignment(IComponent *component, IMidiMapping *midiMapping)
{
MidiCCMapping midiCCMapping{};
if (!midiMapping || !component)
return midiCCMapping;
int32 busses = std::min<int32>(component->getBusCount(kEvent, kInput), kMaxMidiMappingBusses);
if (midiCCMapping[0][0].empty())
{
for (int32 b = 0; b < busses; b++)
for (int32 i = 0; i < kMaxMidiChannels; i++)
midiCCMapping[b][i].resize(Vst::kCountCtrlNumber);
}
ParamID paramID;
for (int32 b = 0; b < busses; b++)
{
for (int16 ch = 0; ch < kMaxMidiChannels; ch++)
{
for (int32 i = 0; i < Vst::kCountCtrlNumber; i++)
{
paramID = kNoParamId;
if (midiMapping->getMidiControllerAssignment(b, ch, (CtrlNumber)i, paramID) ==
kResultTrue)
{
// TODO check if tag is associated to a parameter
midiCCMapping[b][ch][i] = paramID;
}
else
midiCCMapping[b][ch][i] = kNoParamId;
}
}
}
return midiCCMapping;
}
//------------------------------------------------------------------------
static void assignBusBuffers(const IAudioClient::Buffers &buffers, HostProcessData &processData,
bool unassign = false)
{
// Set outputs
auto bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numOutputs; busIndex++)
{
auto channelCount = processData.outputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numOutputs)
{
processData.setChannelBuffer(BusDirections::kOutput, busIndex, chanIndex,
unassign ? nullptr : buffers.outputs[bufferIndex]);
bufferIndex++;
}
}
}
// Set inputs
bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numInputs; busIndex++)
{
auto channelCount = processData.inputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numInputs)
{
processData.setChannelBuffer(BusDirections::kInput, busIndex, chanIndex,
unassign ? nullptr : buffers.inputs[bufferIndex]);
bufferIndex++;
}
}
}
}
//------------------------------------------------------------------------
static void unassignBusBuffers(const IAudioClient::Buffers &buffers, HostProcessData &processData)
{
assignBusBuffers(buffers, processData, true);
}
//------------------------------------------------------------------------
// Vst3Processor
//------------------------------------------------------------------------
Vst3EffectImpl::Vst3EffectImpl()
{
buffers.inputs = nullptr;
buffers.outputs = nullptr;
componentHandler.setEffect(this);
}
//------------------------------------------------------------------------
Vst3EffectImpl::~Vst3EffectImpl()
{
delete[] buffers.inputs;
delete[] buffers.outputs;
terminate();
}
//------------------------------------------------------------------------
void Vst3EffectImpl::SetControl(int index, float value)
{
this->parameterValues[index] = value;
ParamID paramId = lv2ToVstParam[index];
double normalizedValue = controller->plainParamToNormalized(paramId, value);
this->controller->setParamNormalized(paramId, normalizedValue);
{
RtInversionGuard inversionGuard; // boost priority to prevent priority inversion.
std::lock_guard guard{parameterMutex};
paramTransferrer.addChange(paramId, normalizedValue, 0);
}
if (!isProcessing)
{
Run(0, nullptr); // pump parameter changes on UI thread.
}
}
void Vst3EffectImpl::Load(uint64_t instanceId, const Vst3PluginInfo &info, IHost *pHost)
{
processContext = {};
processContext.tempo = 120;
this->pHost = pHost;
this->instanceId = instanceId;
this->info = info;
size_t nControls = info.pluginInfo_.controls().size();
inputParameterChanges.setMaxParameters(nControls);
outputParameterChanges.setMaxParameters(nControls);
lv2ToVstParam.resize(nControls);
parameterValues.resize(nControls);
for (size_t i = 0; i < nControls; ++i)
{
const auto &control = info.pluginInfo_.controls()[i];
ParamID paramId;
std::stringstream ss(control.symbol());
ss >> paramId;
assert(i == control.index());
lv2ToVstParam[control.index()] = paramId;
parameterValues[control.index()] = control.default_value();
if (control.is_bypass())
{
bypassControl = i;
}
}
std::string error;
this->module = Module::create(info.filePath_, error);
if (!module)
{
throw Vst3Exception(SS("Vst3 load failed: " << error << "(" << info.filePath_ << ")"));
}
const PluginFactory &factory = module->getFactory();
ClassInfo myClassInfo;
bool foundClassInfo = false;
VST3::Optional<VST3::UID> myUid = VST3::UID::fromString(info.uid_);
if (!myUid)
{
throw Vst3Exception(SS("Invalid UID"));
}
for (const ClassInfo &classInfo : factory.classInfos())
{
if (classInfo.category() == kVstAudioEffectClass &&
classInfo.ID() == *myUid)
{
myClassInfo = classInfo;
foundClassInfo = true;
}
}
if (!foundClassInfo)
{
throw Vst3Exception(SS("Vst3 load failed: effect class not found in " << info.filePath_));
}
plugProvider = owned(NEW PlugProvider(factory, myClassInfo, true));
this->component = plugProvider->getComponent();
this->processor = component;
this->controller = plugProvider->getController();
controller->queryInterface(IMidiMapping::iid, (void **)&midiMapping);
paramTransferrer.setMaxParameters(1000);
if (midiMapping)
midiCCMapping = initMidiCtrlerAssignment(component, midiMapping);
setBlockSize((Steinberg::int32)pHost->GetMaxAudioBufferSize());
setSamplerate((SampleRate)pHost->GetSampleRate());
ProcessSetup setup{kRealtime, kSample32, blockSize, sampleRate};
initProcessData();
if (processor->setupProcessing(setup) != kResultOk)
{
throw Vst3Exception(SS(info.pluginInfo_.name() << ": setupProcessing() failed."));
}
if (component->setActive(true) != kResultOk)
throw Vst3Exception(SS(info.pluginInfo_.name() << ": setActive() failed."));
OPtr<IBStream> state = new MemoryStream();
assert(GetRefCount(state.get()) == 1);
this->supportsState = false;
if (component->getState(state) == kResultOk)
{
this->supportsState = true;
state->seek(0, IBStream::kIBSeekSet);
controller->setComponentState(state);
refreshControlValues();
}
IComponentHandler *handler;
controller->setComponentHandler(&componentHandler);
paramTransferrer.setMaxParameters(1000);
if (midiMapping)
midiCCMapping = initMidiCtrlerAssignment(component, midiMapping);
component->setActive(true);
for (size_t i = 0; i < this->lv2ToVstParam.size(); ++i)
{
fireControlChanged(i, (float)(controller->getParamNormalized(lv2ToVstParam[i])));
}
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// void Vst3PluginImpl::createLocalMediaServer (const Name& name)
// {
// mediaServer = createMediaServer (name);
// mediaServer->registerAudioClient (this);
// mediaServer->registerMidiClient (this);
// }
//------------------------------------------------------------------------
//------------------------------------------------------------------------
void Vst3EffectImpl::terminate()
{
// mediaServer = nullptr;
if (!processor)
return;
processor->setProcessing(false);
component->setActive(false);
}
//------------------------------------------------------------------------
void Vst3EffectImpl::initProcessData()
{
// processData.prepare will be done in setBlockSize
buffers.numInputs = info.pluginInfo_.audio_inputs();
buffers.numOutputs = info.pluginInfo_.audio_outputs();
buffers.numSamples = 0;
buffers.inputs = new float *[buffers.numInputs + 1];
buffers.outputs = new float *[buffers.numOutputs + 1];
for (size_t i = 0; i < buffers.numInputs + 1; ++i)
{
buffers.inputs[i] = nullptr;
}
for (size_t i = 0; i < buffers.numOutputs + 1; ++i)
{
buffers.outputs[i] = nullptr;
}
processData.inputEvents = &eventList;
processData.inputParameterChanges = &inputParameterChanges;
processData.outputParameterChanges = &outputParameterChanges;
processData.processContext = &processContext;
setSamplerate(pHost->GetSampleRate());
setBlockSize((Steinberg::int32)pHost->GetMaxAudioBufferSize());
}
//------------------------------------------------------------------------
IMidiClient::IOSetup Vst3EffectImpl::getMidiIOSetup() const
{
IMidiClient::IOSetup iosetup;
auto count = component->getBusCount(MediaTypes::kEvent, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo(MediaTypes::kEvent, BusDirections::kInput, i, info) != kResultOk)
continue;
auto busName = VST3::StringConvert::convert(info.name, 128);
iosetup.inputs.push_back(busName);
}
count = component->getBusCount(MediaTypes::kEvent, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo(MediaTypes::kEvent, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
auto busName = VST3::StringConvert::convert(info.name, 128);
iosetup.outputs.push_back(busName);
}
return iosetup;
}
//------------------------------------------------------------------------
IAudioClient::IOSetup Vst3EffectImpl::getIOSetup() const
{
IAudioClient::IOSetup iosetup;
auto count = component->getBusCount(MediaTypes::kAudio, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = VST3::StringConvert::convert(info.name, 128);
iosetup.outputs.push_back(channelName + " " + std::to_string(j));
}
}
count = component->getBusCount(MediaTypes::kAudio, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kInput, i, info) != kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = VST3::StringConvert::convert(info.name, 128);
iosetup.inputs.push_back(channelName + " " + std::to_string(j));
}
}
return iosetup;
}
//------------------------------------------------------------------------
void Vst3EffectImpl::preprocess(Buffers &buffers, int64_t continousFrames)
{
processData.numSamples = buffers.numSamples;
processContext.continousTimeSamples = continousFrames;
assignBusBuffers(buffers, processData);
{
std::lock_guard guard{parameterMutex};
paramTransferrer.transferChangesTo(inputParameterChanges);
}
outputParameterChanges.clearQueue();
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::process(Buffers &buffers, int64_t continousFrames)
{
if (!processor || !isProcessing)
return false;
buffers.numSamples = continousFrames;
preprocess(buffers, continousFrames);
if (processor->process(processData) != kResultOk)
return false;
postprocess(buffers);
return true;
}
//------------------------------------------------------------------------
void Vst3EffectImpl::postprocess(Buffers &buffers)
{
eventList.clear();
inputParameterChanges.clearQueue();
unassignBusBuffers(buffers, processData);
}
void Vst3EffectImpl::SendControlChanges(RealtimeRingBufferWriter *realtimeRingBufferWriter)
{
for (auto i = 0; i < outputParameterChanges.getParameterCount(); ++i)
{
IParamValueQueue *queue = outputParameterChanges.getParameterData(i);
auto points = queue->getPointCount();
if (points != 0)
{
Steinberg::Vst::ParamValue value;
Steinberg::int32 sampleOffset;
queue->getPoint(points - 1, sampleOffset, value);
auto paramId = queue->getParameterId();
int thisControlId = -1;
for (size_t lv2Id = 0; lv2Id < this->lv2ToVstParam.size(); ++lv2Id)
{
if (lv2ToVstParam[lv2Id] == paramId)
{
thisControlId = (int)lv2Id;
break;
}
}
if (thisControlId != -1)
{
// realtimeRingBufferWriter->NotifyVst3ControlValue(this->instanceId,thisControlId,(float)value);
}
}
}
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::setSamplerate(SampleRate value)
{
if (sampleRate == value)
return true;
sampleRate = value;
processContext.sampleRate = sampleRate;
if (blockSize == 0)
return true;
return true;
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::setBlockSize(int32 value)
{
blockSize = value;
if (sampleRate == 0)
return true;
processData.prepare(*component, blockSize, kSample32);
return true;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
bool Vst3EffectImpl::isPortInRange(int32 port, int32 channel) const
{
return port < kMaxMidiMappingBusses && !midiCCMapping[port][channel].empty();
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::processVstEvent(const IMidiClient::Event &event, int32 port)
{
auto vstEvent = midiToEvent(event.type, event.channel, event.data0, event.data1);
if (vstEvent)
{
vstEvent->busIndex = port;
if (eventList.addEvent(*vstEvent) != kResultOk)
{
assert(false && "Event was not added to EventList!");
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::processParamChange(const IMidiClient::Event &event, int32 port)
{
auto paramMapping = [port, this](int32 channel, MidiData data1) -> ParamID
{
if (!isPortInRange(port, channel))
return kNoParamId;
return midiCCMapping[port][channel][data1];
};
auto paramChange =
midiToParameter(event.type, event.channel, event.data0, event.data1, paramMapping);
if (paramChange)
{
int32 index = 0;
IParamValueQueue *queue =
inputParameterChanges.addParameterData((*paramChange).first, index);
if (queue)
{
if (queue->addPoint(event.timestamp, (*paramChange).second, index) != kResultOk)
{
assert(false && "Parameter point was not added to ParamValueQueue!");
}
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool Vst3EffectImpl::onEvent(const IMidiClient::Event &event, int32_t port)
{
// Try to create Event first.
if (processVstEvent(event, port))
return true;
// In case this is no event it must be a parameter.
if (processParamChange(event, port))
return true;
// TODO: Something else???
return true;
}
//------------------------------------------------------------------------
void Vst3EffectImpl::Activate()
{
processor->setProcessing(true); // != kResultOk
this->isProcessing = true;
}
void Vst3EffectImpl::Deactivate()
{
if (isProcessing)
{
Run(0, nullptr); // make sure all pending events have been processed.
isProcessing = false;
}
}
void Vst3EffectImpl::Prepare(int32_t sampleRate, size_t maxBufferSize, int inputChannels, int outputChannels)
{
}
void Vst3EffectImpl::Unprepare()
{
if (isProcessing)
{
Deactivate();
}
}
ssize_t Vst3EffectImpl::ParamIdToLv2Id(ParamID id) const
{
for (size_t i = 0; i < this->lv2ToVstParam.size(); ++i)
{
if (lv2ToVstParam[i] == id)
{
return (ssize_t)i;
}
}
return -1;
}
tresult Vst3EffectImpl::beginEdit(ParamID id)
{
return kResultOk;
}
tresult Vst3EffectImpl::performEdit(ParamID id, ParamValue valueNormalized)
{
int ix = ParamIdToLv2Id(id);
if (ix != -1)
{
fireControlChanged(ix, (float)valueNormalized);
}
return kResultOk;
}
tresult Vst3EffectImpl::endEdit(ParamID id)
{
return kResultOk;
}
void Vst3EffectImpl::transferControllerStateToComponent()
{
OPtr<IRtStream> bStream = this->streamPool.AllocateBStream();
assert(GetRefCount(bStream.get()) == 1);
RtInversionGuard inversionGuard; // boost priority to prevent priority inversion.
std::lock_guard guard{parameterMutex};
paramTransferrer.removeChanges();
// assume that parameters are straightforward and uncomplicated if they
// didn't provide BStream-based state management
// Vst2 used to do this. It's not clear that this is still legal in Vst3.
for (int index = 0; index < this->lv2ToVstParam.size(); ++index)
{
ParamID paramId = lv2ToVstParam[index];
paramTransferrer.addChange(paramId,
controller->plainParamToNormalized(paramId, parameterValues[index]),
0);
}
if (!this->isProcessing)
{
Run(0,nullptr);
}
}
tresult Vst3EffectImpl::restartComponent(int32 flags)
{
if (flags & (RestartFlags::kParamValuesChanged))
{
refreshControlValues();
transferControllerStateToComponent();
}
return kResultOk;
}
static std::vector<uint8_t> StreamToVec(IBStream *stream)
{
Steinberg::int64 length;
stream->seek(0, IBStream::kIBSeekEnd, &length);
stream->seek(0, IBStream::kIBSeekSet);
std::vector<uint8_t> result;
result.resize(length);
Steinberg::int32 numRead = 0;
stream->read(&result[0], length, &numRead);
if (numRead != length)
{
throw Vst3Exception("Failed to read.");
}
return result;
}
void Vst3EffectImpl::CheckSync()
{
OPtr<IBStream> stream{new MemoryStream()};
if (this->controller->getState(stream) == kResultOk)
{
std::vector<uint8_t> initialState = StreamToVec(stream);
OPtr<IBStream> stream2{new MemoryStream()};
this->component->getState(stream2);
stream2->seek(0, IBStream::kIBSeekSet);
this->controller->setState(stream2);
OPtr<IBStream> stream3{new MemoryStream()};
this->controller->getState(stream3);
std::vector<uint8_t> newState = StreamToVec(stream3);
if (initialState.size() != newState.size())
{
throw Vst3Exception("State changed.");
}
for (size_t i = 0; i < newState.size(); ++i)
{
if (initialState[i] != newState[i])
{
throw Vst3Exception("State changed.");
}
}
}
}
bool Vst3EffectImpl::GetState(std::vector<uint8_t>*state)
{
OPtr<IBStream> stream{new MemoryStream()};
if (component->getState(stream) != kResultOk)
{
return false;
}
Steinberg::int64 length = 0;
if (stream->seek(0, IBStream::kIBSeekEnd, &length) != kResultOk)
{
return false;
}
stream->seek(0, IBStream::kIBSeekSet);
std::vector<uint8_t> result;
result.resize(length);
int64 ix = 0;
while (length != 0)
{
int32 thisTime;
if (length > 0x10000000)
{
thisTime = 0x10000000;
}
else
{
thisTime = (int32)length;
}
if (stream->read((void *)(&result[ix]), thisTime, &thisTime) != kResultOk)
{
throw Vst3Exception(this->info.pluginInfo_.name() + ": Failed to read state.");
}
if (thisTime == 0)
{
throw Vst3Exception(this->info.pluginInfo_.name() + ": Unexpected end of file while reading state.");
}
ix += thisTime;
length -= thisTime;
}
*state = std::move(result);
return true;
}
void Vst3EffectImpl::SetState(const std::vector<uint8_t> state)
{
OPtr<IBStream> stream{new MemoryStream((void *)(&state[0]), state.size())};
if (controller->setComponentState(stream) != kResultOk)
{
throw Vst3Exception(this->info.pluginInfo_.name() + " Failed to restore state.");
}
refreshControlValues();
stream->seek(0,IBStream::kIBSeekSet);
if (component->setState(stream) != kResultOk)
{
transferControllerStateToComponent();
}
}
void Vst3EffectImpl::refreshControlValues()
{
for (size_t i = 0; i < lv2ToVstParam.size(); ++i)
{
fireControlChanged(i, controller->getParamNormalized(lv2ToVstParam[i]));
}
}
std::vector<Vst3ProgramList> Vst3EffectImpl::GetProgramList(int32_t programListId)
{
std::vector<Vst3ProgramList> result;
FUnknownPtr<IUnitInfo> iUnitInfo(controller);
if (!iUnitInfo)
throw Vst3Exception("Invalid unit info");
int count = iUnitInfo->getProgramListCount();
for (int i = 0; i < count; ++i)
{
ProgramListInfo programListInfo;
if (iUnitInfo->getProgramListInfo(i, programListInfo) != kResultOk)
{
break;
}
if (programListInfo.id == programListId)
{
Vst3ProgramList list;
list.name = VST3::StringConvert::convert(programListInfo.name);
for (int prog = 0; prog < programListInfo.programCount; ++prog)
{
Vst::String128 progName;
if (iUnitInfo->getProgramName(programListInfo.id, prog, progName) == kResultOk)
{
Vst3ProgramListEntry entry;
entry.id = prog;
entry.name = VST3::StringConvert::convert(progName);
list.programs.push_back(std::move(entry));
}
}
result.push_back(std::move(list));
}
}
return result;
}
void Vst3EffectImpl::fireControlChanged(int control, float normalizedValue)
{
normalizedValue = NaNGuard(normalizedValue);
float plainValue = NaNGuard(this->controller->normalizedParamToPlain(this->lv2ToVstParam[control], normalizedValue));
if (parameterValues[control] != plainValue)
{
parameterValues[control] = plainValue;
controlChangedHandler(control,plainValue);
}
}
const Lv2PluginUiInfo& Vst3EffectImpl::GetCurrentPluginInfo()
{
Vst3Host::Private::UpdateControlInfo(this->controller,this->info.pluginInfo_);
return this->info.pluginInfo_;
}
JSON_MAP_BEGIN(Vst3PluginInfo)
JSON_MAP_REFERENCE(Vst3PluginInfo, filePath)
JSON_MAP_REFERENCE(Vst3PluginInfo, version)
JSON_MAP_REFERENCE(Vst3PluginInfo, uid)
JSON_MAP_REFERENCE(Vst3PluginInfo, pluginInfo)
JSON_MAP_END()