Checkpoint
This commit is contained in:
@@ -206,6 +206,9 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
ModTemplateGenerator.cpp ModTemplateGenerator.hpp
|
||||
WebServerMod.cpp WebServerMod.hpp
|
||||
ModGui.cpp ModGui.hpp
|
||||
PipewireInputStream.cpp PipewireInputStream.hpp
|
||||
AudioFiles.cpp AudioFiles.hpp
|
||||
AudioFileMetadataReader.cpp AudioFileMetadataReader.hpp
|
||||
@@ -414,6 +417,8 @@ add_executable(AuxInTest
|
||||
|
||||
add_executable(pipedaltest
|
||||
testMain.cpp
|
||||
|
||||
ModGuiTest.cpp
|
||||
PipewireInputStreamTest.cpp
|
||||
|
||||
AudioFilesTest.cpp
|
||||
|
||||
@@ -36,7 +36,7 @@ TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) {
|
||||
{
|
||||
PluginHost host;
|
||||
|
||||
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
|
||||
host.LoadLilv("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
|
||||
}
|
||||
MemStats finalMemory = GetMemStats();
|
||||
|
||||
|
||||
+11
-1
@@ -22,6 +22,7 @@
|
||||
#include "ss.hpp"
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -30,7 +31,16 @@ static std::string empty;
|
||||
const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension) const
|
||||
{
|
||||
auto iter = extensionToMimeType.find(extension);
|
||||
if (iter == extensionToMimeType.end()) return empty;
|
||||
if (iter == extensionToMimeType.end())
|
||||
{
|
||||
std::string lowerExt = ToLower(extension);
|
||||
auto iter2 = extensionToMimeType.find(lowerExt);
|
||||
if (iter2 != extensionToMimeType.end())
|
||||
{
|
||||
return iter2->second;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ ModFileTypes::ModFileTypes(const std::string &fileTypes)
|
||||
if (wellKnownType)
|
||||
{
|
||||
rootDirectories_.push_back(type);
|
||||
modFileTypes_.push_back(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -32,12 +32,14 @@ namespace pipedal
|
||||
ModFileTypes(const std::string &fileTypes);
|
||||
const std::vector<std::string> &rootDirectories() const { return rootDirectories_; }
|
||||
std::vector<std::string> &rootDirectories() { return rootDirectories_; }
|
||||
const std::vector<std::string> &modFileTypes() const { return modFileTypes_; }
|
||||
const std::vector<std::string> &fileTypes() const { return fileTypes_; }
|
||||
|
||||
static constexpr const char *DEFAULT_FILE_TYPES = "audio,nammodel,mlmodel,sf2,sfz,midisong,midiclip,*"; // (everything)
|
||||
|
||||
private:
|
||||
std::vector<std::string> rootDirectories_;
|
||||
std::vector<std::string> modFileTypes_; // e.g. "audio", "nammodel", etc.
|
||||
std::vector<std::string> fileTypes_;
|
||||
|
||||
public:
|
||||
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 "ModGui.hpp"
|
||||
#include "MapFeature.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Finally.hpp"
|
||||
#include "lv2/atom/atom.h"
|
||||
|
||||
|
||||
#define MOD_GUI_PREFIX "http://moddevices.com/ns/modgui#"
|
||||
#define MOD_GUI__gui (MOD_GUI_PREFIX "gui")
|
||||
#define MOD_GUI__modgui (MOD_GUI_PREFIX "modgui")
|
||||
#define MOD_GUI__resourcesDirectory (MOD_GUI_PREFIX "resourcesDirectory")
|
||||
#define MOD_GUI__iconTemplate (MOD_GUI_PREFIX "iconTemplate")
|
||||
#define MOD_GUI__settingsTemplate (MOD_GUI_PREFIX "settingsTemplate")
|
||||
#define MOD_GUI__javascript (MOD_GUI_PREFIX "javascript")
|
||||
#define MOD_GUI__stylesheet (MOD_GUI_PREFIX "stylesheet")
|
||||
#define MOD_GUI__screenshot (MOD_GUI_PREFIX "screenshot")
|
||||
#define MOD_GUI__thumbnail (MOD_GUI_PREFIX "thumbnail")
|
||||
#define MOD_GUI__discussionURL (MOD_GUI_PREFIX "discussionURL")
|
||||
#define MOD_GUI__documentation (MOD_GUI_PREFIX "documentation")
|
||||
#define MOD_GUI__brand (MOD_GUI_PREFIX "brand")
|
||||
#define MOD_GUI__label (MOD_GUI_PREFIX "label")
|
||||
#define MOD_GUI__model (MOD_GUI_PREFIX "model")
|
||||
#define MOD_GUI__panel (MOD_GUI_PREFIX "panel")
|
||||
#define MOD_GUI__color (MOD_GUI_PREFIX "color")
|
||||
#define MOD_GUI__knob (MOD_GUI_PREFIX "knob")
|
||||
#define MOD_GUI__port (MOD_GUI_PREFIX "port")
|
||||
#define MOD_GUI__monitoredOutputs (MOD_GUI_PREFIX "monitoredOutputs")
|
||||
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
ModGuiUris::ModGuiUris(LilvWorld *pWorld, MapFeature &mapFeature)
|
||||
{
|
||||
mod_gui__gui = lilv_new_uri(pWorld, MOD_GUI__gui);
|
||||
mod_gui__modgui = lilv_new_uri(pWorld, MOD_GUI__modgui);
|
||||
mod_gui__resourceDirectory = lilv_new_uri(pWorld, MOD_GUI__resourcesDirectory);
|
||||
mod_gui__iconTemplate = lilv_new_uri(pWorld, MOD_GUI__iconTemplate);
|
||||
mod_gui__settingsTemplate = lilv_new_uri(pWorld, MOD_GUI__settingsTemplate);
|
||||
mod_gui__javascript = lilv_new_uri(pWorld, MOD_GUI__javascript);
|
||||
mod_gui__stylesheet = lilv_new_uri(pWorld, MOD_GUI__stylesheet);
|
||||
mod_gui__screenshot = lilv_new_uri(pWorld, MOD_GUI__screenshot);
|
||||
mod_gui__thumbnail = lilv_new_uri(pWorld, MOD_GUI__thumbnail);
|
||||
mod_gui__discussionURL = lilv_new_uri(pWorld, MOD_GUI__discussionURL);
|
||||
mod_gui__documentation = lilv_new_uri(pWorld, MOD_GUI__documentation);
|
||||
mod_gui__brand = lilv_new_uri(pWorld, MOD_GUI__brand);
|
||||
mod_gui__label = lilv_new_uri(pWorld, MOD_GUI__label);
|
||||
mod_gui__model = lilv_new_uri(pWorld, MOD_GUI__model);
|
||||
mod_gui__panel = lilv_new_uri(pWorld, MOD_GUI__panel);
|
||||
mod_gui__color = lilv_new_uri(pWorld, MOD_GUI__color);
|
||||
mod_gui__knob = lilv_new_uri(pWorld, MOD_GUI__knob);
|
||||
mod_gui__port = lilv_new_uri(pWorld, MOD_GUI__port);
|
||||
mod_gui__monitoredOutputs = lilv_new_uri(pWorld, MOD_GUI__monitoredOutputs);
|
||||
}
|
||||
|
||||
static const char*NonNull(const char*string) {
|
||||
return string ? string : "";
|
||||
}
|
||||
|
||||
|
||||
ModGui::ModGui(
|
||||
PluginHost *lv2Host,
|
||||
const LilvPlugin *lilvPlugin,
|
||||
const std::string &resourceDirectory,
|
||||
const LilvNode * modGuiUrl)
|
||||
: pluginUri_(lilv_node_as_uri(lilv_plugin_get_uri(lilvPlugin))),
|
||||
resourceDirectory_(resourceDirectory)
|
||||
{
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
ModGuiUris &modGuiUrids = *lv2Host->mod_gui_uris;
|
||||
PluginHost::LilvUris &lilvUris = *lv2Host->lilvUris;
|
||||
|
||||
AutoLilvNode modguiIcon = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__iconTemplate, nullptr);
|
||||
if (modguiIcon) {
|
||||
this->iconTemplate_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiIcon), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiSettingsTemplate = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__settingsTemplate, nullptr);
|
||||
if (modguiSettingsTemplate) {
|
||||
this->settingsTemplate_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiSettingsTemplate), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiJavascript = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__javascript, nullptr);
|
||||
if (modguiJavascript) {
|
||||
this->javascript_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiJavascript), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiStylesheet = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__stylesheet, nullptr);
|
||||
if (modguiStylesheet) {
|
||||
this->stylesheet_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiStylesheet), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiScreenshot = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__screenshot, nullptr);
|
||||
if (modguiScreenshot) {
|
||||
this->screenshot_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiScreenshot), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiThumbnail = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__thumbnail, nullptr);
|
||||
if (modguiThumbnail) {
|
||||
this->thumbnail_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiThumbnail), nullptr));
|
||||
}
|
||||
AutoLilvNode discussionUrl = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__discussionURL, nullptr);
|
||||
if (discussionUrl) {
|
||||
this->discussionUrl_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(discussionUrl), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode documentationUrl = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__documentation, nullptr);
|
||||
if (documentationUrl) {
|
||||
this->documentationUrl_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(documentationUrl), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode brand = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__brand, nullptr);
|
||||
if (brand) {
|
||||
this->brand_ = NonNull(lilv_node_as_string(brand));
|
||||
}
|
||||
|
||||
AutoLilvNode label = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__label, nullptr);
|
||||
if (label) {
|
||||
this->label_ = NonNull(lilv_node_as_string(label));
|
||||
}
|
||||
|
||||
AutoLilvNode model = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__model, nullptr);
|
||||
if (model) {
|
||||
this->model_ = NonNull(lilv_node_as_string(model));
|
||||
}
|
||||
|
||||
AutoLilvNode panel = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__panel, nullptr);
|
||||
if (panel) {
|
||||
this->panel_ = NonNull(lilv_node_as_string(panel));
|
||||
}
|
||||
|
||||
AutoLilvNode color = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__color, nullptr);
|
||||
if (color) {
|
||||
this->color_ = NonNull(lilv_node_as_string(color));
|
||||
}
|
||||
|
||||
AutoLilvNode knob = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__knob, nullptr);
|
||||
if (knob) {
|
||||
this->knob_ = NonNull(lilv_node_as_string(knob));
|
||||
}
|
||||
|
||||
AutoLilvNodes ports = lilv_world_find_nodes(pWorld, modGuiUrl, modGuiUrids.mod_gui__port, nullptr);
|
||||
if (ports) {
|
||||
LILV_FOREACH(nodes, it, ports.Get()) {
|
||||
AutoLilvNode port_node = lilv_nodes_get(ports, it);
|
||||
this->ports_.push_back(ModGuiPort(lv2Host,port_node));
|
||||
}
|
||||
}
|
||||
std::sort(this->ports_.begin(), this->ports_.end(),
|
||||
[](const ModGuiPort &p1, const ModGuiPort &p2) {
|
||||
return p1.index() < p2.index();
|
||||
});
|
||||
AutoLilvNodes monitoredOutputs = lilv_world_find_nodes(pWorld, modGuiUrl, modGuiUrids.mod_gui__monitoredOutputs, nullptr);
|
||||
if (monitoredOutputs) {
|
||||
LILV_FOREACH(nodes, it, monitoredOutputs.Get()) {
|
||||
AutoLilvNode monitoredOutput = lilv_nodes_get(monitoredOutputs.Get(), it);
|
||||
AutoLilvNode symbol = lilv_world_get(pWorld, monitoredOutput, lilvUris.lv2core__symbol, nullptr);
|
||||
if (symbol) {
|
||||
this->monitoredOutputs_.push_back(NonNull(lilv_node_as_string(symbol)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ModGui::ptr ModGui::Create(PluginHost *lv2Host, const LilvPlugin *lilvPlugin)
|
||||
{
|
||||
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
ModGuiUris &modGuiUrids = *(lv2Host->mod_gui_uris);
|
||||
|
||||
AutoLilvNode modGuiUri;
|
||||
std::string resourceDirectory;
|
||||
|
||||
AutoLilvNodes modGuiNodes = lilv_plugin_get_value(lilvPlugin,modGuiUrids.mod_gui__gui);
|
||||
|
||||
if (!modGuiNodes) {
|
||||
return nullptr; // no mod gui found.
|
||||
}
|
||||
LILV_FOREACH(nodes, it, modGuiNodes.Get()) {
|
||||
AutoLilvNode node = lilv_nodes_get(modGuiNodes.Get(), it);
|
||||
|
||||
AutoLilvNode resourceDir = lilv_world_get(pWorld, node, modGuiUrids.mod_gui__resourceDirectory,nullptr);
|
||||
if (resourceDir) {
|
||||
resourceDirectory = lilv_file_uri_parse(lilv_node_as_string(resourceDir),nullptr);
|
||||
modGuiUri = lilv_node_duplicate(node);
|
||||
} else {
|
||||
resourceDirectory.clear();
|
||||
modGuiUri.Free();
|
||||
}
|
||||
}
|
||||
if (!modGuiUri) {
|
||||
return nullptr; // no mod gui found.
|
||||
}
|
||||
|
||||
|
||||
return std::shared_ptr<ModGui>(new ModGui(lv2Host, lilvPlugin, resourceDirectory, modGuiUri.Get()));
|
||||
}
|
||||
|
||||
|
||||
ModGuiPort::ModGuiPort(PluginHost *lv2Host, const LilvNode *portNode)
|
||||
{
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
PluginHost::LilvUris &lilvUris = *lv2Host->lilvUris;
|
||||
|
||||
this->index_ = lilv_node_as_int(lilv_world_get(pWorld, portNode, lilvUris.lv2core__index, nullptr));
|
||||
this->symbol_ = NonNull(lilv_node_as_string(lilv_world_get(pWorld, portNode, lilvUris.lv2core__symbol, nullptr)));
|
||||
this->name_ = NonNull(lilv_node_as_string(lilv_world_get(pWorld, portNode, lilvUris.lv2core__name, nullptr)));
|
||||
}
|
||||
|
||||
static json_variant UnitsObj(const std::string &label, const std::string &render, const std::string &symbol)
|
||||
{
|
||||
json_variant obj = json_variant::make_object();
|
||||
obj["label"] = label;
|
||||
obj["render"] = render;
|
||||
obj["symbol"] = symbol;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
static std::map<Units,json_variant> unitsMap
|
||||
{
|
||||
{Units::none, UnitsObj("","%f","")},
|
||||
{Units::unknown, UnitsObj("","%f","")},
|
||||
|
||||
{Units::s, UnitsObj("seconds", "%f s", "s")},
|
||||
{Units::ms, UnitsObj("milliseconds", "%f ms", "ms")},
|
||||
{Units::min, UnitsObj("minutes", "%f mins", "min")},
|
||||
{Units::bar, UnitsObj("bars", "%f bars", "bars")},
|
||||
{Units::beat, UnitsObj("beats", "%f beats", "beats")},
|
||||
{Units::frame, UnitsObj("audio frames", "%f frames", "frames")},
|
||||
{Units::m, UnitsObj("metres", "%f m", "m")},
|
||||
{Units::cm, UnitsObj("centimetres", "%f cm", "cm")},
|
||||
{Units::mm, UnitsObj("millimetres", "%f mm", "mm")},
|
||||
{Units::km, UnitsObj("kilometres", "%f km", "km")},
|
||||
{Units::inch, UnitsObj("inches", "\"%f\"", "in")},
|
||||
{Units::mile, UnitsObj("miles", "%f mi", "mi")},
|
||||
{Units::db, UnitsObj("decibels", "%f dB", "dB")},
|
||||
{Units::pc, UnitsObj("percent", "%f%%", "%")},
|
||||
{Units::coef, UnitsObj("coefficient", "* %f", "*")},
|
||||
{Units::hz, UnitsObj("hertz", "%f Hz", "Hz")},
|
||||
{Units::khz, UnitsObj("kilohertz", "%f kHz", "kHz")},
|
||||
{Units::mhz, UnitsObj("megahertz", "%f MHz", "MHz")},
|
||||
{Units::bpm, UnitsObj("beats per minute", "%f BPM", "BPM")},
|
||||
{Units::oct, UnitsObj("octaves", "%f octaves", "oct")},
|
||||
{Units::cent, UnitsObj("cents", "%f ct", "ct")},
|
||||
{Units::semitone12TET, UnitsObj("semitones", "%f semi", "semi")},
|
||||
{Units::degree, UnitsObj("degrees", "%f°", "°")},
|
||||
{Units::midiNote, UnitsObj("MIDI note", "MIDI note %d", "note")},
|
||||
};
|
||||
|
||||
static json_variant UnitsToJsonVariant(Units units)
|
||||
{
|
||||
if (unitsMap.find(units) != unitsMap.end())
|
||||
{
|
||||
return unitsMap[units];
|
||||
}
|
||||
return unitsMap[Units::none];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline json_variant MakeJsonArray(const std::vector<T> &vec)
|
||||
{
|
||||
json_variant array = json_variant::make_array();
|
||||
auto &arrayRef = *array.as_array();
|
||||
for (const auto &item : vec)
|
||||
{
|
||||
json_variant itemVariant = json_variant(item);
|
||||
arrayRef.push_back(itemVariant);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static std::string AtomTypeToRangesType(const std::string& atomType)
|
||||
{
|
||||
if (atomType == LV2_ATOM__Path ||
|
||||
atomType == LV2_ATOM__String ||
|
||||
atomType == LV2_ATOM__URI ||
|
||||
atomType == LV2_ATOM__URID)
|
||||
{
|
||||
return "s";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Float)
|
||||
{
|
||||
return "f";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Double)
|
||||
{
|
||||
return "d";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Long)
|
||||
{
|
||||
return "l";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Vector)
|
||||
{
|
||||
return "v";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
json_variant pipedal::MakeModGuiTemplateData(
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo)
|
||||
{
|
||||
json_variant context = json_variant::make_object();
|
||||
auto &contextObj = *(context.as_object());
|
||||
|
||||
auto modGui = pluginInfo->modGui();
|
||||
|
||||
contextObj["brand"] = modGui->brand();
|
||||
contextObj["label"] = modGui->label();
|
||||
contextObj["model"] = modGui->model();
|
||||
contextObj["panel"] = modGui->panel();
|
||||
contextObj["color"] = modGui->color();
|
||||
contextObj["knob"] = modGui->knob();
|
||||
|
||||
json_variant controls = json_variant::make_array();
|
||||
auto &controlArray = *controls.as_array();
|
||||
size_t ix = 0;
|
||||
for (const auto& modGuiPort : modGui->ports())
|
||||
{
|
||||
const Lv2PortInfo &pluginPort = pluginInfo->getPort(modGuiPort.symbol());
|
||||
if (!pluginPort.is_control_port() || !pluginPort.is_input() || pluginPort.not_on_gui())
|
||||
{
|
||||
continue; // only include control ports
|
||||
}
|
||||
json_variant control = json_variant::make_object();
|
||||
auto &portObj = *control.as_object();
|
||||
portObj["index"] = double(pluginPort.index());
|
||||
portObj["symbol"] = pluginPort.symbol();
|
||||
portObj["name"] = modGuiPort.name();
|
||||
portObj["comment"] = pluginPort.comment();
|
||||
|
||||
portObj["units"] = UnitsToJsonVariant(pluginPort.units());
|
||||
|
||||
{
|
||||
json_variant ranges = json_variant::make_object();
|
||||
ranges["minimum"] = SS(pluginPort.min_value());
|
||||
ranges["maximum"] = SS(pluginPort.max_value());
|
||||
ranges["default"] = SS(pluginPort.default_value());
|
||||
portObj["ranges"] = json_variant::make_array();
|
||||
}
|
||||
json_variant scalePoints = json_variant::make_array();
|
||||
auto &scalePointsArray = *scalePoints.as_array();
|
||||
for (const auto &scalePoint: pluginPort.scale_points())
|
||||
{
|
||||
json_variant scalePointObj = json_variant::make_object();
|
||||
scalePointObj["valid"] = true;
|
||||
scalePointObj["label"] = scalePoint.label();
|
||||
scalePointObj["value"] = SS(scalePoint.value());
|
||||
scalePointsArray.push_back(scalePointObj);
|
||||
}
|
||||
portObj["scalePoints"] = scalePoints;
|
||||
|
||||
controlArray.push_back(control);
|
||||
}
|
||||
contextObj["controls"] = controls;
|
||||
|
||||
json_variant effect = json_variant::make_object();
|
||||
contextObj["effect"] = effect;
|
||||
|
||||
json_variant ports = json_variant::make_object();
|
||||
(*effect.as_object())["ports"] = ports;
|
||||
|
||||
json_variant audio = json_variant::make_object();
|
||||
(*ports.as_object())["audio"] = audio;
|
||||
|
||||
json_variant audio_input = json_variant::make_array();
|
||||
(*audio.as_object())["input"] = audio_input;
|
||||
|
||||
json_variant audio_output = json_variant::make_array();
|
||||
(*audio.as_object())["output"] = audio_output;
|
||||
json_variant midi = json_variant::make_object();
|
||||
(*ports.as_object())["midi"] = midi;
|
||||
|
||||
json_variant midi_input = json_variant::make_array();
|
||||
(*midi.as_object())["input"] = midi_input;
|
||||
json_variant midi_output = json_variant::make_array();
|
||||
(*midi.as_object())["output"] = midi_output;
|
||||
|
||||
json_variant cv = json_variant::make_object();
|
||||
(*ports.as_object())["cv"] = cv;
|
||||
|
||||
json_variant cv_input = json_variant::make_array();
|
||||
(*cv.as_object())["input"] = cv_input;
|
||||
json_variant cv_output = json_variant::make_array();
|
||||
(*cv.as_object())["output"] = cv_output;
|
||||
|
||||
{
|
||||
json_variant parametersObj = json_variant::make_object();
|
||||
json_variant pathObj = json_variant::make_array();
|
||||
parametersObj["path"] = pathObj;
|
||||
|
||||
for (const auto &patchProperty: pluginInfo->patchProperties())
|
||||
{
|
||||
json_variant parameterObj = json_variant::make_object();
|
||||
parameterObj["valid"] = true;
|
||||
parameterObj["readable"] = patchProperty.readable();
|
||||
parameterObj["writable"] = patchProperty.writable();
|
||||
parameterObj["uri"] = patchProperty.uri();
|
||||
parameterObj["label"] = patchProperty.label();
|
||||
parameterObj["type"] = patchProperty.type();
|
||||
|
||||
json_variant rangesObj = json_variant::make_object();
|
||||
rangesObj["type"] = AtomTypeToRangesType(patchProperty.type());
|
||||
rangesObj["atomType"] = patchProperty.type();
|
||||
parameterObj["ranges"] = rangesObj;
|
||||
|
||||
parameterObj["comment"] = patchProperty.comment();
|
||||
parameterObj["shortName"] = patchProperty.shortName();
|
||||
|
||||
parameterObj["fileTypes"] = MakeJsonArray(patchProperty.fileTypes());
|
||||
parameterObj["supportedExtensions"] = MakeJsonArray(patchProperty.supportedExtensions());
|
||||
|
||||
if (patchProperty.type() == LV2_ATOM__Path && patchProperty.writable()) {
|
||||
// Feed them no files; they will be added later.
|
||||
json_variant filesObj = json_variant::make_array();
|
||||
json_variant fileObj = json_variant::make_object();
|
||||
fileObj["fileType"] = "{{filetype}}"; // magic tags that we will use to generate files at runtime.
|
||||
fileObj["fullname"] = "{{fullname}}";
|
||||
fileObj["basename"] = "{{basename}}";
|
||||
filesObj.as_array()->push_back(fileObj);
|
||||
parameterObj["files"] = filesObj;
|
||||
|
||||
pathObj.as_array()->push_back(parameterObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
effect["parameters"] = parametersObj;
|
||||
|
||||
|
||||
}
|
||||
|
||||
for (const auto &pluginPort : pluginInfo->ports())
|
||||
{
|
||||
if (pluginPort->is_control_port())
|
||||
{
|
||||
continue; // only include control ports
|
||||
}
|
||||
if (pluginPort->is_audio_port())
|
||||
{
|
||||
json_variant audioPort = json_variant::make_object();
|
||||
auto &audioPortObj = *audioPort.as_object();
|
||||
audioPortObj["index"] = double(pluginPort->index());
|
||||
audioPortObj["symbol"] = pluginPort->symbol();
|
||||
audioPortObj["name"] = pluginPort->name();
|
||||
if (pluginPort->is_input())
|
||||
{
|
||||
audio_input.as_array()->push_back(audioPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_output.as_array()->push_back(audioPort);
|
||||
}
|
||||
}
|
||||
else if (pluginPort->is_atom_port())
|
||||
{
|
||||
if (pluginPort->supports_midi())
|
||||
{
|
||||
json_variant midiPort = json_variant::make_object();
|
||||
auto &midiPortObj = *midiPort.as_object();
|
||||
midiPortObj["index"] = double(pluginPort->index());
|
||||
midiPortObj["symbol"] = pluginPort->symbol();
|
||||
midiPortObj["name"] = pluginPort->name();
|
||||
if (pluginPort->is_input())
|
||||
{
|
||||
midi_input.as_array()->push_back(midiPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
midi_output.as_array()->push_back(midiPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* else if plugin_port->is_cv_port()... */
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(ModGuiPort)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,index)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,symbol)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,name)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(ModGui)
|
||||
JSON_MAP_REFERENCE(ModGui,pluginUri)
|
||||
JSON_MAP_REFERENCE(ModGui,resourceDirectory)
|
||||
JSON_MAP_REFERENCE(ModGui,iconTemplate)
|
||||
JSON_MAP_REFERENCE(ModGui,settingsTemplate)
|
||||
JSON_MAP_REFERENCE(ModGui,screenshot)
|
||||
JSON_MAP_REFERENCE(ModGui,javascript)
|
||||
JSON_MAP_REFERENCE(ModGui,stylesheet)
|
||||
JSON_MAP_REFERENCE(ModGui,thumbnail)
|
||||
JSON_MAP_REFERENCE(ModGui,discussionUrl)
|
||||
JSON_MAP_REFERENCE(ModGui,documentationUrl)
|
||||
JSON_MAP_REFERENCE(ModGui,brand)
|
||||
JSON_MAP_REFERENCE(ModGui,label)
|
||||
JSON_MAP_REFERENCE(ModGui,model)
|
||||
JSON_MAP_REFERENCE(ModGui,panel)
|
||||
JSON_MAP_REFERENCE(ModGui,color)
|
||||
JSON_MAP_REFERENCE(ModGui,knob)
|
||||
JSON_MAP_REFERENCE(ModGui,ports)
|
||||
JSON_MAP_REFERENCE(ModGui,monitoredOutputs)
|
||||
JSON_MAP_END()
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lv2/urid/urid.h"
|
||||
#include <string>
|
||||
#include <lilv/lilv.h>
|
||||
#include <memory>
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "json.hpp"
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
class MapFeature;
|
||||
class Lv2PluginInfo;
|
||||
|
||||
class ModGuiUris
|
||||
{
|
||||
public:
|
||||
ModGuiUris(LilvWorld *pWorld, MapFeature &mapFeature);
|
||||
|
||||
AutoLilvNode mod_gui__gui;
|
||||
AutoLilvNode mod_gui__modgui;
|
||||
AutoLilvNode mod_gui__resourceDirectory;
|
||||
AutoLilvNode mod_gui__iconTemplate;
|
||||
AutoLilvNode mod_gui__settingsTemplate;
|
||||
AutoLilvNode mod_gui__javascript;
|
||||
AutoLilvNode mod_gui__stylesheet;
|
||||
AutoLilvNode mod_gui__screenshot;
|
||||
AutoLilvNode mod_gui__thumbnail;
|
||||
AutoLilvNode mod_gui__discussionURL;
|
||||
AutoLilvNode mod_gui__documentation;
|
||||
AutoLilvNode mod_gui__brand;
|
||||
AutoLilvNode mod_gui__label;
|
||||
AutoLilvNode mod_gui__model;
|
||||
AutoLilvNode mod_gui__panel;
|
||||
AutoLilvNode mod_gui__color;
|
||||
AutoLilvNode mod_gui__knob;
|
||||
AutoLilvNode mod_gui__port;
|
||||
AutoLilvNode mod_gui__monitoredOutputs;
|
||||
};
|
||||
|
||||
class PluginHost;
|
||||
|
||||
class ModGuiPort {
|
||||
public:
|
||||
ModGuiPort() = default;
|
||||
ModGuiPort(PluginHost *lv2Host, const LilvNode *portNode);
|
||||
private:
|
||||
uint32_t index_ = 0;
|
||||
std::string symbol_;
|
||||
std::string name_;
|
||||
public:
|
||||
uint32_t index() const { return index_; }
|
||||
void index(uint32_t value) { index_ = value; }
|
||||
const std::string &symbol() const { return symbol_; }
|
||||
void symbol(const std::string &value) { symbol_ = value; }
|
||||
const std::string &name() const { return name_; }
|
||||
void name(const std::string &value) { name_ = value; }
|
||||
|
||||
DECLARE_JSON_MAP(ModGuiPort);
|
||||
};
|
||||
|
||||
class ModGui
|
||||
{
|
||||
private:
|
||||
ModGui(PluginHost *lv2Host, const LilvPlugin *lilvPlugin, const std::string &resourceDirectory, const LilvNode *modGuiNode);
|
||||
|
||||
public:
|
||||
using self = ModGui;
|
||||
using ptr = std::shared_ptr<self>;
|
||||
static ptr Create(PluginHost *lv2Host, const LilvPlugin *lilvPlugin);
|
||||
|
||||
ModGui() = default;
|
||||
virtual ~ModGui() = default;
|
||||
|
||||
ModGui(const ModGui &) = default;
|
||||
ModGui &operator=(const ModGui &) = default;
|
||||
ModGui(ModGui &&) = default;
|
||||
ModGui &operator=(ModGui &&) = default;
|
||||
|
||||
private:
|
||||
std::string pluginUri_;
|
||||
|
||||
std::string resourceDirectory_;
|
||||
std::string iconTemplate_;
|
||||
std::string settingsTemplate_;
|
||||
std::string javascript_;
|
||||
std::string stylesheet_;
|
||||
std::string screenshot_;
|
||||
std::string thumbnail_;
|
||||
std::string discussionUrl_;
|
||||
std::string documentationUrl_;
|
||||
std::string brand_;
|
||||
std::string label_;
|
||||
std::string model_;
|
||||
std::string panel_;
|
||||
std::string color_;
|
||||
std::string knob_;
|
||||
std::vector<ModGuiPort> ports_;
|
||||
|
||||
std::vector<std::string> monitoredOutputs_;
|
||||
|
||||
public:
|
||||
const std::string &pluginUri() const { return pluginUri_; }
|
||||
void pluginUri(const std::string &value) { pluginUri_ = value; }
|
||||
|
||||
const std::string &resourceDirectory() const { return resourceDirectory_; }
|
||||
void resourceDirectory(const std::string &value) { resourceDirectory_ = value; }
|
||||
const std::string &iconTemplate() const { return iconTemplate_; }
|
||||
void iconTemplate(const std::string &value) { iconTemplate_ = value; }
|
||||
const std::string &settingsTemplate() const { return settingsTemplate_; }
|
||||
void settingsTemplate(const std::string &value) { settingsTemplate_ = value; }
|
||||
const std::string &screenshot() const { return screenshot_; }
|
||||
const std::string &javascript() const { return javascript_; }
|
||||
void javascript(const std::string &value) { javascript_ = value; }
|
||||
const std::string &stylesheet() const { return stylesheet_; }
|
||||
void stylesheet(const std::string &value) { stylesheet_ = value; }
|
||||
void screenshot(const std::string &value) { screenshot_ = value; }
|
||||
const std::string &thumbnail() const { return thumbnail_; }
|
||||
void thumbnail(const std::string &value) { thumbnail_ = value; }
|
||||
const std::string &discussionUrl() const { return discussionUrl_; }
|
||||
void discussionUrl(const std::string &value) { discussionUrl_ = value; }
|
||||
const std::string &documentationUrl() const { return documentationUrl_; }
|
||||
void documentationUrl(const std::string &value) { documentationUrl_ = value; }
|
||||
const std::string &brand() const { return brand_; }
|
||||
void brand(const std::string &value) { brand_ = value; }
|
||||
const std::string &label() const { return label_; }
|
||||
void label(const std::string &value) { label_ = value; }
|
||||
const std::string &model() const { return model_; }
|
||||
void model(const std::string &value) { model_ = value; }
|
||||
const std::string &panel() const { return panel_; }
|
||||
void panel(const std::string &value) { panel_ = value; }
|
||||
|
||||
const std::string &color() const { return color_; }
|
||||
void color(const std::string &value) { color_ = value; }
|
||||
const std::string &knob() const { return knob_; }
|
||||
void knob(const std::string &value) { knob_ = value; }
|
||||
|
||||
const std::vector<ModGuiPort> &ports() const { return ports_; }
|
||||
std::vector<ModGuiPort> &ports() { return ports_; }
|
||||
void ports(const std::vector<ModGuiPort> &value) { ports_ = value; }
|
||||
|
||||
|
||||
const std::vector<std::string> &monitoredOutputs() const { return monitoredOutputs_; }
|
||||
std::vector<std::string> &monitoredOutputs() { return monitoredOutputs_; }
|
||||
void monitoredOutputs(const std::vector<std::string> &value) { monitoredOutputs_ = value; }
|
||||
|
||||
DECLARE_JSON_MAP(ModGui);
|
||||
|
||||
};
|
||||
|
||||
json_variant MakeModGuiTemplateData(
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo
|
||||
);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2025 Robin 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 "catch.hpp"
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "ModTemplateGenerator.hpp"
|
||||
|
||||
#include "PiPedalModel.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
class PluginHostTest {
|
||||
|
||||
public:
|
||||
static void TestInit()
|
||||
{
|
||||
PiPedalModel model;
|
||||
model.GetPluginHost().LoadLilv();
|
||||
const auto&plugins = model.GetPluginHost().GetPlugins();
|
||||
REQUIRE(!plugins.empty());
|
||||
size_t modGuicount = 0;
|
||||
for (const auto&plugin : plugins)
|
||||
{
|
||||
REQUIRE(plugin != nullptr);
|
||||
|
||||
if (plugin->modGui())
|
||||
{
|
||||
modGuicount++;
|
||||
const auto&modGui = plugin->modGui();
|
||||
REQUIRE(!modGui->iconTemplate().empty());
|
||||
REQUIRE(modGui->javascript().empty());
|
||||
REQUIRE(!modGui->stylesheet().empty());
|
||||
REQUIRE(!modGui->screenshot().empty());
|
||||
REQUIRE(!modGui->thumbnail().empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("ModGui Init Test", "[mod_gui_init]")
|
||||
{
|
||||
PluginHostTest::TestInit();
|
||||
}
|
||||
|
||||
TEST_CASE("ModGui Templates", "[mod_gui_templates]")
|
||||
{
|
||||
|
||||
json_variant data = json_variant::make_object();
|
||||
data["name"] = "Test Plugin";
|
||||
data["uri"] = "http://example.com/test-plugin";
|
||||
data["version"] = "1.0.0";
|
||||
data["author"] = "Test Author";
|
||||
data["cn"] = "14323";
|
||||
json_variant items = json_variant::make_array();
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
json_variant item = json_variant::make_object();
|
||||
item["label"] = "Item " + std::to_string(i + 1);
|
||||
items.as_array()->push_back(item);
|
||||
|
||||
}
|
||||
data["items"] = items;
|
||||
|
||||
data["inputs"] = json_variant::make_object();
|
||||
data["inputs"]["input1"] = "Input 1";
|
||||
data["inputs"]["input2"] = "Input 2";
|
||||
|
||||
data["_cn"] = "?cn=1234";
|
||||
data["_cns"] = "_toobamp.lv2_ToobAmp__1234";
|
||||
|
||||
std::string templateString = R"(
|
||||
<div>
|
||||
<h1>{{name}}</h1>
|
||||
<p>URI: {{uri}}</p>
|
||||
<p>Version: {{version}}</p>
|
||||
<p>Author: {{author}}</p>
|
||||
<ul>
|
||||
{{#items}}
|
||||
<li id="item-{{cn}}">{{label}}</li>
|
||||
{{/items}}
|
||||
</ul>
|
||||
<h2>{{inputs.input1}}</h2>
|
||||
<h2>{{inputs.input2}}</h2>
|
||||
<h2>{{inputs.input3}}</h2>
|
||||
<img src="img/helpIcon.png{{{cn}}}" class="{{{cns}}}_help_icon" alt="Help Icon" />
|
||||
</div>
|
||||
)";
|
||||
auto result = GenerateFromTemplateString(templateString, data);
|
||||
|
||||
cout << result;
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 "ModTemplateGenerator.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "util.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
|
||||
static std::string trim(const std::string &str)
|
||||
{
|
||||
size_t first = str.find_first_not_of(" \t\n\r");
|
||||
if (first == std::string::npos)
|
||||
return "";
|
||||
size_t last = str.find_last_not_of(" \t\n\r");
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
static bool variableEndsWithNumber(const std::string &variableName)
|
||||
{
|
||||
size_t pos = variableName.find_last_of(".");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (size_t i = pos + 1; i < variableName.length(); ++i)
|
||||
{
|
||||
if (!std::isdigit(variableName[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check if the last character is a digit
|
||||
return true;
|
||||
}
|
||||
void splitIndexedArrayVariable(
|
||||
const std::string &variableName,
|
||||
std::string &arrayName,
|
||||
int64_t &arrayIndex)
|
||||
{
|
||||
size_t pos = variableName.find_last_of(".");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
throw std::logic_error("Variable name does not contain an index: " + variableName);
|
||||
}
|
||||
else
|
||||
{
|
||||
arrayName = variableName.substr(0, pos);
|
||||
arrayIndex = std::stoll(variableName.substr(pos + 1));
|
||||
}
|
||||
}
|
||||
|
||||
class VariableContext
|
||||
{
|
||||
public:
|
||||
VariableContext(
|
||||
const json_variant &context,
|
||||
VariableContext *parent = nullptr)
|
||||
: context(context), parent(parent)
|
||||
{
|
||||
}
|
||||
json_variant getVariable(const std::string &name)
|
||||
{
|
||||
std::vector<std::string> parts = split(trim(name), '.');
|
||||
json_variant *current = &context;
|
||||
for (const auto &part : parts)
|
||||
{
|
||||
if (current->is_object())
|
||||
{
|
||||
auto ff = current->as_object()->find(part);
|
||||
if (ff != current->as_object()->end())
|
||||
{
|
||||
current = &ff->second;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
return parent->getVariable(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return json_variant(json_null());
|
||||
}
|
||||
}
|
||||
return *current;
|
||||
}
|
||||
|
||||
private:
|
||||
json_variant context;
|
||||
VariableContext *parent;
|
||||
};
|
||||
|
||||
static std::string GenerateTemplateFromString(
|
||||
const std::string &content,
|
||||
VariableContext &context)
|
||||
{
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
size_t ix = 0;
|
||||
size_t end = content.length();
|
||||
while (ix < end)
|
||||
{
|
||||
// copy content until we find a '{{'
|
||||
char c = content[ix++];
|
||||
if (c != '{')
|
||||
{
|
||||
ss << c;
|
||||
continue;
|
||||
}
|
||||
if (ix >= end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = content[ix++];
|
||||
if (c != '{')
|
||||
{
|
||||
ss << '{';
|
||||
ss << c;
|
||||
continue;
|
||||
}
|
||||
if (ix < end && content[ix] == '{')
|
||||
{
|
||||
// this is a '{{{', so we just copy it.
|
||||
++ix; // skip the third '{'
|
||||
size_t endTagPos = content.find("}}}", ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag '{{{' in template.");
|
||||
}
|
||||
std::string variableString = content.substr(ix, endTagPos - ix);
|
||||
ix = endTagPos + 3; // Move past the closing '}}}'
|
||||
auto result = context.getVariable("_" + variableString);
|
||||
if (result.is_null())
|
||||
{
|
||||
result = ""; // Default to empty string if variable not found
|
||||
}
|
||||
if (!result.is_string())
|
||||
{
|
||||
throw std::runtime_error("Variable '" + variableString + "' is not a string.");
|
||||
}
|
||||
ss << result.as_string();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Now we have a '{{' at position ix-2.
|
||||
size_t endTagPos = content.find("}}", ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag '{{' in template.");
|
||||
}
|
||||
std::string variableString = content.substr(ix, endTagPos - ix);
|
||||
ix = endTagPos + 2; // Move past the closing '}}'
|
||||
if (!variableString.starts_with("#"))
|
||||
{
|
||||
// a straightforward variable substitution.
|
||||
json_variant value = context.getVariable(variableString);
|
||||
if (value.is_null())
|
||||
{
|
||||
value = ""; // Default to empty string if variable not found
|
||||
}
|
||||
if (!value.is_string())
|
||||
{
|
||||
throw std::runtime_error("Variable '" + variableString + "' is not a string.");
|
||||
}
|
||||
ss << value.as_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a looping construct.
|
||||
std::string variableName = variableString.substr(1);
|
||||
std::string endTag = SS("{{/" << variableName << "}}");
|
||||
size_t endTagPos = content.find(endTag, ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag for '" + variableString + "' in template.");
|
||||
}
|
||||
|
||||
std::string arrayContent = content.substr(ix, endTagPos - ix);
|
||||
|
||||
if (variableEndsWithNumber(variableName))
|
||||
{
|
||||
std::string arrayName;
|
||||
int64_t arrayIndex = 0;
|
||||
splitIndexedArrayVariable(variableName, arrayName, arrayIndex);
|
||||
|
||||
json_variant array = context.getVariable(arrayName);
|
||||
auto &arrayValue = *array.as_array();
|
||||
if (arrayIndex >= 0 && arrayIndex < static_cast<int64_t>(arrayValue.size()))
|
||||
{
|
||||
json_variant contextValue = arrayValue[(size_t)arrayIndex];
|
||||
// variable frame.
|
||||
VariableContext itemContext{contextValue, &context};
|
||||
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
json_variant variable = context.getVariable(variableName);
|
||||
if (variable.is_array())
|
||||
{
|
||||
auto &arrayValue = *variable.as_array();
|
||||
|
||||
for (auto iter = arrayValue.begin(); iter != arrayValue.end(); ++iter)
|
||||
{
|
||||
// variable frame.
|
||||
VariableContext itemContext{*iter, &context};
|
||||
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
} else if (variable.is_object())
|
||||
{
|
||||
VariableContext itemContext(variable,&context);
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
}
|
||||
|
||||
ix = endTagPos + endTag.length(); // Move past the closing tag
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
using namespace impl;
|
||||
|
||||
std::string GenerateFromTemplateString(
|
||||
const std::string &templateString,
|
||||
const json_variant &data)
|
||||
{
|
||||
VariableContext context{data, nullptr};
|
||||
return impl::GenerateTemplateFromString(
|
||||
templateString,
|
||||
context);
|
||||
}
|
||||
|
||||
std::string GenerateFromTemplateFile(
|
||||
const std::filesystem::path &templateFilePath,
|
||||
const json_variant &data)
|
||||
{
|
||||
std::string templateContent;
|
||||
if (!fs::exists(templateFilePath))
|
||||
{
|
||||
throw std::runtime_error("Template file not found: " + templateFilePath.string());
|
||||
}
|
||||
std::ifstream file(templateFilePath);
|
||||
if (!file.is_open())
|
||||
{
|
||||
throw std::runtime_error("Failed to open template file: " + templateFilePath.string());
|
||||
}
|
||||
templateContent.assign((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
return GenerateFromTemplateString(
|
||||
templateContent,
|
||||
data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
std::string GenerateFromTemplateString(
|
||||
const std::string& templateString,
|
||||
const json_variant& data
|
||||
);
|
||||
|
||||
std::string GenerateFromTemplateFile(
|
||||
const std::filesystem::path& templateFilePath,
|
||||
const json_variant& data
|
||||
);
|
||||
}
|
||||
@@ -242,7 +242,7 @@ void PiPedalModel::LoadLv2PluginInfo()
|
||||
}
|
||||
|
||||
pluginChangeMonitor = std::make_unique<Lv2PluginChangeMonitor>(*this);
|
||||
pluginHost.Load(configuration.GetLv2Path().c_str());
|
||||
pluginHost.LoadLilv(configuration.GetLv2Path().c_str());
|
||||
|
||||
// Copy all presets out of Lilv data to json files
|
||||
// so that we can close lilv while we're actually
|
||||
|
||||
@@ -331,7 +331,9 @@ namespace pipedal
|
||||
void LoadLv2PluginInfo();
|
||||
void Load();
|
||||
|
||||
const PluginHost &GetLv2Host() const { return pluginHost; }
|
||||
const PluginHost &GetPluginHost() const { return pluginHost; }
|
||||
PluginHost &GetPluginHost() { return pluginHost; }
|
||||
|
||||
Pedalboard GetCurrentPedalboardCopy()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
|
||||
@@ -1331,12 +1331,12 @@ public:
|
||||
}
|
||||
else if (message == "plugins")
|
||||
{
|
||||
auto ui_plugins = model.GetLv2Host().GetUiPlugins();
|
||||
auto ui_plugins = model.GetPluginHost().GetUiPlugins();
|
||||
Reply(replyTo, "plugins", ui_plugins);
|
||||
}
|
||||
else if (message == "pluginClasses")
|
||||
{
|
||||
auto classes = model.GetLv2Host().GetLv2PluginClass();
|
||||
auto classes = model.GetPluginHost().GetLv2PluginClass();
|
||||
Reply(replyTo, "pluginClasses", classes);
|
||||
}
|
||||
else if (message == "hello")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2025 Robin 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
|
||||
|
||||
+207
-59
@@ -136,8 +136,11 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
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);
|
||||
@@ -150,7 +153,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
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__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);
|
||||
@@ -159,7 +162,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
pipedalUI__yBottom = lilv_new_uri(pWorld, PIPEDAL_UI__yBottom);
|
||||
pipedalUI__xLog = lilv_new_uri(pWorld, PIPEDAL_UI__xLog);
|
||||
pipedalUI__width = lilv_new_uri(pWorld, PIPEDAL_UI__width);
|
||||
pipedalUI__graphicEq = lilv_new_uri(pWorld,PIPEDAL_UI__graphicEq);
|
||||
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);
|
||||
@@ -171,6 +174,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
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");
|
||||
@@ -194,16 +198,39 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -270,7 +297,6 @@ PluginHost::PluginHost()
|
||||
fileMetadataFeature.Prepare(mapFeature);
|
||||
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
||||
|
||||
|
||||
lv2Features.push_back(nullptr);
|
||||
|
||||
this->urids = new Urids(mapFeature);
|
||||
@@ -294,8 +320,11 @@ PluginHost::~PluginHost()
|
||||
{
|
||||
delete lilvUris;
|
||||
lilvUris = nullptr;
|
||||
|
||||
delete urids;
|
||||
urids = nullptr;
|
||||
delete mod_gui_uris;
|
||||
mod_gui_uris = nullptr;
|
||||
free_world();
|
||||
}
|
||||
|
||||
@@ -397,7 +426,7 @@ void PluginHost::LoadPluginClassesFromLilv()
|
||||
}
|
||||
}
|
||||
|
||||
void PluginHost::Load(const char *lv2Path)
|
||||
void PluginHost::LoadLilv(const char *lv2Path)
|
||||
{
|
||||
|
||||
this->plugins_.clear();
|
||||
@@ -418,6 +447,9 @@ void PluginHost::Load(const char *lv2Path)
|
||||
|
||||
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)
|
||||
|
||||
@@ -439,7 +471,8 @@ void PluginHost::Load(const char *lv2Path)
|
||||
if (pluginInfo->hasCvPorts())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
} else if (pluginInfo->hasUnsupportedPatchProperties())
|
||||
}
|
||||
else if (pluginInfo->hasUnsupportedPatchProperties())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has unsupported patch parameters).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
@@ -497,20 +530,20 @@ void PluginHost::Load(const char *lv2Path)
|
||||
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) {
|
||||
// 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());
|
||||
|
||||
(int)info.audio_inputs(), (int)info.audio_outputs());
|
||||
}
|
||||
else if (info.audio_inputs() == 0 && info.audio_outputs() == 0 )
|
||||
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)
|
||||
}
|
||||
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());
|
||||
@@ -536,7 +569,8 @@ void PluginHost::Load(const char *lv2Path)
|
||||
#endif
|
||||
else
|
||||
{
|
||||
if (info.audio_inputs() == 0) {
|
||||
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));
|
||||
@@ -619,7 +653,7 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
// example:
|
||||
|
||||
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
|
||||
// a lv2:Parameter;
|
||||
// a lv2:Parameter;
|
||||
// rdfs:label "Model";
|
||||
// rdfs:range atom:Path.
|
||||
// ...
|
||||
@@ -644,7 +678,6 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
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();
|
||||
|
||||
@@ -662,19 +695,16 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
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;
|
||||
std::string fileTypes = ModFileTypes::DEFAULT_FILE_TYPES;
|
||||
if (mod__fileTypes)
|
||||
{
|
||||
// "nam,nammodel"
|
||||
@@ -682,8 +712,6 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
}
|
||||
ModFileTypes modFileTypes(fileTypes);
|
||||
|
||||
|
||||
|
||||
// Legacy case: audio_uploads/<plugin_directory> exists.
|
||||
|
||||
std::filesystem::path bundleDirectoryName = std::filesystem::path(bundle_path()).parent_path().filename();
|
||||
@@ -693,13 +721,14 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
|
||||
if (std::filesystem::exists(legacyUploadPath)
|
||||
&& !std::filesystem::exists(legacyUploadPath / ".migrated"))
|
||||
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 {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modFileTypes.rootDirectories().size() == 1)
|
||||
{
|
||||
std::string modName = modFileTypes.rootDirectories()[0];
|
||||
@@ -708,9 +737,10 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileProperties.push_back(fileProperty);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
unsupportedPatchProperty = true;
|
||||
}
|
||||
}
|
||||
@@ -718,24 +748,25 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
}
|
||||
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;
|
||||
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();
|
||||
|
||||
});
|
||||
// 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;
|
||||
@@ -764,6 +795,12 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
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);
|
||||
|
||||
@@ -838,6 +875,59 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
}
|
||||
|
||||
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fetch pipedal plugin UI
|
||||
|
||||
@@ -886,7 +976,7 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
++nOutputs;
|
||||
}
|
||||
}
|
||||
|
||||
this->modGui_ = ModGui::Create(lv2Host, pPlugin);
|
||||
this->is_valid_ = isValid;
|
||||
}
|
||||
|
||||
@@ -1012,22 +1102,21 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
||||
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->pipedal_graphicEq_ = lilv_port_has_property(plugin, pPort, host->lilvUris->pipedalUI__graphicEq);
|
||||
|
||||
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);
|
||||
|
||||
AutoLilvNode port_ledColor = lilv_port_get(plugin,pPort,host->lilvUris->pipedalUI__ledColor);
|
||||
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) {
|
||||
if (value)
|
||||
{
|
||||
this->pipedal_ledColor_ = value;
|
||||
}
|
||||
}
|
||||
@@ -1164,6 +1253,8 @@ bool PluginHost::is_a(const std::string &class_, const std::string &target_class
|
||||
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()),
|
||||
@@ -1173,7 +1264,9 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
||||
audio_outputs_(0),
|
||||
description_(plugin->comment()),
|
||||
has_midi_input_(false),
|
||||
has_midi_output_(false)
|
||||
has_midi_output_(false),
|
||||
modGui_(plugin->modGui()),
|
||||
patchProperties_(plugin->patchProperties())
|
||||
{
|
||||
PLUGIN_MAP_CHECK();
|
||||
auto pluginClass = pHost->GetPluginClass(plugin->plugin_class());
|
||||
@@ -1253,7 +1346,7 @@ std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri)
|
||||
return ff->second;
|
||||
}
|
||||
|
||||
Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,Lv2Pedalboard *existingPedalboard,Lv2PedalboardErrorList &errorList)
|
||||
Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard, Lv2Pedalboard *existingPedalboard, Lv2PedalboardErrorList &errorList)
|
||||
{
|
||||
ExistingEffectMap existingEffects;
|
||||
|
||||
@@ -1261,7 +1354,8 @@ Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,L
|
||||
{
|
||||
for (auto &effect : existingPedalboard->GetSharedEffectList())
|
||||
{
|
||||
if (effect->IsLv2Effect()) {
|
||||
if (effect->IsLv2Effect())
|
||||
{
|
||||
existingEffects[effect->GetInstanceId()] = effect;
|
||||
}
|
||||
}
|
||||
@@ -1269,7 +1363,7 @@ Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,L
|
||||
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
|
||||
try
|
||||
{
|
||||
pPedalboard->Prepare(this, pedalboard, errorList,&existingEffects);
|
||||
pPedalboard->Prepare(this, pedalboard, errorList, &existingEffects);
|
||||
return pPedalboard;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@@ -1589,17 +1683,16 @@ static void createTargetLinks(const std::filesystem::path &sourceDirectory, cons
|
||||
}
|
||||
}
|
||||
|
||||
std::string PluginHost::MapResourcePath(const std::string&pluginUri, const std::string&filePath)
|
||||
std::string PluginHost::MapResourcePath(const std::string &pluginUri, const std::string &filePath)
|
||||
{
|
||||
auto plugin = GetPluginInfo(pluginUri);
|
||||
if (plugin) {
|
||||
|
||||
if (plugin)
|
||||
{
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
return filePath;
|
||||
|
||||
}
|
||||
|
||||
void PluginHost::CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory)
|
||||
@@ -1679,6 +1772,43 @@ std::string PluginHost::AbstractPath(const std::string &path)
|
||||
}
|
||||
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,',');
|
||||
}
|
||||
}
|
||||
|
||||
// void PiPedalHostLogError(const std::string &error)
|
||||
// {
|
||||
@@ -1757,6 +1887,8 @@ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||
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_),
|
||||
@@ -1774,6 +1906,8 @@ json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
||||
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::storage_type<Lv2PluginClass> Lv2PluginClass::jmap{{
|
||||
@@ -1831,6 +1965,8 @@ json_map::storage_type<Lv2PluginUiInfo>
|
||||
{
|
||||
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()),
|
||||
@@ -1848,4 +1984,16 @@ json_map::storage_type<Lv2PluginUiInfo>
|
||||
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()
|
||||
+67
-3
@@ -32,6 +32,7 @@
|
||||
#include <string>
|
||||
#include "IHost.hpp"
|
||||
#include <set>
|
||||
#include "ModGui.hpp"
|
||||
|
||||
//#include "lv2.h"
|
||||
#include "Units.hpp"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "MapPathFeature.hpp"
|
||||
#include "ModGui.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -325,6 +327,38 @@ namespace pipedal
|
||||
static json_map::storage_type<Lv2PortInfo> jmap;
|
||||
};
|
||||
|
||||
class Lv2PatchPropertyInfo {
|
||||
|
||||
private:
|
||||
std::string uri_;
|
||||
bool writable_ = false;
|
||||
bool readable_ = false;
|
||||
std::string label_;
|
||||
uint32_t index_ = -1;
|
||||
std::string type_;
|
||||
std::string comment_;
|
||||
std::string shortName_;
|
||||
std::vector<std::string> fileTypes_;
|
||||
std::vector<std::string> supportedExtensions_;
|
||||
public:
|
||||
Lv2PatchPropertyInfo() {}
|
||||
Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri);
|
||||
|
||||
LV2_PROPERTY_GETSET(uri);
|
||||
LV2_PROPERTY_GETSET(writable);
|
||||
LV2_PROPERTY_GETSET(readable);
|
||||
LV2_PROPERTY_GETSET(label);
|
||||
LV2_PROPERTY_GETSET_SCALAR(index);
|
||||
LV2_PROPERTY_GETSET(type);
|
||||
LV2_PROPERTY_GETSET(comment);
|
||||
LV2_PROPERTY_GETSET(shortName);
|
||||
LV2_PROPERTY_GETSET(fileTypes);
|
||||
LV2_PROPERTY_GETSET(supportedExtensions);
|
||||
|
||||
DECLARE_JSON_MAP(Lv2PatchPropertyInfo);
|
||||
|
||||
};
|
||||
|
||||
class Lv2PortGroup
|
||||
{
|
||||
private:
|
||||
@@ -364,6 +398,8 @@ namespace pipedal
|
||||
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
||||
std::string bundle_path_;
|
||||
std::string uri_;
|
||||
uint32_t minorVersion_ = 0;
|
||||
uint32_t microVersion_ = 0;
|
||||
std::string name_;
|
||||
std::string plugin_class_;
|
||||
std::string brand_;
|
||||
@@ -380,8 +416,12 @@ namespace pipedal
|
||||
std::string comment_;
|
||||
std::vector<std::shared_ptr<Lv2PortInfo>> ports_;
|
||||
std::vector<std::shared_ptr<Lv2PortGroup>> port_groups_;
|
||||
std::vector<Lv2PatchPropertyInfo> patchProperties_;
|
||||
|
||||
bool is_valid_ = false;
|
||||
PiPedalUI::ptr piPedalUI_;
|
||||
ModGui::ptr modGui_;
|
||||
|
||||
bool hasUnsupportedPatchProperties_ = false;
|
||||
|
||||
bool IsSupportedFeature(const std::string &feature) const;
|
||||
@@ -390,6 +430,8 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(bundle_path)
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
LV2_PROPERTY_GETSET(name)
|
||||
LV2_PROPERTY_GETSET(minorVersion)
|
||||
LV2_PROPERTY_GETSET(microVersion)
|
||||
LV2_PROPERTY_GETSET(brand)
|
||||
LV2_PROPERTY_GETSET(label)
|
||||
LV2_PROPERTY_GETSET(plugin_class)
|
||||
@@ -406,6 +448,8 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(has_factory_presets)
|
||||
LV2_PROPERTY_GETSET(piPedalUI)
|
||||
LV2_PROPERTY_GETSET(hasUnsupportedPatchProperties)
|
||||
LV2_PROPERTY_GETSET(modGui)
|
||||
LV2_PROPERTY_GETSET(patchProperties)
|
||||
|
||||
const Lv2PortInfo &getPort(const std::string &symbol)
|
||||
{
|
||||
@@ -618,6 +662,8 @@ namespace pipedal
|
||||
private:
|
||||
std::string uri_;
|
||||
std::string name_;
|
||||
uint32_t minorVersion_ = 0;
|
||||
uint32_t microVersion_ = 0;;
|
||||
std::string brand_;
|
||||
std::string label_;
|
||||
std::string author_name_;
|
||||
@@ -636,10 +682,15 @@ namespace pipedal
|
||||
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||
std::vector<UiFrequencyPlot::ptr> frequencyPlots_;
|
||||
std::vector<UiPortNotification::ptr> uiPortNotifications_;
|
||||
ModGui::ptr modGui_;
|
||||
std::vector<Lv2PatchPropertyInfo> patchProperties_;
|
||||
|
||||
|
||||
public:
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
LV2_PROPERTY_GETSET(name)
|
||||
LV2_PROPERTY_GETSET(minorVersion)
|
||||
LV2_PROPERTY_GETSET(microVersion)
|
||||
LV2_PROPERTY_GETSET(brand)
|
||||
LV2_PROPERTY_GETSET(label)
|
||||
LV2_PROPERTY_GETSET(author_name)
|
||||
@@ -657,6 +708,8 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(fileProperties)
|
||||
LV2_PROPERTY_GETSET(frequencyPlots)
|
||||
LV2_PROPERTY_GETSET(uiPortNotifications)
|
||||
LV2_PROPERTY_GETSET(modGui)
|
||||
LV2_PROPERTY_GETSET(patchProperties)
|
||||
|
||||
static json_map::storage_type<Lv2PluginUiInfo> jmap;
|
||||
};
|
||||
@@ -679,6 +732,8 @@ namespace pipedal
|
||||
#endif
|
||||
friend class pipedal::AutoLilvNode;
|
||||
friend class pipedal::PiPedalUI;
|
||||
friend class PluginHostTest;
|
||||
|
||||
static const char *RDFS__comment;
|
||||
static const char *RDFS__range;
|
||||
|
||||
@@ -707,6 +762,7 @@ namespace pipedal
|
||||
AutoLilvNode invada_units__unit; // typo in invada plugins.
|
||||
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
|
||||
|
||||
|
||||
AutoLilvNode atom__bufferType;
|
||||
AutoLilvNode atom__Path;
|
||||
AutoLilvNode presets__preset;
|
||||
@@ -714,8 +770,11 @@ namespace pipedal
|
||||
AutoLilvNode rdfs__label;
|
||||
AutoLilvNode lv2core__symbol;
|
||||
AutoLilvNode lv2core__name;
|
||||
AutoLilvNode lv2core__shortName;
|
||||
AutoLilvNode lv2core__index;
|
||||
AutoLilvNode lv2core__Parameter;
|
||||
AutoLilvNode lv2core__minorVersion;
|
||||
AutoLilvNode lv2core__microVersion;
|
||||
AutoLilvNode pipedalUI__ui;
|
||||
AutoLilvNode pipedalUI__fileProperties;
|
||||
AutoLilvNode pipedalUI__directory;
|
||||
@@ -766,6 +825,7 @@ namespace pipedal
|
||||
AutoLilvNode patch__readable;
|
||||
AutoLilvNode pipedal_patch__readable;
|
||||
|
||||
|
||||
AutoLilvNode mod__brand;
|
||||
AutoLilvNode mod__label;
|
||||
AutoLilvNode mod__preferMomentaryOffByDefault;
|
||||
@@ -773,6 +833,7 @@ namespace pipedal
|
||||
AutoLilvNode dc__format;
|
||||
|
||||
AutoLilvNode mod__fileTypes;
|
||||
AutoLilvNode mod__supportedExtensions;
|
||||
AutoLilvNode pipedalui__fileTypes;
|
||||
|
||||
|
||||
@@ -817,6 +878,7 @@ namespace pipedal
|
||||
friend class Lv2PluginInfo;
|
||||
friend class Lv2PortInfo;
|
||||
friend class Lv2PortGroup;
|
||||
friend class ModGui;
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> GetPluginClass(const LilvPluginClass *pClass);
|
||||
std::shared_ptr<Lv2PluginClass> MakePluginClass(const LilvPluginClass *pClass);
|
||||
@@ -924,7 +986,9 @@ namespace pipedal
|
||||
|
||||
class Urids;
|
||||
|
||||
Urids *urids;
|
||||
Urids *urids = nullptr;
|
||||
ModGuiUris* mod_gui_uris = nullptr;
|
||||
|
||||
|
||||
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings);
|
||||
|
||||
@@ -933,7 +997,7 @@ namespace pipedal
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> GetLv2PluginClass() const;
|
||||
|
||||
std::vector<std::shared_ptr<Lv2PluginInfo>> GetPlugins() const { return plugins_; }
|
||||
const std::vector<std::shared_ptr<Lv2PluginInfo>>& GetPlugins() const { return plugins_; }
|
||||
const std::vector<Lv2PluginUiInfo> &GetUiPlugins() const { return ui_plugins_; }
|
||||
|
||||
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const;
|
||||
@@ -941,7 +1005,7 @@ namespace pipedal
|
||||
static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2";
|
||||
|
||||
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
|
||||
void Load(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
|
||||
void LoadLilv(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
|
||||
|
||||
virtual LV2_URID GetLv2Urid(const char *uri)
|
||||
{
|
||||
|
||||
@@ -68,3 +68,4 @@ json_enum_converter<Units> *get_units_enum_converter();
|
||||
|
||||
} // namespace.
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "Ipv6Helpers.hpp"
|
||||
#include "util.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include "ModTemplateGenerator.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include "WebServer.hpp"
|
||||
@@ -741,6 +742,14 @@ namespace pipedal
|
||||
request.set_body_file(bodyFile->Path(),bodyFile->DeleteFile());
|
||||
bodyFile->Detach();
|
||||
}
|
||||
|
||||
|
||||
virtual void clearBody() override {
|
||||
// clear the body, but leave the file size intact (e.g for a HEAD request).
|
||||
request.set_body("");
|
||||
|
||||
// STUB: Don't know how to clear the body file!!
|
||||
}
|
||||
virtual void setBodyFile(std::filesystem::path&path, bool deleteWhenDone) override {
|
||||
// cast away const to do what ther request would do if it had that method.
|
||||
request.set_body_file(path,deleteWhenDone);
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
virtual void setBody(const std::string&body) = 0;
|
||||
virtual void setBodyFile(std::shared_ptr<TemporaryFile>&temporaryFile) = 0;
|
||||
virtual void setBodyFile(std::filesystem::path&path, bool deleteWhenDone) = 0;
|
||||
virtual void clearBody() = 0; // but leave the file size intact (e.g for a HEAD request).
|
||||
|
||||
virtual void keepAlive(bool value) = 0;
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "AudioFiles.hpp"
|
||||
#include "util.hpp"
|
||||
#include "HtmlHelper.hpp"
|
||||
#include "WebServerMod.hpp"
|
||||
|
||||
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||
#define PRESET_EXTENSION ".piPreset"
|
||||
@@ -249,7 +250,7 @@ public:
|
||||
void GetPluginPresets(const uri &request_uri, std::string *pName, std::string *pContent)
|
||||
{
|
||||
std::string pluginUri = request_uri.query("id");
|
||||
auto plugin = model->GetLv2Host().GetPluginInfo(pluginUri);
|
||||
auto plugin = model->GetPluginHost().GetPluginInfo(pluginUri);
|
||||
*pName = plugin->name();
|
||||
|
||||
PluginPresets pluginPresets = model->GetPluginPresets(pluginUri);
|
||||
@@ -1102,4 +1103,7 @@ void pipedal::ConfigureWebServer(
|
||||
|
||||
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
|
||||
server.AddRequestHandler(downloadIntercept);
|
||||
|
||||
std::shared_ptr<ModWebIntercept> modWebIntercept = ModWebIntercept::Create(&model);
|
||||
server.AddRequestHandler(modWebIntercept);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 "WebServerMod.hpp"
|
||||
#include <filesystem>
|
||||
#include "json_variant.hpp"
|
||||
#include "ss.hpp"
|
||||
#include "ModTemplateGenerator.hpp"
|
||||
#include "HtmlHelper.hpp"
|
||||
#include "MimeTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace pipedal::impl
|
||||
{
|
||||
|
||||
class ModWebInterceptImpl : public ModWebIntercept
|
||||
{
|
||||
private:
|
||||
using super = ModWebIntercept;
|
||||
using self = ModWebInterceptImpl;
|
||||
|
||||
PiPedalModel *model;
|
||||
|
||||
public:
|
||||
ModWebInterceptImpl(PiPedalModel *model)
|
||||
: super("/resources"),
|
||||
model(model)
|
||||
{
|
||||
}
|
||||
virtual bool wants(const std::string &method, const uri &request_uri) const override;
|
||||
|
||||
virtual void head_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
HttpResponse &res,
|
||||
std::error_code &ec) override;
|
||||
|
||||
virtual void get_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
HttpResponse &res,
|
||||
std::error_code &ec) override;
|
||||
|
||||
private:
|
||||
std::string GenerateTemplate(
|
||||
const fs::path &templateFile,
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo);
|
||||
};
|
||||
}
|
||||
|
||||
using namespace pipedal::impl;
|
||||
|
||||
ModWebIntercept::ptr ModWebIntercept::Create(PiPedalModel *model)
|
||||
{
|
||||
return std::make_shared<impl::ModWebInterceptImpl>(model);
|
||||
}
|
||||
|
||||
bool ModWebInterceptImpl::wants(const std::string &method, const uri &request_uri) const
|
||||
{
|
||||
if (request_uri.segment_count() < 2 || request_uri.segment(0) != "resources")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModWebInterceptImpl::head_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
HttpResponse &res,
|
||||
std::error_code &ec)
|
||||
{
|
||||
get_response(request_uri, req, res, ec);
|
||||
res.clearBody();
|
||||
}
|
||||
|
||||
static void setCacheControl(HttpResponse &res, const fs::path &path)
|
||||
{
|
||||
if (fs::exists(path))
|
||||
{
|
||||
res.set("Cache-Control", "public, max-age=31536000"); // 1 month
|
||||
auto lastModified = std::filesystem::last_write_time(path);
|
||||
res.set(HttpField::LastModified, HtmlHelper::timeToHttpDate(lastModified));
|
||||
}
|
||||
else
|
||||
{
|
||||
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
}
|
||||
}
|
||||
void ModWebInterceptImpl::get_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
HttpResponse &res,
|
||||
std::error_code &ec)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string ns = request_uri.query("ns");
|
||||
std::string strVersion = request_uri.query("v");
|
||||
(void)strVersion; // this is just cache-busting, so we don't actually use it.
|
||||
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo;
|
||||
if (!ns.empty())
|
||||
{
|
||||
pluginInfo = model->GetPluginInfo(ns);
|
||||
if (!pluginInfo)
|
||||
{
|
||||
throw std::runtime_error("Plugin not found.");
|
||||
}
|
||||
}
|
||||
if (!pluginInfo->modGui())
|
||||
{
|
||||
throw std::runtime_error("Plugin does not have a ModGui.");
|
||||
}
|
||||
if (request_uri.segment_count() < 2)
|
||||
{
|
||||
throw std::runtime_error("Invalid request URI.");
|
||||
}
|
||||
if (request_uri.segment(0) != "resources")
|
||||
{
|
||||
throw std::runtime_error("Invalid request URI: expected 'resources'.");
|
||||
}
|
||||
std::string segment = request_uri.segment(1);
|
||||
if (segment == "_")
|
||||
{
|
||||
segment = request_uri.segment(2);
|
||||
if (segment == "iconTemplate")
|
||||
{
|
||||
res.set("Content-Type", "text/html");
|
||||
res.setBody(GenerateTemplate(
|
||||
pluginInfo->modGui()->iconTemplate(),
|
||||
pluginInfo));
|
||||
setCacheControl(res, pluginInfo->modGui()->iconTemplate());
|
||||
}
|
||||
else if (segment == "stylesheet")
|
||||
{
|
||||
res.set("Content-Type", "text/css");
|
||||
res.setBody(GenerateTemplate(
|
||||
pluginInfo->modGui()->stylesheet(),
|
||||
pluginInfo));
|
||||
setCacheControl(res, pluginInfo->modGui()->stylesheet());
|
||||
|
||||
}
|
||||
|
||||
else if (segment == "screenshot")
|
||||
{
|
||||
std::filesystem::path path = pluginInfo->modGui()->screenshot();
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
ec = std::make_error_code(std::errc::no_such_file_or_directory);
|
||||
return;
|
||||
}
|
||||
if (!fs::is_regular_file(path))
|
||||
{
|
||||
throw std::runtime_error("Screenshot is not a regular file: " + path.string());
|
||||
}
|
||||
|
||||
size_t size = fs::file_size(path);
|
||||
std::string mimeType = MimeTypes::instance().MimeTypeFromExtension(path.extension().string());
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw std::runtime_error("Unknown file type for screenshot: " + path.string());
|
||||
}
|
||||
res.setBodyFile(
|
||||
path,
|
||||
false); // delete when done
|
||||
res.set("Content-Type", mimeType);
|
||||
res.setContentLength(size);
|
||||
setCacheControl(res, path);
|
||||
|
||||
}
|
||||
else if (segment == "thumbnail")
|
||||
{
|
||||
std::filesystem::path path = pluginInfo->modGui()->thumbnail();
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
throw std::runtime_error("Thumbnail not found: " + path.string());
|
||||
}
|
||||
if (!fs::is_regular_file(path))
|
||||
{
|
||||
throw std::runtime_error("Thumbnail is not a regular file: " + path.string());
|
||||
}
|
||||
|
||||
size_t size = fs::file_size(path);
|
||||
std::string mimeType = MimeTypes::instance().MimeTypeFromExtension(path.extension().string());
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw std::runtime_error("Unknown file type for thumbnail: " + path.string());
|
||||
}
|
||||
res.setBodyFile(
|
||||
path,
|
||||
false); // delete when done
|
||||
res.set("Content-Type", mimeType);
|
||||
setCacheControl(res, path);
|
||||
res.setContentLength(size);
|
||||
} else {
|
||||
throw std::runtime_error("Unknown resource: _/" + segment);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// a request for a plugin resource file.
|
||||
fs::path resourcefile = pluginInfo->modGui()->resourceDirectory();
|
||||
for (size_t i = 1; i < request_uri.segment_count(); ++i)
|
||||
{
|
||||
resourcefile /= request_uri.segment(i);
|
||||
}
|
||||
if (!fs::exists(resourcefile) && !fs::is_regular_file(resourcefile))
|
||||
{
|
||||
ec = std::make_error_code(std::errc::no_such_file_or_directory);
|
||||
return;
|
||||
}
|
||||
size_t size = fs::file_size(resourcefile);
|
||||
std::string mimeType = MimeTypes::instance().MimeTypeFromExtension(resourcefile.extension().string());
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw std::runtime_error("Unknown file type for resource: " + resourcefile.string());
|
||||
}
|
||||
res.setBodyFile(
|
||||
resourcefile,
|
||||
false); // delete when done
|
||||
res.set("Content-Type", mimeType);
|
||||
Lv2Log::debug(SS("ModWebIntercept: Serving resource file: " << resourcefile.string() << " (" << size << " bytes)"));
|
||||
Lv2Log::debug(SS(" url: " << request_uri.str()));
|
||||
setCacheControl(res, resourcefile);
|
||||
res.setContentLength(size);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
res.setBody("Error: " + std::string(e.what()));
|
||||
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string makeCns(const std::string &encodedUri, int64_t instanceId)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "_";
|
||||
for (char c : encodedUri)
|
||||
{
|
||||
if (
|
||||
(c >= 'a' && c <= 'z') || (c >= 'A' & c <= 'Z') || (c >= '0' && c <= '9'))
|
||||
{
|
||||
ss << c;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << '_'; // replace non-alphanumeric characters with '_'
|
||||
}
|
||||
}
|
||||
ss << "_";
|
||||
if (instanceId < 0)
|
||||
{
|
||||
ss << "_";
|
||||
instanceId = -instanceId;
|
||||
}
|
||||
ss << instanceId;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ModWebInterceptImpl::GenerateTemplate(
|
||||
const fs::path &templateFile,
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo)
|
||||
{
|
||||
if (!fs::exists(templateFile))
|
||||
{
|
||||
throw std::runtime_error("File not found. " + templateFile.string());
|
||||
}
|
||||
|
||||
json_variant context = MakeModGuiTemplateData(pluginInfo);
|
||||
int64_t version = pluginInfo->minorVersion() * 1000 +
|
||||
pluginInfo->microVersion();
|
||||
|
||||
std::string encodedUri = HtmlHelper::encode_url_segment(pluginInfo->uri(), true);
|
||||
std::string ns = SS("?ns=" << encodedUri << "&v=" << version);
|
||||
context["_ns"] = ns;
|
||||
|
||||
std::string cns = makeCns(encodedUri, version);
|
||||
context["_cns"] = cns;
|
||||
|
||||
return GenerateFromTemplateFile(
|
||||
templateFile,
|
||||
context);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WebServer.hpp"
|
||||
|
||||
#include "PiPedalModel.hpp"
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
class ModWebIntercept : public RequestHandler
|
||||
{
|
||||
|
||||
PiPedalModel *model;
|
||||
protected:
|
||||
ModWebIntercept(const std::string &target_url)
|
||||
: RequestHandler(target_url.c_str())
|
||||
{
|
||||
}
|
||||
public:
|
||||
using self = ModWebIntercept;
|
||||
using super = RequestHandler;
|
||||
using ptr = std::shared_ptr<self>;
|
||||
|
||||
static ptr Create(PiPedalModel *model);
|
||||
|
||||
ModWebIntercept(PiPedalModel *model)
|
||||
: RequestHandler("/var"),
|
||||
model(model)
|
||||
{
|
||||
}
|
||||
virtual ~ModWebIntercept() = default;
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user