Merge pull request #482 from rerdavies/dev

PiPedal 2.0.102 Alpha
This commit is contained in:
Robin Davies
2026-05-24 14:19:49 -04:00
committed by GitHub
218 changed files with 18410 additions and 3463 deletions
+7 -6
View File
@@ -28,12 +28,13 @@ jobs:
npm view npm get version of @babel/plugin-proposal-private-property-in-object version command
sudo apt-get update
sudo apt install gcc-12 g++-12 libnm-dev
sudo apt-get install liblilv-dev libboost-dev libjack-jackd2-dev libnl-3-dev libnl-genl-3-dev libsystemd-dev catch
sudo apt install libasound2-dev
sudo apt install libwebsocketpp-dev authbind
sudo apt install libsdbus-c++-dev libsdbus-c++-bin
sudo apt install libavahi-client-dev libzip-dev libicu-dev apt
sudo apt install libpipewire-0.3-dev
sudo apt-get install -y liblilv-dev libboost-dev libjack-jackd2-dev libnl-3-dev libnl-genl-3-dev libsystemd-dev catch
sudo apt install -y libasound2-dev
sudo apt install -y libwebsocketpp-dev authbind
sudo apt install -y libsdbus-c++-dev libsdbus-c++-bin
sudo apt install -y libavahi-client-dev libzip-dev libicu-dev apt
sudo apt install -y libpipewire-0.3-dev
sudo apt install -y libssl-dev
sudo apt install -y librsvg2-dev
git submodule update --init --recursive
-1
View File
@@ -13,7 +13,6 @@
"/usr/include/x86_64-linux-gnu",
"/usr/lib"
],
"compilerPath": "/usr/bin/gcc-12",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64",
+32 -1
View File
@@ -328,6 +328,37 @@
"ignoreFailures": true
}
]
}
},
{
"name": "(gdb) pipedalProfilePlugin",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [
"--no-profile",
"--preset-file", "/home/robin/Downloads/A1 Profiling.piPreset",
"--seconds", "100",
"-w"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
]
}
+6 -2
View File
@@ -103,7 +103,8 @@
"locale": "cpp",
"stdfloat": "cpp",
"text_encoding": "cpp",
"forward_list": "cpp"
"forward_list": "cpp",
"*.bak": "cpp"
},
"cSpell.words": [
"Alsa",
@@ -170,7 +171,7 @@
"cSpell.ignoreWords": [
"nammodel"
],
"cSpell.enabled": false,
"cSpell.enabled": true,
// Disable all automatic completion suggestions - only show when Ctrl+Space is pressed
"editor.quickSuggestions": {
@@ -190,5 +191,8 @@
// Disable parameter hints popup (optional)
"editor.parameterHints.enabled": false,
"cSpell.enabledFileTypes": {
"markdown": true
},
}
+22 -4
View File
@@ -1,16 +1,30 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 1.5.99
VERSION 2.0.102
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
set (DISPLAY_VERSION "PiPedal v1.5.99-Beta")
set (DISPLAY_VERSION "PiPedal v2.0.102-Alpha")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/")
set(PIPEDAL_T3K_PUBLISHABLE_KEY "t3k_pub_bHrH8btdwXXTtxz5ryEU8sNLF-2TGRT9")
set (LIBZIP_DO_INSTALL OFF CACHE BOOL "Disable libzip install" FORCE)
set (SQLITECPP_INSTALL OFF CACHE BOOL "Disable SQLiteCpp install" FORCE)
set(SUBMODULE_INSTALL OFF CACHE BOOL "" FORCE)
# Remove SQLiteCPP install files.
set(CPACK_SOURCE_IGNORE_FILES
"*.a;sqlite3.h;$(CPACK_SOURCE_IGNORE_FILES)"
)
set (PIPEDAL_EXCLUDE_TESTS false)
include(CTest)
enable_testing()
@@ -96,9 +110,9 @@ set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
# Accept either libzip4 or libzip5 to satisfy libzip 4 linkage (because Ubuntu nonsense)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev,iw, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
@@ -118,4 +132,8 @@ set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
)
include(CPack)
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
+2 -1
View File
@@ -208,8 +208,9 @@ void DBusDispatcher::WaitForClose()
serviceThread.join();
}
}
catch (const std::exception &)
catch (const std::exception &e)
{
std::cout << "DBusDispatcher: " << e.what() << std::endl;
}
if (eventFd != -1)
{
+118 -1
View File
@@ -21,6 +21,8 @@
#include <sstream>
#include <string>
#include <cstring>
#include "ss.hpp"
#include <map>
using namespace pipedal;
@@ -245,8 +247,30 @@ std::string HtmlHelper::Rfc5987EncodeFileName(const std::string &name)
#define MAX_FILE_NAME_LENGHT 96
const std::string SF_SPECIALS = " <>@;:\"\'/[]?=";
const std::string SF_SPECIALS = "<>@;:\"\'/[]?=";
const std::string ISF_SPECIALS = "\\:";
bool HtmlHelper::IsSafeFileName(const std::filesystem::path &path)
{
for (const auto&segment: path)
{
if (segment.string() == "..")
{
return false;
}
for (char c: segment.string())
{
unsigned char uc = (unsigned char)c;
if (uc < 0x20 || uc >= 0x80 || ISF_SPECIALS.find(c) != std::string::npos)
{
return false;
}
}
}
return true;
}
std::string HtmlHelper::SafeFileName(const std::string &name)
{
std::stringstream s;
@@ -388,3 +412,96 @@ std::string HtmlHelper::generateEtag(const std::filesystem::path &path)
crc = crc64((uint8_t*)&fTime, sizeof(fTime));
return std::to_string(crc);
}
static std::map<int,std::string> httpErrorStrings =
{
{200,"OK"},
{201, "Created"},
{202, "Accepted"},
{203, "Non-Authoritative Information"},
{204, "No Content"},
{205, "Reset Content"},
{206, "Partial Content"},
{300, "Multiple Choices"},
{301, "Moved Permanently"},
{302, "Found"},
{303, "See Other"},
{304, "Not Modified"},
{305, "Use Proxy"},
{307, "Temporary Redirect"},
{308, "Permanent Redirect"},
{400, "Bad Request"},
{401, "Unauthorized"},
{402, "Payment Required"},
{403, "Forbidden"},
{404, "Not Found"},
{405, "Method Not Allowed"},
{406, "Not Acceptable"},
{407, "Proxy Authentication Required"},
{408, "Request Timeout"},
{409, "Conflict"},
{410, "Gone"},
{411, "Length Required"},
{412, "Precondition Failed"},
{413, "Payload Too Large"},
{414, "URI Too Long"},
{415, "Unsupported Media Type"},
{416, "Range Not Satisfiable"},
{417, "Expectation Failed"},
{418, "I'm a teapot"},
{421, "Misdirected Request"},
{422, "Unprocessable Content"},
{423, "Locked"},
{424, "Failed Dependency"},
{426, "Upgrade Required"},
{428, "Precondition Required"},
{429, "Too Many Requests"},
{431, "Request Header Fields Too Large"},
{451, "Unavailable For Legal Reasons"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"},
{505, "HTTP Version Not Supported"},
{506, "Variant Also Negotiates"},
{507, "Insufficient Storage"},
{508, "Loop Detected"},
{510, "Not Extended"},
{511, "Network Authentication Required"}
};
std::string HtmlHelper::httpErrorString(int errorCode)
{
auto ff = httpErrorStrings.find(errorCode);
if (ff != httpErrorStrings.end())
{
return SS(errorCode << ": " << ff->second);
}
return SS(errorCode << ": Unknown error");
}
std::string HtmlFormBuilder::build()
{
std::ostringstream ss;
bool firstTime = true;
for (const auto &entry: entries_)
{
if (!firstTime)
{
ss << "&";
}
ss << entry.key();
if (entry.value().has_value())
{
ss << "=" << HtmlHelper::encode_url_segment(entry.value().value(),true);
}
firstTime = false;
}
return ss.str();
}
+1 -1
View File
@@ -78,7 +78,7 @@ private:
std::atomic<bool> stopping;
std::vector<CallbackEntry> postedEvents;
bool threadStarted;
bool threadStarted = false;
std::thread serviceThread;
std::mutex postMutex;
};
+61 -29
View File
@@ -20,49 +20,81 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <chrono>
#include <filesystem>
#include <optional>
namespace pipedal {
namespace pipedal
{
class HtmlHelper {
public:
static std::string timeToHttpDate();
static std::string timeToHttpDate(time_t time);
static std::string timeToHttpDate(std::filesystem::file_time_type time);
static std::string encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
static std::string encode_url_segment(const std::string &segment, bool isQuerySegment = false) {
return encode_url_segment(segment.c_str(), segment.c_str() + segment.length(), isQuerySegment);
}
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)
class HtmlHelper
{
const char*p = segment.c_str();
encode_url_segment(os,p, p + segment.length(),isQuerySegment);
}
public:
static std::string timeToHttpDate();
static std::string timeToHttpDate(time_t time);
static std::string timeToHttpDate(std::filesystem::file_time_type time);
static std::string decode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
static std::string encode_url_segment(const char *pStart, const char *pEnd, bool isQuerySegment = false);
static std::string encode_url_segment(const std::string &segment, bool isQuerySegment = false)
{
return encode_url_segment(segment.c_str(), segment.c_str() + segment.length(), isQuerySegment);
}
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*text, bool isQuerySegment = false);
static std::string decode_url_segment(const char *pStart, const char *pEnd, bool isQuerySegment = false);
static void utf32_to_utf8_stream(std::ostream &s, uint32_t uc);
static std::string decode_url_segment(const char *text, bool isQuerySegment = false);
static std::string Rfc5987EncodeFileName(const std::string&name);
static void utf32_to_utf8_stream(std::ostream &s, uint32_t uc);
static std::string SafeFileName(const std::string &name);
static std::string HtmlEncode(const std::string& text);
static std::string Rfc5987EncodeFileName(const std::string &name);
static uint64_t crc64(uint8_t *data, size_t length,uint64_t crc = 0);
static uint64_t crc64(const std::string&value, uint64_t crc = 0);
static std::string SafeFileName(const std::string &name);
static bool IsSafeFileName(const std::filesystem::path &path);
static std::string HtmlEncode(const std::string &text);
static std::string generateEtag(const std::filesystem::path &path);
static uint64_t crc64(uint8_t *data, size_t length, uint64_t crc = 0);
static uint64_t crc64(const std::string &value, uint64_t crc = 0);
};
static std::string generateEtag(const std::filesystem::path &path);
static std::string httpErrorString(int errorCode);
};
class HtmlFormBuilder
{
public:
class FormEntry
{
public:
FormEntry() = default;
FormEntry(const std::string &key) : key_(key) {}
FormEntry(const std::string &key, const std::string &value) : key_(key), value_(value) {}
const std::string&key() const { return key_; }
const std::optional<std::string>&value() const { return value_; }
private:
std::string key_;
std::optional<std::string> value_;
};
HtmlFormBuilder() = default;
HtmlFormBuilder(const std::vector<FormEntry> &&entries) : entries_(std::move(entries)) {}
void Add(const std::string &key) { entries_.push_back(FormEntry(key)); }
void Add(const std::string &key, const std::string &value) { entries_.push_back(FormEntry(key, value)); }
std::string build();
private:
std::vector<FormEntry> entries_;
};
} // namespace.
+31 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
@@ -38,6 +38,7 @@
#include <vector>
#include <memory>
#ifndef __INTELLISENSE__
#define DECLARE_JSON_MAP(CLASSNAME) \
static pipedal::json_map::storage_type<CLASSNAME> jmap
@@ -56,6 +57,35 @@
} \
} \
;
#else
#define DECLARE_JSON_MAP(CLASSNAME)
#define JSON_MAP_BEGIN(CLASSNAME)
#define JSON_MAP_REFERENCE(class, name)
#define JSON_MAP_END() ;
;
#endif
#define JSON_GETTER_SETTER_REF(name) \
const decltype(name##_) &name() const { return name##_; } \
decltype(name##_) &name() { return name##_; } \
void name(const decltype(name##_) &value) { name##_ = value; }
#define JSON_GETTER_SETTER(name) \
decltype(name##_) name() const { return name##_; } \
void name(decltype(name##_) value) { name##_ = value; }
#define JSON_GETTER_REF(name) \
const decltype(name##_) &name() const { return name##_; } \
decltype(name##_) &name() { return name##_; }
#define JSON_GETTER(name) \
decltype(name##_) name() const { return name##_; }
namespace pipedal
{
+4
View File
@@ -124,5 +124,9 @@ namespace pipedal
std::string ToLower(const std::string&value);
std::string SafeFilenameToString(const std::string &filename);
std::string StringToSafeFilename(const std::string &name);
std::string ShellEscape(const std::string &input);
}
+3 -3
View File
@@ -562,8 +562,8 @@ void json_reader::skip_array()
void json_reader::skip_object()
{
int c;
consume('{');
int c;
while (true)
{
c = peek();
@@ -571,11 +571,11 @@ void json_reader::skip_object()
if (c == '}')
{
c = get();
break;
return;
}
skip_string(); // name.
consume(':');
skip_object();
skip_property();
if (peek() == ',')
{
consume(',');
+151 -34
View File
@@ -32,32 +32,29 @@
using namespace pipedal;
void pipedal::SetThreadName(const std::string &name)
{
std::string threadName = "ppdl_" + name;
if (threadName.length () > 15)
if (threadName.length() > 15)
{
threadName = threadName.substr(0,15);
threadName = threadName.substr(0, 15);
}
pthread_t pid = pthread_self();
pthread_setname_np(pid,threadName.c_str());
pthread_setname_np(pid, threadName.c_str());
}
static const uint8_t utf8extraBytes[256] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5};
constexpr char32_t ILLEGAL_CHAR32 = U'';
static const uint8_t utf8Offset[] = { 0,0b11000000, 0b11100000,0b11110000,0b11111000,0b11111100};
static const uint8_t utf8Offset[] = {0, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100};
std::u32string pipedal::ToUtf32(const std::string &s)
{
@@ -72,16 +69,21 @@ std::u32string pipedal::ToUtf32(const std::string &s)
if (c < 0x80)
{
result << (char32_t)c;
} else {
}
else
{
auto extraBytes = utf8extraBytes[c];
if (extraBytes == 0)
{
result << ILLEGAL_CHAR32;
} else if (p+extraBytes > end)
}
else if (p + extraBytes > end)
{
result << ILLEGAL_CHAR32;
break;
} else {
}
else
{
char32_t cResult = c -= utf8Offset[extraBytes];
while (extraBytes != 0)
{
@@ -102,11 +104,10 @@ std::u32string pipedal::ToUtf32(const std::string &s)
return result.str();
}
std::string pipedal::GetHostName()
{
char buffer[1024];
if (gethostname(buffer,1024) != 0)
if (gethostname(buffer, 1024) != 0)
{
buffer[0] = '\0';
}
@@ -114,7 +115,6 @@ std::string pipedal::GetHostName()
return buffer;
}
std::vector<std::string> pipedal::split(const std::string &value, char delimiter)
{
size_t start = 0;
@@ -138,8 +138,7 @@ std::vector<std::string> pipedal::split(const std::string &value, char delimiter
return result;
}
bool pipedal::IsChildDirectory(const std::filesystem::path &path, const std::filesystem::path&rootPath)
bool pipedal::IsChildDirectory(const std::filesystem::path &path, const std::filesystem::path &rootPath)
{
auto iter = path.begin();
@@ -154,8 +153,7 @@ bool pipedal::IsChildDirectory(const std::filesystem::path &path, const std::fil
return true;
}
std::string pipedal::ToLower(const std::string&value)
std::string pipedal::ToLower(const std::string &value)
{
std::string result;
result.resize(value.length());
@@ -166,16 +164,17 @@ std::string pipedal::ToLower(const std::string&value)
return result;
}
std::filesystem::path pipedal::MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path&parentPath)
std::filesystem::path pipedal::MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path &parentPath)
{
if (path.is_relative()) return path;
if (path.is_relative())
return path;
auto iter = path.begin();
for (auto i = parentPath.begin(); i != parentPath.end(); ++i)
{
if (iter == path.end() || *iter != *i) {
if (iter == path.end() || *iter != *i)
{
// not a child directory of parent directory.
throw std::runtime_error("Not a child directory.");
}
@@ -187,7 +186,6 @@ std::filesystem::path pipedal::MakeRelativePath(const std::filesystem::path &pat
remander /= *iter++;
}
return remander;
}
bool pipedal::HasDotDot(const std::filesystem::path &path)
@@ -218,7 +216,7 @@ bool pipedal::IsSubdirectory(const std::filesystem::path &path, const std::files
if ((*i) != (*iPath))
{
if (*i == "" && ++i == basePath.end()) // match xyz/ (trailing /)
if (*i == "" && ++i == basePath.end()) // match xyz/ (trailing /)
{
break;
}
@@ -237,12 +235,131 @@ bool pipedal::IsSubdirectory(const std::filesystem::path &path, const std::files
return true;
}
bool pipedal::HasWritePermissions(const std::filesystem::path &path)
{
// posix, but may not work on windows.
// posix, but may not work on windows.
// allegedly windows provies an _access function, which is probably a superset of
// access.
return access(path.c_str(), W_OK) == 0;
}
const std::string SAFE_FILENAME_SPECIAL_CHARS = "<>@;:\"\'/[]?=+%.&|*^~`#";
static std::array<bool, 256> makeSfBits()
{
std::array<bool, 256> result;
for (char c : SAFE_FILENAME_SPECIAL_CHARS)
{
result[(unsigned char)c] = true;
}
for (size_t i = 128; i < 256; ++i)
{
result[i] = true;
}
for (size_t i = 0; i < 0x20; ++i)
{
result[i] = true;
}
return result;
}
static std::array<bool, 256> IS_SPECIAL_FILENAME_CHAR = makeSfBits();
static const char *HEX_CHARS = "0123456789abcdef";
static bool isHexChar(char c)
{
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static int fromHexChar(char c)
{
if (c >= '0' && c <= '9')
return (int)(c - '0');
if (c >= 'a' && c <= 'f')
return (int)(c - 'a') + 10;
if (c >= 'A' && c <= 'F')
return (int)(c - 'A') + 10;
return 0;
}
static char fromHexChars(char c1, char c2)
{
int uc = fromHexChar(c1) * 16 + fromHexChar(c2);
return (char)uc;
}
std::string pipedal::SafeFilenameToString(const std::string &filename)
{
size_t ix = 0;
std::ostringstream os;
while (ix < filename.size())
{
char c = filename[ix++];
if (c == '%')
{
// when faced with an invalid encoding, assume that user has done it.
// So return the original.
if (ix + 2 >= filename.size())
{
return filename; // just return the original.
}
if (!isHexChar(filename[ix]) || !isHexChar(filename[ix + 1]))
{
return filename;
}
os << fromHexChars(filename[ix], filename[ix + 1]);
ix += 2;
}
else
{
os << c;
}
}
return os.str();
}
std::string pipedal::StringToSafeFilename(const std::string &name)
{
std::ostringstream os;
for (char c : name)
{
unsigned char uc = (unsigned char)c;
if (uc < 0x20)
{
continue;
}
if (IS_SPECIAL_FILENAME_CHAR[uc])
{
os << '%'
<< HEX_CHARS[uc >> 4]
<< HEX_CHARS[uc & 0x0F];
}
else
{
os << c;
}
}
return os.str();
}
std::string pipedal::ShellEscape(const std::string &input)
{
// Escape string for safe use in shell commands by using single quotes
// and escaping any single quotes in the input
std::string result = "'";
for (char c : input)
{
if (c == '\'')
{
// End quote, add escaped single quote, start quote again
result += "'\\''";
}
else
{
result += c;
}
}
result += "'";
return result;
}
+3 -3
View File
@@ -9,15 +9,15 @@
<img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.99</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v2.0.102</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
#### NEW version 1.5.99 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!
#### Announcing PiPedal 2.0 (2.0.102 Alpha). See the [documentation](https://rerdavies.github.io/pipedal/PiPedal2.html) for information, and instructions on how to install Pipedal 2.0. New features include support for Neural Amp Modeler A2 models, integration with Tone3000.com services for easy downloading of Neural Amp Modeler A2 models and IIRs, a new Channel Routing dialog for global routing of auxiliary input channels, and unprocessed re-amp output channels, and many other minor features, improvements, and bug fixes. Pipedal 2.0 also adds support for running PiPedal as a Progressive Web Application (PWA), which allows you to launch Pipedal from your desktop as a native desktop application. (A nice feature if you are accessing Pipedal from an Apple device). See the [documentation](https://rerdavies.github.io/pipedal/PiPedal2.html) for instructions on how to install Pipedal on your desktop as a standalone app.
&nbsp;
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal using a remote web browsers, from your phone or tablet.
PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats. 
PiPedal will also run on Ubuntu 24.04, 24.10, or 25.04 (amd64/x64 or aarch64). Make sure you follow the [Ubuntu post-install instructions](https://rerdavies.github.io/pipedal/Configuring.html) to ensure that your Ubuntu OS is using a realtime-capable kernel.
+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933332 16.933334"
version="1.1"
id="svg1"
inkscape:export-filename="../vite/public/logo192.png"
inkscape:export-xdpi="288.00006"
inkscape:export-ydpi="288.00006"
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
sodipodi:docname="Pi-Icon-Inkscape.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="false"
inkscape:lockguides="true"
inkscape:zoom="3.1914063"
inkscape:cx="61.101591"
inkscape:cy="46.844553"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2">
<inkscape:grid
id="grid2"
units="px"
originx="0"
originy="0"
spacingx="1.0583333"
spacingy="1.0583333"
empcolor="#000000"
empopacity="0.30196078"
color="#000000"
opacity="0.14901961"
empspacing="4"
enabled="true"
visible="false"
dotted="true" />
</sodipodi:namedview>
<defs
id="defs1">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter35"
x="-0.081083745"
y="-0.081083748"
width="1.1621675"
height="1.1790599">
<feFlood
result="flood"
in="SourceGraphic"
flood-opacity="0.670588"
flood-color="rgb(0,0,0)"
id="feFlood34" />
<feGaussianBlur
result="blur"
in="SourceGraphic"
stdDeviation="0.400000"
id="feGaussianBlur34" />
<feOffset
result="offset"
in="blur"
dx="0.000000"
dy="0.200000"
id="feOffset34" />
<feComposite
result="comp1"
operator="out"
in="flood"
in2="offset"
id="feComposite34" />
<feComposite
result="comp2"
operator="atop"
in="comp1"
in2="SourceGraphic"
id="feComposite35" />
</filter>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:none">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:#5e2edf;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path3"
cx="8.4666653"
cy="8.4666653"
r="8.4666653" />
<g
id="g5"
transform="matrix(1.1241207,0,0,1.1241207,-1.0580763,-1.0508884)">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path4"
cx="8.4666662"
cy="8.4666653"
r="4.8967609" />
<ellipse
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.396875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path5"
cx="8.4666662"
cy="8.4666653"
rx="4.2097802"
ry="4.2333322" />
<path
id="path4-9"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#5e2edf;stroke-width:2.11667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 12.164115,4.1470337 7.9597209,7.8558512 8.9829142,9.0702474 13.214697,5.3371419 C 12.920982,4.8932514 12.566824,4.4925879 12.164115,4.1470337 Z" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;display:inline;fill:#7f2edf;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="circle34"
cx="8.4666653"
cy="8.4666653"
r="8.4666653" />
<path
id="path10"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.925474;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
inkscape:label="Dial only"
d="m 8.4589152,2.5468613 c -3.2597966,0 -5.9198054,2.6600088 -5.9198054,5.9198054 0,3.2597973 2.6600088,5.9198053 5.9198054,5.9198053 3.2597968,0 5.9198058,-2.660008 5.9198058,-5.9198053 0,-0.797827 -0.159653,-1.5596729 -0.448123,-2.2554373 l -0.382123,0.3366895 -1.250152,1.1023713 v 4.782e-4 c 0.05509,0.2630316 0.08417,0.5358673 0.08417,0.8158983 0,2.1754753 -1.748106,3.9221463 -3.9235808,3.9221463 -2.1754745,0 -3.9216678,-1.746671 -3.9216678,-3.9221463 0,-2.1754745 1.7461933,-3.9235808 3.9216678,-3.9235808 0.4532168,0 0.8876009,0.075865 1.2917591,0.2156917 L 10.998434,3.657841 11.38008,3.3211515 C 10.517327,2.8287984 9.5196885,2.5468613 8.4589152,2.5468613 Z M 12.305975,3.972531 7.9318813,7.8310695 8.9964706,9.0946117 13.39926,5.2107259 C 13.093696,4.7489266 12.724932,4.3320266 12.305975,3.972531 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933332 16.933334"
version="1.1"
id="svg1"
inkscape:export-filename="../vite/public/logo192.png"
inkscape:export-xdpi="288.00006"
inkscape:export-ydpi="288.00006"
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
sodipodi:docname="Pi-Icon-Inkscape.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="false"
inkscape:lockguides="true"
inkscape:zoom="3.1914063"
inkscape:cx="61.101591"
inkscape:cy="46.844553"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2">
<inkscape:grid
id="grid2"
units="px"
originx="0"
originy="0"
spacingx="1.0583333"
spacingy="1.0583333"
empcolor="#000000"
empopacity="0.30196078"
color="#000000"
opacity="0.14901961"
empspacing="4"
enabled="true"
visible="false"
dotted="true" />
</sodipodi:namedview>
<defs
id="defs1">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter35"
x="-0.081083745"
y="-0.081083748"
width="1.1621675"
height="1.1790599">
<feFlood
result="flood"
in="SourceGraphic"
flood-opacity="0.670588"
flood-color="rgb(0,0,0)"
id="feFlood34" />
<feGaussianBlur
result="blur"
in="SourceGraphic"
stdDeviation="0.400000"
id="feGaussianBlur34" />
<feOffset
result="offset"
in="blur"
dx="0.000000"
dy="0.200000"
id="feOffset34" />
<feComposite
result="comp1"
operator="out"
in="flood"
in2="offset"
id="feComposite34" />
<feComposite
result="comp2"
operator="atop"
in="comp1"
in2="SourceGraphic"
id="feComposite35" />
</filter>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:none">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:#5e2edf;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path3"
cx="8.4666653"
cy="8.4666653"
r="8.4666653" />
<g
id="g5"
transform="matrix(1.1241207,0,0,1.1241207,-1.0580763,-1.0508884)">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path4"
cx="8.4666662"
cy="8.4666653"
r="4.8967609" />
<ellipse
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.396875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path5"
cx="8.4666662"
cy="8.4666653"
rx="4.2097802"
ry="4.2333322" />
<path
id="path4-9"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#5e2edf;stroke-width:2.11667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 12.164115,4.1470337 7.9597209,7.8558512 8.9829142,9.0702474 13.214697,5.3371419 C 12.920982,4.8932514 12.566824,4.4925879 12.164115,4.1470337 Z" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;display:inline;fill:#7f2edf;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="circle34"
cx="8.4666653"
cy="8.4666653"
r="8.4666653" />
<path
id="path10"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.925474;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
inkscape:label="Dial only"
d="m 8.4589152,2.5468613 c -3.2597966,0 -5.9198054,2.6600088 -5.9198054,5.9198054 0,3.2597973 2.6600088,5.9198053 5.9198054,5.9198053 3.2597968,0 5.9198058,-2.660008 5.9198058,-5.9198053 0,-0.797827 -0.159653,-1.5596729 -0.448123,-2.2554373 l -0.382123,0.3366895 -1.250152,1.1023713 v 4.782e-4 c 0.05509,0.2630316 0.08417,0.5358673 0.08417,0.8158983 0,2.1754753 -1.748106,3.9221463 -3.9235808,3.9221463 -2.1754745,0 -3.9216678,-1.746671 -3.9216678,-3.9221463 0,-2.1754745 1.7461933,-3.9235808 3.9216678,-3.9235808 0.4532168,0 0.8876009,0.075865 1.2917591,0.2156917 L 10.998434,3.657841 11.38008,3.3211515 C 10.517327,2.8287984 9.5196885,2.5468613 8.4589152,2.5468613 Z M 12.305975,3.972531 7.9318813,7.8310695 8.9964706,9.0946117 13.39926,5.2107259 C 13.093696,4.7489266 12.724932,4.3320266 12.305975,3.972531 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

+3 -1
View File
@@ -33,7 +33,9 @@
"logLevel": 3,
/* Maximum filesize to allow when uploading */
"maxUploadSize": 536870912 // 512MiB
"maxUploadSize": 536870912, // 512MiB
/* false-> Download A1 models; true -> Download A2 models */
"tone3000A2Models": true
}
+4 -3
View File
@@ -18,8 +18,9 @@ layout: default
</style>
<div class="container">
<h1>404</h1>
<h2>404 Page not found</h2>
<p><strong>Page not found :(</strong></p>
<p>The requested page could not be found.</p>
<p style="padding-top: 96px;border-radius: 4px; font-family: serif; font-style: italic; font-size: 1.0em;color:#0008">
You step in the stream, but the water has moved on.
</p>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@ Run the following commands to install dependent libraries required by the PiPeda
authbind libavahi-client-dev libnm-dev libicu-dev \
libsdbus-c++-dev libzip-dev google-perftools \
libgoogle-perftools-dev \
libpipewire-0.3-dev libbz2-dev librsvg2-dev
libpipewire-0.3-dev libbz2-dev libssl-dev librsvg2-dev
### Installing Sources
+5 -5
View File
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_amd64.deb)
- [Raspberry Pi OS bookworm (aarch64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
- [Ubuntu 24.04 through 25.04 (aarch64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
- [Ubuntu 24.04 through 25.04 (amd64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_amd64.deb)
Version 1.5.99 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
Version 2.0.102 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
```
sudo apt update
sudo apt upgrade
cd ~/Downloads
sudo apt-get install ./pipedal_1.5.99_arm64.deb
sudo apt-get install ./pipedal_2.0.102_arm64.deb
```
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
+62
View File
@@ -0,0 +1,62 @@
<div style="position: relative">
<img src="img/pipedal.png" style="width: 100%; height: 160px;object-fit: cover"/>
</div>
<h2 style="font-weight: 300; font-size: 1.6em;line-height: 1em; padding-top: 32px; padding-bottom: 0px;margin-bottom: 4px">PiPedal 2.0 Guitar Effects Processor</h2>
<p style="padding-top: 0px; opacity: 0.6; padding-bottom: 16px">A Raspberry Pi-based stomp box designed to be controlled from a phone or tablet.</p>
<span style="display: none">
<a href="https://rerdavies.github.io/pipedal/ReleaseNotes"><img src="https://img.shields.io/github/v/release/rerdavies/pipedal?color=%23808080"/></a>
</span>
<a href="https://rerdavies.github.io/pipedal/download2"><img src="https://img.shields.io/badge/Download-008060" /></a>
<a href="https://rerdavies.github.io/pipedal/Documentation"><img src="https://img.shields.io/badge/Documentation-0060d0"/></a>
_To download PiPedal v2.0.102-alpha, click [*here*](download.md).
To view PiPedal documentation, click [*here*](Documentation.md)._
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.
PiPedal will also run on Ubuntu 22.x (amd64/x64 and aarch64). Make sure you follow the [Ubuntu post-install
instructions](https://rerdavies.github.io/pipedal/Configuring.html) to make sure your Ubuntu OS is using a realtime-capable kernel.
PiPedal 2.0 adds support for Neural Amp Modeler A2 models&mdash;the next generation of the ground-breaking Neural Amp Modeler technology. NAM A2 models provide even more accurate and realistic amp emulation than the original NAM A1 modelling technology, with while requiring less CPU. "Slimmable" A2 models allow users to select the level of CPU use vs. emulation accuracy that best suits their needs Slim down models for lower CPU use; or use full models for the best possible emulation quality.
NAM A2 models are available as free downloads from Tone3000.com. PiPedal 2.0 now integrates with Tone3000.com services in order to allow you to easily download NAM A2 models and Cabinet Impulse Response Files directly into Pipedal right from within the PiPedal app. All existing NAM models on Tone3000.com have been retrained using the new A2 architecture, and are available as A2 models right now. There is also a rich ecosystem of professionally developed NAM models available for purchase on the Internet.
Whether you are looking for free models, or premium models, NAM A2 provides the best amp modelling available anywhere.
PiPedal 2.0 provides a new Channel Routing dialog, which replaces the Channel Selection Dialog in Pipedal 1.x. You can now globally (for all existing presets) route auxiliary input channels on your audio device (such as backing tracks or microphone inputs), or route clean unprocessed guitar input signals to otherwise-unused output audio channels for later re-amping later in a DAW.
PiPedal 2.0 can now be run as a "Progressive Web Application". This allows you to launch Pipedal from your desktop as a native desktop application, without extra browser address bars and window decorations. See below for instructions on how to install Pipedal on your desktop as a standalone app.
{% include gallery.html %}
PiPedal includes state-of-the-art AI-based guitar amp emulation plugins based on the famous Neural Amp Modeler (NAM) and ML libraries which provide amp modelling that will blow your mind.
{% include demo.html %}
PiPedal can be remotely controlled via a web interface over Ethernet, or Wi-Fi. If you don't have access to a Wi-Fi router, PiPedal can be configured to
start a Wi-Fi hotspot automatically, whenever your Raspberry Pi can't connect to your home network.
Install the [PiPedal Remote Android app](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) to get one-click access to PiPedal via Wi-Fi networks, or Wi-Fi hotspots. If you are using PiPedal away from home, you can configure PiPedal to automatically start a Wi-Fi hotspot on your Raspberry Pi
whenever Pipedal is unable to detect your home network. The PiPedal Client Android app will allow to connect by simply launching the app, whether you are at home, or using a Wi-Fi auto-hotspot at a gig, when away from home.
PiPedal 2.0 can no be installed as a Progressive Web Application (PWA). This allows you to launch Pipedal with a native window frame. Launch it from a desktop shortcut, and it will run without browser address bars, tabs, and other browser window decorations. I looks and works like a normal native application. To install PiPedal on your desktop as a PWA, load PiPedal in your web browser, and click on the "Install" button found here:
![Install PiPedal as a Browser Application](img/chrome_install.jpg)
As an added bonus, when running PiPedal as a PWA, press the F11 key to toggle full-screen mode, and get a completely immersive experience, with no distractions on your screen at all.
Using Pipedal as a PWA also provides a nice way to use Pipedal on Apple devices. We recommend that you use Google Chrome to host the PWA on Apple devices, rather than Safari.
PiPedal's user interface has been specifically designed to work well on small form-factor touch devices like phones or tablets. Clip a phone or tablet on your microphone stand on stage, and you're ready to play! Or connect via a desktop browser, for a slightly more luxurious experience. The PiPedal user-interface adapts to the screen size and orientation of your device, providing easy control of your guitar effects across a broad variety devices and screen sizes.
PiPedal includes a pre-installed selection of LV2 plugins from the ToobAmp collection of plugins; but it works with most LV2 Audio plugins. There are literally hundreds of free high-quality LV2 audio plugins that will work with PiPedal. Just install them on your Raspberry Pi, and they will show up in PiPedal.
If your USB audio adapter has MIDI connectors, you can use MIDI devices (keyboards, controllers, or midi floor boards) to control PiPedal while performing. A simple interface allows you to select how you would like to bind PiPedal controls to midi messages.
+39
View File
@@ -1,5 +1,44 @@
# Release Notes
## PiPedal 2.0.102 Alpha
PiPedal 2.0 provides support for Neural Amp Modeler A2 models.
NAM A2 provides significant improvements in sound quality and performance compared to NAM A1. NAM A2 also provides "Slimable" models. Slimmable models provide multiple levels of performance and sound quality in a single model file, allowing you to choose the level of performance and sound quality that best suits your needs. This is particularly useful for users of Raspberry Pi 4, since NAM A2 models can provide better sound quality than NAM A1 models, while consuming significantly less CPU resources.
A new integrated Tone3000 model downloader provides a convenient way to download the latest NAM A2 models and IIR files from Tone3000 directly to the PiPedal server. All existing NAM A1 models on Tone3000 have been retrained for NAM A2. When
you download from Tone3000 using the integrated downloader, you will get NAM A2 models. Just click on the DOWNLOAD MODELS FROM TONE3000 button.
Currently, the best way to get access to NAM A2 models is via the integrated Tone3000 model downloader. Models downloaded externally from the Tone3000 website will still be NAM A1 models; however this should change soon with a forthcoming announcement from Tone3000.
A new channel routing dialog replaces the channel configurationg dialog. The Channel Routing dialog allows global (i.e. not per-preset) configuration of audio channels. You can now configure audio channels as Aux channels (e.g. using the non-guitar channel on your audio interface as an input for backing tracks), or sending unprocessed guitar input signal to an unused output channel for re-amping in a DAW or external hardware. Refer to the (?) help dialog in the Channel Routing Dialog for more information on how to use this feature.
PiPedal 2.0 can now be installed as a Progressive Web Application (PWA). This allows you to launch Pipedal from your desktop as a native desktop application, without extra browser address bars and window decorations.
### Features
- Support for Neural Amp Modeler A2 models. The latest version of the TooB Neural Amp Modeler plugin now supports NAM A2 models, which provide improved performance and sound quality compared to older NAM A1 models.
- Download Tone3000 NAM models and IIR files directly to the PiPedal server. Click on the DOWNLOAD MODELS FROM TONE3000 button in the NAM file selection dialog to download the latest NAM A2 models.
- Download IIR files directly into TooB Cab IR from Tone3000.
- New Channel Routing Dialog.
- Install PiPedal as a Progressive Web Application (PWA), which allows you to launch PiPedal from your desktop as a native desktop application. (A nice feature if you are accessing Pipedal from an Apple device).
- New TooB Tone plugin: a simple one-knob tone control. The new TooB Tone plugin provides a simple one-knob tone control that can be used to adjust the overall tone of your guitar signal. It is a great tool for quickly shaping your tone without having to use a more complex EQ plugin.
- Copy and Paste plugins (and their settings) between pedalboards. Accessible via the "Copy Plugin" and "Paste Plugin" menu items in the former Plugin Preset selector menu.
### Minor features and bug fixes
- Better handling of step sizes in numeric edit controls.
- Short names of TooB CE-2 Chorus and TooB BF-2 Flanger changed to "TooB Chorus" and "TooB Flanger", repsectively.
- Split controls now support double-tap to reset to a default value. (Other controls already support this feature).
## PiPedal 1.5.99 Beta
### Bug Fixes
+40 -5
View File
@@ -5,15 +5,50 @@ page_icon: img/Requirements2.jpg
{% include pageIcon.html %}
* A Raspberry PI 5, PI 4B or PI 400, with at least 2GB of RAM to run (4GB recommended). PiPedal will run on a PI 3, but you will not be able to use some plugins due to excessive CPU use.
* To build PiPedal, at least 8GB of RAM is required.
* An external USB Audio Adapter, or a Pi audio hat with at least one audio input, and one audio output.
### Processor and Memory Requirements
* An ARM64 or x86-64 CPU. Any of the following provide excellent performance.
* Raspberry Pi 5, Pi 500, Pi 500+ (or equivalent ARM64 single-board computer). Recommended.
* Intel N100-class NUCs or mini computers. Also recommended.
* Most AMD/Intel laptop or desktop CPUs.
* PiPedal will also run well on a Raspberry Pi 4.
* A Raspberry Pi 3 (or equivalent SBC) can run PiPedal, but may struggle to keep up. Not recommended.
* At least 2GB of RAM (4GB recommended).
* To compile PiPedal from source, at least 8GB of RAM is required.
### Audio System Requirements:
* On Raspberry Pi's, you will need an external USB Audio Adapter, or a Pi audio hat with at least one audio input. You cannot use the built-in audio inputs and outputs.
* On x86-64 systems, any standard audio interface with at least one input and one output will work; but an external USB audio interfaces is highly recommended.
### Operating System Support
PiPedal has been tested on the following Operating Systems:
* Raspberry Pi OS 64-bit Bookworm
* Raspberry Pi OS 64-bit Bookworm, or Trixie.
* Ubuntu 24.x or later, 64-bit (amd64/x86-64 and aarch64). Make sure you follow the [Ubuntu post-install instructions](https://rerdavies.github.io/pipedal/Configuring.html) to ensure your Ubuntu OS is using a realtime kernel. Pipedal will also run on Ubuntu Server 24.x or later.
Older versions of PiPedal (v1.1.31) have been tested on the following operating systems, but are no longer supported:
PiPedal may also run on other Debian 12 or Debian 13-based Linux distributions, but these have not been tested, and we can provide only limited support for these distributions, since we are not able to locally test every possible Linux distribution.
You may be able to compile Pipedal from sources on non-Debian Linux distributions, but working out compiler and library dependencies on non-Debian-based Linux distributions may be difficult, and is not officially supported. If you do manage to compile PiPedal on a non-Debian-based Linux distribution, please let us know how you did it, and we will publish instructions for others that follow in your footsteps.
A Linux kernel version of 5.15 or later is required, since support for USB audio adapters is poor in prior kernel versions. A Linux kernel version of 6.15 or later is recommended.
PiPedal requires a realtime-capable Linux kernel (a PREEMPT_RT kernel). Current versions of Ubuntu can be configured to enable PREEMPT_RT at runtime. (See the [Ubuntu post-install instructions](https://rerdavies.github.io/pipedal/Configuring.html) for details). Raspberry Pi OS 64-bit Bookworm and Trixie include a PREEMPT_RT kernel by default, so no additional configuration is required to enable PREEMPT_RT on these OS versions.
### Headless Operation
PiPedal provides best performance and audio latency when controlled from a remote browser, since GPU activity can interfere with real-time audio processing. The PiPedal server does not require a graphical desktop, so headless installs are fully supported (and more-or-less recommended). You can completely install and configure PiPedal using Linux commandline alone; however, you may find it easier to set up, configure and maintain your PiPedal server if you have a desktop environment installed.
You should not run Pipedal using a browser on the same machine that is running the PiPedal server, unless you have a relatively performant desktop or laptop class machine.
The Pipedal server does update itself automatically (it prompts for permission from the browser interface if there is an update available). However, it does not take care of Operating System updates. You should make sure to keep your OS up to date, and to install security updates as they become available. You will need to do that yourself from a commandline terminal, or using a desktop environment on the machine running the PiPedal server.
You may find it much easier to set up and maintain your PiPedal server if you enable SSH access on the server machine.
### Older Versions of PiPedal
Older versions of PiPedal (v1.1.31) have been tested on the following operating systems, but are no longer supported (and are not really recommended):
* Ubuntu 21.04 or later, 64-bit
* Raspberry PI OS 64-bit bullseye
+35 -7
View File
@@ -1,12 +1,13 @@
## Download
### Install for Ubuntu or Raspberry Pi OS (64-bit)
# Download PiPedal 2.0.102-alpha
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.99 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.99 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (amd64) v1.5.99 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_amd64.deb)
- [Raspberry Pi OS bookworm (aarch64) v2.0.102 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
- [Ubuntu 24.x, 25.04 (aarch64) v2.0.102 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
- [Ubuntu 24.x, 25.04 (amd64/x86_64) v2.0.102 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_amd64.deb)
Install the package by running
@@ -14,9 +15,36 @@ Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.5.99_arm64.deb
sudo apt install ./pipedal_2.0.102_arm64.deb
# or ... _amd64.deb as appropriate for your platform
```
You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is
expected, and can be safely ignored.
The message about missing permissions given by `apt` is
expected, and can be safely ignored. Apt is unable to unpack the debian package in a sandbox because the
package is a local file, rather than a file from the Internet. This is a known issue with apt, and does not indicate any problem with the package or installation process.
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
# Download PiPedal 1.5.99 Stable Release
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (amd64/x86_64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_amd64.deb)
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt install ./pipedal_1.5.99_arm64.deb
# or ... _amd64.deb as appropriate for your platform
```
The message about missing permissions given by `apt` is
expected, and can be safely ignored. Apt is unable to unpack the debian package in a sandbox because the
package is a local file, rather than a file from the Internet. This is a known issue with apt, and does not indicate any problem with the package or installation process.
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+36 -4
View File
@@ -5,17 +5,49 @@
<h2 style="font-weight: 300; font-size: 1.6em;line-height: 1em; padding-top: 32px; padding-bottom: 0px;margin-bottom: 4px">PiPedal Guitar Effects Processor</h2>
<p style="padding-top: 0px; opacity: 0.6; padding-bottom: 16px">A Raspberry Pi-based stomp box designed to be controlled from a phone or tablet.</p>
<div style="padding-left:48px; padding-right: 48px; padding-top: 32px; padding-bottom: 16px;
background-color: #DCDCFF; border-radius: 8px; border: 0px solid #555;
margin-bottom: 48px; margin-top: 16px;
margin-left: 16px; margin-right: 48px;
box-shadow: 1px 4px 12px rgba(0, 0, 0, 0.3);">
<div style="opacity: 0.8;">
<h2 style='font-weight: 300; font-family: "Roboto Light", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-weight: 400; font-size: 1.3em;line-height: 1.2em; padding-top: 0px; padding-bottom: 16px;margin-bottom: 8px'
>Announcing <a href="PiPedal2.html">PiPedal 2.0</a></h2>
<p>
The first public preview release of Pipedal version 2.0.102-alpha is now available!
</p>
<p>This release includes </p>
<ul>
<li style="margin-bottom: 8px">
Support for Neural Amp Modeler A2 models&mdash;the next generation of the ground-breaking
Neural Amp Modeler technology.</li>
<li style="margin-bottom: 8px">
Integration with Tone3000.com services for easy downloading of Neural Amp Modeler A2 models and IIRs.</li>
<li style="margin-bottom: 8px">
A new Channel Routing dialog for global routing of auxiliary input channels, and unprocessed re-amp output channels.</li>
<li>Install Pipedal as a Progressive Web Application (PWA), which allows you to launch Pipedal from your desktop as a native desktop application. (A nice feature if you are accessing Pipedal from an Apple device).</li>
<li style="margin-bottom: 8px">
Many other minor features, improvements, and bug fixes.</li>
</ul>
<p>For more information, and to download PiPedal v2.0.102-alpha <a href="PiPedal2.html">click here</a>.
</p>
</div>
</div>
<a href="https://rerdavies.github.io/pipedal/ReleaseNotes"><img src="https://img.shields.io/github/v/release/rerdavies/pipedal?color=%23808080"/></a>
<a href="https://rerdavies.github.io/pipedal/download"><img src="https://img.shields.io/badge/Download-008060" /></a>
<a href="https://rerdavies.github.io/pipedal/Documentation"><img src="https://img.shields.io/badge/Docmentation-0060d0"/></a>
_To download PiPedal, click [*here*](download.md).
<a href="https://rerdavies.github.io/pipedal/Documentation"><img src="https://img.shields.io/badge/Documentation-0060d0"/></a>
_To download PiPedal v1.5.99, click [*here*](download.md).
To view PiPedal documentation, click [*here*](Documentation.md)._
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.
PiPedal will also run on Ubuntu 22.x (amd64/x64 and aarch64). Make sure you follow the [Ubuntu post-install
PiPedal will also run on Ubuntu 24.x or later (amd64/x86-64 and aarch64). Make sure you follow the [Ubuntu post-install
instructions](https://rerdavies.github.io/pipedal/Configuring.html) to make sure your Ubuntu OS is using a realtime-capable kernel.
{% include gallery.html %}
+1 -1
View File
@@ -93,7 +93,7 @@ cabir:impulseFile3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
+1 -1
View File
@@ -50,7 +50,7 @@ toob:frequencyResponseVector
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
mod:brand "TooB";
mod:label "TooB CabSim";
@@ -53,7 +53,7 @@ toobimpulse:impulseFile
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Toob Convolution Reverb Stereo uses convolution reverb impulse/response files in order to produce highly
@@ -51,7 +51,7 @@ toobimpulse:impulseFile
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
+1 -1
View File
@@ -66,7 +66,7 @@ inputStage:filterGroup
doap:license <https://two-play.com/TooB/licenses/isc> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
mod:brand "TooB";
mod:label "TooB Input";
+1 -1
View File
@@ -1,4 +1,4 @@
© 2020-2023, Robin E. R. Davies
© 2020-2026, Robin E. R. Davies
The MIT License
+1 -1
View File
@@ -68,7 +68,7 @@ pstage:stage3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
mod:brand "TooB";
mod:label "Power Stage";
+1 -1
View File
@@ -57,7 +57,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment "TooB spectrum analyzer" ;
mod:brand "TooB";
+2 -2
View File
@@ -57,13 +57,13 @@ tonestack:eqGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
rdfs:comment """
TooB Tone Stack emulates the tone controls of a 59' Bassman, a JCM8000, or a Baxandall
tone stack used in Polytone and HiWatt amps.
tone stack (used in Polytone and HiWatt amps).
""" ;
mod:brand "TooB";
+3 -3
View File
@@ -45,14 +45,14 @@ toob:frequencyResponseVector
<http://two-play.com/plugins/toob-three-band-eq>
a lv2:Plugin ,
lv2:EQPlugin ;
doap:name "TooB 3 Band EQ (mono)" ,
"TooB 3 Band EQ (mono)"@en-gb-gb
doap:name "TooB 3 Band EQ (Mono)" ,
"TooB 3 Band EQ (Mono)"@en-gb-gb
;
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
uiext:ui <http://two-play.com/plugins/toob-three-band-eq-ui>;
@@ -52,7 +52,7 @@ toob:frequencyResponseVector
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
uiext:ui <http://two-play.com/plugins/toob-three-band-eq-stereo-ui>;
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -40,14 +40,14 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Emulation of a Boss CE-2 Chorus.
""" ;
mod:brand "TooB";
mod:label "TooB CE-2";
mod:label "TooB Chorus";
lv2:optionalFeature lv2:hardRTCapable;
lv2:port
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A straightforward no-frills digital delay.
+2 -2
View File
@@ -41,14 +41,14 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
""" ;
mod:brand "TooB";
mod:label "TooB BF-2";
mod:label "TooB Flanger";
lv2:optionalFeature lv2:hardRTCapable;
lv2:port
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Digital emulation of a Boss BF-2 Flanger.
@@ -52,7 +52,7 @@ should use the monophonic version of this effect.
""" ;
mod:brand "TooB";
mod:label "TooB BF-2 Stereo";
mod:label "TooB Flanger (Stereo)";
lv2:optionalFeature lv2:hardRTCapable;
lv2:port
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration.
+5 -5
View File
@@ -33,21 +33,21 @@
<http://two-play.com/plugins/toob-graphiceq>
a lv2:Plugin ,
lv2:EQPlugin ;
doap:name "TooB GE-7 Graphic Eq" ,
"TooB GE-7 Graphic Eq"@en-gb-gb
lv2:MultiEQPlugin ;
doap:name "TooB Graphic Eq" ,
"TooB Graphic Eq"@en-gb-gb
;
uiext:ui <http://two-play.com/plugins/toob-graphiceq-ui> ;
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A 7 octave graphic equalizer, with frequencies chosen to be useful for EQ-ing guitar signals.
""" ;
mod:brand "TooB";
mod:label "GE-7 Graphic Eq";
mod:label "Graphic Eq";
lv2:optionalFeature lv2:hardRTCapable;
lv2:port [
+2 -2
View File
@@ -91,8 +91,8 @@ myprefix:output_group
;
doap:license <https://opensource.org/license/mit/> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 2 ;
lv2:microVersion 73 ;
lv2:minorVersion 3 ;
lv2:microVersion 78 ;
ui:ui <http://two-play.com/plugins/toob-looper-four-ui>;
+2 -2
View File
@@ -77,8 +77,8 @@ myprefix:output_group
doap:license <https://opensource.org/license/mit/> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 2 ;
lv2:microVersion 73 ;
lv2:minorVersion 3 ;
lv2:microVersion 78 ;
ui:ui <http://two-play.com/plugins/toob-looper-one-ui>;
+1 -1
View File
@@ -67,7 +67,7 @@ toobml:sagGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
using neural-network-based machine learning models of real amplifiers.
+1 -1
View File
@@ -40,7 +40,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Remix a stereo input signal.
+119 -39
View File
@@ -44,7 +44,9 @@ toobNam:modelFile
a lv2:Parameter;
rdfs:label "Model";
mod:fileTypes "nam,nammodel";
rdfs:range atom:Path.
rdfs:range atom:Path.
toobNam:eqGroup
a param:ControlGroup ,
@@ -72,7 +74,7 @@ toobNam:calibrationGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
TooB Neural Amp Modeler is a neural network based Amp Simulator. It uses .nam model files that are generated by training nueral networks
on audio recordings of actual guitar amps, effects pedals and other equipment. .nam files contain data from the trained models, which can be loaded into
@@ -90,11 +92,13 @@ are constantly coming up with new and ingenious applications for NAM models.
.nam model files can be donwloaded from the internet. There are many online sources for .nam model files, both free, and commercial. Foremost among these is the
http://tone3000.com website, which provides a vast collection of free .nam models files that have been developed and uploaded by a large online community of NAM users.
Loading .nam model files into PiPedal is a two-step process. First, download model files to your local computer, or your phone or tablet, using a
web browser. Then upload the model files to the PiPedal server using the PiPedal web interface. You can find an "Upload" button in
the file browser when you click on the "Model" control in the plugin's user interface within the PiPedal web interface.
To upload Tone3000 models directly to the PiPedal server, click on the Model control, and then click on the "DOWNLOAD MODEL FILES FROM TONE3000" button at the bottom
of the browser dialog. This will bring up a browser window for Tone3000 NAM models. When you select a tone bundle, the bundle will be uploaded diretly to the PiPedal server.
If you are not using PiPedal, just click on the Model control, and use the file browser to select the .nam file directly on your local system.
If you obtain .nam models from other source (e.g. commercial tone bundles), you can upload .nam models, or .zip files containing bundles of .nam modesl to
the pipedal server by tapping the "UPLOAD" button found at the bottom of the model file browser dialog.
If you are not using PiPedal, just click on the Model control, and use the host application's file browser to select .nam files directly on your local system.
TooB Neural Amp Modeler supports a much wider range of amp models than ToobML, but usually uses more CPU.
You will need at least a Pi 4 to use Toob Neural Amp Modeler, and you may need to increase your audio buffer sizes to prevent overruns.
@@ -122,23 +126,34 @@ what the threshold is; but the effect on performance is pretty obvious when heat
Note that heat throttling is done to protect your processor from permanent damage due to overheating; but there's no real risk of actual
damage, since heat throttling thresholds are set well below the point at which damage occurs.
TooB Neural Amp Modeler is based on technology developed by the NeuralAmp Modeler Core project (https://www.neuralampmodeler.com/). The TooB team wishes
TooB NAM now supports NAM A2 models, but supports the older NAM A1 format as well. Models downloaded through TooB NAM's "DOWNLOAD MODEL FILES FROM TONE3000" button will usually
be A2 models. Currently, tone bundles downloaded from the Tone3000 website directly will be A1 models. Model bundles that you obtain from other sources may also
be A1 or A2 models.
A2 models generally offer better audio quality and lower CPU use than comparable A1 models.
One of the key new features introduced in NAM A2 is support for "Slimmable" models. A Slimmable model
is a single model file that contains multiple quality tiers, allowing you to interactively trade off
audio quality against CPU usage at runtime. When you load a Slimmable A2 model, a new "Slim" dial
appears in the TooB NAM controls. Turn it down to use a lighter, lower-quality tier that saves CPU;
turn it up to use a higher-quality tier at greater CPU cost.
The number of quality tiers available depends on how the model was trained. Tone3000 A2 models
currently provide two tiers: a Slim weight of 0.5, and a Slim weight of 1.0. The lighter tier (0.5)
delivers quality comparable to a typical A1 model but at noticeably lower CPU cost. The heavier tier
(1.0) delivers significantly better quality than comparable A1 models, with only a modest increase in
CPU use. Future A2 models may provide more flexible Slimmable weight settings.
---
TooB Neural Amp Modeler uses code from the Neural Amp Modeler Core project (https://www.neuralampmodeler.com/). The TooB team wishes
to express gratitude to Steven Atkinson for making this revolutionary technology available as open-source code. This is groundbreaking
technology that changes everything. None of this would have happened without the work that Steven has done.
TooB Neural Amp Modeler uses code from the NeuralAudio project (https://github.com/mikeoliphant/NeuralAudio). The Neural Audio project provides highly
optimized implementations of Neural Amp models. The TooB team wishes to express gratitude to Mike Oliphant for
making his optimizations work available as open-source code.
Steve and Mike have done all of the hard work that was required to make NAM technology available through TooB Neural Amp Modeler. They
deserve all the credit.
Code from the the NeuralAudio project (https://github.com/mikeoliphant/NeuralAudio) is provided
under the following license.
technology that changes everything. None of this would have happened without the work that Steven has done. Code from the Neural Amp
Modeler core project is provided under the following license:
MIT License
Copyright (c) 2024, 2025 Mike Oliphant
Copyright (c) Steven Atkinson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -158,12 +173,23 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Code from the the NeuralAmpModelerCore project (https://github.com/sdatkinson/NeuralAmpModelerCore) is provided
---
TooB Neural Amp Modeler uses code from the NeuralAudio project (https://github.com/mikeoliphant/NeuralAudio). The Neural Audio project provides highly
optimized code for executing NAM A1 models. The TooB team wishes to express gratitude to Mike Oliphant for
making his optimizations work available as open-source code.
Steve and Mike have done all of the hard work that was required to make NAM technology available through TooB Neural Amp Modeler. They
deserve all the credit.
Code from the the NeuralAudio project (https://github.com/mikeoliphant/NeuralAudio) is provided
under the following license.
MIT License
Copyright (c) 2025 Steven Atkinson
Copyright (c) Mike Oliphant
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -232,6 +258,19 @@ SOFTWARE.
lv2:ControlPort ;
lv2:index 2;
lv2:symbol "modelSize" ;
lv2:name "Slim";
lv2:default 0.0 ;
lv2:minimum 0.0;
lv2:maximum 1.0;
lv2:portProperty epp:expensive;
rdfs:comment "Model quality for NAM A2 models that support slimmable models.";
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 3;
lv2:symbol "outputGain" ;
lv2:name "Output Gain";
lv2:default 0.0 ;
@@ -245,7 +284,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 3;
lv2:index 4;
lv2:symbol "gate" ;
lv2:name "Noise Gate";
lv2:default -120.0 ;
@@ -264,7 +303,7 @@ SOFTWARE.
a lv2:OutputPort ,
lv2:ControlPort ;
lv2:index 4;
lv2:index 5;
lv2:symbol "gateOut" ;
lv2:name "\u00A0";
lv2:default 0.0;
@@ -278,7 +317,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 5;
lv2:index 6;
lv2:symbol "buffer" ;
lv2:name "Threaded";
lv2:default 0.0 ;
@@ -295,7 +334,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 6 ;
lv2:index 7 ;
lv2:symbol "toneStack" ;
lv2:name "Type";
lv2:default 3.0 ;
@@ -324,7 +363,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 7;
lv2:index 8;
lv2:symbol "bass" ;
lv2:name "Bass";
lv2:default 5.0;
@@ -338,7 +377,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 8;
lv2:index 9;
lv2:symbol "mid" ;
lv2:name "Mid";
lv2:default 5.0;
@@ -351,13 +390,13 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 9;
lv2:index 10;
lv2:symbol "treble" ;
lv2:name "Treble";
lv2:default 5.0;
lv2:minimum 0.0;
lv2:maximum 10.0;
rdfs:comment "Bass";
rdfs:comment "Treble";
pg:group toobNam:eqGroup ;
],
@@ -367,7 +406,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 10 ;
lv2:index 11 ;
lv2:symbol "inputCalibrationMode" ;
lv2:name "Input Calibration";
lv2:default 1.0 ;
@@ -381,6 +420,7 @@ SOFTWARE.
rdfs:label "Calibrated" ;
rdf:value 1.0
];
rdfs:comment "Calibration method for audio input.";
pg:group toobNam:calibrationGroup ;
@@ -389,7 +429,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 11 ;
lv2:index 12 ;
lv2:symbol "calibration" ;
lv2:name "Value";
lv2:default -6.0 ;
@@ -404,13 +444,13 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 12;
lv2:index 13;
lv2:symbol "outputCalibration" ;
lv2:name "Output Calibration";
lv2:default 0.0;
lv2:minimum 0.0;
lv2:maximum 2.0;
rdfs:comment "Scaling applied to model output";
rdfs:comment "Calbration method for audio output";
lv2:portProperty lv2:enumeration ;
lv2:scalePoint [
@@ -431,7 +471,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 13 ;
lv2:index 14 ;
lv2:symbol "version" ;
lv2:name "Preset Version";
lv2:default 1.0 ;
@@ -439,19 +479,59 @@ SOFTWARE.
lv2:maximum 1.0;
lv2:portProperty epp:notOnGUI;
],
[
a lv2:OutputPort ,
lv2:ControlPort ;
lv2:index 15 ;
lv2:symbol "modelType" ;
lv2:name "";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 4.0;
lv2:portProperty lv2:enumeration ;
lv2:scalePoint [
rdfs:label "" ;
rdf:value 0.0
] , [
rdfs:label "A1" ;
rdf:value 1.0
] , [
rdfs:label "A2" ;
rdf:value 2.0
] , [
rdfs:label "AidaX" ;
rdf:value 3.0
] , [
rdfs:label "Other" ;
rdf:value 4.0
];
],
[
a lv2:OutputPort ,
lv2:ControlPort ;
lv2:index 16 ;
lv2:symbol "currentModelWeight" ;
lv2:name "";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
lv2:portProperty epp:notOnGUI;
],
[
a lv2:AudioPort ,
lv2:InputPort ;
lv2:index 14 ;
lv2:index 17 ;
lv2:symbol "in" ;
lv2:name "In"
],
[
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 15 ;
lv2:index 18 ;
lv2:symbol "out" ;
lv2:name "Out"
],
@@ -463,7 +543,7 @@ SOFTWARE.
lv2:designation lv2:control ;
atom:supports patch:Message ;
lv2:index 16 ;
lv2:index 19 ;
lv2:symbol "control" ;
lv2:name "Control" ;
rdfs:comment "Control" ;
@@ -473,7 +553,7 @@ SOFTWARE.
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 17;
lv2:index 20;
lv2:symbol "notify" ;
lv2:name "Notify" ;
rdfs:comment "Notification" ;
@@ -520,7 +600,7 @@ toobNam:ui
pipedal_ui:frequencyPlot
[
pipedal_ui:patchProperty toobNam:FrequencyResponse;
lv2:index 10 ;
lv2:index 11 ;
pg:group toobNam:eqGroup ;
pipedal_ui:width: 120.0 ;
]
+1 -1
View File
@@ -54,7 +54,7 @@ noisegate:envelope_group
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A noise gate is an audio processing tool that controls the volume of an audio signal
by allowing it to pass through only when it exceeds a set threshold.
+1 -1
View File
@@ -87,7 +87,7 @@ parametric_eq:hfGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
uiext:ui <http://two-play.com/plugins/toob-parametric-eq-ui>;
@@ -87,7 +87,7 @@ toob-parametric-eq-stereo:hfGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
uiext:ui <http://two-play.com/plugins/toob-parametric-eq-stereo-ui>;
+1 -1
View File
@@ -40,7 +40,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A loose emulation of an MXR® Phase 90 Phaser.
+2 -2
View File
@@ -60,7 +60,7 @@ toobPlayer:seek
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button.
@@ -84,7 +84,7 @@ folder artwork files that have a .jpg file extension.
mod:label "File Player";
# ONLY works in PiPedal
lv2:requiredFeature "http://github.com/rerdavies/pipedal#host" ;
lv2:requiredFeature <http://github.com/rerdavies/pipedal#host> ;
lv2:optionalFeature lv2:hardRTCapable,state::freePath, state:makePath, state:mapPath, work:schedule ;
lv2:extensionData state:interface, work:interface;
+2 -2
View File
@@ -50,8 +50,8 @@ recordPrefix:audioFile
;
doap:license <https://opensource.org/license/mit/> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 2 ;
lv2:microVersion 73 ;
lv2:minorVersion 3 ;
lv2:microVersion 78 ;
ui:ui <http://two-play.com/plugins/toob-record-mono-ui>;
+2 -2
View File
@@ -87,8 +87,8 @@ myprefix:loop3_group
;
doap:license <https://opensource.org/license/mit/> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 2 ;
lv2:microVersion 73 ;
lv2:minorVersion 3 ;
lv2:microVersion 78 ;
ui:ui <http://two-play.com/plugins/toob-record-stereo-ui>;
+216
View File
@@ -0,0 +1,216 @@
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix mod: <http://moddevices.com/ns/mod#> .
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix toob: <http://two-play.com/plugins/toob#> .
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
@prefix pipedal_patch: <http://github.com/rerdavies/pipedal/patch#> .
<http://two-play.com/rerdavies#me>
a foaf:Person ;
foaf:name "Robin Davies" ;
foaf:mbox <mailto:rerdavies@gmail.com> ;
foaf:homepage <https://github.com/sponsors/rerdavies> .
<http://two-play.com/plugins/toob-tone>
a lv2:Plugin ,
lv2:EQPlugin;
doap:name "TooB Tone (Mono)" ,
"TooB Tone (Mono)"@en-gb-gb
;
uiext:ui <http://two-play.com/plugins/toob-tone-ui> ;
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 78 ;
rdfs:comment """
TooB Tone is a simple tone control. For more flexible EQ plugins, see TooB 3 Band Eq, or TooB Parameteric EQ.
""" ;
mod:brand "TooB";
mod:label "Tone";
lv2:optionalFeature lv2:hardRTCapable;
pipedal_patch:readable
toob:frequencyResponseVector;
lv2:port
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 0 ;
lv2:symbol "tone" ;
lv2:name "Tone";
lv2:default 0.0 ;
lv2:minimum -1 ;
lv2:maximum 1 ;
rdfs:comment "Tone";
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 1 ;
lv2:symbol "gain" ;
lv2:name "Gain";
lv2:default 0.0 ;
lv2:minimum -40.0 ;
lv2:maximum 30.0;
units:unit units:db;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 2 ;
lv2:symbol "lowFc" ;
lv2:name "Low Fc";
lv2:default 480.0 ;
lv2:minimum 110.0 ;
lv2:maximum 1000.0;
units:unit units:hz;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 3 ;
lv2:symbol "lowBoost" ;
lv2:name "Low Boost";
lv2:default -17.0 ;
lv2:minimum -30.0 ;
lv2:maximum 0.0;
units:unit units:db;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 4 ;
lv2:symbol "highFc" ;
lv2:name "High Fc";
lv2:default 480.0 ;
lv2:minimum 110.0 ;
lv2:maximum 2000.0;
units:unit units:hz;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 5 ;
lv2:symbol "highBoost" ;
lv2:name "High Boost";
lv2:default -10.0 ;
lv2:minimum -30.0 ;
lv2:maximum 0.0;
units:unit units:db;
],
[
a atom:AtomPort ,
lv2:InputPort;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 6 ;
lv2:symbol "controlIn" ;
lv2:name "Control" ;
rdfs:comment "Plugin to GUI communication" ;
] ,
[
a atom:AtomPort ,
lv2:OutputPort ;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 7 ;
lv2:symbol "notifyOut" ;
lv2:name "Notify" ;
rdfs:comment "Plugin to GUI communication" ;
],
# Audio
[
a lv2:AudioPort ,
lv2:InputPort ;
lv2:index 8 ;
lv2:symbol "in" ;
lv2:name "In"
],
[
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 9 ;
lv2:symbol "out" ;
lv2:name "Out"
]
.
<http://two-play.com/plugins/toob-tone-ui>
a uiext:X11UI ;
lv2:binary <ToobAmpUI.so>;
lv2:extensionData uiext::idle ;
lv2:extensionData uiext:resize ;
lv2:extensionData uiext:idleInterface;
lv2:requiredFeature uiext:idleInterface ;
.
<http://two-play.com/plugins/toob-tone>
pipedal_ui:ui <http://two-play.com/plugins/toob-tone#piPedalUI>.
<http://two-play.com/plugins/toob-tone#piPedalUI>
a pipedal_ui:ui ;
pipedal_ui:frequencyPlot
[
lv2:index 0 ;
pipedal_ui:patchProperty toob:frequencyResponseVector;
pipedal_ui:xLeft 30 ;
pipedal_ui:xRight 22050 ;
pipedal_ui:yTop 10 ;
pipedal_ui:yBottom -35 ;
pipedal_ui:xLog 1 ;
pipedal_ui:yDb 1 ;
pipedal_ui:width 100 ;
]
.
+231
View File
@@ -0,0 +1,231 @@
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix mod: <http://moddevices.com/ns/mod#> .
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix toob: <http://two-play.com/plugins/toob#> .
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
@prefix pipedal_patch: <http://github.com/rerdavies/pipedal/patch#> .
<http://two-play.com/rerdavies#me>
a foaf:Person ;
foaf:name "Robin Davies" ;
foaf:mbox <mailto:rerdavies@gmail.com> ;
foaf:homepage <https://github.com/sponsors/rerdavies> .
<http://two-play.com/plugins/toob-tone-stereo>
a lv2:Plugin ,
lv2:EQPlugin;
doap:name "TooB Tone (Stereo)" ,
"TooB Tone (Stereo)"@en-gb-gb
;
uiext:ui <http://two-play.com/plugins/toob-tone-stereo-ui> ;
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 78 ;
rdfs:comment """
TooB Tone is a simple tone control. For more flexible EQ plugins, see TooB 3 Band Eq, or TooB Parameteric EQ.
""" ;
mod:brand "TooB";
mod:label "Tone";
lv2:optionalFeature lv2:hardRTCapable;
pipedal_patch:readable
toob:frequencyResponseVector;
lv2:port
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 0 ;
lv2:symbol "tone" ;
lv2:name "Tone";
lv2:default 0.0 ;
lv2:minimum -1 ;
lv2:maximum 1 ;
rdfs:comment "Tone";
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 1 ;
lv2:symbol "gain" ;
lv2:name "Gain";
lv2:default 0.0 ;
lv2:minimum -40.0 ;
lv2:maximum 30.0;
units:unit units:db;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 2 ;
lv2:symbol "lowFc" ;
lv2:name "Low Fc";
lv2:default 110.0 ;
lv2:minimum 480.0 ;
lv2:maximum 1000.0;
units:unit units:hz;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 3 ;
lv2:symbol "lowBoost" ;
lv2:name "Low Boost";
lv2:default -17.0 ;
lv2:minimum -30.0 ;
lv2:maximum 0.0;
units:unit units:db;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 4 ;
lv2:symbol "highFc" ;
lv2:name "High Fc";
lv2:default 480.0 ;
lv2:minimum 110.0 ;
lv2:maximum 2000.0;
units:unit units:hz;
],
[
lv2:portProperty epp:notOnGUI; # Tuning
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 5 ;
lv2:symbol "highBoost" ;
lv2:name "High Boost";
lv2:default -10.0 ;
lv2:minimum -30.0 ;
lv2:maximum 0.0;
units:unit units:db;
],
[
a atom:AtomPort ,
lv2:InputPort;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 6 ;
lv2:symbol "controlIn" ;
lv2:name "Control" ;
rdfs:comment "Plugin to GUI communication" ;
] ,
[
a atom:AtomPort ,
lv2:OutputPort ;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 7 ;
lv2:symbol "notifyOut" ;
lv2:name "Notify" ;
rdfs:comment "Plugin to GUI communication" ;
],
[
a lv2:AudioPort ,
lv2:InputPort ;
lv2:index 8 ;
lv2:symbol "in" ;
lv2:name "InL"
],
[
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 9 ;
lv2:symbol "out" ;
lv2:name "OutL"
],
[
a lv2:AudioPort ,
lv2:InputPort ;
lv2:index 10 ;
lv2:symbol "inR" ;
lv2:name "InR"
],
[
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 11 ;
lv2:symbol "outR" ;
lv2:name "OutR"
]
.
<http://two-play.com/plugins/toob-tone-stereo-ui>
a uiext:X11UI ;
lv2:binary <ToobAmpUI.so>;
lv2:extensionData uiext::idle ;
lv2:extensionData uiext:resize ;
lv2:extensionData uiext:idleInterface;
lv2:requiredFeature uiext:idleInterface ;
.
<http://two-play.com/plugins/toob-tone-stereo>
pipedal_ui:ui <http://two-play.com/plugins/toob-tone-stereo#piPedalUI>.
<http://two-play.com/plugins/toob-tone-stereo#piPedalUI>
a pipedal_ui:ui ;
pipedal_ui:frequencyPlot
[
lv2:index 0 ;
pipedal_ui:patchProperty toob:frequencyResponseVector;
pipedal_ui:xLeft 30 ;
pipedal_ui:xRight 22050 ;
pipedal_ui:yTop 10 ;
pipedal_ui:yBottom -35 ;
pipedal_ui:xLog 1 ;
pipedal_ui:yDb 1 ;
pipedal_ui:width 100 ;
]
.
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps.
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps.
+2 -1
View File
@@ -40,7 +40,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
TooB Tuner is a chromatic guitar tuner.
""" ;
@@ -150,6 +150,7 @@ TooB Tuner is a chromatic guitar tuner.
lv2:extensionData uiext::idle ;
lv2:extensionData uiext:resize ;
lv2:extensionData uiext:showInterface ;
lv2:extensionData uiext:idleInterface;
lv2:requiredFeature uiext:idleInterface ;
.
+1 -1
View File
@@ -39,7 +39,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 73 ;
lv2:microVersion 78 ;
rdfs:comment """
Volume control.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -135,6 +135,15 @@
lv2:binary <ToobAmp.so> ;
rdfs:seeAlso <ToobVolume.ttl> .
<http://two-play.com/plugins/toob-tone> a lv2:Plugin ;
lv2:binary <ToobAmp.so> ;
rdfs:seeAlso <ToobTone.ttl> .
<http://two-play.com/plugins/toob-tone-stereo> a lv2:Plugin ;
lv2:binary <ToobAmp.so> ;
rdfs:seeAlso <ToobToneStereo.ttl> .
<http://two-play.com/plugins/toob-noise-gate> a lv2:Plugin ;
lv2:binary <ToobAmp.so> ;
rdfs:seeAlso <ToobNoiseGate.ttl> .
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

+5 -1
View File
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.16.0)
add_subdirectory("SQLiteCpp")
# Prevent installation of SQLite built components. (we use the .a library files directly)
add_subdirectory("SQLiteCpp" EXCLUDE_FROM_ALL)
set(SDBUSCPP_BUILD_CODEGEN off)
set(SDBUSCPP_INSTALL off)
@@ -23,3 +26,4 @@ option(BUILD_OSSFUZZ "Build fuzzers for ossfuzz" OFF)
option(BUILD_EXAMPLES "Build examples" OFF)
option(BUILD_DOC "Build documentation" OFF)
add_subdirectory("libzip")
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
build/src/pipedald "/etc/pipedal/config" "/etc/pipedal/react" "-port" "0.0.0.0:8080"
+40
View File
@@ -0,0 +1,40 @@
#include "AesDigest.hpp"
#include <array>
#include <openssl/evp.h>
#include <stdexcept>
using namespace pipedal;
std::string pipedal::AesDigest(const std::string &text)
{
std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
size_t digestLength = 0;
if (EVP_Q_digest(
nullptr,
"SHA256",
nullptr,
reinterpret_cast<const unsigned char *>(text.data()),
text.size(),
digest.data(),
&digestLength)
!= 1)
{
throw std::runtime_error("EVP_Q_digest(SHA256) failed.");
}
static constexpr char hexDigits[] = "0123456789abcdef";
std::string result(digestLength * 2, '\0');
for (size_t i = 0; i < digestLength; ++i)
{
unsigned char value = digest[i];
result[2 * i] = hexDigits[(value >> 4) & 0x0F];
result[2 * i + 1] = hexDigits[value & 0x0F];
}
return result;
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) 2026 Robin E. R. Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <string>
namespace pipedal {
std::string AesDigest(const std::string &text);
}
+455 -149
View File
@@ -23,6 +23,7 @@
*/
#include "pch.h"
#include "PiPedalCommon.hpp"
#include "util.hpp"
#include <cmath>
#include "Finally.hpp"
@@ -39,6 +40,7 @@
#include "CrashGuard.hpp"
#include <iostream>
#include <iomanip>
#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp"
@@ -271,7 +273,7 @@ namespace pipedal
auto inAvail = snd_pcm_avail_update(this->captureHandle);
auto outAvail = snd_pcm_avail_update(this->playbackHandle);
auto total = (inAvail >= 0? inAvail: 0) + (outAvail >= 0 ? outAvail: 0) + framesInBuffer;
auto total = (inAvail >= 0 ? inAvail : 0) + (outAvail >= 0 ? outAvail : 0) + framesInBuffer;
bufferTraces[bufferTraceIndex++] = {
time,
inAvail,
@@ -328,11 +330,20 @@ namespace pipedal
bool inputSwapped = false;
bool outputSwapped = false;
std::vector<float *> activeCaptureBuffers;
std::vector<float *> activePlaybackBuffers;
std::vector<std::vector<float>> allocatedBuffers;
std::vector<float *> deviceCaptureBuffers;
std::vector<float *> devicePlaybackBuffers;
float *zeroInputBuffer = nullptr;
float *discardOutputBuffer = nullptr;
std::vector<float *> mainCaptureBuffers;
std::vector<float *> mainPlaybackBuffers;
std::vector<float *> mainVuPlaybackBuffers;
std::vector<float *> auxCaptureBuffers;
std::vector<float *> auxPlaybackBuffers;
std::vector<float *> auxVuPlaybackBuffers;
std::vector<float *> captureBuffers;
std::vector<float *> playbackBuffers;
std::vector<uint8_t> rawCaptureBuffer;
std::vector<uint8_t> rawPlaybackBuffer;
@@ -342,7 +353,9 @@ namespace pipedal
void validate_capture_handle()
{ // leftover debugging for a buffer overrun :-/
#ifdef DEBUG
if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW)
auto pcmType = snd_pcm_type(captureHandle);
if (pcmType != SND_PCM_TYPE_HW && pcmType != SND_PCM_TYPE_NULL)
{
throw std::runtime_error("Capture handle has been overwritten");
}
@@ -742,7 +755,7 @@ namespace pipedal
{
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -760,7 +773,7 @@ namespace pipedal
{
float *p = getCaptureBuffer<float>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -776,7 +789,7 @@ namespace pipedal
{
int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr double scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -792,7 +805,7 @@ namespace pipedal
{
int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -809,7 +822,7 @@ namespace pipedal
{
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -825,7 +838,7 @@ namespace pipedal
{
uint8_t *p = getCaptureBuffer<uint8_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame)
@@ -842,7 +855,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame)
@@ -859,7 +872,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -875,7 +888,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -891,7 +904,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers;
std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -907,7 +920,7 @@ namespace pipedal
{
int16_t *p = (int16_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits<int16_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -927,7 +940,7 @@ namespace pipedal
{
int16_t *p = (int16_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits<int16_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -947,7 +960,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -969,7 +982,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame)
@@ -991,7 +1004,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame)
@@ -1011,7 +1024,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1031,7 +1044,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1056,7 +1069,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1082,7 +1095,7 @@ namespace pipedal
{
float *p = (float *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -1097,7 +1110,7 @@ namespace pipedal
{
float *p = (float *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers;
std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -1110,27 +1123,9 @@ namespace pipedal
}
}
public:
void TestFormatEncodeDecode(snd_pcm_format_t captureFormat);
private:
void AllocateBuffers(std::vector<float *> &buffers, size_t n)
{
buffers.resize(n);
for (size_t i = 0; i < n; ++i)
{
buffers[i] = new float[this->bufferSize];
for (size_t j = 0; j < this->bufferSize; ++j)
{
buffers[i][j] = 0;
}
}
}
JackChannelSelection channelSelection;
bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) override
{
this->isDummyDriver = jackServerSettings.IsDummyAudioDevice();
terminateAudio_ = false;
if (open)
{
@@ -1154,6 +1149,27 @@ namespace pipedal
}
}
public:
void TestFormatEncodeDecode(snd_pcm_format_t captureFormat);
private:
void AllocateBuffers(std::vector<float *> &buffers, size_t n)
{
buffers.resize(n);
for (size_t i = 0; i < n; ++i)
{
buffers[i] = new float[this->bufferSize];
for (size_t j = 0; j < this->bufferSize; ++j)
{
buffers[i][j] = 0;
}
}
}
ChannelSelection channelSelection;
bool open = false;
void RestartAlsa()
{
std::lock_guard lock{restartMutex};
@@ -1180,13 +1196,13 @@ namespace pipedal
throw std::runtime_error("Unable to restart the audio stream.");
}
int err;
if ((err = snd_pcm_start(captureHandle)) < 0)
{
Lv2Log::error(SS("Unable to restart ALSA capture: " << snd_strerror(err)));
throw PiPedalStateException("Unable to restart ALSA capture.");
}
TraceBufferPositions(0,'+');
TraceBufferPositions(0, '+');
audioRunning = true;
}
@@ -1249,7 +1265,7 @@ namespace pipedal
rawCaptureBuffer.resize(captureFrameSize * bufferSize * 2);
memset(rawCaptureBuffer.data(), 0, rawCaptureBuffer.size());
AllocateBuffers(captureBuffers, captureChannels);
AllocateBuffers(deviceCaptureBuffers, captureChannels);
}
virtual std::string GetConfigurationDescription()
@@ -1260,8 +1276,10 @@ namespace pipedal
<< ", " << GetAlsaFormatDescription(this->captureFormat)
<< ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers
<< ", in: " << this->InputBufferCount() << "/" << this->captureChannels
<< ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels);
<< ", " << "device: " << this->DeviceInputBufferCount() << "/" << this->DeviceOutputBufferCount()
<< ", main: " << this->MainInputBufferCount() << "/" << this->MainOutputBufferCount()
<< ", aux: " << this->AuxInputBufferCount() << "/" << this->AuxOutputBufferCount()
);
return result;
}
void PreparePlaybackFunctions(snd_pcm_format_t playbackFormat)
@@ -1321,19 +1339,18 @@ namespace pipedal
rawPlaybackBuffer.resize(playbackFrameSize * bufferSize);
memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize);
AllocateBuffers(playbackBuffers, playbackChannels);
AllocateBuffers(devicePlaybackBuffers, playbackChannels);
}
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
void OpenAudio(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
std::lock_guard lock{restartMutex};
int err;
std::string inputName = jackServerSettings.GetAlsaInputDevice();
std::string inputName = jackServerSettings.GetAlsaInputDevice();
std::string outputName = jackServerSettings.GetAlsaOutputDevice();
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize();
this->user_threshold = jackServerSettings.GetBufferSize();
@@ -1376,7 +1393,7 @@ namespace pipedal
{
snd_pcm_nonblock(playbackHandle, 0);
}
this->alsa_device_name = inputName;
err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
@@ -1467,6 +1484,10 @@ namespace pipedal
memset(rawPlaybackBuffer.data(), 0, rawPlaybackBuffer.size());
int retry = 0;
if (this->isDummyDriver)
{
return; // dummy driver is insatiable.
}
while (true)
{
auto avail = snd_pcm_avail(this->playbackHandle);
@@ -1486,7 +1507,7 @@ namespace pipedal
{
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err)));
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
if (avail == 0)
@@ -1631,7 +1652,7 @@ namespace pipedal
auto frame_bytes = this->captureFrameSize;
do
{
TraceBufferPositions(framesRead,'1');
TraceBufferPositions(framesRead, '1');
framesRead = snd_pcm_readi(handle, buffer, frames);
if (framesRead < 0)
@@ -1649,7 +1670,7 @@ namespace pipedal
}
} while (frames > 0);
TraceBufferPositions(framesRead,'2');
TraceBufferPositions(framesRead, '2');
return framesRead;
}
@@ -1700,6 +1721,7 @@ namespace pipedal
}
private:
bool isDummyDriver = false;
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
{
long framesRead;
@@ -1717,7 +1739,7 @@ namespace pipedal
}
return 0;
}
void AudioThread()
PIPEDAL_NON_INLINE void AudioThread()
{
SetThreadName("alsaDriver");
@@ -1797,7 +1819,20 @@ namespace pipedal
cpuUse.AddSample(ProfileCategory::Execute);
// Perform any neccessary mixing of outputs.
for (auto&mixOp: this->mixOps)
{
mixOp(framesRead);
}
// final format conversion.
(this->*copyOutputFn)(framesRead);
if (this->driverHost)
{
driverHost->OnRealtimeUpdateDeviceVus(framesRead);
}
cpuUse.AddSample(ProfileCategory::Driver);
// process.
@@ -1810,6 +1845,10 @@ namespace pipedal
recover_from_output_underrun(captureHandle, playbackHandle, err, framesRead);
framesRead = 0;
}
if (isDummyDriver)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
}
cpuUse.AddSample(ProfileCategory::Write);
}
}
@@ -1824,9 +1863,9 @@ namespace pipedal
{
this->driverHost->OnAlsaDriverStopped();
// zero out input buffers.
for (size_t i = 0; i < this->captureBuffers.size(); ++i)
for (size_t i = 0; i < this->deviceCaptureBuffers.size(); ++i)
{
float *pBuffer = captureBuffers[i];
float *pBuffer = deviceCaptureBuffers[i];
for (size_t j = 0; j < this->bufferSize; ++j)
{
pBuffer[j] = 0;
@@ -1836,7 +1875,7 @@ namespace pipedal
{
while (!terminateAudio())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::this_thread::sleep_for(std::chrono::milliseconds(1));
this->driverHost->OnProcess(this->bufferSize);
}
}
@@ -1849,63 +1888,208 @@ namespace pipedal
bool alsaActive = false;
static int IndexFromPortName(const std::string &s)
PIPEDAL_NON_INLINE void AllocateInputChannels(
const std::vector<int64_t> &channelSelection,
std::vector<float *> &channelBuffers)
{
auto pos = s.find_last_of('_');
if (pos == std::string::npos)
size_t nChannels = channelSelection.size();
if (nChannels == 0)
{
throw std::invalid_argument("Bad port name.");
channelBuffers.resize(0);
return;
}
const char *p = s.c_str() + (pos + 1);
int v = atoi(p);
if (v < 0)
channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i)
{
throw std::invalid_argument("Bad port name.");
int64_t deviceChannel = channelSelection[i];
if (deviceChannel == -1 || deviceChannel >= captureChannels)
{
channelBuffers[i] = zeroInputBuffer;
}
else
{
channelBuffers[i] = deviceCaptureBuffers[deviceChannel];
}
}
return v;
}
PIPEDAL_NON_INLINE void AllocateOutputChannels(
const std::vector<int64_t> &channelSelection,
std::vector<float *> &channelBuffers)
{
size_t nChannels = channelSelection.size();
if (nChannels == 0)
{
channelBuffers.resize(0);
return;
}
channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i)
{
int64_t deviceChannel = channelSelection[i];
if (deviceChannel == -1)
{
channelBuffers[i] = this->GetDiscardOutputBuffer();
;
}
else
{
float *mixBuffer = AllocateAudioBuffer();
channelBuffers[i] = mixBuffer;
}
}
}
PIPEDAL_NON_INLINE void AllocateAuxChannels()
{
for (auto ix : channelSelection.auxInputChannels())
{
auxCaptureBuffers.push_back(this->deviceCaptureBuffers[ix]);
}
for (auto ix : channelSelection.auxOutputChannels())
{
auxPlaybackBuffers.push_back(this->devicePlaybackBuffers[ix]);
}
}
using MixOp = std::function<void(size_t nFrames)>;
std::vector<MixOp> mixOps;
PIPEDAL_NON_INLINE
void AddMixCopyOp(float*inputBuffer, float*outputBuffer)
{
mixOps.push_back([inputBuffer,outputBuffer](size_t nFrames) {
float*PIPEDAL_RESTRICT pIn = inputBuffer;
float*PIPEDAL_RESTRICT pOut = outputBuffer;
for (size_t i = 0; i < nFrames; ++i)
{
pOut[i] = pIn[i];
}
});
}
PIPEDAL_NON_INLINE
void AddMixAddOp(float*inputBuffer, float*outputBuffer)
{
mixOps.push_back([inputBuffer,outputBuffer](size_t nFrames) {
float*PIPEDAL_RESTRICT pIn = inputBuffer;
float*PIPEDAL_RESTRICT pOut = outputBuffer;
for (size_t i = 0; i < nFrames; ++i)
{
pOut[i] += pIn[i];
}
});
}
PIPEDAL_NON_INLINE
void AddMixCopyOp(float scale, float*inputBuffer, float*outputBuffer)
{
mixOps.push_back([scale,inputBuffer,outputBuffer](size_t nFrames) {
float*PIPEDAL_RESTRICT pIn = inputBuffer;
float*PIPEDAL_RESTRICT pOut = outputBuffer;
for (size_t i = 0; i < nFrames; ++i)
{
pOut[i] = scale*pIn[i];
}
});
}
PIPEDAL_NON_INLINE
void AddMixAddOp(float scale, float*inputBuffer, float*outputBuffer)
{
mixOps.push_back([scale,inputBuffer,outputBuffer](size_t nFrames) {
float*PIPEDAL_RESTRICT pIn = inputBuffer;
float*PIPEDAL_RESTRICT pOut = outputBuffer;
for (size_t i = 0; i < nFrames; ++i)
{
pOut[i] += scale*pIn[i];
}
});
}
PIPEDAL_NON_INLINE void AddMixOps()
{
std::set<size_t> usedOutputChannels;
for (size_t i = 0; i < this->channelSelection.mainOutputChannels().size(); ++i) {
size_t outputChannel = this->channelSelection.mainOutputChannels()[i];
AddMixCopyOp(this->mainPlaybackBuffers[i],this->devicePlaybackBuffers[outputChannel]);
usedOutputChannels.insert(outputChannel);
}
if (channelSelection.auxInputChannels().size() <= channelSelection.auxOutputChannels().size())
{
for (size_t i = 0; i < this->channelSelection.auxOutputChannels().size(); ++i)
{
size_t outputChannel = this->channelSelection.auxOutputChannels()[i];
size_t inputChannel;
if (this->channelSelection.auxInputChannels().size() == 0) break;
if (i >= this->channelSelection.auxInputChannels().size()) {
inputChannel = 0;
} else {
inputChannel = this->channelSelection.auxInputChannels()[i];
}
if (outputChannel >= this->devicePlaybackBuffers.size())
{
continue;
}
if (usedOutputChannels.contains(outputChannel))
{
AddMixAddOp(this->deviceCaptureBuffers[inputChannel],this->devicePlaybackBuffers[outputChannel]);
} else {
AddMixCopyOp(this->deviceCaptureBuffers[inputChannel],this->devicePlaybackBuffers[outputChannel]);
}
usedOutputChannels.insert(outputChannel);
}
} else if (channelSelection.auxInputChannels().size() >= 2 && channelSelection.auxOutputChannels().size() == 1) {
float scale = 1.0/channelSelection.auxInputChannels().size();
for (size_t i = 0; i < this->channelSelection.auxOutputChannels().size(); ++i)
{
size_t outputChannel = this->channelSelection.auxOutputChannels()[i];
if (usedOutputChannels.contains(outputChannel))
{
AddMixAddOp(scale,this->auxCaptureBuffers[0],this->devicePlaybackBuffers[outputChannel]);
} else {
AddMixCopyOp(scale,this->auxCaptureBuffers[0],this->devicePlaybackBuffers[outputChannel]);
}
usedOutputChannels.insert(outputChannel);
}
}
}
bool activated = false;
virtual void Activate()
PIPEDAL_NON_INLINE virtual void Activate()
{
if (activated)
{
throw PiPedalStateException("Already activated.");
}
activated = true;
this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size());
// Reset previously allocated buffers.
allocatedBuffers.resize(0);
int ix = 0;
for (auto &x : channelSelection.GetInputAudioPorts())
// Allocate device capture buffers.
zeroInputBuffer = AllocateAudioBuffer();
deviceCaptureBuffers.resize(captureChannels);
for (size_t i = 0; i < captureChannels; ++i)
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= captureBuffers.size())
{
Lv2Log::error(SS("Invalid audio input port: " << x));
}
else
{
this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex];
}
deviceCaptureBuffers[i] = AllocateAudioBuffer();
}
devicePlaybackBuffers.resize(playbackChannels);
for (size_t i = 0; i < playbackChannels; ++i)
{
devicePlaybackBuffers[i] = AllocateAudioBuffer();
}
this->activePlaybackBuffers.resize(channelSelection.GetOutputAudioPorts().size());
ix = 0;
for (auto &x : channelSelection.GetOutputAudioPorts())
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= playbackBuffers.size())
{
Lv2Log::error(SS("Invalid audio output port: " << x));
}
else
{
this->activePlaybackBuffers[ix++] = this->playbackBuffers[sourceIndex];
}
}
AllocateInputChannels(
channelSelection.mainInputChannels(),
this->mainCaptureBuffers);
AllocateOutputChannels(
channelSelection.mainOutputChannels(),
this->mainPlaybackBuffers);
AllocateAuxChannels();
AddMixOps();
audioThread = std::make_unique<std::jthread>([this]()
{ AudioThread(); });
@@ -1936,15 +2120,85 @@ namespace pipedal
AlsaSequencer::ptr alsaSequencer;
public:
virtual const ChannelSelection &GetChannelSelection() const override
{
return channelSelection;
}
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{
this->alsaSequencer = alsaSequencer;
}
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
virtual float *GetInputBuffer(size_t channel) override
virtual size_t DeviceInputBufferCount() const override
{
return activeCaptureBuffers[channel];
return deviceCaptureBuffers.size();
}
virtual size_t DeviceOutputBufferCount() const override
{
return devicePlaybackBuffers.size();
}
virtual float *GetDeviceInputBuffer(size_t channel) const override
{
if (channel >= deviceCaptureBuffers.size())
return nullptr;
return deviceCaptureBuffers[channel];
}
virtual float *GetDeviceOutputBuffer(size_t channel) const override
{
if (channel >= devicePlaybackBuffers.size())
return nullptr;
return devicePlaybackBuffers[channel];
}
virtual float *GetZeroInputBuffer()
{
if (zeroInputBuffer == nullptr)
{
zeroInputBuffer = AllocateAudioBuffer();
}
return zeroInputBuffer;
}
virtual float *GetDiscardOutputBuffer()
{
if (discardOutputBuffer == nullptr)
{
discardOutputBuffer = AllocateAudioBuffer();
}
return discardOutputBuffer;
}
virtual std::vector<float *> &DeviceInputBuffers() override { return this->deviceCaptureBuffers; }
virtual std::vector<float *> &DeviceOutputBuffers() override { return this->devicePlaybackBuffers; }
virtual std::vector<float *> &MainInputBuffers() override { return this->mainCaptureBuffers; }
virtual std::vector<float *> &MainOutputBuffers() override { return this->mainPlaybackBuffers; }
virtual std::vector<float *> &AuxInputBuffers() override { return this->auxCaptureBuffers; }
virtual std::vector<float *> &AuxOutputBuffers() override { return this->auxPlaybackBuffers; }
virtual size_t MainInputBufferCount() const { return mainCaptureBuffers.size(); }
virtual float *GetMainInputBuffer(size_t channel) override
{
if (channel >= (int64_t)mainCaptureBuffers.size())
{
throw std::runtime_error("Argument out of range.");
}
return mainCaptureBuffers[channel];
}
virtual size_t AuxInputBufferCount() const { return auxCaptureBuffers.size(); }
virtual float *GetAuxInputBuffer(size_t channel) override
{
return auxCaptureBuffers[channel];
}
virtual size_t AuxOutputBufferCount() const
{
return auxPlaybackBuffers.size();
}
virtual float *GetAuxOutputBuffer(size_t channel) override
{
return auxPlaybackBuffers[channel];
}
virtual size_t GetMidiInputEventCount() override
@@ -1956,27 +2210,28 @@ namespace pipedal
return this->midiEvents.data();
}
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel) override
virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
virtual float *GetMainOutputBuffer(size_t channel) override
{
return activePlaybackBuffers[channel];
return mainPlaybackBuffers[channel];
}
void FreeBuffers(std::vector<float *> &buffer)
float *AllocateAudioBuffer()
{
for (size_t i = 0; i < buffer.size(); ++i)
{
delete[] buffer[i];
buffer[i] = 0;
}
buffer.clear();
std::vector<float> buffer;
buffer.resize(this->bufferSize);
float *pBuffer = buffer.data();
allocatedBuffers.push_back(std::move(buffer));
return pBuffer;
}
void DeleteBuffers()
{
activeCaptureBuffers.clear();
activePlaybackBuffers.clear();
FreeBuffers(this->playbackBuffers);
FreeBuffers(this->captureBuffers);
mainCaptureBuffers.clear();
mainPlaybackBuffers.clear();
auxCaptureBuffers.clear();
auxPlaybackBuffers.clear();
zeroInputBuffer = nullptr;
discardOutputBuffer = nullptr;
allocatedBuffers.clear();
}
virtual void Close()
{
@@ -2015,19 +2270,19 @@ namespace pipedal
std::vector<std::string> &inputAudioPorts,
std::vector<std::string> &outputAudioPorts)
{
if (jackServerSettings.IsDummyAudioDevice())
{
auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice());
// if (jackServerSettings.IsDummyAudioDevice())
// {
// auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice());
inputAudioPorts.clear();
outputAudioPorts.clear();
for (uint32_t i = 0; i < nChannels; ++i)
{
inputAudioPorts.push_back(std::string(SS("system::capture_" << i)));
outputAudioPorts.push_back(std::string(SS("system::playback_" << i)));
}
return true;
}
// inputAudioPorts.clear();
// outputAudioPorts.clear();
// for (uint32_t i = 0; i < nChannels; ++i)
// {
// inputAudioPorts.push_back(std::string(SS("system::capture_" << i)));
// outputAudioPorts.push_back(std::string(SS("system::playback_" << i)));
// }
// return true;
// }
snd_pcm_t *playbackHandle = nullptr;
snd_pcm_t *captureHandle = nullptr;
@@ -2241,7 +2496,7 @@ namespace pipedal
{
value += 1.0f * (c) / (128.0 * 256.0 * 256.0);
}
this->playbackBuffers[c][i] = value;
this->devicePlaybackBuffers[c][i] = value;
}
}
@@ -2257,14 +2512,14 @@ namespace pipedal
for (size_t c = 0; c < captureChannels; ++c)
{
float error =
this->captureBuffers[c][i] - this->playbackBuffers[c][i];
this->deviceCaptureBuffers[c][i] - this->devicePlaybackBuffers[c][i];
assert(std::abs(error) < 4e-5);
}
}
}
void AlsaFormatEncodeDecodeTest(AudioDriverHost *testDriverHost)
void test::AlsaFormatEncodeDecodeTest(AudioDriverHost *testDriverHost)
{
static snd_pcm_format_t formats[] = {
snd_pcm_format_t::SND_PCM_FORMAT_S16_LE,
@@ -2286,7 +2541,7 @@ namespace pipedal
alsaDriver->TestFormatEncodeDecode(format);
}
}
void MidiDecoderTest()
void test::MidiDecoderTest()
{
#ifdef JUNK
AlsaDriverImpl::AlsaMidiDeviceImpl midiState;
@@ -2355,37 +2610,41 @@ namespace pipedal
{
using namespace std;
int savedPrecision = cout.precision();
auto savedFlags = cout.flags();
auto savedFlags = cout.flags();
size_t ix = bufferTraceIndex;
if (ix < nEntries)
{
ix = ix + bufferTraces.size()-nEntries;
} else {
ix = ix + bufferTraces.size() - nEntries;
}
else
{
ix -= nEntries;
}
uint64_t t0;
if (bufferTraceIndex == 0) {
t0 = bufferTraces[bufferTraces.size()-1].time;
} else {
t0 = bufferTraces[bufferTraceIndex-1].time;
if (bufferTraceIndex == 0)
{
t0 = bufferTraces[bufferTraces.size() - 1].time;
}
else
{
t0 = bufferTraces[bufferTraceIndex - 1].time;
}
while (ix != bufferTraceIndex)
{
auto&bufferTrace = bufferTraces[ix];
auto &bufferTrace = bufferTraces[ix];
if (bufferTrace.time != 0)
{
int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0;
cout << bufferTrace.code << " "
<< fixed << setprecision(3) << dt*0.001
<< " " << "inAvail: " << bufferTrace.inAvail
<< " " << "outAvail: " << bufferTrace.outAvail
<< " " << "buffered: " << bufferTrace.buffered
<< " " << "total: " << bufferTrace.total
<< endl;
cout << bufferTrace.code << " "
<< fixed << setprecision(3) << dt * 0.001
<< " " << "inAvail: " << bufferTrace.inAvail
<< " " << "outAvail: " << bufferTrace.outAvail
<< " " << "buffered: " << bufferTrace.buffered
<< " " << "total: " << bufferTrace.total
<< endl;
}
++ix;
@@ -2399,4 +2658,51 @@ namespace pipedal
cout.flags(savedFlags);
}
AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels)
{
AlsaDeviceInfo result;
constexpr int DUMMY_DEVICE_ID_OFFSET = 100974;
result.cardId_ = DUMMY_DEVICE_ID_OFFSET + channels;
result.id_ = SS("dummy:channels_" << channels);
result.name_ = SS("Dummy Device (" << channels << " channels)");
result.longName_ = result.name_;
result.sampleRates_.push_back(44100);
result.sampleRates_.push_back(48000);
result.minBufferSize_ = 16;
result.maxBufferSize_ = 1024;
result.supportsCapture_ = true;
result.supportsPlayback_ = true;
return result;
}
uint32_t GetDummyAudioChannels(const std::string &deviceName)
{
uint32_t channels;
int pos = deviceName.find_last_of('_');
if (pos == std::string::npos)
{
throw std::runtime_error("Invalid dummy device name");
}
std::istringstream ss(deviceName.substr(pos + 1));
ss >> channels;
return channels;
}
AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost, const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
auto dummyServerSettings = jackServerSettings;
dummyServerSettings.UseDummyAudioDevice();
auto dummyChannelSelection = channelSelection;
AudioDriver *driver = new AlsaDriverImpl(driverHost);
try
{
driver->Open(jackServerSettings, channelSelection);
return driver;
}
catch (const std::exception &e)
{
delete driver;
throw;
}
}
} // namespace
+12 -4
View File
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2025 Robin E. R. Davies
* Copyright (c) 2026 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -24,6 +24,7 @@
#pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp"
#include "JackServerSettings.hpp"
@@ -37,10 +38,17 @@ namespace pipedal {
AudioDriver* CreateAlsaDriver(AudioDriverHost*driverHost);
// test only.
void AlsaFormatEncodeDecodeTest(AudioDriverHost*driverHost);
void MidiDecoderTest();
namespace test {
// Private use, test only.
void AlsaFormatEncodeDecodeTest(AudioDriverHost*driverHost);
void MidiDecoderTest();
}
void FreeAlsaGlobals(); // for valgrind. Free the Alsa configuration cache.
// AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels);
uint32_t GetDummyAudioChannels(const std::string &deviceName);
// std::shared_ptr<AudioDriver> CreateDummyAudioDriver(AudioDriverHost*driverHost,const std::string&deviceId);
}
+117 -100
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2022 Robin E. R. Davies
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -27,28 +27,34 @@
#include <cmath>
#include <mutex>
#include <iostream>
#include <algorithm>
#include "AlsaDriver.hpp"
#include "JackDriver.hpp"
#include "ChannelRouterSettings.hpp"
using namespace pipedal;
using namespace std;
class AlsaTester: private AudioDriverHost {
class AlsaTester : private AudioDriverHost
{
public:
enum class TestType { Oscillator, LatencyMonitor, NullTest};
enum class TestType
{
Oscillator,
LatencyMonitor,
NullTest
};
private:
AudioDriver *audioDriver = nullptr;
TestType testType;
public:
AlsaTester(TestType testType)
: testType(testType)
: testType(testType)
{
// audioDriver = CreateAlsaDriver(this);
// audioDriver = CreateJackDriver(this);
}
~AlsaTester()
{
@@ -57,47 +63,49 @@ public:
delete[] outputBuffers;
}
bool useJack = false;
bool OnRealtimeUpdateDeviceVus(size_t nFrames) override { return false; }
void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection)
{
size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2));
size_t nOutputs = std::max(jackConfiguration.outputAudioPorts().size(),size_t(2));
auto &inputSelections = channelSelection.mainInputChannels();
auto &outputSelections = channelSelection.mainOutputChannels();
inputSelections.resize(nInputs);
for (size_t i = 0; i < nInputs; ++i)
{
inputSelections[i] = i;
}
outputSelections.resize(nOutputs);
for (size_t i = 0; i < nOutputs; ++i)
{
outputSelections[i] = i;
}
}
void Test()
{
AlsaFormatEncodeDecodeTest(this);
test::AlsaFormatEncodeDecodeTest(this);
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3);
JackServerSettings serverSettings("hw:M2","hw:M2", 48000, 32, 3);
JackConfiguration jackConfiguration;
if (useJack)
{
jackConfiguration.JackInitialize();
} else {
jackConfiguration.AlsaInitialize(serverSettings);
}
jackConfiguration.AlsaInitialize(serverSettings);
JackChannelSelection channelSelection(
jackConfiguration.inputAudioPorts(),
jackConfiguration.outputAudioPorts(),
jackConfiguration.inputMidiDevices());
ChannelSelection channelSelection;
#if JACK_HOST
if (useJack)
{
audioDriver = CreateJackDriver(this);
} else {
audioDriver = CreateAlsaDriver(this);
}
#else
audioDriver = CreateAlsaDriver(this);
#endif
initializeChannelSelection(jackConfiguration, channelSelection);
oscillator.Init(440,jackConfiguration.sampleRate());
oscillator.Init(440, jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection);
audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
outputBuffers = new float*[channelSelection.GetOutputAudioPorts().size()];
inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate();
@@ -107,7 +115,7 @@ public:
if (testType == TestType::LatencyMonitor)
{
auto latency = this->latencyMonitor.GetLatency();
double ms = 1000.0*latency/jackConfiguration.sampleRate();
double ms = 1000.0 * latency / jackConfiguration.sampleRate();
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
}
@@ -115,37 +123,40 @@ public:
audioDriver->Deactivate();
audioDriver->Close();
}
float**inputBuffers = nullptr;
float**outputBuffers = nullptr;
float **inputBuffers = nullptr;
float **outputBuffers = nullptr;
class Oscillator {
class Oscillator
{
private:
double dx = 0;
double x = 0;
double dx2 = 0;
double x2 = 0;
public:
public:
void Init(float frequency, size_t sampleRate)
{
dx = frequency*3.141592736*2/sampleRate;
dx2 = 0.5*3.141592736*2/sampleRate;
dx = frequency * 3.141592736 * 2 / sampleRate;
dx2 = 0.5 * 3.141592736 * 2 / sampleRate;
}
float Next() {
float Next()
{
float result = (float)std::cos(x);
float env = (float)std::cos(x2);
x += dx;
x2 += dx2;
return result*env;
return result * env;
}
};
class LatencyMonitor {
enum class State {
class LatencyMonitor
{
enum class State
{
Idle,
Waiting,
};
@@ -156,91 +167,99 @@ public:
size_t current_latency = 0;
size_t latency = 0;
std::mutex sync;
public:
void Init(uint64_t sampleRate)
{
idle_samples = (uint64_t)sampleRate*2;
waiting_samples = (uint64_t)sampleRate*2;
idle_samples = (uint64_t)sampleRate * 2;
waiting_samples = (uint64_t)sampleRate * 2;
state = State::Idle;
t = idle_samples;
latency = 0;
}
size_t GetLatency() {
std::lock_guard lock { sync};
size_t GetLatency()
{
std::lock_guard lock{sync};
return latency;
}
float Next(float input)
{
switch(state)
switch (state)
{
default:
case State::Idle:
default:
case State::Idle:
{
if (t-- == 0)
{
if (t-- == 0) {
state = State::Waiting;
current_latency = 0;
}
return 0.01;
state = State::Waiting;
current_latency = 0;
}
break;
case State::Waiting:
return 0.01;
}
break;
case State::Waiting:
{
if (std::abs(input) > 0.1 || current_latency > 500)
{
if (std::abs(input) > 0.1 || current_latency > 500) {
{
std::lock_guard lock { sync};
latency = current_latency;
}
state = State::Idle;
t = idle_samples;
} else {
++current_latency;
{
std::lock_guard lock{sync};
latency = current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
state = State::Idle;
t = idle_samples;
}
break;
else
{
++current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
}
break;
}
}
};
Oscillator oscillator;
LatencyMonitor latencyMonitor;
virtual void OnAudioTerminated() {
virtual void OnAudioTerminated()
{
}
virtual void OnAlsaDriverStopped() {
virtual void OnAlsaDriverStopped()
{
}
virtual void OnProcess(size_t nFrames) {
if (testType == TestType::NullTest) return;
virtual void OnProcess(size_t nFrames)
{
if (testType == TestType::NullTest)
return;
size_t inputs = audioDriver->InputBufferCount();
size_t outputs = audioDriver->OutputBufferCount();
size_t inputs = audioDriver->MainInputBufferCount();
size_t outputs = audioDriver->MainOutputBufferCount();
for (size_t i = 0; i < inputs; ++i)
{
inputBuffers[i] = audioDriver->GetInputBuffer(i);
inputBuffers[i] = audioDriver->GetMainInputBuffer(i);
}
for (size_t i = 0; i < outputs; ++i)
{
outputBuffers[i] = audioDriver->GetOutputBuffer(i);
outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
}
if (this->testType == TestType::Oscillator)
{
for (size_t i = 0; i < nFrames; ++i)
{
float v = oscillator.Next()*0.25f;
float v = oscillator.Next() * 0.25f;
for (size_t c = 0; c < outputs; ++c)
{
outputBuffers[c][i] = v;
}
}
}
else {
else
{
for (size_t i = 0; i < nFrames; ++i)
{
float v = latencyMonitor.Next(inputBuffers[0][i]);
@@ -249,36 +268,34 @@ public:
outputBuffers[c][i] = v;
}
}
}
}
std::mutex sync;
uint64_t xruns;
uint64_t GetXruns() {
lock_guard lock { sync};
uint64_t GetXruns()
{
lock_guard lock{sync};
return xruns;
}
virtual void OnUnderrun() {
lock_guard lock { sync };
virtual void OnUnderrun()
{
lock_guard lock{sync};
++xruns;
}
};
TEST_CASE("alsa_test", "[alsa_test]")
{
TEST_CASE( "alsa_test", "[alsa_test]" ) {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
alsaDriver.Test();
}
TEST_CASE( "alsa_midi_test", "[alsa_midi_test]" ) {
TEST_CASE("alsa_midi_test", "[alsa_midi_test]")
{
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
MidiDecoderTest();
test::MidiDecoderTest();
}
+34 -7
View File
@@ -27,6 +27,7 @@
#include <functional>
#include "AlsaSequencer.hpp"
#include "MidiEvent.hpp"
#include "DeviceVus.hpp"
@@ -35,15 +36,16 @@ namespace pipedal {
using ProcessCallback = std::function<void (size_t)>;
class ChannelSelection;
class AudioDriverHost {
public:
virtual void OnProcess(size_t nFrames) = 0;
virtual bool OnRealtimeUpdateDeviceVus(size_t nFrames) = 0;
virtual void OnUnderrun() = 0;
virtual void OnAlsaDriverStopped() = 0;
virtual void OnAudioTerminated() = 0;
};
class AudioDriver {
public:
@@ -58,14 +60,39 @@ namespace pipedal {
virtual size_t GetMidiInputEventCount() = 0;
virtual MidiEvent*GetMidiEvents() = 0;
virtual size_t InputBufferCount() const = 0;
virtual float*GetInputBuffer(size_t channel) = 0;
virtual const ChannelSelection &GetChannelSelection() const = 0;
virtual size_t OutputBufferCount() const = 0;
virtual float*GetOutputBuffer(size_t channe) = 0;
virtual std::vector<float*> &DeviceInputBuffers() = 0;
virtual size_t DeviceInputBufferCount() const = 0;
virtual float* GetDeviceInputBuffer(size_t channel) const = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0;
virtual std::vector<float*> &DeviceOutputBuffers() = 0;
virtual size_t DeviceOutputBufferCount() const = 0;
virtual float* GetDeviceOutputBuffer(size_t channel) const = 0;
virtual std::vector<float*> &MainInputBuffers() = 0;
virtual size_t MainInputBufferCount() const = 0;
virtual float*GetMainInputBuffer(size_t channel) = 0;
virtual std::vector<float*> &MainOutputBuffers() = 0;
virtual size_t MainOutputBufferCount() const = 0;
virtual float*GetMainOutputBuffer(size_t channel) = 0;
virtual std::vector<float*>&AuxInputBuffers() = 0;
virtual std::vector<float*>&AuxOutputBuffers() = 0;
virtual size_t AuxInputBufferCount() const = 0;
virtual float*GetAuxInputBuffer(size_t channel) = 0;
virtual size_t AuxOutputBufferCount() const = 0;
virtual float*GetAuxOutputBuffer(size_t channel) = 0;
virtual float*GetZeroInputBuffer() = 0;
virtual float*GetDiscardOutputBuffer() = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const ChannelSelection &channelSelection) = 0;
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) = 0;
virtual void Activate() = 0;
virtual void Deactivate() = 0;
+297 -121
View File
@@ -18,6 +18,7 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "PiPedalCommon.hpp"
#include "AudioHost.hpp"
#include "util.hpp"
#include <lv2/atom/atom.h>
@@ -347,7 +348,8 @@ bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_RISING_EDGE)
{
result = value >= 0x64 && lastControlValue < 0x64;
} else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY)
}
else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY)
{
result = true;
}
@@ -519,12 +521,15 @@ private:
SystemMidiBinding rebootMidiBinding;
SystemMidiBinding shutdownMidiBinding;
JackChannelSelection channelSelection;
ChannelSelection channelSelection;
std::atomic<bool> active = false;
std::atomic<bool> audioStopped = false;
std::atomic<bool> isDummyAudioDriver = false;
std::shared_ptr<Lv2Pedalboard> currentPedalboard;
std::shared_ptr<Lv2Pedalboard> currentMainInsertPedalboard;
std::shared_ptr<Lv2Pedalboard> currentAuxInsertPedalboard;
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
@@ -537,7 +542,7 @@ private:
std::string GetAtomObjectType(uint8_t *pData)
{
LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData;
LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData;
if (pAtom->atom.type != uris.atom_Object)
{
throw std::invalid_argument("Not an Lv2 Object");
@@ -620,9 +625,9 @@ private:
void ZeroOutputBuffers(size_t nframes)
{
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i)
for (size_t i = 0; i < audioDriver->MainOutputBufferCount(); ++i)
{
float * out = (float *)audioDriver->GetOutputBuffer(i);
float *out = (float *)audioDriver->GetMainOutputBuffer(i);
if (out)
{
ZeroBuffer(out, nframes);
@@ -631,7 +636,7 @@ private:
}
RealtimeVuBuffers *realtimeVuBuffers = nullptr;
size_t vuSamplesPerUpdate = 0;
int64_t vuSamplesRemaining = 0;
int64_t realtimeVuSamplesRemaining = 0;
void freeRealtimeVuConfiguration()
{
@@ -657,11 +662,15 @@ private:
virtual void SetSystemMidiBindings(const std::vector<MidiBinding> &bindings);
void writeVu()
void realtimeWriteVus()
{
// throttling: we send one; but won't send another until the host thread
// acknowledges receipt.
if (!realtimeVuBuffers)
{
return;
}
if (!realtimeVuBuffers->waitingForAcknowledge)
{
auto pResult = realtimeVuBuffers->GetResult(currentSample);
@@ -671,7 +680,9 @@ private:
}
}
void processMonitorPortSubscriptions(uint32_t nframes)
void processMonitorPortSubscriptions(
Lv2Pedalboard *pedalboard,
uint32_t nframes)
{
for (size_t i = 0; i < this->realtimeMonitorPortSubscriptions->subscriptions.size(); ++i)
{
@@ -683,7 +694,7 @@ private:
portSubscription.samplesToNextCallback += portSubscription.sampleRate;
if (!portSubscription.waitingForAck)
{
float value = realtimeActivePedalboard->GetControlOutputValue(
float value = pedalboard->GetControlOutputValue(
portSubscription.instanceIndex,
portSubscription.portIndex);
if (value != portSubscription.lastValue)
@@ -845,7 +856,7 @@ private:
{
this->realtimeVuBuffers->waitingForAcknowledge = false;
}
vuSamplesRemaining = vuSamplesPerUpdate;
realtimeVuSamplesRemaining = vuSamplesPerUpdate;
break;
}
@@ -923,7 +934,7 @@ private:
return (event.size == 3 && event.buffer[0] == 0xB0 && event.buffer[1] == 0x00);
}
bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
bool ProcessMidiMonitor(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
{
// eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
@@ -947,7 +958,7 @@ private:
}
if (isNote || isControl)
{
MidiNotifyBody notifyBody (event.buffer[0],event.buffer[1], event.buffer[2]);
MidiNotifyBody notifyBody(event.buffer[0], event.buffer[1], event.buffer[2]);
realtimeWriter.OnMidiListen(notifyBody);
}
}
@@ -1060,7 +1071,7 @@ private:
}
else
{
onMidiEvent(eventBufferWriter, iterator, event);
ProcessMidiMonitor(eventBufferWriter, iterator, event);
}
}
@@ -1103,7 +1114,35 @@ private:
}
}
}
void ProcessMidiInput()
bool GetMainDriverBuffers(
float **inputBuffers,
float **outputBuffers)
{
bool buffersValid = true;
for (int i = 0; i < audioDriver->MainInputBufferCount(); ++i)
{
float *input = (float *)audioDriver->GetMainInputBuffer(i);
if (input == nullptr)
{
buffersValid = false;
}
inputBuffers[i] = input;
}
inputBuffers[audioDriver->MainInputBufferCount()] = nullptr;
for (int i = 0; i < audioDriver->MainOutputBufferCount(); ++i)
{
float *output = audioDriver->GetMainOutputBuffer(i);
if (output == nullptr)
{
buffersValid = false;
}
outputBuffers[i] = output;
}
outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr;
return buffersValid;
}
void ProcessGlobalMidiInput()
{
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids);
Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin();
@@ -1142,11 +1181,167 @@ private:
Lv2Log::info("Audio thread terminated.");
}
PIPEDAL_NON_INLINE void ProcessLv2Pedalboard(size_t nframes)
{
Lv2Pedalboard *pedalboard = nullptr;
std::vector<float *> *pInputBuffers;
std::vector<float *> *pOutputBuffers;
pedalboard = this->realtimeActivePedalboard;
pInputBuffers = &(audioDriver->MainInputBuffers());
pOutputBuffers = &(audioDriver->MainOutputBuffers());
if (pedalboard == nullptr || pInputBuffers->size() == 0 || pOutputBuffers->size() == 0)
{
return;
}
float *inputBuffers[3];
float *outputBuffers[3];
bool buffersValid = true;
inputBuffers[0] = pInputBuffers->at(0);
inputBuffers[1] = pInputBuffers->size() >= 2 ? pInputBuffers->at(1) : nullptr;
inputBuffers[2] = nullptr;
outputBuffers[0] = pOutputBuffers->at(0);
outputBuffers[1] = pOutputBuffers->size() >= 2 ? pOutputBuffers->at(1) : nullptr;
outputBuffers[2] = nullptr;
if (pedalboard != nullptr)
{
pedalboard->ProcessParameterRequests(pParameterRequests, nframes);
pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
pedalboard->GatherPatchProperties(pParameterRequests);
pedalboard->GatherPathPatchProperties(this);
if (this->realtimeMonitorPortSubscriptions != nullptr)
{
processMonitorPortSubscriptions(pedalboard, nframes);
}
}
else
{
// zero output buffers.
size_t ix = 0;
while (outputBuffers[ix])
{
float *outputBuffer = outputBuffers[ix];
for (size_t i = 0; i < nframes; ++i)
{
outputBuffer[i] = 0;
}
++ix;
}
}
}
void GetMasterChannels(float **masterInputBuffers, float **masterOutputBuffers)
{
size_t inputIx = 0;
size_t outputIx = 0;
masterInputBuffers[0] = nullptr;
masterInputBuffers[1] = nullptr;
masterOutputBuffers[0] = nullptr;
masterOutputBuffers[1] = nullptr;
for (auto nChannel : channelSelection.mainInputChannels())
{
masterInputBuffers[inputIx++] = audioDriver->GetDeviceInputBuffer(nChannel);
}
for (auto nChannel : channelSelection.mainOutputChannels())
{
masterOutputBuffers[outputIx++] = audioDriver->GetDeviceOutputBuffer(nChannel);
}
}
inline void AccumulateVu(float*result, size_t nFrames, float*buffer)
{
float maxVal = *result;
for (size_t i = 0; i < nFrames; ++i)
{
float v = std::fabs(buffer[i]);
if (v > maxVal)
{
maxVal = v;
}
}
*result = maxVal;
}
void AccumulateVuInputs(size_t nFrames,VuUpdateX&vuUpdate, const std::vector<int64_t>&channels)
{
if (channels.size() == 0) return;
AccumulateVu(&vuUpdate.inputMaxValueL_,nFrames,this->audioDriver->DeviceInputBuffers()[channels[0]]);
if (channels.size() == 2) {
AccumulateVu(&vuUpdate.inputMaxValueR_,nFrames,this->audioDriver->DeviceInputBuffers()[channels[1]]);
}
}
void AccumulateVuOutputs(size_t nFrames,VuUpdateX&vuUpdate, const std::vector<int64_t>&channels)
{
if (channels.size() == 0) return;
AccumulateVu(&vuUpdate.outputMaxValueL_,nFrames,this->audioDriver->DeviceOutputBuffers()[channels[0]]);
if (channels.size() >= 2) {
AccumulateVu(&vuUpdate.outputMaxValueR_,nFrames,this->audioDriver->DeviceOutputBuffers()[channels[1]]);
}
}
void ComputeMasterVus(size_t nFrames) {
if (this->realtimeVuBuffers)
{
float** audioBuffers;
for (auto& vuUpdate: this->realtimeVuBuffers->vuUpdateWorkingData)
{
if (vuUpdate.instanceId_ < 0)
{
switch (vuUpdate.instanceId_)
{
case Pedalboard::START_CONTROL_ID:
AccumulateVuInputs(nFrames, vuUpdate, pHost->GetChannelSelection().mainInputChannels());
break;
case Pedalboard::END_CONTROL_ID:
AccumulateVuOutputs(nFrames, vuUpdate, pHost->GetChannelSelection().mainOutputChannels());
break;
case Pedalboard::AUX_START_CONTROL_ID:
AccumulateVuInputs(nFrames, vuUpdate, pHost->GetChannelSelection().auxInputChannels());
break;
case Pedalboard::AUX_END_CONTROL_ID:
AccumulateVuOutputs(nFrames, vuUpdate, pHost->GetChannelSelection().auxOutputChannels());
break;
}
}
}
}
}
bool OnRealtimeUpdateDeviceVus(size_t nFrames) override
{
// all lv2pedalboards processed, and all channels downmixed.
// Now we can do the real vu update work!
float *masterInputBuffers[2];
float *masterOutputBuffers[2];
if (this->realtimeActivePedalboard != nullptr)
{
realtimeActivePedalboard->ComputeVus(this->realtimeVuBuffers, nFrames);
}
ComputeMasterVus(nFrames);
// periodically send updates.
realtimeVuSamplesRemaining -= nFrames;
if (realtimeVuSamplesRemaining <= 0)
{
realtimeWriteVus();
realtimeVuSamplesRemaining = vuSamplesPerUpdate;
}
return true;
}
virtual void OnProcess(size_t nframes)
{
try
{
float * restrict in , * restrict out;
float *restrict in, *restrict out;
Lv2Pedalboard *pedalboard = nullptr;
pedalboard = this->realtimeActivePedalboard;
@@ -1154,6 +1349,7 @@ private:
{
pedalboard->ResetAtomBuffers();
}
while (true)
{
@@ -1164,71 +1360,13 @@ private:
// else a new pedalboard was installed. start again.
pedalboard = this->realtimeActivePedalboard;
}
bool processed = false;
if (pedalboard != nullptr)
{
ProcessMidiInput();
float *inputBuffers[4];
float *outputBuffers[4];
bool buffersValid = true;
for (int i = 0; i < audioDriver->InputBufferCount(); ++i)
{
float *input = (float *)audioDriver->GetInputBuffer(i);
if (input == nullptr)
{
buffersValid = false;
break;
}
inputBuffers[i] = input;
}
inputBuffers[audioDriver->InputBufferCount()] = nullptr;
for (int i = 0; i < audioDriver->OutputBufferCount(); ++i)
{
float *output = audioDriver->GetOutputBuffer(i);
if (output == nullptr)
{
buffersValid = false;
break;
}
outputBuffers[i] = output;
}
outputBuffers[audioDriver->OutputBufferCount()] = nullptr;
if (buffersValid)
{
pedalboard->ProcessParameterRequests(pParameterRequests,nframes);
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
if (processed)
{
if (this->realtimeVuBuffers != nullptr)
{
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes, inputBuffers, outputBuffers);
vuSamplesRemaining -= nframes;
if (vuSamplesRemaining <= 0)
{
writeVu();
vuSamplesRemaining += vuSamplesPerUpdate;
}
}
if (this->realtimeMonitorPortSubscriptions != nullptr)
{
processMonitorPortSubscriptions(nframes);
}
}
pedalboard->GatherPatchProperties(pParameterRequests);
pedalboard->GatherPathPatchProperties(this);
}
}
if (!processed)
{
ZeroOutputBuffers(nframes);
ProcessGlobalMidiInput();
}
ProcessLv2Pedalboard(nframes);
if (pParameterRequests != nullptr)
{
@@ -1271,13 +1409,12 @@ public:
this->alsaDeviceMonitor->StartMonitoring(
[this](
AlsaSequencerDeviceMonitor::MonitorAction action,
AlsaSequencerDeviceMonitor::MonitorAction action,
int client,
const std::string &clientName)
{
HandleAlsaSequencerDevicesChanged(action, client, clientName);
}
);
const std::string &clientName)
{
HandleAlsaSequencerDevicesChanged(action, client, clientName);
});
}
virtual ~AudioHostImpl()
{
@@ -1311,10 +1448,10 @@ public:
}
else if (action == AlsaSequencerDeviceMonitor::MonitorAction::DeviceAdded)
{
pNotifyCallbacks->OnAlsaSequencerDeviceAdded(client,clientName);
pNotifyCallbacks->OnAlsaSequencerDeviceAdded(client, clientName);
}
}
}
}
void HandleAudioTerminatedAbnormally()
{
Lv2Log::error("Audio processing terminated unexpectedly.");
@@ -1461,7 +1598,7 @@ public:
}
else if (command == RingBufferCommand::SendVuUpdate)
{
const std::vector<VuUpdate> *updates = nullptr;
const std::vector<VuUpdateX> *updates = nullptr;
hostReader.read(&updates);
if (this->pNotifyCallbacks)
@@ -1516,7 +1653,7 @@ public:
if (property != nullptr && value != nullptr && property->type == uris.atom_URID)
{
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
if (this->pNotifyCallbacks)
if (this->pNotifyCallbacks)
{
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
}
@@ -1670,7 +1807,7 @@ public:
return result;
}
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection_)
{
std::lock_guard guard(mutex);
@@ -1681,21 +1818,8 @@ public:
}
isOpen = true;
if (jackServerSettings.IsDummyAudioDevice())
{
this->isDummyAudioDriver = true;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
}
else
{
this->isDummyAudioDriver = false;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
}
if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0)
{
return;
}
this->isDummyAudioDriver = jackServerSettings.IsDummyAudioDevice();
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
this->currentSample = 0;
this->underruns = 0;
@@ -1707,7 +1831,7 @@ public:
this->realtimeReader.Reset();
this->realtimeWriter.Reset();
this->channelSelection = channelSelection;
this->channelSelection = channelSelection_;
StartReaderThread();
@@ -1781,6 +1905,7 @@ public:
}
}
virtual void SetBypass(uint64_t instanceId, bool enabled)
{
std::lock_guard guard(mutex);
@@ -1865,7 +1990,6 @@ public:
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) override;
void OnNotifyPathPatchPropertyReceived(
int64_t instanceId,
const std::string &pathPatchPropertyUri,
@@ -1896,6 +2020,35 @@ public:
pendingSnapshots.clear();
}
PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId)
{
int64_t index = -1;
if (this->currentPedalboard)
{
if (instanceId == Pedalboard::START_CONTROL_ID)
{
index = Pedalboard::START_CONTROL_ID;
}
else if (instanceId == Pedalboard::END_CONTROL_ID)
{
index = Pedalboard::END_CONTROL_ID;
}
else if (instanceId == Pedalboard::AUX_START_CONTROL_ID)
{
index = Pedalboard::AUX_START_CONTROL_ID;
}
else if (instanceId == Pedalboard::AUX_END_CONTROL_ID)
{
index = Pedalboard::AUX_END_CONTROL_ID;
}
else
{
index = this->currentPedalboard->GetIndexOfInstanceId(instanceId);
}
}
RealtimePedalboardItemIndex result{index};
return result;
}
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{
std::lock_guard guard(mutex);
@@ -1914,35 +2067,56 @@ public:
for (size_t i = 0; i < instanceIds.size(); ++i)
{
int64_t instanceId = instanceIds[i];
auto effect = this->currentPedalboard->GetEffect(instanceId);
if (effect)
std::shared_ptr<Lv2Pedalboard>& pedalboard = this->currentPedalboard;
int64_t effectIndex = -1;
if (pedalboard != nullptr)
{
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
effectIndex = pedalboard->GetIndexOfInstanceId(instanceId);
}
if (effectIndex != -1)
{
IEffect *effect = pedalboard->GetEffect(instanceId);
RealtimePedalboardItemIndex index = RealtimePedalboardItemIndex(effectIndex);
vuConfig->enabledIndexes.push_back(index);
VuUpdate v;
VuUpdateX v;
v.instanceId_ = instanceId;
// Display mono VUs if a stereo device is being fed identical L/R inputs.
v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2
&& effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2;
v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2;
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
}
else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID)
else if (
instanceId == Pedalboard::START_CONTROL_ID ||
instanceId == Pedalboard::END_CONTROL_ID ||
instanceId == Pedalboard::AUX_START_CONTROL_ID ||
instanceId == Pedalboard::AUX_END_CONTROL_ID
)
{
int index = (int)instanceId;
VuUpdate v;
auto index = GetRealtimeItemIndex(instanceId);
VuUpdateX v;
vuConfig->enabledIndexes.push_back(index);
v.instanceId_ = instanceId;
if (instanceId == Pedalboard::INPUT_VOLUME_ID)
size_t nChannels = 0;
switch (instanceId)
{
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1;
}
else
{
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfOutputAudioChannels() > 1;
case Pedalboard::START_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().mainInputChannels().size();
break;
case Pedalboard::END_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size();
break;
case Pedalboard::AUX_START_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().auxInputChannels().size();
break;
case Pedalboard::AUX_END_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().auxOutputChannels().size();
break;
}
v.isStereoInput_ = v.isStereoOutput_ = nChannels > 1;
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
}
@@ -1953,6 +2127,10 @@ public:
}
}
Lv2Pedalboard *GetPedalboard()
{
return this->currentPedalboard.get();
}
RealtimeMonitorPortSubscription MakeRealtimeSubscription(const MonitorPortSubscription &subscription)
{
RealtimeMonitorPortSubscription result;
@@ -2318,8 +2496,6 @@ void AudioHostImpl::SetAlsaSequencerConfiguration(const AlsaSequencerConfigurati
this->alsaSequencer->SetConfiguration(alsaSequencerConfiguration);
}
JSON_MAP_BEGIN(JackHostStatus)
JSON_MAP_REFERENCE(JackHostStatus, active)
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
+6 -3
View File
@@ -32,6 +32,7 @@
#include "Promise.hpp"
#include "json_variant.hpp"
#include "RealtimeMidiEventType.hpp"
#include "ChannelRouterSettings.hpp"
namespace pipedal
{
@@ -157,8 +158,8 @@ namespace pipedal
{
public:
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
virtual bool OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
virtual void OnNotifyVusSubscription(const std::vector<VuUpdateX> &updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
virtual void OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2) = 0;
@@ -227,7 +228,7 @@ namespace pipedal
virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0;
virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) = 0;
virtual void Close() = 0;
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
@@ -237,6 +238,8 @@ namespace pipedal
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
virtual void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) = 0;
virtual void SetInputVolume(float value) = 0;
virtual void SetOutputVolume(float value) = 0;
+37 -12
View File
@@ -1,6 +1,13 @@
set(VERBOSE true)
cmake_minimum_required(VERSION 3.16.0)
configure_file(config.hpp.in config.hpp)
file(GENERATE
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/T3kConfig.h"
CONTENT "#define PIPEDAL_T3K_PUBLISHABLE_KEY \"${PIPEDAL_T3K_PUBLISHABLE_KEY}\"\n")
set (CMAKE_INSTALL_PREFIX "/usr/")
set (USE_PCH 1)
@@ -10,19 +17,13 @@ set (ENABLE_BACKTRACE 0)
set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
set(CXX_STANDARD 20)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PipeWire REQUIRED libpipewire-0.3)
message(STATUS "PipeWire_INCLUDE_DIRS: ${PipeWire_INCLUDE_DIRS}"
"PipeWire_LIBRARIES: ${PipeWire_LIBRARIES}"
"PipeWire_CFLAGS: ${PipeWire_CFLAGS}"
"PipeWire_DEFINES: ${PipeWire_DEFINES}"
"PipeWire_VERSION: ${PipeWire_VERSION}")
include(FetchContent)
# FOR FTXUI
@@ -58,6 +59,8 @@ include(FindPkgConfig)
find_package(OpenSSL REQUIRED)
find_package(ICU REQUIRED COMPONENTS uc i18n)
message(STATUS "ICU_LIBRARIES: ${ICU_LIBRARIES}")
@@ -204,6 +207,16 @@ else()
endif()
set (PIPEDAL_SOURCES
Tone3000Tone.hpp Tone3000Tone.cpp
AesDigest.cpp AesDigest.hpp
ChannelRouterSettings.cpp ChannelRouterSettings.hpp
Curl.cpp Curl.hpp
Tone3000DownloadType.cpp Tone3000DownloadType.hpp
Tone3000DownloadProgress.cpp Tone3000DownloadProgress.hpp
Tone3000Download.cpp Tone3000Download.hpp
Tone3000Downloader.cpp Tone3000Downloader.hpp
Tone3000Throttler.cpp Tone3000Throttler.hpp
CrashGuard.cpp CrashGuard.hpp
ModTemplateGenerator.cpp ModTemplateGenerator.hpp
WebServerMod.cpp WebServerMod.hpp
@@ -265,6 +278,7 @@ set (PIPEDAL_SOURCES
GovernorSettings.cpp GovernorSettings.hpp
WebServer.cpp WebServer.hpp pch.h Uri.cpp Uri.hpp
RequestHandler.hpp
Scratch.cpp PluginHost.hpp PluginHost.cpp
PluginType.hpp PluginType.cpp
@@ -315,7 +329,7 @@ set (PIPEDAL_SOURCES
)
configure_file(config.hpp.in config.hpp)
include_directories(
${pipedald_SOURCE_DIR}/.
../build/src
@@ -351,9 +365,11 @@ target_compile_definitions(libpipedald PUBLIC "_REENTRANT")
target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES})
target_link_libraries(libpipedald
PUBLIC
PUBLIC
PiPedalCommon
PRIVATE
SQLiteCpp
OpenSSL::Crypto
${PipeWire_LIBRARIES}
)
@@ -731,6 +747,7 @@ add_executable(pipedalconfig
ModFileTypes.cpp ModFileTypes.hpp
MimeTypes.cpp MimeTypes.hpp
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
JackServerSettings.hpp JackServerSettings.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
WifiChannelSelectors.cpp WifiChannelSelectors.hpp
@@ -761,6 +778,7 @@ add_executable(pipedal_latency_test
target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon)
target_include_directories(pipedal_latency_test PRIVATE ${PIPEDAL_INCLUDES})
add_executable(pipedal_alsa_info
alsaCheck.cpp alsaCheck.hpp
@@ -807,6 +825,7 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
SetWifiConfig.cpp SetWifiConfig.hpp
JackServerSettings.hpp JackServerSettings.cpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
@@ -814,7 +833,7 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
asan_options.cpp
)
target_link_libraries(pipedaladmind PRIVATE PiPedalCommon pthread atomic stdc++fs systemd )
target_link_libraries(pipedaladmind PRIVATE PiPedalCommon pthread atomic stdc++fs systemd asound)
add_executable(processcopyrights copyrightMain.cpp
CommandLineParser.hpp
@@ -857,11 +876,17 @@ add_custom_target (
DEPENDS ${REACT_NOTICES_FILE}
)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.31)
cmake_policy(SET CMP0177 NEW)
endif()
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets)
EXPORT pipedalTargets
)
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
EXPORT pipedalSbinTargets)
EXPORT pipedalSbinTargets
)
+170
View File
@@ -0,0 +1,170 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "ChannelRouterSettings.hpp"
#include <stdexcept>
using namespace pipedal;
static uint64_t countChannels(const std::vector<int64_t> &channels)
{
if (channels.size() != 2)
{
throw std::runtime_error("Invalid Channel Router channel configuration.");
}
if (channels[0] == -1)
{
if (channels[1] == -1)
{
return 0;
}
else
{
return 1;
}
}
else
{
if (channels[1] == -1)
{
return 1;
}
if (channels[0] == channels[1])
{
return 1;
}
return 2;
}
}
uint64_t ChannelRouterSettings::numberOfAudioInputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainInputChannels_);
}
uint64_t ChannelRouterSettings::numberOfAudioOutputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainOutputChannels_);
}
ChannelSelection::ChannelSelection(ChannelRouterSettings&settings)
: mainInputChannels_(settings.mainInputChannels())
, mainOutputChannels_(settings.mainOutputChannels())
, auxInputChannels_(settings.auxInputChannels())
, auxOutputChannels_(settings.auxOutputChannels())
{
normalizeChannelSelection();
}
static void normalizeInputChannels(std::vector<int64_t>&channels) {
if (channels.size() == 2) {
if (channels[0] == -1 && channels[1] == -1)
{
channels.resize(0);
} else if (channels[0] == channels[1]) {
channels.resize(1);
} else if (channels[1] == -1) {
channels.resize(1);} {
}
}
}
static void normalizeOutputChannels(std::vector<int64_t>&channels) {
normalizeInputChannels(channels);
}
void ChannelSelection::normalizeChannelSelection() {
normalizeInputChannels(mainInputChannels_);
normalizeOutputChannels(mainOutputChannels_);
normalizeInputChannels(auxInputChannels_);
normalizeOutputChannels(auxOutputChannels_);
// If either aux inputs or outputs are zero, don't do ANY aux processing.
if (auxInputChannels_.size() == 0)
{
auxOutputChannels_.resize(0);
} else if (auxOutputChannels_.size() == 0) {
auxInputChannels_.resize(0);
}
// If either main inputs or outputs are empty, add send/receive dummy buffers. (Lv2 plugins shouldn't have to deal with this)
if (mainInputChannels_.size() == 0)
{
mainInputChannels_.resize(1);
mainInputChannels_[0] = -1;
}
if (mainOutputChannels_.size() == 0) {
mainInputChannels_.resize(1);
mainOutputChannels_[0] = -1;
}
// Send buffers are what they are. Let the send plugin deal with it.
}
ChannelRouterSettings::ChannelRouterSettings()
{
}
std::vector<ChannelRouterPresetIndexEntry> ChannelRouterPresetBank::getIndexEntries()
{
std::vector<ChannelRouterPresetIndexEntry> result;
for (auto &entry: entries_)
{
result.push_back(ChannelRouterPresetIndexEntry{entry->id(), entry->name()});
}
return result;
}
JSON_MAP_BEGIN(ChannelRouterSettings)
JSON_MAP_REFERENCE(ChannelRouterSettings, configured)
JSON_MAP_REFERENCE(ChannelRouterSettings, changed)
JSON_MAP_REFERENCE(ChannelRouterSettings, channelRouterPresetId)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxOutputChannels)
JSON_MAP_END();
JSON_MAP_BEGIN(ChannelRouterPresetIndexEntry)
JSON_MAP_REFERENCE(ChannelRouterPresetIndexEntry, id)
JSON_MAP_REFERENCE(ChannelRouterPresetIndexEntry, name)
JSON_MAP_END();
JSON_MAP_BEGIN(ChannelRouterPresetBankEntry)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, id)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, name)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, channelRouterSettings)
JSON_MAP_END();
JSON_MAP_BEGIN(ChannelRouterPresetBank)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, nextId)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, version)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, entries)
JSON_MAP_END();
+148
View File
@@ -0,0 +1,148 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <vector>
#include "json.hpp"
#include "Pedalboard.hpp"
#include <memory>
namespace pipedal
{
class ChannelRouterSettings
{
protected:
bool configured_ = false;
int64_t channelRouterPresetId_ = -1;
bool changed_ = false;
std::vector<int64_t> mainInputChannels_ = {1, 1};
std::vector<int64_t> mainOutputChannels_ = {0, 1};
std::vector<int64_t> auxInputChannels_ = {-1, -1};
std::vector<int64_t> auxOutputChannels_ = {-1, -1};
public:
using self = ChannelRouterSettings;
using ptr = std::shared_ptr<self>;
ChannelRouterSettings();
uint64_t numberOfAudioInputChannels() const;
uint64_t numberOfAudioOutputChannels() const;
JSON_GETTER_SETTER(configured)
JSON_GETTER_SETTER(channelRouterPresetId)
JSON_GETTER_SETTER(changed)
JSON_GETTER_SETTER_REF(mainInputChannels)
JSON_GETTER_SETTER_REF(mainOutputChannels)
JSON_GETTER_SETTER_REF(auxInputChannels)
JSON_GETTER_SETTER_REF(auxOutputChannels)
DECLARE_JSON_MAP(ChannelRouterSettings);
};
// just the channel selecttions.
class ChannelSelection
{
public:
ChannelSelection() = default;
ChannelSelection(ChannelRouterSettings &settings);
ChannelSelection(const ChannelSelection &other) = default;
ChannelSelection(ChannelSelection &&other) = default;
ChannelSelection &operator=(const ChannelSelection &other) = default;
ChannelSelection &operator=(ChannelSelection &&other) = default;
~ChannelSelection() = default;
const std::vector<int64_t> &mainInputChannels() const { return mainInputChannels_; }
const std::vector<int64_t> &mainOutputChannels() const { return mainOutputChannels_; }
const std::vector<int64_t> &auxInputChannels() const { return auxInputChannels_; }
const std::vector<int64_t> &auxOutputChannels() const { return auxOutputChannels_; }
std::vector<int64_t> &mainInputChannels() { return mainInputChannels_; }
std::vector<int64_t> &mainOutputChannels() { return mainOutputChannels_; }
std::vector<int64_t> &auxInputChannels() { return auxInputChannels_; }
std::vector<int64_t> &auxOutputChannels() { return auxOutputChannels_; }
private:
void normalizeChannelSelection();
std::vector<int64_t> mainInputChannels_;
std::vector<int64_t> mainOutputChannels_;
std::vector<int64_t> auxInputChannels_;
std::vector<int64_t> auxOutputChannels_;
};
class ChannelRouterPresetIndexEntry {
private:
int64_t id_ = -1;
std::string name_;
public:
ChannelRouterPresetIndexEntry() = default;
ChannelRouterPresetIndexEntry(const ChannelRouterPresetIndexEntry&other) = default;
ChannelRouterPresetIndexEntry(ChannelRouterPresetIndexEntry&&other) = default;
ChannelRouterPresetIndexEntry&operator=(const ChannelRouterPresetIndexEntry&other) = default;
ChannelRouterPresetIndexEntry&operator=(ChannelRouterPresetIndexEntry&&other) = default;
ChannelRouterPresetIndexEntry(int64_t id, const std::string&name)
: id_(id),name_(name) { }
JSON_GETTER_SETTER(id)
JSON_GETTER_SETTER_REF(name)
DECLARE_JSON_MAP(ChannelRouterPresetIndexEntry);
};
class ChannelRouterPresetBankEntry {
private:
int64_t id_;
std::string name_;
ChannelRouterSettings channelRouterSettings_;
public:
JSON_GETTER_SETTER(id)
JSON_GETTER_SETTER_REF(name)
JSON_GETTER_SETTER_REF(channelRouterSettings)
DECLARE_JSON_MAP(ChannelRouterPresetBankEntry);
};
class ChannelRouterPresetBank {
private:
int64_t nextId_ = 1;
int64_t version_ = 1;
std::vector<std::shared_ptr<ChannelRouterPresetBankEntry>> entries_;
public:
JSON_GETTER_SETTER(nextId)
JSON_GETTER_SETTER(version)
JSON_GETTER_SETTER_REF(entries)
std::vector<ChannelRouterPresetIndexEntry> getIndexEntries();
DECLARE_JSON_MAP(ChannelRouterPresetBank);
};
}
+927
View File
@@ -0,0 +1,927 @@
/*
* Copyright (c) 2026 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "Curl.hpp"
#include "ss.hpp"
#include "TemporaryFile.hpp"
#include <filesystem>
#include "SysExec.hpp"
#include <stdexcept>
#include "util.hpp"
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <algorithm>
#include "Finally.hpp"
#include "Lv2Log.hpp"
static const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
static bool enableLogging = true;
namespace pipedal
{
static void logHeaders(const std::vector<std::string> &headers)
{
if (enableLogging)
{
std::ofstream os{"/tmp/PipedalCurl.log"};
for (const auto &header : headers)
{
os << header << "\n";
}
}
}
static std::string getCurlExitCodeString(int exitCode)
{
// Translate documented curl exit codes to strings.
// https://curl.se/libcurl/c/libcurl-errors.html
switch (exitCode)
{
case 0:
return "OK"; // CURLE_OK - All fine
case 1:
return "Unsupported protocol.";
case 2:
return "Failed to initialize.";
case 3:
return "URL malformed.";
case 4:
return "Feature not built-in.";
case 5:
return "Couldn't resolve proxy.";
case 6:
return "Couldn't resolve host.";
case 7:
return "Failed to connect to host.";
case 8:
return "Weird server reply.";
case 9:
return "Access denied to remote resource.";
case 10:
return "FTP accept failed.";
case 11:
return "FTP weird PASS reply.";
case 12:
return "FTP accept timeout.";
case 13:
return "FTP weird PASV reply.";
case 14:
return "FTP weird 227 format.";
case 15:
return "FTP can't get host.";
case 16:
return "HTTP/2 error.";
case 17:
return "FTP couldn't set type.";
case 18:
return "Partial file transfer.";
case 19:
return "FTP couldn't retrieve file.";
case 21:
return "FTP quote error.";
case 22:
return "HTTP page not retrieved.";
case 23:
return "Write error.";
case 25:
return "Upload failed.";
case 26:
return "Read error.";
case 27:
return "Out of memory.";
case 28:
return "Operation timeout.";
case 30:
return "FTP PORT failed.";
case 31:
return "FTP couldn't use REST.";
case 33:
return "Range error.";
case 35:
return "SSL connect error.";
case 36:
return "Bad download resume.";
case 37:
return "FILE couldn't read file.";
case 38:
return "LDAP cannot bind.";
case 39:
return "LDAP search failed.";
case 42:
return "Aborted by callback.";
case 43:
return "Bad function argument.";
case 45:
return "Interface failed.";
case 47:
return "Too many redirects.";
case 48:
return "Unknown option.";
case 49:
return "Setopt option syntax error.";
case 52:
return "Got nothing from server.";
case 53:
return "SSL engine not found.";
case 54:
return "SSL engine set failed.";
case 55:
return "Failed sending network data.";
case 56:
return "Failure receiving network data.";
case 58:
return "Problem with local client certificate.";
case 59:
return "Couldn't use specified SSL cipher.";
case 60:
return "Peer SSL certificate or SSH fingerprint verification failed.";
case 61:
return "Unrecognized transfer encoding.";
case 63:
return "Maximum file size exceeded.";
case 64:
return "Requested FTP SSL level failed.";
case 65:
return "Send failed to rewind.";
case 66:
return "SSL engine initialization failed.";
case 67:
return "Login denied.";
case 68:
return "TFTP file not found.";
case 69:
return "TFTP permission problem.";
case 70:
return "Remote disk full.";
case 71:
return "TFTP illegal operation.";
case 72:
return "TFTP unknown transfer ID.";
case 73:
return "Remote file already exists.";
case 74:
return "TFTP no such user.";
case 77:
return "Problem reading SSL CA cert.";
case 78:
return "Remote file not found.";
case 79:
return "SSH error.";
case 80:
return "SSL shutdown failed.";
case 82:
return "Failed to load CRL file.";
case 83:
return "SSL issuer check failed.";
case 84:
return "FTP PRET command failed.";
case 85:
return "RTSP CSeq mismatch.";
case 86:
return "RTSP session ID mismatch.";
case 87:
return "Unable to parse FTP file list.";
case 88:
return "Chunk callback error.";
case 89:
return "No connection available.";
case 90:
return "SSL pinned public key mismatch.";
case 91:
return "SSL invalid certificate status.";
case 92:
return "HTTP/2 stream error.";
case 93:
return "Recursive API call.";
case 94:
return "Authentication error.";
case 95:
return "HTTP/3 error.";
case 96:
return "QUIC connection error.";
case 97:
return "Proxy handshake error.";
case 98:
return "SSL client certificate required.";
case 99:
return "Unrecoverable poll error.";
case 100:
return "Value or data field too large.";
case 101:
return "ECH failed.";
default:
return SS("Failed with exit code " << exitCode << ".");
}
}
static void throwCurlExitCode(int exitCode)
{
if (exitCode == 0)
return;
std::string message = getCurlExitCodeString(exitCode);
throw std::runtime_error(SS("Server download failed. " << message));
}
static int checkCurlHttpResponse(const std::vector<std::string> &headers)
{
if (headers.size() == 0)
{
throw std::runtime_error("Download failed. Invalid curl response: no headers.");
}
int returnCode = 0;
for (const auto &httpResponse : headers)
{
if (httpResponse.starts_with("HTTP/"))
{
size_t codeStart = httpResponse.find(' ');
if (codeStart == std::string::npos)
{
throw std::runtime_error(SS("Download failed. Invalid curl response: " << httpResponse));
}
++codeStart;
size_t codeEnd = httpResponse.find(' ', codeStart);
if (codeEnd == std::string::npos)
{
codeEnd = httpResponse.length();
}
int statusCode = 0;
try
{
statusCode = std::stoi(httpResponse.substr(codeStart, codeEnd - codeStart));
if (statusCode >= 200 && statusCode < 300) // a completion code.
{
return statusCode;
}
if (statusCode >= 400) // an error
{
return statusCode;
}
// otherwise it's a redirect of some kind, keep going.
returnCode = statusCode; // return the last one if there's not 200 or 400+ result.
}
catch (const std::exception &)
{
// ignored. Weird. but ignored.
}
}
}
return returnCode;
}
int CurlGet(
const std::string &url,
std::vector<uint8_t> &output,
std::vector<std::string> *outputHeadersOpt,
const std::vector<std::string> *inputHeadersOpt)
{
TemporaryFile tempFile{WEB_TEMP_DIR};
int rc = CurlGet(url, tempFile.Path(), outputHeadersOpt, inputHeadersOpt);
if (rc == 200)
{
std::ifstream inputStream(tempFile.Path(), std::ios::binary);
if (!inputStream)
{
throw std::runtime_error("Failed to open download file.");
}
inputStream.seekg(0, std::ios::end);
std::streamsize size = inputStream.tellg();
inputStream.seekg(0, std::ios::beg);
output.resize(static_cast<size_t>(size));
if (size > 0)
{
inputStream.read(reinterpret_cast<char *>(output.data()), size);
}
}
return rc;
}
int CurlGet(
const std::string &url,
std::vector<std::string> &output,
std::vector<std::string> *outputHeadersOpt,
const std::vector<std::string> *inputHeadersOpt)
{
TemporaryFile tempFile{WEB_TEMP_DIR};
int rc = CurlGet(url, tempFile.Path(), outputHeadersOpt, inputHeadersOpt);
std::ifstream inputStream(tempFile.Path(), std::ios::binary);
if (!inputStream)
{
throw std::runtime_error("Failed to open download file.");
}
output.clear();
std::string line;
while (std::getline(inputStream, line))
{
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
output.push_back(line);
}
return rc;
}
int CurlGet(
const std::string &url,
const std::filesystem::path &outputPath,
std::vector<std::string> *outputHeadersOpt,
const std::vector<std::string> *inputHeadersOpt)
{
TemporaryFile headersFile{WEB_TEMP_DIR};
std::vector<std::string> defaultHeaders;
if (outputHeadersOpt == nullptr)
{
outputHeadersOpt = &defaultHeaders;
}
bool bResult = true;
std::stringstream ssArgs;
if (inputHeadersOpt)
{
for (const std::string &header : *inputHeadersOpt)
{
ssArgs << "-H " << ShellEscape(header) << " ";
}
}
ssArgs << "-s -L -D " << ShellEscape(headersFile.Path().c_str())
<< " " << ShellEscape(url)
<< " -o " << ShellEscape(outputPath.c_str());
std::string args = ssArgs.str();
auto curlOutput = sysExecForOutput("/usr/bin/curl", args);
if (outputHeadersOpt != nullptr)
{
std::ifstream headersStream(headersFile.Path());
if (headersStream)
{
outputHeadersOpt->clear();
std::string line;
while (std::getline(headersStream, line))
{
// Remove trailing carriage return if present
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
if (!line.empty())
{
outputHeadersOpt->push_back(line);
}
}
}
}
logHeaders(*outputHeadersOpt);
if (curlOutput.exitCode == 512)
{
throw std::runtime_error("Invalid curl arguments.");
}
if (curlOutput.exitCode != EXIT_SUCCESS && curlOutput.exitCode != 22 * 256)
{
if (WIFEXITED(curlOutput.exitCode))
{
auto exitCode = WEXITSTATUS(curlOutput.exitCode);
throwCurlExitCode(exitCode);
return -99;
}
else if (WIFSIGNALED(curlOutput.exitCode))
{
throw std::runtime_error("No internet access.");
}
else
{
throw std::runtime_error(SS("Unable to exec curl. (exit status = " << curlOutput.exitCode << ")"));
}
}
int errorCode = checkCurlHttpResponse(*outputHeadersOpt);
return errorCode;
}
static int curlExec(
const std::string &commandPath, // path to executable.
const std::vector<std::string> &arguments, // commandline arguments.
const std::function<bool(const std::string &string)> &onStdoutLine,
std::string &stdErrOutput)
{
// Unexpected errors throw std::runtime_error.
// create linux socket pairs for stdout and stderr.
int stdoutPipe[2] = {-1, -1};
int stderrPipe[2] = {-1, -1};
Finally ffPipes{
[&stdoutPipe, &stderrPipe]()
{
if (stdoutPipe[0] != -1)
close(stdoutPipe[0]);
if (stdoutPipe[1] != -1)
close(stdoutPipe[1]);
if (stderrPipe[0] != -1)
close(stdoutPipe[0]);
if (stderrPipe[1] != -1)
close(stdoutPipe[1]);
}};
if (socketpair(AF_UNIX, SOCK_STREAM, 0, stdoutPipe) == -1)
{
throw std::runtime_error("Failed to create stdout socket pair");
}
if (socketpair(AF_UNIX, SOCK_STREAM, 0, stderrPipe) == -1)
{
throw std::runtime_error("Failed to create stderr socket pair");
}
// exec the command with the supplied arguments
pid_t pid = fork();
if (pid == -1)
{
throw std::runtime_error("Failed to fork process");
}
if (pid == 0)
{
// Child process
close(stdoutPipe[0]); // Close read end
close(stderrPipe[0]); // Close read end
// Redirect stdout and stderr
dup2(stdoutPipe[1], STDOUT_FILENO);
dup2(stderrPipe[1], STDERR_FILENO);
close(stdoutPipe[1]);
close(stderrPipe[1]);
// stdin to /dev/null.
int devNull = open("/dev/null", O_RDONLY);
if (devNull == -1)
{
std::cerr << "Failed to open /dev/null" << std::endl;
exit(EXIT_FAILURE);
}
if (dup2(devNull, STDIN_FILENO) == -1)
{
std::cerr << "Failed to redirect stdin" << std::endl;
close(devNull);
exit(EXIT_FAILURE);
}
close(devNull);
// Build argv array
std::vector<char *> argv;
argv.push_back(const_cast<char *>(commandPath.c_str()));
for (const auto &arg : arguments)
{
argv.push_back(const_cast<char *>(arg.c_str()));
}
argv.push_back(nullptr);
execv(commandPath.c_str(), argv.data());
// If execv returns, it failed
std::cerr << "Failed to execute: " << commandPath << std::endl;
exit(EXIT_FAILURE);
}
// Parent process
close(stdoutPipe[1]); // Close write end
stdoutPipe[1] = -1;
close(stderrPipe[1]); // Close write end
stderrPipe[1] = -1;
// Read from stderr and stdout.
// stderr output gets appended to the stdErrOutput argument.
// stdin output gets read line by line, and onStdOutLine is called for each line of text that's read.
// Set non-blocking mode for both pipes
fcntl(stdoutPipe[0], F_SETFL, O_NONBLOCK);
fcntl(stderrPipe[0], F_SETFL, O_NONBLOCK);
std::string stdoutBuffer;
std::string stderrBuffer;
bool stdoutOpen = true;
bool stderrOpen = true;
while (stdoutOpen || stderrOpen)
{
fd_set readfds;
FD_ZERO(&readfds);
int maxfd = -1;
if (stdoutOpen)
{
FD_SET(stdoutPipe[0], &readfds);
maxfd = std::max(maxfd, stdoutPipe[0]);
}
if (stderrOpen)
{
FD_SET(stderrPipe[0], &readfds);
maxfd = std::max(maxfd, stderrPipe[0]);
}
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int ret = select(maxfd + 1, &readfds, nullptr, nullptr, &timeout);
if (ret > 0)
{
// Read from stdout
if (stdoutOpen && FD_ISSET(stdoutPipe[0], &readfds))
{
char buffer[4096];
ssize_t n = read(stdoutPipe[0], buffer, sizeof(buffer));
if (n > 0)
{
stdoutBuffer.append(buffer, n);
// Process complete lines
size_t pos;
while ((pos = stdoutBuffer.find('\n')) != std::string::npos)
{
std::string line = stdoutBuffer.substr(0, pos);
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
if (!onStdoutLine(line))
{
// Kill the child process and return immediately
// do NOT waitpid, as that will be done in the processing loop.
kill(pid, SIGTERM);
}
stdoutBuffer.erase(0, pos + 1);
}
}
else if (n == 0)
{
stdoutOpen = false;
close(stdoutPipe[0]);
// Process any remaining data
if (!stdoutBuffer.empty())
{
if (!stdoutBuffer.empty() && stdoutBuffer.back() == '\r')
{
stdoutBuffer.pop_back();
}
if (!stdoutBuffer.empty())
{
if (!onStdoutLine(stdoutBuffer))
{
// abort the child process
kill(pid, SIGINT); // Curl: SIGINT= graceful shutdown. SIGTERM=abrupt shutdown.
// but wait for output conforming the cancellation
// i.e. don't waitpid -- that will be done on cleanup, and don't abort the loop.
}
}
}
}
}
// Read from stderr
if (stderrOpen && FD_ISSET(stderrPipe[0], &readfds))
{
char buffer[4096];
ssize_t n = read(stderrPipe[0], buffer, sizeof(buffer));
if (n > 0)
{
stdErrOutput.append(buffer, n);
}
else if (n == 0)
{
stderrOpen = false;
close(stderrPipe[0]);
}
}
}
}
// Return the procesess's exit code.
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
{
auto exitCode = WEXITSTATUS(status);
if (exitCode == EXIT_SUCCESS)
{
return EXIT_SUCCESS;
}
throwCurlExitCode(exitCode);
return exitCode;
}
else if (WIFSIGNALED(status))
{
return -1;
}
return -2;
}
int CurlGet(
const std::vector<CurlDownloadRequest> &request,
const std::function<void(size_t completed, size_t total)> &progressCallback,
std::vector<std::string> *outputHeadersOpt)
{
if (request.empty())
{
return 200;
}
size_t currentDownload = 0;
size_t totalDownloads = request.size();
int lastStatusCode = 200;
// Create temporary file for curl config
TemporaryFile configFile{WEB_TEMP_DIR};
// Write curl config file with URL and output pairs
{
std::ofstream configStream(configFile.Path());
if (!configStream)
{
throw std::runtime_error("Failed to create curl config file");
}
for (const auto &req : request)
{
configStream << "url = \"" << req.url << "\"\n";
configStream << "output = \"" << req.outputFile.string() << "\"\n";
}
}
// Build curl arguments
std::vector<std::string> curlArgs;
curlArgs.push_back("-s"); // Silent mode
curlArgs.push_back("-L"); // Follow redirects
// curlArgs.push_back("-w"); // Write format
// curlArgs.push_back("URL: %{url_effective}\\n");
curlArgs.push_back("--parallel-max");
curlArgs.push_back("1");
curlArgs.push_back("-D"); // Dump headers to stdout
curlArgs.push_back("-"); // Write headers to stdout
curlArgs.push_back("--no-progress-meter"); // Suppress progress
curlArgs.push_back("--retry-delay");
curlArgs.push_back("2");
curlArgs.push_back("--retry-max-time");
curlArgs.push_back("6");
curlArgs.push_back("-K");
curlArgs.push_back(configFile.Path().string());
curlArgs.push_back("--fail-early"); // quit as soon as one transfer fails.
std::string stderrOutput;
std::string savedHttpHeader;
// Process stdout lines
auto onStdoutLine = [&](const std::string &line) -> bool
{
if (outputHeadersOpt != nullptr)
{
outputHeadersOpt->push_back(line);
}
// Empty line indicates start of new download or end of headers
if (line.empty())
{
if (!savedHttpHeader.empty())
{
std::string lastHeader = savedHttpHeader;
savedHttpHeader = "";
// Parse status code
size_t codeStart = lastHeader.find(' ');
if (codeStart != std::string::npos)
{
codeStart++; // Skip the space
size_t codeEnd = lastHeader.find(' ', codeStart);
if (codeEnd == std::string::npos)
{
codeEnd = lastHeader.length();
}
std::string codeStr = lastHeader.substr(codeStart, codeEnd - codeStart);
try
{
int statusCode = std::stoi(codeStr);
// Handle different status codes
if (statusCode == 200)
{
// Successful download
currentDownload++;
lastStatusCode = 200;
if (progressCallback)
{
progressCallback(currentDownload, totalDownloads);
}
}
else if (statusCode == 429)
{
// Retry - curl will handle it, just ignore
return true;
}
else if (statusCode == 503)
{
// Throttling - save position and expect exit
lastStatusCode = 503;
Lv2Log::warning("%s", "curl: Received 503 response.");
return false;
}
else if (statusCode >= 300 && statusCode < 400)
{
// Redirect, &c - advisory only, ignore, and hope curl will deal with it.
return true;
}
else if (statusCode >= 400)
{
// Error - fatal
lastStatusCode = statusCode;
return false; // Stop processing
}
}
catch (const std::exception &)
{
// Failed to parse status code - continue
return true;
}
}
}
}
else if (line.starts_with("HTTP/"))
{
// Delay processing until we get a blank line.
savedHttpHeader = line;
}
// Other lines are headers - ignore unless we need to save them
return true;
};
// Execute curl
int exitCode = curlExec("/usr/bin/curl", curlArgs, onStdoutLine, stderrOutput);
logHeaders(*outputHeadersOpt);
if (exitCode != EXIT_SUCCESS && exitCode != -1)
{
throwCurlExitCode(exitCode);
}
// Return appropriate status code
return lastStatusCode;
}
int CurlPostFile(
const std::string &url,
const std::filesystem::path &inputPath,
const std::filesystem::path &outputPath,
std::vector<std::string> *outputHeadersOpt,
std::vector<std::string> *inputHeadersOpt)
{
TemporaryFile headersFile{WEB_TEMP_DIR};
std::vector<std::string> defaultHeaders;
if (outputHeadersOpt == nullptr)
{
outputHeadersOpt = &defaultHeaders;
}
bool bResult = true;
std::stringstream ssArgs;
if (inputHeadersOpt)
{
for (const std::string &header : *inputHeadersOpt)
{
ssArgs << "-H " << ShellEscape(header) << " ";
}
}
ssArgs << "-s -L -X POST --data-binary "
<< SS("@" << inputPath.c_str()).c_str()
<< " -D " << ShellEscape(headersFile.Path().c_str());
std::string args = ssArgs.str();
args += SS(" " << ShellEscape(url)
<< " -o " << ShellEscape(outputPath.c_str()));
auto curlOutput = sysExecForOutput("/usr/bin/curl", args);
if (outputHeadersOpt != nullptr)
{
std::ifstream headersStream(headersFile.Path());
if (headersStream)
{
outputHeadersOpt->clear();
std::string line;
while (std::getline(headersStream, line))
{
// Remove trailing carriage return if present
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
if (!line.empty())
{
outputHeadersOpt->push_back(line);
}
}
}
}
logHeaders(*outputHeadersOpt);
if (curlOutput.exitCode == 512)
{
throw std::runtime_error("Invalid curl arguments.");
}
if (curlOutput.exitCode != EXIT_SUCCESS && curlOutput.exitCode != 22 * 256)
{
if (WIFEXITED(curlOutput.exitCode))
{
auto exitCode = WEXITSTATUS(curlOutput.exitCode);
throwCurlExitCode(exitCode);
return -99;
}
else if (WIFSIGNALED(curlOutput.exitCode))
{
throw std::runtime_error("No internet access.");
}
else
{
throw std::runtime_error(SS("Unable to exec curl. (exit status = " << curlOutput.exitCode << ")"));
}
}
int errorCode = checkCurlHttpResponse(*outputHeadersOpt);
return errorCode;
}
int CurlPostStrings(
const std::string &url,
const std::string &body,
std::string &outputBody,
std::vector<std::string> *outputHeadersOpt,
std::vector<std::string> *inputHeadersOpt)
{
TemporaryFile inputFile(WEB_TEMP_DIR);
{
std::ofstream f{inputFile.Path()};
if (!f.is_open())
{
throw std::runtime_error(SS("Can't open file " << inputFile.Path()));
}
f << body;
}
TemporaryFile outputFile(WEB_TEMP_DIR);
int result = CurlPostFile(url, inputFile.Path(), outputFile.Path(), outputHeadersOpt, inputHeadersOpt);
std::ifstream outputStream(outputFile.Path());
if (outputStream)
{
outputBody.assign(
std::istreambuf_iterator<char>(outputStream),
std::istreambuf_iterator<char>());
}
return result;
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2026 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <filesystem>
#include <functional>
namespace pipedal {
extern int CurlGet(
const std::string &url,
const std::filesystem::path&outputFile,
std::vector<std::string> *outputHeadersOpt = nullptr,
const std::vector<std::string> *inputHeadersOpt = nullptr
);
extern int CurlGet(
const std::string &url,
std::vector<uint8_t> &output,
std::vector<std::string> *outputHeadersOpt = nullptr,
const std::vector<std::string> *inputHeadersOpt = nullptr
);
extern int CurlGet(
const std::string&url,
std::vector<std::string> &output,
std::vector<std::string> *outputHeadersOpt = nullptr,
const std::vector<std::string> *inputHeadersOpt = nullptr
);
struct CurlDownloadRequest{
std::string url;
std::filesystem::path outputFile;
};
extern int CurlGet(
const std::vector<CurlDownloadRequest> &request,
const std::function<void(size_t completed, size_t total)> &progressCallback,
std::vector<std::string>*outputHeadersOpt
);
extern int CurlPostFile(
const std::string &url,
const std::filesystem::path&inputFile,
const std::filesystem::path&outputFile,
std::vector<std::string> *outputHeadersOpt = nullptr,
std::vector<std::string> *inputHeadersOpt = nullptr
);
extern int CurlPostStrings(
const std::string &url,
const std::string&body,
std::string&responseBody,
std::vector<std::string> *outputHeadersOpt = nullptr,
std::vector<std::string> *inputHeadersOpt = nullptr
);
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2026 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <cstdint>
#include "json.hpp"
namespace pipedal {
class RealtimeDeviceVus {
public:
static constexpr size_t MAX_CHANNELS = 64;
size_t inputChannels;
size_t outputChannels;
float values[MAX_CHANNELS*2];
size_t MessageSize() const {
return sizeof(RealtimeDeviceVus)-sizeof(float)*(
MAX_CHANNELS*2-
(inputChannels+outputChannels+1)/2*2);
}
};
};
+97 -108
View File
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2024 Robin E. R. Davies
* Copyright (c) 2026 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -23,6 +23,7 @@
*/
#include "pch.h"
#include "PiPedalCommon.hpp"
#include "util.hpp"
#include <bit>
#include <memory>
@@ -39,6 +40,7 @@
#include "ss.hpp"
#include "SchedulerPriority.hpp"
#include "CrashGuard.hpp"
#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp"
@@ -56,6 +58,8 @@
using namespace pipedal;
#ifdef JUNK
namespace pipedal
{
@@ -85,14 +89,19 @@ namespace pipedal
uint32_t captureFrameSize = 0;
std::vector<float *> activeCaptureBuffers;
std::vector<float *> activePlaybackBuffers;
std::vector<std::vector<float>> allocatedBuffers;
std::vector<float *> mainCaptureBuffers;
std::vector<float *> mainPlaybackBuffers;
std::vector<float *> captureBuffers;
std::vector<float *> playbackBuffers;
std::vector<float *> auxCaptureBuffers;
std::vector<float *> auxPlaybackBuffers;
std::vector<float*> sendCaptureBuffers;
std::vector<float*> sendPlaybackBuffers;
std::vector<float *> deviceCaptureBuffers;
std::vector<float *> devicePlaybackBuffers;
uint8_t *rawCaptureBuffer = nullptr;
uint8_t *rawPlaybackBuffer = nullptr;
AudioDriverHost *driverHost = nullptr;
uint32_t channels = 2;
@@ -145,7 +154,7 @@ namespace pipedal
std::atomic<bool> terminateAudio_ = false;
void terminateAudio(bool terminate)
PIPEDAL_NON_INLINE void terminateAudio(bool terminate)
{
this->terminateAudio_ = terminate;
}
@@ -161,32 +170,28 @@ namespace pipedal
}
private:
void AllocateBuffers(std::vector<float *> &buffers, size_t n)
PIPEDAL_NON_INLINE void AllocateBuffers(std::vector<float *> &buffers, size_t n)
{
buffers.resize(n);
for (size_t i = 0; i < n; ++i)
{
buffers[i] = new float[this->bufferSize];
for (size_t j = 0; j < this->bufferSize; ++j)
{
buffers[i][j] = 0;
}
buffers[i] = AllocateAudioBuffer();
}
}
virtual size_t GetMidiInputEventCount() override
PIPEDAL_NON_INLINE virtual size_t GetMidiInputEventCount() override
{
return midiEventCount;
}
virtual MidiEvent *GetMidiEvents() override
PIPEDAL_NON_INLINE virtual MidiEvent *GetMidiEvents() override
{
return this->midiEvents.data();
}
JackChannelSelection channelSelection;
ChannelSelection channelSelection;
bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
PIPEDAL_NON_INLINE virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
terminateAudio_ = false;
if (open)
@@ -208,7 +213,7 @@ namespace pipedal
throw;
}
}
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
PIPEDAL_NON_INLINE virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{
this->alsaSequencer = alsaSequencer;
}
@@ -220,23 +225,28 @@ namespace pipedal
<< ", " << "Native float"
<< ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers
<< ", in: " << this->InputBufferCount() << "/" << this->captureChannels
<< ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels);
<< ", " << "device in: " << this->DeviceInputBufferCount()
<< ", " << "device out: " << this->DeviceOutputBufferCount()
<< ", main in: " << this->MainInputBufferCount()
<< ", main out: " << this->MainOutputBufferCount()
<< ", aux in: " << this->AuxInputBufferCount()
<< ", aux out: " << this->AuxOutputBufferCount()
<< ", send in: " << this->SendInputBufferCount()
<< ", send out: " << this->SendOutputBufferCount()
);
return result;
}
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
void OpenAudio(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
int err;
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize();
AllocateBuffers(captureBuffers, channels);
AllocateBuffers(playbackBuffers, channels);
}
std::jthread *audioThread;
std::unique_ptr<std::jthread> audioThread;
bool audioRunning;
bool block = false;
@@ -303,7 +313,7 @@ namespace pipedal
this->driverHost->OnProcess(framesRead);
/// no attempt at realtime. Just as long as we run occasionally.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
@@ -318,22 +328,6 @@ namespace pipedal
bool alsaActive = false;
static int IndexFromPortName(const std::string &s)
{
auto pos = s.find_last_of('_');
if (pos == std::string::npos)
{
throw std::invalid_argument("Bad port name.");
}
const char *p = s.c_str() + (pos + 1);
int v = atoi(p);
if (v < 0)
{
throw std::invalid_argument("Bad port name.");
}
return v;
}
bool activated = false;
virtual void Activate()
@@ -344,41 +338,17 @@ namespace pipedal
}
activated = true;
this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size());
AllocateBuffers(deviceCaptureBuffers, channels);
AllocateBuffers(devicePlaybackBuffers, channels);
playbackBuffers.resize(channels);
AllocateBuffers(mainCaptureBuffers, channelSelection.mainInputChannels().size());
AllocateBuffers(mainPlaybackBuffers, channelSelection.mainOutputChannels().size());
AllocateBuffers(auxCaptureBuffers, channelSelection.auxInputChannels().size());
AllocateBuffers(auxPlaybackBuffers, channelSelection.auxOutputChannels().size());
AllocateBuffers(sendCaptureBuffers, channelSelection.sendInputChannels().size());
AllocateBuffers(sendPlaybackBuffers, channelSelection.sendOutputChannels().size());
int ix = 0;
for (auto &x : channelSelection.GetInputAudioPorts())
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= captureBuffers.size())
{
Lv2Log::error(SS("Invalid audio input port: " << x));
}
else
{
this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex];
}
}
this->activePlaybackBuffers.resize(channelSelection.GetOutputAudioPorts().size());
ix = 0;
for (auto &x : channelSelection.GetOutputAudioPorts())
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= playbackBuffers.size())
{
Lv2Log::error(SS("Invalid audio output port: " << x));
}
else
{
this->activePlaybackBuffers[ix++] = this->playbackBuffers[sourceIndex];
}
}
audioThread = new std::jthread([this]()
audioThread = std::make_unique<std::jthread>([this]()
{ AudioThread(); });
}
@@ -393,7 +363,7 @@ namespace pipedal
if (audioThread)
{
this->audioThread->join();
this->audioThread = 0;
this->audioThread = nullptr;
}
Lv2Log::debug("Audio thread joined.");
}
@@ -402,46 +372,64 @@ namespace pipedal
public:
virtual size_t DeviceInputBufferCount() const override { return deviceCaptureBuffers.size(); }
virtual size_t DeviceOutputBufferCount() const override { return devicePlaybackBuffers.size(); }
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
virtual float *GetInputBuffer(size_t channel)
virtual size_t MainInputBufferCount() const override { return mainCaptureBuffers.size(); }
virtual float *GetMainInputBuffer(size_t channel) override
{
return activeCaptureBuffers[channel];
return mainCaptureBuffers[channel];
}
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel)
virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
virtual float *GetMainOutputBuffer(size_t channel) override
{
return activePlaybackBuffers[channel];
return mainPlaybackBuffers[channel];
}
void FreeBuffers(std::vector<float *> &buffer)
virtual size_t AuxInputBufferCount() const override { return auxCaptureBuffers.size(); }
virtual float *GetAuxInputBuffer(size_t channel) override
{
for (size_t i = 0; i < buffer.size(); ++i)
{
// delete[] buffer[i];
buffer[i] = 0;
}
buffer.clear();
return auxCaptureBuffers[channel];
}
void DeleteBuffers()
virtual size_t AuxOutputBufferCount() const override { return auxPlaybackBuffers.size(); }
virtual float *GetAuxOutputBuffer(size_t channel) override
{
activeCaptureBuffers.clear();
activePlaybackBuffers.clear();
FreeBuffers(this->playbackBuffers);
FreeBuffers(this->captureBuffers);
if (rawCaptureBuffer)
{
delete[] rawCaptureBuffer;
rawCaptureBuffer = nullptr;
}
if (rawPlaybackBuffer)
{
delete[] rawPlaybackBuffer;
rawPlaybackBuffer = nullptr;
}
return auxPlaybackBuffers[channel];
}
virtual void Close()
virtual size_t SendInputBufferCount() const override { return sendCaptureBuffers.size(); }
virtual float *GetSendInputBuffer(size_t channel) override
{
return sendCaptureBuffers[channel];
}
virtual size_t SendOutputBufferCount() const override { return sendPlaybackBuffers.size(); }
virtual float *GetSendOutputBuffer(size_t channel) override
{
return sendPlaybackBuffers[channel];
}
PIPEDAL_NON_INLINE float*AllocateAudioBuffer() {
allocatedBuffers.push_back(std::vector<float>(bufferSize));
return allocatedBuffers.back().data();
}
PIPEDAL_NON_INLINE void DeleteBuffers()
{
mainCaptureBuffers.clear();
mainPlaybackBuffers.clear();
auxCaptureBuffers.clear();
auxPlaybackBuffers.clear();
sendCaptureBuffers.clear();
sendPlaybackBuffers.clear();
allocatedBuffers.clear();
}
PIPEDAL_NON_INLINE virtual void Close()
{
if (!open)
{
@@ -453,18 +441,18 @@ namespace pipedal
DeleteBuffers();
}
virtual float CpuUse()
PIPEDAL_NON_INLINE virtual float CpuUse()
{
return 0;
}
virtual float CpuOverhead()
PIPEDAL_NON_INLINE virtual float CpuOverhead()
{
return 0.1;
}
};
AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost,const std::string&deviceName)
PIPEDAL_NON_INLINE AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost,const std::string&deviceName)
{
return new DummyDriverImpl(driverHost,deviceName);
}
@@ -538,3 +526,4 @@ uint32_t pipedal::GetDummyAudioChannels(const std::string &deviceName)
}
#endif
+3
View File
@@ -24,6 +24,8 @@
#pragma once
#ifdef JUNK
#include <lilv/lilv.h>
#include "AudioDriver.hpp"
#include "JackServerSettings.hpp"
@@ -35,4 +37,5 @@ namespace pipedal {
AudioDriver* CreateDummyAudioDriver(AudioDriverHost*driverHost,const std::string&deviceId);
}
#endif
+2 -2
View File
@@ -26,6 +26,7 @@ namespace pipedal {
class Lv2PluginInfo;
class IEffect;
class HostWorkerThread;
class ChannelSelection;
class IHost
{
@@ -43,13 +44,12 @@ namespace pipedal {
virtual size_t GetAtomBufferSize() const = 0;
virtual bool HasMidiInputChannel() const = 0;
virtual int GetNumberOfInputAudioChannels() const = 0;
virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
virtual std::string GetPluginStoragePath() const = 0;
virtual const ChannelSelection&GetChannelSelection() const = 0;
};
}
+4 -6
View File
@@ -130,12 +130,8 @@ void JackConfiguration::AlsaInitialize(
this->isValid_ = false;
this->errorStatus_ = "";
if (jackServerSettings.IsDummyAudioDevice())
{
this->inputMidiDevices_.clear();
} else {
this->inputMidiDevices_ = GetAlsaSequencers(); // NB: Sequencers, not rawmidi devices, anymore.
}
this->inputMidiDevices_ = GetAlsaSequencers(); // NB: Sequencers, not rawmidi devices, anymore.
if (jackServerSettings.IsValid())
{
this->blockLength_ = jackServerSettings.GetBufferSize();
@@ -149,6 +145,8 @@ void JackConfiguration::AlsaInitialize(
}
} catch (const std::exception& /*ignore*/)
{
this->isValid_ = false;
this->errorStatus_ = "Not configured.";
throw;
}
-1
View File
@@ -124,5 +124,4 @@ namespace pipedal
DECLARE_JSON_MAP(JackChannelSelection);
};
} // namespace.
+57 -146
View File
@@ -28,6 +28,7 @@
#include <filesystem>
#include "Lv2Log.hpp"
#include "SysExec.hpp"
#include "PiPedalAlsa.hpp"
#define DRC_FILENAME "/etc/jackdrc"
@@ -60,33 +61,41 @@ static std::vector<std::string> SplitArgs(const char *szBuff)
return result;
}
static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string &shortOption, const std::string &longOption)
{
std::string strVal;
for (size_t i = 1; i < args.size(); ++i)
{
auto pos = args[i].rfind(longOption,0);
if (pos != std::string::npos) {
auto pos = args[i].rfind(longOption, 0);
if (pos != std::string::npos)
{
if (args[i].length() == longOption.length())
{
if (i == args.size()-1) {
throw PiPedalException("Can't read Jack configuration.");
if (i == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
strVal = args[i+1];
} else {
strVal = args[i + 1];
}
else
{
strVal = args[i].substr(longOption.length());
}
break;
}
pos = args[i].rfind(shortOption,0);
if (pos != std::string::npos) {
pos = args[i].rfind(shortOption, 0);
if (pos != std::string::npos)
{
if (args[i].length() == shortOption.length())
{
if (i == args.size()-1) {
throw PiPedalException("Can't read Jack configuration.");
if (i == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
strVal = args[i+1];
} else {
strVal = args[i + 1];
}
else
{
strVal = args[i].substr(shortOption.length());
}
break;
@@ -94,9 +103,9 @@ static std::string GetJackStringArg(const std::vector<std::string> &args, const
}
return strVal;
}
static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string &shortOption, const std::string &longOption)
{
std::string strVal = GetJackStringArg(args,shortOption,longOption);
std::string strVal = GetJackStringArg(args, shortOption, longOption);
try
{
unsigned long value = std::stoul(strVal);
@@ -108,145 +117,45 @@ static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::
}
}
void JackServerSettings::ReadJackDaemonConfiguration()
static std::string GetAlsaDeviceName(const std::vector<AlsaDeviceInfo> &availableDevices, const std::string &id)
{
#if !JACK_HOST
return;
#endif
this->valid_ = false;
std::string lastLine;
if (id.empty())
return "";
std::string name;
for (const auto &availableDevice : availableDevices)
{
if (availableDevice.id_ == id)
{
name = availableDevice.name_;
break;
}
}
if (name.empty())
{
auto pos = id.find(":");
if (pos != std::string::npos)
{
name = id.substr(pos + 1);
}
}
return name;
}
void JackServerSettings::FixUpDeviceNames()
{
if (
((!this->alsaInputDevice_.empty()) && this->alsaInputDeviceName_.empty()) ||
(!this->alsaOutputDevice_.empty()) && this->alsaOutputDeviceName_.empty())
{
ifstream input(DRC_FILENAME);
if (!input.is_open())
{
return;
}
PiPedalAlsaDevices& alsaDevices = PiPedalAlsaDevices::instance();
std::vector<AlsaDeviceInfo> availableDevices = alsaDevices.GetAlsaDevices();
while (true)
{
std::string line;
std::getline(input,line);
if (line.length() != 0) {
lastLine = line;
}
if (input.eof()) {
break;
}
}
try
{
std::vector<std::string> argv = SplitArgs(lastLine.c_str());
for (auto i = argv.begin(); i != argv.end(); ++i)
{
if ((*i) == "-dalsa") {
argv.erase(i);
break;
}
}
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
// read new dual device flags, fallback on old -d/--device
std::string capDev = GetJackStringArg(argv, "-C", "--capture");
std::string playDev = GetJackStringArg(argv, "-P", "--playback");
std::string dev = "";
try { dev = GetJackStringArg(argv, "-d", "--device"); } catch(...) {}
this->alsaInputDevice_ = capDev.empty() ? dev : capDev;
this->alsaOutputDevice_ = playDev.empty() ? dev : playDev;
this->valid_ = true;
}
catch (std::exception &)
{
//Lv2Log::error("Can't parse " DRC_FILENAME);
}
this->alsaInputDeviceName_ = GetAlsaDeviceName(availableDevices, this->alsaInputDevice_);
this->alsaOutputDeviceName_ = GetAlsaDeviceName(availableDevices, this->alsaOutputDevice_);
}
}
void JackServerSettings::WriteDaemonConfig()
{
#if JACK_HOST
this->valid_ = false;
std::vector<std::string> precedingLines;
std::vector<std::string> argv;
std::string lastLine;
if (std::filesystem::exists(DRC_FILENAME)) {
ifstream input(DRC_FILENAME);
if (!input.is_open())
{
return;
}
while (true)
{
if (input.eof())
{
break;
}
std::getline(input,lastLine);
precedingLines.push_back(lastLine);
if (input.eof())
{
break;
}
}
// erase blank lines at the end.
while (precedingLines.size() != 0 && precedingLines[precedingLines.size()-1] == "")
{
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
// erase the last line, which should contain the command invocation.
if (precedingLines.size() != 0)
{
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
}
// write to the output.
try
{
ofstream output(DRC_FILENAME);
if (!output.is_open())
{
throw PiPedalException("Can't write " DRC_FILENAME);
}
if (precedingLines.size() == 0)
{
// jack1 incantation for promiscuous servers.
output << "#!/bin/sh" <<endl;
output << "export JACK_PROMISCUOUS_SERVER=audio" << endl;
output << "export JACK_NO_AUDIO_RESERVATION=1" << endl;
output << "umask 0" << endl;
}
for (auto line : precedingLines)
{
output << line << endl;
}
// the style used by qjackctl. :-/
// Lower to -P70 in order to allow the USB soft-irq to run at higher priority than JACK (it runs at 80).
output << "/usr/bin/jackd "
<< "-R -P70 --silent"
<< " -dalsa -d" << this->alsaDevice_
<< " -r" << this->sampleRate_
<< " -p" << this->bufferSize_
<< " -n" << this->numberOfBuffers_ << " -Xseq"
<< endl;
if (silentSysExec("/usr/bin/chmod 755 " DRC_FILENAME) != 0)
{
Lv2Log::error("Failed to set permissions on /etc/jackdrc");
}
}
catch (const std::exception &e)
{
}
#else
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
#endif
}
JSON_MAP_BEGIN(JackServerSettings)
JSON_MAP_REFERENCE(JackServerSettings, valid)
@@ -256,6 +165,8 @@ JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) // legacy field
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDeviceName)
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDeviceName)
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
+19 -13
View File
@@ -33,7 +33,9 @@ namespace pipedal
bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false;
std::string alsaInputDevice_;
std::string alsaInputDeviceName_;
std::string alsaOutputDevice_;
std::string alsaOutputDeviceName_;
std::string alsaDevice_; // legacy
uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 64;
@@ -44,6 +46,7 @@ namespace pipedal
JackServerSettings(
const std::string &alsaInputDevice,
const std::string &alsaOutputDevice,
uint64_t sampleRate,
uint32_t bufferSize,
uint32_t numberOfBuffers)
@@ -55,39 +58,41 @@ namespace pipedal
numberOfBuffers_(numberOfBuffers),
isOnboarding_(false)
{
FixUpDeviceNames();
}
uint64_t GetSampleRate() const { return sampleRate_; }
uint32_t GetBufferSize() const { return bufferSize_; }
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; }
const std::string &GetAlsaInputDeviceName() const { return alsaInputDeviceName_; }
const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; }
const std::string &GetAlsaOutputDeviceName() const { return alsaOutputDeviceName_; }
const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy
void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; }
void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; }
void SetAlsaInputDevice(const std::string &id, const std::string&name){ alsaInputDevice_ = id; alsaInputDeviceName_ = name; }
void SetAlsaOutputDevice(const std::string &id, const std::string&name){ alsaOutputDevice_ = id; alsaOutputDeviceName_ = name; }
void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; }
void UseDummyAudioDevice() {
this->valid_ = true;
if (sampleRate_ == 0) sampleRate_ = 48000;
this->alsaDevice_ = "dummy:channels_2";
this->alsaInputDevice_ = "dummy:channels_2";
this->alsaOutputDevice_ = "dummy:channels_2";
bufferSize_ = 256;
numberOfBuffers_ = 6;
this->alsaDevice_ = "";
this->alsaInputDevice_ = "null";
this->alsaInputDeviceName_ = "null";
this->alsaOutputDevice_ = "null";
this->alsaOutputDeviceName_ = "null";
}
bool IsDummyAudioDevice() const {
return
this->alsaDevice_.starts_with("__DUMMY_AUDIO__")
|| this->alsaDevice_.starts_with("dummy:")
|| this->alsaInputDevice_.starts_with("dummy:");
return alsaInputDevice_ == "null" && alsaOutputDevice_ == "null";
}
void ReadJackDaemonConfiguration();
bool IsValid() const { return valid_; }
void WriteDaemonConfig(); // requires root perms.
void SetRebootRequired(bool value)
{
rebootRequired_ = value;
@@ -106,6 +111,7 @@ namespace pipedal
this->bufferSize_ == other.bufferSize_ &&
this->numberOfBuffers_ == other.numberOfBuffers_;
}
void FixUpDeviceNames();
DECLARE_JSON_MAP(JackServerSettings);
};
+42 -18
View File
@@ -29,6 +29,7 @@
#include "Lv2Log.hpp"
#include "CrashGuard.hpp"
#include "restrict.hpp"
#include "AudioDriver.hpp"
using namespace pipedal;
@@ -329,13 +330,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardE
inputVolume.SetTarget(pedalboard.input_volume_db());
outputVolume.SetTarget(pedalboard.output_volume_db());
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
size_t nInputs = std::max(GetNumberOfAudioInputChannels(),(size_t)1);
for (size_t i = 0; i < nInputs; ++i)
{
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
size_t nOutputs = GetNumberOfAudioOutputChannels();
if (nOutputs == 1)
{
this->pedalboardOutputBuffers.push_back(outputs[0]);
@@ -543,6 +546,9 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
float volume = outputVolume.Tick();
for (size_t c = 0; c < this->pedalboardOutputBuffers.size(); ++c)
{
if (outputBuffers[c] == nullptr) {
break;
}
outputBuffers[c][i] = this->pedalboardOutputBuffers[c][i] * volume;
}
}
@@ -568,37 +574,46 @@ void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
effect->SetBypass(enabled);
}
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers)
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *realtimeVuBuffers, uint32_t samples)
{
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
if (realtimeVuBuffers == nullptr)
{
int index = vuConfiguration->enabledIndexes[i];
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
if (index == Pedalboard::INPUT_VOLUME_ID)
return;
}
for (size_t i = 0; i < realtimeVuBuffers->enabledIndexes.size(); ++i)
{
auto& rtIndex = realtimeVuBuffers->enabledIndexes[i];
int index = rtIndex.index;
if (index == -1) continue;
if (index == Pedalboard::AUX_START_CONTROL_ID || index == Pedalboard::AUX_END_CONTROL_ID)
{
if (this->pedalboardInputBuffers.size() > 1)
// handled by master VU updates.
continue;
}
VuUpdateX *pUpdate = &realtimeVuBuffers->vuUpdateWorkingData[i];
if (index == Pedalboard::START_CONTROL_ID)
{
if (this->pedalboardInputBuffers.size() == 2)
{
GetInputBuffers();
pUpdate->AccumulateInputs(inputBuffers[0], inputBuffers[1], samples);
// input is handled by master VU updates.
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), &(this->pedalboardInputBuffers[1][0]), samples); // after outputVolume applied.
}
else
else if (this->pedalboardInputBuffers.size() == 1)
{
pUpdate->AccumulateInputs(inputBuffers[0], samples);
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // after input volume applied.
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // before input volume applied.
// output is handled by master VU updates.
}
}
else if (index == Pedalboard::OUTPUT_VOLUME_ID)
else if (index == Pedalboard::END_CONTROL_ID)
{
if (this->pedalboardOutputBuffers.size() > 1)
if (this->pedalboardOutputBuffers.size() == 2)
{
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]), &(this->pedalboardOutputBuffers[1][0]), samples);
pUpdate->AccumulateOutputs(outputBuffers[0], outputBuffers[1], samples);
}
else
else if (this->pedalboardOutputBuffers.size() == 1)
{
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]), samples);
pUpdate->AccumulateOutputs(outputBuffers[0], samples);
}
}
else
@@ -1016,3 +1031,12 @@ void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp
mapping.lastTapTimestamp = timestamp;
}
}
size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const {
return pHost->GetChannelSelection().mainInputChannels().size();
}
size_t Lv2Pedalboard::GetNumberOfAudioOutputChannels() const {
return pHost->GetChannelSelection().mainOutputChannels().size();
}
+5 -1
View File
@@ -31,6 +31,7 @@
namespace pipedal
{
class AudioDriver;
class IPatchWriterCallback;
class RealtimeVuBuffers;
class RealtimePatchPropertyRequest;
@@ -128,6 +129,9 @@ namespace pipedal
std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; }
size_t GetNumberOfAudioInputChannels() const;
size_t GetNumberOfAudioOutputChannels() const;
int GetIndexOfInstanceId(uint64_t instanceId)
{
for (int i = 0; i < this->realtimeEffects.size(); ++i)
@@ -169,7 +173,7 @@ namespace pipedal
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value); }
void SetBypass(int effectIndex, bool enabled);
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers);
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples);
float GetControlOutputValue(int effectIndex, int portIndex);
+5
View File
@@ -510,6 +510,11 @@ Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousP
}
Pedalboard::Pedalboard()
{
nextInstanceId_ = 0;
}
JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,key)
+215 -207
View File
@@ -25,13 +25,14 @@
#include "StateInterface.hpp"
#include "atom_object.hpp"
namespace pipedal {
namespace pipedal
{
class SnapshotValue;
class Snapshot;
class PluginHost;
#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split"
#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty"
#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"
@@ -41,238 +42,245 @@ namespace pipedal {
#define SPLIT_PANR_KEY "panR"
#define SPLIT_VOLR_KEY "volR"
#define GETTER_SETTER_REF(name) \
const decltype(name##_)& name() const { return name##_;} \
#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##_;} \
void name(decltype(name##_)&&value) { this->name##_ = std::move(value); }\
void name(const decltype(name##_)&value) { this->name##_ = value; }
#define GETTER_SETTER_VEC(name) \
decltype(name##_) &name() { return name##_; } \
const decltype(name##_) &name() const { return name##_; } \
void name(decltype(name##_) &&value) { this->name##_ = std::move(value); } \
void name(const decltype(name##_) &value) { this->name##_ = value; }
#define GETTER_SETTER(name) \
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()
class ControlValue
{
private:
std::string key_;
float value_;
}
ControlValue(const char*key, float value)
:key_(key)
, value_(value)
{
}
ControlValue(const std::string&key, float value)
:key_(key)
, value_(value)
{
}
GETTER_SETTER_REF(key)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(ControlValue);
};
class PedalboardItem {
public:
using PropertyMap = std::map<std::string,atom_object>;
int64_t 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_;
std::optional<MidiChannelBinding> midiChannelBinding_;
std::string vstState_;
uint32_t stateUpdateCount_ = 0;
Lv2PluginState lv2State_;
std::string lilvPresetUri_;
std::map<std::string,std::string> pathProperties_;
std::string title_;
bool useModUi_ = false;
std::string iconColor_;
int64_t sideChainInputId_ = -1;
// non persistent state.
PropertyMap patchProperties;
public:
ControlValue*GetControlValue(const std::string&symbol);
const ControlValue*GetControlValue(const std::string&symbol) const;
bool SetControlValue(const std::string&key, float value);
bool IsStructurallyIdentical(const PedalboardItem&other) const;
void ApplySnapshotValue(SnapshotValue*snapshotValue);
void ApplyDefaultValues(PluginHost&pluginHost);
bool hasLv2State() const {
return lv2State_.isValid_ != 0;
}
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(midiChannelBinding)
GETTER_SETTER_REF(pathProperties)
GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State)
GETTER_SETTER_REF(title)
GETTER_SETTER_REF(iconColor)
GETTER_SETTER(useModUi)
GETTER_SETTER(sideChainInputId)
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
GETTER_SETTER_REF(lilvPresetUri)
PropertyMap&PatchProperties() { return patchProperties; }
const PropertyMap&PatchProperties() const { return patchProperties; }
bool isSplit() const
{
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
}
bool isEmpty() const {
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
}
void AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const;
void AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const;
// 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_);
// if (isSplit())
// {
// writer.write_member("topChain",topChain_);
// writer.write_member("bottomChain",bottomChain_);
// }
// }
DECLARE_JSON_MAP(PedalboardItem);
};
class SnapshotValue {
public:
uint64_t instanceId_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
Lv2PluginState lv2State_;
std::map<std::string,std::string> pathProperties_;
DECLARE_JSON_MAP(SnapshotValue);
ControlValue*GetControlValue(const std::string& key) {
for (ControlValue&controlValue: controlValues_) {
if (controlValue.key() == key) {
return &controlValue;
}
public:
ControlValue()
{
}
return nullptr;
}
void SetControlValue(const std::string& key,float value) {
for (ControlValue&controlValue: controlValues_) {
if (controlValue.key() == key) {
controlValue.value(value);
return;
}
ControlValue(const char *key, float value)
: key_(key), value_(value)
{
}
controlValues_.push_back(ControlValue{key,value});
}
};
ControlValue(const std::string &key, float value)
: key_(key), value_(value)
{
}
GETTER_SETTER_REF(key)
GETTER_SETTER_REF(value)
class Snapshot {
public:
std::string name_;
std::string color_;
bool isModified_ = false;
std::vector<SnapshotValue> values_;
DECLARE_JSON_MAP(ControlValue);
};
DECLARE_JSON_MAP(Snapshot);
class PedalboardItem
{
public:
using PropertyMap = std::map<std::string, atom_object>;
int64_t 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_;
std::optional<MidiChannelBinding> midiChannelBinding_;
std::string vstState_;
uint32_t stateUpdateCount_ = 0;
Lv2PluginState lv2State_;
std::string lilvPresetUri_;
std::map<std::string, std::string> pathProperties_;
std::string title_;
bool useModUi_ = false;
std::string iconColor_;
int64_t sideChainInputId_ = -1;
};
// non persistent state.
PropertyMap patchProperties;
class Pedalboard {
std::string name_;
float input_volume_db_ = 0;
float output_volume_db_ = 0;
public:
ControlValue *GetControlValue(const std::string &symbol);
const ControlValue *GetControlValue(const std::string &symbol) const;
bool SetControlValue(const std::string &key, float value);
std::vector<PedalboardItem> items_;
uint64_t nextInstanceId_ = 0;
uint64_t NextInstanceId() { return ++nextInstanceId_; }
bool IsStructurallyIdentical(const PedalboardItem &other) const;
std::vector<std::shared_ptr<Snapshot>> snapshots_;
int64_t selectedSnapshot_ = -1;
void ApplySnapshotValue(SnapshotValue *snapshotValue);
void ApplyDefaultValues(PluginHost &pluginHost);
bool hasLv2State() const
{
return lv2State_.isValid_ != 0;
}
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(midiChannelBinding)
GETTER_SETTER_REF(pathProperties)
GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State)
GETTER_SETTER_REF(title)
GETTER_SETTER_REF(iconColor)
GETTER_SETTER(useModUi)
GETTER_SETTER(sideChainInputId)
int64_t selectedPlugin_ = -1;
Lv2PluginState &lv2State() { return lv2State_; } // non-const version.
GETTER_SETTER_REF(lilvPresetUri)
public:
// deep copy, breaking shared pointers.
Pedalboard DeepCopy();
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor);
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
bool SetItemUseModUi(int64_t pedalItemId, bool enabled);
void SetCurrentSnapshotModified(bool modified);
PropertyMap &PatchProperties() { return patchProperties; }
const PropertyMap &PatchProperties() const { return patchProperties; }
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
bool isSplit() const
{
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
}
bool isEmpty() const
{
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
}
PedalboardItem*GetItem(int64_t pedalItemId);
const PedalboardItem*GetItem(int64_t pedalItemId) const;
std::vector<PedalboardItem*>GetAllPlugins();
void AddToSnapshotFromCurrentSettings(Snapshot &snapshot) const;
void AddResetsForMissingProperties(Snapshot &snapshot, size_t *index) const;
bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
bool ApplySnapshot(int64_t snapshotIndex, PluginHost &pluginHost);
// 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_);
// if (isSplit())
// {
// writer.write_member("topChain",topChain_);
// writer.write_member("bottomChain",bottomChain_);
// }
// }
GETTER_SETTER_REF(name)
GETTER_SETTER_VEC(items)
GETTER_SETTER(input_volume_db)
GETTER_SETTER(output_volume_db)
GETTER_SETTER_VEC(snapshots)
GETTER_SETTER(selectedSnapshot)
GETTER_SETTER(selectedPlugin)
DECLARE_JSON_MAP(PedalboardItem);
};
class SnapshotValue
{
public:
uint64_t instanceId_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
Lv2PluginState lv2State_;
std::map<std::string, std::string> pathProperties_;
DECLARE_JSON_MAP(SnapshotValue);
ControlValue *GetControlValue(const std::string &key)
{
for (ControlValue &controlValue : controlValues_)
{
if (controlValue.key() == key)
{
return &controlValue;
}
}
return nullptr;
}
void SetControlValue(const std::string &key, float value)
{
for (ControlValue &controlValue : controlValues_)
{
if (controlValue.key() == key)
{
controlValue.value(value);
return;
}
}
controlValues_.push_back(ControlValue{key, value});
}
};
class Snapshot
{
public:
std::string name_;
std::string color_;
bool isModified_ = false;
std::vector<SnapshotValue> values_;
DECLARE_JSON_MAP(Snapshot);
};
DECLARE_JSON_MAP(Pedalboard);
class Pedalboard
{
std::string name_;
float input_volume_db_ = 0;
float output_volume_db_ = 0;
PedalboardItem MakeEmptyItem();
PedalboardItem MakeSplit();
std::vector<PedalboardItem> items_;
uint64_t nextInstanceId_ = 0;
uint64_t NextInstanceId() { return ++nextInstanceId_; }
std::vector<std::shared_ptr<Snapshot>> snapshots_;
int64_t selectedSnapshot_ = -1;
int64_t selectedPlugin_ = -1;
static Pedalboard MakeDefault();
};
public:
static constexpr int64_t START_CONTROL_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t END_CONTROL_ID = -3; // synthetic PedalboardItem for output volume.
static constexpr int64_t AUX_START_CONTROL_ID = -4; // synthetic PedalboardItem for aux input volume.
static constexpr int64_t AUX_END_CONTROL_ID = -5; // synthetic PedalboardItem for aux output volume.
Pedalboard();
// deep copy, breaking shared pointers.
Pedalboard DeepCopy();
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string &iconColor);
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
bool SetItemUseModUi(int64_t pedalItemId, bool enabled);
void SetCurrentSnapshotModified(bool modified);
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
PedalboardItem *GetItem(int64_t pedalItemId);
const PedalboardItem *GetItem(int64_t pedalItemId) const;
std::vector<PedalboardItem *> GetAllPlugins();
bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
bool ApplySnapshot(int64_t snapshotIndex, PluginHost &pluginHost);
GETTER_SETTER_REF(name)
GETTER_SETTER_VEC(items)
GETTER_SETTER(input_volume_db)
GETTER_SETTER(output_volume_db)
GETTER_SETTER_VEC(snapshots)
GETTER_SETTER(selectedSnapshot)
GETTER_SETTER(selectedPlugin)
DECLARE_JSON_MAP(Pedalboard);
PedalboardItem MakeEmptyItem();
PedalboardItem MakeSplit();
static Pedalboard MakeDefault();
};
#undef GETTER_SETTER_REF
#undef GETTER_SETTER_VEC
#undef GETTER_SETTER
} // namespace pipedal
+30 -19
View File
@@ -34,6 +34,7 @@
#include <chrono>
#include <thread>
#include <sched.h>
#include "ChannelRouterSettings.hpp"
using namespace pipedal;
@@ -102,8 +103,7 @@ void PrintHelp()
void ListDevices()
{
PiPedalAlsaDevices alsaDevices;
auto devices = alsaDevices.GetAlsaDevices();
auto devices = PiPedalAlsaDevices::instance().GetAlsaDevices();
PrettyPrinter pp;
if (devices.size() == 0)
@@ -169,19 +169,30 @@ public:
delete[] inputBuffers;
delete[] outputBuffers;
}
std::vector<std::string> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
{
if (selection.size() == 0)
return available;
virtual bool OnRealtimeUpdateDeviceVus(size_t nFrames) {
return true;
}
std::vector<int64_t> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
{
std::vector<int64_t> result;
if (selection.size() == 0)
{
result.resize(available.size());
for (size_t i = 0; i < result.size(); ++i)
{
result[i] = (int64_t)i;
}
return result;
}
std::vector<std::string> result;
for (int sel : selection)
{
if (sel < 0 || sel >= available.size())
{
throw PiPedalArgumentException(SS("Invalid channel: " + sel));
}
result.push_back(available[sel]);
result.push_back(sel);
}
return result;
}
@@ -191,7 +202,7 @@ public:
TestResult result;
try
{
JackServerSettings serverSettings(inputDeviceId, outputDeviceId, sampleRate, bufferSize, buffers);
JackServerSettings serverSettings(inputDeviceId,outputDeviceId, sampleRate, bufferSize, buffers);
JackConfiguration jackConfiguration;
jackConfiguration.AlsaInitialize(serverSettings);
@@ -199,22 +210,22 @@ public:
auto &availableInputs = jackConfiguration.inputAudioPorts();
auto &availableOutputs = jackConfiguration.outputAudioPorts();
std::vector<std::string> inputAudioPorts, outputAudioPorts;
std::vector<int64_t> inputAudioPorts, outputAudioPorts;
inputAudioPorts = SelectChannels(availableInputs, this->inputChannels);
outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels);
JackChannelSelection channelSelection(
inputAudioPorts, outputAudioPorts,
std::vector<AlsaMidiDeviceInfo>());
ChannelSelection channelSelection;
channelSelection.mainInputChannels() = inputAudioPorts;
channelSelection.mainOutputChannels() = outputAudioPorts;
audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
outputBuffers = new float *[channelSelection.GetOutputAudioPorts().size()];
inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate();
@@ -364,16 +375,16 @@ public:
virtual void OnProcess(size_t nFrames)
{
size_t inputs = audioDriver->InputBufferCount();
size_t outputs = audioDriver->OutputBufferCount();
size_t inputs = audioDriver->MainInputBufferCount();
size_t outputs = audioDriver->MainOutputBufferCount();
for (size_t i = 0; i < inputs; ++i)
{
inputBuffers[i] = audioDriver->GetInputBuffer(i);
inputBuffers[i] = audioDriver->GetMainInputBuffer(i);
}
for (size_t i = 0; i < outputs; ++i)
{
outputBuffers[i] = audioDriver->GetOutputBuffer(i);
outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
}
for (size_t i = 0; i < nFrames; ++i)

Some files were not shown because too many files have changed in this diff Show More