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
static json_variant parse(const std::string&text);
json_variant &operator=(json_variant &&value);
json_variant &operator=(const json_variant &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*/ 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")
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_REGRESS "Build regression tests" OFF)
option(BUILD_OSSFUZZ "Build fuzzers for ossfuzz" OFF)
+19 -14
View File
@@ -26,6 +26,7 @@
#include <cstddef>
#include <cstring>
#include <sstream>
#include <filesystem>
#include "json.hpp"
#include "ss.hpp"
#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)
{
std::string oType = json[OTYPE_TAG].as_string();
if (oType == SHORT_ATOM__Path)
if (json.is_null())
{
std::string path = json["value"].as_string().c_str();
if (path.length() == 0 || path[0] == '/')
{
return json;
return EmptyPath();
}
if (json.is_object() && json[OTYPE_TAG].as_string() == SHORT_ATOM__Path)
{
std::filesystem::path path = json["value"].as_string();
if (path.empty()) {
return EmptyPath();
}
std::string newPath = SS(pluginStoragePath << "/" << path);
json_variant result = json_variant::make_object();
result[OTYPE_TAG] = json_variant(SHORT_ATOM__Path);
result["value"] = newPath;
return result;
if (path.is_relative())
{
path = pluginStoragePath / path;
}
return TypedProperty(SHORT_ATOM__Path,path.string());
}
else {
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)
{
std::string oType = json[OTYPE_TAG].as_string();
if (oType == SHORT_ATOM__Path)
if (json.is_object() && json[OTYPE_TAG].as_string() == SHORT_ATOM__Path)
{
std::string path = json["value"].as_string().c_str();
if (path.length() == 0)
@@ -592,6 +593,10 @@ static bool gEmptyPathInitialized = false;
static std::mutex gInitMutex;
std::string gEmptyPathstring;
json_variant AtomConverter::gEmptyPath = TypedProperty(SHORT_ATOM__Path,std::string(""));
std::string AtomConverter::EmptyPathstring()
{
std::lock_guard lock{gInitMutex};
+6 -2
View File
@@ -153,17 +153,21 @@ namespace pipedal
std::string ToString(const LV2_Atom*atom);
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 MapPath(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 const json_variant& EmptyPath() { return gEmptyPath; }
private:
static const std::string OTYPE_TAG;
static const std::string VTYPE_TAG;
static const std::string ID_TAG;
static json_variant gEmptyPath;
std::map<std::string, LV2_URID> stringToTypeUrid;
std::map<LV2_URID,std::string> typeUridToString;
+1
View File
@@ -56,6 +56,7 @@ namespace pipedal
int64_t clientId;
int64_t instanceId;
LV2_URID uridUri;
std::string uri;
enum class RequestType
{
+10 -7
View File
@@ -30,7 +30,7 @@ using namespace pipedal;
using namespace std;
using namespace std::filesystem;
Storage storage;
std::unique_ptr<Storage> storage;
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)
{
std::string id = GetPluginid(uri);
PluginPresets presets = storage.GetPluginPresets(uri);
PluginPresets presets = storage->GetPluginPresets(uri);
f << "###" << endl;
@@ -197,7 +197,7 @@ void WritePresetFile(const path &outputDirectory, const std::string &uri)
WritePrefixes(f);
PluginPresets presets = storage.GetPluginPresets(uri);
PluginPresets presets = storage->GetPluginPresets(uri);
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[])
{
storage = std::make_unique<Storage>();
// 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.
@@ -284,11 +287,11 @@ int main(int argc, const char *argv[])
try
{
storage.SetConfigRoot("/etc/pipedal/config");
storage.SetDataRoot("/var/pipedal");
storage.Initialize();
storage->SetConfigRoot("/etc/pipedal/config");
storage->SetDataRoot("/var/pipedal");
storage->Initialize();
const PluginPresetIndex &index = storage.GetPluginPresetIndex();
const PluginPresetIndex &index = storage->GetPluginPresetIndex();
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
// extensions observed in the field.
{"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
{"mlmodel", "ToobMlModels", "ML Models", {".json"}}, //
+3
View File
@@ -125,6 +125,7 @@ public:
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(midiChannelBinding)
GETTER_SETTER_REF(pathProperties)
GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State)
GETTER_SETTER_REF(title)
@@ -134,6 +135,8 @@ public:
GETTER_SETTER_REF(lilvPresetUri)
PropertyMap&PatchProperties() { return patchProperties; }
const PropertyMap&PatchProperties() const { return patchProperties; }
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)
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId);
if (pedalboardItem && value.is_string())
if (pedalboardItem)
{
std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pedalboardItem->uri_);
auto pipedalUi = pluginInfo->piPedalUI();
auto fileProperty = pipedalUi->GetFileProperty(propertyUri);
if (fileProperty && value.is_string())
if (fileProperty)
{
json_variant abstractPath = pluginHost.AbstractPath(value);
std::ostringstream ss;
json_writer writer(ss);
writer.write(abstractPath);
std::string atomString = ss.str();
std::string atomString = abstractPath.to_string();
pedalboardItem->pathProperties_[propertyUri] = atomString;
}
this->SetPresetChanged(clientId, true);
}
}
LV2_Atom *atomValue = atomConverter.ToAtom(value);
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
@@ -1827,7 +1824,24 @@ void PiPedalModel::SendGetPatchProperty(
{
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
@@ -1854,6 +1868,7 @@ void PiPedalModel::SendGetPatchProperty(
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
onRequestComplete,
clientId, instanceId, urid, onSuccess, onError, sampleTimeout);
request->uri = uri;
outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request);
@@ -2058,14 +2073,24 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
if (pPlugin->piPedalUI())
{
PiPedalUI::ptr piPedalUI = pPlugin->piPedalUI();
std::set<std::string> validFileProperties;
for (auto &fileProperty : piPedalUI->fileProperties())
{
validFileProperties.insert(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.
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
@@ -2162,8 +2187,9 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
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);
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->lilvPresetUri(presetValues.lilvPresetUri);
pedalboardItem->stateUpdateCount(oldStateUpdateCount + 1);
pedalboardItem->pathProperties(presetValues.pathProperties);
FirePedalboardChanged(-1); // does a complete reload of both client and audio server.
}
this->SetPresetChanged(-1, true);
@@ -2297,7 +2324,8 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
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()};
for (auto &subscriber : t)
@@ -2305,7 +2333,7 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
subscriber->OnNotifyPathPatchPropertyChanged(
instanceId,
pathPatchPropertyUri,
atomString);
abstractAtomString);
}
}
}
@@ -2382,16 +2410,23 @@ void PiPedalModel::MonitorPatchProperty(int64_t clientId, int64_t clientHandle,
PedalboardItem *item = this->pedalboard.GetItem(instanceId);
if (item)
{
auto &map = item->PatchProperties();
auto &map = item->pathProperties();
if (map.contains(propertyUri))
{
const auto &value = map[propertyUri];
std::string json = this->audioHost->AtomToJson(value.get());
for (auto &subscriber : this->subscribers)
{
if (subscriber->GetClientId() == clientId)
{
subscriber->OnNotifyPatchProperty(clientHandle, instanceId, propertyUri, json);
const auto &value = map.at(propertyUri);
if (value != "null") {
try {
std::string json = storage.FromAbstractPathJson(value);
for (auto &subscriber : this->subscribers)
{
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
};
bool GetHasWifi();
Storage&GetStorage() { return storage; }
bool GetHasWifi();
void NextBank(Direction direction = Direction::Increase);
void PreviousBank() { NextBank(Direction::Decrease); }
+26 -17
View File
@@ -49,7 +49,7 @@
#include "StdErrorCapture.hpp"
#include "util.hpp"
#include "ModFileTypes.hpp"
#include <algorithm>
#include <algorithm>
#include "Locale.hpp"
@@ -304,7 +304,6 @@ PluginHost::PluginHost()
lv2Features.push_back(nullptr);
this->urids = new Urids(mapFeature);
}
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
@@ -660,13 +659,14 @@ static std::vector<UiFileType> ToPiPedalFileTypes(
std::string type = fileType;
if (type.find("/") != std::string::npos) // mime type?
{
UiFileType t = UiFileType(type,type,"");
UiFileType t = UiFileType(type, type, "");
result.push_back(t);
}
else
{
else
{
// add a leading dot.
if (!type.starts_with(".")) {
if (!type.starts_with("."))
{
type = "." + type;
}
auto t = UiFileType(SS(type << " file"), type);
@@ -776,7 +776,8 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
auto modDirectory = modFileTypes.GetModDirectory(modName);
fileProperty->directory(modDirectory->pipedalPath);
std::set<std::string> fileExtensions = modDirectory->fileExtensions;
if (!modFileExtensions.empty()) {
if (!modFileExtensions.empty())
{
auto extensions = split(modFileExtensions, ',');
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";
}
AutoLilvNodes required_features = lilv_plugin_get_required_features(pPlugin);
this->required_features_ = nodeAsStringArray(required_features);
@@ -1236,18 +1236,19 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
if (unitsValueUri)
{
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
if (this->units_ == Units::unknown) {
if (this->units_ == Units::unknown)
{
// try for a custom format string.
AutoLilvNode render = lilv_world_get(
pWorld,
unitsValueUri,
host->lilvUris->units__render,
nullptr);
if (render) {
const char*strRender = lilv_node_as_string(render);
if (render)
{
const char *strRender = lilv_node_as_string(render);
custom_units_ = strRender;
}
}
}
else
@@ -1630,7 +1631,13 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
{
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
@@ -1904,11 +1911,13 @@ Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNod
}
// ffs.
static inline bool contains(const std::vector<std::string> &vec, const std::string &value) {
return std::find(vec.begin(),vec.end(),value) != vec.end();
static inline bool contains(const std::vector<std::string> &vec, const std::string &value)
{
return std::find(vec.begin(), vec.end(), value) != vec.end();
}
bool Lv2PluginInfo::WantsWorkerThread() const {
return contains(this->required_features_,LV2_WORKER__schedule) || contains(this->supported_features_,LV2_WORKER__schedule);
bool Lv2PluginInfo::WantsWorkerThread() const
{
return contains(this->required_features_, LV2_WORKER__schedule) || contains(this->supported_features_, LV2_WORKER__schedule);
}
// 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,label)
JSON_MAP_REFERENCE(PluginPreset,controlValues)
JSON_MAP_REFERENCE(PluginPreset,pathProperties)
JSON_MAP_REFERENCE(PluginPreset,state)
JSON_MAP_REFERENCE(PluginPreset,lilvPresetUri)
JSON_MAP_END()
+5
View File
@@ -76,6 +76,7 @@ public:
PluginPreset(uint64_t instanceId, const std::string&label,const PedalboardItem&pedalboardItem)
: instanceId_(instanceId)
, label_(label)
, pathProperties_(pedalboardItem.pathProperties())
, state_(pedalboardItem.lv2State())
{
for (auto & controlValue : pedalboardItem.controlValues())
@@ -89,10 +90,12 @@ public:
const std::string&label,
const std::map<std::string,
float> & controlValues,
const std::map<std::string,std::string>&pathProperties,
const Lv2PluginState &state)
: instanceId_(instanceId),
label_(label),
controlValues_((controlValues)),
pathProperties_(pathProperties),
state_(state)
{
}
@@ -117,7 +120,9 @@ public:
std::string label_;
std::string lilvPresetUri_;
std::map<std::string,float> controlValues_;
std::map<std::string,std::string> pathProperties_;
Lv2PluginState state_;
DECLARE_JSON_MAP(PluginPreset);
};
+64 -10
View File
@@ -40,6 +40,7 @@
#include "util.hpp"
#include "AudioFiles.hpp"
#include "Utf8Utils.hpp"
#include "AtomConverter.hpp"
using namespace pipedal;
namespace fs = std::filesystem;
@@ -189,6 +190,7 @@ void Storage::SetConfigRoot(const std::filesystem::path &path)
void Storage::SetDataRoot(const std::filesystem::path &path)
{
this->dataRoot = ResolveHomePath(path);
this->dataRoot__audio_uploads = dataRoot / "audio_uploads";
}
const std::filesystem::path &Storage::GetConfigRoot()
@@ -302,9 +304,9 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{
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
@@ -1409,6 +1411,10 @@ PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri,
{
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.lilvPresetUri = preset.lilvPresetUri_;
return result;
@@ -1417,6 +1423,31 @@ PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri,
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(
const std::string &name,
const PedalboardItem &pedalboardItem)
@@ -1425,6 +1456,7 @@ uint64_t Storage::SavePluginPreset(
auto presets = GetPluginPresets(pluginUri);
uint64_t result = -1;
bool existing = false;
for (size_t i = 0; i < presets.presets_.size(); ++i)
{
auto &preset = presets.presets_[i];
@@ -1432,6 +1464,8 @@ uint64_t Storage::SavePluginPreset(
{
existing = true;
result = preset.instanceId_;
PluginPreset pluginPreset{preset.instanceId_, preset.label_, pedalboardItem};
presets.presets_[i] = PluginPreset(preset.instanceId_, preset.label_, pedalboardItem);
break;
}
@@ -1439,10 +1473,12 @@ uint64_t Storage::SavePluginPreset(
if (!existing)
{
result = presets.nextInstanceId_++;
presets.presets_.push_back(
PluginPreset(result,
name,
pedalboardItem));
auto preset = PluginPreset(
result,
name,
pedalboardItem);
ToAbstractPaths(preset);
presets.presets_.push_back(preset);
}
this->SavePluginPresets(pluginUri, presets);
return result;
@@ -1451,9 +1487,8 @@ uint64_t Storage::SavePluginPreset(
uint64_t Storage::SavePluginPreset(
const std::string &pluginUri,
const std::string &name,
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values,
const std::map<std::string,std::string> &pathProperties,
const Lv2PluginState &lv2State)
{
auto presets = GetPluginPresets(pluginUri);
@@ -1466,6 +1501,7 @@ uint64_t Storage::SavePluginPreset(
{
existing = true;
preset.controlValues_ = values;
preset.pathProperties_ = pathProperties;
preset.state_ = lv2State;
result = preset.instanceId_;
@@ -1480,6 +1516,7 @@ uint64_t Storage::SavePluginPreset(
result,
name,
values,
pathProperties,
lv2State));
}
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);
if (pos == -1)
{
throw PiPedalException("Perset not found.");
throw PiPedalException("Preset not found.");
}
PluginPreset t = presets.presets_[pos];
t.instanceId_ = presets.nextInstanceId_++;
@@ -1922,7 +1959,7 @@ static void AddTracksToResult(
FileRequestResult Storage::GetModFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
{
FileRequestResult result;
fs::path uploadsDirectory = GetPluginUploadDirectory();
const fs::path &uploadsDirectory = GetPluginUploadDirectory();
if (relativePath.empty())
{
@@ -2651,6 +2688,23 @@ std::string Storage::GetTone3000Auth() const
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_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+13 -4
View File
@@ -60,15 +60,19 @@ public:
struct PluginPresetValues {
std::vector<ControlValue> controls;
std::map<std::string,std::string> pathProperties;
Lv2PluginState state;
std::string lilvPresetUri;
};
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
class Storage {
class Storage {
private:
std::filesystem::path dataRoot;
std::filesystem::path dataRoot__audio_uploads;
std::filesystem::path configRoot;
BankIndex bankIndex;
BankFile currentBank;
@@ -125,7 +129,11 @@ public:
const std::filesystem::path&GetConfigRoot();
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();
@@ -208,6 +216,8 @@ private:
std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const;
bool IsValidSampleFileName(const std::filesystem::path&fileName);
std::filesystem::path MakeUserFilePath(const std::string &directory, const std::string&filename);
void ToAbstractPaths(PluginPreset&pluginPreset);
void FromAbstractPaths(PluginPreset&pluginPreset);
public:
bool HasPluginPresets(const std::string&pluginUri) const;
@@ -227,9 +237,8 @@ public:
uint64_t SavePluginPreset(
const std::string &pluginUri,
const std::string &name,
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values,
const std::map<std::string,std::string>&pathProperties,
const Lv2PluginState& lv2State);
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
+1 -1
View File
@@ -494,7 +494,7 @@ public:
res.set(HttpField::content_type, "application/json");
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))
{
+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.
sudo apt install libbz2-dev
Move status to pedalboard view.
Default to dark theme.
README, license files in file browser.
ls
Carla Project icons.
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
// extensions observed in the field.
{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: "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();
try {
let pedalboardItem = pedalboard.tryGetItem(instanceId);
if (pedalboardItem) {
if (pedalboardItem && pedalboardItem.pathProperties[propertyUri] !== undefined) {
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
}
} catch (ignored) {