Alpha
This commit is contained in:
+62
-15
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,6 +141,9 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
DbDezipper.hpp DbDezipper.cpp
|
||||
WebServerLog.hpp
|
||||
|
||||
util.hpp util.cpp
|
||||
PiPedalUI.hpp
|
||||
PiPedalUI.cpp
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace pipedal
|
||||
class Lv2Effect : public IEffect
|
||||
{
|
||||
private:
|
||||
|
||||
std::unique_ptr<StateInterface> stateInterface;
|
||||
void RestoreState(const PedalboardItem&pedalboardItem);
|
||||
|
||||
|
||||
+73
-29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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(']');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -44,7 +44,14 @@ enum class Units {
|
||||
pc,
|
||||
s,
|
||||
semitone12TET,
|
||||
custom
|
||||
custom,
|
||||
degree,
|
||||
coef,
|
||||
frame,
|
||||
inch,
|
||||
mile,
|
||||
mm,
|
||||
oct,
|
||||
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -3,6 +3,7 @@ Description=${DESCRIPTION}
|
||||
After=network.target
|
||||
After=sound.target
|
||||
After=local-fs.target
|
||||
After=avahi-daemon
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user