Snapshots
This commit is contained in:
+7
-1
@@ -953,9 +953,12 @@ namespace pipedal
|
||||
{
|
||||
OpenMidi(jackServerSettings, channelSelection);
|
||||
OpenAudio(jackServerSettings, channelSelection);
|
||||
std::atomic_thread_fence(std::memory_order::release);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order::release);
|
||||
|
||||
Close();
|
||||
throw;
|
||||
}
|
||||
@@ -1359,7 +1362,7 @@ namespace pipedal
|
||||
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
|
||||
{
|
||||
long framesRead;
|
||||
auto frame_bytes = this->captureFrameSize;
|
||||
auto frame_bytes = this->playbackFrameSize;
|
||||
|
||||
while (frames > 0)
|
||||
{
|
||||
@@ -1969,6 +1972,8 @@ namespace pipedal
|
||||
}
|
||||
virtual void Close()
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order::acquire);
|
||||
|
||||
if (!open)
|
||||
{
|
||||
return;
|
||||
@@ -1977,6 +1982,7 @@ namespace pipedal
|
||||
Deactivate();
|
||||
AlsaCleanup();
|
||||
DeleteBuffers();
|
||||
std::atomic_thread_fence(std::memory_order::release);
|
||||
}
|
||||
|
||||
virtual float CpuUse()
|
||||
|
||||
+95
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
* Copyright (c) 2023-2024 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "json.hpp"
|
||||
#include "ss.hpp"
|
||||
#include "lv2/atom/util.h"
|
||||
#include "ss.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -69,6 +70,76 @@ json_variant AtomConverter::ToJson(const LV2_Atom *atom)
|
||||
json_variant variant = ToVariant(const_cast<LV2_Atom*>(atom));
|
||||
return std::move(variant);
|
||||
}
|
||||
|
||||
LV2_Atom*AtomConverter::ToAtom(const std::string&jsonString)
|
||||
{
|
||||
json_variant jvValue;
|
||||
std::istringstream ss(jsonString);
|
||||
json_reader reader(ss);
|
||||
reader.read(&jvValue);
|
||||
return ToAtom(jvValue);
|
||||
}
|
||||
|
||||
std::string AtomConverter::ToString(const LV2_Atom*atom)
|
||||
{
|
||||
json_variant v = ToJson(atom);
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(v);
|
||||
return ss.str();
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::string path = json["value"].as_string().c_str();
|
||||
if (path.length() == 0 || path[0] == '/')
|
||||
{
|
||||
return json;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::string path = json["value"].as_string().c_str();
|
||||
if (path.length() == 0)
|
||||
{
|
||||
return json;
|
||||
}
|
||||
if (path.starts_with(pluginStoragePath))
|
||||
{
|
||||
auto pos = pluginStoragePath.length();
|
||||
if (pos < path.length() && path[pos] == '/')
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
path = path.substr(pos);
|
||||
}
|
||||
json_variant result = json_variant::make_object();
|
||||
result[OTYPE_TAG] = json_variant(SHORT_ATOM__Path);
|
||||
result["value"] = path;
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
LV2_Atom*AtomConverter::ToAtom(const json_variant&json)
|
||||
{
|
||||
if (outputBuffer.size() == 0)
|
||||
@@ -517,6 +588,29 @@ std::string AtomConverter::TypeUridToString(LV2_URID urid)
|
||||
return map.UridToString(urid);
|
||||
}
|
||||
|
||||
static bool gEmptyPathInitialized = false;
|
||||
static std::mutex gInitMutex;
|
||||
std::string gEmptyPathstring;
|
||||
|
||||
std::string AtomConverter::EmptyPathstring()
|
||||
{
|
||||
std::lock_guard lock{gInitMutex};
|
||||
|
||||
if (!gEmptyPathInitialized)
|
||||
{
|
||||
json_variant v = TypedProperty(SHORT_ATOM__Path,std::string("",0));
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(v);
|
||||
|
||||
gEmptyPathstring = ss.str();
|
||||
|
||||
}
|
||||
return gEmptyPathstring;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void AtomConverter::InitUrids()
|
||||
{
|
||||
#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName)
|
||||
|
||||
+21
-6
@@ -31,6 +31,7 @@
|
||||
#include "lv2/atom/forge.h"
|
||||
#include "json_variant.hpp"
|
||||
#include "atom_object.hpp"
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -139,11 +140,25 @@ namespace pipedal
|
||||
|
||||
/// @brief Convert a json variant to an atom.
|
||||
/// @param json Json variant that matches the structure of the prototype.
|
||||
/// @return An atom.
|
||||
/// @remarks
|
||||
/// The json variant must match the structure of the LV2_Atom prototype supplied to
|
||||
/// the constructor.
|
||||
/// @return An atom. Not valid beyond the lifetime of the AtomConverter. Memory is owned by the AtomConverter.
|
||||
LV2_Atom*ToAtom(const json_variant& json);
|
||||
|
||||
|
||||
/// @brief Convert a json string to an atom.
|
||||
/// @param json Json string that matches the structure of the prototype.
|
||||
/// @return An atom. Not valid beyond the lifetime of the AtomConverter. Memory is owned by the AtomConverter.
|
||||
/// @remarks
|
||||
|
||||
LV2_Atom*ToAtom(const std::string &jsonString);
|
||||
|
||||
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 std::string EmptyPathstring();
|
||||
|
||||
private:
|
||||
static const std::string OTYPE_TAG;
|
||||
static const std::string VTYPE_TAG;
|
||||
@@ -160,14 +175,14 @@ namespace pipedal
|
||||
LV2_URID InitUrid(const char*uri, const char*shortUri);
|
||||
|
||||
template <typename U>
|
||||
json_variant TypedProperty(const std::string type,U value)
|
||||
static json_variant TypedProperty(const std::string type,U value)
|
||||
{
|
||||
json_variant result = json_variant::make_object();
|
||||
result[OTYPE_TAG] = type;
|
||||
result["value"] = json_variant(value);
|
||||
return result;
|
||||
}
|
||||
json_variant TypedProperty(const std::string type,const json_variant&& value)
|
||||
static json_variant TypedProperty(const std::string type,const json_variant&& value)
|
||||
{
|
||||
json_variant result = json_variant::make_object();
|
||||
result[OTYPE_TAG] = type;
|
||||
|
||||
+345
-19
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "AudioHost.hpp"
|
||||
#include "util.hpp"
|
||||
#include <lv2/atom/atom.h>
|
||||
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
@@ -26,6 +27,9 @@
|
||||
#include "AlsaDriver.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
#include <unordered_map>
|
||||
#include "PluginHost.hpp"
|
||||
#include "PatchPropertyWriter.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -107,6 +111,167 @@ static std::string GetGovernor()
|
||||
return pipedal::GetCpuGovernor();
|
||||
}
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
struct PathPatchProperty
|
||||
{
|
||||
LV2_URID propertyUrid = 0;
|
||||
std::vector<uint8_t> atomBuffer;
|
||||
};
|
||||
|
||||
class IndexedSnapshotValue
|
||||
{
|
||||
private:
|
||||
struct InputControlEntry
|
||||
{
|
||||
bool isInputControl = false;
|
||||
float value = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
IndexedSnapshotValue(IEffect *effect, SnapshotValue *snapshotValue, PluginHost &pluginHost)
|
||||
: pEffect(effect)
|
||||
{
|
||||
auto maxInputControl = effect->GetMaxInputControl();
|
||||
inputControlValues.resize(maxInputControl);
|
||||
|
||||
for (uint64_t i = 0; i < maxInputControl; ++i)
|
||||
{
|
||||
bool isInputControl = effect->IsInputControl(i);
|
||||
if (isInputControl)
|
||||
{
|
||||
inputControlValues[i] = InputControlEntry{isInputControl : true, value : effect->GetDefaultInputControlValue(i)};
|
||||
}
|
||||
else
|
||||
{
|
||||
inputControlValues[i] = InputControlEntry{isInputControl : false, value : 0};
|
||||
}
|
||||
}
|
||||
if (snapshotValue)
|
||||
{
|
||||
for (auto &controlValue : snapshotValue->controlValues_)
|
||||
{
|
||||
auto index = effect->GetControlIndex(controlValue.key());
|
||||
if (index >= 0 && index < inputControlValues.size())
|
||||
{
|
||||
inputControlValues[index].value = controlValue.value();
|
||||
}
|
||||
}
|
||||
this->lv2State = snapshotValue->lv2State_;
|
||||
|
||||
if (effect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
|
||||
for (auto &pathProperty : snapshotValue->pathProperties_)
|
||||
{
|
||||
// only transmit changed path patch properties.
|
||||
if (lv2Effect->GetPathPatchProperty(pathProperty.first) != pathProperty.second)
|
||||
{
|
||||
lv2Effect->SetPathPatchProperty(pathProperty.first, pathProperty.second);
|
||||
|
||||
PathPatchProperty pathPatchProperty;
|
||||
pathPatchProperty.propertyUrid = pluginHost.GetLv2Urid(pathProperty.first.c_str());
|
||||
// convert to json variant so we do a mappath operation.
|
||||
json_variant vProperty;
|
||||
std::istringstream ss(pathProperty.second);
|
||||
json_reader reader(ss);
|
||||
reader.read(&vProperty);
|
||||
vProperty = pluginHost.MapPath(vProperty);
|
||||
|
||||
// now to atom format (what we want on the rt thread0)
|
||||
AtomConverter atomConverter(pluginHost.GetMapFeature());
|
||||
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
|
||||
|
||||
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
|
||||
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
|
||||
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
|
||||
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void ApplyValues(IEffect *effect)
|
||||
{
|
||||
if (effect != pEffect)
|
||||
{
|
||||
throw std::runtime_error("Wrong effect");
|
||||
}
|
||||
for (size_t i = 0; i < inputControlValues.size(); ++i)
|
||||
{
|
||||
InputControlEntry &e = inputControlValues[i];
|
||||
if (e.isInputControl)
|
||||
{
|
||||
effect->SetControl((int)i, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &patchProperty : pathPatchProperties)
|
||||
{
|
||||
effect->SetPatchProperty(
|
||||
patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data());
|
||||
}
|
||||
// effect->SetLv2State(lv2State);
|
||||
}
|
||||
|
||||
private:
|
||||
IEffect *pEffect;
|
||||
Lv2PluginState lv2State;
|
||||
std::vector<InputControlEntry> inputControlValues;
|
||||
std::vector<PathPatchProperty> pathPatchProperties;
|
||||
};
|
||||
class IndexedSnapshot
|
||||
{
|
||||
public:
|
||||
IndexedSnapshot(Snapshot *snapshot, std::shared_ptr<Lv2Pedalboard> currentPedalboard, PluginHost &pluginHost)
|
||||
{
|
||||
std::unordered_map<uint64_t, SnapshotValue *> index;
|
||||
for (auto &value : snapshot->values_)
|
||||
{
|
||||
index[value.instanceId_] = &value;
|
||||
}
|
||||
std::vector<IEffect *> &effects = currentPedalboard->GetEffects();
|
||||
snapshotValues.reserve(effects.size());
|
||||
|
||||
for (size_t i = 0; i < effects.size(); ++i)
|
||||
{
|
||||
auto &effect = effects[i];
|
||||
|
||||
SnapshotValue *snapshotValue = getSnapshotValue(index, effect->GetInstanceId());
|
||||
snapshotValues.push_back(IndexedSnapshotValue(effect, snapshotValue, pluginHost));
|
||||
}
|
||||
}
|
||||
static SnapshotValue *getSnapshotValue(std::unordered_map<uint64_t, SnapshotValue *> &index, uint64_t instanceId)
|
||||
{
|
||||
auto iter = index.find(instanceId);
|
||||
if (iter == index.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void Apply(std::vector<IEffect *> &effects)
|
||||
{
|
||||
if (effects.size() != snapshotValues.size())
|
||||
{
|
||||
throw std::runtime_error("Effects and values don't match");
|
||||
}
|
||||
for (size_t i = 0; i < snapshotValues.size(); ++i)
|
||||
{
|
||||
snapshotValues[i].ApplyValues(effects[i]);
|
||||
}
|
||||
}
|
||||
~IndexedSnapshot()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<IndexedSnapshotValue> snapshotValues;
|
||||
};
|
||||
}
|
||||
|
||||
class SystemMidiBinding
|
||||
{
|
||||
private:
|
||||
@@ -192,9 +357,12 @@ bool SystemMidiBinding::IsMatch(const MidiEvent &event)
|
||||
}
|
||||
}
|
||||
|
||||
class AudioHostImpl : public AudioHost, private AudioDriverHost
|
||||
class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchWriterCallback
|
||||
{
|
||||
private:
|
||||
void OnWritePatchPropertyBuffer(
|
||||
PatchPropertyWriter::Buffer *);
|
||||
|
||||
IHost *pHost = nullptr;
|
||||
LV2_Atom_Forge inputWriterForge;
|
||||
|
||||
@@ -368,6 +536,8 @@ private:
|
||||
|
||||
StopReaderThread();
|
||||
|
||||
// delete any leaked snapshots.
|
||||
CleanUpSnapshots();
|
||||
|
||||
// release any pdealboards owned by the process thread.
|
||||
this->activePedalboards.resize(0);
|
||||
@@ -489,8 +659,26 @@ private:
|
||||
|
||||
RealtimePatchPropertyRequest *pParameterRequests = nullptr;
|
||||
|
||||
void cancelParameterRequests()
|
||||
{
|
||||
return;
|
||||
|
||||
// auto p = this->pParameterRequests;
|
||||
// while (pParameterRequests != nullptr)
|
||||
// {
|
||||
// auto nextParameterRequest = p->pNext;
|
||||
// if (p->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) {
|
||||
// p->errorMessage = "Effect unloaded.";
|
||||
// }
|
||||
// p = nextParameterRequest;
|
||||
// }
|
||||
// this->realtimeWriter.ParameterRequestComplete(pParameterRequests);
|
||||
// this->pParameterRequests = nullptr;
|
||||
}
|
||||
|
||||
bool reEntered = false;
|
||||
void ProcessInputCommands()
|
||||
// returns false if the current effect has been replaced.
|
||||
bool ProcessInputCommands()
|
||||
{
|
||||
if (reEntered)
|
||||
{
|
||||
@@ -586,6 +774,14 @@ private:
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RingBufferCommand::LoadSnapshot:
|
||||
{
|
||||
IndexedSnapshot *snapshot;
|
||||
realtimeReader.readComplete(&snapshot);
|
||||
ApplySnapshot(snapshot);
|
||||
realtimeWriter.FreeSnapshot(snapshot);
|
||||
break;
|
||||
}
|
||||
case RingBufferCommand::SetVuSubscriptions:
|
||||
{
|
||||
RealtimeVuBuffers *configuration;
|
||||
@@ -622,16 +818,35 @@ private:
|
||||
// invalidate the possibly no-good subscriptions. Model will update them shortly.
|
||||
freeRealtimeVuConfiguration();
|
||||
freeRealtimeMonitorPortSubscriptions();
|
||||
cancelParameterRequests();
|
||||
|
||||
if (realtimeActivePedalboard)
|
||||
{
|
||||
realtimeActivePedalboard->ResetAtomBuffers();
|
||||
// issue patch gets for all writable path properties.
|
||||
for (auto pEffect : realtimeActivePedalboard->GetEffects())
|
||||
{
|
||||
pEffect->RequestAllPathPatchProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
reEntered = false;
|
||||
|
||||
return false; // signal to caller that the effect has been replaced, and processing needs to start again.
|
||||
}
|
||||
default:
|
||||
throw PiPedalStateException("Unknown Ringbuffer command.");
|
||||
}
|
||||
}
|
||||
reEntered = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplySnapshot(IndexedSnapshot *snapshot)
|
||||
{
|
||||
auto &effects = this->realtimeActivePedalboard->GetEffects();
|
||||
snapshot->Apply(effects);
|
||||
}
|
||||
virtual void AckMidiProgramRequest(uint64_t requestId)
|
||||
{
|
||||
hostWriter.AckMidiProgramRequest(requestId);
|
||||
@@ -799,8 +1014,6 @@ private:
|
||||
|
||||
std::mutex audioStoppedMutex;
|
||||
|
||||
|
||||
|
||||
bool IsAudioRunning()
|
||||
{
|
||||
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
||||
@@ -816,20 +1029,31 @@ private:
|
||||
Lv2Log::info("Audio thread terminated.");
|
||||
}
|
||||
|
||||
|
||||
virtual void OnProcess(size_t nframes)
|
||||
{
|
||||
try
|
||||
{
|
||||
float *in, *out;
|
||||
|
||||
pParameterRequests = nullptr;
|
||||
Lv2Pedalboard *pedalboard = nullptr;
|
||||
pedalboard = this->realtimeActivePedalboard;
|
||||
if (pedalboard)
|
||||
{
|
||||
pedalboard->ResetAtomBuffers();
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
|
||||
ProcessInputCommands();
|
||||
if (ProcessInputCommands())
|
||||
{
|
||||
break;
|
||||
}
|
||||
// else a new pedalboard was installed. start again.
|
||||
pedalboard = this->realtimeActivePedalboard;
|
||||
}
|
||||
|
||||
bool processed = false;
|
||||
|
||||
Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard;
|
||||
if (pedalboard != nullptr)
|
||||
{
|
||||
ProcessMidiInput();
|
||||
@@ -862,7 +1086,6 @@ private:
|
||||
|
||||
if (buffersValid)
|
||||
{
|
||||
pedalboard->ResetAtomBuffers();
|
||||
pedalboard->ProcessParameterRequests(pParameterRequests);
|
||||
|
||||
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
||||
@@ -885,6 +1108,7 @@ private:
|
||||
}
|
||||
}
|
||||
pedalboard->GatherPatchProperties(pParameterRequests);
|
||||
pedalboard->GatherPathPatchProperties(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,6 +1120,7 @@ private:
|
||||
if (pParameterRequests != nullptr)
|
||||
{
|
||||
this->realtimeWriter.ParameterRequestComplete(pParameterRequests);
|
||||
pParameterRequests = nullptr;
|
||||
}
|
||||
// provide a grace period for undderruns, while spinning up. (15 second-ish)
|
||||
if (currentSample <= this->overrunGracePeriodSamples && currentSample + nframes > this->overrunGracePeriodSamples)
|
||||
@@ -926,7 +1151,6 @@ public:
|
||||
{
|
||||
realtimeAtomBuffer.resize(32 * 1024);
|
||||
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
|
||||
|
||||
}
|
||||
virtual ~AudioHostImpl()
|
||||
{
|
||||
@@ -1193,6 +1417,18 @@ public:
|
||||
hostReader.read(&body);
|
||||
OnActivePedalboardReleased(body.oldEffect);
|
||||
}
|
||||
else if (command == RingBufferCommand::FreeSnapshot)
|
||||
{
|
||||
IndexedSnapshot *snapshot;
|
||||
hostReader.read(&snapshot);
|
||||
OnFreeSnapshot(snapshot);
|
||||
}
|
||||
else if (command == RingBufferCommand::SendPathPropertyBuffer)
|
||||
{
|
||||
PatchPropertyWriter::Buffer *buffer = nullptr;
|
||||
hostReader.read(&buffer);
|
||||
OnPathPropertyReceived(buffer);
|
||||
}
|
||||
else if (command == RingBufferCommand::AudioStopped)
|
||||
{
|
||||
AudioStoppedBody body;
|
||||
@@ -1200,7 +1436,8 @@ public:
|
||||
OnAudioComplete();
|
||||
return;
|
||||
}
|
||||
else if (command == RingBufferCommand::MidiProgramChange)
|
||||
else if (command == RingBufferCommand::
|
||||
MidiProgramChange)
|
||||
{
|
||||
RealtimeMidiProgramRequest programRequest;
|
||||
hostReader.read(&programRequest);
|
||||
@@ -1307,7 +1544,9 @@ public:
|
||||
{
|
||||
this->isDummyAudioDriver = true;
|
||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->isDummyAudioDriver = false;
|
||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
|
||||
}
|
||||
@@ -1320,7 +1559,6 @@ public:
|
||||
this->currentSample = 0;
|
||||
this->underruns = 0;
|
||||
|
||||
|
||||
this->inputRingBuffer.reset();
|
||||
this->outputRingBuffer.reset();
|
||||
this->hostReader.Reset();
|
||||
@@ -1364,6 +1602,18 @@ public:
|
||||
{
|
||||
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
|
||||
}
|
||||
|
||||
void OnPathPropertyReceived(PatchPropertyWriter::Buffer *buffer)
|
||||
{
|
||||
if (pNotifyCallbacks)
|
||||
{
|
||||
pNotifyCallbacks->OnNotifyPathPatchPropertyReceived(
|
||||
buffer->instanceId,
|
||||
buffer->patchPropertyUrid,
|
||||
(LV2_Atom *)(buffer->memory.data()));
|
||||
}
|
||||
buffer->OnBufferReadComplete();
|
||||
}
|
||||
void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
|
||||
{
|
||||
if (pPedalboard)
|
||||
@@ -1465,6 +1715,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<IndexedSnapshot *> pendingSnapshots;
|
||||
|
||||
virtual void LoadSnapshot(Snapshot &snapshot, PluginHost &pluginHost) override
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (active && this->currentPedalboard)
|
||||
{
|
||||
IndexedSnapshot *indexedSnapshot = new IndexedSnapshot(&snapshot, this->currentPedalboard, pluginHost);
|
||||
pendingSnapshots.push_back(indexedSnapshot);
|
||||
this->hostWriter.LoadSnapshot(indexedSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
void OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
const std::string &pathPatchPropertyUri,
|
||||
const std::string &jsonAtom) override;
|
||||
|
||||
void OnFreeSnapshot(IndexedSnapshot *snapshot)
|
||||
{
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i)
|
||||
{
|
||||
if (*i == snapshot)
|
||||
{
|
||||
pendingSnapshots.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete snapshot;
|
||||
}
|
||||
void CleanUpSnapshots()
|
||||
{
|
||||
for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i)
|
||||
{
|
||||
IndexedSnapshot *snapshot = *i;
|
||||
delete snapshot;
|
||||
}
|
||||
pendingSnapshots.clear();
|
||||
}
|
||||
|
||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
@@ -1754,19 +2047,25 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
|
||||
else if (i->symbol() == "prevProgram")
|
||||
{
|
||||
this->prevMidiBinding.SetBinding(*i);
|
||||
} else if (i->symbol() == "startHotspot")
|
||||
}
|
||||
else if (i->symbol() == "startHotspot")
|
||||
{
|
||||
this->startHotspotMidiBinding.SetBinding(*i);
|
||||
} else if (i->symbol() == "stopHotspot")
|
||||
}
|
||||
else if (i->symbol() == "stopHotspot")
|
||||
{
|
||||
this->stopHotspotMidiBinding.SetBinding(*i);
|
||||
} else if (i->symbol() == "reboot")
|
||||
}
|
||||
else if (i->symbol() == "reboot")
|
||||
{
|
||||
this->rebootMidiBinding.SetBinding(*i);
|
||||
} else if (i->symbol() == "shutdown")
|
||||
}
|
||||
else if (i->symbol() == "shutdown")
|
||||
{
|
||||
this->shutdownMidiBinding.SetBinding(*i);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
|
||||
}
|
||||
}
|
||||
@@ -1793,6 +2092,27 @@ bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioHostImpl::OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
const std::string &pathPatchPropertyUri,
|
||||
const std::string &jsonAtom)
|
||||
{
|
||||
if (this->currentPedalboard)
|
||||
{
|
||||
IEffect*effect = this->currentPedalboard->GetEffect(instanceId);
|
||||
if (!effect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (effect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect*lv2Effect = (Lv2Effect*)effect;
|
||||
lv2Effect->SetPathPatchProperty(pathPatchPropertyUri,jsonAtom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
||||
{
|
||||
IEffect *effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
||||
@@ -1821,6 +2141,12 @@ bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioHostImpl::OnWritePatchPropertyBuffer(
|
||||
PatchPropertyWriter::Buffer *buffer)
|
||||
{
|
||||
this->realtimeWriter.SendPathPropertyBuffer(buffer);
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(JackHostStatus)
|
||||
JSON_MAP_REFERENCE(JackHostStatus, active)
|
||||
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
||||
|
||||
+174
-167
@@ -32,211 +32,218 @@
|
||||
#include "json_variant.hpp"
|
||||
#include "RealtimeMidiEventType.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
struct RealtimeMidiProgramRequest;
|
||||
struct RealtimeNextMidiProgramRequest;
|
||||
|
||||
using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
|
||||
|
||||
|
||||
class MonitorPortUpdate
|
||||
namespace pipedal
|
||||
{
|
||||
public:
|
||||
PortMonitorCallback *callbackPtr; // pointer because function<>'s probably aren't POD.
|
||||
int64_t subscriptionHandle;
|
||||
float value;
|
||||
};
|
||||
class RealtimePatchPropertyRequest {
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t instanceId;
|
||||
LV2_URID uridUri;
|
||||
|
||||
enum class RequestType {
|
||||
PatchGet,
|
||||
PatchSet
|
||||
};
|
||||
RequestType requestType;
|
||||
struct RealtimeMidiProgramRequest;
|
||||
struct RealtimeNextMidiProgramRequest;
|
||||
class PluginHost;
|
||||
class Pedalboard;
|
||||
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestComplete;
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess;
|
||||
std::function<void(const std::string &error)> onError;
|
||||
using PortMonitorCallback = std::function<void(int64_t handle, float value)>;
|
||||
|
||||
const char*errorMessage = nullptr;
|
||||
std::string jsonResponse;
|
||||
|
||||
RealtimePatchPropertyRequest *pNext = nullptr;
|
||||
|
||||
void SetSize(size_t size)
|
||||
class MonitorPortUpdate
|
||||
{
|
||||
responseLength = size;
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
longAtomBuffer.resize(size);
|
||||
}
|
||||
}
|
||||
size_t GetSize() const { return responseLength; }
|
||||
uint8_t *GetBuffer() {
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
return &(longAtomBuffer[0]);
|
||||
}
|
||||
return atomBuffer;
|
||||
}
|
||||
private:
|
||||
int responseLength = 0;
|
||||
uint8_t atomBuffer[2048];
|
||||
std::vector<uint8_t> longAtomBuffer;
|
||||
public:
|
||||
PortMonitorCallback *callbackPtr; // pointer because function<>'s probably aren't POD.
|
||||
int64_t subscriptionHandle;
|
||||
float value;
|
||||
};
|
||||
class RealtimePatchPropertyRequest
|
||||
{
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t instanceId;
|
||||
LV2_URID uridUri;
|
||||
|
||||
public:
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_
|
||||
)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
enum class RequestType
|
||||
{
|
||||
requestType = RequestType::PatchGet;
|
||||
PatchGet,
|
||||
PatchSet
|
||||
};
|
||||
RequestType requestType;
|
||||
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestComplete;
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess;
|
||||
std::function<void(const std::string &error)> onError;
|
||||
|
||||
const char *errorMessage = nullptr;
|
||||
std::string jsonResponse;
|
||||
|
||||
RealtimePatchPropertyRequest *pNext = nullptr;
|
||||
|
||||
void SetSize(size_t size)
|
||||
{
|
||||
responseLength = size;
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
longAtomBuffer.resize(size);
|
||||
}
|
||||
}
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
LV2_Atom*atomValue,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_
|
||||
)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
size_t GetSize() const { return responseLength; }
|
||||
uint8_t *GetBuffer()
|
||||
{
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
return &(longAtomBuffer[0]);
|
||||
}
|
||||
return atomBuffer;
|
||||
}
|
||||
|
||||
private:
|
||||
int responseLength = 0;
|
||||
uint8_t atomBuffer[2048];
|
||||
std::vector<uint8_t> longAtomBuffer;
|
||||
|
||||
public:
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
{
|
||||
requestType = RequestType::PatchGet;
|
||||
}
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
LV2_Atom *atomValue,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
{
|
||||
requestType = RequestType::PatchSet;
|
||||
size_t size = atomValue->size+ sizeof(LV2_Atom);
|
||||
size_t size = atomValue->size + sizeof(LV2_Atom);
|
||||
SetSize(size);
|
||||
memcpy(GetBuffer(),atomValue,size);
|
||||
memcpy(GetBuffer(), atomValue, size);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
class MonitorPortSubscription
|
||||
{
|
||||
public:
|
||||
int64_t subscriptionHandle;
|
||||
int64_t instanceid;
|
||||
std::string key;
|
||||
float updateInterval;
|
||||
PortMonitorCallback onUpdate;
|
||||
};
|
||||
|
||||
class IAudioHostCallbacks
|
||||
{
|
||||
public:
|
||||
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
|
||||
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
||||
|
||||
class MonitorPortSubscription {
|
||||
public:
|
||||
int64_t subscriptionHandle;
|
||||
int64_t instanceid;
|
||||
std::string key;
|
||||
float updateInterval;
|
||||
PortMonitorCallback onUpdate;
|
||||
virtual void OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
LV2_URID pathPatchProperty,
|
||||
LV2_Atom *pathProperty) = 0;
|
||||
|
||||
};
|
||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) = 0;
|
||||
|
||||
class IAudioHostCallbacks {
|
||||
public:
|
||||
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
|
||||
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
||||
// virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
|
||||
// virtual void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
|
||||
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) = 0;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) = 0;
|
||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
|
||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
||||
};
|
||||
|
||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) = 0;
|
||||
class JackHostStatus
|
||||
{
|
||||
public:
|
||||
bool active_ = false;
|
||||
std::string errorMessage_;
|
||||
bool restarting_;
|
||||
uint64_t underruns_;
|
||||
float cpuUsage_ = 0;
|
||||
uint64_t msSinceLastUnderrun_ = 0;
|
||||
int32_t temperaturemC_ = -100000;
|
||||
uint64_t cpuFreqMax_ = 0;
|
||||
uint64_t cpuFreqMin_ = 0;
|
||||
std::string governor_;
|
||||
|
||||
//virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
|
||||
//virtual void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
|
||||
DECLARE_JSON_MAP(JackHostStatus);
|
||||
};
|
||||
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
|
||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
|
||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
||||
class IHost;
|
||||
|
||||
};
|
||||
class AudioHost
|
||||
{
|
||||
protected:
|
||||
AudioHost() {}
|
||||
|
||||
public:
|
||||
static AudioHost *CreateInstance(IHost *pHost);
|
||||
virtual ~AudioHost() {};
|
||||
|
||||
class JackHostStatus {
|
||||
public:
|
||||
bool active_ = false;
|
||||
std::string errorMessage_;
|
||||
bool restarting_;
|
||||
uint64_t underruns_;
|
||||
float cpuUsage_ = 0;
|
||||
uint64_t msSinceLastUnderrun_ = 0;
|
||||
int32_t temperaturemC_ = -100000;
|
||||
uint64_t cpuFreqMax_ = 0;
|
||||
uint64_t cpuFreqMin_ = 0;
|
||||
std::string governor_;
|
||||
virtual void UpdateServerConfiguration(const JackServerSettings &jackServerSettings,
|
||||
std::function<void(bool success, const std::string &errorMessage)> onComplete) = 0;
|
||||
|
||||
DECLARE_JSON_MAP(JackHostStatus);
|
||||
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
|
||||
|
||||
virtual void SetListenForMidiEvent(bool listen) = 0;
|
||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||
|
||||
};
|
||||
virtual bool UpdatePluginStates(Pedalboard &pedalboard) = 0;
|
||||
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) = 0;
|
||||
|
||||
virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
|
||||
|
||||
class IHost;
|
||||
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
class AudioHost {
|
||||
protected:
|
||||
AudioHost() { }
|
||||
public:
|
||||
static AudioHost*CreateInstance(IHost *pHost);
|
||||
virtual ~AudioHost() { };
|
||||
virtual uint32_t GetSampleRate() = 0;
|
||||
|
||||
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
|
||||
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0;
|
||||
virtual JackConfiguration GetServerConfiguration() = 0;
|
||||
|
||||
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
|
||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||
|
||||
virtual void SetListenForMidiEvent(bool listen) = 0;
|
||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||
virtual void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) = 0;
|
||||
virtual void SetInputVolume(float value) = 0;
|
||||
virtual void SetOutputVolume(float value) = 0;
|
||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) = 0;
|
||||
virtual void SetBypass(uint64_t instanceId, bool enabled) = 0;
|
||||
|
||||
virtual bool UpdatePluginStates(Pedalboard& pedalboard) = 0;
|
||||
virtual bool UpdatePluginState(PedalboardItem& pedalboardItem) = 0;
|
||||
virtual bool IsOpen() const = 0;
|
||||
|
||||
virtual std::string AtomToJson(const LV2_Atom*atom) = 0;
|
||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
|
||||
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
|
||||
|
||||
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
|
||||
virtual void Close() = 0;
|
||||
virtual void SetSystemMidiBindings(const std::vector<MidiBinding> &bindings) = 0;
|
||||
|
||||
virtual uint32_t GetSampleRate() = 0;
|
||||
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) = 0;
|
||||
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
||||
|
||||
virtual JackConfiguration GetServerConfiguration() = 0;
|
||||
virtual JackHostStatus getJackStatus() = 0;
|
||||
|
||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||
virtual void LoadSnapshot(Snapshot &snapshot, PluginHost &pluginHost) = 0;
|
||||
|
||||
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
|
||||
virtual void SetInputVolume(float value) = 0;
|
||||
virtual void SetOutputVolume(float value) = 0;
|
||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) = 0;
|
||||
virtual void SetBypass(uint64_t instanceId, bool enabled) = 0;
|
||||
virtual void OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
const std::string &pathPatchPropertyUri,
|
||||
const std::string &jsonAtom) = 0;
|
||||
};
|
||||
|
||||
virtual bool IsOpen() const = 0;
|
||||
|
||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
|
||||
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
|
||||
|
||||
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
|
||||
|
||||
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest*pParameterRequest) = 0;
|
||||
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
||||
|
||||
virtual JackHostStatus getJackStatus() = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} //namespace pipedal.
|
||||
} // namespace pipedal.
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
presets_.clear();
|
||||
selectedPreset_ = -1;
|
||||
}
|
||||
|
||||
void move(size_t from, size_t to)
|
||||
{
|
||||
if (from >= this->presets_.size()) {
|
||||
|
||||
@@ -146,6 +146,8 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
PatchPropertyWriter.hpp
|
||||
PresetBundle.cpp PresetBundle.hpp
|
||||
RealtimeMidiEventType.hpp
|
||||
DBusToLv2Log.cpp DBusToLv2Log.hpp
|
||||
HotspotManager.cpp HotspotManager.hpp
|
||||
|
||||
@@ -691,6 +691,10 @@ void SetVarPermissions(
|
||||
{
|
||||
if (fs::exists(path))
|
||||
{
|
||||
if (fs::is_symlink(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::ignore = chown(path.c_str(), uid, gid);
|
||||
if (fs::is_directory(path))
|
||||
{
|
||||
|
||||
+12
-3
@@ -32,8 +32,19 @@ namespace pipedal {
|
||||
public:
|
||||
virtual ~IEffect() {}
|
||||
virtual uint64_t GetInstanceId() const = 0;
|
||||
virtual bool IsLv2Effect() const = 0;
|
||||
virtual uint64_t GetMaxInputControl() const = 0;
|
||||
virtual bool IsInputControl(uint64_t index) const = 0;
|
||||
virtual float GetDefaultInputControlValue(uint64_t index) const = 0;
|
||||
|
||||
virtual int GetControlIndex(const std::string&symbol) const = 0;
|
||||
virtual void SetControl(int index, float value) = 0;
|
||||
|
||||
virtual void SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value) = 0;
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri) = 0;
|
||||
virtual void RequestAllPathPatchProperties() = 0;
|
||||
|
||||
|
||||
virtual float GetControlValue(int index) const = 0;
|
||||
virtual void SetBypass(bool enable) = 0;
|
||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||
@@ -47,9 +58,6 @@ namespace pipedal {
|
||||
virtual bool GetRequestStateChangedNotification() const = 0;
|
||||
virtual void SetRequestStateChangedNotification(bool value) = 0;
|
||||
|
||||
//virtual std::string AtomToJson(uint8_t*pAtom) = 0;
|
||||
//virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
|
||||
|
||||
|
||||
virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
|
||||
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
|
||||
@@ -62,6 +70,7 @@ namespace pipedal {
|
||||
|
||||
virtual bool IsVst3() const = 0;
|
||||
virtual bool GetLv2State(Lv2PluginState*state) = 0;
|
||||
virtual void SetLv2State(Lv2PluginState&state) = 0;
|
||||
|
||||
virtual bool HasErrorMessage() const = 0;
|
||||
virtual const char*TakeErrorMessage() = 0;
|
||||
|
||||
+178
-12
@@ -50,7 +50,7 @@ Lv2Effect::Lv2Effect(
|
||||
IHost *pHost_,
|
||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||
PedalboardItem &pedalboardItem)
|
||||
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost)
|
||||
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost), instanceId(pedalboardItem.instanceId())
|
||||
{
|
||||
auto pWorld = pHost_->getWorld();
|
||||
|
||||
@@ -60,6 +60,23 @@ Lv2Effect::Lv2Effect(
|
||||
|
||||
this->bypass = pedalboardItem.isEnabled();
|
||||
|
||||
// stash a list of known file properties that we want to keep synced.
|
||||
if (info->piPedalUI())
|
||||
{
|
||||
for (auto fileProperty : info->piPedalUI()->fileProperties())
|
||||
{
|
||||
LV2_URID filePropertyUrid = pHost->GetLv2Urid(fileProperty->patchProperty().c_str());
|
||||
this->pathProperties.push_back(filePropertyUrid);
|
||||
|
||||
this->pathPropertyWriters.push_back(PatchPropertyWriter(instanceId, filePropertyUrid));
|
||||
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||
}
|
||||
}
|
||||
for (auto &pathProperty: pedalboardItem.pathProperties_)
|
||||
{
|
||||
SetPathPatchProperty(pathProperty.first,pathProperty.second);
|
||||
}
|
||||
|
||||
// initialize the atom forge used on the realtime thread.
|
||||
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
||||
lv2_atom_forge_init(&inputForgeRt, map);
|
||||
@@ -146,9 +163,28 @@ Lv2Effect::Lv2Effect(
|
||||
|
||||
this->instanceId = pedalboardItem.instanceId();
|
||||
|
||||
this->controlValues.resize(info->ports().size());
|
||||
PreparePortIndices();
|
||||
|
||||
// Copy default pedalboard settings.
|
||||
size_t maxPortIndex = 0;
|
||||
std::vector<std::shared_ptr<Lv2PortInfo>> &t = info->ports();
|
||||
for (std::shared_ptr<Lv2PortInfo> &port : info->ports())
|
||||
{
|
||||
if (port->is_control_port())
|
||||
{
|
||||
auto index = port->index();
|
||||
if (index + 1 > maxPortIndex)
|
||||
{
|
||||
maxPortIndex = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxPortIndex > info->ports().size())
|
||||
{
|
||||
throw std::runtime_error("Ports are not consecutive");
|
||||
}
|
||||
|
||||
this->controlValues.resize(info->ports().size());
|
||||
for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i)
|
||||
{
|
||||
auto &v = (*i);
|
||||
@@ -158,7 +194,7 @@ Lv2Effect::Lv2Effect(
|
||||
this->controlValues[index] = v.value();
|
||||
}
|
||||
}
|
||||
PreparePortIndices();
|
||||
|
||||
ConnectControlPorts();
|
||||
|
||||
if (!pedalboardItem.lilvPresetUri().empty())
|
||||
@@ -194,7 +230,9 @@ void Lv2Effect::RestoreState(PedalboardItem &pedalboardItem)
|
||||
if (pedalboardItem.lv2State().isValid_)
|
||||
{
|
||||
this->stateInterface->Restore(pedalboardItem.lv2State());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// set the state to default state.
|
||||
auto savedState = this->stateInterface->Save();
|
||||
pedalboardItem.lv2State(savedState);
|
||||
@@ -234,6 +272,9 @@ void Lv2Effect::ConnectControlPorts()
|
||||
}
|
||||
void Lv2Effect::PreparePortIndices()
|
||||
{
|
||||
size_t nPorts = info->ports().size();
|
||||
isInputControlPort.resize(nPorts);
|
||||
this->defaultInputControlValues.resize(nPorts);
|
||||
|
||||
for (int i = 0; i < info->ports().size(); ++i)
|
||||
{
|
||||
@@ -270,7 +311,23 @@ void Lv2Effect::PreparePortIndices()
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (port->is_control_port())
|
||||
{
|
||||
controlIndex[port->symbol()] = port->index();
|
||||
if (port->is_input())
|
||||
{
|
||||
this->isInputControlPort[i] = true;
|
||||
this->defaultInputControlValues[i] = port->default_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t maxInputControlPort = isInputControlPort.size();
|
||||
while (maxInputControlPort != 0 && !isInputControlPort[maxInputControlPort - 1])
|
||||
{
|
||||
--maxInputControlPort;
|
||||
}
|
||||
this->maxInputControlPort = maxInputControlPort;
|
||||
|
||||
inputAudioBuffers.resize(inputAudioPortIndices.size());
|
||||
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
||||
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
||||
@@ -323,13 +380,12 @@ void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
||||
|
||||
int Lv2Effect::GetControlIndex(const std::string &key) const
|
||||
{
|
||||
for (int i = 0; i < info->ports().size(); ++i)
|
||||
auto i = controlIndex.find(key);
|
||||
if (i == controlIndex.end())
|
||||
{
|
||||
auto &port = info->ports()[i];
|
||||
if (port->symbol() == key)
|
||||
return port->index();
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
Lv2Effect::~Lv2Effect()
|
||||
@@ -438,7 +494,6 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
worker->EmitResponses();
|
||||
}
|
||||
|
||||
|
||||
// do soft bypass.
|
||||
if (this->bypassSamplesRemaining == 0)
|
||||
{
|
||||
@@ -610,6 +665,14 @@ void Lv2Effect::RequestPatchProperty(LV2_URID uridUri)
|
||||
lv2_atom_forge_urid(&inputForgeRt, uridUri);
|
||||
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
|
||||
}
|
||||
void Lv2Effect::RequestAllPathPatchProperties()
|
||||
{
|
||||
for (LV2_URID urid : this->pathProperties)
|
||||
{
|
||||
RequestPatchProperty(urid);
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value)
|
||||
{
|
||||
lv2_atom_forge_frame_time(&inputForgeRt, 0);
|
||||
@@ -660,12 +723,68 @@ void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId, RealtimeRingBufferWri
|
||||
{
|
||||
requestStateChangedNotification = false;
|
||||
realtimeRingBufferWriter->Lv2StateChanged(instanceId);
|
||||
} else if (maybeStateChanged)
|
||||
}
|
||||
else if (maybeStateChanged)
|
||||
{
|
||||
realtimeRingBufferWriter->MaybeLv2StateChanged(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter)
|
||||
{
|
||||
if (pathPropertyWriters.size() != 0)
|
||||
{
|
||||
LV2_Atom_Sequence *controlInput = (LV2_Atom_Sequence *)GetAtomOutputBuffer();
|
||||
if (controlInput == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||
{
|
||||
|
||||
auto frame_offset = ev->time.frames; // not really interested.
|
||||
|
||||
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||
{
|
||||
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
|
||||
if (obj->body.otype == urids.patch__Set)
|
||||
{
|
||||
// Get the property and value of the set message
|
||||
const LV2_Atom *property = NULL;
|
||||
const LV2_Atom *value = NULL;
|
||||
|
||||
lv2_atom_object_get(
|
||||
obj,
|
||||
urids.patch__property, &property,
|
||||
urids.patch__value, &value,
|
||||
0);
|
||||
|
||||
if (property && property->type == urids.atom__URID && value)
|
||||
{
|
||||
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
|
||||
|
||||
for (PatchPropertyWriter &pathPropertyWriter : pathPropertyWriters)
|
||||
{
|
||||
if (key == pathPropertyWriter.patchPropertyUrid)
|
||||
{
|
||||
auto buffer = pathPropertyWriter.AquireWriteBuffer();
|
||||
size_t atom_size = value->size + sizeof(LV2_Atom);
|
||||
buffer->memory.resize(atom_size);
|
||||
memcpy(buffer->memory.data(), value, atom_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &writer : this->pathPropertyWriters)
|
||||
{
|
||||
writer.FlushWrites(cbPatchWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
||||
{
|
||||
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
@@ -711,6 +830,27 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::SetLv2State(Lv2PluginState &state)
|
||||
{
|
||||
if (state.isValid_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!this->stateInterface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
this->stateInterface->Restore(state);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error("Failed to restore LV2 state.");
|
||||
}
|
||||
}
|
||||
bool Lv2Effect::GetLv2State(Lv2PluginState *state)
|
||||
{
|
||||
if (!this->stateInterface)
|
||||
@@ -755,5 +895,31 @@ void Lv2Effect::OnLogDebug(const char *message)
|
||||
Lv2Log::debug(message);
|
||||
}
|
||||
|
||||
bool Lv2Effect::GetRequestStateChangedNotification() const{ return requestStateChangedNotification; }
|
||||
bool Lv2Effect::GetRequestStateChangedNotification() const { return requestStateChangedNotification; }
|
||||
void Lv2Effect::SetRequestStateChangedNotification(bool value) { requestStateChangedNotification = value; }
|
||||
|
||||
uint64_t Lv2Effect::GetMaxInputControl() const { return maxInputControlPort; }
|
||||
|
||||
bool Lv2Effect::IsInputControl(uint64_t index) const
|
||||
{
|
||||
if (index < 0 || index >= isInputControlPort.size())
|
||||
return false;
|
||||
return isInputControlPort[index];
|
||||
}
|
||||
float Lv2Effect::GetDefaultInputControlValue(uint64_t index) const
|
||||
{
|
||||
return defaultInputControlValues[index];
|
||||
}
|
||||
|
||||
std::string Lv2Effect::GetPathPatchProperty(const std::string &propertyUri)
|
||||
{
|
||||
if (!this->mainThreadPathProperties.contains(propertyUri))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return this->mainThreadPathProperties[propertyUri];
|
||||
}
|
||||
void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::string &jsonAtom)
|
||||
{
|
||||
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||
}
|
||||
|
||||
+39
-7
@@ -25,6 +25,8 @@
|
||||
#include <lilv/lilv.h>
|
||||
#include "BufferPool.hpp"
|
||||
#include "FileBrowserFilesFeature.hpp"
|
||||
#include "PatchPropertyWriter.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
#include "IEffect.hpp"
|
||||
#include "Worker.hpp"
|
||||
@@ -42,6 +44,7 @@ namespace pipedal
|
||||
{
|
||||
|
||||
class RealtimeRingBufferWriter;
|
||||
class IPatchWriterCallback;
|
||||
|
||||
class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
|
||||
{
|
||||
@@ -52,6 +55,8 @@ namespace pipedal
|
||||
virtual void OnLogDebug(const char*message);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string,int> controlIndex;
|
||||
|
||||
FileBrowserFilesFeature fileBrowserFilesFeature;
|
||||
std::unique_ptr<StateInterface> stateInterface;
|
||||
void RestoreState(PedalboardItem&pedalboardItem);
|
||||
@@ -86,6 +91,11 @@ namespace pipedal
|
||||
std::vector<const LV2_Feature *> features;
|
||||
LV2_Feature *work_schedule_feature = nullptr;
|
||||
|
||||
|
||||
uint64_t maxInputControlPort = 0;
|
||||
std::vector<bool> isInputControlPort;
|
||||
std::vector<float> defaultInputControlValues;
|
||||
|
||||
virtual std::string GetUri() const { return info->uri(); }
|
||||
|
||||
std::vector<const Lv2PortInfo *> realtimePortInfo;
|
||||
@@ -99,6 +109,10 @@ namespace pipedal
|
||||
|
||||
LV2_Atom_Forge outputForgeRt;
|
||||
|
||||
std::vector<LV2_URID> pathProperties;
|
||||
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||
|
||||
std::unordered_map<std::string,std::string> mainThreadPathProperties;
|
||||
|
||||
class Urids
|
||||
{
|
||||
@@ -164,6 +178,7 @@ namespace pipedal
|
||||
uint32_t size,
|
||||
const void *data);
|
||||
|
||||
|
||||
void ResetInputAtomBuffer(char*data);
|
||||
void ResetOutputAtomBuffer(char*data);
|
||||
|
||||
@@ -179,17 +194,29 @@ namespace pipedal
|
||||
|
||||
void BypassTo(float value);
|
||||
|
||||
|
||||
public:
|
||||
|
||||
virtual bool GetLv2State(Lv2PluginState*state);
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri);
|
||||
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
|
||||
virtual bool GetRequestStateChangedNotification() const;
|
||||
virtual void SetRequestStateChangedNotification(bool value);
|
||||
// non RT-thread use only.
|
||||
std::string GetPathPatchProperty(const std::string&propertyUri);
|
||||
// non RT-thread use only.
|
||||
void SetPathPatchProperty(const std::string &propertyUri, const std::string&jsonAtom);
|
||||
|
||||
virtual bool IsLv2Effect() const { return true; }
|
||||
virtual bool GetLv2State(Lv2PluginState*state) override;
|
||||
virtual void SetLv2State(Lv2PluginState&state) override;
|
||||
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri) ;
|
||||
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value) override;
|
||||
virtual void RequestAllPathPatchProperties();
|
||||
|
||||
virtual bool GetRequestStateChangedNotification() const override;
|
||||
virtual void SetRequestStateChangedNotification(bool value) override;
|
||||
|
||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest);
|
||||
void GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter);
|
||||
virtual bool IsVst3() const { return false; }
|
||||
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) ;
|
||||
|
||||
virtual uint8_t*GetAtomInputBuffer() {
|
||||
if (this->inputAtomBuffers.size() == 0) return nullptr;
|
||||
@@ -240,7 +267,7 @@ namespace pipedal
|
||||
|
||||
virtual int GetControlIndex(const std::string &symbol) const;
|
||||
|
||||
virtual void SetControl(int index, float value)
|
||||
virtual void SetControl(int index, float value) override
|
||||
{
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -250,6 +277,11 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
|
||||
virtual uint64_t GetMaxInputControl() const override;
|
||||
virtual bool IsInputControl(uint64_t index) const override;
|
||||
virtual float GetDefaultInputControlValue(uint64_t index) const override;
|
||||
|
||||
|
||||
virtual float GetControlValue(int index) const {
|
||||
if (index == -1)
|
||||
{
|
||||
|
||||
+34
-14
@@ -507,6 +507,19 @@ void Lv2Pedalboard::ResetAtomBuffers()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Lv2Pedalboard::GatherPathPatchProperties(IPatchWriterCallback*cbPatchWriter)
|
||||
{
|
||||
for (auto& pEffect : this->effects) {
|
||||
if (pEffect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *pLv2Effect = (Lv2Effect*)pEffect.get();
|
||||
pLv2Effect->GatherPathPatchProperties(cbPatchWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
|
||||
{
|
||||
while (pParameterRequests != nullptr)
|
||||
@@ -522,25 +535,29 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(pEffect);
|
||||
if (pEffect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(pEffect);
|
||||
|
||||
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
pLv2Effect->RequestPatchProperty(pParameterRequests->uridUri);
|
||||
}
|
||||
else if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchSet)
|
||||
{
|
||||
pLv2Effect->SetPatchProperty(
|
||||
pParameterRequests->uridUri,
|
||||
pParameterRequests->GetSize(),
|
||||
(LV2_Atom *)pParameterRequests->GetBuffer());
|
||||
pLv2Effect->SetRequestStateChangedNotification(true);
|
||||
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
pLv2Effect->RequestPatchProperty(pParameterRequests->uridUri);
|
||||
}
|
||||
else if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchSet)
|
||||
{
|
||||
pLv2Effect->SetPatchProperty(
|
||||
pParameterRequests->uridUri,
|
||||
pParameterRequests->GetSize(),
|
||||
(LV2_Atom *)pParameterRequests->GetBuffer());
|
||||
pLv2Effect->SetRequestStateChangedNotification(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
pParameterRequests = pParameterRequests->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
|
||||
{
|
||||
while (pParameterRequests != nullptr)
|
||||
@@ -558,8 +575,11 @@ void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParamet
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(effect);
|
||||
pLv2Effect->GatherPatchProperties(pParameterRequests);
|
||||
if (effect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(effect);
|
||||
pLv2Effect->GatherPatchProperties(pParameterRequests);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+114
-112
@@ -27,140 +27,142 @@
|
||||
#include <functional>
|
||||
#include "DbDezipper.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
|
||||
class RealtimeVuBuffers;
|
||||
class RealtimePatchPropertyRequest;
|
||||
class RealtimeRingBufferWriter;
|
||||
|
||||
struct Lv2PedalboardError {
|
||||
int64_t intanceId;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class Lv2PedalboardErrorList: public std::vector<Lv2PedalboardError> // (forward declaration issues with a using statement)
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
};
|
||||
class IPatchWriterCallback;
|
||||
class RealtimeVuBuffers;
|
||||
class RealtimePatchPropertyRequest;
|
||||
class RealtimeRingBufferWriter;
|
||||
|
||||
class Lv2Pedalboard {
|
||||
IHost *pHost = nullptr;
|
||||
|
||||
DbDezipper inputVolume;
|
||||
DbDezipper outputVolume;
|
||||
|
||||
BufferPool bufferPool;
|
||||
std::vector<float*> pedalboardInputBuffers;
|
||||
std::vector<float*> pedalboardOutputBuffers;
|
||||
|
||||
std::vector<std::shared_ptr<IEffect> > effects;
|
||||
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
|
||||
|
||||
using Action = std::function<void()>;
|
||||
using ProcessAction = std::function<void(uint32_t frames)>;
|
||||
|
||||
std::vector<Action> activateActions;
|
||||
|
||||
std::vector<ProcessAction> processActions;
|
||||
|
||||
std::vector<Action> deactivateActions;
|
||||
|
||||
float *CreateNewAudioBuffer();
|
||||
|
||||
RealtimeRingBufferWriter *ringBufferWriter;
|
||||
|
||||
enum class MappingType {
|
||||
Linear,
|
||||
Circular,
|
||||
Momentary,
|
||||
Latched,
|
||||
};
|
||||
class MidiMapping {
|
||||
public:
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo; // lifecycle
|
||||
const Lv2PortInfo *pPortInfo = nullptr; // owned by port.
|
||||
int instanceId = -1;
|
||||
int effectIndex = -1;
|
||||
int controlIndex = -1;
|
||||
int key; // key to the note or control. internal use only.
|
||||
bool hasLastValue = false;
|
||||
float lastValue = -1;
|
||||
MappingType mappingType;
|
||||
MidiBinding midiBinding;
|
||||
struct Lv2PedalboardError
|
||||
{
|
||||
int64_t intanceId;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
std::vector<MidiMapping> midiMappings;
|
||||
class Lv2PedalboardErrorList : public std::vector<Lv2PedalboardError> // (forward declaration issues with a using statement)
|
||||
{
|
||||
};
|
||||
|
||||
class Lv2Pedalboard
|
||||
{
|
||||
IHost *pHost = nullptr;
|
||||
|
||||
std::vector<float*> PrepareItems(
|
||||
std::vector<PedalboardItem> & items,
|
||||
std::vector<float*> inputBuffers,
|
||||
Lv2PedalboardErrorList &errorList
|
||||
);
|
||||
DbDezipper inputVolume;
|
||||
DbDezipper outputVolume;
|
||||
|
||||
void PrepareMidiMap(const Pedalboard&pedalboard);
|
||||
void PrepareMidiMap(const PedalboardItem&pedalboardItem);
|
||||
BufferPool bufferPool;
|
||||
std::vector<float *> pedalboardInputBuffers;
|
||||
std::vector<float *> pedalboardOutputBuffers;
|
||||
|
||||
std::vector<float*> AllocateAudioBuffers(int nChannels);
|
||||
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
|
||||
void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
|
||||
public:
|
||||
Lv2Pedalboard() { }
|
||||
~Lv2Pedalboard() { }
|
||||
std::vector<std::shared_ptr<IEffect>> effects;
|
||||
std::vector<IEffect *> realtimeEffects;
|
||||
|
||||
void Prepare(IHost *pHost,Pedalboard&pedalboard, Lv2PedalboardErrorList &errorList);
|
||||
using Action = std::function<void()>;
|
||||
using ProcessAction = std::function<void(uint32_t frames)>;
|
||||
|
||||
std::vector<IEffect* > GetEffects() { return realtimeEffects; }
|
||||
std::vector<Action> activateActions;
|
||||
|
||||
int GetIndexOfInstanceId(uint64_t instanceId) {
|
||||
for (int i = 0; i < this->realtimeEffects.size(); ++i)
|
||||
std::vector<ProcessAction> processActions;
|
||||
|
||||
std::vector<Action> deactivateActions;
|
||||
|
||||
float *CreateNewAudioBuffer();
|
||||
|
||||
RealtimeRingBufferWriter *ringBufferWriter;
|
||||
|
||||
enum class MappingType
|
||||
{
|
||||
if (this->realtimeEffects[i]->GetInstanceId() == instanceId) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
IEffect*GetEffect(uint64_t instanceId) {
|
||||
for (int i = 0; i < realtimeEffects.size(); ++i) {
|
||||
if (realtimeEffects[i]->GetInstanceId() == instanceId)
|
||||
Linear,
|
||||
Circular,
|
||||
Momentary,
|
||||
Latched,
|
||||
};
|
||||
class MidiMapping
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo; // lifecycle
|
||||
const Lv2PortInfo *pPortInfo = nullptr; // owned by port.
|
||||
int instanceId = -1;
|
||||
int effectIndex = -1;
|
||||
int controlIndex = -1;
|
||||
int key; // key to the note or control. internal use only.
|
||||
bool hasLastValue = false;
|
||||
float lastValue = -1;
|
||||
MappingType mappingType;
|
||||
MidiBinding midiBinding;
|
||||
};
|
||||
|
||||
std::vector<MidiMapping> midiMappings;
|
||||
|
||||
std::vector<float *> PrepareItems(
|
||||
std::vector<PedalboardItem> &items,
|
||||
std::vector<float *> inputBuffers,
|
||||
Lv2PedalboardErrorList &errorList);
|
||||
|
||||
void PrepareMidiMap(const Pedalboard &pedalboard);
|
||||
void PrepareMidiMap(const PedalboardItem &pedalboardItem);
|
||||
|
||||
std::vector<float *> AllocateAudioBuffers(int nChannels);
|
||||
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
|
||||
void AppendParameterRequest(uint8_t *atomBuffer, LV2_URID uridParameter);
|
||||
|
||||
public:
|
||||
Lv2Pedalboard() {}
|
||||
~Lv2Pedalboard() {}
|
||||
|
||||
void Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList);
|
||||
|
||||
std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
|
||||
|
||||
int GetIndexOfInstanceId(uint64_t instanceId)
|
||||
{
|
||||
for (int i = 0; i < this->realtimeEffects.size(); ++i)
|
||||
{
|
||||
return realtimeEffects[i];
|
||||
if (this->realtimeEffects[i]->GetInstanceId() == instanceId)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
bool Run(float**inputBuffers, float**outputBuffers,uint32_t samples, RealtimeRingBufferWriter*realtimeWriter);
|
||||
IEffect *GetEffect(uint64_t instanceId)
|
||||
{
|
||||
for (int i = 0; i < realtimeEffects.size(); ++i)
|
||||
{
|
||||
if (realtimeEffects[i]->GetInstanceId() == instanceId)
|
||||
{
|
||||
return realtimeEffects[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
bool Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter *realtimeWriter);
|
||||
|
||||
void ResetAtomBuffers();
|
||||
void ResetAtomBuffers();
|
||||
|
||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter);
|
||||
|
||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
std::vector<float *> &GetInputBuffers() { return this->pedalboardInputBuffers; }
|
||||
std::vector<float *> &GetoutputBuffers() { return this->pedalboardOutputBuffers; }
|
||||
|
||||
int GetControlIndex(uint64_t instanceId, const std::string &symbol);
|
||||
void SetControlValue(int effectIndex, int portIndex, float value);
|
||||
void SetInputVolume(float value) { this->inputVolume.SetTarget(value); }
|
||||
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value); }
|
||||
void SetBypass(int effectIndex, bool enabled);
|
||||
|
||||
std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
|
||||
std::vector<float*> &GetoutputBuffers() { return this->pedalboardOutputBuffers;}
|
||||
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers);
|
||||
|
||||
float GetControlOutputValue(int effectIndex, int portIndex);
|
||||
|
||||
|
||||
int GetControlIndex(uint64_t instanceId,const std::string&symbol);
|
||||
void SetControlValue(int effectIndex, int portIndex, float value);
|
||||
void SetInputVolume(float value) { this->inputVolume.SetTarget(value);}
|
||||
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value);}
|
||||
void SetBypass(int effectIndex,bool enabled);
|
||||
|
||||
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers);
|
||||
|
||||
float GetControlOutputValue(int effectIndex, int portIndex);
|
||||
|
||||
typedef void (MidiCallbackFn) (void* data,uint64_t intanceId, int controlIndex, float value);
|
||||
void OnMidiMessage(size_t size, uint8_t*data,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn*pfnCallback);
|
||||
|
||||
};
|
||||
typedef void(MidiCallbackFn)(void *data, uint64_t intanceId, int controlIndex, float value);
|
||||
void OnMidiMessage(size_t size, uint8_t *data,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2024 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include "util.hpp"
|
||||
#include <lv2/atom/atom.h>
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
// A mechanism for writing (path) patch properties back to the non-realtime threads.
|
||||
// Has the following characteristics:
|
||||
// 1. Only one property in flight to the non-realtime thread at any givien moment.
|
||||
// 2. No blocking, no spinning on the realtime thread. RT operations require nothing other than atomic operations.
|
||||
// 3. Multiple responses in the same RT frame are coaslesced into one response (the latest).
|
||||
// 4. Responses that are found while a property notification is in flight are coaslesced.
|
||||
|
||||
class IPatchWriterCallback;
|
||||
|
||||
class PatchPropertyWriter
|
||||
{
|
||||
private:
|
||||
enum class StateT : char
|
||||
{
|
||||
Empty,
|
||||
WriteStarted,
|
||||
WriteLocked, // written data is available.
|
||||
ReadLocked, // data is in flight between the realtime thread and non-realtime service thread.
|
||||
Deleted // detect use after free.
|
||||
};
|
||||
|
||||
public:
|
||||
PatchPropertyWriter(int64_t instanceId, LV2_URID patchPropertyUrid)
|
||||
: instanceId(instanceId), patchPropertyUrid(patchPropertyUrid),
|
||||
buffer0(new Buffer(instanceId, patchPropertyUrid)),
|
||||
buffer1(new Buffer(instanceId, patchPropertyUrid))
|
||||
{
|
||||
}
|
||||
// no copy.
|
||||
PatchPropertyWriter(const PatchPropertyWriter&) = delete;
|
||||
// move
|
||||
PatchPropertyWriter(PatchPropertyWriter&&other) {
|
||||
std::swap(this->buffer0,other.buffer0);
|
||||
std::swap(this->buffer1,other.buffer1);
|
||||
this->currentWriteBuffer = nullptr;
|
||||
this->instanceId = other.instanceId;
|
||||
this->patchPropertyUrid = other.patchPropertyUrid;
|
||||
|
||||
}
|
||||
|
||||
~PatchPropertyWriter()
|
||||
{
|
||||
delete buffer0;
|
||||
delete buffer1;
|
||||
}
|
||||
class Buffer
|
||||
{
|
||||
public:
|
||||
std::atomic<StateT> state{StateT::Empty};
|
||||
|
||||
Buffer(int64_t instanceId, LV2_URID propertyUrid)
|
||||
: instanceId(instanceId), patchPropertyUrid(propertyUrid)
|
||||
{
|
||||
memory.reserve(1024);
|
||||
}
|
||||
|
||||
void OnBufferWriteStarted()
|
||||
{
|
||||
if (state != StateT::Empty && state != StateT::WriteLocked)
|
||||
{
|
||||
throw std::runtime_error("Bad state.");
|
||||
}
|
||||
state = StateT::WriteStarted;
|
||||
}
|
||||
void OnBufferWritten()
|
||||
{
|
||||
if (state != StateT::WriteStarted)
|
||||
{
|
||||
throw std::runtime_error("Bad state.");
|
||||
}
|
||||
state = StateT::ReadLocked;
|
||||
WriteBarrier();
|
||||
}
|
||||
|
||||
void OnBufferReadStarted()
|
||||
{
|
||||
if (state != StateT::ReadLocked)
|
||||
{
|
||||
throw std::runtime_error("Bad state.");
|
||||
}
|
||||
ReadBarrier();
|
||||
}
|
||||
void OnBufferReadComplete()
|
||||
{
|
||||
if (state != StateT::ReadLocked)
|
||||
{
|
||||
throw std::runtime_error("Bad state.");
|
||||
}
|
||||
state = StateT::Empty;
|
||||
}
|
||||
int64_t instanceId;
|
||||
LV2_URID patchPropertyUrid;
|
||||
|
||||
std::vector<uint8_t> memory;
|
||||
};
|
||||
|
||||
// RT thread only.
|
||||
Buffer *AquireWriteBuffer()
|
||||
{
|
||||
// RT only.
|
||||
if (currentWriteBuffer)
|
||||
{
|
||||
return currentWriteBuffer;
|
||||
}
|
||||
if (buffer0->state == StateT::Empty)
|
||||
{
|
||||
buffer0->OnBufferWriteStarted();
|
||||
currentWriteBuffer = buffer0;
|
||||
return currentWriteBuffer;
|
||||
}
|
||||
if (buffer1->state == StateT::Empty)
|
||||
{
|
||||
buffer1->OnBufferWriteStarted();
|
||||
currentWriteBuffer = buffer1;
|
||||
return currentWriteBuffer;
|
||||
}
|
||||
throw std::runtime_error("Bad state. Unable to aquire a write buffer.");
|
||||
}
|
||||
// RT thread ony.
|
||||
void OnBufferWritten()
|
||||
{
|
||||
if (currentWriteBuffer)
|
||||
{
|
||||
currentWriteBuffer->OnBufferWritten();
|
||||
currentWriteBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
Buffer *GetCurrentWriteBuffer()
|
||||
{
|
||||
return currentWriteBuffer;
|
||||
}
|
||||
|
||||
void FlushWrites(IPatchWriterCallback*cbWrite);
|
||||
|
||||
int64_t instanceId;
|
||||
LV2_URID patchPropertyUrid;
|
||||
|
||||
private:
|
||||
Buffer *buffer0 = 0;
|
||||
Buffer *buffer1 = 0;
|
||||
Buffer *currentWriteBuffer = nullptr;
|
||||
};
|
||||
|
||||
class IPatchWriterCallback {
|
||||
public:
|
||||
virtual void OnWritePatchPropertyBuffer(
|
||||
PatchPropertyWriter::Buffer *
|
||||
) = 0;
|
||||
};
|
||||
|
||||
inline void PatchPropertyWriter::FlushWrites(IPatchWriterCallback*cbWrite)
|
||||
{
|
||||
auto buffer = GetCurrentWriteBuffer();
|
||||
if (buffer)
|
||||
{
|
||||
// is the OTHER buffer empty?
|
||||
if (buffer0->state == StateT::Empty || buffer1->state == StateT::Empty)
|
||||
{
|
||||
OnBufferWritten();
|
||||
cbWrite->OnWritePatchPropertyBuffer(buffer);
|
||||
} else {
|
||||
// we have an update in flight, so just keep accumulating, and maybe write next time.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
@@ -177,9 +178,200 @@ bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector<Pedalboa
|
||||
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
|
||||
}
|
||||
|
||||
bool Pedalboard::ApplySnapshot(int64_t snapshotIndex)
|
||||
{
|
||||
if (snapshotIndex < 0 ||
|
||||
snapshotIndex >= this->snapshots_.size() ||
|
||||
this->snapshots_[snapshotIndex] == nullptr ||
|
||||
this->selectedSnapshot() == snapshotIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::map<int64_t, SnapshotValue*> indexedValues;
|
||||
Snapshot *snapshot = this->snapshots_[snapshotIndex].get();
|
||||
|
||||
for (auto &value: snapshot->values_)
|
||||
{
|
||||
indexedValues[value.instanceId_] = &value;
|
||||
}
|
||||
|
||||
auto plugins = this->GetAllPlugins();
|
||||
for (PedalboardItem *pedalboardItem: plugins)
|
||||
{
|
||||
SnapshotValue*snapshotValue = indexedValues[pedalboardItem->instanceId()];
|
||||
if (snapshotValue)
|
||||
{
|
||||
pedalboardItem->ApplySnapshotValue(snapshotValue);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PedalboardItem::ApplySnapshotValue(SnapshotValue*snapshotValue)
|
||||
{
|
||||
std::map<std::string,float> cumulativeValues;
|
||||
for (auto &controlValue: this->controlValues())
|
||||
{
|
||||
cumulativeValues[controlValue.key()] = controlValue.value();
|
||||
}
|
||||
for (auto&controlValue : snapshotValue->controlValues_)
|
||||
{
|
||||
cumulativeValues[controlValue.key()] = controlValue.value();
|
||||
}
|
||||
this->controlValues().clear();
|
||||
for (auto&pair: cumulativeValues)
|
||||
{
|
||||
this->controlValues_.push_back(ControlValue(pair.first.c_str(),pair.second));
|
||||
}
|
||||
if (this->lv2State() != snapshotValue->lv2State_)
|
||||
{
|
||||
this->lv2State(snapshotValue->lv2State_);
|
||||
this->stateUpdateCount(this->stateUpdateCount()+1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// can we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
|
||||
bool Pedalboard::IsStructureIdentical(const Pedalboard &other) const
|
||||
{
|
||||
if (this->nextInstanceId_ != other.nextInstanceId_) // quick check that catches 95% of structural changes.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this->items_.size() != other.items_.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < this->items_.size();++i)
|
||||
{
|
||||
if (!this->items_[i].IsStructurallyIdentical(other.items_[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PedalboardItem::IsStructurallyIdentical(const PedalboardItem&other) const
|
||||
{
|
||||
if (this->instanceId() != other.instanceId()) // must match in order to ensure that realtime message passing works.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this->uri() != other.uri())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this->isSplit()) // so is the other by virtue of idential uris.
|
||||
{
|
||||
if (topChain().size() != other.topChain().size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < topChain().size(); ++i)
|
||||
{
|
||||
if (!topChain()[i].IsStructurallyIdentical(other.topChain()[i] ))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (bottomChain().size() != other.bottomChain().size())
|
||||
{
|
||||
for (size_t i = 0; i < bottomChain().size(); ++i)
|
||||
{
|
||||
if (!bottomChain()[i].IsStructurallyIdentical(other.bottomChain()[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PedalboardItem::AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const
|
||||
{
|
||||
SnapshotValue snapshotValue;
|
||||
snapshotValue.instanceId_ = this->instanceId_;
|
||||
|
||||
for (const ControlValue &value: this->controlValues_)
|
||||
{
|
||||
snapshotValue.controlValues_.push_back(value);
|
||||
}
|
||||
for (const auto&pathProperty: this->pathProperties_)
|
||||
{
|
||||
snapshotValue.pathProperties_[pathProperty.first] = pathProperty.second;
|
||||
}
|
||||
snapshotValue.lv2State_ = this->lv2State_;
|
||||
snapshot.values_.push_back(std::move(snapshotValue));
|
||||
|
||||
if (this->isSplit())
|
||||
{
|
||||
for (auto&item: this->topChain_)
|
||||
{
|
||||
item.AddToSnapshotFromCurrentSettings(snapshot);;
|
||||
}
|
||||
for (auto&item: this->bottomChain_)
|
||||
{
|
||||
item.AddToSnapshotFromCurrentSettings(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PedalboardItem::AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const
|
||||
{
|
||||
// structure must be identical
|
||||
// items must be enumerated in the same order as AddToSnapshotFromCurrentSettings
|
||||
SnapshotValue&snapshotValue = snapshot.values_[*index];
|
||||
|
||||
if (snapshotValue.instanceId_ != this->instanceId())
|
||||
{
|
||||
throw std::runtime_error("Pedalboard structure does not match.");
|
||||
}
|
||||
|
||||
for (auto&property: this->pathProperties_)
|
||||
{
|
||||
auto f = snapshotValue.pathProperties_.find(property.first);
|
||||
if (f == snapshotValue.pathProperties_.end())
|
||||
{
|
||||
snapshotValue.pathProperties_[property.first] = AtomConverter::EmptyPathstring();
|
||||
}
|
||||
}
|
||||
++(*index);
|
||||
|
||||
if (this->isSplit())
|
||||
{
|
||||
for (auto&item: this->topChain())
|
||||
{
|
||||
item.AddResetsForMissingProperties(snapshot,index);
|
||||
}
|
||||
for (auto&item: this->bottomChain())
|
||||
{
|
||||
item.AddResetsForMissingProperties(snapshot,index);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard)
|
||||
{
|
||||
Snapshot snapshot;
|
||||
// name and color don't matter. this is strictly for loading purposes.
|
||||
for (auto &item : this->items())
|
||||
{
|
||||
item.AddToSnapshotFromCurrentSettings(snapshot);
|
||||
}
|
||||
// a neccesary precondition: the previous pedalboard must have identical structure,
|
||||
// so we can just
|
||||
size_t index = 0;
|
||||
for (auto&item: previousPedalboard.items_)
|
||||
{
|
||||
item.AddResetsForMissingProperties(snapshot,&index);
|
||||
}
|
||||
return snapshot;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -202,6 +394,7 @@ JSON_MAP_BEGIN(PedalboardItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,stateUpdateCount)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
@@ -211,5 +404,21 @@ JSON_MAP_BEGIN(Pedalboard)
|
||||
JSON_MAP_REFERENCE(Pedalboard,output_volume_db)
|
||||
JSON_MAP_REFERENCE(Pedalboard,items)
|
||||
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
||||
JSON_MAP_REFERENCE(Pedalboard,snapshots)
|
||||
JSON_MAP_REFERENCE(Pedalboard,selectedSnapshot)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(SnapshotValue)
|
||||
JSON_MAP_REFERENCE(SnapshotValue,instanceId)
|
||||
JSON_MAP_REFERENCE(SnapshotValue,controlValues)
|
||||
JSON_MAP_REFERENCE(SnapshotValue,lv2State)
|
||||
JSON_MAP_REFERENCE(SnapshotValue,pathProperties)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(Snapshot)
|
||||
JSON_MAP_REFERENCE(Snapshot,name)
|
||||
JSON_MAP_REFERENCE(Snapshot,color)
|
||||
JSON_MAP_REFERENCE(Snapshot,values)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
+43
-3
@@ -26,7 +26,9 @@
|
||||
#include "atom_object.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class SnapshotValue;
|
||||
class Snapshot;
|
||||
|
||||
#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split"
|
||||
#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty"
|
||||
|
||||
@@ -45,7 +47,9 @@ namespace pipedal {
|
||||
|
||||
#define GETTER_SETTER_VEC(name) \
|
||||
decltype(name##_)& name() { return name##_;} \
|
||||
const decltype(name##_)& name() const { return name##_;}
|
||||
const decltype(name##_)& name() const { return name##_;} \
|
||||
void name(decltype(name##_)&&value) { this->name##_ = std::move(value); }\
|
||||
void name(const decltype(name##_)&value) { this->name##_ = value; }
|
||||
|
||||
|
||||
|
||||
@@ -91,12 +95,16 @@ public:
|
||||
uint32_t stateUpdateCount_ = 0;
|
||||
Lv2PluginState lv2State_;
|
||||
std::string lilvPresetUri_;
|
||||
std::map<std::string,std::string> pathProperties_;
|
||||
|
||||
// non persistent state.
|
||||
PropertyMap patchProperties;
|
||||
public:
|
||||
ControlValue*GetControlValue(const std::string&symbol);
|
||||
|
||||
|
||||
bool IsStructurallyIdentical(const PedalboardItem&other) const;
|
||||
|
||||
void ApplySnapshotValue(SnapshotValue*snapshotValue);
|
||||
bool hasLv2State() const {
|
||||
return lv2State_.isValid_ != 0;
|
||||
}
|
||||
@@ -124,6 +132,10 @@ public:
|
||||
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
|
||||
}
|
||||
|
||||
void AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const;
|
||||
void AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const;
|
||||
|
||||
|
||||
virtual void write_members(json_writer&writer) const {
|
||||
writer.write_member("instanceId",instanceId_);
|
||||
writer.write_member("uri",uri_);
|
||||
@@ -139,6 +151,25 @@ public:
|
||||
DECLARE_JSON_MAP(PedalboardItem);
|
||||
};
|
||||
|
||||
class SnapshotValue {
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::vector<ControlValue> controlValues_;
|
||||
Lv2PluginState lv2State_;
|
||||
std::map<std::string,std::string> pathProperties_;
|
||||
DECLARE_JSON_MAP(SnapshotValue);
|
||||
|
||||
};
|
||||
|
||||
class Snapshot {
|
||||
public:
|
||||
std::string name_;
|
||||
std::string color_;
|
||||
std::vector<SnapshotValue> values_;
|
||||
|
||||
DECLARE_JSON_MAP(Snapshot);
|
||||
|
||||
};
|
||||
|
||||
class Pedalboard {
|
||||
std::string name_;
|
||||
@@ -149,22 +180,31 @@ class Pedalboard {
|
||||
uint64_t nextInstanceId_ = 0;
|
||||
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
||||
|
||||
std::vector<std::shared_ptr<Snapshot>> snapshots_;
|
||||
int64_t selectedSnapshot_ = -1;
|
||||
|
||||
public:
|
||||
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
||||
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
||||
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
||||
|
||||
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
|
||||
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
|
||||
|
||||
PedalboardItem*GetItem(int64_t pedalItemId);
|
||||
const PedalboardItem*GetItem(int64_t pedalItemId) const;
|
||||
std::vector<PedalboardItem*>GetAllPlugins();
|
||||
|
||||
bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
|
||||
bool ApplySnapshot(int64_t snapshotIndex);
|
||||
|
||||
GETTER_SETTER_REF(name)
|
||||
GETTER_SETTER_VEC(items)
|
||||
GETTER_SETTER(input_volume_db)
|
||||
GETTER_SETTER(output_volume_db)
|
||||
GETTER_SETTER_VEC(snapshots)
|
||||
GETTER_SETTER(selectedSnapshot)
|
||||
|
||||
|
||||
DECLARE_JSON_MAP(Pedalboard);
|
||||
|
||||
+360
-101
@@ -89,16 +89,15 @@ PiPedalModel::PiPedalModel()
|
||||
this->OnUpdateStatusChanged(updateStatus);
|
||||
});
|
||||
|
||||
|
||||
DbusLogToLv2Log();
|
||||
SetDBusLogLevel(DBusLogLevel::Info);
|
||||
|
||||
hotspotManager = HotspotManager::Create();
|
||||
hotspotManager->SetNetworkChangingListener(
|
||||
[this](bool ethernetConnected, bool hotspotEnabling) {
|
||||
OnNetworkChanging(ethernetConnected,hotspotEnabling);
|
||||
}
|
||||
);
|
||||
[this](bool ethernetConnected, bool hotspotEnabling)
|
||||
{
|
||||
OnNetworkChanging(ethernetConnected, hotspotEnabling);
|
||||
});
|
||||
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
|
||||
}
|
||||
|
||||
@@ -107,14 +106,14 @@ void PiPedalModel::Close()
|
||||
std::unique_ptr<AudioHost> oldAudioHost;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (closed)
|
||||
if (closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
|
||||
|
||||
if (avahiService) {
|
||||
if (avahiService)
|
||||
{
|
||||
this->avahiService = nullptr; // and close.
|
||||
}
|
||||
|
||||
@@ -361,7 +360,7 @@ void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
|
||||
{
|
||||
// a sent PATCH_Set, or an explicit state changed notification.
|
||||
OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
this->SetPresetChanged(-1, true);
|
||||
this->SetPresetChanged(-1, true, false);
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
||||
@@ -566,6 +565,69 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (this->pedalboard.selectedSnapshot() == selectedSnapshot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this->pedalboard.ApplySnapshot(selectedSnapshot))
|
||||
{
|
||||
if (this->audioHost)
|
||||
{
|
||||
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
|
||||
}
|
||||
SetPresetChanged(-1, true, false);
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
FirePedalboardChanged(-1, false);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshots, int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
// notionally, snapshots are saved whenever they are modified, but pedalboards are not.
|
||||
// so we must add the current snapshots BOTH to the current pedalbaoard, AND to the
|
||||
// current saved instance of the pedalboard to create the illusion that the are stored separately.
|
||||
// In practice, snapshots have to track structure changes (delete pedalboard items that have been deleted,
|
||||
// and set default settings for pedalboard items that have ben added since the snapshots was last saved.
|
||||
//
|
||||
// This is acheived by:
|
||||
// 1. updating BOTH the current pedalboard, and its saved instance.
|
||||
// 2. discarding references to dangling instances as plugins are loaded (see UpdateDefaults).
|
||||
// 3. Setting missing snapshot controls to default values (NOT the current pedalboard values)
|
||||
// 4. (We currently don't do the right thing with patch properties if the patch property has never been set).
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
|
||||
{
|
||||
// stealth update of the saved snapshots.
|
||||
auto savedPedalboard = storage.GetCurrentPreset();
|
||||
savedPedalboard.snapshots(snapshots); // makes a shallow copy
|
||||
|
||||
if (selectedSnapshot != -1)
|
||||
{
|
||||
// implies that this is a snapshot of the currently running pedalboard. so we can mark it as the selected pedalboard.
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
}
|
||||
// UpdateDefaults(&savedPedalboard); // The update is awfully fresh. wait until it gets loaded again before pruning.
|
||||
storage.SaveCurrentPreset(savedPedalboard);
|
||||
}
|
||||
|
||||
this->pedalboard.snapshots(std::move(snapshots));
|
||||
if (selectedSnapshot != -1)
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
}
|
||||
|
||||
this->FirePedalboardChanged(-1, false); // notify clients (but don't change the running pedalboard, because it's still the same)
|
||||
// this means that all clients get an up-to-date copy of the snapshots AND the currently selected snapshot if that applies
|
||||
// (and a fresh copy of the pedalboard settings as well, which is harmless, since they have not changed)
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||
{
|
||||
{
|
||||
@@ -628,12 +690,60 @@ void PiPedalModel::GetBank(int64_t instanceId, BankFile *pResult)
|
||||
this->storage.GetBankFile(instanceId, pResult);
|
||||
}
|
||||
|
||||
void PiPedalModel::SetPresetChanged(int64_t clientId, bool value)
|
||||
void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect)
|
||||
{
|
||||
if (changeSnapshotSelect && value && this->pedalboard.selectedSnapshot() != -1)
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(-1);
|
||||
FireSelectedSnapshotChanged(-1);
|
||||
}
|
||||
if (value != this->hasPresetChanged)
|
||||
{
|
||||
hasPresetChanged = value;
|
||||
FirePresetsChanged(clientId);
|
||||
FirePresetChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||
{
|
||||
t[i] = this->subscribers[i];
|
||||
}
|
||||
size_t n = this->subscribers.size();
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
t[i]->OnSelectedSnapshotChanged(selectedSnapshot);
|
||||
}
|
||||
delete[] t;
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::FirePresetChanged(bool changed)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||
{
|
||||
t[i] = this->subscribers[i];
|
||||
}
|
||||
size_t n = this->subscribers.size();
|
||||
|
||||
PresetIndex presets;
|
||||
GetPresets(&presets);
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
t[i]->OnPresetChanged(changed);
|
||||
}
|
||||
delete[] t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,48 +886,65 @@ int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
||||
return newPreset;
|
||||
}
|
||||
|
||||
void PiPedalModel::NextBank(Direction direction)
|
||||
{
|
||||
|
||||
}
|
||||
void PiPedalModel::NextPreset(Direction direction)
|
||||
{
|
||||
PresetIndex index;
|
||||
|
||||
storage.GetPresetIndex(&index);
|
||||
auto currentPresetId = storage.GetCurrentPresetId();
|
||||
size_t currentPresetIndex = 0;
|
||||
|
||||
for (size_t i = 0; i < index.presets().size(); ++i)
|
||||
{
|
||||
if (index.presets()[i].instanceId() == currentPresetId)
|
||||
{
|
||||
currentPresetIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index.presets().size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (direction == Direction::Decrease)
|
||||
{
|
||||
if (currentPresetIndex == 0)
|
||||
{
|
||||
currentPresetIndex = index.presets().size() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
--currentPresetIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++currentPresetIndex;
|
||||
if (currentPresetIndex >= index.presets().size())
|
||||
{
|
||||
currentPresetIndex = 0;
|
||||
}
|
||||
}
|
||||
LoadPreset(-1, index.presets()[currentPresetIndex].instanceId());
|
||||
}
|
||||
|
||||
|
||||
void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
try
|
||||
{
|
||||
PresetIndex index;
|
||||
storage.GetPresetIndex(&index);
|
||||
auto currentPresetId = storage.GetCurrentPresetId();
|
||||
size_t currentPresetIndex = 0;
|
||||
|
||||
for (size_t i = 0; i < index.presets().size(); ++i)
|
||||
{
|
||||
if (index.presets()[i].instanceId() == currentPresetId)
|
||||
{
|
||||
currentPresetIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index.presets().size() == 0)
|
||||
{
|
||||
throw PiPedalException("No presets loaded.");
|
||||
}
|
||||
if (request.direction < 0)
|
||||
{
|
||||
if (currentPresetIndex == 0)
|
||||
{
|
||||
currentPresetIndex = index.presets().size() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
--currentPresetIndex;
|
||||
}
|
||||
NextPreset();
|
||||
} else {
|
||||
PreviousPreset();
|
||||
}
|
||||
else
|
||||
{
|
||||
++currentPresetIndex;
|
||||
if (currentPresetIndex >= index.presets().size())
|
||||
{
|
||||
currentPresetIndex = 0;
|
||||
}
|
||||
}
|
||||
LoadPreset(-1, index.presets()[currentPresetIndex].instanceId());
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
@@ -994,8 +1121,6 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
|
||||
|
||||
#if NEW_WIFI_CONFIG
|
||||
if (this->storage.SetWifiConfigSettings(wifiConfigSettings))
|
||||
{
|
||||
@@ -1065,7 +1190,7 @@ void PiPedalModel::UpdateDnsSd()
|
||||
std::string hostName = GetHostName();
|
||||
if (serviceName != "" && deviceIdFile.uuid != "")
|
||||
{
|
||||
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName,true);
|
||||
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1475,6 +1600,18 @@ void PiPedalModel::SendSetPatchProperty(
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
// save the property to the preset (currently used to reconstruct snapshots only)
|
||||
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId);
|
||||
if (pedalboardItem)
|
||||
{
|
||||
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(abstractPath);
|
||||
std::string atomString = ss.str();
|
||||
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
LV2_Atom *atomValue = atomConverter.ToAtom(value);
|
||||
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
|
||||
@@ -1727,11 +1864,19 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
||||
#endif
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
|
||||
std::shared_ptr<Lv2PluginInfo> PiPedalModel::GetPluginInfo(const std::string &uri)
|
||||
{
|
||||
return pluginHost.GetPluginInfo(uri);
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap)
|
||||
{
|
||||
itemMap[pedalboardItem->instanceId()] = pedalboardItem;
|
||||
|
||||
std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem->uri());
|
||||
if (!pPlugin)
|
||||
{
|
||||
pedalboardItem->instanceId();
|
||||
if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
|
||||
{
|
||||
pPlugin = GetSplitterPluginInfo();
|
||||
@@ -1753,21 +1898,74 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pPlugin->piPedalUI())
|
||||
{
|
||||
PiPedalUI::ptr piPedalUI = pPlugin->piPedalUI();
|
||||
for (auto &fileProperty : piPedalUI->fileProperties())
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// an old bug leaks lv2states. Clean it up here.
|
||||
if (pedalboardItem->uri() == EMPTY_PEDALBOARD_ITEM_URI)
|
||||
{
|
||||
pedalboardItem->lv2State(Lv2PluginState());
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < pedalboardItem->topChain().size(); ++i)
|
||||
{
|
||||
UpdateDefaults(&(pedalboardItem->topChain()[i]));
|
||||
UpdateDefaults(&(pedalboardItem->topChain()[i]), itemMap);
|
||||
}
|
||||
for (size_t i = 0; i < pedalboardItem->bottomChain().size(); ++i)
|
||||
{
|
||||
UpdateDefaults(&(pedalboardItem->bottomChain()[i]));
|
||||
UpdateDefaults(&(pedalboardItem->bottomChain()[i]), itemMap);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap)
|
||||
{
|
||||
if (!snapshot)
|
||||
return;
|
||||
for (size_t i = 0; i < snapshot->values_.size(); ++i)
|
||||
{
|
||||
SnapshotValue &value = snapshot->values_[i];
|
||||
auto f = itemMap.find(value.instanceId_);
|
||||
if (f == itemMap.end())
|
||||
{
|
||||
// plugin is no longer present. Remove from the snapshot.
|
||||
snapshot->values_.erase(snapshot->values_.begin() + i);
|
||||
--i;
|
||||
}
|
||||
// missing values in snapshots get filled in with plugin defaults at load time,
|
||||
// so we don't need to actually populate the missing values.
|
||||
}
|
||||
for (auto &snapshotValue : snapshot->values_)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateDefaults(Pedalboard *pedalboard)
|
||||
{
|
||||
// add missing values.
|
||||
std::unordered_map<int64_t, PedalboardItem *> itemMap;
|
||||
for (size_t i = 0; i < pedalboard->items().size(); ++i)
|
||||
{
|
||||
UpdateDefaults(&(pedalboard->items()[i]));
|
||||
UpdateDefaults(&(pedalboard->items()[i]), itemMap);
|
||||
}
|
||||
// set all missiong values on snapshots to default values.
|
||||
for (auto snapshot : pedalboard->snapshots())
|
||||
{
|
||||
if (snapshot)
|
||||
{
|
||||
UpdateDefaults(snapshot.get(), itemMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1919,6 +2117,56 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
LV2_URID pathPatchProperty,
|
||||
LV2_Atom *pathProperty)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
std::string pathPatchPropertyUri = this->pluginHost.Lv2UridToString(pathPatchProperty);
|
||||
std::string atomString = atomConverter.ToString(pathProperty);
|
||||
auto pedalboardItem = this->pedalboard.GetItem(instanceId);
|
||||
|
||||
if (this->audioHost)
|
||||
{
|
||||
this->audioHost->OnNotifyPathPatchPropertyReceived(instanceId,pathPatchPropertyUri,atomString);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (pedalboardItem == nullptr)
|
||||
{
|
||||
Lv2Log::error(SS("OnNotifyPathPatchPropertyReceived: " << pathPatchPropertyUri << " discarded."));
|
||||
return;
|
||||
}
|
||||
Lv2Log::error(SS("OnNotifyPathPatchPropertyReceived: " << pathPatchPropertyUri <<": " << atomString));
|
||||
|
||||
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
|
||||
if (i != pedalboardItem->pathProperties_.end())
|
||||
{
|
||||
pedalboardItem->pathProperties_[pathPatchPropertyUri] = atomString;
|
||||
|
||||
std::vector<IPiPedalModelSubscriber *> t;
|
||||
t.reserve(subscribers.size());
|
||||
|
||||
for (auto subscriber : subscribers)
|
||||
{
|
||||
t.push_back(subscriber);
|
||||
}
|
||||
|
||||
for (auto subscriber : t)
|
||||
{
|
||||
subscriber->OnNotifyPathPatchPropertyChanged(
|
||||
instanceId,
|
||||
pathPatchPropertyUri,
|
||||
atomString);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
@@ -2011,7 +2259,7 @@ void PiPedalModel::CancelMonitorPatchProperty(int64_t clientId, int64_t clientHa
|
||||
atomOutputListeners.erase(atomOutputListeners.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (midiEventListeners.size() == 0)
|
||||
{
|
||||
audioHost->SetListenForMidiEvent(false);
|
||||
@@ -2169,6 +2417,15 @@ std::shared_ptr<Lv2Pedalboard> PiPedalModel::GetLv2Pedalboard()
|
||||
}
|
||||
bool PiPedalModel::LoadCurrentPedalboard()
|
||||
{
|
||||
if (previousPedalboardLoaded && pedalboard.IsStructureIdentical(previousPedalboard))
|
||||
{
|
||||
// then we can send a snapshot update instead!
|
||||
Snapshot snapshot = pedalboard.MakeSnapshotFromCurrentSettings(previousPedalboard);
|
||||
audioHost->LoadSnapshot(snapshot,pluginHost);
|
||||
this->previousPedalboard = this->pedalboard;
|
||||
return true;
|
||||
}
|
||||
|
||||
Lv2PedalboardErrorList errorMessages;
|
||||
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard, errorMessages)};
|
||||
this->lv2Pedalboard = lv2Pedalboard;
|
||||
@@ -2300,7 +2557,7 @@ void PiPedalModel::StartHotspotMonitoring()
|
||||
this->avahiService = std::make_unique<AvahiService>();
|
||||
|
||||
SetThreadName("avahi"); // hack to name the avahi service thread.
|
||||
UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
|
||||
UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
|
||||
SetThreadName("main");
|
||||
|
||||
this->hotspotManager->Open();
|
||||
@@ -2353,7 +2610,7 @@ void PiPedalModel::WaitForAudioDeviceToComeOnline()
|
||||
GetAlsaDevices();
|
||||
}
|
||||
|
||||
PiPedalModel::PostHandle PiPedalModel::Post(PostCallback&&fn)
|
||||
PiPedalModel::PostHandle PiPedalModel::Post(PostCallback &&fn)
|
||||
{
|
||||
// I know. odd place to forward this to, but it's a very serviceable dispatcher implementation.
|
||||
// Why? because it's there, and PiPedalModel has no thread of its own to do dispatching.
|
||||
@@ -2363,16 +2620,16 @@ PiPedalModel::PostHandle PiPedalModel::Post(PostCallback&&fn)
|
||||
}
|
||||
return hotspotManager->Post(std::move(fn));
|
||||
}
|
||||
PiPedalModel::PostHandle PiPedalModel::PostDelayed(const clock::duration&delay,PostCallback&&fn)
|
||||
PiPedalModel::PostHandle PiPedalModel::PostDelayed(const clock::duration &delay, PostCallback &&fn)
|
||||
{
|
||||
if (!hotspotManager)
|
||||
{
|
||||
throw std::runtime_error("Too early. It's not ready yet.");
|
||||
}
|
||||
return hotspotManager->PostDelayed(delay,std::move(fn));
|
||||
|
||||
return hotspotManager->PostDelayed(delay, std::move(fn));
|
||||
}
|
||||
bool PiPedalModel::CancelPost(PostHandle handle) {
|
||||
bool PiPedalModel::CancelPost(PostHandle handle)
|
||||
{
|
||||
if (!hotspotManager)
|
||||
{
|
||||
throw std::runtime_error("Too early. It's not ready yet.");
|
||||
@@ -2388,7 +2645,6 @@ void PiPedalModel::CancelNetworkChangingTimer()
|
||||
networkChangingDelayHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
|
||||
{
|
||||
@@ -2399,16 +2655,16 @@ std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
|
||||
return this->hotspotManager->GetKnownWifiNetworks();
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnected)
|
||||
void PiPedalModel::OnNetworkChanging(bool ethernetConnected, bool hotspotConnected)
|
||||
{
|
||||
CancelNetworkChangingTimer();
|
||||
this->networkChangingDelayHandle =
|
||||
this->networkChangingDelayHandle =
|
||||
PostDelayed(std::chrono::seconds(10), // takes a while for network configuration to be fully applied.
|
||||
[this,ethernetConnected,hotspotConnected]() {
|
||||
this->networkChangingDelayHandle = 0;
|
||||
OnNetworkChanged(ethernetConnected,hotspotConnected);
|
||||
}
|
||||
);
|
||||
[this, ethernetConnected, hotspotConnected]()
|
||||
{
|
||||
this->networkChangingDelayHandle = 0;
|
||||
OnNetworkChanged(ethernetConnected, hotspotConnected);
|
||||
});
|
||||
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber *> t;
|
||||
@@ -2422,51 +2678,52 @@ void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnecte
|
||||
{
|
||||
t[i]->OnNetworkChanging(hotspotConnected);
|
||||
}
|
||||
|
||||
}
|
||||
void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnected)
|
||||
{
|
||||
FireNetworkChanged();
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
||||
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case RealtimeMidiEventType::Shutdown:
|
||||
{
|
||||
this->RequestShutdown(false);
|
||||
}
|
||||
break;
|
||||
case RealtimeMidiEventType::Reboot:
|
||||
{
|
||||
this->RequestShutdown(true);
|
||||
}
|
||||
break;
|
||||
case RealtimeMidiEventType::StartHotspot:
|
||||
{
|
||||
WifiConfigSettings settings = storage.GetWifiConfigSettings();
|
||||
if (!settings.hasSavedPassword_)
|
||||
{
|
||||
throw std::runtime_error("Can't start Wi-Fi hotspot because no password has been configured.");
|
||||
}
|
||||
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Always;
|
||||
this->SetWifiConfigSettings(settings);
|
||||
}
|
||||
break;
|
||||
case RealtimeMidiEventType::StopHotspot:
|
||||
{
|
||||
WifiConfigSettings settings = storage.GetWifiConfigSettings();
|
||||
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
|
||||
this->SetWifiConfigSettings(settings);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
case RealtimeMidiEventType::Shutdown:
|
||||
{
|
||||
this->RequestShutdown(false);
|
||||
}
|
||||
} catch (const std::exception&e)
|
||||
break;
|
||||
case RealtimeMidiEventType::Reboot:
|
||||
{
|
||||
this->RequestShutdown(true);
|
||||
}
|
||||
break;
|
||||
case RealtimeMidiEventType::StartHotspot:
|
||||
{
|
||||
WifiConfigSettings settings = storage.GetWifiConfigSettings();
|
||||
if (!settings.hasSavedPassword_)
|
||||
{
|
||||
throw std::runtime_error("Can't start Wi-Fi hotspot because no password has been configured.");
|
||||
}
|
||||
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Always;
|
||||
this->SetWifiConfigSettings(settings);
|
||||
}
|
||||
break;
|
||||
case RealtimeMidiEventType::StopHotspot:
|
||||
{
|
||||
WifiConfigSettings settings = storage.GetWifiConfigSettings();
|
||||
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
|
||||
this->SetWifiConfigSettings(settings);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Failed to process realtime MIDI event. " << e.what()));
|
||||
}
|
||||
@@ -2507,3 +2764,5 @@ void PiPedalModel::RequestShutdown(bool restart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+67
-42
@@ -40,6 +40,7 @@
|
||||
#include "Promise.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
#include "FileEntry.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -58,11 +59,13 @@ namespace pipedal
|
||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnInputVolumeChanged(float value) = 0;
|
||||
virtual void OnOutputVolumeChanged(float value) = 0;
|
||||
virtual void OnUpdateStatusChanged(const UpdateStatus&updateStatus) = 0;
|
||||
virtual void OnLv2StateChanged(int64_t pedalItemId,const Lv2PluginState&newState ) = 0;
|
||||
virtual void OnUpdateStatusChanged(const UpdateStatus &updateStatus) = 0;
|
||||
virtual void OnLv2StateChanged(int64_t pedalItemId, const Lv2PluginState &newState) = 0;
|
||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
||||
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
|
||||
virtual void OnPresetChanged(bool changed) = 0;
|
||||
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
|
||||
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
|
||||
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
|
||||
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
|
||||
@@ -78,10 +81,11 @@ namespace pipedal
|
||||
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
|
||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding> &bindings) = 0;
|
||||
virtual void OnNotifyPathPatchPropertyChanged(int64_t instanceId, const std::string &pathPatchPropertyString, const std::string &atomString) = 0;
|
||||
|
||||
//virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||
virtual void OnErrorMessage(const std::string&message) = 0;
|
||||
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||
virtual void OnErrorMessage(const std::string &message) = 0;
|
||||
virtual void OnLv2PluginsChanging() = 0;
|
||||
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
||||
virtual void Close() = 0;
|
||||
@@ -97,14 +101,13 @@ namespace pipedal
|
||||
using PostHandle = uint64_t;
|
||||
using PostCallback = std::function<void()>;
|
||||
using NetworkChangedListener = std::function<void(void)>;
|
||||
|
||||
|
||||
private:
|
||||
std::unique_ptr<HotspotManager> hotspotManager;
|
||||
|
||||
std::unique_ptr<Updater> updater;
|
||||
UpdateStatus currentUpdateStatus;
|
||||
void OnUpdateStatusChanged(const UpdateStatus&updateStatus);
|
||||
void OnUpdateStatusChanged(const UpdateStatus &updateStatus);
|
||||
std::function<void(void)> restartListener;
|
||||
|
||||
std::unique_ptr<Lv2PluginChangeMonitor> pluginChangeMonitor;
|
||||
@@ -131,8 +134,10 @@ namespace pipedal
|
||||
class AtomOutputListener
|
||||
{
|
||||
public:
|
||||
bool WantsProperty(uint64_t instanceId,LV2_URID patchProperty) const {
|
||||
if (instanceId != this->instanceId) return false;
|
||||
bool WantsProperty(uint64_t instanceId, LV2_URID patchProperty) const
|
||||
{
|
||||
if (instanceId != this->instanceId)
|
||||
return false;
|
||||
return this->propertyUrid == 0 || patchProperty == this->propertyUrid;
|
||||
}
|
||||
int64_t clientId;
|
||||
@@ -151,11 +156,14 @@ namespace pipedal
|
||||
AtomConverter atomConverter; // must be AFTER pluginHost!
|
||||
|
||||
Pedalboard pedalboard;
|
||||
bool previousPedalboardLoaded = false;
|
||||
Pedalboard previousPedalboard;
|
||||
Storage storage;
|
||||
bool hasPresetChanged = false;
|
||||
|
||||
NetworkChangedListener networkChangedListener;
|
||||
void FireNetworkChanged() {
|
||||
void FireNetworkChanged()
|
||||
{
|
||||
if (networkChangedListener)
|
||||
{
|
||||
networkChangedListener();
|
||||
@@ -168,15 +176,18 @@ namespace pipedal
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
std::vector<IPiPedalModelSubscriber *> subscribers;
|
||||
void SetPresetChanged(int64_t clientId, bool value);
|
||||
void SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect = true);
|
||||
void FireSelectedSnapshotChanged(int64_t selectedSnapshot);
|
||||
void FirePresetsChanged(int64_t clientId);
|
||||
void FirePresetChanged(bool changed);
|
||||
void FirePluginPresetsChanged(const std::string &pluginUri);
|
||||
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
|
||||
void FireChannelSelectionChanged(int64_t clientId);
|
||||
void FireBanksChanged(int64_t clientId);
|
||||
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
|
||||
|
||||
void UpdateDefaults(PedalboardItem *pedalboardItem);
|
||||
void UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
|
||||
void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
|
||||
void UpdateDefaults(Pedalboard *pedalboard);
|
||||
class VuSubscription
|
||||
{
|
||||
@@ -206,22 +217,22 @@ namespace pipedal
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
|
||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) override;
|
||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
||||
|
||||
|
||||
void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson);
|
||||
|
||||
|
||||
void OnNotifyPathPatchPropertyReceived(
|
||||
int64_t instanceId,
|
||||
LV2_URID pathPatchProperty,
|
||||
LV2_Atom *pathProperty) override;
|
||||
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override;
|
||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) override;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) override;
|
||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) override;
|
||||
|
||||
PostHandle networkChangingDelayHandle = 0;
|
||||
void CancelNetworkChangingTimer();
|
||||
|
||||
void OnNetworkChanging(bool ethernetConnected,bool hotspotConnected);
|
||||
|
||||
void OnNetworkChanging(bool ethernetConnected, bool hotspotConnected);
|
||||
void OnNetworkChanged(bool ethernetConnected, bool hotspotConnected);
|
||||
|
||||
void UpdateVst3Settings(Pedalboard &pedalboard);
|
||||
@@ -229,25 +240,39 @@ namespace pipedal
|
||||
PiPedalConfiguration configuration;
|
||||
|
||||
void CheckForResourceInitialization(Pedalboard &pedalboard);
|
||||
|
||||
public:
|
||||
PiPedalModel();
|
||||
virtual ~PiPedalModel();
|
||||
|
||||
enum class Direction
|
||||
{
|
||||
Increase,
|
||||
Decrease
|
||||
};
|
||||
void NextBank(Direction direction = Direction::Increase);
|
||||
void PreviousBank() { NextBank(Direction::Decrease); }
|
||||
void NextPreset(Direction direction = Direction::Increase);
|
||||
void PreviousPreset() { NextPreset(Direction::Decrease); }
|
||||
|
||||
void RequestShutdown(bool restart);
|
||||
|
||||
virtual PostHandle Post(PostCallback&&fn);
|
||||
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn);
|
||||
virtual PostHandle Post(PostCallback &&fn);
|
||||
virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn);
|
||||
virtual bool CancelPost(PostHandle handle);
|
||||
|
||||
template<class REP,class PERIOD>
|
||||
PostHandle PostDelayed(const std::chrono::duration<REP,PERIOD>&delay,PostCallback&&fn)
|
||||
template <class REP, class PERIOD>
|
||||
PostHandle PostDelayed(const std::chrono::duration<REP, PERIOD> &delay, PostCallback &&fn)
|
||||
{
|
||||
return PostDelayed(
|
||||
std::chrono::duration_cast<clock::duration,REP,PERIOD>(delay),
|
||||
std::chrono::duration_cast<clock::duration, REP, PERIOD>(delay),
|
||||
std::move(fn));
|
||||
}
|
||||
|
||||
void SetNetworkChangedListener(NetworkChangedListener listener) {
|
||||
std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri);
|
||||
|
||||
void SetNetworkChangedListener(NetworkChangedListener listener)
|
||||
{
|
||||
networkChangedListener = listener;
|
||||
}
|
||||
|
||||
@@ -256,8 +281,8 @@ namespace pipedal
|
||||
void WaitForAudioDeviceToComeOnline();
|
||||
|
||||
UpdateStatus GetUpdateStatus();
|
||||
void UpdateNow(const std::string&updateUrl);
|
||||
void FireUpdateStatusChanged(const UpdateStatus&updateStatus);
|
||||
void UpdateNow(const std::string &updateUrl);
|
||||
void FireUpdateStatusChanged(const UpdateStatus &updateStatus);
|
||||
uint16_t GetWebPort() const { return webPort; }
|
||||
std::filesystem::path GetPluginUploadDirectory() const;
|
||||
void Close();
|
||||
@@ -297,17 +322,18 @@ namespace pipedal
|
||||
void SetOutputVolume(float value);
|
||||
void PreviewInputVolume(float value);
|
||||
void PreviewOutputVolume(float value);
|
||||
|
||||
|
||||
|
||||
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||
Pedalboard&GetPedalboard();
|
||||
Pedalboard &GetPedalboard();
|
||||
std::shared_ptr<Lv2Pedalboard> GetLv2Pedalboard();
|
||||
|
||||
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||
|
||||
void SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshots, int64_t selectedSnapshot);
|
||||
void SetSnapshot(int64_t selectedSnapshot);
|
||||
|
||||
void GetPresets(PresetIndex *pResult);
|
||||
|
||||
|
||||
Pedalboard GetPreset(int64_t instanceId);
|
||||
void GetBank(int64_t instanceId, BankFile *pBank);
|
||||
|
||||
@@ -369,10 +395,9 @@ namespace pipedal
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
const json_variant&value,
|
||||
const json_variant &value,
|
||||
std::function<void()> onSuccess,
|
||||
std::function<void(const std::string &error)> onError);
|
||||
|
||||
|
||||
BankIndex GetBankIndex() const;
|
||||
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||
@@ -389,7 +414,7 @@ namespace pipedal
|
||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri);
|
||||
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri);
|
||||
void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
@@ -399,15 +424,15 @@ namespace pipedal
|
||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||
void ForceUpdateCheck();
|
||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
||||
std::vector<FileEntry> GetFileList2(const std::string &relativePath,const UiFileProperty&fileProperty);
|
||||
std::vector<std::string> GetFileList(const UiFileProperty &fileProperty);
|
||||
std::vector<FileEntry> GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty);
|
||||
|
||||
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||
std::string CreateNewSampleDirectory(const std::string&relativePath, const UiFileProperty&uiFileProperty);
|
||||
std::string RenameFilePropertyFile(const std::string&oldRelativePath,const std::string&newRelativePath,const UiFileProperty&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty);
|
||||
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
|
||||
std::string RenameFilePropertyFile(const std::string &oldRelativePath, const std::string &newRelativePath, const UiFileProperty &uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty);
|
||||
|
||||
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,std::istream&inputStream,size_t streamLength);
|
||||
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &inputStream, size_t streamLength);
|
||||
uint64_t CreateNewPreset();
|
||||
|
||||
bool LoadCurrentPedalboard();
|
||||
|
||||
+178
-101
@@ -46,8 +46,23 @@
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
|
||||
class PathPatchPropertyChangedBody
|
||||
{
|
||||
public:
|
||||
int64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
std::string atomJson_;
|
||||
DECLARE_JSON_MAP(PathPatchPropertyChangedBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(PathPatchPropertyChangedBody)
|
||||
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, propertyUri)
|
||||
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, atomJson)
|
||||
JSON_MAP_END()
|
||||
|
||||
class GetPatchPropertyBody {
|
||||
|
||||
class GetPatchPropertyBody
|
||||
{
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
@@ -59,8 +74,8 @@ JSON_MAP_REFERENCE(GetPatchPropertyBody, instanceId)
|
||||
JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class CreateNewSampleDirectoryArgs {
|
||||
class CreateNewSampleDirectoryArgs
|
||||
{
|
||||
public:
|
||||
std::string relativePath_;
|
||||
UiFileProperty uiFileProperty_;
|
||||
@@ -72,7 +87,8 @@ JSON_MAP_REFERENCE(CreateNewSampleDirectoryArgs, relativePath)
|
||||
JSON_MAP_REFERENCE(CreateNewSampleDirectoryArgs, uiFileProperty)
|
||||
JSON_MAP_END()
|
||||
|
||||
class RenameSampleFileArgs {
|
||||
class RenameSampleFileArgs
|
||||
{
|
||||
public:
|
||||
std::string oldRelativePath_;
|
||||
std::string newRelativePath_;
|
||||
@@ -86,8 +102,8 @@ JSON_MAP_REFERENCE(RenameSampleFileArgs, newRelativePath)
|
||||
JSON_MAP_REFERENCE(RenameSampleFileArgs, uiFileProperty)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class Lv2StateChangedBody {
|
||||
class Lv2StateChangedBody
|
||||
{
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
Lv2PluginState state_;
|
||||
@@ -99,9 +115,8 @@ JSON_MAP_REFERENCE(Lv2StateChangedBody, instanceId)
|
||||
JSON_MAP_REFERENCE(Lv2StateChangedBody, state)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
class SetPatchPropertyBody {
|
||||
class SetPatchPropertyBody
|
||||
{
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
@@ -171,7 +186,7 @@ public:
|
||||
JSON_MAP_BEGIN(MonitorPatchPropertyBody)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, propertyUri)
|
||||
JSON_MAP_END()
|
||||
|
||||
class OnLoadPluginPresetBody
|
||||
@@ -240,9 +255,6 @@ JSON_MAP_REFERENCE(FileRequestArgs, relativePath)
|
||||
JSON_MAP_REFERENCE(FileRequestArgs, fileProperty)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
|
||||
class MonitorPortBody
|
||||
{
|
||||
public:
|
||||
@@ -370,6 +382,20 @@ JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId)
|
||||
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
|
||||
JSON_MAP_END()
|
||||
|
||||
class SetSnapshotsBody
|
||||
{
|
||||
public:
|
||||
std::vector<std::shared_ptr<Snapshot>> snapshots_;
|
||||
int64_t selectedSnapshot_;
|
||||
|
||||
DECLARE_JSON_MAP(SetSnapshotsBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(SetSnapshotsBody)
|
||||
JSON_MAP_REFERENCE(SetSnapshotsBody, snapshots)
|
||||
JSON_MAP_REFERENCE(SetSnapshotsBody, selectedSnapshot)
|
||||
JSON_MAP_END()
|
||||
|
||||
class ChannelSelectionChangedBody
|
||||
{
|
||||
public:
|
||||
@@ -451,7 +477,6 @@ JSON_MAP_REFERENCE(Vst3ControlChangedBody, value)
|
||||
JSON_MAP_REFERENCE(Vst3ControlChangedBody, state)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class PiPedalSocketHandler : public SocketHandler, public IPiPedalModelSubscriber
|
||||
{
|
||||
private:
|
||||
@@ -480,25 +505,25 @@ private:
|
||||
PortMonitorSubscription(
|
||||
int64_t subscriptionHandle,
|
||||
int64_t instanceId,
|
||||
const std::string& key)
|
||||
:subscriptionHandle(subscriptionHandle),
|
||||
instanceId(instanceId),
|
||||
key(key)
|
||||
const std::string &key)
|
||||
: subscriptionHandle(subscriptionHandle),
|
||||
instanceId(instanceId),
|
||||
key(key)
|
||||
{
|
||||
|
||||
}
|
||||
~PortMonitorSubscription() {
|
||||
~PortMonitorSubscription()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
PortMonitorSubscription() {}
|
||||
PortMonitorSubscription(const PortMonitorSubscription&) = delete;
|
||||
PortMonitorSubscription& operator=(const PortMonitorSubscription&) = delete;
|
||||
void Close() {
|
||||
std::lock_guard lock {pmMutex};
|
||||
PortMonitorSubscription(const PortMonitorSubscription &) = delete;
|
||||
PortMonitorSubscription &operator=(const PortMonitorSubscription &) = delete;
|
||||
void Close()
|
||||
{
|
||||
std::lock_guard lock{pmMutex};
|
||||
closed = true;
|
||||
}
|
||||
|
||||
|
||||
std::mutex pmMutex;
|
||||
|
||||
bool closed = false;
|
||||
@@ -532,7 +557,8 @@ public:
|
||||
bool finalCleanup = false;
|
||||
void FinalCleanup()
|
||||
{
|
||||
if (finalCleanup) return;
|
||||
if (finalCleanup)
|
||||
return;
|
||||
finalCleanup = true;
|
||||
// avoid use after free.
|
||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||
@@ -547,12 +573,12 @@ public:
|
||||
activeVuSubscriptions.resize(0);
|
||||
|
||||
model.RemoveNotificationSubsription(this);
|
||||
|
||||
}
|
||||
|
||||
virtual void Close()
|
||||
{
|
||||
if (closed) return;
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
|
||||
FinalCleanup(); // do it while we can. &model will no longer be valid after this. ( :-( )
|
||||
@@ -566,7 +592,8 @@ public:
|
||||
std::stringstream imageList;
|
||||
const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
|
||||
bool firstTime = true;
|
||||
try {
|
||||
try
|
||||
{
|
||||
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
|
||||
{
|
||||
if (!firstTime)
|
||||
@@ -576,7 +603,8 @@ public:
|
||||
firstTime = false;
|
||||
imageList << entry.path().filename().string();
|
||||
}
|
||||
} catch (const std::exception&)
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str());
|
||||
}
|
||||
@@ -814,7 +842,7 @@ public:
|
||||
{
|
||||
std::shared_ptr<PortMonitorSubscription> result;
|
||||
{
|
||||
std::lock_guard lock (activePortMonitorsMutex);
|
||||
std::lock_guard lock(activePortMonitorsMutex);
|
||||
|
||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||
{
|
||||
@@ -832,9 +860,10 @@ public:
|
||||
{
|
||||
// running on RT_output thread, or on Socket thread.
|
||||
{
|
||||
std::lock_guard lock {subscription->pmMutex};
|
||||
if (subscription->closed) return;
|
||||
if (value == subscription->currentValue)
|
||||
std::lock_guard lock{subscription->pmMutex};
|
||||
if (subscription->closed)
|
||||
return;
|
||||
if (value == subscription->currentValue)
|
||||
return;
|
||||
if (subscription->waitingForAck)
|
||||
{
|
||||
@@ -846,7 +875,7 @@ public:
|
||||
subscription->lastValue = value;
|
||||
subscription->waitingForAck = true;
|
||||
}
|
||||
SendMonitorPortMessage_Inner(subscription,value);
|
||||
SendMonitorPortMessage_Inner(subscription, value);
|
||||
}
|
||||
|
||||
// post-sync send.
|
||||
@@ -863,12 +892,14 @@ public:
|
||||
{
|
||||
// running on PiPedalSocket thread.
|
||||
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
|
||||
if (!subscription) return;
|
||||
if (!subscription)
|
||||
return;
|
||||
|
||||
float value;
|
||||
{
|
||||
std::unique_lock lock {subscription->pmMutex};
|
||||
if (subscription->closed) return;
|
||||
std::unique_lock lock{subscription->pmMutex};
|
||||
if (subscription->closed)
|
||||
return;
|
||||
if (subscription->pendingValue)
|
||||
{
|
||||
value = subscription->currentValue;
|
||||
@@ -892,7 +923,7 @@ public:
|
||||
Lv2Log::debug("Failed to monitor port output. (%s)", e.what());
|
||||
});
|
||||
}
|
||||
void MonitorPort(int replyTo,MonitorPortBody &body)
|
||||
void MonitorPort(int replyTo, MonitorPortBody &body)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
||||
int64_t subscriptionHandle = model.MonitorPort(
|
||||
@@ -965,18 +996,19 @@ public:
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
}
|
||||
}
|
||||
else if (message == "previewControl")
|
||||
{
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
} else if (message == "setControl")
|
||||
}
|
||||
else if (message == "setControl")
|
||||
{
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
}
|
||||
}
|
||||
else if (message == "setInputVolume")
|
||||
{
|
||||
float value;
|
||||
@@ -1029,7 +1061,7 @@ public:
|
||||
else if (message == "getUpdateStatus")
|
||||
{
|
||||
UpdateStatus updateStatus = model.GetUpdateStatus();
|
||||
this->Reply(replyTo,"getUpdateStatus",updateStatus);
|
||||
this->Reply(replyTo, "getUpdateStatus", updateStatus);
|
||||
}
|
||||
else if (message == "updateNow")
|
||||
{
|
||||
@@ -1037,7 +1069,7 @@ public:
|
||||
pReader->read(&updateUrl);
|
||||
model.UpdateNow(updateUrl);
|
||||
bool result = true;
|
||||
this->Reply(replyTo,"updateNow",result);
|
||||
this->Reply(replyTo, "updateNow", result);
|
||||
}
|
||||
else if (message == "getJackStatus")
|
||||
{
|
||||
@@ -1053,7 +1085,6 @@ public:
|
||||
{
|
||||
std::vector<std::string> channels = this->model.GetKnownWifiNetworks();
|
||||
this->Reply(replyTo, "getWifiChannels", channels);
|
||||
|
||||
}
|
||||
else if (message == "getWifiChannels")
|
||||
{
|
||||
@@ -1199,6 +1230,18 @@ public:
|
||||
this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_);
|
||||
}
|
||||
}
|
||||
else if (message == "setSnapshot")
|
||||
{
|
||||
int64_t snapshotIndex = -1;
|
||||
pReader->read(&snapshotIndex);
|
||||
this->model.SetSnapshot(snapshotIndex);
|
||||
}
|
||||
else if (message == "setSnapshots")
|
||||
{
|
||||
SetSnapshotsBody body;
|
||||
pReader->read(&body);
|
||||
this->model.SetSnapshots(body.snapshots_, body.selectedSnapshot_);
|
||||
}
|
||||
else if (message == "currentPedalboard")
|
||||
{
|
||||
auto pedalboard = model.GetCurrentPedalboardCopy();
|
||||
@@ -1347,6 +1390,23 @@ public:
|
||||
this->SendError(replyTo, std::string(e.what()));
|
||||
}
|
||||
}
|
||||
else if (message == "nextBank")
|
||||
{
|
||||
model.NextBank();
|
||||
}
|
||||
else if (message == "previousBank")
|
||||
{
|
||||
model.PreviousBank();
|
||||
}
|
||||
else if (message == "nextPreset")
|
||||
{
|
||||
model.NextPreset();
|
||||
}
|
||||
else if (message == "previousPreset")
|
||||
{
|
||||
model.PreviousPreset();
|
||||
}
|
||||
|
||||
else if (message == "renamePresetItem")
|
||||
{
|
||||
RenamePresetBody body;
|
||||
@@ -1373,19 +1433,16 @@ public:
|
||||
{
|
||||
SetPatchPropertyBody body;
|
||||
pReader->read(&body);
|
||||
model.SendSetPatchProperty(clientId,body.instanceId_,body.propertyUri_,body.value_,
|
||||
[this, replyTo] () {
|
||||
this->JsonReply(replyTo, "setPatchProperty", "true");
|
||||
|
||||
},
|
||||
[this, replyTo] (const std::string&error) {
|
||||
this->SendError(replyTo, error.c_str());
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
model.SendSetPatchProperty(clientId, body.instanceId_, body.propertyUri_, body.value_, [this, replyTo]()
|
||||
{
|
||||
this->JsonReply(replyTo, "setPatchProperty", "true");
|
||||
},
|
||||
[this, replyTo](const std::string &error)
|
||||
{
|
||||
this->SendError(replyTo, error.c_str());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
else if (message == "getPatchProperty")
|
||||
{
|
||||
GetPatchPropertyBody body;
|
||||
@@ -1410,7 +1467,7 @@ public:
|
||||
MonitorPortBody body;
|
||||
pReader->read(&body);
|
||||
|
||||
MonitorPort(replyTo,body);
|
||||
MonitorPort(replyTo, body);
|
||||
}
|
||||
else if (message == "unmonitorPort")
|
||||
{
|
||||
@@ -1418,7 +1475,7 @@ public:
|
||||
pReader->read(&subscriptionHandle);
|
||||
{
|
||||
{
|
||||
std::lock_guard guard(activePortMonitorsMutex);
|
||||
std::lock_guard guard(activePortMonitorsMutex);
|
||||
for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i)
|
||||
{
|
||||
if ((*i)->subscriptionHandle == subscriptionHandle)
|
||||
@@ -1490,9 +1547,8 @@ public:
|
||||
this->model.SetUpdatePolicy((UpdatePolicyT)iPolicy);
|
||||
}
|
||||
else if (message == "forceUpdateCheck")
|
||||
{
|
||||
{
|
||||
this->model.ForceUpdateCheck();
|
||||
|
||||
}
|
||||
else if (message == "setSystemMidiBindings")
|
||||
{
|
||||
@@ -1503,56 +1559,57 @@ public:
|
||||
else if (message == "getSystemMidiBindings")
|
||||
{
|
||||
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
|
||||
this->Reply(replyTo,"getSystemMidiBindings",bindings);
|
||||
this->Reply(replyTo, "getSystemMidiBindings", bindings);
|
||||
}
|
||||
else if (message == "requestFileList")
|
||||
{
|
||||
UiFileProperty fileProperty;
|
||||
pReader->read(&fileProperty);
|
||||
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
||||
this->Reply(replyTo,"requestFileList",list);
|
||||
}
|
||||
this->Reply(replyTo, "requestFileList", list);
|
||||
}
|
||||
else if (message == "requestFileList2")
|
||||
{
|
||||
FileRequestArgs requestArgs;
|
||||
pReader->read(&requestArgs);
|
||||
std::vector<FileEntry> list = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
|
||||
this->Reply(replyTo,"requestFileList2",list);
|
||||
}
|
||||
this->Reply(replyTo, "requestFileList2", list);
|
||||
}
|
||||
else if (message == "newPreset")
|
||||
{
|
||||
int64_t presetId = this->model.CreateNewPreset();
|
||||
this->Reply(replyTo,"newPreset",presetId);
|
||||
}
|
||||
this->Reply(replyTo, "newPreset", presetId);
|
||||
}
|
||||
else if (message == "deleteUserFile")
|
||||
{
|
||||
std::string fileName;
|
||||
pReader->read(&fileName);
|
||||
|
||||
this->model.DeleteSampleFile(fileName);
|
||||
this->Reply(replyTo,"deleteUserFile",true);
|
||||
this->Reply(replyTo, "deleteUserFile", true);
|
||||
}
|
||||
else if (message == "createNewSampleDirectory")
|
||||
{
|
||||
CreateNewSampleDirectoryArgs args;
|
||||
pReader->read(&args);
|
||||
|
||||
std::string newFileName = this->model.CreateNewSampleDirectory(args.relativePath_,args.uiFileProperty_);
|
||||
this->Reply(replyTo,"createNewSampleDirectory",newFileName);
|
||||
std::string newFileName = this->model.CreateNewSampleDirectory(args.relativePath_, args.uiFileProperty_);
|
||||
this->Reply(replyTo, "createNewSampleDirectory", newFileName);
|
||||
}
|
||||
else if (message == "renameFilePropertyFile")
|
||||
{
|
||||
RenameSampleFileArgs args;
|
||||
pReader->read(&args);
|
||||
|
||||
std::string newFileName = this->model.RenameFilePropertyFile(args.oldRelativePath_,args.newRelativePath_,args.uiFileProperty_);
|
||||
this->Reply(replyTo,"renameFilePropertyFile",newFileName);
|
||||
} else if (message == "getFilePropertyDirectoryTree"){
|
||||
std::string newFileName = this->model.RenameFilePropertyFile(args.oldRelativePath_, args.newRelativePath_, args.uiFileProperty_);
|
||||
this->Reply(replyTo, "renameFilePropertyFile", newFileName);
|
||||
}
|
||||
else if (message == "getFilePropertyDirectoryTree")
|
||||
{
|
||||
UiFileProperty uiFileProperty;
|
||||
pReader->read(&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr result = model.GetFilePropertydirectoryTree(uiFileProperty);
|
||||
this->Reply(replyTo,"GetFilePropertydirectoryTree",result);
|
||||
|
||||
this->Reply(replyTo, "GetFilePropertydirectoryTree", result);
|
||||
}
|
||||
|
||||
else if (message == "setOnboarding")
|
||||
@@ -1633,37 +1690,37 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void OnUpdateStatusChanged(const UpdateStatus&updateStatus) {
|
||||
Send("onUpdateStatusChanged",updateStatus);
|
||||
virtual void OnUpdateStatusChanged(const UpdateStatus &updateStatus)
|
||||
{
|
||||
Send("onUpdateStatusChanged", updateStatus);
|
||||
}
|
||||
|
||||
virtual void OnLv2StateChanged(int64_t instanceId, const Lv2PluginState &state)
|
||||
{
|
||||
Lv2StateChangedBody message { (uint64_t)instanceId, state};
|
||||
Send("onLv2StateChanged",message);
|
||||
|
||||
Lv2StateChangedBody message{(uint64_t)instanceId, state};
|
||||
Send("onLv2StateChanged", message);
|
||||
}
|
||||
|
||||
virtual void OnLv2PluginsChanging() override {
|
||||
Send("onLv2PluginsChanging",true);
|
||||
virtual void OnLv2PluginsChanging() override
|
||||
{
|
||||
Send("onLv2PluginsChanging", true);
|
||||
Flush();
|
||||
}
|
||||
virtual void OnNetworkChanging(bool hotspotConnected) override {
|
||||
try {
|
||||
Send("onNetworkChanging",hotspotConnected);
|
||||
Flush();
|
||||
|
||||
} catch (const std::exception&ignored)
|
||||
virtual void OnNetworkChanging(bool hotspotConnected) override
|
||||
{
|
||||
try
|
||||
{
|
||||
Send("onNetworkChanging", hotspotConnected);
|
||||
Flush();
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
virtual void OnErrorMessage(const std::string&message)
|
||||
virtual void OnErrorMessage(const std::string &message)
|
||||
{
|
||||
Send("onErrorMessage",message);
|
||||
Send("onErrorMessage", message);
|
||||
}
|
||||
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
|
||||
// {
|
||||
@@ -1675,8 +1732,9 @@ private:
|
||||
// Send("onPatchPropertyChanged",body);
|
||||
// }
|
||||
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
|
||||
Send("onSystemMidiBindingsChanged",bindings);
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding> &bindings)
|
||||
{
|
||||
Send("onSystemMidiBindingsChanged", bindings);
|
||||
}
|
||||
|
||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
|
||||
@@ -1696,6 +1754,16 @@ private:
|
||||
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
|
||||
Send("onChannelSelectionChanged", body);
|
||||
}
|
||||
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) override
|
||||
{
|
||||
Send("onSelectedSnapshotChanged", selectedSnapshot);
|
||||
}
|
||||
|
||||
virtual void OnPresetChanged(bool changed) override
|
||||
{
|
||||
Send("onPresetChanged", changed);
|
||||
}
|
||||
|
||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets)
|
||||
{
|
||||
PresetsChangedBody body;
|
||||
@@ -1852,8 +1920,8 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
|
||||
void Flush()
|
||||
{
|
||||
}
|
||||
int outstandingNotifyAtomOutputs = 0;
|
||||
|
||||
@@ -1896,7 +1964,7 @@ private:
|
||||
body.atomJson_.Set(atomJson);
|
||||
|
||||
// flow control. We can only have one in-flight NotifyAtomOutput at a time.
|
||||
// Subsequent notifications are held until we receive an ack.
|
||||
// Subsequent notifications are held until we receive an ack.
|
||||
//
|
||||
// If a duplicate atomProperty is queued, overwrite the previous value.
|
||||
{
|
||||
@@ -1910,7 +1978,7 @@ private:
|
||||
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty)
|
||||
{
|
||||
// better to erase than overwrite, since it provides better idempotence.
|
||||
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i);
|
||||
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1930,6 +1998,16 @@ private:
|
||||
|
||||
});
|
||||
}
|
||||
virtual void OnNotifyPathPatchPropertyChanged(int64_t instanceId, const std::string &pathPatchPropertyString, const std::string &atomString)
|
||||
{
|
||||
PathPatchPropertyChangedBody body;
|
||||
body.instanceId_ = instanceId;
|
||||
body.propertyUri_ = pathPatchPropertyString;
|
||||
body.atomJson_ = atomString;
|
||||
|
||||
Send("onNotifyPathPatchPropertyChanged", body);
|
||||
}
|
||||
|
||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl)
|
||||
{
|
||||
NotifyMidiListenerBody body;
|
||||
@@ -2014,4 +2092,3 @@ std::shared_ptr<ISocketFactory> pipedal::MakePiPedalSocketFactory(PiPedalModel &
|
||||
{
|
||||
return std::make_shared<PiPedalSocketFactory>(model);
|
||||
}
|
||||
|
||||
|
||||
+66
-6
@@ -18,6 +18,7 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AtomConverter.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include <lilv/lilv.h>
|
||||
#include <stdexcept>
|
||||
@@ -44,6 +45,7 @@
|
||||
#include <fstream>
|
||||
#include "PiPedalException.hpp"
|
||||
#include "StdErrorCapture.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "Locale.hpp"
|
||||
|
||||
@@ -188,6 +190,9 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
patch__readable = lilv_new_uri(pWorld, LV2_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");
|
||||
|
||||
}
|
||||
|
||||
void PluginHost::LilvUris::Free()
|
||||
@@ -618,13 +623,28 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
||||
std::make_shared<UiFileProperty>(
|
||||
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
||||
|
||||
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->dc__format, nullptr);
|
||||
LILV_FOREACH(nodes, i, dc_types)
|
||||
AutoLilvNodes mod__fileTypes = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
||||
LILV_FOREACH(nodes,i,mod__fileTypes)
|
||||
{
|
||||
AutoLilvNode dc_type = lilv_nodes_get(dc_types, i);
|
||||
std::string fileType = dc_type.AsString();
|
||||
std::string label = "";
|
||||
fileProperty->fileTypes().push_back(UiFileType(label, fileType));
|
||||
// "nam,nammodel"
|
||||
AutoLilvNode lilvfileType {lilv_nodes_get(mod__fileTypes, i)};
|
||||
std::string fileTypes = lilvfileType.AsString();
|
||||
|
||||
for (std::string&type: split(fileTypes,','))
|
||||
{
|
||||
fileProperty->fileTypes().push_back(UiFileType(SS(type << " file"),SS('.' << type)));
|
||||
}
|
||||
}
|
||||
if (!mod__fileTypes)
|
||||
{
|
||||
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->dc__format, nullptr);
|
||||
LILV_FOREACH(nodes, i, dc_types)
|
||||
{
|
||||
AutoLilvNode dc_type = lilv_nodes_get(dc_types, i);
|
||||
std::string fileType = dc_type.AsString();
|
||||
std::string label = "";
|
||||
fileProperty->fileTypes().push_back(UiFileType(label, fileType));
|
||||
}
|
||||
}
|
||||
|
||||
fileProperties.push_back(
|
||||
@@ -1479,6 +1499,46 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
|
||||
}
|
||||
}
|
||||
|
||||
json_variant PluginHost::MapPath(const json_variant&json)
|
||||
{
|
||||
AtomConverter converter(GetMapFeature());
|
||||
return converter.MapPath(json,GetPluginStoragePath());
|
||||
|
||||
}
|
||||
json_variant PluginHost::AbstractPath(const json_variant&json)
|
||||
{
|
||||
AtomConverter converter(GetMapFeature());
|
||||
return converter.AbstractPath(json,GetPluginStoragePath());
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::string PluginHost::MapPath(const std::string &abstractPath)
|
||||
{
|
||||
auto storagePath = GetPluginStoragePath();
|
||||
if (abstractPath.length() == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return SS(storagePath << '/' << abstractPath);
|
||||
|
||||
|
||||
}
|
||||
std::string PluginHost::AbstractPath(const std::string&path)
|
||||
{
|
||||
auto storagePath = GetPluginStoragePath();
|
||||
if (path.starts_with(storagePath))
|
||||
{
|
||||
auto start = storagePath.length();
|
||||
if (start < path.length() && path[start] == '/')
|
||||
{
|
||||
++start;
|
||||
}
|
||||
return path.substr(start);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// void PiPedalHostLogError(const std::string &error)
|
||||
// {
|
||||
// Lv2Log::error("%s",error.c_str());
|
||||
|
||||
@@ -729,6 +729,10 @@ namespace pipedal
|
||||
AutoLilvNode mod__label;
|
||||
|
||||
AutoLilvNode dc__format;
|
||||
|
||||
AutoLilvNode mod__fileTypes;
|
||||
|
||||
|
||||
};
|
||||
LilvUris* lilvUris = nullptr;
|
||||
|
||||
@@ -815,6 +819,12 @@ namespace pipedal
|
||||
void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory);
|
||||
void ReloadPlugins();
|
||||
|
||||
// equivalent to LV2 MapPath AbstractPath features.
|
||||
std::string MapPath(const std::string &abstractPath);
|
||||
std::string AbstractPath(const std::string&path);
|
||||
json_variant MapPath(const json_variant&json);
|
||||
json_variant AbstractPath(const json_variant&json);
|
||||
|
||||
private:
|
||||
|
||||
std::set<std::string> pluginsThatHaveBeenCheckedForResources;
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
// Copyright (c) 2024 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 "PresetBundle.hpp"
|
||||
#include "PiPedalModel.hpp"
|
||||
#include <vector>
|
||||
#include "Pedalboard.hpp"
|
||||
#include "Banks.hpp"
|
||||
#include "json.hpp"
|
||||
#include <sstream>
|
||||
#include "lv2/atom/atom.h"
|
||||
#include "ZipFile.hpp"
|
||||
#include <set>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static std::string ToString(const std::vector<uint8_t> &value)
|
||||
{
|
||||
const char *start = (const char *)(&value[0]);
|
||||
const char *end = start + value.size();
|
||||
while (end != start && end[-1] == 0)
|
||||
{
|
||||
--end;
|
||||
}
|
||||
|
||||
return std::string(start, end);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> ToBinary(const std::string &value)
|
||||
{
|
||||
std::vector<uint8_t> result(value.length() + 1);
|
||||
for (size_t i = 0; i < value.length(); ++i)
|
||||
{
|
||||
result[i] = (uint8_t)value[i];
|
||||
}
|
||||
result[value.length()] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
class PresetBundleWriterImpl : public PresetBundleWriter
|
||||
{
|
||||
public:
|
||||
using self = PresetBundleWriterImpl;
|
||||
using super = PresetBundleWriter;
|
||||
|
||||
PresetBundleWriterImpl()
|
||||
{
|
||||
}
|
||||
|
||||
void LoadPresets(PiPedalModel &model, const std::string &presetJson)
|
||||
{
|
||||
BankFile bankFile;
|
||||
|
||||
std::istringstream s(presetJson);
|
||||
json_reader reader(s);
|
||||
|
||||
reader.read(&bankFile);
|
||||
GatherMediaPaths(model, bankFile);
|
||||
configFiles["bankFile.json"] = presetJson;
|
||||
|
||||
pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||
}
|
||||
|
||||
void LoadPluginPresets(PiPedalModel &model, const std::string pluginPresetJson)
|
||||
{
|
||||
PluginPresets pluginPresets;
|
||||
|
||||
std::istringstream s(pluginPresetJson);
|
||||
json_reader reader(s);
|
||||
|
||||
reader.read(&pluginPresets);
|
||||
|
||||
GatherMediaPaths(model, pluginPresets);
|
||||
configFiles["pluginPresets.json"] = pluginPresetJson;
|
||||
|
||||
pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||
}
|
||||
virtual ~PresetBundleWriterImpl() noexcept;
|
||||
|
||||
virtual void WriteToFile(const std::filesystem::path &filePath) override;
|
||||
|
||||
private:
|
||||
void AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name);
|
||||
|
||||
void GatherMediaPaths(PiPedalModel &model, BankFile &bankFile);
|
||||
void GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPreset);
|
||||
|
||||
std::map<std::string, std::string> configFiles;
|
||||
std::set<std::string> mediaPaths;
|
||||
std::set<std::string> usedPlugins;
|
||||
std::vector<std::map<std::string, std::string>> pluginMetadata;
|
||||
|
||||
std::filesystem::path pluginUploadDirectory;
|
||||
};
|
||||
|
||||
void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath)
|
||||
{
|
||||
|
||||
ZipFileWriter::ptr zipFile = ZipFileWriter::Create(filePath);
|
||||
|
||||
for (const auto &configFile : configFiles)
|
||||
{
|
||||
|
||||
zipFile->WriteFile(configFile.first, configFile.second.data(), configFile.second.size());
|
||||
}
|
||||
std::string metadata;
|
||||
{
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(this->pluginMetadata);
|
||||
metadata = ss.str();
|
||||
zipFile->WriteFile("pluginsUsed.json", metadata.data(), metadata.length());
|
||||
}
|
||||
|
||||
for (const auto &mediaPath : mediaPaths)
|
||||
{
|
||||
std::string zipName = (std::filesystem::path("media") / std::filesystem::path(mediaPath)).string();
|
||||
std::filesystem::path sourcePath = this->pluginUploadDirectory / std::filesystem::path(mediaPath);
|
||||
if (std::filesystem::exists(sourcePath))
|
||||
{
|
||||
zipFile->WriteFile(zipName, sourcePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Media file not found: " << sourcePath));
|
||||
}
|
||||
}
|
||||
zipFile->Close();
|
||||
}
|
||||
|
||||
void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, BankFile &bankFile)
|
||||
{
|
||||
for (auto &preset : bankFile.presets())
|
||||
{
|
||||
Pedalboard &pedalboard = preset->preset();
|
||||
auto items = pedalboard.GetAllPlugins();
|
||||
for (auto plugin : items)
|
||||
{
|
||||
AddUsedPlugin(model, plugin->uri(), plugin->pluginName());
|
||||
const Lv2PluginState &state = plugin->lv2State();
|
||||
for (const auto &value : state.values_)
|
||||
{
|
||||
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||
{
|
||||
std::string path = ToString(value.second.value_);
|
||||
if (!mediaPaths.contains(path))
|
||||
{
|
||||
mediaPaths.insert(std::move(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPresets)
|
||||
{
|
||||
AddUsedPlugin(model, pluginPresets.pluginUri_, "");
|
||||
|
||||
for (auto &preset : pluginPresets.presets_)
|
||||
{
|
||||
const Lv2PluginState &state = preset.state_;
|
||||
for (const auto &value : state.values_)
|
||||
{
|
||||
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||
{
|
||||
std::string path = ToString(value.second.value_);
|
||||
if (!mediaPaths.contains(path))
|
||||
{
|
||||
mediaPaths.insert(std::move(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PresetBundleWriter::ptr PresetBundleWriter::CreatePresetsFile(PiPedalModel &model, const std::string &presetJson)
|
||||
{
|
||||
auto result = std::make_unique<PresetBundleWriterImpl>();
|
||||
result->LoadPresets(model, presetJson);
|
||||
|
||||
return result;
|
||||
}
|
||||
PresetBundleWriter::ptr PresetBundleWriter::CreatePluginPresetsFile(PiPedalModel &model, const std::string &pluginPresetJson)
|
||||
{
|
||||
auto result = std::make_unique<PresetBundleWriterImpl>();
|
||||
result->LoadPluginPresets(model, pluginPresetJson);
|
||||
return result;
|
||||
}
|
||||
|
||||
class PresetBundleReaderImpl : public PresetBundleReader
|
||||
{
|
||||
public:
|
||||
PresetBundleReaderImpl()
|
||||
{
|
||||
}
|
||||
|
||||
void LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &path)
|
||||
{
|
||||
this->pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||
zipFile = ZipFileReader::Create(path);
|
||||
auto s = zipFile->GetFileInputStream("pluginPresets.json");
|
||||
json_reader reader(s);
|
||||
reader.read(&pluginPresets);
|
||||
}
|
||||
void LoadPluginPresetFile(PiPedalModel &model, const std::filesystem::path &path)
|
||||
{
|
||||
this->pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||
zipFile = ZipFileReader::Create(path);
|
||||
auto s = zipFile->GetFileInputStream("bankFile.json");
|
||||
json_reader reader(s);
|
||||
reader.read(&bankFile);
|
||||
}
|
||||
virtual ~PresetBundleReaderImpl() noexcept;
|
||||
|
||||
virtual void ExtractMediaFiles() override;
|
||||
virtual std::string GetPresetJson() override;
|
||||
virtual std::string GetPluginPresetsJson() override;
|
||||
|
||||
private:
|
||||
void ExtractMediaFile(const std::string &zipFileName);
|
||||
bool IsSameFile(const std::string &zipFileName, const std::filesystem::path &filePath)
|
||||
{
|
||||
return zipFile->CompareFiles(zipFileName, filePath);
|
||||
}
|
||||
void RenameMediaFileProperty(const std::string oldName, const std::string &newName);
|
||||
|
||||
std::filesystem::path pluginUploadDirectory;
|
||||
// BankFile bankFile;
|
||||
ZipFileReader::ptr zipFile;
|
||||
|
||||
BankFile bankFile;
|
||||
PluginPresets pluginPresets;
|
||||
};
|
||||
|
||||
void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName)
|
||||
{
|
||||
for (auto &preset : bankFile.presets())
|
||||
{
|
||||
Pedalboard &pedalboard = preset->preset();
|
||||
auto items = pedalboard.GetAllPlugins();
|
||||
for (auto plugin : items)
|
||||
{
|
||||
Lv2PluginState &state = plugin->lv2State();
|
||||
for (auto &value : state.values_)
|
||||
{
|
||||
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||
{
|
||||
std::string path = ToString(value.second.value_);
|
||||
if (path == oldName)
|
||||
{
|
||||
state.values_.at(value.first).value_ = ToBinary(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto preset : pluginPresets.presets_)
|
||||
{
|
||||
Lv2PluginState state = preset.state_;
|
||||
for (auto &value : state.values_)
|
||||
{
|
||||
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||
{
|
||||
std::string path = ToString(value.second.value_);
|
||||
if (path == oldName)
|
||||
{
|
||||
state.values_.at(value.first).value_ = ToBinary(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PresetBundleReader::ptr PresetBundleReader::LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath)
|
||||
{
|
||||
std::unique_ptr<PresetBundleReaderImpl> result = std::make_unique<PresetBundleReaderImpl>();
|
||||
result->LoadPresetsFile(model, filePath);
|
||||
return result;
|
||||
}
|
||||
PresetBundleReader::ptr PresetBundleReader::LoadPluginPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath)
|
||||
{
|
||||
std::unique_ptr<PresetBundleReaderImpl> result = std::make_unique<PresetBundleReaderImpl>();
|
||||
result->LoadPluginPresetsFile(model, filePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PresetBundleReaderImpl::ExtractMediaFiles()
|
||||
{
|
||||
auto zipFiles = zipFile->GetFiles();
|
||||
for (const auto &zipFile : zipFiles)
|
||||
{
|
||||
if (zipFile != "bankFile.json")
|
||||
{
|
||||
if (!zipFile.ends_with('/')) // don't process diretories.
|
||||
{
|
||||
ExtractMediaFile(zipFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ExtractFileVersion(std::string &baseName, int &n)
|
||||
{
|
||||
if (baseName.ends_with(')'))
|
||||
{
|
||||
size_t endPos = baseName.length() - 1;
|
||||
int startPos = baseName.find_last_of('(');
|
||||
if (startPos != std::string::npos)
|
||||
{
|
||||
std::string number = baseName.substr(startPos + 1, endPos - (startPos + 1));
|
||||
std::istringstream ss(number);
|
||||
ss >> n;
|
||||
if (!ss.fail())
|
||||
{
|
||||
while (startPos > 0 && baseName[startPos - 1] == ' ')
|
||||
{
|
||||
--startPos;
|
||||
}
|
||||
baseName = baseName.substr(0, startPos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
n = 0;
|
||||
}
|
||||
static std::filesystem::path NextFileName(const std::filesystem::path &fileName)
|
||||
{
|
||||
int n = 0;
|
||||
auto fileNameOnly = fileName.filename().string();
|
||||
auto extPos = fileNameOnly.find_last_of('.');
|
||||
if (extPos == std::string::npos)
|
||||
{
|
||||
extPos = fileNameOnly.length();
|
||||
}
|
||||
std::string baseName = fileNameOnly.substr(0, extPos);
|
||||
ExtractFileVersion(baseName, n);
|
||||
if (n >= 1024)
|
||||
{
|
||||
// something has gone badly wrong.
|
||||
throw new std::runtime_error(SS("Can't increase the version number of " << fileName));
|
||||
}
|
||||
std::string newFileName = SS(baseName << "(" << (n + 1) << ')' << fileNameOnly.substr(extPos));
|
||||
return fileName.parent_path() / newFileName;
|
||||
}
|
||||
void PresetBundleReaderImpl::ExtractMediaFile(const std::string &zipFileName)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
if (zipFileName.starts_with("media/"))
|
||||
{
|
||||
std::string baseName = zipFileName.substr(6);
|
||||
fs::path targetFileName = this->pluginUploadDirectory / std::filesystem::path(baseName);
|
||||
bool renamed = false;
|
||||
while (true)
|
||||
{
|
||||
if (fs::exists(targetFileName))
|
||||
{
|
||||
if (IsSameFile(zipFileName, targetFileName))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
renamed = true;
|
||||
targetFileName = NextFileName(targetFileName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
zipFile->ExtractTo(zipFileName, targetFileName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (renamed)
|
||||
{
|
||||
std::string newName = fs::path(baseName).parent_path() / targetFileName.filename();
|
||||
RenameMediaFileProperty(baseName, newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string PresetBundleReaderImpl::GetPresetJson()
|
||||
{
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(this->bankFile);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string PresetBundleReaderImpl::GetPluginPresetsJson()
|
||||
{
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(this->pluginPresets);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
PresetBundleReaderImpl::~PresetBundleReaderImpl() noexcept
|
||||
{
|
||||
}
|
||||
PresetBundleReader::~PresetBundleReader() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
PresetBundleWriterImpl::~PresetBundleWriterImpl() noexcept
|
||||
{
|
||||
}
|
||||
PresetBundleWriter::~PresetBundleWriter() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
class PluginMetadata
|
||||
{
|
||||
public:
|
||||
PluginMetadata(std::shared_ptr<Lv2PluginInfo> &pluginInfo)
|
||||
{
|
||||
metadata_["uri"] = pluginInfo->uri();
|
||||
metadata_["name"] = pluginInfo->name();
|
||||
metadata_["brand"] = pluginInfo->brand();
|
||||
metadata_["authorName"] = pluginInfo->author_name();
|
||||
metadata_["authorHomePage"] = pluginInfo->author_homepage();
|
||||
}
|
||||
PluginMetadata(const std::string &uri, const std::string &name)
|
||||
{
|
||||
metadata_["uri"] = uri;
|
||||
metadata_["name"] = name.empty() ? uri : name;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> metadata_;
|
||||
};
|
||||
|
||||
void PresetBundleWriterImpl::AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name)
|
||||
{
|
||||
if (!usedPlugins.contains(pluginUri))
|
||||
{
|
||||
usedPlugins.insert(pluginUri);
|
||||
|
||||
auto pluginInfo = model.GetPluginInfo(pluginUri);
|
||||
|
||||
if (pluginInfo)
|
||||
{
|
||||
// use dictionaries because it's more version tolerant.
|
||||
PluginMetadata metadata(pluginInfo);
|
||||
this->pluginMetadata.push_back(std::move(metadata.metadata_));
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginMetadata metadata(pluginUri, name);
|
||||
this->pluginMetadata.push_back(std::move(metadata.metadata_));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2024 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pipedal {
|
||||
class PiPedalModel;
|
||||
|
||||
class PresetBundleWriter {
|
||||
public:
|
||||
using self = PresetBundleWriter;
|
||||
using ptr = std::unique_ptr<self>;
|
||||
|
||||
virtual ~PresetBundleWriter() noexcept = 0;
|
||||
|
||||
virtual void WriteToFile(const std::filesystem::path&filePath) = 0;
|
||||
|
||||
|
||||
static ptr CreatePresetsFile(PiPedalModel&model,const std::string&presetJson);
|
||||
static ptr CreatePluginPresetsFile(PiPedalModel&model,const std::string&presetJson);
|
||||
};
|
||||
|
||||
class PresetBundleReader {
|
||||
public:
|
||||
using self = PresetBundleReader;
|
||||
using ptr = std::unique_ptr<self>;
|
||||
|
||||
virtual ~PresetBundleReader() noexcept = 0;
|
||||
|
||||
virtual void ExtractMediaFiles() = 0;
|
||||
virtual std::string GetPresetJson() = 0;
|
||||
virtual std::string GetPluginPresetsJson() = 0;
|
||||
static ptr LoadPresetsFile(PiPedalModel&model,const std::filesystem::path &path);
|
||||
static ptr LoadPluginPresetsFile(PiPedalModel&model,const std::filesystem::path &path);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -26,8 +26,12 @@
|
||||
#include "lv2/atom/atom.h"
|
||||
#include "RealtimeMidiEventType.hpp"
|
||||
#include <chrono>
|
||||
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
class IndexedSnapshot;
|
||||
|
||||
enum class RingBufferCommand : int64_t
|
||||
{
|
||||
@@ -39,6 +43,10 @@ namespace pipedal
|
||||
AudioStopped,
|
||||
SetVuSubscriptions,
|
||||
FreeVuSubscriptions,
|
||||
|
||||
LoadSnapshot,
|
||||
FreeSnapshot,
|
||||
|
||||
SendVuUpdate,
|
||||
AckVuUpdate,
|
||||
|
||||
@@ -68,6 +76,8 @@ namespace pipedal
|
||||
|
||||
RealtimeMidiEvent,
|
||||
|
||||
SendPathPropertyBuffer,
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -449,6 +459,11 @@ namespace pipedal
|
||||
{
|
||||
write(RingBufferCommand::SetVuSubscriptions, configuration);
|
||||
}
|
||||
void LoadSnapshot(IndexedSnapshot*snapshot)
|
||||
{
|
||||
write(RingBufferCommand::LoadSnapshot,snapshot);
|
||||
}
|
||||
|
||||
void AckMidiProgramRequest(int64_t requestId)
|
||||
{
|
||||
write(RingBufferCommand::AckMidiProgramChange,requestId);
|
||||
@@ -485,11 +500,20 @@ namespace pipedal
|
||||
write(RingBufferCommand::EffectReplaced, pedalboard);
|
||||
}
|
||||
|
||||
void FreeSnapshot(IndexedSnapshot*snapshot)
|
||||
{
|
||||
write(RingBufferCommand::FreeSnapshot,snapshot);
|
||||
}
|
||||
|
||||
void WriteLv2ErrorMessage(int64_t instanceId, const char*message)
|
||||
{
|
||||
size_t length = strlen(message);
|
||||
write(RingBufferCommand::Lv2ErrorMessage,instanceId,length,(uint8_t*)message);
|
||||
}
|
||||
void SendPathPropertyBuffer(PatchPropertyWriter::Buffer*buffer)
|
||||
{
|
||||
write(RingBufferCommand::SendPathPropertyBuffer,buffer);
|
||||
}
|
||||
};
|
||||
|
||||
typedef RingBufferReader<true, false> RealtimeRingBufferReader;
|
||||
|
||||
+49
-15
@@ -165,23 +165,55 @@ Lv2PluginInfo::ptr g_splitterPluginInfo = std::make_shared<Lv2PluginInfo>(makeSp
|
||||
|
||||
Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; }
|
||||
|
||||
SplitEffect::SplitEffect(
|
||||
uint64_t instanceId,
|
||||
double sampleRate,
|
||||
const std::vector<float *> &inputs)
|
||||
: instanceId(instanceId), inputs(inputs), sampleRate(sampleRate)
|
||||
|
||||
{
|
||||
controlIndex["splitType"] = SPLIT_TYPE_CTL;
|
||||
controlIndex["select"] = SELECT_CTL;
|
||||
controlIndex["mix"] = MIX_CTL;
|
||||
controlIndex["panL"] = PANL_CTL;
|
||||
controlIndex["volL"] = VOLL_CTL;
|
||||
controlIndex["panR"] = PANR_CTL;
|
||||
controlIndex["volR"] = VOLR_CTL;
|
||||
|
||||
Lv2PluginInfo::ptr puginInfo = g_splitterPluginInfo;
|
||||
|
||||
for (auto&port: puginInfo->ports())
|
||||
{
|
||||
if (port->is_control_port() && port->is_input())
|
||||
{
|
||||
auto index = port->index();
|
||||
if (index < defaultInputControlValues.size()) {
|
||||
defaultInputControlValues[index] = port->default_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SplitEffect::GetMaxInputControl() const { return MAX_INPUT_CONTROL;}
|
||||
bool SplitEffect::IsInputControl(uint64_t index) const
|
||||
{
|
||||
return index >= 0 && index < MAX_INPUT_CONTROL;
|
||||
}
|
||||
float SplitEffect::GetDefaultInputControlValue(uint64_t index) const
|
||||
{
|
||||
if (index < 0 || index >= MAX_INPUT_CONTROL) return 0;
|
||||
return defaultInputControlValues[index];
|
||||
}
|
||||
|
||||
|
||||
int SplitEffect::GetControlIndex(const std::string &symbol) const
|
||||
{
|
||||
if (symbol == "splitType")
|
||||
return SPLIT_TYPE_CTL;
|
||||
if (symbol == "select")
|
||||
return SELECT_CTL;
|
||||
if (symbol == "mix")
|
||||
return MIX_CTL;
|
||||
if (symbol == "panL")
|
||||
return PANL_CTL;
|
||||
if (symbol == "volL")
|
||||
return VOLL_CTL;
|
||||
if (symbol == "panR")
|
||||
return PANR_CTL;
|
||||
if (symbol == "volR")
|
||||
return VOLR_CTL;
|
||||
return -1;
|
||||
auto i = controlIndex.find(symbol);
|
||||
if (i == controlIndex.end())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return i->second;
|
||||
}
|
||||
|
||||
void SplitEffect::Activate()
|
||||
@@ -344,6 +376,8 @@ float SplitEffect::GetControlValue(int portIndex) const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SplitEffect::SetControl(int index, float value)
|
||||
{
|
||||
switch (index)
|
||||
|
||||
+21
-9
@@ -23,6 +23,8 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include "PiPedalMath.hpp"
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -75,6 +77,9 @@ namespace pipedal
|
||||
|
||||
const double MIX_TRANSITION_TIME_S = 0.1;
|
||||
double sampleRate;
|
||||
|
||||
std::unordered_map<std::string,int> controlIndex;
|
||||
|
||||
std::vector<float *> inputs;
|
||||
std::vector<float *> topInputs;
|
||||
std::vector<float *> bottomInputs;
|
||||
@@ -111,6 +116,8 @@ namespace pipedal
|
||||
float blendDxLBottom = 0;
|
||||
float blendDxRBottom = 0;
|
||||
|
||||
std::vector<float> defaultInputControlValues;
|
||||
|
||||
int32_t blendFadeSamples;
|
||||
|
||||
bool selectA = true;
|
||||
@@ -288,27 +295,23 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
uint64_t instanceId;
|
||||
virtual bool IsLv2Effect() const override { return true; }
|
||||
|
||||
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
|
||||
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri) {}
|
||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
|
||||
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
|
||||
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
|
||||
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
|
||||
virtual bool GetLv2State(Lv2PluginState*state) override { return false; }
|
||||
virtual void SetLv2State(Lv2PluginState&state) override { }
|
||||
virtual bool HasErrorMessage() const { return false; }
|
||||
const char* TakeErrorMessage() { return ""; }
|
||||
virtual bool IsVst3() const { return false; }
|
||||
|
||||
public:
|
||||
SplitEffect(
|
||||
uint64_t instanceId,
|
||||
double sampleRate,
|
||||
const std::vector<float *> &inputs)
|
||||
: instanceId(instanceId), inputs(inputs), sampleRate(sampleRate)
|
||||
|
||||
{
|
||||
}
|
||||
const std::vector<float *> &inputs);
|
||||
~SplitEffect()
|
||||
{
|
||||
}
|
||||
@@ -321,6 +324,12 @@ namespace pipedal
|
||||
static constexpr int VOLL_CTL = 4;
|
||||
static constexpr int PANR_CTL = 5;
|
||||
static constexpr int VOLR_CTL = 6;
|
||||
static constexpr int MAX_INPUT_CONTROL =7;
|
||||
|
||||
virtual uint64_t GetMaxInputControl() const override;
|
||||
virtual bool IsInputControl(uint64_t index) const override;
|
||||
virtual float GetDefaultInputControlValue(uint64_t index) const override;
|
||||
|
||||
|
||||
|
||||
virtual uint64_t GetInstanceId() const
|
||||
@@ -356,7 +365,10 @@ namespace pipedal
|
||||
return GetControlValue(index);
|
||||
}
|
||||
virtual float GetControlValue(int portIndex) const;
|
||||
virtual void SetControl(int index, float value);
|
||||
virtual void SetControl(int index, float value) override;
|
||||
virtual void SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value) override {}
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri) {}
|
||||
virtual void RequestAllPathPatchProperties() {}
|
||||
|
||||
|
||||
virtual int GetNumberOfOutputAudioPorts() const
|
||||
|
||||
+11
-11
@@ -352,7 +352,7 @@ void Storage::SavePluginPresetIndex()
|
||||
throw PiPedalException(SS("Can't write to " << path));
|
||||
}
|
||||
|
||||
json_writer writer(os, false);
|
||||
json_writer writer(os, true);
|
||||
writer.write(this->pluginPresetIndex);
|
||||
}
|
||||
}
|
||||
@@ -361,7 +361,7 @@ void Storage::SaveBankIndex()
|
||||
{
|
||||
pipedal::ofstream_synced os;
|
||||
os.open(GetIndexFileName(), std::ios_base::trunc);
|
||||
json_writer writer(os, false);
|
||||
json_writer writer(os, true);
|
||||
writer.write(this->bankIndex);
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
|
||||
{
|
||||
pipedal::ofstream_synced s;
|
||||
s.open(fileName, std::ios_base::trunc);
|
||||
json_writer writer(s, false);
|
||||
json_writer writer(s, true);
|
||||
writer.write(bankFile);
|
||||
if (std::filesystem::exists(backupFile))
|
||||
{
|
||||
@@ -722,7 +722,7 @@ void Storage::SaveChannelSelection()
|
||||
try
|
||||
{
|
||||
pipedal::ofstream_synced s(fileName);
|
||||
json_writer writer(s, false);
|
||||
json_writer writer(s, true);
|
||||
writer.write(this->jackChannelSelection);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@@ -911,7 +911,7 @@ int64_t Storage::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
||||
{
|
||||
throw PiPedalException("Can't write to bank file.");
|
||||
}
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(bankFile);
|
||||
|
||||
lastBank = this->bankIndex.addBank(lastBank, bankFile.name());
|
||||
@@ -933,7 +933,7 @@ void Storage::SaveUserSettings()
|
||||
{
|
||||
throw PiPedalException("Unable to write to " + ((std::string)path));
|
||||
}
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(userSettings);
|
||||
}
|
||||
}
|
||||
@@ -1048,7 +1048,7 @@ void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset)
|
||||
std::filesystem::path path = GetCurrentPresetPath();
|
||||
|
||||
pipedal::ofstream_synced f(path);
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(currentPreset);
|
||||
}
|
||||
catch (std::exception &)
|
||||
@@ -1128,7 +1128,7 @@ void Storage::SavePluginPresets(const std::string &pluginUri, const PluginPreset
|
||||
{
|
||||
throw PiPedalException(SS("Can't write to " << path));
|
||||
}
|
||||
json_writer writer(os, false);
|
||||
json_writer writer(os, true);
|
||||
writer.write(presets);
|
||||
}
|
||||
if (std::filesystem::exists(path))
|
||||
@@ -1370,7 +1370,7 @@ void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(favorites);
|
||||
}
|
||||
}
|
||||
@@ -1399,7 +1399,7 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(jackConfiguration);
|
||||
}
|
||||
#if JACK_HOST
|
||||
@@ -1414,7 +1414,7 @@ void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_writer writer(f, false);
|
||||
json_writer writer(f, true);
|
||||
writer.write(bindings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ TemporaryFile::TemporaryFile(const std::filesystem::path&directory)
|
||||
this->path = filename;
|
||||
|
||||
}
|
||||
void TemporaryFile::Detach() {
|
||||
this->path.clear();
|
||||
}
|
||||
TemporaryFile::~TemporaryFile()
|
||||
{
|
||||
if (!path.empty())
|
||||
|
||||
@@ -22,10 +22,16 @@
|
||||
namespace pipedal {
|
||||
class TemporaryFile {
|
||||
public:
|
||||
TemporaryFile() {}
|
||||
TemporaryFile(const TemporaryFile&) = delete;
|
||||
TemporaryFile&operator=(const TemporaryFile&) = delete;
|
||||
|
||||
TemporaryFile(TemporaryFile&&other) {
|
||||
this->path = std::move(other.path);
|
||||
}
|
||||
TemporaryFile(const std::filesystem::path&parentDirectory);
|
||||
|
||||
void Detach();
|
||||
~TemporaryFile();
|
||||
const std::filesystem::path&Path()const { return path;}
|
||||
std::string str() const { return path.c_str(); }
|
||||
|
||||
@@ -376,17 +376,6 @@ GithubRelease::GithubRelease(json_variant &v)
|
||||
|
||||
|
||||
|
||||
static std::vector<std::string> split(const std::string &s, char delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
static std::string justTheVersion(const std::string &assetName)
|
||||
{
|
||||
// eg. pipedal_1.2.41_arm64.deb
|
||||
|
||||
+23
-5
@@ -54,14 +54,17 @@
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
const bool ENABLE_KEEP_ALIVE = true;
|
||||
const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
||||
static const bool ENABLE_KEEP_ALIVE = true;
|
||||
static const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
||||
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
|
||||
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;
|
||||
;
|
||||
using namespace boost;
|
||||
using namespace websocketpp::http;
|
||||
|
||||
|
||||
|
||||
class request_with_file_upload : public websocketpp::http::parser::parser
|
||||
{
|
||||
@@ -516,6 +519,7 @@ inline std::error_code request_with_file_upload::process(std::string::iterator b
|
||||
class CustomPpConfig : public websocketpp::config::asio
|
||||
{
|
||||
public:
|
||||
using super = websocketpp::config::asio;
|
||||
typedef CustomPpConfig type;
|
||||
typedef websocketpp::config::asio base;
|
||||
|
||||
@@ -523,6 +527,7 @@ public:
|
||||
typedef pipedal_elog elog_type;
|
||||
typedef pipedal_alog alog_type;
|
||||
|
||||
|
||||
typedef request_with_file_upload request_type;
|
||||
|
||||
struct transport_config : public base::transport_config
|
||||
@@ -538,6 +543,8 @@ public:
|
||||
|
||||
typedef websocketpp::transport::asio::endpoint<transport_config>
|
||||
transport_type;
|
||||
|
||||
typedef transport_type::transport_con_type x;
|
||||
};
|
||||
|
||||
size_t CustomPpConfig::max_http_body_size = MAX_READ_SIZE;
|
||||
@@ -693,8 +700,19 @@ namespace pipedal
|
||||
ss << size;
|
||||
request.replace_header(HttpField::content_length, ss.str());
|
||||
}
|
||||
virtual void setBody(const std::string &body) { request.set_body(body); }
|
||||
virtual void keepAlive(bool value)
|
||||
virtual void setBody(const std::string &body) override{ request.set_body(body); }
|
||||
virtual void setBodyFile(std::shared_ptr<TemporaryFile>&bodyFile) override {
|
||||
// cast away const to do what ther request would do if it had that method.
|
||||
|
||||
request.set_body_file(bodyFile->Path(),true);
|
||||
bodyFile->Detach();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
virtual void keepAlive(bool value) override
|
||||
{
|
||||
if ((!value) || (!ENABLE_KEEP_ALIVE))
|
||||
{
|
||||
@@ -1126,7 +1144,7 @@ namespace pipedal
|
||||
|
||||
Lv2Log::error("Failed to open session: %s", e.what());
|
||||
}
|
||||
m_endpoint.close(hdl,websocketpp::close::status::abnormal_close, "");
|
||||
//m_endpoint.close(hdl,websocketpp::close::status::abnormal_close, "");
|
||||
}
|
||||
|
||||
void on_fail(connection_hdl hdl)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Uri.hpp"
|
||||
#include <string_view>
|
||||
#include <filesystem>
|
||||
#include "TemporaryFile.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +41,9 @@ public:
|
||||
virtual void set(const std::string&key, const std::string&value) = 0;
|
||||
virtual void setContentLength(size_t size) = 0;
|
||||
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 keepAlive(bool value) = 0;
|
||||
};
|
||||
|
||||
|
||||
+177
-40
@@ -30,24 +30,41 @@
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include "UpdaterSecurity.hpp"
|
||||
#include "TemporaryFile.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
|
||||
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||
#define PRESET_EXTENSION ".piPreset"
|
||||
#define BANK_EXTENSION ".piBank"
|
||||
#define OLD_BANK_EXTENSION ".piBank"
|
||||
#define BANK_EXTENSION ".piBankBundle"
|
||||
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
|
||||
|
||||
static const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
||||
|
||||
static const std::string PLUGIN_PRESETS_MIME_TYPE = "application/vnd.pipedal.pluginPresets";
|
||||
static const std::string PRESET_MIME_TYPE = "application/vnd.pipedal.preset";
|
||||
static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank";
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace boost::system;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
static bool IsZipFile(const std::filesystem::path &path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) return false;
|
||||
|
||||
char c[4];
|
||||
memset(c,0,sizeof(c));
|
||||
|
||||
f >> c[0] >> c[1] >> c[2] >> c[3];
|
||||
|
||||
// official file header according to PKware documetnation.
|
||||
return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04;
|
||||
|
||||
|
||||
static std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
class ExtensionChecker {
|
||||
@@ -192,7 +209,14 @@ public:
|
||||
std::string name;
|
||||
std::string content;
|
||||
GetPluginPresets(request_uri, &name, &content);
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||
@@ -204,9 +228,14 @@ public:
|
||||
std::string content;
|
||||
GetPreset(request_uri, &name, &content);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
res.set(HttpField::content_type, PRESET_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
res.setContentLength(contentLength);
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||
return;
|
||||
}
|
||||
@@ -216,10 +245,18 @@ public:
|
||||
std::string content;
|
||||
GetBank(request_uri, &name, &content);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
|
||||
|
||||
|
||||
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||
res.setContentLength(content.length());
|
||||
res.setContentLength(contentLength);
|
||||
return;
|
||||
}
|
||||
throw PiPedalException("Not found.");
|
||||
@@ -252,11 +289,18 @@ public:
|
||||
std::string name;
|
||||
std::string content;
|
||||
GetPluginPresets(request_uri, &name, &content);
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
res.setContentLength(contentLength);
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||
res.setBody(content);
|
||||
res.setBodyFile(tmpFile);
|
||||
}
|
||||
else if (segment == "downloadPreset")
|
||||
{
|
||||
@@ -264,11 +308,17 @@ public:
|
||||
std::string content;
|
||||
GetPreset(request_uri, &name, &content);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PRESET_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
res.setContentLength(contentLength);
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||
res.setBody(content);
|
||||
res.setBodyFile(tmpFile);
|
||||
}
|
||||
else if (segment == "downloadBank")
|
||||
{
|
||||
@@ -276,11 +326,18 @@ public:
|
||||
std::string content;
|
||||
GetBank(request_uri, &name, &content);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
|
||||
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
res.setContentLength(contentLength);
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||
res.setBody(content);
|
||||
res.setBodyFile(tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -315,6 +372,32 @@ public:
|
||||
return fileNames[0];
|
||||
}
|
||||
}
|
||||
static bool HasSingleRootDirectory(ZipFileReader&zipFile) {
|
||||
bool hasDirectory = false;
|
||||
std::string previousDirectory;
|
||||
for (auto& file: zipFile.GetFiles())
|
||||
{
|
||||
auto pos = file.find('/');
|
||||
if (pos == std::string::npos) {
|
||||
// a file in the root.
|
||||
return false;
|
||||
} else
|
||||
{
|
||||
std::string currentRootDirectory = file.substr(0,pos);
|
||||
if (hasDirectory) {
|
||||
if (currentRootDirectory != previousDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
hasDirectory = true;
|
||||
previousDirectory = currentRootDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void post_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
@@ -327,11 +410,28 @@ public:
|
||||
|
||||
if (segment == "uploadPluginPresets")
|
||||
{
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
PluginPresets presets;
|
||||
reader.read(&presets);
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model),filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPluginPresetsJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&presets);
|
||||
|
||||
} else {
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&presets);
|
||||
}
|
||||
model->UploadPluginPresets(presets);
|
||||
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
std::stringstream sResult;
|
||||
@@ -343,7 +443,27 @@ public:
|
||||
}
|
||||
else if (segment == "uploadPreset")
|
||||
{
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
|
||||
BankFile bankFile;
|
||||
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPresetJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&bankFile);
|
||||
} else {
|
||||
// legacy json format, no zip, no media files.
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&bankFile);
|
||||
}
|
||||
|
||||
uint64_t uploadAfter = -1;
|
||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||
@@ -351,10 +471,6 @@ public:
|
||||
{
|
||||
uploadAfter = std::stol(strUploadAfter);
|
||||
}
|
||||
|
||||
BankFile bankFile;
|
||||
reader.read(&bankFile);
|
||||
|
||||
uint64_t instanceId = model->UploadPreset(bankFile, uploadAfter);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
@@ -363,12 +479,31 @@ public:
|
||||
sResult << instanceId;
|
||||
std::string result = sResult.str();
|
||||
res.setContentLength(result.length());
|
||||
|
||||
res.setBody(result);
|
||||
}
|
||||
else if (segment == "uploadBank")
|
||||
{
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
|
||||
BankFile bankFile;
|
||||
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPresetJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&bankFile);
|
||||
} else {
|
||||
// legacy json format, no zip, no media files.
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&bankFile);
|
||||
}
|
||||
|
||||
uint64_t uploadAfter = -1;
|
||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||
@@ -377,9 +512,6 @@ public:
|
||||
uploadAfter = std::stol(strUploadAfter);
|
||||
}
|
||||
|
||||
BankFile bankFile;
|
||||
reader.read(&bankFile);
|
||||
|
||||
uint64_t instanceId = model->UploadBank(bankFile, uploadAfter);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
@@ -394,10 +526,10 @@ public:
|
||||
{
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
const std::string instanceId = request_uri.query("id");
|
||||
const std::string directory = request_uri.query("directory");
|
||||
const std::string filename = request_uri.query("filename");
|
||||
const std::string patchProperty = request_uri.query("property");
|
||||
std::string instanceId = request_uri.query("id");
|
||||
std::string directory = request_uri.query("directory");
|
||||
std::string filename = request_uri.query("filename");
|
||||
std::string patchProperty = request_uri.query("property");
|
||||
|
||||
|
||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||
@@ -415,9 +547,14 @@ public:
|
||||
{
|
||||
ExtensionChecker extensionChecker { request_uri.query("ext") };
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
try {
|
||||
auto zipFile = ZipFile::Create(req.get_body_temporary_file());
|
||||
auto zipFile = ZipFileReader::Create(req.get_body_temporary_file());
|
||||
std::vector<std::string> files = zipFile->GetFiles();
|
||||
bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile);
|
||||
if (!hasSingleRootDirectory) {
|
||||
directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string();
|
||||
}
|
||||
for (const auto&inputFile : files)
|
||||
{
|
||||
if (!inputFile.ends_with("/")) // don't process directory entries.
|
||||
|
||||
+157
-6
@@ -27,14 +27,20 @@
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
ZipFile::ZipFile()
|
||||
ZipFileReader::ZipFileReader()
|
||||
{
|
||||
}
|
||||
ZipFile::~ZipFile()
|
||||
ZipFileReader::~ZipFileReader()
|
||||
{
|
||||
}
|
||||
ZipFileWriter::ZipFileWriter()
|
||||
{
|
||||
}
|
||||
ZipFileWriter::~ZipFileWriter()
|
||||
{
|
||||
}
|
||||
|
||||
class ZipFileImpl : public ZipFile
|
||||
class ZipFileImpl : public ZipFileReader
|
||||
{
|
||||
public:
|
||||
ZipFileImpl(const std::filesystem::path &path)
|
||||
@@ -49,6 +55,7 @@ public:
|
||||
}
|
||||
virtual ~ZipFileImpl();
|
||||
virtual std::vector<std::string> GetFiles() override;
|
||||
virtual bool CompareFiles(const std::string &zipName, const std::filesystem::path& path) override;
|
||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path &path) override;
|
||||
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) override;
|
||||
virtual size_t GetFileSize(const std::string&filename) override;
|
||||
@@ -59,9 +66,9 @@ private:
|
||||
zip_t *zipFile = nullptr;
|
||||
};
|
||||
|
||||
ZipFile::ptr ZipFile::Create(const std::filesystem::path &path)
|
||||
ZipFileReader::ptr ZipFileReader::Create(const std::filesystem::path &path)
|
||||
{
|
||||
return std::shared_ptr<ZipFile>(new ZipFileImpl(path));
|
||||
return std::shared_ptr<ZipFileReader>(new ZipFileImpl(path));
|
||||
}
|
||||
ZipFileImpl::~ZipFileImpl()
|
||||
{
|
||||
@@ -88,6 +95,85 @@ std::vector<std::string> ZipFileImpl::GetFiles()
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool ZipFileImpl::CompareFiles(const std::string &zipName, const std::filesystem::path &path)
|
||||
{
|
||||
auto fi = nameMap.find(zipName);
|
||||
if (fi == nameMap.end())
|
||||
{
|
||||
// must call GetFiles() firest.
|
||||
throw std::runtime_error("Zip content file not found.");
|
||||
}
|
||||
zip_int64_t fileIndex = fi->second;
|
||||
|
||||
zip_stat_t zip_stat;
|
||||
if (zip_stat_index(zipFile, fileIndex, 0, &zip_stat) < 0) {
|
||||
throw std::runtime_error(SS("Failed to get file stats: " << zip_strerror(zipFile)));
|
||||
}
|
||||
|
||||
auto targetSize = std::filesystem::file_size(path);
|
||||
if (targetSize != zip_stat.size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
zip_file_t *fIn = zip_fopen_index(this->zipFile, fileIndex, 0);
|
||||
if (fIn == nullptr)
|
||||
{
|
||||
zip_error_t *error = zip_get_error(this->zipFile);
|
||||
const char *strError = zip_error_strerror(error);
|
||||
|
||||
throw std::runtime_error(SS("Failed to read zip content file. " << strError));
|
||||
}
|
||||
Finally t{[fIn]() mutable
|
||||
{
|
||||
zip_fclose(fIn);
|
||||
}};
|
||||
|
||||
FILE*fTarget = fopen(path.c_str(),"r");
|
||||
if (fTarget == nullptr)
|
||||
{
|
||||
throw std::runtime_error(SS("Failed to read file " << path << "."));
|
||||
}
|
||||
Finally ffTarget{[fTarget]() mutable {
|
||||
fclose(fTarget);
|
||||
}};
|
||||
|
||||
constexpr int BUFFER_SIZE = 16 * 1024;
|
||||
std::vector<char> vBuff(BUFFER_SIZE);
|
||||
std::vector<char> vBuff2(BUFFER_SIZE);
|
||||
char *pBuff = (char *)&(vBuff[0]);
|
||||
char *pBuff2 = (char *)&(vBuff2[0]);
|
||||
while (true)
|
||||
{
|
||||
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
||||
if (nRead == -1)
|
||||
{
|
||||
zip_error_t *error = zip_file_get_error(fIn);
|
||||
const char *strError = zip_error_strerror(error);
|
||||
throw std::runtime_error(SS("Error reading zip content file." << strError));
|
||||
}
|
||||
auto nTargetRead = fread(pBuff2,1,BUFFER_SIZE,fTarget);
|
||||
|
||||
if ((zip_int64_t)nTargetRead != nRead)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (nRead == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (zip_int64_t i = 0; i < nRead; ++i)
|
||||
{
|
||||
if (pBuff2[i] != pBuff[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::path &path)
|
||||
{
|
||||
auto fi = nameMap.find(zipName);
|
||||
@@ -120,7 +206,7 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
|
||||
while (true)
|
||||
{
|
||||
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
||||
if (nRead = 0)
|
||||
if (nRead == 0)
|
||||
break;
|
||||
if (nRead == -1)
|
||||
{
|
||||
@@ -215,3 +301,68 @@ size_t ZipFileImpl::GetFileSize(const std::string&filename)
|
||||
}
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
class ZipFileWriterImpl : public ZipFileWriter
|
||||
{
|
||||
public:
|
||||
ZipFileWriterImpl(const std::filesystem::path &path)
|
||||
: path(path)
|
||||
{
|
||||
int errorOp = 0;
|
||||
zipFile = zip_open(path.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorOp);
|
||||
if (zipFile == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Can't open zip file.");
|
||||
}
|
||||
}
|
||||
virtual ~ZipFileWriterImpl();
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
virtual void WriteFile(const std::string &zipFilename, const std::filesystem::path&sourceFilePath) override;
|
||||
virtual void WriteFile(const std::string&filename, const void*buffer, size_t length) override;
|
||||
private:
|
||||
std::map<std::string, zip_int64_t> nameMap; // avoid o(2) extraction operations.
|
||||
const std::filesystem::path path;
|
||||
zip_t *zipFile = nullptr;
|
||||
};
|
||||
|
||||
ZipFileWriter::ptr ZipFileWriter::Create(const std::filesystem::path &path)
|
||||
{
|
||||
return std::make_unique<ZipFileWriterImpl>(path);
|
||||
}
|
||||
|
||||
void ZipFileWriterImpl::Close() {
|
||||
if (zipFile) {
|
||||
zip_close(zipFile);
|
||||
zipFile = nullptr;
|
||||
}
|
||||
}
|
||||
ZipFileWriterImpl::~ZipFileWriterImpl()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void ZipFileWriterImpl::WriteFile(const std::string &filename, const std::filesystem::path &path)
|
||||
{
|
||||
zip_error_t error;
|
||||
zip_source_t *source = zip_source_file_create(path.c_str(),0,-1,&error);
|
||||
if (!source) {
|
||||
throw std::runtime_error(SS("Unable to create zip source for file " << path));
|
||||
}
|
||||
if (zip_file_add(zipFile,filename.c_str(),source,ZIP_FL_ENC_UTF_8) < 0)
|
||||
{
|
||||
zip_source_free(source);
|
||||
throw std::runtime_error(SS("Unable to create add file " << path));
|
||||
}
|
||||
}
|
||||
void ZipFileWriterImpl::WriteFile(const std::string&filename, const void*buffer, size_t length)
|
||||
{
|
||||
zip_source_t *source = zip_source_buffer(zipFile,buffer,length,0);
|
||||
|
||||
if (zip_file_add(zipFile,filename.c_str(),source,ZIP_FL_ENC_UTF_8) < 0)
|
||||
{
|
||||
zip_source_free(source);
|
||||
throw std::runtime_error(SS("Failed to add file to zip: " << zip_strerror(zipFile)));
|
||||
}
|
||||
}
|
||||
|
||||
+35
-6
@@ -28,6 +28,12 @@
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class ZipFileFileReader {
|
||||
public:
|
||||
private:
|
||||
zip_file_t*file = nullptr;
|
||||
};
|
||||
|
||||
class zip_file_input_stream_buf : public std::streambuf {
|
||||
private:
|
||||
zip_file_t* file;
|
||||
@@ -51,21 +57,44 @@ namespace pipedal {
|
||||
|
||||
}
|
||||
};
|
||||
class ZipFile {
|
||||
class ZipFileReader {
|
||||
protected:
|
||||
ZipFile();
|
||||
ZipFileReader();
|
||||
|
||||
public:
|
||||
using ptr = std::shared_ptr<ZipFile>;
|
||||
using ptr = std::shared_ptr<ZipFileReader>;
|
||||
static ptr Create(const std::filesystem::path &path);
|
||||
ZipFile(const ZipFile&) = delete;
|
||||
ZipFile&operator=(const ZipFile&) = delete;
|
||||
virtual ~ZipFile();
|
||||
ZipFileReader(const ZipFileReader&) = delete;
|
||||
ZipFileReader&operator=(const ZipFileReader&) = delete;
|
||||
virtual ~ZipFileReader();
|
||||
|
||||
virtual std::vector<std::string> GetFiles() = 0;
|
||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path& path) = 0;
|
||||
virtual bool CompareFiles(const std::string &zipName, const std::filesystem::path& path) = 0;
|
||||
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) = 0;
|
||||
virtual size_t GetFileSize(const std::string&filename) = 0;
|
||||
|
||||
};
|
||||
|
||||
class ZipFileWriter;
|
||||
|
||||
|
||||
class ZipFileWriter {
|
||||
protected:
|
||||
ZipFileWriter();
|
||||
|
||||
public:
|
||||
using self = ZipFileWriter();
|
||||
using ptr = std::shared_ptr<ZipFileWriter>;
|
||||
static ptr Create(const std::filesystem::path &path);
|
||||
|
||||
ZipFileWriter(const ZipFileReader&) = delete;
|
||||
ZipFileWriter&operator=(const ZipFileWriter&) = delete;
|
||||
virtual ~ZipFileWriter();
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual void WriteFile(const std::string &filename, const std::filesystem::path&path) = 0;
|
||||
virtual void WriteFile(const std::string&filename, const void*buffer, size_t length) = 0;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user