First commit.
This commit is contained in:
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <locale>
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class Locale {
|
||||
public:
|
||||
static const std::collate<char>& collation;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+634
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
Lv2Logger *MakeLv2SystemdLogger();
|
||||
} // namespace
|
||||
@@ -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.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();
|
||||
|
||||
}
|
||||
@@ -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 ↦}
|
||||
LV2_URID_Map *GetMap() { return ↦}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "BeastServer.hpp"
|
||||
#include "PiPedalModel.hpp"
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
std::shared_ptr<ISocketFactory> MakePiPedalSocketFactory(PiPedalModel &model);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
void Shutdown(); // implemented in main.cpp
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user