First commit.

This commit is contained in:
Robin Davies
2021-08-15 12:42:46 -04:00
commit d8a6022947
264 changed files with 95068 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
#include "pch.h"
#include "Banks.hpp"
using namespace pipedal;
JSON_MAP_BEGIN(PresetIndexEntry)
JSON_MAP_REFERENCE(PresetIndexEntry,instanceId)
JSON_MAP_REFERENCE(PresetIndexEntry,name)
JSON_MAP_END()
JSON_MAP_BEGIN(PresetIndex)
JSON_MAP_REFERENCE(PresetIndex,selectedInstanceId)
JSON_MAP_REFERENCE(PresetIndex,presetChanged)
JSON_MAP_REFERENCE(PresetIndex,presets)
JSON_MAP_END()
JSON_MAP_BEGIN(BankIndex)
JSON_MAP_REFERENCE(BankIndex,selectedBank)
JSON_MAP_REFERENCE(BankIndex,nextInstanceId)
JSON_MAP_REFERENCE(BankIndex,entries)
JSON_MAP_END()
JSON_MAP_BEGIN(BankIndexEntry)
JSON_MAP_REFERENCE(BankIndexEntry,instanceId)
JSON_MAP_REFERENCE(BankIndexEntry,name)
JSON_MAP_END()
JSON_MAP_BEGIN(BankFile)
JSON_MAP_REFERENCE(BankFile,name)
JSON_MAP_REFERENCE(BankFile,nextInstanceId)
JSON_MAP_REFERENCE(BankFile,selectedPreset)
JSON_MAP_REFERENCE(BankFile,presets)
JSON_MAP_END()
JSON_MAP_BEGIN(BankFileEntry)
JSON_MAP_REFERENCE(BankFileEntry,instanceId)
JSON_MAP_REFERENCE(BankFileEntry,preset)
JSON_MAP_END()
+300
View File
@@ -0,0 +1,300 @@
#pragma once
#include "json.hpp"
#include "PedalBoard.hpp"
#include "PiPedalException.hpp"
namespace pipedal {
#define GETTER_SETTER_REF(name) \
decltype(name##_)& name() { return name##_;} \
const decltype(name##_)& name() const { return name##_;} \
void name(const decltype(name##_) &value) { name##_ = value; }
#define GETTER_SETTER_VEC(name) \
decltype(name##_)& name() { return name##_;} \
const decltype(name##_)& name() const { return name##_;}
#define GETTER_SETTER(name) \
decltype(name##_) name() const { return name##_;} \
void name(decltype(name##_) value) { name##_ = value; }
class PresetIndexEntry {
int64_t instanceId_ = 0;
std::string name_;
public:
GETTER_SETTER(instanceId);
GETTER_SETTER(name);
DECLARE_JSON_MAP(PresetIndexEntry);
};
class PresetIndex {
int64_t selectedInstanceId_ = -1;
bool presetChanged_ = false;
std::vector<PresetIndexEntry> presets_;
public:
bool GetPresetName(int64_t instanceId, std::string*pResult) {
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i].instanceId() == instanceId)
{
*pResult = presets_[i].name();
return true;
}
}
return false;
}
GETTER_SETTER(selectedInstanceId);
GETTER_SETTER_VEC(presets);
GETTER_SETTER(presetChanged);
DECLARE_JSON_MAP(PresetIndex);
};
class BankFileEntry {
int64_t instanceId_;
PedalBoard preset_;
public:
GETTER_SETTER(instanceId);
GETTER_SETTER_REF(preset);
DECLARE_JSON_MAP(BankFileEntry);
};
class BankFile {
std::string name_;
int64_t nextInstanceId_ = 0;
int64_t selectedPreset_ = -1;
std::vector<std::unique_ptr<BankFileEntry>> presets_;
public:
GETTER_SETTER(name);
GETTER_SETTER(nextInstanceId);
GETTER_SETTER(selectedPreset);
GETTER_SETTER_VEC(presets);
void clear()
{
nextInstanceId_ = 0;
presets_.clear();
selectedPreset_ = -1;
}
void move(size_t from, size_t to)
{
if (from >= this->presets_.size()) {
throw std::invalid_argument("Argument out of range.");
}
if (to >= this->presets_.size()) {
throw std::invalid_argument("Argument out of range.");
}
std::unique_ptr<BankFileEntry> t = std::move(this->presets_[from]);
presets_.erase(presets_.begin()+from);
presets_.insert(presets_.begin()+to,std::move(t));
}
int64_t addPreset(const PedalBoard&preset, int64_t afterItem = -1)
{
if (hasName(preset.name()))
{
throw PiPedalStateException("A preset by that name already exists.");
}
std::unique_ptr<BankFileEntry> entry = std::make_unique<BankFileEntry>();
entry->instanceId(++nextInstanceId_);
entry->preset(preset);
int64_t instanceId = entry->instanceId();
if (afterItem == -1)
{
this->presets_.push_back(std::move(entry));
} else {
for (auto it = this->presets_.begin(); it != this->presets_.end(); ++it)
{
if ((*it)->instanceId() == afterItem)
{
++it;
this->presets_.insert(it,std::move(entry));
break;
}
}
}
return instanceId;
}
bool hasItem(int64_t instanceId)
{
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i]->instanceId() == instanceId)
{
return true;
}
}
return false;
}
bool hasName(const std::string &name) {
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i]->preset().name() == name)
{
return true;
}
}
return false;
}
bool renamePreset(int64_t instanceId, const std::string&name)
{
if (hasName(name))
{
throw PiPedalStateException("A preset by that name already exists.");
}
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i]->instanceId() ==instanceId)
{
presets_[i]->preset().name(name);
return true;
}
}
return false;
}
BankFileEntry &getItem(int64_t instanceId)
{
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i]->instanceId() == instanceId)
{
return *(presets_[i].get());
}
}
throw PiPedalArgumentException("Instance not found.");
}
int64_t deletePreset(int64_t instanceId) {
for (size_t i = 0; i < presets_.size(); ++i)
{
if (presets_[i]->instanceId() == instanceId)
{
presets_.erase(presets_.begin()+i);
int64_t newSelection;
if (i < presets_.size())
{
newSelection = presets_[i]->instanceId();
} else if (presets_.size() > 1) {
newSelection = presets_[0]->instanceId();
} 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);
newSelection = presets_[0]->instanceId();
}
if (instanceId == this->selectedPreset_)
{
this->selectedPreset_ = newSelection;
}
return newSelection;
}
}
throw PiPedalStateException("Preset not found.");
}
DECLARE_JSON_MAP(BankFile);
};
class BankIndexEntry {
int64_t instanceId_ = 0;
std::string name_;
public:
GETTER_SETTER(instanceId);
GETTER_SETTER_REF(name);
DECLARE_JSON_MAP(BankIndexEntry);
};
class BankIndex {
int64_t selectedBank_ = 0;
int64_t nextInstanceId_ = 0;
std::vector<BankIndexEntry> entries_;
public:
GETTER_SETTER(selectedBank);
GETTER_SETTER_VEC(entries);
DECLARE_JSON_MAP(BankIndex);
void move(size_t from, size_t to)
{
if (from >= this->entries_.size()) {
throw std::invalid_argument("Argument out of range.");
}
if (to >= this->entries_.size()) {
throw std::invalid_argument("Argument out of range.");
}
BankIndexEntry t = std::move(this->entries_[from]);
entries_.erase(entries_.begin()+from);
entries_.insert(entries_.begin()+to,std::move(t));
}
void clear() {
entries_.clear();
selectedBank_ = 0;
}
int64_t addBank(int64_t afterId,const std::string& name)
{
BankIndexEntry bank;
bank.name(name);
bank.instanceId(++nextInstanceId_);
for (size_t i = 0; i < this->entries_.size(); ++i)
{
if (entries_[i].instanceId() == afterId)
{
entries_.insert(entries_.begin()+(i+1), bank);
return bank.instanceId();
}
}
// else at the end.
entries_.push_back(bank);
return bank.instanceId();
}
BankIndexEntry* getEntryByName(const std::string &name)
{
for (size_t i = 0; i < entries_.size(); ++i)
{
if (entries_[i].name() == name)
{
return &entries_[i];
}
}
return nullptr;
}
BankIndexEntry&getBankIndexEntry(int64_t instanceId)
{
for (size_t i = 0; i < entries_.size(); ++i)
{
if (entries_[i].instanceId() == instanceId)
{
return entries_[i];
}
}
throw PiPedalArgumentException("Bank not found.");
}
const BankIndexEntry&getBankIndexEntry(int64_t instanceId) const
{
for (size_t i = 0; i < entries_.size(); ++i)
{
if (entries_[i].instanceId() == instanceId)
{
return entries_[i];
}
}
throw PiPedalArgumentException("Bank not found.");
}
};
#undef GETTER_SETTER
#undef GETTER_SETTER_VEC
#undef GETTER_SETTER_REF
}
+1241
View File
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
#pragma once
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/ip/network_v4.hpp>
#include <mutex>
#include <thread>
#include "Uri.hpp"
#include <string_view>
#include <filesystem>
// forward declaration.
class websocket_session;
namespace pipedal {
using HttpRequest =boost::beast::http::request<boost::beast::http::string_body>;
std::string last_modified(const std::filesystem::path& path);
class SocketHandler {
public:
class IWriteCallback {
public:
virtual void close() = 0;
virtual void writeCallback(const std::string& text) = 0;
};
private:
friend class ::websocket_session;
IWriteCallback *writeCallback_;
void setWriteCallback(IWriteCallback *writeCallback) {
writeCallback_ = writeCallback;
}
protected:
virtual void onReceive(const std::string_view&text) = 0;
public:
void receive(const std::string_view&text) {
onReceive(text);
}
void send(const std::string &text) {
if (writeCallback_ != nullptr)
{
writeCallback_->writeCallback(text);
}
}
virtual void Close()
{
writeCallback_->close();
}
virtual ~SocketHandler() = default;
virtual void onAttach() { }
};
class ISocketFactory {
public:
virtual bool wants(const uri &request) = 0;
virtual std::shared_ptr<SocketHandler> CreateHandler(const uri& request) = 0;
};
class RequestHandler {
private:
uri target_url_;
public:
RequestHandler(const char*target_url)
: target_url_(target_url)
{
}
~RequestHandler() = default;
virtual bool wantsRedirect(const uri& requestUri)
{
return false;
}
uri GetRedirect(const uri& requestUri)
{
return requestUri;
}
virtual bool wants(boost::beast::http::verb verb,const uri &request_uri) const {
if (request_uri.segment_count() < target_url_.segment_count())
{
return false;
}
for (int i = 0; i < target_url_.segment_count(); ++i)
{
if (request_uri.segment(i) != target_url_.segment(i))
{
return false;
}
}
return true;
}
virtual void head_response(
const uri&request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::empty_body> &res,
boost::beast::error_code &ec) = 0;
virtual void get_response(
const uri&request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec) = 0;
virtual void post_response(
const uri&request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec)
{
get_response(request_uri,req,res,ec);
}
};
class BeastServer {
private:
boost::asio::ip::address address;
unsigned short port;
std::string doc_root;
int threads;
std::mutex log_mutex;
std::vector<std::shared_ptr<RequestHandler> > request_handlers;
std::vector<std::shared_ptr<ISocketFactory> > socket_factories;
std::mutex io_mutex;
boost::asio::io_context *pIoContext = nullptr;
void*pListener = nullptr;
std::thread*pBgThread = nullptr;
bool logHttpRequests = false;
bool hasAccessPointGateway = false;
boost::asio::ip::network_v4 accessPointGateway;
std::string accessPointServerAddress;
bool stopping = false;
public:
BeastServer(
const boost::asio::ip::address& address,
int port,
const char*rootPath,
int threads = 10);
void Run();
void RunInBackground();
bool Stopping() const { return this->stopping; }
void Stop();
void Join();
void SetLogHttpRequests(bool logRequests) { this->logHttpRequests = logRequests; }
bool LogHttpRequests() const { return this->logHttpRequests; }
boost::asio::io_context& IoContext() {
return *pIoContext;
}
void AddRequestHandler(std::shared_ptr<RequestHandler> requestHandler)
{
request_handlers.push_back(requestHandler);
}
void AddSocketFactory(const std::shared_ptr<ISocketFactory> &socketHandler)
{
socket_factories.push_back(socketHandler);
}
std::vector<std::shared_ptr<RequestHandler> > &RequestHandlers() { return request_handlers; }
const std::string&GetDocRoot() { return doc_root; }
virtual ~BeastServer();
std::shared_ptr<ISocketFactory> GetSocketFactory(const uri &request_uri);
void SetAccessPointGateway(const boost::asio::ip::network_v4 &gateway, const std::string&accessPointServerAddress)
{
this->accessPointGateway = gateway;
this->hasAccessPointGateway = true;
this->accessPointServerAddress = accessPointServerAddress;
}
bool HasAccessPointGateway() const { return hasAccessPointGateway; }
boost::asio::ip::network_v4 &GetAccessPointGateway() { return this->accessPointGateway; }
const std::string & GetAccessPointServerAddress() const { return this->accessPointServerAddress; }
public:
virtual void onError(boost::system::error_code ec, char const* what);
virtual void onWarning(boost::system::error_code ec, char const* what);
virtual void onInfo(char const* what);
virtual void onListenerClosed();
};
} // namespace pipedal;
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <type_traits>
// maintains ownership of allocated buffers.
namespace pipedal {
class BufferPool {
std::vector<void*> allocatedBuffers;
public:
~BufferPool() {
Clear();
}
template <typename TYPE>
TYPE *AllocateBuffer(size_t size)
{
TYPE *result= new TYPE[size];
for (int i = 0; i < size; ++i)
{
result[i] = 0;
}
allocatedBuffers.push_back(result);
return result;
}
void Clear() {
for (int i = 0; i < allocatedBuffers.size(); ++i)
{
delete[] (char*)allocatedBuffers[i];
}
allocatedBuffers.resize(0);
}
};
} // namespace.
+136
View File
@@ -0,0 +1,136 @@
set(VERBOSE true)
cmake_minimum_required(VERSION 3.19.0)
include(FindPkgConfig)
pkg_check_modules(SYSTEMD "systemd")
if(!SYSTEMD_FOUND)
message(ERROR "libsystemd-dev package not installed.")
else()
message(STATUS "SYSTEMD_LIBRARIES: ${SYSTEMD_LIBRARIES}")
message(STATUS "SYSTEMD_INCLUDE_DIRS: ${SYSTEMD_INCLUDE_DIRS}")
endif()
pkg_check_modules(LILV_0 "lilv-0")
if(!LILV_0_FOUND)
message(ERROR "lilv-0 package not installed.")
else()
message(STATUS "LILV_0_LIBRARIES: ${LILV_0_LIBRARIES}")
message(STATUS "LILV_0_INCLUDE_DIRS: ${LILV_0_INCLUDE_DIRS}")
endif()
pkg_check_modules(JACK "jack")
if(!JACK_FOUND)
message(ERROR "jack package not installed.")
else()
message(STATUS "JACK_LIBRARIES: ${JACK_LIBRARIES}")
message(STATUS "JACK_INCLUDE_DIRS: ${JACK_INCLUDE_DIRS}")
endif()
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build")
set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi -fsanitize=address -static-libasan -O0 -DDEBUG" )
elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
message(STATUS "RelWithgDebInfo build")
set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi" )
else()
message(STATUS "Release build")
set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi" )
endif()
# message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
# set(CMAKE_ENABLE_EXPORTS 1)
message (STATUS "Cxx flags: ${CMAKE_CXX_FLAGS}" )
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
add_executable(pipedald BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp
RequestHandler.hpp main.cpp json.cpp json.hpp Scratch.cpp Lv2Host.hpp Lv2Host.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
Presets.hpp Presets.cpp
Storage.hpp Storage.cpp
Banks.hpp Banks.cpp
JackHost.hpp JackHost.cpp
JackConfiguration.hpp JackConfiguration.cpp
defer.hpp
Lv2Effect.cpp Lv2Effect.hpp
Lv2PedalBoard.cpp Lv2PedalBoard.hpp
BufferPool.hpp
SplitEffect.hpp SplitEffect.cpp
RingBufferReader.hpp
MapFeature.hpp MapFeature.cpp
LogFeature.hpp LogFeature.cpp
Worker.hpp Worker.cpp
OptionsFeature.hpp OptionsFeature.cpp
VuUpdate.hpp VuUpdate.cpp
asan_options.cpp
Units.hpp Units.cpp
RingBuffer.hpp
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
Shutdown.hpp
CommandLineParser.hpp
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
JackServerSettings.hpp JackServerSettings.cpp
ShutdownClient.hpp ShutdownClient.cpp
MidiBinding.hpp MidiBinding.cpp
PiPedalMath.hpp
Locale.hpp Locale.cpp
Lv2EventBufferWriter.hpp Lv2EventBufferWriter.cpp
IpSubnet.hpp
)
configure_file(config.hpp.in config.hpp)
include_directories( ${PiPedal_SOURCE_DIR}/. ../build/src)
target_precompile_headers(pipedald PRIVATE pch.h)
target_include_directories(pipedald PRIVATE
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${SYSTEMD_INCLUDE_DIRS}
)
target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${SYSTEMD_LIBRARIES} systemd
)
#target_link_libraries(pipedald boost_system)
add_executable(pipedalconfig json.cpp json.hpp
HtmlHelper.cpp HtmlHelper.hpp
CommandLineParser.hpp
WriteTemplateFile.hpp WriteTemplateFile.cpp
PiPedalException.hpp
ConfigMain.cpp
)
target_link_libraries(pipedalconfig PRIVATE pthread atomic stdc++fs
)
add_executable(pipedalshutdownd ShutdownMain.cpp CommandLineParser.hpp
JackServerSettings.hpp JackServerSettings.cpp
json.hpp json.cpp HtmlHelper.hpp HtmlHelper.cpp Lv2Log.hpp Lv2Log.cpp
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
)
target_link_libraries(pipedalshutdownd PRIVATE pthread atomic stdc++fs systemd)
install (TARGETS pipedalconfig pipedald pipedalshutdownd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets)
+177
View File
@@ -0,0 +1,177 @@
#pragma once
#include <string>
#include <vector>
#include "PiPedalException.hpp"
#include <sstream>
namespace pipedal
{
class CommandLineParser
{
private:
class OptionBase
{
std::string option;
public:
OptionBase(const std::string &option_)
: option(option_)
{
}
virtual ~OptionBase()
{
}
protected:
const std::string &GetName() const { return option; }
public:
bool Matches(const std::string &text) const
{
return option == text;
}
virtual int Execute(int argcRemaining, const char **argvRemaining) = 0;
};
std::vector<OptionBase *> options;
class BooleanOption : public OptionBase
{
bool *pOutput;
bool value;
public:
BooleanOption(const std::string &name, bool *pOutput, bool value)
: OptionBase(name),
pOutput(pOutput),
value(value)
{
}
virtual int Execute(int argcRemaining, const char **argvRemaining)
{
*pOutput = value;
return 0;
}
};
template <typename T>
class Option : public OptionBase
{
T *pOutput;
public:
Option(const std::string &name, T *pOutput)
: OptionBase(name),
pOutput(pOutput)
{
}
virtual int Execute(int argcRemaining, const char **argvRemaining)
{
if (argcRemaining == 0)
{
throw PiPedalException("Expecting a parameter for option " + GetName());
}
std::stringstream s(argvRemaining[0]);
try
{
s >> *pOutput;
if (s.fail() || !s.eof())
{
throw std::exception();
}
}
catch (const std::exception &e)
{
throw PiPedalException("Argument for option " + GetName() + " is not in the correct format.");
}
return 1; // consumed one argument.
}
};
class StringOption : public OptionBase
{
std::string name;
std::string *pOutput;
public:
StringOption(const std::string &name, std::string *pOutput)
: OptionBase(name),
pOutput(pOutput)
{
}
virtual int Execute(int argcRemaining, const char **argvRemaining)
{
if (argcRemaining == 0 || argvRemaining[0][0] == '-')
{
throw PiPedalException("Expecting a parameter for option " + GetName());
}
*pOutput = argvRemaining[0];
return 1;
}
};
int ProcessOption(const std::string &text, int argsRemaining, const char *argv[])
{
for (auto option : options)
{
if (option->Matches(text))
{
return option->Execute(argsRemaining, argv);
}
}
std::stringstream s;
s << "Invalid option: " << text;
throw PiPedalException(s.str());
}
std::vector<std::string> args;
public:
~CommandLineParser()
{
for (auto item : options)
{
delete item;
}
}
void AddOption(const std::string &option, bool *pResult)
{
options.push_back(new BooleanOption(option, pResult, true));
options.push_back(new BooleanOption(option + "+", pResult, true));
options.push_back(new BooleanOption(option + "-", pResult, false));
}
void AddOption(const std::string &option, std::string *pResult)
{
options.push_back(new StringOption(option, pResult));
}
template <typename T>
void AddOption(const std::string &option, T *pResult)
{
options.push_back(new Option<T>(option, pResult));
}
bool Parse(int argc, const char *argv[])
{
for (int i = 1; i < argc; ++i)
{
const char *arg = argv[i];
if (arg[0] == '-')
{
int argsConsumed = ProcessOption(arg, argc - i - 1, argv + i + 1);
i += argsConsumed;
}
else
{
args.push_back(std::string(arg));
}
}
return true;
}
const std::vector<std::string> Arguments() { return args; }
};
} // namespace
+387
View File
@@ -0,0 +1,387 @@
#include <iostream>
#include "CommandLineParser.hpp"
#include "PiPedalException.hpp"
#include <filesystem>
#include <stdlib.h>
#include "WriteTemplateFile.hpp"
#include <fstream>
using namespace std;
using namespace pipedal;
#define SERVICE_ACCOUNT_NAME "pipedal_d"
#define SERVICE_GROUP_NAME "pipedal_d"
#define SERVICE_PATH "/usr/lib/systemd/system"
#define NATIVE_SERVICE "pipedald"
#define SHUTDOWN_SERVICE "pipedalshutdownd"
// #define REACT_SERVICE "pipedal_react"
bool exec(std::stringstream &s)
{
return system(s.str().c_str()) == EXIT_SUCCESS;
}
std::filesystem::path GetServiceFileName(const std::string &serviceName)
{
return std::filesystem::path(SERVICE_PATH) / (serviceName + ".service");
}
std::filesystem::path findOnPath(const std::string &command)
{
std::string path = getenv("PATH");
std::vector<std::string> paths;
size_t t = 0;
while (t < path.length())
{
size_t pos = path.find(':', t);
if (pos == string::npos)
{
pos = path.length();
}
std::string thisPath = path.substr(t, pos - t);
std::filesystem::path path = std::filesystem::path(thisPath) / command;
if (std::filesystem::exists(path))
{
return path;
}
t = pos + 1;
}
std::stringstream s;
s << "'" << command << "' is not installed.";
throw PiPedalException(s.str());
}
void EnableService()
{
if (system("systemctl enable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to enable the " NATIVE_SERVICE " service.";
}
if (system("systemctl enable " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to enable the " SHUTDOWN_SERVICE " service.";
}
}
void DisableService()
{
if (system("systemctl disable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to disable the " NATIVE_SERVICE " service.";
}
if (system("systemctl disable " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to disable the " SHUTDOWN_SERVICE " service.";
}
}
void StopService()
{
if (system("systemctl stop " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to stop the " NATIVE_SERVICE " service.";
}
if (system("systemctl stop " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
{
cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service.";
}
}
void StartService()
{
if (system("systemctl start " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
{
throw PiPedalException("Failed to start the " NATIVE_SERVICE " service.");
}
if (system("systemctl start " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS)
{
throw PiPedalException("Failed to start the " SHUTDOWN_SERVICE " service.");
}
}
void RestartService()
{
StopService();
StartService();
}
void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress)
{
auto endpos = endpointAddress.find_last_of(':');
if (endpos == string::npos)
{
throw PiPedalException("Invalid endpoint address: " + endpointAddress);
}
uint16_t port;
auto strPort = endpointAddress.substr(endpos + 1);
try
{
auto lport = std::stoul(strPort);
if (lport == 0 || lport >= std::numeric_limits<uint16_t>::max())
{
throw PiPedalException("out of range.");
}
port = (uint16_t)lport;
std::stringstream s;
s << port;
strPort = s.str(); // normalized.
}
catch (const std::exception &)
{
std::stringstream s;
s << "Invalid port number: " << strPort;
throw PiPedalException(s.str());
}
bool authBindRequired = port < 512;
// Create and configur service account.
if (system("groupadd -f " SERVICE_GROUP_NAME) != EXIT_SUCCESS)
{
throw PiPedalException("Failed to create service group.");
}
if (system("useradd " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
{
// throw PiPedalException("Failed to create service account.");
}
system("usermod -a -G jack " SERVICE_ACCOUNT_NAME);
// create and configure /var directory.
std::filesystem::path varDirectory("/var/pipedal");
std::filesystem::create_directory(varDirectory);
{
std::stringstream s;
s << "chgrp " SERVICE_GROUP_NAME " " << varDirectory;
system(s.str().c_str());
}
{
std::stringstream s;
s << "chown " SERVICE_ACCOUNT_NAME << " " << varDirectory;
system(s.str().c_str());
}
{
std::stringstream s;
s << "chmod 775 " << varDirectory;
system(s.str().c_str());
}
{
std::stringstream s;
s << "chmod g+s " << varDirectory; // child files/directories inherit ownership.
system(s.str().c_str());
}
// authbind port.
if (authBindRequired)
{
std::filesystem::path portAuthFile = std::filesystem::path("/etc/authbind/byport") / strPort;
{
// create it.
std::ofstream f(portAuthFile);
}
{
// own it.
std::stringstream s;
s << "chown " SERVICE_ACCOUNT_NAME " " << portAuthFile;
exec(s);
}
{
// group own it.
std::stringstream s;
s << "chgrp " SERVICE_GROUP_NAME " " << portAuthFile;
exec(s);
}
{
std::stringstream s;
s << "chmod 770 " << portAuthFile;
exec(s);
}
}
cout << "Creating Systemd file." << endl;
std::map<string, string> map;
int shutdownPort = 3147;
map["DESCRIPTION"] = "PiPedal Web Service";
{
std::stringstream s;
if (authBindRequired)
{
s << findOnPath("authbind").string() << " --deep ";
}
s
<< (programPrefix / "bin/pipedald").string()
<< " /etc/pipedal/config /etc/pipedal/react -port " << endpointAddress << " -systemd -shutdownPort " << shutdownPort;
map["COMMAND"] = s.str();
}
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/template.service"), GetServiceFileName(NATIVE_SERVICE));
map["DESCRIPTION"] = "PiPedal Shutdown Service";
{
std::stringstream s;
s
<< (programPrefix / "bin/pipedalshutdownd").string()
<< " -port " << shutdownPort;
map["COMMAND"] = s.str();
}
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateShutdown.service"), GetServiceFileName(SHUTDOWN_SERVICE));
system("systemctl daemon-reload");
cout << "Starting service" << endl;
RestartService();
EnableService();
cout << "Complete" << endl;
}
int main(int argc, char **argv)
{
CommandLineParser parser;
bool install = false;
bool uninstall = false;
bool help = false;
bool stop = false, start = false;
bool enable = false, disable = false, restart = false;
std::string prefixOption;
std::string portOption;
parser.AddOption("-install", &install);
parser.AddOption("-uninstall", &uninstall);
parser.AddOption("-stop", &stop);
parser.AddOption("-start", &start);
parser.AddOption("-enable", &enable);
parser.AddOption("-disable", &disable);
parser.AddOption("-restart", &restart);
parser.AddOption("-h", &help);
parser.AddOption("--help", &help);
parser.AddOption("-prefix", &prefixOption);
parser.AddOption("-port", &portOption);
try
{
parser.Parse(argc, (const char **)argv);
int actionCount = install + uninstall + stop + start + enable + disable;
if (actionCount > 1)
{
throw PiPedalException("Please provide only one action.");
}
if (actionCount == 0)
{
help = true;
}
}
catch (const std::exception &e)
{
std::string s = e.what();
cout << "Error: " << s << endl;
exit(EXIT_FAILURE);
}
if (help || parser.Arguments().size() != 0)
{
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
<< "Copyright (c) 2021 Robin Davies. All rights reserved." << endl
<< endl
<< "Syntax:" << endl
<< " pipedalconfig [Options...]" << endl
<< "Options: " << endl
<< " -install Install services and service accounts." << endl
<< " -uninstall Remove installed services and service accounts." << endl
<< " -enable Start the pipedal at boot time." << endl
<< " -disable Do not start the pipedal at boot time." << endl
<< " -h --help Display this message." << endl
<< " -stop Stop the pipedal services." << endl
<< " -start Start the pipedal services." << endl
<< " -restart Restart the pipedal services." << endl
<< " -prefix Either /usr/local or /usr as appropriate for the install" << endl
<< " (only valid with the -install option)." << endl
<< " -port Port for the web server (only with -install option)." << endl
<< " Either a port (e.g. '81'), or a full endpoint (e.g.: " << endl
<< " '127.0.0.1:8080'). If no address is specified, the " << endl
<< " web server will listen on 0.0.0.0 (any)." << endl
<< endl
<< "Description:" << endl
<< " pipedalconfig performs post-install configuration of pipedal." << endl
<< endl;
exit(EXIT_SUCCESS);
}
if (portOption.size() != 0 && !install)
{
cout << "Error: -port option can only be specified with the -install option." << endl;
exit(EXIT_FAILURE);
}
try
{
std::filesystem::path prefix;
if (prefixOption.length() != 0)
{
prefix = std::filesystem::path(prefixOption);
}
else
{
prefix = std::filesystem::path(argv[0]).parent_path().parent_path();
std::filesystem::path pipedalPath = prefix / "bin/pipedald";
if (!std::filesystem::exists(pipedalPath))
{
std::stringstream s;
s << "Can't find pipedald executable at " << pipedalPath << ". Try again using the -prefix option.";
throw PiPedalException(s.str());
}
}
if (install)
{
if (portOption == "")
{
portOption = "80";
}
if (portOption.find(':') == string::npos)
{
portOption = "0.0.0.0:" + portOption;
}
Install(prefix, portOption);
}
else if (uninstall)
{
}
else if (stop)
{
StopService();
}
else if (start)
{
StartService();
}
else if (restart)
{
RestartService();
}
else if (enable)
{
EnableService();
}
else if (disable)
{
DisableService();
}
}
catch (const std::exception &e)
{
cout << "Error: " << e.what() << endl;
exit(EXIT_FAILURE);
}
}
+226
View File
@@ -0,0 +1,226 @@
#include "pch.h"
#include "HtmlHelper.hpp"
#include <sstream>
#include <string>
#include <cstring>
using namespace pipedal;
// non-localizable. RFC 7231 Date Format
;
static const char*INVARIANT_WEEK_DAYS[] =
{ "Sun","Mon", "Tue", "Wed","Thu", "Fri","Sat"};
// non-localizable. RFC 7231 Date Format
static const char*INVARIANT_MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep","Oct","Nov", "Dec"};
// non-localizable. RFC 7231 Date Format
static const char*FORMAT = "%s, %02d %s %04d %02d:%02d:%02d GMT";
std::string HtmlHelper::timeToHttpDate()
{
return timeToHttpDate(time(nullptr));
}
std::string HtmlHelper::timeToHttpDate(time_t time)
{
// RFC 7231, IMF-fixdate.
struct tm gmTm;
if (gmtime_r(&time,&gmTm) == nullptr)
{
return "Mon, 01 Jan 1970 00:00:00 GMT";
}
char szBuffer[sizeof("Mon, 01 Jan 1970 00:00:00 GMT+")];
snprintf(szBuffer,sizeof(szBuffer), FORMAT,
INVARIANT_MONTHS[gmTm.tm_wday],
gmTm.tm_mday,
INVARIANT_MONTHS[gmTm.tm_mon],
gmTm.tm_year,
gmTm.tm_hour,
gmTm.tm_min,
gmTm.tm_sec);
return szBuffer;
}
static int hexN(char c)
{
if (c >= '0' && c <= '9') return c-'0';
if (c >= 'a' && c <= 'f') return 10+c-'a';
if (c >= 'A' && c <= 'F') return 10+c-'A';
throw std::invalid_argument("Malformed URL.");
}
static char hexChar(char c1, char c2)
{
return (char)((hexN(c1) << 4) + hexN(c2));
}
class SafeCharacterMap {
bool safeCharacter[256];
public:
SafeCharacterMap()
{
for (int i = 0; i < 0x20; ++i) {
safeCharacter[i] = false;
}
for (int i = 0x20; i < 0x80; ++i) {
safeCharacter[i] = true;
}
for (int i = 0x80; i < 256; ++i)
{
safeCharacter[i] = false;
}
const char *unsafeCharacters = ":/+?=#%@ ";
for (const char*p = unsafeCharacters; *p != 0; ++p)
{
safeCharacter[(uint8_t)*p] = false;
}
}
bool isSafeCharacter(char c) { return safeCharacter[(uint8_t)c];}
} g_safeCharacterMap;
std::string HtmlHelper::encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment )
{
std::stringstream s;
encode_url_segment(s,pStart,pEnd,isQuerySegment);
return s.str();
}
static char hexChars[] = "0123456789ABCDEF";
void HtmlHelper::encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment)
{
while (pStart != pEnd)
{
char c = *pStart++;
if (g_safeCharacterMap.isSafeCharacter(c))
{
os << c;
} else if (c == ' ' && isQuerySegment)
{
os << '+';
os << c;
} else {
os << '%' << hexChars[(c >> 4) & 0x0f] << hexChars[c & 0x0F];
}
}
}
std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, bool isQuery)
{
std::stringstream s;
auto p = pStart;
while (p != pEnd)
{
char c = *p;
++p;
if (c == '+')
{
s << ' ';
} else if (c != '%')
{
s << c;
} else {
if (pStart+2 < pEnd)
{
char c1 = *p; ++p;
char c2 = *p; ++p;
s << hexChar(c1,c2);
} else {
throw std::invalid_argument("Malformed URL.");
}
}
}
return s.str();
}
std::string HtmlHelper::decode_url_segment(const char*text, bool isQuery)
{
return decode_url_segment(text, text + strlen(text),isQuery);
}
void HtmlHelper::utf32_to_utf8_stream(std::ostream &s, uint32_t uc)
{
if (uc < 0x80u)
{
s << (char)uc;
} else if (uc < 0x800u) {
s << (char)(0xC0 + (uc >> 6));
s << (char)(0x80 + (uc & 0x3F));
} else if (uc < 0x10000u) {
s << (char)(0xE0 + (uc >> 12));
s << (char)(0x80 + ((uc >> 6) & 0x3F));
s << (char)(0x80 + (uc & 0x3F));
} else if (uc < 0x0110000) {
s << (char)(0xF0 + (uc >> 18));
s << (char)(0x80 + ((uc >> 12) & 0x3F));
s << (char)(0x80 + ((uc >> 6) & 0x3F));
s << (char)(0x80 + (uc & 0x3F));
} else {
throw std::range_error("Illegal UTF-32 character.");
}
}
const std::string ESPECIALS = "()<>@,;:\"/[]?.=";
#define MAX_FILENAME_LENGTH 96
#define MAX_SEGMENT_LENGTH 74 // 75 per RFC2047.2. But leave space for a ';' to allow easier breaking of lines.
std::string HtmlHelper::Rfc5987EncodeFileName(const std::string&name)
{
// Encode all characters. Let the browser handle safe-ifying the actual name, since that's os dependent.
std::stringstream s;
std::string prefix = "UTF-8''";
std::string postfix = "";
s << prefix;
for (char c: name)
{
uint8_t uc = (uint8_t)c;
if (uc < 0x20 || uc >= 0x80 || ESPECIALS.find(c) != std::string::npos)
{
s << '%' << hexChars[uc >> 4] << hexChars[uc & 0x0F];
} else {
s << c;
}
}
s << postfix;
return s.str();
}
#define MAX_FILE_NAME_LENGHT 96
const std::string SF_SPECIALS = " <>@;:\"\'/[]?=";
std::string HtmlHelper::SafeFileName(const std::string name)
{
std::stringstream s;
for (char c : name)
{
uint8_t uc = (uint8_t)c;
if (uc < 0x20 || uc >= 0x80 || SF_SPECIALS.find(c) != std::string::npos)
{
s << '_';
}
else
{
s << c;
}
}
return s.str();
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <string>
namespace pipedal {
class HtmlHelper {
public:
static std::string timeToHttpDate();
static std::string timeToHttpDate(time_t time);
static std::string encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
static void encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment = false);
static void encode_url_segment(std::ostream&os, const std::string&segment, bool isQuerySegment = false)
{
const char*p = segment.c_str();
encode_url_segment(os,p, p + segment.length(),isQuerySegment);
}
static std::string decode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
static std::string decode_url_segment(const char*text, bool isQuerySegment = false);
static void utf32_to_utf8_stream(std::ostream &s, uint32_t uc);
static std::string Rfc5987EncodeFileName(const std::string&name);
static std::string SafeFileName(const std::string name);
};
} // namespace.
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <lv2/urid/urid.h>
namespace pipedal {
class RealtimeParameterRequest;
class IEffect {
public:
virtual ~IEffect() {}
virtual long GetInstanceId() const = 0;
virtual int GetControlIndex(const std::string&symbol) const = 0;
virtual void SetControl(int index, float value) = 0;
virtual float GetControlValue(int index) const = 0;
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 void SetAudioOutputBuffer(int index, float*buffer) = 0;
virtual void Activate() = 0;
virtual void Deactivate() = 0;
};
} //namespace
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "PiPedalException.hpp"
namespace pipedal {
class IpSubnet
{
bool isValid = false;
uint32_t ipAddress = 0;
uint32_t mask = 0;
IpSubnet()
{
}
public:
IPSubnet() {
isValid = false;
}
static IpSubment Parse(std::string&text)
{
int pos = text.find('/');
if (pos == std::string::npos)
{
throw PiPedalArgumentException("Not a valid netmask.");
}
std::string ipText = text.substr(0,npos);
std::string netmaskText = text.substr(npos+1);
uint32_t bitMask;
try {
unsigned long t = std::stoul(netmaskText);
if (t > 32 || t == 0) {
throw PiPedalStateException("Invalid netmask.");
}
bitMask ((uint32_t)0xFFFF) << (int)t;
} catch (const std::exception &e)
{
throw PiPedalArgumentException("Invalid netmask.");
}
}
};
}
+227
View File
@@ -0,0 +1,227 @@
#include "pch.h"
#include "JackConfiguration.hpp"
#include "Lv2Log.hpp"
#include "PiPedalException.hpp"
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/session.h>
#include <string.h>
#include "defer.hpp"
#include <algorithm>
using namespace pipedal;
std::string JackStatusToString(jack_status_t status)
{
switch (status)
{
default:
return "Unknown Jack Audio error.";
case JackStatus::JackFailure:
return "Jack Audio operation failed.";
case JackStatus::JackInvalidOption:
return "Invalid Jack Audio operation.";
case JackStatus::JackNameNotUnique:
return "Jack Audio name in use.";
case JackStatus::JackServerStarted:
return "Jack Audio Server started.";
case JackStatus::JackServerFailed:
return "Jack Audio Server failed.";
case JackStatus::JackServerError:
return "Can't connect to the Jack Audio Server.";
case JackStatus::JackNoSuchClient:
return "Jack Audio client not found.";
case JackLoadFailure:
return "Jack Audio client failed to load.";
case JackStatus::JackInitFailure:
return "Failed to initialize the Jack Audio client.";
case JackStatus::JackShmFailure:
return "Jack Audio failed to access shared memory.";
case JackStatus::JackVersionError:
return "Jack Audio Server version error.";
case JackStatus::JackClientZombie:
return "JackClientZombie error.";
}
};
JackConfiguration::JackConfiguration()
{
this->errorStatus_ = "Not initialized.";
}
JackConfiguration::~JackConfiguration()
{
}
static void AddPorts(jack_client_t *client, std::vector<std::string> *output, const char *port_name_pattern,
const char *type_name_pattern,
unsigned long flags)
{
const char**ports = jack_get_ports(client, port_name_pattern, type_name_pattern, flags);
if (ports != nullptr)
{
for (const char **p = ports; *p != nullptr; ++p)
{
const char *portName = *p;
output->push_back(portName);
}
jack_free(ports);
}
}
namespace pipedal {
std::string GetJackErrorMessage(jack_status_t status)
{
std::stringstream s;
s << "Unable connect to Jack audio sever. ";
if (status & JackVersionError) {
s << "Jack server/client version mismatch.";
} else if (status & JackServerError)
{
s << "Server error.";
} else if (status & JackServerFailed)
{
s << "Unable to connect to server.";
} else if (status & JackShmFailure)
{
s << "Unable to access shared memory.";
} else if (status & JackInvalidOption)
{
s << "Invalid option.";
} else if (status & JackInitFailure)
{
s << "Initialization error.";
} else if (status & JackLoadFailure)
{
s << "Can't load client.";
} else if (status )
{
s << "Unknown error.";
}
return s.str();
}
}
void JackConfiguration::Initialize()
{
jack_status_t status;
jack_client_t *client = nullptr;
try
{
client = jack_client_open("PiPedal", JackNullOption, &status);
if (client == nullptr)
{
std::string error = GetJackErrorMessage(status);
Lv2Log::error(error);
throw PiPedalStateException(error.c_str());
}
//jack_set_process_callback(client, process_fn, this);
//jack_on_shutdown(client, jack_shutdown_fn, this);
//jack_set_session_callback(client, session_callback_fn, NULL);
this->sampleRate_ = jack_get_sample_rate(client);
blockLength_ = jack_get_buffer_size(client);
midiBufferSize_ = jack_port_type_get_buffer_size(client, JACK_DEFAULT_MIDI_TYPE);
maxAllowedMidiDelta_ = (uint32_t)(jack_nframes_t)(sampleRate_ * 0.2); // max 200ms of allowed delta
// if (jack_activate(client))
// {
// this->errorStatus_ = "Failed to activate Jack Audio client.";
// Lv2Log::Error("jack_activate failed.");
// throw PiPedalStateException("jack_activate failed.");
// }
// enumerate the output ports.
AddPorts(client, &this->inputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput);
AddPorts(client, &this->outputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput);
AddPorts(client, &this->inputMidiPorts_, "system", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
AddPorts(client, &this->outputMidiPorts_, "system", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
isValid_ = true;
this->errorStatus_ = "";
jack_client_close(client);
}
catch (PiPedalException &e)
{
jack_client_close(client);
this->errorStatus_ = e.what();
}
}
JackChannelSelection JackChannelSelection::MakeDefault(const JackConfiguration&config){
JackChannelSelection result;
for (size_t i = 0; i < std::max((size_t)2,config.GetInputAudioPorts().size()); ++i)
{
result.inputAudioPorts_.push_back(config.GetInputAudioPorts()[i]);
}
for (size_t i = 0; i < std::max((size_t)2,config.GetOutputAudioPorts().size()); ++i)
{
result.outputAudioPorts_.push_back(config.GetOutputAudioPorts()[i]);
}
return result;
}
static std::vector<std::string> makeValid(const std::vector<std::string> & selected, const std::vector<std::string> &available)
{
std::vector<std::string> result;
for (size_t i = 0; i < selected.size(); ++i)
{
std::string t = selected[i];
bool found = false;
for (size_t j = 0; j < available.size(); ++j)
{
if (t == available[j])
{
found = true;
break;
}
}
if (found) {
result.push_back(t);
}
}
return result;
}
JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfiguration&config) const
{
JackChannelSelection result;
result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,config.GetInputAudioPorts());
result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.GetOutputAudioPorts());
result.inputMidiPorts_ = makeValid(this->inputMidiPorts_, config.GetInputMidiPorts());
if (!result.isValid())
{
return this->MakeDefault(config);
}
return result;
}
JSON_MAP_BEGIN(JackChannelSelection)
JSON_MAP_REFERENCE(JackChannelSelection,inputAudioPorts)
JSON_MAP_REFERENCE(JackChannelSelection,outputAudioPorts)
JSON_MAP_REFERENCE(JackChannelSelection,inputMidiPorts)
JSON_MAP_END()
JSON_MAP_BEGIN(JackConfiguration)
JSON_MAP_REFERENCE(JackConfiguration,isValid)
JSON_MAP_REFERENCE(JackConfiguration,isRestarting)
JSON_MAP_REFERENCE(JackConfiguration,errorStatus)
JSON_MAP_REFERENCE(JackConfiguration,sampleRate)
JSON_MAP_REFERENCE(JackConfiguration,blockLength)
JSON_MAP_REFERENCE(JackConfiguration,midiBufferSize)
JSON_MAP_REFERENCE(JackConfiguration,maxAllowedMidiDelta)
JSON_MAP_REFERENCE(JackConfiguration,inputAudioPorts)
JSON_MAP_REFERENCE(JackConfiguration,outputAudioPorts)
JSON_MAP_REFERENCE(JackConfiguration,inputMidiPorts)
JSON_MAP_REFERENCE(JackConfiguration,outputMidiPorts)
JSON_MAP_END()
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <vector>
#include <string>
#include "json.hpp"
namespace pipedal
{
class JackConfiguration
{
private:
bool isValid_ = false;
std::string errorStatus_;
bool isRestarting_ = false;
uint32_t sampleRate_ = 48000;
size_t blockLength_ = 1024;
size_t midiBufferSize_ = 16*1024;
uint32_t maxAllowedMidiDelta_ = 0;
std::vector<std::string> inputAudioPorts_;
std::vector<std::string> outputAudioPorts_;
std::vector<std::string> inputMidiPorts_;
std::vector<std::string> outputMidiPorts_;
public:
JackConfiguration();
void Initialize();
~JackConfiguration();
bool isValid() const { return isValid_;}
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_; }
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
const std::vector<std::string> &GetInputMidiPorts() const { return inputMidiPorts_; }
const std::vector<std::string> &GetOutputMidiPorts() const { return outputMidiPorts_; }
DECLARE_JSON_MAP(JackConfiguration);
};
class JackChannelSelection {
private:
std::vector<std::string> inputAudioPorts_;
std::vector<std::string> outputAudioPorts_;
std::vector<std::string> inputMidiPorts_;
public:
JackChannelSelection()
{
}
bool isValid() const {
return inputAudioPorts_.size() != 0 && outputAudioPorts_.size() != 0;
}
const std::vector<std::string>& GetInputAudioPorts() const
{
return inputAudioPorts_;
}
const std::vector<std::string>& getOutputAudioPorts() const
{
return outputAudioPorts_;
}
const std::vector<std::string>& GetInputMidiPorts() const
{
return inputMidiPorts_;
}
JackChannelSelection RemoveInvalidChannels(const JackConfiguration&config) const;
static JackChannelSelection MakeDefault(const JackConfiguration&config);
DECLARE_JSON_MAP(JackChannelSelection);
};
} // namespace.
+1376
View File
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
#pragma once
#include "JackConfiguration.hpp"
#include "Lv2PedalBoard.hpp"
#include "VuUpdate.hpp"
#include "RingBuffer.hpp"
#include "json.hpp"
#include "JackServerSettings.hpp"
#include <functional>
namespace pipedal {
using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
class MonitorPortUpdate
{
public:
PortMonitorCallback *callbackPtr; // pointer because function<>'s probably aren't POD.
int64_t subscriptionHandle;
float value;
};
class RealtimeParameterRequest {
public:
int64_t clientId;
int64_t instanceId;
LV2_URID uridUri;
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete;
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;
public:
RealtimeParameterRequest(
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete_,
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_),
clientId(clientId_),
instanceId(instanceId_),
uridUri(uridUri_),
onSuccess(onSuccess_),
onError(onError_)
{
}
};
class MonitorPortSubscription {
public:
int64_t subscriptionHandle;
int64_t instanceid;
std::string key;
float updateInterval;
PortMonitorCallback onUpdate;
};
class IJackHostCallbacks {
public:
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
};
class JackHostStatus {
public:
bool active_;
bool restarting_;
uint64_t underruns_;
float cpuUsage_ = 0;
uint64_t msSinceLastUnderrun_ = 0;
int32_t temperaturemC_ = -100000;
DECLARE_JSON_MAP(JackHostStatus);
};
class IHost;
class JackHost {
protected:
JackHost() { }
public:
static JackHost*CreateInstance(IHost *pHost);
virtual ~JackHost() { };
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0;
virtual void SetNotificationCallbacks(IJackHostCallbacks *pNotifyCallbacks) = 0;
virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void Open(const JackChannelSelection & channelSelection) = 0;
virtual void Close() = 0;
virtual uint32_t GetSampleRate() = 0;
virtual JackConfiguration GetServerConfiguration() = 0;
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard) = 0;
virtual void SetControlValue(long instanceId,const std::string&symbol, float value) = 0;
virtual void SetPluginPreset(long isntanceId, const std::vector<ControlValue> & values) = 0;
virtual void SetBypass(long instanceId, bool enabled) = 0;
virtual bool IsOpen() const = 0;
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
virtual JackHostStatus getJackStatus() = 0;
};
} //namespace pipedal.
+201
View File
@@ -0,0 +1,201 @@
#include "pch.h"
#include "Lv2Log.hpp"
#include "JackServerSettings.hpp"
#include <fstream>
#include "PiPedalException.hpp"
#define DRC_FILENAME "/etc/jackdrc"
using namespace std;
using namespace pipedal;
JackServerSettings::JackServerSettings()
{
}
static std::vector<std::string> SplitArgs(const char *szBuff)
{
std::vector<std::string> result;
while (*szBuff != '\0')
{
while (*szBuff == ' ' || *szBuff == '\t' || *szBuff == '\r' || *szBuff == '\n')
++szBuff;
if (*szBuff == 0)
break;
std::stringstream s;
while (*szBuff != ' ' && *szBuff != '\t' && *szBuff != '\0' && *szBuff != '\r' && *szBuff != '\n')
{
s.put(*szBuff++);
}
result.push_back(s.str());
}
return result;
}
static uint64_t GetJackArg(const std::vector<std::string> &args, const char *shortOption, const char *longOption)
{
int pos = -1;
for (size_t i = 1; i < args.size(); ++i)
{
if (args[i] == shortOption || args[i] == longOption)
{
pos = i;
break;
}
}
if (pos == -1 || pos == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
try
{
unsigned long value = std::stoul(args[pos + 1]);
return value;
}
catch (const std::exception &)
{
throw PiPedalException("Can't read Jack configuration.");
}
}
static void SetJackArg(std::vector<std::string> &args, const char *shortOption, const char *longOption,uint64_t value)
{
int pos = -1;
for (size_t i = 1; i < args.size(); ++i)
{
if (args[i] == shortOption || args[i] == longOption)
{
pos = i;
break;
}
}
if (pos == -1 || pos == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
stringstream s;
s << value;
args[pos+1] = s.str();
return;
}
void JackServerSettings::ReadJackConfiguration()
{
this->valid_ = false;
char firstLine[1024];
{
ifstream input(DRC_FILENAME);
if (!input.is_open())
{
Lv2Log::error("Can't read " DRC_FILENAME);
return;
}
while (true)
{
if (input.eof())
{
Lv2Log::error("Premature end of file in " DRC_FILENAME);
return;
}
input.getline(firstLine, sizeof(firstLine));
if (firstLine[0] != '#')
break;
}
try {
std::vector < std::string> argv = SplitArgs(firstLine);
this->bufferSize_ = GetJackArg(argv, "-p", "-period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "-nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "-rate");
valid_ = true;
} catch (std::exception &)
{
Lv2Log::error("Can't parse " DRC_FILENAME);
}
}
}
void JackServerSettings::Write()
{
this->valid_ = false;
std::vector<std::string> precedingLines;
std::vector < std::string> argv;
{
char firstLine[1024];
ifstream input(DRC_FILENAME);
if (!input.is_open())
{
Lv2Log::error("Can't read " DRC_FILENAME);
return;
}
while (true)
{
if (input.eof())
{
Lv2Log::error("Premature end of file in " DRC_FILENAME);
return;
}
input.getline(firstLine, sizeof(firstLine));
if (firstLine[0] != '#')
break;
precedingLines.push_back(firstLine);
}
// set new values for arguments.
argv = SplitArgs(firstLine);
try {
SetJackArg(argv, "-p", "-period", this->bufferSize_);
SetJackArg(argv, "-n", "-nperiods", this->numberOfBuffers_);
SetJackArg(argv, "-r", "-rate",this->sampleRate_);
} catch (std::exception &)
{
Lv2Log::error("Can't parse " DRC_FILENAME);
return;
}
}
// write to the output.
try {
ofstream output(DRC_FILENAME);
if (!output.is_open())
{
throw PiPedalException("Can't write " DRC_FILENAME);
}
for (auto line: precedingLines)
{
output << line << endl;
}
for (size_t i = 0; i < argv.size(); ++i)
{
if (i != 0) {
output << " ";
}
output << argv[i];
}
output << endl;
} catch (const std::exception &e) {
stringstream s;
s << "jack - " << e.what();
Lv2Log::error(s.str());
}
}
JSON_MAP_BEGIN(JackServerSettings)
JSON_MAP_REFERENCE(JackServerSettings,valid)
JSON_MAP_REFERENCE(JackServerSettings,rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings,sampleRate)
JSON_MAP_REFERENCE(JackServerSettings,bufferSize)
JSON_MAP_REFERENCE(JackServerSettings,numberOfBuffers)
JSON_MAP_END()
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include "json.hpp"
namespace pipedal {
// Jack Daemon configuration.
class JackServerSettings {
bool valid_ = false;
bool rebootRequired_ = false;
uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 0;
uint32_t numberOfBuffers_ = 0;
public:
JackServerSettings();
uint64_t GetSampleRate() const { return sampleRate_; }
uint32_t GetBufferSize() const { return bufferSize_; }
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
void ReadJackConfiguration();
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.
void SetRebootRequired(bool value)
{
rebootRequired_ = value;
}
bool Equals(const JackServerSettings&other)
{
return this->sampleRate_ == other.sampleRate_
&& this->bufferSize_ == other.bufferSize_
&& this->numberOfBuffers_ == other.numberOfBuffers_;
}
DECLARE_JSON_MAP(JackServerSettings);
};
} // namespace
+28
View File
@@ -0,0 +1,28 @@
#include "pch.h"
#include "Locale.hpp"
#include <stdlib.h>
// Must be UNICODE. Should reflect system locale. (.e.g en-US.UTF8, de-de.UTF8)
// This is correct for libstdc++; most probably not correct for Windows or CLANG. :-(
using namespace pipedal;
static const char*getLocale(const char*localeEnvironmentVariable)
{
const char*result = getenv(localeEnvironmentVariable);
if (result == nullptr)
{
result = getenv("LC_ALL");
}
if (result == nullptr) {
result = "en_US.UTF-8";
}
return result;
}
static std::locale collationLocale(getLocale("LC_COLLATION"));
const std::collate<char>& Locale::collation = std::use_facet<std::collate<char> >(collationLocale);
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <locale>
namespace pipedal {
class Locale {
public:
static const std::collate<char>& collation;
};
}
+74
View File
@@ -0,0 +1,74 @@
#include "LogFeature.hpp"
#include <mutex>
#include <cstdio>
#include "Lv2Log.hpp"
using namespace pipedal;
using namespace std;
int LogFeature::printfFn(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...)
{
va_list va;
va_start(va, fmt);
LogFeature* logFeature = (LogFeature*)handle;
return logFeature->vprintf(type, fmt, va);
}
int LogFeature::vprintfFn(LV2_Log_Handle handle,
LV2_URID type,
const char* fmt,
va_list ap)
{
LogFeature* logFeature = (LogFeature*)handle;
return logFeature->vprintf(type, fmt, ap);
}
int LogFeature::vprintf(LV2_URID type,const char*fmt, va_list va)
{
std::lock_guard<std::mutex> guard(logMutex);
const char* prefix = "";
char buffer[1024];
int result = vsnprintf(buffer, sizeof(buffer), fmt, va);
if (type == uris.ridError)
{
Lv2Log::error(buffer);
}
else if (type == uris.ridWarning)
{
Lv2Log::warning(buffer);
}
else if (type == uris.ridNote)
{
Lv2Log::info(buffer);
}
else if (type == uris.ridTrace)
{
Lv2Log::debug(buffer);
}
else {
Lv2Log::info(buffer);
}
return result;
}
LogFeature::LogFeature()
{
feature.URI = LV2_LOG__log;
feature.data = &log;
log.handle = (void*)this;
log.printf = printfFn;
log.vprintf = vprintfFn;
}
void LogFeature::Prepare(MapFeature*map)
{
uris.Map(map);
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "lv2/core/lv2.h"
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "lv2/atom/atom.h"
#include "MapFeature.hpp"
#include <map>
#include <string>
#include <mutex>
namespace pipedal {
class LogFeature {
private:
LV2_URID nextAtom = 0;
LV2_Feature feature;
LV2_Log_Log log;
std::mutex logMutex;
struct Uri {
void Map(MapFeature* map)
{
ridError = map->GetUrid(LV2_LOG__Error);
ridNote = map->GetUrid(LV2_LOG__Note);
ridTrace = map->GetUrid(LV2_LOG__Trace);
ridWarning = map->GetUrid(LV2_LOG__Warning);
}
LV2_URID ridError;
LV2_URID ridWarning;
LV2_URID ridNote;
LV2_URID ridTrace;
};
Uri uris;
public:
LogFeature();
void Prepare(MapFeature* map);
public:
const LV2_Feature* GetFeature()
{
return &feature;
}
LV2_URID GetUrid(const char* uri);
private:
static int printfFn(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...);
static int vprintfFn(LV2_Log_Handle handle,
LV2_URID type,
const char* fmt,
va_list ap);
int vprintf(LV2_URID type, const char* fmt, va_list va);
};
}
+667
View File
@@ -0,0 +1,667 @@
#include "pch.h"
#include "Lv2Effect.hpp"
#include "PiPedalException.hpp"
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <lilv/lilv.h>
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "lv2/log/logger.h"
#include "lv2/uri-map/uri-map.h"
#include "lv2/atom/forge.h"
#include "lv2/worker/worker.h"
#include "lv2/patch/patch.h"
#include "lv2/parameters/parameters.h"
#include "lv2/units/units.h"
#include "lv2/atom/util.h"
#include "JackHost.hpp"
using namespace pipedal;
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)
{
auto pWorld = pHost_->getWorld();
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
this->bypass = pedalBoardItem.isEnabled();
// initialize the atom forge used on the realtime thread.
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
lv2_atom_forge_init(&inputForgeRt, map);
lv2_atom_forge_init(&outputForgeRt, map);
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
auto uriNode = lilv_new_uri(pWorld, pedalBoardItem.uri().c_str());
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
lilv_node_free(uriNode);
LV2_Feature *const *features = pHost_->GetLv2Features();
for (auto p = features; *p != nullptr; ++p)
{
this->features.push_back(*p);
}
this->work_schedule_feature = nullptr;
if (info_->hasExtension(LV2_WORKER__interface))
{
// insane implementation. :-(
LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule));
schedule->handle = this;
schedule->schedule_work = worker_schedule_fn;
work_schedule_feature = (LV2_Feature *)malloc(sizeof(LV2_Feature));
work_schedule_feature->URI = LV2_WORKER__schedule;
work_schedule_feature->data = schedule;
this->features.push_back(work_schedule_feature);
}
this->features.push_back(nullptr);
LV2_Feature **myFeatures = &this->features[0];
LilvInstance *pInstance = lilv_plugin_instantiate(pPlugin, pHost->GetSampleRate(), myFeatures);
this->pInstance = pInstance;
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);
this->worker = std::make_unique<Worker>(pInstance, worker_interface);
}
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)
{
auto &v = (*i);
int index = GetControlIndex(v.key());
if (index != -1)
{
this->controlValues[index] = v.value();
}
}
PreparePortIndices();
ConnectControlPorts();
}
void Lv2Effect::ConnectControlPorts()
{
// shared_ptr is not thread-safe.
// Get naked pointers to use on the realtime thread.
int controlArrayLength = 0;
for (int i = 0; i < info->ports().size(); ++i)
{
if (info->ports()[i]->index() >= controlArrayLength)
{
controlArrayLength = info->ports()[i]->index() + 1;
}
}
this->realtimePortInfo.resize(controlArrayLength);
for (int i = 0; i < info->ports().size(); ++i)
{
const auto &port = info->ports()[i];
if (port->is_control_port())
{
int index = port->index();
realtimePortInfo[index] = port.get();
lilv_instance_connect_port(pInstance, i, &this->controlValues[index]);
}
}
}
void Lv2Effect::PreparePortIndices()
{
for (int i = 0; i < info->ports().size(); ++i)
{
const auto &port = info->ports()[i];
int portIndex = port->index();
if (port->is_audio_port())
{
if (port->is_input())
{
this->inputAudioPortIndices.push_back(portIndex);
}
else
{
this->outputAudioPortIndices.push_back(portIndex);
}
}
else if (port->is_atom_port())
{
if (port->is_input())
{
if (port->supports_midi())
{
this->inputMidiPortIndices.push_back(portIndex);
}
this->inputAtomPortIndices.push_back(portIndex);
}
else
{
this->outputAtomPortIndices.push_back(portIndex);
if (port->supports_midi())
{
this->outputMidiPortIndices.push_back(portIndex);
}
}
}
}
inputAudioBuffers.resize(inputAudioPortIndices.size());
outputAudioBuffers.resize(outputAudioPortIndices.size());
inputAtomBuffers.resize(inputAtomPortIndices.size());
outputAtomBuffers.resize(outputAtomPortIndices.size());
}
void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
{
if (index >= inputAudioPortIndices.size())
{
throw PiPedalArgumentException("Buffer index out of range.");
}
this->inputAudioBuffers[index] = buffer;
int pluginIndex = this->inputAudioPortIndices[index];
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
}
void Lv2Effect::SetAudioInputBuffer(float *left)
{
if (GetNumberOfInputs() > 1)
{
SetAudioInputBuffer(0, left);
SetAudioInputBuffer(1, left);
}
else
{
SetAudioInputBuffer(0, left);
}
}
void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
{
if (GetNumberOfInputs() == 1)
{
SetAudioInputBuffer(0, left);
}
else
{
SetAudioInputBuffer(0, left);
SetAudioInputBuffer(1, right);
}
}
void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
{
this->outputAudioBuffers[index] = buffer;
int pluginIndex = this->outputAudioPortIndices[index];
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
}
int Lv2Effect::GetControlIndex(const std::string &key) const
{
for (int i = 0; i < info->ports().size(); ++i)
{
auto &port = info->ports()[i];
if (port->symbol() == key)
return port->index();
}
return -1;
}
Lv2Effect::~Lv2Effect()
{
if (worker)
{
worker = nullptr; // delete the worker first!
}
if (pInstance)
{
lilv_instance_free(pInstance);
pInstance = nullptr;
}
if (work_schedule_feature)
{
free(work_schedule_feature->data);
free(work_schedule_feature);
}
}
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)
{
if (GetAudioInputBuffer(i) == nullptr)
{
int pluginIndex = this->inputAudioPortIndices[i];
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
}
}
for (int i = 0; i < this->GetNumberOfOutputAudioPorts(); ++i)
{
if (GetAudioOutputBuffer(i) == nullptr)
{
int pluginIndex = this->outputAudioPortIndices[i];
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
}
}
for (int i = 0; i < this->GetNumberOfInputAtomPorts(); ++i)
{
if (GetAtomInputBuffer(i) == nullptr)
{
int pluginIndex = this->inputAtomPortIndices[i];
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
ResetInputAtomBuffer((char *)buffer);
this->inputAtomBuffers[i] = (char *)buffer;
}
}
for (int i = 0; i < this->GetNumberOfOutputAtomPorts(); ++i)
{
if (GetAtomOutputBuffer(i) == nullptr)
{
int pluginIndex = this->outputAtomPortIndices[i];
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
ResetOutputAtomBuffer((char *)buffer);
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
this->outputAtomBuffers[i] = (char *)buffer;
}
}
}
void Lv2Effect::Deactivate()
{
lilv_instance_deactivate(pInstance);
}
static inline void CopyBuffer(float *input, float *output, uint32_t frames)
{
for (uint32_t i = 0; i < frames; ++i)
{
output[i] = input[i];
}
}
void Lv2Effect::Run(uint32_t samples)
{
// close off the atom input frame.
if (this->inputAtomBuffers.size() != 0)
{
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
}
if (worker)
{
// relay worker response
worker->EmitResponses();
}
lilv_instance_run(pInstance, samples);
// do soft bypass.
if (this->bypassSamplesRemaining == 0)
{
if (this->currentBypass == 0)
{
// replace the contents of the output buffer(s) with the input buffer(s).
if (this->outputAudioBuffers.size() == 1)
{
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
}
else
{
if (this->inputAudioBuffers.size() == 1)
{
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[1], samples);
}
}
} // else leave the output alone.
}
else
{
double currentBypass = this->currentBypass;
double currentBypassDx = this->currentBypassDx;
int32_t bypassSamplesRemaining = (int)this->bypassSamplesRemaining;
if (this->outputAudioBuffers.size() == 1)
{
float *input = this->inputAudioBuffers[0];
float *output = this->outputAudioBuffers[0];
for (uint32_t i = 0; i < samples; ++i)
{
output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i];
if (--bypassSamplesRemaining == 0)
{
currentBypassDx = 0;
currentBypass = this->targetBypass;
}
currentBypass += currentBypassDx;
}
}
else
{
float *inputL;
float *inputR;
if (this->inputAudioBuffers.size() == 1)
{
inputL = inputR = inputAudioBuffers[0];
}
else
{
inputL = inputAudioBuffers[0];
inputR = inputAudioBuffers[1];
}
float *outputL = outputAudioBuffers[0];
float *outputR = outputAudioBuffers[1];
for (uint32_t i = 0; i < samples; ++i)
{
outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i];
outputR[i] = currentBypass * outputR[i] + (1 - currentBypass) * inputR[i];
if (--bypassSamplesRemaining == 0)
{
currentBypassDx = 0;
currentBypass = this->targetBypass;
}
currentBypass += currentBypassDx;
}
}
if (bypassSamplesRemaining <= 0)
{
this->bypassSamplesRemaining = 0;
this->currentBypass = this->targetBypass;
this->currentBypassDx = 0;
}
else
{
this->currentBypass = currentBypass;
this->currentBypassDx = currentBypassDx;
this->bypassSamplesRemaining = bypassSamplesRemaining;
}
}
}
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
uint32_t size,
const void *data)
{
Lv2Effect *this_ = (Lv2Effect *)handle;
this_->worker->ScheduleWork(size, data);
return LV2_WORKER_SUCCESS;
}
struct BufferHeader
{
uint32_t size;
uint32_t type;
};
void Lv2Effect::ResetInputAtomBuffer(char *data)
{
BufferHeader *header = (BufferHeader *)data;
header->size = sizeof(LV2_Atom_Sequence_Body);
header->type = uris.atom_Sequence;
}
void Lv2Effect::ResetOutputAtomBuffer(char *data)
{
BufferHeader *header = (BufferHeader *)data;
header->size = pHost->GetAtomBufferSize() - 8;
header->type = uris.atom_Chunk;
}
void Lv2Effect::BypassTo(float targetValue)
{
this->targetBypass = targetValue;
double dx = targetValue - this->currentBypass;
if (dx != 0)
{
this->bypassSamplesRemaining = (int)(bypassStartingSamples * std::abs(dx));
if (this->bypassStartingSamples == 0)
{
currentBypassDx = 0;
this->currentBypass = targetBypass;
}
else
{
this->currentBypassDx = dx / this->bypassSamplesRemaining;
}
}
}
void Lv2Effect::ResetAtomBuffers()
{
for (size_t i = 0; i < this->inputAtomBuffers.size(); ++i)
{
ResetInputAtomBuffer(this->inputAtomBuffers[i]);
}
for (size_t i = 0; i < this->outputAtomBuffers.size(); ++i)
{
ResetOutputAtomBuffer(this->outputAtomBuffers[i]);
}
if (inputAtomBuffers.size() != 0)
{
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
lv2_atom_forge_set_buffer(
&(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers[0]), notify_capacity);
// Start a sequence in the notify input port.
lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, uris.unitsFrame);
}
}
void Lv2Effect::RequestParameter(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_key(&inputForgeRt, uris.patch_property);
lv2_atom_forge_urid(&inputForgeRt, uridUri);
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
}
void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest)
{
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
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))
{
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
if (obj->body.otype == uris.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);
if (!property)
{
}
else if (property->type != uris.atom_URID)
{
}
else
{
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
if (key == pRequest->uridUri)
{
if (value->size > sizeof(pRequest->response))
{
pRequest->errorMessage = "Response is too large.";
} else {
pRequest->responseLength = value->size;
memcpy(pRequest->response,value,value->size);
}
break;
}
}
}
}
}
}
std::string Lv2Effect::AtomToJson(uint8_t*pData)
{
std::stringstream s;
json_writer writer(s);
LV2_Atom *pAtom = (LV2_Atom*)pData;
WriteAtom(writer,pAtom);
return s.str();
}
void Lv2Effect::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*) (((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(LV2_Atom_Vector))/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;
std::string id = pHost->Lv2UriudToString(obj->body.id);
std::string type = pHost->Lv2UriudToString(obj->body.otype);
writer.write_json_member("id",id.c_str());
writer.write_json_member("lv2Type",type.c_str());
bool firstMember = true;
LV2_ATOM_OBJECT_FOREACH (obj, prop) {
if (!firstMember) {
writer.write_raw(",");
}
firstMember = false;
std::string key = pHost->Lv2UriudToString(prop->key);
writer.write(key);
writer.write_raw(": ");
LV2_Atom *value = &(prop->value);
}
writer.end_object();
}
}
+246
View File
@@ -0,0 +1,246 @@
#pragma once
#include "Lv2Host.hpp"
#include "PedalBoard.hpp"
#include <lilv/lilv.h>
#include "BufferPool.hpp"
#include "IEffect.hpp"
#include "Worker.hpp"
#include "lv2/patch/patch.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
namespace pipedal
{
class Lv2Effect : public IEffect
{
private:
IHost *pHost = nullptr;
int numberOfInputs = 0;
int numberOfOutputs = 0;
int numberOfMidiInputs = 0;
std::unique_ptr<Worker> worker;
LilvInstance *pInstance;
std::shared_ptr<Lv2PluginInfo> info;
std::vector<float> controlValues;
std::vector<int> inputAudioPortIndices;
std::vector<int> outputAudioPortIndices;
std::vector<int> inputAtomPortIndices;
std::vector<int> outputAtomPortIndices;
std::vector<int> inputMidiPortIndices;
std::vector<int> outputMidiPortIndices;
std::vector<int> midiInputIndices;
std::vector<float *> inputAudioBuffers;
std::vector<float *> outputAudioBuffers;
std::vector<char *> inputAtomBuffers;
std::vector<char *> outputAtomBuffers;
std::vector<LV2_Feature *> features;
LV2_Feature *work_schedule_feature = nullptr;
virtual std::string GetUri() const { return info->uri(); }
std::vector<const Lv2PortInfo *> realtimePortInfo;
void PreparePortIndices();
void ConnectControlPorts();
void AssignUnconnectedPorts();
LV2_Atom_Forge inputForgeRt;
LV2_Atom_Forge_Frame input_frame;
LV2_Atom_Forge outputForgeRt;
void WriteAtom(json_writer &writer, LV2_Atom*pAtom);
class Uris
{
public:
Uris(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_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);
}
LV2_URID patch_accept;
LV2_URID unitsFrame;
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;
};
Uris uris;
long instanceId;
BufferPool bufferPool;
static LV2_Worker_Status worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
uint32_t size,
const void *data);
void ResetInputAtomBuffer(char*data);
void ResetOutputAtomBuffer(char*data);
uint32_t bypassStartingSamples = 0;
bool bypass = true;
double targetBypass = 0;
double currentBypass = 0;
double currentBypassDx = 0;
uint32_t bypassSamplesRemaining = 0;
void BypassTo(float value);
virtual void RequestParameter(LV2_URID uridUri);
virtual void GatherParameter(RealtimeParameterRequest*pRequest);
virtual uint8_t*GetAtomInputBuffer() {
if (this->inputAtomBuffers.size() == 0) return nullptr;
return (uint8_t*)this->inputAtomBuffers[0];
}
virtual uint8_t*GetAtomOutputBuffer()
{
if (this->outputAtomBuffers.size() == 0) return nullptr;
return (uint8_t*)this->outputAtomBuffers[0];
}
virtual std::string AtomToJson(uint8_t*pAtom);
public:
Lv2Effect(
IHost *pHost,
const std::shared_ptr<Lv2PluginInfo> &info,
const PedalBoardItem &pedalBoardItem);
~Lv2Effect();
virtual void ResetAtomBuffers();
virtual long GetInstanceId() const { return instanceId; }
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
virtual void SetAudioInputBuffer(int index, float *buffer);
float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
void SetAudioInputBuffer(float *buffer);
void SetAudioInputBuffers(float *left, float *right);
virtual void SetAudioOutputBuffer(int index, float *buffer);
float *GetAudioOutputBuffer(int index) const { return this->outputAudioBuffers[index]; }
void SetAtomInputBuffer(int index, void *buffer);
void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
void SetAtomOutputBuffer(int index, void *buffer);
void *GetAtomOutputBuffer(int index) const { return this->outputAtomBuffers[index]; }
virtual int GetControlIndex(const std::string &symbol) const;
virtual void SetControl(int index, float value)
{
if (index == -1)
{
this->bypass = value != 0;
} else {
controlValues[index] = value;
}
}
virtual float GetControlValue(int index) const {
if (index == -1)
{
return this->bypass? 1: 0;
}
return controlValues[index];
}
virtual float GetOutputControlValue(int portIndex) const
{
return controlValues[portIndex];
}
virtual void SetBypass(bool bypass)
{
if (bypass != this->bypass)
{
this->bypass = bypass;
BypassTo(bypass? 1.0f: 0.0f);
}
}
int GetNumberOfInputs() const
{
return this->inputAudioPortIndices.size();
}
int GetNumberOfOutputs() const
{
return this->outputAudioPortIndices.size();
}
int GetNumberOfMidiInputs() const
{
return this->inputMidiPortIndices.size();
}
void Activate();
void Run(uint32_t samples);
void Deactivate();
};
} // namespace
+15
View File
@@ -0,0 +1,15 @@
#include "pch.h"
#include "Lv2EventBufferWriter.hpp"
#include "Lv2Host.hpp"
using namespace pipedal;
Lv2EventBufferUrids::Lv2EventBufferUrids(IHost *pHost)
{
atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
midi_Event = pHost->GetLv2Urid(LV2_MIDI__MidiEvent);
}
+153
View File
@@ -0,0 +1,153 @@
#pragma once
/*
Based on lv2_evbuf.h/lv2_evbuf.c in https://raw.githubusercontent.com/drobilla/jalv/master/src/lv2_evbuf.h
Copyright 2008-2014 David Robillard <d@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.
*/
#pragma once
#include <stdint.h>
#include <lv2/core/lv2.h>
#include <lv2/atom/atom.h>
#include <lv2/midi/midi.h>
namespace pipedal
{
class IHost;
class Lv2EventBufferUrids
{
public:
Lv2EventBufferUrids(IHost *pHost);
LV2_URID atom_Chunk;
LV2_URID atom_Sequence;
LV2_URID midi_Event;
};
class Lv2EventBufferWriter
{
const Lv2EventBufferUrids &urids;
LV2_Atom_Sequence *evbuf;
size_t bufferSize;
size_t offset;
size_t padSize(size_t size) const
{
return (size + 7) & (~7);
}
size_t size() const {
assert(evbuf->atom.type != urids.atom_Sequence
|| evbuf->atom.size >= sizeof(LV2_Atom_Sequence_Body));
return evbuf->atom.type == urids.atom_Sequence
? evbuf->atom.size - sizeof(LV2_Atom_Sequence_Body)
: 0;
}
public:
Lv2EventBufferWriter(const Lv2EventBufferUrids &urids)
: urids(urids),
evbuf(nullptr),
bufferSize(0),
offset(0)
{
}
Lv2EventBufferWriter(const Lv2EventBufferUrids &urids, uint8_t *buffer, size_t size)
: urids(urids),
evbuf((LV2_Atom_Sequence *)buffer),
bufferSize(size),
offset(0)
{
this->evbuf->atom.size = sizeof(LV2_Atom_Sequence_Body);
this->evbuf->atom.type = urids.atom_Sequence;
}
void Reset(uint8_t *buffer, size_t size)
{
this->evbuf = (LV2_Atom_Sequence*)buffer;
this->bufferSize = size;
this->offset = 0;
this->evbuf->atom.size = sizeof(LV2_Atom_Sequence_Body);
this->evbuf->atom.type = urids.atom_Sequence;
}
class LV2_EvBuf_Iterator
{
public:
Lv2EventBufferWriter *writer;
size_t offset;
};
LV2_EvBuf_Iterator begin() const
{
LV2_EvBuf_Iterator iter = {const_cast<Lv2EventBufferWriter*>(this), 0};
return iter;
}
LV2_EvBuf_Iterator end() const
{
LV2_EvBuf_Iterator iter = {const_cast<Lv2EventBufferWriter*>(this), padSize(size())};
return iter;
}
bool isValid(const LV2_EvBuf_Iterator &iterator) const
{
return iterator.offset < size();
}
bool write(
LV2_EvBuf_Iterator &iterator,
uint32_t frameOffset,
uint32_t type,
size_t size,
const void*data
)
{
LV2_Atom_Sequence *aseq = evbuf;
if (bufferSize - sizeof(LV2_Atom) - evbuf->atom.size <
sizeof(LV2_Atom_Event) + size)
{
return false;
}
LV2_Atom_Event *aev = (LV2_Atom_Event *)((char *)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + iterator.offset);
aev->time.frames = frameOffset;
aev->body.type = type;
aev->body.size = size;
memcpy(LV2_ATOM_BODY(&aev->body), data, size);
size = padSize(sizeof(LV2_Atom_Event) + size);
aseq->atom.size += size;
iterator.offset += size;
return true;
}
bool writeMidiEvent(
LV2_EvBuf_Iterator &iterator,
int32_t frameOffset,
size_t midiSize,
void *midiData
)
{
write(iterator,frameOffset,urids.midi_Event,midiSize,midiData);
return false;
}
};
} // namespace.
+1165
View File
File diff suppressed because it is too large Load Diff
+634
View File
@@ -0,0 +1,634 @@
#pragma once
#include <vector>
#include <memory>
#include "json.hpp"
#include "PluginType.hpp"
#include "PedalBoard.hpp"
#include <lilv/lilv.h>
#include "MapFeature.hpp"
#include "LogFeature.hpp"
#include "OptionsFeature.hpp"
#include <filesystem>
#include <cmath>
#include "lv2/core/lv2.h"
#include "Units.hpp"
namespace pipedal {
// forward declarations
class Lv2Effect;
class Lv2PedalBoard;
class Lv2Host;
class JackConfiguration;
class JackChannelSelection;
#define LV2_PROPERTY_GETSET(name) \
const decltype(name##_) & name() const { return name##_; }; \
decltype(name##_) & name() { return name##_; }; \
void name(const decltype(name##_) &value) { name##_ = value; };
#define LV2_PROPERTY_GETSET_SCALAR(name) \
decltype(name##_) name() const { return name##_;}; \
void name(decltype(name##_) value) { name##_ = value; };
class Lv2PluginPreset {
public:
Lv2PluginPreset(const std::string &presetUri, const std::string&name)
: presetUri_(presetUri),
name_(name)
{
}
Lv2PluginPreset() { }
std::string presetUri_;
std::string name_;
DECLARE_JSON_MAP(Lv2PluginPreset);
};
class Lv2PluginClass {
public:
friend class Lv2Host;
private:
Lv2PluginClass* parent_; // NOT SERIALIZED!
std::string parent_uri_;
std::string display_name_;
std::string uri_;
PluginType plugin_type_;
std::vector<std::shared_ptr<Lv2PluginClass>> children_;
friend class ::pipedal::Lv2Host;
// hide copy constructor.
Lv2PluginClass(const Lv2PluginClass &other)
{
}
void set_parent(std::shared_ptr<Lv2PluginClass> &parent)
{
this->parent_ = parent.get();
}
void add_child(std::shared_ptr<Lv2PluginClass> &child) {
children_.push_back(child);
}
public:
Lv2PluginClass(
const char*display_name, const char*uri, const char*parent_uri)
: parent_uri_(parent_uri)
, display_name_(display_name)
, uri_(uri)
, plugin_type_(uri_to_plugin_type(uri))
{
}
Lv2PluginClass() { }
LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(display_name)
LV2_PROPERTY_GETSET(parent_uri)
const Lv2PluginClass *parent() { return parent_;}
bool operator==(const Lv2PluginClass &other)
{
return uri_ == other.uri_;
}
bool is_a(const std::string& classUri) const;
static json_map::storage_type<Lv2PluginClass> jmap;
};
class Lv2PluginClasses {
private:
std::vector<std::string> classes_;
public:
Lv2PluginClasses()
{
}
Lv2PluginClasses(std::vector<std::string> classes)
:classes_(classes)
{
}
const std::vector<std::string> & classes() const
{
return classes_;
}
bool is_a(Lv2Host*lv2Plugins,const char*classUri) const;
static json_map::storage_type<Lv2PluginClasses> jmap;
};
class Lv2ScalePoint {
private:
float value_;
std::string label_;
public:
Lv2ScalePoint() { }
Lv2ScalePoint(float value, std::string label)
: value_(value)
, label_(label)
{
}
LV2_PROPERTY_GETSET_SCALAR(value);
LV2_PROPERTY_GETSET(label);
static json_map::storage_type<Lv2ScalePoint> jmap;
};
enum class Lv2BufferType {
None,
Event,
Sequence,
Unknown
};
class Lv2PortInfo {
private:
friend class Lv2PluginInfo;
Lv2PortInfo(Lv2Host*lv2Host,const LilvPlugin*pPlugin,const LilvPort *pPort);
uint32_t index_;
std::string symbol_;
std::string name_;
float min_value_, max_value_, default_value_;
Lv2PluginClasses classes_;
std::vector<Lv2ScalePoint> scale_points_;
bool is_input_ = false;
bool is_output_ = false;
bool is_control_port_ = false;
bool is_audio_port_ = false;
bool is_atom_port_ = false;
bool is_cv_port_ = false;
bool is_valid_ = false;
bool supports_midi_ = false;
bool is_logarithmic_ = false;
int display_priority_ = -1;
int range_steps_ = 0;
bool trigger_;
bool integer_property_;
bool enumeration_property_;
bool toggled_property_;
bool not_on_gui_;
std::string buffer_type_;
std::string port_group_;
std::string designation_;
Units units_ = Units::none;
public:
bool IsSwitch() const {
return min_value_ == 0 && max_value_ == 1
&& (integer_property_ || toggled_property_ || enumeration_property_);
}
float rangeToValue(float range) const
{
float value;
if (is_logarithmic_)
{
value = std::pow(max_value_/min_value_,range)*min_value_;
} else {
value = (max_value_-min_value_)*range+min_value_;
}
if (integer_property_ || enumeration_property_)
{
value = std::round(value);
} else if (range_steps_ >= 2)
{
value = std::round(value*(range_steps_-1))/(range_steps_-1);
}
if (toggled_property_)
{
value = value == 0 ? 0: max_value_;
}
if (value > max_value_) value = max_value_;
if (value < min_value_) value = min_value_;
return value;
}
LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET_SCALAR(index);
LV2_PROPERTY_GETSET(name);
LV2_PROPERTY_GETSET(classes);
LV2_PROPERTY_GETSET(scale_points);
LV2_PROPERTY_GETSET_SCALAR(min_value);
LV2_PROPERTY_GETSET_SCALAR(max_value);
LV2_PROPERTY_GETSET_SCALAR(default_value);
LV2_PROPERTY_GETSET_SCALAR(is_input);
LV2_PROPERTY_GETSET_SCALAR(is_output);
LV2_PROPERTY_GETSET_SCALAR(is_control_port);
LV2_PROPERTY_GETSET_SCALAR(is_audio_port);
LV2_PROPERTY_GETSET_SCALAR(is_atom_port);
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
LV2_PROPERTY_GETSET_SCALAR(is_valid);
LV2_PROPERTY_GETSET_SCALAR(supports_midi);
LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
LV2_PROPERTY_GETSET_SCALAR(display_priority);
LV2_PROPERTY_GETSET_SCALAR(range_steps);
LV2_PROPERTY_GETSET_SCALAR(trigger);
LV2_PROPERTY_GETSET_SCALAR(integer_property);
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
LV2_PROPERTY_GETSET_SCALAR(not_on_gui);
LV2_PROPERTY_GETSET(port_group);
LV2_PROPERTY_GETSET_SCALAR(units);
LV2_PROPERTY_GETSET(buffer_type);
Lv2BufferType GetBufferType() {
if (buffer_type_ == "") return Lv2BufferType::None;
if (buffer_type_ == LV2_ATOM__Sequence) return Lv2BufferType::Sequence;
return Lv2BufferType::Unknown;
}
public:
Lv2PortInfo() { }
~Lv2PortInfo() = default;
bool is_a(Lv2Host*lv2Plugins,const char*classUri);
static json_map::storage_type<Lv2PortInfo> jmap;
};
class Lv2PortGroup {
private:
std::string uri_;
std::string symbol_;
std::string name_;
public:
LV2_PROPERTY_GETSET(uri);
LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET(name);
Lv2PortGroup() { }
Lv2PortGroup(Lv2Host*lv2Host,const std::string &groupUri);
static json_map::storage_type<Lv2PortGroup> jmap;
};
class Lv2PluginInfo {
private:
friend class Lv2Host;
public:
Lv2PluginInfo(Lv2Host*lv2Host,const LilvPlugin*);
Lv2PluginInfo() { }
private:
std::string uri_;
std::string name_;
std::string plugin_class_;
std::vector<std::string> supported_features_;
std::vector<std::string> required_features_;
std::vector<std::string> optional_features_;
std::vector<std::string> extensions_;
std::string author_name_;
std::string author_homepage_;
std::string comment_;
std::vector<std::shared_ptr<Lv2PortInfo> > ports_;
std::vector<std::shared_ptr<Lv2PortGroup> > port_groups_;
bool is_valid_ = false;
bool IsSupportedFeature(const std::string &feature) const;
public:
LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name)
LV2_PROPERTY_GETSET(plugin_class)
LV2_PROPERTY_GETSET(supported_features)
LV2_PROPERTY_GETSET(required_features)
LV2_PROPERTY_GETSET(optional_features)
LV2_PROPERTY_GETSET(author_name)
LV2_PROPERTY_GETSET(author_homepage)
LV2_PROPERTY_GETSET(comment)
LV2_PROPERTY_GETSET(extensions)
LV2_PROPERTY_GETSET(ports)
LV2_PROPERTY_GETSET(is_valid)
LV2_PROPERTY_GETSET(port_groups)
const Lv2PortInfo& getPort(const std::string&symbol)
{
for (size_t i = 0; i < ports_.size(); ++i)
{
if (ports_[i]->symbol() == symbol)
{
return *(ports_[i].get());
}
}
throw PiPedalArgumentException("Port not found.");
}
bool hasExtension(const std::string&uri)
{
for (int i = 0; i < extensions_.size(); ++i)
{
if (extensions_[i] == uri) return true;
}
return false;
}
bool hasCvPorts() const {
for (size_t i = 0; i < ports_.size(); ++i)
{
if (ports_[i]->is_cv_port())
{
return true;
}
}
return false;
}
public:
virtual ~Lv2PluginInfo();
static json_map::storage_type<Lv2PluginInfo> jmap;
};
class Lv2PluginUiControlPort {
public:
Lv2PluginUiControlPort() {
}
Lv2PluginUiControlPort(const Lv2PluginInfo *pPlugin,const Lv2PortInfo*pPort)
: symbol_(pPort->symbol())
, index_(pPort->index())
, name_(pPort->name())
, min_value_(pPort->min_value())
, max_value_(pPort->max_value())
, default_value_(pPort->default_value())
, range_steps_(pPort->range_steps())
, display_priority_(pPort->display_priority())
, is_logarithmic_(pPort->is_logarithmic())
, integer_property_(pPort->integer_property())
, enumeration_property_(pPort->enumeration_property())
, toggled_property_(pPort->toggled_property())
, not_on_gui_(pPort->not_on_gui())
, scale_points_(pPort->scale_points())
, units_(pPort->units())
{
// Use symbols to index port groups, instead of uris.
// symbols are guaranteed to be unique.
auto &portGroup = pPort->port_group();
for (int i = 0; i < pPlugin->port_groups().size(); ++i)
{
auto &p = pPlugin->port_groups()[i];
if (p->uri() == portGroup)
{
this->port_group_ = p->symbol();
break;
}
}
}
private:
std::string symbol_;
int index_;
std::string name_;
float min_value_ = 0, max_value_ = 1,default_value_ = 0;
int range_steps_ = 0;
int display_priority_ = -1;
bool is_logarithmic_ = false;
bool integer_property_ = false;
bool enumeration_property_ = false;
bool toggled_property_ = false;
bool not_on_gui_ = false;
std::vector<Lv2ScalePoint> scale_points_;
std::string port_group_;
Units units_ = Units::none;
public:
LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET_SCALAR(index);
LV2_PROPERTY_GETSET(name);
LV2_PROPERTY_GETSET_SCALAR(min_value);
LV2_PROPERTY_GETSET_SCALAR(max_value);
LV2_PROPERTY_GETSET_SCALAR(default_value);
LV2_PROPERTY_GETSET_SCALAR(range_steps);
LV2_PROPERTY_GETSET_SCALAR(display_priority);
LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
LV2_PROPERTY_GETSET_SCALAR(integer_property);
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
LV2_PROPERTY_GETSET_SCALAR(not_on_gui);
LV2_PROPERTY_GETSET(scale_points);
LV2_PROPERTY_GETSET(units);
public:
static json_map::storage_type<Lv2PluginUiControlPort> jmap;
};
class Lv2PluginUiPortGroup {
private:
std::string symbol_;
std::string name_;
public:
Lv2PluginUiPortGroup() { }
Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup)
: symbol_(pPortGroup->symbol())
, name_(pPortGroup->name())
{
}
public:
static json_map::storage_type<Lv2PluginUiPortGroup> jmap;
};
class Lv2PluginUiInfo {
public:
Lv2PluginUiInfo() { }
Lv2PluginUiInfo(Lv2Host *pPlugins,const Lv2PluginInfo *plugin);
private:
std::string uri_;
std::string name_;
std::string author_name_;
std::string author_homepage_;
PluginType plugin_type_;
std::string plugin_display_type_;
int audio_inputs_ = 0;
int audio_outputs_ = 0;
int has_midi_input_ = false;
int has_midi_output_ = false;
std::string description_;
std::vector<Lv2PluginUiControlPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_;
public:
LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name)
LV2_PROPERTY_GETSET(author_name)
LV2_PROPERTY_GETSET(author_homepage)
LV2_PROPERTY_GETSET_SCALAR(plugin_type)
LV2_PROPERTY_GETSET(plugin_display_type)
LV2_PROPERTY_GETSET_SCALAR(audio_inputs)
LV2_PROPERTY_GETSET_SCALAR(audio_outputs)
LV2_PROPERTY_GETSET_SCALAR(has_midi_input)
LV2_PROPERTY_GETSET_SCALAR(has_midi_output)
LV2_PROPERTY_GETSET_SCALAR(description)
LV2_PROPERTY_GETSET(port_groups)
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 Lv2Effect*CreateEffect(const PedalBoardItem &pedalBoard) = 0;
};
class Lv2Host: private IHost {
private:
size_t maxBufferSize = 1024;
size_t maxAtomBufferSize = 16*1024;
bool hasMidiInputChannel;
int numberOfAudioInputChannels = 2;
int numberOfAudioOutputChannels = 2;
double sampleRate = 0;
LV2_Feature*const*lv2Features = nullptr;
MapFeature mapFeature;
LogFeature logFeature;
OptionsFeature optionsFeature;
static void fn_LilvSetPortValueFunc(const char* port_symbol,
void* user_data,
const void* value,
uint32_t size,
uint32_t type);
LilvWorld *pWorld;
void free_world();
std::vector<std::shared_ptr<Lv2PluginInfo> > plugins_;
std::vector<Lv2PluginUiInfo> ui_plugins_;
std::map<std::string,std::shared_ptr<Lv2PluginClass> > classesMap;
friend class Lv2PluginInfo;
friend class Lv2PortInfo;
friend class Lv2PortGroup;
std::shared_ptr<Lv2PluginClass> GetPluginClass(const LilvPluginClass*pClass);
std::shared_ptr<Lv2PluginClass> MakePluginClass(const LilvPluginClass*pClass);
Lv2PluginClasses GetPluginPortClass(const LilvPlugin*lilvPlugin,const LilvPort *lilvPort);
bool classesLoaded = false;
private:
// IHost implementation
virtual LilvWorld*getWorld() {
return pWorld;
}
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_URID_Map* GetLv2UridMap() {
return this->mapFeature.GetMap();
}
virtual Lv2Effect*CreateEffect(const PedalBoardItem &pedalBoardItem);
void LoadPluginClassesFromLilv();
void AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass);
public:
Lv2Host();
virtual ~Lv2Host();
IHost* asIHost() { return this;}
virtual Lv2PedalBoard * CreateLv2PedalBoard(const PedalBoard& pedalBoard);
void setSampleRate(double sampleRate)
{
this->sampleRate = sampleRate;
}
double GetSampleRate() const {
return sampleRate;
}
class Urids;
Urids *urids;
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings);
std::shared_ptr<Lv2PluginClass> GetPluginClass(const std::string&uri) const;
bool is_a(const std::string&class_, const std::string & target_class);
std::shared_ptr<Lv2PluginClass> GetLv2PluginClass() const;
std::vector<std::shared_ptr<Lv2PluginInfo> > GetPlugins() const { return plugins_; }
const std::vector<Lv2PluginUiInfo> &GetUiPlugins() const { return ui_plugins_; }
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string&uri) const;
static constexpr const char * DEFAULT_LV2_PATH="/usr/lib/lv2";
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
void Load(const char*lv2Path = Lv2Host::DEFAULT_LV2_PATH);
virtual LV2_URID GetLv2Urid(const char*uri) {
return this->mapFeature.GetUrid(uri);
}
virtual std::string Lv2UriudToString(LV2_URID urid) {
return this->mapFeature.UridToString(urid);
}
std::vector<Lv2PluginPreset> GetPluginPresets(const std::string &pluginUri);
std::vector<ControlValue> LoadPluginPreset(PedalBoardItem*pedalBoardItem, const std::string&presetUri);
};
#undef LV2_PROPERTY_GETSET
} // namespace pipedal.
+95
View File
@@ -0,0 +1,95 @@
#include "pch.h"
#include "Lv2Log.hpp"
#include <iostream>
#include <chrono>
#include <iomanip>
using namespace pipedal;
#include <mutex>
static auto timeZero = std::chrono::system_clock::now();
std::string timeTag()
{
using namespace std::chrono;
auto t = std::chrono::system_clock::now()- timeZero;
auto hours_ = duration_cast<hours>(t).count() % 24;
auto minutes_ = duration_cast<minutes>(t).count() % 60;
auto seconds_ = duration_cast<seconds>(t).count() % 60;
auto milliseconds_ = duration_cast<milliseconds>(t).count() % 1000;
std::stringstream s;
using namespace std;
s << setfill('0') << setw(2) << hours_ << ':' << setw(2) << minutes_ << ':' << setw(2) << seconds_ << "." << setw(3) << milliseconds_;
return s.str();
}
class StdErrLogger : public Lv2Logger
{
std::mutex m;
public : virtual ~StdErrLogger() {}
virtual void onError(const char* message)
{
std::lock_guard lock(m);
std::cerr << timeTag() << " ERR: " << message << std::endl;
}
virtual void onWarning(const char* message)
{
std::lock_guard lock(m);
std::cerr << timeTag() << " WRN: " << message << std::endl;
}
virtual void onDebug(const char* message)
{
std::lock_guard lock(m);
std::cerr << timeTag() << " DBG: " << message << std::endl;
}
virtual void onInfo(const char* message)
{
std::lock_guard lock(m);
std::cerr << timeTag() << " INF: " << message << std::endl;
}
};
Lv2Logger *Lv2Log::logger_ = new StdErrLogger();
LogLevel Lv2Log::log_level_ = LogLevel::Debug;
void Lv2Log::v_error(const char *format, va_list arglist)
{
char buffer[512];
vsnprintf(buffer,sizeof(buffer),format,arglist);
logger_->onError(buffer);
}
void Lv2Log::v_warning(const char *format, va_list arglist)
{
char buffer[512];
vsnprintf(buffer,sizeof(buffer),format,arglist);
logger_->onWarning(buffer);
}
void Lv2Log::v_debug(const char *format, va_list arglist)
{
char buffer[512];
vsnprintf(buffer,sizeof(buffer),format,arglist);
logger_->onDebug(buffer);
}
void Lv2Log::v_info(const char *format, va_list arglist)
{
char buffer[512];
vsnprintf(buffer,sizeof(buffer),format,arglist);
logger_->onInfo(buffer);
}
+139
View File
@@ -0,0 +1,139 @@
#pragma once
#include <stdarg.h>
#include <chrono>
namespace pipedal
{
class Lv2Logger
{
public:
virtual ~Lv2Logger() = default;
virtual void onError(const char *message) = 0;
virtual void onWarning(const char *message) = 0;
virtual void onDebug(const char *message) = 0;
virtual void onInfo(const char *message) = 0;
};
enum class LogLevel
{
None = 0,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4
};
class Lv2Log
{
private:
static Lv2Logger *logger_;
static LogLevel log_level_;
static void v_error(const char *format, va_list arglist);
static void v_warning(const char *format, va_list arglist);
static void v_debug(const char *format, va_list arglist);
static void v_info(const char *format, va_list arglist);
public:
static void set_logger(Lv2Logger *logger)
{
Lv2Log::logger_ = logger;
}
static void log_level(LogLevel level)
{
Lv2Log::log_level_ = level;
}
static LogLevel log_level()
{
return Lv2Log::log_level_;
}
// static void error(const char* message) {
// if (Lv2Log::log_level_ >= LogLevel::Error)
// {
// logger_->onError(message);
// }
// }
// static void warning(const char* message) {
// if (Lv2Log::log_level_ >= LogLevel::Warning)
// {
// logger_->onWarning(message);
// }
// }
// static void debug(const char* message) {
// if (Lv2Log::log_level_ >= LogLevel::Debug)
// {
// logger_->onDebug(message);
// }
// }
// static void info(const char* message) {
// if (Lv2Log::log_level_ >= LogLevel::Info)
// {
// logger_->onInfo(message);
// }
// }
static void info(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Info)
{
va_list arglist;
va_start(arglist, format);
v_info(format, arglist);
va_end(arglist);
}
}
static void info(const std::string &str)
{
info("%s", str.c_str());
}
static void debug(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Debug)
{
va_list arglist;
va_start(arglist, format);
v_debug(format, arglist);
va_end(arglist);
}
}
static void debug(const std::string &str)
{
debug("%s", str.c_str());
}
static void warning(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Warning)
{
va_list arglist;
va_start(arglist, format);
v_warning(format, arglist);
va_end(arglist);
}
}
static void warning(const std::string &str)
{
warning("%s", str.c_str());
}
static void error(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Warning)
{
va_list arglist;
va_start(arglist, format);
v_error(format, arglist);
va_end(arglist);
}
}
static void error(const std::string &str)
{
error("%s", str.c_str());
}
};
} // namespace pipedal
+584
View File
@@ -0,0 +1,584 @@
#include "pch.h"
#include "Lv2PedalBoard.hpp"
#include "Lv2Effect.hpp"
#include "SplitEffect.hpp"
#include "RingBufferReader.hpp"
#include "VuUpdate.hpp"
#include "JackHost.hpp"
#include "Lv2EventBufferWriter.hpp"
using namespace pipedal;
float *Lv2PedalBoard::CreateNewAudioBuffer()
{
return bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
}
std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
{
std::vector<float *> result;
for (int i = 0; i < nChannels; ++i)
{
result.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
return result;
}
int Lv2PedalBoard::GetControlIndex(long instanceId, const std::string &symbol)
{
for (int i = 0; i < realtimeEffects.size(); ++i)
{
auto item = realtimeEffects[i];
if (item->GetInstanceId() == instanceId)
{
return item->GetControlIndex(symbol);
}
}
return -1;
}
std::vector<float *> Lv2PedalBoard::PrepareItems(
const std::vector<PedalBoardItem> &items,
std::vector<float *> inputBuffers)
{
for (int i = 0; i < items.size(); ++i)
{
auto &item = items[i];
if (!item.isEmpty())
{
IEffect *pEffect = nullptr;
if (item.isSplit())
{
auto pSplit = new SplitEffect(item.instanceId(), pHost->GetSampleRate(), inputBuffers);
pEffect = pSplit;
int topInputChannels = inputBuffers.size();
int bottomInputChannels = inputBuffers.size();
std::vector<float *> topInputs = AllocateAudioBuffers(topInputChannels);
std::vector<float *> bottomInputs = AllocateAudioBuffers(bottomInputChannels);
auto preMixAction = [pSplit](uint32_t frames)
{ pSplit->PreMix(frames); };
this->processActions.push_back(preMixAction);
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs);
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs);
this->processActions.push_back(
[pSplit](uint32_t frames)
{ pSplit->PostMix(frames); });
pSplit->SetChainBuffers(topInputs, bottomInputs, topResult, bottomResult);
for (int i = 0; i < item.controlValues().size(); ++i)
{
auto &controlValue = item.controlValues()[i];
int index = pSplit->GetControlIndex(controlValue.key());
if (index != -1)
{
pSplit->SetControl(index, controlValue.value());
}
}
}
else
{
auto pLv2Effect = this->pHost->CreateEffect(item);
if (pLv2Effect)
{
pEffect = pLv2Effect;
long instanceId = pEffect->GetInstanceId();
if (inputBuffers.size() == 1)
{
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
{
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
}
else
{
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[0]);
}
}
else
{
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
{
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
auto inputBuffer = inputBuffers[0];
}
else
{
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[1]);
auto bufferL = inputBuffers[0];
auto bufferR = inputBuffers[1];
}
}
this->processActions.push_back(
[pLv2Effect](uint32_t frames)
{
pLv2Effect->Run(frames);
});
}
}
if (pEffect)
{
this->effects.push_back(std::shared_ptr<IEffect>(pEffect)); // for ownership.
this->realtimeEffects.push_back(pEffect); // because std::shared_ptr is not threadsafe.
std::vector<float *> effectOutput;
#ifdef RECYCLE_AUDIO_BUFFERS
// can't do this anymore if we're going to do pop-less bypbass.
if (pEffect->GetNumberOfOutputAudioPorts() == 1)
{
float *pLeft = inputBuffers[0];
effectOutput.push_back(pLeft);
}
else
{
if (inputBuffers.size() == 1)
{
effectOutput.push_back(inputBuffers[0]);
effectOutput.push_back(CreateNewAudioBuffer());
}
else
{
effectOutput.push_back(inputBuffers[0]);
effectOutput.push_back(inputBuffers[1]);
}
}
#else
if (pEffect->GetNumberOfOutputAudioPorts() == 1)
{
effectOutput.push_back(CreateNewAudioBuffer());
}
else
{
effectOutput.push_back(CreateNewAudioBuffer());
effectOutput.push_back(CreateNewAudioBuffer());
}
#endif
for (size_t i = 0; i < effectOutput.size(); ++i)
{
pEffect->SetAudioOutputBuffer(i, effectOutput[i]);
}
inputBuffers = effectOutput;
}
}
}
return inputBuffers;
}
void Lv2PedalBoard::Prepare(IHost *pHost, const PedalBoard &pedalBoard)
{
this->pHost = pHost;
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
{
this->pedalBoardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
auto outputs = PrepareItems(pedalBoard.items(), this->pedalBoardInputBuffers);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
if (nOutputs == 1)
{
this->pedalBoardOutputBuffers.push_back(outputs[0]);
}
else
{
if (outputs.size() == 1)
{
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]);
}
}
PrepareMidiMap(pedalBoard);
}
void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
{
if (pedalBoardItem.midiBindings().size() != 0)
{
auto pluginInfo = pHost->GetPluginInfo(pedalBoardItem.uri());
const Lv2PluginInfo *pPluginInfo;
if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
{
pPluginInfo = GetSplitterPluginInfo();
} else {
pPluginInfo = pluginInfo.get();
}
int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId());
if (pluginInfo && effectIndex != -1)
{
for (size_t bindingIndex = 0; bindingIndex < pedalBoardItem.midiBindings().size(); ++bindingIndex)
{
auto &binding = pedalBoardItem.midiBindings()[bindingIndex];
{
const Lv2PortInfo*pPortInfo;
int controlIndex;
if (binding.symbol() == "__bypass")
{
pPortInfo = GetBypassPortInfo();
controlIndex = -1;
} else {
try {
pPortInfo = &pluginInfo->getPort(binding.symbol());
controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol());
} catch (const std::exception&ignored)
{
continue;
}
}
MidiMapping mapping;
mapping.pluginInfo = pluginInfo; // for lifetime management. <shrugs> We're holding internal pointers to this. May save us in a disorderly shutdown.
mapping.pPortInfo = pPortInfo;
mapping.effectIndex = effectIndex;
mapping.controlIndex = controlIndex;
mapping.midiBinding = binding;
mapping.instanceId = pedalBoardItem.instanceId();
if (pPortInfo->IsSwitch())
{
mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary;
}
else
{
mapping.mappingType = binding.linearControlType() == LATCH_CONTROL_TYPE ? MappingType::Linear : MappingType::Circular;
}
if (binding.bindingType() == BINDING_TYPE_NOTE)
{
mapping.key = 0x9000 | binding.note(); // i.e. midi note on.
}
else if (binding.bindingType() == BINDING_TYPE_CONTROL)
{
mapping.key = 0xB000 | binding.control(); // i.e. midi control
}
else
{
mapping.key = -1;
}
if (mapping.key != -1)
{
midiMappings.push_back(std::move(mapping));
}
}
}
}
}
for (size_t i = 0; i < pedalBoardItem.topChain().size(); ++i)
{
PrepareMidiMap(pedalBoardItem.topChain()[i]);
}
for (size_t i = 0; i < pedalBoardItem.bottomChain().size(); ++i)
{
PrepareMidiMap(pedalBoardItem.bottomChain()[i]);
}
}
void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
{
for (size_t i = 0; i < pedalBoard.items().size(); ++i)
{
auto &item = pedalBoard.items()[i];
PrepareMidiMap(item);
auto pluginInfo = pHost->GetPluginInfo(item.uri());
if (pluginInfo)
{
for (size_t bindingIndex = 0; bindingIndex < item.midiBindings().size(); ++bindingIndex)
{
auto &binding = item.midiBindings()[i];
{
}
}
}
std::sort(this->midiMappings.begin(), this->midiMappings.end(),
[](const MidiMapping &left, const MidiMapping &right)
{ return left.key < right.key; });
}
}
void Lv2PedalBoard::Activate()
{
for (int i = 0; i < this->effects.size(); ++i)
{
this->realtimeEffects[i]->Activate();
}
}
void Lv2PedalBoard::Deactivate()
{
for (int i = 0; i < this->effects.size(); ++i)
{
this->realtimeEffects[i]->Deactivate();
}
}
static void Copy(float *input, float *output, uint32_t samples)
{
for (uint32_t i = 0; i < samples; ++i)
{
output[i] = input[i];
}
}
bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples)
{
for (int i = 0; i < this->pedalBoardInputBuffers.size(); ++i)
{
if (inputBuffers[i] == nullptr)
return false;
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)
{
if (outputBuffers[i] == nullptr)
return false;
Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples);
}
return true;
}
float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex)
{
auto effect = realtimeEffects[effectIndex];
return effect->GetOutputControlValue(portIndex);
}
void Lv2PedalBoard::SetControlValue(int effectIndex, int index, float value)
{
auto effect = realtimeEffects[effectIndex];
effect->SetControl(index, value);
}
void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled)
{
auto effect = realtimeEffects[effectIndex];
effect->SetBypass(enabled);
}
void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
{
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{
int index = vuConfiguration->enabledIndexes[i];
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
auto effect = this->realtimeEffects[index];
if (effect->GetNumberOfInputAudioPorts() == 1)
{
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
}
else
{
pUpdate->AccumulateInputs(
effect->GetAudioInputBuffer(0),
effect->GetAudioInputBuffer(1), samples);
}
if (effect->GetNumberOfOutputAudioPorts() == 1)
{
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
}
else
{
pUpdate->AccumulateOutputs(
effect->GetAudioOutputBuffer(0),
effect->GetAudioOutputBuffer(1),
samples);
}
}
}
void Lv2PedalBoard::ResetAtomBuffers()
{
for (size_t i = 0; i < this->effects.size(); ++i)
{
auto effect = this->effects[i];
effect->ResetAtomBuffers();
}
}
void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests)
{
while (pParameterRequests != nullptr)
{
IEffect *pEffect = this->GetEffect(pParameterRequests->instanceId);
if (pEffect == nullptr)
{
pParameterRequests->errorMessage = "No such effect.";
}
else
{
pEffect->RequestParameter(pParameterRequests->uridUri);
}
pParameterRequests = pParameterRequests->pNext;
}
}
void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests)
{
while (pParameterRequests != nullptr)
{
IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
if (effect == nullptr)
{
pParameterRequests->errorMessage = "No such effect.";
}
else
{
effect->GatherParameter(pParameterRequests);
}
pParameterRequests = pParameterRequests->pNext;
}
}
void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message,
void *callbackHandle,
MidiCallbackFn *pfnCallback)
{
if (midiMappings.size() == 0)
return;
if (size < 2)
return;
uint8_t cmd = message[0];
uint8_t channel = cmd & 0x0F;
cmd &= 0xF0;
uint8_t value;
uint8_t index;
if (cmd == 0x80) // note off.
{
index = message[1];
cmd = 0x90;
index = message[1];
value = 0;
}
else if (cmd == 0x90) // note on.
{
if (size < 3)
return;
index = message[1];
value = 127;
}
else if (cmd == 0xB0) // midi control.
{
if (size < 3)
return;
index = message[1];
value = message[2] & 0x7F;
}
int searchKey = (cmd << 8) | index;
int min = 0;
int max = midiMappings.size() - 1;
while (max > min)
{
int mid = (min + max) / 2;
if (midiMappings[mid].key < searchKey)
{
min = mid + 1;
}
else if (midiMappings[mid].key > searchKey)
{
max = mid - 1;
}
else
{
if (mid == 0)
{
min = max = mid;
}
else
{
if (midiMappings[mid - 1].key == searchKey)
{
max = mid - 1;
}
else
{
min = max = mid;
}
}
}
}
if (midiMappings[min].key == searchKey)
{
float range = value / 127.0;
for (int i = min; i < midiMappings.size(); ++i)
{
auto &mapping = midiMappings[i];
if (mapping.key != searchKey)
break;
if (mapping.midiBinding.channel() == -1 || mapping.midiBinding.channel() == channel)
{
switch (mapping.mappingType)
{
case MappingType::Circular:
case MappingType::Linear:
{
float thisRange = (mapping.midiBinding.maxValue() - mapping.midiBinding.minValue()) * range + mapping.midiBinding.minValue();
float value = mapping.pPortInfo->rangeToValue(thisRange);
this->SetControlValue(mapping.effectIndex, mapping.controlIndex, value);
pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), value);
break;
}
case MappingType::Latched:
{
range = std::round(range);
if (!mapping.hasLastValue)
{
mapping.lastValue = 0;
mapping.hasLastValue = true;
}
if (range != mapping.lastValue && range == 1)
{
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
float currentValue = pEffect->GetControlValue(mapping.controlIndex);
currentValue = currentValue == 0 ? 1 : 0;
pEffect->SetControl(mapping.controlIndex, currentValue);
pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), currentValue);
}
mapping.lastValue = range;
break;
}
case MappingType::Momentary:
{
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
float value = mapping.pPortInfo->rangeToValue(range);
if (pEffect->GetControlValue(mapping.controlIndex) != value)
{
this->SetControlValue(mapping.effectIndex, mapping.controlIndex, value);
pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), value);
}
break;
}
}
}
}
}
}
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include "PedalBoard.hpp"
#include "Lv2Host.hpp"
#include "Lv2Effect.hpp"
#include "BufferPool.hpp"
#include <functional>
#include <lv2/urid/urid.h>
#include <functional>
namespace pipedal {
class RealtimeVuBuffers;
class RealtimeParameterRequest;
class Lv2PedalBoard {
IHost *pHost = nullptr;
BufferPool bufferPool;
std::vector<float*> pedalBoardInputBuffers;
std::vector<float*> pedalBoardOutputBuffers;
std::vector<std::shared_ptr<IEffect> > effects;
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
using Action = std::function<void()>;
using ProcessAction = std::function<void(uint32_t frames)>;
std::vector<Action> activateActions;
std::vector<ProcessAction> processActions;
std::vector<Action> deactivateActions;
float *CreateNewAudioBuffer();
enum class MappingType {
Linear,
Circular,
Momentary,
Latched,
};
class MidiMapping {
public:
std::shared_ptr<Lv2PluginInfo> pluginInfo; // lifecycle
const Lv2PortInfo *pPortInfo = nullptr; // owned by port.
int instanceId = -1;
int effectIndex = -1;
int controlIndex = -1;
int key; // key to the note or control. internal use only.
bool hasLastValue = false;
float lastValue = -1;
MappingType mappingType;
MidiBinding midiBinding;
};
std::vector<MidiMapping> midiMappings;
std::vector<float*> PrepareItems(
const std::vector<PedalBoardItem> & items,
std::vector<float*> inputBuffers
);
void PrepareMidiMap(const PedalBoard&pedalBoard);
void PrepareMidiMap(const PedalBoardItem&pedalBoardItem);
std::vector<float*> AllocateAudioBuffers(int nChannels);
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalBoardItem> &items);
void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
public:
Lv2PedalBoard() { }
~Lv2PedalBoard() { }
void Prepare(IHost *pHost,const PedalBoard&pedalBoard);
int GetIndexOfInstanceId(long instanceId) {
for (int i = 0; i < this->realtimeEffects.size(); ++i)
{
if (this->realtimeEffects[i]->GetInstanceId() == instanceId) return i;
}
return -1;
}
IEffect*GetEffect(long instanceId) {
for (int i = 0; i < realtimeEffects.size(); ++i) {
if (realtimeEffects[i]->GetInstanceId() == instanceId)
{
return realtimeEffects[i];
}
}
return nullptr;
}
void Activate();
void Deactivate();
bool Run(float**inputBuffers, float**outputBuffers,uint32_t samples);
void ResetAtomBuffers();
void ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests);
void GatherParameterRequests(RealtimeParameterRequest *pParameterRequests);
std::vector<float*> &GetInputBuffers() { return this->pedalBoardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalBoardOutputBuffers;}
int GetControlIndex(long instanceId,const std::string&symbol);
void SetControlValue(int effectIndex, int portIndex, float value);
void SetBypass(int effectIndex,bool enabled);
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples);
float GetControlOutputValue(int effectIndex, int portIndex);
typedef void (MidiCallbackFn) (void* data,uint64_t intanceId, int controlIndex, float value);
void OnMidiMessage(size_t size, uint8_t*data,
void *callbackHandle,
MidiCallbackFn*pfnCallback);
};
} // namespace
+35
View File
@@ -0,0 +1,35 @@
#include "pch.h"
#include "Lv2SystemdLogger.hpp"
#include <systemd/sd-journal.h>
using namespace pipedal;
class Lv2SystemdLogger: public Lv2Logger {
public:
virtual ~Lv2SystemdLogger() { }
virtual void onError(const char* message)
{
sd_journal_print(LOG_ERR,"%s",message);
}
virtual void onWarning(const char* message)
{
sd_journal_print(LOG_WARNING,"%s",message);
}
virtual void onDebug(const char* message)
{
sd_journal_print(LOG_DEBUG,"%s",message);
}
virtual void onInfo(const char* message)
{
sd_journal_print(LOG_INFO,"%s",message);
}
};
Lv2Logger *pipedal::MakeLv2SystemdLogger()
{
return new Lv2SystemdLogger();
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "Lv2Log.hpp"
namespace pipedal {
Lv2Logger *MakeLv2SystemdLogger();
} // namespace
+63
View File
@@ -0,0 +1,63 @@
#include "MapFeature.hpp"
#include <mutex>
using namespace pipedal;
static LV2_URID mapFn(LV2_URID_Map_Handle handle, const char* uri)
{
MapFeature* feature = (MapFeature*)(void*)handle;
return feature->GetUrid(uri);
}
static const char* unmapFn(LV2_URID_Map_Handle handle, LV2_URID urid)
{
MapFeature* feature = (MapFeature*)(void*)handle;
return feature->UridToString(urid);
}
MapFeature::MapFeature()
{
mapFeature.URI = LV2_URID__map;
mapFeature.data = &map;
map.handle = (void*)this;
map.map = &mapFn;
unmapFeature.URI = LV2_URID__unmap;
unmapFeature.data = &unmap;
unmap.handle = (void*)this;
unmap.unmap = &unmapFn;
}
MapFeature::~MapFeature()
{
for (auto i = stdUnmap.begin(); i != stdUnmap.end(); ++i)
{
delete i->second;
}
}
LV2_URID MapFeature::GetUrid(const char* uri)
{
std::lock_guard<std::mutex> guard(mapMutex);
LV2_URID result = stdMap[uri];
if (result == 0)
{
stdMap[uri] = ++nextAtom;
result = nextAtom;
std::string *stringRef = new std::string(uri);
stdUnmap[result] = stringRef;
}
return result;
}
const char*MapFeature::UridToString(LV2_URID urid)
{
std::lock_guard<std::mutex> guard(mapMutex);
std::string*pResult = stdUnmap[urid];
if (pResult == nullptr) return nullptr;
return pResult->c_str();
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include "lv2/core/lv2.h"
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "lv2/atom/atom.h"
#include <map>
#include <string>
#include <mutex>
namespace pipedal {
class MapFeature {
private:
LV2_URID nextAtom = 0;
LV2_Feature mapFeature;
LV2_Feature unmapFeature;
LV2_URID_Map map;
LV2_URID_Unmap unmap;
std::map<std::string, LV2_URID> stdMap;
std::map<LV2_URID, std::string*> stdUnmap;
std::mutex mapMutex;
public:
MapFeature();
~MapFeature();
public:
const LV2_Feature* GetMapFeature()
{
return &mapFeature;
}
const LV2_Feature* GetUnmapFeature()
{
return &unmapFeature;
}
LV2_URID GetUrid(const char* uri);
const char*UridToString(LV2_URID urid);
const LV2_URID_Map *GetMap() const { return &map;}
LV2_URID_Map *GetMap() { return &map;}
};
}
+19
View File
@@ -0,0 +1,19 @@
#include "pch.h"
#include "MidiBinding.hpp"
using namespace pipedal;
JSON_MAP_BEGIN(MidiBinding)
JSON_MAP_REFERENCE(MidiBinding,channel)
JSON_MAP_REFERENCE(MidiBinding,symbol)
JSON_MAP_REFERENCE(MidiBinding,bindingType)
JSON_MAP_REFERENCE(MidiBinding,note)
JSON_MAP_REFERENCE(MidiBinding,control)
JSON_MAP_REFERENCE(MidiBinding,minValue)
JSON_MAP_REFERENCE(MidiBinding,maxValue)
JSON_MAP_REFERENCE(MidiBinding,rotaryScale)
JSON_MAP_REFERENCE(MidiBinding,linearControlType)
JSON_MAP_REFERENCE(MidiBinding,switchControlType)
JSON_MAP_END()
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include "json.hpp"
namespace pipedal {
#define GETTER_SETTER_REF(name) \
const decltype(name##_)& name() const { return name##_;} \
void name(const decltype(name##_) &value) { name##_ = value; }
#define GETTER_SETTER_VEC(name) \
decltype(name##_)& name() { return name##_;} \
const decltype(name##_)& name() const { return name##_;}
#define GETTER_SETTER(name) \
decltype(name##_) name() const { return name##_;} \
void name(decltype(name##_) value) { name##_ = value; }
const int BINDING_TYPE_NONE = 0;
const int BINDING_TYPE_NOTE = 1;
const int BINDING_TYPE_CONTROL = 2;
const int LINEAR_CONTROL_TYPE = 0;
const int CIRCULAR_CONTROL_TYPE = 1;
const int LATCH_CONTROL_TYPE = 0;
const int MOMENTARY_CONTROL_TYPE = 1;
class MidiBinding {
public:
private:
std::string symbol_;
int channel_ = -1;
int bindingType_;
int note_ = 12*4+24;
int control_ = 1;
float minValue_ = 0;
float maxValue_ = 1;
float rotaryScale_ = 1;
int linearControlType_ = 0;
int switchControlType_ = 0;
public:
GETTER_SETTER(channel);
GETTER_SETTER_REF(symbol);
GETTER_SETTER(bindingType);
GETTER_SETTER(note);
GETTER_SETTER(control);
GETTER_SETTER(minValue);
GETTER_SETTER(maxValue);
GETTER_SETTER(rotaryScale);
GETTER_SETTER(linearControlType);
GETTER_SETTER(switchControlType);
DECLARE_JSON_MAP(MidiBinding);
};
#undef GETTER_SETTER_REF
#undef GETTER_SETTER_VEC
#undef GETTER_SETTER
} // namespace
+67
View File
@@ -0,0 +1,67 @@
#include "pch.h"
#include "OptionsFeature.hpp"
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
using namespace pipedal;
OptionsFeature::OptionsFeature()
{
feature.URI = LV2_OPTIONS__options;
feature.data = options;
}
OptionsFeature::~OptionsFeature()
{
}
void OptionsFeature::Prepare(MapFeature&map,double sampleRate, int32_t blockLength, int32_t atomBufferBlockLength)
{
this->sampleRate = (float)sampleRate;
this->blockLength = blockLength;
this->atomBufferBlockLength = atomBufferBlockLength;
options[0].context = LV2_OPTIONS_INSTANCE;
options[0].subject = 0;
options[0].key = map.GetUrid(LV2_PARAMETERS__sampleRate);
options[0].size = sizeof(float);
options[0].type = map.GetUrid(LV2_ATOM__Float);
options[0].value = &(this->sampleRate);
options[1].context = LV2_OPTIONS_INSTANCE;
options[1].subject = 0;
options[1].key = map.GetUrid(LV2_BUF_SIZE__minBlockLength);
options[1].size = sizeof(int32_t);
options[1].type = map.GetUrid(LV2_ATOM__Int);
options[1].value = &(this->blockLength);
options[2].context = LV2_OPTIONS_INSTANCE;
options[2].subject = 0;
options[2].key = map.GetUrid(LV2_BUF_SIZE__maxBlockLength);
options[2].size = sizeof(int32_t);
options[2].type = map.GetUrid(LV2_ATOM__Int);
options[2].value = &(this->blockLength);
options[3].context = LV2_OPTIONS_INSTANCE;
options[3].subject = 0;
options[3].key = map.GetUrid(LV2_BUF_SIZE__nominalBlockLength);
options[3].size = sizeof(int32_t);
options[3].type = map.GetUrid(LV2_ATOM__Int);
options[3].value = &(this->blockLength);
options[4].context = LV2_OPTIONS_INSTANCE;
options[4].subject = 0;
options[4].key = map.GetUrid(LV2_BUF_SIZE__sequenceSize);
options[4].size = sizeof(int32_t);
options[4].type = map.GetUrid(LV2_ATOM__Int);
options[4].value = &(this->atomBufferBlockLength);;
options[5].context = LV2_OPTIONS_INSTANCE;
options[5].subject = 0;
options[5].key = 0;
options[5].size = 0;
options[5].type = 0;
options[5].value = NULL;
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "MapFeature.hpp"
#include "lv2/lv2plug.in/ns/ext/options/options.h"
namespace pipedal {
class OptionsFeature {
private:
float sampleRate;
int32_t blockLength;
int32_t atomBufferBlockLength;
LV2_Feature feature;
LV2_Options_Option options[6];
public:
OptionsFeature();
void Prepare(MapFeature&map,
double sampleRate,
int32_t blockLength,
int32_t atomBufferBlockLength);
~OptionsFeature();
public:
const LV2_Feature* GetFeature()
{
return &feature;
}
};
}
+183
View File
@@ -0,0 +1,183 @@
#include "pch.h"
#include "PedalBoard.hpp"
using namespace pipedal;
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)
{
return &(items[i]);
}
if (item.isSplit())
{
const PedalBoardItem* t = GetItem_(item.topChain(),pedalBoardItemId);
if (t != nullptr) return t;
t = GetItem_(item.bottomChain(),pedalBoardItemId);
if (t != nullptr) return t;
}
}
return nullptr;
}
const PedalBoardItem*PedalBoard::GetItem(long pedalItemId) const
{
return GetItem_(this->items(),pedalItemId);
}
PedalBoardItem*PedalBoard::GetItem(long pedalItemId)
{
return const_cast<PedalBoardItem*>(GetItem_(this->items(),pedalItemId));
}
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
{
for (size_t i = 0; i < this->controlValues().size(); ++i)
{
if (this->controlValues()[i].key() == symbol)
{
return &(this->controlValues()[i]);
}
}
return nullptr;
}
bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled)
{
PedalBoardItem*item = GetItem(pedalItemId);
if (!item) return false;
if (item->isEnabled() != enabled)
{
item->isEnabled(enabled);
return true;
}
return false;
}
bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, float value)
{
PedalBoardItem*item = GetItem(pedalItemId);
if (!item) return false;
ControlValue*controlValue = item->GetControlValue(symbol);
if (controlValue == nullptr) return false;
if (controlValue->value() != value)
{
controlValue->value(value);
return true;
}
return false;
}
PedalBoardItem PedalBoard::MakeEmptyItem()
{
long instanceId = NextInstanceId();
PedalBoardItem result;
result.instanceId(instanceId);
result.uri(EMPTY_PEDALBOARD_ITEM_URI);
result.pluginName("");
result.isEnabled(true);
return result;
}
PedalBoardItem PedalBoard::MakeSplit()
{
long instanceId = NextInstanceId();
PedalBoardItem result;
result.instanceId(instanceId);
result.uri(SPLIT_PEDALBOARD_ITEM_URI);
result.pluginName("");
result.isEnabled(true);
result.topChain().push_back(MakeEmptyItem());
result.bottomChain().push_back(MakeEmptyItem());
result.controlValues().push_back(
ControlValue(SPLIT_SPLITTYPE_KEY,0));
result.controlValues().push_back(
ControlValue(SPLIT_SELECT_KEY,0));
result.controlValues().push_back(
ControlValue(SPLIT_MIX_KEY,0));
result.controlValues().push_back(
ControlValue(SPLIT_PANL_KEY,0));
result.controlValues().push_back(
ControlValue(SPLIT_VOLL_KEY,-3));
result.controlValues().push_back(
ControlValue(SPLIT_PANR_KEY,0));
result.controlValues().push_back(
ControlValue(SPLIT_VOLR_KEY,-3));
return result;
}
PedalBoard PedalBoard::MakeDefault()
{
// copy insanity. but it happens so rarely.
PedalBoard result;
auto split = result.MakeSplit();
split.topChain().push_back(result.MakeEmptyItem());
split.bottomChain().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(split);
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.items().push_back(result.MakeEmptyItem());
result.name("Default Preset");
return result;
}
JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,key)
JSON_MAP_REFERENCE(ControlValue,value)
JSON_MAP_END()
bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector<PedalBoardItem>&value)
{
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
}
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_END()
JSON_MAP_BEGIN(PedalBoard)
JSON_MAP_REFERENCE(PedalBoard,name)
JSON_MAP_REFERENCE(PedalBoard,items)
JSON_MAP_REFERENCE(PedalBoard,nextInstanceId)
JSON_MAP_END()
+137
View File
@@ -0,0 +1,137 @@
#pragma once
#include "json.hpp"
#include "MidiBinding.hpp"
namespace pipedal {
#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split"
#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty"
#define SPLIT_SPLITTYPE_KEY "splitType"
#define SPLIT_SELECT_KEY "select"
#define SPLIT_MIX_KEY "mix"
#define SPLIT_PANL_KEY "panL"
#define SPLIT_VOLL_KEY "volL"
#define SPLIT_PANR_KEY "panR"
#define SPLIT_VOLR_KEY "volR"
#define GETTER_SETTER_REF(name) \
const decltype(name##_)& name() const { return name##_;} \
void name(const decltype(name##_) &value) { name##_ = value; }
#define GETTER_SETTER_VEC(name) \
decltype(name##_)& name() { return name##_;} \
const decltype(name##_)& name() const { return name##_;}
#define GETTER_SETTER(name) \
decltype(name##_) name() const { return name##_;} \
void name(decltype(name##_) value) { name##_ = value; }
class ControlValue {
private:
std::string key_;
float value_;
public:
ControlValue()
{
}
ControlValue(const char*key, float value)
:key_(key)
, value_(value)
{
}
GETTER_SETTER_REF(key)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(ControlValue);
};
class PedalBoardItem: public JsonWritable {
long instanceId_ = 0;
std::string uri_;
std::string pluginName_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
std::vector<PedalBoardItem> topChain_;
std::vector<PedalBoardItem> bottomChain_;
std::vector<MidiBinding> midiBindings_;
public:
ControlValue*GetControlValue(const std::string&symbol);
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
bool isSplit() const
{
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
}
bool isEmpty() const {
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
}
virtual void write_members(json_writer&writer) const {
writer.write_member("instanceId",instanceId_);
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_);
writer.write_member("bottomChain",bottomChain_);
}
}
DECLARE_JSON_MAP(PedalBoardItem);
};
class PedalBoard {
std::string name_;
std::vector<PedalBoardItem> items_;
long nextInstanceId_ = 0;
long NextInstanceId() { return ++nextInstanceId_; }
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;
bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
GETTER_SETTER_REF(name)
GETTER_SETTER_VEC(items)
DECLARE_JSON_MAP(PedalBoard);
PedalBoardItem MakeEmptyItem();
PedalBoardItem MakeSplit();
static PedalBoard MakeDefault();
};
#undef GETTER_SETTER_REF
#undef GETTER_SETTER_VEC
#undef GETTER_SETTER
} // namespace pipedal
+20
View File
@@ -0,0 +1,20 @@
#include "pch.h"
#include "PiPedalConfiguration.hpp"
#include "json.hpp"
using namespace pipedal;
JSON_MAP_BEGIN(PiPedalConfiguration)
JSON_MAP_REFERENCE(PiPedalConfiguration, lv2_path)
JSON_MAP_REFERENCE(PiPedalConfiguration, local_storage_path)
JSON_MAP_REFERENCE(PiPedalConfiguration, mlock)
JSON_MAP_REFERENCE(PiPedalConfiguration, reactServerAddresses)
JSON_MAP_REFERENCE(PiPedalConfiguration, socketServerAddress)
JSON_MAP_REFERENCE(PiPedalConfiguration, threads)
JSON_MAP_REFERENCE(PiPedalConfiguration, logLevel)
JSON_MAP_REFERENCE(PiPedalConfiguration, logHttpRequests)
JSON_MAP_REFERENCE(PiPedalConfiguration, maxUploadSize)
JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointGateway)
JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointServerAddress)
JSON_MAP_END()
+116
View File
@@ -0,0 +1,116 @@
#pragma once
#include "json.hpp"
#include <fstream>
#include <filesystem>
#include "PiPedalException.hpp"
#include <string>
#include <limits>
#include <filesystem>
#include "Lv2Log.hpp"
#include <boost/asio/ip/network_v4.hpp>
namespace pipedal {
class PiPedalConfiguration
{
private:
std::string lv2_path_ = "/usr/lib/lv2:/usr/local/lib/lv2";
std::filesystem::path docRoot_;
std::string local_storage_path_;
bool mlock_ = true;
std::vector<std::string> reactServerAddresses_ = {"*:5000"};
std::string socketServerAddress_ = "0.0.0.0:8080";
uint32_t threads_ = 5;
bool logHttpRequests_ = false;
int logLevel_ = 0;
uint64_t maxUploadSize_ = 1024*1024;
std::string accessPointGateway_;
std::string accessPointServerAddress_;
public:
std::filesystem::path GetConfigFilePath() const {
return docRoot_ / "config.jason";
}
void Load(std::filesystem::path path) {
std::filesystem::path configPath = path / "config.json";
if (!std::filesystem::exists(configPath))
{
throw PiPedalException("File not found.");
}
std::ifstream f(configPath);
if (!f.is_open())
{
std::stringstream s;
s << "Unable to open " << configPath;
throw PiPedalStateException(s.str());
}
json_reader reader(f);
reader.read(this);
docRoot_ = path;
}
const std::filesystem::path &GetDocRoot() const { return docRoot_; }
const std::string &GetLv2Path() const { return lv2_path_; }
const std::string &GetLocalStoragePath() const { return local_storage_path_; }
const std::vector<std::string> &GetReactServerAddresses() const { return reactServerAddresses_;}
bool GetMLock() const { return mlock_; }
uint64_t GetMaxUploadSize() const { return maxUploadSize_; }
LogLevel GetLogLevel() const { return (LogLevel)this->logLevel_; }
bool LogHttpRequests() const { return this->logHttpRequests_; }
void SetSocketServerEndpoint(const std::string &endpoint)
{
this->socketServerAddress_ = endpoint;
}
bool GetAccessPointGateway(boost::asio::ip::network_v4 *pGatewayNetwork) const {
if (this->accessPointGateway_.length() == 0) return false;
*pGatewayNetwork = boost::asio::ip::make_network_v4(this->accessPointGateway_.c_str());
return true;
}
std::string GetAccessPointServerAddress() const { return this->accessPointServerAddress_; }
std::string GetSocketServerAddress() const {
size_t pos = this->socketServerAddress_.find_last_of(':');
if (pos == std::string::npos)
{
return socketServerAddress_;
}
return socketServerAddress_.substr(0,pos);
}
uint16_t GetSocketServerPort() const {
try {
size_t pos = this->socketServerAddress_.find_last_of(':');
if (pos == std::string::npos)
{
throw std::exception();
}
std::string strPort = socketServerAddress_.substr(pos+1);
unsigned long port = std::stoul(strPort);
if (port == 0)
{
throw std::exception();
}
if (port > std::numeric_limits<uint16_t>::max())
{
throw std::exception();
}
return (uint16_t) port;
} catch (const std::exception &)
{
std::stringstream s;
s << "Invalid port number in '" << this->socketServerAddress_ << "'.";
throw PiPedalArgumentException(s.str());
}
}
uint32_t GetThreads() const { return threads_; }
DECLARE_JSON_MAP(PiPedalConfiguration);
};
};
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <exception>
namespace pipedal
{
class PiPedalException : public std::exception
{
std::string what_;
public:
PiPedalException(const std::string &what)
: std::exception(), what_(what)
{
}
virtual ~PiPedalException() {
}
virtual const char *what() const noexcept { return what_.c_str(); }
};
class PiPedalArgumentException : public PiPedalException
{
public:
PiPedalArgumentException()
: PiPedalException("Invalid argument")
{
}
PiPedalArgumentException(std::string &&what)
: PiPedalException(std::forward<std::string>(what))
{
}
};
class PiPedalStateException : public PiPedalException
{
public:
PiPedalStateException(std::string &&what)
: PiPedalException(std::forward<std::string>(what))
{
}
};
class PiPedalLogicException : public PiPedalException
{
public:
PiPedalLogicException(const char *what)
: PiPedalException(what)
{
}
};
} // namespace
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <cmath>
namespace pipedal {
const double DB0=-96;
inline float db2a(float db)
{
return std::pow(10,db/20);
}
inline float a2db(float amplitude)
{
if (amplitude == 0) return DB0;
return 20*std::log10(std::abs(amplitude));
}
} // namespace
+986
View File
@@ -0,0 +1,986 @@
#include "pch.h"
#include "PiPedalModel.hpp"
#include "JackHost.hpp"
#include "Lv2Log.hpp"
#include <set>
#include "PiPedalConfiguration.hpp"
#include "ShutdownClient.hpp"
#include "SplitEffect.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
#endif /* NO_MLOCK */
using namespace pipedal;
template <typename T>
T &constMutex(const T &mutex)
{
return const_cast<T &>(mutex);
}
std::string AtomToJson(int length, uint8_t *pData)
{
return "";
}
PiPedalModel::PiPedalModel()
{
this->pedalBoard = PedalBoard::MakeDefault();
}
void PiPedalModel::Close()
{
std::lock_guard lock(this->mutex);
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->Close();
}
delete[] t;
if (jackHost)
{
jackHost->Close();
}
}
PiPedalModel::~PiPedalModel()
{
if (jackHost)
{
jackHost->Close();
}
}
#include <fstream>
void PiPedalModel::Load(const PiPedalConfiguration &configuration)
{
this->jackServerSettings.ReadJackConfiguration();
storage.SetDataRoot(configuration.GetLocalStoragePath().c_str());
storage.Initialize();
// Not all Lv2 directories have the lv2 base declarations. Load a full set of plugin classes generated on a default /usr/local/lib/lv2 directory.
std::filesystem::path pluginClassesPath = configuration.GetDocRoot() / "plugin_classes.json";
try
{
if (!std::filesystem::exists(pluginClassesPath))
throw PiPedalException("File not found.");
lv2Host.LoadPluginClassesFromJson(pluginClassesPath);
}
catch (const std::exception &e)
{
std::stringstream s;
s << "Unable to load " << pluginClassesPath << ". " << e.what();
throw PiPedalException(s.str().c_str());
}
lv2Host.Load(configuration.GetLv2Path().c_str());
this->pedalBoard = storage.GetCurrentPreset();
updateDefaults(&this->pedalBoard);
std::unique_ptr<JackHost> p{JackHost::CreateInstance(lv2Host.asIHost())};
this->jackHost = std::move(p);
this->jackHost->SetNotificationCallbacks(this);
if (configuration.GetMLock())
{
#ifndef NO_MLOCK
int result = mlockall(MCL_CURRENT | MCL_FUTURE);
if (result)
{
throw PiPedalStateException("mlockall failed. You can disable the call to mlockall in 'config.json'.");
}
#endif
}
this->jackConfiguration = jackHost->GetServerConfiguration();
if (this->jackConfiguration.isValid())
{
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
selection = selection.RemoveInvalidChannels(jackConfiguration);
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
try
{
jackHost->Open(selection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
}
catch (PiPedalException &e)
{
Lv2Log::error("Failed to load initial plugin. (%s)", e.what());
}
}
}
IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientId)
{
for (size_t i = 0; i < subscribers.size(); ++i)
{
if (subscribers[i]->GetClientId() == clientId)
{
return subscribers[i];
}
}
return nullptr;
}
void PiPedalModel::AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber)
{
std::lock_guard lock(this->mutex);
this->subscribers.push_back(pSubscriber);
}
void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber)
{
{
std::lock_guard lock(this->mutex);
for (auto it = this->subscribers.begin(); it != this->subscribers.end(); ++it)
{
if ((*it) == pSubscriber)
{
this->subscribers.erase(it);
break;
}
}
int64_t clientId = pSubscriber->GetClientId();
this->deleteMidiListeners(clientId);
for (int i = 0; i < this->outstandingParameterRequests.size(); ++i)
{
if (outstandingParameterRequests[i]->clientId == clientId)
{
outstandingParameterRequests.erase(outstandingParameterRequests.begin() + i);
--i;
}
}
}
}
void PiPedalModel::previewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
{
jackHost->SetControlValue(pedalItemId, symbol, value);
}
void PiPedalModel::setControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
{
{
std::lock_guard guard(mutex);
previewControl(clientId, pedalItemId, symbol, value);
this->pedalBoard.SetControlValue(pedalItemId, symbol, value);
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnControlChanged(clientId, pedalItemId, symbol, value);
}
delete[] t;
this->setPresetChanged(clientId, true);
}
}
}
void PiPedalModel::fireJackConfigurationChanged(const JackConfiguration &jackConfiguration)
{
// noify subscribers.
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnJackConfigurationChanged(jackConfiguration);
}
delete[] t;
}
void PiPedalModel::fireBanksChanged(int64_t clientId)
{
// noify subscribers.
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnBankIndexChanged(this->storage.GetBanks());
}
delete[] t;
}
void PiPedalModel::firePedalBoardChanged(int64_t clientId)
{
// noify subscribers.
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnPedalBoardChanged(clientId, this->pedalBoard);
}
delete[] t;
// notify the audio thread.
if (jackHost->IsOpen())
{
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
updateRealtimeVuSubscriptions();
updateRealtimeMonitorPortSubscriptions();
}
}
void PiPedalModel::setPedalBoard(int64_t clientId, PedalBoard &pedalBoard)
{
{
std::lock_guard guard(this->mutex);
this->pedalBoard = pedalBoard;
updateDefaults(&this->pedalBoard);
this->firePedalBoardChanged(clientId);
this->setPresetChanged(clientId, true);
}
}
void PiPedalModel::setPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled)
{
std::lock_guard(this->mutex);
{
this->pedalBoard.SetItemEnabled(pedalItemId, enabled);
// Notify clients.
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnItemEnabledChanged(clientId, pedalItemId, enabled);
}
delete[] t;
this->setPresetChanged(clientId, true);
// Notify audo thread.
this->jackHost->SetBypass(pedalItemId, enabled);
}
}
void PiPedalModel::getPresets(PresetIndex *pResult)
{
std::lock_guard guard(mutex);
this->storage.GetPresetIndex(pResult);
pResult->presetChanged(this->hasPresetChanged);
}
PedalBoard PiPedalModel::getPreset(int64_t instanceId)
{
std::lock_guard guard(mutex);
return this->storage.GetPreset(instanceId);
}
void PiPedalModel::setPresetChanged(int64_t clientId, bool value)
{
if (value != this->hasPresetChanged)
{
hasPresetChanged = value;
firePresetsChanged(clientId);
}
}
void PiPedalModel::firePresetsChanged(int64_t clientId)
{
std::lock_guard(this->mutex);
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
PresetIndex presets;
getPresets(&presets);
for (size_t i = 0; i < n; ++i)
{
t[i]->OnPresetsChanged(clientId, presets);
}
delete[] t;
}
}
void PiPedalModel::saveCurrentPreset(int64_t clientId)
{
std::lock_guard(this->mutex);
storage.saveCurrentPreset(this->pedalBoard);
this->setPresetChanged(clientId, false);
}
int64_t PiPedalModel::saveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId)
{
std::lock_guard(this->mutex);
int64_t result = storage.saveCurrentPresetAs(this->pedalBoard, name, saveAfterInstanceId);
this->hasPresetChanged = false;
firePresetsChanged(clientId);
return result;
}
int64_t PiPedalModel::uploadPreset(const BankFile&bankFile,int64_t uploadAfter)
{
std::lock_guard(this->mutex);
int64_t newPreset = this->storage.UploadPreset(bankFile,uploadAfter);
firePresetsChanged(-1);
return newPreset;
}
void PiPedalModel::loadPreset(int64_t clientId, int64_t instanceId)
{
std::lock_guard(this->mutex);
if (storage.LoadPreset(instanceId))
{
this->pedalBoard = storage.GetCurrentPreset();
updateDefaults(&this->pedalBoard);
this->hasPresetChanged = false; // no fire.
this->firePedalBoardChanged(clientId);
this->firePresetsChanged(clientId); // fire now.
}
}
int64_t PiPedalModel::copyPreset(int64_t clientId, int64_t from, int64_t to)
{
std::lock_guard(this->mutex);
int64_t result = storage.CopyPreset(from, to);
if (result != -1)
{
this->firePresetsChanged(clientId); // fire now.
}
else
{
throw PiPedalStateException("Copy failed.");
}
return result;
}
bool PiPedalModel::updatePresets(int64_t clientId, const PresetIndex &presets)
{
std::lock_guard(this->mutex);
storage.SetPresetIndex(presets);
firePresetsChanged(clientId);
return true;
}
void PiPedalModel::moveBank(int64_t clientId, int from, int to)
{
std::lock_guard(this->mutex);
storage.MoveBank(from, to);
fireBanksChanged(clientId);
}
int64_t PiPedalModel::deleteBank(int64_t clientId, int64_t instanceId)
{
std::lock_guard(this->mutex);
int64_t selectedBank = this->storage.GetBanks().selectedBank();
int64_t newSelection = storage.DeleteBank(instanceId);
int64_t newSelectedBank = this->storage.GetBanks().selectedBank();
this->fireBanksChanged(clientId); // fire now.
if (newSelectedBank != selectedBank)
{
this->openBank(clientId, newSelectedBank);
}
return newSelection;
}
int64_t PiPedalModel::deletePreset(int64_t clientId, int64_t instanceId)
{
std::lock_guard(this->mutex);
int64_t newSelection = storage.DeletePreset(instanceId);
this->firePresetsChanged(clientId); // fire now.
return newSelection;
}
bool PiPedalModel::renamePreset(int64_t clientId, int64_t instanceId, const std::string &name)
{
std::lock_guard guard(mutex);
if (storage.RenamePreset(instanceId, name))
{
this->firePresetsChanged(clientId);
return true;
}
else
{
throw PiPedalStateException("Rename failed.");
}
}
JackConfiguration PiPedalModel::getJackConfiguration()
{
std::lock_guard lock(this->mutex); // copy atomically.
return this->jackConfiguration;
}
void PiPedalModel::setJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
{
std::lock_guard lock(this->mutex); // copy atomically.
this->storage.SetJackChannelSelection(channelSelection);
this->fireChannelSelectionChanged(clientId);
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
if (this->jackHost->IsOpen())
{
// do a complete reload.
this->jackHost->Close();
this->jackHost->SetPedalBoard(nullptr);
this->jackHost->Open(channelSelection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
this->updateRealtimeVuSubscriptions();
}
}
void PiPedalModel::fireChannelSelectionChanged(int64_t clientId)
{
std::lock_guard(this->mutex);
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
JackChannelSelection channelSelection = storage.GetJackChannelSelection(this->jackConfiguration);
for (size_t i = 0; i < n; ++i)
{
t[i]->OnChannelSelectionChanged(clientId, channelSelection);
}
delete[] t;
}
}
JackChannelSelection PiPedalModel::getJackChannelSelection()
{
std::lock_guard guard(mutex);
JackChannelSelection t = this->storage.GetJackChannelSelection(this->jackConfiguration);
if (this->jackConfiguration.isValid())
{
t = t.RemoveInvalidChannels(this->jackConfiguration);
}
return t;
}
int64_t PiPedalModel::addVuSubscription(int64_t instanceId)
{
std::lock_guard guard(mutex);
int64_t subscriptionId = ++nextSubscriptionId;
activeVuSubscriptions.push_back(VuSubscription{subscriptionId, instanceId});
updateRealtimeVuSubscriptions();
return subscriptionId;
}
void PiPedalModel::removeVuSubscription(int64_t subscriptionHandle)
{
std::lock_guard guard(mutex);
for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i)
{
if ((*i).subscriptionHandle == subscriptionHandle)
{
activeVuSubscriptions.erase(i);
break;
}
}
updateRealtimeVuSubscriptions();
}
void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value)
{
std::lock_guard guard(mutex);
PedalBoardItem *item = this->pedalBoard.GetItem(instanceId);
if (item)
{
const Lv2PluginInfo *pPluginInfo;
if (item->uri() == SPLIT_PEDALBOARD_ITEM_URI)
{
pPluginInfo = GetSplitterPluginInfo();
}
else
{
auto pluginInfo = lv2Host.GetPluginInfo(item->uri());
pPluginInfo = pluginInfo.get();
}
if (pPluginInfo)
{
if (portIndex == -1)
{
// bypass!
this->pedalBoard.SetItemEnabled(instanceId, value != 0);
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnItemEnabledChanged(-1, instanceId, value != 0);
}
delete[] t;
this->setPresetChanged(-1, true);
return;
}
else
{
for (int i = 0; i < pPluginInfo->ports().size(); ++i)
{
auto &port = pPluginInfo->ports()[i];
if (port->index() == portIndex)
{
std::string symbol = port->symbol();
this->pedalBoard.SetControlValue(instanceId, symbol, value);
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnMidiValueChanged(instanceId, symbol, value);
}
delete[] t;
this->setPresetChanged(-1, true);
return;
}
}
}
}
}
}
}
void PiPedalModel::OnNotifyVusSubscription(const std::vector<VuUpdate> &updates)
{
std::lock_guard guard(mutex);
for (size_t i = 0; i < updates.size(); ++i)
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnVuMeterUpdate(updates);
}
delete[] t;
}
}
void PiPedalModel::updateRealtimeVuSubscriptions()
{
std::set<int64_t> addedInstances;
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
{
auto instanceId = activeVuSubscriptions[i].instanceid;
if (pedalBoard.HasItem(instanceId))
{
addedInstances.insert(activeVuSubscriptions[i].instanceid);
}
}
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
jackHost->SetVuSubscriptions(instanceids);
}
void PiPedalModel::updateRealtimeMonitorPortSubscriptions()
{
jackHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
}
int64_t PiPedalModel::monitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate)
{
std::lock_guard guard(mutex);
int64_t subscriptionId = ++nextSubscriptionId;
activeMonitorPortSubscriptions.push_back(
MonitorPortSubscription{subscriptionId, instanceId, key, updateInterval, onUpdate});
updateRealtimeMonitorPortSubscriptions();
return subscriptionId;
}
void PiPedalModel::unmonitorPort(int64_t subscriptionHandle)
{
std::lock_guard guard(mutex);
for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i)
{
if ((*i).subscriptionHandle == subscriptionHandle)
{
activeMonitorPortSubscriptions.erase(i);
updateRealtimeMonitorPortSubscriptions();
break;
}
}
}
void PiPedalModel::OnNotifyMonitorPort(const MonitorPortUpdate &update)
{
std::lock_guard guard(mutex);
for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i)
{
if ((*i).subscriptionHandle == update.subscriptionHandle)
{
// make the call ONLY if the subscription handle is still valid.
(*update.callbackPtr)(update.subscriptionHandle, update.value);
break;
}
}
}
void PiPedalModel::getLv2Parameter(
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)
{
std::function<void(RealtimeParameterRequest *)> onRequestComplete{
[this](RealtimeParameterRequest *pParameter)
{
{
std::lock_guard guard(this->mutex);
bool cancelled = true;
for (auto i = this->outstandingParameterRequests.begin();
i != this->outstandingParameterRequests.end(); ++i)
{
if ((*i) == pParameter)
{
cancelled = false;
this->outstandingParameterRequests.erase(i);
break;
}
}
if (!cancelled)
{
if (pParameter->errorMessage != nullptr)
{
pParameter->onError(pParameter->errorMessage);
}
else if (pParameter->responseLength == 0)
{
pParameter->onError("No response.");
}
else
{
pParameter->onSuccess(pParameter->jsonResponse);
}
}
delete pParameter;
}
}};
LV2_URID urid = this->lv2Host.GetLv2Urid(uri.c_str());
RealtimeParameterRequest *request = new RealtimeParameterRequest(
onRequestComplete,
clientId, instanceId, urid, onSuccess, onError);
std::lock_guard guard(constMutex(mutex));
outstandingParameterRequests.push_back(request);
this->jackHost->getRealtimeParameter(request);
}
BankIndex PiPedalModel::getBankIndex() const
{
std::lock_guard guard(constMutex(mutex));
return storage.GetBanks();
}
void PiPedalModel::renameBank(int64_t clientId, int64_t bankId, const std::string &newName)
{
std::lock_guard guard(mutex);
storage.RenameBank(bankId, newName);
fireBanksChanged(clientId);
}
int64_t PiPedalModel::saveBankAs(int64_t clientId, int64_t bankId, const std::string &newName)
{
std::lock_guard guard(mutex);
int64_t newId = storage.SaveBankAs(bankId, newName);
fireBanksChanged(clientId);
return newId;
}
void PiPedalModel::openBank(int64_t clientId, int64_t bankId)
{
std::lock_guard guard(mutex);
storage.LoadBank(bankId);
fireBanksChanged(clientId);
firePresetsChanged(clientId);
this->pedalBoard = storage.GetCurrentPreset();
updateDefaults(&this->pedalBoard);
this->hasPresetChanged = false;
this->firePedalBoardChanged(clientId);
}
JackServerSettings PiPedalModel::GetJackServerSettings()
{
std::lock_guard guard(mutex);
return this->jackServerSettings;
}
void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSettings)
{
std::lock_guard guard(mutex);
this->jackServerSettings = jackServerSettings;
if (!ShutdownClient::CanUseShutdownClient())
{
throw PiPedalException("Can't change server settings when running interactively.");
}
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnJackServerSettingsChanged(jackServerSettings);
}
delete[] t;
if (ShutdownClient::CanUseShutdownClient())
{
this->jackConfiguration.SetIsRestarting(true);
fireJackConfigurationChanged(this->jackConfiguration);
this->jackHost->UpdateServerConfiguration(jackServerSettings,
[this](bool success, const std::string &errorMessage)
{
std::lock_guard guard(mutex);
if (!success)
{
std::stringstream s;
s << "UpdateServerconfiguration failed: " << errorMessage;
Lv2Log::error(s.str().c_str());
}
// Update jack server status.
this->jackConfiguration.SetIsRestarting(false);
fireJackConfigurationChanged(this->jackConfiguration);
// restart the pedalboard on a new instance.
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
updateRealtimeVuSubscriptions();
updateRealtimeMonitorPortSubscriptions();
});
}
}
void PiPedalModel::updateDefaults(PedalBoardItem *pedalBoardItem)
{
std::shared_ptr<Lv2PluginInfo> t = lv2Host.GetPluginInfo(pedalBoardItem->uri());
const Lv2PluginInfo *pPlugin = t.get();
if (pPlugin == nullptr)
{
if (pedalBoardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
{
pPlugin = GetSplitterPluginInfo();
}
}
if (pPlugin != nullptr)
{
for (size_t i = 0; i < pPlugin->ports().size(); ++i)
{
auto port = pPlugin->ports()[i];
if (port->is_control_port() && port->is_input())
{
ControlValue *pValue = pedalBoardItem->GetControlValue(port->symbol());
if (pValue == nullptr)
{
// Missing? Set it to default value.
pedalBoardItem->controlValues().push_back(
pipedal::ControlValue(port->symbol().c_str(), port->default_value()));
}
}
}
}
for (size_t i = 0; i < pedalBoardItem->topChain().size(); ++i)
{
updateDefaults(&(pedalBoardItem->topChain()[i]));
}
for (size_t i = 0; i < pedalBoardItem->bottomChain().size(); ++i)
{
updateDefaults(&(pedalBoardItem->bottomChain()[i]));
}
}
void PiPedalModel::updateDefaults(PedalBoard *pedalBoard)
{
for (size_t i = 0; i < pedalBoard->items().size(); ++i)
{
updateDefaults(&(pedalBoard->items()[i]));
}
}
std::vector<Lv2PluginPreset> PiPedalModel::GetPluginPresets(std::string pluginUri)
{
std::lock_guard guard(mutex);
return lv2Host.GetPluginPresets(pluginUri);
}
void PiPedalModel::LoadPluginPreset(int64_t instanceId, const std::string &presetUri)
{
std::lock_guard guard(mutex);
PedalBoardItem *pedalBoardItem = this->pedalBoard.GetItem(instanceId);
if (pedalBoardItem != nullptr)
{
std::vector<ControlValue> controlValues = lv2Host.LoadPluginPreset(pedalBoardItem, presetUri);
jackHost->SetPluginPreset(instanceId, controlValues);
for (size_t i = 0; i < controlValues.size(); ++i)
{
const ControlValue &value = controlValues[i];
this->pedalBoard.SetControlValue(instanceId, value.key(), value.value());
}
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnLoadPluginPreset(instanceId, controlValues);
}
delete[] t;
}
}
void PiPedalModel::deleteMidiListeners(int64_t clientId)
{
std::lock_guard guard(mutex);
for (size_t i = 0; i < midiEventListeners.size(); ++i)
{
if (midiEventListeners[i].clientId == clientId)
{
midiEventListeners.erase(midiEventListeners.begin() + i);
--i;
}
}
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
{
std::lock_guard guard(mutex);
for (int i = 0; i < midiEventListeners.size(); ++i)
{
auto &listener = midiEventListeners[i];
if ((!isNote) || (!listener.listenForControlsOnly))
{
auto subscriber = this->GetNotificationSubscriber(listener.clientId);
if (subscriber)
{
subscriber->OnNotifyMidiListener(listener.clientHandle, isNote, noteOrControl);
}
midiEventListeners.erase(midiEventListeners.begin() + i);
--i;
}
}
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
void PiPedalModel::listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly)
{
std::lock_guard guard(mutex);
MidiListener listener{clientId, clientHandle, listenForControlsOnly};
midiEventListeners.push_back(listener);
jackHost->SetListenForMidiEvent(true);
}
void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled)
{
std::lock_guard guard(mutex);
for (size_t i = 0; i < midiEventListeners.size(); ++i)
{
midiEventListeners.erase(midiEventListeners.begin() + i);
}
if (midiEventListeners.size() == 0)
{
jackHost->SetListenForMidiEvent(false);
}
}
+179
View File
@@ -0,0 +1,179 @@
#pragma once
#include <mutex>
#include "Lv2Host.hpp"
#include "PedalBoard.hpp"
#include "Storage.hpp"
#include "Banks.hpp"
#include "JackConfiguration.hpp"
#include "JackHost.hpp"
#include "VuUpdate.hpp"
#include <functional>
#include <filesystem>
#include "Banks.hpp"
#include "PiPedalConfiguration.hpp"
#include "JackServerSettings.hpp"
namespace pipedal {
class IPiPedalModelSubscriber {
public:
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 OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex&presets) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection&channelSelection) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate>& updates) = 0;
virtual void OnBankIndexChanged(const BankIndex& bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings& jackServerSettings) = 0;
virtual void OnJackConfigurationChanged(const JackConfiguration& jackServerConfiguration) = 0;
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 Close() = 0;
};
class PiPedalModel: private IJackHostCallbacks {
private:
class MidiListener {
public:
int64_t clientId;
int64_t clientHandle;
bool listenForControlsOnly;
};
void deleteMidiListeners(int64_t clientId);
std::vector<MidiListener> midiEventListeners;
JackServerSettings jackServerSettings;
Lv2Host lv2Host;
std::recursive_mutex mutex;
PedalBoard pedalBoard;
Storage storage;
bool hasPresetChanged = false;
std::unique_ptr<JackHost> jackHost;
JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::vector<IPiPedalModelSubscriber*> subscribers;
void setPresetChanged(int64_t clientId,bool value);
void firePresetsChanged(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);
class VuSubscription {
public:
int64_t subscriptionHandle;
int64_t instanceid;
};
int64_t nextSubscriptionId = 1;
std::vector<VuSubscription> activeVuSubscriptions;
std::vector<MonitorPortSubscription> activeMonitorPortSubscriptions;
void updateRealtimeVuSubscriptions();
void updateRealtimeMonitorPortSubscriptions();
void OnVuUpdate(const std::vector<VuUpdate>& updates);
std::vector<RealtimeParameterRequest*> outstandingParameterRequests;
IPiPedalModelSubscriber*GetNotificationSubscriber(int64_t clientId);
private: // IJackHostCallbacks
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);
public:
PiPedalModel();
virtual ~PiPedalModel();
void Close();
void Load(const PiPedalConfiguration&configuration);
const Lv2Host& getPlugins() const { return lv2Host; }
PedalBoard getCurrentPedalBoardCopy() {
std::lock_guard guard(mutex);
return pedalBoard;
}
std::vector<Lv2PluginPreset> GetPluginPresets(std::string pluginUri);
void LoadPluginPreset(int64_t instanceId,const std::string &presetUri);
void AddNotificationSubscription(IPiPedalModelSubscriber* pSubscriber);
void RemoveNotificationSubsription(IPiPedalModelSubscriber* pSubscriber);
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 getPresets(PresetIndex*pResult);
PedalBoard getPreset(int64_t instanceId);
int64_t uploadPreset(const BankFile&bankFile, int64_t uploadAfter = -1);
void saveCurrentPreset(int64_t clientId);
int64_t saveCurrentPresetAs(int64_t clientId, const std::string& name,int64_t saveAfterInstanceId = -1);
void loadPreset(int64_t clientId, int64_t instanceId);
bool updatePresets(int64_t clientId,const PresetIndex&presets);
int64_t deletePreset(int64_t clientId,int64_t instanceId);
bool renamePreset(int64_t clientId,int64_t instanceId,const std::string&name);
int64_t copyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
void moveBank(int64_t clientId,int from, int to);
int64_t deleteBank(int64_t clientId, int64_t instanceId);
int64_t duplicatePreset(int64_t clientId, int64_t instanceId) { return copyPreset(clientId,instanceId,-1); }
JackConfiguration getJackConfiguration();
void setJackChannelSelection(int64_t clientId,const JackChannelSelection &channelSelection);
JackChannelSelection getJackChannelSelection();
int64_t addVuSubscription(int64_t instanceId);
void removeVuSubscription(int64_t subscriptionHandle);
int64_t monitorPort(int64_t instanceId,const std::string &key,float updateInterval, PortMonitorCallback onUpdate);
void unmonitorPort(int64_t subscriptionHandle);
void getLv2Parameter(
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
);
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);
void openBank(int64_t clientId,int64_t bankId);
JackHostStatus getJackStatus() {
return this->jackHost->getJackStatus();
}
JackServerSettings GetJackServerSettings();
void SetJackServerSettings(const JackServerSettings& jackServerSettings);
void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled);
};
} // namespace pipedal.
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "BeastServer.hpp"
#include "PiPedalModel.hpp"
namespace pipedal {
std::shared_ptr<ISocketFactory> MakePiPedalSocketFactory(PiPedalModel &model);
};
+55
View File
@@ -0,0 +1,55 @@
#include "pch.h"
#include "config.hpp"
#include "PiPedalVersion.hpp"
#ifdef _WIN32
#include "windows.h"
#include "sstream"
#else
#include <sys/utsname.h>
#endif
using namespace pipedal;
JSON_MAP_BEGIN(PiPedalVersion)
JSON_MAP_REFERENCE(PiPedalVersion,server)
JSON_MAP_REFERENCE(PiPedalVersion,serverVersion)
JSON_MAP_REFERENCE(PiPedalVersion,operatingSystem)
JSON_MAP_REFERENCE(PiPedalVersion,osVersion)
JSON_MAP_REFERENCE(PiPedalVersion,debug)
JSON_MAP_END()
PiPedalVersion::PiPedalVersion()
{
server_ = "PiPedal Server";
// defined on build command line.
serverVersion_ = PROJECT_VER;
#ifdef _WIN32
OSVERSIONINFOEXA info;
ZeroMemory(&info, sizeof(OSVERSIONINFOEA));
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEA);
GetVersionExA(&info);
::stringstream s;
s << info.dwMajorVersion << '.' << info.dwMinorVersion << '.' << info.dwBuildNumber;
#ifdef _WIN32
operatingSystem_ = "Windows 32-bit";
#elif _WIN64
operatingSystem_ = "Windows 64-bit";
#endif
osVersion_ = s.str();
#else
struct utsname uts;
uname(&uts);
operatingSystem_ = uts.sysname;
osVersion_ = std::string(uts.release) + "/"+ std::string(uts.machine);
#endif
#ifdef DEBUG
debug_ = true;
#else
debug_ = false;
#endif
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "json.hpp"
namespace pipedal {
class PiPedalVersion {
private:
std::string server_;
std::string serverVersion_;
std::string operatingSystem_;
std::string osVersion_;
bool debug_;
public:
PiPedalVersion();
~PiPedalVersion() = default;
DECLARE_JSON_MAP(PiPedalVersion);
};
} // namepace pipedal
+189
View File
@@ -0,0 +1,189 @@
#include "pch.h"
#include <boost/bimap/bimap.hpp>
#include "PluginType.hpp"
using namespace pipedal;
class PluginTypeMapEntry {
public:
PluginType pedal_type;
const std::string uri;
};
#define CORE_URI_PREFIX "http://lv2plug.in/ns/lv2core#"
#define URI_TO_TYPE_MAP_ENTRY(TYPE) \
{std::string(CORE_URI_PREFIX #TYPE),PluginType::TYPE }
static std::pair<std::string,PluginType> urisToNames[] {
{ "", PluginType::None},
URI_TO_TYPE_MAP_ENTRY(EmptyPlugin),
URI_TO_TYPE_MAP_ENTRY(SplitterPlugin),
URI_TO_TYPE_MAP_ENTRY(Plugin),
URI_TO_TYPE_MAP_ENTRY(InvalidPlugin),
URI_TO_TYPE_MAP_ENTRY(Plugin),
URI_TO_TYPE_MAP_ENTRY(AllpassPlugin),
URI_TO_TYPE_MAP_ENTRY(AmplifierPlugin),
URI_TO_TYPE_MAP_ENTRY(AnalyserPlugin),
URI_TO_TYPE_MAP_ENTRY(BandpassPlugin),
URI_TO_TYPE_MAP_ENTRY(ChorusPlugin),
URI_TO_TYPE_MAP_ENTRY(CombPlugin),
URI_TO_TYPE_MAP_ENTRY(CompressorPlugin),
URI_TO_TYPE_MAP_ENTRY(ConstantPlugin),
URI_TO_TYPE_MAP_ENTRY(ConverterPlugin),
URI_TO_TYPE_MAP_ENTRY(DelayPlugin),
URI_TO_TYPE_MAP_ENTRY(DistortionPlugin),
URI_TO_TYPE_MAP_ENTRY(DynamicsPlugin),
URI_TO_TYPE_MAP_ENTRY(EQPlugin),
URI_TO_TYPE_MAP_ENTRY(EnvelopePlugin),
URI_TO_TYPE_MAP_ENTRY(ExpanderPlugin),
URI_TO_TYPE_MAP_ENTRY(FilterPlugin),
URI_TO_TYPE_MAP_ENTRY(FlangerPlugin),
URI_TO_TYPE_MAP_ENTRY(FunctionPlugin),
URI_TO_TYPE_MAP_ENTRY(GatePlugin),
URI_TO_TYPE_MAP_ENTRY(GeneratorPlugin),
URI_TO_TYPE_MAP_ENTRY(HighpassPlugin),
URI_TO_TYPE_MAP_ENTRY(InstrumentPlugin),
URI_TO_TYPE_MAP_ENTRY(LimiterPlugin),
URI_TO_TYPE_MAP_ENTRY(LowpassPlugin),
URI_TO_TYPE_MAP_ENTRY(MixerPlugin),
URI_TO_TYPE_MAP_ENTRY(ModulatorPlugin),
URI_TO_TYPE_MAP_ENTRY(MultiEQPlugin),
URI_TO_TYPE_MAP_ENTRY(OscillatorPlugin),
URI_TO_TYPE_MAP_ENTRY(ParaEQPlugin),
URI_TO_TYPE_MAP_ENTRY(PhaserPlugin),
URI_TO_TYPE_MAP_ENTRY(PitchPlugin),
URI_TO_TYPE_MAP_ENTRY(ReverbPlugin),
URI_TO_TYPE_MAP_ENTRY(SimulatorPlugin),
URI_TO_TYPE_MAP_ENTRY(SpatialPlugin),
URI_TO_TYPE_MAP_ENTRY(SpectralPlugin),
URI_TO_TYPE_MAP_ENTRY(UtilityPlugin),
URI_TO_TYPE_MAP_ENTRY(WaveshaperPlugin),
URI_TO_TYPE_MAP_ENTRY(MIDIPlugin),
};
PluginType pipedal::uri_to_plugin_type(const std::string&uri)
{
for (auto& i: urisToNames)
{
if (i.first == uri)
{
return i.second;
}
}
return PluginType::None;
}
const std::string& pipedal::plugin_type_to_uri(PluginType type)
{
for (auto& i: urisToNames)
{
if (i.second == type)
{
return i.first;
}
}
throw std::invalid_argument("Plugin type not valid.");
}
#define STRING_TO_TYPE_MAP_ENTRY(TYPE) \
{std::string(#TYPE),PluginType::TYPE }
static std::pair<std::string,PluginType> strings_to_type_map[] {
{ "", PluginType::None},
STRING_TO_TYPE_MAP_ENTRY(InvalidPlugin),
STRING_TO_TYPE_MAP_ENTRY(Plugin),
STRING_TO_TYPE_MAP_ENTRY(AllpassPlugin),
STRING_TO_TYPE_MAP_ENTRY(AmplifierPlugin),
STRING_TO_TYPE_MAP_ENTRY(AnalyserPlugin),
STRING_TO_TYPE_MAP_ENTRY(BandpassPlugin),
STRING_TO_TYPE_MAP_ENTRY(ChorusPlugin),
STRING_TO_TYPE_MAP_ENTRY(CombPlugin),
STRING_TO_TYPE_MAP_ENTRY(CompressorPlugin),
STRING_TO_TYPE_MAP_ENTRY(ConstantPlugin),
STRING_TO_TYPE_MAP_ENTRY(ConverterPlugin),
STRING_TO_TYPE_MAP_ENTRY(DelayPlugin),
STRING_TO_TYPE_MAP_ENTRY(DistortionPlugin),
STRING_TO_TYPE_MAP_ENTRY(DynamicsPlugin),
STRING_TO_TYPE_MAP_ENTRY(EQPlugin),
STRING_TO_TYPE_MAP_ENTRY(EnvelopePlugin),
STRING_TO_TYPE_MAP_ENTRY(ExpanderPlugin),
STRING_TO_TYPE_MAP_ENTRY(FilterPlugin),
STRING_TO_TYPE_MAP_ENTRY(FlangerPlugin),
STRING_TO_TYPE_MAP_ENTRY(FunctionPlugin),
STRING_TO_TYPE_MAP_ENTRY(GatePlugin),
STRING_TO_TYPE_MAP_ENTRY(GeneratorPlugin),
STRING_TO_TYPE_MAP_ENTRY(HighpassPlugin),
STRING_TO_TYPE_MAP_ENTRY(InstrumentPlugin),
STRING_TO_TYPE_MAP_ENTRY(LimiterPlugin),
STRING_TO_TYPE_MAP_ENTRY(LowpassPlugin),
STRING_TO_TYPE_MAP_ENTRY(MixerPlugin),
STRING_TO_TYPE_MAP_ENTRY(ModulatorPlugin),
STRING_TO_TYPE_MAP_ENTRY(MultiEQPlugin),
STRING_TO_TYPE_MAP_ENTRY(OscillatorPlugin),
STRING_TO_TYPE_MAP_ENTRY(ParaEQPlugin),
STRING_TO_TYPE_MAP_ENTRY(PhaserPlugin),
STRING_TO_TYPE_MAP_ENTRY(PitchPlugin),
STRING_TO_TYPE_MAP_ENTRY(ReverbPlugin),
STRING_TO_TYPE_MAP_ENTRY(SimulatorPlugin),
STRING_TO_TYPE_MAP_ENTRY(SpatialPlugin),
STRING_TO_TYPE_MAP_ENTRY(SpectralPlugin),
STRING_TO_TYPE_MAP_ENTRY(UtilityPlugin),
STRING_TO_TYPE_MAP_ENTRY(WaveshaperPlugin),
STRING_TO_TYPE_MAP_ENTRY(MIDIPlugin),
};
PluginType pipedal::string_to_plugin_type(const std::string&uri)
{
for (auto& i: strings_to_type_map)
{
if (i.first == uri)
{
return i.second;
}
}
return PluginType::None;
}
const std::string &pipedal::plugin_type_to_string(PluginType type)
{
for (auto& i: strings_to_type_map)
{
if (i.second == type)
{
return i.first;
}
}
throw std::invalid_argument("Plugin type not valid.");
}
class PluginTypeEnumConverter: public json_enum_converter<PluginType> {
public:
virtual PluginType fromString(const std::string&value) const
{
return string_to_plugin_type(value);
}
virtual const std::string& toString(PluginType value) const
{
return plugin_type_to_string(value);
}
} g_plugin_type_converter;
json_enum_converter<PluginType> *pipedal::get_plugin_type_enum_converter()
{
return &g_plugin_type_converter;
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <string>
#include "json.hpp"
namespace pipedal {
enum class PluginType {
None,
EmptyPlugin, // Pedalboards only.
SplitterPlugin, // Pedalboards only.
InvalidPlugin,
Plugin,
AllpassPlugin,
AmplifierPlugin,
AnalyserPlugin,
BandpassPlugin,
ChorusPlugin,
CombPlugin,
CompressorPlugin,
ConstantPlugin,
ConverterPlugin,
DelayPlugin,
DistortionPlugin,
DynamicsPlugin,
EQPlugin,
EnvelopePlugin,
ExpanderPlugin,
FilterPlugin,
FlangerPlugin,
FunctionPlugin,
GatePlugin,
GeneratorPlugin,
HighpassPlugin,
InstrumentPlugin,
LimiterPlugin,
LowpassPlugin,
MixerPlugin,
ModulatorPlugin,
MultiEQPlugin,
OscillatorPlugin,
ParaEQPlugin,
PhaserPlugin,
PitchPlugin,
ReverbPlugin,
SimulatorPlugin,
SpatialPlugin,
SpectralPlugin,
UtilityPlugin,
WaveshaperPlugin,
MIDIPlugin,
};
const std::string &plugin_type_to_uri(PluginType type);
PluginType uri_to_plugin_type(const std::string&uri);
PluginType string_to_plugin_type(const std::string&value);
const std::string& plugin_type_to_string(PluginType value);
json_enum_converter<PluginType> *get_plugin_type_enum_converter();
}; // namespace pipedal.
+25
View File
@@ -0,0 +1,25 @@
#include "pch.h"
#include "Presets.hpp"
using namespace pipedal;
JSON_MAP_BEGIN(PedalPreset)
JSON_MAP_REFERENCE(PedalPreset,instanceId)
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_END()
JSON_MAP_BEGIN(PedalBoardPresets)
JSON_MAP_REFERENCE(PedalBoardPresets,nextInstanceId)
JSON_MAP_REFERENCE(PedalBoardPresets,currentPreset)
JSON_MAP_REFERENCE(PedalBoardPresets,presets)
JSON_MAP_END()
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "PedalBoard.hpp"
#include "json.hpp"
namespace pipedal {
class PedalPreset {
private:
int instanceId_ = -1;
std::vector<ControlValue> values_;
public:
DECLARE_JSON_MAP(PedalPreset);
};
class PedalBoardPreset {
long instanceId_;
std::string displayName_;
std::vector<std::unique_ptr<PedalPreset> > values_;
public:
DECLARE_JSON_MAP(PedalBoardPreset);
};
class PedalBoardPresets {
long nextInstanceId_ = 0;
long currentPreset_ = 0;
std::vector<std::unique_ptr<PedalBoardPreset> > presets_;
public:
DECLARE_JSON_MAP(PedalBoardPresets);
};
} // namespace.
+6
View File
@@ -0,0 +1,6 @@
#pragma once
+176
View File
@@ -0,0 +1,176 @@
#pragma once
#include <cstddef>
#include "PiPedalException.hpp"
#include <atomic>
#include <mutex>
#include <semaphore.h>
#ifndef NO_MLOCK
#include <sys/mman.h>
#endif /* NO_MLOCK */
namespace pipedal
{
template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false>
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;
sem_t readSemaphore;
bool semaphore_open = false;
size_t nextPowerOfTwo(size_t size)
{
size_t v = 1;
while (v < size)
{
v *= 2;
}
return v;
}
public:
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
{
this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize);
ringBufferMask = ringBufferSize-1;
buffer = new char[ringBufferSize];
if (SEMAPHORE_READER) {
sem_init(&readSemaphore,0,0);
semaphore_open = true;
}
#ifndef NO_MLOCK
if (mLock)
{
if (mlock (buffer, ringBufferSize)) {
throw PiPedalStateException("Mlock failed.");
}
this->mlocked = true;
}
#endif
}
void reset() {
this->readPosition = 0;
this->writePosition = 0;
if (SEMAPHORE_READER)
{
sem_destroy(&readSemaphore);
sem_init(&readSemaphore,0,0);
this->semaphore_open = true;
}
}
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.");
}
}
bool readWait() {
if (SEMAPHORE_READER)
{
sem_wait(&readSemaphore);
return semaphore_open;
} else {
throw PiPedalStateException("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;
return (size_t)size;
}
size_t readSpace() {
int64_t size = writePosition-readPosition;
if (size < 0) size += this->ringBufferSize;
return size_t(size);
}
bool write(size_t bytes, uint8_t *data)
{
if (MULTI_WRITER)
{
std::lock_guard guard(write_mutex);
if (writeSpace() < bytes) {
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
}
this->writePosition = (index+bytes) & ringBufferMask;
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
}
return true;
} else {
if (writeSpace() < bytes) {
return false;
}
size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i)
{
buffer[(index+i) & ringBufferMask] = data[i];
}
this->writePosition = (index+bytes) & ringBufferMask;
if (SEMAPHORE_READER)
{
sem_post(&readSemaphore);
}
return true;
}
}
bool read(size_t bytes, uint8_t*data)
{
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];
}
this->readPosition = (readPosition + bytes) & this->ringBufferMask;
return true;
}
~RingBuffer()
{
#ifdef USE_MLOCK
if (this->mlocked)
{
munlock(buffer,ringBufferSize);
}
#endif
if (SEMAPHORE_READER)
{
sem_destroy(&this->readSemaphore);
}
delete[] buffer;
}
};
};
+338
View File
@@ -0,0 +1,338 @@
#pragma once
#include "PiPedalException.hpp"
#include "Lv2Log.hpp"
#include "VuUpdate.hpp"
#include "JackHost.hpp"
namespace pipedal
{
enum class RingBufferCommand : int64_t
{
ReplaceEffect = 0,
EffectReplaced = 1,
SetValue = 2,
SetBypass = 3,
AudioStopped = 4,
SetVuSubscriptions = 5,
FreeVuSubscriptions = 6,
SendVuUpdate = 7,
AckVuUpdate = 8,
SetMonitorPortSubscription = 9,
FreeMonitorPortSubscription = 10,
SendMonitorPortUpdate = 11,
AckMonitorPortUpdate = 12,
ParameterRequest = 13,
ParameterRequestComplete = 14,
MidiValueChanged = 15,
OnMidiListen = 16,
};
class RealtimeMonitorPortSubscription
{
public:
RealtimeMonitorPortSubscription() { delete callbackPtr; }
int64_t subscriptionHandle;
int instanceIndex = 0;
int portIndex = 0;
PortMonitorCallback *callbackPtr = nullptr;
int sampleRate = 0;
int samplesToNextCallback = 0;
bool waitingForAck = false;
};
class RealtimeMonitorPortSubscriptions
{
public:
std::vector<RealtimeMonitorPortSubscription> subscriptions;
};
struct RealtimeVuBuffers
{
RealtimeVuBuffers()
{
Reset();
}
bool waitingForAcknowledge = false;
const std::vector<VuUpdate> *GetResult(size_t currentSample)
{
for (size_t i = 0; i < vuUpdateWorkingData.size(); ++i)
{
vuUpdateResponseData[i] = vuUpdateWorkingData[i];
vuUpdateResponseData[i].sampleTime_ = currentSample;
vuUpdateWorkingData[i].reset();
}
return &vuUpdateResponseData;
}
std::vector<int> enabledIndexes;
std::vector<VuUpdate> vuUpdateWorkingData;
std::vector<VuUpdate> vuUpdateResponseData;
void Reset()
{
for (int i = 0; i < vuUpdateWorkingData.size(); ++i)
{
vuUpdateWorkingData[i].reset();
}
}
};
class AudioStoppedBody
{
bool dummy = true;
};
class SetBypassBody
{
public:
int effectIndex;
bool enabled;
};
class MidiValueChangedBody
{
public:
int64_t instanceId;
int controlIndex;
float value;
};
class SetControlValueBody
{
public:
int effectIndex;
int controlIndex;
float value;
};
class Lv2PedalBoard;
class ReplaceEffectBody
{
public:
Lv2PedalBoard *effect;
};
class EffectReplacedBody
{
public:
Lv2PedalBoard *oldEffect;
};
template <bool MULTI_WRITE, bool SEMAPHORE_READ>
class RingBufferReader
{
private:
RingBuffer<MULTI_WRITE,SEMAPHORE_READ> *ringBuffer = nullptr;
public:
RingBufferReader()
: ringBuffer(nullptr)
{
}
RingBufferReader(RingBuffer<MULTI_WRITE,SEMAPHORE_READ> *ringBuffer)
: ringBuffer(ringBuffer)
{
}
// 0 -> ready. -1: timed out. -2: closing.
int wait(struct timespec &timeout) {
return ringBuffer->readWait(timeout);
}
bool wait() {
return ringBuffer->readWait();
}
size_t readSpace() const
{
return ringBuffer->readSpace();
}
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?");
}
return true;
}
template <typename T>
void readComplete(T *output)
{
if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
{
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
}
}
};
template <typename T>
class CommandBuffer
{
public:
CommandBuffer(RingBufferCommand command)
: command(command)
{
}
CommandBuffer(RingBufferCommand command, const T &value)
: command(command), value(value)
{
}
RingBufferCommand command;
size_t size() const { return sizeof(RingBufferCommand) + sizeof(T); }
T value;
};
template<bool MULTI_WRITER, bool SEMAPHORE_READER>
class RingBufferWriter
{
private:
RingBuffer<MULTI_WRITER,SEMAPHORE_READER>* ringBuffer;
public:
RingBufferWriter()
: ringBuffer(nullptr)
{
}
RingBufferWriter(RingBuffer<MULTI_WRITER,SEMAPHORE_READER>*ringBuffer)
: ringBuffer(ringBuffer)
{
}
template <typename T>
void write(RingBufferCommand command, const T &value)
{
// the goal: to atomically write the command and associated data.
CommandBuffer<T> buffer(command, value);
if (!ringBuffer->write(buffer.size(),(uint8_t *)&buffer))
{
Lv2Log::error("No space in audio service ringbuffer.");
return;
}
}
void ParameterRequest(RealtimeParameterRequest *pRequest)
{
write(RingBufferCommand::ParameterRequest,pRequest);
}
void ParameterRequestComplete(RealtimeParameterRequest *pRequest)
{
write(RingBufferCommand::ParameterRequestComplete,pRequest);
}
void MidiValueChanged(int64_t instanceId, int controlIndex,float value)
{
MidiValueChangedBody body;
body.instanceId = instanceId;
body.controlIndex = controlIndex;
body.value = value;
write(RingBufferCommand::MidiValueChanged,body);
}
void OnMidiListen(bool isNote, uint8_t noteOrControl) {
uint16_t msg = noteOrControl;
if (isNote) msg |= 0x100;
write(RingBufferCommand::OnMidiListen, msg);
}
void SetControlValue(int effectIndex, int controlIndex, float value)
{
SetControlValueBody body;
body.effectIndex = effectIndex;
body.controlIndex = controlIndex;
body.value = value;
write(RingBufferCommand::SetValue,body);
}
void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
{
write(RingBufferCommand::FreeVuSubscriptions, configuration);
}
void FreeMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
{
write(RingBufferCommand::FreeMonitorPortSubscription, subscriptions);
}
void SendMonitorPortUpdate(
PortMonitorCallback* callback,
int64_t subscriptionHandle,
float value)
{
MonitorPortUpdate body{callback, subscriptionHandle, value};
write(RingBufferCommand::SendMonitorPortUpdate, body);
}
void SendVuUpdate(const std::vector<VuUpdate> *pUpdates)
{
write(RingBufferCommand::SendVuUpdate,pUpdates);
}
void AckVuUpdate()
{
bool value = true;
write(RingBufferCommand::AckVuUpdate,value);
}
void AckMonitorPortUpdate(int64_t subscriptionHandle)
{
// we assume no padding between the command and the data, so we can do an atomic write.
write(RingBufferCommand::AckMonitorPortUpdate, subscriptionHandle);
}
void SetVuSubscriptions(RealtimeVuBuffers *configuration)
{
write(RingBufferCommand::SetVuSubscriptions, configuration);
}
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
{
write(RingBufferCommand::SetMonitorPortSubscription, subscriptions);
}
void SetBypass(int effectIndex, bool enabled)
{
SetBypassBody body;
body.effectIndex = effectIndex;
body.enabled = enabled;
write(RingBufferCommand::SetBypass,body);
}
void ReplaceEffect(Lv2PedalBoard *pedalBoard)
{
write(RingBufferCommand::ReplaceEffect, pedalBoard);
}
void AudioStopped()
{
AudioStoppedBody body;
write(RingBufferCommand::AudioStopped, body);
}
void EffectReplaced(Lv2PedalBoard *pedalBoard)
{
write(RingBufferCommand::EffectReplaced, pedalBoard);
}
};
} //namespace
+32
View File
@@ -0,0 +1,32 @@
#include "pch.h"
class TargetClass
{
public:
void read(int*t) { }
void read(double*t) { }
void read2(double*t) { }
};
#ifdef JUNK
template <typename T>
class HasJsonRead {
template <typename TYPE, typename ARG>
static std::true_type test (decltype(TargetClass().read((ARG*)nullptr))*v) { return std::true_type();};
template <typename TYPE, typename ARG>
static std::false_type test (...);
public:
static constexpr bool value = decltype(test<TargetClass,T>(nullptr))::value;
};
class X{
static_assert(HasJsonRead<float>::value,"TEST FAILED.");
};
#endif
+4
View File
@@ -0,0 +1,4 @@
#pragma once
void Shutdown(); // implemented in main.cpp
+140
View File
@@ -0,0 +1,140 @@
#include "pch.h"
#include "ShutdownClient.hpp"
#include "PiPedalException.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Lv2Log.hpp"
#include <sstream>
using namespace pipedal;
extern uint16_t g_ShutdownPort; // set in main.cpp
bool ShutdownClient::CanUseShutdownClient()
{
return g_ShutdownPort != 0;
}
bool ShutdownClient::RequestShutdown(bool restart)
{
const char*message = restart? "restart\n": "shutdown\n";
return WriteMessage(message);
}
bool ShutdownClient::WriteMessage(const char*message) {
uint16_t shutdownServerPort = g_ShutdownPort;
if (g_ShutdownPort == 0)
{
throw PiPedalException("No shutdown server available.");
}
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(shutdownServerPort);
if (inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr) <= 0)
{
Lv2Log::error("Failed to parse socket address.");
return false;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
Lv2Log::error("Can't create socket.");
return false;
}
if (connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)
{
std::stringstream s;
s << "Connect to 127.0.0.1:" << shutdownServerPort << " failed.";
Lv2Log::error(s.str());
close(sock);
return false;
}
int length = strlen(message);
const char *p = message;
bool success = true;
while (length != 0) {
auto nWritten = write(sock,p,length);
if (nWritten < 0)
{
success = false;
Lv2Log::error("Shutdown server write failed.");
close(sock);
return false;
}
p += nWritten;
length -= nWritten;
}
shutdown(sock, SHUT_WR);
char responseBuffer[1024];
ssize_t available = sizeof(responseBuffer);
char *pWrite = responseBuffer;
char*pEndOfLine = responseBuffer;
bool eolFound = false;
while (!eolFound)
{
ssize_t nRead = read(sock,pWrite,available);
if (nRead <= 0) {
Lv2Log::error("Failed to read shutdown server response.");
close(sock);
return false;
}
pWrite += nRead;
available -= nRead;
if (available == 0)
{
Lv2Log::error("Bad response from shutdown server.");
close(sock);
return false;
}
while (pEndOfLine != pWrite)
{
if (*pEndOfLine++ == '\n')
{
eolFound = true;
pEndOfLine[-1] = '\0';
break;
}
}
}
int response = atoi(responseBuffer);
close(sock);
if (response == -2) // throw exception with message
{
const char *p = responseBuffer;
while (*p != ' ' && *p != '\n' && *p != '\0')
{
++p;
}
if (*p != 0) ++p;
throw PiPedalStateException(p);
}
return response == 0;
}
bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackServerSettings)
{
std::stringstream s;
s << "setJackConfiguration "
<< jackServerSettings.GetSampleRate()
<< " " << jackServerSettings.GetBufferSize()
<< " " << jackServerSettings.GetNumberOfBuffers()
<< "\n";
return WriteMessage(s.str().c_str());
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <string>
#include "JackServerSettings.hpp"
namespace pipedal {
class ShutdownClient {
static bool WriteMessage(const char*message);
public:
static bool CanUseShutdownClient();
static bool RequestShutdown(bool restart);
static bool SetJackServerConfiguration(const JackServerSettings & jackServerSettings);
};
} // namespace
+350
View File
@@ -0,0 +1,350 @@
// shim file to allow shutdown to be called from a service.
// (Gets suid root permissions during install, since otherwise
// non-interactive services are not allowed to execute)
#include "pch.h"
#include "CommandLineParser.hpp"
#include <iostream>
#include <cstdint>
#include <boost/asio.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <iostream>
#include <boost/bind.hpp>
#include <memory>
#include <stdlib.h>
#include "JackServerSettings.hpp"
#include <cstdlib>
#include "Lv2Log.hpp"
#include "Lv2SystemdLogger.hpp"
using namespace std;
using namespace pipedal;
using namespace boost;
using namespace boost::asio;
using ip::tcp;
const size_t MAX_LENGTH = 128;
static bool startsWith(const std::string&s, const char*text)
{
if (s.length() < strlen(text)) return false;
const char*sp = s.c_str();
while (*text)
{
if (*text++ != *sp++) return false;
}
return true;
}
static std::vector<std::string> tokenize(std::string value)
{
std::vector<std::string> result;
std::stringstream s(value);
std::string item;
while (std::getline(s,item,' '))
{
if (item.length() != 0)
result.push_back(item);
}
return result;
}
static void CaptureAccessPoint(const std::string gatewayAddress)
{
}
static void ReleaseAccessPoint(const std::string gatewayAddress)
{
}
bool setJackConfiguration(uint32_t sampleRate,uint32_t bufferSize,uint32_t numberOfBuffers)
{
bool success = true;
JackServerSettings serverSettings(sampleRate,bufferSize,numberOfBuffers);
try {
serverSettings.Write();
} catch (const std::exception &e) {
std::stringstream s;
s << "Failed to write jackdrc settings. " << e.what();
Lv2Log::error(s.str());
success = false;
}
::system("pulseaudio --kill");
if (::system("systemctl restart jack") != 0)
{
Lv2Log::error("Failed to restart jack server.");
success = false;
}
return false;
}
class Reader : public std::enable_shared_from_this<Reader>
{
private:
tcp::socket socket;
int readAvailable = MAX_LENGTH;
char data[MAX_LENGTH];
char *writePointer;
ssize_t writeLength;
std::stringstream message;
size_t writePosition = 0;
std::string response;
public:
Reader(io_service &ios)
: socket(ios)
{
}
static std::shared_ptr<Reader> Create(io_service &ios)
{
return std::make_shared<Reader>(ios);
}
tcp::socket &Socket() { return socket; }
void Start()
{
ReadSome();
}
private:
void ReadSome()
{
socket.async_read_some(
boost::asio::buffer(data, readAvailable),
boost::bind(&Reader::HandleRead,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void WriteSome()
{
if (writePosition == response.size())
{
socket.close();
return;
}
socket.async_write_some(
boost::asio::buffer(response.c_str()+ writePosition, response.length()-writePosition),
boost::bind(&Reader::HandleWrite,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void HandleWrite(const boost::system::error_code &err, size_t bytes_transferred)
{
writePosition += bytes_transferred;
WriteSome();
}
void HandleRead(const boost::system::error_code &err, size_t bytes_transferred)
{
if (!err)
{
for (size_t i = 0; i < bytes_transferred; ++i)
{
char c = data[i];
if (c == '\n')
{
std::string command = message.str();
cout << "Received command: " << command << endl;
HandleCommand(command);
socket.close();
return;
}
else
{
message.put(data[i]);
}
}
ReadSome();
}
else
{
socket.close();
}
}
void HandleCommand(const std::string &s)
{
int result = -1;
try {
if (startsWith(s,"release_ap "))
{
std::vector<std::string> argv = tokenize(s);
if (argv.size() == 2) {
ReleaseAccessPoint(argv[1]);
result = 0;
} else {
throw PiPedalArgumentException("Invalid arguments.");
}
} else if (startsWith(s,"capture_ap "))
{
std::vector<std::string> argv = tokenize(s);
if (argv.size() == 2) {
CaptureAccessPoint(argv[1]);
result = 0;
} else {
throw PiPedalArgumentException("Invalid arguments.");
}
} else if (s == "shutdown")
{
result = ::system("/usr/sbin/shutdown -P now");
}
else if (s == "restart")
{
result = ::system("/usr/sbin/shutdown -r now");
} else if (startsWith(s,"setJackConfiguration "))
{
auto remainder = s.substr(strlen("setJackConfiguration "));
std::stringstream input(remainder);
uint32_t sampleRate;
uint32_t bufferSize;
uint32_t numberOfBuffers;
input >> sampleRate >> bufferSize >> numberOfBuffers;
if (input.fail())
{
result = -1;
} else {
result = setJackConfiguration(sampleRate,bufferSize,numberOfBuffers) ? 0: -1;
}
}
} catch (const std::exception &e)
{
std::stringstream t;
t << "-2 " << e.what();
this->response = t.str();
this->writePosition = 0;
WriteSome();
return;
}
std::stringstream t;
t << result << "\n";
this->response = t.str();
this->writePosition = 0;
WriteSome();
}
};
class Server
{
private:
tcp::acceptor acceptor_;
void HandleAccept(std::shared_ptr<Reader> connection, const boost::system::error_code &err)
{
if (!err)
{
connection->Start();
}
StartAccept();
}
void StartAccept()
{
// socket
std::shared_ptr<Reader> reader = Reader::Create(acceptor_.get_io_service());
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(reader->Socket(),
boost::bind(&Server::HandleAccept, this, reader,
boost::asio::placeholders::error));
}
public:
//constructor for accepting connection from client
Server(boost::asio::io_service &io_service, const tcp::endpoint &endpoint)
: acceptor_(io_service, endpoint)
{
StartAccept();
}
~Server() {
}
};
void runServer(uint16_t port)
{
asio::ip::tcp::endpoint endpoint(ip::make_address_v4("127.0.0.1"), port);
asio::io_service ios;
try {
Server server(ios,endpoint);
ios.run();
cout << "Processing terminated." << endl;
} catch (const std::exception & e)
{
cout << "Error: " << e.what() << endl;
}
}
int main(int argc, char **argv)
{
Lv2Log::set_logger(MakeLv2SystemdLogger());
CommandLineParser parser;
uint16_t port = 0;
std::string captureAccessPoint;
std::string releaseAccessPoint;
parser.AddOption("-port", &port);
parser.AddOption("-captureAP",&captureAccessPoint);
parser.AddOption("-releaseAP",&releaseAccessPoint);
try
{
parser.Parse(argc, (const char **)argv);
if (parser.Arguments().size() != 0)
{
throw PiPedalException("Invalid argument.");
}
}
catch (const std::exception &e)
{
cout << "Error: " << e.what() << endl;
return EXIT_FAILURE;
}
if (captureAccessPoint.length() != 0)
{
try{
CaptureAccessPoint(captureAccessPoint);
} catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
std::cout << "Access point captured." << std::endl;
} else if (releaseAccessPoint.length() != 0)
{
try{
ReleaseAccessPoint(releaseAccessPoint);
} catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
std::cout << "Access point released." << std::endl;
} else {
if (port == 0) {
std::cerr << "Error: port not specified." << std::endl;
return EXIT_FAILURE;
}
runServer(port);
}
return EXIT_SUCCESS;
}
+165
View File
@@ -0,0 +1,165 @@
#include "pch.h"
#include "SplitEffect.hpp"
#include "Lv2Host.hpp"
#include "PedalBoard.hpp"
using namespace pipedal;
static std::shared_ptr<Lv2PortInfo> MakeBypassPortInfo() {
std::shared_ptr<Lv2PortInfo> bypassPortInfo = std::make_shared<Lv2PortInfo>();
bypassPortInfo->symbol("__bypass");
bypassPortInfo->name("Bypass");
bypassPortInfo->is_input(true);
bypassPortInfo->is_control_port(true);
bypassPortInfo->index(-1);
bypassPortInfo->min_value(0);
bypassPortInfo->max_value(1);
bypassPortInfo->default_value(1);
bypassPortInfo->toggled_property(true);
return bypassPortInfo;
}
std::shared_ptr<Lv2PortInfo> g_BypassPortInfo = MakeBypassPortInfo();
// Pseudo-port info used to perform midi port value mapping.
const Lv2PortInfo *pipedal::GetBypassPortInfo()
{
return g_BypassPortInfo.get();
}
// Just enough to do control value defaulting, and midi value mapping :-/
static Lv2PluginInfo makeSplitterPluginInfo() {
Lv2PluginInfo result;
result.uri(SPLIT_PEDALBOARD_ITEM_URI);
result.name("Split");
result.author_name("Robin Davies");
result.comment("Internal only.");
result.is_valid(true);
std::shared_ptr<Lv2PortInfo> splitTypeControl= std::make_shared<Lv2PortInfo>();
splitTypeControl->symbol(SPLIT_SPLITTYPE_KEY);
splitTypeControl->name("Type");
splitTypeControl->is_input(true);
splitTypeControl->is_control_port(true);
splitTypeControl->index(0);
splitTypeControl->min_value(0);
splitTypeControl->max_value(2);
splitTypeControl->default_value(0);
splitTypeControl->enumeration_property(true);
splitTypeControl->scale_points().push_back(
Lv2ScalePoint(0,"A/B")
);
splitTypeControl->scale_points().push_back(
Lv2ScalePoint(1,"Mix")
);
splitTypeControl->scale_points().push_back(
Lv2ScalePoint(1,"L/R")
);
result.ports().push_back(splitTypeControl);
std::shared_ptr<Lv2PortInfo> abControl= std::make_shared<Lv2PortInfo>();
abControl->symbol(SPLIT_SELECT_KEY);
abControl->name("Select");
abControl->is_input(true);
abControl->is_control_port(true);
abControl->index(1);
abControl->min_value(0);
abControl->max_value(1);
abControl->default_value(0);
abControl->enumeration_property(true);
abControl->scale_points().push_back(
Lv2ScalePoint(0,"A")
);
abControl->scale_points().push_back(
Lv2ScalePoint(1,"B")
);
result.ports().push_back(abControl);
std::shared_ptr<Lv2PortInfo> mixControl= std::make_shared<Lv2PortInfo>();
mixControl->symbol(SPLIT_MIX_KEY);
mixControl->name("Mix");
mixControl->is_input(true);
mixControl->is_control_port(true);
mixControl->index(2);
mixControl->min_value(-1);
mixControl->max_value(1);
mixControl->default_value(0);
result.ports().push_back(mixControl);
std::shared_ptr<Lv2PortInfo> panLControl= std::make_shared<Lv2PortInfo>();
panLControl->symbol(SPLIT_PANL_KEY);
panLControl->name("Pan L");
panLControl->is_input(true);
panLControl->is_control_port(true);
panLControl->index(3);
panLControl->min_value(-1);
panLControl->max_value(1);
panLControl->default_value(0);
result.ports().push_back(panLControl);
std::shared_ptr<Lv2PortInfo> volLControl= std::make_shared<Lv2PortInfo>();
volLControl->symbol(SPLIT_VOLL_KEY);
volLControl->name("Vol L");
volLControl->is_input(true);
volLControl->is_control_port(true);
volLControl->index(4);
volLControl->min_value(-60);
volLControl->max_value(12);
volLControl->default_value(-3);
volLControl->scale_points().push_back(
Lv2ScalePoint(-60,"-INF")
);
result.ports().push_back(volLControl);
std::shared_ptr<Lv2PortInfo> panRControl= std::make_shared<Lv2PortInfo>();
panRControl->symbol(SPLIT_PANR_KEY);
panRControl->name("Pan R");
panRControl->is_input(true);
panRControl->is_control_port(true);
panRControl->index(5);
panRControl->min_value(-1);
panRControl->max_value(1);
panRControl->default_value(0);
result.ports().push_back(panRControl);
std::shared_ptr<Lv2PortInfo> volRControl= std::make_shared<Lv2PortInfo>();
volRControl->symbol(SPLIT_VOLR_KEY);
volRControl->name("Vol R");
volRControl->is_input(true);
volRControl->is_control_port(true);
volRControl->index(6);
volRControl->min_value(-60);
volRControl->max_value(12);
volRControl->default_value(-3);
volRControl->scale_points().push_back(
Lv2ScalePoint(-60,"-INF")
);
result.ports().push_back(volRControl);
return result;
}
Lv2PluginInfo g_splitterPluginInfo = makeSplitterPluginInfo();
const Lv2PluginInfo * pipedal::GetSplitterPluginInfo() { return &g_splitterPluginInfo; }
+652
View File
@@ -0,0 +1,652 @@
#pragma once
#include "IEffect.hpp"
#include "PiPedalException.hpp"
#include "PiPedalMath.hpp"
namespace pipedal
{
enum class SplitType
{
Ab = 0,
Mix = 1,
Lr = 2
};
class Lv2PluginInfo;
class Lv2PortInfo;
const Lv2PluginInfo *GetSplitterPluginInfo();
const Lv2PortInfo *GetBypassPortInfo();
const double SPLIT_DB_MIN = -60;
inline SplitType valueToSplitType(float value)
{
switch ((int)value)
{
case 0:
return SplitType::Ab;
case 1:
return SplitType::Mix;
case 2:
return SplitType::Lr;
default:
return SplitType::Ab;
}
};
class SplitEffect : public IEffect
{
private:
const double MIX_TRANSITION_TIME_S = 0.1;
double sampleRate;
std::vector<float *> inputs;
std::vector<float *> topInputs;
std::vector<float *> bottomInputs;
std::vector<float *> topOutputs;
std::vector<float *> bottomOutputs;
std::vector<float *> mixTopInputs;
std::vector<float *> mixBottomInputs;
std::vector<float *> outputBuffers;
int numberOfOutputPorts;
SplitType splitType = SplitType::Ab;
float mix = 0;
float panL = 0;
float volL = -3;
float panR = 0;
float volR = -3;
float currentMix = 0;
float targetBlendLTop = 0.5;
float targetBlendRTop = 0.5;
float targetBlendLBottom = 0.5;
float targetBlendRBottom = 0.5;
float blendLTop = 0.5;
float blendRTop = 0.5;
float blendLBottom = 0.5;
float blendRBottom = 0.5;
float blendDxLTop = 0;
float blendDxRTop = 0;
float blendDxLBottom = 0;
float blendDxRBottom = 0;
int32_t blendFadeSamples;
bool selectA = true;
bool activated = false;
using MixFunction = void (SplitEffect::*)(uint32_t frames);
MixFunction preAbTop;
MixFunction preAbBottom;
void Copy(float *input, float *output, uint32_t frames)
{
for (int i = 0; i < frames; ++i)
{
output[i] = input[i];
}
}
void AbTopMonoMono(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
}
void AbTopMonoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
Copy(this->inputs[0], this->topInputs[1], frames);
}
void AbTopStereoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
Copy(this->inputs[1], this->topInputs[1], frames);
}
void AbBottomMonoMono(uint32_t frames)
{
Copy(this->inputs[0], this->bottomInputs[0], frames);
}
void AbBottomMonoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->bottomInputs[0], frames);
Copy(this->inputs[0], this->bottomInputs[1], frames);
}
void AbBottomStereoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->bottomInputs[0], frames);
Copy(this->inputs[1], this->bottomInputs[1], frames);
}
void LrTopMonoMono(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
}
void LrTopMonoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
Copy(this->inputs[0], this->topInputs[1], frames);
}
void LrTopStereoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->topInputs[0], frames);
Copy(this->inputs[0], this->topInputs[1], frames);
}
void LrBottomMonoMono(uint32_t frames)
{
Copy(this->inputs[0], this->bottomInputs[0], frames);
}
void LrBottomMonoStereo(uint32_t frames)
{
Copy(this->inputs[0], this->bottomInputs[0], frames);
Copy(this->inputs[0], this->bottomInputs[1], frames);
}
void LrBottomStereoStereo(uint32_t frames)
{
Copy(this->inputs[1], this->bottomInputs[0], frames);
Copy(this->inputs[1], this->bottomInputs[1], frames);
}
void UpdateMixFunction()
{
if (activated)
{
// Input Mix Functions.
if (splitType != SplitType::Lr)
{
if (this->inputs.size() == 1)
{
if (this->topInputs.size() == 1)
this->preAbTop = &SplitEffect::AbTopMonoMono;
else
{
this->preAbTop = &SplitEffect::AbTopMonoStereo;
}
if (this->bottomInputs.size() == 1)
{
this->preAbBottom = &SplitEffect::AbBottomMonoMono;
}
else
{
this->preAbBottom = &SplitEffect::AbBottomMonoStereo;
}
}
else
{
if (this->topInputs.size() == 1)
{
this->preAbTop == &SplitEffect::AbTopMonoMono;
}
else
{
this->preAbTop = &SplitEffect::AbTopStereoStereo;
}
if (this->bottomInputs.size() == 1)
{
this->preAbBottom == &SplitEffect::AbBottomMonoMono;
}
else
{
this->preAbBottom = &SplitEffect::AbBottomStereoStereo;
}
}
}
else
{
if (this->inputs.size() == 1)
{
if (this->topInputs.size() == 1)
this->preAbTop = &SplitEffect::LrTopMonoMono;
else
{
this->preAbTop = &SplitEffect::LrTopMonoStereo;
}
if (this->bottomInputs.size() == 1)
{
this->preAbBottom = &SplitEffect::LrBottomMonoMono;
}
else
{
this->preAbBottom = &SplitEffect::LrBottomMonoStereo;
}
}
else
{
if (this->topInputs.size() == 1)
{
this->preAbTop == &SplitEffect::LrTopMonoMono;
}
else
{
this->preAbTop = &SplitEffect::LrTopStereoStereo;
}
if (this->bottomInputs.size() == 1)
{
this->preAbBottom == &SplitEffect::LrBottomMonoMono;
}
else
{
this->preAbBottom = &SplitEffect::LrBottomStereoStereo;
}
}
}
if (splitType == SplitType::Ab)
{
mixTo(selectA ? -1 : 1);
}
else if (splitType == SplitType::Mix)
{
mixTo(mix);
}
else
{
mixTo(panL, volL, panR, volR);
}
}
}
long instanceId;
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
virtual void RequestParameter(LV2_URID uridUri) {}
virtual void GatherParameter(RealtimeParameterRequest *pRequest) {}
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
public:
SplitEffect(
long instanceId,
double sampleRate,
const std::vector<float *> &inputs)
: instanceId(instanceId), inputs(inputs), sampleRate(sampleRate)
{
}
~SplitEffect()
{
}
virtual long GetInstanceId() const
{
return instanceId;
}
void Activate()
{
activated = true;
UpdateMixFunction();
snapToMixTarget();
mixBottomInputs.clear();
mixTopInputs.clear();
if (outputBuffers.size() == 1)
{
mixTopInputs.push_back(this->topOutputs[0]);
mixBottomInputs.push_back(this->bottomOutputs[0]);
}
else
{
if (this->topOutputs.size() == 1)
{
mixTopInputs.push_back(topOutputs[0]);
mixTopInputs.push_back(topOutputs[0]);
}
else
{
mixTopInputs.push_back(topOutputs[0]);
mixTopInputs.push_back(topOutputs[1]);
}
if (this->bottomOutputs.size() == 1)
{
mixBottomInputs.push_back(bottomOutputs[0]);
mixBottomInputs.push_back(bottomOutputs[0]);
}
else
{
mixBottomInputs.push_back(bottomOutputs[0]);
mixBottomInputs.push_back(bottomOutputs[1]);
}
}
}
void Deactivate()
{
activated = false;
}
void SetChainBuffers(
const std::vector<float *> &topInputs,
const std::vector<float *> &bottomInputs,
const std::vector<float *> &topOutputs,
const std::vector<float *> &bottomOutputs)
{
this->topInputs = topInputs;
this->bottomInputs = bottomInputs;
this->topOutputs = topOutputs;
this->bottomOutputs = bottomOutputs;
numberOfOutputPorts = topOutputs.size() > 1 || bottomOutputs.size() > 1 ? 2 : 1;
outputBuffers.resize(numberOfOutputPorts);
}
virtual void ResetAtomBuffers() {}
virtual int GetControlIndex(const std::string &symbol) const
{
if (symbol == "splitType")
return 0;
if (symbol == "select")
return 1;
if (symbol == "mix")
return 2;
return -1;
}
void snapToMixTarget()
{
this->blendLTop = targetBlendLTop;
this->blendRTop = targetBlendRTop;
this->blendRBottom = targetBlendRBottom;
this->blendLBottom = targetBlendLBottom;
this->blendFadeSamples = 0;
this->blendDxLTop = 0;
this->blendDxRTop = 0;
this->blendDxLBottom = 0;
this->blendDxRBottom = 0;
}
void mixToTarget()
{
uint32_t transitionSamples = (uint32_t)(this->sampleRate * MIX_TRANSITION_TIME_S);
if (transitionSamples < 1)
transitionSamples = 1;
double dxScale = 1.0 / transitionSamples;
this->blendFadeSamples = transitionSamples;
this->blendDxLTop = dxScale * (this->targetBlendLTop - this->blendLTop);
this->blendDxRTop = dxScale * (this->targetBlendRTop - this->blendRTop);
this->blendDxLBottom = dxScale * (this->targetBlendLBottom - this->blendLBottom);
this->blendDxRBottom = dxScale * (this->targetBlendRBottom - this->blendRBottom);
}
void mixTo(float value)
{
float blend = (value + 1) * 0.5f;
this->targetBlendRTop = this->targetBlendLTop = 1 - blend;
this->targetBlendLBottom = this->targetBlendLBottom = blend;
mixToTarget();
}
void mixTo(float panL, float volL, float panR, float volR)
{
float aTop = (volL <= SPLIT_DB_MIN) ? 0 : db2a(volL);
float aBottom = (volL <= SPLIT_DB_MIN) ? 0 : db2a(volR);
if (this->outputBuffers.size() == 1)
{
// ignore pan. The R values actually have no effect.
this->targetBlendLTop = this->targetBlendRTop = aTop;
this->targetBlendLBottom = this->targetBlendRBottom = aBottom;
}
else
{
float blendTop = (panL + 1) * 0.5;
float blendBottom = (panR + 1) * 0.5;
this->targetBlendLTop = (1 - blendTop) * aTop;
this->targetBlendRTop = (blendTop)*aTop;
this->targetBlendLBottom = (1 - blendBottom) * aBottom;
this->targetBlendRBottom = (blendBottom)*aBottom;
}
mixToTarget();
}
virtual void SetBypass(bool enabled)
{
throw PiPedalArgumentException("Not implmented. Should not have been called.");
}
virtual float GetOutputControlValue(int index) const {
return GetControlValue(index);
}
virtual float GetControlValue(int portIndex) const
{
switch (portIndex)
{
case -1: /* (bypass) */
return 1;
case 0:
{
return (int)(this->splitType);
}
case 1:
{
return selectA ? 0 : 1;
}
case 2:
return mix;
case 3:
return this->panL;
case 4:
return this->volL;
case 5:
return this->panR;
case 6:
return this->volR;
default:
throw PiPedalArgumentException("Invalid argument");
}
}
virtual void SetControl(int index, float value)
{
switch (index)
{
case -1: /* (bypass) */
return; // no can bypass.
case 0:
{
SplitType t = valueToSplitType(value);
if (splitType != t)
{
splitType = t;
UpdateMixFunction();
}
break;
}
case 1:
{
bool t = value == 0;
if (selectA != t)
{
selectA = t;
if (splitType == SplitType::Ab)
{
mixTo(selectA ? -1 : 1);
}
}
break;
}
case 2:
mix = value;
if (splitType == SplitType::Mix)
{
mixTo(value);
}
break;
case 3:
panL = value;
if (splitType == SplitType::Lr)
{
mixTo(panL, volL, panR, volR);
}
break;
case 4:
volL = value;
if (splitType == SplitType::Lr)
{
mixTo(panL, volL, panR, volR);
}
break;
case 5:
panR = value;
if (splitType == SplitType::Lr)
{
mixTo(panL, volL, panR, volR);
}
break;
case 6:
volR = value;
if (splitType == SplitType::Lr)
{
mixTo(panL, volL, panR, volR);
}
break;
}
}
virtual int GetNumberOfOutputAudioPorts() const
{
return numberOfOutputPorts;
}
virtual int GetNumberOfInputAudioPorts() const
{
return (int)this->inputs.size();
}
virtual float *GetAudioInputBuffer(int index) const
{
return inputs[index];
}
virtual float *GetAudioOutputBuffer(int index) const
{
return this->outputBuffers[index];
}
virtual void SetAudioOutputBuffer(int index, float *buffer)
{
outputBuffers[index] = buffer;
}
void PreMix(uint32_t frames)
{
(this->*preAbTop)(frames);
(this->*preAbBottom)(frames);
}
void PostMixMono(uint32_t frames)
{
uint32_t ix = 0;
float *top = this->mixTopInputs[0];
float *bottom = this->mixBottomInputs[0];
float *output = this->outputBuffers[0];
while (frames)
{
if (this->blendFadeSamples != 0)
{
uint32_t framesThisTime = this->blendFadeSamples < frames ? this->blendFadeSamples : frames;
for (uint32_t i = 0; i < framesThisTime; ++i)
{
output[ix] = blendLBottom * bottom[ix] + blendLTop * top[ix];
++ix;
this->blendLTop += this->blendDxLTop;
this->blendLBottom += this->blendDxLBottom;
}
this->blendFadeSamples -= framesThisTime;
frames -= framesThisTime;
if (blendFadeSamples == 0)
{
this->blendLTop = this->targetBlendLTop;
this->blendLBottom = this->targetBlendLBottom;
this->blendDxLTop = this->blendDxLBottom = 0;
}
}
else
{
float blendTop = this->blendLTop;
float blendBottom = this->blendLBottom;
while (frames--)
{
output[ix] = blendBottom * bottom[ix] + blendTop * top[ix];
++ix;
}
return;
}
}
}
void PostMixStereo(uint32_t frames)
{
uint32_t ix = 0;
float *top = this->mixTopInputs[0];
float *bottom = this->mixBottomInputs[0];
float *output = this->outputBuffers[0];
float *topR = this->mixTopInputs[1];
float *bottomR = this->mixBottomInputs[1];
float *outputR = this->outputBuffers[1];
while (frames != 0)
{
if (this->blendFadeSamples != 0)
{
uint32_t framesThisTime = this->blendFadeSamples < frames ? this->blendFadeSamples : frames;
for (int i = 0; i < framesThisTime; ++i)
{
output[ix] = blendLBottom * bottom[ix] + blendLTop * top[ix];
outputR[ix] = blendRBottom * bottomR[ix] + blendRTop * topR[ix];
++ix;
this->blendLTop += this->blendDxLTop;
this->blendRTop += this->blendDxRTop;
this->blendLBottom += this->blendDxLBottom;
this->blendRBottom += this->blendDxRBottom;
}
blendFadeSamples -= framesThisTime;
frames -= framesThisTime;
if (blendFadeSamples == 0)
{
this->blendLTop = this->targetBlendLTop;
this->blendRTop = this->targetBlendRTop;
this->blendLBottom = this->targetBlendLBottom;
this->blendRBottom = this->targetBlendRBottom;
this->blendDxLTop = this->blendDxRTop = this->blendDxLBottom = this->blendDxRBottom = 0;
}
}
else
{
float blendLTop = this->blendLTop;
float blendRTop = this->blendRTop;
float blendLBottom = this->blendLBottom;
float blendRBottom = this->blendRBottom;
for (int i = 0; i < frames; ++i)
{
output[ix] = blendLBottom * bottom[ix] + blendLTop * top[ix];
outputR[ix] = blendRBottom * bottomR[ix] + blendRTop * topR[ix];
++ix;
}
return;
}
}
}
void PostMix(uint32_t frames)
{
if (this->outputBuffers.size() == 1)
{
PostMixMono(frames);
}
else
{
PostMixStereo(frames);
}
}
};
} // namespace
+685
View File
@@ -0,0 +1,685 @@
#include "pch.h"
#include "Storage.hpp"
#include "PiPedalException.hpp"
#include <sstream>
#include "json.hpp"
#include <fstream>
#include "Lv2Log.hpp"
using namespace pipedal;
const char *BANK_EXTENSION = ".bank";
const char *BANKS_FILENAME = "index.banks";
Storage::Storage()
{
SetDataRoot("~/var/PiPedal");
}
inline bool isSafeCharacter(char c)
{
return (c >= '0' && c <= '9') | (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-';
}
static int fromhexStrict(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
// lowercase letters won't round-trip properly.
return -1;
}
std::string Storage::SafeDecodeName(const std::string &name)
{
std::stringstream s;
for (int i = 0; i < name.length(); /**/)
{
char c = name[i++];
if (c < ' ')
throw PiPedalArgumentException("Unsafe file name.");
if (c == '+')
{
s << ' ';
}
else if (c == '%')
{
if (i + 2 > name.length())
{
throw PiPedalArgumentException("Unsafe file name.");
}
int v1 = fromhexStrict(name[i]);
int v2 = fromhexStrict(name[i + 1]);
i += 2;
if (v1 == -1 || v2 == -1)
throw PiPedalArgumentException("Unsafe file name.");
char c = (char)((v1 << 4) + v2);
if (isSafeCharacter(c))
{
// name won't round-trip because the escape will not be re-encoded.
throw PiPedalArgumentException("Unsafe file name.");
}
s << c;
}
else
{
if (isSafeCharacter(c))
{
s << c;
}
else
{
throw PiPedalArgumentException("Unsafe file name.");
}
}
}
return s.str();
}
static const char *hex = "0123456789ABCDEF";
std::string Storage::SafeEncodeName(const std::string &name)
{
std::stringstream s;
for (char c : name)
{
if (c < ' ')
c = ' ';
bool safe = isSafeCharacter(c);
if (safe)
{
s << c;
}
else if (c == ' ')
{
s << '+';
}
else
{
s << '%' << hex[(c >> 4) & 0x0F] << hex[(c)&0x0F];
}
}
return s.str();
}
std::filesystem::path ResolveHomePath(std::filesystem::path path)
{
if (path.begin() == path.end())
return path;
// is the first element "~"?
auto it = path.begin();
auto el = (*it);
if (el != "~")
return path;
const char *homeDirectory = nullptr;
homeDirectory = getenv("HOME");
if (homeDirectory == nullptr)
{
homeDirectory = getenv("USERPROFILE");
}
std::filesystem::path result = homeDirectory;
bool first = true;
for (auto i = path.begin(); i != path.end(); ++i)
{
if (!first)
{
result /= *i;
}
first = false;
}
return result;
}
void Storage::SetDataRoot(const char *path)
{
this->dataRoot = ResolveHomePath(path);
}
void Storage::Initialize()
{
try
{
std::filesystem::create_directories(this->GetPresetsDirectory());
}
catch (const std::exception &e)
{
throw PiPedalStateException("Can't create presets directory. (" + (std::string)this->GetPresetsDirectory() + ") (" + e.what() + ")");
}
LoadBankIndex();
LoadCurrentBank();
try
{
LoadChannelSelection();
}
catch (const std::exception &)
{
}
}
void Storage::LoadBank(int64_t instanceId)
{
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
LoadBankFile(indexEntry.name(), &(this->currentBank));
if (this->bankIndex.selectedBank() != instanceId)
{
this->bankIndex.selectedBank(instanceId);
SaveBankIndex();
}
this->LoadPreset(this->bankIndex.selectedBank());
}
void Storage::LoadCurrentBank()
{
LoadBank(bankIndex.selectedBank());
}
std::filesystem::path Storage::GetPresetsDirectory() const
{
return this->dataRoot / "presets";
}
std::filesystem::path Storage::GetChannelSelectionFileName()
{
return this->dataRoot / "JackChannelSelection.json";
}
std::filesystem::path Storage::GetIndexFileName()
{
return this->GetPresetsDirectory() / BANKS_FILENAME;
}
std::filesystem::path Storage::GetBankFileName(const std::string &name)
{
std::string fileName = SafeEncodeName(name) + BANK_EXTENSION;
return this->GetPresetsDirectory() / fileName;
}
void Storage::LoadBankIndex()
{
try
{
auto path = GetIndexFileName();
std::ifstream s;
s.open(path);
json_reader reader(s);
reader.read(&bankIndex);
}
catch (const std::exception &)
{
bankIndex.clear();
ReIndex();
if (bankIndex.entries().size() == 0)
{
currentBank.clear();
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
int64_t instanceId = currentBank.addPreset(defaultPedalBoard);
currentBank.selectedPreset(instanceId);
std::string name = "Default Bank";
SaveBankFile(name, currentBank);
int64_t selectedBank = bankIndex.addBank(-1,name);
bankIndex.selectedBank(selectedBank);
}
SaveBankIndex();
}
}
void Storage::SaveBankIndex()
{
std::ofstream os;
os.open(GetIndexFileName(), std::ios_base::trunc);
json_writer writer(os);
writer.write(this->bankIndex);
}
void Storage::ReIndex()
{
for (const auto &dirEntry : std::filesystem::directory_iterator(GetPresetsDirectory()))
{
if (!dirEntry.is_directory())
{
auto path = dirEntry.path();
if (path.extension() == BANK_EXTENSION)
{
std::string name = SafeDecodeName(path.stem());
bankIndex.addBank(-1,name);
}
}
}
if (bankIndex.entries().size() == 0)
{
CreateBank("Default Bank");
}
bankIndex.selectedBank(bankIndex.entries()[0].instanceId());
}
void Storage::CreateBank(const std::string &name)
{
BankFile bankFile;
PedalBoard defaultPreset = PedalBoard::MakeDefault();
defaultPreset.name(std::string("Default Preset"));
bankFile.addPreset(defaultPreset);
int64_t instanceId = bankFile.presets()[0]->instanceId();
bankFile.selectedPreset(instanceId);
SaveBankFile(name, bankFile);
this->bankIndex.addBank(-1,name);
this->SaveBankIndex();
}
void Storage::LoadBankFile(const std::string &name, BankFile *pBank)
{
std::filesystem::path fileName = GetBankFileName(name);
std::ifstream is(fileName);
json_reader reader(is);
reader.read(pBank);
}
void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
{
std::filesystem::path fileName = GetBankFileName(name);
std::filesystem::path backupFile = ((std::string)fileName) + ".$$$";
if (std::filesystem::exists(backupFile))
{
std::filesystem::remove(backupFile);
}
if (std::filesystem::exists(fileName))
{
std::filesystem::rename(fileName, backupFile);
}
try
{
std::ofstream s;
s.open(fileName, std::ios_base::trunc);
json_writer writer(s);
writer.write(bankFile);
if (std::filesystem::exists(backupFile))
{
std::filesystem::remove(backupFile);
}
}
catch (const std::exception &e)
{
std::filesystem::remove(fileName);
if (std::filesystem::exists(backupFile))
{
std::filesystem::rename(backupFile, fileName);
}
throw;
}
}
void Storage::SaveCurrentBank()
{
auto indexEntry = this->bankIndex.getBankIndexEntry(this->bankIndex.selectedBank());
SaveBankFile(indexEntry.name(), this->currentBank);
}
const PedalBoard &Storage::GetCurrentPreset()
{
auto &item = currentBank.getItem(currentBank.selectedPreset());
return item.preset();
}
bool Storage::LoadPreset(int64_t instanceId)
{
if (!currentBank.hasItem(instanceId))
return false;
currentBank.selectedPreset(instanceId);
SaveCurrentBank();
return true;
}
void Storage::saveCurrentPreset(const PedalBoard &pedalBoard)
{
auto &item = currentBank.getItem(currentBank.selectedPreset());
item.preset(pedalBoard);
SaveCurrentBank();
}
int64_t Storage::saveCurrentPresetAs(const PedalBoard &pedalBoard, const std::string &name, int64_t saveAfterInstanceId)
{
PedalBoard newPedalBoard = pedalBoard;
newPedalBoard.name(name);
int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId);
currentBank.selectedPreset(newInstanceId);
SaveCurrentBank();
return newInstanceId;
}
void Storage::SetPresetIndex(const PresetIndex &presets)
{
// painful because we must move unique_ptrs.
std::map<int64_t, std::unique_ptr<BankFileEntry> *> entries;
for (int i = 0; i < this->currentBank.presets().size(); ++i)
{
auto & preset = this->currentBank.presets()[i];
entries[preset->instanceId()] = &(this->currentBank.presets()[i]);
}
std::vector<std::unique_ptr<BankFileEntry>*> newPresets;
for (size_t i = 0; i < presets.presets().size(); ++i)
{
std::unique_ptr<BankFileEntry>* p = entries[presets.presets()[i].instanceId()];
if (p == nullptr)
{
throw PiPedalStateException("Presets do not match the currently loaded bank.");
}
newPresets.push_back(p);
}
// we made it this far. So now we know we can rebuild a new BankFile.
BankFile bankFile;
bankFile.presets().resize(newPresets.size());
for (int i = 0; i < presets.presets().size(); ++i)
{
std::unique_ptr<BankFileEntry> *oldBankFilePreset = newPresets[i];
bankFile.presets()[i] = std::move((*oldBankFilePreset));
}
bankFile.nextInstanceId(currentBank.nextInstanceId());
bankFile.selectedPreset(currentBank.selectedPreset());
this->currentBank = std::move(bankFile); // deleting any stray presets while we're at it.
this->SaveCurrentBank();
}
void Storage::GetPresetIndex(PresetIndex *pResult)
{
*pResult = PresetIndex();
pResult->selectedInstanceId(this->currentBank.selectedPreset());
for (auto &item : this->currentBank.presets())
{
PresetIndexEntry entry;
entry.instanceId(item->instanceId());
entry.name(item->preset().name());
pResult->presets().push_back(entry);
}
}
PedalBoard Storage::GetPreset(int64_t instanceId) const
{
for (size_t i = 0; i < currentBank.presets().size(); ++i)
{
if (currentBank.presets()[i]->instanceId() == instanceId)
{
return currentBank.presets()[i]->preset();
}
}
throw PiPedalException("Not found.");
}
int64_t Storage::DeletePreset(int64_t presetId)
{
int64_t newSelection = currentBank.deletePreset(presetId);
SaveCurrentBank();
return newSelection;
}
bool Storage::RenamePreset(int64_t presetId, const std::string &name)
{
if (this->currentBank.renamePreset(presetId, name))
{
SaveCurrentBank();
return true;
}
return false;
}
static int lastIndexOf(const char *sz, char c)
{
size_t len = strlen(sz);
for (size_t i = len - 1; i >= 0; --i)
{
if (sz[i] == c)
{
if (i > 0 && sz[i - 1] == ' ')
--i;
return i;
}
}
return -1;
}
std::string Storage::GetPresetCopyName(const std::string &name)
{
const char *str = name.c_str();
std::string stem;
if (name.length() != 0 && name[name.length() - 1] == ')')
{
const char *str = name.c_str();
size_t pos = lastIndexOf(str, '(');
if (pos == -1)
{
stem = name;
}
else
{
stem = name.substr(0, pos);
}
}
else
{
stem = name;
}
int copyNumber = 1;
while (true)
{
std::string candidate;
if (copyNumber == 1)
{
candidate = stem + " (Copy)";
}
else
{
std::stringstream s;
s << stem << " (Copy " << copyNumber << ")";
candidate = s.str();
}
if (!this->currentBank.hasName(candidate))
{
return candidate;
}
++copyNumber;
}
}
int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
{
auto &fromItem = this->currentBank.getItem(fromId);
if (toId == -1)
{
PedalBoard newPedalBoard = fromItem.preset();
std::string name = GetPresetCopyName(fromItem.preset().name());
newPedalBoard.name(name);
return this->currentBank.addPreset(newPedalBoard, fromId);
}
else
{
auto &toItem = this->currentBank.getItem(toId);
toItem.preset(fromItem.preset());
return toId;
}
}
void Storage::SetJackChannelSelection(const JackChannelSelection &channelSelection)
{
this->jackChannelSelection = channelSelection;
this->isJackChannelSelectionValid = true;
SaveChannelSelection();
}
const JackChannelSelection &Storage::GetJackChannelSelection(const JackConfiguration &jackConfiguration)
{
if (!this->isJackChannelSelectionValid)
{
if (jackConfiguration.isValid())
{
auto defaultSelection = JackChannelSelection::MakeDefault(jackConfiguration);
SetJackChannelSelection(defaultSelection);
return this->jackChannelSelection;
}
}
return jackChannelSelection;
}
void Storage::LoadChannelSelection()
{
auto fileName = this->GetChannelSelectionFileName();
if (std::filesystem::exists(fileName))
{
try
{
std::ifstream s(fileName);
json_reader reader(s);
reader.read(&this->jackChannelSelection);
this->isJackChannelSelectionValid = true;
}
catch (const std::exception &e)
{
Lv2Log::error("I/O error reading %s: %s", fileName.c_str(), e.what());
throw PiPedalStateException("Unexpected error reading Jack settings file.");
}
}
}
void Storage::SaveChannelSelection()
{
auto fileName = this->GetChannelSelectionFileName();
try
{
std::ofstream s(fileName);
json_writer writer(s);
writer.write(this->jackChannelSelection);
}
catch (const std::exception &e)
{
Lv2Log::error("I/O error writing %s: %s", fileName.c_str(), e.what());
throw PiPedalStateException("Unexpected error writing Jack settings file.");
}
}
void Storage::RenameBank(int64_t bankId, const std::string &newName)
{
auto existingBank = this->bankIndex.getEntryByName(newName);
if (existingBank != nullptr)
{
throw PiPedalStateException("A bank by that name already exists.");
}
auto &entry = this->bankIndex.getBankIndexEntry(bankId);
std::filesystem::path oldPath = this->GetBankFileName(entry.name());
std::filesystem::path newPath = this->GetBankFileName(newName);
try
{
std::filesystem::rename(oldPath, newPath);
}
catch (std::exception &e)
{
std::stringstream s;
s << "Unable to rename the bank. (" << e.what() << ")";
throw PiPedalException(s.str());
}
entry.name(newName);
SaveBankIndex();
}
int64_t Storage::SaveBankAs(int64_t bankId, const std::string &newName)
{
auto existingBank = this->bankIndex.getEntryByName(newName);
if (existingBank != nullptr)
{
throw PiPedalStateException("A bank by that name already exists.");
}
auto &entry = this->bankIndex.getBankIndexEntry(bankId);
std::filesystem::path oldPath = this->GetBankFileName(entry.name());
std::filesystem::path newPath = this->GetBankFileName(newName);
try
{
std::filesystem::copy(oldPath,newPath);
}
catch (std::exception &e)
{
std::stringstream s;
s << "Unable to save the bank. (" << e.what() << ")";
throw PiPedalException(s.str());
}
int64_t newId = this->bankIndex.addBank(bankId,newName);
SaveBankIndex();
return newId;
}
void Storage::MoveBank(int from, int to)
{
this->bankIndex.move(from,to);
this->SaveBankIndex();
}
int64_t Storage::DeleteBank(int64_t bankId)
{
auto & entries = this->bankIndex.entries();
for (size_t i = 0; i < entries.size(); ++i)
{
auto & entry = entries[i];
if (entry.instanceId() == bankId) {
std::filesystem::path fileName = this->GetBankFileName(entry.name());
entries.erase(entries.begin()+i);
int64_t newSelection;
if (i < entries.size())
{
newSelection = entries[i].instanceId();
} else if (entries.size() > 0)
{
newSelection = entries[entries.size()-1].instanceId();
} else {
// zero entries?
// Create a default empty bank.
BankIndexEntry newEntry;
BankFile defaultBank;
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
int64_t instanceId = defaultBank.addPreset(defaultPedalBoard);
defaultBank.selectedPreset(instanceId);
std::string name = "Default Bank";
SaveBankFile(name, defaultBank);
int64_t selectedBank = bankIndex.addBank(-1,name);
newSelection = selectedBank;
}
if (this->bankIndex.selectedBank() == bankId)
{
this->bankIndex.selectedBank(newSelection);
}
this->SaveBankIndex();
std::filesystem::remove(fileName);
return newSelection;
}
}
throw PiPedalStateException("Bank not found.");
}
int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter)
{
int64_t lastPreset = this->currentBank.selectedPreset();
if (uploadAfter != -1)
{
lastPreset = uploadAfter;
}
if (bankFile.presets().size() == 0) {
throw PiPedalException("Invalid preset.");
}
for (size_t i = 0; i < bankFile.presets().size(); ++i)
{
PedalBoard preset = bankFile.presets()[i]->preset();
int n = 2;
std::string baseName = preset.name();
while (this->currentBank.hasName(preset.name()))
{
std::stringstream s;
s << baseName << "(" << n++ << ")";
preset.name(s.str());
}
lastPreset = this->currentBank.addPreset(preset,lastPreset);
}
this->SaveCurrentBank();
return lastPreset;
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <filesystem>
#include <iostream>
#include "PedalBoard.hpp"
#include "Presets.hpp"
#include "Banks.hpp"
#include "JackConfiguration.hpp"
namespace pipedal {
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
class Storage {
private:
std::filesystem::path dataRoot;
BankIndex bankIndex;
BankFile currentBank;
private:
static std::string SafeEncodeName(const std::string& name);
static std::string SafeDecodeName(const std::string& name);
std::filesystem::path GetPresetsDirectory() const;
std::filesystem::path GetIndexFileName();
std::filesystem::path GetBankFileName(const std::string & name);
std::filesystem::path GetChannelSelectionFileName();
void LoadBankIndex();
void SaveBankIndex();
void ReIndex();
void LoadCurrentBank();
void SaveCurrentBank();
void LoadChannelSelection();
void SaveChannelSelection();
void SaveBankFile(const std::string& name,const BankFile&bankFile);
void LoadBankFile(const std::string &name,BankFile *pBank);
std::string GetPresetCopyName(const std::string &name);
bool isJackChannelSelectionValid = false;
JackChannelSelection jackChannelSelection;
public:
Storage();
void Initialize();
void CreateBank(const std::string & name);
void SetDataRoot(const char*path);
std::vector<std::string> GetPedalBoards();
const BankIndex & GetBanks() const { return bankIndex; }
void LoadBank(int64_t instanceId);
const PedalBoard& GetCurrentPreset();
void saveCurrentPreset(const PedalBoard&pedalBoard);
int64_t saveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
void GetPresetIndex(PresetIndex*pResult);
void SetPresetIndex(const PresetIndex &presetIndex);
PedalBoard GetPreset(int64_t instanceId) const;
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
bool LoadPreset(int64_t presetId);
int64_t DeletePreset(int64_t presetId);
bool RenamePreset(int64_t presetId, const std::string&name);
int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
void RenameBank(int64_t bankId, const std::string&newName);
int64_t SaveBankAs(int64_t bankId, const std::string&newName);
void MoveBank(int from, int to);
int64_t DeleteBank(int64_t bankId);
void SetJackChannelSelection(const JackChannelSelection&channelSelection);
const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration);
};
} // namespace pipedal
+135
View File
@@ -0,0 +1,135 @@
#include "pch.h"
#include "Units.hpp"
#include "PiPedalException.hpp"
#include <algorithm>
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
using namespace pipedal;
Units UriToUnits(const std::string &uri);
#define CASE(name) \
{ Units::name, #name },
std::map<Units,std::string> unitsToStringMap =
{
CASE(none)
CASE(unknown)
CASE(bar)
CASE(beat)
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(db)
CASE(hz)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(min)
CASE(ms)
CASE(pc)
CASE(s)
CASE(semitone12TET)
};
const std::string& pipedal::UnitsToString(Units units)
{
return unitsToStringMap[units];
}
#undef CASE
#define CASE(name) \
{ #name, Units::name},
std::map<std::string,Units> unitMap = {
CASE(none)
CASE(unknown)
CASE(bar)
CASE(beat)
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(db)
CASE(hz)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(min)
CASE(ms)
CASE(pc)
CASE(s)
CASE(semitone12TET)
};
#undef CASE
#define CASE(name) \
{ LV2_UNITS__##name, Units::name},
std::map<std::string,Units> uriToUnitsMap = {
CASE(bar)
CASE(beat)
CASE(bpm)
CASE(cent)
CASE(cm)
CASE(db)
CASE(hz)
CASE(khz)
CASE(km)
CASE(m)
CASE(mhz)
CASE(midiNote)
CASE(min)
CASE(ms)
CASE(pc)
CASE(s)
CASE(semitone12TET)
};
Units pipedal::StringToUnits(const std::string &text)
{
return unitMap[text];
}
Units pipedal::UriToUnits(const std::string &text)
{
if (text.length() == 0) return Units::none;
auto result = uriToUnitsMap[text];
if (result == Units::none) return Units::unknown;
return result;
}
class UnitsEnumConverter: public json_enum_converter<Units> {
public:
virtual Units fromString(const std::string&value) const
{
return StringToUnits(value);
}
virtual const std::string& toString(Units value) const
{
return UnitsToString(value);
}
} g_units_converter;
json_enum_converter<Units> *pipedal::get_units_enum_converter()
{
return &g_units_converter;
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <string>
#include "json.hpp"
namespace pipedal {
enum class Units {
none,
unknown,
bar,
beat,
bpm,
cent,
cm,
db,
hz,
khz,
km,
m,
mhz,
midiNote,
min,
ms,
pc,
s,
semitone12TET,
};
Units UriToUnits(const std::string &uri);
const std::string& UnitsToString(Units units);
Units StringToUnits(const std::string &text);
json_enum_converter<Units> *get_units_enum_converter();
} // namespace.
+449
View File
@@ -0,0 +1,449 @@
#include "pch.h"
#include "Uri.hpp"
#include "HtmlHelper.hpp"
#include "PiPedalException.hpp"
using namespace pipedal;
std::string uri::segment(int index) const
{
const char *p = path_start;
int segment = 0;
if (p != path_end && *p == '/') ++p;
while (p != path_end && *p != '?' && *p != '#')
{
if (segment == index)
{
const char*segmentStart = p;
while(p != path_end && *p != '/' && *p != '?' && *p != '#')
{
++p;
}
return HtmlHelper::decode_url_segment(segmentStart,p);
} else {
while(p != path_end && *p != '/' && *p != '?' && *p != '#')
{
++p;
}
}
if (p != path_end && *p == '/') ++p;
++segment;
}
throw std::invalid_argument("Invalid segement number.");
};
int uri::segment_count() const {
const char *p = path_start;
int segment = 0;
if (p != path_end && *p == '/') ++p;
while (p != path_end && *p != '?' && *p != '#')
{
while(p != path_end && *p != '/' && *p != '?' && *p != '#')
{
++p;
}
if (p != path_end && *p == '/') ++p;
++segment;
}
return segment;
}
void uri::set(const char*start, const char*end)
{
this->text = std::string(start,end);
set_();
}
void uri::set(const char*text)
{
this->text = text;
set_();
}
void uri::set_()
{
const char*start = this->text.c_str();
const char*end = start+this->text.size();
const char* p = start;
this->isRelative = true;
bool hasScheme = false;
while (p != end)
{
char c = *p;
if (c == ':') {
hasScheme = true;
break;
}
if (c == '/' || c == '?' || c == '#')
{
break;
}
++p;
}
if (hasScheme)
{
scheme_start = start;
scheme_end = p;
++p;
} else {
scheme_start = start;
scheme_end = start;
p = start;
}
if (p[0] == '/' && p[1] == '/')
{
this->isRelative = false;
// authority.
p += 2;
user_start = p;
user_end = p;
authority_start = p;
authority_end = nullptr;
const char*port_start = nullptr;
while (p != end)
{
char c = *p;
if (c == '@')
{
user_end = p;
authority_start = p+1;
port_start = nullptr;
} else if (c == ':')
{
authority_end = p;
port_start = p+1;
}
if (c == '/' || c == '?' || c == '#')
{
break;
}
++p;
}
if (authority_end == nullptr) authority_end = p;
if (port_start != nullptr)
{
char*intEnd;
port_ = (int)std::strtol(port_start,&intEnd,10);
if (intEnd != p) throwInvalid();
} else {
port_ = -1;
}
} else {
user_start = p;
user_end = p;
authority_start = p;
authority_end = p;
port_ = -1;
}
path_start = p;
if (p != end && *p == '/') this->isRelative = false;
while (p != end && *p != '?' && *p != '#')
{
++p;
}
path_end = p;
if (p != end && *p == '?')
{
++p;
query_start = p;
while (p != end && *p != '#')
{
++p;
}
query_end = p;
} else {
query_start = query_end = p;
}
if (p != end && *p == '#')
{
++p;
fragment_start = p;
fragment_end = end;
} else {
fragment_start = fragment_end = p;
}
}
void uri::throwInvalid() {
throw std::invalid_argument("Invalid uri.");
}
static bool compare_name(const char*start, const char*end, const char*szName)
{
while (start != end)
{
if (*start != *szName) return false;
++start; ++szName;
}
return *szName == 0;
}
int uri::query_count() const
{
int count = 0;
const char*p = query_start;
while (p != query_end && *p != '#')
{
const char*nameStart = p;
while (p != query_end && *p != '&' && *p != '#')
{
++p;
}
if (p != query_end && *p == '&') ++p;
++count;
if (p == query_end || *p == '#')
{
break;
}
}
return count;
}
bool uri::has_query(const char*name) const
{
const char*p = query_start;
while (p != query_end && *p != '#')
{
const char*nameStart = p;
while (p != query_end && *p != '&' && *p != '=' && *p != '#')
{
++p;
}
// compare names.
if (compare_name(nameStart,p,name))
{
return true;
}
while (p != query_end && *p != '&' && *p != '#')
{
++p;
}
if (p != query_end && *p == '&') ++p;
}
return false;
}
query_segment uri::query(int index) const
{
const char*p = query_start;
int ix = 0;
while (p != query_end && *p != '#')
{
const char*nameStart = p;
while (p != query_end && *p != '&' && *p != '=' && *p != '#')
{
++p;
}
// compare names.
//if (compare_name(nameStart,p,name))
if (ix == index)
{
const char *nameEnd = p;
if (p != query_end && *p == '=')
{
++p;
const char*value_start = p;
while(p != query_end && *p != '&' && *p != '#')
{
++p;
}
return query_segment(
std::string(nameStart,nameEnd),
std::string(value_start,p)
);
} else {
return query_segment(
std::string(nameStart,nameEnd),
""
);
}
}
while (p != query_end && *p != '&' && *p != '#')
{
++p;
}
if (p != query_end && *p == '&')
{
++p;
}
++ix;
}
throw PiPedalArgumentException("Index out of range.");
}
std::string uri::query(const char*name) const
{
const char*p = query_start;
while (p != query_end && *p != '#')
{
const char*nameStart = p;
while (p != query_end && *p != '&' && *p != '=' && *p != '#')
{
++p;
}
// compare names.
if (compare_name(nameStart,p,name))
{
if (p != query_end && *p == '=')
{
++p;
const char*value_start = p;
while(p != query_end && *p != '&' && *p != '#')
{
++p;
}
return HtmlHelper::decode_url_segment(value_start,p);
} else {
return "";
}
}
while (p != query_end && *p != '&' && *p != '#')
{
++p;
}
if (p != query_end && *p == '&')
{
++p;
}
}
return "";
}
query_segment uri::query(int index)
{
const char*p = query_start;
int count = 0;
while (p != query_end && *p != '#')
{
if (count == index)
{
const char*nameStart = p;
while (p != query_end && *p != '&' && *p != '=' && *p != '#')
{
++p;
}
const char *nameEnd = p;
const char *valueStart, *valueEnd;
if(p == query_end || *p != '=')
{
valueStart = valueEnd = p;
} else {
++p;
valueStart = p;
while(p != query_end && *p != '&' && *p != '#')
{
++p;
}
valueEnd = p;
}
return query_segment(std::string(nameStart,nameEnd), HtmlHelper::decode_url_segment(valueStart,valueEnd));
} else {
while (p != query_end && *p != '&' && *p != '#')
{
++p;
}
}
if (p != query_end && *p == '&') ++p;
++count;
}
throw std::invalid_argument("Argument out of range.");
}
std::vector<std::string> uri::segments()
{
std::vector<std::string> result;
for (int i = 0; i < segment_count(); ++i)
{
result.push_back(segment(i));
}
return result;
}
std::string uri::get_extension() const
{
const char*p = this->path_end;
while (p != this->path_start)
{
char c = p[-1];
if (c == '/') return "";
if (c == '.') {
--p;
return std::string(p,this->path_end-p);
}
--p;
}
return "";
}
std::string uri::to_canonical_form() const {
uri_builder builder(*this);
return builder.str();
}
std::string uri_builder::str() const {
std::stringstream s;
if (scheme_.length() != 0)
{
s << scheme_ << ':';
}
if (authority_.length() != 0) {
s << "//";
if (user_.length() != 0)
{
s << user_ << '@';
}
s << authority_;
if (port_ != -1) {
s << ':' << (uint16_t)port_;
}
}
if ( authority_.length() != 0 || !isRelative_) { // canonical root form is http://xyz/ not httP://xyz
s << '/';
}
for (size_t i = 0; i < segments_.size(); ++i)
{
if (i != 0) s << '/';
HtmlHelper::encode_url_segment(s,segments_[i],false);
}
if (queries_.size() != 0)
{
s << '?';
for (size_t i = 0; i < queries_.size(); ++i)
{
s << queries_[i].key << "=";
HtmlHelper::encode_url_segment(s,queries_[i].value,true);
}
}
if (fragment_.length() != 0)
{
s << "#" << fragment_;
}
return s.str();
}
+214
View File
@@ -0,0 +1,214 @@
#pragma once
#include <string>
#include <vector>
#include <string_view>
#include <stdexcept>
#include <sstream>
#include <cstdlib>
namespace pipedal {
class query_segment {
public:
query_segment(const std::string &key, const std::string &value)
: key(key)
, value(value) {
}
const std::string key;
const std::string value;
bool operator==(const query_segment &other)
{
return key == other.key && value == other.value;
}
};
class uri
{
private:
std::string text;
const char*scheme_start, *scheme_end;
const char *user_start, *user_end;
const char* authority_start, *authority_end;
const char* path_start, *path_end;
bool isRelative;
int port_;
const char*query_start, *query_end;
const char*fragment_start, *fragment_end;
public:
uri()
{
set("");
}
const std::string& str() {
return text;
}
uri(const char*text)
{
set(text);
}
void set(const char*text);
void set(const char*start, const char*end);
private:
void set_();
static void throwInvalid();
public:
bool has_scheme() const { return scheme_start != scheme_end; }
std::string scheme() const{ return std::string(scheme_start, scheme_end); }
bool has_user() const{ return user_start != user_end; }
std::string user() const{ return std::string(user_start,user_end); }
bool has_authority() const{ return authority_start != authority_end; }
std::string authority() const{ return std::string(authority_start,authority_end);}
bool has_port() const{ return port_ != -1; }
int port() const{ return port_; }
bool is_relative() const { return isRelative; }
std::string path() const { return std::string(path_start, path_end); }
int segment_count() const;
std::string segment(int n) const;
std::vector<std::string> segments();
const std::vector<std::string> segments() const;
int query_count() const;
query_segment query(int index);
bool has_query(const char*name) const;
std::string query(const char*name) const;
query_segment query(int index) const;
std::string get_extension() const;
std::string fragment() const { return std::string(fragment_start, fragment_end);}
std::string to_canonical_form() const;
};
class uri_builder {
private:
std::string scheme_ = "http";
std::string user_ = "";
std::string authority_ = "";
int port_ = -1;
bool isRelative_ = false;
std::vector<std::string> segments_;
std::vector<query_segment> queries_;
std::string fragment_;
public:
uri_builder()
{
}
uri_builder(const uri&uri)
: scheme_(uri.scheme()),
user_(uri.user()),
authority_(uri.authority()),
port_(uri.port()),
isRelative_(uri.is_relative()),
fragment_(uri.fragment())
{
for (int i = 0; i < uri.segment_count(); ++i)
{
segments_.push_back(uri.segment(i));
}
for (int i = 0; i < uri.query_count(); ++i)
{
queries_.push_back(uri.query(i));
}
}
std::string str() const;
const std::string & scheme() const { return scheme_; }
void set_scheme(const std::string &scheme_) {
this->scheme_ = scheme_;
}
const std::string & user() const { return this->user_;}
void set_user(const std::string&user) { this->user_ = user; }
const std::string &authority() const {
return this->authority_;
}
void set_authority(const std::string &authority) {
this->authority_ = authority;
}
int port() const { return port_; }
void set_port(int port) {
this->port_ = port;
}
bool is_relative() const { return this->isRelative_ && authority_.length() == 0; }
void set_is_relative(bool isRelative)
{
this->isRelative_ = isRelative;
}
int segment_count() const { return (int)segments_.size(); }
const std::string& segment(int i) const { return segments_[i];}
void append_segment(const std::string & segment) {
segments_.push_back(segment);
}
void insert_segment(int position, std::string & segment) {
segments_.insert(segments_.begin()+position,segment);
}
void erase_segment(int position)
{
segments_.erase(segments_.begin()+position);
}
void replace_segment(int position, std::string& segment) {
segments_[position] = segment;
}
int query_count() const { return (int)(queries_.size());}
bool has_query(const std::string &key) const {
for (size_t i = 0; i < queries_.size(); ++i)
{
if (queries_[i].key == key) true;
}
return false;
}
std::string query(const std::string &key) const {
for (size_t i = 0; i < queries_.size(); ++i)
{
if (queries_[i].key == key) return queries_[i].value;
}
return "";
}
std::vector<std::string> queries(const std::string &key) const {
std::vector<std::string> result;
for (size_t i = 0; i < queries_.size(); ++i)
{
if (queries_[i].key == key) result.push_back(queries_[i].value);
}
return result;
}
const query_segment & query(int index) {
return queries_[index];
}
const std::string&fragment() { return this->fragment_; }
void set_fragment(const std::string & fragment) { this->fragment_ = fragment;}
};
}; // namespace pipedal.
+17
View File
@@ -0,0 +1,17 @@
#include "pch.h"
#include "VuUpdate.hpp"
using namespace pipedal;
JSON_MAP_BEGIN(VuUpdate)
JSON_MAP_REFERENCE(VuUpdate,instanceId)
JSON_MAP_REFERENCE(VuUpdate,sampleTime)
JSON_MAP_REFERENCE(VuUpdate,isStereoInput)
JSON_MAP_REFERENCE(VuUpdate,isStereoOutput)
JSON_MAP_REFERENCE(VuUpdate,inputMaxValueL)
JSON_MAP_REFERENCE(VuUpdate,inputMaxValueR)
JSON_MAP_REFERENCE(VuUpdate,outputMaxValueL)
JSON_MAP_REFERENCE(VuUpdate,outputMaxValueR)
JSON_MAP_END()
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "json.hpp"
namespace pipedal
{
class VuUpdate
{
public:
long instanceId_ = 0;
long sampleTime_ = 0;
bool isStereoInput_ = false;
bool isStereoOutput_ = false;
float inputMaxValueL_ = 0;
float inputMaxValueR_ = 0;
float outputMaxValueL_ = 0;
float outputMaxValueR_ = 0;
public:
void reset() {
inputMaxValueL_ = 0;
inputMaxValueR_ = 0;
outputMaxValueL_ = 0;
outputMaxValueR_ = 0;
}
void AccumulateVu(float *value,float *input, uint32_t samples)
{
float v = *value;
for (uint32_t i = 0; i < samples; ++i)
{
float t = std::abs(input[i]);
if (t > v) {
v = t;
}
}
*value = v;
}
void AccumulateInputs(float* input, uint32_t samples)
{
AccumulateVu(&inputMaxValueL_,input,samples);
}
void AccumulateInputs(float* inputL, float*inputR, uint32_t samples)
{
AccumulateVu(&inputMaxValueL_,inputL,samples);
AccumulateVu(&inputMaxValueR_,inputR,samples);
}
void AccumulateOutputs(float* output, uint32_t samples)
{
AccumulateVu(&outputMaxValueL_,output,samples);
}
void AccumulateOutputs(float* outputL, float*outputR, uint32_t samples)
{
AccumulateVu(&outputMaxValueL_,outputL,samples);
AccumulateVu(&outputMaxValueR_,outputR,samples);
}
DECLARE_JSON_MAP(VuUpdate);
};
}
+157
View File
@@ -0,0 +1,157 @@
/*
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"
using namespace pipedal;
const int RING_BUFFER_SIZE = 16 * 1024;
Worker::Worker(LilvInstance *lilvInstance_, const LV2_Worker_Interface *workerInterface_)
: lilvInstance(lilvInstance_),
requestRingBuffer(RING_BUFFER_SIZE),
responseRingBuffer(RING_BUFFER_SIZE),
workerInterface(workerInterface_)
{
responseBuffer = (char *)malloc(RING_BUFFER_SIZE);
StartWorkerThread();
}
Worker::~Worker()
{
StopWorkerThread();
sem_destroy(&requestSemaphore);
free(responseBuffer);
}
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);
}
LV2_Worker_Status Worker::WorkerResponse(uint32_t size, const void *data)
{
if (responseRingBuffer.write(sizeof(size), (uint8_t *)&size))
{
return LV2_WORKER_ERR_NO_SPACE;
}
if (!responseRingBuffer.write(size, (uint8_t *)data))
{
return LV2_WORKER_ERR_NO_SPACE;
}
return LV2_WORKER_SUCCESS;
}
void Worker::EmitResponses()
{
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);
}
}
void Worker::ThreadProc()
{
try
{
while (true)
{
if (!requestRingBuffer.readWait())
{
return;
}
size_t size;
while (requestRingBuffer.readSpace() > sizeof(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);
}
}
}
catch (const std::exception &e)
{
Lv2Log::error("Lv2 Worker thread proc exited abnormally. (%s)", e.what());
}
}
void Worker::StopWorkerThread()
{
if (pThread)
{
auto pThread = this->pThread;
this->pThread = nullptr;
exiting = true;
requestRingBuffer.close();
pThread->join();
delete pThread;
}
}
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)
{
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_ERR_NO_SPACE;
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <lilv/lilv.h>
#include "lv2/core/lv2.h"
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "lv2/atom/atom.h"
#include "lv2/worker/worker.h"
#include <map>
#include <string>
#include <mutex>
#include <thread>
#include "RingBuffer.hpp"
namespace pipedal {
class Worker {
private:
LilvInstance*lilvInstance;
const LV2_Worker_Interface*workerInterface;
sem_t requestSemaphore;
bool exiting = false;
RingBuffer<false,true> requestRingBuffer;
RingBuffer<true,false> responseRingBuffer;
char *responseBuffer;
std::thread* pThread = nullptr;
void ThreadProc();
void StartWorkerThread();
void StopWorkerThread();
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);
public:
Worker(LilvInstance *instance, const LV2_Worker_Interface *iface);
~Worker();
LV2_Worker_Status ScheduleWork(
uint32_t size,
const void *data);
void EmitResponses();
};
}
+47
View File
@@ -0,0 +1,47 @@
#include "pch.h"
#include "WriteTemplateFile.hpp"
#include <fstream>
#include "PiPedalException.hpp"
using namespace pipedal;
void pipedal::WriteTemplateFile(
const std::map<std::string,std::string> &map,
std::filesystem::path inputFile,
std::filesystem::path outputFile)
{
std::ofstream out(outputFile);
std::ifstream in(inputFile);
while (in.peek() != -1)
{
char c = in.get();
if (c == '$')
{
if (in.peek() == '{') {
in.get();
std::stringstream sName;
try {
while ((c = in.get()) != '}')
{
sName.put(c);
}
} catch (const std::exception&)
{
throw PiPedalException("Unexpected end of file. Expecting '}'.");
}
std::string s = map.at(sName.str());
out << s;
} else if (in.peek() == '$')
{
out.put(in.get());
} else {
out.put(c);
}
} else {
out.put(c);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <map>
#include <filesystem>
namespace pipedal {
void WriteTemplateFile(
const std::map<std::string,std::string> &map,
std::filesystem::path inputFile,
std::filesystem::path outputFile);
}
+13
View File
@@ -0,0 +1,13 @@
#include "pch.h"
// Options for GCC Address Sanitizer.
extern "C" {
const char* __asan_default_options() { return
"detect_leaks=0" // undesirable behavior on abnormal termination.
":alloc_dealloc_mismatch=0" // Guitarix components trigger this. It's not actually a problem on GCC.
":new_delete_type_mismatch=0" //GxTuner
":print_stacktrace=1"
":halt_on_error=1"
;
}
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef CONFIG_HPP_H
#define CONFIG_HPP_H
#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VER "@PROJECT_VERSION@"
#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@"
#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@"
#define PTOJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"
#endif // CONFIG_HPP_H
+114
View File
@@ -0,0 +1,114 @@
#pragma once
// =============================================================================
// deferred_call:
// --------------
// This struct enables us to implement deferred function calls simply in
// the defer() function below. It forces a given function to automatically
// be called at the end of scope using move-only semantics. Most
// commonly, the given function will be a lambda but that is not required.
// See the defer() function (below) for more on this
// =============================================================================
template <typename FUNC>
struct deferred_call
{
// Disallow assignment and copy
deferred_call(const deferred_call& that) = delete;
deferred_call& operator=(const deferred_call& that) = delete;
// Pass in a lambda
deferred_call(FUNC&& f)
: m_func(std::forward<FUNC>(f)), m_bOwner(true)
{
}
// Move constructor, since we disallow the copy
deferred_call(deferred_call&& that)
: m_func(std::move(that.m_func)), m_bOwner(that.m_bOwner)
{
that.m_bOwner = false;
}
// Destructor forces deferred call to be executed
~deferred_call()
{
execute();
}
// Prevent the deferred call from ever being invoked
bool cancel()
{
bool bWasOwner = m_bOwner;
m_bOwner = false;
return bWasOwner;
}
// Cause the deferred call to be invoked NOW
bool execute()
{
const auto bWasOwner = m_bOwner;
if (m_bOwner)
{
m_bOwner = false;
m_func();
}
return bWasOwner;
}
private:
FUNC m_func;
bool m_bOwner;
};
// -----------------------------------------------------------------------------
// defer: Generic, deferred function calls
// ----------------------------------------
// This function template the user the ability to easily set up any
// arbitrary function to be called *automatically* at the end of
// the current scope, even if return is called or an exception is
// thrown. This is sort of a fire-and-forget. Saves you from having
// to repeat the same code over and over or from having to add
// exception blocks just to be sure that the given function is called.
//
// If you wish, you may cancel the deferred call as well as force it
// to be executed BEFORE the end of scope.
//
// Example:
// void Foo()
// {
// auto callOnException = defer([]{ SomeGlobalFunction(); });
// auto callNoMatterWhat = defer([pObj](pObj->SomeMemberFunction(); });
//
// // Do dangerous stuff that might throw an exception ...
//
// ...
// ... blah blah blah
// ...
//
// // Done with dangerous code. We can now...
// // a) cancel either of the above calls (i.e. call cancel()) OR
// // b) force them to be executed (i.e. call execute()) OR
// // c) do nothing and they'll be executed at end of scope.
//
// callOnException.cancel(); // no exception, prevent this from happening
//
// // End of scope, If we had not canceled or executed the two
// // above objects, they'd both be executed now.
// }
// -----------------------------------------------------------------------------
template <typename F>
deferred_call<F> defer(F&& f)
{
return deferred_call<F>(std::forward<F>(f));
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
template<typename R, typename... Args>
struct FunctionTraitsBase
{
using RetType = R;
using ArgTypes = std::tuple<Args...>;
static constexpr std::size_t ArgCount = sizeof...(Args);
};
template<typename F> struct FunctionTraits;
template<typename R, typename... Args>
struct FunctionTraits<R(*)(Args...)>
: FunctionTraitsBase<R, Args...>
{
using Pointer = R(*)(Args...);
};
template<typename R>
struct FunctionTraits<R(*)()>
: FunctionTraitsBase<R>
{
using Pointer = R(*)();
};
+580
View File
@@ -0,0 +1,580 @@
#include "pch.h"
#include "json.hpp"
#include <string_view>
#include <cctype>
#include "PiPedalException.hpp"
using namespace pipedal;
uint32_t json_writer::continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end)
{
if (p == end)
throw_encoding_error();
uint8_t c = *p++;
if ((c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_BITS)
throw_encoding_error();
return c & 0x3F;
}
static char hex(int v)
{
if (v < 10)
return (char)('0' + v);
return (char)('A' + v - 10);
}
void json_writer::throw_encoding_error()
{
throw std::invalid_argument("Invalid UTF-8 character sequence");
}
void json_writer::write_utf16_char(uint16_t uc)
{
os << "\\u"
<< hex((int)((uc >> 12) & 0x0F))
<< hex((int)((uc >> 8) & 0x0F))
<< hex((int)((uc >> 4) & 0x0F))
<< hex((int)((uc)&0x0F));
}
void json_writer::write(string_view v,bool enforceValidUtf8Encoding)
{
// convert to utf-32.
// convert utf-32 to normalized utf-16.
// write non-7-bit and unsafe characters as \uxxxx.
auto p = v.begin();
os << '"';
while (p != v.end())
{
uint32_t uc;
uint8_t c = (uint8_t)*p++;
if ((c & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_BITS)
{
uc = c;
}
else
{
uint32_t c2 = continuation_byte(p, v.end());
if ((c & UTF8_TWO_BYTES_MASK) == UTF8_TWO_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)(~UTF8_TWO_BYTES_MASK);
if (c1 <= 1 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1 << 6) | c2;
}
else
{
uint32_t c3 = continuation_byte(p, v.end());
if ((c & UTF8_THREE_BYTES_MASK) == UTF8_THREE_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)~UTF8_THREE_BYTES_MASK;
if (c1 == 0 && c2 < 0x20 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1) << 12 | (c2 << 6) | c3;
}
else
{
uint32_t c4 = continuation_byte(p, v.end());
if ((c & UTF8_FOUR_BYTES_MASK) == UTF8_FOUR_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)~UTF8_FOUR_BYTES_MASK;
if (c1 == 0 && c2 < 0x10 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
} else {
// outside legal UCS range.
throw_encoding_error();
}
}
}
}
if ((uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK) || (uc >= UTF16_SURROGATE_2_BASE && uc <= UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK))
{
// MUST not encode UTF16 surrogates in UTF8.
throw_encoding_error();
}
if (uc == '"' || uc == '\\')
{
os << (char)'\\';
os << (char)uc;
}
else if (uc >= 0x20 && uc < 0x80)
{
os << (char)uc;
}
else if (uc == '\r')
{
os << "\\r";
}
else if (uc == '\n')
{
os << "\\n";
}
else if (uc == '\t')
{
os << "\\t";
}
else if (uc < 0x10000ul)
{
write_utf16_char((uint16_t)uc);
}
else
{
// write UTF-16 surrogate pair.
uc -= 0x10000;
uint16_t s1 = (uint16_t)(UTF16_SURROGATE_1_BASE + ((uc >> 10) & 0x3FFu));
uint16_t s2 = (uint16_t)(UTF16_SURROGATE_2_BASE + (uc & 0x03FFu));
// surrogate pair.
write_utf16_char(s1);
write_utf16_char(s2);
}
}
os << '"';
}
void json_writer::indent()
{
if (!compressed)
{
for (int i = 0; i < indent_level; ++i)
{
os << " ";
}
}
}
void json_writer::start_object()
{
os << "{" << CRLF;
indent_level += TAB_SIZE;
}
void json_writer::end_object()
{
indent_level -= TAB_SIZE;
os << CRLF;
indent();
os << "}";
}
void json_writer::start_array()
{
indent();
os << "[" << CRLF;
indent_level += TAB_SIZE;
}
void json_writer::end_array()
{
indent_level -= TAB_SIZE;
indent();
os << "]" << CRLF;
}
void json_reader::skip_whitespace()
{
char c;
while (true)
{
int ic = is_.peek();
if (ic == -1)
break;
if (is_whitespace((char)ic))
{
c = get();
}
else if (ic == '/')
{
get();
int c2 = is_.peek();
if (c2 == '/') {
// skip to end of line.
get();
while (true) {
c2 = is_.peek();
if (c2 == '\r' || c2 == '\n')
{
get(); // and continue.
break;
}
if (c2 == -1)
{
break;
}
get();
}
} else if (c2 == '*') {
get();
int level = 1;
while (true)
{
c = get();
if (c == '*' && is_.peek() == '/')
{
get();
if (--level == 0)
{
break;
}
}
if (c == '/' && is_.peek() == '*')
{
get();
++level;
}
}
} else {
throw_format_error();
}
} else
{
break;
}
}
}
std::string json_reader::read_string()
{
// To completely normalize UTF-32 values we must covert to UTF-16, resolve surrogate pairs, and then convert UTF-32 to UTF-8.
skip_whitespace();
char c;
char startingCharacter;
startingCharacter = get();
if (startingCharacter != '\'' && startingCharacter != '\"')
{
throw_format_error();
}
std::stringstream s;
while (true)
{
c = get();
if (c == startingCharacter)
{
if (is_.peek() == startingCharacter) // "" -> "
{
get();
s.put(c);
} else {
break;
}
}
if (c != '\\')
{
s << (char)c;
}
else
{
c = get();
switch (c)
{
case '"':
case '\\':
default:
s << c;
break;
case 'r':
s << '\r';
break;
case 'b':
s << '\b';
break;
case 'f':
s << '\f';
break;
case 'n':
s << '\n';
break;
case 't':
s << '\t';
break;
case 'u':
{
uint32_t uc = read_u_escape();
if (uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK)
{
// MUST be a UTF16_SURROGATE 2 to be legal.
c = get();
if (c != '\\')
throw_format_error("Invalid UTF16 surrogate pair");
c = get();
if (c != 'u')
throw_format_error("Invalid UTF16 surrogate pair");
uint16_t uc2 = read_u_escape();
if (uc2 < UTF16_SURROGATE_2_BASE || uc2 > UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK)
{
throw_format_error("Invalid UTF16 surrogate pair");
}
uc = ((uc & UTF16_SURROGATE_MASK) << 10) + (uc2 & UTF16_SURROGATE_MASK) + 0x10000U;
}
HtmlHelper::utf32_to_utf8_stream(s, uc);
}
break;
}
}
}
return s.str();
}
uint16_t json_reader::read_hex()
{
char c;
c = get();
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw_format_error("Invalid \\u escape character");
return 0;
}
uint16_t json_reader::read_u_escape()
{
uint16_t c1 = read_hex();
uint16_t c2 = read_hex();
uint16_t c3 = read_hex();
uint16_t c4 = read_hex();
return (c1 << 12) + (c2 << 8) + (c3 << 4) + c4;
}
bool json_reader::is_complete()
{
skip_whitespace();
return is_.peek() == -1;
}
void json_reader::consumeToken(const char*expectedToken, const char*errorMessage)
{
skip_whitespace();
const char*p = expectedToken;
while (*p != '\0')
{
char expectedChar = *p++;
int c = is_.get();
if (expectedChar != c) {
this->throw_format_error(errorMessage);
}
}
}
void json_reader::consume(char expected)
{
skip_whitespace();
char c;
c = get();
if (c != expected)
{
std::stringstream s;
s << "Expecting '" << expected << "'";
throw_format_error(s.str().c_str());
}
}
void json_reader::skip_property()
{
skip_whitespace();
int c = is_.peek();
switch (c)
{
case '[':
skip_array();
break;
case '{':
skip_object();
break;
case '\"':
skip_string();
break;
case 't':
case 'f':
read_boolean();
break;
case 'n':
read_null();
break;
default:
skip_number();
break;
}
}
void json_reader::skip_string()
{
consume('"');
while (true)
{
char c;
c = get();
if (c == '\"')
{
if (peek() == '\"')
{
get();
} else {
break;
}
}
if (c == '\\')
{
c = get(); // all of standard escapes, enough to get past \u
}
}
}
void json_reader::skip_number()
{
skip_whitespace();
char c;
while (true)
{
int ic = is_.peek();
if (ic == -1)
break;
if (is_whitespace((char)ic))
{
break;
}
c = get();
}
}
void json_reader::skip_array()
{
char c;
consume('[');
while (true)
{
if (peek() == ']')
{
c = get();
break;
}
skip_property();
skip_whitespace();
if (is_.peek() == ',')
{
c = get();
}
}
}
void json_reader::skip_object()
{
char c;
consume('{');
while (true)
{
if (peek() == '}')
{
c = get();
break;
}
skip_string(); // name.
consume(':');
skip_object();
if (peek() == ',')
{
consume(',');
}
}
}
void json_reader::read_object_start()
{
skip_whitespace();
char c;
c = get();
if (c != '{')
throw_format_error();
}
std::string json_reader::readToken()
{
skip_whitespace();
std::stringstream s;
while (true)
{
int ic = is_.peek();
if (ic == -1)
break;
char c = (char)ic;
if (!std::isalpha(c))
break;
is_.get();
s << c;
}
return s.str();
}
void json_reader::read_null()
{
std::string s = readToken();
if (s != "null")
{
throw_format_error("Format error. Expecting 'null'.");
}
}
bool json_reader::read_boolean()
{
std::string ss = readToken();
if (ss == "true")
return true;
if (ss == "false")
return false;
throw_format_error("Format error. Expectiong 'true' or 'false'");
return false;
}
void json_reader::throw_format_error(const char*error)
{
std::stringstream s;
s << error;
s << ", near: '";
skip_whitespace();
if (is_.peek() == -1) {
s << "<eof>";
} else {
for (int i = 0; i < 40; ++i)
{
if (is_.peek()) break;
int c = get();
if (c == -1) break;
if (c == '\r') {
s << "\\r";
} else if (c == '\n')
{
s << "\\n";
} else {
s << c;
}
}
}
s << "'.";
throw PiPedalException(s.str());
}
+829
View File
@@ -0,0 +1,829 @@
#pragma once
#include <iostream>
#include <cstdint>
#include <boost/utility/string_view.hpp>
#include <boost/format.hpp>
#include <iomanip>
#include <type_traits>
#include <sstream>
#include "HtmlHelper.hpp"
#include <cctype>
#include "PiPedalException.hpp"
#define DECLARE_JSON_MAP(CLASSNAME) \
static json_map::storage_type<CLASSNAME> jmap
#define JSON_MAP_BEGIN(CLASSNAME) \
json_map::storage_type<CLASSNAME> CLASSNAME::jmap { {
#define JSON_MAP_REFERENCE(class,name) \
json_map::reference(#name,&class::name##_),
#define JSON_MAP_REFERENCE_CONDITIONAL(class,name, conditionFn) \
json_map::conditional_reference(#name,&class::name##_,conditionFn) ,
#define JSON_MAP_END() \
}};
namespace pipedal
{
// a function pointer of type bool (*)(const CLASS*,const MEMBER_TYPE&value) (because pre-C++ 20 templated function pointers are *hard*)
template<typename CLASS,typename MEMBER_TYPE> class JsonConditionFunction {
public:
using Pointer = bool (*)(const CLASS*self,const MEMBER_TYPE&value);
};
//----------------------------------------------------------------------------------
class json_reader;
class json_writer;
class JsonWritable {
public:
virtual void write_members(json_writer&writer) const = 0;
};
template<typename CLASS>
class json_member_reference_base {
private:
const char*name_;
protected:
json_member_reference_base(const char*name)
:name_(name)
{
}
public:
virtual ~json_member_reference_base() {}
const char*name() { return this->name_; }
virtual bool canWrite(const CLASS *self) { return true; }
virtual void read_value(json_reader&reader, CLASS*self) = 0;
virtual void write_value(json_writer&writer, const CLASS*self) = 0;
};
template<typename CLASS, typename MEMBER_TYPE>
class json_member_reference: public json_member_reference_base<CLASS> {
public:
virtual ~json_member_reference() {}
json_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer)
: json_member_reference_base<CLASS>(name)
, member_pointer(member_pointer)
{
}
MEMBER_TYPE CLASS::*member_pointer;
void read_value(json_reader&reader, CLASS*self);
void write_value(json_writer&writer, const CLASS*self);
};
template<typename CLASS, typename MEMBER_TYPE>
class json_conditional_member_reference: public json_member_reference_base<CLASS> {
public:
virtual ~json_conditional_member_reference() {}
json_conditional_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction<CLASS,MEMBER_TYPE>::Pointer condition)
: json_member_reference_base<CLASS>(name)
, member_pointer(member_pointer)
, condition_(condition)
{
}
MEMBER_TYPE CLASS::*member_pointer;
typename JsonConditionFunction<CLASS,MEMBER_TYPE>::Pointer condition_;
virtual bool canWrite(const CLASS *self);
void read_value(json_reader&reader, CLASS*self);
void write_value(json_writer&writer, const CLASS*self);
};
template <typename CLASS>
class json_enum_converter {
public:
virtual CLASS fromString(const std::string&value) const = 0;
virtual const std::string& toString(CLASS value) const = 0;
};
template<typename CLASS, typename MEMBER_TYPE>
class json_enum_member_reference: public json_member_reference_base<CLASS> {
public:
json_enum_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer, const json_enum_converter<MEMBER_TYPE>*converter)
: json_member_reference_base<CLASS>(name)
, member_pointer(member_pointer)
, converter_(converter)
{
}
MEMBER_TYPE CLASS::*member_pointer;
const json_enum_converter<MEMBER_TYPE> *converter_;
void read_value(json_reader&reader, CLASS*self);
void write_value(json_writer&writer, const CLASS*self);
};
//----------------------------------------------------------------------------------
class json_map_base {
};
template<typename CLASS>
class json_map_impl: public json_map_base {
private:
std::vector<json_member_reference_base<CLASS>* > members_;
json_map_impl(const json_map_impl&) { } // disable copy constructor.
public:
json_map_impl(std::vector<json_member_reference_base<CLASS>*> members)
: members_(members)
{
}
virtual ~json_map_impl()
{
for (auto member: members_)
{
delete member;
}
members_.resize(0);
}
void read_property(json_reader* reader,const char*memberName,CLASS *pObject);
void write_members(json_writer* writer, const CLASS*pObject);
};
class json_map {
public:
template <typename CLASS> class storage_type : public json_map_impl<CLASS> {
public:
storage_type(std::vector<json_member_reference_base<CLASS>*> members)
: json_map_impl<CLASS>(members)
{
}
};
template <typename CLASS, typename MEMBER_TYPE>
static json_member_reference<CLASS,MEMBER_TYPE> *
reference(const char*name,MEMBER_TYPE CLASS::*member_pointer)
{
return new json_member_reference<CLASS,MEMBER_TYPE>(name,member_pointer);
};
template <typename CLASS, typename MEMBER_TYPE,typename ENUM_CONVERTER>
static json_enum_member_reference<CLASS,MEMBER_TYPE> *
enum_reference(
const char*name,
MEMBER_TYPE CLASS::*member_pointer,
const ENUM_CONVERTER*converter)
{
return new json_enum_member_reference<CLASS,MEMBER_TYPE>(name,member_pointer,converter);
};
template <typename CLASS, typename MEMBER_TYPE>
static json_conditional_member_reference<CLASS,MEMBER_TYPE> *
conditional_reference(const char*name,MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction<CLASS,MEMBER_TYPE>::Pointer condition )
{
return new json_conditional_member_reference<CLASS,MEMBER_TYPE>(name,member_pointer,condition);
};
};
//----------------------------------------------------------------------------------
template <typename T>
class HasJsonPropertyMap {
template <class TYPE>
static std::true_type test (decltype(TYPE::jmap) *) { return std::true_type();};
template <class TYPE>
static std::false_type test (...);
public:
static constexpr bool value = decltype(test<T>(nullptr))::value;
};
//----------------------------------------------------------------------------------
class json_writer;
class json_writer {
public:
const char*CRLF;
private:
std::ostream &os;
int indent_level;
bool compressed;
const int TAB_SIZE = 2;
const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U;
const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U;
const uint16_t UTF16_SURROGATE_MASK = 0x3FFFU;
static const uint32_t UTF8_ONE_BYTE_MASK = 0x80;
static const uint32_t UTF8_ONE_BYTE_BITS = 0;
static const uint32_t UTF8_TWO_BYTES_MASK = 0xE0;
static const uint32_t UTF8_TWO_BYTES_BITS = 0xC0;
static const uint32_t UTF8_THREE_BYTES_MASK = 0xF0;
static const uint32_t UTF8_THREE_BYTES_BITS = 0xE0;
static const uint32_t UTF8_FOUR_BYTES_MASK = 0xF8;
static const uint32_t UTF8_FOUR_BYTES_BITS = 0xF0;
static const uint32_t UTF8_CONTINUATION_MASK = 0xC0;
static const uint32_t UTF8_CONTINUATION_BITS = 0x80;
public:
void write_raw(const char*text) {
os << text;
}
using string_view = boost::string_view;
json_writer(std::ostream &os, bool compressed = false)
: os(os)
, compressed(compressed)
, indent_level(0)
{
this->CRLF = compressed? "" : "\r\n";
}
void write(long value)
{
os << value;
}
void write(int64_t value)
{
os << value;
}
void write(uint64_t value)
{
os << value;
}
void write(int32_t value)
{
os << value;
}
void write(uint32_t value)
{
os << value;
}
private:
static void throw_encoding_error();
static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end);
void write_utf16_char(uint16_t uc);
public:
void indent();
std::ostream & output_stream() { return os; }
void write(bool value) {
os << (value? "true": "false");
}
void write(
string_view v,
bool enforceValidUtf8 // throw errors for illegal (non-minimal) UTF-8 sequences.
);
void write(string_view v)
{
write(v,true);
}
void write(const char*p)
{
write(string_view(p));
}
void write(const std::string&s)
{
write(string_view(s.c_str()));
}
void write(float f)
{
os << std::setprecision(std::numeric_limits<float>::max_digits10) << f; // round-trip format
}
void write(double f)
{
os << std::setprecision(std::numeric_limits<double>::max_digits10) << f; // round-trip format
}
template <typename T> void write(const std::vector<T> &value)
{
if (std::is_fundamental<T>() || std::is_assignable<T,const char*>() || value.size() == 0)
{
// simple types: all on same line.
os << "[ ";
if (value.size() >= 1)
{
write(value[0]);
}
for (int i = 1; i < value.size(); ++i)
{
os << ",";
write(value[i]);
}
os << "]";
} else {
// complex types: one line per entry.
os << "[" << CRLF;
indent_level += TAB_SIZE;
bool first = true;
for (int i = 0; i < value.size(); ++i)
{
if (!first)
{
os << ',' << CRLF;
}
first = false;
indent();
write(value[i]);
}
indent_level -= TAB_SIZE;
os << CRLF;
indent();
os << "]";
}
}
template<
class Category,
class T,
class Distance = std::ptrdiff_t,
class Pointer = T*,
class Reference = T&
> void write(std::iterator<Category,T,Distance,Pointer,Reference> &it) {
start_array();
bool first = true;
for( Reference ref: it)
{
if (!first)
{
os << ", ";
os << CRLF;
}
indent();
os << ref;
}
end_array();
}
void write_json_member(const char*name, const char*json_text)
{
write(name);
os << ": ";
os << json_text;
}
template<typename T> void write_member(const char*name,const T&value)
{
write(name);
os << ": ";
write(value);
}
void start_object();
void end_object();
void start_array();
void end_array();
/*
void write(const json_writable *obj)
{
if (obj == nullptr)
{
os << "null";
} else {
start_object();
obj->write(*this);
end_object();
}
}
void write(const json_writable &obj)
{
start_object();
obj.write(*this);
end_object();
}
*/
private:
template<typename T>
json_map::storage_type<T>& get_object_map()
{
//static_assert(HasJsonPropertyMap<T>::value,"Type does not have a Json propery map name jmap.");
// Type T must have public static propery jmap.
return T::jmap;
}
public:
void write(const JsonWritable*pWritable)
{
start_object();
pWritable->write_members(*this);
end_object();
}
template <typename T>
void write(const std::shared_ptr<T> &obj)
{
write(obj.get());
}
template <typename T>
void write(const std::weak_ptr<T> &obj)
{
auto p = obj.lock();
write(p.get());
}
template <typename T>
void write(const std::unique_ptr<T> &obj)
{
write(obj.get());
}
template <typename T>
void write(const T*obj)
{
static_assert(HasJsonPropertyMap<T>::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap).");
if (obj == nullptr)
{
write_raw("null");
} else {
write(*obj);
}
}
template <typename T>
void write(T*obj)
{
static_assert(HasJsonPropertyMap<T>::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap).");
if (obj == nullptr)
{
write_raw("null");
} else {
write(*obj);
}
}
template <typename T>
void write(const T&obj)
{
static_assert(HasJsonPropertyMap<T>::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap).");
auto &map = get_object_map<T>();
start_object();
map.write_members(this,&obj);
end_object();
}
};
class json_reader {
std::istream &is_;
const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U;
const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U;
const uint16_t UTF16_SURROGATE_MASK = 0x3FFU;
public:
json_reader(std::istream &input)
: is_(input)
{
}
private:
void throw_format_error(const char*error);
void throw_format_error()
{
throw_format_error("Invalid file format");
}
inline bool is_whitespace(char c)
{
switch (c)
{
case 0x20:
case 0x0A:
case 0x0D:
case 0x09:
return true;
default:
return false;
}
}
char get() {
int ic = is_.get();
if (ic == -1) throw_format_error("Unexpected end of file");
return (char)ic;
}
void skip_whitespace();
void read_object_start();
uint16_t read_hex();
uint16_t read_u_escape();
template<typename T>
json_map::storage_type<T>& get_object_map()
{
// Type T must have public static propery map.
return T::jmap;
}
public:
void consume(char expected);
void consumeToken(const char*expectedToken, const char*errorMessage);
int peek()
{
skip_whitespace();
return is_.peek();
}
public:
std::string read_string();
public:
bool is_complete();
template<typename T>
void read(T*pObject)
{
char c;
consume('{');
auto &map = get_object_map<T>();
while (true)
{
c = peek();
if (c == '}')
{
c = get();
break;
}
std::string memberName = read_string();
consume(':');
skip_whitespace();
map.read_property(this,memberName.c_str(),pObject);
skip_whitespace();
if (is_.peek() == ',')
{
c = get();
}
}
}
template<typename T>
void read(T**pObject)
{
int c = peek();
if (c != '{')
{
if (c == 'n')
{
consumeToken("null", "Expecting '{' or 'null'.");
*pObject = nullptr;
return;
}
}
*pObject = new T();
read(*pObject);
}
template<typename T>
void read(std::unique_ptr<T> *pUniquePtr)
{
std::unique_ptr<T> p = std::make_unique<T>();
read(p.get());
*pUniquePtr = std::move(p);
}
template<typename T>
void read(std::shared_ptr<T> *pUniquePtr)
{
std::shared_ptr<T> p = std::make_shared<T>();
read(p.get());
(*pUniquePtr) = std::move(p);
}
template<typename T>
void read(std::weak_ptr<T> *pUniquePtr)
{
throw std::domain_error("Can't read std::weak_ptr");
}
private:
void skip_string();
void skip_number();
void skip_array();
void skip_object();
std::string readToken();
bool read_boolean();
void read_null();
void skip_token();
public:
void skip_property();
void read(std::string*value) {
skip_whitespace();
*value = read_string();
}
void read(bool *value)
{
skip_whitespace();
*value = read_boolean();
}
void read(long*value)
{
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(int32_t *value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(int64_t*value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(uint32_t *value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(uint64_t*value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(float*value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
void read(double*value) {
skip_whitespace();
is_ >> *value;
if (is_.fail()) throw PiPedalException("Invalid format.");
}
template <typename T>
void read(std::vector<T>*value)
{
char c;
std::vector<T> result;
consume('[');
while (true)
{
if (peek() == ']')
{
c = get();
break;
}
T item;
read(&item);
result.push_back(std::move(item));
if (peek() == ',')
{
c = get();
}
}
*value = std::move(result);
}
};
template <typename T>
class HasJsonRead {
template <typename TYPE, typename ARG>
static std::true_type test (decltype(json_reader(std::cin).read((ARG*)nullptr))*v) { return std::true_type();};
template <typename TYPE, typename ARG>
static std::false_type test (...);
public:
static constexpr bool value = decltype(test<json_reader,T>(nullptr))::value;
};
template<typename CLASS, typename MEMBER_TYPE>
void json_member_reference<CLASS, MEMBER_TYPE>::read_value(json_reader&reader, CLASS*self) {
static_assert(HasJsonRead<MEMBER_TYPE>::value,"Type not supported for Json serialization.");
MEMBER_TYPE *ref = &((*self).*member_pointer);
reader.read(ref);
}
template<typename CLASS, typename MEMBER_TYPE>
void json_member_reference<CLASS,MEMBER_TYPE>::write_value(json_writer&writer, const CLASS*self) {
const MEMBER_TYPE &ref = (*self).*member_pointer;
writer.write_member(this->name(),ref);
}
template<typename CLASS, typename MEMBER_TYPE>
void json_enum_member_reference<CLASS, MEMBER_TYPE>::read_value(json_reader&reader, CLASS*self) {
MEMBER_TYPE *ref = &((*self).*member_pointer);
std::string val = reader.read_string();
*ref = converter_->fromString(val);
}
template<typename CLASS, typename MEMBER_TYPE>
void json_enum_member_reference<CLASS,MEMBER_TYPE>::write_value(json_writer&writer, const CLASS*self) {
const MEMBER_TYPE &ref = (*self).*member_pointer;
std::string val = converter_->toString(ref);
writer.write_member(this->name(),val);
}
template <typename CLASS>
void json_map_impl<CLASS>::read_property(json_reader* reader,const char*memberName,CLASS *pObject)
{
for (json_member_reference_base<CLASS>*member: members_)
{
if (strcmp(member->name(),memberName) == 0)
{
member->read_value(*reader,pObject);
return;
}
}
reader->skip_property();
}
template<typename CLASS, typename MEMBER_TYPE>
void json_conditional_member_reference<CLASS, MEMBER_TYPE>::read_value(json_reader&reader, CLASS*self) {
static_assert(HasJsonRead<MEMBER_TYPE>::value,"Type not supported for Json serialization.");
MEMBER_TYPE *ref = &((*self).*member_pointer);
reader.read(ref);
}
template<typename CLASS, typename MEMBER_TYPE>
bool json_conditional_member_reference<CLASS,MEMBER_TYPE>::canWrite(const CLASS*self) {
const MEMBER_TYPE &ref = (*self).*member_pointer;
return this->condition_(self,ref);
}
template<typename CLASS, typename MEMBER_TYPE>
void json_conditional_member_reference<CLASS,MEMBER_TYPE>::write_value(json_writer&writer, const CLASS*self) {
const MEMBER_TYPE &ref = (*self).*member_pointer;
writer.write_member(this->name(),ref);
}
template <typename CLASS>
void json_map_impl<CLASS>::write_members(json_writer* writer, const CLASS*pObject)
{
bool first = true;
for (json_member_reference_base<CLASS>*member: members_)
{
if (member->canWrite(pObject))
{
if (!first)
{
writer->output_stream() << ',';
writer->output_stream() << writer->CRLF;
}
first = false;
writer->indent();
member->write_value(*writer,pObject);
}
}
}
} // namespace pipedal
+490
View File
@@ -0,0 +1,490 @@
#include "pch.h"
#include "BeastServer.hpp"
#include <iostream>
#include "Lv2Log.hpp"
#include "PiPedalSocket.hpp"
#include "Lv2Host.hpp"
#include <boost/system/error_code.hpp>
#include <filesystem>
#include "PiPedalConfiguration.hpp"
#include "Shutdown.hpp"
#include "CommandLineParser.hpp"
#include "Lv2SystemdLogger.hpp"
#include <sys/stat.h>
#include <boost/asio.hpp>
#include "HtmlHelper.hpp"
#include <signal.h>
#include <semaphore.h>
using namespace pipedal;
using namespace boost::beast;
#define PRESET_EXTENSION ".piPreset"
#define BANK_EXTENSION ".piBank"
sem_t signalSemaphore;
class application_category : public boost::system::error_category
{
public:
const char *name() const noexcept { return "PiPedal"; }
std::string message(int ev) const { return "error message"; }
};
void sig_handler(int signo)
{
sem_post(&signalSemaphore);
}
using namespace boost::system;
class DownloadIntercept : public RequestHandler
{
PiPedalModel *model;
public:
DownloadIntercept(PiPedalModel *model)
: RequestHandler("/var"),
model(model)
{
}
virtual bool wants(boost::beast::http::verb verb, const uri &request_uri) const
{
if (request_uri.segment_count() != 2 || request_uri.segment(0) != "var")
{
return false;
}
if (request_uri.segment(1) == "downloadPreset")
{
std::string strInstanceId = request_uri.query("id");
if (strInstanceId != "")
return true;
}
if (request_uri.segment(1) == "uploadPreset")
{
return true;
}
return false;
}
std::string GetContentDispositionHeader(const std::string &name, const std::string &extension)
{
std::string fileName = name.substr(0,64) + extension;
std::stringstream s;
s << "attachment; filename*=" << HtmlHelper::Rfc5987EncodeFileName(fileName) << "; filename=\"" << HtmlHelper::SafeFileName(fileName) << "\"";
std::string result = s.str();
return result;
}
void GetPreset(const uri &request_uri, std::string *pName, std::string *pContent)
{
std::string strInstanceId = request_uri.query("id");
int64_t instanceId = std::stol(strInstanceId);
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.selectedPreset(newInstanceId);
std::stringstream s;
json_writer writer(s);
writer.write(file);
*pContent = s.str();
*pName = pedalBoard.name();
}
virtual void head_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::empty_body> &res,
boost::beast::error_code &ec)
{
try
{
if (request_uri.segment(1) == "downloadPreset")
{
std::string name;
std::string content;
GetPreset(request_uri, &name, &content);
res.set(http::field::content_type, "application/octet-stream");
res.set(http::field::cache_control, "no-cache");
res.set(http::field::content_length, content.length());
res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
return;
}
throw PiPedalException("Not found.");
}
catch (const std::exception &e)
{
if (strcmp(e.what(), "Not found") == 0)
{
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
}
else
{
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
}
}
}
virtual void get_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec)
{
try
{
if (request_uri.segment(1) == "downloadPreset")
{
std::string name;
std::string content;
GetPreset(request_uri, &name, &content);
res.set(http::field::content_type, "application/octet-stream");
res.set(http::field::cache_control, "no-cache");
res.set(http::field::content_length, content.length());
res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
res.body() = content;
}
}
catch (const std::exception &e)
{
if (strcmp(e.what(), "Not found") == 0)
{
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
}
else
{
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
}
}
}
virtual void post_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec)
{
try
{
if (request_uri.segment(1) == "uploadPreset")
{
std::string presetBody = req.body();
std::stringstream s(presetBody);
json_reader reader(s);
uint64_t uploadAfter = -1;
std::string strUploadAfter = request_uri.query("uploadAfter");
if (strUploadAfter.length() != 0)
{
uploadAfter = std::stol(strUploadAfter);
}
BankFile bankFile;
reader.read(&bankFile);
uint64_t instanceId = model->uploadPreset(bankFile,uploadAfter);
res.set(http::field::content_type, "application/json");
res.set(http::field::cache_control, "no-cache");
std::stringstream sResult;
sResult << instanceId;
std::string result = sResult.str();
res.set(http::field::content_length, result.length());
res.body() = result;
}
}
catch (const std::exception &e)
{
if (strcmp(e.what(), "Not found") == 0)
{
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
}
else
{
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
}
}
}
};
/* When hosting a react app, replace /var/config.json with
data that will connect the react app with our socket server.
*/
class InterceptConfig : public RequestHandler
{
private:
std::string response;
uint64_t maxUploadSize;
public:
InterceptConfig(int portNumber,uint64_t maxUploadSize)
: RequestHandler("/var/config.json"),
maxUploadSize(maxUploadSize)
{
std::stringstream s;
s << "{ \"socket_server_port\": " << portNumber
<< ", \"socket_server_address\": \"*\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }";
response = s.str();
}
virtual ~InterceptConfig() {}
virtual void head_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::empty_body> &res,
boost::beast::error_code &ec)
{
res.set(http::field::content_type, "application/json");
res.set(http::field::cache_control, "no-cache");
res.content_length(response.length());
return;
}
virtual void get_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec)
{
res.set(http::field::content_type, "application/json");
res.set(http::field::cache_control, "no-cache");
res.content_length(response.length());
res.body() = response;
}
};
class ReactRedirector : public RequestHandler
{
PiPedalConfiguration config;
public:
ReactRedirector(const PiPedalConfiguration &config)
: RequestHandler("/"),
config(config)
{
}
virtual ~ReactRedirector() {}
virtual bool wantsRedirect(const uri &requestUri)
{
if (requestUri.segment_count() == 0 || (requestUri.segment_count() == 1 && requestUri.segment(0) == "index.htm"))
{
return true;
}
return false;
}
uri GetRedirect(const uri &requestUri)
{
return requestUri;
}
virtual bool wants(boost::beast::http::verb verb, const uri &request_uri) const
{
return false;
}
virtual void head_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::empty_body> &res,
boost::beast::error_code &ec)
{
ec = errc::make_error_code(errc::no_such_file_or_directory);
return;
}
virtual void get_response(
const uri &request_uri,
const boost::beast::http::request<boost::beast::http::string_body> &req,
boost::beast::http::response<boost::beast::http::string_body> &res,
boost::beast::error_code &ec)
{
ec = errc::make_error_code(errc::no_such_file_or_directory);
}
};
uint16_t g_ShutdownPort = 0;
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.
#endif
// Check command line arguments.
bool help = false;
bool error = false;
bool systemd = false;
uint16_t shutdownPort = 0;
std::string portOption;
CommandLineParser parser;
parser.AddOption("-h", &help);
parser.AddOption("--help", &help);
parser.AddOption("-systemd", &systemd);
parser.AddOption("-port", &portOption);
parser.AddOption("-shutdownPort", &shutdownPort);
try
{
parser.Parse(argc, (const char **)argv);
if (parser.Arguments().size() > 2)
{
throw PiPedalException("Too many arguments.");
}
if (parser.Arguments().size() == 0)
{
throw PiPedalException("<config_root> not provided.");
}
if (help || parser.Arguments().size() == 0)
{
std::cout << "pipedald - Pipedal web socket server.\n"
"Copyright (c) 2021 Robin Davies.\n"
"\n";
}
}
catch (const std::exception &e)
{
std::cout << "Error: " << e.what() << "\n\n";
error = true;
help = true;
}
if (help)
{
std::cout << "Usage: pipedal <doc_root> [<web_root>] [options...]\n\n"
<< "Options:\n"
<< " -systemd: Log to systemd journals.\n"
<< " -port: Port to listen on e.g. 0.0.0.0:80\n"
<< " -shutdownPort: Port for the shutdown service.\n"
<< "Example:\n"
<< " pipedal /etc/pipedal/config /etc/pipedal/react -port 0.0.0.0:80 \n"
"\n"
"Description:\n\n"
" Configuration is read from <doc_root>/config.json\n"
"\n"
" If <web_root> is not provided, pipedal will serve from <doc_root>\n"
"\n"
" While debugging, bind the port to 0.0.0.0:8080, and connect to the default React\n"
" server that's provided when you run 'npm run start' in 'react/src'. By default, the\n"
" React app will connect to the socket server on 0.0.0.0:8080.\n\n";
return error ? EXIT_FAILURE : EXIT_SUCCESS;
}
g_ShutdownPort = shutdownPort;
if (systemd)
{
Lv2Log::set_logger(MakeLv2SystemdLogger());
}
signal(SIGTERM, sig_handler);
std::filesystem::path doc_root = parser.Arguments()[0];
std::filesystem::path web_root = doc_root;
if (parser.Arguments().size() >= 2)
{
web_root = parser.Arguments()[1];
}
PiPedalConfiguration configuration;
try
{
configuration.Load(doc_root);
}
catch (const std::exception &e)
{
std::stringstream s;
s << "Unable to read configuration from '" << (doc_root / "config.json") << "'. (" << e.what() << ")";
Lv2Log::error(s.str());
return EXIT_FAILURE;
}
Lv2Log::log_level(configuration.GetLogLevel());
if (portOption.length() != 0)
{
configuration.SetSocketServerEndpoint(portOption);
}
uint16_t port;
std::shared_ptr<BeastServer> server;
try
{
auto const address = boost::asio::ip::make_address(configuration.GetSocketServerAddress());
port = static_cast<uint16_t>(configuration.GetSocketServerPort());
auto const threads = std::max<int>(1, configuration.GetThreads());
server = std::make_shared<BeastServer>(
address, port, web_root.c_str(), threads);
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)threads);
server->SetLogHttpRequests(configuration.LogHttpRequests());
boost::asio::ip::network_v4 gateway;
if (configuration.GetAccessPointGateway(&gateway))
{
server->SetAccessPointGateway(gateway, configuration.GetAccessPointServerAddress());
}
}
catch (const std::exception &e)
{
std::stringstream s;
s << "Fatal error: " << e.what() << std::endl;
Lv2Log::error(s.str());
return EXIT_FAILURE;
}
try
{
PiPedalModel model;
model.Load(configuration);
auto pipedalSocketFactory = MakePiPedalSocketFactory(model);
server->AddSocketFactory(pipedalSocketFactory);
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port,configuration.GetMaxUploadSize())};
server->AddRequestHandler(interceptConfig);
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
server->AddRequestHandler(downloadIntercept);
server->RunInBackground();
sem_wait(&signalSemaphore);
Lv2Log::info("Shutting down gracefully.");
model.Close();
Lv2Log::info("Stopping web server.");
server->Stop();
server->Join();
Lv2Log::info("Shutdown complete.");
}
catch (const std::exception &e)
{
Lv2Log::error(e.what());
}
Lv2Log::info("PiPedal terminating.");
return EXIT_FAILURE; // only exit in response to a signal.
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
// Diagnostic type used to report calculated type T in an error message.
template <typename T> class TypeDisplay;
#include <mutex>
#include <string>
#include <vector>
#include <string_view>
#include <stdexcept>
#include <sstream>
#include <cstdlib>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/http.hpp>
// #include <lilv/lilv.h>
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/midi/midi.h"
#include "lv2/urid/urid.h"
#include "lv2/log/logger.h"
#include "lv2/uri-map/uri-map.h"
#include "lv2/atom/forge.h"
#include "lv2/worker/worker.h"
#include "lv2/patch/patch.h"
#include "lv2/parameters/parameters.h"
#include "lv2/units/units.h"
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
+23
View File
@@ -0,0 +1,23 @@
[Unit]
Description=${DESCRIPTION}
After=jack.service
After=network.target
BindsTo=jack.service
[Service]
LimitMEMLOCK=infinity
LimitRTPRIO=95
ExecStart=${COMMAND}
User=pipedal_d
Group=pipedal_d
Restart=always
RestartSec=15
TimeoutStopSec=10
WorkingDirectory=/var/pipedal
Environment=JACK_PROMISCUOUS_SERVER=jack
Environment=JACK_START_SERVER=1
[Install]
WantedBy=multi-user.target
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=${DESCRIPTION}
After=network.target
[Service]
ExecStart=${COMMAND}
Restart=always
RestartSec=60
WorkingDirectory=/var/pipedal
[Install]
WantedBy=multi-user.target
+116
View File
@@ -0,0 +1,116 @@
#pragma once
#include <cstring>
#include <iostream>
#include <istream>
#include <string_view>
template<typename __char_type, class __traits_type >
class view_streambuf final: public std::basic_streambuf<__char_type, __traits_type > {
private:
typedef std::basic_streambuf<__char_type, __traits_type > super_type;
typedef view_streambuf<__char_type, __traits_type> self_type;
public:
/**
* These are standard types. They permit a standardized way of
* referring to names of (or names dependent on) the template
* parameters, which are specific to the implementation.
*/
typedef typename super_type::char_type char_type;
typedef typename super_type::traits_type traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
typedef typename std::basic_string_view<char_type, traits_type> source_view;
view_streambuf(const source_view& src) noexcept:
super_type(),
src_( src )
{
char_type *buff = const_cast<char_type*>( src_.data() );
this->setg( buff , buff, buff + src_.length() );
}
virtual std::streamsize xsgetn(char_type* __s, std::streamsize __n) override
{
if(0 == __n)
return 0;
if( (this->gptr() + __n) >= this->egptr() ) {
__n = this->egptr() - this->gptr();
if(0 == __n && !traits_type::not_eof( this->underflow() ) )
return -1;
}
std::memmove( static_cast<void*>(__s), this->gptr(), __n);
this->gbump( static_cast<int>(__n) );
return __n;
}
virtual int_type pbackfail(int_type __c) override
{
char_type *pos = this->gptr() - 1;
*pos = traits_type::to_char_type( __c );
this->pbump(-1);
return 1;
}
virtual int_type underflow() override
{
return traits_type::eof();
}
virtual std::streamsize showmanyc() override
{
return static_cast<std::streamsize>( this->egptr() - this->gptr() );
}
virtual ~view_streambuf() override
{}
private:
const source_view& src_;
};
template<typename _char_type>
class view_istream final:public std::basic_istream<_char_type, std::char_traits<_char_type> > {
view_istream(const view_istream&) = delete;
view_istream& operator=(const view_istream&) = delete;
private:
typedef std::basic_istream<_char_type, std::char_traits<_char_type> > super_type;
typedef view_streambuf<_char_type, std::char_traits<_char_type> > streambuf_type;
public:
typedef _char_type char_type;
typedef typename super_type::int_type int_type;
typedef typename super_type::pos_type pos_type;
typedef typename super_type::off_type off_type;
typedef typename super_type::traits_type traits_type;
typedef typename streambuf_type::source_view source_view;
view_istream(const source_view& src):
super_type( nullptr ),
sb_(nullptr)
{
sb_ = new streambuf_type(src);
this->init( sb_ );
}
view_istream(view_istream&& other) noexcept:
super_type( std::forward<view_istream>(other) ),
sb_( std::move( other.sb_ ) )
{}
view_istream& operator=(view_istream&& rhs) noexcept
{
view_istream( std::forward<view_istream>(rhs) ).swap( *this );
return *this;
}
virtual ~view_istream() override {
delete sb_;
}
private:
streambuf_type *sb_;
};