Initial VST3 Support (disabled)

Disabled until Steinberg licensing compliance  can be sorted out.
This commit is contained in:
Robin Davies
2022-08-15 18:14:49 -04:00
parent 351d1542e1
commit 553ae2a3f7
56 changed files with 4840 additions and 637 deletions
+689
View File
@@ -0,0 +1,689 @@
/*
* MIT License
*
* Copyright (c) 2022 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 "PiPedalHost.hpp"
#include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp"
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <filesystem>
#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 "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 "json.hpp"
using namespace std;
using namespace pipedal;
using namespace VST3::Hosting;
using namespace Steinberg;
using namespace Steinberg::Vst;
namespace pipedal
{
class Vst3HostApplication : public HostApplication
{
public:
Vst3HostApplication() {}
//--- IHostApplication ---------------
tresult PLUGIN_API getName(String128 name)
{
return VST3::StringConvert::convert("PiPedal", name) ? kResultTrue : kInternalError;
}
};
class Vst3HostImpl : public Vst3Host
{
private:
OPtr<Vst3HostApplication> hostApplication;
std::string cacheFilePath;
public:
Vst3HostImpl(const std::string&cacheFilePath);
const Vst3Host::PluginList &RescanPlugins() override;
const Vst3Host::PluginList &RefreshPlugins() override;
const Vst3Host::PluginList &getPluginList() override { return pluginList; }
std::unique_ptr<Vst3Effect> CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override;
std::unique_ptr<Vst3Effect> CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost) override;
private:
Lv2PluginUiInfo *GetPluginInfo(const std::string&uri);
void LoadPluginCache();
void SavePluginCache();
bool havePluginsChanged() const;
PluginList pluginList;
void EnsureContext();
std::unique_ptr<Vst3PluginInfo> MakeDetailedPluginInfo(const PluginFactory &factory, const ClassInfo &classInfo, const std::string &path);
void MakeVst3Info(const std::filesystem::path &path, Vst3Host::PluginList &list);
};
Vst3Host::Ptr Vst3Host::CreateInstance(const std::string&cacheFilePath)
{
return std::make_unique<Vst3HostImpl>(cacheFilePath);
}
//--------------------------------------------------------------
} // namespace pipedal
Vst3HostImpl::Vst3HostImpl(const std::string&cacheFilePath)
:cacheFilePath(cacheFilePath)
{
this->hostApplication = new Vst3HostApplication();
EnsureContext();
}
static bool hasSubCategory(const ClassInfo &classInfo, const std::string &subcategory)
{
for (auto &category : classInfo.subCategories())
{
if (category == subcategory)
{
return true;
}
}
return false;
}
static PluginType getVst3PluginType(const ClassInfo &classInfo)
{
if (hasSubCategory(classInfo, "Instrument"))
{
return PluginType::InstrumentPlugin;
}
if (!hasSubCategory(classInfo, "Fx"))
{
return PluginType::InvalidPlugin;
}
// kFxAnalyzer = "Fx|Analyzer"; ///< Scope, FFT-Display, Loudness Processing...
if (hasSubCategory(classInfo, "Analyzer"))
{
return PluginType::AnalyserPlugin;
}
// kFxDelay = "Fx|Delay"; ///< Delay, Multi-tap Delay, Ping-Pong Delay...
if (hasSubCategory(classInfo, "Delay"))
{
return PluginType::DelayPlugin;
}
// kFxDistortion = "Fx|Distortion"; ///< Amp Simulator, Sub-Harmonic, SoftClipper...
if (hasSubCategory(classInfo, "Distortion"))
{
return PluginType::DistortionPlugin;
}
// kFxDynamics = "Fx|Dynamics"; ///< Compressor, Expander, Gate, Limiter, Maximizer, Tape Simulator, EnvelopeShaper...
if (hasSubCategory(classInfo, "Dynamics"))
{
return PluginType::DynamicsPlugin;
}
// kFxEQ = "Fx|EQ"; ///< Equalization, Graphical EQ...
if (hasSubCategory(classInfo, "EQ"))
{
return PluginType::EQPlugin;
}
// kFxFilter = "Fx|Filter"; ///< WahWah, ToneBooster, Specific Filter,...
if (hasSubCategory(classInfo, "Filter"))
{
return PluginType::FilterPlugin;
}
// kFx = "Fx"; ///< others type (not categorized)
// kFxSpatial = "Fx|Spatial"; ///< MonoToStereo, StereoEnhancer,...
if (hasSubCategory(classInfo, "Spatial"))
{
return PluginType::SpatialPlugin;
}
// kFxGenerator = "Fx|Generator"; ///< Tone Generator, Noise Generator...
if (hasSubCategory(classInfo, "Generator"))
{
return PluginType::GeneratorPlugin;
}
// kFxReverb = "Fx|Reverb"; ///< Reverberation, Room Simulation, Convolution Reverb...
if (hasSubCategory(classInfo, "Reverb"))
{
return PluginType::ReverbPlugin;
}
// kFxPitchShift = "Fx|Pitch Shift"; ///< Pitch Processing, Pitch Correction, Vocal Tuning...
if (hasSubCategory(classInfo, "Pitch Shift"))
{
return PluginType::PitchPlugin;
}
// kFxModulation = "Fx|Modulation"; ///< Phaser, Flanger, Chorus, Tremolo, Vibrato, AutoPan, Rotary, Cloner...
if (hasSubCategory(classInfo, "Modulation"))
{
return PluginType::ModulatorPlugin;
}
// kFxRestoration = "Fx|Restoration"; ///< Denoiser, Declicker,...
if (hasSubCategory(classInfo, "Restoration"))
{
return PluginType::UtilityPlugin;
}
// kFxSurround = "Fx|Surround"; ///< dedicated to surround processing: LFE Splitter, Bass Manager...
if (hasSubCategory(classInfo, "Surround"))
{
return PluginType::UtilityPlugin;
}
// kFxTools = "Fx|Tools"; ///< Volume, Mixer, Tuner...
if (hasSubCategory(classInfo, "Tools"))
{
return PluginType::UtilityPlugin;
}
// kFxMastering = "Fx|Mastering"; ///< Dither, Noise Shaping,...
if (hasSubCategory(classInfo, "Mastering"))
{
return PluginType::UtilityPlugin;
}
// kFxNetwork = "Fx|Network"; ///< using Network
if (hasSubCategory(classInfo, "Network"))
{
return PluginType::UtilityPlugin;
}
// kFxInstrument = "Fx|Instrument"; ///< Fx which could be loaded as Instrument too
// kFxInstrumentExternal = "Fx|Instrument|External"; ///< Fx which could be loaded as Instrument too and is external (wrapped Hardware)
if (hasSubCategory(classInfo, "Instrument"))
{
return PluginType::InstrumentPlugin;
}
return PluginType::Plugin;
}
void Vst3HostImpl::EnsureContext()
{
PluginContextFactory::instance().setPluginContext(this->hostApplication);
}
unique_ptr<Vst3PluginInfo> Vst3HostImpl::MakeDetailedPluginInfo(const PluginFactory &factory, const ClassInfo &classInfo, const std::string &path)
{
IPtr<PlugProvider> plugProvider = owned(NEW PlugProvider(factory, classInfo, true));
{
OPtr<IComponent> component = plugProvider->getComponent();
OPtr<IEditController> controller = plugProvider->getController();
FUnknownPtr<IMidiMapping> midiMapping(controller);
FUnknownPtr<IUnitInfo> iUnitInfo(controller);
unique_ptr<Vst3PluginInfo> info = make_unique<Vst3PluginInfo>();
info->filePath_ = path;
info->version_ = classInfo.version();
info->uid_ = classInfo.ID().toString();
Lv2PluginUiInfo &pluginInfo = info->pluginInfo_;
pluginInfo.uri(SS("vst3:" << classInfo.ID().toString()));
pluginInfo.is_vst3(true);
pluginInfo.name(classInfo.name());
pluginInfo.author_name(classInfo.vendor());
pluginInfo.plugin_type(
getVst3PluginType(classInfo));
pluginInfo.plugin_display_type(""); // fill this in later. VSTHost has list of name translations; we don;'t.
if (iUnitInfo)
{
auto nUnits = iUnitInfo->getUnitCount();
for (int i = 0; i < nUnits; ++i)
{
Steinberg::Vst::UnitInfo unitInfo;
iUnitInfo->getUnitInfo(i, unitInfo);
Lv2PluginUiPortGroup portGroup(
SS(unitInfo.id), VST3::StringConvert::convert(unitInfo.name), SS(unitInfo.parentUnitId), unitInfo.programListId);
pluginInfo.port_groups().push_back(portGroup);
}
}
auto nParams = controller->getParameterCount();
for (int32 param = 0; param < nParams; ++param)
{
Steinberg::Vst::ParameterInfo info;
tresult tErr = controller->getParameterInfo(param, info);
Lv2PluginUiControlPort port;
port.index(param); // used for LV2 purposes.
port.symbol(SS(info.id)); // Used for VST3 purposes. convert to int to specifiy a vst control id.
std::string shortTitle = VST3::StringConvert::convert(info.shortTitle);
std::string title = VST3::StringConvert::convert(info.title);
if (shortTitle.length() == 0)
{
shortTitle = title;
}
port.name(shortTitle);
port.comment(title);
// guard against NaN value (mda Talkbox)
port.display_priority(param);
bool isList = (info.flags & ParameterInfo::kIsList) != ParameterInfo::kNoFlags;
bool canAutomate = (info.flags & ParameterInfo::kCanAutomate) != ParameterInfo::kNoFlags;
bool isProgramChange = (info.flags & ParameterInfo::kIsProgramChange) != ParameterInfo::kNoFlags;
bool isReadOnly = (info.flags & ParameterInfo::kIsReadOnly) != ParameterInfo::kNoFlags;
bool notOnGui = (info.flags & ParameterInfo::kIsHidden) != ParameterInfo::kNoFlags;
bool isBypass = (info.flags & ParameterInfo::kIsBypass) != ParameterInfo::kNoFlags;
port.is_bypass(isBypass);
port.not_on_gui(isReadOnly | notOnGui);
port.min_value(controller->normalizedParamToPlain(info.id,0));
port.max_value(controller->normalizedParamToPlain(info.id,1));
double t = controller->normalizedParamToPlain(info.id,info.defaultNormalizedValue);
if (std::isnan(t) || std::isinf(t))
{
t = 0;
}
port.default_value(t);
port.range_steps(info.stepCount == 0 ? 0 : info.stepCount + 1);
if (isList && !isProgramChange)
{
port.enumeration_property(true);
for (int i = 0; i <= info.stepCount; ++i)
{
double normalizedValue;
if (info.stepCount == 0)
{
normalizedValue = 0;
}
else
{
normalizedValue = i * 1.0 / (info.stepCount);
}
String128 strValue;
if (controller->getParamStringByValue(info.id, normalizedValue, strValue) == kResultOk)
{
Lv2ScalePoint scalePoint((float)controller->normalizedParamToPlain(info.id,normalizedValue), VST3::StringConvert::convert(strValue));
port.scale_points().push_back(scalePoint);
}
}
}
if (info.unitId != 0)
{
port.port_group(SS(info.unitId));
}
std::string units = VST3::StringConvert::convert(info.units);
if (units.size() != 0)
{
if (units == "dB")
{
port.units(Units::db);
} else if (units == "%")
{
port.units(Units::pc);
} else if (units == "Hz")
{
port.units(Units::hz);
} else if (units == "kHz")
{
port.units(Units::khz);
} else if (units == "sec" || units == "s")
{
port.units(Units::s);
} else if (units == "ms")
{
port.units(Units::ms);
} else if (units == "cent")
{
port.units(Units::cent);
} else {
port.units(Units::custom);
port.custom_units(units);
}
}
pluginInfo.controls().push_back(port);
}
auto count = component->getBusCount(MediaTypes::kAudio, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo busInfo;
if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kOutput, i, busInfo) ==
kResultOk)
{
auto channelName = VST3::StringConvert::convert(busInfo.name, 128);
if (busInfo.busType == BusTypes::kMain) // aux channels not currently supported.
{
pluginInfo.audio_outputs(pluginInfo.audio_outputs() + busInfo.channelCount);
}
}
}
count = component->getBusCount(MediaTypes::kAudio, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo busInfo;
if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kInput, i, busInfo) ==
kResultOk)
{
auto channelName = VST3::StringConvert::convert(busInfo.name, 128);
if (busInfo.busType == BusTypes::kMain) // aux channels not currently supported.
{
pluginInfo.audio_inputs(pluginInfo.audio_inputs() + busInfo.channelCount);
}
}
}
count = component->getBusCount(MediaTypes::kEvent, BusDirections::kInput);
if (count >= 1)
{
pluginInfo.has_midi_input(1);
}
count = component->getBusCount(MediaTypes::kEvent, BusDirections::kOutput);
if (count >= 1)
{
pluginInfo.has_midi_output(1);
}
return info;
}
}
void Vst3HostImpl::MakeVst3Info(const std::filesystem::path &path, Vst3Host::PluginList &list)
{
EnsureContext();
unique_ptr<Vst3PluginInfo> result = std::make_unique<Vst3PluginInfo>();
result->filePath_ = path;
string error;
Module::Ptr module = Module::create(path.string(), error);
if (!module)
{
Lv2Log::error(SS("Vst3 load failed: " << error << "(" << path.string() << ")"));
return;
}
const PluginFactory &factory = module->getFactory();
for (const ClassInfo &classInfo : factory.classInfos())
{
if (classInfo.category() == kVstAudioEffectClass)
{
bool found = false;
std::string newUid = classInfo.ID().toString();
for (const auto &existingPlugin : list)
{
if (existingPlugin->uid_ == newUid)
{
found = true;
break; // first instance takes precedence (e.g. ~/.vst3 takes precedence over /usr/lib/vst3 )
}
}
if (!found)
{
std::unique_ptr<Vst3PluginInfo> pluginInfo = MakeDetailedPluginInfo(factory, classInfo, path);
list.push_back(std::move(pluginInfo));
}
}
}
}
bool Vst3HostImpl::havePluginsChanged() const
{
std::unordered_set<std::string> modules;
for (const auto &plugin : pluginList)
{
if (!std::filesystem::exists(plugin->filePath_))
{
return true;
}
modules.insert(plugin->filePath_);
}
Module::PathList pathList = Module::getModulePaths();
for (const auto &path : pathList)
{
if (!modules.contains(path))
{
return true;
}
}
return false;
}
const Vst3Host::PluginList &Vst3HostImpl::RefreshPlugins()
{
if (cacheFilePath.length() != 0)
{
LoadPluginCache();
}
if (havePluginsChanged())
{
RescanPlugins();
}
return pluginList;
}
const Vst3Host::PluginList &Vst3HostImpl::RescanPlugins()
{
Module::PathList pathList = Module::getModulePaths();
PluginList list;
//pathList.insert(pathList.begin(),"/home/pi/.vst3/mda-vst3.vst3");
for (auto &path : pathList)
{
if (path == std::string("/home/pi/.vst3/mda-vst3.vst3"))
{
Lv2Log::debug("Processing MDA plugins");
}
filesystem::path p{path};
MakeVst3Info(p, list);
}
this->pluginList = std::move(list);
SavePluginCache();
return pluginList;
}
static std::string ParseVst3Url(const std::string &url)
{
if (!url.starts_with("vst3:"))
{
throw Vst3Exception("Not a valid vst3 url.: " + url);
}
std::string strUrl = url.substr(5);
return strUrl;
}
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(long instanceId, const std::string &url, IHost *pHost)
{
EnsureContext();
auto uuid = ParseVst3Url(url);
std::unique_ptr<Vst3Effect> result;
for (const auto &plugin : pluginList)
{
if (plugin->uid_ == uuid)
{
return Vst3Effect::CreateInstance(instanceId, *(plugin.get()), pHost);
}
}
throw Vst3Exception("Plugin not found.");
}
static inline uint8_t Hex(char c)
{
if (c >= '0' && c <= '9') return c-'0';
if (c >= 'a' && c <= 'z') return c-'a'+10;
if (c >= 'A' && c <= 'Z') return c-'A'+10;
throw Vst3Exception("Invalid state bundle.");
}
static std::vector<uint8_t> HexToByteArray(const std::string&hexState)
{
std::vector<uint8_t> result;
result.resize(hexState.length()/2);
for (int i = 0; i < result.size(); ++i)
{
result[i] = Hex(hexState[i*2])*16 + Hex(hexState[i*2+1]);
}
return result;
}
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost)
{
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalBoardItem.instanceId(),pedalBoardItem.uri(),pHost);
auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri());
if (!pluginInfo)
{
throw Vst3Exception(SS("Plugin " << pedalBoardItem.pluginName() << " not found."));
}
if (pedalBoardItem.vstState().length() != 0)
{
std::vector<uint8_t> state = HexToByteArray(pedalBoardItem.vstState());
result->SetState(state);
for (ControlValue &controlValue: pedalBoardItem.controlValues())
{
int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
{
if (pluginInfo->controls()[i].symbol() == controlValue.key())
{
index = (int32_t)(pluginInfo->controls()[i].index());
break;
}
}
if (index != -1)
{
float value = result->GetControlValue(index);
controlValue.value(value);
}
}
} else {
for (const ControlValue &controlValue: pedalBoardItem.controlValues())
{
int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
{
if (pluginInfo->controls()[i].symbol() == controlValue.key())
{
index = (int32_t)(pluginInfo->controls()[i].index());
break;
}
}
if (index != -1)
{
result->SetControl(index,controlValue.value());
}
}
}
return result;
}
#include <fstream>
void Vst3HostImpl::LoadPluginCache()
{
if (cacheFilePath.length() != 0)
{
std::ifstream f(cacheFilePath);
if (f.is_open())
{
json_reader reader(f);
reader.read(&(this->pluginList));
}
}
}
void Vst3HostImpl::SavePluginCache()
{
if (cacheFilePath.length() != 0)
{
std::ofstream f(cacheFilePath);
if (f.is_open())
{
json_writer writer(f);
writer.write(this->pluginList);
}
}
}
Lv2PluginUiInfo *Vst3HostImpl::GetPluginInfo(const std::string&uri)
{
for (const auto &pluginInfo: pluginList)
{
if (pluginInfo->pluginInfo_.uri() == uri)
{
return &(pluginInfo->pluginInfo_);
}
}
return nullptr;
}