2471 lines
86 KiB
C++
2471 lines
86 KiB
C++
// 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 "pch.h"
|
|
#include "AtomConverter.hpp"
|
|
#include "HtmlHelper.hpp"
|
|
#include "PluginHost.hpp"
|
|
#include <lilv/lilv.h>
|
|
#include <stdexcept>
|
|
#include "Locale.hpp"
|
|
#include <string_view>
|
|
#include "Lv2Log.hpp"
|
|
#include <functional>
|
|
#include "Pedalboard.hpp"
|
|
#include "Lv2Effect.hpp"
|
|
#include "Lv2Pedalboard.hpp"
|
|
#include "JackConfiguration.hpp"
|
|
#include "lv2/urid/urid.h"
|
|
#include "lv2/ui/ui.h"
|
|
#include "lv2/core/lv2.h"
|
|
#include "ChannelRouterSettings.hpp"
|
|
|
|
// #include "lv2.h"
|
|
#include "lv2/atom/atom.h"
|
|
#include "lv2/time/time.h"
|
|
#include "lv2/state/state.h"
|
|
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
|
|
#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
|
|
#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
|
|
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
|
|
#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h"
|
|
#include <fstream>
|
|
#include "PiPedalException.hpp"
|
|
#include "StdErrorCapture.hpp"
|
|
#include "util.hpp"
|
|
#include "ModFileTypes.hpp"
|
|
#include <algorithm>
|
|
|
|
#include "Locale.hpp"
|
|
|
|
using namespace pipedal;
|
|
|
|
#define MAP_CHECK() \
|
|
{ \
|
|
if (GetPluginClass("http://lv2plug.in/ns/lv2core#Plugin") == nullptr) \
|
|
throw PiPedalStateException("Map is broken"); \
|
|
}
|
|
|
|
#define PLUGIN_MAP_CHECK() \
|
|
{ \
|
|
if (pHost->GetPluginClass("http://lv2plug.in/ns/lv2core#Plugin") == nullptr) \
|
|
throw PiPedalStateException("Map is broken"); \
|
|
}
|
|
|
|
#define P_LV2_CORE_URI "http://lv2plug.in/ns/lv2core#"
|
|
#define P_LV2_PPROPS "http://lv2plug.in/ns/ext/port-props#"
|
|
|
|
namespace pipedal
|
|
{
|
|
|
|
// in ttl files, but not header files.
|
|
|
|
static const char *LV2_MIDI_PLUGIN = "http://lv2plug.in/ns/lv2core#MIDIPlugin";
|
|
|
|
class PluginHost::Urids
|
|
{
|
|
public:
|
|
Urids(MapFeature &mapFeature)
|
|
{
|
|
atom__Float = mapFeature.GetUrid(LV2_ATOM__Float);
|
|
atom__Double = mapFeature.GetUrid(LV2_ATOM__Double);
|
|
atom_Int = mapFeature.GetUrid(LV2_ATOM__Int);
|
|
ui__portNotification = mapFeature.GetUrid(LV2_UI__portNotification);
|
|
ui__plugin = mapFeature.GetUrid(LV2_UI__plugin);
|
|
ui__protocol = mapFeature.GetUrid(LV2_UI__protocol);
|
|
ui__floatProtocol = mapFeature.GetUrid(LV2_UI__protocol);
|
|
ui__peakProtocol = mapFeature.GetUrid(LV2_UI__peakProtocol);
|
|
}
|
|
LV2_URID atom__Float;
|
|
LV2_URID atom__Double;
|
|
LV2_URID atom_Int;
|
|
LV2_URID ui__portNotification;
|
|
LV2_URID ui__plugin;
|
|
LV2_URID ui__protocol;
|
|
LV2_URID ui__floatProtocol;
|
|
LV2_URID ui__peakProtocol;
|
|
};
|
|
}
|
|
|
|
void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration)
|
|
{
|
|
this->vst3CachePath =
|
|
std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json";
|
|
this->vst3Enabled = configuration.IsVst3Enabled();
|
|
}
|
|
|
|
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
|
{
|
|
isA = lilv_new_uri(pWorld, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
rdfs__Comment = lilv_new_uri(pWorld, PluginHost::RDFS__comment);
|
|
rdfs__range = lilv_new_uri(pWorld, PluginHost::RDFS__range);
|
|
port_logarithmic = lilv_new_uri(pWorld, LV2_PORT_PROPS__logarithmic);
|
|
port__display_priority = lilv_new_uri(pWorld, LV2_PORT_PROPS__displayPriority);
|
|
port_range_steps = lilv_new_uri(pWorld, LV2_PORT_PROPS__rangeSteps);
|
|
integer_property_uri = lilv_new_uri(pWorld, LV2_CORE__integer);
|
|
enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration);
|
|
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
|
|
core__connectionOptional = lilv_new_uri(pWorld, LV2_CORE__connectionOptional);
|
|
core__isSideChain = lilv_new_uri(pWorld, LV2_CORE_PREFIX "isSideChain"); // missing in lv2.h
|
|
portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
|
|
portprops__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger);
|
|
portprops__expensive = lilv_new_uri(pWorld, LV2_PORT_PROPS__expensive);
|
|
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
|
|
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
|
|
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
|
|
units__unit = lilv_new_uri(pWorld, LV2_UNITS__unit);
|
|
units__render = lilv_new_uri(pWorld, LV2_UNITS__render);
|
|
|
|
invada_units__unit = lilv_new_uri(pWorld, "http://lv2plug.in/ns/extension/units#unit"); // a typo in invada plugin ttl files.
|
|
invada_portprops__logarithmic = lilv_new_uri(pWorld, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); // a typo in invada plugin ttl files.
|
|
|
|
atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
|
|
atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path);
|
|
atom__String = lilv_new_uri(pWorld, LV2_ATOM__String);
|
|
presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
|
|
state__state = lilv_new_uri(pWorld, LV2_STATE__state);
|
|
rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
|
|
|
|
lv2core__symbol = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
|
lv2core__name = lilv_new_uri(pWorld, LV2_CORE__name);
|
|
lv2core__shortName = lilv_new_uri(pWorld, LV2_CORE_PREFIX "shortName"); // ?? from mod sources
|
|
lv2core__index = lilv_new_uri(pWorld, LV2_CORE__index);
|
|
lv2core__Parameter = lilv_new_uri(pWorld, LV2_CORE_PREFIX "Parameter");
|
|
lv2core__minorVersion = lilv_new_uri(pWorld, LV2_CORE__minorVersion);
|
|
lv2core__microVersion = lilv_new_uri(pWorld, LV2_CORE__microVersion);
|
|
|
|
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
|
|
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
|
|
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
|
|
pipedalUI__directory = lilv_new_uri(pWorld, PIPEDAL_UI__directory);
|
|
pipedalUI__fileTypes = lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
|
|
pipedalUI__resourceDirectory = lilv_new_uri(pWorld, PIPEDAL_UI__resourceDirectory);
|
|
pipedalUI__fileProperty = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperty);
|
|
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
|
|
pipedalUI__mimeType = lilv_new_uri(pWorld, PIPEDAL_UI__mimeType);
|
|
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
|
|
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text);
|
|
pipedalUI__ledColor = lilv_new_uri(pWorld, PIPEDAL_UI__ledColor);
|
|
|
|
pipedalUI__frequencyPlot = lilv_new_uri(pWorld, PIPEDAL_UI__frequencyPlot);
|
|
pipedalUI__xLeft = lilv_new_uri(pWorld, PIPEDAL_UI__xLeft);
|
|
pipedalUI__xRight = lilv_new_uri(pWorld, PIPEDAL_UI__xRight);
|
|
pipedalUI__yTop = lilv_new_uri(pWorld, PIPEDAL_UI__yTop);
|
|
pipedalUI__yBottom = lilv_new_uri(pWorld, PIPEDAL_UI__yBottom);
|
|
pipedalUI__xLog = lilv_new_uri(pWorld, PIPEDAL_UI__xLog);
|
|
pipedalUI__yDb = lilv_new_uri(pWorld, PIPEDAL_UI__yDb);
|
|
pipedalUI__width = lilv_new_uri(pWorld, PIPEDAL_UI__width);
|
|
pipedalUI__graphicEq = lilv_new_uri(pWorld, PIPEDAL_UI__graphicEq);
|
|
|
|
ui__portNotification = lilv_new_uri(pWorld, LV2_UI__portNotification);
|
|
ui__plugin = lilv_new_uri(pWorld, LV2_UI__plugin);
|
|
ui__protocol = lilv_new_uri(pWorld, LV2_UI__protocol);
|
|
ui__floatProtocol = lilv_new_uri(pWorld, LV2_UI__protocol);
|
|
ui__peakProtocol = lilv_new_uri(pWorld, LV2_UI__peakProtocol);
|
|
ui__portIndex = lilv_new_uri(pWorld, LV2_UI__portIndex);
|
|
lv2__symbol = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
|
lv2__port = lilv_new_uri(pWorld, LV2_CORE__port);
|
|
|
|
#define MOD_PREFIX "http://moddevices.com/ns/mod#"
|
|
|
|
mod__label = lilv_new_uri(pWorld, MOD_PREFIX "label");
|
|
mod__brand = lilv_new_uri(pWorld, MOD_PREFIX "brand");
|
|
mod__preferMomentaryOffByDefault = lilv_new_uri(pWorld, MOD_PREFIX "preferMomentaryOffByDefault");
|
|
mod__preferMomentaryOnByDefault = lilv_new_uri(pWorld, MOD_PREFIX "preferMomentaryOnByDefault");
|
|
// ui:portNotification
|
|
// [
|
|
// ui:portIndex 3;
|
|
// ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
|
|
// ui:protocol ui:floatProtocol;
|
|
// // pipedal_ui:style pipedal_ui:text ;
|
|
// // pipedal_ui:redLevel 0;
|
|
// // pipedal_ui:yellowLevel -12;
|
|
// ]
|
|
|
|
time_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
|
|
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
|
|
time_beatsPerMinute = lilv_new_uri(pWorld, LV2_TIME__beatsPerMinute);
|
|
time_speed = lilv_new_uri(pWorld, LV2_TIME__speed);
|
|
|
|
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
|
|
|
|
patch__writable = lilv_new_uri(pWorld, LV2_PATCH__writable);
|
|
patch__readable = lilv_new_uri(pWorld, LV2_PATCH__readable);
|
|
pipedal_patch__readable = lilv_new_uri(pWorld, PIPEDAL_PATCH__readable);
|
|
|
|
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
|
|
|
mod__fileTypes = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#fileTypes");
|
|
mod__supportedExtensions = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#supportedExtensions");
|
|
|
|
buf_size__minBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__minBlockLength);
|
|
buf_size__maxBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__maxBlockLength);
|
|
buf_size__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength);
|
|
buf_size__coarseBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__coarseBlockLength);
|
|
port_groups__sideChainOf = lilv_new_uri(pWorld, LV2_PORT_GROUPS__sideChainOf);
|
|
|
|
|
|
}
|
|
|
|
void PluginHost::LilvUris::Free()
|
|
{
|
|
}
|
|
static int32_t nodesAsInt(const LilvNodes *nodes)
|
|
{
|
|
if (!nodes)
|
|
return 0;
|
|
|
|
LILV_FOREACH(nodes, iNode, nodes)
|
|
{
|
|
const LilvNode *node = lilv_nodes_get(nodes, iNode);
|
|
if (!node)
|
|
return 0;
|
|
if (lilv_node_is_int(node))
|
|
{
|
|
return lilv_node_as_int(node);
|
|
}
|
|
if (lilv_node_is_float(node))
|
|
{
|
|
return static_cast<int32_t>(lilv_node_as_float(node));
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static std::string nodeAsString(const LilvNode *node)
|
|
{
|
|
if (node == nullptr)
|
|
{
|
|
return "";
|
|
}
|
|
if (lilv_node_is_uri(node))
|
|
{
|
|
return lilv_node_as_uri(node);
|
|
}
|
|
if (lilv_node_is_string(node))
|
|
{
|
|
return lilv_node_as_string(node);
|
|
}
|
|
if (lilv_node_is_blank(node))
|
|
{
|
|
return "";
|
|
}
|
|
return "#TypeError";
|
|
}
|
|
static float nodeAsFloat(const LilvNode *node, float default_ = 0)
|
|
{
|
|
if (node == nullptr)
|
|
{
|
|
return default_;
|
|
}
|
|
if (lilv_node_is_float(node))
|
|
{
|
|
return lilv_node_as_float(node);
|
|
}
|
|
if (lilv_node_is_int(node))
|
|
{
|
|
return lilv_node_as_int(node);
|
|
}
|
|
if (lilv_node_is_blank(node))
|
|
{
|
|
return default_;
|
|
}
|
|
return default_;
|
|
}
|
|
|
|
void PluginHost::SetPluginStoragePath(const std::filesystem::path &path)
|
|
{
|
|
pluginStoragePath = path;
|
|
fileMetadataFeature.SetPluginStoragePath(path);
|
|
}
|
|
|
|
std::string PluginHost::GetPluginStoragePath() const
|
|
{
|
|
return pluginStoragePath;
|
|
}
|
|
|
|
PluginHost::PluginHost()
|
|
{
|
|
pWorld = nullptr;
|
|
|
|
lv2Features.push_back(mapFeature.GetMapFeature());
|
|
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
|
|
|
|
|
fileMetadataFeature.Prepare(mapFeature);
|
|
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
|
|
|
lv2Features.push_back(nullptr);
|
|
|
|
this->urids = new Urids(mapFeature);
|
|
}
|
|
|
|
|
|
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const ChannelSelection &channelSelection)
|
|
{
|
|
this->sampleRate = configuration.sampleRate();
|
|
if (configuration.isValid())
|
|
{
|
|
this->maxBufferSize = configuration.blockLength();
|
|
}
|
|
this->channelSelection = channelSelection;
|
|
}
|
|
|
|
PluginHost::~PluginHost()
|
|
{
|
|
delete lilvUris;
|
|
lilvUris = nullptr;
|
|
|
|
delete urids;
|
|
urids = nullptr;
|
|
delete mod_gui_uris;
|
|
mod_gui_uris = nullptr;
|
|
free_world();
|
|
}
|
|
|
|
void PluginHost::free_world()
|
|
{
|
|
if (pWorld)
|
|
{
|
|
lilv_world_free(pWorld);
|
|
pWorld = nullptr;
|
|
}
|
|
}
|
|
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const std::string &uri) const
|
|
{
|
|
auto it = this->classesMap.find(uri);
|
|
if (it == this->classesMap.end())
|
|
return nullptr;
|
|
return (*it).second;
|
|
}
|
|
|
|
void PluginHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
|
|
{
|
|
this->classesMap[pluginClass->uri()] = pluginClass;
|
|
for (int i = 0; i < pluginClass->children_.size(); ++i)
|
|
{
|
|
std::shared_ptr<Lv2PluginClass> child = pluginClass->children_[i];
|
|
AddJsonClassesToMap(pluginClass->children_[i]);
|
|
child->parent_ = pluginClass.get();
|
|
}
|
|
}
|
|
void PluginHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
|
|
{
|
|
std::ifstream f(jsonFile);
|
|
json_reader reader(f);
|
|
|
|
std::shared_ptr<Lv2PluginClass> classes;
|
|
reader.read(&classes);
|
|
|
|
AddJsonClassesToMap(classes);
|
|
this->classesLoaded = true;
|
|
|
|
MAP_CHECK();
|
|
}
|
|
|
|
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const LilvPluginClass *pClass)
|
|
{
|
|
MAP_CHECK();
|
|
|
|
std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
|
|
std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri];
|
|
return pResult;
|
|
}
|
|
std::shared_ptr<Lv2PluginClass> PluginHost::MakePluginClass(const LilvPluginClass *pClass)
|
|
{
|
|
std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
|
|
auto t = this->classesMap.find(uri);
|
|
if (t != classesMap.end())
|
|
{
|
|
return t->second;
|
|
}
|
|
const LilvNode *parentNode = lilv_plugin_class_get_parent_uri(pClass);
|
|
std::string parent_uri = nodeAsString(parentNode);
|
|
|
|
std::string name = nodeAsString(lilv_plugin_class_get_label(pClass));
|
|
|
|
std::shared_ptr<Lv2PluginClass> result = std::make_shared<Lv2PluginClass>(
|
|
name.c_str(), uri.c_str(), parent_uri.c_str());
|
|
|
|
classesMap[uri] = result;
|
|
return result;
|
|
}
|
|
|
|
void PluginHost::LoadPluginClassesFromLilv()
|
|
{
|
|
const auto classes = lilv_world_get_plugin_classes(pWorld);
|
|
|
|
LILV_FOREACH(plugin_classes, iPluginClass, classes)
|
|
{
|
|
const LilvPluginClass *pClass = lilv_plugin_classes_get(classes, iPluginClass);
|
|
// Get it to pre-populate the map.
|
|
MakePluginClass(pClass);
|
|
}
|
|
for (auto value : classesMap)
|
|
{
|
|
std::shared_ptr<Lv2PluginClass> lv2Class{value.second};
|
|
|
|
auto t = GetPluginClass(lv2Class->uri());
|
|
|
|
if (lv2Class && lv2Class->parent_uri().size() != 0)
|
|
{
|
|
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
|
|
if (parentClass)
|
|
{
|
|
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
|
|
ccClass->set_parent(parentClass);
|
|
Lv2PluginClass *ccParentClass = parentClass.get();
|
|
ccParentClass->add_child(lv2Class);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PluginHost::LoadLilv(const char *lv2Path)
|
|
{
|
|
|
|
this->plugins_.clear();
|
|
this->ui_plugins_.clear();
|
|
if (!classesLoaded)
|
|
{
|
|
this->classesMap.clear();
|
|
}
|
|
|
|
free_world();
|
|
|
|
Lv2Log::info("Scanning for LV2 Plugins");
|
|
|
|
setenv("LV2_PATH", lv2Path, true);
|
|
StdErrorCapture stdoutCapture; // captures lilv messages written to STDOUT.
|
|
pWorld = lilv_world_new();
|
|
{
|
|
|
|
lilv_world_load_all(pWorld);
|
|
}
|
|
// Not a safe pointer,because auto-deleting after freeWorld() would be death.
|
|
this->mod_gui_uris = new ModGuiUris(pWorld, mapFeature);
|
|
|
|
// LilvNode*lv2_path = lilv_new_file_uri(pWorld,NULL,lv2Path);
|
|
// lilv_world_set_option(world,LILV_OPTION_LV2_PATH,lv)
|
|
|
|
lilvUris = new LilvUris();
|
|
lilvUris->Initialize(pWorld);
|
|
|
|
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
|
|
|
LoadPluginClassesFromLilv();
|
|
|
|
LILV_FOREACH(plugins, iPlugin, plugins)
|
|
{
|
|
const LilvPlugin *lilvPlugin = lilv_plugins_get(plugins, iPlugin);
|
|
|
|
std::shared_ptr<Lv2PluginInfo> pluginInfo = std::make_shared<Lv2PluginInfo>(this, pWorld, lilvPlugin);
|
|
|
|
Lv2Log::debug("Plugin: " + pluginInfo->name());
|
|
|
|
if (pluginInfo->hasCvPorts())
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
|
}
|
|
else if (pluginInfo->hasUnsupportedPatchProperties())
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. (Has unsupported patch parameters).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
|
}
|
|
#if !SUPPORT_MIDI
|
|
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN)
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
|
}
|
|
#endif
|
|
else if (!pluginInfo->is_valid())
|
|
{
|
|
auto &ports = pluginInfo->ports();
|
|
for (int i = 0; i < ports.size(); ++i)
|
|
{
|
|
auto &port = ports[i];
|
|
if (!port->is_valid())
|
|
{
|
|
Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str());
|
|
}
|
|
}
|
|
Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
|
}
|
|
else
|
|
{
|
|
this->plugins_.push_back(pluginInfo);
|
|
}
|
|
}
|
|
auto messages = stdoutCapture.GetOutputLines();
|
|
|
|
for (const std::string &s : messages)
|
|
{
|
|
if (s.length() != 0)
|
|
{
|
|
Lv2Log::info("lilv: " + s);
|
|
}
|
|
}
|
|
|
|
auto collator = Locale::GetInstance()->GetCollator();
|
|
auto compare = [&collator](
|
|
const std::shared_ptr<Lv2PluginInfo> &left,
|
|
const std::shared_ptr<Lv2PluginInfo> &right)
|
|
{
|
|
const char *pb1 = left->name().c_str();
|
|
const char *pb2 = right->name().c_str();
|
|
return collator->Compare(
|
|
left->name(), right->name()) < 0;
|
|
};
|
|
std::sort(this->plugins_.begin(), this->plugins_.end(), compare);
|
|
|
|
for (auto plugin : this->plugins_)
|
|
{
|
|
pluginsByUri[plugin->uri()] = plugin;
|
|
|
|
Lv2PluginUiInfo info(this, plugin.get());
|
|
if (plugin->is_valid())
|
|
{
|
|
#if 1
|
|
// no plugins with more than 2 inputs or outputs.
|
|
// no zero-input or zero-output plugins (temporarily disables midi plugins)
|
|
// no zero output devices (permanent, I think)
|
|
if (info.audio_inputs() > 2 || info.audio_outputs() > 2)
|
|
{
|
|
Lv2Log::debug(
|
|
"Plugin %s (%s) skipped. %d inputs, %d outputs.", plugin->name().c_str(), plugin->uri().c_str(),
|
|
(int)info.audio_inputs(), (int)info.audio_outputs());
|
|
}
|
|
else if (info.audio_inputs() == 0 && info.audio_outputs() == 0)
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No audio i/o.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
else if (info.audio_inputs() == 0)
|
|
{
|
|
// temporarily disable this feature.
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
#elif SUPPORT_MIDI
|
|
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
else if (info.audio_outputs() == 0 && !info.has_midi_output())
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
#else
|
|
if (info.audio_inputs() == 0)
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
else if (info.audio_outputs() == 0)
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
if (info.audio_inputs() == 0)
|
|
{
|
|
Lv2Log::debug("************* ZERO INPUTS: %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
|
}
|
|
ui_plugins_.push_back(std::move(info));
|
|
}
|
|
}
|
|
}
|
|
|
|
#if ENABLE_VST3
|
|
if (vst3Enabled)
|
|
{
|
|
Lv2Log::info("Scanning for VST3 Plugins");
|
|
this->vst3Host = Vst3Host::CreateInstance(
|
|
this->vst3CachePath);
|
|
this->vst3Host->RefreshPlugins();
|
|
|
|
const auto &vst3PluginList = this->vst3Host->getPluginList();
|
|
for (const auto &vst3Plugin : vst3PluginList)
|
|
{
|
|
// copy not move!
|
|
ui_plugins_.push_back(vst3Plugin->pluginInfo_);
|
|
}
|
|
auto ui_compare = [&collation](
|
|
Lv2PluginUiInfo &left,
|
|
Lv2PluginUiInfo &right)
|
|
{
|
|
const char *pb1 = left.name().c_str();
|
|
const char *pb2 = right.name().c_str();
|
|
return collation.compare(
|
|
pb1, pb1 + left.name().size(),
|
|
pb2, pb2 + right.name().size()) < 0;
|
|
};
|
|
std::sort(this->ui_plugins_.begin(), this->ui_plugins_.end(), ui_compare);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
|
|
{
|
|
std::vector<std::string> result;
|
|
LILV_FOREACH(nodes, iNode, nodes)
|
|
{
|
|
result.push_back(nodeAsString(lilv_nodes_get(nodes, iNode)));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const char *PluginHost::RDFS__comment = "http://www.w3.org/2000/01/rdf-schema#"
|
|
"comment";
|
|
|
|
const char *PluginHost::RDFS__range = "http://www.w3.org/2000/01/rdf-schema#"
|
|
"range";
|
|
|
|
LilvNode *PluginHost::get_comment(const std::string &uri)
|
|
{
|
|
AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
|
|
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris->rdfs__Comment, nullptr);
|
|
return result;
|
|
}
|
|
|
|
static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shared_ptr<Lv2PortInfo> &p2)
|
|
{
|
|
return p1->index() < p2->index();
|
|
}
|
|
|
|
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
|
|
{
|
|
AutoLilvNodes nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris->presets__preset);
|
|
bool result = false;
|
|
LILV_FOREACH(nodes, iNode, nodes)
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static std::vector<UiFileType> ToPiPedalFileTypes(
|
|
const std::set<std::string> &modFileTypes)
|
|
{
|
|
std::vector<UiFileType> result;
|
|
for (const auto &fileType : modFileTypes)
|
|
{
|
|
UiFileType uiFileType;
|
|
std::string type = fileType;
|
|
if (type.find("/") != std::string::npos) // mime type?
|
|
{
|
|
UiFileType t = UiFileType(type, type, "");
|
|
result.push_back(t);
|
|
}
|
|
else
|
|
{
|
|
// add a leading dot.
|
|
if (!type.starts_with("."))
|
|
{
|
|
type = "." + type;
|
|
}
|
|
auto t = UiFileType(SS(type << " file"), type);
|
|
result.push_back(t);
|
|
}
|
|
}
|
|
std::sort(result.begin(), result.end(),
|
|
[](const UiFileType &left, const UiFileType &right)
|
|
{
|
|
return left.label() < right.label();
|
|
});
|
|
return result;
|
|
}
|
|
|
|
Lv2PluginInfo::FindWritablePathPropertiesResult
|
|
Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin)
|
|
{
|
|
// example:
|
|
|
|
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
|
|
// a lv2:Parameter;
|
|
// rdfs:label "Model";
|
|
// rdfs:range atom:Path.
|
|
// ...
|
|
// patch:writable <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>;
|
|
|
|
LilvWorld *pWorld = lv2Host->getWorld();
|
|
AutoLilvNode pluginUri = lilv_plugin_get_uri(pPlugin);
|
|
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, pluginUri, lv2Host->lilvUris->patch__writable, nullptr);
|
|
|
|
std::vector<UiFileProperty::ptr> fileProperties;
|
|
|
|
bool unsupportedPatchProperty = false;
|
|
LILV_FOREACH(nodes, iNode, patchWritables)
|
|
{
|
|
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
|
|
if (propertyUri)
|
|
{
|
|
// a lv2:Parameter?
|
|
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->isA, lv2Host->lilvUris->lv2core__Parameter))
|
|
{
|
|
// rfs:range atom:Path?
|
|
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->rdfs__range, lv2Host->lilvUris->atom__Path))
|
|
{
|
|
|
|
AutoLilvNode label = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->rdfs__label, nullptr);
|
|
std::string strLabel = label.AsString();
|
|
|
|
if (strLabel.length() == 0)
|
|
{
|
|
strLabel = "File";
|
|
}
|
|
|
|
std::filesystem::path path = this->bundle_path();
|
|
path = path.parent_path();
|
|
std::string lv2DirectoryName = path.filename().string();
|
|
// we have a valid path property!
|
|
|
|
auto fileProperty =
|
|
std::make_shared<UiFileProperty>(
|
|
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
|
|
|
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->lv2core__index, nullptr);
|
|
int32_t index = indexNode.AsInt(-1);
|
|
fileProperty->index(index);
|
|
|
|
// if there's a pipedalui_fileTypes node, use that instead.
|
|
|
|
AutoLilvNode mod__fileTypes = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
|
|
|
// default: everything.
|
|
std::string fileTypes = ModFileTypes::DEFAULT_FILE_TYPES;
|
|
if (mod__fileTypes)
|
|
{
|
|
// "nam,nammodel"
|
|
fileTypes = mod__fileTypes.AsString();
|
|
}
|
|
std::string modFileExtensions;
|
|
AutoLilvNode mod__extensionsNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->mod__supportedExtensions, nullptr);
|
|
{
|
|
// "wav,flac,mp3"
|
|
modFileExtensions = mod__extensionsNode.AsString();
|
|
}
|
|
std::string fileExtensions = modFileExtensions;
|
|
ModFileTypes modFileTypes(fileTypes);
|
|
|
|
// Legacy case: audio_uploads/<plugin_directory> exists.
|
|
|
|
std::filesystem::path bundleDirectoryName = std::filesystem::path(bundle_path()).parent_path().filename();
|
|
std::filesystem::path legacyUploadPath = lv2Host->MapPath(bundleDirectoryName.string());
|
|
|
|
fileProperty->setModFileTypes(modFileTypes);
|
|
|
|
fileProperty->directory(bundleDirectoryName);
|
|
|
|
if (std::filesystem::exists(legacyUploadPath) && !std::filesystem::exists(legacyUploadPath / ".migrated"))
|
|
{
|
|
fileProperty->useLegacyModDirectory(true);
|
|
fileProperty->directory(bundleDirectoryName);
|
|
modFileTypes.rootDirectories().push_back(bundleDirectoryName.filename()); // push the private directory!
|
|
}
|
|
else
|
|
{
|
|
if (modFileTypes.rootDirectories().size() == 1)
|
|
{
|
|
std::string modName = modFileTypes.rootDirectories()[0];
|
|
auto modDirectory = modFileTypes.GetModDirectory(modName);
|
|
fileProperty->directory(modDirectory->pipedalPath);
|
|
std::set<std::string> fileExtensions = modDirectory->fileExtensions;
|
|
if (!modFileExtensions.empty())
|
|
{
|
|
auto extensions = split(modFileExtensions, ',');
|
|
fileExtensions = std::set<std::string>(extensions.begin(), extensions.end());
|
|
}
|
|
fileProperty->fileTypes(ToPiPedalFileTypes(fileExtensions));
|
|
}
|
|
}
|
|
|
|
fileProperties.push_back(fileProperty);
|
|
}
|
|
else
|
|
{
|
|
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->rdfs__range, lv2Host->lilvUris->atom__String))
|
|
{
|
|
|
|
} else {
|
|
unsupportedPatchProperty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FindWritablePathPropertiesResult result;
|
|
|
|
if (fileProperties.size() != 0)
|
|
{
|
|
std::sort(
|
|
fileProperties.begin(),
|
|
fileProperties.end(),
|
|
[](const UiFileProperty::ptr &left, const UiFileProperty::ptr &right)
|
|
{
|
|
// properies with indexes first.
|
|
int32_t indexL = left->index();
|
|
if (indexL < 0)
|
|
indexL = std::numeric_limits<int32_t>::max();
|
|
int32_t indexR = right->index();
|
|
if (indexR < 0)
|
|
indexR = std::numeric_limits<int32_t>::max();
|
|
if (indexL < indexR)
|
|
return true;
|
|
if (indexL > indexR)
|
|
return false;
|
|
|
|
// there is no natural order. TTL indexing means that the order we see them in is random.
|
|
// We can't order them sensibly. Let's at least order them consistently.
|
|
return left->label() < right->label(); });
|
|
result.pipedalUi = std::make_shared<PiPedalUI>(std::move(fileProperties));
|
|
}
|
|
result.hasUnsupportedPatchProperties = unsupportedPatchProperty;
|
|
return result;
|
|
}
|
|
|
|
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
|
{
|
|
AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin);
|
|
if (!bundleUriNode)
|
|
throw std::logic_error("Invalid bundle uri.");
|
|
|
|
std::string bundleUri = bundleUriNode.AsUri();
|
|
|
|
char *lilvBundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr);
|
|
std::string bundlePath = lilvBundlePath;
|
|
lilv_free(lilvBundlePath);
|
|
|
|
if (bundlePath.length() == 0)
|
|
throw std::logic_error("Bundle uri is not a file uri.");
|
|
|
|
this->bundle_path_ = bundlePath;
|
|
|
|
this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin);
|
|
|
|
AutoLilvNode plugUri = lilv_plugin_get_uri(pPlugin);
|
|
this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin));
|
|
|
|
AutoLilvNode name = (lilv_plugin_get_name(pPlugin));
|
|
this->name_ = nodeAsString(name);
|
|
|
|
AutoLilvNodes minorVersion = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->lv2core__minorVersion);
|
|
this->minorVersion_ = nodesAsInt(minorVersion);
|
|
|
|
AutoLilvNodes microVersion = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->lv2core__microVersion);
|
|
this->microVersion_ = nodesAsInt(microVersion);
|
|
|
|
AutoLilvNode brand = lilv_world_get(pWorld, plugUri, lv2Host->lilvUris->mod__brand, nullptr);
|
|
this->brand_ = nodeAsString(brand);
|
|
|
|
AutoLilvNode label = lilv_world_get(pWorld, plugUri, lv2Host->lilvUris->mod__label, nullptr);
|
|
this->label_ = nodeAsString(label);
|
|
if (label_.length() == 0)
|
|
{
|
|
this->label_ = this->name_;
|
|
}
|
|
|
|
AutoLilvNode author_name = (lilv_plugin_get_author_name(pPlugin));
|
|
this->author_name_ = nodeAsString(author_name);
|
|
|
|
AutoLilvNode author_homepage = (lilv_plugin_get_author_homepage(pPlugin));
|
|
this->author_homepage_ = nodeAsString(author_homepage);
|
|
|
|
const LilvPluginClass *pClass = lilv_plugin_get_class(pPlugin);
|
|
this->plugin_class_ = nodeAsString(lilv_plugin_class_get_uri(pClass));
|
|
|
|
if (this->plugin_class_ == "") // carla does this.
|
|
{
|
|
// set to plain Plugin.
|
|
this->plugin_class_ = "http://lv2plug.in/ns/lv2core#Plugin";
|
|
}
|
|
|
|
AutoLilvNodes required_features = lilv_plugin_get_required_features(pPlugin);
|
|
this->required_features_ = nodeAsStringArray(required_features);
|
|
|
|
AutoLilvNodes supported_features = lilv_plugin_get_supported_features(pPlugin);
|
|
this->supported_features_ = nodeAsStringArray(supported_features);
|
|
|
|
AutoLilvNodes optional_features = lilv_plugin_get_optional_features(pPlugin);
|
|
this->optional_features_ = nodeAsStringArray(optional_features);
|
|
|
|
AutoLilvNodes extensions = lilv_plugin_get_extension_data(pPlugin);
|
|
this->extensions_ = nodeAsStringArray(extensions);
|
|
|
|
AutoLilvNode comment = lv2Host->get_comment(this->uri_);
|
|
this->comment_ = nodeAsString(comment);
|
|
|
|
uint32_t ports = lilv_plugin_get_num_ports(pPlugin);
|
|
|
|
bool isValid = true;
|
|
std::vector<std::string> portGroups;
|
|
|
|
for (uint32_t i = 0; i < ports; ++i)
|
|
{
|
|
const LilvPort *pPort = lilv_plugin_get_port_by_index(pPlugin, i);
|
|
|
|
std::shared_ptr<Lv2PortInfo> portInfo = std::make_shared<Lv2PortInfo>(lv2Host, pPlugin, pPort);
|
|
if (!portInfo->is_valid())
|
|
{
|
|
isValid = false;
|
|
}
|
|
const auto &portGroup = portInfo->port_group();
|
|
if (portGroup.size() != 0)
|
|
{
|
|
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
|
|
{
|
|
portGroups.push_back(portGroup);
|
|
}
|
|
}
|
|
ports_.push_back(std::move(portInfo));
|
|
}
|
|
|
|
for (auto &portGroup : portGroups)
|
|
{
|
|
port_groups_.push_back(std::make_shared<Lv2PortGroup>(lv2Host, portGroup));
|
|
}
|
|
|
|
for (int i = 0; i < this->required_features_.size(); ++i)
|
|
{
|
|
if (!this->IsSupportedFeature(this->required_features_[i]))
|
|
{
|
|
Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
|
|
isValid = false;
|
|
}
|
|
}
|
|
AutoLilvNode minBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__minBlockLength,nullptr);
|
|
this->minBlockLength_ = minBlockLength.AsFloat(-1);
|
|
AutoLilvNode maxBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__maxBlockLength,nullptr);
|
|
this->maxBlockLength_ = maxBlockLength.AsFloat(-1);
|
|
|
|
|
|
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
|
|
|
|
|
// Check the portgroup for audio inputs to see if they are sidechains.
|
|
std::string last_sidechain_title;
|
|
for (auto&port: this->ports_)
|
|
{
|
|
if (port->is_audio_port() && port->is_input()) {
|
|
if (!port->port_group().empty())
|
|
{
|
|
for (auto &pg: this->port_groups_)
|
|
{
|
|
if (pg->uri() == port->port_group())
|
|
{
|
|
if (!pg->sideChainOf().empty())
|
|
{
|
|
port->is_sidechain(true);
|
|
audio_sidechain_title_ = pg->name();
|
|
if (last_sidechain_title.empty()) {
|
|
last_sidechain_title = pg->name();
|
|
}
|
|
else if (last_sidechain_title != pg->name()) {
|
|
// multiple sidechain titles!
|
|
isValid = false;
|
|
Lv2Log::debug("Plugin %s (%s) has multiple sidechain titles (%s and %s).",
|
|
this->name_.c_str(),
|
|
this->uri_.c_str(),
|
|
last_sidechain_title.c_str(),
|
|
pg->name().c_str());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (port->is_sidechain()) {
|
|
// port is marked as sidechain, but has no port group.
|
|
if (last_sidechain_title.empty())
|
|
{
|
|
last_sidechain_title = port->name();
|
|
this->audio_sidechain_title_ = port->name();
|
|
}
|
|
else if (last_sidechain_title != port->name()) {
|
|
isValid = false;
|
|
Lv2Log::debug("Plugin %s (%s) has multiple sidechain ports, but no portgroup. (%s)",
|
|
this->name_.c_str(),
|
|
this->uri_.c_str(),
|
|
port->name().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Fetch patch properties.
|
|
{
|
|
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__writable, nullptr);
|
|
|
|
bool unsupportedPatchProperty = false;
|
|
LILV_FOREACH(nodes, iNode, patchWritables)
|
|
{
|
|
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
|
|
if (propertyUri)
|
|
{
|
|
// a lv2:Parameter?
|
|
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->isA, lv2Host->lilvUris->lv2core__Parameter))
|
|
{
|
|
Lv2PatchPropertyInfo propertyInfo{lv2Host, propertyUri};
|
|
propertyInfo.writable(true);
|
|
patchProperties_.push_back(std::move(propertyInfo));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
AutoLilvNodes patchReadables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__readable, nullptr);
|
|
|
|
bool unsupportedPatchProperty = false;
|
|
LILV_FOREACH(nodes, iNode, patchReadables)
|
|
{
|
|
AutoLilvNode propertyUri = lilv_nodes_get(patchReadables, iNode);
|
|
if (propertyUri)
|
|
{
|
|
std::string uri = propertyUri.AsUri();
|
|
bool found = false;
|
|
for (auto &property : patchProperties_)
|
|
{
|
|
if (property.uri() == uri)
|
|
{
|
|
property.readable(true);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->isA, lv2Host->lilvUris->lv2core__Parameter))
|
|
{
|
|
Lv2PatchPropertyInfo propertyInfo{lv2Host, propertyUri};
|
|
propertyInfo.readable(true);
|
|
patchProperties_.push_back(std::move(propertyInfo));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// default state.
|
|
{
|
|
AutoLilvNodes stateNodes = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->state__state);
|
|
if (stateNodes)
|
|
{
|
|
auto stateNode = lilv_nodes_get_first(stateNodes);
|
|
LilvState *defaultState = lilv_state_new_from_world(pWorld, lv2Host->GetMapFeature().GetMap(), stateNode);
|
|
if (defaultState)
|
|
{
|
|
Lv2Log::debug("Plugin %s (%s) has default state.", this->name_.c_str(), this->uri_.c_str());
|
|
this->hasDefaultState_ = true;
|
|
|
|
lilv_state_free(defaultState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fetch pipedal plugin UI
|
|
|
|
AutoLilvNode pipedalUINode = lilv_world_get(
|
|
pWorld,
|
|
lilv_plugin_get_uri(pPlugin),
|
|
lv2Host->lilvUris->pipedalUI__ui,
|
|
nullptr);
|
|
if (pipedalUINode)
|
|
{
|
|
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
|
|
}
|
|
else
|
|
{
|
|
// look for
|
|
auto result = FindWritablePathProperties(lv2Host, pPlugin);
|
|
this->piPedalUI_ = result.pipedalUi;
|
|
this->hasUnsupportedPatchProperties_ = result.hasUnsupportedPatchProperties;
|
|
}
|
|
|
|
int nInputs = 0;
|
|
int nSideChains = 0;
|
|
for (size_t i = 0; i < ports_.size(); ++i)
|
|
{
|
|
auto port = ports_[i];
|
|
if (port->is_audio_port() && port->is_input())
|
|
{
|
|
if (!port->is_sidechain()) {
|
|
if (nInputs >= 2)
|
|
{
|
|
isValid = false;
|
|
Lv2Log::debug("Plugin %s (%s) has more than 2 audio inputs.", this->name_.c_str(), this->uri_.c_str());
|
|
break;
|
|
}
|
|
++nInputs;
|
|
} else {
|
|
if (nSideChains >= 2) {
|
|
Lv2Log::debug("Plugin %s (%s) has more than 2 audio sidechain inputs.", this->name_.c_str(), this->uri_.c_str());
|
|
isValid = false;
|
|
break;
|
|
}
|
|
++nSideChains;
|
|
}
|
|
}
|
|
}
|
|
int nOutputs = 0;
|
|
for (size_t i = 0; i < ports_.size(); ++i)
|
|
{
|
|
auto port = ports_[i];
|
|
if (port->is_audio_port() && port->is_output())
|
|
{
|
|
if (nOutputs >= 2 && !port->connection_optional())
|
|
{
|
|
isValid = false;
|
|
break;
|
|
}
|
|
++nOutputs;
|
|
}
|
|
}
|
|
if (nOutputs == 0 || nInputs == 0)
|
|
{
|
|
isValid = false;
|
|
}
|
|
this->modGui_ = ModGui::Create(lv2Host, pPlugin);
|
|
this->is_valid_ = isValid;
|
|
}
|
|
|
|
std::vector<std::string> supportedFeatures = {
|
|
LV2_URID__map,
|
|
LV2_URID__unmap,
|
|
LV2_LOG__log,
|
|
LV2_WORKER__schedule,
|
|
LV2_OPTIONS__options,
|
|
LV2_BUF_SIZE__boundedBlockLength,
|
|
LV2_BUF_SIZE__fixedBlockLength,
|
|
LV2_BUF_SIZE__powerOf2BlockLength,
|
|
LV2_CORE__isLive,
|
|
LV2_STATE__loadDefaultState,
|
|
LV2_STATE__makePath,
|
|
LV2_STATE__mapPath,
|
|
LV2_STATE__freePath,
|
|
LV2_CORE__inPlaceBroken,
|
|
PIPEDAL_HOST_FEATURE,
|
|
PIPEDAL__FILE_METADATA_FEATURE,
|
|
|
|
// UI features that we can ignore, since we won't load their ui.
|
|
"http://lv2plug.in/ns/extensions/ui#makeResident",
|
|
"http://lv2plug.in/ns/ext/port-props#supportsStrictBounds"
|
|
|
|
};
|
|
|
|
bool Lv2PluginInfo::IsSupportedFeature(const std::string &feature) const
|
|
{
|
|
for (int i = 0; i < supportedFeatures.size(); ++i)
|
|
{
|
|
if (supportedFeatures[i] == feature)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
Lv2PluginInfo::~Lv2PluginInfo()
|
|
{
|
|
}
|
|
|
|
bool Lv2PortInfo::is_a(PluginHost *lv2Plugins, const char *classUri)
|
|
{
|
|
return classes_.is_a(lv2Plugins, classUri);
|
|
}
|
|
|
|
static float getFloat(const LilvNode *pNode)
|
|
{
|
|
if (lilv_node_is_float(pNode))
|
|
{
|
|
return lilv_node_as_float(pNode);
|
|
}
|
|
if (lilv_node_is_int(pNode))
|
|
{
|
|
return lilv_node_as_int(pNode);
|
|
}
|
|
throw std::invalid_argument("Invalid float node.");
|
|
}
|
|
|
|
static bool scale_points_sort_compare(const Lv2ScalePoint &v1, const Lv2ScalePoint &v2)
|
|
{
|
|
return v1.value() < v2.value();
|
|
}
|
|
Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
|
|
{
|
|
auto pWorld = host->pWorld;
|
|
index_ = lilv_port_get_index(plugin, pPort);
|
|
symbol_ = nodeAsString(lilv_port_get_symbol(plugin, pPort));
|
|
|
|
AutoLilvNode name = lilv_port_get_name(plugin, pPort);
|
|
name_ = nodeAsString(name);
|
|
|
|
classes_ = host->GetPluginPortClass(plugin, pPort);
|
|
|
|
AutoLilvNode minNode, maxNode, defaultNode;
|
|
min_value_ = 0;
|
|
max_value_ = 1;
|
|
default_value_ = 0;
|
|
lilv_port_get_range(plugin, pPort, defaultNode, minNode, maxNode);
|
|
if (defaultNode)
|
|
{
|
|
default_value_ = getFloat(defaultNode);
|
|
}
|
|
if (minNode)
|
|
{
|
|
min_value_ = getFloat(minNode);
|
|
}
|
|
if (maxNode)
|
|
{
|
|
max_value_ = getFloat(maxNode);
|
|
}
|
|
if (default_value_ > max_value_)
|
|
default_value_ = max_value_;
|
|
if (default_value_ < min_value_)
|
|
default_value_ = min_value_;
|
|
|
|
this->is_logarithmic_ = lilv_port_has_property(plugin, pPort, host->lilvUris->port_logarithmic);
|
|
|
|
// typo in invada plugins.
|
|
this->is_logarithmic_ |= lilv_port_has_property(plugin, pPort, host->lilvUris->invada_portprops__logarithmic);
|
|
|
|
AutoLilvNodes priority_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris->port__display_priority);
|
|
|
|
this->display_priority_ = -1;
|
|
if (priority_nodes)
|
|
{
|
|
auto priority_node = lilv_nodes_get_first(priority_nodes);
|
|
if (priority_node)
|
|
{
|
|
this->display_priority_ = lilv_node_as_int(priority_node);
|
|
}
|
|
}
|
|
|
|
AutoLilvNodes range_steps_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris->port_range_steps);
|
|
this->range_steps_ = 0;
|
|
if (range_steps_nodes)
|
|
{
|
|
auto range_steps_node = lilv_nodes_get_first(range_steps_nodes);
|
|
if (range_steps_node)
|
|
{
|
|
this->range_steps_ = lilv_node_as_int(range_steps_node);
|
|
}
|
|
}
|
|
this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->integer_property_uri);
|
|
this->mod_momentaryOffByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOffByDefault);
|
|
this->mod_momentaryOnByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOnByDefault);
|
|
this->is_expensive_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__expensive);
|
|
this->pipedal_graphicEq_ = lilv_port_has_property(plugin, pPort, host->lilvUris->pipedalUI__graphicEq);
|
|
|
|
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->enumeration_property_uri);
|
|
|
|
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__toggled);
|
|
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri);
|
|
this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional);
|
|
this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger);
|
|
this->is_sidechain_ = lilv_port_has_property(plugin,pPort,host->lilvUris->core__isSideChain);
|
|
|
|
AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor);
|
|
if (port_ledColor)
|
|
{
|
|
auto value = lilv_node_as_string(port_ledColor);
|
|
if (value)
|
|
{
|
|
this->pipedal_ledColor_ = value;
|
|
}
|
|
}
|
|
|
|
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
|
|
LILV_FOREACH(scale_points, iSP, pScalePoints)
|
|
{
|
|
const LilvScalePoint *pSP = lilv_scale_points_get(pScalePoints, iSP);
|
|
float value = nodeAsFloat(lilv_scale_point_get_value(pSP));
|
|
auto label = nodeAsString(lilv_scale_point_get_label(pSP));
|
|
scale_points_.push_back(Lv2ScalePoint(value, label));
|
|
}
|
|
lilv_scale_points_free(pScalePoints);
|
|
|
|
// sort is what we want, but the implementation is broken in g++ 8.3
|
|
|
|
std::sort(scale_points_.begin(), scale_points_.end(), scale_points_sort_compare);
|
|
|
|
is_input_ = is_a(host, LV2_CORE__InputPort);
|
|
is_output_ = is_a(host, LV2_CORE__OutputPort);
|
|
|
|
is_control_port_ = is_a(host, LV2_CORE__ControlPort);
|
|
is_audio_port_ = is_a(host, LV2_CORE__AudioPort);
|
|
is_atom_port_ = is_a(host, LV2_ATOM__AtomPort);
|
|
is_cv_port_ = is_a(host, LV2_CORE__CVPort);
|
|
|
|
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris->midi__event);
|
|
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris->time_Position);
|
|
|
|
AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris->core__designation);
|
|
designation_ = nodeAsString(designationValue);
|
|
is_bypass_ = designation_ == LV2_CORE__enabled;
|
|
|
|
AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris->portgroups__group);
|
|
port_group_ = nodeAsString(portGroup_value);
|
|
|
|
AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris->units__unit);
|
|
if (unitsValueUri)
|
|
{
|
|
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
|
|
if (this->units_ == Units::unknown)
|
|
{
|
|
// try for a custom format string.
|
|
AutoLilvNode render = lilv_world_get(
|
|
pWorld,
|
|
unitsValueUri,
|
|
host->lilvUris->units__render,
|
|
nullptr);
|
|
if (render)
|
|
{
|
|
const char *strRender = lilv_node_as_string(render);
|
|
custom_units_ = strRender;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// invada plugins use the wrong URI.
|
|
AutoLilvNode invadaUnitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris->invada_units__unit);
|
|
if (invadaUnitsValueUri)
|
|
{
|
|
std::string uri = nodeAsString(invadaUnitsValueUri);
|
|
static const char *INCORRECT_URI = "http://lv2plug.in/ns/extension/units#";
|
|
static const char *CORRECT_URI = "http://lv2plug.in/ns/extensions/units#";
|
|
if (uri.starts_with(INCORRECT_URI))
|
|
{
|
|
uri = uri.replace(0, strlen(INCORRECT_URI), CORRECT_URI);
|
|
}
|
|
this->units_ = UriToUnits(uri);
|
|
}
|
|
else
|
|
{
|
|
this->units(Units::none);
|
|
}
|
|
}
|
|
|
|
AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris->rdfs__Comment);
|
|
this->comment_ = nodeAsString(commentNode);
|
|
|
|
AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris->atom__bufferType);
|
|
|
|
this->buffer_type_ = "";
|
|
if (bufferType)
|
|
{
|
|
this->buffer_type_ = nodeAsString(bufferType);
|
|
}
|
|
|
|
is_valid_ = is_control_port_ ||
|
|
((is_input_ || is_output_) && (is_audio_port_ || is_atom_port_ || is_cv_port_));
|
|
}
|
|
|
|
Lv2PluginClasses PluginHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
|
|
{
|
|
std::vector<std::string> result;
|
|
const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort);
|
|
if (nodes != nullptr)
|
|
{
|
|
LILV_FOREACH(nodes, iNode, nodes)
|
|
{
|
|
const LilvNode *node = lilv_nodes_get(nodes, iNode);
|
|
std::string classUri = nodeAsString(node);
|
|
result.push_back(classUri);
|
|
}
|
|
}
|
|
return Lv2PluginClasses(result);
|
|
}
|
|
|
|
bool Lv2PluginClass::is_a(const std::string &classUri) const
|
|
{
|
|
if (this->uri_ == classUri)
|
|
return true;
|
|
if (parent_)
|
|
{
|
|
return parent_->is_a(classUri);
|
|
}
|
|
// some classes have parent uris that are not themselves
|
|
// classes because the class tree is incomplete.
|
|
if (this->parent_uri_ == classUri)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool Lv2PluginClasses::is_a(PluginHost *lv2Plugins, const char *classUri) const
|
|
{
|
|
std::string classUri_(classUri);
|
|
for (auto i : classes_)
|
|
{
|
|
if (lv2Plugins->is_a(classUri_, i))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PluginHost::is_a(const std::string &class_, const std::string &target_class)
|
|
{
|
|
if (class_ == target_class)
|
|
return true;
|
|
std::shared_ptr<Lv2PluginClass> pClass = this->GetPluginClass(target_class);
|
|
if (pClass)
|
|
{
|
|
return pClass->is_a(class_);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
|
: uri_(plugin->uri()),
|
|
name_(plugin->name()),
|
|
minorVersion_(plugin->minorVersion()),
|
|
microVersion_(plugin->microVersion()),
|
|
brand_(plugin->brand()),
|
|
label_(plugin->label()),
|
|
author_name_(plugin->author_name()),
|
|
author_homepage_(plugin->author_homepage()),
|
|
plugin_type_(uri_to_plugin_type(plugin->plugin_class())),
|
|
audio_inputs_(0),
|
|
audio_outputs_(0),
|
|
description_(plugin->comment()),
|
|
has_midi_input_(false),
|
|
has_midi_output_(false),
|
|
modGui_(plugin->modGui()),
|
|
patchProperties_(plugin->patchProperties())
|
|
{
|
|
PLUGIN_MAP_CHECK();
|
|
auto pluginClass = pHost->GetPluginClass(plugin->plugin_class());
|
|
if (pluginClass)
|
|
{
|
|
this->plugin_display_type_ = pluginClass->display_name();
|
|
}
|
|
else
|
|
{
|
|
PLUGIN_MAP_CHECK();
|
|
this->plugin_display_type_ = "Plugin";
|
|
Lv2Log::warning("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str());
|
|
}
|
|
for (auto port : plugin->ports())
|
|
{
|
|
|
|
if (port->is_input())
|
|
{
|
|
if (port->is_control_port())
|
|
{
|
|
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
|
|
}
|
|
else if (port->is_atom_port())
|
|
{
|
|
if (port->supports_midi())
|
|
{
|
|
has_midi_input_ = true;
|
|
}
|
|
}
|
|
else if (port->is_audio_port())
|
|
{
|
|
if (port->is_sidechain()) {
|
|
// sidechain outputs don't count.
|
|
++audio_side_chain_inputs_;
|
|
} else {
|
|
++audio_inputs_;
|
|
}
|
|
}
|
|
}
|
|
else if (port->is_output())
|
|
{
|
|
if (port->is_control_port())
|
|
{
|
|
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
|
|
}
|
|
else if (port->is_atom_port() && port->supports_midi())
|
|
{
|
|
this->has_midi_output_ = true;
|
|
}
|
|
else if (port->is_audio_port())
|
|
{
|
|
++audio_outputs_;
|
|
}
|
|
}
|
|
}
|
|
for (auto &portGroup : plugin->port_groups())
|
|
{
|
|
this->port_groups_.push_back(Lv2PluginUiPortGroup(portGroup.get()));
|
|
}
|
|
auto &piPedalUI = plugin->piPedalUI();
|
|
|
|
if (piPedalUI)
|
|
{
|
|
this->fileProperties_ = piPedalUI->fileProperties();
|
|
this->frequencyPlots_ = piPedalUI->frequencyPlots();
|
|
this->uiPortNotifications_ = piPedalUI->portNotifications();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
|
|
{
|
|
return this->GetPluginClass(LV2_CORE__Plugin);
|
|
}
|
|
|
|
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
|
|
{
|
|
auto ff = pluginsByUri.find(uri);
|
|
if (ff == pluginsByUri.end())
|
|
{
|
|
return nullptr;
|
|
}
|
|
return ff->second;
|
|
}
|
|
|
|
Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard, Lv2Pedalboard *existingPedalboard, Lv2PedalboardErrorList &errorList)
|
|
{
|
|
ExistingEffectMap existingEffects;
|
|
|
|
if (existingPedalboard)
|
|
{
|
|
for (auto &effect : existingPedalboard->GetSharedEffectList())
|
|
{
|
|
if (effect->IsLv2Effect())
|
|
{
|
|
existingEffects[effect->GetInstanceId()] = effect;
|
|
}
|
|
}
|
|
}
|
|
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
|
|
try
|
|
{
|
|
pPedalboard->Prepare(this, pedalboard, errorList, &existingEffects);
|
|
return pPedalboard;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
delete pPedalboard;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard, Lv2PedalboardErrorList &errorMessages)
|
|
{
|
|
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
|
|
try
|
|
{
|
|
pPedalboard->Prepare(this, pedalboard, errorMessages);
|
|
return pPedalboard;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
delete pPedalboard;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
struct StateCallbackData
|
|
{
|
|
PluginHost *pHost;
|
|
std::vector<ControlValue> *pResult;
|
|
};
|
|
|
|
void PluginHost::fn_LilvSetPortValueFunc(const char *port_symbol,
|
|
void *user_data,
|
|
const void *value,
|
|
uint32_t size,
|
|
uint32_t type)
|
|
{
|
|
StateCallbackData *pData = (StateCallbackData *)user_data;
|
|
PluginHost *pHost = pData->pHost;
|
|
std::vector<ControlValue> *pResult = pData->pResult;
|
|
LV2_URID valueType = (LV2_URID)type;
|
|
if (valueType != pHost->urids->atom__Float)
|
|
{
|
|
throw PiPedalStateException("Preset format not supported.");
|
|
}
|
|
pResult->push_back(ControlValue(port_symbol, *(float *)value));
|
|
}
|
|
|
|
std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
|
|
PedalboardItem *pedalboardItem, const std::string &presetUri)
|
|
{
|
|
|
|
std::vector<ControlValue> result;
|
|
|
|
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
|
|
|
|
AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalboardItem->uri().c_str());
|
|
|
|
lilv_world_load_resource(pWorld, uriNode);
|
|
|
|
const LilvPlugin *plugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
|
if (plugin == nullptr)
|
|
{
|
|
throw PiPedalStateException("No such plugin.");
|
|
}
|
|
|
|
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris->presets__preset);
|
|
LILV_FOREACH(nodes, i, presets)
|
|
{
|
|
const LilvNode *preset = lilv_nodes_get(presets, i);
|
|
lilv_world_load_resource(pWorld, preset);
|
|
|
|
std::string thisPresetUri = nodeAsString(preset);
|
|
if (presetUri == thisPresetUri)
|
|
{
|
|
|
|
/*********************************/
|
|
|
|
// AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
|
|
|
|
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
|
|
|
|
if (lilvState == nullptr)
|
|
{
|
|
throw PiPedalStateException("Preset not found.");
|
|
}
|
|
|
|
StateCallbackData cbData;
|
|
cbData.pHost = this;
|
|
cbData.pResult = &result;
|
|
auto n = lilv_state_get_num_properties(lilvState);
|
|
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
|
|
|
|
lilv_state_free(lilvState);
|
|
break;
|
|
|
|
/*********************************/
|
|
}
|
|
}
|
|
lilv_nodes_free(presets);
|
|
|
|
return result;
|
|
}
|
|
|
|
struct PresetCallbackState
|
|
{
|
|
PluginHost *pHost;
|
|
std::map<std::string, float> *values;
|
|
bool failed = false;
|
|
};
|
|
|
|
void PluginHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
|
|
{
|
|
PresetCallbackState *pState = static_cast<PresetCallbackState *>(user_data);
|
|
PluginHost *pHost = pState->pHost;
|
|
|
|
if (type == pHost->urids->atom__Double)
|
|
{
|
|
(*pState->values)[symbol] = (float)(*static_cast<const double *>(value));
|
|
}
|
|
else if (type == pHost->urids->atom__Float)
|
|
{
|
|
(*pState->values)[symbol] = *static_cast<const float *>(value);
|
|
}
|
|
else if (type == pHost->urids->atom_Int)
|
|
{
|
|
(*pState->values)[symbol] = *static_cast<const int32_t *>(value);
|
|
}
|
|
else
|
|
{
|
|
pState->failed = true;
|
|
}
|
|
}
|
|
|
|
static bool lineStartsWith(const std::string &line, const char*text) {
|
|
for (char c: line) {
|
|
if (c == ' ' || c == '\t') {
|
|
continue;
|
|
}
|
|
if (c != *text) {
|
|
return false;
|
|
}
|
|
++text;
|
|
if (*text == '\0')
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void skipWhitespace(std::istream &s) {
|
|
while (true)
|
|
{
|
|
auto c = s.peek();
|
|
if (c == ' ' || c == '\t') {
|
|
s.get();
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool parseTtlUrl(std::istream &s, std::string *result)
|
|
{
|
|
if (s.peek() != '<') {
|
|
return false;
|
|
}
|
|
s.get();
|
|
std::ostringstream o;
|
|
while (s.peek() != '>')
|
|
{
|
|
|
|
int c = s.get();
|
|
if (c == EOF)
|
|
{
|
|
return false;
|
|
|
|
}
|
|
o << (char)c;
|
|
}
|
|
s.get();
|
|
|
|
*result = o.str();
|
|
return true;
|
|
}
|
|
|
|
static uint32_t readTtlUnicodeEscape(std::istream&s, size_t nChars)
|
|
{
|
|
uint32_t result = 0;
|
|
|
|
for (size_t i = 0; i < nChars; ++i)
|
|
{
|
|
int c = s.peek();
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
result = result*16 + (uint32_t)(c-'0');
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
result = result*16 + (uint32_t)(c-'a'+10);
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
result = result*16 + (uint32_t)(c-'A'+10);
|
|
}
|
|
s.get();
|
|
}
|
|
return result;
|
|
}
|
|
static bool parseTtlString(std::istream&s, std::string *result)
|
|
{
|
|
if (s.peek() != '"') {
|
|
return false;
|
|
}
|
|
s.get(); // consume opening quote
|
|
std::ostringstream o;
|
|
while (s.peek() != '"') {
|
|
int c = s.get();
|
|
if (c == EOF) {
|
|
return false;
|
|
}
|
|
if (c == '\\') {
|
|
// Handle escape sequences
|
|
int next = s.get();
|
|
if (next == EOF) {
|
|
return false;
|
|
}
|
|
switch (next) {
|
|
case 'n':
|
|
o << '\n';
|
|
break;
|
|
case 't':
|
|
o << '\t';
|
|
break;
|
|
case 'r':
|
|
o << '\r';
|
|
break;
|
|
case '\\':
|
|
o << '\\';
|
|
break;
|
|
case '"':
|
|
o << '"';
|
|
break;
|
|
case 'u':
|
|
{
|
|
uint32_t cc = readTtlUnicodeEscape(s,4);
|
|
HtmlHelper::utf32_to_utf8_stream(o,cc);
|
|
break;
|
|
}
|
|
case 'U':
|
|
{
|
|
uint32_t cc = readTtlUnicodeEscape(s,8);
|
|
HtmlHelper::utf32_to_utf8_stream(o,cc);
|
|
break;
|
|
}
|
|
default:
|
|
o << (char)next;
|
|
break;
|
|
}
|
|
} else {
|
|
o << (char)c;
|
|
}
|
|
}
|
|
s.get(); // consume closing quote
|
|
*result = o.str();
|
|
return true;
|
|
}
|
|
static bool parseStateProperty(const std::string &line, std::string *property, std::string *value) {
|
|
std::istringstream s { line};
|
|
|
|
skipWhitespace(s);
|
|
if (!parseTtlUrl(s,property)) {
|
|
return false;
|
|
}
|
|
skipWhitespace(s);
|
|
if (!parseTtlString(s,value))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static std::map<std::string,std::string> ExtractPathPropertiesFromLilvState(
|
|
Lv2PluginInfo *pluginInfo,
|
|
const std::string lilvPresetText
|
|
)
|
|
{
|
|
// <urn:xxx>
|
|
// a pset:Preset ;
|
|
// ...
|
|
// state:state [
|
|
// <http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Arthur Sykes Rymer Auditorium.wav"
|
|
// ] .
|
|
|
|
std::map<std::string,std::string> result;
|
|
std::istringstream s{lilvPresetText};
|
|
std::string line;
|
|
|
|
bool foundState = false;
|
|
for (std::string line; std::getline(s, line);) {
|
|
if (lineStartsWith(line,"state:state"))
|
|
{
|
|
foundState = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundState)
|
|
{
|
|
for (std::string line; std::getline(s, line);) {
|
|
if (lineStartsWith(line,"]"))
|
|
{
|
|
break;
|
|
}
|
|
std::string property, value;
|
|
if (parseStateProperty(line,&property,&value)) {
|
|
if (pluginInfo->IsPathProperty(property))
|
|
{ result[property] = AtomConverter::MakePathVariant(value).to_string();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
bool Lv2PluginInfo::IsPathProperty(const std::string &uri) const {
|
|
if (!piPedalUI_) {
|
|
return false;
|
|
}
|
|
for (const UiFileProperty::ptr& fileProperty: this->piPedalUI_->fileProperties())
|
|
{
|
|
if (fileProperty->patchProperty() == uri)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
|
{
|
|
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
|
|
|
|
AutoLilvNode uriNode = lilv_new_uri(pWorld, pluginUri.c_str());
|
|
|
|
lilv_world_load_resource(pWorld, uriNode);
|
|
|
|
const LilvPlugin *plugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
|
if (plugin == nullptr)
|
|
{
|
|
throw PiPedalStateException("No such plugin.");
|
|
}
|
|
|
|
PluginPresets result;
|
|
result.pluginUri_ = pluginUri;
|
|
|
|
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris->presets__preset);
|
|
LILV_FOREACH(nodes, i, presets)
|
|
{
|
|
const LilvNode *preset = lilv_nodes_get(presets, i);
|
|
lilv_world_load_resource(pWorld, preset);
|
|
|
|
LilvState *state = lilv_state_new_from_world(pWorld, this->mapFeature.GetMap(), preset);
|
|
if (state != nullptr)
|
|
{
|
|
const char *tLabel = lilv_state_get_label(state);
|
|
if (tLabel != nullptr)
|
|
{
|
|
std::string strLabel = tLabel;
|
|
std::map<std::string, float> controlValues;
|
|
PresetCallbackState cbData{this, &controlValues, false};
|
|
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
|
|
|
|
int numProperties = lilv_state_get_num_properties(state);
|
|
// can't handle std:state part of preset.
|
|
if (numProperties == 0)
|
|
{
|
|
|
|
|
|
if (!cbData.failed)
|
|
{
|
|
result.presets_.push_back(
|
|
PluginPreset(
|
|
result.nextInstanceId_++,
|
|
strLabel,
|
|
controlValues,
|
|
std::map<std::string, std::string>(),
|
|
Lv2PluginState()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
std::string stateString = lilv_state_to_string(
|
|
pWorld,mapFeature.GetMap(),mapFeature.GetUnmap(),
|
|
state,
|
|
"urn:xxx",
|
|
nullptr);
|
|
(void)stateString;
|
|
|
|
std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pluginUri);
|
|
|
|
PluginPreset pluginPreset = PluginPreset::MakeLilvPreset(result.nextInstanceId_++, strLabel, controlValues, lilv_node_as_uri(preset));
|
|
|
|
pluginPreset.pathProperties_ = ExtractPathPropertiesFromLilvState(
|
|
pluginInfo.get(),
|
|
stateString
|
|
);
|
|
result.presets_.push_back(pluginPreset);
|
|
}
|
|
lilv_state_free(state);
|
|
}
|
|
}
|
|
}
|
|
lilv_nodes_free(presets);
|
|
return result;
|
|
}
|
|
|
|
IEffect *PluginHost::CreateEffect(PedalboardItem &pedalboardItem)
|
|
{
|
|
if (pedalboardItem.uri().starts_with("vst3:"))
|
|
{
|
|
#if ENABLE_VST3
|
|
try
|
|
{
|
|
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalboardItem, this);
|
|
return vst3Plugin.release();
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
|
|
throw;
|
|
}
|
|
#else
|
|
Lv2Log::error(std::string("VST3 support not enabled at compile time."));
|
|
throw PiPedalException("VST3 support not enabled at compile time, SEE ENABLE_VST3 in CMakeList.txt.");
|
|
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
auto info = this->GetPluginInfo(pedalboardItem.uri());
|
|
if (!info)
|
|
return nullptr;
|
|
|
|
return new Lv2Effect(this, info, pedalboardItem);
|
|
}
|
|
}
|
|
|
|
Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
|
|
{
|
|
LilvWorld *pWorld = lv2Host->pWorld;
|
|
|
|
// Enumerate all Lilv nodes for groupUri that have an 'a' predicate
|
|
|
|
this->uri_ = groupUri;
|
|
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
|
|
|
|
|
|
AutoLilvNode aPredicate = lilv_new_uri(pWorld, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
|
|
|
|
AutoLilvNodes types = lilv_world_find_nodes(pWorld, uri, aPredicate, nullptr);
|
|
LILV_FOREACH(nodes, iNode, types)
|
|
{
|
|
AutoLilvNode typeNode = lilv_nodes_get(types, iNode);
|
|
std::string typeUri = nodeAsString(typeNode);
|
|
this->isA_.push_back(std::move(typeUri));
|
|
}
|
|
|
|
|
|
AutoLilvNode symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr);
|
|
symbol_ = nodeAsString(symbolNode);
|
|
AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr);
|
|
name_ = nodeAsString(nameNode);
|
|
AutoLilvNode sideChainOfNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->port_groups__sideChainOf, nullptr);
|
|
sideChainOf_ = nodeAsString(sideChainOfNode);
|
|
}
|
|
|
|
bool Lv2PluginInfo::isSplit() const
|
|
{
|
|
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
|
|
}
|
|
|
|
class ResourceInfo
|
|
{
|
|
public:
|
|
ResourceInfo(const std::string &filePropertyDirectory, const std::string &resourceDirectory)
|
|
: filePropertyDirectory(filePropertyDirectory), resourceDirectory(resourceDirectory) {}
|
|
|
|
std::string filePropertyDirectory;
|
|
std::string resourceDirectory;
|
|
bool operator==(const ResourceInfo &other) const
|
|
{
|
|
return this->filePropertyDirectory == other.filePropertyDirectory && this->resourceDirectory == other.resourceDirectory;
|
|
}
|
|
bool operator<(const ResourceInfo &other) const
|
|
{
|
|
if (this->filePropertyDirectory < other.filePropertyDirectory)
|
|
return true;
|
|
if (this->filePropertyDirectory > other.filePropertyDirectory)
|
|
return false;
|
|
return this->resourceDirectory < other.resourceDirectory;
|
|
}
|
|
};
|
|
|
|
static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, const std::filesystem::path &targetDirectory)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
try
|
|
{
|
|
if (!fs::exists(targetDirectory))
|
|
return false;
|
|
for (auto &directoryEntry : fs::directory_iterator(sourceDirectory))
|
|
{
|
|
fs::path thisPath = directoryEntry.path();
|
|
if (directoryEntry.is_directory())
|
|
{
|
|
auto name = thisPath.filename();
|
|
fs::path childSource = sourceDirectory / name;
|
|
fs::path childTarget = targetDirectory / name;
|
|
if (anyTargetFilesExist(childSource, childTarget))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fs::path targetPath = targetDirectory / thisPath.filename();
|
|
if (fs::exists(targetPath))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void createTargetLinks(const std::filesystem::path &sourceDirectory, const std::filesystem::path &targetDirectory)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
|
|
fs::create_directories(targetDirectory);
|
|
|
|
for (auto &dirEntry : fs::directory_iterator(sourceDirectory))
|
|
{
|
|
fs::path childSource = dirEntry.path();
|
|
fs::path childTarget = targetDirectory / childSource.filename();
|
|
if (dirEntry.is_directory())
|
|
{
|
|
createTargetLinks(childSource, childTarget);
|
|
}
|
|
else
|
|
{
|
|
fs::create_symlink(childSource, childTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string PluginHost::MapResourcePath(const std::string &pluginUri, const std::string &filePath)
|
|
{
|
|
auto plugin = GetPluginInfo(pluginUri);
|
|
if (plugin)
|
|
{
|
|
|
|
return filePath;
|
|
}
|
|
|
|
return filePath;
|
|
}
|
|
|
|
void PluginHost::CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory)
|
|
{
|
|
|
|
auto plugin = GetPluginInfo(pluginUri);
|
|
if (plugin)
|
|
{
|
|
std::filesystem::path bundlePath = plugin->bundle_path();
|
|
if (!plugin->piPedalUI())
|
|
return;
|
|
const auto &fileProperties = plugin->piPedalUI()->fileProperties();
|
|
if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri))
|
|
{
|
|
pluginsThatHaveBeenCheckedForResources.insert(pluginUri);
|
|
|
|
// eliminate duplicates.
|
|
std::set<ResourceInfo> resourceInfoSet;
|
|
for (const auto &fileProperty : fileProperties)
|
|
{
|
|
if (!fileProperty->resourceDirectory().empty() && !fileProperty->directory().empty())
|
|
{
|
|
resourceInfoSet.insert(ResourceInfo(fileProperty->directory(), fileProperty->resourceDirectory()));
|
|
}
|
|
}
|
|
try
|
|
{
|
|
for (const ResourceInfo &resourceInfo : resourceInfoSet)
|
|
{
|
|
std::filesystem::path sourcePath = bundlePath / resourceInfo.resourceDirectory;
|
|
std::filesystem::path targetPath = pluginUploadDirectory / resourceInfo.filePropertyDirectory;
|
|
if (!anyTargetFilesExist(sourcePath, targetPath))
|
|
{
|
|
createTargetLinks(sourcePath, targetPath);
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
Lv2Log::error(SS("CheckForResourceInitialization: " << e.what()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
json_variant PluginHost::MapPath(const json_variant &json)
|
|
{
|
|
AtomConverter converter(GetMapFeature());
|
|
return converter.MapPath(json, GetPluginStoragePath());
|
|
}
|
|
json_variant PluginHost::AbstractPath(const json_variant &json)
|
|
{
|
|
AtomConverter converter(GetMapFeature());
|
|
return converter.AbstractPath(json, GetPluginStoragePath());
|
|
}
|
|
|
|
std::string PluginHost::MapPath(const std::string &abstractPath)
|
|
{
|
|
auto storagePath = GetPluginStoragePath();
|
|
if (abstractPath.length() == 0)
|
|
{
|
|
return "";
|
|
}
|
|
return SS(storagePath << '/' << abstractPath);
|
|
}
|
|
std::string PluginHost::AbstractPath(const std::string &path)
|
|
{
|
|
auto storagePath = GetPluginStoragePath();
|
|
if (path.starts_with(storagePath))
|
|
{
|
|
auto start = storagePath.length();
|
|
if (start < path.length() && path[start] == '/')
|
|
{
|
|
++start;
|
|
}
|
|
return path.substr(start);
|
|
}
|
|
return path;
|
|
}
|
|
Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri)
|
|
{
|
|
LilvWorld *pWorld = pluginHost->getWorld();
|
|
this->uri_ = nodeAsString(propertyUri);
|
|
|
|
AutoLilvNode range = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->rdfs__range, nullptr);
|
|
if (range)
|
|
{
|
|
this->type_ = range.AsUri();
|
|
}
|
|
AutoLilvNode comment = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->rdfs__Comment, nullptr);
|
|
if (comment)
|
|
{
|
|
this->comment_ = comment.AsString();
|
|
}
|
|
AutoLilvNode shortName = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->lv2core__shortName, nullptr);
|
|
if (shortName)
|
|
{
|
|
this->shortName_ = shortName.AsString();
|
|
}
|
|
else
|
|
{
|
|
this->shortName_ = this->label_;
|
|
}
|
|
|
|
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->lv2core__index, nullptr);
|
|
this->index_ = indexNode.AsInt(-1);
|
|
|
|
AutoLilvNode modFileTypesNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->mod__fileTypes, nullptr);
|
|
if (modFileTypesNode)
|
|
{
|
|
std::string strFileTypes = modFileTypesNode.AsString();
|
|
this->fileTypes_ = split(strFileTypes, ',');
|
|
}
|
|
AutoLilvNode modSupportedExtensionsNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->mod__supportedExtensions, nullptr);
|
|
if (modSupportedExtensionsNode)
|
|
{
|
|
std::string strSupportedExtensions = modSupportedExtensionsNode.AsString();
|
|
this->supportedExtensions_ = split(strSupportedExtensions, ',');
|
|
}
|
|
}
|
|
|
|
// ffs.
|
|
static inline bool contains(const std::vector<std::string> &vec, const std::string &value)
|
|
{
|
|
return std::find(vec.begin(), vec.end(), value) != vec.end();
|
|
}
|
|
bool Lv2PluginInfo::WantsWorkerThread() const
|
|
{
|
|
return contains(this->required_features_, LV2_WORKER__schedule) || contains(this->supported_features_, LV2_WORKER__schedule);
|
|
}
|
|
|
|
uint32_t Lv2PluginInfo::getBypassPortIndex() const
|
|
{
|
|
for (const auto &port : ports_)
|
|
{
|
|
if (port->is_bypass())
|
|
{
|
|
return port->index();
|
|
}
|
|
}
|
|
return (uint32_t)-1;
|
|
}
|
|
|
|
// void PiPedalHostLogError(const std::string &error)
|
|
// {
|
|
// Lv2Log::error("%s",error.c_str());
|
|
// }
|
|
|
|
#define MAP_REF(class, name) \
|
|
json_map::reference(#name, &class ::name##_)
|
|
|
|
#define CONDITIONAL_MAP_REF(class, name, condition) \
|
|
json_map::conditional_reference(#name, &class ::name##_, condition)
|
|
|
|
json_map::storage_type<Lv2PluginClasses> Lv2PluginClasses::jmap{{
|
|
// json_map::reference("char_", &JsonTestTarget::char_),
|
|
json_map::reference("classes", &Lv2PluginClasses::classes_),
|
|
}};
|
|
|
|
json_map::storage_type<Lv2ScalePoint> Lv2ScalePoint::jmap{{
|
|
json_map::reference("value", &Lv2ScalePoint::value_),
|
|
json_map::reference("label", &Lv2ScalePoint::label_),
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
|
{json_map::reference("index", &Lv2PortInfo::index_),
|
|
|
|
json_map::reference("symbol", &Lv2PortInfo::symbol_),
|
|
json_map::reference("index", &Lv2PortInfo::index_),
|
|
json_map::reference("name", &Lv2PortInfo::name_),
|
|
|
|
json_map::reference("min_value", &Lv2PortInfo::min_value_),
|
|
json_map::reference("max_value", &Lv2PortInfo::max_value_),
|
|
json_map::reference("default_value", &Lv2PortInfo::default_value_),
|
|
json_map::reference("classes", &Lv2PortInfo::classes_),
|
|
|
|
json_map::reference("scale_points", &Lv2PortInfo::scale_points_),
|
|
|
|
json_map::reference("is_input", &Lv2PortInfo::is_input_),
|
|
json_map::reference("is_output", &Lv2PortInfo::is_output_),
|
|
json_map::reference("is_sidechain", &Lv2PortInfo::is_sidechain_),
|
|
|
|
json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_),
|
|
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
|
|
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
|
|
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
|
|
json_map::reference("connection_optional", &Lv2PortInfo::connection_optional_),
|
|
|
|
json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
|
|
|
|
json_map::reference("supports_midi", &Lv2PortInfo::supports_midi_),
|
|
json_map::reference("supports_time_position", &Lv2PortInfo::supports_time_position_),
|
|
json_map::reference("port_group", &Lv2PortInfo::port_group_),
|
|
json_map::reference("is_logarithmic", &Lv2PortInfo::is_logarithmic_),
|
|
|
|
MAP_REF(Lv2PortInfo, is_logarithmic),
|
|
MAP_REF(Lv2PortInfo, display_priority),
|
|
MAP_REF(Lv2PortInfo, range_steps),
|
|
MAP_REF(Lv2PortInfo, trigger_property),
|
|
MAP_REF(Lv2PortInfo, integer_property),
|
|
MAP_REF(Lv2PortInfo, enumeration_property),
|
|
MAP_REF(Lv2PortInfo, toggled_property),
|
|
MAP_REF(Lv2PortInfo, mod_momentaryOffByDefault),
|
|
MAP_REF(Lv2PortInfo, mod_momentaryOnByDefault),
|
|
MAP_REF(Lv2PortInfo, is_expensive),
|
|
MAP_REF(Lv2PortInfo, pipedal_graphicEq),
|
|
MAP_REF(Lv2PortInfo, not_on_gui),
|
|
MAP_REF(Lv2PortInfo, buffer_type),
|
|
MAP_REF(Lv2PortInfo, port_group),
|
|
MAP_REF(Lv2PortInfo, is_bypass),
|
|
MAP_REF(Lv2PortInfo, pipedal_ledColor),
|
|
|
|
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
|
|
MAP_REF(Lv2PortInfo, custom_units),
|
|
MAP_REF(Lv2PortInfo, comment),
|
|
|
|
}};
|
|
|
|
|
|
|
|
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
|
MAP_REF(Lv2PortGroup, isA),
|
|
MAP_REF(Lv2PortGroup, uri),
|
|
MAP_REF(Lv2PortGroup, name),
|
|
MAP_REF(Lv2PortGroup, symbol),
|
|
MAP_REF(Lv2PortGroup, sideChainOf),
|
|
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
|
json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_),
|
|
json_map::reference("uri", &Lv2PluginInfo::uri_),
|
|
json_map::reference("minorVersion", &Lv2PluginInfo::minorVersion_),
|
|
json_map::reference("microVersion", &Lv2PluginInfo::microVersion_),
|
|
json_map::reference("name", &Lv2PluginInfo::name_),
|
|
json_map::reference("brand", &Lv2PluginInfo::brand_),
|
|
json_map::reference("label", &Lv2PluginInfo::label_),
|
|
json_map::reference("plugin_class", &Lv2PluginInfo::plugin_class_),
|
|
json_map::reference("supported_features", &Lv2PluginInfo::supported_features_),
|
|
json_map::reference("required_features", &Lv2PluginInfo::required_features_),
|
|
json_map::reference("optional_features", &Lv2PluginInfo::optional_features_),
|
|
json_map::reference("extensions", &Lv2PluginInfo::extensions_),
|
|
|
|
json_map::reference("author_name", &Lv2PluginInfo::author_name_),
|
|
json_map::reference("author_homepage", &Lv2PluginInfo::author_homepage_),
|
|
|
|
json_map::reference("comment", &Lv2PluginInfo::comment_),
|
|
json_map::reference("ports", &Lv2PluginInfo::ports_),
|
|
json_map::reference("port_groups", &Lv2PluginInfo::port_groups_),
|
|
json_map::reference("is_valid", &Lv2PluginInfo::is_valid_),
|
|
json_map::reference("has_factory_presets", &Lv2PluginInfo::has_factory_presets_),
|
|
json_map::reference("modGui", &Lv2PluginInfo::modGui_),
|
|
json_map::reference("patchProperties", &Lv2PluginInfo::patchProperties_),
|
|
json_map::reference("hasDefaultState", &Lv2PluginInfo::hasDefaultState_),
|
|
json_map::reference("minBlockLength", &Lv2PluginInfo::minBlockLength_),
|
|
json_map::reference("maxBlockLength", &Lv2PluginInfo::maxBlockLength_),
|
|
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PluginClass> Lv2PluginClass::jmap{{
|
|
MAP_REF(Lv2PluginClass, uri),
|
|
MAP_REF(Lv2PluginClass, display_name),
|
|
MAP_REF(Lv2PluginClass, parent_uri),
|
|
json_map::enum_reference("plugin_type", &Lv2PluginClass::plugin_type_, get_plugin_type_enum_converter()),
|
|
MAP_REF(Lv2PluginClass, children),
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PluginUiPortGroup> Lv2PluginUiPortGroup::jmap{{
|
|
MAP_REF(Lv2PluginUiPortGroup, uri),
|
|
MAP_REF(Lv2PluginUiPortGroup, symbol),
|
|
MAP_REF(Lv2PluginUiPortGroup, name),
|
|
MAP_REF(Lv2PluginUiPortGroup, parent_group),
|
|
MAP_REF(Lv2PluginUiPortGroup, program_list_id),
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
|
|
json_map::reference("symbol", &Lv2PluginUiPort::symbol_),
|
|
json_map::reference("name", &Lv2PluginUiPort::name_),
|
|
MAP_REF(Lv2PluginUiPort, index),
|
|
MAP_REF(Lv2PluginUiPort, is_input),
|
|
MAP_REF(Lv2PluginUiPort, is_sidechain),
|
|
MAP_REF(Lv2PluginUiPort, min_value),
|
|
MAP_REF(Lv2PluginUiPort, max_value),
|
|
MAP_REF(Lv2PluginUiPort, default_value),
|
|
MAP_REF(Lv2PluginUiPort, is_logarithmic),
|
|
MAP_REF(Lv2PluginUiPort, display_priority),
|
|
|
|
MAP_REF(Lv2PluginUiPort, range_steps),
|
|
MAP_REF(Lv2PluginUiPort, integer_property),
|
|
|
|
MAP_REF(Lv2PluginUiPort, mod_momentaryOffByDefault),
|
|
MAP_REF(Lv2PluginUiPort, mod_momentaryOnByDefault),
|
|
MAP_REF(Lv2PluginUiPort, is_expensive),
|
|
MAP_REF(Lv2PluginUiPort, pipedal_graphicEq),
|
|
MAP_REF(Lv2PluginUiPort, enumeration_property),
|
|
MAP_REF(Lv2PluginUiPort, not_on_gui),
|
|
MAP_REF(Lv2PluginUiPort, toggled_property),
|
|
MAP_REF(Lv2PluginUiPort, trigger_property),
|
|
MAP_REF(Lv2PluginUiPort, scale_points),
|
|
MAP_REF(Lv2PluginUiPort, port_group),
|
|
MAP_REF(Lv2PluginUiPort, pipedal_ledColor),
|
|
|
|
json_map::enum_reference("units", &Lv2PluginUiPort::units_, get_units_enum_converter()),
|
|
MAP_REF(Lv2PluginUiPort, custom_units),
|
|
MAP_REF(Lv2PluginUiPort, comment),
|
|
MAP_REF(Lv2PluginUiPort, is_bypass),
|
|
MAP_REF(Lv2PluginUiPort, is_program_controller),
|
|
MAP_REF(Lv2PluginUiPort, connection_optional),
|
|
|
|
}};
|
|
|
|
json_map::storage_type<Lv2PluginUiInfo>
|
|
Lv2PluginUiInfo::jmap{
|
|
{
|
|
json_map::reference("uri", &Lv2PluginUiInfo::uri_),
|
|
json_map::reference("name", &Lv2PluginUiInfo::name_),
|
|
json_map::reference("minorVersion", &Lv2PluginUiInfo::minorVersion_),
|
|
json_map::reference("microVersion", &Lv2PluginUiInfo::microVersion_),
|
|
json_map::reference("brand", &Lv2PluginUiInfo::brand_),
|
|
json_map::reference("label", &Lv2PluginUiInfo::label_),
|
|
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
|
|
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
|
|
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
|
|
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
|
|
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
|
|
json_map::reference("audio_side_chain_inputs", &Lv2PluginUiInfo::audio_side_chain_inputs_),
|
|
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
|
|
json_map::reference("audio_side_chain_title", &Lv2PluginUiInfo::audio_side_chain_title_),
|
|
json_map::reference("description", &Lv2PluginUiInfo::description_),
|
|
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
|
|
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
|
|
json_map::reference("controls", &Lv2PluginUiInfo::controls_),
|
|
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
|
|
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
|
|
json_map::reference("fileProperties", &Lv2PluginUiInfo::fileProperties_),
|
|
json_map::reference("frequencyPlots", &Lv2PluginUiInfo::frequencyPlots_),
|
|
json_map::reference("uiPortNotifications", &Lv2PluginUiInfo::uiPortNotifications_),
|
|
json_map::reference("modGui", &Lv2PluginUiInfo::modGui_),
|
|
json_map::reference("patchProperties", &Lv2PluginUiInfo::patchProperties_),
|
|
}};
|
|
JSON_MAP_BEGIN(Lv2PatchPropertyInfo)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, uri)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, label)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, type)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, comment)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, shortName)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, fileTypes)
|
|
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, supportedExtensions)
|
|
|
|
JSON_MAP_END() |