This commit is contained in:
Robin Davies
2023-04-16 01:13:03 -04:00
parent d05c7c7104
commit 845af2ee47
93 changed files with 3987 additions and 1688 deletions
+62 -15
View File
@@ -501,6 +501,20 @@ private:
this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
break;
}
case RingBufferCommand::SetInputVolume:
{
SetVolumeBody body;
realtimeReader.readComplete(&body);
this->realtimeActivePedalboard->SetInputVolume(body.value);
break;
}
case RingBufferCommand::SetOutputVolume:
{
SetVolumeBody body;
realtimeReader.readComplete(&body);
this->realtimeActivePedalboard->SetOutputVolume(body.value);
break;
}
case RingBufferCommand::ParameterRequest:
{
RealtimePatchPropertyRequest *pRequest = nullptr;
@@ -809,7 +823,7 @@ private:
{
if (this->realtimeVuBuffers != nullptr)
{
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes,inputBuffers,outputBuffers);
vuSamplesRemaining -= nframes;
if (vuSamplesRemaining <= 0)
@@ -1332,6 +1346,7 @@ public:
}
}
}
}
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
@@ -1350,8 +1365,23 @@ public:
}
}
virtual void SetInputVolume(float value) {
std::lock_guard guard(mutex);
if (active && this->currentPedalboard)
{
hostWriter.SetInputVolume(value);
}
}
virtual void SetOutputVolume(float value) {
std::lock_guard guard(mutex);
if (active && this->currentPedalboard)
{
hostWriter.SetOutputVolume(value);
}
}
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{
@@ -1372,21 +1402,33 @@ public:
{
int64_t instanceId = instanceIds[i];
auto effect = this->currentPedalboard->GetEffect(instanceId);
if (!effect)
if (effect)
{
throw PiPedalStateException("Effect not found.");
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index);
VuUpdate v;
v.instanceId_ = instanceId;
// Display mono VUs if a stereo device is being fed identical L/R inputs.
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
} else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) {
int index = (int)instanceId;
VuUpdate v;
vuConfig->enabledIndexes.push_back(index);
v.instanceId_ = instanceId;
if (instanceId == Pedalboard::INPUT_VOLUME_ID)
{
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1;
} else {
v.isStereoInput_ = v.isStereoOutput_ =this->pHost->GetNumberOfOutputAudioChannels() > 1;
}
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
}
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index);
VuUpdate v;
v.instanceId_ = instanceId;
// Display mono VUs if a stereo device is being fed identical L/R inputs.
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
}
this->hostWriter.SetVuSubscriptions(vuConfig);
@@ -1653,7 +1695,12 @@ void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
if (effect != nullptr)
{
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
try {
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
} catch(const std::exception &e)
{
Lv2Log::warning(SS(pedalboardItem.pluginName() << ": " << e.what()));
}
}
}
+3 -1
View File
@@ -207,7 +207,9 @@ public:
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> & values) = 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 IsOpen() const = 0;
+6 -1
View File
@@ -43,12 +43,17 @@ float AutoLilvNode::AsFloat(float defaultValue)
}
return defaultValue;
}
int AutoLilvNode::AsInt(int defaultValue)
int32_t AutoLilvNode::AsInt(int defaultValue)
{
if (node == nullptr)
return defaultValue;
if (lilv_node_is_int(node))
return lilv_node_as_int(node);
if (lilv_node_is_float(node))
{
return (int32_t)lilv_node_as_float(node);
}
return defaultValue;
}
bool AutoLilvNode::AsBool(bool defaultValue)
+76 -7
View File
@@ -37,9 +37,9 @@ namespace pipedal
{
}
AutoLilvNode(const LilvNode *node)
: node(const_cast<LilvNode*>(node))
: node(const_cast<LilvNode *>(node))
{
isConst = true;
isConst = true;
}
AutoLilvNode(LilvNode *node)
: node(node)
@@ -66,15 +66,17 @@ namespace pipedal
return *this;
}
operator LilvNode**() {
operator LilvNode **()
{
Free();
this->isConst = false;
return &node;
}
operator const LilvNode**() {
operator const LilvNode **()
{
Free();
this->isConst = true;
return (const LilvNode**)&node;
return (const LilvNode **)&node;
}
AutoLilvNode &operator=(AutoLilvNode &&other)
{
@@ -83,7 +85,7 @@ namespace pipedal
}
float AsFloat(float defaultValue = 0);
int AsInt(int defaultValue = 0);
int32_t AsInt(int defaultValue = 0);
bool AsBool(bool defaultValue = false);
std::string AsUri();
std::string AsString();
@@ -99,4 +101,71 @@ namespace pipedal
LilvNode *node = nullptr;
bool isConst = true;
};
};
class AutoLilvNodes
{
public:
AutoLilvNodes()
{
}
AutoLilvNodes(const LilvNodes *nodes)
: nodes(const_cast<LilvNodes *>(nodes))
{
isConst = true;
}
AutoLilvNodes(LilvNodes *nodes)
: nodes(nodes)
{
isConst = false;
}
~AutoLilvNodes() { Free(); }
operator const LilvNodes *()
{
return this->nodes;
}
operator bool()
{
return this->nodes != nullptr;
}
const LilvNodes *Get() { return nodes; }
AutoLilvNodes &operator=(LilvNodes *node)
{
Free();
this->nodes = nodes;
return *this;
}
operator LilvNodes **()
{
Free();
this->isConst = false;
return &nodes;
}
operator const LilvNodes **()
{
Free();
this->isConst = true;
return (const LilvNodes **)&nodes;
}
AutoLilvNodes &operator=(AutoLilvNodes &&other)
{
std::swap(this->nodes, other.nodes);
return *this;
}
void Free()
{
if (nodes != nullptr && !isConst)
lilv_nodes_free(nodes);
nodes = nullptr;
}
private:
LilvNodes *nodes = nullptr;
bool isConst = true;
};
}
+3
View File
@@ -141,6 +141,9 @@ else()
endif()
set (PIPEDAL_SOURCES
DbDezipper.hpp DbDezipper.cpp
WebServerLog.hpp
util.hpp util.cpp
PiPedalUI.hpp
PiPedalUI.cpp
+109
View File
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2022 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "DbDezipper.hpp"
using namespace pipedal;
static const int SEGMENT_SIZE = 64;
void DbDezipper::SetSampleRate(double sampleRate)
{
this->sampleRate = sampleRate;
this->dbPerSegment = 96/rate * SEGMENT_SIZE / sampleRate;
}
void DbDezipper::SetRate(float seconds)
{
this->rate = seconds;
this->dbPerSegment = 96/rate * SEGMENT_SIZE / sampleRate;
}
void DbDezipper::SetMinDb(float minDb)
{
this->minDb = minDb;
if (targetDb < minDb)
{
targetDb = minDb;
count = 0;
}
if (currentDb < minDb)
{
currentDb = minDb;
x = targetX = 0;
dx = 0;
count = 0;
}
}
void DbDezipper::NextSegment()
{
if (targetDb == currentDb)
{
x = targetX;
dx = 0;
if (targetDb <= minDb)
{
x = 0;
}
count = -1;
return;
}
else if (targetDb < currentDb)
{
currentDb -= dbPerSegment;
if (currentDb < targetDb)
{
currentDb = targetDb;
}
}
else
{
currentDb += dbPerSegment;
if (currentDb > targetDb)
{
currentDb = targetDb;
}
}
this->targetX = db2a(currentDb);
this->dx = (targetX - x) / SEGMENT_SIZE;
this->count = SEGMENT_SIZE;
}
void DbDezipper::Reset(float db)
{
float value;
if (db <= minDb)
{
value = 0;
} else {
value = db2a(db);
}
x = targetX = value;
dx = 0;
currentDb = db;
targetDb = db;
count = -1;
}
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2022 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include "PiPedalMath.hpp"
namespace pipedal
{
class DbDezipper
{
public:
void SetSampleRate(double rate);
void SetRate(float seconds);
void Reset(float db = -96);
void SetMinDb(float db = -96);
void SetTarget(float db)
{
if (db < minDb)
db = minDb;
if (db != targetDb)
{
targetDb = db;
count = 0;
}
}
bool IsIdle() const { return count < 0;}
inline float Tick()
{
if (count >= 0)
{
if (count-- <= 0)
{
NextSegment();
}
float result = x;
x += dx;
return result;
}
return x;
}
private:
float minDb = -96;
double sampleRate = 44100;
float rate = 0.1;
float targetDb = -96;
float currentDb = -96;
float targetX = 0;
float x = 0;
float dx = 0;
int32_t count = -1;
float dbPerSegment;
void NextSegment();
};
}
+1
View File
@@ -55,6 +55,7 @@ namespace pipedal
void JackInitialize(); // from jack server instance.
~JackConfiguration();
bool isValid() const { return isValid_;}
void isValid(bool value) { isValid_ = value; }
bool isOnboarding() const { return isOnboarding_;}
void isOnboarding(bool value) { isOnboarding_ = value; }
bool isRestarting() const { return isRestarting_; }
+26 -5
View File
@@ -148,7 +148,16 @@ void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem)
// Restore state if present.
if (this->stateInterface)
{
this->stateInterface->Restore(pedalboardItem.lv2State());
try {
if (pedalboardItem.lv2State().isValid_)
{
this->stateInterface->Restore(pedalboardItem.lv2State());
}
} catch (const std::exception &e)
{
std::string name = pedalboardItem.pluginName();
Lv2Log::warning(SS(name << ": " << e.what()));
}
}
}
@@ -613,7 +622,7 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
{
// frame_offset = ev->time.frames; // not really interested.
auto frame_offset = ev->time.frames; // not really interested.
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
{
@@ -649,9 +658,21 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
bool Lv2Effect::GetLv2State(Lv2PluginState*state)
{
if (!this->stateInterface) return false;
*state = this->stateInterface->Save();
return true;
try {
if (this->stateInterface == nullptr)
{
state->Erase();
return false;
}
*state = this->stateInterface->Save();
state->isValid_ = true;
return true;
}
catch (const std::exception&e)
{
state->Erase();
throw;
}
}
-1
View File
@@ -44,7 +44,6 @@ namespace pipedal
class Lv2Effect : public IEffect
{
private:
std::unique_ptr<StateInterface> stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
+73 -29
View File
@@ -210,6 +210,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
{
this->pHost = pHost;
inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
inputVolume.SetMinDb(-60);
outputVolume.SetMinDb(-60);
inputVolume.SetTarget(pedalboard.input_volume_db());
outputVolume.SetTarget(pedalboard.output_volume_db());
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
{
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
@@ -368,21 +377,32 @@ static void Copy(float *input, float *output, uint32_t samples)
bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
{
this->ringBufferWriter = ringBufferWriter;
for (int i = 0; i < this->pedalboardInputBuffers.size(); ++i)
for (size_t i = 0; i < this->pedalboardInputBuffers.size(); ++i)
{
if (inputBuffers[i] == nullptr)
return false;
Copy(inputBuffers[i], this->pedalboardInputBuffers[i], samples);
{
return false;
}
}
for (size_t i = 0; i < samples; ++i)
{
float volume = this->inputVolume.Tick();
for (int c = 0; c < this->pedalboardInputBuffers.size(); ++c)
{
this->pedalboardInputBuffers[c][i] = inputBuffers[c][i]*volume;
}
}
for (int i = 0; i < this->processActions.size(); ++i)
{
processActions[i](samples);
}
for (int i = 0; i < this->pedalboardOutputBuffers.size(); ++i)
for (size_t i = 0; i < samples; ++i)
{
if (outputBuffers[i] == nullptr)
return false;
Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples);
float volume = outputVolume.Tick();
for (size_t c = 0; c < this->pedalboardOutputBuffers.size(); ++c)
{
outputBuffers[c][i] = this->pedalboardOutputBuffers[c][i]*volume;
}
}
return true;
}
@@ -404,35 +424,59 @@ void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
effect->SetBypass(enabled);
}
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers)
{
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{
int index = vuConfiguration->enabledIndexes[i];
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
if (index == Pedalboard::INPUT_VOLUME_ID)
{
if (this->pedalboardInputBuffers.size() > 1)
{
GetInputBuffers();
pUpdate->AccumulateInputs(inputBuffers[0],inputBuffers[1],samples);
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]),&(this->pedalboardInputBuffers[1][0]),samples); // after outputVolume applied.
} else {
pUpdate->AccumulateInputs(inputBuffers[0],samples);
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]),samples); // after input volume applied.
}
} else if (index == Pedalboard::OUTPUT_VOLUME_ID)
{
if (this->pedalboardOutputBuffers.size() > 1)
{
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]),&(this->pedalboardOutputBuffers[1][0]),samples);
pUpdate->AccumulateOutputs(outputBuffers[0],outputBuffers[1],samples);
} else {
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]),samples);
pUpdate->AccumulateOutputs(outputBuffers[0],samples);
}
auto effect = this->realtimeEffects[index];
}
else {
auto effect = this->realtimeEffects[index];
if (effect->GetNumberOfInputAudioPorts() == 1)
{
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
}
else
{
pUpdate->AccumulateInputs(
effect->GetAudioInputBuffer(0),
effect->GetAudioInputBuffer(1), samples);
}
if (effect->GetNumberOfOutputAudioPorts() == 1)
{
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
}
else
{
pUpdate->AccumulateOutputs(
effect->GetAudioOutputBuffer(0),
effect->GetAudioOutputBuffer(1),
samples);
if (effect->GetNumberOfInputAudioPorts() == 1)
{
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
}
else
{
pUpdate->AccumulateInputs(
effect->GetAudioInputBuffer(0),
effect->GetAudioInputBuffer(1), samples);
}
if (effect->GetNumberOfOutputAudioPorts() == 1)
{
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
}
else
{
pUpdate->AccumulateOutputs(
effect->GetAudioOutputBuffer(0),
effect->GetAudioOutputBuffer(1),
samples);
}
}
}
}
+7 -1
View File
@@ -25,6 +25,7 @@
#include <functional>
#include <lv2/urid.lv2/urid.h>
#include <functional>
#include "DbDezipper.hpp"
namespace pipedal {
@@ -37,6 +38,9 @@ class RealtimeRingBufferWriter;
class Lv2Pedalboard {
IHost *pHost = nullptr;
DbDezipper inputVolume;
DbDezipper outputVolume;
BufferPool bufferPool;
std::vector<float*> pedalboardInputBuffers;
std::vector<float*> pedalboardOutputBuffers;
@@ -133,9 +137,11 @@ public:
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);
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers);
float GetControlOutputValue(int effectIndex, int portIndex);
+2 -13
View File
@@ -165,19 +165,6 @@ Pedalboard Pedalboard::MakeDefault()
// copy insanity. but it happens so rarely.
Pedalboard result;
auto split = result.MakeSplit();
split.topChain().push_back(result.MakeEmptyItem());
split.bottomChain().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(split);
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.name("Default Preset");
@@ -218,6 +205,8 @@ JSON_MAP_END()
JSON_MAP_BEGIN(Pedalboard)
JSON_MAP_REFERENCE(Pedalboard,name)
JSON_MAP_REFERENCE(Pedalboard,input_volume_db)
JSON_MAP_REFERENCE(Pedalboard,output_volume_db)
JSON_MAP_REFERENCE(Pedalboard,items)
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
JSON_MAP_END()
+8 -1
View File
@@ -90,7 +90,7 @@ public:
ControlValue*GetControlValue(const std::string&symbol);
bool hasLv2State() const {
return lv2State_.values_.size() != 0;
return lv2State_.isValid_ != 0;
}
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
@@ -130,11 +130,16 @@ public:
class Pedalboard {
std::string name_;
float input_volume_db_ = 0;
float output_volume_db_ = 0;
std::vector<PedalboardItem> items_;
uint64_t nextInstanceId_ = 0;
uint64_t NextInstanceId() { return ++nextInstanceId_; }
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(long pedalItemId, const std::string &symbol, float value);
bool SetItemEnabled(long pedalItemId, bool enabled);
@@ -146,6 +151,8 @@ public:
GETTER_SETTER_REF(name)
GETTER_SETTER_VEC(items)
GETTER_SETTER(input_volume_db)
GETTER_SETTER(output_volume_db)
DECLARE_JSON_MAP(Pedalboard);
+137 -25
View File
@@ -137,7 +137,7 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
storage.SetConfigRoot(configuration.GetDocRoot());
storage.SetDataRoot(configuration.GetLocalStoragePath());
storage.Initialize();
lv2Host.SetPluginStoragePath(storage.GetPluginStorageDirectory());
lv2Host.SetPluginStoragePath(storage.GetPluginAudioFileDirectory());
this->systemMidiBindings = storage.GetSystemMidiBindings();
@@ -233,7 +233,30 @@ void PiPedalModel::Load()
this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else
this->jackServerSettings = storage.GetJackServerSettings();
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
if (jackServerSettings.IsValid())
{
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
if (jackServerSettings.IsValid())
{
for (size_t retry = 0; retry < 5; ++retry)
{
// retry until the device comes online.
sleep(3);
Lv2Log::info("Retrying AlsaInitialize");
jackConfiguration.isValid(true);
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
if (jackConfiguration.isValid())
{
Lv2Log::info("Retry succeeded.");
break;
}
}
}
} else {
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
}
#endif
if (this->jackConfiguration.isValid())
{
@@ -312,9 +335,12 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc
}
}
void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
{
IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId);
if (effect->IsVst3())
{
@@ -354,6 +380,67 @@ void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
}
}
void PiPedalModel::SetInputVolume(float value)
{
PreviewInputVolume(value);
{
std::lock_guard<std::recursive_mutex> lock(mutex);
this->pedalboard.input_volume_db(value);
// 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]->OnInputVolumeChanged(value);
}
}
delete[] t;
this->SetPresetChanged(-1, true);
}
}
void PiPedalModel::SetOutputVolume(float value)
{
PreviewOutputVolume(value);
{
std::lock_guard<std::recursive_mutex> lock(mutex);
this->pedalboard.output_volume_db(value);
// 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]->OnOutputVolumeChanged(value);
}
}
delete[] t;
this->SetPresetChanged(-1, true);
}
}
void PiPedalModel::PreviewInputVolume(float value)
{
audioHost->SetInputVolume(value);
}
void PiPedalModel::PreviewOutputVolume(float value)
{
audioHost->SetOutputVolume(value);
}
void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
{
{
@@ -613,17 +700,12 @@ void PiPedalModel::UpdatePluginPresets(const PluginUiPresets &pluginPresets)
}
int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &name)
{
auto *item = this->pedalboard.GetItem(instanceId);
PedalboardItem *item = this->pedalboard.GetItem(instanceId);
if (!item)
{
throw PiPedalException("Plugin not found.");
}
std::map<std::string, float> values;
for (const auto &controlValue : item->controlValues())
{
values[controlValue.key()] = controlValue.value();
}
uint64_t presetId = storage.SavePluginPreset(item->uri(), name, values);
uint64_t presetId = storage.SavePluginPreset(name, *item);
FirePluginPresetsChanged(item->uri());
return presetId;
}
@@ -1250,7 +1332,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
{
auto instanceId = activeVuSubscriptions[i].instanceid;
if (pedalboard.HasItem(instanceId))
if (pedalboard.HasItem(instanceId) || instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID)
{
addedInstances.insert(activeVuSubscriptions[i].instanceid);
}
@@ -1629,26 +1711,34 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(pluginInstanceId);
if (pedalboardItem != nullptr)
{
std::vector<ControlValue> controlValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId);
audioHost->SetPluginPreset(pluginInstanceId, controlValues);
PluginPresetValues presetValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId);
// if the plugin has state, we have to rebuild the pedalboard, since setting state is not thread-safe.
for (size_t i = 0; i < controlValues.size(); ++i)
for (auto&control : presetValues.controls)
{
const ControlValue &value = controlValues[i];
this->pedalboard.SetControlValue(pluginInstanceId, value.key(), value.value());
this->pedalboard.SetControlValue(pluginInstanceId, control.key(), control.value());
}
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
if (!presetValues.state.isValid_)
{
t[i] = this->subscribers[i];
audioHost->SetPluginPreset(pluginInstanceId, presetValues.controls);
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]->OnLoadPluginPreset(pluginInstanceId, presetValues.controls);
}
delete[] t;
} else {
pedalboardItem->lv2State(presetValues.state);
FirePedalboardChanged(-1); // does a complete reload of both client and audio server.
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnLoadPluginPreset(pluginInstanceId, controlValues);
}
delete[] t;
this->SetPresetChanged(-1, true);
}
}
@@ -1875,3 +1965,25 @@ std::vector<std::string> PiPedalModel::GetFileList(const UiFileProperty &filePro
return std::vector<std::string>(); // don't disclose to users what the problem is.
}
}
void PiPedalModel::DeleteSampleFile(const std::filesystem::path &fileName)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
storage.DeleteSampleFile(fileName);
}
std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody)
{
return storage.UploadUserFile(directory,patchProperty,filename,fileBody);
}
uint64_t PiPedalModel::CreateNewPreset()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
return storage.CreateNewPreset();
}
+14
View File
@@ -52,6 +52,9 @@ namespace pipedal
virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
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 OnLv2StateChanged(int64_t pedalItemId) = 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;
@@ -215,6 +218,13 @@ namespace pipedal
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
void SetInputVolume(float value);
void SetOutputVolume(float value);
void PreviewInputVolume(float value);
void PreviewOutputVolume(float value);
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
@@ -310,6 +320,10 @@ namespace pipedal
void SetFavorites(const std::map<std::string, bool> &favorites);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
void DeleteSampleFile(const std::filesystem::path &fileName);
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody);
uint64_t CreateNewPreset();
};
} // namespace pipedal.
+152 -39
View File
@@ -31,6 +31,7 @@
#include <atomic>
#include "Ipv6Helpers.hpp"
#include "Promise.hpp"
#include <mutex>
#include "AdminClient.hpp"
#include "WifiConfigSettings.hpp"
@@ -418,16 +419,45 @@ private:
struct PortMonitorSubscription
{
PortMonitorSubscription(
int64_t subscriptionHandle,
int64_t instanceId,
const std::string& key)
:subscriptionHandle(subscriptionHandle),
instanceId(instanceId),
key(key)
{
}
~PortMonitorSubscription() {
Close();
}
PortMonitorSubscription() {}
PortMonitorSubscription(const PortMonitorSubscription&) = delete;
PortMonitorSubscription& operator=(const PortMonitorSubscription&) = delete;
void Close() {
std::lock_guard lock {pmMutex};
closed = true;
}
std::mutex pmMutex;
bool closed = false;
int64_t subscriptionHandle = -1;
int64_t instanceId = -1;
std::string key;
float lastSentValue = 0;
static constexpr float INVALID_VALUE = std::numeric_limits<float>::max();
float lastValue = INVALID_VALUE;
float currentValue = INVALID_VALUE;
bool pendingValue = false;
bool waitingForAck = false;
};
std::vector<PortMonitorSubscription> activePortMonitors;
std::mutex activePortMonitorsMutex;
std::vector<std::shared_ptr<PortMonitorSubscription>> activePortMonitors;
public:
virtual int64_t GetClientId() { return clientId; }
@@ -436,7 +466,7 @@ public:
{
for (int i = 0; i < this->activePortMonitors.size(); ++i)
{
model.UnmonitorPort(activePortMonitors[i].subscriptionHandle);
model.UnmonitorPort(activePortMonitors[i]->subscriptionHandle);
}
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
{
@@ -701,22 +731,48 @@ public:
Reply(replyTo, "error", what);
}
PortMonitorSubscription *getPortMonitorSubscription(uint64_t subscriptionId)
std::shared_ptr<PortMonitorSubscription> getPortMonitorSubscription(uint64_t subscriptionId)
{
for (int i = 0; i < this->activePortMonitors.size(); ++i)
std::shared_ptr<PortMonitorSubscription> result;
{
auto *portMonitor = &activePortMonitors[i];
if (portMonitor->subscriptionHandle == subscriptionId)
std::lock_guard lock (activePortMonitorsMutex);
for (int i = 0; i < this->activePortMonitors.size(); ++i)
{
return portMonitor;
if (activePortMonitors[i]->subscriptionHandle == subscriptionId)
{
result = activePortMonitors[i];
break;
}
}
}
return nullptr;
return result;
}
void SendMonitorPortMessage(PortMonitorSubscription *subscription, float value)
void SendMonitorPortMessage(std::shared_ptr<PortMonitorSubscription> &subscription, float value)
{
// running on RT_output thread, or on Socket thread.
{
std::lock_guard lock {subscription->pmMutex};
if (subscription->closed) return;
if (value == subscription->currentValue)
return;
if (subscription->waitingForAck)
{
subscription->pendingValue = true;
subscription->currentValue = value;
return;
}
subscription->currentValue = value;
subscription->lastValue = value;
subscription->waitingForAck = true;
}
SendMonitorPortMessage_Inner(subscription,value);
}
// post-sync send.
void SendMonitorPortMessage_Inner(std::shared_ptr<PortMonitorSubscription> &subscription, float value)
{
auto subscriptionHandle_ = subscription->subscriptionHandle;
MonitorResultBody body;
body.subscriptionHandle_ = subscriptionHandle_;
@@ -726,17 +782,30 @@ public:
body,
[this, subscriptionHandle_](const bool &result)
{
PortMonitorSubscription *subscription = getPortMonitorSubscription(subscriptionHandle_);
if (subscription == nullptr) return;
// running on PiPedalSocket thread.
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
if (!subscription) return;
if (subscription->pendingValue)
float value;
{
subscription->pendingValue = false;
SendMonitorPortMessage(subscription, subscription->lastSentValue);
}
else
{
subscription->waitingForAck = false;
std::unique_lock lock {subscription->pmMutex};
if (subscription->closed) return;
if (subscription->pendingValue)
{
value = subscription->currentValue;
subscription->lastValue = value;
subscription->pendingValue = false;
subscription->waitingForAck = true;
lock.unlock();
SendMonitorPortMessage_Inner(subscription, value);
return;
}
else
{
subscription->waitingForAck = false;
}
}
},
[](const std::exception &e)
@@ -753,31 +822,19 @@ public:
body.updateRate_,
[this](int64_t subscriptionHandle_, float value)
{
PortMonitorSubscription *subscription = getPortMonitorSubscription(subscriptionHandle_);
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
if (subscription)
{
if (subscription->waitingForAck)
{
if (subscription->lastSentValue != value)
{
subscription->lastSentValue = value;
subscription->pendingValue = true;
}
}
else
{
subscription->lastSentValue = value;
subscription->waitingForAck = true;
SendMonitorPortMessage(subscription, value);
}
SendMonitorPortMessage(subscription, value);
}
}
);
activePortMonitors.push_back(
PortMonitorSubscription{subscriptionHandle, body.instanceId_, body.key_});
{
std::lock_guard lock(activePortMonitorsMutex);
activePortMonitors.push_back(std::make_shared<PortMonitorSubscription>(subscriptionHandle, body.instanceId_, body.key_));
}
this->Reply(replyTo, "monitorPort", subscriptionHandle);
}
@@ -831,7 +888,37 @@ public:
ControlChangedBody message;
pReader->read(&message);
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
} 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;
pReader->read(&value);
this->model.SetInputVolume(value);
}
else if (message == "setOutputVolume")
{
float value;
pReader->read(&value);
this->model.SetOutputVolume(value);
}
else if (message == "previewInputVolume")
{
float value;
pReader->read(&value);
this->model.PreviewInputVolume(value);
}
else if (message == "previewOutputVolume")
{
float value;
pReader->read(&value);
this->model.PreviewOutputVolume(value);
}
else if (message == "listenForMidiEvent")
{
ListenForMidiEventBody body;
@@ -1233,11 +1320,14 @@ public:
pReader->read(&subscriptionHandle);
{
{
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
std::lock_guard guard(activePortMonitorsMutex);
for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i)
{
if ((*i).subscriptionHandle == subscriptionHandle)
if ((*i)->subscriptionHandle == subscriptionHandle)
{
auto subscription = (*i);
subscription->Close();
this->activePortMonitors.erase(i);
break;
}
@@ -1311,6 +1401,19 @@ public:
pReader->read(&fileProperty);
std::vector<std::string> list = this->model.GetFileList(fileProperty);
this->Reply(replyTo,"requestFileList",list);
}
else if (message == "newPreset")
{
int64_t presetId = this->model.CreateNewPreset();
this->Reply(replyTo,"newPreset",presetId);
}
else if (message == "deleteUserFile")
{
std::string fileName;
pReader->read(&fileName);
this->model.DeleteSampleFile(fileName);
this->Reply(replyTo,"deleteUserFile",true);
}
else if (message == "setOnboarding")
{
@@ -1518,6 +1621,16 @@ private:
body.value_ = value;
Send("onControlChanged", body);
}
virtual void OnInputVolumeChanged(float value)
{
ControlChangedBody body;
Send("onInputVolumeChanged", value);
}
virtual void OnOutputVolumeChanged(float value)
{
ControlChangedBody body;
Send("onOutputVolumeChanged", value);
}
class DeferredValue
{
+70 -18
View File
@@ -39,7 +39,7 @@ PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::files
try
{
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(pHost, fileNode, resourcePath);
this->fileProperites_.push_back(std::move(fileUI));
this->fileProperties_.push_back(std::move(fileUI));
}
catch (const std::exception &e)
{
@@ -69,18 +69,18 @@ PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
{
auto pWorld = pHost->getWorld();
AutoLilvNode name = lilv_world_get(
AutoLilvNode label = lilv_world_get(
pWorld,
node,
pHost->lilvUris.lv2Core__name,
pHost->lilvUris.rdfs__label,
nullptr);
if (name)
if (label)
{
this->name_ = name.AsString();
this->label_ = label.AsString();
}
else
{
throw std::logic_error("pipedal_ui:fileType is missing name property.");
throw std::logic_error("pipedal_ui:fileType is missing label property.");
}
AutoLilvNode fileExtension = lilv_world_get(
pWorld,
@@ -103,7 +103,7 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
AutoLilvNode name = lilv_world_get(
pWorld,
node,
pHost->lilvUris.lv2Core__name,
pHost->lilvUris.lv2core__name,
nullptr);
if (name)
{
@@ -113,6 +113,19 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
{
this->name_ = "File";
}
AutoLilvNode index = lilv_world_get(
pWorld,
node,
pHost->lilvUris.lv2core__index,
nullptr);
if (index)
{
this->index_ = name.AsInt();
}
else
{
this->index_ = -1;
}
AutoLilvNode directory = lilv_world_get(
pWorld,
@@ -192,11 +205,31 @@ bool UiFileProperty::IsDirectoryNameValid(const std::string &value)
{
if (value.length() == 0)
return false;
return IsAlphaNumeric(value);
if (value.find_first_of('/') != std::string::npos)
{
return false;
}
if (value.find_first_of('\\') != std::string::npos)
{
return false;
}
if (value.find_first_of("::") != std::string::npos)
{
return false;
}
if (value.find_first_of(":") != std::string::npos)
{
return false;
}
return true;
}
bool UiFileProperty::IsValidExtension(const std::string &extension) const
{
if (fileTypes_.size() == 0)
{
return true;
}
for (auto &fileType : fileTypes_)
{
@@ -221,39 +254,57 @@ UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
// ]
LilvWorld *pWorld = pHost->getWorld();
AutoLilvNode portIndex = lilv_world_get(pWorld,node,pHost->lilvUris.ui__portIndex,nullptr);
AutoLilvNode portIndex = lilv_world_get(pWorld, node, pHost->lilvUris.ui__portIndex, nullptr);
if (!portIndex)
{
this->portIndex_ = -1;
} else {
}
else
{
this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex);
}
AutoLilvNode symbol = lilv_world_get(pWorld,node,pHost->lilvUris.lv2__symbol,nullptr);
AutoLilvNode symbol = lilv_world_get(pWorld, node, pHost->lilvUris.lv2__symbol, nullptr);
if (!symbol)
{
this->symbol_ = "";
} else {
}
else
{
this->symbol_ = symbol.AsString();
}
AutoLilvNode plugin = lilv_world_get(pWorld,node,pHost->lilvUris.ui__plugin,nullptr);
AutoLilvNode plugin = lilv_world_get(pWorld, node, pHost->lilvUris.ui__plugin, nullptr);
if (!plugin)
{
this->plugin_ = "";
} else {
}
else
{
this->plugin_ = plugin.AsUri();
}
AutoLilvNode protocol = lilv_world_get(pWorld,node,pHost->lilvUris.ui__protocol,nullptr);
AutoLilvNode protocol = lilv_world_get(pWorld, node, pHost->lilvUris.ui__protocol, nullptr);
if (!protocol)
{
this->protocol_ = "";
} else {
}
else
{
this->protocol_ = protocol.AsUri();
}
if (this->portIndex_ == -1 &&this->symbol_ == "")
if (this->portIndex_ == -1 && this->symbol_ == "")
{
pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol.");
}
}
UiFileProperty::UiFileProperty(const std::string &name, const std::string &patchProperty, const std::string &directory)
: name_(name),
patchProperty_(patchProperty),
directory_(directory)
{
}
PiPedalUI::PiPedalUI(std::vector<UiFileProperty::ptr> &&fileProperties)
{
this->fileProperties_ = std::move(fileProperties);
}
JSON_MAP_BEGIN(UiPortNotification)
@@ -264,12 +315,13 @@ JSON_MAP_REFERENCE(UiPortNotification, protocol)
JSON_MAP_END()
JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType, name)
JSON_MAP_REFERENCE(PiPedalFileType, label)
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
JSON_MAP_END()
JSON_MAP_BEGIN(UiFileProperty)
JSON_MAP_REFERENCE(UiFileProperty, name)
JSON_MAP_REFERENCE(UiFileProperty, index)
JSON_MAP_REFERENCE(UiFileProperty, directory)
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
+10 -4
View File
@@ -57,7 +57,7 @@ namespace pipedal {
class PiPedalFileType {
private:
std::string name_;
std::string label_;
std::string fileExtension_;
public:
PiPedalFileType() { }
@@ -65,7 +65,7 @@ namespace pipedal {
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
const std::string& name() const { return name_;}
const std::string& label() const { return label_;}
const std::string &fileExtension() const { return fileExtension_; }
public:
@@ -94,6 +94,7 @@ namespace pipedal {
class UiFileProperty {
private:
std::string name_;
std::int64_t index_ = -1;
std::string directory_;
std::vector<PiPedalFileType> fileTypes_;
std::string patchProperty_;
@@ -101,9 +102,11 @@ namespace pipedal {
using ptr = std::shared_ptr<UiFileProperty>;
UiFileProperty() { }
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
UiFileProperty(const std::string&name, const std::string&patchProperty,const std::string &directory);
const std::string &name() const { return name_; }
int64_t index() const { return index_; }
const std::string &directory() const { return directory_; }
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
@@ -120,9 +123,12 @@ namespace pipedal {
public:
using ptr = std::shared_ptr<PiPedalUI>;
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
PiPedalUI(std::vector<UiFileProperty::ptr> &&fileProperites);
const std::vector<UiFileProperty::ptr>& fileProperties() const
{
return fileProperites_;
return fileProperties_;
}
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
@@ -140,7 +146,7 @@ namespace pipedal {
}
private:
std::vector<UiFileProperty::ptr> fileProperites_;
std::vector<UiFileProperty::ptr> fileProperties_;
std::vector<UiPortNotification::ptr> portNotifications_;
};
+245 -109
View File
@@ -66,26 +66,31 @@ using namespace pipedal;
namespace pipedal
{
static const char *LV2_AUDIO_PORT = P_LV2_CORE_URI "AudioPort";
static const char *LV2_PLUGIN = P_LV2_CORE_URI "Plugin";
static const char *LV2_CONTROL_PORT = P_LV2_CORE_URI "ControlPort";
static const char *LV2_INPUT_PORT = P_LV2_CORE_URI "InputPort";
static const char *LV2_OUTPUT_PORT = P_LV2_CORE_URI "OutputPort";
static const char *LV2_INTEGER = P_LV2_CORE_URI "integer";
static const char *LV2_ENUMERATION = P_LV2_CORE_URI "enumeration";
static const char *LV2_PORT_LOGARITHMIC = P_LV2_PPROPS "logarithmic";
static const char *LV2_PORT_RANGE_STEPS = P_LV2_PPROPS "rangeSteps";
static const char *LV2_PORT_TRIGGER = P_LV2_PPROPS "trigger";
static const char *LV2_PORT_DISPLAY_PRIORITY = P_LV2_PPROPS "displayPriority";
// in ttl files, but not header files.
static const char *LV2_MIDI_PLUGIN = "http://lv2plug.in/ns/lv2core#MIDIPlugin";
static const char *LV2_ATOM_PORT = "http://lv2plug.in/ns/ext/atom#AtomPort";
static const char *LV2_ATOM_SEQUENCE = "http://lv2plug.in/ns/ext/atom#Sequence";
static const char *LV2_ATOM_SOUND = "http://lv2plug.in/ns/ext/atom#Sound";
static const char *LV2_ATOM_VECTOR = "http://lv2plug.in/ns/ext/atom#Vector";
static const char *LV2_ATOM_STRING = "http://lv2plug.in/ns/ext/atom#String";
static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
// static const char *LV2_AUDIO_PORT = P_LV2_CORE_URI "AudioPort";
// static const char *LV2_PLUGIN = P_LV2_CORE_URI "Plugin";
// static const char *LV2_CONTROL_PORT = P_LV2_CORE_URI "ControlPort";
// static const char *LV2_INPUT_PORT = P_LV2_CORE_URI "InputPort";
// static const char *LV2_OUTPUT_PORT = P_LV2_CORE_URI "OutputPort";
// static const char *LV2_INTEGER = P_LV2_CORE_URI "integer";
// static const char *LV2_ENUMERATION = P_LV2_CORE_URI "enumeration";
// static const char *LV2_PORT_LOGARITHMIC = P_LV2_PPROPS "logarithmic";
// static const char *LV2_PORT_RANGE_STEPS = P_LV2_PPROPS "rangeSteps";
// static const char *LV2_PORT_TRIGGER = P_LV2_PPROPS "trigger";
// static const char *LV2_PORT_DISPLAY_PRIORITY = P_LV2_PPROPS "displayPriority";
// static const char *LV2_ATOM_PORT = "http://lv2plug.in/ns/ext/atom#AtomPort";
// static const char *LV2_ATOM_SEQUENCE = "http://lv2plug.in/ns/ext/atom#Sequence";
// static const char *LV2_ATOM_SOUND = "http://lv2plug.in/ns/ext/atom#Sound";
// static const char *LV2_ATOM_VECTOR = "http://lv2plug.in/ns/ext/atom#Vector";
// static const char *LV2_ATOM_STRING = "http://lv2plug.in/ns/ext/atom#String";
// static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
// static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
class PluginHost::Urids
{
@@ -117,25 +122,35 @@ void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration)
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
{
rdfsComment = lilv_new_uri(pWorld, PluginHost::RDFS_COMMENT_URI);
logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC);
display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY);
range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS);
integer_property_uri = lilv_new_uri(pWorld, LV2_INTEGER);
enumeration_property_uri = lilv_new_uri(pWorld, LV2_ENUMERATION);
toggle_property_uri = lilv_new_uri(pWorld, LV2_CORE__toggled);
not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
midiEventNode = lilv_new_uri(pWorld, LV2_MIDI_EVENT);
designationNode = lilv_new_uri(pWorld, LV2_DESIGNATION);
portGroupUri = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
unitsUri = lilv_new_uri(pWorld, LV2_UNITS__unit);
bufferType_uri = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
pset_Preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
rdfs_label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
symbolUri = lilv_new_uri(pWorld, LV2_CORE__symbol);
nameUri = lilv_new_uri(pWorld, LV2_CORE__name);
rdfs__Comment = lilv_new_uri(pWorld, PluginHost::RDFS__comment);
rdfs__range = lilv_new_uri(pWorld,PluginHost::RDFS__range);
port_logarithmic = lilv_new_uri(pWorld, LV2_PORT_PROPS__logarithmic);
port__display_priority = lilv_new_uri(pWorld, LV2_PORT_PROPS__displayPriority);
port_range_steps = lilv_new_uri(pWorld, LV2_PORT_PROPS__rangeSteps);
integer_property_uri = lilv_new_uri(pWorld, LV2_CORE__integer);
enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration);
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
core__connectionOptional = lilv_new_uri(pWorld,LV2_CORE__connectionOptional);
portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
units__unit = lilv_new_uri(pWorld, LV2_UNITS__unit);
invada_units__unit = lilv_new_uri(pWorld, "http://lv2plug.in/ns/extension/units#unit"); // a typo in invada plugin ttl files.
invada_portprops__logarithmic = lilv_new_uri(pWorld, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); // a typo in invada plugin ttl files.
atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path);
presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
lv2core__symbol = lilv_new_uri(pWorld, LV2_CORE__symbol);
lv2core__name = lilv_new_uri(pWorld, LV2_CORE__name);
lv2core__index = lilv_new_uri(pWorld, LV2_CORE__index);
lv2core__Parameter = lilv_new_uri(pWorld,LV2_CORE_PREFIX "Parameter");
lv2Core__name = lilv_new_uri(pWorld, LV2_CORE__name);
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
@@ -173,27 +188,30 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
time_speed = lilv_new_uri(pWorld, LV2_TIME__speed);
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
patch__writable = lilv_new_uri(pWorld,LV2_PATCH__writable);
patch__readable = lilv_new_uri(pWorld,LV2_PATCH__readable);
}
void PluginHost::LilvUris::Free()
{
rdfsComment.Free();
logarithic_uri.Free();
display_priority_uri.Free();
range_steps_uri.Free();
rdfs__Comment.Free();
port_logarithmic.Free();
port__display_priority.Free();
port_range_steps.Free();
integer_property_uri.Free();
enumeration_property_uri.Free();
toggle_property_uri.Free();
not_on_gui_property_uri.Free();
midiEventNode.Free();
designationNode.Free();
portGroupUri.Free();
unitsUri.Free();
bufferType_uri.Free();
pset_Preset.Free();
rdfs_label.Free();
symbolUri.Free();
nameUri.Free();
core__toggled.Free();
portprops__not_on_gui_property_uri.Free();
midi__event.Free();
core__designation.Free();
portgroups__group.Free();
units__unit.Free();
atom__bufferType.Free();
presets__preset.Free();
rdfs__label.Free();
lv2core__symbol.Free();
lv2core__name.Free();
time_Position.Free();
time_barBeat.Free();
@@ -574,13 +592,18 @@ static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
return result;
}
const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
const char *PluginHost::RDFS__comment = "http://www.w3.org/2000/01/rdf-schema#"
"comment";
const char *PluginHost::RDFS__range = "http://www.w3.org/2000/01/rdf-schema#"
"range";
LilvNode *PluginHost::get_comment(const std::string &uri)
{
AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfs__Comment, nullptr);
return result;
}
@@ -591,7 +614,7 @@ static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shar
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
{
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.presets__preset);
bool result = false;
LILV_FOREACH(nodes, iNode, nodes)
{
@@ -601,9 +624,65 @@ bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plu
return result;
}
std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host,const LilvPlugin*pPlugin)
{
// example:
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
// a lv2:Parameter;
// rdfs:label "Model";
// rdfs:range atom:Path.
// ...
// patch:writable <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>;
LilvWorld* pWorld = lv2Host->getWorld();
AutoLilvNode pluginUri = lilv_plugin_get_uri(pPlugin);
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld,pluginUri,lv2Host->lilvUris.patch__writable,nullptr);
std::vector<UiFileProperty::ptr> fileProperties;
LILV_FOREACH(nodes, iNode, patchWritables)
{
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
if (propertyUri)
{
// isA lv2:Parameter?
if (lilv_world_ask(pWorld,propertyUri,lv2Host->lilvUris.isA,lv2Host->lilvUris.lv2core__Parameter))
{
// rfs:range atom:Path?
if (lilv_world_ask(pWorld,propertyUri,lv2Host->lilvUris.rdfs__range,lv2Host->lilvUris.atom__Path))
{
AutoLilvNode label = lilv_world_get(pWorld,propertyUri,lv2Host->lilvUris.rdfs__label,nullptr);
std::string strLabel = label.AsString();
if (strLabel.length() != 0)
{
std::filesystem::path path = this->bundle_path();
path = path.parent_path();
std::string lv2DirectoryName = path.filename().string();
// we have a valid path property!
fileProperties.push_back(
std::make_shared<UiFileProperty>(
strLabel,propertyUri.AsUri(),lv2DirectoryName)
);
}
}
}
}
}
if (fileProperties.size() != 0)
{
return std::make_shared<PiPedalUI>(std::move(fileProperties));
}
return std::shared_ptr<PiPedalUI>();
}
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
{
AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin);
if (!bundleUriNode)
throw std::logic_error("Invalid bundle uri.");
@@ -698,15 +777,39 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
if (pipedalUINode)
{
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
} else {
// look for
this->piPedalUI_ = FindWritablePathProperties(lv2Host,pPlugin);
}
int nInputs = 0;
for (size_t i = 0; i < ports_.size(); ++i)
{
auto port = ports_[i];
if (port->is_audio_port() && port->is_input())
{
if (nInputs >= 2 && !port->connection_optional())
{
isValid = false;
break;
}
++nInputs;
}
}
int nOutputs = 0;
for (size_t i = 0; i < ports_.size(); ++i)
{
auto port = ports_[i];
if (port->is_audio_port() && port->is_output())
{
if (nOutputs >= 2 && !port->connection_optional())
{
isValid = false;
break;
}
++nOutputs;
}
}
// xxx lilv_world_get(pWorld,pluginUri,);
// for (auto&portInfo: ports_)
// {
// if (portInfo->is_control_port() && portInfo->is_output())
// {
// std::cout << "Dbg: " << "Has an output control port. " << this->uri_ << std::endl;
// }
// }
this->is_valid_ = isValid;
}
@@ -724,9 +827,11 @@ std::vector<std::string> supportedFeatures = {
LV2_STATE__loadDefaultState,
LV2_STATE__makePath,
LV2_STATE__mapPath,
LV2_CORE__inPlaceBroken,
// UI features that we can ignore, since we won't load their ui.
"http://lv2plug.in/ns/extensions/ui#makeResident",
"http://lv2plug.in/ns/ext/port-props#supportsStrictBounds"
};
@@ -798,9 +903,12 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
if (default_value_ < min_value_)
default_value_ = min_value_;
this->is_logarithmic_ = lilv_port_has_property(plugin, pPort, host->lilvUris.logarithic_uri);
this->is_logarithmic_ = lilv_port_has_property(plugin, pPort, host->lilvUris.port_logarithmic);
NodesAutoFree priority_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.display_priority_uri);
// typo in invada plugins.
this->is_logarithmic_ |= lilv_port_has_property(plugin, pPort, host->lilvUris.invada_portprops__logarithmic);
NodesAutoFree priority_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.port__display_priority);
this->display_priority_ = -1;
if (priority_nodes)
@@ -812,7 +920,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
}
}
NodesAutoFree range_steps_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.range_steps_uri);
NodesAutoFree range_steps_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.port_range_steps);
this->range_steps_ = 0;
if (range_steps_nodes)
{
@@ -826,9 +934,9 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.enumeration_property_uri);
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.toggle_property_uri);
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris.not_on_gui_property_uri);
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.core__toggled);
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris.portprops__not_on_gui_property_uri);
this->connection_optional_ = lilv_port_has_property(plugin,pPort,host->lilvUris.core__connectionOptional);
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
LILV_FOREACH(scale_points, iSP, pScalePoints)
@@ -844,30 +952,50 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
std::sort(scale_points_.begin(), scale_points_.end(), scale_points_sort_compare);
is_input_ = is_a(host, LV2_INPUT_PORT);
is_output_ = is_a(host, LV2_OUTPUT_PORT);
is_input_ = is_a(host, LV2_CORE__InputPort);
is_output_ = is_a(host, LV2_CORE__OutputPort);
is_control_port_ = is_a(host, LV2_CORE__ControlPort);
is_audio_port_ = is_a(host, LV2_CORE__AudioPort);
is_atom_port_ = is_a(host, LV2_ATOM_PORT);
is_atom_port_ = is_a(host, LV2_ATOM__AtomPort);
is_cv_port_ = is_a(host, LV2_CORE__CVPort);
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midiEventNode);
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midi__event);
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position);
AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris.designationNode);
AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris.core__designation);
designation_ = nodeAsString(designationValue);
AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portGroupUri);
AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portgroups__group);
port_group_ = nodeAsString(portGroup_value);
AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.unitsUri);
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.units__unit);
if (unitsValueUri)
{
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
} else {
// invada plugins use the wrong URI.
AutoLilvNode invadaUnitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.invada_units__unit);
if (invadaUnitsValueUri)
{
std::string uri = nodeAsString(invadaUnitsValueUri);
static const char*INCORRECT_URI = "http://lv2plug.in/ns/extension/units#";
static const char*CORRECT_URI = "http://lv2plug.in/ns/extensions/units#";
if (uri.starts_with(INCORRECT_URI))
{
uri = uri.replace(0,strlen(INCORRECT_URI),CORRECT_URI);
}
this->units_ = UriToUnits(uri);
} else {
this->units(Units::none);
}
AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment);
}
AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfs__Comment);
this->comment_ = nodeAsString(commentNode);
AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris.bufferType_uri);
AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris.atom__bufferType);
this->buffer_type_ = "";
if (bufferType)
@@ -962,11 +1090,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
}
for (auto port : plugin->ports())
{
if (port->is_input())
{
if (port->is_control_port() && port->is_input())
if (port->is_control_port())
{
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
}
else if (port->is_atom_port())
{
@@ -982,7 +1111,11 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
}
else if (port->is_output())
{
if (port->is_atom_port() && port->supports_midi())
if (port->is_control_port())
{
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
}
else if (port->is_atom_port() && port->supports_midi())
{
this->has_midi_output_ = true;
}
@@ -1007,7 +1140,7 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
{
return this->GetPluginClass(LV2_PLUGIN);
return this->GetPluginClass(LV2_CORE__Plugin);
}
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
@@ -1078,7 +1211,7 @@ std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
throw PiPedalStateException("No such plugin.");
}
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.pset_Preset);
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.presets__preset);
LILV_FOREACH(nodes, i, presets)
{
const LilvNode *preset = lilv_nodes_get(presets, i);
@@ -1154,7 +1287,7 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
PluginPresets result;
result.pluginUri_ = pluginUri;
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.pset_Preset);
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.presets__preset);
LILV_FOREACH(nodes, i, presets)
{
const LilvNode *preset = lilv_nodes_get(presets, i);
@@ -1171,7 +1304,7 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
if (!cbData.failed)
{
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, label, controlValues,Lv2PluginState()));
}
}
}
@@ -1216,9 +1349,9 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
this->uri_ = groupUri;
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.symbolUri, nullptr);
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.lv2core__symbol, nullptr);
symbol_ = nodeAsString(symbolNode);
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.nameUri, nullptr);
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.lv2core__name, nullptr);
name_ = nodeAsString(nameNode);
}
@@ -1269,6 +1402,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
json_map::reference("connection_optional", &Lv2PortInfo::connection_optional_),
json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
@@ -1330,29 +1464,31 @@ json_map::storage_type<Lv2PluginUiPortGroup> Lv2PluginUiPortGroup::jmap{{
MAP_REF(Lv2PluginUiPortGroup, program_list_id),
}};
json_map::storage_type<Lv2PluginUiControlPort> Lv2PluginUiControlPort::jmap{{
json_map::reference("symbol", &Lv2PluginUiControlPort::symbol_),
json_map::reference("name", &Lv2PluginUiControlPort::name_),
MAP_REF(Lv2PluginUiControlPort, index),
MAP_REF(Lv2PluginUiControlPort, min_value),
MAP_REF(Lv2PluginUiControlPort, max_value),
MAP_REF(Lv2PluginUiControlPort, default_value),
MAP_REF(Lv2PluginUiControlPort, is_logarithmic),
MAP_REF(Lv2PluginUiControlPort, display_priority),
json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
json_map::reference("symbol", &Lv2PluginUiPort::symbol_),
json_map::reference("name", &Lv2PluginUiPort::name_),
MAP_REF(Lv2PluginUiPort, index),
MAP_REF(Lv2PluginUiPort, is_input),
MAP_REF(Lv2PluginUiPort, min_value),
MAP_REF(Lv2PluginUiPort, max_value),
MAP_REF(Lv2PluginUiPort, default_value),
MAP_REF(Lv2PluginUiPort, is_logarithmic),
MAP_REF(Lv2PluginUiPort, display_priority),
MAP_REF(Lv2PluginUiControlPort, range_steps),
MAP_REF(Lv2PluginUiControlPort, integer_property),
MAP_REF(Lv2PluginUiControlPort, enumeration_property),
MAP_REF(Lv2PluginUiControlPort, not_on_gui),
MAP_REF(Lv2PluginUiControlPort, toggled_property),
MAP_REF(Lv2PluginUiControlPort, scale_points),
MAP_REF(Lv2PluginUiControlPort, port_group),
MAP_REF(Lv2PluginUiPort, range_steps),
MAP_REF(Lv2PluginUiPort, integer_property),
MAP_REF(Lv2PluginUiPort, enumeration_property),
MAP_REF(Lv2PluginUiPort, not_on_gui),
MAP_REF(Lv2PluginUiPort, toggled_property),
MAP_REF(Lv2PluginUiPort, scale_points),
MAP_REF(Lv2PluginUiPort, port_group),
json_map::enum_reference("units", &Lv2PluginUiControlPort::units_, get_units_enum_converter()),
MAP_REF(Lv2PluginUiControlPort, comment),
MAP_REF(Lv2PluginUiControlPort, is_bypass),
MAP_REF(Lv2PluginUiControlPort, is_program_controller),
MAP_REF(Lv2PluginUiControlPort, custom_units),
json_map::enum_reference("units", &Lv2PluginUiPort::units_, get_units_enum_converter()),
MAP_REF(Lv2PluginUiPort, comment),
MAP_REF(Lv2PluginUiPort, is_bypass),
MAP_REF(Lv2PluginUiPort, is_program_controller),
MAP_REF(Lv2PluginUiPort, custom_units),
MAP_REF(Lv2PluginUiPort, connection_optional),
}};
+57 -26
View File
@@ -206,6 +206,7 @@ namespace pipedal
bool is_audio_port_ = false;
bool is_atom_port_ = false;
bool is_cv_port_ = false;
bool connection_optional_ = false;
bool is_valid_ = false;
bool supports_midi_ = false;
@@ -275,6 +276,7 @@ namespace pipedal
LV2_PROPERTY_GETSET_SCALAR(is_control_port);
LV2_PROPERTY_GETSET_SCALAR(is_audio_port);
LV2_PROPERTY_GETSET_SCALAR(is_atom_port);
LV2_PROPERTY_GETSET_SCALAR(connection_optional);
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
LV2_PROPERTY_GETSET_SCALAR(is_valid);
LV2_PROPERTY_GETSET_SCALAR(supports_midi);
@@ -338,6 +340,8 @@ namespace pipedal
Lv2PluginInfo() {}
private:
std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host,const LilvPlugin*pPlugin);
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
std::string bundle_path_;
std::string uri_;
@@ -467,14 +471,20 @@ namespace pipedal
static json_map::storage_type<Lv2PluginUiPortGroup> jmap;
};
class Lv2PluginUiControlPort
class Lv2PluginUiPort
{
public:
Lv2PluginUiControlPort()
Lv2PluginUiPort()
{
}
Lv2PluginUiControlPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
: symbol_(pPort->symbol()), index_(pPort->index()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()), default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()), is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()), toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()), comment_(pPort->comment()), units_(pPort->units())
Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
: symbol_(pPort->symbol()), index_(pPort->index()),
is_input_(pPort->is_input()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()),
default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()),
is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()),
toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()),
comment_(pPort->comment()), units_(pPort->units()),
connection_optional_(pPort->connection_optional())
{
// Use symbols to index port groups, instead of uris.
// symbols are guaranteed to be unique.
@@ -489,31 +499,40 @@ namespace pipedal
break;
}
}
is_bypass_ = name_ == "bypass"
|| name_ == "Bypass"
|| symbol_ == "bypass"
|| symbol_ == "Bypass";
}
private:
std::string symbol_;
int index_;
std::string name_;
bool is_input_ = true;
float min_value_ = 0, max_value_ = 1, default_value_ = 0;
int range_steps_ = 0;
int display_priority_ = -1;
bool is_logarithmic_ = false;
int display_priority_ = -1;
int range_steps_ = 0;
bool integer_property_ = false;
bool enumeration_property_ = false;
bool toggled_property_ = false;
bool not_on_gui_ = false;
bool toggled_property_ = false;
std::vector<Lv2ScalePoint> scale_points_;
std::string port_group_;
Units units_ = Units::none;
std::string comment_;
bool is_bypass_ = false;
bool is_program_controller_ = false;
std::string custom_units_;
bool connection_optional_ = false;
public:
LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET_SCALAR(index);
LV2_PROPERTY_GETSET_SCALAR(is_input);
LV2_PROPERTY_GETSET(name);
LV2_PROPERTY_GETSET(port_group);
LV2_PROPERTY_GETSET_SCALAR(min_value);
@@ -532,9 +551,10 @@ namespace pipedal
LV2_PROPERTY_GETSET_SCALAR(is_bypass);
LV2_PROPERTY_GETSET_SCALAR(is_program_controller);
LV2_PROPERTY_GETSET(custom_units);
LV2_PROPERTY_GETSET(connection_optional);
public:
static json_map::storage_type<Lv2PluginUiControlPort> jmap;
static json_map::storage_type<Lv2PluginUiPort> jmap;
};
class Lv2PluginUiInfo
@@ -557,7 +577,7 @@ namespace pipedal
std::string description_;
bool is_vst3_ = false;
std::vector<Lv2PluginUiControlPort> controls_;
std::vector<Lv2PluginUiPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<UiFileProperty::ptr> fileProperties_;
std::vector<UiPortNotification::ptr> uiPortNotifications_;
@@ -601,7 +621,8 @@ namespace pipedal
#endif
friend class pipedal::AutoLilvNode;
friend class pipedal::PiPedalUI;
static const char *RDFS_COMMENT_URI;
static const char *RDFS__comment;
static const char *RDFS__range;
public:
class LilvUris
@@ -610,25 +631,31 @@ namespace pipedal
void Initialize(LilvWorld *pWorld);
void Free();
AutoLilvNode rdfsComment;
AutoLilvNode logarithic_uri;
AutoLilvNode display_priority_uri;
AutoLilvNode range_steps_uri;
AutoLilvNode rdfs__Comment;
AutoLilvNode rdfs__range;
AutoLilvNode port_logarithmic;
AutoLilvNode port__display_priority;
AutoLilvNode port_range_steps;
AutoLilvNode integer_property_uri;
AutoLilvNode enumeration_property_uri;
AutoLilvNode toggle_property_uri;
AutoLilvNode not_on_gui_property_uri;
AutoLilvNode midiEventNode;
AutoLilvNode designationNode;
AutoLilvNode portGroupUri;
AutoLilvNode unitsUri;
AutoLilvNode bufferType_uri;
AutoLilvNode pset_Preset;
AutoLilvNode rdfs_label;
AutoLilvNode symbolUri;
AutoLilvNode nameUri;
AutoLilvNode core__toggled;
AutoLilvNode core__connectionOptional;
AutoLilvNode portprops__not_on_gui_property_uri;
AutoLilvNode midi__event;
AutoLilvNode core__designation;
AutoLilvNode portgroups__group;
AutoLilvNode units__unit;
AutoLilvNode invada_units__unit; // typo in invada plugins.
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
AutoLilvNode lv2Core__name;
AutoLilvNode atom__bufferType;
AutoLilvNode atom__Path;
AutoLilvNode presets__preset;
AutoLilvNode rdfs__label;
AutoLilvNode lv2core__symbol;
AutoLilvNode lv2core__name;
AutoLilvNode lv2core__index;
AutoLilvNode lv2core__Parameter;
AutoLilvNode pipedalUI__ui;
AutoLilvNode pipedalUI__fileProperties;
AutoLilvNode pipedalUI__directory;
@@ -657,6 +684,10 @@ namespace pipedal
AutoLilvNode ui__peakProtocol;
AutoLilvNode ui__portIndex;
AutoLilvNode lv2__symbol;
AutoLilvNode patch__writable;
AutoLilvNode patch__readable;
};
LilvUris lilvUris;
+19 -8
View File
@@ -25,6 +25,7 @@
#include <map>
#include <memory>
#include "StateInterface.hpp"
#include "Pedalboard.hpp"
namespace pipedal {
@@ -69,20 +70,30 @@ public:
};
class PluginPreset {
public:
PluginPreset() { }
PluginPreset(uint64_t instanceId, std::string&&label, std::map<std::string,float> && controlValues)
: instanceId_(instanceId),
label_(std::forward<std::string>(label)),
controlValues_(std::forward<std::map<std::string,float>>(controlValues))
{
PluginPreset(uint64_t instanceId, const std::string&label,const PedalboardItem&pedalboardItem)
: instanceId_(instanceId)
, label_(label)
, state_(pedalboardItem.lv2State())
{
for (auto & controlValue : pedalboardItem.controlValues())
{
this->controlValues_[controlValue.key()] = controlValue.value();
}
}
PluginPreset(uint64_t instanceId, const std::string&label, const std::map<std::string,float> & controlValues)
PluginPreset(
uint64_t instanceId,
const std::string&label,
const std::map<std::string,
float> & controlValues,
const Lv2PluginState &state)
: instanceId_(instanceId),
label_(label),
controlValues_(controlValues)
controlValues_((controlValues)),
state_(state)
{
}
+43 -22
View File
@@ -30,35 +30,38 @@ namespace pipedal
enum class RingBufferCommand : int64_t
{
ReplaceEffect = 0,
EffectReplaced = 1,
SetValue = 2,
SetBypass = 3,
AudioStopped = 4,
SetVuSubscriptions = 5,
FreeVuSubscriptions = 6,
SendVuUpdate = 7,
AckVuUpdate = 8,
Invalid = 0,
ReplaceEffect,
EffectReplaced,
SetValue,
SetBypass,
AudioStopped,
SetVuSubscriptions,
FreeVuSubscriptions,
SendVuUpdate,
AckVuUpdate,
SetMonitorPortSubscription = 9,
FreeMonitorPortSubscription = 10,
SendMonitorPortUpdate = 11,
AckMonitorPortUpdate = 12,
ParameterRequest = 13,
ParameterRequestComplete = 14,
SetMonitorPortSubscription,
FreeMonitorPortSubscription,
SendMonitorPortUpdate,
AckMonitorPortUpdate,
ParameterRequest,
ParameterRequestComplete,
MidiValueChanged = 15,
MidiValueChanged,
OnMidiListen = 16,
OnMidiListen,
AtomOutput = 17,
AtomOutput,
MidiProgramChange = 18, // program change requested via midi.
AckMidiProgramChange = 19,
MidiProgramChange, // program change requested via midi.
AckMidiProgramChange,
NextMidiProgram = 20,
NextMidiProgram,
Lv2StateChanged = 21,
Lv2StateChanged,
SetInputVolume,
SetOutputVolume,
};
@@ -154,6 +157,11 @@ namespace pipedal
int controlIndex;
float value;
};
class SetVolumeBody
{
public:
float value;
};
class Lv2Pedalboard;
@@ -369,6 +377,19 @@ namespace pipedal
body.value = value;
write(RingBufferCommand::SetValue,body);
}
void SetInputVolume(float value)
{
SetVolumeBody body;
body.value = value;
write(RingBufferCommand::SetInputVolume,body);
}
void SetOutputVolume(float value)
{
SetVolumeBody body;
body.value = value;
write(RingBufferCommand::SetOutputVolume,body);
}
void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
{
+9 -1
View File
@@ -153,7 +153,7 @@ void StateInterface::Restore(const Lv2PluginState &state)
}
catch (const std::exception &e)
{
throw std::logic_error(SS("State save failed. " << e.what()));
throw std::logic_error(SS("State restore failed. " << e.what()));
}
}
/*static*/ const void *StateInterface::FnStateRetreiveFunction(
@@ -199,10 +199,18 @@ const void *StateInterface::StateRetrieveFunction(
void Lv2PluginState::write_json(json_writer &writer) const
{
writer.start_array();
writer.write(isValid_);
writer.write_raw(",");
writer.write(values_);
writer.end_array();
}
void Lv2PluginState::read_json(json_reader &reader) {
reader.consume('[');
reader.read(&isValid_);
reader.consume(',');
reader.read(&values_);
reader.consume(']');
}
+5
View File
@@ -41,7 +41,12 @@ namespace pipedal
class Lv2PluginState: public JsonSerializable
{
public:
bool isValid_ = false;
std::map<std::string,Lv2PluginStateEntry> values_;
void Erase() {
isValid_ = false;
values_.clear();
}
std::string ToString() const;
private:
+164 -8
View File
@@ -264,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{
return this->dataRoot / "plugin_presets";
}
std::filesystem::path Storage::GetPluginStorageDirectory() const
std::filesystem::path Storage::GetPluginAudioFileDirectory() const
{
return this->dataRoot / "audio_uploads";
}
@@ -635,6 +635,29 @@ std::string Storage::GetPresetCopyName(const std::string &name)
++copyNumber;
}
}
int64_t Storage::CreateNewPreset()
{
Pedalboard newPedalboard = Pedalboard::MakeDefault();
std::string name = "New";
if (currentBank.hasName(name))
{
int copyNumber = 2;
while (true)
{
name = SS("New (" << copyNumber << ")");
if (!currentBank.hasName(name))
{
break;
}
++copyNumber;
}
}
newPedalboard.name(name);
return this->currentBank.addPreset(newPedalboard,-1);
}
int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
{
auto &fromItem = this->currentBank.getItem(fromId);
@@ -1189,28 +1212,64 @@ PluginUiPresets Storage::GetPluginUiPresets(const std::string &pluginUri) const
return result;
}
std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string &pluginUri, uint64_t instanceId)
PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri, uint64_t instanceId)
{
auto presets = GetPluginPresets(pluginUri);
for (const auto &preset : presets.presets_)
{
if (preset.instanceId_ == instanceId)
{
std::vector<ControlValue> result;
PluginPresetValues result;
for (const auto &valuePair : preset.controlValues_)
{
result.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
result.controls.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
}
result.state = preset.state_;
return result;
}
}
throw PiPedalException("Plugin preset not found.");
}
uint64_t Storage::SavePluginPreset(
const std::string &name,
const PedalboardItem &pedalboardItem)
{
const std::string&pluginUri = pedalboardItem.uri();
auto presets = GetPluginPresets(pluginUri);
uint64_t result = -1;
bool existing = false;
for (size_t i = 0; i < presets.presets_.size(); ++i)
{
auto &preset = presets.presets_[i];
if (preset.label_ == name)
{
existing = true;
result = preset.instanceId_;
presets.presets_[i] = PluginPreset(preset.instanceId_,preset.label_,pedalboardItem);
break;
}
}
if (!existing)
{
result = presets.nextInstanceId_++;
presets.presets_.push_back(
PluginPreset(result,
name,
pedalboardItem));
}
this->SavePluginPresets(pluginUri, presets);
return result;
}
uint64_t Storage::SavePluginPreset(
const std::string &pluginUri,
const std::string &name,
const std::map<std::string, float> &values)
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values,
const Lv2PluginState &lv2State)
{
auto presets = GetPluginPresets(pluginUri);
uint64_t result = -1;
@@ -1220,8 +1279,10 @@ uint64_t Storage::SavePluginPreset(
auto &preset = presets.presets_[i];
if (preset.label_ == name)
{
preset.controlValues_ = values;
existing = true;
preset.controlValues_ = values;
preset.state_ = lv2State;
result = preset.instanceId_;
break;
}
@@ -1233,7 +1294,8 @@ uint64_t Storage::SavePluginPreset(
PluginPreset(
result,
name,
values));
values,
lv2State));
}
this->SavePluginPresets(pluginUri, presets);
return result;
@@ -1446,7 +1508,7 @@ std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty
// if fileProperty has a user-accessible directory, push the entire file path.
if (fileProperty.directory().size() != 0)
{
std::filesystem::path audioFileDirectory = this->GetPluginStorageDirectory() / fileProperty.directory();
std::filesystem::path audioFileDirectory = this->GetPluginAudioFileDirectory() / fileProperty.directory();
try
{
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
@@ -1474,6 +1536,100 @@ std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty
return result;
}
bool Storage::IsValidSampleFile(const std::filesystem::path &fileName)
{
if (!fileName.is_absolute())
{
return false;
}
std::filesystem::path audioFilePath = this->GetPluginAudioFileDirectory();
std::filesystem::path parentDirectory = fileName.parent_path();
while (true)
{
if (!parentDirectory.has_parent_path())
{
return false;
}
std::string name = parentDirectory.filename().string();
if (name == ".." || name == ".")
return false;
if (parentDirectory == audioFilePath)
return true;
parentDirectory = parentDirectory.parent_path();
if (parentDirectory.string().length() < audioFilePath.string().length())
{
return false;
}
}
}
void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
{
if (!IsValidSampleFile(fileName))
{
throw std::logic_error("Permission denied.");
}
if (!std::filesystem::exists(fileName))
{
throw std::logic_error("File not found.");
}
try
{
std::filesystem::remove(fileName);
}
catch (const std::exception &)
{
throw std::logic_error("Permission denied.");
}
}
std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, const std::string &filename)
{
if (!UiFileProperty::IsDirectoryNameValid(directory))
{
throw std::logic_error("Permission denied.");
}
std::filesystem::path filePath{filename};
if (filePath.has_parent_path())
{
throw std::logic_error("Permission denied.");
}
std::filesystem::path result = this->GetPluginAudioFileDirectory() / directory / filename;
if (!this->IsValidSampleFile(result))
{
throw std::logic_error("Permission denied.");
}
return result;
}
std::string Storage::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, const std::string &fileBody)
{
std::filesystem::path path;
if (directory.length() != 0)
{
path = this->MakeUserFilePath(directory, filename);
}
else
{
if (patchProperty.length() == 0)
{
throw std::logic_error("Malformed request.");
}
throw std::logic_error("patchProperty directory not implemented.");
}
{
std::filesystem::create_directories(path.parent_path());
std::ofstream f(path, std::ios_base::trunc | std::ios_base::binary);
if (!f.is_open())
{
throw std::logic_error(SS("Can't create file " << path << "."));
}
f.write(fileBody.c_str(), fileBody.length());
}
return path.string();
}
JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+29 -4
View File
@@ -53,6 +53,12 @@ public:
};
struct PluginPresetValues {
std::vector<ControlValue> controls;
Lv2PluginState state;
};
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
class Storage {
@@ -100,7 +106,7 @@ public:
void SetDataRoot(const std::filesystem::path& path);
void SetConfigRoot(const std::filesystem::path& path);
std::filesystem::path GetPluginStorageDirectory() const;
std::filesystem::path GetPluginAudioFileDirectory() const;
std::vector<std::string> GetPedalboards();
@@ -132,6 +138,8 @@ public:
int64_t DeletePreset(int64_t presetId);
bool RenamePreset(int64_t presetId, const std::string&name);
int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
int64_t CreateNewPreset();
void RenameBank(int64_t bankId, const std::string&newName);
int64_t SaveBankAs(int64_t bankId, const std::string&newName);
@@ -166,13 +174,29 @@ private:
void LoadPluginPresetIndex();
void SavePluginPresetIndex();
std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const;
bool IsValidSampleFile(const std::filesystem::path&fileName);
std::filesystem::path MakeUserFilePath(const std::string &directory, const std::string&filename);
public:
bool HasPluginPresets(const std::string&pluginUri) const;
void SavePluginPresets(const std::string&pluginUri, const PluginPresets&presets);
PluginPresets GetPluginPresets(const std::string&pluginUri) const;
PluginUiPresets GetPluginUiPresets(const std::string&pluginUri) const;
std::vector<ControlValue> GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId);
uint64_t SavePluginPreset(const std::string&pluginUri, const std::string&name, const std::map<std::string,float> & values);
PluginPresetValues GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId);
uint64_t SavePluginPreset(
const std::string&name,
const PedalboardItem&pedalboardEntry
);
uint64_t SavePluginPreset(
const std::string &pluginUri,
const std::string &name,
float inputVolume,
float outputVolume,
const std::map<std::string, float> &values,
const Lv2PluginState& lv2State);
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
uint64_t CopyPluginPreset(const std::string&pluginUri,uint64_t presetId);
@@ -183,7 +207,8 @@ public:
bool GetShowStatusMonitor() const;
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
std::vector<MidiBinding> GetSystemMidiBindings();
void DeleteSampleFile(const std::filesystem::path &fileName);
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody);
};
+45 -10
View File
@@ -36,32 +36,46 @@ Units UriToUnits(const std::string &uri);
std::map<Units,std::string> unitsToStringMap =
{
//LV2_UNITS__conversion
//LV2_UNITS__name
CASE(none)
CASE(unknown)
CASE(bar)
CASE(beat)
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(coef)
CASE(db)
CASE(degree)
CASE(frame)
CASE(hz)
CASE(inch)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(mile)
CASE(min)
CASE(mm)
CASE(ms)
CASE(pc)
CASE(oct)
CASE(s)
CASE(pc)
CASE(semitone12TET)
};
static const std::string emptyString;
const std::string& pipedal::UnitsToString(Units units)
{
return unitsToStringMap[units];
if (unitsToStringMap.find(units) != unitsToStringMap.end())
{
return unitsToStringMap[units];
} else {
return emptyString;
}
}
#undef CASE
@@ -76,17 +90,24 @@ std::map<std::string,Units> unitMap = {
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(coef)
CASE(db)
CASE(degree)
CASE(frame)
CASE(hz)
CASE(inch)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(mile)
CASE(min)
CASE(mm)
CASE(ms)
CASE(pc)
CASE(oct)
CASE(s)
CASE(pc)
CASE(semitone12TET)
};
#undef CASE
@@ -97,38 +118,52 @@ std::map<std::string,Units> unitMap = {
std::map<std::string,Units> uriToUnitsMap = {
{ "", Units::none,},
CASE(bar)
CASE(beat)
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(coef)
CASE(db)
CASE(degree)
CASE(frame)
CASE(hz)
CASE(inch)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(mile)
CASE(min)
CASE(mm)
CASE(ms)
CASE(pc)
CASE(oct)
CASE(s)
CASE(pc)
CASE(semitone12TET)
};
#undef CASE
Units pipedal::StringToUnits(const std::string &text)
{
return unitMap[text];
if (unitMap.find(text) != unitMap.end()) {
return unitMap[text];
}
return Units::none;
}
Units pipedal::UriToUnits(const std::string &text)
{
if (text.length() == 0) return Units::none;
auto result = uriToUnitsMap[text];
if (result == Units::none) return Units::unknown;
return result;
if (uriToUnitsMap.contains(text))
{
auto result = uriToUnitsMap[text];
return result;
}
return Units::unknown;
}
+8 -1
View File
@@ -44,7 +44,14 @@ enum class Units {
pc,
s,
semitone12TET,
custom
custom,
degree,
coef,
frame,
inch,
mile,
mm,
oct,
};
+1 -1
View File
@@ -272,7 +272,7 @@ void Vst3Host::Private::UpdateControlInfo(IEditController *controller, Lv2Plugin
Steinberg::Vst::ParameterInfo info;
tresult tErr = controller->getParameterInfo(param, info);
Lv2PluginUiControlPort port;
Lv2PluginUiPort port;
port.index(param); // used for LV2 purposes.
port.symbol(SS(info.id)); // Used for VST3 purposes. convert to int to specifiy a vst control id.
+1 -2
View File
@@ -26,7 +26,7 @@ namespace pipedal
class VuUpdate
{
public:
uint64_t instanceId_ = 0;
int64_t instanceId_ = 0;
long sampleTime_ = 0;
bool isStereoInput_ = false;
bool isStereoOutput_ = false;
@@ -76,7 +76,6 @@ namespace pipedal
{
AccumulateVu(&outputMaxValueL_,outputL,samples);
AccumulateVu(&outputMaxValueR_,outputR,samples);
}
DECLARE_JSON_MAP(VuUpdate);
+92 -35
View File
@@ -27,6 +27,8 @@
#include <websocketpp/server.hpp>
#include "WebServerLog.hpp"
using namespace pipedal;
using namespace std;
@@ -37,6 +39,34 @@ using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
const size_t MAX_READ_SIZE = 1 * 1024 * 204;
using namespace boost;
class CustomPpConfig: public websocketpp::config::asio {
public:
typedef CustomPpConfig type;
typedef websocketpp::config::asio base;
static const size_t max_http_body_size = 100000000; //websocketpp::config::asio::max_http_body_size;
typedef pipedal_elog elog_type;
typedef pipedal_alog alog_type;
struct transport_config : public base::transport_config {
typedef type::concurrency_type concurrency_type;
typedef type::alog_type alog_type;
typedef type::elog_type elog_type;
typedef type::request_type request_type;
typedef type::response_type response_type;
typedef websocketpp::transport::asio::basic_socket::endpoint
socket_type;
};
typedef websocketpp::transport::asio::endpoint<transport_config>
transport_type;
};
std::string
pipedal::last_modified(const std::filesystem::path &path)
{
@@ -149,7 +179,7 @@ namespace pipedal
boost::asio::io_context *pIoContext = nullptr;
typedef websocketpp::connection_hdl connection_hdl;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::server<CustomPpConfig> server;
class HttpRequestImpl : public HttpRequest
{
@@ -208,11 +238,17 @@ namespace pipedal
private:
// IWriteCallback
virtual void close() { webSocket->close(websocketpp::close::status::normal, ""); }
virtual void close() {
webSocket->close(websocketpp::close::status::normal, "");
webSocket = nullptr;
}
virtual void writeCallback(const std::string &text)
{
webSocket->send(text, websocketpp::frame::opcode::text);
if (webSocket)
{
webSocket->send(text, websocketpp::frame::opcode::text);
}
}
virtual std::string getFromAddress() const
{
@@ -222,12 +258,18 @@ namespace pipedal
public:
~WebSocketSession()
{
this->socketHandler = nullptr;
webSocket = nullptr;
pServer = nullptr;
Lv2Log::info("WebSocketSession closed.");
}
using ptr = std::shared_ptr<WebSocketSession>;
WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
: pServer(pServer),
webSocket(webSocket)
{
}
void Open()
{
uri requestUri(webSocket->get_uri()->str().c_str());
fromAddress = SS(webSocket->get_socket().remote_endpoint());
@@ -276,48 +318,58 @@ namespace pipedal
std::set<WebSocketSession::ptr, std::owner_less<WebSocketSession::ptr>> m_sessions;
void on_session_closed(WebSocketSession::ptr session, connection_hdl hConnection)
void on_session_closed(WebSocketSession::ptr &session, connection_hdl hConnection)
{
m_sessions.erase(session);
m_sessions.erase(session);
session = nullptr; // probably delete here.
m_connections.erase(hConnection);
}
void NotFound(server::connection_type &connection, const std::string &filename)
{
// 404 error
std::stringstream ss;
try {
// 404 error
std::stringstream ss;
ss << "<!doctype html><html><head>"
<< "<title>Error 404 (Resource not found)</title><body>"
<< "<h1>Error 404</h1>"
<< "<p>The requested URL " << HtmlHelper::HtmlEncode(filename) << " was not found on this server.</p>"
<< "</body></head></html>";
ss << "<!doctype html><html><head>"
<< "<title>Error 404 (Resource not found)</title><body>"
<< "<h1>Error 404</h1>"
<< "<p>The requested URL " << HtmlHelper::HtmlEncode(filename) << " was not found on this server.</p>"
<< "</body></head></html>";
std::string body = ss.str();
connection.set_body(body);
std::stringstream ssLen;
ssLen << body.length();
connection.replace_header(HttpField::content_length, ssLen.str());
connection.set_status(websocketpp::http::status_code::not_found);
std::string body = ss.str();
connection.set_body(body);
std::stringstream ssLen;
ssLen << body.length();
connection.replace_header(HttpField::content_length, ssLen.str());
connection.set_status(websocketpp::http::status_code::not_found);
} catch (const std::exception&)
{
// ignored. Things weren't going well anyway.
}
return;
};
void ServerError(server::connection_type &connection, const std::string &error)
{
// 404 error
std::stringstream ss;
try {
// 404 error
std::stringstream ss;
ss << "<!doctype html><html><head>"
<< "<title>Error 500 (Server error)</title><body>"
<< "<h1>Error 500</h1>"
<< "<p>" << HtmlHelper::HtmlEncode(error) << "</p>"
<< "</body></head></html>";
std::string body = ss.str();
connection.set_body(body);
std::stringstream ssLen;
ssLen << body.length();
connection.replace_header(HttpField::content_length, ssLen.str());
ss << "<!doctype html><html><head>"
<< "<title>Error 500 (Server error)</title><body>"
<< "<h1>Error 500</h1>"
<< "<p>" << HtmlHelper::HtmlEncode(error) << "</p>"
<< "</body></head></html>";
std::string body = ss.str();
connection.set_body(body);
std::stringstream ssLen;
ssLen << body.length();
connection.replace_header(HttpField::content_length, ssLen.str());
connection.set_status(websocketpp::http::status_code::internal_server_error);
connection.set_status(websocketpp::http::status_code::internal_server_error);
} catch (const std::exception&)
{
}
return;
};
@@ -564,10 +616,16 @@ namespace pipedal
{
m_connections.insert(hdl);
server::connection_ptr webSocket = m_endpoint.get_con_from_hdl(hdl);
WebSocketSession::ptr socketSession = std::make_shared<WebSocketSession>(this, webSocket);
m_sessions.insert(socketSession);
try {
server::connection_ptr webSocket = m_endpoint.get_con_from_hdl(hdl);
WebSocketSession::ptr socketSession = std::make_shared<WebSocketSession>(this, webSocket);
socketSession->Open();
m_sessions.insert(socketSession);
} catch (const std::exception&e)
{
Lv2Log::error("Failed to open session: %s", e.what());
}
}
void on_fail(connection_hdl hdl)
@@ -591,7 +649,6 @@ namespace pipedal
m_endpoint.set_reuse_addr(true);
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
// m_endpoint.set_access_channels(websocketpp::log::alevel::access_core);
m_endpoint.set_access_channels(websocketpp::log::alevel::fail);
m_endpoint.init_asio(&ioc);
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright (c) 2014, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
Based on websocketpp/logger/pipedal_logger.hpp, Copyright Peter Thorson.
*/
/// Logger that redirects webserverpp messages to Lv2Log.
#ifndef WEBSOCKETPP_LOGGER_PIPEDAL_HPP
#define WEBSOCKETPP_LOGGER_PIPEDAL_HPP
/* Need a way to print a message to the log
*
* - timestamps
* - channels
* - thread safe
* - output to stdout or file
* - selective output channels, both compile time and runtime
* - named channels
* - ability to test whether a log message will be printed at compile time
*
*/
#include <ctime>
#include <iostream>
#include <iomanip>
#include <string>
#include "Lv2Log.hpp"
#include "ss.hpp"
#include <websocketpp/logger/levels.hpp>
namespace pipedal {
class pipedal_elog {
public:
using level = websocketpp::log::level;
using elevel = websocketpp::log::elevel;
using channel_type_hint = websocketpp::log::channel_type_hint;
pipedal_elog()
: m_channels(0xffffffff)
{
}
/// Destructor
~pipedal_elog() {}
/// Copy constructor
pipedal_elog(pipedal_elog const & other)
: m_channels(other.m_channels)
{}
pipedal_elog(level level,channel_type_hint::value hint = channel_type_hint::error)
{
this->m_channels = level;
}
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
// no copy assignment operator because of const member variables
pipedal_elog & operator=(pipedal_elog const &) = delete;
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
/// Move constructor
pipedal_elog(pipedal_elog && other)
: m_channels(other.m_channels)
{}
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
// no move assignment operator because of const member variables
pipedal_elog & operator=(pipedal_elog &&) = delete;
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
#endif // _WEBSOCKETPP_MOVE_SEMANTICS_
void set_channels(level channels) {
m_channels |= channels;
}
void clear_channels(level channels) {
m_channels &= ~m_channels;
}
/// Write a string message to the given channel
/**
* @param channel The channel to write to
* @param msg The message to write
*/
void write(level channel, std::string const & msg) {
if (!dynamic_test(channel)) return;
switch (channel)
{
case elevel::devel:
case elevel::library:
Lv2Log::debug(msg);
break;
case elevel::info:
Lv2Log::info(msg);
break;
case elevel::warn:
Lv2Log::warning(msg);
break;
case elevel::rerror:
Lv2Log::error(msg);
break;
case elevel::fatal:
Lv2Log::error("Fatal error: " + msg);
break;
default:
break;
}
}
/// Write a cstring message to the given channel
/**
* @param channel The channel to write to
* @param msg The message to write
*/
void write(level channel, char const * msg) {
write(channel,std::string(msg));
}
constexpr bool static_test(level channel) const {
return m_static_channels;
}
bool dynamic_test(level channel) {
return (m_channels) & channel != 0;
}
protected:
private:
static constexpr level m_static_channels = elevel::warn | elevel::rerror | elevel::fatal;
level m_channels;
};
class pipedal_alog {
public:
using level = websocketpp::log::level;
using alevel = websocketpp::log::alevel;
using channel_type_hint = websocketpp::log::channel_type_hint;
pipedal_alog()
:m_channels(alevel::fail)
{
}
pipedal_alog(level level, channel_type_hint::value hint = channel_type_hint::access)
: m_channels(level)
{
}
/// Destructor
~pipedal_alog() {}
/// Copy constructor
pipedal_alog(pipedal_alog const & other)
:m_channels(other.m_channels)
{}
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
// no copy assignment operator because of const member variables
pipedal_alog & operator=(pipedal_elog const &) = delete;
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
/// Move constructor
pipedal_alog(pipedal_alog && other)
{}
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
// no move assignment operator because of const member variables
pipedal_alog & operator=(pipedal_elog &&) = delete;
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
#endif // _WEBSOCKETPP_MOVE_SEMANTICS_
void set_channels(level channels) {
m_channels |= channels;
}
void clear_channels(level channels) {
m_channels = m_channels & ~channels;
}
/// Write a string message to the given channel
/**
* @param channel The channel to write to
* @param msg The message to write
*/
void write(level channel, std::string const & msg) {
if (dynamic_test(channel))
{
Lv2Log::info(SS('[' << alevel::channel_name(channel) << "] " << msg));
}
}
/// Write a cstring message to the given channel
/**
* @param channel The channel to write to
* @param msg The message to write
*/
void write(level channel, char const * msg) {
write(channel,std::string(msg));
}
constexpr bool static_test(level channel) const {
return (this->m_static_channels & channel) != 0;
}
bool dynamic_test(level channel) {
return false;
}
protected:
private:
static constexpr level m_static_channels = alevel::access_core;
level m_channels;
};
} //namespace pipedal
#endif // WEBSOCKETPP_LOGGER_PIPEDAL_HPP
+49 -22
View File
@@ -139,6 +139,9 @@ public:
return true;
}
else if (segment == "uploadBank")
{
return true;
} else if (segment == "uploadUserFile")
{
return true;
}
@@ -402,6 +405,37 @@ public:
res.setContentLength(result.length());
res.setBody(result);
} else if (segment == "uploadUserFile")
{
res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache");
const std::string &fileBody = req.body();
const std::string &directory = request_uri.query("directory");
const std::string &filename = request_uri.query("filename");
const std::string &patchProperty = request_uri.query("property");
if (patchProperty.length() == 0 && directory.length() == 0)
{
throw PiPedalException("Malformed request.");
}
res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache");
std::string outputFileName = std::filesystem::path(directory) / filename;
std::string path = this->model->UploadUserFile(directory,patchProperty,filename,fileBody);
std::stringstream ss;
json_writer writer(ss);
writer.write(outputFileName);
std::string response = ss.str();
res.setContentLength(response.length());
res.setBody(response);
}
else
{
@@ -698,6 +732,21 @@ int main(int argc, char *argv[])
model.GetAlsaDevices();
}
// only accept signals on the main thread.
int sig;
sigset_t sigSet;
int s;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGINT);
sigaddset(&sigSet, SIGTERM);
sigaddset(&sigSet, SIGUSR1);
s = pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
if (s != 0)
{
throw std::logic_error("pthread_sigmask failed.");
}
#if JACK_HOST
if (systemd)
{
@@ -767,28 +816,6 @@ int main(int argc, char *argv[])
{
int sig;
sigset_t sigSet;
int s;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGINT);
sigaddset(&sigSet, SIGTERM);
sigaddset(&sigSet, SIGUSR1);
s = pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
if (s != 0)
{
throw std::logic_error("pthread_sigmask failed.");
}
if (s != 0)
{
throwSystemError(s);
}
//signal(SIGINT, sig_handler);
//signal(SIGTERM, sig_handler);
//signal(SIGUSR1, sig_handler);
sigwait(&sigSet,&sig);
if (systemd)
+1
View File
@@ -3,6 +3,7 @@ Description=${DESCRIPTION}
After=network.target
After=sound.target
After=local-fs.target
After=avahi-daemon