Fixes to handling of path properties in presets.

This commit is contained in:
Robin E. R. Davies
2025-09-04 15:20:56 -04:00
parent 0af57da5dd
commit ac194fbbb5
20 changed files with 231 additions and 96 deletions
@@ -87,6 +87,8 @@ namespace pipedal
json_variant(const void *) = delete; // do NOT allow implicit conversion of pointers to bool json_variant(const void *) = delete; // do NOT allow implicit conversion of pointers to bool
static json_variant parse(const std::string&text);
json_variant &operator=(json_variant &&value); json_variant &operator=(json_variant &&value);
json_variant &operator=(const json_variant &value); json_variant &operator=(const json_variant &value);
json_variant &operator=(bool value); json_variant &operator=(bool value);
+9
View File
@@ -570,6 +570,15 @@ json_variant::json_variant(json_reader&reader)
} }
json_variant json_variant::parse(const std::string&jsonText)
{
std::istringstream s(jsonText);
json_reader reader(s);
json_variant result;
reader.read(&result);
return result;
}
/*static*/ json_null json_null::instance; /*static*/ json_null json_null::instance;
/*static*/ int64_t json_array::allocation_count_ = 0; // strictly for testing purposes. not thread safe. /*static*/ int64_t json_array::allocation_count_ = 0; // strictly for testing purposes. not thread safe.
+7
View File
@@ -10,6 +10,13 @@ set(SDBUSCPP_BUILD_TESTS off)
add_subdirectory("sdbus-cpp") add_subdirectory("sdbus-cpp")
option(ENABLE_COMMONCRYPTO "Enable use of CommonCrypto" OFF)
option(ENABLE_GNUTLS "Enable use of GnuTLS" OFF)
option(ENABLE_MBEDTLS "Enable use of mbed TLS" OFF)
option(ENABLE_OPENSSL "Enable use of OpenSSL" OFF)
option(ENABLE_WINDOWS_CRYPTO "Enable use of Windows cryptography libraries" OFF)
option(BUILD_TOOLS "Build tools in the src directory (zipcmp, zipmerge, ziptool)" OFF) option(BUILD_TOOLS "Build tools in the src directory (zipcmp, zipmerge, ziptool)" OFF)
option(BUILD_REGRESS "Build regression tests" OFF) option(BUILD_REGRESS "Build regression tests" OFF)
option(BUILD_OSSFUZZ "Build fuzzers for ossfuzz" OFF) option(BUILD_OSSFUZZ "Build fuzzers for ossfuzz" OFF)
+19 -14
View File
@@ -26,6 +26,7 @@
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <sstream> #include <sstream>
#include <filesystem>
#include "json.hpp" #include "json.hpp"
#include "ss.hpp" #include "ss.hpp"
#include "lv2/atom/util.h" #include "lv2/atom/util.h"
@@ -90,20 +91,21 @@ std::string AtomConverter::ToString(const LV2_Atom*atom)
} }
json_variant AtomConverter::MapPath(const json_variant&json, const std::string &pluginStoragePath) json_variant AtomConverter::MapPath(const json_variant&json, const std::string &pluginStoragePath)
{ {
std::string oType = json[OTYPE_TAG].as_string(); if (json.is_null())
if (oType == SHORT_ATOM__Path)
{ {
std::string path = json["value"].as_string().c_str(); return EmptyPath();
if (path.length() == 0 || path[0] == '/') }
{ if (json.is_object() && json[OTYPE_TAG].as_string() == SHORT_ATOM__Path)
return json; {
std::filesystem::path path = json["value"].as_string();
if (path.empty()) {
return EmptyPath();
} }
std::string newPath = SS(pluginStoragePath << "/" << path); if (path.is_relative())
json_variant result = json_variant::make_object(); {
result[OTYPE_TAG] = json_variant(SHORT_ATOM__Path); path = pluginStoragePath / path;
result["value"] = newPath; }
return TypedProperty(SHORT_ATOM__Path,path.string());
return result;
} }
else { else {
return json; return json;
@@ -111,8 +113,7 @@ json_variant AtomConverter::MapPath(const json_variant&json, const std::string &
} }
json_variant AtomConverter::AbstractPath(const json_variant&json, const std::string &pluginStoragePath) json_variant AtomConverter::AbstractPath(const json_variant&json, const std::string &pluginStoragePath)
{ {
std::string oType = json[OTYPE_TAG].as_string(); if (json.is_object() && json[OTYPE_TAG].as_string() == SHORT_ATOM__Path)
if (oType == SHORT_ATOM__Path)
{ {
std::string path = json["value"].as_string().c_str(); std::string path = json["value"].as_string().c_str();
if (path.length() == 0) if (path.length() == 0)
@@ -592,6 +593,10 @@ static bool gEmptyPathInitialized = false;
static std::mutex gInitMutex; static std::mutex gInitMutex;
std::string gEmptyPathstring; std::string gEmptyPathstring;
json_variant AtomConverter::gEmptyPath = TypedProperty(SHORT_ATOM__Path,std::string(""));
std::string AtomConverter::EmptyPathstring() std::string AtomConverter::EmptyPathstring()
{ {
std::lock_guard lock{gInitMutex}; std::lock_guard lock{gInitMutex};
+6 -2
View File
@@ -153,17 +153,21 @@ namespace pipedal
std::string ToString(const LV2_Atom*atom); std::string ToString(const LV2_Atom*atom);
json_variant MapPath(const json_variant&json, const std::string &pluginStoragePath); static json_variant MapPath(const json_variant&json, const std::string &pluginStoragePath);
json_variant AbstractPath(const json_variant&json, const std::string &pluginStoragePath); static json_variant AbstractPath(const json_variant&json, const std::string &pluginStoragePath);
static std::string EmptyPathstring(); static std::string EmptyPathstring();
static const json_variant& EmptyPath() { return gEmptyPath; }
private: private:
static const std::string OTYPE_TAG; static const std::string OTYPE_TAG;
static const std::string VTYPE_TAG; static const std::string VTYPE_TAG;
static const std::string ID_TAG; static const std::string ID_TAG;
static json_variant gEmptyPath;
std::map<std::string, LV2_URID> stringToTypeUrid; std::map<std::string, LV2_URID> stringToTypeUrid;
std::map<LV2_URID,std::string> typeUridToString; std::map<LV2_URID,std::string> typeUridToString;
+1
View File
@@ -56,6 +56,7 @@ namespace pipedal
int64_t clientId; int64_t clientId;
int64_t instanceId; int64_t instanceId;
LV2_URID uridUri; LV2_URID uridUri;
std::string uri;
enum class RequestType enum class RequestType
{ {
+10 -7
View File
@@ -30,7 +30,7 @@ using namespace pipedal;
using namespace std; using namespace std;
using namespace std::filesystem; using namespace std::filesystem;
Storage storage; std::unique_ptr<Storage> storage;
static std::string GetPluginid(const std::string &pluginUrl) static std::string GetPluginid(const std::string &pluginUrl)
{ {
@@ -136,7 +136,7 @@ void WritePrefixes(ofstream &f)
static void WriteSeeAlso(ofstream &f, const std::string &uri) static void WriteSeeAlso(ofstream &f, const std::string &uri)
{ {
std::string id = GetPluginid(uri); std::string id = GetPluginid(uri);
PluginPresets presets = storage.GetPluginPresets(uri); PluginPresets presets = storage->GetPluginPresets(uri);
f << "###" << endl; f << "###" << endl;
@@ -197,7 +197,7 @@ void WritePresetFile(const path &outputDirectory, const std::string &uri)
WritePrefixes(f); WritePrefixes(f);
PluginPresets presets = storage.GetPluginPresets(uri); PluginPresets presets = storage->GetPluginPresets(uri);
for (size_t i = 0; i < presets.presets_.size(); ++i) for (size_t i = 0; i < presets.presets_.size(); ++i)
{ {
@@ -273,6 +273,9 @@ void WritePresetFile(const path &outputDirectory, const std::string &uri)
int main(int argc, const char *argv[]) int main(int argc, const char *argv[])
{ {
storage = std::make_unique<Storage>();
// A utility for the ToobAmp project which converts Pipedal presets to LV2 presets so // A utility for the ToobAmp project which converts Pipedal presets to LV2 presets so
// that they can be imported as pre-declared presets respective TTL files. // that they can be imported as pre-declared presets respective TTL files.
@@ -284,11 +287,11 @@ int main(int argc, const char *argv[])
try try
{ {
storage.SetConfigRoot("/etc/pipedal/config"); storage->SetConfigRoot("/etc/pipedal/config");
storage.SetDataRoot("/var/pipedal"); storage->SetDataRoot("/var/pipedal");
storage.Initialize(); storage->Initialize();
const PluginPresetIndex &index = storage.GetPluginPresetIndex(); const PluginPresetIndex &index = storage->GetPluginPresetIndex();
std::filesystem::create_directories(outputPath); std::filesystem::create_directories(outputPath);
+1 -1
View File
@@ -49,7 +49,7 @@ static std::vector<ModFileTypes::ModDirectory> *CreateModDirectories()
{"sfz", "shared/sfz", "Sfz Files", {".sfz"}}, // SFZ Instruments, must have sfz file extension {"sfz", "shared/sfz", "Sfz Files", {".sfz"}}, // SFZ Instruments, must have sfz file extension
// extensions observed in the field. // extensions observed in the field.
{"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatouille) {"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatouille)
{"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam"}}, // Ratatouille, Mike's NAM. {"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam" ,".aidax"}}, // Ratatouille, Mike's NAM.
{"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatouille {"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatouille
{"mlmodel", "ToobMlModels", "ML Models", {".json"}}, // {"mlmodel", "ToobMlModels", "ML Models", {".json"}}, //
+3
View File
@@ -125,6 +125,7 @@ public:
GETTER_SETTER_VEC(bottomChain) GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings) GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(midiChannelBinding) GETTER_SETTER_REF(midiChannelBinding)
GETTER_SETTER_REF(pathProperties)
GETTER_SETTER(stateUpdateCount) GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State) GETTER_SETTER_REF(lv2State)
GETTER_SETTER_REF(title) GETTER_SETTER_REF(title)
@@ -134,6 +135,8 @@ public:
GETTER_SETTER_REF(lilvPresetUri) GETTER_SETTER_REF(lilvPresetUri)
PropertyMap&PatchProperties() { return patchProperties; } PropertyMap&PatchProperties() { return patchProperties; }
const PropertyMap&PatchProperties() const { return patchProperties; }
bool isSplit() const bool isSplit() const
{ {
+54 -19
View File
@@ -1721,23 +1721,20 @@ void PiPedalModel::SendSetPatchProperty(
// save the property to the preset (currently used to reconstruct snapshots only) // save the property to the preset (currently used to reconstruct snapshots only)
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId); PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId);
if (pedalboardItem && value.is_string()) if (pedalboardItem)
{ {
std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pedalboardItem->uri_); std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pedalboardItem->uri_);
auto pipedalUi = pluginInfo->piPedalUI(); auto pipedalUi = pluginInfo->piPedalUI();
auto fileProperty = pipedalUi->GetFileProperty(propertyUri); auto fileProperty = pipedalUi->GetFileProperty(propertyUri);
if (fileProperty && value.is_string()) if (fileProperty)
{ {
json_variant abstractPath = pluginHost.AbstractPath(value); json_variant abstractPath = pluginHost.AbstractPath(value);
std::ostringstream ss; std::string atomString = abstractPath.to_string();
json_writer writer(ss);
writer.write(abstractPath);
std::string atomString = ss.str();
pedalboardItem->pathProperties_[propertyUri] = atomString; pedalboardItem->pathProperties_[propertyUri] = atomString;
} }
this->SetPresetChanged(clientId, true); this->SetPresetChanged(clientId, true);
} }
LV2_Atom *atomValue = atomConverter.ToAtom(value); LV2_Atom *atomValue = atomConverter.ToAtom(value);
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{ std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
@@ -1827,7 +1824,24 @@ void PiPedalModel::SendGetPatchProperty(
{ {
if (pParameter->onError) if (pParameter->onError)
{ {
pParameter->onError("No response."); // For plugins that don't respond (e.g. a buncha MOD plugins), use the value we last set on the plugin!
bool foundValue = false;
std::lock_guard<std::recursive_mutex> lock(mutex);
auto pedalboardItem = this->pedalboard.GetItem(pParameter->instanceId);
if (pedalboardItem) {
if (pedalboardItem->pathProperties_.contains(pParameter->uri))
{
pParameter->jsonResponse = pedalboardItem->pathProperties_[pParameter->uri];
if (pParameter->onSuccess) {
foundValue = true;
pParameter->onSuccess(pParameter->jsonResponse);
}
}
}
if (!foundValue)
{
pParameter->onError("No response.");
}
} }
} }
else else
@@ -1854,6 +1868,7 @@ void PiPedalModel::SendGetPatchProperty(
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest( RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
onRequestComplete, onRequestComplete,
clientId, instanceId, urid, onSuccess, onError, sampleTimeout); clientId, instanceId, urid, onSuccess, onError, sampleTimeout);
request->uri = uri;
outstandingParameterRequests.push_back(request); outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request); this->audioHost->sendRealtimeParameterRequest(request);
@@ -2058,14 +2073,24 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
if (pPlugin->piPedalUI()) if (pPlugin->piPedalUI())
{ {
PiPedalUI::ptr piPedalUI = pPlugin->piPedalUI(); PiPedalUI::ptr piPedalUI = pPlugin->piPedalUI();
std::set<std::string> validFileProperties;
for (auto &fileProperty : piPedalUI->fileProperties()) for (auto &fileProperty : piPedalUI->fileProperties())
{ {
validFileProperties.insert(fileProperty->patchProperty());
if (!pedalboardItem->pathProperties_.contains(fileProperty->patchProperty())) if (!pedalboardItem->pathProperties_.contains(fileProperty->patchProperty()))
{ {
// make sure each pedalboard item has a complete list of path properties, even if it doesn't yet have values. // make sure each pedalboard item has a complete list of path properties, even if it doesn't yet have values.
pedalboardItem->pathProperties_[fileProperty->patchProperty()] = "null"; pedalboardItem->pathProperties_[fileProperty->patchProperty()] = "null";
} }
} }
for (auto i = pedalboardItem->pathProperties_.begin(); i != pedalboardItem->pathProperties_.end(); /**/)
{
if (!validFileProperties.contains(i->first)) {
i = pedalboardItem->pathProperties_.erase(i);
} else {
++i;
}
}
} }
} }
else else
@@ -2162,8 +2187,9 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
this->pedalboard.SetControlValue(pluginInstanceId, control.key(), control.value()); this->pedalboard.SetControlValue(pluginInstanceId, control.key(), control.value());
} }
if ((!presetValues.state.isValid_) && presetValues.lilvPresetUri.empty()) if ((!presetValues.state.isValid_) && presetValues.lilvPresetUri.empty() && presetValues.pathProperties.empty())
{ {
// fast path for control changes only.
audioHost->SetPluginPreset(pluginInstanceId, presetValues.controls); audioHost->SetPluginPreset(pluginInstanceId, presetValues.controls);
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()}; std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
@@ -2177,6 +2203,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
pedalboardItem->lv2State(presetValues.state); pedalboardItem->lv2State(presetValues.state);
pedalboardItem->lilvPresetUri(presetValues.lilvPresetUri); pedalboardItem->lilvPresetUri(presetValues.lilvPresetUri);
pedalboardItem->stateUpdateCount(oldStateUpdateCount + 1); pedalboardItem->stateUpdateCount(oldStateUpdateCount + 1);
pedalboardItem->pathProperties(presetValues.pathProperties);
FirePedalboardChanged(-1); // does a complete reload of both client and audio server. FirePedalboardChanged(-1); // does a complete reload of both client and audio server.
} }
this->SetPresetChanged(-1, true); this->SetPresetChanged(-1, true);
@@ -2297,7 +2324,8 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri); auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
if (i != pedalboardItem->pathProperties_.end()) if (i != pedalboardItem->pathProperties_.end())
{ {
pedalboardItem->pathProperties_[pathPatchPropertyUri] = atomString; std::string abstractAtomString = storage.ToAbstractPathJson(atomString);
pedalboardItem->pathProperties_[pathPatchPropertyUri] = abstractAtomString;
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()}; std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t) for (auto &subscriber : t)
@@ -2305,7 +2333,7 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
subscriber->OnNotifyPathPatchPropertyChanged( subscriber->OnNotifyPathPatchPropertyChanged(
instanceId, instanceId,
pathPatchPropertyUri, pathPatchPropertyUri,
atomString); abstractAtomString);
} }
} }
} }
@@ -2382,16 +2410,23 @@ void PiPedalModel::MonitorPatchProperty(int64_t clientId, int64_t clientHandle,
PedalboardItem *item = this->pedalboard.GetItem(instanceId); PedalboardItem *item = this->pedalboard.GetItem(instanceId);
if (item) if (item)
{ {
auto &map = item->PatchProperties(); auto &map = item->pathProperties();
if (map.contains(propertyUri)) if (map.contains(propertyUri))
{ {
const auto &value = map[propertyUri]; const auto &value = map.at(propertyUri);
std::string json = this->audioHost->AtomToJson(value.get()); if (value != "null") {
for (auto &subscriber : this->subscribers) try {
{ std::string json = storage.FromAbstractPathJson(value);
if (subscriber->GetClientId() == clientId)
{ for (auto &subscriber : this->subscribers)
subscriber->OnNotifyPatchProperty(clientHandle, instanceId, propertyUri, json); {
if (subscriber->GetClientId() == clientId)
{
subscriber->OnNotifyPatchProperty(clientHandle, instanceId, propertyUri, json);
}
}
} catch (const std::exception& ignored) {
} }
} }
} }
+2 -1
View File
@@ -279,7 +279,8 @@ namespace pipedal
Decrease Decrease
}; };
bool GetHasWifi(); Storage&GetStorage() { return storage; }
bool GetHasWifi();
void NextBank(Direction direction = Direction::Increase); void NextBank(Direction direction = Direction::Increase);
void PreviousBank() { NextBank(Direction::Decrease); } void PreviousBank() { NextBank(Direction::Decrease); }
+26 -17
View File
@@ -49,7 +49,7 @@
#include "StdErrorCapture.hpp" #include "StdErrorCapture.hpp"
#include "util.hpp" #include "util.hpp"
#include "ModFileTypes.hpp" #include "ModFileTypes.hpp"
#include <algorithm> #include <algorithm>
#include "Locale.hpp" #include "Locale.hpp"
@@ -304,7 +304,6 @@ PluginHost::PluginHost()
lv2Features.push_back(nullptr); lv2Features.push_back(nullptr);
this->urids = new Urids(mapFeature); this->urids = new Urids(mapFeature);
} }
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings) void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
@@ -660,13 +659,14 @@ static std::vector<UiFileType> ToPiPedalFileTypes(
std::string type = fileType; std::string type = fileType;
if (type.find("/") != std::string::npos) // mime type? if (type.find("/") != std::string::npos) // mime type?
{ {
UiFileType t = UiFileType(type,type,""); UiFileType t = UiFileType(type, type, "");
result.push_back(t); result.push_back(t);
} }
else else
{ {
// add a leading dot. // add a leading dot.
if (!type.starts_with(".")) { if (!type.starts_with("."))
{
type = "." + type; type = "." + type;
} }
auto t = UiFileType(SS(type << " file"), type); auto t = UiFileType(SS(type << " file"), type);
@@ -776,7 +776,8 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
auto modDirectory = modFileTypes.GetModDirectory(modName); auto modDirectory = modFileTypes.GetModDirectory(modName);
fileProperty->directory(modDirectory->pipedalPath); fileProperty->directory(modDirectory->pipedalPath);
std::set<std::string> fileExtensions = modDirectory->fileExtensions; std::set<std::string> fileExtensions = modDirectory->fileExtensions;
if (!modFileExtensions.empty()) { if (!modFileExtensions.empty())
{
auto extensions = split(modFileExtensions, ','); auto extensions = split(modFileExtensions, ',');
fileExtensions = std::set<std::string>(extensions.begin(), extensions.end()); fileExtensions = std::set<std::string>(extensions.begin(), extensions.end());
} }
@@ -879,7 +880,6 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
this->plugin_class_ = "http://lv2plug.in/ns/lv2core#Plugin"; this->plugin_class_ = "http://lv2plug.in/ns/lv2core#Plugin";
} }
AutoLilvNodes required_features = lilv_plugin_get_required_features(pPlugin); AutoLilvNodes required_features = lilv_plugin_get_required_features(pPlugin);
this->required_features_ = nodeAsStringArray(required_features); this->required_features_ = nodeAsStringArray(required_features);
@@ -1236,18 +1236,19 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
if (unitsValueUri) if (unitsValueUri)
{ {
this->units_ = UriToUnits(nodeAsString(unitsValueUri)); this->units_ = UriToUnits(nodeAsString(unitsValueUri));
if (this->units_ == Units::unknown) { if (this->units_ == Units::unknown)
{
// try for a custom format string. // try for a custom format string.
AutoLilvNode render = lilv_world_get( AutoLilvNode render = lilv_world_get(
pWorld, pWorld,
unitsValueUri, unitsValueUri,
host->lilvUris->units__render, host->lilvUris->units__render,
nullptr); nullptr);
if (render) { if (render)
const char*strRender = lilv_node_as_string(render); {
const char *strRender = lilv_node_as_string(render);
custom_units_ = strRender; custom_units_ = strRender;
} }
} }
} }
else else
@@ -1630,7 +1631,13 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
{ {
if (!cbData.failed) if (!cbData.failed)
{ {
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, strLabel, controlValues, Lv2PluginState())); result.presets_.push_back(
PluginPreset(
result.nextInstanceId_++,
strLabel,
controlValues,
std::map<std::string, std::string>(),
Lv2PluginState()));
} }
} }
else else
@@ -1904,11 +1911,13 @@ Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNod
} }
// ffs. // ffs.
static inline bool contains(const std::vector<std::string> &vec, const std::string &value) { static inline bool contains(const std::vector<std::string> &vec, const std::string &value)
return std::find(vec.begin(),vec.end(),value) != vec.end(); {
return std::find(vec.begin(), vec.end(), value) != vec.end();
} }
bool Lv2PluginInfo::WantsWorkerThread() const { bool Lv2PluginInfo::WantsWorkerThread() const
return contains(this->required_features_,LV2_WORKER__schedule) || contains(this->supported_features_,LV2_WORKER__schedule); {
return contains(this->required_features_, LV2_WORKER__schedule) || contains(this->supported_features_, LV2_WORKER__schedule);
} }
// void PiPedalHostLogError(const std::string &error) // void PiPedalHostLogError(const std::string &error)
// { // {
+1
View File
@@ -98,6 +98,7 @@ JSON_MAP_BEGIN(PluginPreset)
JSON_MAP_REFERENCE(PluginPreset,instanceId) JSON_MAP_REFERENCE(PluginPreset,instanceId)
JSON_MAP_REFERENCE(PluginPreset,label) JSON_MAP_REFERENCE(PluginPreset,label)
JSON_MAP_REFERENCE(PluginPreset,controlValues) JSON_MAP_REFERENCE(PluginPreset,controlValues)
JSON_MAP_REFERENCE(PluginPreset,pathProperties)
JSON_MAP_REFERENCE(PluginPreset,state) JSON_MAP_REFERENCE(PluginPreset,state)
JSON_MAP_REFERENCE(PluginPreset,lilvPresetUri) JSON_MAP_REFERENCE(PluginPreset,lilvPresetUri)
JSON_MAP_END() JSON_MAP_END()
+5
View File
@@ -76,6 +76,7 @@ public:
PluginPreset(uint64_t instanceId, const std::string&label,const PedalboardItem&pedalboardItem) PluginPreset(uint64_t instanceId, const std::string&label,const PedalboardItem&pedalboardItem)
: instanceId_(instanceId) : instanceId_(instanceId)
, label_(label) , label_(label)
, pathProperties_(pedalboardItem.pathProperties())
, state_(pedalboardItem.lv2State()) , state_(pedalboardItem.lv2State())
{ {
for (auto & controlValue : pedalboardItem.controlValues()) for (auto & controlValue : pedalboardItem.controlValues())
@@ -89,10 +90,12 @@ public:
const std::string&label, const std::string&label,
const std::map<std::string, const std::map<std::string,
float> & controlValues, float> & controlValues,
const std::map<std::string,std::string>&pathProperties,
const Lv2PluginState &state) const Lv2PluginState &state)
: instanceId_(instanceId), : instanceId_(instanceId),
label_(label), label_(label),
controlValues_((controlValues)), controlValues_((controlValues)),
pathProperties_(pathProperties),
state_(state) state_(state)
{ {
} }
@@ -117,7 +120,9 @@ public:
std::string label_; std::string label_;
std::string lilvPresetUri_; std::string lilvPresetUri_;
std::map<std::string,float> controlValues_; std::map<std::string,float> controlValues_;
std::map<std::string,std::string> pathProperties_;
Lv2PluginState state_; Lv2PluginState state_;
DECLARE_JSON_MAP(PluginPreset); DECLARE_JSON_MAP(PluginPreset);
}; };
+64 -10
View File
@@ -40,6 +40,7 @@
#include "util.hpp" #include "util.hpp"
#include "AudioFiles.hpp" #include "AudioFiles.hpp"
#include "Utf8Utils.hpp" #include "Utf8Utils.hpp"
#include "AtomConverter.hpp"
using namespace pipedal; using namespace pipedal;
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -189,6 +190,7 @@ void Storage::SetConfigRoot(const std::filesystem::path &path)
void Storage::SetDataRoot(const std::filesystem::path &path) void Storage::SetDataRoot(const std::filesystem::path &path)
{ {
this->dataRoot = ResolveHomePath(path); this->dataRoot = ResolveHomePath(path);
this->dataRoot__audio_uploads = dataRoot / "audio_uploads";
} }
const std::filesystem::path &Storage::GetConfigRoot() const std::filesystem::path &Storage::GetConfigRoot()
@@ -302,9 +304,9 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{ {
return this->dataRoot / "plugin_presets"; return this->dataRoot / "plugin_presets";
} }
std::filesystem::path Storage::GetPluginUploadDirectory() const const std::filesystem::path &Storage::GetPluginUploadDirectory() const
{ {
return this->dataRoot / "audio_uploads"; return dataRoot__audio_uploads;
} }
std::filesystem::path Storage::GetCurrentPresetPath() const std::filesystem::path Storage::GetCurrentPresetPath() const
@@ -1409,6 +1411,10 @@ PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri,
{ {
result.controls.push_back(ControlValue(valuePair.first.c_str(), valuePair.second)); result.controls.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
} }
for (const auto&pair: preset.pathProperties_)
{
result.pathProperties[pair.first] = pair.second;
}
result.state = preset.state_; result.state = preset.state_;
result.lilvPresetUri = preset.lilvPresetUri_; result.lilvPresetUri = preset.lilvPresetUri_;
return result; return result;
@@ -1417,6 +1423,31 @@ PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri,
throw PiPedalException("Plugin preset not found."); throw PiPedalException("Plugin preset not found.");
} }
void Storage::ToAbstractPaths(PluginPreset &pluginPreset)
{
if (pluginPreset.pathProperties_.size() != 0)
{
std::map<std::string, std::string> newPathProperties;
for (const auto &pair : pluginPreset.pathProperties_)
{
newPathProperties[pair.first] = this->ToAbstractPathJson(pair.second);
}
pluginPreset.pathProperties_ = newPathProperties;
}
}
void Storage::FromAbstractPaths(PluginPreset &pluginPreset)
{
if (pluginPreset.pathProperties_.size() != 0)
{
std::map<std::string, std::string> newPathProperties;
for (const auto &pair : pluginPreset.pathProperties_)
{
newPathProperties[pair.first] = this->FromAbstractPathJson(pair.second);
}
pluginPreset.pathProperties_ = newPathProperties;
}
}
uint64_t Storage::SavePluginPreset( uint64_t Storage::SavePluginPreset(
const std::string &name, const std::string &name,
const PedalboardItem &pedalboardItem) const PedalboardItem &pedalboardItem)
@@ -1425,6 +1456,7 @@ uint64_t Storage::SavePluginPreset(
auto presets = GetPluginPresets(pluginUri); auto presets = GetPluginPresets(pluginUri);
uint64_t result = -1; uint64_t result = -1;
bool existing = false; bool existing = false;
for (size_t i = 0; i < presets.presets_.size(); ++i) for (size_t i = 0; i < presets.presets_.size(); ++i)
{ {
auto &preset = presets.presets_[i]; auto &preset = presets.presets_[i];
@@ -1432,6 +1464,8 @@ uint64_t Storage::SavePluginPreset(
{ {
existing = true; existing = true;
result = preset.instanceId_; result = preset.instanceId_;
PluginPreset pluginPreset{preset.instanceId_, preset.label_, pedalboardItem};
presets.presets_[i] = PluginPreset(preset.instanceId_, preset.label_, pedalboardItem); presets.presets_[i] = PluginPreset(preset.instanceId_, preset.label_, pedalboardItem);
break; break;
} }
@@ -1439,10 +1473,12 @@ uint64_t Storage::SavePluginPreset(
if (!existing) if (!existing)
{ {
result = presets.nextInstanceId_++; result = presets.nextInstanceId_++;
presets.presets_.push_back( auto preset = PluginPreset(
PluginPreset(result, result,
name, name,
pedalboardItem)); pedalboardItem);
ToAbstractPaths(preset);
presets.presets_.push_back(preset);
} }
this->SavePluginPresets(pluginUri, presets); this->SavePluginPresets(pluginUri, presets);
return result; return result;
@@ -1451,9 +1487,8 @@ uint64_t Storage::SavePluginPreset(
uint64_t Storage::SavePluginPreset( uint64_t Storage::SavePluginPreset(
const std::string &pluginUri, const std::string &pluginUri,
const std::string &name, const std::string &name,
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values, const std::map<std::string, float> &values,
const std::map<std::string,std::string> &pathProperties,
const Lv2PluginState &lv2State) const Lv2PluginState &lv2State)
{ {
auto presets = GetPluginPresets(pluginUri); auto presets = GetPluginPresets(pluginUri);
@@ -1466,6 +1501,7 @@ uint64_t Storage::SavePluginPreset(
{ {
existing = true; existing = true;
preset.controlValues_ = values; preset.controlValues_ = values;
preset.pathProperties_ = pathProperties;
preset.state_ = lv2State; preset.state_ = lv2State;
result = preset.instanceId_; result = preset.instanceId_;
@@ -1480,6 +1516,7 @@ uint64_t Storage::SavePluginPreset(
result, result,
name, name,
values, values,
pathProperties,
lv2State)); lv2State));
} }
this->SavePluginPresets(pluginUri, presets); this->SavePluginPresets(pluginUri, presets);
@@ -1533,7 +1570,7 @@ uint64_t Storage::CopyPluginPreset(const std::string &pluginUri, uint64_t preset
size_t pos = presets.Find(presetId); size_t pos = presets.Find(presetId);
if (pos == -1) if (pos == -1)
{ {
throw PiPedalException("Perset not found."); throw PiPedalException("Preset not found.");
} }
PluginPreset t = presets.presets_[pos]; PluginPreset t = presets.presets_[pos];
t.instanceId_ = presets.nextInstanceId_++; t.instanceId_ = presets.nextInstanceId_++;
@@ -1922,7 +1959,7 @@ static void AddTracksToResult(
FileRequestResult Storage::GetModFileList2(const std::string &relativePath, const UiFileProperty &fileProperty) FileRequestResult Storage::GetModFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
{ {
FileRequestResult result; FileRequestResult result;
fs::path uploadsDirectory = GetPluginUploadDirectory(); const fs::path &uploadsDirectory = GetPluginUploadDirectory();
if (relativePath.empty()) if (relativePath.empty())
{ {
@@ -2651,6 +2688,23 @@ std::string Storage::GetTone3000Auth() const
return tone3000Auth; return tone3000Auth;
} }
std::string Storage::ToAbstractPathJson(const std::string &pathJson)
{
json_variant v = json_variant::parse(pathJson);
v = AtomConverter::AbstractPath(v,GetPluginUploadDirectory().string());
return v.to_string();
}
std::string Storage::FromAbstractPathJson(const std::string &pathJson)
{
json_variant v = json_variant::parse(pathJson);
v = AtomConverter::MapPath(v,GetPluginUploadDirectory().string());
return v.to_string();
}
JSON_MAP_BEGIN(UserSettings) JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor) JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+13 -4
View File
@@ -60,15 +60,19 @@ public:
struct PluginPresetValues { struct PluginPresetValues {
std::vector<ControlValue> controls; std::vector<ControlValue> controls;
std::map<std::string,std::string> pathProperties;
Lv2PluginState state; Lv2PluginState state;
std::string lilvPresetUri; std::string lilvPresetUri;
}; };
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps) // controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
class Storage { class Storage {
private: private:
std::filesystem::path dataRoot; std::filesystem::path dataRoot;
std::filesystem::path dataRoot__audio_uploads;
std::filesystem::path configRoot; std::filesystem::path configRoot;
BankIndex bankIndex; BankIndex bankIndex;
BankFile currentBank; BankFile currentBank;
@@ -125,7 +129,11 @@ public:
const std::filesystem::path&GetConfigRoot(); const std::filesystem::path&GetConfigRoot();
const std::filesystem::path&GetDataRoot(); const std::filesystem::path&GetDataRoot();
std::filesystem::path GetPluginUploadDirectory() const; const std::filesystem::path &GetPluginUploadDirectory() const;
std::string ToAbstractPathJson(const std::string &jsonAtomPath);
std::string FromAbstractPathJson(const std::string &jsonAtomPath);
//std::vector<std::string> GetPedalboards(); //std::vector<std::string> GetPedalboards();
@@ -208,6 +216,8 @@ private:
std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const; std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const;
bool IsValidSampleFileName(const std::filesystem::path&fileName); bool IsValidSampleFileName(const std::filesystem::path&fileName);
std::filesystem::path MakeUserFilePath(const std::string &directory, const std::string&filename); std::filesystem::path MakeUserFilePath(const std::string &directory, const std::string&filename);
void ToAbstractPaths(PluginPreset&pluginPreset);
void FromAbstractPaths(PluginPreset&pluginPreset);
public: public:
bool HasPluginPresets(const std::string&pluginUri) const; bool HasPluginPresets(const std::string&pluginUri) const;
@@ -227,9 +237,8 @@ public:
uint64_t SavePluginPreset( uint64_t SavePluginPreset(
const std::string &pluginUri, const std::string &pluginUri,
const std::string &name, const std::string &name,
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values, const std::map<std::string, float> &values,
const std::map<std::string,std::string>&pathProperties,
const Lv2PluginState& lv2State); const Lv2PluginState& lv2State);
void UpdatePluginPresets(const PluginUiPresets &pluginPresets); void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
+1 -1
View File
@@ -494,7 +494,7 @@ public:
res.set(HttpField::content_type, "application/json"); res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
fs::path path = request_uri.query("path"); fs::path path = model->GetStorage().FromAbstractPathJson(request_uri.query("path"));
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path)) if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
{ {
+4 -18
View File
@@ -1,26 +1,12 @@
ffmpeg -ss <start_time> -i input.mp4 -filter_complex "loop=loop=<num_loops>:size=<frame_count>:start=<start_frame>" output.mp4
-ss <start_time>: Seeks to the desired start point (e.g., 00:00:10 or 10).
loop=<num_loops>: Number of times to loop the segment (e.g., 3 for 4 total plays).
size=<frame_count>: The number of frames in the segment to loop.
start=<start_frame>: The starting frame of the segment to loop within the input.
Note: This method requires re-encoding, as filters are applied.
3. Looping a single image:
To loop a single image for a specific duration, use the -loop option with the image demuxer:
Code
Use ffmpeg
Check ToOB Cab SIM F/R range.
Convert CRVB presets!
double select on double-click/ok in file dialog. double select on double-click/ok in file dialog.
sudo apt install libbz2-dev sudo apt install libbz2-dev
Move status to pedalboard view.
Default to dark theme. README, license files in file browser.
ls
Carla Project icons. Carla Project icons.
check reload after change of LV2 plugins. check reload after change of LV2 plugins.
+1 -1
View File
@@ -42,7 +42,7 @@ export const modDirectories: ModDirectory[] = [
{modType: "sfz", pipedalPath: "shared/sfz",displayName: "Sfz Files", defaultFileExtensions: [".sfz"]}, // SFZ Instruments, must have sfz file extension {modType: "sfz", pipedalPath: "shared/sfz",displayName: "Sfz Files", defaultFileExtensions: [".sfz"]}, // SFZ Instruments, must have sfz file extension
// extensions observed in the field. // extensions observed in the field.
{modType: "audio", pipedalPath: "shared/audio", displayName: "Audio", defaultFileExtensions: ["audio/*"]}, // all audio files (Ratatoille) {modType: "audio", pipedalPath: "shared/audio", displayName: "Audio", defaultFileExtensions: ["audio/*"]}, // all audio files (Ratatoille)
{modType: "nammodel", pipedalPath: "NeuralAmpModels", displayName: "Neural Amp Models", defaultFileExtensions: [".nam"]}, // Ratatoille, Mike's NAM. {modType: "nammodel", pipedalPath: "NeuralAmpModels", displayName: "Neural Amp Models", defaultFileExtensions: [".nam","aidax"]}, // Ratatoille, Mike's NAM.
{modType: "aidadspmodel", pipedalPath: "shared/aidaaix", displayName: "AIDA IAX Models", defaultFileExtensions: [".json", ".aidaiax"]}, // Ratatoille {modType: "aidadspmodel", pipedalPath: "shared/aidaaix", displayName: "AIDA IAX Models", defaultFileExtensions: [".json", ".aidaiax"]}, // Ratatoille
{modType: "mlmodel", pipedalPath: "ToobMlModels", displayName: "ML Models", defaultFileExtensions: [".json"]}, // {modType: "mlmodel", pipedalPath: "ToobMlModels", displayName: "ML Models", defaultFileExtensions: [".json"]}, //
+2 -1
View File
@@ -2623,7 +2623,8 @@ export class PiPedalModel //implements PiPedalModel
let pedalboard = this.pedalboard.get(); let pedalboard = this.pedalboard.get();
try { try {
let pedalboardItem = pedalboard.tryGetItem(instanceId); let pedalboardItem = pedalboard.tryGetItem(instanceId);
if (pedalboardItem) {
if (pedalboardItem && pedalboardItem.pathProperties[propertyUri] !== undefined) {
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject); pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
} }
} catch (ignored) { } catch (ignored) {