Interim checking, Convolution Reverb

This commit is contained in:
Robin Davies
2023-04-05 03:00:51 -04:00
parent ed1dc75c53
commit b83ba7ca94
115 changed files with 7643 additions and 3055 deletions
+1 -1
View File
@@ -206,7 +206,7 @@ bool setJackConfiguration(JackServerSettings serverSettings)
#if JACK_HOST
serverSettings.Write();
serverSettings.WriteDaemonConfig();
silentSysExec("/usr/bin/systemctl unmask jack");
silentSysExec("/usr/bin/systemctl enable jack");
+2
View File
@@ -23,6 +23,7 @@
*/
#include "pch.h"
#include "util.hpp"
#include <bit>
#include <memory>
#include "ss.hpp"
@@ -1367,6 +1368,7 @@ namespace pipedal
}
void AudioThread()
{
SetThreadName("alsaDriver");
try
{
#if defined(__WIN32)
+3 -3
View File
@@ -92,8 +92,8 @@ public:
#endif
oscillator.Init(440,jackConfiguration.GetSampleRate());
latencyMonitor.Init(jackConfiguration.GetSampleRate());
oscillator.Init(440,jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
@@ -107,7 +107,7 @@ public:
if (testType == TestType::LatencyMonitor)
{
auto latency = this->latencyMonitor.GetLatency();
double ms = 1000.0*latency/jackConfiguration.GetSampleRate();
double ms = 1000.0*latency/jackConfiguration.sampleRate();
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
}
+54
View File
@@ -0,0 +1,54 @@
/*
* MIT License
*
* Copyright (c) 2023 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <vector>
#include "lv2/atom.lv2/atom.h"
namespace pipedal
{
class AtomBuffer
{
public:
AtomBuffer() { }
AtomBuffer(const LV2_Atom *atom)
{
size_t size = atom->size + sizeof(LV2_Atom);
data.resize(size);
uint8_t *p = (uint8_t *)atom;
for (size_t i = 0; i < size; ++i)
{
data[i] = p[i];
}
}
const LV2_Atom *AsAtom() const
{
return (const LV2_Atom *)&(data[0]);
}
private:
std::vector<uint8_t> data;
};
}
+548
View File
@@ -0,0 +1,548 @@
/*
* MIT License
*
* Copyright (c) 2023 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* 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 "AtomConverter.hpp"
#include <cstddef>
#include <cstring>
#include <sstream>
#include "json.hpp"
#include "ss.hpp"
#include "lv2/atom.lv2/util.h"
using namespace pipedal;
const std::string AtomConverter::ID_TAG {"id_"};
const std::string AtomConverter::OTYPE_TAG {"otype_"};
const std::string AtomConverter::VTYPE_TAG {"vtype_"};
#define SHORT_ATOM__Bool "Bool"
#define SHORT_ATOM__Float "Float"
#define SHORT_ATOM__Int "Int"
#define SHORT_ATOM__Long "Long"
#define SHORT_ATOM__Double "Double"
#define SHORT_ATOM__Vector "Vector"
#define SHORT_ATOM__Object "Object"
#define SHORT_ATOM__Tuple "Tuple"
#define SHORT_ATOM__URI "URI"
#define SHORT_ATOM__URID "URID"
#define SHORT_ATOM__String "String"
#define SHORT_ATOM__Path "Path"
AtomConverter::AtomConverter(MapFeature &map)
: map(map)
{
InitUrids();
if (prototype)
{
this->prototypeBuffer.resize(prototype->size+sizeof(LV2_Atom));
std::memcpy(&(this->prototypeBuffer[0]), prototype, this->prototypeBuffer.size());
this->prototype = (LV2_Atom*)&(prototype[0]);
}
lv2_atom_forge_init(&outputForge, map.GetMap());
}
json_variant AtomConverter::ToJson(const LV2_Atom *atom)
{
json_variant variant = ToVariant(const_cast<LV2_Atom*>(atom));
return std::move(variant);
}
LV2_Atom*AtomConverter::ToAtom(const json_variant&json)
{
if (outputBuffer.size() == 0)
{
outputBuffer.resize(512);
}
while (true)
{
try {
LV2_Atom *result = (LV2_Atom*)(&outputBuffer[0]);
result->size = outputBuffer.size();
lv2_atom_forge_set_buffer(&outputForge,(uint8_t*)&(outputBuffer[0]),outputBuffer.size());
ToForge(json);
return (LV2_Atom*)(&outputBuffer[0]);
} catch (const BufferOverflowException&)
{
}
if (outputBuffer.size() >= 1024*1024)
{
throw std::logic_error("Atom is too large.");
}
outputBuffer.resize(outputBuffer.size()*2);
}
}
static inline void *AtomContent(LV2_Atom*atom,size_t offset = 0)
{
// always % sizeof(LV2_Atom)
return (void*)((char*)atom + sizeof(LV2_Atom)+offset);
}
static inline LV2_Atom*NextAtom(LV2_Atom*atom)
{
size_t size = sizeof(LV2_Atom) + (atom->size +(sizeof(LV2_Atom)-1))/sizeof(LV2_Atom)*sizeof(LV2_Atom);
return (LV2_Atom*)((char*)atom + size);
}
json_variant AtomConverter::ToVariant(LV2_Atom *atom)
{
if (atom->type == urids.ATOM__Float)
{
LV2_Atom_Float*pVal = (LV2_Atom_Float*)atom;
return json_variant((double)pVal->body);
}
if (atom->type == urids.ATOM__Bool)
{
LV2_Atom_Bool *pVal = (LV2_Atom_Bool*)atom;
return json_variant(pVal->body != 0);
}
if (atom->type == urids.ATOM__Int)
{
LV2_Atom_Int *pVal = (LV2_Atom_Int*)atom;
return TypedProperty(SHORT_ATOM__Int,(double)pVal->body);
}
if (atom->type == urids.ATOM__Long)
{
LV2_Atom_Long *pVal = (LV2_Atom_Long*)atom;
return TypedProperty(SHORT_ATOM__Long,(double)pVal->body);
}
if (atom->type == urids.ATOM__Double)
{
LV2_Atom_Double*pVal = (LV2_Atom_Double*)atom;
return TypedProperty(SHORT_ATOM__Double,(double)pVal->body);
}
if (atom->type == urids.ATOM__URID)
{
LV2_Atom_URID*pVal = (LV2_Atom_URID*)atom;
const char*strValue = map.UridToString(pVal->body);
return TypedProperty(SHORT_ATOM__URID,std::string(strValue));
}
if (atom->type == urids.ATOM__String)
{
const char*p = (const char*)atom +sizeof(LV2_Atom);
size_t size = atom->size;
while (size > 0 && p[size-1] == '\0')
{
--size;
}
return json_variant(std::string(p,size));
} else if (atom->type == urids.ATOM__Path)
{
const char*p = (const char*)atom +sizeof(LV2_Atom);
size_t size = atom->size;
while (size > 0 && p[size-1] == '\0')
{
--size;
}
return TypedProperty(SHORT_ATOM__Path,std::string(p,size));
} else if (atom->type == urids.ATOM__URI)
{
const char*p = (const char*)atom +sizeof(LV2_Atom);
size_t size = atom->size;
while (size > 0 && p[size-1] == '\0')
{
--size;
}
return TypedProperty(SHORT_ATOM__URI,std::string(p,size));
}
else if (atom->type == urids.ATOM__Tuple)
{
json_array array;
LV2_Atom*current = (LV2_Atom*)AtomContent(atom,0);
LV2_Atom*end = (LV2_Atom*)AtomContent(atom,atom->size);
while (current < end)
{
array.push_back(ToVariant(current));
current = NextAtom(current);
}
json_variant vArray { std::move(array)};
return TypedProperty(SHORT_ATOM__Tuple,std::move(vArray));
}
if (atom->type == urids.ATOM__URID)
{
LV2_Atom_URID *pVal = (LV2_Atom_URID*)atom;
return TypedProperty(SHORT_ATOM__URID,std::string(map.UridToString(pVal->body)));
}
if (atom->type == urids.ATOM__Vector)
{
json_array array;
LV2_Atom_Vector *pVal = (LV2_Atom_Vector*)atom;
size_t n = (atom->size-sizeof(LV2_Atom_Vector_Body))/pVal->body.child_size;
void *vectorData = AtomContent(atom,sizeof(LV2_Atom_Vector_Body));
if (pVal->body.child_type == urids.ATOM__Float)
{
float*p = (float*)vectorData;
for (size_t i = 0; i < n; ++i)
{
array.push_back(json_variant((double)p[i]));
}
}
else if (pVal->body.child_type == urids.ATOM__Int)
{
auto p = (int32_t*)vectorData;
for (size_t i = 0; i < n; ++i)
{
array.push_back(json_variant((double)p[i]));
}
}
else if (pVal->body.child_type == urids.ATOM__Bool)
{
auto p = (int32_t*)vectorData;
for (size_t i = 0; i < n; ++i)
{
array.push_back(json_variant(p[i] != 0));
}
}
else if (pVal->body.child_type == urids.ATOM__Long)
{
auto p = (int64_t*)vectorData;
for (size_t i = 0; i < n; ++i)
{
array.push_back(json_variant((double)(p[i])));
}
}
else if (pVal->body.child_type == urids.ATOM__Double)
{
auto p = (double*)vectorData;
for (size_t i = 0; i < n; ++i)
{
array.push_back(json_variant((double)p[i]));
}
} else {
std::string dataType = map.UridToString(pVal->body.child_type);
throw std::logic_error("AtomConverter: Vector dataype not supported. (" + dataType + ") Please contact support if you get this message.");
}
json_variant vArray {std::move(array)};
json_variant object = json_variant::make_object();
object[OTYPE_TAG] = json_variant(std::string(SHORT_ATOM__Vector));
object[VTYPE_TAG] = json_variant(std::string(TypeUridToString(pVal->body.child_type)));
object["value"] = std::move(vArray);
return std::move(object);
} else if (atom->type == urids.ATOM__Property)
{
throw std::logic_error("Not implemented.");
} else if (atom->type == urids.ATOM__Object)
{
LV2_Atom_Object *pVal = (LV2_Atom_Object*)atom;
json_variant result = json_variant::make_object();
if (pVal->body.id != 0)
{
result[ ID_TAG] = map.UridToString(pVal->body.id);
}
if (pVal->body.otype != 0)
{
result[OTYPE_TAG] = map.UridToString(pVal->body.otype);
}
LV2_Atom_Property_Body *current = (LV2_Atom_Property_Body *)AtomContent(atom,sizeof(LV2_Atom_Object_Body));
LV2_Atom_Property_Body *end = (LV2_Atom_Property_Body *)AtomContent(atom,atom->size);
while (current < end)
{
std::string key = map.UridToString(current->key);
json_variant value = ToVariant(&current->value);
result[key] = std::move(value);
current = (LV2_Atom_Property_Body*)(NextAtom(&(current->value)));
}
return result;
}
throw std::logic_error(
SS("AtomConverter: Datatype not supported. ("
<< map.UridToString(atom->type)
<< ") Please contact support if you get this message."));
}
bool AtomConverter::AreTypesTheSame(LV2_Atom*left, LV2_Atom *right) const
{
if (left->type != right->type) return false;
if (left->type == urids.ATOM__Object)
{
LV2_Atom_Object *l = (LV2_Atom_Object*)left;
LV2_Atom_Object *r = (LV2_Atom_Object*)right;
return l->body.id == r->body.id && l->body.otype == r->body.otype;
}
return true;
}
void AtomConverter::ObjectToForge(const json_variant&json)
{
LV2_URID bodyId = 0;
assert(json.is_object());
std::string oType = json[OTYPE_TAG].as_string();
if (oType == SHORT_ATOM__Int)
{
lv2_atom_forge_int(&this->outputForge,(int32_t)json["value"].as_number());
}
else if (oType == SHORT_ATOM__Long)
{
lv2_atom_forge_long(&this->outputForge,(int64_t)json["value"].as_number());
} else if (oType == SHORT_ATOM__Double)
{
lv2_atom_forge_double(&this->outputForge,(double)json["value"].as_number());
}
else if (oType == SHORT_ATOM__Path)
{
std::string value = json["value"].as_string().c_str();
lv2_atom_forge_path(&this->outputForge,value.c_str(),value.length()+1);
}
else if (oType == SHORT_ATOM__URI)
{
std::string value = json["value"].as_string().c_str();
lv2_atom_forge_uri(&this->outputForge,value.c_str(),value.length()+1);
}
else if (oType == SHORT_ATOM__URID)
{
LV2_URID urid = map.GetUrid(json["value"].as_string().c_str());
lv2_atom_forge_urid(&outputForge,urid);
}
else if (oType == SHORT_ATOM__Tuple)
{
TupleToForge(json);
} else if (oType == SHORT_ATOM__Vector)
{
VectorToForge(json);
} else
{
LV2_URID id = 0;
if (json.contains(ID_TAG))
{
id = map.GetUrid(json[ID_TAG].as_string().c_str());
}
LV2_URID oType = 0;
if (json.contains(OTYPE_TAG))
{
oType = map.GetUrid(json[OTYPE_TAG].as_string().c_str());
}
LV2_Atom_Forge_Frame frame;
lv2_atom_forge_object(&outputForge,&frame,id,oType);
const auto& obj = json.as_object();
for (auto member: *obj.get())
{
const std::string& property = member.first;
if (property != OTYPE_TAG && property != ID_TAG)
{
LV2_URID property = map.GetUrid(member.first.c_str());
lv2_atom_forge_key(&outputForge,property);
ToForge(member.second);
}
}
lv2_atom_forge_pop(&outputForge,&frame);
}
}
void AtomConverter::ToForge(const json_variant&json)
{
if (json.is_number())
{
CheckResult(lv2_atom_forge_float(&outputForge,(float)json.as_number()));
}
else if (json.is_bool())
{
CheckResult(lv2_atom_forge_bool(&outputForge,json.as_bool()));
}
else if (json.is_string())
{
const std::string&str = json.as_string();
CheckResult(lv2_atom_forge_string(&outputForge,str.c_str(),str.size()+1));
} else if (json.is_object())
{
return ObjectToForge(json);
} else {
throw std::logic_error("Malformed json atom.");
}
}
void AtomConverter::VectorToForge(const json_variant&json)
{
const json_array& array = *(json["value"].as_array().get());
size_t size = array.size();
LV2_Atom_Forge_Frame frame;
LV2_URID childType = GetTypeUrid(json[VTYPE_TAG].as_string().c_str());
if (childType == urids.ATOM__Float)
{
using T = float;
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
for (size_t i = 0; i < size; ++i)
{
T value = (T)(array[i].as_number());
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
}
// does this pad the frame?
lv2_atom_forge_pop(&outputForge,&frame);
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
}
else if (childType == urids.ATOM__Int)
{
using T = int32_t;
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
for (size_t i = 0; i < size; ++i)
{
T value = (T)(array[i].as_number());
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
}
// does this pad the frame?
lv2_atom_forge_pop(&outputForge,&frame);
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
}
else if (childType == urids.ATOM__Bool)
{
using T = int32_t;
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
for (size_t i = 0; i < size; ++i)
{
T value = (T)(array[i].as_bool());
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
}
// does this pad the frame?
lv2_atom_forge_pop(&outputForge,&frame);
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
}
else if (childType == urids.ATOM__Long)
{
using T = int64_t;
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
for (size_t i = 0; i < size; ++i)
{
T value = (T)(array[i].as_number());
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
}
// does this pad the frame?
lv2_atom_forge_pop(&outputForge,&frame);
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
}
else if (childType == urids.ATOM__Double)
{
using T = double;
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
for (size_t i = 0; i < size; ++i)
{
T value = (T)(array[i].as_number());
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
}
// does this pad the frame?
lv2_atom_forge_pop(&outputForge,&frame);
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
} else {
std::string dataType = map.UridToString(childType);
throw std::logic_error("AtomConverter: Vector dataype not supported. (" + dataType + ") Please contact support if you get this message.");
}
}
void AtomConverter::TupleToForge(const json_variant&json)
{
LV2_Atom_Forge_Frame frame;
const json_array&array = *(json["value"].as_array().get());
lv2_atom_forge_tuple(&outputForge,&frame);
{
for (const json_variant&v: array)
{
ToForge(v);
}
}
lv2_atom_forge_pop(&outputForge,&frame);
}
LV2_URID AtomConverter::GetTypeUrid(const std::string uri)
{
if (stringToTypeUrid.find(uri) != stringToTypeUrid.end())
{
return stringToTypeUrid[uri];
}
return map.GetUrid(uri.c_str());
}
LV2_URID AtomConverter::InitUrid(const char*uri, const char*shortUri )
{
LV2_URID urid = map.GetUrid(uri);
stringToTypeUrid[uri] = urid;
stringToTypeUrid[shortUri] = urid;
typeUridToString[urid] = shortUri;
return urid;
}
std::string AtomConverter::TypeUridToString(LV2_URID urid)
{
if (typeUridToString.find(urid) != typeUridToString.end())
{
return typeUridToString[urid];
}
return map.UridToString(urid);
}
void AtomConverter::InitUrids()
{
#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName)
ATOM_INIT(ATOM__Bool,Bool);
ATOM_INIT(ATOM__Chunk,Chunk);
ATOM_INIT(ATOM__Double,Double);
ATOM_INIT(ATOM__Event,Event);
ATOM_INIT(ATOM__Float,Float);
ATOM_INIT(ATOM__Int,Int);
ATOM_INIT(ATOM__Literal,Literal);
ATOM_INIT(ATOM__Long,Long);
ATOM_INIT(ATOM__Number,Number);
ATOM_INIT(ATOM__Object,Object);
ATOM_INIT(ATOM__Path,Path);
ATOM_INIT(ATOM__Property,Property);
//ATOM_INIT(ATOM__Resource,Resource);
//ATOM_INIT(ATOM__Sequence,Sequence);
//ATOM_INIT(ATOM__Sound,Sound);
ATOM_INIT(ATOM__String,String);
ATOM_INIT(ATOM__Tuple,Tuple);
ATOM_INIT(ATOM__Vector,Vector);
ATOM_INIT(ATOM__URI,URI);
ATOM_INIT(ATOM__URID,URID);
ATOM_INIT(ATOM__Vector,Vector);
//ATOM_INIT(ATOM__beatTime,beatTime);
//ATOM_INIT(ATOM__frameTime,frameTime);
//ATOM_INIT(ATOM__timeUnit,timeUnit);
#undef ATOM_INIT
}
+239
View File
@@ -0,0 +1,239 @@
/*
* MIT License
*
* Copyright (c) 2023 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* 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 <cstddef>
#include <vector>
#include "MapFeature.hpp"
#include "lv2/atom.lv2/atom.h"
#include "lv2/atom.lv2/forge.h"
#include "json_variant.hpp"
namespace pipedal
{
/** @brief Roud-trippable Conversion of atoms to json, and json to atoms.
@remarks
This class implements round-trippable conversion of atoms to json and
vice-versa.
Json objects are used for atom objects with non-trivial types; and
the "otype_" of a json object resolves the type of the corresponding
atom object. For the sake of density and readability, the otype_ of
basic LV2_ATOM__xxx types are represented using only portion of the
ATOM uri following the '#'.
Augmented semantics are required to preserve typed atoms in json. For
example, an ATOM_Patch__Get atom would be reprsented as follows.
{
"otype_": "http://lv2plug.in/ns/ext/patch#Set",
"http://lv2plug.in/ns/ext/patch#property": {
"otype_": "URID",
"value": "http://something.com/custom#prop"
},
"http://lv2plug.in/ns/ext/patch#value": {
"otype_": "Path",
"value": "/var/pipdeal/test.wav"
}
LV2_ATOM__String's are represented as strings in the corresponding json.
LV2_ATOM__Float's are represented as json numbers.
LV2_ATOM__Bool's are represented as unadorned boolean values in json.
Remaining Atom types require augmented json in order to preserve type information.
LV2_ATOM__Int:
{"otype_": "Int","value": 1}
LV2_ATOM__Long
{"otype_": "Int","value": 1}
LV2_ATOM__Double
{"otype_": "Double","value": 1.234}
LV2_ATOM__Path
{"otype_": "Path", "value": "/var/pipdeal/test.wav"}
LV2_ATOM__URID
{"otype_": "URID", "value": "http://something.com/custom#value"}
The value is transmitted as an LV2_URID when translated to an atom, and un-mapped
when transmitted in json.
LV2_ATOM__URI
{"otype_": "URI", "value": "http://something.com/custom#prop"}
The value is a string in both json and in atoms.
LV2_ATOM_TUPLE
{"otype_": "Tuple","value": [ ... arbitrary atoms ... ]}
LV2_ATOM_VECTOR
{ "otype_": "Vector", "vtype_": "Int", "value": [ 1, 2, 3, 4 ] }
Json values are co-erced to the type specified in "vtype_" and do not require
type annotation. vtype_ can be "Bool", "Int", "Long", "Float", or "Double".
LV2_ATOM_OBJECT
{ "otype_": "http://lv2plug.in/ns/ext/patch#Set",
"http://lv2plug.in/ns/ext/patch#property": {
"otype_": "URID",
"value": "http://something.com/custom#prop"
},
"http://two-play.com/ConvolutionReverb#volume": 3.5
}
An object has an "otype_" that specifies the type of the object, and contains
a list of propertys of the form "propertyname": <atom_value>. Property names
are strings, but are converted to LV2_URID's in the object body. By usual Atom
convention, the are URIs, but they could be simple strings as well.
**/
class AtomConverter
{
public:
/// @brief Constructor
/// @brief map used to map LV2_URIDs. Must have a lifetime longer than the AtomConverter instance.
AtomConverter(MapFeature &map);
/// @brief Convert the supplied atom to json.
/// @param atom The atom to convert.
/// @return The atom data in json format.
json_variant ToJson(const LV2_Atom *atom);
/// @brief Convert a json variant to an atom.
/// @param json Json variant that matches the structure of the prototype.
/// @return An atom.
/// @remarks
/// The json variant must match the structure of the LV2_Atom prototype supplied to
/// the constructor.
LV2_Atom*ToAtom(const json_variant& json);
private:
static const std::string OTYPE_TAG;
static const std::string VTYPE_TAG;
static const std::string ID_TAG;
std::map<std::string, LV2_URID> stringToTypeUrid;
std::map<LV2_URID,std::string> typeUridToString;
LV2_URID GetTypeUrid(const std::string uri);
std::string TypeUridToString(LV2_URID urid);
void InitUrids();
LV2_URID InitUrid(const char*uri, const char*shortUri);
template <typename U>
json_variant TypedProperty(const std::string type,U value)
{
json_variant result = json_variant::make_object();
result[OTYPE_TAG] = type;
result["value"] = json_variant(value);
return result;
}
json_variant TypedProperty(const std::string type,const json_variant&& value)
{
json_variant result = json_variant::make_object();
result[OTYPE_TAG] = type;
result["value"] = std::move(value);
return result;
}
bool AreTypesTheSame(LV2_Atom*left, LV2_Atom *right) const;
json_variant ToVariant(LV2_Atom *atom);
void ToForge(const json_variant&json);
void VectorToForge(const json_variant&json);
void TupleToForge(const json_variant&json);
void ObjectToForge(const json_variant&json);
class BufferOverflowException: public std::exception {
public:
BufferOverflowException() { }
virtual const char*what() const noexcept override {
return "Buffer overflow";
}
};
inline static void CheckResult(LV2_Atom_Forge_Ref ref)
{
if (ref == 0)
{
throw BufferOverflowException();
}
}
private:
class Urids
{
public:
LV2_URID ATOM__Bool;
LV2_URID ATOM__Chunk;
LV2_URID ATOM__Double;
LV2_URID ATOM__Event;
LV2_URID ATOM__Float;
LV2_URID ATOM__Int;
LV2_URID ATOM__Literal;
LV2_URID ATOM__Long;
LV2_URID ATOM__Number;
LV2_URID ATOM__Object;
LV2_URID ATOM__Path;
LV2_URID ATOM__Property;
LV2_URID ATOM__Resource;
LV2_URID ATOM__Sequence;
LV2_URID ATOM__Sound;
LV2_URID ATOM__String;
LV2_URID ATOM__Tuple;
LV2_URID ATOM__Vector;
LV2_URID ATOM__URI;
LV2_URID ATOM__URID;
LV2_URID ATOM__atomTransfer;
LV2_URID ATOM__beatTime;
LV2_URID ATOM__frameTime;
LV2_URID ATOM__supports;
LV2_URID ATOM__timeUnit;
};
Urids urids;
MapFeature &map;
std::vector<uint8_t> prototypeBuffer;
std::vector<uint8_t> outputBuffer;
LV2_Atom_Forge outputForge;
LV2_Atom * prototype = nullptr;
};
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "catch.hpp"
#include <sstream>
#include <cstdint>
#include <string>
#include <iostream>
#include "json.hpp"
#include "json_variant.hpp"
#include "AtomConverter.hpp"
#include "lv2/atom.lv2/util.h"
#include "lv2/patch.lv2/patch.h"
#include "MapFeature.hpp"
#include <cstring>
using namespace pipedal;
using namespace std;
MapFeature mapFeature;
using AtomBuffer = std::vector<uint8_t>;
static void RoundTripTest(const AtomBuffer &buffer)
{
LV2_Atom *prototype = (LV2_Atom *)&(buffer[0]);
AtomConverter converter(mapFeature);
json_variant variant = converter.ToJson(prototype);
cout << " " << variant.to_string() << endl;
LV2_Atom *atomOut = converter.ToAtom(variant);
REQUIRE(atomOut->size == prototype->size);
size_t size = atomOut->size + sizeof(LV2_Atom);
REQUIRE(std::memcmp(atomOut, prototype, size) == 0);
}
AtomBuffer MakeSimpleAtom()
{
AtomBuffer atomBuffer(512);
LV2_Atom_Forge t;
LV2_Atom_Forge *forge = &t;
lv2_atom_forge_init(forge, mapFeature.GetMap());
lv2_atom_forge_set_buffer(forge, &(atomBuffer[0]), atomBuffer.size());
lv2_atom_forge_string(forge, "abcde\0", 6);
return atomBuffer;
}
AtomBuffer MakeTestTupleAtom()
{
AtomBuffer atomBuffer(512);
LV2_Atom_Forge t;
LV2_Atom_Forge *forge = &t;
lv2_atom_forge_init(forge, mapFeature.GetMap());
lv2_atom_forge_set_buffer(forge, &(atomBuffer[0]), atomBuffer.size());
LV2_Atom_Forge_Frame frame;
lv2_atom_forge_tuple(forge, &frame);
{
lv2_atom_forge_bool(forge, true);
lv2_atom_forge_int(forge, 1);
lv2_atom_forge_float(forge, 2.1f);
lv2_atom_forge_double(forge, 1.23456789);
lv2_atom_forge_string(forge, "abcd\0", 5);
float vectorValues[] = {1.1f, 2.2f, 3.3f, 4.4f};
lv2_atom_forge_vector(forge, sizeof(float), mapFeature.GetUrid(LV2_ATOM__Float), 4, vectorValues);
{
LV2_Atom_Forge_Frame objFrame;
lv2_atom_forge_object(forge, &objFrame, 0, mapFeature.GetUrid(LV2_PATCH__Set));
lv2_atom_forge_key(forge,mapFeature.GetUrid(LV2_PATCH__property));
lv2_atom_forge_urid(forge,mapFeature.GetUrid("http://something.com/custom#prop"));
lv2_atom_forge_key(forge,mapFeature.GetUrid(LV2_PATCH__value));
std::string path = "/var/pipdeal/test.wav";
lv2_atom_forge_path(forge, path.c_str(),path.size()+1);
lv2_atom_forge_pop(forge, &objFrame);
}
}
lv2_atom_forge_pop(forge, &frame);
lv2_atom_forge_string(forge, "abcde\0", 6);
return atomBuffer;
}
TEST_CASE("AtomConverter", "[atom_converter][Build][Dev]")
{
cout << "=== AtomConverter Test ====" << endl;
cout << " testTuple" << endl;
RoundTripTest(MakeTestTupleAtom());
cout << " simpleAtom" << endl;
RoundTripTest(MakeSimpleAtom());
}
+5
View File
@@ -24,8 +24,13 @@
#pragma once
#ifndef JACK_HOST
#define JACK_HOST 0
#endif
#ifndef ALSA_HOST
#define ALSA_HOST 1
#endif
#if JACK_HOST && ALSA_HOST
#error Choose either JACK or ALSA
+175 -234
View File
@@ -18,11 +18,13 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "AudioHost.hpp"
#include "util.hpp"
#include "Lv2Log.hpp"
#include "JackDriver.hpp"
#include "AlsaDriver.hpp"
#include "AtomConverter.hpp"
using namespace pipedal;
@@ -177,6 +179,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost
{
private:
IHost *pHost = nullptr;
LV2_Atom_Forge inputWriterForge;
std::mutex atomConverterMutex;
AtomConverter atomConverter;
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
@@ -277,9 +284,9 @@ private:
JackChannelSelection channelSelection;
bool active = false;
std::shared_ptr<Lv2PedalBoard> currentPedalBoard;
std::vector<std::shared_ptr<Lv2PedalBoard>> activePedalBoards; // pedalboards that have been sent to the audio queue.
Lv2PedalBoard *realtimeActivePedalBoard = nullptr;
std::shared_ptr<Lv2Pedalboard> currentPedalboard;
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
std::vector<uint8_t *> midiLv2Buffers;
@@ -297,18 +304,19 @@ private:
{
throw std::invalid_argument("Not an Lv2 Object");
}
return pHost->Lv2UriudToString(pAtom->body.otype);
return pHost->Lv2UridToString(pAtom->body.otype);
}
void WriteAtom(json_writer &writer, LV2_Atom *pAtom);
std::string AtomToJson(uint8_t *pData)
std::string AtomToJson(const LV2_Atom *pAtom)
{
std::lock_guard lock(atomConverterMutex);
json_variant vAtom = atomConverter.ToJson(pAtom);
std::stringstream s;
json_writer writer(s);
LV2_Atom *pAtom = (LV2_Atom *)pData;
WriteAtom(writer, pAtom);
writer.write(vAtom);
return s.str();
}
@@ -325,7 +333,6 @@ private:
return;
isOpen = false;
StopReaderThread();
if (realtimeMonitorPortSubscriptions != nullptr)
{
@@ -341,9 +348,13 @@ private:
audioDriver->Close();
StopReaderThread();
pHost->GetHostWorkerThread()->Close();
// release any pdealboards owned by the process thread.
this->activePedalBoards.resize(0);
this->realtimeActivePedalBoard = nullptr;
this->activePedalboards.resize(0);
this->realtimeActivePedalboard = nullptr;
// clean up any realtime buffers that may have been lost in transit.
// TODO: These should be lists, really. There may be multiple items in flight..
@@ -365,6 +376,7 @@ private:
delete[] midiLv2Buffers[i];
}
midiLv2Buffers.resize(0);
}
void ZeroBuffer(float *buffer, size_t nframes)
@@ -442,7 +454,7 @@ private:
portSubscription.samplesToNextCallback += portSubscription.sampleRate;
if (!portSubscription.waitingForAck)
{
float value = realtimeActivePedalBoard->GetControlOutputValue(
float value = realtimeActivePedalboard->GetControlOutputValue(
portSubscription.instanceIndex,
portSubscription.portIndex);
if (value != portSubscription.lastValue)
@@ -459,7 +471,7 @@ private:
}
}
RealtimeParameterRequest *pParameterRequests = nullptr;
RealtimePatchPropertyRequest *pParameterRequests = nullptr;
bool reEntered = false;
void ProcessInputCommands()
@@ -486,12 +498,12 @@ private:
{
SetControlValueBody body;
realtimeReader.readComplete(&body);
this->realtimeActivePedalBoard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
break;
}
case RingBufferCommand::ParameterRequest:
{
RealtimeParameterRequest *pRequest = nullptr;
RealtimePatchPropertyRequest *pRequest = nullptr;
realtimeReader.readComplete(&pRequest);
// link to the list of parameter requests.
@@ -562,7 +574,7 @@ private:
{
SetBypassBody body;
realtimeReader.readComplete(&body);
this->realtimeActivePedalBoard->SetBypass(body.effectIndex, body.enabled);
this->realtimeActivePedalboard->SetBypass(body.effectIndex, body.enabled);
break;
}
case RingBufferCommand::ReplaceEffect:
@@ -572,8 +584,8 @@ private:
if (body.effect != nullptr)
{
auto oldValue = this->realtimeActivePedalBoard;
this->realtimeActivePedalBoard = body.effect;
auto oldValue = this->realtimeActivePedalboard;
this->realtimeActivePedalboard = body.effect;
realtimeWriter.EffectReplaced(oldValue);
@@ -612,7 +624,7 @@ private:
{
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
this->realtimeActivePedalBoard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
if (listenForMidiEvent)
{
if (event.size >= 3)
@@ -756,8 +768,8 @@ private:
bool processed = false;
Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard;
if (pedalBoard != nullptr)
Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard;
if (pedalboard != nullptr)
{
ProcessMidiInput();
float *inputBuffers[4];
@@ -789,15 +801,15 @@ private:
if (buffersValid)
{
pedalBoard->ResetAtomBuffers();
pedalBoard->ProcessParameterRequests(pParameterRequests);
pedalboard->ResetAtomBuffers();
pedalboard->ProcessParameterRequests(pParameterRequests);
processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
if (processed)
{
if (this->realtimeVuBuffers != nullptr)
{
pedalBoard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
vuSamplesRemaining -= nframes;
if (vuSamplesRemaining <= 0)
@@ -811,14 +823,10 @@ private:
processMonitorPortSubscriptions(nframes);
}
}
pedalBoard->GatherParameterRequests(pParameterRequests);
pedalboard->GatherPatchProperties(pParameterRequests);
}
}
// in = jack_port_get_buffer(input_port, nframes);
// out = jack_port_get_buffer(output_port, nframes);
// memcpy(out, in,
// sizeof(jack_default_audio_sample_t) * nframes);
if (!processed)
{
ZeroOutputBuffers(nframes);
@@ -841,7 +849,6 @@ private:
throw;
}
}
public:
AudioHostImpl(IHost *pHost)
: inputRingBuffer(RING_BUFFER_SIZE),
@@ -852,8 +859,11 @@ public:
hostWriter(&this->inputRingBuffer),
eventBufferUrids(pHost),
pHost(pHost),
uris(pHost)
uris(pHost),
atomConverter(pHost->GetMapFeature())
{
realtimeAtomBuffer.resize(32*1024);
lv2_atom_forge_init(&inputWriterForge,pHost->GetMapFeature().GetMap());
#if JACK_HOST
audioDriver = CreateJackDriver(this);
@@ -870,6 +880,7 @@ public:
delete audioDriver;
}
virtual JackConfiguration GetServerConfiguration()
{
JackConfiguration result;
@@ -893,6 +904,7 @@ public:
realtimeWriter.AudioStopped();
}
std::vector<uint8_t> atomBuffer;
std::vector<uint8_t> realtimeAtomBuffer;
bool terminateThread;
void ThreadProc()
@@ -917,6 +929,7 @@ public:
{
Lv2Log::debug("Service thread priority successfully boosted.");
}
SetThreadName("aout");
#else
xxx; // TODO!
#endif
@@ -924,44 +937,41 @@ public:
try
{
struct timespec ts;
// ever 30 seconds, timeout check for and log any overruns.
int pollRateS = 30;
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
{
Lv2Log::error("clock_gettime failed!");
return;
}
ts.tv_sec += pollRateS;
uint64_t lastUnderrunCount = this->underruns;
using clock = std::chrono::steady_clock;
using clock_time = std::chrono::steady_clock::time_point;
using clock_duration = clock_time::duration;
// check for overruns every 30 seconds.
clock_duration waitPeriod =
std::chrono::duration_cast<clock_duration>(std::chrono::seconds(30));
clock_time waitTime = std::chrono::steady_clock::now();
while (true)
{
// wait for an event.
// 0 -> ready. -1: timed out. -2: closing.
int result = hostReader.wait(ts);
if (result == -2)
auto result = hostReader.wait_until(sizeof(RingBufferCommand), waitTime);
if (result == RingBufferStatus::Closed)
{
return;
}
else if (result == -1)
else if (result == RingBufferStatus::TimedOut)
{
// timeout.
ts.tv_sec += pollRateS;
uint64_t underruns = this->underruns;
if (underruns != lastUnderrunCount)
{
if (underrunMessagesGiven < 60) // limit how much log file clutter we generate.
{
Lv2Log::info("Jack - Underrun count: %lu", (unsigned long)underruns);
Lv2Log::info("Audio underrun count: %lu", (unsigned long)underruns);
lastUnderrunCount = underruns;
++underrunMessagesGiven;
}
}
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += pollRateS;
waitTime += waitPeriod;
}
else
{
@@ -996,32 +1006,42 @@ public:
}
else if (command == RingBufferCommand::ParameterRequestComplete)
{
RealtimeParameterRequest *pRequest = nullptr;
RealtimePatchPropertyRequest *pRequest = nullptr;
hostReader.read(&pRequest);
std::shared_ptr<Lv2PedalBoard> currentpedalBoard;
std::shared_ptr<Lv2Pedalboard> currentpedalboard;
{
std::lock_guard guard(mutex);
currentPedalBoard = this->currentPedalBoard;
currentPedalboard = this->currentPedalboard;
}
while (pRequest != nullptr)
{
auto pNext = pRequest->pNext;
if (pRequest->errorMessage == nullptr && pRequest->responseLength != 0)
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
{
IEffect *pEffect = currentPedalBoard->GetEffect(pRequest->instanceId);
if (pEffect == nullptr)
if (pRequest->errorMessage == nullptr)
{
pRequest->errorMessage = "Effect no longer available.";
}
else
{
pRequest->jsonResponse = AtomToJson(pRequest->response);
if (pRequest->GetSize() != 0) {
IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId);
if (pEffect == nullptr)
{
pRequest->errorMessage = "Effect no longer available.";
}
else
{
pRequest->jsonResponse = AtomToJson((LV2_Atom*)pRequest->GetBuffer());
}
} else {
pRequest->errorMessage = "Plugin did not respond.";
}
}
}
pRequest->onJackRequestComplete(pRequest);
if (pRequest->onPatchRequestComplete)
{
pRequest->onPatchRequestComplete(pRequest);
}
pRequest = pNext;
}
}
@@ -1047,6 +1067,12 @@ public:
}
this->hostWriter.AckVuUpdate(); // please sir, can I have some more?
}
else if (command == RingBufferCommand::Lv2StateChanged)
{
uint64_t instanceId;
hostReader.read(&instanceId);
this->pNotifyCallbacks->OnNotifyLv2StateChanged(instanceId);
}
else if (command == RingBufferCommand::AtomOutput)
{
uint64_t instanceId;
@@ -1059,12 +1085,35 @@ public:
}
hostReader.read(extraBytes, &(atomBuffer[0]));
IEffect *pEffect = currentPedalBoard->GetEffect(instanceId);
IEffect *pEffect = currentPedalboard->GetEffect(instanceId);
if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput)
{
std::string atomType = GetAtomObjectType(&atomBuffer[0]);
auto json = AtomToJson(&(atomBuffer[0]));
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId, atomType, json);
LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0];
if (atom->type == uris.atom_Object)
{
LV2_Atom_Object *obj = (LV2_Atom_Object*)atom;
if (obj->body.otype == uris.patch_Set)
{
// Get the property and value of the set message
const LV2_Atom *property = nullptr;
const LV2_Atom *value = nullptr;
lv2_atom_object_get(
obj,
uris.patch_property, &property,
uris.patch_value, &value,
0);
if (property != nullptr && value != nullptr && property->type == uris.atom_URID)
{
LV2_URID propertyUrid = ((LV2_Atom_URID*)property)->body;
if (pNotifyCallbacks->WantsAtomOutput(instanceId,propertyUrid))
{
auto json = AtomToJson(value);
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,propertyUrid,json);
}
}
}
}
}
}
else if (command == RingBufferCommand::FreeVuSubscriptions)
@@ -1083,7 +1132,7 @@ public:
{
EffectReplacedBody body;
hostReader.read(&body);
OnActivePedalBoardReleased(body.oldEffect);
OnActivePedalboardReleased(body.oldEffect);
}
else if (command == RingBufferCommand::AudioStopped)
{
@@ -1218,45 +1267,45 @@ public:
{
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
}
void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard)
void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
{
if (pPedalBoard)
if (pPedalboard)
{
pPedalBoard->Deactivate();
pPedalboard->Deactivate();
std::lock_guard guard(mutex);
for (auto it = activePedalBoards.begin(); it != activePedalBoards.end(); ++it)
for (auto it = activePedalboards.begin(); it != activePedalboards.end(); ++it)
{
if ((*it).get() == pPedalBoard)
if ((*it).get() == pPedalboard)
{
// erase it, relinquishing shared_ptr ownership, usually deleting the object.
activePedalBoards.erase(it);
activePedalboards.erase(it);
return;
}
}
}
}
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard)
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard)
{
std::lock_guard guard(mutex);
this->currentPedalBoard = pedalBoard;
this->currentPedalboard = pedalboard;
if (active)
{
pedalBoard->Activate();
this->activePedalBoards.push_back(pedalBoard);
hostWriter.ReplaceEffect(pedalBoard.get());
pedalboard->Activate();
this->activePedalboards.push_back(pedalboard);
hostWriter.ReplaceEffect(pedalboard.get());
}
}
virtual void SetBypass(uint64_t instanceId, bool enabled)
{
std::lock_guard guard(mutex);
if (active && this->currentPedalBoard)
if (active && this->currentPedalboard)
{
// use indices not instance ids, so we can just do a straight array index on the audio thread.
auto index = currentPedalBoard->GetIndexOfInstanceId(instanceId);
auto index = currentPedalboard->GetIndexOfInstanceId(instanceId);
if (index >= 0)
{
hostWriter.SetBypass((uint32_t)index, enabled);
@@ -1267,15 +1316,15 @@ public:
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values)
{
std::lock_guard guard(mutex);
if (active && this->currentPedalBoard)
if (active && this->currentPedalboard)
{
auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId);
auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId);
if (effectIndex != -1)
{
for (size_t i = 0; i < values.size(); ++i)
{
const ControlValue &value = values[i];
int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, value.key());
int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, value.key());
if (controlIndex != -1 && effectIndex != -1)
{
hostWriter.SetControlValue(effectIndex, controlIndex, value.value());
@@ -1288,11 +1337,11 @@ public:
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
{
std::lock_guard guard(mutex);
if (active && this->currentPedalBoard)
if (active && this->currentPedalboard)
{
// use indices not instance ids, so we can just do a straight array index on the audio thread.
int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, symbol);
auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId);
int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, symbol);
auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId);
if (controlIndex != -1 && effectIndex != -1)
{
@@ -1301,11 +1350,14 @@ public:
}
}
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{
std::lock_guard guard(mutex);
if (active && this->currentPedalBoard)
if (active && this->currentPedalboard)
{
if (instanceIds.size() == 0)
@@ -1319,13 +1371,13 @@ public:
for (size_t i = 0; i < instanceIds.size(); ++i)
{
int64_t instanceId = instanceIds[i];
auto effect = this->currentPedalBoard->GetEffect(instanceId);
auto effect = this->currentPedalboard->GetEffect(instanceId);
if (!effect)
{
throw PiPedalStateException("Effect not found.");
}
int index = this->currentPedalBoard->GetIndexOfInstanceId(instanceIds[i]);
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index);
VuUpdate v;
v.instanceId_ = instanceId;
@@ -1346,8 +1398,8 @@ public:
{
RealtimeMonitorPortSubscription result;
result.subscriptionHandle = subscription.subscriptionHandle;
result.instanceIndex = this->currentPedalBoard->GetIndexOfInstanceId(subscription.instanceid);
IEffect *pEffect = this->currentPedalBoard->GetEffect(subscription.instanceid);
result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid);
IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid);
result.portIndex = pEffect->GetControlIndex(subscription.key);
result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval);
@@ -1360,7 +1412,7 @@ public:
{
if (!active)
return;
if (this->currentPedalBoard == nullptr)
if (this->currentPedalboard == nullptr)
return;
if (subscriptions.size() == 0)
{
@@ -1372,7 +1424,7 @@ public:
for (size_t i = 0; i < subscriptions.size(); ++i)
{
if (this->currentPedalBoard->GetEffect(subscriptions[i].instanceid) != nullptr)
if (this->currentPedalboard->GetEffect(subscriptions[i].instanceid) != nullptr)
{
pSubscriptions->subscriptions.push_back(
MakeRealtimeSubscription(subscriptions[i]));
@@ -1383,6 +1435,10 @@ public:
}
private:
virtual void UpdatePluginStates(Pedalboard& pedalboard);
void UpdatePluginState(PedalboardItem &pedalboardItem);
class RestartThread
{
AudioHostImpl *this_;
@@ -1478,12 +1534,12 @@ public:
}
}
virtual void getRealtimeParameter(RealtimeParameterRequest *pParameterRequest)
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest)
{
if (!active)
{
pParameterRequest->errorMessage = "Not active.";
pParameterRequest->onJackRequestComplete(pParameterRequest);
pParameterRequest->onPatchRequestComplete(pParameterRequest);
return;
}
this->hostWriter.ParameterRequest(pParameterRequest);
@@ -1560,145 +1616,6 @@ static std::string UriToFieldName(const std::string &uri)
return uri.substr(pos + 1);
}
void AudioHostImpl::WriteAtom(json_writer &writer, LV2_Atom *pAtom)
{
if (pAtom->type == uris.atom_Blank)
{
writer.write_raw("null");
}
else if (pAtom->type == uris.atom_float)
{
writer.write(
((LV2_Atom_Float *)pAtom)->body);
}
else if (pAtom->type == uris.atom_Int)
{
writer.write(
((LV2_Atom_Int *)pAtom)->body);
}
else if (pAtom->type == uris.atom_Long)
{
writer.write(
((LV2_Atom_Long *)pAtom)->body);
}
else if (pAtom->type == uris.atom_Double)
{
writer.write(
((LV2_Atom_Double *)pAtom)->body);
}
else if (pAtom->type == uris.atom_Bool)
{
writer.write(
((LV2_Atom_Bool *)pAtom)->body);
}
else if (pAtom->type == uris.atom_String)
{
const char *p = (((const char *)pAtom) + sizeof(LV2_Atom_String));
writer.write(
p);
}
else if (pAtom->type == uris.atom_Vector)
{
LV2_Atom_Vector *pVector = (LV2_Atom_Vector *)pAtom;
writer.start_array();
{
size_t n = (pAtom->size - sizeof(pVector->body)) / pVector->body.child_size;
char *pItems = ((char *)pAtom) + sizeof(LV2_Atom_Vector);
if (pVector->body.child_type == uris.atom_float)
{
float *p = (float *)pItems;
for (size_t i = 0; i < n; ++i)
{
if (i != 0)
writer.write_raw(",");
writer.write(*p++);
}
}
else if (pVector->body.child_type == uris.atom_Int)
{
int32_t *p = (int32_t *)pItems;
for (size_t i = 0; i < n; ++i)
{
if (i != 0)
writer.write_raw(",");
writer.write(*p++);
}
}
else if (pVector->body.child_type == uris.atom_Long)
{
int64_t *p = (int64_t *)pItems;
for (size_t i = 0; i < n; ++i)
{
if (i != 0)
writer.write_raw(",");
writer.write(*p++);
}
}
else if (pVector->body.child_type == uris.atom_Double)
{
double *p = (double *)pItems;
for (size_t i = 0; i < n; ++i)
{
if (i != 0)
writer.write_raw(",");
writer.write(*p++);
}
}
else if (pVector->body.child_type == uris.atom_Bool)
{
bool *p = (bool *)pItems;
for (size_t i = 0; i < n; ++i)
{
if (i != 0)
writer.write_raw(",");
writer.write(*p++);
}
}
}
writer.end_array();
}
else if (pAtom->type == uris.atom_Object)
{
writer.start_object();
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom;
bool firstMember = true;
if (obj->body.id != 0)
{
std::string id = pHost->Lv2UriudToString(obj->body.id);
writer.write_member("id", id.c_str());
firstMember = false;
}
if (obj->body.otype != 0)
{
std::string type = pHost->Lv2UriudToString(obj->body.otype);
writer.write_member("lv2Type", type.c_str());
if (!firstMember)
{
writer.write_raw(",");
}
firstMember = false;
}
LV2_ATOM_OBJECT_FOREACH(obj, prop)
{
if (!firstMember)
{
writer.write_raw(",");
}
firstMember = false;
std::string key = pHost->Lv2UriudToString(prop->key);
key = UriToFieldName(key);
writer.write(key);
writer.write_raw(": ");
LV2_Atom *value = &(prop->value);
WriteAtom(writer, value);
}
writer.end_object();
}
}
void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
{
for (auto i = bindings.begin(); i != bindings.end(); ++i)
@@ -1719,6 +1636,30 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost)
return new AudioHostImpl(pHost);
}
void AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard)
{
auto pedalboardItems = pedalboard.GetAllPlugins();
for (PedalboardItem* item : pedalboardItems)
{
if (!item->isSplit())
{
UpdatePluginState(*item);
}
}
}
void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
{
IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
if (effect != nullptr)
{
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
}
}
JSON_MAP_BEGIN(JackHostStatus)
JSON_MAP_REFERENCE(JackHostStatus, active)
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
+67 -15
View File
@@ -21,13 +21,15 @@
#include "JackConfiguration.hpp"
#include "Lv2PedalBoard.hpp"
#include "Lv2Pedalboard.hpp"
#include "VuUpdate.hpp"
#include "json.hpp"
#include "AudioHost.hpp"
#include "JackServerSettings.hpp"
#include <functional>
#include "PiPedalAlsa.hpp"
#include "Promise.hpp"
#include "json_variant.hpp"
namespace pipedal {
@@ -45,39 +47,86 @@ public:
int64_t subscriptionHandle;
float value;
};
class RealtimeParameterRequest {
class RealtimePatchPropertyRequest {
public:
int64_t clientId;
int64_t instanceId;
LV2_URID uridUri;
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete;
enum class RequestType {
PatchGet,
PatchSet
};
RequestType requestType;
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestComplete;
std::function<void(const std::string &jsonResjult)> onSuccess;
std::function<void(const std::string &error)> onError;
const char*errorMessage = nullptr;
int responseLength = 0;
uint8_t response[2048];
std::string jsonResponse;
RealtimeParameterRequest *pNext = nullptr;
RealtimePatchPropertyRequest *pNext = nullptr;
void SetSize(size_t size)
{
responseLength = size;
if (responseLength > sizeof(atomBuffer))
{
longAtomBuffer.resize(size);
}
}
size_t GetSize() const { return responseLength; }
uint8_t *GetBuffer() {
if (responseLength > sizeof(atomBuffer))
{
return &(longAtomBuffer[0]);
}
return atomBuffer;
}
private:
int responseLength = 0;
uint8_t atomBuffer[2048];
std::vector<uint8_t> longAtomBuffer;
public:
RealtimeParameterRequest(
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete_,
RealtimePatchPropertyRequest(
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
int64_t clientId_,
int64_t instanceId_,
LV2_URID uridUri_,
std::function<void(const std::string &jsonResjult)> onSuccess_,
std::function<void(const std::string &error)> onError_)
: onJackRequestComplete(onJackRequestComplete_),
std::function<void(const std::string &error)> onError_
)
: onPatchRequestComplete(onPatchRequestcomplete_),
clientId(clientId_),
instanceId(instanceId_),
uridUri(uridUri_),
onSuccess(onSuccess_),
onError(onError_)
{
requestType = RequestType::PatchGet;
}
RealtimePatchPropertyRequest(
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
int64_t clientId_,
int64_t instanceId_,
LV2_URID uridUri_,
LV2_Atom*atomValue,
std::function<void(const std::string &jsonResjult)> onSuccess_,
std::function<void(const std::string &error)> onError_
)
: onPatchRequestComplete(onPatchRequestcomplete_),
clientId(clientId_),
instanceId(instanceId_),
uridUri(uridUri_),
onSuccess(onSuccess_),
onError(onError_)
{
requestType = RequestType::PatchSet;
size_t size = atomValue->size+ sizeof(LV2_Atom);
SetSize(size);
memcpy(GetBuffer(),atomValue,size);
}
};
@@ -95,11 +144,13 @@ public:
class IAudioHostCallbacks {
public:
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0;
virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
@@ -143,6 +194,8 @@ public:
virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(bool listen) = 0;
virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0;
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
virtual void Close() = 0;
@@ -151,7 +204,7 @@ public:
virtual JackConfiguration GetServerConfiguration() = 0;
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard) = 0;
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;
@@ -164,12 +217,11 @@ public:
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest*pParameterRequest) = 0;
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual JackHostStatus getJackStatus() = 0;
};
+1 -1
View File
@@ -23,7 +23,7 @@
*/
#include "AutoLilvNode.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
+30 -17
View File
@@ -32,24 +32,23 @@ namespace pipedal
class AutoLilvNode
{
// const LilvNode* returns must not be freed, by convention.
private:
LilvNode *node = nullptr;
AutoLilvNode(const LilvNode *node) = delete;
public:
AutoLilvNode()
{
}
AutoLilvNode(const LilvNode *node)
: node(const_cast<LilvNode*>(node))
{
isConst = true;
}
AutoLilvNode(LilvNode *node)
: node(node)
{
isConst = false;
}
~AutoLilvNode() { Free(); }
operator const LilvNode *()
{
return this->node;
@@ -58,18 +57,30 @@ namespace pipedal
{
return this->node != nullptr;
}
LilvNode*&Get() { return node; }
const LilvNode *Get() { return node; }
AutoLilvNode&operator=(LilvNode*node) {
Free();
this->node = node;
return *this;
}
AutoLilvNode&operator=(AutoLilvNode&&other) {
std::swap(this->node,other.node);
AutoLilvNode &operator=(LilvNode *node)
{
Free();
this->node = node;
return *this;
}
operator LilvNode**() {
Free();
this->isConst = false;
return &node;
}
operator const LilvNode**() {
Free();
this->isConst = true;
return (const LilvNode**)&node;
}
AutoLilvNode &operator=(AutoLilvNode &&other)
{
std::swap(this->node, other.node);
return *this;
}
float AsFloat(float defaultValue = 0);
int AsInt(int defaultValue = 0);
@@ -79,11 +90,13 @@ namespace pipedal
void Free()
{
if (node != nullptr)
if (node != nullptr && !isConst)
lilv_node_free(node);
node = nullptr;
}
private:
LilvNode *node = nullptr;
bool isConst = true;
};
};
+5 -5
View File
@@ -20,7 +20,7 @@
#pragma once
#include "json.hpp"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include "PiPedalException.hpp"
namespace pipedal {
@@ -75,7 +75,7 @@ public:
class BankFileEntry {
int64_t instanceId_;
PedalBoard preset_;
Pedalboard preset_;
public:
GETTER_SETTER(instanceId);
GETTER_SETTER_REF(preset);
@@ -122,7 +122,7 @@ public:
}
this->nextInstanceId_ = t;
}
int64_t addPreset(const PedalBoard&preset, int64_t afterItem = -1)
int64_t addPreset(const Pedalboard&preset, int64_t afterItem = -1)
{
if (hasName(preset.name()))
{
@@ -213,8 +213,8 @@ public:
} else {
// zero length? We can never have a zero-length bank.
// Add a default preset and make it the selected preset.
PedalBoard pedalBoard = PedalBoard::MakeDefault();
this->addPreset(pedalBoard);
Pedalboard pedalboard = Pedalboard::MakeDefault();
this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId();
}
if (instanceId == this->selectedPreset_)
+144
View File
@@ -0,0 +1,144 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* 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.
*/
/*
Converted to take uint8_t* input/output instead of std::string.
*/
#include <string>
#include <cstddef>
#include <vector>
#include <stdexcept>
namespace macaron
{
class Base64
{
public:
static std::string Encode(const std::vector<uint8_t> &data)
{
return Encode(data.size(),&(data[0]));
}
static std::string Encode(size_t size, const uint8_t *data)
{
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
size_t in_len = size;
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char *>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3)
{
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len)
{
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1))
{
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else
{
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::vector<uint8_t> Decode(const std::string &input)
{
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64};
size_t in_len = input.size();
if (in_len % 4 != 0)
throw std::invalid_argument("Input data size is not a multiple of 4");
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=')
out_len--;
if (input[in_len - 2] == '=')
out_len--;
std::vector<uint8_t> out(out_len);
for (size_t i = 0, j = 0; i < in_len;)
{
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len)
out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len)
out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len)
out[j++] = (triple >> 0 * 8) & 0xFF;
}
return out;
}
};
}
#endif /* _MACARON_BASE64_H_ */
+56 -7
View File
@@ -14,6 +14,10 @@ include(FindPkgConfig)
# Disabled, pending approval of Steinberg VST3 License.
# Do not enable unless you have non-GPL3 access to the Steinberg
# SDK.
#
# Plus, there don't seem to be any VST3 plugins for linux, as it
# turns out. :-/ Status: not completely implemented due to lack
# of test targets. Deprecated, and non-functional.
#################################################################
set (ENABLE_VST3 0)
@@ -137,10 +141,20 @@ else()
endif()
set (PIPEDAL_SOURCES
AutoLilvNode.hpp
AutoLilvNode.cpp
util.hpp util.cpp
PiPedalUI.hpp
PiPedalUI.cpp
MapPathFeature.hpp
MapPathFeature.cpp
IHost.hpp
StateInterface.hpp
StateInterface.cpp
AtomConverter.hpp
AtomConverter.cpp
AtomBuffer.hpp
AutoLilvNode.hpp
AutoLilvNode.cpp
RtInversionGuard.hpp
CpuUse.hpp CpuUse.cpp
P2pConfigFiles.hpp
@@ -162,13 +176,13 @@ set (PIPEDAL_SOURCES
RequestHandler.hpp json.cpp json.hpp
json_variant.hpp json_variant.cpp
Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp
Scratch.cpp PluginHost.hpp PluginHost.cpp
PluginType.hpp PluginType.cpp
Lv2Log.hpp Lv2Log.cpp
PiPedalSocket.hpp PiPedalSocket.cpp
PiPedalVersion.hpp PiPedalVersion.cpp
PiPedalModel.hpp PiPedalModel.cpp
PedalBoard.hpp PedalBoard.cpp
Pedalboard.hpp Pedalboard.cpp
Presets.hpp Presets.cpp
Storage.hpp Storage.cpp
Banks.hpp Banks.cpp
@@ -176,7 +190,7 @@ set (PIPEDAL_SOURCES
JackConfiguration.hpp JackConfiguration.cpp
defer.hpp
Lv2Effect.cpp Lv2Effect.hpp
Lv2PedalBoard.cpp Lv2PedalBoard.hpp
Lv2Pedalboard.cpp Lv2Pedalboard.hpp
BufferPool.hpp
SplitEffect.hpp SplitEffect.cpp
RingBufferReader.hpp
@@ -219,7 +233,7 @@ include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
set (PIPEDAL_INCLUDES
${LV2DEV_INCLUDE_DIRS}
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS}
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS}
${VST3_INCLUDES}
.
)
@@ -261,7 +275,10 @@ target_link_libraries(pipedald PRIVATE
#################################
add_executable(pipedaltest testMain.cpp
jsonTest.cpp
AlsaDriverTest.cpp
json_variant.cpp
json_variant.hpp
AlsaDriverTest.cpp
AvahiServiceTest.cpp
WifiChannelsTest.cpp
PiPedalAlsaTest.cpp
@@ -277,6 +294,36 @@ add_executable(pipedaltest testMain.cpp
MemDebug.hpp
)
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
)
add_executable(jsonTest
testMain.cpp
jsonTest.cpp
json.hpp
json.cpp
json_variant.cpp
json_variant.hpp
MapFeature.cpp
MapFeature.hpp
HtmlHelper.cpp
HtmlHelper.hpp
AtomConverter.hpp
AtomConverter.cpp
AtomConverterTest.cpp
AtomBuffer.hpp
Promise.hpp
PromiseTest.cpp
)
target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES}
)
target_link_libraries(jsonTest PRIVATE pthread)
add_test(NAME jsonTest COMMAND jsonTest)
include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
if(${USE_PCH})
@@ -539,6 +586,8 @@ target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs
)
add_executable(pipedal_latency_test
util.hpp
util.cpp
PrettyPrinter.hpp
CommandLineParser.hpp
PiLatencyMain.cpp
+1 -1
View File
@@ -191,7 +191,7 @@ void StartService(bool excludeShutdownService = false)
}
#if INSTALL_JACK_SERVICE
JackServerSettings serverSettings;
serverSettings.ReadJackConfiguration();
serverSettings.ReadJackDaemonConfiguration();
if (serverSettings.IsValid())
{
if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS)
+5 -5
View File
@@ -20,10 +20,13 @@
#pragma once
#include <lv2/urid.lv2/urid.h>
#include <lv2/atom.lv2/atom.h>
namespace pipedal {
class RealtimeParameterRequest;
class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter;
class Lv2PluginState;
class IEffect {
public:
@@ -35,15 +38,11 @@ namespace pipedal {
virtual void SetBypass(bool enable) = 0;
virtual float GetOutputControlValue(int controlIndex) const = 0;
virtual int GetNumberOfInputAudioPorts() const = 0;
virtual int GetNumberOfOutputAudioPorts() const = 0;
virtual float *GetAudioInputBuffer(int index) const = 0;
virtual float *GetAudioOutputBuffer(int index) const = 0;
virtual void ResetAtomBuffers() = 0;
virtual void RequestParameter(LV2_URID uridUri) = 0;
virtual void GatherParameter(RealtimeParameterRequest*pRequest) = 0;
//virtual std::string AtomToJson(uint8_t*pAtom) = 0;
//virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
@@ -59,5 +58,6 @@ namespace pipedal {
virtual void Deactivate() = 0;
virtual bool IsVst3() const = 0;
virtual bool GetLv2State(Lv2PluginState*state) = 0;
};
} //namespace
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <lilv/lilv.h>
namespace pipedal {
class MapFeature;
class PedalboardItem;
class Lv2PluginInfo;
class IEffect;
class HostWorkerThread;
class IHost
{
public:
virtual LilvWorld *getWorld() = 0;
virtual LV2_URID_Map *GetLv2UridMap() = 0;
virtual MapFeature&GetMapFeature() = 0;
virtual LV2_URID GetLv2Urid(const char *uri) = 0;
virtual std::string Lv2UridToString(LV2_URID urid) = 0;
virtual LV2_Feature *const *GetLv2Features() const = 0;
virtual double GetSampleRate() const = 0;
virtual void SetMaxAudioBufferSize(size_t size) = 0;
virtual size_t GetMaxAudioBufferSize() const = 0;
virtual size_t GetAtomBufferSize() const = 0;
virtual bool HasMidiInputChannel() const = 0;
virtual int GetNumberOfInputAudioChannels() const = 0;
virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread() = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
};
}
+1
View File
@@ -297,6 +297,7 @@ JSON_MAP_END()
JSON_MAP_BEGIN(JackConfiguration)
JSON_MAP_REFERENCE(JackConfiguration,isValid)
JSON_MAP_REFERENCE(JackConfiguration,isOnboarding)
JSON_MAP_REFERENCE(JackConfiguration,isRestarting)
JSON_MAP_REFERENCE(JackConfiguration,errorStatus)
JSON_MAP_REFERENCE(JackConfiguration,sampleRate)
+10 -6
View File
@@ -37,6 +37,7 @@ namespace pipedal
std::string errorStatus_;
bool isRestarting_ = false;
bool isOnboarding_ = true;
uint32_t sampleRate_ = 48000;
size_t blockLength_ = 1024;
@@ -54,13 +55,16 @@ namespace pipedal
void JackInitialize(); // from jack server instance.
~JackConfiguration();
bool isValid() const { return isValid_;}
bool isOnboarding() const { return isOnboarding_;}
void isOnboarding(bool value) { isOnboarding_ = value; }
bool isRestarting() const { return isRestarting_; }
void SetIsRestarting(bool value) { isRestarting_ = value; }
uint32_t GetSampleRate() const { return sampleRate_; }
size_t GetBlockLength() const { return blockLength_; }
size_t GetMidiBufferSize() const { return midiBufferSize_;}
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; }
void isRestarting(bool value) { isRestarting_ = value; }
uint32_t sampleRate() const { return sampleRate_; }
size_t blockLength() const { return blockLength_; }
size_t midiBufferSize() const { return midiBufferSize_;}
double maxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
void setErrorStatus(const std::string&message) { this->errorStatus_ = message; }
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
+6 -2
View File
@@ -108,8 +108,11 @@ static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::
}
void JackServerSettings::ReadJackConfiguration()
void JackServerSettings::ReadJackDaemonConfiguration()
{
#if !JACK_HOST
return;
#endif
this->valid_ = false;
std::string lastLine;
@@ -155,7 +158,7 @@ void JackServerSettings::ReadJackConfiguration()
}
}
void JackServerSettings::Write()
void JackServerSettings::WriteDaemonConfig()
{
#if JACK_HOST
this->valid_ = false;
@@ -240,6 +243,7 @@ void JackServerSettings::Write()
JSON_MAP_BEGIN(JackServerSettings)
JSON_MAP_REFERENCE(JackServerSettings, valid)
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
+18 -11
View File
@@ -28,6 +28,7 @@ namespace pipedal
class JackServerSettings
{
bool valid_ = false;
bool isOnboarding_ = true;
bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false;
std::string alsaDevice_;
@@ -44,7 +45,8 @@ namespace pipedal
alsaDevice_(alsaInputDevice),
sampleRate_(sampleRate),
bufferSize_(bufferSize),
numberOfBuffers_(numberOfBuffers)
numberOfBuffers_(numberOfBuffers),
isOnboarding_(false)
{
}
@@ -53,23 +55,28 @@ namespace pipedal
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string &GetAlsaInputDevice() const { return alsaDevice_; }
void ReadJackConfiguration();
void ReadJackDaemonConfiguration();
bool IsValid() const { return valid_; }
JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
{
this->valid_ = true;
this->rebootRequired_ = true;
this->sampleRate_ = sampleRate;
this->bufferSize_ = bufferSize;
this->numberOfBuffers_ = numberOfBuffers;
}
void Write(); // requires root perms.
// JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
// {
// this->valid_ = true;
// this->rebootRequired_ = true;
// this->sampleRate_ = sampleRate;
// this->bufferSize_ = bufferSize;
// this->numberOfBuffers_ = numberOfBuffers;
// }
void WriteDaemonConfig(); // requires root perms.
void SetRebootRequired(bool value)
{
rebootRequired_ = value;
}
void SetIsOnboarding(bool value)
{
isOnboarding_ = value;
}
bool Equals(const JackServerSettings &other)
{
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
+111 -57
View File
@@ -32,6 +32,7 @@
#include "lv2/log.lv2/logger.h"
#include "lv2/uri-map.lv2/uri-map.h"
#include "lv2/atom.lv2/forge.h"
#include "lv2/state.lv2/state.h"
#include "lv2/worker.lv2/worker.h"
#include "lv2/patch.lv2/patch.h"
#include "lv2/parameters.lv2/parameters.h"
@@ -48,14 +49,14 @@ const float BYPASS_TIME_S = 0.1f;
Lv2Effect::Lv2Effect(
IHost *pHost_,
const std::shared_ptr<Lv2PluginInfo> &info_,
const PedalBoardItem &pedalBoardItem)
: pHost(pHost_), pInstance(nullptr), info(info_), uris(pHost)
const PedalboardItem &pedalboardItem)
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost)
{
auto pWorld = pHost_->getWorld();
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
this->bypass = pedalBoardItem.isEnabled();
this->bypass = pedalboardItem.isEnabled();
// initialize the atom forge used on the realtime thread.
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
@@ -64,7 +65,7 @@ Lv2Effect::Lv2Effect(
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
auto uriNode = lilv_new_uri(pWorld, pedalBoardItem.uri().c_str());
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
lilv_node_free(uriNode);
@@ -76,7 +77,7 @@ Lv2Effect::Lv2Effect(
}
this->work_schedule_feature = nullptr;
if (info_->hasExtension(LV2_WORKER__interface))
if (true) //info_->hasExtension(LV2_WORKER__interface))
{
// insane implementation. :-(
LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule));
@@ -99,29 +100,36 @@ Lv2Effect::Lv2Effect(
} catch (const std::exception &e)
{
this->pInstance = nullptr;
throw PiPedalException(SS("Plugin threw an exception: " << e.what()));
throw PiPedalException(SS("Plugin threw an exception: " << e.what() << " '" << info_->name() << "'"));
}
this->pInstance = pInstance;
if (this->pInstance == nullptr)
{
throw PiPedalException("Failed to create plugin.");
throw PiPedalException(SS("Failed to create plugin \'" << info_->name() << "\'."));
}
if (info_->hasExtension(LV2_WORKER__interface))
const LV2_Worker_Interface *worker_interface =
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
LV2_WORKER__interface);
if (worker_interface) {
this->worker = std::make_unique<Worker>(pHost->GetHostWorkerThread(), pInstance, worker_interface);
}
const LV2_State_Interface*state_interface =
(const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance,
LV2_STATE__interface);
if (state_interface)
{
const LV2_Worker_Interface *worker_interface =
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
LV2_WORKER__interface);
this->worker = std::make_unique<Worker>(pInstance, worker_interface);
this->stateInterface = std::make_unique<StateInterface>(pHost,pInstance,state_interface);
}
this->instanceId = pedalBoardItem.instanceId();
this->instanceId = pedalboardItem.instanceId();
this->controlValues.resize(info->ports().size());
// Copy default pedalboard settings.
for (auto i = pedalBoardItem.controlValues().begin(); i != pedalBoardItem.controlValues().end(); ++i)
for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i)
{
auto &v = (*i);
int index = GetControlIndex(v.key());
@@ -132,6 +140,16 @@ Lv2Effect::Lv2Effect(
}
PreparePortIndices();
ConnectControlPorts();
RestoreState(pedalboardItem);
}
void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem)
{
// Restore state if present.
if (this->stateInterface)
{
this->stateInterface->Restore(pedalboardItem.lv2State());
}
}
void Lv2Effect::ConnectControlPorts()
@@ -262,6 +280,7 @@ Lv2Effect::~Lv2Effect()
{
if (worker)
{
worker->Close();
worker = nullptr; // delete the worker first!
}
if (pInstance)
@@ -281,8 +300,11 @@ void Lv2Effect::Activate()
this->AssignUnconnectedPorts();
lilv_instance_activate(pInstance);
this->BypassTo(this->bypass ? 1.0f : 0.0f);
}
void Lv2Effect::AssignUnconnectedPorts()
{
for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i)
@@ -332,6 +354,10 @@ void Lv2Effect::AssignUnconnectedPorts()
}
void Lv2Effect::Deactivate()
{
if (worker)
{
worker->Close();
}
lilv_instance_deactivate(pInstance);
}
@@ -441,7 +467,7 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe
}
}
RelayOutputMessages(this->instanceId,realtimeRingBufferWriter);
RelayPatchSetMessages(this->instanceId,realtimeRingBufferWriter);
}
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
@@ -463,13 +489,13 @@ void Lv2Effect::ResetInputAtomBuffer(char *data)
{
BufferHeader *header = (BufferHeader *)data;
header->size = sizeof(LV2_Atom_Sequence_Body);
header->type = uris.atom_Sequence;
header->type = urids.atom__Sequence;
}
void Lv2Effect::ResetOutputAtomBuffer(char *data)
{
BufferHeader *header = (BufferHeader *)data;
header->size = pHost->GetAtomBufferSize() - 8;
header->type = uris.atom_Chunk;
header->type = urids.atom__Chunk;
}
void Lv2Effect::BypassTo(float targetValue)
@@ -509,24 +535,41 @@ void Lv2Effect::ResetAtomBuffers()
// Start a sequence in the notify input port.
lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, uris.unitsFrame);
lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, urids.units__frame);
}
}
void Lv2Effect::RequestParameter(LV2_URID uridUri)
void Lv2Effect::RequestPatchProperty(LV2_URID uridUri)
{
lv2_atom_forge_frame_time(&inputForgeRt, 0);
LV2_Atom_Forge_Frame objectFrame;
LV2_Atom_Forge_Ref set =
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, uris.patch_Get);
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Get);
lv2_atom_forge_key(&inputForgeRt, uris.patch_property);
lv2_atom_forge_key(&inputForgeRt, urids.patch__property);
lv2_atom_forge_urid(&inputForgeRt, uridUri);
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
}
void Lv2Effect::SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value)
{
lv2_atom_forge_frame_time(&inputForgeRt, 0);
void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter)
LV2_Atom_Forge_Frame objectFrame;
LV2_Atom_Forge_Ref set =
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Set);
{
lv2_atom_forge_key(&inputForgeRt, urids.patch__property);
lv2_atom_forge_urid(&inputForgeRt, uridUri);
lv2_atom_forge_key(&inputForgeRt, urids.patch__value);
lv2_atom_forge_write(&inputForgeRt,value,size);
}
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
}
void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter)
{
LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
if (controlOutput == nullptr)
@@ -534,6 +577,7 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter
return;
}
bool stateChanged = false;
LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev)
{
@@ -542,63 +586,73 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
{
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
if (obj->body.otype != uris.patch_Set) // patch_Set is handled elsewhere.
if (obj->body.otype == urids.state__StateChanged)
{
stateChanged = true;
} else if (obj->body.otype == urids.patch__Set) // patch_Set is handled elsewhere.
{
realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj);
}
}
if (stateChanged)
{
realtimeRingBufferWriter->Lv2StateChanged(instanceId);
}
}
}
void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest)
void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
{
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
{
// frame_offset = ev->time.frames; // not really interested.
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
if (controlInput == nullptr)
{
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
if (obj->body.otype == uris.patch_Set)
return;
}
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
{
// frame_offset = ev->time.frames; // not really interested.
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
{
// Get the property and value of the set message
const LV2_Atom *property = NULL;
const LV2_Atom *value = NULL;
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
if (obj->body.otype == urids.patch__Set)
{
// Get the property and value of the set message
const LV2_Atom *property = NULL;
const LV2_Atom *value = NULL;
lv2_atom_object_get(
obj,
uris.patch_property, &property,
uris.patch_value, &value,
0);
lv2_atom_object_get(
obj,
urids.patch__property, &property,
urids.patch__value, &value,
0);
if (!property)
{
}
else if (property->type != uris.atom_URID)
{
}
else
{
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
if (key == pRequest->uridUri)
if (property && property->type == urids.atom__URID && value)
{
int atom_size = value->size + sizeof(LV2_Atom);
if (atom_size > sizeof(pRequest->response))
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
if (key == pRequest->uridUri)
{
pRequest->errorMessage = "Response is too large.";
} else {
pRequest->responseLength = atom_size;
memcpy(pRequest->response,value,atom_size);
int atom_size = value->size + sizeof(LV2_Atom);
pRequest->SetSize(atom_size);
memcpy(pRequest->GetBuffer(),value,atom_size);
break;
}
break;
}
}
}
}
}
}
bool Lv2Effect::GetLv2State(Lv2PluginState*state)
{
if (!this->stateInterface) return false;
*state = this->stateInterface->Save();
return true;
}
+65 -59
View File
@@ -20,8 +20,8 @@
#pragma once
#include "PiPedalHost.hpp"
#include "PedalBoard.hpp"
#include "PluginHost.hpp"
#include "Pedalboard.hpp"
#include <lilv/lilv.h>
#include "BufferPool.hpp"
@@ -32,6 +32,8 @@
#include "lv2/log.lv2/logger.h"
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
#include "lv2/atom.lv2/forge.h"
#include "AtomBuffer.hpp"
#include "StateInterface.hpp"
namespace pipedal
@@ -42,6 +44,12 @@ namespace pipedal
class Lv2Effect : public IEffect
{
private:
std::unique_ptr<StateInterface> stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
std::map<std::string,AtomBuffer> patchPropertyPrototypes;
IHost *pHost = nullptr;
int numberOfInputs = 0;
int numberOfOutputs = 0;
@@ -73,6 +81,7 @@ namespace pipedal
virtual std::string GetUri() const { return info->uri(); }
std::vector<const Lv2PortInfo *> realtimePortInfo;
void PreparePortIndices();
void ConnectControlPorts();
void AssignUnconnectedPorts();
@@ -83,68 +92,62 @@ namespace pipedal
LV2_Atom_Forge outputForgeRt;
class Uris
class Urids
{
public:
Uris(IHost *pHost)
Urids(IHost *pHost)
{
atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank);
atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path);
atom_float = pHost->GetLv2Urid(LV2_ATOM__Float);
atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double);
atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int);
atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long);
atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
atom_String = pHost->GetLv2Urid(LV2_ATOM__String);
atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object);
atom__Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
atom__Path = pHost->GetLv2Urid(LV2_ATOM__Path);
atom__Float = pHost->GetLv2Urid(LV2_ATOM__Float);
atom__Double = pHost->GetLv2Urid(LV2_ATOM__Double);
atom__Int = pHost->GetLv2Urid(LV2_ATOM__Int);
atom__Long = pHost->GetLv2Urid(LV2_ATOM__Long);
atom__Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
atom__String = pHost->GetLv2Urid(LV2_ATOM__String);
atom__Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
atom__Object = pHost->GetLv2Urid(LV2_ATOM__Object);
atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID);
atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer);
patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get);
patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set);
patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put);
patch_body = pHost->GetLv2Urid(LV2_PATCH__body);
patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject);
patch_property = pHost->GetLv2Urid(LV2_PATCH__property);
//patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept);
patch_value = pHost->GetLv2Urid(LV2_PATCH__value);
unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame);
atom__Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
atom__URID = pHost->GetLv2Urid(LV2_ATOM__URID);
patch__Get = pHost->GetLv2Urid(LV2_PATCH__Get);
patch__Set = pHost->GetLv2Urid(LV2_PATCH__Set);
patch__Put = pHost->GetLv2Urid(LV2_PATCH__Put);
patch__body = pHost->GetLv2Urid(LV2_PATCH__body);
patch__subject = pHost->GetLv2Urid(LV2_PATCH__subject);
patch__property = pHost->GetLv2Urid(LV2_PATCH__property);
patch__value = pHost->GetLv2Urid(LV2_PATCH__value);
units__frame = pHost->GetLv2Urid(LV2_UNITS__frame);
state__StateChanged = pHost->GetLv2Urid(LV2_STATE__StateChanged);
}
//LV2_URID patch_accept;
LV2_URID unitsFrame;
LV2_URID atom__Chunk;
LV2_URID units__frame;
LV2_URID pluginUri;
LV2_URID atom_Blank;
LV2_URID atom_Bool;
LV2_URID atom_float;
LV2_URID atom_Double;
LV2_URID atom_Int;
LV2_URID atom_Long;
LV2_URID atom_String;
LV2_URID atom_Object;
LV2_URID atom_Vector;
LV2_URID atom_Path;
LV2_URID atom_Sequence;
LV2_URID atom_Chunk;
LV2_URID atom_URID;
LV2_URID atom_eventTransfer;
LV2_URID midi_Event;
LV2_URID patch_Get;
LV2_URID patch_Set;
LV2_URID patch_Put;
LV2_URID patch_body;
LV2_URID patch_subject;
LV2_URID patch_property;
LV2_URID patch_value;
LV2_URID param_uiState;
LV2_URID atom__Bool;
LV2_URID atom__Float;
LV2_URID atom__Double;
LV2_URID atom__Int;
LV2_URID atom__Long;
LV2_URID atom__String;
LV2_URID atom__Object;
LV2_URID atom__Vector;
LV2_URID atom__Path;
LV2_URID atom__Sequence;
LV2_URID atom__URID;
LV2_URID midi__Event;
LV2_URID patch__Get;
LV2_URID patch__Set;
LV2_URID patch__Put;
LV2_URID patch__body;
LV2_URID patch__subject;
LV2_URID patch__property;
LV2_URID patch__value;
LV2_URID state__StateChanged;
};
Uris uris;
Urids urids;
uint64_t instanceId;
BufferPool bufferPool;
@@ -166,11 +169,13 @@ namespace pipedal
void BypassTo(float value);
virtual void RequestParameter(LV2_URID uridUri);
virtual void GatherParameter(RealtimeParameterRequest*pRequest);
public:
virtual bool GetLv2State(Lv2PluginState*state);
virtual void RequestPatchProperty(LV2_URID uridUri);
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest);
virtual bool IsVst3() const { return false; }
virtual void RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
virtual uint8_t*GetAtomInputBuffer() {
if (this->inputAtomBuffers.size() == 0) return nullptr;
@@ -188,7 +193,7 @@ namespace pipedal
Lv2Effect(
IHost *pHost,
const std::shared_ptr<Lv2PluginInfo> &info,
const PedalBoardItem &pedalBoardItem);
const PedalboardItem &pedalboardItem);
~Lv2Effect();
@@ -228,6 +233,7 @@ namespace pipedal
controlValues[index] = value;
}
}
virtual float GetControlValue(int index) const {
if (index == -1)
{
+1 -1
View File
@@ -19,7 +19,7 @@
#include "pch.h"
#include "Lv2EventBufferWriter.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
+3 -3
View File
@@ -24,17 +24,17 @@
#include <string>
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "MemDebug.hpp"
using namespace pipedal;
TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) {
TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) {
MemStats initialMemory = GetMemStats();
{
PiPedalHost host;
PluginHost host;
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
}
+85 -57
View File
@@ -18,7 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "Lv2PedalBoard.hpp"
#include "Lv2Pedalboard.hpp"
#include "Lv2Effect.hpp"
@@ -27,15 +27,16 @@
#include "VuUpdate.hpp"
#include "AudioHost.hpp"
#include "Lv2EventBufferWriter.hpp"
#include "Lv2Log.hpp"
using namespace pipedal;
float *Lv2PedalBoard::CreateNewAudioBuffer()
float *Lv2Pedalboard::CreateNewAudioBuffer()
{
return bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
}
std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
std::vector<float *> Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
{
std::vector<float *> result;
for (int i = 0; i < nChannels; ++i)
@@ -45,7 +46,7 @@ std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
return result;
}
int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
{
for (int i = 0; i < realtimeEffects.size(); ++i)
{
@@ -57,8 +58,8 @@ int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbo
}
return -1;
}
std::vector<float *> Lv2PedalBoard::PrepareItems(
std::vector<PedalBoardItem> &items,
std::vector<float *> Lv2Pedalboard::PrepareItems(
std::vector<PedalboardItem> &items,
std::vector<float *> inputBuffers)
{
for (int i = 0; i < items.size(); ++i)
@@ -105,7 +106,14 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
else
{
auto pLv2Effect = this->pHost->CreateEffect(item);
IEffect* pLv2Effect = nullptr;
try {
pLv2Effect = this->pHost->CreateEffect(item);
} catch (const std::exception &e)
{
Lv2Log::warning(SS(e.what()));
}
if (pLv2Effect)
{
pEffect = pLv2Effect;
@@ -198,57 +206,57 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
return inputBuffers;
}
void Lv2PedalBoard::Prepare(IHost *pHost, PedalBoard &pedalBoard)
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
{
this->pHost = pHost;
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
{
this->pedalBoardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
auto outputs = PrepareItems(pedalBoard.items(), this->pedalBoardInputBuffers);
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
if (nOutputs == 1)
{
this->pedalBoardOutputBuffers.push_back(outputs[0]);
this->pedalboardOutputBuffers.push_back(outputs[0]);
}
else
{
if (outputs.size() == 1)
{
this->pedalBoardOutputBuffers.push_back(outputs[0]);
this->pedalBoardOutputBuffers.push_back(outputs[0]);
this->pedalboardOutputBuffers.push_back(outputs[0]);
this->pedalboardOutputBuffers.push_back(outputs[0]);
}
else
{
this->pedalBoardOutputBuffers.push_back(outputs[0]);
this->pedalBoardOutputBuffers.push_back(outputs[1]);
this->pedalboardOutputBuffers.push_back(outputs[0]);
this->pedalboardOutputBuffers.push_back(outputs[1]);
}
}
PrepareMidiMap(pedalBoard);
PrepareMidiMap(pedalboard);
}
void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
{
if (pedalBoardItem.midiBindings().size() != 0)
if (pedalboardItem.midiBindings().size() != 0)
{
auto pluginInfo = pHost->GetPluginInfo(pedalBoardItem.uri());
auto pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri());
const Lv2PluginInfo *pPluginInfo;
if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
{
pPluginInfo = GetSplitterPluginInfo();
} else {
pPluginInfo = pluginInfo.get();
}
int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId());
int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId());
if (pluginInfo && effectIndex != -1)
{
for (size_t bindingIndex = 0; bindingIndex < pedalBoardItem.midiBindings().size(); ++bindingIndex)
for (size_t bindingIndex = 0; bindingIndex < pedalboardItem.midiBindings().size(); ++bindingIndex)
{
auto &binding = pedalBoardItem.midiBindings()[bindingIndex];
auto &binding = pedalboardItem.midiBindings()[bindingIndex];
{
const Lv2PortInfo*pPortInfo;
int controlIndex;
@@ -259,7 +267,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
} else {
try {
pPortInfo = &pluginInfo->getPort(binding.symbol());
controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol());
controlIndex = this->GetControlIndex(pedalboardItem.instanceId(), binding.symbol());
} catch (const std::exception&ignored)
{
continue;
@@ -274,7 +282,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
mapping.effectIndex = effectIndex;
mapping.controlIndex = controlIndex;
mapping.midiBinding = binding;
mapping.instanceId = pedalBoardItem.instanceId();
mapping.instanceId = pedalboardItem.instanceId();
if (pPortInfo->IsSwitch())
{
mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary;
@@ -303,20 +311,20 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
}
}
}
for (size_t i = 0; i < pedalBoardItem.topChain().size(); ++i)
for (size_t i = 0; i < pedalboardItem.topChain().size(); ++i)
{
PrepareMidiMap(pedalBoardItem.topChain()[i]);
PrepareMidiMap(pedalboardItem.topChain()[i]);
}
for (size_t i = 0; i < pedalBoardItem.bottomChain().size(); ++i)
for (size_t i = 0; i < pedalboardItem.bottomChain().size(); ++i)
{
PrepareMidiMap(pedalBoardItem.bottomChain()[i]);
PrepareMidiMap(pedalboardItem.bottomChain()[i]);
}
}
void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
void Lv2Pedalboard::PrepareMidiMap(const Pedalboard &pedalboard)
{
for (size_t i = 0; i < pedalBoard.items().size(); ++i)
for (size_t i = 0; i < pedalboard.items().size(); ++i)
{
auto &item = pedalBoard.items()[i];
auto &item = pedalboard.items()[i];
PrepareMidiMap(item);
auto pluginInfo = pHost->GetPluginInfo(item.uri());
@@ -335,14 +343,14 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
{ return left.key < right.key; });
}
}
void Lv2PedalBoard::Activate()
void Lv2Pedalboard::Activate()
{
for (int i = 0; i < this->effects.size(); ++i)
{
this->realtimeEffects[i]->Activate();
}
}
void Lv2PedalBoard::Deactivate()
void Lv2Pedalboard::Deactivate()
{
for (int i = 0; i < this->effects.size(); ++i)
{
@@ -357,46 +365,46 @@ static void Copy(float *input, float *output, uint32_t samples)
output[i] = input[i];
}
}
bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
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 (int i = 0; i < this->pedalboardInputBuffers.size(); ++i)
{
if (inputBuffers[i] == nullptr)
return false;
Copy(inputBuffers[i], this->pedalBoardInputBuffers[i], samples);
Copy(inputBuffers[i], this->pedalboardInputBuffers[i], samples);
}
for (int i = 0; i < this->processActions.size(); ++i)
{
processActions[i](samples);
}
for (int i = 0; i < this->pedalBoardOutputBuffers.size(); ++i)
for (int i = 0; i < this->pedalboardOutputBuffers.size(); ++i)
{
if (outputBuffers[i] == nullptr)
return false;
Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples);
Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples);
}
return true;
}
float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex)
float Lv2Pedalboard::GetControlOutputValue(int effectIndex, int portIndex)
{
auto effect = realtimeEffects[effectIndex];
return effect->GetOutputControlValue(portIndex);
}
void Lv2PedalBoard::SetControlValue(int effectIndex, int index, float value)
void Lv2Pedalboard::SetControlValue(int effectIndex, int index, float value)
{
auto effect = realtimeEffects[effectIndex];
effect->SetControl(index, value);
}
void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled)
void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
{
auto effect = realtimeEffects[effectIndex];
effect->SetBypass(enabled);
}
void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
{
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{
@@ -429,7 +437,7 @@ void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
}
}
void Lv2PedalBoard::ResetAtomBuffers()
void Lv2Pedalboard::ResetAtomBuffers()
{
for (size_t i = 0; i < this->effects.size(); ++i)
{
@@ -438,7 +446,7 @@ void Lv2PedalBoard::ResetAtomBuffers()
}
}
void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests)
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
{
while (pParameterRequests != nullptr)
{
@@ -446,34 +454,54 @@ void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParamete
if (pEffect == nullptr)
{
pParameterRequests->errorMessage = "No such effect.";
}
else
} else if (pEffect->IsVst3())
{
pEffect->RequestParameter(pParameterRequests->uridUri);
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
} else {
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect*>(pEffect);
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
{
pLv2Effect->RequestPatchProperty(pParameterRequests->uridUri);
} else if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchSet) {
pLv2Effect->SetPatchProperty(
pParameterRequests->uridUri,
pParameterRequests->GetSize(),
(LV2_Atom*)pParameterRequests->GetBuffer()
);
}
}
pParameterRequests = pParameterRequests->pNext;
}
}
void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests)
void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
{
while (pParameterRequests != nullptr)
{
IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
if (effect == nullptr)
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
{
pParameterRequests->errorMessage = "No such effect.";
}
else
{
effect->GatherParameter(pParameterRequests);
IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
if (effect == nullptr)
{
pParameterRequests->errorMessage = "No such effect.";
} else if (effect->IsVst3())
{
pParameterRequests->errorMessage = "Not supported for VST3";
}
else
{
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect*>(effect);
pLv2Effect->GatherPatchProperties(pParameterRequests);
}
}
pParameterRequests = pParameterRequests->pNext;
}
}
void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message,
void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
void *callbackHandle,
MidiCallbackFn *pfnCallback)
+17 -17
View File
@@ -18,8 +18,8 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include "PedalBoard.hpp"
#include "PiPedalHost.hpp"
#include "Pedalboard.hpp"
#include "PluginHost.hpp"
#include "Lv2Effect.hpp"
#include "BufferPool.hpp"
#include <functional>
@@ -31,15 +31,15 @@ namespace pipedal {
class RealtimeVuBuffers;
class RealtimeParameterRequest;
class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter;
class Lv2PedalBoard {
class Lv2Pedalboard {
IHost *pHost = nullptr;
BufferPool bufferPool;
std::vector<float*> pedalBoardInputBuffers;
std::vector<float*> pedalBoardOutputBuffers;
std::vector<float*> pedalboardInputBuffers;
std::vector<float*> pedalboardOutputBuffers;
std::vector<std::shared_ptr<IEffect> > effects;
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
@@ -81,21 +81,21 @@ class Lv2PedalBoard {
std::vector<float*> PrepareItems(
std::vector<PedalBoardItem> & items,
std::vector<PedalboardItem> & items,
std::vector<float*> inputBuffers
);
void PrepareMidiMap(const PedalBoard&pedalBoard);
void PrepareMidiMap(const PedalBoardItem&pedalBoardItem);
void PrepareMidiMap(const Pedalboard&pedalboard);
void PrepareMidiMap(const PedalboardItem&pedalboardItem);
std::vector<float*> AllocateAudioBuffers(int nChannels);
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalBoardItem> &items);
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
public:
Lv2PedalBoard() { }
~Lv2PedalBoard() { }
Lv2Pedalboard() { }
~Lv2Pedalboard() { }
void Prepare(IHost *pHost,PedalBoard&pedalBoard);
void Prepare(IHost *pHost,Pedalboard&pedalboard);
std::vector<IEffect* > GetEffects() { return realtimeEffects; }
@@ -122,12 +122,12 @@ public:
void ResetAtomBuffers();
void ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests);
void GatherParameterRequests(RealtimeParameterRequest *pParameterRequests);
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
std::vector<float*> &GetInputBuffers() { return this->pedalBoardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalBoardOutputBuffers;}
std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalboardOutputBuffers;}
View File
+92
View File
@@ -0,0 +1,92 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "MapPathFeature.hpp"
#include <string.h>
#include <algorithm>
#include "json.hpp"
#include <sstream>
using namespace pipedal;
MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath)
:storagePath(storagePath)
{
lv2_state_map_path.handle = (LV2_State_Map_Path_Handle *)this;
lv2_state_map_path.absolute_path = FnAbsolutePath;
lv2_state_map_path.abstract_path = FnAbstractPath;
lv2_state_make_path.handle = (LV2_State_Make_Path_Handle*)this;
lv2_state_make_path.path = FnAbsolutePath;
mapPathFeature.URI = LV2_STATE__mapPath;
mapPathFeature.data = (void*)&lv2_state_map_path;
makePathFeature.URI = LV2_STATE__makePath;
makePathFeature.data = (void*)&lv2_state_make_path;
}
void MapPathFeature::Prepare(MapFeature* map)
{
}
/*static*/ char *MapPathFeature::FnAbsolutePath(
LV2_State_Map_Path_Handle handle,
const char *abstract_path)
{
return ((MapPathFeature *)handle)->AbsolutePath(abstract_path);
}
char *MapPathFeature::AbsolutePath(const char *abstract_path)
{
std::filesystem::path t (abstract_path);
if (t.is_absolute()) {
return strdup(abstract_path);
}
std::filesystem::path result = storagePath / t;
return strdup(result.c_str());
}
/*static*/
char *MapPathFeature::FnAbstractPath(
LV2_State_Map_Path_Handle handle,
const char *absolute_path)
{
return ((MapPathFeature*)handle)->AbstractPath(absolute_path);
}
char *MapPathFeature::AbstractPath(const char *absolute_path)
{
if (strncmp(storagePath.c_str(),absolute_path,storagePath.length()) == 0)
{
const char*result = absolute_path + storagePath.length();
if (*result == '/')
{
++result;
return strdup(result);
}
if (*result == '0') {
return strdup(result);
}
}
return strdup(absolute_path);
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include "lv2/state.lv2/state.h"
#include <filesystem>
#include "MapFeature.hpp"
namespace pipedal
{
class MapPathFeature
{
public:
MapPathFeature(const std::filesystem::path &storagePath);
void Prepare(MapFeature* map);
void SetPluginStoragePath(const std::filesystem::path&path) { storagePath = path;}
const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;}
const LV2_Feature*GetMakePathFeature() { return &makePathFeature;}
private:
char *AbsolutePath(const char *abstract_path);
static char *FnAbsolutePath(LV2_State_Map_Path_Handle handle,
const char *abstract_path);
LV2_State_Map_Path lv2_state_map_path;
LV2_State_Make_Path lv2_state_make_path;
LV2_Feature mapPathFeature;
LV2_Feature makePathFeature;
char *AbstractPath(const char *abstract_path);
static char * FnAbstractPath(
LV2_State_Map_Path_Handle handle,
const char *absolute_path);
private:
std::string storagePath;
};
}
+56 -52
View File
@@ -18,54 +18,63 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
using namespace pipedal;
static const PedalBoardItem* GetItem_(const std::vector<PedalBoardItem>&items,long pedalBoardItemId)
static const PedalboardItem* GetItem_(const std::vector<PedalboardItem>&items,long pedalboardItemId)
{
for (size_t i = 0; i < items.size(); ++i)
{
auto &item = items[i];
if (items[i].instanceId() == pedalBoardItemId)
if (items[i].instanceId() == pedalboardItemId)
{
return &(items[i]);
}
if (item.isSplit())
{
const PedalBoardItem* t = GetItem_(item.topChain(),pedalBoardItemId);
const PedalboardItem* t = GetItem_(item.topChain(),pedalboardItemId);
if (t != nullptr) return t;
t = GetItem_(item.bottomChain(),pedalBoardItemId);
t = GetItem_(item.bottomChain(),pedalboardItemId);
if (t != nullptr) return t;
}
}
return nullptr;
}
const PedalBoardItem*PedalBoard::GetItem(long pedalItemId) const
static void GetAllItems(std::vector<PedalboardItem*> & result, std::vector<PedalboardItem>&items)
{
for (auto& item: items)
{
if (item.isSplit())
{
GetAllItems(result,item.topChain());
GetAllItems(result,item.bottomChain());
}
result.push_back(&item);
}
}
std::vector<PedalboardItem*> Pedalboard::GetAllPlugins()
{
std::vector<PedalboardItem*> result;
GetAllItems(result,this->items());
return result;
}
const PedalboardItem*Pedalboard::GetItem(long pedalItemId) const
{
return GetItem_(this->items(),pedalItemId);
}
PedalBoardItem*PedalBoard::GetItem(long pedalItemId)
PedalboardItem*Pedalboard::GetItem(long pedalItemId)
{
return const_cast<PedalBoardItem*>(GetItem_(this->items(),pedalItemId));
return const_cast<PedalboardItem*>(GetItem_(this->items(),pedalItemId));
}
PropertyValue*PedalBoardItem::GetPropertyValue(const std::string&propertyUri)
{
for (auto&propertyValue: this->propertyValues_)
{
if (propertyValue.propertyUri() == propertyUri)
{
return &propertyValue;
}
}
return nullptr;
}
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
{
for (size_t i = 0; i < this->controlValues().size(); ++i)
{
@@ -77,9 +86,9 @@ ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
return nullptr;
}
bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled)
bool Pedalboard::SetItemEnabled(long pedalItemId, bool enabled)
{
PedalBoardItem*item = GetItem(pedalItemId);
PedalboardItem*item = GetItem(pedalItemId);
if (!item) return false;
if (item->isEnabled() != enabled)
{
@@ -91,9 +100,9 @@ bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled)
}
bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, float value)
bool Pedalboard::SetControlValue(long pedalItemId, const std::string &symbol, float value)
{
PedalBoardItem*item = GetItem(pedalItemId);
PedalboardItem*item = GetItem(pedalItemId);
if (!item) return false;
ControlValue*controlValue = item->GetControlValue(symbol);
if (controlValue == nullptr) return false;
@@ -106,11 +115,11 @@ bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, fl
}
PedalBoardItem PedalBoard::MakeEmptyItem()
PedalboardItem Pedalboard::MakeEmptyItem()
{
uint64_t instanceId = NextInstanceId();
PedalBoardItem result;
PedalboardItem result;
result.instanceId(instanceId);
result.uri(EMPTY_PEDALBOARD_ITEM_URI);
result.pluginName("");
@@ -119,11 +128,11 @@ PedalBoardItem PedalBoard::MakeEmptyItem()
}
PedalBoardItem PedalBoard::MakeSplit()
PedalboardItem Pedalboard::MakeSplit()
{
uint64_t instanceId = NextInstanceId();
PedalBoardItem result;
PedalboardItem result;
result.instanceId(instanceId);
result.uri(SPLIT_PEDALBOARD_ITEM_URI);
result.pluginName("");
@@ -151,10 +160,10 @@ PedalBoardItem PedalBoard::MakeSplit()
PedalBoard PedalBoard::MakeDefault()
Pedalboard Pedalboard::MakeDefault()
{
// copy insanity. but it happens so rarely.
PedalBoard result;
Pedalboard result;
auto split = result.MakeSplit();
split.topChain().push_back(result.MakeEmptyItem());
@@ -176,7 +185,7 @@ PedalBoard PedalBoard::MakeDefault()
}
bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector<PedalBoardItem>&value)
bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector<PedalboardItem>&value)
{
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
}
@@ -192,29 +201,24 @@ JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,value)
JSON_MAP_END()
JSON_MAP_BEGIN(PropertyValue)
JSON_MAP_REFERENCE(PropertyValue,propertyUri)
JSON_MAP_REFERENCE(PropertyValue,value)
JSON_MAP_BEGIN(PedalboardItem)
JSON_MAP_REFERENCE(PedalboardItem,instanceId)
JSON_MAP_REFERENCE(PedalboardItem,uri)
JSON_MAP_REFERENCE(PedalboardItem,isEnabled)
JSON_MAP_REFERENCE(PedalboardItem,controlValues)
JSON_MAP_REFERENCE(PedalboardItem,pluginName)
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,topChain,IsPedalboardSplitItem)
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,bottomChain,&IsPedalboardSplitItem)
JSON_MAP_REFERENCE(PedalboardItem,midiBindings)
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoardItem)
JSON_MAP_REFERENCE(PedalBoardItem,instanceId)
JSON_MAP_REFERENCE(PedalBoardItem,uri)
JSON_MAP_REFERENCE(PedalBoardItem,isEnabled)
JSON_MAP_REFERENCE(PedalBoardItem,controlValues)
JSON_MAP_REFERENCE(PedalBoardItem,propertyValues)
JSON_MAP_REFERENCE(PedalBoardItem,pluginName)
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,topChain,IsPedalBoardSplitItem)
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,bottomChain,&IsPedalBoardSplitItem)
JSON_MAP_REFERENCE(PedalBoardItem,midiBindings)
JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoard)
JSON_MAP_REFERENCE(PedalBoard,name)
JSON_MAP_REFERENCE(PedalBoard,items)
JSON_MAP_REFERENCE(PedalBoard,nextInstanceId)
JSON_MAP_BEGIN(Pedalboard)
JSON_MAP_REFERENCE(Pedalboard,name)
JSON_MAP_REFERENCE(Pedalboard,items)
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
JSON_MAP_END()
+19 -39
View File
@@ -22,6 +22,7 @@
#include "json.hpp"
#include "json_variant.hpp"
#include "MidiBinding.hpp"
#include "StateInterface.hpp"
namespace pipedal {
@@ -73,55 +74,34 @@ public:
};
class PropertyValue {
private:
std::string propertyUri_;
json_variant value_;
public:
PropertyValue()
{
}
template <typename T>
PropertyValue(const std::string&propertyUri, T value)
:propertyUri_(propertyUri)
, value_(value)
{
}
GETTER_SETTER_REF(propertyUri)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(PropertyValue);
};
class PedalBoardItem: public JsonMemberWritable {
class PedalboardItem: public JsonMemberWritable {
int64_t instanceId_ = 0;
std::string uri_;
std::string pluginName_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
std::vector<PropertyValue> propertyValues_;
std::vector<PedalBoardItem> topChain_;
std::vector<PedalBoardItem> bottomChain_;
std::vector<PedalboardItem> topChain_;
std::vector<PedalboardItem> bottomChain_;
std::vector<MidiBinding> midiBindings_;
std::string vstState_;
Lv2PluginState lv2State_;
public:
ControlValue*GetControlValue(const std::string&symbol);
PropertyValue*GetPropertyValue(const std::string&propertyUri);
bool hasLv2State() const {
return lv2State_.values_.size() != 0;
}
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(propertyValues)
GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(lv2State)
bool isSplit() const
@@ -137,7 +117,6 @@ public:
writer.write_member("uri",uri_);
writer.write_member("pluginName",pluginName_);
writer.write_member("isEnabled",isEnabled_);
writer.write_member("controlValues",controlValues_);
if (isSplit())
{
writer.write_member("topChain",topChain_);
@@ -145,13 +124,13 @@ public:
}
}
DECLARE_JSON_MAP(PedalBoardItem);
DECLARE_JSON_MAP(PedalboardItem);
};
class PedalBoard {
class Pedalboard {
std::string name_;
std::vector<PedalBoardItem> items_;
std::vector<PedalboardItem> items_;
uint64_t nextInstanceId_ = 0;
uint64_t NextInstanceId() { return ++nextInstanceId_; }
@@ -159,8 +138,9 @@ public:
bool SetControlValue(long pedalItemId, const std::string &symbol, float value);
bool SetItemEnabled(long pedalItemId, bool enabled);
PedalBoardItem*GetItem(long pedalItemId);
const PedalBoardItem*GetItem(long pedalItemId) const;
PedalboardItem*GetItem(long pedalItemId);
const PedalboardItem*GetItem(long pedalItemId) const;
std::vector<PedalboardItem*>GetAllPlugins();
bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
@@ -168,13 +148,13 @@ public:
GETTER_SETTER_VEC(items)
DECLARE_JSON_MAP(PedalBoard);
DECLARE_JSON_MAP(Pedalboard);
PedalBoardItem MakeEmptyItem();
PedalBoardItem MakeSplit();
PedalboardItem MakeEmptyItem();
PedalboardItem MakeSplit();
static PedalBoard MakeDefault();
static Pedalboard MakeDefault();
};
#undef GETTER_SETTER_REF
+1 -1
View File
@@ -198,7 +198,7 @@ public:
audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.GetSampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
+329 -198
View File
File diff suppressed because it is too large Load Diff
+57 -30
View File
@@ -19,9 +19,9 @@
#pragma once
#include <mutex>
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "GovernorSettings.hpp"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include "Storage.hpp"
#include "Banks.hpp"
#include "JackConfiguration.hpp"
@@ -37,6 +37,8 @@
#include "AdminClient.hpp"
#include "AvahiService.hpp"
#include <thread>
#include "Promise.hpp"
#include "AtomConverter.hpp"
namespace pipedal
{
@@ -50,8 +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 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;
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
@@ -62,19 +65,21 @@ namespace pipedal
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) = 0;
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0;
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
virtual void Close() = 0;
};
class PiPedalModel : private IAudioHostCallbacks
{
private:
std::unique_ptr<std::jthread> pingThread;
std::vector<MidiBinding> systemMidiBindings;
@@ -97,9 +102,14 @@ namespace pipedal
class AtomOutputListener
{
public:
bool WantsProperty(uint64_t instanceId,LV2_URID patchProperty) const {
if (instanceId != this->instanceId) return false;
return this->propertyUrid == 0 || patchProperty == this->propertyUrid;
}
int64_t clientId;
int64_t clientHandle;
uint64_t instanceId;
LV2_URID propertyUrid;
};
void DeleteMidiListeners(int64_t clientId);
void DeleteAtomOutputListeners(int64_t clientId);
@@ -108,27 +118,29 @@ namespace pipedal
std::vector<AtomOutputListener> atomOutputListeners;
JackServerSettings jackServerSettings;
PiPedalHost lv2Host;
PedalBoard pedalBoard;
PluginHost lv2Host;
AtomConverter atomConverter; // must be AFTER lv2Host!
Pedalboard pedalboard;
Storage storage;
bool hasPresetChanged = false;
std::unique_ptr<AudioHost> audioHost;
JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard;
std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber *> subscribers;
void SetPresetChanged(int64_t clientId, bool value);
void FirePresetsChanged(int64_t clientId);
void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalBoardChanged(int64_t clientId);
void FirePedalboardChanged(int64_t clientId);
void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void UpdateDefaults(PedalBoardItem *pedalBoardItem);
void UpdateDefaults(PedalBoard *pedalBoard);
void UpdateDefaults(PedalboardItem *pedalboardItem);
void UpdateDefaults(Pedalboard *pedalboard);
class VuSubscription
{
@@ -147,20 +159,23 @@ namespace pipedal
void RestartAudio();
std::vector<RealtimeParameterRequest *> outstandingParameterRequests;
std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
private: // IAudioHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates);
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson);
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest);
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request);
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override;
virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) override;
;
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override;
void UpdateVst3Settings(PedalBoard &pedalBoard);
void UpdateVst3Settings(Pedalboard &pedalboard);
PiPedalConfiguration configuration;
@@ -171,6 +186,8 @@ namespace pipedal
uint16_t GetWebPort() const { return webPort; }
void Close();
void SetOnboarding(bool value);
void UpdateDnsSd();
AdminClient &GetAdminClient() { return adminClient; }
@@ -180,11 +197,11 @@ namespace pipedal
void LoadLv2PluginInfo();
void Load();
const PiPedalHost &GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy()
const PluginHost &GetLv2Host() const { return lv2Host; }
Pedalboard GetCurrentPedalboardCopy()
{
std::lock_guard<std::recursive_mutex> guard(mutex);
return pedalBoard; // can return a referece because we'd lose mutex protection
return pedalboard; // can return a referece because we'd lose mutex protection
}
PluginUiPresets GetPluginUiPresets(const std::string &pluginUri);
PluginPresets GetPluginPresets(const std::string &pluginUri);
@@ -194,15 +211,16 @@ namespace pipedal
void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber);
void RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber);
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
void SetPedalboardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
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 SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
void GetPresets(PresetIndex *pResult);
PedalBoard GetPreset(int64_t instanceId);
Pedalboard GetPreset(int64_t instanceId);
void GetBank(int64_t instanceId, BankFile *pBank);
int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1);
@@ -251,13 +269,22 @@ namespace pipedal
int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
void UnmonitorPort(int64_t subscriptionHandle);
void GetLv2Parameter(
void SendGetPatchProperty(
int64_t clientId,
int64_t instanceId,
const std::string uri,
std::function<void(const std::string &jsonResjult)> onSuccess,
std::function<void(const std::string &error)> onError);
void SendSetPatchProperty(
int64_t clientId,
int64_t instanceId,
const std::string uri,
const json_variant&value,
std::function<void()> onSuccess,
std::function<void(const std::string &error)> onError);
BankIndex GetBankIndex() const;
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
@@ -273,8 +300,8 @@ namespace pipedal
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri);
void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
std::vector<AlsaDeviceInfo> GetAlsaDevices();
const std::filesystem::path &GetWebRoot() const;
@@ -282,7 +309,7 @@ namespace pipedal
std::map<std::string, bool> GetFavorites() const;
void SetFavorites(const std::map<std::string, bool> &favorites);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
};
} // namespace pipedal.
+170 -84
View File
@@ -30,6 +30,7 @@
#include <future>
#include <atomic>
#include "Ipv6Helpers.hpp"
#include "Promise.hpp"
#include "AdminClient.hpp"
#include "WifiConfigSettings.hpp"
@@ -42,6 +43,33 @@
using namespace std;
using namespace pipedal;
class GetPatchPropertyBody {
public:
uint64_t instanceId_;
std::string propertyUri_;
DECLARE_JSON_MAP(GetPatchPropertyBody);
};
JSON_MAP_BEGIN(GetPatchPropertyBody)
JSON_MAP_REFERENCE(GetPatchPropertyBody, instanceId)
JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri)
JSON_MAP_END()
class SetPatchPropertyBody {
public:
uint64_t instanceId_;
std::string propertyUri_;
json_variant value_;
DECLARE_JSON_MAP(SetPatchPropertyBody);
};
JSON_MAP_BEGIN(SetPatchPropertyBody)
JSON_MAP_REFERENCE(SetPatchPropertyBody, instanceId)
JSON_MAP_REFERENCE(SetPatchPropertyBody, propertyUri)
JSON_MAP_REFERENCE(SetPatchPropertyBody, value)
JSON_MAP_END()
class NotifyMidiListenerBody
{
public:
@@ -61,14 +89,15 @@ class NotifyAtomOutputBody
public:
int64_t clientHandle_;
uint64_t instanceId_;
std::string atomJson_;
std::string propertyUri_;
raw_json_string atomJson_;
DECLARE_JSON_MAP(NotifyAtomOutputBody);
};
JSON_MAP_BEGIN(NotifyAtomOutputBody)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, propertyUri)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson)
JSON_MAP_END()
@@ -85,17 +114,19 @@ JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly)
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
JSON_MAP_END()
class ListenForAtomOutputBody
class MonitorPatchPropertyBody
{
public:
uint64_t instanceId_;
int64_t handle_;
DECLARE_JSON_MAP(ListenForAtomOutputBody);
int64_t clientHandle_;
std::string propertyUri_;
DECLARE_JSON_MAP(MonitorPatchPropertyBody);
};
JSON_MAP_BEGIN(ListenForAtomOutputBody)
JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId)
JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle)
JSON_MAP_BEGIN(MonitorPatchPropertyBody)
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri)
JSON_MAP_END()
class OnLoadPluginPresetBody
@@ -151,18 +182,6 @@ JSON_MAP_REFERENCE(MonitorResultBody, subscriptionHandle)
JSON_MAP_REFERENCE(MonitorResultBody, value)
JSON_MAP_END()
class GetLv2ParameterBody
{
public:
int64_t instanceId_;
std::string uri_;
DECLARE_JSON_MAP(GetLv2ParameterBody);
};
JSON_MAP_BEGIN(GetLv2ParameterBody)
JSON_MAP_REFERENCE(GetLv2ParameterBody, instanceId)
JSON_MAP_REFERENCE(GetLv2ParameterBody, uri)
JSON_MAP_END()
class MonitorPortBody
{
@@ -262,33 +281,33 @@ JSON_MAP_REFERENCE(CopyPluginPresetBody, pluginUri)
JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId)
JSON_MAP_END()
class PedalBoardItemEnabledBody
class PedalboardItemEnabledBody
{
public:
int64_t clientId_ = -1;
int64_t instanceId_ = -1;
bool enabled_ = true;
DECLARE_JSON_MAP(PedalBoardItemEnabledBody);
DECLARE_JSON_MAP(PedalboardItemEnabledBody);
};
JSON_MAP_BEGIN(PedalBoardItemEnabledBody)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, clientId)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled)
JSON_MAP_BEGIN(PedalboardItemEnabledBody)
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, clientId)
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, instanceId)
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, enabled)
JSON_MAP_END()
class UpdateCurrentPedalBoardBody
class UpdateCurrentPedalboardBody
{
public:
int64_t clientId_ = -1;
PedalBoard pedalBoard_;
Pedalboard pedalboard_;
DECLARE_JSON_MAP(UpdateCurrentPedalBoardBody);
DECLARE_JSON_MAP(UpdateCurrentPedalboardBody);
};
JSON_MAP_BEGIN(UpdateCurrentPedalBoardBody)
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, clientId)
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, pedalBoard)
JSON_MAP_BEGIN(UpdateCurrentPedalboardBody)
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId)
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
JSON_MAP_END()
class ChannelSelectionChangedBody
@@ -335,6 +354,24 @@ JSON_MAP_REFERENCE(ControlChangedBody, symbol)
JSON_MAP_REFERENCE(ControlChangedBody, value)
JSON_MAP_END()
class PatchPropertyChangedBody
{
public:
int64_t clientId_;
int64_t instanceId_;
std::string propertyUri_;
json_variant value_;
DECLARE_JSON_MAP(PatchPropertyChangedBody);
};
JSON_MAP_BEGIN(PatchPropertyChangedBody)
JSON_MAP_REFERENCE(PatchPropertyChangedBody, clientId)
JSON_MAP_REFERENCE(PatchPropertyChangedBody, instanceId)
JSON_MAP_REFERENCE(PatchPropertyChangedBody, propertyUri)
JSON_MAP_REFERENCE(PatchPropertyChangedBody, value)
JSON_MAP_END()
class Vst3ControlChangedBody
{
public:
@@ -420,14 +457,19 @@ public:
std::stringstream imageList;
const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
bool firstTime = true;
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
{
if (!firstTime)
try {
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
{
imageList << ";";
if (!firstTime)
{
imageList << ";";
}
firstTime = false;
imageList << entry.path().filename().string();
}
firstTime = false;
imageList << entry.path().filename().string();
} catch (const std::exception&)
{
Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str());
}
this->imageList = imageList.str();
}
@@ -778,17 +820,17 @@ public:
}
return;
}
if (message == "previewControl")
{
ControlChangedBody message;
pReader->read(&message);
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
}
else if (message == "setControl")
if (message == "setControl")
{
ControlChangedBody message;
pReader->read(&message);
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
}
else if (message == "previewControl")
{
ControlChangedBody message;
pReader->read(&message);
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
}
else if (message == "listenForMidiEvent")
{
@@ -802,17 +844,17 @@ public:
pReader->read(&handle);
this->model.CancelListenForMidiEvent(this->clientId, handle);
}
else if (message == "listenForAtomOutput")
else if (message == "monitorPatchProperty")
{
ListenForAtomOutputBody body;
MonitorPatchPropertyBody body;
pReader->read(&body);
this->model.ListenForAtomOutputs(this->clientId, body.handle_, body.instanceId_);
this->model.MonitorPatchProperty(this->clientId, body.clientHandle_, body.instanceId_, body.propertyUri_);
}
else if (message == "cancelListenForAtomOutput")
else if (message == "cancelMonitorPatchProperty")
{
int64_t handle;
pReader->read(&handle);
this->model.CancelListenForMidiEvent(this->clientId, handle);
this->model.CancelMonitorPatchProperty(this->clientId, handle);
}
else if (message == "getJackStatus")
@@ -954,25 +996,25 @@ public:
this->model.GetPresets(&presets);
Reply(replyTo, "getPresets", presets);
}
else if (message == "setPedalBoardItemEnable")
else if (message == "setPedalboardItemEnable")
{
PedalBoardItemEnabledBody body;
PedalboardItemEnabledBody body;
pReader->read(&body);
model.SetPedalBoardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
model.SetPedalboardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
}
else if (message == "updateCurrentPedalBoard")
else if (message == "updateCurrentPedalboard")
{
{
UpdateCurrentPedalBoardBody body;
UpdateCurrentPedalboardBody body;
pReader->read(&body);
this->model.UpdateCurrentPedalBoard(body.clientId_, body.pedalBoard_);
this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_);
}
}
else if (message == "currentPedalBoard")
else if (message == "currentPedalboard")
{
auto pedalBoard = model.GetCurrentPedalBoardCopy();
Reply(replyTo, "currentPedalBoard", pedalBoard);
auto pedalboard = model.GetCurrentPedalboardCopy();
Reply(replyTo, "currentPedalboard", pedalboard);
}
else if (message == "plugins")
{
@@ -1142,18 +1184,35 @@ public:
uint64_t result = model.CopyPluginPreset(body.pluginUri_, body.instanceId_);
this->Reply(replyTo, "copyPluginPreset", result);
}
else if (message == "getLv2Parameter")
else if (message == "setPatchProperty")
{
GetLv2ParameterBody body;
SetPatchPropertyBody body;
pReader->read(&body);
model.SendSetPatchProperty(clientId,body.instanceId_,body.propertyUri_,body.value_,
[this, replyTo] () {
this->JsonReply(replyTo, "setPatchProperty", "true");
},
[this, replyTo] (const std::string&error) {
this->SendError(replyTo, error.c_str());
}
);
}
else if (message == "getPatchProperty")
{
GetPatchPropertyBody body;
pReader->read(&body);
model.GetLv2Parameter(
model.SendGetPatchProperty(
this->clientId,
body.instanceId_,
body.uri_,
body.propertyUri_,
[this, replyTo](const std::string &jsonResult)
{
this->JsonReply(replyTo, "getLv2Parameter", jsonResult.c_str());
this->JsonReply(replyTo, "getPatchProperty", jsonResult.c_str());
},
[this, replyTo](const std::string &error)
{
@@ -1248,10 +1307,17 @@ public:
}
else if (message == "requestFileList")
{
PiPedalFileProperty fileProperty;
UiFileProperty fileProperty;
pReader->read(&fileProperty);
std::vector<std::string> list = this->model.GetFileList(fileProperty);
this->Reply(replyTo,"requestFileList",list);
}
else if (message == "setOnboarding")
{
bool value;
pReader->read(&value);
this->model.SetOnboarding(value);
}
else
{
Lv2Log::error("Unknown message received: %s", message.c_str());
@@ -1260,8 +1326,7 @@ public:
}
protected:
virtual void
onReceive(const std::string_view &text)
virtual void onReceive(const std::string_view &text)
{
view_istream<char> s(text);
@@ -1324,7 +1389,22 @@ protected:
}
}
public:
private:
virtual void OnLv2StateChanged(int64_t instanceId)
{
Send("onLv2StateChanged",instanceId);
}
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
{
PatchPropertyChangedBody body;
body.clientId_ = clientId;
body.instanceId_ = instanceId;
body.propertyUri_ = propertyUri;
body.value_ = value;
Send("onPatchPropertyChanged",body);
}
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
Send("onSystemMidiBindingsChanged",bindings);
}
@@ -1499,13 +1579,13 @@ public:
public:
int64_t clientHandle;
uint64_t instanceId;
std::string atomType;
std::string propertyUri;
std::string json;
};
std::vector<PendingNotifyAtomOutput> pendingNotifyAtomOutputs;
void OnAckNotifyAtomOutput()
void OnAckNotifyPatchProperty()
{
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
if (--outstandingNotifyAtomOutputs <= 0)
@@ -1518,19 +1598,24 @@ public:
OnNotifyAtomOutput(
t.clientHandle,
t.instanceId,
t.atomType,
t.propertyUri,
t.json);
}
}
}
virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomType, const std::string &atomJson)
virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomProperty, const std::string &atomJson)
{
NotifyAtomOutputBody body;
body.clientHandle_ = clientHandle;
body.instanceId_ = instanceId;
body.atomJson_ = atomJson;
body.propertyUri_ = atomProperty;
body.atomJson_.Set(atomJson);
// flow control. We can only have one in-flight NotifyAtomOutput at a time.
// Subsequent notifications are held until we receive an ack.
//
// If a duplicate atomProperty is queued, overwrite the previous value.
{
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
@@ -1539,23 +1624,24 @@ public:
for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i)
{
auto &output = pendingNotifyAtomOutputs[i];
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.atomType == atomType)
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty)
{
output.json = atomJson;
return;
// better to erase than overwrite, since it provides better idempotence.
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i);
break;
}
}
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomType, atomJson});
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomProperty, atomJson});
return;
}
++outstandingNotifyAtomOutputs;
}
Request<bool>(
"onNotifyAtomOut", body,
"onNotifyPatchProperty", body,
[this](const bool &value)
{
this->OnAckNotifyAtomOutput();
this->OnAckNotifyPatchProperty();
},
[](const std::exception &e) {
@@ -1594,17 +1680,17 @@ public:
Send("onGovernorSettingsChanged", governor);
}
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard)
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard)
{
UpdateCurrentPedalBoardBody body;
UpdateCurrentPedalboardBody body;
body.clientId_ = clientId;
body.pedalBoard_ = pedalBoard;
Send("onPedalBoardChanged", body);
body.pedalboard_ = pedalboard;
Send("onPedalboardChanged", body);
}
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled)
{
PedalBoardItemEnabledBody body;
PedalboardItemEnabledBody body;
body.clientId_ = clientId;
body.instanceId_ = pedalItemId;
body.enabled_ = enabled;
+104 -40
View File
@@ -23,11 +23,12 @@
*/
#include "PiPedalUI.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "ss.hpp"
using namespace pipedal;
PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::filesystem::path &resourcePath)
{
auto pWorld = pHost->getWorld();
@@ -37,18 +38,35 @@ PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
const LilvNode *fileNode = lilv_nodes_get(fileNodes, i);
try
{
PiPedalFileProperty::ptr fileUI = std::make_shared<PiPedalFileProperty>(pHost, fileNode);
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(pHost, fileNode, resourcePath);
this->fileProperites_.push_back(std::move(fileUI));
}
catch (const std::exception &e)
{
pHost->LogError(e.what());
pHost->LogWarning(SS("Failed to read pipedalui::fileProperties. " << e.what()));
}
}
LilvNodes *portNotifications = lilv_world_find_nodes(pWorld, uiNode, pHost->lilvUris.ui__portNotification, nullptr);
LILV_FOREACH(nodes, i, portNotifications)
{
const LilvNode *portNotificationNode = lilv_nodes_get(portNotifications, i);
try
{
UiPortNotification::ptr portNotification = std::make_shared<UiPortNotification>(pHost, portNotificationNode);
this->portNotifications_.push_back(std::move(portNotification));
}
catch (const std::exception &e)
{
pHost->LogWarning(SS("Failed to read ui:portNotifications. " << e.what()));
}
}
lilv_nodes_free(fileNodes);
}
PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) {
PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
{
auto pWorld = pHost->getWorld();
AutoLilvNode name = lilv_world_get(
@@ -77,9 +95,8 @@ PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) {
{
throw std::logic_error("pipedal_ui:fileType is missing fileExtension property.");
}
}
PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *node)
UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath)
{
auto pWorld = pHost->getWorld();
@@ -110,9 +127,9 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod
throw std::logic_error("Pipedal FileProperty::directory must have only alpha-numeric characters.");
}
}
else
if (directory_.length() == 0)
{
throw std::logic_error("PiPedal FileProperty is missing a pipedalui:directory value.");
throw std::logic_error("PipedalUI::fileProperty: must specify at least a directory.");
}
AutoLilvNode patchProperty = lilv_world_get(
@@ -123,24 +140,19 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod
if (patchProperty)
{
this->patchProperty_ = patchProperty.AsUri();
} else {
}
else
{
throw std::logic_error("PiPedal FileProperty is missing pipedalui:patchProperty value.");
}
AutoLilvNode defaultFile = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__defaultFile,
nullptr);
this->defaultFile_ = defaultFile.AsString();
this->fileTypes_ = PiPedalFileType::GetArray(pHost,node,pHost->lilvUris.pipedalUI__fileTypes);
this->fileTypes_ = PiPedalFileType::GetArray(pHost, node, pHost->lilvUris.pipedalUI__fileTypes);
}
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri)
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
{
std::vector<PiPedalFileType> result;
LilvWorld* pWorld = pHost->getWorld();
LilvWorld *pWorld = pHost->getWorld();
LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr);
LILV_FOREACH(nodes, i, fileTypeNodes)
@@ -160,35 +172,33 @@ std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const
return result;
}
bool pipedal::IsAlphaNumeric(const std::string&value)
bool pipedal::IsAlphaNumeric(const std::string &value)
{
for (char c:value)
for (char c : value)
{
if (
(c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c == '_')
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')
) {
)
{
continue;
}
return false;
}
return true;
}
bool PiPedalFileProperty::IsDirectoryNameValid(const std::string&value)
bool UiFileProperty::IsDirectoryNameValid(const std::string &value)
{
if (value.length() == 0) return false;
if (value.length() == 0)
return false;
return IsAlphaNumeric(value);
}
bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const
bool UiFileProperty::IsValidExtension(const std::string &extension) const
{
for (auto&fileType: fileTypes_)
for (auto &fileType : fileTypes_)
{
if (fileType.fileExtension() == extension)
{
@@ -198,15 +208,69 @@ bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const
return false;
}
UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
{
// ui:portNotification
// [
// ui:portIndex 3;
// ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
// ui:protocol ui:floatProtocol;
// // pipedal_ui:style pipedal_ui:text ;
// // pipedal_ui:redLevel 0;
// // pipedal_ui:yellowLevel -12;
// ]
LilvWorld *pWorld = pHost->getWorld();
JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType,name)
JSON_MAP_REFERENCE(PiPedalFileType,fileExtension)
AutoLilvNode portIndex = lilv_world_get(pWorld,node,pHost->lilvUris.ui__portIndex,nullptr);
if (!portIndex)
{
this->portIndex_ = -1;
} else {
this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex);
}
AutoLilvNode symbol = lilv_world_get(pWorld,node,pHost->lilvUris.lv2__symbol,nullptr);
if (!symbol)
{
this->symbol_ = "";
} else {
this->symbol_ = symbol.AsString();
}
AutoLilvNode plugin = lilv_world_get(pWorld,node,pHost->lilvUris.ui__plugin,nullptr);
if (!plugin)
{
this->plugin_ = "";
} else {
this->plugin_ = plugin.AsUri();
}
AutoLilvNode protocol = lilv_world_get(pWorld,node,pHost->lilvUris.ui__protocol,nullptr);
if (!protocol)
{
this->protocol_ = "";
} else {
this->protocol_ = protocol.AsUri();
}
if (this->portIndex_ == -1 &&this->symbol_ == "")
{
pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol.");
}
}
JSON_MAP_BEGIN(UiPortNotification)
JSON_MAP_REFERENCE(UiPortNotification, portIndex)
JSON_MAP_REFERENCE(UiPortNotification, symbol)
JSON_MAP_REFERENCE(UiPortNotification, plugin)
JSON_MAP_REFERENCE(UiPortNotification, protocol)
JSON_MAP_END()
JSON_MAP_BEGIN(PiPedalFileProperty)
JSON_MAP_REFERENCE(PiPedalFileProperty,patchProperty)
JSON_MAP_REFERENCE(PiPedalFileProperty,name)
JSON_MAP_REFERENCE(PiPedalFileProperty,defaultFile)
JSON_MAP_REFERENCE(PiPedalFileProperty,fileTypes)
JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType, name)
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
JSON_MAP_END()
JSON_MAP_BEGIN(UiFileProperty)
JSON_MAP_REFERENCE(UiFileProperty, name)
JSON_MAP_REFERENCE(UiFileProperty, directory)
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
JSON_MAP_END()
+46 -14
View File
@@ -28,6 +28,7 @@
#include <memory>
#include <lilv/lilv.h>
#include "json.hpp"
#include <filesystem>
#define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui"
@@ -38,7 +39,6 @@
#define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties"
#define PIPEDAL_UI__fileProperty PIPEDAL_UI_PREFIX "fileProperty"
#define PIPEDAL_UI__defaultFile PIPEDAL_UI_PREFIX "defaultFile"
#define PIPEDAL_UI__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
@@ -53,7 +53,7 @@
namespace pipedal {
class PiPedalHost;
class PluginHost;
class PiPedalFileType {
private:
@@ -61,9 +61,9 @@ namespace pipedal {
std::string fileExtension_;
public:
PiPedalFileType() { }
PiPedalFileType(PiPedalHost*pHost, const LilvNode*node);
PiPedalFileType(PluginHost*pHost, const LilvNode*node);
static std::vector<PiPedalFileType> GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri);
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
const std::string& name() const { return name_;}
const std::string &fileExtension() const { return fileExtension_; }
@@ -73,22 +73,38 @@ namespace pipedal {
};
class PiPedalFileProperty {
class UiPortNotification {
private:
int32_t portIndex_;
std::string symbol_;
std::string plugin_;
std::string protocol_;
public:
using ptr = std::shared_ptr<UiPortNotification>;
UiPortNotification() { }
UiPortNotification(PluginHost*pHost, const LilvNode*node);
public:
DECLARE_JSON_MAP(UiPortNotification);
};
class UiFileProperty {
private:
std::string name_;
std::string directory_;
std::vector<PiPedalFileType> fileTypes_;
std::string patchProperty_;
std::string defaultFile_;
public:
using ptr = std::shared_ptr<PiPedalFileProperty>;
PiPedalFileProperty() { }
PiPedalFileProperty(PiPedalHost*pHost, const LilvNode*node);
using ptr = std::shared_ptr<UiFileProperty>;
UiFileProperty() { }
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
const std::string &name() const { return name_; }
const std::string &directory() const { return directory_; }
const std::string &defaultFile() const { return defaultFile_; }
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
@@ -97,19 +113,35 @@ namespace pipedal {
static bool IsDirectoryNameValid(const std::string&value);
public:
DECLARE_JSON_MAP(PiPedalFileProperty);
DECLARE_JSON_MAP(UiFileProperty);
};
class PiPedalUI {
public:
using ptr = std::shared_ptr<PiPedalUI>;
PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode);
const std::vector<PiPedalFileProperty::ptr>& fileProperties() const
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
const std::vector<UiFileProperty::ptr>& fileProperties() const
{
return fileProperites_;
}
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
const UiFileProperty*GetFileProperty(const std::string &propertyUri) const
{
for (const auto&fileProperty : fileProperties())
{
if (fileProperty->patchProperty() == propertyUri)
{
return fileProperty.get();
}
}
return nullptr;
}
private:
std::vector<PiPedalFileProperty::ptr> fileProperites_;
std::vector<UiFileProperty::ptr> fileProperites_;
std::vector<UiPortNotification::ptr> portNotifications_;
};
// Utiltities for validating file paths received via PiPedalFileProperty-related APIs.
+270 -202
View File
@@ -18,19 +18,20 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include <lilv/lilv.h>
#include <stdexcept>
#include <locale>
#include <string_view>
#include "Lv2Log.hpp"
#include <functional>
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include "Lv2Effect.hpp"
#include "Lv2PedalBoard.hpp"
#include "Lv2Pedalboard.hpp"
#include "OptionsFeature.hpp"
#include "JackConfiguration.hpp"
#include "lv2/urid.lv2/urid.h"
#include "lv2/ui.lv2/ui.h"
#include "lv2.h"
#include "lv2/atom.lv2/atom.h"
#include "lv2/time/time.h"
@@ -86,27 +87,37 @@ namespace pipedal
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 PiPedalHost::Urids
class PluginHost::Urids
{
public:
Urids(MapFeature &mapFeature)
{
atom_Float = mapFeature.GetUrid(LV2_ATOM__Float);
ui__portNotification = mapFeature.GetUrid(LV2_UI__portNotification);
ui__plugin = mapFeature.GetUrid(LV2_UI__plugin);
ui__protocol = mapFeature.GetUrid(LV2_UI__protocol);
ui__floatProtocol = mapFeature.GetUrid(LV2_UI__protocol);
ui__peakProtocol = mapFeature.GetUrid(LV2_UI__peakProtocol);
}
LV2_URID atom_Float;
LV2_URID ui__portNotification;
LV2_URID ui__plugin;
LV2_URID ui__protocol;
LV2_URID ui__floatProtocol;
LV2_URID ui__peakProtocol;
};
}
void PiPedalHost::SetConfiguration(const PiPedalConfiguration &configuration)
void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration)
{
this->vst3CachePath =
std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json";
this->vst3Enabled = configuration.IsVst3Enabled();
}
void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
{
rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI);
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);
@@ -126,15 +137,35 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
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__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
pipedalUI__directory = lilv_new_uri(pWorld,PIPEDAL_UI__directory);
pipedalUI__fileTypes = lilv_new_uri(pWorld,PIPEDAL_UI__fileTypes);
pipedalUI__fileProperty = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperty);
pipedalUI__directory = lilv_new_uri(pWorld, PIPEDAL_UI__directory);
pipedalUI__fileTypes = lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
pipedalUI__fileProperty = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperty);
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text);
pipedalUI__defaultFile = lilv_new_uri(pWorld, PIPEDAL_UI__defaultFile);
ui__portNotification = lilv_new_uri(pWorld, LV2_UI__portNotification);
ui__plugin = lilv_new_uri(pWorld, LV2_UI__plugin);
ui__protocol = lilv_new_uri(pWorld, LV2_UI__protocol);
ui__floatProtocol = lilv_new_uri(pWorld, LV2_UI__protocol);
ui__peakProtocol = lilv_new_uri(pWorld, LV2_UI__peakProtocol);
ui__portIndex = lilv_new_uri(pWorld,LV2_UI__portIndex);
lv2__symbol = lilv_new_uri(pWorld,LV2_CORE__symbol);
// ui:portNotification
// [
// ui:portIndex 3;
// ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
// ui:protocol ui:floatProtocol;
// // pipedal_ui:style pipedal_ui:text ;
// // pipedal_ui:redLevel 0;
// // pipedal_ui:yellowLevel -12;
// ]
time_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
@@ -144,7 +175,7 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
}
void PiPedalHost::LilvUris::Free()
void PluginHost::LilvUris::Free()
{
rdfsComment.Free();
logarithic_uri.Free();
@@ -214,45 +245,56 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0)
return default_;
}
PiPedalHost::PiPedalHost()
void PluginHost::SetPluginStoragePath(const std::filesystem::path &path)
{
mapPathFeature.SetPluginStoragePath(path);
}
PluginHost::PluginHost()
: mapPathFeature("")
{
pWorld = nullptr;
LV2_Feature **features = new LV2_Feature *[10];
features[0] = const_cast<LV2_Feature *>(mapFeature.GetMapFeature());
features[1] = const_cast<LV2_Feature *>(mapFeature.GetUnmapFeature());
lv2Features.push_back(mapFeature.GetMapFeature());
lv2Features.push_back(mapFeature.GetUnmapFeature());
logFeature.Prepare(&mapFeature);
features[2] = const_cast<LV2_Feature *>(logFeature.GetFeature());
lv2Features.push_back(logFeature.GetFeature());
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
features[3] = const_cast<LV2_Feature *>(optionsFeature.GetFeature());
features[4] = nullptr;
this->lv2Features = features;
mapPathFeature.Prepare(&mapFeature);
lv2Features.push_back(mapPathFeature.GetMapPathFeature());
lv2Features.push_back(mapPathFeature.GetMakePathFeature());
lv2Features.push_back(optionsFeature.GetFeature());
lv2Features.push_back(nullptr);
this->urids = new Urids(mapFeature);
pHostWorkerThread = std::make_shared<HostWorkerThread>();
}
void PiPedalHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
{
this->sampleRate = configuration.GetSampleRate();
this->sampleRate = configuration.sampleRate();
if (configuration.isValid())
{
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
this->maxBufferSize = configuration.GetBlockLength();
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize());
this->maxBufferSize = configuration.blockLength();
optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize());
}
}
PiPedalHost::~PiPedalHost()
PluginHost::~PluginHost()
{
delete[] lv2Features;
lilvUris.Free();
free_world();
delete urids;
}
void PiPedalHost::free_world()
void PluginHost::free_world()
{
if (pWorld)
{
@@ -260,7 +302,7 @@ void PiPedalHost::free_world()
pWorld = nullptr;
}
}
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &uri) const
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const std::string &uri) const
{
auto it = this->classesMap.find(uri);
if (it == this->classesMap.end())
@@ -268,7 +310,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &u
return (*it).second;
}
void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
void PluginHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
{
this->classesMap[pluginClass->uri()] = pluginClass;
for (int i = 0; i < pluginClass->children_.size(); ++i)
@@ -278,7 +320,7 @@ void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClas
child->parent_ = pluginClass.get();
}
}
void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
void PluginHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
{
std::ifstream f(jsonFile);
json_reader reader(f);
@@ -292,7 +334,7 @@ void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
MAP_CHECK();
}
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClass *pClass)
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const LilvPluginClass *pClass)
{
MAP_CHECK();
@@ -300,7 +342,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClas
std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri];
return pResult;
}
std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginClass *pClass)
std::shared_ptr<Lv2PluginClass> PluginHost::MakePluginClass(const LilvPluginClass *pClass)
{
std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
auto t = this->classesMap.find(uri);
@@ -320,7 +362,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginCla
return result;
}
void PiPedalHost::LoadPluginClassesFromLilv()
void PluginHost::LoadPluginClassesFromLilv()
{
const auto classes = lilv_world_get_plugin_classes(pWorld);
@@ -338,19 +380,19 @@ void PiPedalHost::LoadPluginClassesFromLilv()
if (lv2Class && lv2Class->parent_uri().size() != 0)
{
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
if (parentClass)
{
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
ccClass->set_parent(parentClass);
Lv2PluginClass *ccParentClass = parentClass.get();
ccParentClass->add_child(lv2Class);
}
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
if (parentClass)
{
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
ccClass->set_parent(parentClass);
Lv2PluginClass *ccParentClass = parentClass.get();
ccParentClass->add_child(lv2Class);
}
}
}
}
void PiPedalHost::Load(const char *lv2Path)
void PluginHost::Load(const char *lv2Path)
{
this->plugins_.clear();
@@ -388,30 +430,30 @@ void PiPedalHost::Load(const char *lv2Path)
if (pluginInfo->hasCvPorts())
{
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
}
#if !SUPPORT_MIDI
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN)
{
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
}
#endif
else if (!pluginInfo->is_valid())
{
auto &ports = pluginInfo->ports();
for (int i = 0; i < ports.size(); ++i)
{
auto &port = ports[i];
if (!port->is_valid())
auto &ports = pluginInfo->ports();
for (int i = 0; i < ports.size(); ++i)
{
Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str());
auto &port = ports[i];
if (!port->is_valid())
{
Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str());
}
}
}
Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
}
else
{
this->plugins_.push_back(pluginInfo);
this->plugins_.push_back(pluginInfo);
}
}
auto messages = stdoutCapture.GetOutputLines();
@@ -440,28 +482,28 @@ void PiPedalHost::Load(const char *lv2Path)
if (plugin->is_valid())
{
#if SUPPORT_MIDI
if (info.audio_inputs() == 0 && !info.has_midi_input())
{
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
}
else if (info.audio_outputs() == 0 && !info.has_midi_output())
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
}
if (info.audio_inputs() == 0 && !info.has_midi_input())
{
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
}
else if (info.audio_outputs() == 0 && !info.has_midi_output())
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
}
#else
if (info.audio_inputs() == 0)
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
}
else if (info.audio_outputs() == 0)
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
}
if (info.audio_inputs() == 0)
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
}
else if (info.audio_outputs() == 0)
{
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
}
#endif
else
{
ui_plugins_.push_back(std::move(info));
}
else
{
ui_plugins_.push_back(std::move(info));
}
}
}
@@ -476,8 +518,8 @@ void PiPedalHost::Load(const char *lv2Path)
const auto &vst3PluginList = this->vst3Host->getPluginList();
for (const auto &vst3Plugin : vst3PluginList)
{
// copy not move!
ui_plugins_.push_back(vst3Plugin->pluginInfo_);
// copy not move!
ui_plugins_.push_back(vst3Plugin->pluginInfo_);
}
auto ui_compare = [&collation](
Lv2PluginUiInfo &left,
@@ -532,10 +574,10 @@ static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
return result;
}
const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
"comment";
const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
"comment";
LilvNode *PiPedalHost::get_comment(const std::string &uri)
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);
@@ -547,7 +589,7 @@ static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shar
return p1->index() < p2->index();
}
bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin)
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
{
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
bool result = false;
@@ -559,8 +601,21 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl
return result;
}
Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
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.");
std::string bundleUri = bundleUriNode.AsUri();
std::string bundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr);
if (bundlePath.length() == 0)
throw std::logic_error("Bundle uri is not a file uri.");
this->bundle_path_ = bundlePath;
this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin);
this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin));
@@ -604,15 +659,15 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
std::shared_ptr<Lv2PortInfo> portInfo = std::make_shared<Lv2PortInfo>(lv2Host, pPlugin, pPort);
if (!portInfo->is_valid())
{
isValid = false;
isValid = false;
}
const auto &portGroup = portInfo->port_group();
if (portGroup.size() != 0)
{
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
{
portGroups.push_back(portGroup);
}
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
{
portGroups.push_back(portGroup);
}
}
ports_.push_back(std::move(portInfo));
}
@@ -626,8 +681,8 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
{
if (!this->IsSupportedFeature(this->required_features_[i]))
{
Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
isValid = false;
Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
isValid = false;
}
}
@@ -642,7 +697,7 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
nullptr);
if (pipedalUINode)
{
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode);
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
}
// xxx lilv_world_get(pWorld,pluginUri,);
// for (auto&portInfo: ports_)
@@ -666,7 +721,12 @@ std::vector<std::string> supportedFeatures = {
LV2_BUF_SIZE__fixedBlockLength,
LV2_BUF_SIZE__powerOf2BlockLength,
LV2_CORE__isLive,
LV2_STATE__loadDefaultState
LV2_STATE__loadDefaultState,
LV2_STATE__makePath,
LV2_STATE__mapPath,
// UI features that we can ignore, since we won't load their ui.
"http://lv2plug.in/ns/extensions/ui#makeResident",
};
@@ -675,7 +735,7 @@ bool Lv2PluginInfo::IsSupportedFeature(const std::string &feature) const
for (int i = 0; i < supportedFeatures.size(); ++i)
{
if (supportedFeatures[i] == feature)
return true;
return true;
}
return false;
}
@@ -683,7 +743,7 @@ Lv2PluginInfo::~Lv2PluginInfo()
{
}
bool Lv2PortInfo::is_a(PiPedalHost *lv2Plugins, const char *classUri)
bool Lv2PortInfo::is_a(PluginHost *lv2Plugins, const char *classUri)
{
return classes_.is_a(lv2Plugins, classUri);
}
@@ -705,7 +765,7 @@ static bool scale_points_sort_compare(const Lv2ScalePoint &v1, const Lv2ScalePoi
{
return v1.value() < v2.value();
}
Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
{
auto pWorld = host->pWorld;
index_ = lilv_port_get_index(plugin, pPort);
@@ -720,7 +780,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
min_value_ = 0;
max_value_ = 1;
default_value_ = 0;
lilv_port_get_range(plugin, pPort, &defaultNode.Get(), &minNode.Get(), &maxNode.Get());
lilv_port_get_range(plugin, pPort, defaultNode, minNode, maxNode);
if (defaultNode)
{
default_value_ = getFloat(defaultNode);
@@ -748,7 +808,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
auto priority_node = lilv_nodes_get_first(priority_nodes);
if (priority_node)
{
this->display_priority_ = lilv_node_as_int(priority_node);
this->display_priority_ = lilv_node_as_int(priority_node);
}
}
@@ -759,7 +819,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
auto range_steps_node = lilv_nodes_get_first(range_steps_nodes);
if (range_steps_node)
{
this->range_steps_ = lilv_node_as_int(range_steps_node);
this->range_steps_ = lilv_node_as_int(range_steps_node);
}
}
this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.integer_property_uri);
@@ -819,7 +879,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
((is_input_ || is_output_) && (is_audio_port_ || is_atom_port_ || is_cv_port_));
}
Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
Lv2PluginClasses PluginHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
{
std::vector<std::string> result;
const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort);
@@ -827,9 +887,9 @@ Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, c
{
LILV_FOREACH(nodes, iNode, nodes)
{
const LilvNode *node = lilv_nodes_get(nodes, iNode);
std::string classUri = nodeAsString(node);
result.push_back(classUri);
const LilvNode *node = lilv_nodes_get(nodes, iNode);
std::string classUri = nodeAsString(node);
result.push_back(classUri);
}
}
return Lv2PluginClasses(result);
@@ -851,20 +911,20 @@ bool Lv2PluginClass::is_a(const std::string &classUri) const
}
return false;
}
bool Lv2PluginClasses::is_a(PiPedalHost *lv2Plugins, const char *classUri) const
bool Lv2PluginClasses::is_a(PluginHost *lv2Plugins, const char *classUri) const
{
std::string classUri_(classUri);
for (auto i : classes_)
{
if (lv2Plugins->is_a(classUri_, i))
{
return true;
return true;
}
}
return false;
}
bool PiPedalHost::is_a(const std::string &class_, const std::string &target_class)
bool PluginHost::is_a(const std::string &class_, const std::string &target_class)
{
if (class_ == target_class)
return true;
@@ -876,7 +936,7 @@ bool PiPedalHost::is_a(const std::string &class_, const std::string &target_clas
return false;
}
Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin)
Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
: uri_(plugin->uri()),
name_(plugin->name()),
author_name_(plugin->author_name()),
@@ -898,38 +958,38 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
{
PLUGIN_MAP_CHECK();
this->plugin_display_type_ = "Plugin";
Lv2Log::error("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str());
Lv2Log::warning("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str());
}
for (auto port : plugin->ports())
{
if (port->is_input())
{
if (port->is_control_port() && port->is_input())
{
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
}
else if (port->is_atom_port())
{
if (port->supports_midi())
if (port->is_control_port() && port->is_input())
{
has_midi_input_ = true;
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
}
else if (port->is_atom_port())
{
if (port->supports_midi())
{
has_midi_input_ = true;
}
}
else if (port->is_audio_port())
{
++audio_inputs_;
}
}
else if (port->is_audio_port())
{
++audio_inputs_;
}
}
else if (port->is_output())
{
if (port->is_atom_port() && port->supports_midi())
{
this->has_midi_output_ = true;
}
else if (port->is_audio_port())
{
++audio_outputs_;
}
if (port->is_atom_port() && port->supports_midi())
{
this->has_midi_output_ = true;
}
else if (port->is_audio_port())
{
++audio_outputs_;
}
}
}
for (auto &portGroup : plugin->port_groups())
@@ -941,55 +1001,56 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
if (piPedalUI)
{
this->fileProperties_ = piPedalUI->fileProperties();
this->uiPortNotifications_ = piPedalUI->portNotifications();
}
}
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetLv2PluginClass() const
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
{
return this->GetPluginClass(LV2_PLUGIN);
}
std::shared_ptr<Lv2PluginInfo> PiPedalHost::GetPluginInfo(const std::string &uri) const
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
{
for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i)
{
if ((*i)->uri() == uri)
{
return (*i);
return (*i);
}
}
return nullptr;
}
Lv2PedalBoard *PiPedalHost::CreateLv2PedalBoard(PedalBoard &pedalBoard)
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard)
{
Lv2PedalBoard *pPedalBoard = new Lv2PedalBoard();
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
try
{
pPedalBoard->Prepare(this, pedalBoard);
return pPedalBoard;
pPedalboard->Prepare(this, pedalboard);
return pPedalboard;
}
catch (const std::exception &)
catch (const std::exception &e)
{
delete pPedalBoard;
delete pPedalboard;
throw;
}
}
struct StateCallbackData
{
PiPedalHost *pHost;
PluginHost *pHost;
std::vector<ControlValue> *pResult;
};
void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol,
void *user_data,
const void *value,
uint32_t size,
uint32_t type)
void PluginHost::fn_LilvSetPortValueFunc(const char *port_symbol,
void *user_data,
const void *value,
uint32_t size,
uint32_t type)
{
StateCallbackData *pData = (StateCallbackData *)user_data;
PiPedalHost *pHost = pData->pHost;
PluginHost *pHost = pData->pHost;
std::vector<ControlValue> *pResult = pData->pResult;
LV2_URID valueType = (LV2_URID)type;
if (valueType != pHost->urids->atom_Float)
@@ -999,15 +1060,15 @@ void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol,
pResult->push_back(ControlValue(port_symbol, *(float *)value));
}
std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
PedalBoardItem *pedalBoardItem, const std::string &presetUri)
std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
PedalboardItem *pedalboardItem, const std::string &presetUri)
{
std::vector<ControlValue> result;
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalBoardItem->uri().c_str());
AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalboardItem->uri().c_str());
lilv_world_load_resource(pWorld, uriNode);
@@ -1027,27 +1088,27 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
if (presetUri == thisPresetUri)
{
/*********************************/
/*********************************/
// AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
// AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
if (lilvState == nullptr)
{
throw PiPedalStateException("Preset not found.");
}
if (lilvState == nullptr)
{
throw PiPedalStateException("Preset not found.");
}
StateCallbackData cbData;
cbData.pHost = this;
cbData.pResult = &result;
auto n = lilv_state_get_num_properties(lilvState);
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
StateCallbackData cbData;
cbData.pHost = this;
cbData.pResult = &result;
auto n = lilv_state_get_num_properties(lilvState);
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
lilv_state_free(lilvState);
break;
lilv_state_free(lilvState);
break;
/*********************************/
/*********************************/
}
}
lilv_nodes_free(presets);
@@ -1057,15 +1118,15 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
struct PresetCallbackState
{
PiPedalHost *pHost;
PluginHost *pHost;
std::map<std::string, float> *values;
bool failed = false;
};
void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
void PluginHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
{
PresetCallbackState *pState = static_cast<PresetCallbackState *>(user_data);
PiPedalHost *pHost = pState->pHost;
PluginHost *pHost = pState->pHost;
if (type == pHost->urids->atom_Float)
{
@@ -1076,7 +1137,7 @@ void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const v
pState->failed = true;
}
}
PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
{
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
@@ -1102,36 +1163,36 @@ PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
LilvState *state = lilv_state_new_from_world(pWorld, this->mapFeature.GetMap(), preset);
if (state != nullptr)
{
std::string label = lilv_state_get_label(state);
std::map<std::string, float> controlValues;
PresetCallbackState cbData{this, &controlValues, false};
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
lilv_state_free(state);
std::string label = lilv_state_get_label(state);
std::map<std::string, float> controlValues;
PresetCallbackState cbData{this, &controlValues, false};
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
lilv_state_free(state);
if (!cbData.failed)
{
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
}
if (!cbData.failed)
{
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
}
}
}
lilv_nodes_free(presets);
return result;
}
IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
IEffect *PluginHost::CreateEffect(PedalboardItem &pedalboardItem)
{
if (pedalBoardItem.uri().starts_with("vst3:"))
if (pedalboardItem.uri().starts_with("vst3:"))
{
#if ENABLE_VST3
try
{
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this);
return vst3Plugin.release();
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalboardItem, this);
return vst3Plugin.release();
}
catch (const std::exception &e)
{
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
throw;
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
throw;
}
#else
Lv2Log::error(std::string("VST3 support not enabled at compile time."));
@@ -1141,15 +1202,15 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
}
else
{
auto info = this->GetPluginInfo(pedalBoardItem.uri());
auto info = this->GetPluginInfo(pedalboardItem.uri());
if (!info)
return nullptr;
return nullptr;
return new Lv2Effect(this, info, pedalBoardItem);
return new Lv2Effect(this, info, pedalboardItem);
}
}
Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
{
LilvWorld *pWorld = lv2Host->pWorld;
@@ -1161,11 +1222,15 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
name_ = nodeAsString(nameNode);
}
void PiPedalHostLogError(const std::string&errror)
std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread()
{
return pHostWorkerThread;
}
// void PiPedalHostLogError(const std::string &error)
// {
// Lv2Log::error("%s",error.c_str());
// }
#define MAP_REF(class, name) \
json_map::reference(#name, &class ::name##_)
@@ -1231,6 +1296,7 @@ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
}};
json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_),
json_map::reference("uri", &Lv2PluginInfo::uri_),
json_map::reference("name", &Lv2PluginInfo::name_),
json_map::reference("plugin_class", &Lv2PluginInfo::plugin_class_),
@@ -1290,20 +1356,22 @@ json_map::storage_type<Lv2PluginUiControlPort> Lv2PluginUiControlPort::jmap{{
}};
json_map::storage_type<Lv2PluginUiInfo> Lv2PluginUiInfo::jmap{{
json_map::reference("uri", &Lv2PluginUiInfo::uri_),
json_map::reference("name", &Lv2PluginUiInfo::name_),
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
json_map::reference("description", &Lv2PluginUiInfo::description_),
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
json_map::reference("controls", &Lv2PluginUiInfo::controls_),
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
json_map::reference("fileProperties",&Lv2PluginUiInfo::fileProperties_)
}};
json_map::storage_type<Lv2PluginUiInfo>
Lv2PluginUiInfo::jmap{
{json_map::reference("uri", &Lv2PluginUiInfo::uri_),
json_map::reference("name", &Lv2PluginUiInfo::name_),
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
json_map::reference("description", &Lv2PluginUiInfo::description_),
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
json_map::reference("controls", &Lv2PluginUiInfo::controls_),
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
json_map::reference("fileProperties", &Lv2PluginUiInfo::fileProperties_),
json_map::reference("uiPortNotifications", &Lv2PluginUiInfo::uiPortNotifications_),
}};
+57 -57
View File
@@ -23,7 +23,7 @@
#include <memory>
#include "json.hpp"
#include "PluginType.hpp"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include <lilv/lilv.h>
#include "MapFeature.hpp"
#include "LogFeature.hpp"
@@ -31,6 +31,7 @@
#include <filesystem>
#include <cmath>
#include <string>
#include "IHost.hpp"
#include "lv2.h"
#include "Units.hpp"
@@ -40,14 +41,15 @@
#include "PiPedalConfiguration.hpp"
#include "AutoLilvNode.hpp"
#include "PiPedalUI.hpp"
#include "MapPathFeature.hpp"
namespace pipedal
{
// forward declarations
class Lv2Effect;
class Lv2PedalBoard;
class PiPedalHost;
class Lv2Pedalboard;
class PluginHost;
class JackConfiguration;
class JackChannelSelection;
@@ -82,7 +84,7 @@ namespace pipedal
class Lv2PluginClass
{
public:
friend class PiPedalHost;
friend class PluginHost;
private:
Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED!
@@ -92,7 +94,7 @@ namespace pipedal
PluginType plugin_type_;
std::vector<std::shared_ptr<Lv2PluginClass>> children_;
friend class ::pipedal::PiPedalHost;
friend class ::pipedal::PluginHost;
// hide copy constructor.
Lv2PluginClass(const Lv2PluginClass &other)
{
@@ -151,7 +153,7 @@ namespace pipedal
{
return classes_;
}
bool is_a(PiPedalHost *lv2Plugins, const char *classUri) const;
bool is_a(PluginHost *lv2Plugins, const char *classUri) const;
static json_map::storage_type<Lv2PluginClasses> jmap;
};
@@ -186,7 +188,7 @@ namespace pipedal
class Lv2PortInfo
{
public:
Lv2PortInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort);
Lv2PortInfo(PluginHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort);
private:
friend class Lv2PluginInfo;
@@ -303,7 +305,7 @@ namespace pipedal
public:
Lv2PortInfo() {}
~Lv2PortInfo() = default;
bool is_a(PiPedalHost *lv2Plugins, const char *classUri);
bool is_a(PluginHost *lv2Plugins, const char *classUri);
static json_map::storage_type<Lv2PortInfo> jmap;
};
@@ -321,7 +323,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(name);
Lv2PortGroup() {}
Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri);
Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri);
static json_map::storage_type<Lv2PortGroup> jmap;
};
@@ -329,14 +331,15 @@ namespace pipedal
class Lv2PluginInfo
{
private:
friend class PiPedalHost;
friend class PluginHost;
public:
Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {}
private:
bool HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin);
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
std::string bundle_path_;
std::string uri_;
std::string name_;
std::string plugin_class_;
@@ -358,6 +361,7 @@ namespace pipedal
bool IsSupportedFeature(const std::string &feature) const;
public:
LV2_PROPERTY_GETSET(bundle_path)
LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name)
LV2_PROPERTY_GETSET(plugin_class)
@@ -537,7 +541,7 @@ namespace pipedal
{
public:
Lv2PluginUiInfo() {}
Lv2PluginUiInfo(PiPedalHost *pPlugins, const Lv2PluginInfo *plugin);
Lv2PluginUiInfo(PluginHost *pPlugins, const Lv2PluginInfo *plugin);
private:
std::string uri_;
@@ -555,7 +559,8 @@ namespace pipedal
std::vector<Lv2PluginUiControlPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<PiPedalFileProperty::ptr> fileProperties_;
std::vector<UiFileProperty::ptr> fileProperties_;
std::vector<UiPortNotification::ptr> uiPortNotifications_;
public:
LV2_PROPERTY_GETSET(uri)
@@ -573,32 +578,11 @@ namespace pipedal
LV2_PROPERTY_GETSET(port_groups)
LV2_PROPERTY_GETSET_SCALAR(is_vst3)
LV2_PROPERTY_GETSET(fileProperties)
LV2_PROPERTY_GETSET(uiPortNotifications)
static json_map::storage_type<Lv2PluginUiInfo> jmap;
};
class IHost
{
public:
virtual LilvWorld *getWorld() = 0;
virtual LV2_URID_Map *GetLv2UridMap() = 0;
virtual LV2_URID GetLv2Urid(const char *uri) = 0;
virtual std::string Lv2UriudToString(LV2_URID urid) = 0;
virtual LV2_Feature *const *GetLv2Features() const = 0;
virtual double GetSampleRate() const = 0;
virtual void SetMaxAudioBufferSize(size_t size) = 0;
virtual size_t GetMaxAudioBufferSize() const = 0;
virtual size_t GetAtomBufferSize() const = 0;
virtual bool HasMidiInputChannel() const = 0;
virtual int GetNumberOfInputAudioChannels() const = 0;
virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
virtual IEffect *CreateEffect(PedalBoardItem &pedalBoard) = 0;
};
}
#if ENABLE_VST3
@@ -608,7 +592,7 @@ namespace pipedal
namespace pipedal
{
class PiPedalHost : private IHost
class PluginHost : private IHost
{
private:
#if ENABLE_VST3
@@ -618,6 +602,7 @@ namespace pipedal
friend class pipedal::AutoLilvNode;
friend class pipedal::PiPedalUI;
static const char *RDFS_COMMENT_URI;
public:
class LilvUris
{
@@ -648,7 +633,6 @@ namespace pipedal
AutoLilvNode pipedalUI__fileProperties;
AutoLilvNode pipedalUI__directory;
AutoLilvNode pipedalUI__patchProperty;
AutoLilvNode pipedalUI__defaultFile;
AutoLilvNode pipedalUI__fileProperty;
@@ -665,11 +649,18 @@ namespace pipedal
AutoLilvNode appliesTo;
AutoLilvNode isA;
AutoLilvNode ui__portNotification;
AutoLilvNode ui__plugin;
AutoLilvNode ui__protocol;
AutoLilvNode ui__floatProtocol;
AutoLilvNode ui__peakProtocol;
AutoLilvNode ui__portIndex;
AutoLilvNode lv2__symbol;
};
LilvUris lilvUris;
private:
bool vst3Enabled = true;
LilvNode *get_comment(const std::string &uri);
@@ -683,10 +674,11 @@ namespace pipedal
std::string vst3CachePath;
LV2_Feature *const *lv2Features = nullptr;
std::vector<const LV2_Feature *> lv2Features;
MapFeature mapFeature;
LogFeature logFeature;
OptionsFeature optionsFeature;
MapPathFeature mapPathFeature;
static void fn_LilvSetPortValueFunc(const char *port_symbol,
void *user_data,
@@ -714,21 +706,21 @@ namespace pipedal
// IHost implementation
public:
void LogError(const std::string&message)
void LogError(const std::string &message)
{
logFeature.LogError("%s",message.c_str());
logFeature.LogError("%s", message.c_str());
}
void LogWarning(const std::string&message)
void LogWarning(const std::string &message)
{
logFeature.LogWarning("%s",message.c_str());
logFeature.LogWarning("%s", message.c_str());
}
void LogNote(const std::string&message)
void LogNote(const std::string &message)
{
logFeature.LogNote("%s",message.c_str());
logFeature.LogNote("%s", message.c_str());
}
void LogTrace(const std::string&message)
void LogTrace(const std::string &message)
{
logFeature.LogTrace("%s",message.c_str());
logFeature.LogTrace("%s", message.c_str());
}
virtual LilvWorld *getWorld()
{
@@ -736,33 +728,41 @@ namespace pipedal
}
private:
std::shared_ptr<HostWorkerThread> pHostWorkerThread;
// IHost implementation.
virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; }
virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; }
virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; }
virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; }
virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; }
virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; }
virtual LV2_Feature *const *GetLv2Features() const { return this->lv2Features; }
virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); }
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread();
public:
virtual MapFeature &GetMapFeature() { return this->mapFeature; }
private:
virtual LV2_URID_Map *GetLv2UridMap()
{
return this->mapFeature.GetMap();
}
static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type);
virtual IEffect *CreateEffect(PedalBoardItem &pedalBoardItem);
virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem);
void LoadPluginClassesFromLilv();
void AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass);
public:
PiPedalHost();
PluginHost();
void SetPluginStoragePath(const std::filesystem::path &path);
void SetConfiguration(const PiPedalConfiguration &configuration);
virtual ~PiPedalHost();
virtual ~PluginHost();
IHost *asIHost() { return this; }
virtual Lv2PedalBoard *CreateLv2PedalBoard(PedalBoard &pedalBoard);
virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard);
void setSampleRate(double sampleRate)
{
@@ -792,19 +792,19 @@ namespace pipedal
static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2";
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
void Load(const char *lv2Path = PiPedalHost::DEFAULT_LV2_PATH);
void Load(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
virtual LV2_URID GetLv2Urid(const char *uri)
{
return this->mapFeature.GetUrid(uri);
}
virtual std::string Lv2UriudToString(LV2_URID urid)
virtual std::string Lv2UridToString(LV2_URID urid)
{
return this->mapFeature.UridToString(urid);
}
PluginPresets GetFactoryPluginPresets(const std::string &pluginUri);
std::vector<ControlValue> LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem,
std::vector<ControlValue> LoadFactoryPluginPreset(PedalboardItem *pedalboardItem,
const std::string &presetUri);
};
+1
View File
@@ -27,6 +27,7 @@ JSON_MAP_BEGIN(PluginPreset)
JSON_MAP_REFERENCE(PluginPreset,instanceId)
JSON_MAP_REFERENCE(PluginPreset,label)
JSON_MAP_REFERENCE(PluginPreset,controlValues)
JSON_MAP_REFERENCE(PluginPreset,state)
JSON_MAP_END()
JSON_MAP_BEGIN(PluginPresets)
+3
View File
@@ -23,6 +23,8 @@
#include "PiPedalException.hpp"
#include <utility>
#include <map>
#include <memory>
#include "StateInterface.hpp"
namespace pipedal {
@@ -87,6 +89,7 @@ public:
uint64_t instanceId_;
std::string label_;
std::map<std::string,float> controlValues_;
Lv2PluginState state_;
DECLARE_JSON_MAP(PluginPreset);
};
+8 -8
View File
@@ -27,17 +27,17 @@ JSON_MAP_BEGIN(PedalPreset)
JSON_MAP_REFERENCE(PedalPreset,values)
JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoardPreset)
JSON_MAP_REFERENCE(PedalBoardPreset,instanceId)
JSON_MAP_REFERENCE(PedalBoardPreset,displayName)
JSON_MAP_REFERENCE(PedalBoardPreset,values)
JSON_MAP_BEGIN(PedalboardPreset)
JSON_MAP_REFERENCE(PedalboardPreset,instanceId)
JSON_MAP_REFERENCE(PedalboardPreset,displayName)
JSON_MAP_REFERENCE(PedalboardPreset,values)
JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoardPresets)
JSON_MAP_REFERENCE(PedalBoardPresets,nextInstanceId)
JSON_MAP_REFERENCE(PedalBoardPresets,currentPreset)
JSON_MAP_REFERENCE(PedalBoardPresets,presets)
JSON_MAP_BEGIN(PedalboardPresets)
JSON_MAP_REFERENCE(PedalboardPresets,nextInstanceId)
JSON_MAP_REFERENCE(PedalboardPresets,currentPreset)
JSON_MAP_REFERENCE(PedalboardPresets,presets)
JSON_MAP_END()
+6 -6
View File
@@ -19,7 +19,7 @@
#pragma once
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include "json.hpp"
namespace pipedal {
@@ -32,21 +32,21 @@ public:
DECLARE_JSON_MAP(PedalPreset);
};
class PedalBoardPreset {
class PedalboardPreset {
uint64_t instanceId_;
std::string displayName_;
std::vector<std::unique_ptr<PedalPreset> > values_;
public:
DECLARE_JSON_MAP(PedalBoardPreset);
DECLARE_JSON_MAP(PedalboardPreset);
};
class PedalBoardPresets {
class PedalboardPresets {
long nextInstanceId_ = 0;
long currentPreset_ = 0;
std::vector<std::unique_ptr<PedalBoardPreset> > presets_;
std::vector<std::unique_ptr<PedalboardPreset> > presets_;
public:
DECLARE_JSON_MAP(PedalBoardPresets);
DECLARE_JSON_MAP(PedalboardPresets);
};
+493
View File
@@ -0,0 +1,493 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <functional>
#include <mutex>
#include <stdexcept>
#include <condition_variable>
namespace pipedal
{
// thread-safe javascript-like future.
template <typename T>
class Promise;
template <typename T>
class PromiseInner;
template <typename T>
using ResolveFunction = std::function<void(const T &value)>;
using RejectFunction = std::function<void(const std::string &message)>;
template <typename T>
using WorkerFunction = std::function<void(ResolveFunction<T>, RejectFunction reject)>;
using CatchFunction = std::function<void(const std::string &message)>;
class PromiseInnerBase
{
public:
static int64_t allocationCount; // not thread-safe. for testing purposes only.
public:
virtual void Cancel() = 0;
protected:
virtual ~PromiseInnerBase()
{
if (this->inputPromise)
{
this->inputPromise->ReleaseRef();
}
}
void AddRef()
{
std::lock_guard lock{mutex};
++referenceCount;
}
void ReleaseRef()
{
bool deleteMe = false;
{
std::lock_guard lock{mutex};
if (referenceCount == 0)
{
throw std::logic_error("Reference count blown.");
}
--referenceCount;
deleteMe = (referenceCount == 0); // can't do it while holding the mutex.
}
if (deleteMe)
{
delete this;
}
}
void SetInputPromise(PromiseInnerBase *promise)
{
PromiseInnerBase *oldPromise;
{
std::lock_guard lock{mutex};
oldPromise = this->inputPromise;
this->inputPromise = promise;
}
if (promise)
{
promise->AddRef();
}
if (oldPromise)
{
oldPromise->ReleaseRef();
}
}
protected:
PromiseInnerBase *inputPromise = nullptr;
int referenceCount = 0;
std::mutex mutex;
};
inline int64_t PromiseInnerBase::allocationCount = 0;
template <typename T>
class PromiseInner : PromiseInnerBase
{
private:
friend class Promise<T>;
using ResolveFunction = pipedal::ResolveFunction<T>;
using WorkerFunction = pipedal::WorkerFunction<T>;
using ThenFunction = std::function<void(const T &value)>;
PromiseInner()
{
++allocationCount;
referenceCount = 1; // one for the owner, none for the worker (yet)
}
void Work(WorkerFunction work)
{
{
std::lock_guard lock{mutex};
if (cancelled)
return;
++referenceCount;
this->worker = work;
this->hasWorker = true;
}
try
{
worker(
[this](const T &value)
{ this->Resolve(value); },
[this](const std::string &message)
{ this->Reject(message); });
}
catch (const std::exception &e)
{
Reject(e.what());
}
}
PromiseInner(WorkerFunction work)
{
++allocationCount;
referenceCount = 2; // one for the owner, one for the worker.
this->worker = work;
this->hasWorker = true;
try
{
worker(
[this](const T &value)
{ this->Resolve(value); },
[this](const std::string &message)
{ this->Reject(message); });
}
catch (const std::exception &e)
{
Reject(e.what());
}
}
~PromiseInner()
{
delete conditionVariable;
--allocationCount;
}
T Get()
{
while (true)
{
std::unique_lock lock{mutex};
if (this->resolved || this->cancelled)
break;
if (conditionVariable != nullptr)
{
conditionVariable = new std::condition_variable();
}
conditionVariable->wait(lock);
}
ReleaseFunctions();
if (cancelled)
{
throw std::logic_error("Cancelled.");
}
if (rejected)
{
throw std::logic_error(catchMessage);
}
return resolvedValue;
}
void Resolve(const T &value)
{
resolvedValue = value;
bool fireResult = false;
std::condition_variable *conditionVariable = nullptr;
bool releaseRef = false;
{
std::lock_guard lock{mutex};
if (!resolved)
{
resolved = true;
releaseRef = true;
if (hasThenFunction && !cancelled)
{
fireResult = true;
}
conditionVariable = this->conditionVariable;
}
}
if (fireResult)
{
thenFunction(resolvedValue);
ReleaseFunctions();
thenFunction = nullptr;
catchFunction = nullptr;
}
if (conditionVariable)
{
conditionVariable->notify_all();
}
if (releaseRef)
{
ReleaseRef();
}
}
void Reject(const std::string &message)
{
bool fireCatch = false;
bool releaseRef = false;
std::condition_variable *conditionVariable = nullptr;
{
std::lock_guard lock{mutex};
if (!resolved)
{
releaseRef = hasWorker;
resolved = true;
rejected = true;
this->catchMessage = message;
if (hasCatchFunction && !cancelled)
{
fireCatch = true;
}
conditionVariable = this->conditionVariable;
resolved = true;
}
}
if (fireCatch)
{
catchFunction(this->catchMessage);
ReleaseFunctions();
}
if (conditionVariable)
{
conditionVariable->notify_all();
}
if (releaseRef)
{
ReleaseRef();
}
}
virtual void Cancel()
{
std::condition_variable *conditionVariable = nullptr;
{
std::lock_guard lock{mutex};
if (!cancelled)
{
cancelled = true;
thenFunction = nullptr;
catchFunction = nullptr;
conditionVariable = this->conditionVariable;
if (inputPromise)
{
inputPromise->Cancel();
SetInputPromise(nullptr);
}
}
}
ReleaseFunctions();
if (conditionVariable)
{
conditionVariable->notify_all();
}
}
bool IsCancelled()
{
std::lock_guard lock{mutex};
return cancelled;
}
void Then(ThenFunction &&handler)
{
bool fireThen;
{
std::lock_guard lock{mutex};
if (hasThenFunction)
{
throw std::logic_error("Then handler already set.");
}
hasThenFunction = true;
thenFunction = std::move(handler);
fireThen = resolved && (!rejected) && (!cancelled);
}
if (fireThen)
{
thenFunction(resolvedValue);
ReleaseFunctions();
}
}
void Then(const ThenFunction &handler)
{
ThenFunction fn = handler;
Then(std::move(fn));
}
void Catch(CatchFunction handler)
{
bool fireCatch;
{
std::lock_guard lock{mutex};
if (hasCatchFunction)
{
throw std::logic_error("Catch handler already set.");
}
if (!hasThenFunction)
{
++referenceCount;
}
hasCatchFunction = true;
catchFunction = handler;
fireCatch = resolved && rejected && !cancelled;
}
if (fireCatch)
{
catchFunction(this->catchMessage);
ReleaseFunctions();
}
}
bool IsReady() const
{
std::lock_guard lock{mutex};
return this->resolved;
}
private:
void ReleaseFunctions()
{
// Most importantly, release capture variables that reference a promise.
thenFunction = nullptr;
catchFunction = nullptr;
worker = nullptr;
SetInputPromise(nullptr);
}
private:
bool hasWorker = false;
bool cancelled = false;
bool resolved = false;
bool rejected = false;
WorkerFunction worker;
std::condition_variable *conditionVariable = nullptr;
bool hasThenFunction = false;
ThenFunction thenFunction;
bool hasCatchFunction = false;
CatchFunction catchFunction;
T resolvedValue;
std::string catchMessage;
};
template <typename T>
class Promise
{
public:
using ResolveFunction = PromiseInner<T>::ResolveFunction;
using RejectFunction = pipedal::RejectFunction;
using WorkerFunction = PromiseInner<T>::WorkerFunction;
using ThenFunction = PromiseInner<T>::ThenFunction;
using CatchFunction = pipedal::CatchFunction;
Promise() { p = new PromiseInner<T>(); }
Promise(const Promise<T> &other)
{
p = other.p;
p->AddRef();
}
Promise(Promise<T> &&other)
{
this->p = other.p;
other.p = nullptr;
}
Promise(WorkerFunction worker) { p = new PromiseInner(worker); }
~Promise()
{
if (p)
p->ReleaseRef();
}
Promise(Promise &other)
{
other.p->AddRef();
this->p = other.p;
}
Promise<T> Then(std::function<void(const T &v)> &&thenFn)
{
p->Then(std::move(thenFn));
return Promise<T>(*this);
}
Promise<T> Then(const std::function<void(const T &v)> &thenFn)
{
std::function<void(const T &v)> t(thenFn);
p->Then(std::move(t));
return Promise<T>(*this);
}
template <typename U>
Promise<U> Then(std::function<void(const T &value, pipedal::ResolveFunction<U> result, RejectFunction reject)> thenFn)
{
Promise<U> t{};
t.SetInputPromise(this->p);
this->Then([t, thenFn](const T &value) mutable
{ t.Work([thenFn, value](pipedal::ResolveFunction<U> resolveU, RejectFunction rejectU) mutable
{ thenFn(value, resolveU, rejectU); }); })
.Catch([t](const std::string &message) mutable
{ t.Reject(message); });
return t;
}
Promise<T> Catch(const CatchFunction &catchFn)
{
p->Catch(catchFn);
return Promise<T>(*this);
}
Promise<T> &operator=(Promise<T> &&other)
{
std::swap(this->p, other.p);
return *this;
}
Promise<T> &operator=(const Promise<T> &other)
{
this->p = other.p;
p->AddRef();
return *this;
}
T Get()
{
return p->Get();
}
class Test
{
// not thread-safe. for testing purposes only.
int GetAllocationCount() { return PromiseInnerBase::allocationCount; }
};
void Work(std::function<void(ResolveFunction resolve, RejectFunction reject)> workFunction)
{
p->Work(workFunction);
}
void Reject(const std::string &message)
{
p->Reject(message);
}
void SetInputPromise(PromiseInnerBase *inputPromise)
{
p->SetInputPromise(inputPromise);
}
private:
PromiseInner<T> *p = nullptr;
};
}
+261
View File
@@ -0,0 +1,261 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "Promise.hpp"
#include "catch.hpp"
#include <future>
#include <thread>
#include <iostream>
using namespace pipedal;
TEST_CASE("Promise", "[promise][Build][Dev]")
{
{
// synchronous then->get
Promise<double> p1 = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
});
Promise<int> p2 = p1
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
{ resolve((int)(value + 3)); });
int result = p2.Get();
REQUIRE(result == 4.0);
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
////////////////////////
{
// synchronous resolve.
double result = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Get();
REQUIRE(result == 1.0);
}
{
// async resolve (Get)
double result = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[resolve]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(200));
resolve(2.0);
});
})
.Get();
REQUIRE(result == 2.0);
}
{
// async resolve (Then)
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[resolve]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(200));
resolve(3.0);
});
})
.Then([&cv, &mutex, &ready](double result)
{
REQUIRE(result == 3.0);
{
std::lock_guard lock { mutex};
ready = true;
}
cv.notify_all(); })
.Catch([](const std::string &message)
{
REQUIRE(true == false); // should not get here.
});
while (true)
{
std::unique_lock lock{mutex};
if (ready)
break;
cv.wait(lock);
}
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// async resolve (Then, Then<int32_t>)
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[resolve]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
resolve(3.0);
});
})
.Then<int32_t>([](double value, ResolveFunction<int32_t> resolve, RejectFunction reject)
{
REQUIRE(value == 3.0);
auto _ = std::async(
std::launch::async,
[resolve, value]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
resolve((int32_t)(value + 10));
});
})
.Then([&cv, &mutex, &ready](int32_t result)
{
REQUIRE(result == 13);
{
std::lock_guard lock { mutex};
ready = true;
}
cv.notify_all(); });
while (true)
{
std::unique_lock lock{mutex};
if (ready)
break;
cv.wait(lock);
}
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous then->get
int result = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
{ resolve((int)(value + 3)); })
.Get();
REQUIRE(result == 4.0);
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous reject
Promise<double> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
reject("Rejected");
});
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// async reject (Get)
Promise<double> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[reject]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
reject("Rejected");
});
});
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous then then<int>/reject->get
Promise<int> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Then<int>(
[](double value, ResolveFunction<int> resolve, RejectFunction reject)
{
reject("reject");
});
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous then reject->then<int>->get
Promise<int> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
reject("reject");
})
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
{ resolve((int)(value + 3)); });
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// async resolve (Catch)
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[reject]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(200));
reject("rejected");
});
})
.Then([&cv, &mutex, &ready](double result)
{ REQUIRE(true == false); })
.Catch([&cv, &mutex, &ready](const std::string &message)
{
{
std::lock_guard lock(mutex);
ready = true;
}
cv.notify_all(); });
while (true)
{
std::unique_lock lock{mutex};
if (ready)
break;
cv.wait(lock);
}
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
}
+253 -99
View File
@@ -22,28 +22,35 @@
#include "PiPedalException.hpp"
#include <atomic>
#include <mutex>
#include <semaphore.h>
#include <condition_variable>
#ifndef NO_MLOCK
#include <sys/mman.h>
#endif /* NO_MLOCK */
namespace pipedal
{
enum class RingBufferStatus
{
Ready,
TimedOut,
Closed
};
template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false>
class RingBuffer {
class RingBuffer
{
char *buffer;
bool mlocked = false;
size_t ringBufferSize;
size_t ringBufferMask;
volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition
volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition
std::mutex write_mutex;
std::mutex mutex;
std::mutex writeMutex;
sem_t readSemaphore;
bool semaphore_open = false;
bool is_open = true;
std::condition_variable cvRead;
size_t nextPowerOfTwo(size_t size)
{
@@ -54,205 +61,352 @@ namespace pipedal
}
return v;
}
public:
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
{
this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize);
ringBufferMask = ringBufferSize-1;
ringBufferMask = ringBufferSize - 1;
buffer = new char[ringBufferSize];
if (SEMAPHORE_READER) {
sem_init(&readSemaphore,0,0);
semaphore_open = true;
}
#ifndef NO_MLOCK
#ifndef NO_MLOCK
if (mLock)
{
if (mlock (buffer, ringBufferSize)) {
throw PiPedalStateException("Mlock failed.");
if (mlock(buffer, ringBufferSize))
{
throw PiPedalStateException("Mlock failed.");
}
this->mlocked = true;
}
#endif
#endif
}
void reset() {
void reset()
{
this->readPosition = 0;
this->writePosition = 0;
if (SEMAPHORE_READER)
{
sem_destroy(&readSemaphore);
sem_init(&readSemaphore,0,0);
this->semaphore_open = true;
}
cvRead.notify_all();
}
void close() {
void close()
{
if (SEMAPHORE_READER)
{
this->semaphore_open = false;
sem_post(&readSemaphore);
}
}
// 0 -> ready. -1: timed out. -2: closing.
int readWait(const struct timespec& timeoutMs) {
if (SEMAPHORE_READER)
{
int result = sem_timedwait(&readSemaphore,&timeoutMs);
if (!semaphore_open) return -2;
return (result == 0) ? 0: -1;
} else {
throw PiPedalStateException("SEMAPHORE_READER is not set to true.");
this->is_open = false;
cvRead.notify_all();
}
}
bool readWait() {
if (SEMAPHORE_READER)
template <class Rep, class Period>
RingBufferStatus readWait_for(const std::chrono::duration<Rep, Period> &timeout)
{
while (true)
{
sem_wait(&readSemaphore);
return semaphore_open;
} else {
throw PiPedalStateException("SEMAPHORE_READER is not set to true.");
if (SEMAPHORE_READER)
{
std::unique_lock lock(mutex);
if (isReadReady_())
{
return RingBufferStatus::Ready;
}
if (!is_open)
return RingBufferStatus::Closed;
auto status = cvRead.wait_for(lock, timeout);
if (status == std::cv_status::timeout)
{
return RingBufferStatus::TimedOut;
}
}
else
{
static_assert("SEMAPHORE_READER is not set to true.");
}
}
}
size_t writeSpace() {
// at most ringBufferSize-1 in order to
template <class Clock, class Duration>
RingBufferStatus readWait_until(const std::chrono::time_point<Clock, Duration> &time_point)
{
while (true)
{
if (SEMAPHORE_READER)
{
std::unique_lock lock(mutex);
if (isReadReady_())
{
return RingBufferStatus::Ready;
}
if (!is_open)
return RingBufferStatus::Closed;
auto status = cvRead.wait_until(lock, time_point);
if (status == std::cv_status::timeout)
{
return RingBufferStatus::TimedOut;
}
}
else
{
static_assert("SEMAPHORE_READER is not set to true.");
}
}
}
template <class Clock, class Duration>
RingBufferStatus readWait_until(size_t size,const std::chrono::time_point<Clock, Duration> &time_point)
{
while (true)
{
if (SEMAPHORE_READER)
{
std::unique_lock lock(mutex);
size_t available = readSpace_();
if (available >= size)
{
return RingBufferStatus::Ready;
}
if (!is_open)
return RingBufferStatus::Closed;
auto status = cvRead.wait_until(lock, time_point);
if (status == std::cv_status::timeout)
{
return RingBufferStatus::TimedOut;
}
}
else
{
static_assert("SEMAPHORE_READER is not set to true.");
}
}
}
bool readWait()
{
if (SEMAPHORE_READER)
{
while (true)
{
std::unique_lock lock(mutex);
if (isReadReady_())
{
return true;
}
if (!is_open)
return false;
cvRead.wait(lock);
}
}
else
{
static_assert("SEMAPHORE_READER is not set to true.");
}
}
size_t writeSpace()
{
// at most ringBufferSize-1 in order to
// to distinguish the empty buffer from the full buffer.
int64_t size = readPosition-1-writePosition;
if (size < 0) size += this->ringBufferSize;
std::unique_lock lock(mutex);
int64_t size = readPosition - 1 - writePosition;
if (size < 0)
size += this->ringBufferSize;
return (size_t)size;
}
size_t readSpace() {
int64_t size = writePosition-readPosition;
if (size < 0) size += this->ringBufferSize;
return size_t(size);
size_t readSpace()
{
std::unique_lock lock(mutex);
return readSpace_();
}
bool write(size_t bytes, uint8_t *data)
{
if (MULTI_WRITER)
{
std::lock_guard guard(write_mutex);
if (writeSpace() < bytes) {
std::lock_guard writeLock{writeMutex};
if (writeSpace() < bytes + sizeof(bytes))
{
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
buffer[(index + i) & ringBufferMask] = data[i];
}
{
std::lock_guard lock(mutex);
this->writePosition = (index + bytes) & ringBufferMask;
}
this->writePosition = (index+bytes) & ringBufferMask;
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
cvRead.notify_all();
}
return true;
} else {
if (writeSpace() < bytes) {
}
else
{
if (writeSpace() < sizeof(bytes) + bytes)
{
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
buffer[(index + i) & ringBufferMask] = data[i];
}
{
std::lock_guard lock{mutex};
this->writePosition = (index + bytes) & ringBufferMask;
}
this->writePosition = (index+bytes) & ringBufferMask;
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
cvRead.notify_all();
}
return true;
}
}
// Write two disjoint areas of memory atomically.
bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t*data2)
bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t *data2)
{
if (MULTI_WRITER)
{
std::lock_guard guard(write_mutex);
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) {
std::lock_guard guard(writeMutex);
if (writeSpace() <= sizeof(bytes) + bytes +bytes2)
{
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
buffer[(index + i) & ringBufferMask] = data[i];
}
index = (index+bytes) & ringBufferMask;
index = (index + bytes) & ringBufferMask;
for (size_t i = 0; i < sizeof(bytes2); ++i)
{
buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i];
buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i];
}
index = (index+sizeof(bytes2)) & ringBufferMask;
index = (index + sizeof(bytes2)) & ringBufferMask;
for (size_t i = 0; i < bytes2; ++i)
{
buffer[(index+i) & ringBufferMask] = data2[i];
buffer[(index + i) & ringBufferMask] = data2[i];
}
{
std::lock_guard lock{mutex};
this->writePosition = (index + bytes2) & ringBufferMask;
}
this->writePosition = (index+bytes2) & ringBufferMask;
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
cvRead.notify_all();
}
return true;
} else {
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) {
}
else
{
if (writeSpace() <= sizeof(bytes2) + bytes + bytes2)
{
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
buffer[(index + i) & ringBufferMask] = data[i];
}
index = (index+bytes) & ringBufferMask;
index = (index + bytes) & ringBufferMask;
for (size_t i = 0; i < sizeof(bytes2); ++i)
{
buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i];
buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i];
}
index = (index+sizeof(bytes2)) & ringBufferMask;
index = (index + sizeof(bytes2)) & ringBufferMask;
for (size_t i = 0; i < bytes2; ++i)
{
buffer[(index+i) & ringBufferMask] = data2[i];
buffer[(index + i) & ringBufferMask] = data2[i];
}
this->writePosition = (index+bytes2) & ringBufferMask;
{
std::lock_guard lock{mutex};
this->writePosition = (index + bytes2) & ringBufferMask;
}
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
cvRead.notify_all();
}
return true;
}
}
bool read(size_t bytes, uint8_t*data)
bool read(size_t bytes, uint8_t *data)
{
if (readSpace() < bytes) return false;
if (readSpace() < bytes)
return false;
int64_t readPosition = this->readPosition;
for (size_t i = 0; i < bytes; ++i)
{
data[i] = this->buffer[(readPosition+i) & this->ringBufferMask];
data[i] = this->buffer[(readPosition + i) & this->ringBufferMask];
}
{
std::lock_guard lock{mutex};
this->readPosition = (readPosition + bytes) & this->ringBufferMask;
}
this->readPosition = (readPosition + bytes) & this->ringBufferMask;
return true;
}
~RingBuffer()
~RingBuffer()
{
#ifdef USE_MLOCK
if (this->mlocked)
{
munlock(buffer,ringBufferSize);
}
#endif
if (SEMAPHORE_READER)
#ifdef USE_MLOCK
if (this->mlocked)
{
sem_destroy(&this->readSemaphore);
munlock(buffer, ringBufferSize);
}
#endif
delete[] buffer;
}
bool isReadReady() {
std::lock_guard lock(mutex);
if (isReadReady_()) return true;
return !this->is_open;
}
bool isReadReady(size_t size) {
size_t available = readSpace();
return available >= size;
}
private:
size_t readSpace_()
{
int64_t size = writePosition - readPosition;
if (size < 0)
size += this->ringBufferSize;
return size_t(size);
}
uint32_t peekSize()
{
volatile uint32_t result;
uint8_t *p = (uint8_t*)&result;
size_t ix = this->readPosition;
for (size_t i = 0; i < sizeof(result); ++i)
{
*p++ = this->buffer[(ix++) & ringBufferMask];
}
return result;
}
bool isReadReady_()
{
size_t available = readSpace_();
if (available < sizeof(uint32_t)) return false;
// peak to get the size!
uint32_t packetSize = peekSize();
return packetSize+sizeof(uint32_t) <= available;
}
};
};
};
+35 -14
View File
@@ -23,6 +23,8 @@
#include "Lv2Log.hpp"
#include "VuUpdate.hpp"
#include "AudioHost.hpp"
#include "lv2/atom.lv2/atom.h"
#include <chrono>
namespace pipedal
{
@@ -56,6 +58,8 @@ namespace pipedal
NextMidiProgram = 20,
Lv2StateChanged = 21,
};
struct RealtimeNextMidiProgramRequest {
@@ -151,17 +155,17 @@ namespace pipedal
float value;
};
class Lv2PedalBoard;
class Lv2Pedalboard;
class ReplaceEffectBody
{
public:
Lv2PedalBoard *effect;
Lv2Pedalboard *effect;
};
class EffectReplacedBody
{
public:
Lv2PedalBoard *oldEffect;
Lv2Pedalboard *oldEffect;
};
template <bool MULTI_WRITE, bool SEMAPHORE_READ>
@@ -182,8 +186,20 @@ namespace pipedal
}
// 0 -> ready. -1: timed out. -2: closing.
int wait(struct timespec &timeout) {
return ringBuffer->readWait(timeout);
template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) {
return ringBuffer->readWait_for(timeout);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(std::chrono::time_point<Clock,Duration>&time_point)
{
return ringBuffer->readWait_until(time_point);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(size_t size,std::chrono::time_point<Clock,Duration>&time_point)
{
return ringBuffer->readWait_until(size,time_point);
}
bool wait() {
@@ -196,9 +212,6 @@ namespace pipedal
template <typename T>
bool read(T *output)
{
size_t available = readSpace();
if (available < sizeof(T))
return false;
if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
{
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
@@ -294,15 +307,23 @@ namespace pipedal
}
}
void Lv2StateChanged(uint64_t instanceId)
{
write(RingBufferCommand::Lv2StateChanged,instanceId);
}
void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data)
{
write(RingBufferCommand::AtomOutput,instanceId,bytes,data);
}
void ParameterRequest(RealtimeParameterRequest *pRequest)
void AtomOutput(uint64_t instanceId, const LV2_Atom*atom)
{
write(RingBufferCommand::AtomOutput,instanceId,atom->size+sizeof(LV2_Atom),(uint8_t*)atom);
}
void ParameterRequest(RealtimePatchPropertyRequest *pRequest)
{
write(RingBufferCommand::ParameterRequest,pRequest);
}
void ParameterRequestComplete(RealtimeParameterRequest *pRequest)
void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest)
{
write(RingBufferCommand::ParameterRequestComplete,pRequest);
}
@@ -407,9 +428,9 @@ namespace pipedal
write(RingBufferCommand::SetBypass,body);
}
void ReplaceEffect(Lv2PedalBoard *pedalBoard)
void ReplaceEffect(Lv2Pedalboard *pedalboard)
{
write(RingBufferCommand::ReplaceEffect, pedalBoard);
write(RingBufferCommand::ReplaceEffect, pedalboard);
}
void AudioStopped()
@@ -418,9 +439,9 @@ namespace pipedal
write(RingBufferCommand::AudioStopped, body);
}
void EffectReplaced(Lv2PedalBoard *pedalBoard)
void EffectReplaced(Lv2Pedalboard *pedalboard)
{
write(RingBufferCommand::EffectReplaced, pedalBoard);
write(RingBufferCommand::EffectReplaced, pedalboard);
}
};
+2 -2
View File
@@ -20,8 +20,8 @@
#include "pch.h"
#include "SplitEffect.hpp"
#include "PiPedalHost.hpp"
#include "PedalBoard.hpp"
#include "PluginHost.hpp"
#include "Pedalboard.hpp"
using namespace pipedal;
+3 -3
View File
@@ -291,10 +291,11 @@ namespace pipedal
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
virtual void RequestParameter(LV2_URID uridUri) {}
virtual void GatherParameter(RealtimeParameterRequest *pRequest) {}
virtual void RequestPatchProperty(LV2_URID uridUri) {}
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
virtual bool IsVst3() const { return false; }
@@ -670,7 +671,6 @@ namespace pipedal
}
}
}
void PostMix(uint32_t frames)
{
if (this->outputBuffers.size() == 1)
+289
View File
@@ -0,0 +1,289 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "StateInterface.hpp"
#include "ss.hpp"
#include "Base64.hpp"
#include "json.hpp"
#include <sstream>
using namespace pipedal;
LV2_State_Status StateInterface::FnStateStoreFunction(
LV2_State_Handle handle,
uint32_t key,
const void *value,
size_t size,
uint32_t type,
uint32_t flags)
{
SaveCallState *pCallState = (SaveCallState *)handle;
pCallState->status = pCallState->pThis->StateStoreFunction(
pCallState,
key,
value,
size,
type,
flags
);
return pCallState->status;
}
LV2_State_Status StateInterface::StateStoreFunction(
SaveCallState *pCallState,
uint32_t key,
const void *value,
size_t size,
uint32_t type,
uint32_t flags)
{
Lv2PluginState &state = pCallState->state;
std::string strKey = map.UridToString(key);
Lv2PluginStateEntry& entry = state.values_[strKey];
std::string atomType = map.UridToString(type);
entry.atomType_ = atomType;
entry.flags_ = flags;
entry.value_.resize(size);
uint8_t *p = (uint8_t*)value;
for (size_t i = 0; i < size; ++i)
{
entry.value_[i] = p[i];
}
return LV2_State_Status::LV2_STATE_SUCCESS;
}
static void CheckState(LV2_State_Status status)
{
const char *lv2Error = nullptr;
switch (status)
{
case LV2_State_Status::LV2_STATE_SUCCESS:
return;
default:
lv2Error = "Unknown error.";
break;
case LV2_State_Status::LV2_STATE_ERR_BAD_TYPE:
lv2Error = "Invalid type.";
break;
case LV2_State_Status::LV2_STATE_ERR_BAD_FLAGS:
lv2Error = "Unsupported flags.";
break;
case LV2_State_Status::LV2_STATE_ERR_NO_FEATURE:
lv2Error = "Feature not supported.";
break;
case LV2_State_Status::LV2_STATE_ERR_NO_PROPERTY:
lv2Error = "No such property.";
break;
case LV2_State_Status::LV2_STATE_ERR_NO_SPACE:
lv2Error = "Insufficient memory.";
break;
}
throw std::logic_error(lv2Error);
}
Lv2PluginState StateInterface::Save()
{
SaveCallState callState;
callState.pThis = this;
callState.status = LV2_State_Status::LV2_STATE_SUCCESS;
LV2_Feature **features = &(this->features[0]);
LV2_Handle instanceHandle = lilv_instance_get_handle(pInstance);
try
{
LV2_State_Status status =
pluginStateInterface->save(
instanceHandle,
FnStateStoreFunction,
(LV2_State_Handle)&callState,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
features);
CheckState(status);
CheckState(callState.status);
}
catch (const std::exception &e)
{
throw std::logic_error(SS("State save failed. " << e.what()));
}
return std::move(callState.state);
}
void StateInterface::Restore(const Lv2PluginState &state)
{
RestoreCallState callState;
callState.pThis = this;
callState.pState = &state;
callState.status = LV2_State_Status::LV2_STATE_SUCCESS;
LV2_Feature **features = &(this->features[0]);
LV2_Handle instanceHandle = lilv_instance_get_handle(pInstance);
try
{
LV2_State_Status status =
pluginStateInterface->restore(
instanceHandle,
FnStateRetreiveFunction,
(LV2_State_Handle)&callState,
LV2_STATE_IS_POD,
features);
CheckState(status);
CheckState(callState.status);
}
catch (const std::exception &e)
{
throw std::logic_error(SS("State save failed. " << e.what()));
}
}
/*static*/ const void *StateInterface::FnStateRetreiveFunction(
LV2_State_Handle handle,
uint32_t key,
size_t *size,
uint32_t *type,
uint32_t *flags)
{
RestoreCallState*pCallState = (RestoreCallState*)handle;
return pCallState->pThis->StateRetrieveFunction(
pCallState,
key,
size,
type,
flags);
}
const void *StateInterface::StateRetrieveFunction(
RestoreCallState *pCallState,
uint32_t key,
size_t *size,
uint32_t *type,
uint32_t *flags)
{
std::string strKey = map.UridToString(key);
auto & values = pCallState->pState->values_;
auto iEntry = values.find(strKey);
if (iEntry == values.end())
{
*size = 0;
*type = urids.atom__Atom;
return nullptr;
}
const Lv2PluginStateEntry& entry = iEntry->second;
*size = entry.value_.size();
*type = map.GetUrid(entry.atomType_.c_str());
*flags = entry.flags_;
return (void*)&entry.value_[0];
}
void Lv2PluginState::write_json(json_writer &writer) const
{
writer.write(values_);
}
void Lv2PluginState::read_json(json_reader &reader) {
reader.read(&values_);
}
void Lv2PluginStateEntry::write_json(json_writer &writer) const
{
writer.start_object();
writer.write_member("flags",flags_);
writer.write_raw(",");
writer.write_member("atomType",atomType_);
writer.write_raw(",");
if (atomType_ == LV2_ATOM__String || atomType_ == LV2_ATOM__Path || atomType_ == LV2_ATOM__URI)
{
const char *p = (const char*)&(value_[0]);
size_t size = value_.size();
while (size > 0 && p[size-1] == '\0')
{
--size;
}
std::string value { p,size};
writer.write_member("value",value);
} else if (atomType_ == LV2_ATOM__Float)
{
if (value_.size() != sizeof(float))
{
throw std::logic_error("Invalid float property in LV2PluginState");
}
writer.write_member("value",*(float*)&(value_[0]));
} else {
std::string base64 = macaron::Base64::Encode(value_);
writer.write_member("value",base64);
}
writer.end_object();
}
void Lv2PluginStateEntry::read_json(json_reader &reader)
{
reader.start_object();
reader.read_member("flags",&flags_);
reader.consume(',');
reader.read_member("atomType",&atomType_);
reader.consume(',');
if (atomType_ == LV2_ATOM__String || atomType_ == LV2_ATOM__Path || atomType_ == LV2_ATOM__URI)
{
std::string v;
reader.read_member("value",&v);
value_.resize(v.length());
for (size_t i = 0; i < v.length(); ++i)
{
value_[i] = (uint8_t)v[i];
}
} else if (atomType_ == LV2_ATOM__Float)
{
float v;
reader.read_member("value",&v);
value_.resize(sizeof(float));
float *pVal = (float*)&(value_[0]);
*pVal = v;
} else {
std::string v;
reader.read_member("value",&v);
value_ = macaron::Base64::Decode(v);
}
reader.end_object();
}
StateInterface::StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface)
: map(host->GetMapFeature()),
pInstance(pInstance),
pluginStateInterface(pluginStateInterface)
{
auto hostFeatures = host->GetLv2Features();
while (*hostFeatures != nullptr)
{
features.push_back((LV2_Feature*)(*hostFeatures));
++hostFeatures;
}
features.push_back(nullptr);
}
std::string Lv2PluginState::ToString() const {
std::stringstream ss;
json_writer writer(ss);
writer.write(*this);
return ss.str();
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include "lilv/lilv.h"
#include "lv2/state.lv2/state.h"
#include <cstddef>
#include "json_variant.hpp"
#include "MapFeature.hpp"
#include "IHost.hpp"
namespace pipedal
{
class Lv2PluginStateEntry: public JsonSerializable {
public:
std::int32_t flags_ = 0;
std::string atomType_;
std::vector<uint8_t> value_;
private:
virtual void write_json(json_writer &writer) const;
virtual void read_json(json_reader &reader);
};
class Lv2PluginState: public JsonSerializable
{
public:
std::map<std::string,Lv2PluginStateEntry> values_;
std::string ToString() const;
private:
virtual void write_json(json_writer &writer) const;
virtual void read_json(json_reader &reader);
};
// state callback interface for plugin's LV2_State
class StateInterface
{
public:
StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface);
public:
Lv2PluginState Save();
void Restore(const Lv2PluginState &state);
private:
struct Urids {
public:
void Init(MapFeature&map)
{
atom__Atom = map.GetUrid(LV2_ATOM__Atom);
atom__String = map.GetUrid(LV2_ATOM__String);
atom__Float = map.GetUrid(LV2_ATOM__Float);
}
LV2_URID atom__Atom;
LV2_URID atom__String;
LV2_URID atom__Float;
};
Urids urids;
struct SaveCallState
{
StateInterface *pThis;
LV2_State_Status status;
Lv2PluginState state;
};
struct RestoreCallState
{
StateInterface *pThis;
LV2_State_Status status;
const Lv2PluginState *pState;
};
static LV2_State_Status FnStateStoreFunction(
LV2_State_Handle handle,
uint32_t key,
const void *value,
size_t size,
uint32_t type,
uint32_t flags);
LV2_State_Status StateStoreFunction(
SaveCallState *pCallState,
uint32_t key,
const void *value,
size_t size,
uint32_t type,
uint32_t flags);
static const void *FnStateRetreiveFunction(
LV2_State_Handle handle,
uint32_t key,
size_t *size,
uint32_t *type,
uint32_t *flags);
const void *StateRetrieveFunction(
RestoreCallState*pCallState,
uint32_t key,
size_t *size,
uint32_t *type,
uint32_t *flags);
private:
MapFeature &map;
LilvInstance *pInstance;
const LV2_State_Interface *pluginStateInterface;
std::vector<LV2_Feature *> features;
};
}
+95 -60
View File
@@ -28,6 +28,8 @@
#include <map>
#include <sys/stat.h>
#include "PiPedalUI.hpp"
#include "PluginHost.hpp"
#include "ss.hpp"
using namespace pipedal;
@@ -233,13 +235,20 @@ void Storage::LoadBank(int64_t instanceId)
{
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
LoadBankFile(indexEntry.name(), &(this->currentBank));
if (this->bankIndex.selectedBank() != instanceId)
try
{
this->bankIndex.selectedBank(instanceId);
SaveBankIndex();
LoadBankFile(indexEntry.name(), &(this->currentBank));
if (this->bankIndex.selectedBank() != instanceId)
{
this->bankIndex.selectedBank(instanceId);
SaveBankIndex();
}
this->LoadPreset(this->currentBank.selectedPreset());
}
catch (const std::exception &e)
{
throw std::logic_error(SS("Bank file corrupted. " << e.what() << "(" << GetBankFileName(indexEntry.name()) << ")"));
}
this->LoadPreset(this->currentBank.selectedPreset());
}
void Storage::LoadCurrentBank()
@@ -255,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{
return this->dataRoot / "plugin_presets";
}
std::filesystem::path Storage::GetAudioFilesDirectory() const
std::filesystem::path Storage::GetPluginStorageDirectory() const
{
return this->dataRoot / "audio_uploads";
}
@@ -297,8 +306,8 @@ void Storage::LoadBankIndex()
if (bankIndex.entries().size() == 0)
{
currentBank.clear();
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
int64_t instanceId = currentBank.addPreset(defaultPedalBoard);
Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
int64_t instanceId = currentBank.addPreset(defaultPedalboard);
currentBank.selectedPreset(instanceId);
std::string name = "Default Bank";
@@ -378,7 +387,7 @@ void Storage::ReIndex()
void Storage::CreateBank(const std::string &name)
{
BankFile bankFile;
PedalBoard defaultPreset = PedalBoard::MakeDefault();
Pedalboard defaultPreset = Pedalboard::MakeDefault();
defaultPreset.name(std::string("Default Preset"));
bankFile.addPreset(defaultPreset);
@@ -449,7 +458,7 @@ void Storage::SaveCurrentBank()
SaveBankFile(indexEntry.name(), this->currentBank);
}
const PedalBoard &Storage::GetCurrentPreset()
const Pedalboard &Storage::GetCurrentPreset()
{
auto &item = currentBank.getItem(currentBank.selectedPreset());
return item.preset();
@@ -466,18 +475,18 @@ bool Storage::LoadPreset(int64_t instanceId)
}
return true;
}
void Storage::SaveCurrentPreset(const PedalBoard &pedalBoard)
void Storage::SaveCurrentPreset(const Pedalboard &pedalboard)
{
auto &item = currentBank.getItem(currentBank.selectedPreset());
item.preset(pedalBoard);
item.preset(pedalboard);
SaveCurrentBank();
}
int64_t Storage::SaveCurrentPresetAs(const PedalBoard &pedalBoard, const std::string &name, int64_t saveAfterInstanceId)
int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, const std::string &name, int64_t saveAfterInstanceId)
{
PedalBoard newPedalBoard = pedalBoard;
newPedalBoard.name(name);
Pedalboard newPedalboard = pedalboard;
newPedalboard.name(name);
int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId);
int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId);
currentBank.selectedPreset(newInstanceId);
SaveCurrentBank();
return newInstanceId;
@@ -530,17 +539,18 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
pResult->presets().push_back(entry);
}
}
int64_t Storage::GetPresetByProgramNumber(uint8_t program)const
int64_t Storage::GetPresetByProgramNumber(uint8_t program) const
{
if (program >= currentBank.presets().size())
{
if (currentBank.presets().size() == 0) return -1;
program = (uint8_t)currentBank.presets().size()-1;
if (currentBank.presets().size() == 0)
return -1;
program = (uint8_t)currentBank.presets().size() - 1;
}
return currentBank.presets()[program]->instanceId();
}
PedalBoard Storage::GetPreset(int64_t instanceId) const
Pedalboard Storage::GetPreset(int64_t instanceId) const
{
for (size_t i = 0; i < currentBank.presets().size(); ++i)
{
@@ -630,10 +640,10 @@ int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
auto &fromItem = this->currentBank.getItem(fromId);
if (toId == -1)
{
PedalBoard newPedalBoard = fromItem.preset();
Pedalboard newPedalboard = fromItem.preset();
std::string name = GetPresetCopyName(fromItem.preset().name());
newPedalBoard.name(name);
return this->currentBank.addPreset(newPedalBoard, fromId);
newPedalboard.name(name);
return this->currentBank.addPreset(newPedalboard, fromId);
}
else
{
@@ -753,13 +763,14 @@ void Storage::MoveBank(int from, int to)
this->SaveBankIndex();
}
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) {
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber)
{
auto &entries = this->bankIndex.entries();
if (bankNumber >= entries.size())
{
if (entries.size() == 0) return -1;
bankNumber = (uint8_t)(entries.size()-1);
if (entries.size() == 0)
return -1;
bankNumber = (uint8_t)(entries.size() - 1);
}
return entries[bankNumber].instanceId();
}
@@ -792,8 +803,8 @@ int64_t Storage::DeleteBank(int64_t bankId)
BankIndexEntry newEntry;
BankFile defaultBank;
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
int64_t instanceId = defaultBank.addPreset(defaultPedalBoard);
Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
int64_t instanceId = defaultBank.addPreset(defaultPedalboard);
defaultBank.selectedPreset(instanceId);
std::string name = "Default Bank";
@@ -832,7 +843,7 @@ int64_t Storage::UploadPreset(const BankFile &bankFile, int64_t uploadAfter)
}
for (size_t i = 0; i < bankFile.presets().size(); ++i)
{
PedalBoard preset = bankFile.presets()[i]->preset();
Pedalboard preset = bankFile.presets()[i]->preset();
int n = 2;
std::string baseName = preset.name();
@@ -1063,8 +1074,11 @@ bool Storage::RestoreCurrentPreset(CurrentPreset *pResult)
reader.read(pResult);
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
}
catch (const std::exception &)
catch (const std::exception &e)
{
Lv2Log::warning(SS("Failed to restore current preset. " << e.what()));
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
return false;
}
return true;
@@ -1330,9 +1344,6 @@ void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
pipedal::JackServerSettings Storage::GetJackServerSettings()
{
JackServerSettings result;
#if JACK_HOST
result.Initialize();
#else
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
std::ifstream f;
f.open(fileName);
@@ -1341,14 +1352,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
json_reader reader(f);
reader.read(&result);
}
#if JACK_HOST
result.Initialize();
#endif
return result;
}
void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration)
{
#if JACK_HOST
#error IMPLEMENT ME
#else
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
std::ofstream f;
f.open(fileName);
@@ -1357,10 +1368,12 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
json_writer writer(f);
writer.write(jackConfiguration);
}
#if JACK_HOST
jackConfiguration.Write();
#endif
}
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
{
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::ofstream f;
@@ -1382,63 +1395,85 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
{
json_reader reader(f);
reader.read(&result);
} else {
}
else
{
result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram"));
}
return result;
}
static bool containsDotDot(const std::string&value)
static bool containsDotDot(const std::string &value)
{
std::size_t offset = value.find("..");
return offset != std::string::npos;
}
static bool containsDirectorySeparator(const std::string&value)
static bool containsDirectorySeparator(const std::string &value)
{
if (value.find("/") != std::string::npos) return true; //linux
if (value.find("\\") != std::string::npos) return true; // windows
if (value.find("::") != std::string::npos) return true; // mac
if (value.find("/") != std::string::npos)
return true; // linux
if (value.find("\\") != std::string::npos)
return true; // windows
if (value.find("::") != std::string::npos)
return true; // mac
return false;
}
static void ThrowPermissionDeniedError()
static void ThrowPermissionDeniedError()
{
throw std::logic_error("Permission denied.");
}
std::vector<std::string> Storage::GetFileList(const PiPedalFileProperty&fileProperty)
class LexicographicCompare
{
if (!PiPedalFileProperty::IsDirectoryNameValid(fileProperty.directory()))
public:
bool operator()(const std::string &left, const std::string &right)
{
return std::lexicographical_compare(left.begin(), left.end(), right.begin(), right.end());
}
} lexicographicCompare;
std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty)
{
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
{
ThrowPermissionDeniedError();
}
std::vector<std::string> result;
std::filesystem::path audioFileDirectory = this->GetAudioFilesDirectory() / fileProperty.directory();
try {
for (auto const&dir_entry: std::filesystem::directory_iterator(audioFileDirectory))
// 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();
try
{
if (dir_entry.is_regular_file())
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
{
auto &path = dir_entry.path();
if (fileProperty.IsValidExtension(path.extension().string()))
if (dir_entry.is_regular_file())
{
result.push_back(fileProperty.directory() / path.filename());
auto &path = dir_entry.path();
if (fileProperty.IsValidExtension(path.extension().string()))
{
// a relative path!
result.push_back(path);
}
}
}
}
} catch(const std::exception&error)
{
throw std::logic_error("Directory not found: " + audioFileDirectory.string());
catch (const std::exception &error)
{
throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
}
}
// sort lexicographically
std::sort(result.begin(), result.end(), lexicographicCompare);
return result;
}
JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+15 -10
View File
@@ -20,7 +20,7 @@
#pragma once
#include <filesystem>
#include <iostream>
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include "Presets.hpp"
#include "PluginPreset.hpp"
#include "Banks.hpp"
@@ -33,12 +33,13 @@
namespace pipedal {
class PiPedalFileProperty;
class UiFileProperty;
class Lv2PluginInfo;
class CurrentPreset {
public:
bool modified_ = false;
PedalBoard preset_;
Pedalboard preset_;
DECLARE_JSON_MAP(CurrentPreset);
};
@@ -63,12 +64,12 @@ private:
PluginPresetIndex pluginPresetIndex;
private:
void MaybeCopyDefaultPresets();
static std::string SafeEncodeName(const std::string& name);
static std::string SafeDecodeName(const std::string& name);
std::filesystem::path GetPresetsDirectory() const;
std::filesystem::path GetPluginPresetsDirectory() const;
std::filesystem::path GetAudioFilesDirectory() const;
std::filesystem::path GetIndexFileName() const;
std::filesystem::path GetBankFileName(const std::string & name) const;
std::filesystem::path GetChannelSelectionFileName();
@@ -99,7 +100,9 @@ public:
void SetDataRoot(const std::filesystem::path& path);
void SetConfigRoot(const std::filesystem::path& path);
std::vector<std::string> GetPedalBoards();
std::filesystem::path GetPluginStorageDirectory() const;
std::vector<std::string> GetPedalboards();
const BankIndex & GetBanks() const { return bankIndex; }
@@ -112,13 +115,13 @@ public:
void SaveUserSettings();
void LoadBank(int64_t instanceId);
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
const PedalBoard& GetCurrentPreset();
void SaveCurrentPreset(const PedalBoard&pedalBoard);
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
const Pedalboard& GetCurrentPreset();
void SaveCurrentPreset(const Pedalboard&pedalboard);
int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, const std::string&namne,int64_t saveAfterInstanceId = -1);
int64_t GetCurrentPresetId() const;
void GetPresetIndex(PresetIndex*pResult);
void SetPresetIndex(const PresetIndex &presetIndex);
PedalBoard GetPreset(int64_t instanceId) const;
Pedalboard GetPreset(int64_t instanceId) const;
int64_t GetPresetByProgramNumber(uint8_t program) const;
void GetBankFile(int64_t instanceId,BankFile*pResult) const;
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
@@ -136,7 +139,7 @@ public:
void MoveBank(int from, int to);
int64_t DeleteBank(int64_t bankId);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
void SetJackChannelSelection(const JackChannelSelection&channelSelection);
@@ -156,6 +159,8 @@ public:
void SaveCurrentPreset(const CurrentPreset &currentPreset);
bool RestoreCurrentPreset(CurrentPreset*pResult);
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
private:
bool pluginPresetIndexChanged = false;
void LoadPluginPresetIndex();
+2 -2
View File
@@ -24,7 +24,7 @@
#include "ss.hpp"
#include <assert.h>
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp"
@@ -60,7 +60,7 @@
#include "Vst3MidiToEvent.hpp"
#include "vst3/Vst3EffectImpl.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
+11 -11
View File
@@ -23,7 +23,7 @@
*/
#include "ss.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp"
#include <unordered_map>
@@ -86,7 +86,7 @@ namespace pipedal
const Vst3Host::PluginList &getPluginList() override { return pluginList; }
std::unique_ptr<Vst3Effect> CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override;
std::unique_ptr<Vst3Effect> CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost) override;
std::unique_ptr<Vst3Effect> CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost) override;
private:
Lv2PluginUiInfo *GetPluginInfo(const std::string &uri);
@@ -615,22 +615,22 @@ static std::vector<uint8_t> HexToByteArray(const std::string &hexState)
}
return result;
}
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost)
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost)
{
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalBoardItem.instanceId(), pedalBoardItem.uri(), pHost);
auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri());
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalboardItem.instanceId(), pedalboardItem.uri(), pHost);
auto pluginInfo = this->GetPluginInfo(pedalboardItem.uri());
if (!pluginInfo)
{
throw Vst3Exception(SS("Plugin " << pedalBoardItem.pluginName() << " not found."));
throw Vst3Exception(SS("Plugin " << pedalboardItem.pluginName() << " not found."));
}
if (pedalBoardItem.vstState().length() != 0)
if (pedalboardItem.vstState().length() != 0)
{
std::vector<uint8_t> state = HexToByteArray(pedalBoardItem.vstState());
std::vector<uint8_t> state = HexToByteArray(pedalboardItem.vstState());
result->SetState(state);
}
else
{
for (const ControlValue &controlValue : pedalBoardItem.controlValues())
for (const ControlValue &controlValue : pedalboardItem.controlValues())
{
int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
@@ -647,7 +647,7 @@ std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar
}
}
}
for (ControlValue &controlValue : pedalBoardItem.controlValues())
for (ControlValue &controlValue : pedalboardItem.controlValues())
{
int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
@@ -667,7 +667,7 @@ std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar
}
controlValue.value(t);
} else {
Lv2Log::warning(SS(pedalBoardItem.pluginName() << ": Control key not found. key=" << controlValue.key() ));
Lv2Log::warning(SS(pedalboardItem.pluginName() << ": Control key not found. key=" << controlValue.key() ));
}
}
+2 -2
View File
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include <iostream>
@@ -75,7 +75,7 @@ void RunVsts()
cout << "Scanning" << endl;
const auto & plugins = vst3Host->RescanPlugins();
PiPedalHost host;
PluginHost host;
IHost *pHost = host.asIHost();
host.setSampleRate(44100);
+4 -1
View File
@@ -17,6 +17,7 @@
#include <set>
#include <strings.h>
#include "Ipv6Helpers.hpp"
#include "util.hpp"
#include "WebServer.hpp"
@@ -582,6 +583,7 @@ namespace pipedal
{
try
{
SetThreadName("webMain");
// The io_context is required for all I/O
boost::asio::io_service ioc{threads};
//*********************************
@@ -632,8 +634,9 @@ namespace pipedal
v.reserve(threads - 1);
for (auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ioc]
[&ioc,i]
{
SetThreadName(SS("web_" << i));
ioc.run();
});
+223 -95
View File
@@ -1,3 +1,20 @@
/*
Copyright 2007-2012 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// (Borrows heavily from worker.c by David Robillard.)
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
@@ -17,97 +34,160 @@
// 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.
/*
Borrows heavily from worker.c by David Robillard.
Copyright 2007-2012 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "Worker.hpp"
#include <mutex>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include "Lv2Log.hpp"
#include <iostream>
#include <unistd.h> // for nice()
#include "util.hpp"
using namespace pipedal;
const int RING_BUFFER_SIZE = 16 * 1024;
const int RING_BUFFER_SIZE = 64 * 1024;
Worker::Worker(LilvInstance *lilvInstance_, const LV2_Worker_Interface *workerInterface_)
Worker::Worker(const std::shared_ptr<HostWorkerThread> &pHostWorker, LilvInstance *lilvInstance_, const LV2_Worker_Interface *workerInterface_)
: lilvInstance(lilvInstance_),
requestRingBuffer(RING_BUFFER_SIZE),
pHostWorker(pHostWorker),
responseRingBuffer(RING_BUFFER_SIZE),
workerInterface(workerInterface_)
{
responseBuffer = (char *)malloc(RING_BUFFER_SIZE);
StartWorkerThread();
responseBuffer.resize(16 * 1024);
}
void Worker::Close()
{
{
std::lock_guard lock(outstandingRequestMutex);
if (closed)
return;
closed = true;
exiting = true;
}
WaitForAllResponses();
}
Worker::~Worker()
{
StopWorkerThread();
sem_destroy(&requestSemaphore);
free(responseBuffer);
Close();
}
LV2_Worker_Status Worker::worker_respond_fn(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data)
{
Worker *this_ = (Worker *)handle;
return this_->WorkerResponse(size, data);
return this_->WorkerRespond(size, data);
}
LV2_Worker_Status Worker::WorkerResponse(uint32_t size, const void *data)
LV2_Worker_Status Worker::WorkerRespond(uint32_t size, const void *data)
{
if (responseRingBuffer.writeSpace() < sizeof(size)+size)
{
std::lock_guard lock(outstandingRequestMutex);
++outstandingRequests;
}
LV2_Worker_Status status;
if (responseRingBuffer.writeSpace() < sizeof(size) + size)
{
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
cvOutstandingRequests.notify_all();
}
return LV2_WORKER_ERR_NO_SPACE;
}
if (!responseRingBuffer.write(sizeof(size), (uint8_t *)&size))
else
{
return LV2_WORKER_ERR_NO_SPACE;
if (!responseRingBuffer.write(sizeof(size), (uint8_t *)&size))
{
throw std::logic_error("Response queue sync lost.");
}
if (!responseRingBuffer.write(size, (uint8_t *)data))
{
throw std::logic_error("Response queue sync lost.");
}
return LV2_WORKER_SUCCESS;
}
if (!responseRingBuffer.write(size, (uint8_t *)data))
{
return LV2_WORKER_ERR_NO_SPACE;
}
return LV2_WORKER_SUCCESS;
}
void Worker::EmitResponses()
bool Worker::EmitResponses()
{
bool emitted = false;
while (true)
{
if (!responseRingBuffer.isReadReady())
{
break;
}
emitted = true;
uint32_t size;
responseRingBuffer.read(sizeof(size), (uint8_t *)&size);
if (size > responseBuffer.size())
{
responseBuffer.resize(size);
}
uint8_t *pResponse = &(responseBuffer[0]);
responseRingBuffer.read(size, pResponse);
workerInterface->work_response(lilvInstance->lv2_handle, size, pResponse);
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
}
cvOutstandingRequests.notify_all();
}
return emitted;
}
void Worker::WaitForAllResponses()
{
while (true)
{
uint32_t available = responseRingBuffer.readSpace();
uint32_t size = 0;
if (available <= sizeof(size)) // i.e. we need a size AND a response.
break;
responseRingBuffer.read(sizeof(size), (uint8_t *)&size);
responseRingBuffer.read(size, (uint8_t *)responseBuffer);
workerInterface->work_response(lilvInstance->lv2_handle, size, responseBuffer);
bool gotResponse = EmitResponses();
{
std::unique_lock lock(outstandingRequestMutex);
if (outstandingRequests == 0)
{
break;
}
if (!gotResponse)
{
cvOutstandingRequests.wait(lock);
}
}
}
}
void Worker::ThreadProc()
LV2_Worker_Status Worker::ScheduleWork(
uint32_t size,
const void *data)
{
// run nice +1 (priority -1 on Windows)
{
std::lock_guard lock(outstandingRequestMutex);
++outstandingRequests;
if (exiting)
{
return LV2_WORKER_ERR_NO_SPACE;
}
}
LV2_Worker_Status status = this->pHostWorker->ScheduleWork(this, size, data);
if (status != LV2_Worker_Status::LV2_WORKER_SUCCESS)
{
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
}
cvOutstandingRequests.notify_all();
}
return status;
}
void HostWorkerThread::ThreadProc() noexcept
{
// run nice +2 (priority -2 on Windows)
SetThreadName("lv2_worker");
errno = 0;
nice(1);
nice(2);
if (errno != 0)
{
std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl;
@@ -122,69 +202,117 @@ void Worker::ThreadProc()
return;
}
uint32_t size;
while (requestRingBuffer.readSpace() > sizeof(size))
Worker *pWorker;
if (!requestRingBuffer.read(sizeof(size), (uint8_t *)&size))
{
if (!requestRingBuffer.read(sizeof(size), (uint8_t *)&size))
{
throw PiPedalStateException("Working ringbuffer read failed.");
}
void *data = malloc(size);
if (!requestRingBuffer.read(size, (uint8_t *)data))
{
throw PiPedalStateException("Working ringbuffer read failed.");
}
workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data);
free(data);
throw PiPedalStateException("Working ringbuffer read failed.");
}
if (!requestRingBuffer.read(sizeof(pWorker), (uint8_t *)&pWorker))
{
throw PiPedalStateException("Worker ringbuffer read failed.");
}
size -= sizeof(pWorker);
if (size > dataBuffer.size())
{
dataBuffer.resize(size);
}
uint8_t *pData = &(dataBuffer[0]);
if (!requestRingBuffer.read(size, pData))
{
throw PiPedalStateException("Worker ringbuffer read failed.");
}
if (pWorker == nullptr) // signals close.
{
break;
}
pWorker->RunBackgroundTask(size, pData);
}
}
catch (const std::exception &e)
{
Lv2Log::error("Lv2 Worker thread proc exited abnormally. (%s)", e.what());
}
requestRingBuffer.close();
}
void Worker::StopWorkerThread()
HostWorkerThread::HostWorkerThread()
{
this->dataBuffer.resize(16*1024);
pThread = std::make_unique<std::thread>([this]()
{ this->ThreadProc(); });
}
void HostWorkerThread::Close()
{
bool sendClose = false;
{
std::lock_guard lock{submitMutex};
if (!closed)
{
closed = true;
sendClose = true;
}
}
if (sendClose)
{
ScheduleWork(nullptr, 0, nullptr);
}
}
HostWorkerThread::~HostWorkerThread()
{
if (pThread)
{
auto pThread = this->pThread;
this->pThread = nullptr;
exiting = true;
requestRingBuffer.close();
// ask worker thread to terminate.
Close();
pThread->join();
delete pThread;
pThread = nullptr;
}
}
void Worker::StartWorkerThread()
{
auto fn = [this]()
{ this->ThreadProc(); };
this->pThread = new std::thread(fn);
}
LV2_Worker_Status Worker::ScheduleWork(
uint32_t size,
const void *data)
LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, const void *data)
{
if (!exiting)
std::lock_guard lock(submitMutex);
if (exiting)
{
size_t space = requestRingBuffer.writeSpace();
if (space < sizeof(size) + size)
{
return LV2_WORKER_ERR_NO_SPACE;
}
if (!requestRingBuffer.write(sizeof(size), (uint8_t *)&size))
{
Lv2Log::debug("Not enough space in Worker ring buffer. Request failed.");
return LV2_WORKER_ERR_NO_SPACE;
}
if (!requestRingBuffer.write(size, (uint8_t *)data))
{
// probably not going to survive. :-(
Lv2Log::error("Not enough space in Worker ring buffer. Request ring buffer probably lost sync.");
return LV2_WORKER_ERR_NO_SPACE;
}
return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE;
}
return LV2_WORKER_ERR_NO_SPACE;
if (worker == nullptr)
{
exiting = true;
}
if (requestRingBuffer.writeSpace() < sizeof(worker) + sizeof(size) + size)
{
return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE;
}
size_t packetSizeL = (size + sizeof(worker));
uint32_t packetSize = (uint32_t)(packetSizeL);
if (packetSizeL != packetSize)
{
return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE;
}
requestRingBuffer.write(sizeof(packetSize), (uint8_t *)&packetSize);
requestRingBuffer.write(sizeof(worker), (uint8_t *)&worker);
requestRingBuffer.write(size, (uint8_t *)data);
return LV2_Worker_Status::LV2_WORKER_SUCCESS;
}
void Worker::RunBackgroundTask(size_t size, uint8_t *data)
{
workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data);
bool notify = false;
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
}
cvOutstandingRequests.notify_all();
}
+40 -12
View File
@@ -32,48 +32,76 @@
#include "lv2/urid.lv2/urid.h"
#include "lv2/atom.lv2/atom.h"
#include "lv2/worker.lv2/worker.h"
#include "condition_variable"
#include <map>
#include <string>
#include <mutex>
#include <thread>
#include "RingBuffer.hpp"
#include <memory>
namespace pipedal {
class Worker;
class HostWorkerThread {
public:
HostWorkerThread();
~HostWorkerThread();
void Close();
LV2_Worker_Status ScheduleWork(Worker*worker, size_t size, const void*data);
private:
bool closed = false;
std::unique_ptr<std::thread> pThread;
void ThreadProc() noexcept;
RingBuffer<false,true> requestRingBuffer;
bool exiting = false;
std::mutex submitMutex;
std::vector<uint8_t> dataBuffer;
};
class Worker {
private:
std::shared_ptr<HostWorkerThread> pHostWorker = nullptr;
LilvInstance*lilvInstance;
const LV2_Worker_Interface*workerInterface;
sem_t requestSemaphore;
bool closed = false;
bool exiting = false;
RingBuffer<false,true> requestRingBuffer;
RingBuffer<true,false> responseRingBuffer;
char *responseBuffer;
std::thread* pThread = nullptr;
void ThreadProc();
void StartWorkerThread();
void StopWorkerThread();
std::vector<uint8_t> responseBuffer;
static LV2_Worker_Status worker_respond_fn(LV2_Worker_Respond_Handle handle, uint32_t size, const void* data);
LV2_Worker_Status WorkerResponse(uint32_t size,const void*data);
LV2_Worker_Status WorkerRespond(uint32_t size,const void*data);
std::mutex outstandingRequestMutex;
std::condition_variable cvOutstandingRequests;
int64_t outstandingRequests = 0;
void WaitForAllResponses();
public:
Worker(LilvInstance *instance, const LV2_Worker_Interface *iface);
Worker(const std::shared_ptr<HostWorkerThread>& pHostWorker,LilvInstance *instance, const LV2_Worker_Interface *iface);
~Worker();
void Close();
LV2_Worker_Status ScheduleWork(
uint32_t size,
const void *data);
void EmitResponses();
void RunBackgroundTask(size_t size, uint8_t*data);
bool EmitResponses();
};
+1
View File
@@ -10,4 +10,5 @@
"http://two-play.com/plugins/toob-tone-stack": true,
"http://two-play.com/plugins/toob-tuner": true,
"http://two-play.com/plugins/toob-convolution-reverb": true
}
+60 -6
View File
@@ -32,6 +32,7 @@
#include <map>
#include <variant>
#include <concepts>
#include <limits>
#define DECLARE_JSON_MAP(CLASSNAME) \
static json_map::storage_type<CLASSNAME> jmap
@@ -277,7 +278,7 @@ namespace pipedal
const char *CRLF;
private:
bool allowNaN_ = true;
bool allowNaN_ = false;
std::ostream &os;
int indent_level;
bool compressed;
@@ -311,7 +312,7 @@ namespace pipedal
os << text;
}
using string_view = boost::string_view;
json_writer(std::ostream &os, bool compressed = true, bool allowNaN = true)
json_writer(std::ostream &os, bool compressed = true, bool allowNaN = false)
: os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0)
{
this->CRLF = compressed ? "" : "\r\n";
@@ -377,9 +378,14 @@ namespace pipedal
}
void write(float f)
{
if (allowNaN_ && (std::isnan(f) || std::isinf(f)))
if ((std::isnan(f) || std::isinf(f)))
{
os << "NaN";
if (allowNaN_)
{
os << "NaN";
} else {
os << std::numeric_limits<float>::max();
}
}
else
{
@@ -388,9 +394,14 @@ namespace pipedal
}
void write(double f)
{
if (allowNaN_ && (std::isnan(f) || std::isinf(f)))
if ((std::isnan(f) || std::isinf(f)))
{
os << "NaN";
if (allowNaN_)
{
os << "NaN";
} else {
os << std::numeric_limits<float>::max();
}
}
else
{
@@ -440,6 +451,23 @@ namespace pipedal
os << "]";
}
}
void write(const std::vector<float> &value)
{
// simple types: all on same line.
os << "[ ";
if (value.size() >= 1)
{
write(value[0]);
}
for (size_t i = 1; i < value.size(); ++i)
{
os << ",";
write(value[i]);
}
os << "]";
}
template <
class Category,
class T,
@@ -561,6 +589,14 @@ namespace pipedal
}
}
template<typename T>
requires IsJsonSerializable<T>
void write(T*obj)
{
writeRawWritable(*obj);
}
template <typename T>
void write(T *obj)
{
@@ -575,6 +611,13 @@ namespace pipedal
}
}
template<typename T>
requires IsJsonSerializable<T>
void write(T&obj)
{
writeRawWritable(obj);
}
template<typename T>
requires IsJsonSerializable<T>
void write(const T&obj)
@@ -678,6 +721,8 @@ namespace pipedal
}
public:
void start_object() { consume('{');}
void end_object() { consume('}');}
void consume(char expected);
void consumeToken(const char *expectedToken, const char *errorMessage);
int peek()
@@ -685,7 +730,16 @@ namespace pipedal
skip_whitespace();
return is_.peek();
}
template<typename U>
void read_member(const std::string&name,U *value)
{
std::string v;
read(&v);
if (v != name) throw std::logic_error("Expecting property '" + name + "'");
consume(':');
read(value);
}
public:
std::string read_string();
+85 -99
View File
@@ -23,7 +23,6 @@
#include <cstdint>
#include <string>
#include "json.hpp"
#include "json_variant.hpp"
#include <concepts>
@@ -31,60 +30,53 @@
using namespace pipedal;
class JsonTestTarget {
class JsonTestTarget
{
private:
JsonTestTarget(const JsonTestTarget&) {} // hide copy constructor.
JsonTestTarget& operator=(const JsonTestTarget&) { return *this;} // hide assignment.
public:
JsonTestTarget(const JsonTestTarget &) {} // hide copy constructor.
JsonTestTarget &operator=(const JsonTestTarget &) { return *this; } // hide assignment.
public:
JsonTestTarget() // make sure reading works without a default cosntructor.
{
}
bool operator==(const JsonTestTarget &other) const
{
return this->int_ == other.int_
&& this->float_ == other.float_
&& this->double_ == other.double_
&& this->string_ == other.string_
&& std::equal(this->ints_.begin(),this->ints_.end(), other.ints_.begin(),other.ints_.end())
&& std::equal(this->strings_.begin(),this->strings_.end(), other.strings_.begin(),other.strings_.end())
;
return this->int_ == other.int_ && this->float_ == other.float_ && this->double_ == other.double_ && this->string_ == other.string_ && std::equal(this->ints_.begin(), this->ints_.end(), other.ints_.begin(), other.ints_.end()) && std::equal(this->strings_.begin(), this->strings_.end(), other.strings_.begin(), other.strings_.end());
}
public:
int int_ = 1;
float float_ = 3;
double double_ = 4;
public:
std::string string_{"5"};
std::vector<int> ints_ { 1,2,3,4,5};
std::vector<std::string> strings_ { "a","b","c","d"};
std::vector<std::vector<int> > compound_array_ { { 1,2},{3},{4,5,6},{9}};
std::vector<int> ints_{1, 2, 3, 4, 5};
std::vector<std::string> strings_{"a", "b", "c", "d"};
std::vector<std::vector<int>> compound_array_{{1, 2}, {3}, {4, 5, 6}, {9}};
public:
static json_map::storage_type<JsonTestTarget> jmap;
public:
JsonTestTarget(json_reader& reader)
JsonTestTarget(json_reader &reader)
{
#ifdef JUNK
#ifdef JUNK
while (true)
{
const char*name = reader.read_member_name();
if (name == nullptr)
const char *name = reader.read_member_name();
if (name == nullptr)
{
break;
}
}
#endif
#endif
}
};
json_map::storage_type<JsonTestTarget> JsonTestTarget::jmap {{
//json_map::reference("char_", &JsonTestTarget::char_),
json_map::storage_type<JsonTestTarget> JsonTestTarget::jmap{{
// json_map::reference("char_", &JsonTestTarget::char_),
json_map::reference("int", &JsonTestTarget::int_),
json_map::reference("floatt", &JsonTestTarget::float_),
json_map::reference("double", &JsonTestTarget::double_),
@@ -94,12 +86,11 @@ json_map::storage_type<JsonTestTarget> JsonTestTarget::jmap {{
json_map::reference("compoundarray", &JsonTestTarget::compound_array_),
}};
TEST_CASE( "json write", "[json_write_test]" ) {
TEST_CASE("json write", "[json_write_test]")
{
std::cout << "== json write ==" << std::endl;
std::stringstream os;
json_writer writer { os };
json_writer writer{os};
JsonTestTarget testTarget;
@@ -111,20 +102,19 @@ TEST_CASE( "json write", "[json_write_test]" ) {
static std::string get_json()
{
std::stringstream os;
json_writer writer { os };
json_writer writer{os};
JsonTestTarget testTarget;
writer.write(testTarget);
return os.str();
}
TEST_CASE( "json read", "[json_read_test]" ) {
TEST_CASE("json read", "[json_read_test]")
{
JsonTestTarget source;
source.ints_ = { 1,7,4};
source.ints_ = {1, 7, 4};
source.string_ = "xyz";
source.double_ = 99483.1837;
@@ -133,57 +123,53 @@ TEST_CASE( "json read", "[json_read_test]" ) {
writer.write(source);
std::string json = os.str();
std::stringstream input (json);
json_reader reader { input };
std::stringstream input(json);
json_reader reader{input};
JsonTestTarget dest;
reader.read(&dest);
REQUIRE(reader.is_complete());
REQUIRE( source == dest);
REQUIRE(source == dest);
JsonTestTarget *pDest;
std::stringstream input2(json);
json_reader reader2 { input2};
json_reader reader2{input2};
reader2.read(&pDest);
//JsonTestTarget *testTarget = reader.read_object<JsonTestTarget>();
// JsonTestTarget *testTarget = reader.read_object<JsonTestTarget>();
}
TEST_CASE( "json smart ptrs", "[json_smart_ptrs][Build][Dev]" ) {
std::string json =get_json();
TEST_CASE("json smart ptrs", "[json_smart_ptrs][Build][Dev]")
{
std::string json = get_json();
{
std::unique_ptr<JsonTestTarget> uniquePtr;
std::stringstream input(json);
json_reader reader { input };
reader.read(&uniquePtr);
json_reader reader{input};
reader.read(&uniquePtr);
}
{
std::shared_ptr<JsonTestTarget> sharedPtr;
std::stringstream input(json);
json_reader reader { input };
reader.read(&sharedPtr);
json_reader reader{input};
reader.read(&sharedPtr);
}
}
template <typename T>
void TestVariantRoundTrip(const T &value)
{
json_variant variant(value);
T out = variant.get<T>();
T out = variant.as<T>();
REQUIRE(out == value);
std::string output;
{
std::stringstream s;
json_writer writer(s);
writer.write(variant);
writer.write(variant);
output = s.str();
}
std::cout << output << std::endl;
@@ -191,11 +177,10 @@ void TestVariantRoundTrip(const T &value)
{
std::stringstream s(output);
json_reader reader(s);
json_variant outputVariant;
reader.read(&outputVariant);
REQUIRE(outputVariant == variant);
}
}
void TestVariantRoundTrip(json_variant &value)
@@ -207,7 +192,7 @@ void TestVariantRoundTrip(json_variant &value)
{
std::stringstream s;
json_writer writer(s);
writer.write(variant);
writer.write(variant);
output = s.str();
}
std::cout << output << std::endl;
@@ -223,14 +208,12 @@ void TestVariantRoundTrip(json_variant &value)
}
}
class X{
class X
{
public:
template<typename T>
requires std::derived_from<T,JsonSerializable>
bool write(T &v)
template <typename T>
requires std::derived_from<T, JsonSerializable> bool
write(T &v)
{
(void)v;
return true;
@@ -243,52 +226,55 @@ public:
}
};
void TestVariantSFINAE()
{
X x;
json_variant v;
dynamic_cast<JsonSerializable&>(v);
dynamic_cast<JsonSerializable &>(v);
REQUIRE(x.write(v) == true);
int i;
REQUIRE(x.write(i) == false);
}
TEST_CASE( "json variants", "[json_variants][Build][Dev]" ) {
TestVariantSFINAE();
json_variant v(0);
TestVariantRoundTrip(json_null());
TestVariantRoundTrip(0.0);
TestVariantRoundTrip(std::string("abc"));
json_array array;
array.push_back(json_null());
array.push_back(3.25E19);
array.push_back(std::string("abc"));
json_variant variantArray { std::move(array) };
TestVariantRoundTrip(variantArray);
TEST_CASE("json variants", "[json_variants][Build][Dev]")
{
{
json_object obj;
obj["a"] = json_null();
obj["b"] = 0.25;
obj["c"] = std::move(variantArray);
json_variant variantObj { std::move(obj)};
TestVariantRoundTrip(variantObj);
TestVariantSFINAE();
variantObj["a"] = std::string("abc");
TestVariantRoundTrip(variantObj);
TestVariantRoundTrip(0.0);
TestVariantRoundTrip(std::string("abc"));
TestVariantRoundTrip(json_null());
json_array array;
array.push_back(json_null());
array.push_back(3.25E19);
array.push_back(std::string("abc"));
json_variant variantArray{std::move(array)};
REQUIRE(array.size() == 0); // did it get moved property?
TestVariantRoundTrip(variantArray);
{
json_object obj;
obj["a"] = json_null();
obj["b"] = 0.25;
obj["c"] = std::move(variantArray);
json_variant variantObj{std::move(obj)};
REQUIRE((variantArray.is_null() || variantArray.size() == 0) == true); // did move happen?
REQUIRE(obj.size() == 0); // did move happen?
TestVariantRoundTrip(variantObj);
variantObj["a"] = std::string("abc");
TestVariantRoundTrip(variantObj);
}
{
json_variant x = json_variant::make_array();
x.resize(3);
x[0] = "def";
}
}
{
json_variant x = json_variant::MakeArray();
x.resize(3);
x[0] = "def";
}
}
REQUIRE(json_object::allocation_count() == 0);
REQUIRE(json_array::allocation_count() == 0);
}
+527 -12
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2023 Robin E. R. Davies
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* 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
@@ -26,19 +26,534 @@
#include <limits>
#include <cmath>
#include <cstddef>
#include <memory>
using namespace pipedal;
void concrete_json_variant_base::write_double_value(json_writer &writer,double value) const
json_variant::~json_variant()
{
if (value < std::numeric_limits<int32_t>::max() && value > std::numeric_limits<int32_t>::min())
free();
}
void json_variant::free()
{
// in-place deletion.
switch (content_type)
{
double frac = value-(int32_t)value;
if (value == 0)
{
writer.write((int32_t)value);
return;
}
case ContentType::String:
memString().std::string::~string();
break;
case ContentType::Object:
memObject().std::shared_ptr<json_object>::~shared_ptr();
break;
case ContentType::Array:
memArray().std::shared_ptr<json_array>::~shared_ptr();
break;
}
content_type = ContentType::Null;
}
json_variant::json_variant(json_variant &&other)
{
this->content_type = ContentType::Null;
switch (other.content_type)
{
case ContentType::Null:
break;
case ContentType::Bool:
this->content.bool_value = other.content.bool_value;
break;
case ContentType::Number:
this->content.double_value = other.content.double_value;
break;
case ContentType::String:
new (content.mem) std::string(std::move(other.memString()));
this->content_type = ContentType::String;
return;
case ContentType::Object:
new (content.mem) std::shared_ptr<json_object>{std::move(other.memObject())};
this->content_type = ContentType::Object;
return;
case ContentType::Array:
new (content.mem) std::shared_ptr<json_array>{std::move(other.memArray())};
this->content_type = ContentType::Array;
return;
}
this->content_type = other.content_type;
other.content_type = ContentType::Null;
}
json_variant &json_variant::operator=(const json_variant &other)
{
free();
switch (other.content_type)
{
case ContentType::Null:
break;
case ContentType::Bool:
this->content.bool_value = other.content.bool_value;
break;
case ContentType::Number:
this->content.double_value = other.content.double_value;
break;
case ContentType::String:
new (content.mem) std::string(other.memString());
break;
case ContentType::Object:
new (content.mem) std::shared_ptr<json_object>{other.memObject()};
break;
case ContentType::Array:
new (content.mem) std::shared_ptr<json_array>{other.memArray()};
this->content_type = ContentType::Array;
break;
}
this->content_type = other.content_type;
return *this;
}
json_variant::json_variant(const json_variant &other)
{
this->content_type = ContentType::Null;
switch (other.content_type)
{
case ContentType::Null:
break;
case ContentType::Bool:
this->content.bool_value = other.content.bool_value;
break;
case ContentType::Number:
this->content.double_value = other.content.double_value;
break;
case ContentType::String:
new (content.mem) std::string(other.memString());
this->content_type = ContentType::String;
return;
case ContentType::Object:
new (content.mem) std::shared_ptr<json_object>{other.memObject()};
this->content_type = ContentType::Object;
return;
case ContentType::Array:
new (content.mem) std::shared_ptr<json_array>{other.memArray()};
this->content_type = ContentType::Array;
return;
}
this->content_type = other.content_type;
}
void json_variant::write_float_value(json_writer &writer, double value) const
{
writer.write(value);
}
void json_variant::write_double_value(json_writer &writer, double value) const
{
writer.write(value);
}
void json_variant::read_json(json_reader &reader)
{
int v = reader.peek();
if (v == '[')
{
json_array array;
reader.read(&array);
(*this) = std::move(array);
}
else if (v == '{')
{
json_object object;
reader.read(&object);
(*this) = std::move(object);
}
else if (v == '\"')
{
std::string s;
reader.read(&s);
(*this) = std::move(s);
}
else if (v == 'n')
{
reader.read_null();
(*this) = json_null();
}
else if (v == 't' || v == 'f')
{
bool b;
reader.read(&b);
(*this) = b;
}
else
{
// it's a number.
double v;
reader.read(&v);
(*this) = v;
}
}
void json_variant::write_json(json_writer &writer) const
{
switch (content_type)
{
case ContentType::Null:
writer.write_raw("null");
break;
case ContentType::Bool:
writer.write(as_bool());
break;
case ContentType::Number:
write_double_value(writer, as_number());
break;
case ContentType::String:
writer.write(as_string());
break;
case ContentType::Object:
writer.write(as_object());
break;
case ContentType::Array:
writer.write(as_array());
break;
default:
throw std::logic_error("Invalid variant type");
}
}
bool json_array::operator==(const json_array &other) const
{
if (!(this->size() == other.size()))
return false;
for (size_t i = 0; i < this->size(); ++i)
{
if (!((*this)[i] == other[i]))
return false;
}
return true;
}
void json_array::read_json(json_reader &reader)
{
reader.read(&(this->values));
}
void json_array::write_json(json_writer &writer) const
{
writer.start_array();
bool first = true;
for (auto &value : values)
{
if (!first)
writer.write_raw(",");
first = false;
writer.write(value);
}
writer.end_array();
}
json_object::iterator json_object::find(const std::string& key)
{
for (auto i = begin(); i != end(); ++i)
{
if (i->first == key)
{
return i;
}
}
return end();
}
json_object::const_iterator json_object::find(const std::string&key) const
{
for (auto i = begin(); i != end(); ++i)
{
if (i->first == key)
{
return i;
}
}
return end();
}
bool json_object::operator==(const json_object &other) const
{
for (const auto &pair : this->values)
{
auto index = other.find(pair.first);
if (index == other.end())
return false;
if (!(index->second == pair.second))
return false;
}
for (const auto &pair : other.values)
{
auto index = this->find(pair.first);
if (index == this->end())
return false;
if (!(index->second == pair.second))
return false;
}
return true;
}
void json_object::read_json(json_reader &reader)
{
reader.start_object();
while (true)
{
if (reader.peek() == '}') break;
std::string key;
json_variant value;
reader.read(&key);
reader.consume(':');
reader.read(&value);
(*this)[key] = value;
if (reader.peek() == ',')
{
reader.consume(',');
}
}
reader.end_object();
}
void json_object::write_json(json_writer &writer) const
{
writer.start_object();
bool first = true;
for (auto &value : values)
{
if (!first)
{
writer.write_raw(",");
}
first = false;
writer.write(value.first);
writer.write_raw(": ");
writer.writeRawWritable(value.second);
}
writer.end_object();
}
json_variant &json_array::at(size_t index)
{
check_index(index);
return values.at(index);
}
const json_variant &json_array::at(size_t index) const
{
check_index(index);
return values.at(index);
}
size_t json_variant::size() const
{
if (content_type == ContentType::Array)
return as_array()->size();
if (content_type == ContentType::Object)
{
return as_object()->size();
}
throw std::logic_error("Not supported.");
}
json_variant &json_object::at(const std::string &index) {
for (auto&entry: values)
{
if (entry.first == index)
{
return entry.second;
}
}
throw std::logic_error("Not found.");
values.push_back(std::pair(index,json_variant()));
return values[values.size()-1].second;
}
const json_variant &json_object::at(const std::string &index) const {
for (const auto&entry: values)
{
if (entry.first == index)
{
return entry.second;
}
}
throw std::logic_error("Not found.");
}
json_variant &json_object::operator[](const std::string &index)
{
for (auto&entry: values)
{
if (entry.first == index)
{
return entry.second;
}
}
values.push_back(std::pair(index,json_variant()));
return values[values.size()-1].second;
}
const json_variant &json_object::operator[](const std::string &index) const
{
return at(index);
}
bool json_object::contains(const std::string &index) const
{
return find(index) != end();
}
json_variant &json_variant::operator=(bool value)
{
free();
this->content_type = ContentType::Bool;
this->content.bool_value = value;
return *this;
}
json_variant &json_variant::operator=(double value)
{
free();
this->content_type = ContentType::Number;
this->content.double_value = value;
return *this;
}
json_variant &json_variant::operator=(const std::string &value)
{
free();
this->content_type = ContentType::String;
new (this->content.mem) std::string(value); // in-place constructor.
return *this;
}
json_variant &json_variant::operator=(std::string &&value)
{
free();
this->content_type = ContentType::String;
new (this->content.mem) std::string(std::move(value)); // in-place constructor.
return *this;
}
json_variant &json_variant::operator=(json_object &&value)
{
free();
new (this->content.mem) std::shared_ptr<json_object>{new json_object(std::move(value))};
this->content_type = ContentType::Object;
return *this;
}
json_variant &json_variant::operator=(json_array &&value)
{
free();
this->content_type = ContentType::Array;
new (this->content.mem) std::shared_ptr<json_array>{new json_array(std::move(value))};
return *this;
}
json_variant &json_variant::operator=(json_variant &&value)
{
if (this->content_type == value.content_type)
{
switch (this->content_type)
{
case ContentType::String:
std::swap(this->memString(), value.memString());
return *this;
case ContentType::Array:
std::swap(this->memArray(), value.memArray());
return *this;
case ContentType::Object:
std::swap(this->memObject(), value.memObject());
return *this;
}
}
free();
switch (value.content_type)
{
case ContentType::String:
new (content.mem) std::string(std::move(value.memString()));
value.memString().std::string::~string();
break;
case ContentType::Object:
new (content.mem) std::shared_ptr<json_object>(std::move(value.memObject()));
value.memObject().std::shared_ptr<json_object>::~shared_ptr();
break;
case ContentType::Array:
new (content.mem) std::shared_ptr<json_array>(std::move(value.memArray()));
value.memArray().std::shared_ptr<json_array>::~shared_ptr();
break;
default:
// undifferentiated copy of POD types.
*(uint64_t *)(this->content.mem) = *(uint64_t *)(value.content.mem);
break;
}
this->content_type = value.content_type;
value.content_type = ContentType::Null;
return *this;
}
bool json_variant::operator==(const json_variant &other) const
{
if (this->content_type != other.content_type)
return false;
switch (this->content_type)
{
case ContentType::Null:
return true;
case ContentType::Bool:
return as_bool() == other.as_bool();
break;
case ContentType::Number:
return as_number() == other.as_number();
case ContentType::String:
return as_string() == other.as_string();
case ContentType::Array:
return *(as_array().get()) == *(other.as_array().get());
case ContentType::Object:
return *(as_object().get()) == *(other.as_object().get());;
default:
throw std::logic_error("Invalid content_type.");
}
}
json_array::json_array(json_array&&other)
: values(std::move(other.values))
{
++allocation_count_;
}
json_object::json_object(json_object&&other)
:values(std::move(other.values))
{
++allocation_count_;
}
bool json_variant::contains(const std::string &index) const
{
if (is_object())
{
return as_object()->contains(index);
}
return false;
}
std::string json_variant::to_string() const
{
std::stringstream ss;
json_writer writer(ss);
writer.write(*this);
return ss.str();
}
json_variant::json_variant(const char*sz)
: json_variant(std::string(sz))
{
}
/*static*/ json_null json_null::instance;
/*static*/ int64_t json_array::allocation_count_ = 0; // strictly for testing purposes. not thread safe.
/*static*/ int64_t json_object::allocation_count_ = 0; // strictly for testing purposes. not thread safe.
+473 -248
View File
@@ -26,8 +26,10 @@
#include <vector>
#include <variant>
#include <map>
#include <string>
#include <stdexcept>
#include <utility>
#include "json.hpp"
namespace pipedal
@@ -35,291 +37,514 @@ namespace pipedal
class json_null
{
public:
bool operator==(const json_null&other) const { return true;}
static json_null instance;
bool operator==(const json_null &other) const { return true; }
bool operator!=(const json_null &other) const { return (!((*this) == other)); }
private:
int value = 0;
};
class json_object;
class json_array;
template <class T> // avoid ordering problem in declarations.
class json_object_base: public JsonSerializable
class json_variant
: public JsonSerializable
{
public:
using json_variant = T;
json_object_base() {}
T &operator[](const std::string &index) { return values[index]; }
const T &operator[](const std::string &index) const { return values[index]; }
public:
bool operator==(const json_object_base<T> &other) const
enum class ContentType
{
for (const auto &pair: this->values)
{
auto index = other.values.find(pair.first);
if (index == other.values.end()) return false;
if (!(index->second == pair.second)) return false;
}
for (const auto &pair: other.values)
{
auto index = this->values.find(pair.first);
if (index == this->values.end()) return false;
if (!(index->second == pair.second)) return false;
}
return true;
}
Null,
Bool,
Number,
String,
Object,
Array
};
using object_ptr = std::shared_ptr<json_object>;
using array_ptr = std::shared_ptr<json_array>;
private:
virtual void read_json(json_reader&reader) {
reader.read(&(this->values));
}
virtual void write_json(json_writer&writer) const {
writer.start_object();
bool first = true;
for (auto&value: values)
{
if (!first)
{
writer.write_raw(",");
}
first = false;
writer.write(value.first);
writer.write_raw(": ");
writer.writeRawWritable(value.second);
}
writer.end_object();
}
std::map<std::string, T> values;
};
template <class T> // avoid ordering problem in declarations.
class json_array_base: public JsonSerializable
{
public:
json_array_base() {}
~json_variant();
json_variant();
json_variant(json_variant &&);
json_variant(const json_variant &);
T &operator[](size_t index) {
check_index(index);
return values[index]; }
const T &operator[](size_t &index) const {
check_index(index);
return values[index];
}
void resize(size_t size)
{
values.resize(size);
}
size_t size() const { return values.size(); }
template <typename U>
void push_back(const U&value) { values.push_back(value); }
template <typename U>
void push_back(U&&value) { values.push_back(value); }
bool operator==(const json_array_base<T>&other) const
{
if (!(this->size() == other.size())) return false;
for (size_t i = 0; i < this->size(); ++i)
{
if (!((*this)[i] == other[i])) return false;
}
return true;
}
private:
virtual void read_json(json_reader&reader) {
reader.read(&(this->values));
}
virtual void write_json(json_writer&writer) const {
writer.start_array();
bool first = true;
for (auto&value: values)
{
if (!first) writer.write_raw(",");
first = false;
writer.writeRawWritable(value);
}
writer.end_array();
}
json_variant(json_null value);
json_variant(bool value);
json_variant(double value);
json_variant(const std::string &value);
json_variant(std::shared_ptr<json_object> &&value);
json_variant(const std::shared_ptr<json_object> &value);
json_variant(std::shared_ptr<json_array> &&value);
json_variant(const std::shared_ptr<json_array> &value);
json_variant(json_array &&array);
json_variant(json_object &&object);
json_variant(const char*sz);
void check_index(size_t size) const
json_variant(const void*) = delete; // do NOT allow implicit conversion of pointers to bool
json_variant &operator=(json_variant &&value);
json_variant &operator=(const json_variant &value);
json_variant &operator=(bool value);
json_variant &operator=(double value);
json_variant &operator=(const std::string &value);
json_variant &operator=(std::string &&value);
json_variant &operator=(json_object &&value);
json_variant &operator=(json_array &&value);
json_variant &operator=(const char*sz) { return (*this) = std::string(sz); }
json_variant &operator=(void*) = delete; // do NOT allow implicit conversion of pointers to bool
void require_type(ContentType content_type) const
{
if (size >= values.size())
if (this->content_type != content_type)
{
throw std::out_of_range("index out of range.");
throw std::logic_error("Content type is not valid.");
}
}
std::vector<T> values;
};
bool is_null() const { return content_type == ContentType::Null; }
bool is_bool() const { return content_type == ContentType::Bool; }
bool is_number() const { return content_type == ContentType::Number; }
bool is_string() const { return content_type == ContentType::String; }
bool is_object() const { return content_type == ContentType::Object; }
bool is_array() const { return content_type == ContentType::Array; }
const json_null &as_null() const
{
require_type(ContentType::Bool);
return json_null::instance;
}
json_null &as_null()
{
require_type(ContentType::Null);
return json_null::instance;
}
class concrete_json_variant_base {
protected:
void write_double_value(json_writer &writer,double value) const;
};
bool as_bool() const
{
require_type(ContentType::Bool);
return content.bool_value;
}
bool &as_bool()
{
require_type(ContentType::Bool);
return content.bool_value;
}
template <typename DUMMY = void>
class json_variant_base
: public std::variant<json_null, bool, double, std::string, json_object_base<json_variant_base<DUMMY>>, json_array_base<json_variant_base<DUMMY>>>,
public JsonSerializable,
private concrete_json_variant_base
{
public:
using base = std::variant<json_null, bool, double, std::string, json_object_base<json_variant_base<DUMMY>>, json_array_base<json_variant_base<DUMMY>>>;
using json_object = json_object_base<json_variant_base<DUMMY>>;
using json_array = json_array_base<json_variant_base<DUMMY>>;
using json_variant = json_variant_base<void>;
double as_number() const
{
require_type(ContentType::Number);
return content.double_value;
}
double &as_number()
{
require_type(ContentType::Number);
return content.double_value;
}
const std::string &as_string() const;
std::string &as_string();
json_variant_base(json_null value)
:base(value)
{
const std::shared_ptr<json_object> &as_object() const;
std::shared_ptr<json_object> &as_object();
}
json_variant_base(double value)
: base(value)
{
}
json_variant_base(int value)
: base((double)value)
{
}
json_variant_base(const std::string &value)
: base(value)
{
}
json_variant_base(const char*value)
:base(std::string(value))
{
}
json_variant_base()
: base(json_null())
{
}
json_variant_base(json_object &&value)
: base(std::forward<json_object>(value))
{
}
json_variant_base(json_array &&value)
: base(std::forward<json_array>(value))
{
}
const std::shared_ptr<json_array> &as_array() const;
std::shared_ptr<json_array> &as_array();
template <typename U>
bool holds_alternative() const { return std::holds_alternative<U>(*this);}
bool IsNull() const { return holds_alternative<json_null>(); }
bool IsBool() const { return holds_alternative<bool>(); }
bool IsNumber() const { return holds_alternative<double>(); }
bool IsString() const { return holds_alternative<std::string>(); }
bool IsObject() const { return holds_alternative<json_object>(); }
bool IsArray() const { return holds_alternative<json_array>(); }
template <typename U>
const U &get() const
{
return std::get<U>(*this);
}
template <typename U>
U &get()
{
return std::get<U>(*this);
}
bool &AsBool() { return get<bool>(); }
bool AsBool() const { return get<bool>(); }
double &AsNumber() { return get<double>(); }
double AsNumber() const { return get<double>(); }
std::string &AsString() { return get<std::string>(); }
const std::string &AsString() const { return get<std::string>(); }
json_object &AsObject() { return get<json_object>(); }
const json_object &AsObject() const { return get<json_object>(); }
std::vector<float> AsFloatArray() { return get<json_object>().AsFloatArray(); }
std::vector<double> AsDoubleArray() { return get<json_object>().AsDoubleArray(); }
json_array &AsArray() { return get<json_array>(); }
const json_array &AsArray() const { return get<json_array>(); }
U &as() { static_assert("Invalid type."); }
// convenience methods for object and array manipulation.
static json_variant MakeObject() { return json_variant{ json_object()};};
static json_variant MakeArray() { return json_variant{ json_array()};};
static json_variant make_object();
static json_variant make_array();
void resize(size_t size) { AsArray().resize(size); }
size_t size() const { return AsArray().size(); }
void resize(size_t size);
size_t size() const;
json_variant&operator[](size_t index) { return AsArray()[index];}
const json_variant&operator[](size_t index) const { return AsArray()[index];}
bool contains(const std::string &index) const;
const json_variant&operator[](const std::string& index) const { return AsObject()[index];}
json_variant&operator[](const std::string& index) { return AsObject()[index];}
json_variant &at(size_t index);
const json_variant &at(size_t index) const;
json_variant &operator[](size_t index);
const json_variant &operator[](size_t index) const;
json_variant &operator[](const std::string &index);
const json_variant &operator[](const std::string &index) const;
bool operator==(const json_variant &other) const;
bool operator!=(const json_variant &other) const;
std::string to_string() const;
private:
void free();
void write_double_value(json_writer &writer, double value) const;
void write_float_value(json_writer &writer, double value) const;
virtual void read_json(json_reader &reader);
virtual void write_json(json_writer &writer) const;
static constexpr size_t stringSize = sizeof(std::string);
static constexpr size_t objectSize = sizeof(std::shared_ptr<json_variant>);
static constexpr size_t memSize = stringSize > objectSize ? stringSize : objectSize;
union Content
{
bool bool_value;
double double_value;
float float_value;
int32_t int32_value;
uint8_t mem[memSize];
};
ContentType content_type = ContentType::Null;
Content content;
std::string &memString();
object_ptr &memObject();
array_ptr &memArray();
const std::string &memString() const;
const object_ptr &memObject() const;
const array_ptr &memArray() const;
};
class json_array : public JsonSerializable
{
private:
json_array(const json_array&) { } // deleted.
public:
using ptr = std::shared_ptr<json_array>;
json_array() { ++allocation_count_; }
json_array(json_array&&other);
~json_array() { --allocation_count_; }
json_variant &at(size_t index);
const json_variant &at(size_t index) const;
json_variant &operator[](size_t index);
const json_variant &operator[](size_t &index) const;
void resize(size_t size) { values.resize(size); }
size_t size() const { return values.size(); }
void push_back(json_variant &&value) { values.push_back(std::move(value)); }
template <typename U>
void push_back(U &&value) { values.push_back(value); }
void push_back(double value) { values.push_back(json_variant{value}); }
void push_back(const std::string &value) { values.push_back(json_variant{value}); }
void push_back(bool value) { values.push_back(json_variant{value}); }
void push_back(const std::shared_ptr<json_array> &value) { values.push_back(json_variant(value)); }
void push_back(const std::shared_ptr<json_object> &value) { values.push_back(json_variant(value)); }
bool operator==(const json_array &other) const;
bool operator!=(const json_array &other) const { return (!((*this) == other)); }
// Strictly for testing purposes. Not thread-safe.
static int64_t allocation_count()
{
return allocation_count_;
}
using iterator = std::vector<json_variant>::iterator;
using const_iterator = std::vector<json_variant>::const_iterator;
iterator begin() { return values.begin(); }
iterator end() { return values.end(); }
const_iterator begin() const { return values.begin(); }
const_iterator end() const { return values.end(); }
private:
virtual void read_json(json_reader &reader)
{
int v = reader.peek();
if (v == '[')
{
json_array array;
reader.read(&array);
(*this) = std::move(array);
} else if (v == '{')
{
json_object object;
reader.read(&object);
(*this) = std::move(object);
}
else if (v == '\"') {
std::string s;
reader.read(&s);
(*this) = std::move(s);
} else if (v == 'n')
{
reader.read_null();
(*this) = json_null();
static int64_t allocation_count_; // strictly for testing purposes. not thread safe.
} else if (v == 't' || v == 'f')
{
bool b;
reader.read(&b);
(*this) = b;
virtual void read_json(json_reader &reader);
virtual void write_json(json_writer &writer) const;
} else {
// it's a number.
double v;
reader.read(&v);
(*this) = v;
}
}
virtual void write_json(json_writer&writer) const
void check_index(size_t size) const;
std::vector<json_variant> values;
};
class json_object : public JsonSerializable
{
private:
json_object(const json_object&) { } // deleted.
public:
using ptr = std::shared_ptr<json_object>;
json_object() { ++ allocation_count_;}
json_object(json_object&&other);
~json_object() { --allocation_count_;}
size_t size() const { return values.size(); }
json_variant &at(const std::string &index);
const json_variant &at(const std::string &index) const;
json_variant &operator[](const std::string &index);
const json_variant &operator[](const std::string &index) const;
bool operator==(const json_object &other) const;
bool operator!=(const json_object &other) const { return (!((*this) == other)); }
bool contains(const std::string &index) const;
using values_t = std::vector< std::pair<std::string, json_variant> >;
using iterator = values_t::iterator;
using const_iterator = values_t::const_iterator;
iterator begin() { return values.begin(); }
iterator end() { return values.end(); }
const_iterator begin() const { return values.begin(); }
const_iterator end() const { return values.end(); }
iterator find(const std::string& key);
const_iterator find(const std::string& key) const;
// strictly for testing purposes. Not thread-safe.
static int64_t allocation_count()
{
switch (this->index())
{
case 0:
writer.write_raw("null");
break;
case 1:
writer.write(this->get<bool>());
break;
case 2:
write_double_value(writer,this->get<double>());
break;
case 3:
writer.write(get<std::string>());
break;
case 4:
writer.writeRawWritable(get<json_object>());
break;
case 5:
writer.writeRawWritable(get<json_array>());
break;
default:
throw std::logic_error("Invalid variant index");
}
return allocation_count_;
}
private:
virtual void read_json(json_reader &reader);
virtual void write_json(json_writer &writer) const;
static int64_t allocation_count_;
values_t values;
};
using json_variant = json_variant_base<void>;
using json_object = json_variant::json_object;
using json_array = json_variant::json_array;
////////////////////////////////////////////////
inline std::string &json_variant::memString() { return *(std::string *)content.mem; }
inline const std::string &json_variant::memString() const { return *(const std::string *)content.mem; }
template <>
inline json_null &json_variant::as<json_null>() { return as_null(); }
template <>
inline bool &json_variant::as<bool>() { return as_bool(); }
template <>
inline double &json_variant::as<double>() { return as_number(); }
template <>
inline std::string &json_variant::as<std::string>() { return as_string(); }
template <>
inline std::shared_ptr<json_object> &json_variant::as<std::shared_ptr<json_object>>() { return as_object(); }
template <>
inline std::shared_ptr<json_array> &json_variant::as<std::shared_ptr<json_array>>() { return as_array(); }
template <>
inline json_variant &json_variant::as<json_variant>() { return *this; }
inline json_variant::object_ptr &json_variant::memObject()
{
return *(object_ptr *)content.mem;
}
inline json_variant::array_ptr &json_variant::memArray()
{
return *(array_ptr *)content.mem;
}
inline const json_variant::object_ptr &json_variant::memObject() const
{
return *(const object_ptr *)content.mem;
}
inline const json_variant::array_ptr &json_variant::memArray() const
{
return *(const array_ptr *)content.mem;
}
inline json_variant::json_variant(json_array &&array)
{
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_array>{new json_array(std::move(array))};
this->content_type = ContentType::Array;
}
inline json_variant::json_variant(json_object &&object)
{
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_object>{new json_object(std::move(object))};
this->content_type = ContentType::Object;
}
inline json_variant::json_variant(const std::string &value)
{
this->content_type = ContentType::Null;
new (content.mem) std::string(value); // placement new.
content_type = ContentType::String;
}
inline json_variant::json_variant(std::shared_ptr<json_object> &&value)
{
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_object>(std::move(value)); // placement new.
content_type = ContentType::Object;
}
inline json_variant::json_variant(const std::shared_ptr<json_object> &value)
{
// don't deep copy!
std::shared_ptr<json_object> t = const_cast<std::shared_ptr<json_object> &>(value);
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_object>(t); // placement new.
content_type = ContentType::Object;
}
inline json_variant::json_variant(const std::shared_ptr<json_array> &value)
{
// Make sure we don't deep copy!
std::shared_ptr<json_array> t = const_cast<std::shared_ptr<json_array> &>(value);
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_array>(t); // placement new.
content_type = ContentType::Array;
}
inline json_variant::json_variant(array_ptr &&value)
{
this->content_type = ContentType::Null;
new (content.mem) std::shared_ptr<json_array>(std::move(value)); // placement new.
content_type = ContentType::Array;
}
inline json_variant::json_variant()
{
content_type = ContentType::Null;
}
inline json_variant::json_variant(json_null value)
{
content_type = ContentType::Null;
}
inline json_variant::json_variant(bool value)
{
content_type = ContentType::Bool;
content.bool_value = value;
}
inline json_variant::json_variant(double value)
{
content_type = ContentType::Number;
content.double_value = value;
}
inline std::string &json_variant::as_string()
{
require_type(ContentType::String);
return memString();
}
inline const std::string &json_variant::as_string() const
{
require_type(ContentType::String);
return memString();
}
inline const json_variant::object_ptr &json_variant::as_object() const
{
require_type(ContentType::Object);
return memObject();
}
inline json_variant::object_ptr &json_variant::as_object()
{
require_type(ContentType::Object);
return memObject();
}
inline const json_variant::array_ptr &json_variant::as_array() const
{
require_type(ContentType::Array);
return memArray();
}
inline json_variant::array_ptr &json_variant::as_array()
{
require_type(ContentType::Array);
return memArray();
}
inline /*static*/ json_variant json_variant::make_object()
{
return json_variant{std::make_shared<json_object>()};
};
inline /*static */ json_variant json_variant::make_array()
{
return json_variant{std::make_shared<json_array>()};
};
inline void json_variant::resize(size_t size)
{
as_array()->resize(size);
}
inline json_variant &json_variant::at(size_t index)
{
return as_array()->at(index);
}
inline const json_variant &json_variant::at(size_t index) const
{
return as_array()->at(index);
}
inline json_variant &json_variant::operator[](size_t index)
{
return as_array()->at(index);
}
inline const json_variant &json_variant::operator[](size_t index) const
{
return as_array()->at(index);
}
inline const json_variant &json_variant::operator[](const std::string &index) const
{
return (*as_object())[index];
}
inline json_variant &json_variant::operator[](const std::string &index)
{
return (*as_object())[index];
}
inline const json_variant &json_array::operator[](size_t &index) const
{
check_index(index);
return values[index];
}
inline json_variant &json_array::operator[](size_t index)
{
return at(index);
}
inline void json_array::check_index(size_t size) const
{
if (size >= values.size())
{
throw std::out_of_range("index out of range.");
}
}
inline bool json_variant::operator!=(const json_variant &other) const
{
return !(*this == other);
}
// Holds a string but is json_read and json_written as an unqoted json object.
class raw_json_string: public JsonSerializable {
public:
raw_json_string() { }
raw_json_string(const std::string &value) : value(value) {}
const std::string& as_string() const { return value; }
void Set(const std::string&value) { this->value = value; }
private:
virtual void write_json(json_writer &writer) const {
writer.write_raw(value.c_str());
}
virtual void read_json(json_reader &reader) {
throw std::logic_error("Not implemented.");
}
std::string value;
};
} // namespace pipedal
+69 -14
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "util.hpp"
#include "AudioConfig.hpp"
#include "WebServer.hpp"
@@ -27,7 +28,7 @@
#include "AvahiService.hpp"
#include "PiPedalSocket.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include <boost/system/error_code.hpp>
#include <filesystem>
#include "PiPedalConfiguration.hpp"
@@ -38,6 +39,8 @@
#include <boost/asio.hpp>
#include "HtmlHelper.hpp"
#include "Ipv6Helpers.hpp"
#include <thread>
#include <atomic>
#include <signal.h>
#include <semaphore.h>
@@ -50,6 +53,12 @@ using namespace pipedal;
#define BANK_EXTENSION ".piBank"
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
#ifdef __ARM_ARCH_ISA_A64
#define AARCH64
#endif
sem_t signalSemaphore;
bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::string &deviceId)
@@ -70,6 +79,8 @@ public:
};
static volatile bool g_SigBreak = false;
std::atomic<bool> ga_SigBreak { false };
void sig_handler(int signo)
{
if (!g_SigBreak)
@@ -78,6 +89,11 @@ void sig_handler(int signo)
sem_post(&signalSemaphore);
}
}
void throwSystemError(int error)
{
}
using namespace boost::system;
class DownloadIntercept : public RequestHandler
@@ -156,19 +172,19 @@ public:
{
std::string strInstanceId = request_uri.query("id");
int64_t instanceId = std::stol(strInstanceId);
auto pedalBoard = model->GetPreset(instanceId);
auto pedalboard = model->GetPreset(instanceId);
// a certain elegance to using same file format for banks and presets.
BankFile file;
file.name(pedalBoard.name());
int64_t newInstanceId = file.addPreset(pedalBoard);
file.name(pedalboard.name());
int64_t newInstanceId = file.addPreset(pedalboard);
file.selectedPreset(newInstanceId);
std::stringstream s;
json_writer writer(s);
writer.write(file);
*pContent = s.str();
*pName = pedalBoard.name();
*pName = pedalboard.name();
}
void GetBank(const uri &request_uri, std::string *pName, std::string *pContent)
{
@@ -501,7 +517,6 @@ int main(int argc, char *argv[])
sem_init(&signalSemaphore, 0, 0);
signal(SIGINT, sig_handler);
#ifndef WIN32
umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction.
@@ -570,8 +585,8 @@ int main(int argc, char *argv[])
{
Lv2Log::set_logger(MakeLv2SystemdLogger());
}
signal(SIGTERM, sig_handler);
signal(SIGUSR1, sig_handler);
SetThreadName("main");
std::filesystem::path doc_root = parser.Arguments()[0];
std::filesystem::path web_root = doc_root;
@@ -627,7 +642,6 @@ int main(int argc, char *argv[])
try
{
PiPedalModel model;
model.Init(configuration);
// Get heavy IO out of the way before letting dependent (Jack/ALSA) services run.
@@ -731,15 +745,56 @@ int main(int argc, char *argv[])
server->AddRequestHandler(downloadIntercept);
{
server->RunInBackground(SIGUSR1);
server->RunInBackground(-1);
SetThreadName("avahi");
model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main");
// AARCH64 sem_wait pins CPU 100%.
// static_assert(std::atomic<bool>::is_always_lock_free);
// while (true)
// {
// auto sigBreak = ga_SigBreak.load();
// if (sigBreak)
// {
// break;
// }
// std::this_thread::sleep_for(std::chrono::milliseconds(50));
// }
sem_wait(&signalSemaphore);
if (systemd)
{
sd_notify(0, "STOPPING=1");
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)
{
sd_notify(0, "STOPPING=1");
}
}
}
@@ -753,7 +808,7 @@ int main(int argc, char *argv[])
catch (const std::exception &e)
{
Lv2Log::error(e.what());
return EXIT_FAILURE;
}
Lv2Log::info("PiPedal terminating.");
return EXIT_SUCCESS; // only exit in response to a signal.
}
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 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 "util.hpp"
#include <pthread.h>
#include <thread>
#include <unistd.h> // for gettid()
using namespace pipedal;
void pipedal::SetThreadName(const std::string &name)
{
std::string threadName = "ppdl_" + name;
if (threadName.length () > 15)
{
threadName = threadName.substr(0,15);
}
pthread_t pid = pthread_self();
pthread_setname_np(pid,threadName.c_str());
}
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 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 <string>
namespace pipedal {
inline bool endsWith(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}
void SetThreadName(const std::string &name);
}
+1 -1
View File
@@ -24,7 +24,7 @@
#pragma once
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include <functional>
#include "json.hpp"
+2 -2
View File
@@ -86,7 +86,7 @@ namespace pipedal
virtual void CheckSync();
//- PiPedalHost Interfaces.
//- PluginHost Interfaces.
virtual void SetControl(int index, float value);
virtual float GetControlValue(int index) const
{
@@ -130,7 +130,7 @@ namespace pipedal
virtual float *GetAudioOutputBuffer(int index) const { return buffers.outputs[index]; }
virtual void ResetAtomBuffers() {}
virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent.
virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} // no vst equivalent.
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} // no vst equivalent.
virtual void SetAudioInputBuffer(int index, float *buffer)
{
+2 -2
View File
@@ -32,7 +32,7 @@
#include "public.sdk/source/vst/utility/uid.h"
#include "vst3/Vst3Effect.hpp"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
namespace Steinberg
@@ -67,7 +67,7 @@ namespace Steinberg
virtual std::unique_ptr<Vst3Effect> CreatePlugin(long instanceId,const std::string&url, IHost*pHost) = 0;
virtual std::unique_ptr<Vst3Effect> CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost) = 0;
virtual std::unique_ptr<Vst3Effect> CreatePlugin(PedalboardItem&pedalboardItem, IHost*pHost) = 0;
static Ptr CreateInstance(const std::string &cacheFilePath = "");