Merge pull request #267 from rerdavies/dev PiPedal 1.4.70-Beta

PiPedal 1.4.70-Beta
This commit is contained in:
Robin Davies
2025-02-20 15:57:01 -05:00
committed by GitHub
436 changed files with 17412 additions and 6451 deletions
+2
View File
@@ -11,3 +11,5 @@ react/package-lock.json
src/dbus/bluez_adaptor.h
src/dbus/bluez_proxy.h
CMakeFiles
+5 -4
View File
@@ -5,13 +5,14 @@
"includePath": [
"${default}",
"${workspaceFolder}",
"${workspaceFolder}/src",
"${workspaceFolder}/build/src/**",
"${workspaceFolder}/src/**",
"${workspaceFolder}/**",
"/usr/include/lilv-0",
"/usr/include/x86_64-linux-gnu",
"/usr/lib",
"~/src/vst3sdk"
"/usr/lib"
],
"compilerPath": "/usr/bin/gcc",
"compilerPath": "/usr/bin/gcc-12",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64",
+48 -42
View File
@@ -21,7 +21,9 @@
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ ],
"args": [
"--no-sudo"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
@@ -138,7 +140,9 @@
//"[inverting_mutex_test]"
// "[utf8_to_utf32]"
//"[pipedal_alsa_test]"
"[wifi_channels_test]"
// "[wifi_channels_test]"
// "[locale]"
"[modFileTypes]"
],
"stopAtEntry": false,
@@ -266,6 +270,48 @@
}
]
},
{
"name": "(gdb) pipedalconfigX",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "/home/robin/src/pipedal/build/src/pipedalconfig",
"args": [
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
//"--get-current-port"
// "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
// "--alsa-devices"
"--prefix", "/usr",
"--install", "--port", "0"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
},
{
"name": "OTHER_VALUE",
"value": "Something something"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
@@ -312,46 +358,6 @@
}
]
},
{
"name": "(gdb) pipedalconfig",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
//"--get-current-port"
// "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
"--alsa-devices"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
},
{
"name": "OTHER_VALUE",
"value": "Something something"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) pipedal_alsa_check",
"type": "cppdbg",
+27 -3
View File
@@ -98,17 +98,41 @@
"p2p_i.h": "c",
"p2p.h": "c",
"hash_set": "cpp",
"barrier": "cpp"
"barrier": "cpp",
"format": "cpp",
"locale": "cpp",
"stdfloat": "cpp",
"text_encoding": "cpp"
},
"cSpell.words": [
"Alsa",
"flac",
"Guitarix",
"hostapd",
"Hotspot",
"invada",
"jmap",
"Lilv",
"midiclip",
"midisong",
"mlmodel",
"Pedalboard",
"pipedal",
"pipedal_0",
"pipedalconfig",
"pipedald",
"pipedalshutdownd",
"pipedaltest",
"ToobAmp"
]
"pipedalui",
"rdfs",
"ToobAmp",
"urids",
"Writables",
"xruns"
],
"C_Cpp.default.compilerPath": "/usr/bin/g++-13",
"cSpell.ignoreWords": [
"nammodel"
],
"cSpell.enabled": false
}
+7 -16
View File
@@ -1,39 +1,30 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 1.3.69
VERSION 1.4.70
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.3.69-Release")
set (DISPLAY_VERSION "PiPedal v1.4.70-Beta")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/")
include(CTest)
enable_testing()
#add_subdirectory("submodules/pipedal_p2pd")
add_subdirectory("PiPedalCommon")
add_subdirectory("react")
add_subdirectory("vite")
add_subdirectory("src")
# Replaced with hotspot.
#add_subdirectory("NetworkManagerP2P")
# install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
# install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
# add_test(NAME Test COMMAND piddletest)
set (REACT_BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/build/react/build/)
set (VITE_BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/vite/dist/)
# install
@@ -51,7 +42,7 @@ install (
FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/10-pipedal-networkmanager.rules DESTINATION /etc/polkit-1/rules.d/
)
install (
DIRECTORY ${REACT_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react
DIRECTORY ${VITE_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react
)
install (FILES ${PROJECT_SOURCE_DIR}/build/src/notices.txt
DESTINATION /etc/pipedal/react/var/)
@@ -98,7 +89,7 @@ endif()
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Robin E. R. Davies<rerdavies@gmail.com>") # required
set(CPACK_PACKAGE_VENDOR "Robin Davies")
set(CPACK_PACKAGE_VENDOR "Robin E. R. Davies")
set(CPACK_PACKAGE_DESCRIPTION "IoT guitar effect pedal for Raspberry Pi, with phone-friendly web interface.")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi")
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2024 Robin Davies
Copyright 2021-2024 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"),
+1 -1
View File
@@ -17,7 +17,7 @@ using namespace std;
static void PrintHelp()
{
cout << "nm-pipedal-p2p - P2P Manager for NetworkManager network stack." << endl
<< "Copyright (c) 2024 Robin Davies." << endl
<< "Copyright (c) 2024 Robin E. R. Davies." << endl
<< endl
<< "Provides P2P connections on LINUX distributions that use NetworkManager." << endl
<< endl
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// 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
+1
View File
@@ -57,6 +57,7 @@ add_library(PiPedalCommon STATIC
include/dbus/org.freedesktop.NetworkManager.Device.Wireless.hpp
include/dbus/org.freedesktop.NetworkManager.DHCP4Config.hpp
Utf8Utils.cpp include/Utf8Utils.hpp
NetworkManagerInterfaces.cpp
include/NetworkManagerInterfaces.hpp
Lv2Log.cpp include/Lv2Log.hpp
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// 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
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// 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
+1 -1
View File
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
Copyright (c) 2007 Andy Lutomirski
Copyright (c) 2007 Mike Kershaw
Copyright (c) 2008 Luis R. Rodriguez
Copyright (c) 2024 Robin Davies
Copyright (c) 2024 Robin E. R. Davies
All rights reserved.
Redistribution and use in source and binary forms, with or without
+1 -1
View File
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
Copyright (c) 2007 Andy Lutomirski
Copyright (c) 2007 Mike Kershaw
Copyright (c) 2008 Luis R. Rodriguez
Copyright (c) 2024 Robin Davies
Copyright (c) 2024 Robin E. R. Davies
All rights reserved.
Redistribution and use in source and binary forms, with or without
+34
View File
@@ -306,3 +306,37 @@ SysExecOutput pipedal::sysExecForOutput(const std::string &program, const std::s
}
}
int pipedal::SudoExec(int argc, char **argv)
{
// re-execute with SUDO in order to prompt for SUDO credentials once only.
std::vector<char *> args;
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
args.push_back((char *)(pkexec.c_str()));
std::string sPath = getSelfExePath();
args.push_back(const_cast<char *>(sPath.c_str()));
for (int arg = 1; arg < argc; ++arg)
{
args.push_back(const_cast<char *>(argv[arg]));
}
args.push_back(nullptr);
char **newArgs = &args[0];
int pbPid;
int returnValue = 0;
if ((pbPid = fork()) == 0)
{
return execv(newArgs[0], newArgs);
}
else
{
waitpid(pbPid, &returnValue, 0);
int exitStatus = WEXITSTATUS(returnValue);
return exitStatus;
}
}
+340
View File
@@ -0,0 +1,340 @@
// Copyright (c) 2023 Robin E. R. Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "Utf8Utils.hpp"
#include "stdexcept"
#include <sstream>
using namespace std;
namespace pipedal
{
static constexpr uint16_t UTF16_SURROGATE_TAG_MASK = 0xFC00;
static constexpr uint16_t UTF16_SURROGATE_1_BASE = 0xD800U;
static constexpr uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U;
static constexpr uint32_t UTF8_ONE_BYTE_MASK = 0x80;
static constexpr uint32_t UTF8_ONE_BYTE_BITS = 0;
static constexpr uint32_t UTF8_TWO_BYTES_MASK = 0xE0;
static constexpr uint32_t UTF8_TWO_BYTES_BITS = 0xC0;
static constexpr uint32_t UTF8_THREE_BYTES_MASK = 0xF0;
static constexpr uint32_t UTF8_THREE_BYTES_BITS = 0xE0;
static constexpr uint32_t UTF8_FOUR_BYTES_MASK = 0xF8;
static constexpr uint32_t UTF8_FOUR_BYTES_BITS = 0xF0;
static constexpr uint32_t UTF8_CONTINUATION_MASK = 0xC0;
static constexpr uint32_t UTF8_CONTINUATION_BITS = 0x80;
static constexpr bool enforceValidUtf8Encoding = false;
namespace implementation
{
void Utf8RangeError()
{
throw std::range_error("String index out of range.");
}
}
size_t Utf8Index(size_t size, const std::string &text)
{
size_t index = 0;
for (size_t i = 0; i < size; ++i)
{
index = Utf8Increment(index, text);
if (index >= text.size())
break;
}
return index;
}
size_t Utf8Length(const std::string &text)
{
size_t index = 0;
size_t length = 0;
while (index < text.length())
{
index = Utf8Increment(index, text);
++length;
}
return length;
}
size_t Utf8Increment(size_t size, const std::string &text)
{
if (size >= text.length())
{
implementation::Utf8RangeError();
}
uint8_t uc = (uint8_t)text[size];
if (uc < 0x80U)
{
return size + 1;
}
else
{
++size;
while (size < text.size() && (((uint8_t)text[size]) & 0xC0) == 0x80)
{
++size;
}
return size;
}
}
size_t Utf8Decrement(size_t size, const std::string &text)
{
if (size == 0)
{
implementation::Utf8RangeError();
}
uint8_t c = text[size - 1];
if (c < 0x80U)
{
return size - 1;
}
else
{
while ((uint8_t)(text[size - 1] & 0xC0) == 0x80)
{
--size;
}
if (size != 0 && (uint8_t)(text[size - 1]) > 0x80)
{
--size;
}
else
{
implementation::Utf8RangeError();
}
return size;
}
}
std::string Utf8Erase(const std::string &text, size_t start, size_t end)
{
size_t uStart = Utf8Index(start, text);
size_t uEnd = Utf8Index(end, text);
std::string result;
result.reserve(uStart + text.size() - uEnd);
result.append(text.begin(), text.begin() + uStart);
result.append(text.begin() + uEnd, text.end());
return result;
}
std::string Utf8Substring(const std::string &text, size_t start, size_t end)
{
size_t uStart = Utf8Index(start, text);
size_t uEnd;
if (end == (size_t)-1)
{
uEnd = text.length();
}
else
{
uEnd = Utf8Index(end, text);
}
return text.substr(uStart, uEnd - uStart);
}
static char32_t bits(int nBits, int shift, char32_t c)
{
return (c >> shift) & ((1 << nBits) - 1);
}
std::string Utf8FromUtf32(char32_t value)
{
std::stringstream ss;
if (value < 0x7F)
{
ss << (char)value;
}
else if (value <= 0x7FF)
{
ss << (char)(0xC0 + bits(5, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else if (value <= 0xFFFFul)
{
ss << (char)(0xE0 + bits(4, 12, value));
ss << (char)(0x80 + bits(6, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else if (value <= 0x10FFFFul)
{
ss << (char)(0xF0 + bits(3, 18, value));
ss << (char)(0x80 + bits(6, 12, value));
ss << (char)(0x80 + bits(6, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else
{
throw std::runtime_error("Invalid unicode character.");
}
return ss.str();
}
std::string Utf16ToUtf8(const std::u16string_view&v)
{
std::stringstream ss;
for (auto i = v.begin(); i != v.end(); /**/)
{
char32_t value = *i++;
if ((value & UTF16_SURROGATE_TAG_MASK) == UTF16_SURROGATE_1_BASE)
{
if (i == v.end())
{
throw std::runtime_error("Invalid UTF32 character sequence.");
}
char32_t value2 = *i++;
if ((value & UTF16_SURROGATE_TAG_MASK) != UTF16_SURROGATE_2_BASE)
{
throw std::runtime_error("Invalid UTF32 character sequence.");
}
value = (value << 10) + value2;
}
if (value < 0x7F)
{
ss << (char)value;
}
else if (value <= 0x7FF)
{
ss << (char)(0xC0 + bits(5, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else if (value <= 0xFFFFul)
{
ss << (char)(0xE0 + bits(4, 12, value));
ss << (char)(0x80 + bits(6, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else if (value <= 0x10FFFFul)
{
ss << (char)(0xF0 + bits(3, 18, value));
ss << (char)(0x80 + bits(6, 12, value));
ss << (char)(0x80 + bits(6, 6, value));
ss << (char)(0x80 + bits(6, 0, value));
}
else
{
throw std::runtime_error("Invalid unicode character.");
}
}
return ss.str();
}
[[noreturn]] static void throw_encoding_error()
{
throw std::runtime_error("Invalid UTF8 character.");
}
static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end)
{
if (p == end)
throw_encoding_error();
uint8_t c = *p++;
if ((c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_BITS)
throw_encoding_error();
return c & 0x3F;
}
std::u16string Utf8ToUtf16(const std::string_view &v)
{
std::basic_stringstream<char16_t> os;
// convert to utf-32.
// convert utf-32 to normalized utf-16.
// write non-7-bit and unsafe characters as \uHHHH.
auto p = v.begin();
while (p != v.end())
{
uint32_t uc;
uint8_t c = (uint8_t)*p++;
if ((c & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_BITS)
{
uc = c;
}
else
{
uint32_t c2 = continuation_byte(p, v.end());
if ((c & UTF8_TWO_BYTES_MASK) == UTF8_TWO_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)(~UTF8_TWO_BYTES_MASK);
if (c1 <= 1 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1 << 6) | c2;
}
else
{
uint32_t c3 = continuation_byte(p, v.end());
if ((c & UTF8_THREE_BYTES_MASK) == UTF8_THREE_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)~UTF8_THREE_BYTES_MASK;
if (c1 == 0 && c2 < 0x20 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1) << 12 | (c2 << 6) | c3;
}
else
{
uint32_t c4 = continuation_byte(p, v.end());
if ((c & UTF8_FOUR_BYTES_MASK) == UTF8_FOUR_BYTES_BITS)
{
uint32_t c1 = c & (uint32_t)~UTF8_FOUR_BYTES_MASK;
if (c1 == 0 && c2 < 0x10 && enforceValidUtf8Encoding)
{
// overlong encoding.
throw_encoding_error();
}
uc = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
}
else
{
// outside legal UCS range.
throw_encoding_error();
}
}
}
}
if (uc < 0x10000ul)
{
os << (char16_t)uc;
}
else
{
// write UTF-16 surrogate pair.
uc -= 0x10000;
char16_t s1 = (char16_t)(UTF16_SURROGATE_1_BASE + ((uc >> 10) & 0x3FFu));
char16_t s2 = (char16_t)(UTF16_SURROGATE_2_BASE + (uc & 0x03FFu));
// surrogate pair.
os << s1;
os << s2;
}
}
return os.str();
}
}
+4
View File
@@ -43,5 +43,9 @@ namespace pipedal
void sysExecTerminate(ProcessId pid_, int termTimeoutMs = 1000, int killTimeoutMs = 500); // returns the process's exit status.
int sysExecWait(ProcessId pid);
// re-execute the current program with current argument, but with sudo.
int SudoExec(int argc, char **argv);
std::string getSelfExePath();
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2023 Robin E. R. Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
namespace pipedal {
size_t Utf8Index(size_t size, const std::string &text);
size_t Utf8Decrement(size_t size, const std::string &text);
size_t Utf8Increment(size_t size, const std::string &text);
size_t Utf8Length(const std::string&text);
std::string Utf8Erase(const std::string&text, size_t start, size_t end);
std::string Utf8Substring(const std::string&text, size_t start, size_t end);
inline std::string Utf8Substring(const std::string&text, size_t start) {
return Utf8Substring(text,start,(size_t)-1);
}
namespace implementation {
void Utf8RangeError();
}
std::string Utf8FromUtf32(char32_t value);
std::u16string Utf8ToUtf16(const std::string_view&s);
std::string Utf16ToUtf8(const std::u16string_view&s);
}
+26
View File
@@ -34,6 +34,7 @@
#include <limits>
#include <stdexcept>
#include <chrono>
#include <optional>
#define DECLARE_JSON_MAP(CLASSNAME) \
static pipedal::json_map::storage_type<CLASSNAME> jmap
@@ -592,6 +593,17 @@ namespace pipedal
write(obj.get());
}
}
template <typename T>
void write(const std::optional<T> &obj)
{
if (!obj) {
write_raw("null");
} else {
write(obj.get());
}
}
template <typename T>
void write(const std::weak_ptr<T> &obj)
{
@@ -883,6 +895,20 @@ namespace pipedal
}
}
template <typename T>
void read(std::optional<T> *pOptional)
{
if (peek() == 'n')
{
consumeToken("null", "Expecting '{' or 'null'.");
*pOptional = std::optional<T>();
} else {
T value;
read(&value);
*pOptional = std::move(value);
}
}
template <typename T>
void read(std::weak_ptr<T> *pUniquePtr)
{
throw std::domain_error("Can't read std::weak_ptr");
+60 -22
View File
@@ -20,67 +20,105 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#pragma once
#include <string>
#include <filesystem>
#include <vector>
#include <barrier>
#include <atomic>
#include <set>
namespace pipedal {
namespace pipedal
{
inline bool endsWith(const std::string& str, const std::string& suffix)
inline bool endsWith(const std::string &str, const std::string &suffix)
{
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
}
void SetThreadName(const std::string &name);
std::u32string ToUtf32(const std::string &s);
inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;}
inline bool isPathSeparator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator; }
std::string GetHostName();
std::vector<std::string> split(const std::string &value, char delimiter);
inline void ReadBarrier() {
inline void ReadBarrier()
{
std::atomic_thread_fence(std::memory_order::acquire);
}
inline void WriteBarrier() {
inline void WriteBarrier()
{
std::atomic_thread_fence(std::memory_order::release);
}
template <typename T>
inline bool contains(const std::vector<T>&vector,const T&value)
inline bool contains(const std::vector<T> &vector, const T &value)
{
for (auto i = vector.begin(); i != vector.end(); ++i)
{
if ((*i) == value) {
if ((*i) == value)
{
return true;
}
}
return false;
}
inline bool contains(const std::vector<std::string>&vector,const char*value)
inline bool contains(const std::vector<std::string> &vector, const char *value)
{
return contains(vector,std::string(value));
return contains(vector, std::string(value));
}
class NoCopy {
class NoCopy
{
public:
NoCopy() { }
NoCopy(const NoCopy&) = delete;
NoCopy&operator=(const NoCopy&) = delete;
NoCopy() {}
NoCopy(const NoCopy &) = delete;
NoCopy &operator=(const NoCopy &) = delete;
~NoCopy() {}
};
class NoMove {
class NoMove
{
public:
NoMove() { }
NoMove(NoMove&&) = delete;
NoMove&operator=(NoMove&&) = delete;
~NoMove() { }
NoMove() {}
NoMove(NoMove &&) = delete;
NoMove &operator=(NoMove &&) = delete;
~NoMove() {}
};
bool IsChildDirectory(const std::filesystem::path &path, const std::filesystem::path&rootPath);
template <typename T>
std::set<T> Intersect(const std::set<T> &left, const std::set<T> &right)
{
std::set<T> result;
for (const T &v : left)
{
if (right.contains(v))
{
result.insert(v);
}
}
return result;
}
template <typename T>
bool IsSubset(const std::set<T> &subset, const std::set<T> &set)
{
std::set<T> result;
for (const T &v : subset)
{
if (!set.contains(v)) return false;
}
return true;
}
// C locale to lower. Only does 'A'-'Z'.
std::string ToLower(const std::string&value);
std::filesystem::path MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path&parentPath);
bool IsSubdirectory(const std::filesystem::path &path, const std::filesystem::path &basePath);
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// 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
+80
View File
@@ -21,6 +21,7 @@
* SOFTWARE.
*/
#include "util.hpp"
#include "ss.hpp"
#include <pthread.h>
#include <thread>
@@ -136,3 +137,82 @@ 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)
{
auto iter = path.begin();
for (auto i = rootPath.begin(); i != rootPath.end(); ++i)
{
if (iter == path.end() || *iter != *i)
{
return false;
}
++iter;
}
return true;
}
std::string pipedal::ToLower(const std::string&value)
{
std::string result;
result.resize(value.length());
for (size_t i = 0; i < value.length(); ++i)
{
result[i] = (char)tolower(value[i]);
}
return result;
}
std::filesystem::path pipedal::MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path&parentPath)
{
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) {
// not a child directory of parent directory.
throw std::runtime_error("Not a child directory.");
}
++iter;
}
std::filesystem::path remander;
while (iter != path.end())
{
remander /= *iter++;
}
return remander;
}
bool pipedal::IsSubdirectory(const std::filesystem::path &path, const std::filesystem::path &basePath)
{
auto iPath = path.begin();
for (auto i = basePath.begin(); i != basePath.end(); ++i)
{
if (iPath == path.end())
{
return false;
}
if ((*i) != (*iPath))
{
return false;
}
++iPath;
}
while (iPath != path.end())
{
if (iPath->string() == "..")
{
return false;
}
++iPath;
}
return true;
}
+2 -2
View File
@@ -6,7 +6,7 @@
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.69</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.4.70</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).
@@ -15,7 +15,7 @@ Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](htt
#### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/).
#### NEW version 1.3.69 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.4.70 Beta. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
+5 -5
View File
@@ -1,24 +1,24 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: pipedal
Upstream-Contact: Robin Davies <rerdavies@gmail.com>
Upstream-Contact: Robin E. R. Davies <rerdavies@gmail.com>
Source: https://www.example.com/software/project
Files: *
Copyright: 2021, Robin Davies
Copyright: 2021, Robin E. R. Davies
License: Expat
Files: src/*
Copyright: 2021, Robin Davies
Copyright: 2021, Robin E. R. Davies
License: Expat
Files: react/src
Copyright: 2020, Robin Davies.
Copyright: 2020, Robin E. R. Davies.
License: Expat
Files: src/Lv2EventBufferWriter.hpp
Copyright: 2008-2014, David Robillard <d@drobilla.net>
2021, Robin Davies
2021, Robin E. R. Davies
License: ISC and Expat
Files: react/node_modules/@babel/*
+2 -2
View File
@@ -1,11 +1,11 @@
---
/:
license: Expat
copyright: 2021, Robin Davies.
copyright: 2021, Robin E. R. Davies.
src/react/:
license: Expat
copyright: 2021, Robin Davies.
copyright: 2021, Robin E. R. Davies.
react/node_modules/webpack/:
license: Expat
+2 -2
View File
@@ -4,7 +4,7 @@ Run the following commands to install build tools required by the PiPedal build.
# install CMake
sudo apt updatee
sudo apt install -y cmake ninja-build gcc git
sudo apt install -y cmake ninja-build build-essential g++ git
The PiPedal build process also requires version 12 or later of `node.js`. Type `node --version` to see if you have a version
of `node.js` installed already. Otherwise run the following commands as root to install the v14.x lts version of `node.js`:
@@ -16,7 +16,7 @@ with
# install NodeJS latest LTS release.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y nodejs npm
Run the following commands to install dependent libraries required by the PiPedal build.
+1 -1
View File
@@ -5,7 +5,7 @@ page_icon: img/Compiling.jpg
{% include pageIcon.html %}
PiPedal has only been tested on Raspberry Pi OS, and Ubuntu, but should run with some modifications on most Linux distributions. Pull requests to correct problems with building PiPedal on other versions of Linux are welcome. However, getting build pre-requisites installed on non-Debian-derived Linux distrubutions (distros that don't use `apt`) will be challenging.
PiPedal has only been tested on Raspberry Pi OS, and Ubuntu 24.x, but should run with some modifications on most Linux distributions. Pull requests to correct problems with building PiPedal on other versions of Linux are welcome. However, getting build pre-requisites installed on non-Debian-derived Linux distrubutions (distros that don't use `apt`) will be challenging.
To build and debug PiPedal using Visual Studio Code, you need 8GB of memory. You can build PiPedal with 2GB of memory.
+16
View File
@@ -3,6 +3,22 @@ page_icon: img/Setup.jpg
icon_width: 320px
icon_float: left
---
## Selecting a real-time kernel on Ubuntu.
This step is unneccesary on Raspberry Pi OS, which installs an RT_PREEMPT kernel by default.
By default, Ubuntu installs a PREEMT_DYNAMIC kernel, configured to run with volutary preemption. You will need to change this. You can do this by running the following command after you have installed PiPedal:
```
pipedal_kconfig
```
In order to support real-time audio processing, select Preempt=full.
An RT_PREEMPT kernel provides slightly better realtime audio than a PREEMPT_DYNAMIC/preempt=full kernel, but the PREEMPT_DYNAMIC should be perfectly adequate. If you are having problems with audio dropouts, you may want to try an RT_PREEMPT kernel instead. Ubuntu provides a pre-built RT_PREEMPT kernel; but on our test systems, you can acheive stable low-latency audio using only the PREEMPT_DYNAMIC/preempt=full kernel. The pre-build Ubuntu RT_PREEMPT
kernel is only available if yo have an Ubuntu Pro subscription. But it is relatively easy to build the Ubuntu RT_PREEMPT kernel from source. That is, however, outside the scope of this document. We recommend using the PREEMPT_DYNAMIC/preempt=full Ubuntu kernel.
## Configuring PiPedal After Installation
+12 -9
View File
@@ -2,7 +2,7 @@
PipPedal consists of the following components:
* A web application build in React, found in the react subdirectory.
* A web application built using vite/React, found in the `vite` subdirectory.
* `pipedald`
@@ -40,7 +40,7 @@ same /etc/pipedal and /var/pipedal directories as an instance of `pipedal` runn
under systemd. Note that when running under systemd, `pipedald` runs under an unprivileged
`piedal_d` service account, and relies it's group (also 'pipedal_d`) in order to access its
data files, and to communicate with the `pipedaladmin` service, which does run with root
priveleges when `pipedald` needs to perform operations that do required root privileges.
privileges when `pipedald` needs to perform operations that do required root privileges (e.g. shutdown/reboot and starting and stopping WiFi services).
For what it's worth, `pipedaldmin` is virtually undebuggable, becuase it does require root privileges to run.
If you really _must_ debug pipedaladmin, you can fire up a sudo isntance of Visual Studio Code
@@ -53,8 +53,8 @@ audio and Wi-Fi configuration changes) may fail if the pipedaladmind service is
with pipedaladmin via Unix docket that can only be opened by members of the pipedal_d group. So if you have
added your own account to the `pipedal_d` group, debug instances of `pipedald` will in fact work properly.
In production, the pipedald web server serves the PiPedal web socket, as well as static HTML from the built
react components. But while debugging, it is much more convenient to use the React debug server for
In production, the pipedald web server serves the PiPedal web socket, as well as static HTML from the built
vite/React components. But while debugging, it is much more convenient to use the Vite debug server for
React sources, and configure pipedald to serve only the websocket.
Note that a debug instance of `pipedald` cannot bind to port 80, since that requires either root privileges or
@@ -72,23 +72,26 @@ make the systemd instance of `pipedald` bind to port 8080 instead of port 80:
(which will also restart the `pipedald` service).
To start the React debug server, from a shell, `cd` to the react directory, and run `./start`. The react debug
server will detect any changes to React sources, and rebuild them automatically (no build step required).
Actual debugging is performed using the Chrome debugger (which is remarkably well integrated with React).
To start the web app debug server, from a shell, `cd` to the `./vite` directory, and run `npm run dev`. The
Vite debug server will automatically detect any changes to web app sources, and rebuild them automatically (no build step required). Note that the `pipedald` service must be running in order for the web app to function properly, either
as a the `pipedald` service, or by running `pipedald` in a debugger.
Actual debugging is performed using the Chrome debugger (which is remarkably well integrated with Vite/React).
You won't actually see changes to the version of the systemd version of the static web app until you
do a full _Release_ or _RelWithDebInfo_ build of PiPedal, followed by running `./install.sh` which pushes
the built react app in the location where the systemd version of `pipedald` serves static web content.
By default, the debug React app will attempt to contact the pipedald server on ws:*:8080 -- the address on which
the debug version of pipedald listens on. This can be reconfigured
in the file `react/src/public/var/config.json` if desired. If you connect to the the pipedald server port (port 80), pipedald intercepts requests for this file and points the react app at itself, so the file has no effect when running in production.
in the file `react/src/public/var/config.json` if desired. If you connect to the the pipedald server port (port 80), pipedald intercepts requests for `http://./var/config.json` and points the react app at itself, so the file has no effect when running in production.
The React app will display the message "Error: Failed to connect to the server", until you start the pipedald websocket server in the VSCode debugger. However, it's quite reasonable to point the react debug app at a systemd instance of the pipedald server instead, if you don't intend to debug C++ code.
react/public/var/config.json:
{
...
"socket_server_port": 8080, # (PiPedald's port number)
# (PiPedald's port number. 80 for the production service, 8080 (by default) for
# pipedal running under a debugger.
"socket_server_port": 8080,
"socket_server_address": "*",
...
}
+8 -7
View File
@@ -13,24 +13,25 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (64-bit) v1.3.69](https://github.com/rerdavies/pipedal/releases/download/)
- [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb)
- [Raspberry Pi OS bookworm (aarch64) v1.4.70 Beta](https://github.com/rerdavies/pipedal/releases/download/)
- [Ubuntu 24.x (aarch64) v1.4.70 Alpha](https://github.com/rerdavies/pipedal/releases/download/)
- [Ubuntu 24.x (amd64) v1.4.70 Alpha](https://github.com/rerdavies/pipedal/releases/download/)
Version 1.3.69 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
Install the package by running
Version 1.4.70 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (aarch64). Download the appropriate package for your platform, and install using the following proceduer:
```
sudo apt update
cd ~/Downloads
sudo apt-get install pipedal_1.3.69_arm64.deb
sudo apt-get install pipedal_1.4.70_arm64.deb
```
Adjust accordingly if you have downloaded v1.1.31.
Adjust accordingly depending which package you downloaded.
On Raspberry Pi OS, if you have a graphical desktop installed, you can also install the package by double-clicking on the downloaded package in the File Manager.
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md).
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md). If
you are using Ubuntu, you will need to reconfigure Ubuntu to use a real-time kernel.
--------
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2024 Robin Davies
Copyright 2021-2024 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"),
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2023 Robin Davies
Copyright 2021-2023 Robin E. R. Davies
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+13
View File
@@ -1,5 +1,18 @@
# Release Notes
## PiPedal 1.4.70 Beta
- Ubuntu 24.x compatibility (arm64 and amd64)
- Ubuntu CPU Freq/governor/thermal status display.
- Support for WiFi on on Ubuntu
- TooB Plugin compatibility issues on x64/amd64 (handling of NaNs)
- Restart audio automatically after ALSA stalls.
- Improvements to File Upload UI for plugins that have complex file types (e.g. Ratatouille)
- Support for LV2 Trigger controls, and MIDI bindings to LV2 trigger controls (common in looper and recording/playback plugsin).
- MIDI bindings for LV2 trigger controls.
- Port Web app from CRA to Vite framework to avoid deprecated dependencies.
- ToobAmp 1.1.57: Correctly set file path of the default file property for Toob Convolution Reverb.
## PiPedal 1.3.69
Bug fixes:
+10
View File
@@ -21,6 +21,10 @@
display: block;
height: 24px;
}
img-icon {
width: 24px;
height:
}
</style>
<div class="footer-offset">
@@ -41,6 +45,12 @@
<span style="margin-right: 60px" >&nbsp;</span>
<a rel="me" href="https://rerdavies.github.io" target="_blank" title="rerdavies.github.io"><img
class="svg-icon" src="img/rerdavies.svg" />
</img>rerdavies.github.io</a>
<span style="margin-right: 60px" >&nbsp;</span>
<a rel="me" href="https://github.com/rerdavies" target="_blank" title="rerdavies"><svg
class="svg-icon grey"><use xlink:href="{{ '/assets/minima-social-icons.svg#github' | relative_url }}"></use>
+4 -3
View File
@@ -3,11 +3,12 @@
{
let galleryPath = "gallery/";
let images = [
"thunder.png",
"rig.jpg",
"dark-sshot1.png",
"jazz.png",
"midi-bindings.png",
"hotspot.png"
"hotspot.png",
"rig.jpg",
"thunder.png"
];
let captions = [
"PiPedal Guitar Effects Pedal running on a Raspberry Pi.",
+2 -2
View File
@@ -4,7 +4,7 @@
Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.69/pipedal_1.3.69_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.69</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.4.70/pipedal_1.4.70_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.4.70</a>
Install the package by running
@@ -12,7 +12,7 @@ Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.3.69_arm64.deb
sudo apt-get install ./pipedal_1.4.70_arm64.deb
```
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: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

+6 -5
View File
@@ -1,16 +1,17 @@
<img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.3.69</i></a>
<a href="Installing.html"><i>v1.4.70</i></a>
&nbsp;
To download PiPedal, click [here](download.md).
To view PiPedal documentation, click [here](Documentation.md).
#### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We urgently need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/).
#### Important notice: [PatchStorage.com](https:///patchstorage.com) has added support for PiPedal. We urgently need your support! I you could please upload patches [here](https://patchstorage.com/platform/pipedal/) I would
be enormously grateful. Note that media and model files are included in patch files, so you can share patches that
include model files or audio samples. If you make use of plugins that are not included in the PiPedal distribution, please include a link to the plugin in the patch description.
#### NEW version 1.3.69 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.4.70 Beta, plus alpha releases of
PiPedal for Ubuntu on aarch64 and amd64. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
+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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
+1 -1
View File
@@ -49,7 +49,7 @@ toob:frequencyResponseVector
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
Convolution reverb is a notoriously compute-intensive effect. If you are having performance issues, use the Max T control to constrain the length of the impulse file to
@@ -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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
+1 -1
View File
@@ -65,7 +65,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 53 ;
lv2:microVersion 57 ;
mod:brand "TooB";
mod:label "TooB Input";
+1 -1
View File
@@ -67,7 +67,7 @@ pstage:stage3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
mod:brand "TooB";
mod:label "Power Stage";
+1 -1
View File
@@ -58,7 +58,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
rdfs:comment "TooB spectrum analyzer" ;
mod:brand "TooB";
+1 -1
View File
@@ -55,7 +55,7 @@ tonestack:eqGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
Binary file not shown.
Binary file not shown.
+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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
Emulation of a Boss CE-2 Chorus.
+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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
A straightforward no-frills digital delay.
+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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
@@ -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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
Digital emulation of a Boss BF-2 Flanger.
+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 53 ;
lv2:microVersion 57 ;
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.
+2 -2
View File
@@ -65,7 +65,7 @@ toobml:sagGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
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.
@@ -313,7 +313,7 @@ and uses model files from the GuitarML Neural Pi project (https://github.com/Gui
lv2:default 3.0 ;
lv2:minimum 0.0 ;
lv2:maximum 10.0 ;
rdfs:comment "Model gain parameter (used by BluesJR model only)"
rdfs:comment "Model gain parameter (if supported by model) ()"
],
[
a lv2:InputPort ,
@@ -61,7 +61,7 @@ toobNam:eqGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 53 ;
lv2:microVersion 57 ;
rdfs:comment """
A port of Steven Atkinson's Neural Amp Modeler to LV2.
@@ -326,7 +326,7 @@ toobNam:ui
pipedal_ui:fileProperties
[
a pipedal_ui:fileProperty;
rdfs:label "Model (*.nam)" ;
rdfs:label "Model" ;
pipedal_ui:directory "NeuralAmpModels";
lv2:index 2 ;
pipedal_ui:patchProperty toobNam:modelFile ;
+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 53 ;
lv2:microVersion 57 ;
rdfs:comment """
TooB Tuner is a chromatic guitar tuner.
""" ;
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/bash
# Install react dependencies
cd react
cd vite
npm install
cd ..
-24
View File
@@ -1,24 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
/package-lock.json
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-208
View File
@@ -1,208 +0,0 @@
find_program(NPM_COMMAND npm)
if (CMAKE_BUILD_TYPE MATCHES Debug)
set(BUILD_REACT "echo")
set(BUILD_REACT_ARGS Skipping react debug build)
else()
set(BUILD_REACT npm)
set(BUILD_REACT_ARGS run build)
endif()
set (BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/build/react/build)
add_custom_command(
OUTPUT ${BUILD_DIRECTORY}/index.html
COMMAND ${BUILD_REACT}
ARGS ${BUILD_REACT_ARGS}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
DEPENDS
src/PiPedalError.tsx
src/OldDeleteIcon.tsx
src/MaterialColors.tsx
src/Pedalboard.tsx
src/SystemMidiBindingView.tsx
src/GovernorSettings.tsx
src/ObservableProperty.tsx
src/SnapshotEditor.tsx
src/SelectMidiChannelsDialog.tsx
src/AlsaMidiDeviceInfo.tsx
src/WifiDirectConfigSettings.tsx
src/WifiConfigSettings.tsx
src/SnapshotButton.tsx
src/WindowScale.tsx
src/JsonAtom.tsx
src/PiPedalSocket.tsx
src/TemporaryDrawer.tsx
src/DarkMode.tsx
src/MidiBindingsDialog.tsx
src/MidiBinding.tsx
src/SnapshotDialog.tsx
src/SettingsDialog.tsx
src/PedalboardView.tsx
src/reportWebVitals.ts
src/OptionsDialog.tsx
src/SelectChannelsDialog.tsx
src/PluginPresetSelector.tsx
src/VuMeter.tsx
src/ToobFrequencyResponseView.tsx
src/SystemMidiBindingsDialog.tsx
src/FullScreenIME.tsx
src/App.tsx
src/ToobInputStageView.tsx
src/XxxSnippet.tsx
src/JackServerSettings.tsx
src/ToobWaveShapeView.tsx
src/ToobPowerStage2View.tsx
src/PluginPreset.tsx
src/logo.svg
src/Banks.tsx
src/NoChangePassword.tsx
src/SearchFilter.tsx
src/FilePropertyDirectorySelectDialog.tsx
src/GxTunerView.tsx
src/UpdateDialog.tsx
src/svg/file_download_black_24dp.svg
src/svg/fx_filter.svg
src/svg/fx_pitch.svg
src/svg/fx_distortion.svg
src/svg/fx_split_b.svg
src/svg/fx_chorus.svg
src/svg/fx_limiter.svg
src/svg/fx_phaser.svg
src/svg/snapshot_0.svg
src/svg/snapshot_3.svg
src/svg/file_upload_black_24dp.svg
src/svg/ic_bank.svg
src/svg/fx_function.svg
src/svg/fx_dial.svg
src/svg/ic_settings.svg
src/svg/fx_split_a.svg
src/svg/fx_empty.svg
src/svg/fx_amplifier.svg
src/svg/fx_spectral.svg
src/svg/fx_eq.svg
src/svg/ic_edit_banks.svg
src/svg/ic_help_outline.svg
src/svg/ic_save_bank_as.svg
src/svg/ic_pluginpreset2.svg
src/svg/fx_flanger2.svg
src/svg/snapshot_6.svg
src/svg/fx_lr.svg
src/svg/fx_generator.svg
src/svg/snapshot_1.svg
src/svg/fx_reverb.svg
src/svg/fx_plugin.svg
src/svg/snapshot_4.svg
src/svg/drive_file_rename_outline_black_24dp.svg
src/svg/snapshot_2.svg
src/svg/fx_instrument.svg
src/svg/fx_modulator.svg
src/svg/fx_error.svg
src/svg/fx_simulator.svg
src/svg/fx_filter_hp.svg
src/svg/fx_compressor.svg
src/svg/fx_gate.svg
src/svg/fx_terminal.svg
src/svg/ic_midi.svg
src/svg/fx_oscillator.svg
src/svg/fx_analyzer.svg
src/svg/fx_flanger.svg
src/svg/old_delete_outline_24dp.svg
src/svg/ic_presets.svg
src/svg/fx_delay.svg
src/svg/fx_parametric_eq.svg
src/svg/fx_spatial.svg
src/svg/fx_converter.svg
src/svg/fx_constant.svg
src/svg/snapshot_5.svg
src/svg/ic_pluginpreset.svg
src/svg/fx_mixer.svg
src/svg/fx_utility.svg
src/Updater.tsx
src/UploadPresetDialog.tsx
src/RadioSelectDialog.tsx
src/setupTests.ts
src/ToobCabSimView.tsx
src/RenameDialog.tsx
src/SplitControlView.tsx
src/SelectHoverBackground.tsx
src/IControlViewFactory.tsx
src/OkCancelDialog.tsx
src/BankDialog.tsx
src/PluginPresetsDialog.tsx
src/App.test.tsx
src/ControlViewFactory.tsx
src/DraggableGrid.tsx
src/ZoomedDial.tsx
src/index.css
src/Pedal.tsx
src/Draggable.tsx
src/PluginIcon.tsx
src/react-app-env.d.ts
src/TextFieldEx.tsx
src/FilePropertyDialog.tsx
src/FilePropertyDirectoryTree.tsx
src/StringBuilder.tsx
src/MainPage.tsx
src/PluginInfoDialog.tsx
src/AppThemed.tsx
src/Lv2Plugin.tsx
src/ToobSpectrumResponseView.tsx
src/PerformanceView.tsx
src/JackServerSettingsDialog.tsx
src/Utility.tsx
src/JackStatusView.tsx
src/ToobSpectrumAnalyzerView.tsx
src/SvgPathBuilder.tsx
src/PresetSelector.tsx
src/PresetDialog.tsx
src/WifiChannel.tsx
src/AboutDialog.tsx
src/GxTunerControl.tsx
src/VirtualKeyboardHandler.tsx
src/PluginControl.tsx
src/LoadPluginDialog.tsx
src/ZoomedUiControl.tsx
src/AlsaDeviceInfo.tsx
src/ToobToneStackView.tsx
src/UploadFileDialog.tsx
src/PluginOutputControl.tsx
src/SelectThemeDialog.tsx
src/Rect.tsx
src/AppThemed.css
src/ColorDropdownButton.tsx
src/AndroidHost.tsx
src/SnapshotPanel.tsx
src/ToobMLView.tsx
src/WifiConfigDialog.tsx
src/Rectangle.tsx
src/SnapshotColors.tsx
src/PiPedalModel.tsx
src/ObservableEvent.tsx
src/JackHostStatus.tsx
src/index.tsx
src/FilePropertyControl.tsx
src/WifiDirectConfigDialog.tsx
src/MidiBindingView.tsx
src/PluginControlView.tsx
src/SplitUiControls.tsx
src/PluginClass.tsx
src/DialogEx.tsx
src/Jack.tsx
src/ResizeResponsiveComponent.tsx
src/NumericInput.tsx
src/SnapshotPropertiesDialog.tsx
src/SearchControl.tsx
src/Units.tsx
)
add_custom_target (
ReactBuild ALL
DEPENDS ${BUILD_DIRECTORY}/index.html
)
-46
View File
@@ -1,46 +0,0 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
-64
View File
@@ -1,64 +0,0 @@
{
"name": "pipedal",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.4.1",
"@mui/material": "^5.4.1",
"@mui/styles": "^5.4.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@types/jest": "^26.0.23",
"@types/node": "^12.20.15",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.8",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-remark": "^2.1.0",
"react-scripts": "^5.0.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"typescript": "^4.3.4",
"web-vitals": "^1.1.2",
"@babel/plugin-proposal-private-property-in-object": "^7.2.11"
},
"scripts": {
"start": "react-scripts start",
"build": "BUILD_PATH='../build/react/build' react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"eqeqeq": 2,
"no-unused-expressions": 2,
"@typescript-eslint/no-unused-vars": 2
}
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

-28
View File
@@ -1,28 +0,0 @@
found 8 vulnerabilities (4 moderate, 4 high)
run `npm audit fix` to fix them, or `npm audit` for details
Created git commit.
Success! Created my-app at /home/patch/src/piddle/react/my-app
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you canât go back!
We suggest that you begin by typing:
cd my-app
npm start
Happy hacking!
-296
View File
@@ -1,296 +0,0 @@
import React, { SyntheticEvent, Component } from 'react';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Divider from '@mui/material/Divider';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import JackHostStatus from './JackHostStatus';
import { PiPedalError } from './PiPedalError';
import DialogEx from './DialogEx';
import { Theme, createStyles } from '@mui/material/styles';
import { withStyles, WithStyles } from '@mui/styles';
import Slide, { SlideProps } from '@mui/material/Slide';
interface AboutDialogProps extends WithStyles<typeof styles> {
open: boolean;
onClose: () => void;
};
interface AboutDialogState {
jackStatus?: JackHostStatus;
openSourceNotices: string;
};
const styles = (theme: Theme) => createStyles({
dialogAppBar: {
position: 'relative',
top: 0, left: 0
},
dialogTitle: {
marginLeft: theme.spacing(2),
flex: 1,
},
sectionHead: {
marginLeft: 24,
marginRight: 24,
marginTop: 16,
paddingBottom: 12
},
textBlock: {
marginLeft: 24,
marginRight: 24,
marginTop: 16,
paddingBottom: 0,
opacity: 0.95
},
textBlockIndented: {
marginLeft: 40,
marginRight: 24,
marginTop: 16,
paddingBottom: 0,
opacity: 0.95
},
setting: {
minHeight: 64,
width: "100%",
textAlign: "left",
paddingLeft: 22,
paddingRight: 22
},
primaryItem: {
},
secondaryItem: {
},
can_select: {
userSelect: "text",
cursor: "text"
},
no_select: {
userSelect: "none",
cursor: "text"
}
});
const Transition = React.forwardRef(function Transition(
props: SlideProps, ref: React.Ref<unknown>
) {
return (<Slide direction="up" ref={ref} {...props} />);
});
const AboutDialog = withStyles(styles, { withTheme: true })(
class extends Component<AboutDialogProps, AboutDialogState> {
model: PiPedalModel;
refNotices: React.RefObject<HTMLDivElement>;
constructor(props: AboutDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.refNotices = React.createRef();
this.handleDialogClose = this.handleDialogClose.bind(this);
this.state = {
jackStatus: undefined,
openSourceNotices: ""
};
}
mounted: boolean = false;
subscribed: boolean = false;
tick() {
if (this.model.state.get() === State.Ready) {
this.model.getJackStatus()
.then(jackStatus => {
this.setState({ jackStatus: jackStatus });
})
.catch(error => { /* ignore*/ });
}
}
timerHandle?: NodeJS.Timeout;
updateNotifications() {
let subscribed = this.mounted && this.props.open;
if (subscribed !== this.subscribed) {
if (subscribed) {
this.timerHandle = setInterval(() => this.tick(), 1000);
} else {
if (this.timerHandle) {
clearInterval(this.timerHandle);
this.timerHandle = undefined;
}
}
this.subscribed = subscribed;
}
}
startFossRequest() {
if (this.state.openSourceNotices === "") {
fetch("var/notices.txt")
.then((request) => {
if (!request.ok) {
throw new PiPedalError("notices request failed.");
}
return request.text();
})
.then((text) => {
if (this.mounted) {
this.setState({ openSourceNotices: text });
}
})
.catch((err) => {
// ok in debug builds. File doesn't get placed until install time.
console.log("Failed to fetch open-source notices. " + err.toString());
});
}
}
componentDidMount() {
super.componentDidMount?.();
this.mounted = true;
this.updateNotifications();
this.startFossRequest();
}
componentWillUnmount() {
super.componentWillUnmount?.();
this.mounted = false;
this.updateNotifications();
}
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps, prevState, snapshot);
this.updateNotifications();
}
handleDialogClose(e: SyntheticEvent) {
this.props.onClose();
}
render() {
let classes = this.props.classes;
let addressKey = 0;
let serverVersion = this.model.serverVersion?.serverVersion ?? "";
let nPos = serverVersion.indexOf(' ');
if (nPos !== -1) {
serverVersion = serverVersion.substring(nPos + 1);
}
return (
<DialogEx tag="about" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
onEnterKey={() => { this.props.onClose() }}
style={{ userSelect: "none" }}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} >
<Toolbar>
<IconButton edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back"
>
<ArrowBackIcon />
</IconButton>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
About
</Typography>
</Toolbar>
</AppBar>
</div>
<div style={{
flex: "1 1 auto", position: "relative", overflow: "hidden",
overflowX: "hidden", overflowY: "auto", userSelect: "text"
}}
>
<div id="debug_info" className={classes.can_select} style={{ margin: 24 }}>
<div style={{ display: "flex", flexFlow: "row nowrap" }}>
<Typography noWrap display="block" variant="h6" color="textPrimary" style={{flexGrow: 1, flexShrink: 1}}>
PiPedal <span style={{ fontSize: "0.7em" }}>
{serverVersion
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
</div>
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022-2024 Robin Davies.
</Typography>
{this.model.isAndroidHosted() && (
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 0 }} >
{this.model.getAndroidHostVersion()}
</Typography>
)}
{this.model.isAndroidHosted() && (
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022-2024 Robin Davies.
</Typography>
)}
<Divider />
<Typography noWrap display="block" variant="caption" >
ADDRESSES
</Typography>
<div style={{ marginBottom: 16 }}>
{
this.model.serverVersion?.webAddresses.map((address) =>
(
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{address}
</Typography>
))
}
</div>
<Divider />
<Typography noWrap display="block" variant="caption" >
SERVER OS
</Typography>
<div style={{ marginBottom: 16 }}>
<Typography display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{this.model.serverVersion?.osVersion ?? ""}
</Typography>
</div>
</div><div>
<Divider />
<Typography display="block" variant="caption" >
LEGAL NOTICES
</Typography>
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em", maxWidth: 400 }}>
</div>
</div>
</div>
</div>
</DialogEx >
);
}
}
);
export default AboutDialog;
-9
View File
@@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
File diff suppressed because it is too large Load Diff
-218
View File
@@ -1,218 +0,0 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import Dialog, {DialogProps} from '@mui/material/Dialog';
interface DialogExProps extends DialogProps {
tag: string;
fullwidth?: boolean;
onEnterKey: () => void;
}
interface DialogExState {
}
export interface IDialogStackable {
getTag: ()=> string;
isOpen: ()=> boolean;
onDialogStackClose(): void;
}
class DialogStackEntry {
constructor(dialog: IDialogStackable)
{
this.tag = dialog.getTag();
this.dialog = dialog;
}
tag: string;
dialog: IDialogStackable;
};
let dialogStack: DialogStackEntry[] = [];
export function popDialogStack(): void {
if (dialogStack.length !== 0)
{
dialogStack.splice(dialogStack.length-1,1);
}
}
export function pushDialogStack(dialog: IDialogStackable): void {
dialogStack.push(new DialogStackEntry(dialog));
}
export interface DialogStackState{
tag: String;
previousState: DialogStackState | null;
};
// Close all dialogs higher in the dialog stack than "tag".
export function removeDialogStackEntriesAbove(tag: string)
{
for (let i = dialogStack.length-1; i >= 0; --i)
{
let entry = dialogStack[i];
if (entry.dialog.getTag() === tag)
{
return;
}
popDialogStack();
if (entry.dialog.isOpen())
{
entry.dialog.onDialogStackClose();
}
}
}
class DialogEx extends React.Component<DialogExProps,DialogExState> implements IDialogStackable {
constructor(props: DialogExProps)
{
super(props);
this.state = {
};
this.handlePopState = this.handlePopState.bind(this);
}
mounted: boolean = false;
hasHooks: boolean = false;
stateWasPopped: boolean = false;
getTag() {
return this.props.tag;
}
isOpen(): boolean {
return this.props.open;
}
onDialogStackClose() {
this.props.onClose?.({},"backdropClick");
}
handlePopState(ev: PopStateEvent): any
{
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
if (evTag && evTag.tag === this.props.tag) {
removeDialogStackEntriesAbove(this.props.tag);
if (!this.stateWasPopped)
{
this.stateWasPopped = true;
popDialogStack();
if (this.props.open) {
this.props.onClose?.(ev,"backdropClick");
}
}
window.history.replaceState(evTag.previousState,"",window.location.href);
window.removeEventListener("popstate",this.handlePopState);
if (window.location.hash === "#menu") // The menu popstate is next?
{
window.history.back(); // then clear off the menu popstate too.
}
ev.stopPropagation();
}
}
updateHooks() : void {
let wantHooks = this.mounted && this.props.open;
if (wantHooks !== this.hasHooks)
{
this.hasHooks = wantHooks;
if (this.hasHooks)
{
this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState);
pushDialogStack(this);
let dialogStackState: DialogStackState = {tag: this.props.tag,previousState: window.history.state as DialogStackState | null};
window.history.replaceState(dialogStackState,"",window.location.href);
window.history.pushState(
null,
"",
"#" + this.props.tag
);
} else {
if (!this.stateWasPopped)
{
window.history.back();
}
}
}
}
componentDidMount()
{
super.componentDidMount?.();
this.mounted = true;
this.updateHooks();
}
componentWillUnmount()
{
super.componentWillUnmount?.();
this.mounted = false;
this.updateHooks();
}
componentDidUpdate()
{
this.updateHooks();
}
myOnClose(event:{}, reason: "backdropClick" | "escapeKeyDown")
{
if (this.props.onClose) this.props.onClose(event,reason);
}
onEnterKey() {
if (this.props.onEnterKey)
{
this.props.onEnterKey();
}
}
onKeyDown(evt: React.KeyboardEvent<HTMLDivElement>)
{
if (evt.key === 'Enter')
{
this.onEnterKey();
}
evt.stopPropagation();
}
render() {
let { tag,onClose,...extra} = this.props;
return (
<Dialog fullWidth={this.props.fullWidth??false}
maxWidth={this.props.fullWidth ? false: undefined} {...extra}
onClose={(event,reason)=>{ this.myOnClose(event,reason);}}
onKeyDown={(evt)=>{ this.onKeyDown(evt); }}
>
{this.props.children}
</Dialog>
);
}
};
export default DialogEx;
-244
View File
@@ -1,244 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { Component, SyntheticEvent } from 'react';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import { UiFileProperty } from './Lv2Plugin';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, ListenHandle,State} from './PiPedalModel';
import ButtonBase from '@mui/material/ButtonBase'
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import {PedalboardItem} from './Pedalboard';
import {isDarkMode} from './DarkMode';
export const StandardItemSize = { width: 80, height: 140 }
const styles = (theme: Theme) => createStyles({
frame: {
position: "relative",
margin: "12px"
},
switchTrack: {
height: '100%',
width: '100%',
borderRadius: 14 / 2,
zIndex: -1,
transition: theme.transitions.create(['opacity', 'background-color'], {
duration: theme.transitions.duration.shortest
}),
backgroundColor: theme.palette.secondary.main,
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
},
displayValue: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 4,
textAlign: "center",
background: "white",
color: "#666",
// zIndex: -1,
}
});
export interface FilePropertyControlProps extends WithStyles<typeof styles> {
fileProperty: UiFileProperty;
pedalboardItem: PedalboardItem;
onFileClick: (fileProperty: UiFileProperty, value: string) => void;
theme: Theme;
}
type FilePropertyControlState = {
error: boolean;
hasValue: boolean;
value: string;
};
const FilePropertyControl =
withStyles(styles, { withTheme: true })(
class extends Component<FilePropertyControlProps, FilePropertyControlState>
{
model: PiPedalModel;
constructor(props: FilePropertyControlProps) {
super(props);
this.state = {
error: false,
hasValue: false,
value: "",
};
this.model = PiPedalModelFactory.getInstance();
this.onStateChanged = this.onStateChanged.bind(this);
}
onStateChanged(state: State) {
if (this.mounted)
{
if (state === State.Ready) {
this.subscribeToPatchProperty();
}
}
}
private propertyGetHandle?: ListenHandle;
monitorPropertyHandle?: ListenHandle;
subscribeToPatchProperty() {
// this.propertyGetHandle = this.model.listenForAtomOutput(this.props.instanceId,(instanceId,atomOutput)=>{
// // PARSE THE OBJECT!
// // this.setState({value: atomOutput?.value as string});
// });
this.unsubscribeToPatchProperty();
this.monitorPropertyHandle = this.model.monitorPatchProperty(this.props.pedalboardItem.instanceId,
this.props.fileProperty.patchProperty,
(instanceId,property,propertyValue) => {
if (propertyValue.otype_ === "Path")
{
this.setState({value: propertyValue.value as string,hasValue: true});
}
}
);
}
unsubscribeToPatchProperty() {
if (this.monitorPropertyHandle !== undefined)
{
this.model.cancelMonitorPatchProperty(this.monitorPropertyHandle);
this.monitorPropertyHandle = undefined;
}
}
private mounted: boolean = false;
componentDidMount() {
this.model.state.addOnChangedHandler(this.onStateChanged);
this.mounted = true;
this.subscribeToPatchProperty();
}
componentWillUnmount() {
this.unsubscribeToPatchProperty();
this.mounted = false;
this.model.state.removeOnChangedHandler(this.onStateChanged);
}
componentDidUpdate(prevProps: Readonly<FilePropertyControlProps>, prevState: Readonly<FilePropertyControlState>, snapshot?: any): void {
if (prevProps.fileProperty.patchProperty !== this.props.fileProperty.patchProperty
|| prevProps.pedalboardItem.instanceId !== this.props.pedalboardItem.instanceId
|| prevProps.pedalboardItem.stateUpdateCount !== this.props.pedalboardItem.stateUpdateCount
) {
this.unsubscribeToPatchProperty();
this.subscribeToPatchProperty();
}
}
inputChanged: boolean = false;
onDrag(e: SyntheticEvent) {
e.preventDefault();
}
onFileClick() {
this.props.onFileClick(this.props.fileProperty, this.state.value);
}
private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/');
if (slashPos < 0) {
slashPos = 0;
} else {
++slashPos;
}
let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos) {
extPos = path.length;
}
return path.substring(slashPos, extPos);
}
render() {
//let classes = this.props.classes;
let fileProperty = this.props.fileProperty;
let value = "\u00A0";
if (this.state.hasValue)
{
if (this.state.value.length === 0)
{
value = "<none>";
} else {
value = this.fileNameOnly(this.state.value);
}
}
let item_width = 264;
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8 }}>
{/* TITLE SECTION */}
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
<Typography variant="caption" display="block" noWrap style={{
width: "100%",
textAlign: "start"
}}> {fileProperty.label}</Typography>
</div>
{/* CONTROL SECTION */}
<div style={{ flex: "0 0 auto", width: "100%" }}>
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden", marginTop: 8 }} onClick={() => { this.onFileClick() }} >
<div style={{ width: "100%", background:
isDarkMode()? "rgba(255,255,255,0.03)": "rgba(0,0,0,0.07)",
borderRadius: "4px 4px 0px 0px" }}>
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4, paddingBottom: 4, paddingLeft: 4 }}
variant='caption'
>{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center", opacity: 0.5, marginRight: 4 }} />
</div>
<div style={{ height: "1px", width: "100%", background: this.props.theme.palette.text.secondary }}>&nbsp;</div>
</div>
</ButtonBase>
</div>
{/* LABEL/EDIT SECTION*/}
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
</div>
</div >
);
}
}
);
export default FilePropertyControl;
File diff suppressed because it is too large Load Diff
-362
View File
@@ -1,362 +0,0 @@
// Copyright (c) 2024 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.
import { WithStyles } from '@mui/styles';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import CloseIcon from '@mui/icons-material/Close';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import CssBaseline from '@mui/material/CssBaseline';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import IconButton from '@mui/material/IconButton';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo } from './PiPedalModel';
import ZoomedUiControl from './ZoomedUiControl'
import MainPage from './MainPage';
import JackStatusView from './JackStatusView';
import { Theme } from '@mui/material/styles';
import { isDarkMode } from './DarkMode';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import { Snapshot } from './Pedalboard';
import ColorDropdownButton, { DropdownAlignment } from './ColorDropdownButton';
import { getBackgroundColor } from './MaterialColors';
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
const appStyles = (theme: Theme) => createStyles({
"&": { // :root
colorScheme: (isDarkMode() ? "dark" : "light")
},
mainFrame: {
overflow: "hidden",
display: "flex",
flexFlow: "column",
marginTop: 8,
flex: "1 1 100%"
},
heroContent: {
backgroundColor: theme.mainBackground,
position: "relative",
height: "100%",
width: "100%"
},
shadowCatcher: {
backgroundColor: theme.mainBackground
},
select: { // fu fu fu.Overrides for white selector on dark background.
'&:before': {
borderColor: selectColor,
},
'&:after': {
borderColor: selectColor,
},
'&:hover:not(.Mui-disabled):before': {
borderColor: selectColor,
}
},
select_icon: {
fill: selectColor,
}
});
function supportsFullScreen(): boolean {
let doc: any = window.document;
let docEl: any = doc.documentElement;
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
return (!!requestFullScreen);
}
function setFullScreen(value: boolean) {
let doc: any = window.document;
let docEl: any = doc.documentElement;
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
requestFullScreen.call(docEl);
}
else {
cancelFullScreen.call(doc);
}
}
type SnapshotEditorState = {
zoomedControlInfo: ZoomedControlInfo | undefined;
zoomedControlOpen: boolean;
canFullScreen: boolean;
isFullScreen: boolean;
isDebug: boolean;
collapseLabel: boolean;
showStatusMonitor: boolean;
name: string,
color: string
};
interface SnapshotEditorProps extends WithStyles<typeof appStyles> {
onClose: () => void;
onOk: (snapshotIndex: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) => void;
snapshotIndex: number;
}
const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveComponent<SnapshotEditorProps, SnapshotEditorState> {
// Before the component mounts, we initialise our state
model_: PiPedalModel;
getCollapseLabel() {
return this.windowSize.width < 500;
}
constructor(props: SnapshotEditorProps) {
super(props);
this.model_ = PiPedalModelFactory.getInstance();
this.model_.zoomedUiControl.addOnChangedHandler(
() => {
this.setState({
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
zoomedControlInfo: this.model_.zoomedUiControl.get()
});
}
);
let snapshot = this.model_.pedalboard.get().snapshots[this.props.snapshotIndex] ?? new Snapshot();
this.state = {
zoomedControlOpen: false,
zoomedControlInfo: this.model_.zoomedUiControl.get(),
canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted(),
isFullScreen: !!document.fullscreenElement,
showStatusMonitor: this.model_.showStatusMonitor.get(),
isDebug: true,
name: snapshot.name,
color: snapshot.color,
collapseLabel: this.getCollapseLabel()
};
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
}
showStatusMonitorHandler() {
this.setState({
showStatusMonitor: this.model_.showStatusMonitor.get()
});
}
toggleFullScreen(): void {
setFullScreen(this.state.isFullScreen);
this.setState({ isFullScreen: !this.state.isFullScreen });
}
componentDidMount() {
super.componentDidMount();
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
}
updateOverscroll(): void {
if (this.model_.serverVersion) {
// no pull-down refresh on android devices once we're ready (unless we're debug)
let preventOverscroll =
this.model_.state.get() === State.Ready
&& !this.model_.debug;
let overscrollBehavior = preventOverscroll ? "none" : "auto";
document.body.style.overscrollBehavior = overscrollBehavior;
}
}
componentDidUpdate() {
}
componentWillUnmount() {
super.componentWillUnmount();
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
}
onWindowSizeChanged(width: number, height: number): void {
super.onWindowSizeChanged(width, height);
this.setState({
collapseLabel: this.getCollapseLabel()
});
}
handleOk() {
let currentPedalboard = this.model_.pedalboard.get();
let selectedSnapshot = this.props.snapshotIndex;
let currentSnapshot: Snapshot | null = currentPedalboard.snapshots[selectedSnapshot];
if (!currentSnapshot)
{
this.props.onClose();
return;
}
let changed = this.state.name !== currentSnapshot.name
|| this.state.color !== currentSnapshot.color
|| currentSnapshot.isModified;
if (!changed)
{
this.props.onClose();
return;
}
let newSnapshots = Snapshot.cloneSnapshots(currentPedalboard.snapshots);
let newSnapshot = this.model_.pedalboard.get().makeSnapshot();
newSnapshot.name = this.state.name;
newSnapshot.color = this.state.color;
newSnapshots[selectedSnapshot] = newSnapshot;
this.model_.setSnapshots(newSnapshots, selectedSnapshot);
this.props.onOk(selectedSnapshot, this.state.name, this.state.color, newSnapshots);
}
render() {
const { classes } = this.props;
return (
<div
className={classes.shadowCatcher}
style={{
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
minHeight: 345, minWidth: 390,
userSelect: "none",
display: "flex", flexDirection: "column", flexWrap: "nowrap",
overscrollBehavior: this.state.isDebug ? "auto" : "none"
}}
onContextMenu={(e) => {
if (!this.model_.debug) {
e.preventDefault(); e.stopPropagation();
}
}}
>
<CssBaseline />
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? getBackgroundColor("purple") : "#200040" }} >
<Toolbar variant="dense" >
<IconButton
edge="start"
aria-label="menu"
color="inherit"
onClick={() => { this.handleOk(); }}
size="large">
<ArrowBackIcon />
</IconButton>
{!this.state.collapseLabel && (
<Typography style={{ flex: "0 1 auto", opacity: 0.66 }}
variant="body1" noWrap
>
{'Snapshot ' + (this.props.snapshotIndex + 1)}
</Typography>
)}
<div style={{ flex: "1 1 auto", display: "flex", flexFlow: "column nowrap", marginRight: 8 }}>
<TextField
sx={{
input: { color: 'white' },
'& .MuiInput-underline:before': { borderBottomColor: '#FFFFFFC0' },
'& .MuiInput-underline:hover:before': { borderBottomColor: '#FFFFFFE0' },
'& .MuiInput-underline:after': { borderBottomColor: '#FFFFFF' }
}}
variant="standard"
style={{ flex: "1 1 auto", marginLeft: 16, maxWidth: 500, color: "white" }}
value={this.state.name}
onChange={(ev) => {
this.setState({ name: ev.target.value });
}}
/>
</div>
<ColorDropdownButton
currentColor={this.state.color}
dropdownAlignment={DropdownAlignment.SW}
onColorChange={(newColor) => {
this.setState({ color: newColor });
}} />
{this.state.canFullScreen &&
<IconButton
aria-label="full-screen"
onClick={() => { this.toggleFullScreen(); }}
color="inherit"
size="medium">
{this.state.isFullScreen ? (
<FullscreenExitIcon style={{ opacity: 0.75 }} />
) : (
<FullscreenIcon style={{ opacity: 0.75 }} />
)}
</IconButton>
}
<IconButton
aria-label="cancel"
onClick={() => { this.props.onClose(); }}
color="inherit"
size="medium">
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<main className={classes.mainFrame} >
<div style={{
overflow: "hidden",
flex: "1 1 auto"
}} >
<div className={classes.heroContent}>
<MainPage hasTinyToolBar={false} enableStructureEditing={false} />
</div>
</div>
</main>
<ZoomedUiControl
dialogOpen={this.state.zoomedControlOpen}
controlInfo={this.state.zoomedControlInfo}
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
}
/>
{ this.state.showStatusMonitor && (<JackStatusView />)}
</div >
);
}
}
);
export default SnapshotEditor;
-13
View File
@@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
-17
View File
@@ -1,17 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
-15
View File
@@ -1,15 +0,0 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
-5
View File
@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
-1
View File
@@ -1 +0,0 @@
npm start
-178
View File
@@ -1,178 +0,0 @@
src/PiPedalError.tsx
src/OldDeleteIcon.tsx
src/MaterialColors.tsx
src/Pedalboard.tsx
src/SystemMidiBindingView.tsx
src/GovernorSettings.tsx
src/ObservableProperty.tsx
src/SnapshotEditor.tsx
src/SelectMidiChannelsDialog.tsx
src/AlsaMidiDeviceInfo.tsx
src/WifiDirectConfigSettings.tsx
src/WifiConfigSettings.tsx
src/SnapshotButton.tsx
src/WindowScale.tsx
src/JsonAtom.tsx
src/PiPedalSocket.tsx
src/TemporaryDrawer.tsx
src/DarkMode.tsx
src/MidiBindingsDialog.tsx
src/MidiBinding.tsx
src/SnapshotDialog.tsx
src/SettingsDialog.tsx
src/PedalboardView.tsx
src/reportWebVitals.ts
src/OptionsDialog.tsx
src/SelectChannelsDialog.tsx
src/PluginPresetSelector.tsx
src/VuMeter.tsx
src/ToobFrequencyResponseView.tsx
src/SystemMidiBindingsDialog.tsx
src/FullScreenIME.tsx
src/App.tsx
src/ToobInputStageView.tsx
src/XxxSnippet.tsx
src/JackServerSettings.tsx
src/ToobWaveShapeView.tsx
src/ToobPowerStage2View.tsx
src/PluginPreset.tsx
src/logo.svg
src/Banks.tsx
src/NoChangePassword.tsx
src/SearchFilter.tsx
src/FilePropertyDirectorySelectDialog.tsx
src/GxTunerView.tsx
src/UpdateDialog.tsx
src/svg/file_download_black_24dp.svg
src/svg/fx_filter.svg
src/svg/fx_pitch.svg
src/svg/fx_distortion.svg
src/svg/fx_split_b.svg
src/svg/fx_chorus.svg
src/svg/fx_limiter.svg
src/svg/fx_phaser.svg
src/svg/snapshot_0.svg
src/svg/snapshot_3.svg
src/svg/file_upload_black_24dp.svg
src/svg/ic_bank.svg
src/svg/fx_function.svg
src/svg/fx_dial.svg
src/svg/ic_settings.svg
src/svg/fx_split_a.svg
src/svg/fx_empty.svg
src/svg/fx_amplifier.svg
src/svg/fx_spectral.svg
src/svg/fx_eq.svg
src/svg/ic_edit_banks.svg
src/svg/ic_help_outline.svg
src/svg/ic_save_bank_as.svg
src/svg/ic_pluginpreset2.svg
src/svg/fx_flanger2.svg
src/svg/snapshot_6.svg
src/svg/fx_lr.svg
src/svg/fx_generator.svg
src/svg/snapshot_1.svg
src/svg/fx_reverb.svg
src/svg/fx_plugin.svg
src/svg/snapshot_4.svg
src/svg/drive_file_rename_outline_black_24dp.svg
src/svg/snapshot_2.svg
src/svg/fx_instrument.svg
src/svg/fx_modulator.svg
src/svg/fx_error.svg
src/svg/fx_simulator.svg
src/svg/fx_filter_hp.svg
src/svg/fx_compressor.svg
src/svg/fx_gate.svg
src/svg/fx_terminal.svg
src/svg/ic_midi.svg
src/svg/fx_oscillator.svg
src/svg/fx_analyzer.svg
src/svg/fx_flanger.svg
src/svg/old_delete_outline_24dp.svg
src/svg/ic_presets.svg
src/svg/fx_delay.svg
src/svg/fx_parametric_eq.svg
src/svg/fx_spatial.svg
src/svg/fx_converter.svg
src/svg/fx_constant.svg
src/svg/snapshot_5.svg
src/svg/ic_pluginpreset.svg
src/svg/fx_mixer.svg
src/svg/fx_utility.svg
src/Updater.tsx
src/UploadPresetDialog.tsx
src/RadioSelectDialog.tsx
src/setupTests.ts
src/ToobCabSimView.tsx
src/RenameDialog.tsx
src/SplitControlView.tsx
src/SelectHoverBackground.tsx
src/IControlViewFactory.tsx
src/OkCancelDialog.tsx
src/BankDialog.tsx
src/PluginPresetsDialog.tsx
src/App.test.tsx
src/ControlViewFactory.tsx
src/DraggableGrid.tsx
src/ZoomedDial.tsx
src/index.css
src/Pedal.tsx
src/Draggable.tsx
src/PluginIcon.tsx
src/react-app-env.d.ts
src/TextFieldEx.tsx
src/FilePropertyDialog.tsx
src/FilePropertyDirectoryTree.tsx
src/StringBuilder.tsx
src/MainPage.tsx
src/PluginInfoDialog.tsx
src/AppThemed.tsx
src/Lv2Plugin.tsx
src/ToobSpectrumResponseView.tsx
src/PerformanceView.tsx
src/JackServerSettingsDialog.tsx
src/Utility.tsx
src/JackStatusView.tsx
src/ToobSpectrumAnalyzerView.tsx
src/SvgPathBuilder.tsx
src/PresetSelector.tsx
src/PresetDialog.tsx
src/WifiChannel.tsx
src/AboutDialog.tsx
src/GxTunerControl.tsx
src/VirtualKeyboardHandler.tsx
src/PluginControl.tsx
src/LoadPluginDialog.tsx
src/ZoomedUiControl.tsx
src/AlsaDeviceInfo.tsx
src/ToobToneStackView.tsx
src/UploadFileDialog.tsx
src/PluginOutputControl.tsx
src/SelectThemeDialog.tsx
src/Rect.tsx
src/AppThemed.css
src/ColorDropdownButton.tsx
src/AndroidHost.tsx
src/SnapshotPanel.tsx
src/ToobMLView.tsx
src/WifiConfigDialog.tsx
src/Rectangle.tsx
src/SnapshotColors.tsx
src/PiPedalModel.tsx
src/ObservableEvent.tsx
src/JackHostStatus.tsx
src/index.tsx
src/FilePropertyControl.tsx
src/WifiDirectConfigDialog.tsx
src/MidiBindingView.tsx
src/PluginControlView.tsx
src/SplitUiControls.tsx
src/PluginClass.tsx
src/DialogEx.tsx
src/Jack.tsx
src/ResizeResponsiveComponent.tsx
src/NumericInput.tsx
src/SnapshotPropertiesDialog.tsx
src/SearchControl.tsx
src/Units.tsx
-27
View File
@@ -1,27 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext",
"es2020"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
+11 -6
View File
@@ -1257,7 +1257,7 @@ namespace pipedal
{
message =
SS("Device " << alsa_device_name << " in use. The following applications are using your soundcard: " << apps
<< ". Stop them as neccesary before trying to restart pipedald.");
<< ". Stop them as neccesary before trying to pipedald.");
}
else
{
@@ -1657,7 +1657,7 @@ namespace pipedal
Lv2Log::error(e.what());
Lv2Log::error("ALSA audio thread terminated abnormally.");
}
this->driverHost->OnAudioStopped();
this->driverHost->OnAlsaDriverStopped();
// if we terminated abnormally, pump messages until we have been terminated.
if (!terminateAudio())
@@ -1671,11 +1671,16 @@ namespace pipedal
pBuffer[j] = 0;
}
}
while (!terminateAudio())
try {
while (!terminateAudio())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// zero out input buffers.
this->driverHost->OnProcess(this->bufferSize);
}
} catch (const std::exception &e)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// zero out input buffers.
this->driverHost->OnProcess(this->bufferSize);
}
}
this->driverHost->OnAudioTerminated();
+1 -1
View File
@@ -212,7 +212,7 @@ public:
}
virtual void OnAudioStopped() {
virtual void OnAlsaDriverStopped() {
}
virtual void OnProcess(size_t nFrames) {
if (testType == TestType::NullTest) return;
+4 -4
View File
@@ -100,8 +100,8 @@ namespace pipedal
long long get_l1_misses()
{
long long count;
read(fd_l1, &count, sizeof(long long));
long long count = 0;
auto _ = read(fd_l1, &count, sizeof(long long));
return count;
}
@@ -111,8 +111,8 @@ namespace pipedal
{
return -1;
}
long long count;
read(fd_l2, &count, sizeof(long long));
long long count = 0;
auto _ = read(fd_l2, &count, sizeof(long long));
return count;
}
};
+1 -1
View File
@@ -48,7 +48,7 @@ namespace pipedal {
public:
virtual void OnProcess(size_t nFrames) = 0;
virtual void OnUnderrun() = 0;
virtual void OnAudioStopped() = 0;
virtual void OnAlsaDriverStopped() = 0;
virtual void OnAudioTerminated() = 0;
+35 -37
View File
@@ -32,6 +32,7 @@
#include <unordered_map>
#include "PluginHost.hpp"
#include "PatchPropertyWriter.hpp"
#include "CpuTemperatureMonitor.hpp"
using namespace pipedal;
@@ -182,7 +183,8 @@ namespace pipedal
reader.read(&vProperty);
if (!vProperty.is_null())
{
try {
try
{
vProperty = pluginHost.MapPath(vProperty);
// now to atom format (what we want on the rt thread0)
@@ -193,7 +195,8 @@ namespace pipedal
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
} catch (const std::exception &e)
}
catch (const std::exception &e)
{
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
}
@@ -206,7 +209,7 @@ namespace pipedal
void ApplyValues(IEffect *effect)
{
effect->SetBypass(this->enabled);
effect->SetBypass(this->enabled);
if (effect != pEffect)
{
throw std::runtime_error("Wrong effect");
@@ -387,6 +390,7 @@ private:
std::mutex atomConverterMutex;
AtomConverter atomConverter;
CpuTemperatureMonitor::ptr cpuTemperatureMonitor;
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE];
@@ -905,7 +909,7 @@ private:
bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
{
//eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
// eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
if (listenForMidiEvent)
@@ -1098,10 +1102,12 @@ private:
{
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
}
virtual void OnAudioStopped() override
virtual void OnAlsaDriverStopped() override
{
this->audioStopped = true;
Lv2Log::info("Audio stopped.");
this->realtimeWriter.AudioTerminatedAbnormally();
}
virtual void OnAudioTerminated() override
{
@@ -1211,7 +1217,7 @@ private:
}
catch (const std::exception &e)
{
Lv2Log::error("Fatal error while processing jack audio. (%s)", e.what());
Lv2Log::error("Fatal error while processing realtime audio. (%s)", e.what());
throw;
}
}
@@ -1231,6 +1237,8 @@ public:
{
realtimeAtomBuffer.resize(32 * 1024);
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
cpuTemperatureMonitor = CpuTemperatureMonitor::Get();
}
virtual ~AudioHostImpl()
{
@@ -1252,12 +1260,14 @@ public:
return this->sampleRate;
}
void OnAudioComplete()
void HandleAudioTerminatedAbnormally()
{
// there is actually no compelling circumstance in which this should ever happen.
Lv2Log::error("Audio processing terminated unexpectedly.");
realtimeWriter.AudioStopped();
if (pNotifyCallbacks)
{
pNotifyCallbacks->OnAlsaDriverTerminatedAbnormally();
}
}
std::vector<uint8_t> atomBuffer;
std::vector<uint8_t> realtimeAtomBuffer;
@@ -1267,7 +1277,7 @@ public:
{
SetThreadName("rtsvc");
SetThreadPriority(SchedulerPriority::AudioService);
int underrunMessagesGiven = 0;
try
{
@@ -1452,7 +1462,7 @@ public:
{
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
//this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
// this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
}
}
}
@@ -1488,11 +1498,11 @@ public:
hostReader.read(&buffer);
OnPathPropertyReceived(buffer);
}
else if (command == RingBufferCommand::AudioStopped)
else if (command == RingBufferCommand::AudioTerminatedAbnormally)
{
AudioStoppedBody body;
hostReader.read(&body);
OnAudioComplete();
HandleAudioTerminatedAbnormally();
return;
}
else if (command == RingBufferCommand::
@@ -1617,7 +1627,7 @@ public:
if (jackServerSettings.IsDummyAudioDevice())
{
this->isDummyAudioDriver = true;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice()));
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
}
else
{
@@ -1852,8 +1862,9 @@ public:
VuUpdate v;
v.instanceId_ = instanceId;
// Display mono VUs if a stereo device is being fed identical L/R inputs.
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2
&& effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2;
vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v);
@@ -1923,7 +1934,7 @@ public:
}
private:
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
// virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
class RestartThread
@@ -2032,21 +2043,6 @@ public:
this->hostWriter.ParameterRequest(pParameterRequest);
}
static int32_t GetRaspberryPiTemperature()
{
try
{
std::ifstream f("/sys/class/thermal/thermal_zone0/temp");
int32_t temp;
f >> temp;
return temp;
}
catch (std::exception &)
{
return -1000000;
}
}
virtual JackHostStatus getJackStatus()
{
CleanRestartThreads(false);
@@ -2062,7 +2058,7 @@ public:
result.msSinceLastUnderrun_ = (uint64_t)dt;
result.temperaturemC_ = GetRaspberryPiTemperature();
result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC() * 1000));
result.active_ = IsAudioRunning();
result.restarting_ = this->restarting;
@@ -2071,12 +2067,14 @@ public:
{
result.cpuUsage_ = audioDriver->CpuUse();
}
GetCpuFrequency(&result.cpuFreqMax_, &result.cpuFreqMin_);
GetCpuFrequency(&result.cpuFreqMin_, &result.cpuFreqMax_);
result.hasCpuGovernor_ = HasCpuGovernor();
if (result.hasCpuGovernor_)
{
result.governor_ = GetGovernor();
} else {
}
else
{
result.governor_ = "";
}
@@ -2121,7 +2119,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
else if (i->symbol() == "prevBank")
{
this->prevBankMidiBinding.SetBinding(*i);
}
}
else if (i->symbol() == "nextProgram")
{
this->nextPresetMidiBinding.SetBinding(*i);
+2
View File
@@ -170,6 +170,8 @@ namespace pipedal
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
virtual void OnAlsaDriverTerminatedAbnormally() = 0;
};
class JackHostStatus
+2 -1
View File
@@ -181,6 +181,7 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
char *n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
this->serviceName = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
@@ -253,7 +254,7 @@ void AvahiService::create_group(AvahiClient *c)
* because it was reset previously, add our entries. */
if (this->makeAnnouncement && avahi_entry_group_is_empty(group))
{
Lv2Log::debug(SS("Adding service '" << avahiNameString << "'"));
Lv2Log::debug(SS("Adding service '" << serviceName << "'"));
std::string instanceTxtRecord = SS("id=" << this->instanceId);
+7 -3
View File
@@ -41,7 +41,7 @@ namespace macaron
public:
static std::string Encode(const std::vector<uint8_t> &data)
{
return Encode(data.size(),&(data[0]));
return Encode(data.size(),data.data());
}
static std::string Encode(size_t size, const uint8_t *data)
{
@@ -55,13 +55,14 @@ namespace macaron
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
size_t in_len = size;
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
size_t i = 0;
char *p = const_cast<char *>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3)
for (i = 0; i+2 < in_len; i += 3)
{
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
@@ -111,6 +112,9 @@ namespace macaron
if (in_len % 4 != 0)
throw std::invalid_argument("Input data size is not a multiple of 4");
if (in_len < 4) {
return std::vector<uint8_t>();
}
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=')
out_len--;
+465
View File
@@ -0,0 +1,465 @@
// Copyright (c) 2024 Robin E. R. Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "BootConfig.hpp"
#include <filesystem>
#include <stdexcept>
#include "ss.hpp"
#include <algorithm>
#include "SysExec.hpp"
#include "util.hpp"
#include <set>
#include "SystemConfigFile.hpp"
namespace fs = std::filesystem;
#include <iostream>
#include <fstream>
#include <string>
#include <filesystem>
#include <cstdlib>
#include <vector>
using namespace pipedal;
static bool IsGrubBootLoader()
{
const std::vector<std::string> grub_config_paths = {
"/boot/grub/grub.cfg",
"/boot/grub2/grub.cfg",
};
for (const auto &path : grub_config_paths)
{
if (std::filesystem::exists(path))
{
return true;
}
}
return false;
}
static std::set<std::string> KNOWN_KERNEL_TYPES = {
"PREEMPT_NONE",
"PREEMPT_VOLUNTARY",
"PREEMPT",
"PREEMPT_RT",
"PREEMPT_DYNAMIC"};
std::string GetKernelType()
{
auto result = sysExecForOutput("uname", "-a");
if (result.exitCode != EXIT_SUCCESS)
{
throw std::runtime_error("Unable to execute 'uname -a'");
}
std::vector<std::string> items = split(result.output, ' ');
for (std::string &item : items)
{
if (KNOWN_KERNEL_TYPES.contains(item))
{
return item;
}
}
return "Unknown";
}
std::string ReadFileLine(const fs::path &path)
{
if (!fs::exists(path))
{
throw std::runtime_error(SS("File does not exist: " << path));
}
std::ifstream f(path);
if (!f.is_open())
{
throw std::runtime_error(SS("Can't read file " << path));
}
std::string line;
std::getline(f, line);
return line;
}
std::vector<std::string> ReadFileArgs(const fs::path &path)
{
std::string line = ReadFileLine(path);
return split(line, ' ');
}
using namespace pipedal;
BootConfig::BootConfig()
{
if (fs::exists("/boot/firmware/cmdline.txt"))
{
this->bootLoader = BootLoaderT::UBoot;
}
else if (IsGrubBootLoader())
{
this->bootLoader = BootLoaderT::Grub;
}
else
{
this->bootLoader = BootLoaderT::Unknown;
}
this->kernelType = GetKernelType();
if (this->kernelType == "PREEMPT_DYNAMIC")
{
this->dynamicScheduler = DynamicSchedulerT::Voluntary; // the default.
}
std::string cmdLine = ReadFileLine("/proc/cmdline");
std::vector<std::string> cmdLineArgs = split(cmdLine, ' ');
for (const auto &arg : cmdLineArgs)
{
if (arg == "preempt=full")
{
this->dynamicScheduler = DynamicSchedulerT::Full;
}
else if (arg == "preempt=voluntary")
{
this->dynamicScheduler = DynamicSchedulerT::Voluntary;
}
else if (arg == "preempt=none")
{
this->dynamicScheduler = DynamicSchedulerT::None;
}
else if (arg.starts_with("preempt="))
{
this->dynamicScheduler = DynamicSchedulerT::Unknown;
}
else if (arg == "threadirqs")
{
this->threadedIrqs = true;
}
}
this->canSetThreadIrqs = this->kernelType == "PREEMPT" || this->kernelType == "PREEMPT_DYNAMIC";
}
BootConfig::~BootConfig()
{
this->thread = nullptr; // join and clean up the tread before deleting ANYTHING else.
}
bool BootConfig::operator==(BootConfig &other) const
{
return this->bootLoader == other.bootLoader && this->kernelType == other.kernelType && this->preemptMode == other.preemptMode && this->threadedIrqs == other.threadedIrqs;
}
bool BootConfig::CanWriteConfig()
{
if (!CanSetThreadIrqs() && dynamicScheduler == DynamicSchedulerT::NotApplicable)
{
return true;
}
return (this->bootLoader != BootLoaderT::Unknown);
}
void BootConfig::WriteConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
{
this->thread = nullptr; // wait for any previous operations to complete.
if (!changed)
{
if (onComplete)
onComplete(true, "");
}
else
{
changed = false;
if (this->bootLoader == BootLoaderT::UBoot)
{
try
{
WriteUBootConfiguration(onComplete);
}
catch (const std::exception &e)
{
if (onComplete)
onComplete(false, e.what());
}
}
else if (this->bootLoader == BootLoaderT::Grub)
{
WriteGrubConfiguration(onComplete);
}
else
{
if (onComplete)
onComplete(false, "Unsupported bootloader.");
}
}
}
static std::string commandLineOption(BootConfig::DynamicSchedulerT dynamicScheduler)
{
switch (dynamicScheduler)
{
case BootConfig::DynamicSchedulerT::None:
return "preempt=none";
case BootConfig::DynamicSchedulerT::Voluntary:
return "preempt=voluntary";
case BootConfig::DynamicSchedulerT::Full:
return "preempt=full";
default:
throw std::range_error("Invalid value.");
}
}
void BootConfig::WriteUBootConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
{
this->thread = nullptr; // blocks if already running.
this->thread = std::make_unique<std::jthread>(
[this, onComplete]()
{
try
{
fs::path cmdlineFile = "/boot/firmware/cmdline.txt";
fs::path backupFile = "/boot/firmware/cmdline.txt.bak";
if (!fs::exists(backupFile))
{
try {
fs::copy_file(cmdlineFile,backupFile);
} catch(...)
{
}
}
std::string line = ReadFileLine(cmdlineFile);
std::string newLine = SetCmdlineArgs(line);
{
std::ofstream f(cmdlineFile);
if (!f.is_open())
{
throw std::runtime_error(SS("Can't write to " << cmdlineFile));
}
f << newLine << '\n'; // trailing \n is REQUIRED
}
if (onComplete)
{
onComplete(true, "");
}
}
catch (const std::exception &e)
{
if (onComplete)
{
onComplete(false, e.what());
}
}
});
}
static std::string QuoteGrubArgument(const std::string &value) {
std::ostringstream ss;
ss << '\"';
for (char c : value) {
if (c == '\\' || c == '\"')
{
ss << '\\' << c;
} else {
ss << c;
}
}
ss << '\"';
return ss.str();
}
static std::string UnquoteGrubArgument(const std::string &value) {
if (value.length() == 0) return value;
std::istringstream ss(value);
if (ss.peek() == '\"')
{
// c-like; \ is escape character.
if (ss.peek() != '\"')
{
throw std::runtime_error("Invalid string in GRUB configuration file.");
}
ss.get();
std::ostringstream result;
while (ss.peek() != '\"' && ss.peek() != -1)
{
int c = ss.get();
if (c == -1 || c == '\n') {
throw std::runtime_error("Invalid string in GRUB configuration file.");
}
if (c == '\\')
{
int c2 = ss.get();
if (c2 == '\n' || c2 == -1) {
throw std::runtime_error("Invalid string in GRUB configuration file.");
}
result << (char)c2;
} else {
result << (char)c;
}
}
return result.str();
} else if (ss.peek() == '\'')
{
ss.get();
std::ostringstream result;
while (true)
{
int c = ss.get();
if (c == -1 || c == '\n') {
throw std::runtime_error("Invalid string in GRUB configuration file.");
}
if (c == '\'')
{
break;
}
result << (char)c;
}
return result.str();
} else {
throw std::runtime_error("Invalid string in GRUB configuration file.");
}
}
std::string BootConfig::SetCmdlineArgs(const std::string &cmdLine)
{
std::vector<std::string> bootArgs = split(cmdLine,' ');
std::vector<std::string> newBootArgs;
for (const auto &value : bootArgs)
{
if (value != "threadirqs" && !value.starts_with("preempt="))
{
newBootArgs.push_back(value);
}
}
if (this->canSetThreadIrqs && this->threadedIrqs)
{
newBootArgs.push_back("threadirqs");
}
if (this->dynamicScheduler != DynamicSchedulerT::NotApplicable)
{
newBootArgs.push_back(commandLineOption(this->dynamicScheduler));
}
std::stringstream ss;
bool firstArg = true;
for (const std::string &arg : newBootArgs)
{
if (!firstArg)
{
ss << ' ';
}
firstArg = false;
ss << arg;
}
return ss.str();
}
void BootConfig::WriteGrubConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
{
this->thread = nullptr; // blocks if already running.
this->thread = std::make_unique<std::jthread>(
[this, onComplete]()
{
try
{
////////// rewrite /etc/default/grub ////////////
fs::path configPath = "/etc/default/grub";
fs::path backupPath = "/etc/default/grub.bak";
if (!fs::exists(backupPath))
{
try {
fs::copy_file(configPath,backupPath);
} catch(...)
{
}
}
SystemConfigFile grubFile(configPath);
std::string linePrefix = "GRUB_CMDLINE_LINUX_DEFAULT=";
auto line = grubFile.GetLineNumberStartingWith(linePrefix);
if (line < 0)
{
throw std::runtime_error(SS("Unexpected file format: " << configPath));
}
std::string cmdlineDefault = grubFile.Get(line);
std::string cmdlineArgs = UnquoteGrubArgument(cmdlineDefault.substr(linePrefix.length()));
std::string newArgs = SetCmdlineArgs(cmdlineArgs);
std::string newLine = linePrefix + QuoteGrubArgument(newArgs);
grubFile.Set(line,newLine);
grubFile.Save(configPath);
/////// regenerate actual GRUB boot files /////////////
auto result = sysExecForOutput("update-grub","");
if (result.exitCode != EXIT_SUCCESS)
{
throw std::runtime_error(SS("update-grub failed. (" << result.output.substr(0,std::min((size_t)60,result.output.length())) << ")"));
}
//// signal completion //////
if (onComplete)
{
onComplete(true,"");
}
}
catch (const std::exception &e)
{
if (onComplete)
{
onComplete(false, e.what());
}
}
});
}
void BootConfig::DynamicScheduler(DynamicSchedulerT value)
{
if (value != this->dynamicScheduler)
{
if (this->dynamicScheduler == DynamicSchedulerT::NotApplicable)
{
throw std::runtime_error("Illegal operation.");
}
this->dynamicScheduler = value;
this->changed = true;
}
}
void BootConfig::ThreadedIrqs(bool value)
{
if (value != threadedIrqs)
{
if (!CanSetThreadIrqs())
{
throw std::runtime_error("Illegal operation.");
}
this->threadedIrqs = value;
this->changed = true;
}
}
+84
View File
@@ -0,0 +1,84 @@
// Copyright (c) 2024 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>
#include <functional>
#include <thread>
#include <memory>
namespace pipedal {
class BootConfig {
public:
BootConfig();
~BootConfig();
enum class BootLoaderT {
UBoot,
Grub,
Unknown
};
enum class DynamicSchedulerT {
None = 0,
Voluntary = 1,
Full = 2,
NotApplicable = -1,
Unknown = -2,
};
// BEWARE: The onComplete function gets called from off the main thread on machines with GRUB bootloaders!
void WriteConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
BootLoaderT BootLoader() const { return bootLoader; }
const std::string&KernelType() const { return kernelType; }
bool CanSetThreadIrqs() const { return canSetThreadIrqs; }
bool ThreadedIrqs() const { return threadedIrqs; }
void ThreadedIrqs(bool value);
const DynamicSchedulerT DynamicScheduler() const { return dynamicScheduler; }
void DynamicScheduler(DynamicSchedulerT value);
bool CanWriteConfig();
bool Changed() const { return changed; }
bool operator==(BootConfig&other) const;
private:
std::unique_ptr<std::jthread> thread;
std::string SetCmdlineArgs(const std::string &args);
void WriteUBootConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
void WriteGrubConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
bool changed = false;
BootLoaderT bootLoader = BootLoaderT::Unknown;
std::string kernelType;
std::string preemptMode;
DynamicSchedulerT dynamicScheduler = DynamicSchedulerT::NotApplicable;
bool canSetThreadIrqs = false;
bool threadedIrqs = false;
};
};
+71 -17
View File
@@ -7,15 +7,49 @@ set (USE_PCH 1)
set (ENABLE_BACKTRACE 1)
set (USE_SANITIZE true)
set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
set(CXX_STANDARD 20)
include(FetchContent)
# FOR FTXUI
if(CMAKE_VERSION VERSION_GREATER 3.30)
cmake_policy(SET CMP0169 OLD) # Allow use of old FETCHCONTENT macros.
endif()
# Our FTXUI option takes precedence over the one in the ftxui CMakeLists.txt
# because FTXUI uses cmake_minimum_required(VERSION 3.12)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(FETCHCONTENT_QUIET ON)
set(FTXUI_QUIET ON)
FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG v5.0.0
)
FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
message(STATUS "*** ${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR}")
include(FindPkgConfig)
find_package(sdbus-c++ REQUIRED)
find_package(ICU REQUIRED COMPONENTS uc i18n )
message(STATUS "ICU_LIBRARIES: ${ICU_LIBRARIES}")
if(DEFINED ENV{GITHUB_ACTIONS})
message(STATUS "Building in GitHub Actions environment")
set(GITHUB_ACTIONS TRUE)
@@ -115,16 +149,16 @@ add_compile_definitions(DEBIAN_ARCHITECTURE="${DEBIAN_ARCHITECTURE}")
if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG -D_GLIBCXX_DEBUG" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -DDEBUG -D_GLIBCXX_DEBUG" )
if (USE_SANITIZE)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan " )
endif()
elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
message(STATUS "RelWithgDebInfo build")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -DNDEBUG" )
else()
message(STATUS "Release build")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG" )
endif()
if (ENABLE_BACKTRACE)
@@ -158,8 +192,10 @@ else()
endif()
set (PIPEDAL_SOURCES
CpuTemperatureMonitor.cpp CpuTemperatureMonitor.hpp
SchedulerPriority.hpp SchedulerPriority.cpp
ModFileTypes.cpp ModFileTypes.hpp
MimeTypes.cpp MimeTypes.hpp
PatchPropertyWriter.hpp
PresetBundle.cpp PresetBundle.hpp
RealtimeMidiEventType.hpp
@@ -178,9 +214,8 @@ set (PIPEDAL_SOURCES
FilePropertyDirectoryTree.cpp FilePropertyDirectoryTree.hpp
FileEntry.cpp FileEntry.hpp
atom_object.hpp atom_object.cpp
FileBrowserFiles.h
lv2ext/pipedal.lv2/ext/fileBrowser.h
FileBrowserFilesFeature.hpp FileBrowserFilesFeature.cpp
MimeTypes.cpp MimeTypes.hpp
inverting_mutex.hpp
DbDezipper.hpp DbDezipper.cpp
WebServerLog.hpp
@@ -272,16 +307,15 @@ set (PIPEDAL_INCLUDES
.
)
set(PIPEDAL_LIBS libpipedald zip
PiPedalCommon
icui18n
icuuc
icudata
pthread atomic stdc++fs asound avahi-common avahi-client systemd
${VST3_LIBRARIES}
${LILV_0_LIBRARIES}
# ${JACK_LIBRARIES} - pending delete for JACK support.
)
)
@@ -299,6 +333,24 @@ if(${USE_PCH})
endif()
#################################
add_executable(pipedal_kconfig
kconfigMain.cpp
BootConfig.cpp BootConfig.hpp
SystemConfigFile.hpp SystemConfigFile.cpp
)
target_link_libraries(pipedal_kconfig
PRIVATE ftxui::screen
PRIVATE ftxui::dom
PRIVATE ftxui::component # Not needed for this example.
PRIVATE PiPedalCommon
systemd
)
#################################
add_executable(pipedald
asan_options.cpp # disable leak checking for sanitize=address.
@@ -325,9 +377,10 @@ set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
add_executable(pipedaltest testMain.cpp
add_executable(pipedaltest
testMain.cpp
InvertingMutexTest.cpp
ModFileTypesTest.cpp
jsonTest.cpp
UpdaterTest.cpp
@@ -339,6 +392,8 @@ add_executable(pipedaltest testMain.cpp
PiPedalAlsaTest.cpp
UnixSocketTest.cpp
LocaleTest.cpp
Lv2HostLeakTest.cpp
@@ -348,7 +403,7 @@ add_executable(pipedaltest testMain.cpp
MemDebug.cpp
MemDebug.hpp
)
target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS})
target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS} ${ICU_LIBRARIES})
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
)
@@ -642,7 +697,9 @@ add_executable(pipedalconfig
ConfigMain.cpp
alsaCheck.cpp
alsaCheck.hpp
BootConfig.cpp BootConfig.hpp
ModFileTypes.cpp ModFileTypes.hpp
MimeTypes.cpp MimeTypes.hpp
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
JackServerSettings.hpp JackServerSettings.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
@@ -653,9 +710,6 @@ add_executable(pipedalconfig
)
target_link_libraries(pipedalconfig PRIVATE PiPedalCommon pthread atomic uuid stdc++fs asound
icui18n
icuuc
icudata
)
@@ -774,7 +828,7 @@ add_custom_target (
)
install (TARGETS pipedalconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets)
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
+185 -55
View File
@@ -27,6 +27,8 @@
#include "alsaCheck.hpp"
#include "RegDb.hpp"
#include "Locale.hpp"
#include "Finally.hpp"
#include "BootConfig.hpp"
#include <filesystem>
#include <stdlib.h>
@@ -560,38 +562,6 @@ void InstallAudioService()
#endif
}
int SudoExec(int argc, char **argv)
{
// re-execute with SUDO in order to prompt for SUDO credentials once only.
std::vector<char *> args;
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
args.push_back((char *)(pkexec.c_str()));
std::string sPath = getSelfExePath();
args.push_back(const_cast<char *>(sPath.c_str()));
for (int arg = 1; arg < argc; ++arg)
{
args.push_back(const_cast<char *>(argv[arg]));
}
args.push_back(nullptr);
char **newArgs = &args[0];
int pbPid;
int returnValue = 0;
if ((pbPid = fork()) == 0)
{
return execv(newArgs[0], newArgs);
}
else
{
waitpid(pbPid, &returnValue, 0);
int exitStatus = WEXITSTATUS(returnValue);
return exitStatus;
}
}
static bool IsP2pServiceEnabled()
{
@@ -953,6 +923,132 @@ void InstallPgpKey()
sysExec(cmd.c_str());
}
}
static uint32_t autoSelectPorts[] = {
80,
81, // unofficial alternate
8080, // unofficial alternate
8081, // unofficial alternate
8008, // unofficial alternate
83 // Not official at all.
};
static bool isPortInUse(int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
throw std::runtime_error(SS("Socket creation failed: " << strerror(errno)));
}
Finally ff{[sockfd]() {
close(sockfd);
}};
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
// Try to bind to the port
int result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if (result < 0) {
if (errno == EADDRINUSE) {
return true; // Port is in use
} else {
cout << "Warning: Unexpected error binding socket to port " << port << ": " << strerror(errno) << endl;
return true;
}
}
return false; // Port is free
}
static std::string AutoSelectPort() {
constexpr size_t MAX_PORT = sizeof(autoSelectPorts)/sizeof(autoSelectPorts[0]);
int32_t port = -1;
for (size_t i = 0; i < MAX_PORT; ++i)
{
if (!isPortInUse(autoSelectPorts[i]))
{
port = autoSelectPorts[i];
break;
}
}
if (port == -1) {
cout << "Warning: Can't find an available HTTP port. Setting to port 80 anyway." << endl;
port = 80;
} else if (port != 80)
{
cout << "Warning: Port 80 is already in use. Using port " << port << " instead." << endl;
}
return SS(port);
}
bool SetWebServerPort(std::string portOption)
{
try {
auto nPos = portOption.find_last_of(':');
std::string strPort;
if (nPos != std::string::npos)
{
strPort = portOption.substr(nPos+1);
} else {
strPort = portOption;
}
std::istringstream ss(strPort);
uint16_t iPort = 0;
ss >> iPort;
if (!ss)
{
throw std::runtime_error(SS("Invalid port number: " << strPort));
}
StopService(false);
PrepareServiceConfigurationFile(iPort);
StartService(true);
cout << "PiPedal web server is listening on port " << iPort << endl;
} catch (const std::exception&e)
{
cout << "Error: " << e.what() << endl;
return false;
}
return true;
}
void CheckPreemptDynamicConfig()
{
try {
BootConfig bootConfig;
if (bootConfig.KernelType() == "PREEMPT_DYNAMIC")
{
if (bootConfig.DynamicScheduler() != BootConfig::DynamicSchedulerT::Full)
{
cout << endl;
cout << "Warning: PREEMPT_DYNAMIC kernel has not been configured for 'preempt=full'" << endl;
cout << "Run pipedal_kconfig to configure the kernel for real-time audio." << endl;
cout << endl;
}
}
} catch(const std::exception& /* ignore */)
{
}
}
void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
cout << "Configuring pipedal" << endl;
@@ -1000,14 +1096,14 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
throw std::runtime_error("Invalid endpoint address: " + endpointAddress);
}
uint16_t port;
uint16_t port = 80;
auto strPort = endpointAddress.substr(endpos + 1);
try
{
auto lport = std::stoul(strPort);
if (lport == 0 || lport >= std::numeric_limits<uint16_t>::max())
{
throw std::runtime_error("out of range.");
throw std::runtime_error("Port number is too large.");
}
port = (uint16_t)lport;
std::stringstream s;
@@ -1020,6 +1116,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
s << "Invalid port number: " << strPort;
throw std::runtime_error(s.str());
}
PrepareServiceConfigurationFile(port);
bool authBindRequired = port < 512;
@@ -1183,14 +1280,25 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
// Restart Wi-Fi Direct if neccessary.
OnWifiReinstall();
CheckPreemptDynamicConfig();
}
catch (const std::exception &e)
{
// don't allow abnormal termination, which leaves the package in a state that's
// difficult to uninstall.
cout << "Error: " << e.what();
cout << "Error: " << e.what() << endl;
cout << " Run 'pipedalconfig --install' again to complete setup of PiPedal." << endl;
}
const std::string ADDR_ANY = "0.0.0.0:";
if (endpointAddress.starts_with(ADDR_ANY))
{
cout << "PiPedal web server is listening on port " << endpointAddress.substr(ADDR_ANY.length()) << endl;
} else {
cout << "PiPedal web server is listening on port " << endpointAddress << endl;
}
}
static std::string GetCurrentWebServicePort()
@@ -1199,7 +1307,7 @@ static std::string GetCurrentWebServicePort()
config.Load("/etc/pipedal/config", "");
std::ostringstream ss;
ss << config.GetSocketServerPort();
ss << config.GetSocketServerAddress() << ":" << config.GetSocketServerPort();
return ss.str();
}
@@ -1208,7 +1316,7 @@ static void PrintHelp()
PrettyPrinter pp;
pp << "pipedalconfig - Command-line post-install configuration for PiPedal"
<< "\n"
<< "Copyright (c) 2022-2024 Robin Davies."
<< "Copyright (c) 2022-2024 Robin E. R. Davies."
<< "\n"
<< "\n"
<< "See https://rerdavies.github.io/pipedal/Documentation.html for "
@@ -1239,6 +1347,10 @@ static void PrintHelp()
<< "The --port option controls which TCP/IP port the web server uses."
<< "\n\n"
<< HangingIndent() << " --port <port#>\t"
<< "Set the TCP/IP port that the web server will use."
<< "\n"
<< HangingIndent() << " --uninstall\t"
<< "Remove installed services."
<< "\n"
@@ -1401,6 +1513,29 @@ void ListValidCountryCodes()
}
}
static std::string ProcessPortOption(std::string portOption)
{
if (portOption == "" || portOption == "0") // allow testing of port autodetect.
{
std::string t = GetCurrentWebServicePort();
if (portOption != "0") {// allows easy debugging of port AutoSelectPort.
portOption = t;
} else {
portOption = "";
}
if (portOption == "")
{
portOption = AutoSelectPort();
}
}
std::string ADDR_ANY = "0.0.0.0:";
if (portOption.find(':') == string::npos)
{
portOption = ADDR_ANY + portOption;
}
return portOption;
}
int main(int argc, char **argv)
{
@@ -1457,6 +1592,10 @@ int main(int argc, char **argv)
uninstall + stop + start + enable + disable + enable_hotspot +
disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels +
fix_permissions + list_wifi_country_codes;
if (actionCount == 0 && portOption.length() != 0)
{
++actionCount;
}
if (actionCount > 1)
{
throw std::runtime_error("Please provide only one action.");
@@ -1553,12 +1692,6 @@ int main(int argc, char **argv)
{
return ListP2PChannels(parser.Arguments());
}
if (portOption.size() != 0 && !install)
{
cout << "Error: -port option can only be specified with the -install option."
<< "\n";
exit(EXIT_FAILURE);
}
auto uid = getuid();
if (uid != 0 && !nosudo)
@@ -1568,6 +1701,12 @@ int main(int argc, char **argv)
try
{
if (portOption.length() != 0 && !install)
{
portOption = ProcessPortOption(portOption);
SetWebServerPort(portOption);
return EXIT_SUCCESS;
}
if (fix_permissions)
{
FixPermissions();
@@ -1595,20 +1734,11 @@ int main(int argc, char **argv)
}
}
if (portOption == "")
{
portOption = GetCurrentWebServicePort();
if (portOption == "")
{
portOption = "80";
}
}
if (portOption.find(':') == string::npos)
{
portOption = "0.0.0.0:" + portOption;
}
portOption = ProcessPortOption(portOption);
Install(prefix, portOption);
FileSystemSync();
}
catch (const std::exception &e)
{
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "CpuTemperatureMonitor.hpp"
#include <iostream>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <optional>
#include <system_error>
namespace fs = std::filesystem;
using namespace pipedal;
static constexpr std::string_view THERMAL_PATH = "/sys/class/thermal/";
static std::optional<std::string> readFile(const fs::path& path) {
std::ifstream file(path);
if (!file) {
return std::nullopt;
}
std::string content;
std::getline(file, content);
return content;
}
static bool isCpuThermal(const fs::path &zone_path) {
auto type = readFile(zone_path / "type");
return type && (*type == "cpu-thermal") || (*type == "x86_pkg_temp");
}
class CpuTemperatureMonitorImpl: public CpuTemperatureMonitor {
private:
std::vector<fs::path> cpuZones;
public:
explicit CpuTemperatureMonitorImpl(std::vector<fs::path>&&cpuZones)
: cpuZones(std::move(cpuZones)) {
}
virtual float GetTemperatureC() override {
float result = INVALID_TEMPERATURE*1000;
for (const auto&cpuZone: cpuZones)
{
std::ifstream file(cpuZone / "temp");
if (file) {
float v;
file >> v;
if (file)
{
if (v > result)
{
result = v;
}
}
}
}
return result/1000;
}
};
static std::vector<fs::path> findCpuThermalZones() {
std::vector<fs::path> cpu_zones;
std::error_code ec;
for (const auto& entry : fs::directory_iterator(THERMAL_PATH, ec)) {
if (ec) {
std::cerr << "Error reading directory: " << ec.message() << '\n';
continue;
}
const auto& path = entry.path();
if (path.filename().string().find("thermal_zone") == 0) {
if (isCpuThermal(path)) {
cpu_zones.push_back(path);
}
}
}
return cpu_zones;
}
CpuTemperatureMonitor::ptr CpuTemperatureMonitor::Get()
{
std::vector<fs::path> cpuZones = findCpuThermalZones();
return std::make_unique<CpuTemperatureMonitorImpl>(std::move(cpuZones));
}
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2024 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
@@ -16,45 +16,28 @@
// 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.
import React from 'react';
import { Theme } from '@mui/material/styles';
#pragma once
import { WithStyles } from '@mui/styles';
import withStyles from '@mui/styles/withStyles';
import createStyles from '@mui/styles/createStyles';
#include <filesystem>
#include <cstdint>
#include <memory>
namespace pipedal {
const styles = (theme: Theme) => createStyles({
});
class CpuTemperatureMonitor {
protected:
CpuTemperatureMonitor() { }
public:
virtual ~CpuTemperatureMonitor() {};
static constexpr float INVALID_TEMPERATURE = -400;
using ptr = std::unique_ptr<CpuTemperatureMonitor>;
interface XxxProps extends WithStyles<typeof styles> {
theme: Theme,
}
static ptr Get(); // may return empty pointer.
interface XxxState {
}
const Xxx = withStyles(styles, { withTheme: true })(
class extends React.Component<XxxProps, XxxState> {
constructor(props: XxxProps)
{
super(props);
this.state = {
};
}
render() {
return (
<div/>
);
}
}
);
export default Xxx;
// degrees Celcius * 1000;
virtual float GetTemperatureC() = 0;
};
}
-138
View File
@@ -1,138 +0,0 @@
/*
* Copyright (c) 2023 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef FILEBROWSERFILES_H
#define FILEBROWSERFILES_H
#define LV2_FILEBROWSER_URI "http://two-play.com/ns/ext/fileBrowser"
#define LV2_FILEBROWSER_PREFIX LV2_FILEBROWSER_URI "#"
#define LV2_FILEBROWSER__files LV2_FILEBROWSER_PREFIX "files" ///< http://two-play.com/ns/ext/fileBrowser#files >
/**
@defgroup filedialog LV2_FileBrowser_Files
@ingroup lv2
A feature for LV2 plugins to provide sample files (or other selectable files) within the plugin's resource bundle to host file selection dialogs.
Browser directories may contain actual files that typically have been uploaded by users; but may also contain symlinks to files
in other locations. This feature allows plugins to publish files within the plugin's resource bundle to browser directories. For
each published file consists of a symlink in the browser directory that links to a corresponding file in the plugins resource bundle.
The LV2_FileBrowser_Files.publish_resource_files() method allows plugins to publish entire directories. publish_resource_files()
implements a versioning system, which tracks whether sample files have been deleted by the user. If the symlink for sample files that were installed
by a previous version of the plugin have been deleted, the sample files from the current version are not reinstalled. See LV2_FileBrowser_Files.publish_resource_files.
not re-installed.
See: <http://rerdavies.github.io/pipedal/TBD XXX.html> for more details.
@{
*/
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
LV2_FileBrowser_Status_Success = 0,
LV2_FileBrowser_Status_Err_Filesystem = 1,
LV2_FileBrowser_Status_Err_InvalidParameter = 1,
} LV2_FileBrowser_Status;
typedef void*
LV2_FileBrowser_Files_Handle; ///< Opaque handle for pipedal:uploadPath feature
/**
Feature data for filedialog:files (@ref LV2_FILEBROWSER_file).
*/
typedef struct {
/**
Opaque host data.
*/
LV2_FileBrowser_Files_Handle handle;
LV2_FileBrowser_Status (*publish_resource_files)(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
/**
Return a path in which upload files will be stored for this plugin.
@param handle MUST be the `handle` member of this struct.
@param path The sub-directory name in which uploaded files will be stored.
@return The absolute path to use for the new directory.
This function can be used by plugins to get the path of files or directories
that can be browsed by file dialogs. get_filebrowser_path, unlike map_path() does
not create or modify files in any way.
The caller must free memory for the returned value with LV2_FileBrowser_Files.free_path().
Example:
[code]
TBD
[/code]
*/
char* (*get_upload_path)(LV2_FileBrowser_Files_Handle handle, const char* path);
/**
Map resource bundle filenames to upload directories.
@param handle MUST be the `handle` member of this struct.
@param path File path name
@param fileBrowserDirectory relative directory of the file browser directory in which to create a link.
@return The absolute pathname of a file in the resource directory.
The primary purpose of this method is to map file references to files in the plugin's bundle
directory to corresponding files in the a file browser directory.
If the path is relative, the full path is resolved using the `rootResourceDirectory` path.
If the full path is not a child of rootResourceDirectory or if the path is relative,
get_upload_path returns the supplied path unmodified.
`
If the filename is a child of rootResourceDirectory, creates a corresponding link to the file in the file browser
directory and returns the path of that link.
The caller must free memory for the returned value with `LV2_FileBrowser_Files.free_path()`.
Example:
*/
char* (*map_path)(LV2_FileBrowser_Files_Handle handle, const char* path, const char*rootResourceDirectory, const char*fileBrowserDirectory);
/**
Free a path returned by LV2_FileBrowser_Files.get_upload_path() or LV2_FileBrowser_Files.map_path()
@param handle MUST be the `handle` member of this struct.
@param path A path previously returned by a method of `LV2_FileBrowser_Files`.
*/
void (*free_path)(LV2_FileBrowser_Files_Handle handle, char* path);
} LV2_FileBrowser_Files;
#ifdef __cplusplus
} // extern "C"
#endif
#endif // FILEBROWSERFILES_H
+74 -6
View File
@@ -30,9 +30,11 @@
#include <stdexcept>
#include "util.hpp"
#include "ofstream_synced.hpp"
#include "ModFileTypes.hpp"
using namespace pipedal;
using namespace pipedal::implementation;
namespace fs = std::filesystem;
namespace pipedal::implementation {
@@ -128,7 +130,9 @@ void FileBrowserFilesFeature::Initialize(
lv2_log_logger_init(&this->lv2Logger,const_cast<LV2_URID_Map *>(lv2Map),const_cast<LV2_Log_Log *>(lv2Log));
this->bundleDirectory = bundleDirectory;
this->browserDirectory = browserDirectory;
this->browserRootDirectory = browserDirectory;
this->privateDirectory = browserDirectory / this->bundleDirectory.filename();
MakeDirectoryMap(browserDirectory);
featureData.handle = (void *)this;
@@ -155,9 +159,73 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::FN_publish_resource_files(LV2_Fi
return ((FileBrowserFilesFeature *)handle)->PublishResourceFiles(version, resourcePath, uploadDirectory);
}
const char*FileBrowserFilesFeature::GetWellKnownDirectory(const std::string&directory)
{
if (strcmp(directory.c_str(), "~") == 0) {
return privateDirectory.c_str();
}
const auto &f = g_wellKnownDirectoryMap->find(directory);
if (f != g_wellKnownDirectoryMap->end())
{
return f->second.c_str();
}
return nullptr;
}
using WellKnownDirectoryMap = std::map<std::string,std::string>;
std::mutex FileBrowserFilesFeature::g_DirectoryMap_mutex;
std::unique_ptr<WellKnownDirectoryMap> FileBrowserFilesFeature::g_wellKnownDirectoryMap;
void FileBrowserFilesFeature::MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory)
{
std::lock_guard lock { g_DirectoryMap_mutex};
if (!g_wellKnownDirectoryMap)
{
g_wellKnownDirectoryMap = std::make_unique<WellKnownDirectoryMap>();
for (const auto&modDirectory: ModFileTypes::ModDirectories())
{
(*g_wellKnownDirectoryMap)[modDirectory.modType] =
(rootBrowserDirectory / modDirectory.pipedalPath).string();
}
}
}
char *FileBrowserFilesFeature::GetUploadPath(const char *fileBrowserPath)
{
std::filesystem::path result = this->browserDirectory / fileBrowserPath;
if (fileBrowserPath == nullptr | fileBrowserPath[0] == '\0')
{
return strdup("");
}
std::filesystem::path path {fileBrowserPath};
auto iter = path.begin();
if (iter == path.end())
{
return strdup("");
}
std::string firstSegment = iter->string();
const auto f = (g_wellKnownDirectoryMap)->find(firstSegment);
if (f == g_wellKnownDirectoryMap->end())
{
return nullptr;
}
fs::path result = f->second;
++iter;
while (iter != path.end())
{
result /= (*iter);
++iter;
}
return strdup(result.c_str());
}
void FileBrowserFilesFeature::FreePath(char *path)
@@ -224,7 +292,7 @@ char *FileBrowserFilesFeature::MapPath(const char *path,const char*resourcePathB
} else {
targetDirectory = fileBrowserPath;
}
targetDirectory = this->browserDirectory / targetDirectory;
targetDirectory = this->browserRootDirectory / targetDirectory;
std::filesystem::create_directories(targetDirectory);
std::filesystem::path targetPath = targetDirectory / relativePath;
@@ -297,7 +365,7 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
static std::string VERSION_FILENAME_PREFIX = ".versionInfo.";
// generate a filename that will allow multiple plugins to install files into the same directory.
std::filesystem::path versionFileName = this->browserDirectory / uploadDirectory /
std::filesystem::path versionFileName = this->browserRootDirectory / uploadDirectory /
(VERSION_FILENAME_PREFIX + GetPluginTag(this->bundleDirectory));
bool loaded = versionInfo.Load(versionFileName);
@@ -325,8 +393,8 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
LogError("uploadDirectory must be relative.");
return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Err_InvalidParameter;
}
targetDirectory = this->browserDirectory / targetDirectory;
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserDirectory / uploadDirectory);
targetDirectory = this->browserRootDirectory / targetDirectory;
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserRootDirectory / uploadDirectory);
versionInfo.Save(versionFileName);
} catch (const std::exception & e)
{
+16 -2
View File
@@ -24,10 +24,12 @@
#pragma once
#include "FileBrowserFiles.h"
#include "lv2ext/pipedal.lv2/ext/fileBrowser.h"
#include <filesystem>
#include <lv2/core/lv2.h>
#include <lv2/log/logger.h>
#include <mutex>
#include <filesystem>
@@ -47,20 +49,32 @@ namespace pipedal {
const std::string&browserDirectory);
const LV2_Feature*GetFeature() { return &feature; }
private:
static void MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory);
using WellKnownDirectoryMap = std::map<std::string,std::string>;
static std::mutex g_DirectoryMap_mutex;
static std::unique_ptr<WellKnownDirectoryMap> g_wellKnownDirectoryMap;
LV2_Feature feature;
const LV2_URID_Map *lv2Map = nullptr;
LV2_Log_Logger lv2Logger = {nullptr,0,0,0,0};
std::filesystem::path bundleDirectory;
std::filesystem::path browserDirectory;
std::filesystem::path browserRootDirectory;
std::filesystem::path privateDirectory;
LV2_FileBrowser_Files featureData;
std::map<std::string,std::string> wellKnownPaths;
static char* FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char* fileBrowserDirectory);
static char* FN_map_path(LV2_FileBrowser_Files_Handle handle, const char* path, const char *resourcePathBase,const char*fileBrowserDirectory);
static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char* path);
static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
const char*GetWellKnownDirectory(const std::string&directory);
void LogError(const char*message);
char* GetUploadPath(const char* fileBrowserDirectory);
char* MapPath(const char* path,const char*resourcePathBase,const char* fileBrowserDirectory);
+1
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "FilePropertyDirectoryTree.hpp"
#include <filesystem>
using namespace pipedal;
namespace fs = std::filesystem;
+2 -2
View File
@@ -372,7 +372,7 @@ void HotspotManagerImpl::onStartMonitoring()
ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET);
if (!ethernetDevice)
{
throw std::runtime_error("eth0 device not found.");
throw std::runtime_error("ethernet device not found.");
}
this->ethernetDevice->OnStateChanged.add(
[this](uint32_t, uint32_t, uint32_t)
@@ -383,7 +383,7 @@ void HotspotManagerImpl::onStartMonitoring()
wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI);
if (!wlanDevice)
{
throw std::runtime_error("wlan0 device not found.");
throw std::runtime_error("Wi-Fi device not found.");
}
wlanWirelessDevice = DeviceWireless::Create(dbusDispatcher, wlanDevice->getObjectPath());
+6 -2
View File
@@ -49,8 +49,11 @@ namespace pipedal {
virtual void SetBypass(bool enable) = 0;
virtual float GetOutputControlValue(int controlIndex) const = 0;
virtual int GetNumberOfInputAudioPorts() const = 0;
virtual int GetNumberOfOutputAudioPorts() const = 0;
virtual int GetNumberOfInputAudioPorts() const = 0; // as declared
virtual int GetNumberOfOutputAudioPorts() const = 0; // as declared.
virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
virtual float *GetAudioInputBuffer(int index) const = 0;
virtual float *GetAudioOutputBuffer(int index) const = 0;
virtual void ResetAtomBuffers() = 0;
@@ -58,6 +61,7 @@ namespace pipedal {
virtual bool GetRequestStateChangedNotification() const = 0;
virtual void SetRequestStateChangedNotification(bool value) = 0;
virtual void PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize) = 0;
virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
+162 -51
View File
@@ -30,10 +30,10 @@
#include <regex>
#include "ss.hpp"
#include "Finally.hpp"
#include <boost/asio.hpp>
using namespace pipedal;
static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6)
{
return IN6_IS_ADDR_V4MAPPED(&inetAddr6) != 0;
@@ -62,6 +62,71 @@ static bool ipv6NetmaskCompare(const struct in6_addr &left, const struct in6_add
return true;
}
NetworkInterfaceType pipedal::GetNetworkInterfaceType(const char *interfaceName)
{
// stock linux.
if (strncmp(interfaceName, "eth", 3) == 0)
{
return NetworkInterfaceType::Ethernet;
}
if (strncmp(interfaceName, "wlan", 4) == 0)
{
return NetworkInterfaceType::WiFi;
}
if (strncmp(interfaceName, "lo", 2) == 0)
{
return NetworkInterfaceType::Loopback;
}
// ubuntu-style names.
if (strncmp(interfaceName, "en", 2) == 0)
{
return NetworkInterfaceType::Ethernet;
}
if (strncmp(interfaceName, "wl", 2) == 0)
{
return NetworkInterfaceType::WiFi;
}
return NetworkInterfaceType::Other;
}
std::optional<std::string> pipedal::GetWlanInterfaceName()
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
throw std::runtime_error("No networks available.");
Finally f{[ifap]()
{
freeifaddrs(ifap);
}};
for (struct ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (GetNetworkInterfaceType(p->ifa_name) == NetworkInterfaceType::WiFi)
{
return std::string(p->ifa_name);
}
}
return std::optional<std::string>();
}
std::optional<std::string> pipedal::GetWlanIpv4Address()
{
auto wlanAddress = GetWlanInterfaceName();
if (wlanAddress)
{
auto result = GetInterfaceIpv4Address(*wlanAddress);
if (result.length() != 0)
{
return result;
}
}
return std::optional<std::string>();
}
static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres)
{
bool result = false;
@@ -71,7 +136,7 @@ static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres)
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
@@ -245,7 +310,7 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
@@ -267,6 +332,44 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
return result;
}
static bool isEthernetAddress(const char *ifName)
{
// either ethN (classic),
if (strncmp(ifName, "eth", 3) == 0)
return true;
// or "enpNNsNN" (ubuntu)
return (ifName[0] == 'e' && ifName[1] == 'n');
}
std::vector<std::string> pipedal::GetEthernetIpv4Addresses()
{
std::vector<std::string> result;
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
return result;
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (isEthernetAddress(p->ifa_name))
{
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
{
if (ifAddr & 0xFF)
{ // has an actual bound IP address.
std::string name = SS(
((ifAddr >> 24) & 0xFF) << '.' << ((ifAddr >> 16) & 0xFF) << '.' << ((ifAddr >> 8) & 0xFF) << '.' << ((ifAddr) & 0xFF));
result.push_back(std::move(name));
}
}
}
}
freeifaddrs(ifap);
return result;
}
static std::string GetInterfaceForIp4Address(uint32_t ipv4Address)
{
struct ifaddrs *ifap = nullptr;
@@ -275,7 +378,7 @@ static std::string GetInterfaceForIp4Address(uint32_t ipv4Address)
std::string result;
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
@@ -299,7 +402,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
std::string result;
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask);
@@ -325,9 +428,10 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
if (getifaddrs(&ifap) != 0)
throw std::runtime_error("No networks available.");
Finally f{[ifap]() {
freeifaddrs(ifap);
}};
Finally f{[ifap]()
{
freeifaddrs(ifap);
}};
std::string result;
@@ -337,7 +441,7 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
// get the nameof the interface for this scopeId.
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
if (pAddr->sin6_scope_id == scopeId)
@@ -355,9 +459,9 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
// an IPV4 address would be the ideal result.
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{
if (strcmp(p->ifa_name,targetInterfaceName) == 0)
if (strcmp(p->ifa_name, targetInterfaceName) == 0)
{
constexpr int BUFSIZE = 128;
char host[BUFSIZE];
@@ -381,14 +485,13 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
{
char buffer[128];
inet_ntop(AF_INET6, &(pAddr->sin6_addr),
buffer,sizeof(buffer));
buffer, sizeof(buffer));
return SS('[' << buffer << ']');
}
}
}
// probably network address settling after the hotspot comes up. :-/
throw std::runtime_error("Not ready. Try again later.");
}
static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
@@ -400,7 +503,7 @@ static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
if (!IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
@@ -444,8 +547,8 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
std::string result = "";
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{
struct sockaddr_in *pAddr = (struct sockaddr_in *)(p->ifa_addr);
{
if (name == p->ifa_name)
@@ -543,7 +646,6 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
// else
// {
// std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
// result = GetNonLinkLocalAddressForInterface(interfaceName);
// }
@@ -556,7 +658,6 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
// return result;
// }
std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
{
std::string address = fromAddress;
@@ -572,12 +673,11 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
throw std::invalid_argument("Bad address.");
address = address.substr(1, address.length() - 2);
auto nPos = address.find('%') ;
auto nPos = address.find('%');
if (nPos != std::string::npos)
{
std::string ifName = address.substr(nPos+1);
std::string ifName = address.substr(nPos + 1);
return GetNonLinkLocalAddressForInterface(ifName);
}
struct in6_addr inetAddr6;
memset(&inetAddr6, 0, sizeof(inetAddr6));
@@ -599,9 +699,8 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
else
{
std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
result = GetNonLinkLocalAddressForInterface(interfaceName);
result = GetNonLinkLocalAddressForInterface(interfaceName);
}
}
}
@@ -612,12 +711,11 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
return result;
}
std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
std::string pipedal::GetInterfaceIpv4Address(const std::string &interfaceName)
{
return GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
// std::string getNonLinkLocalAddress(const std::string&address, const std::string&interface)
// {
// if (!interface.empty())
@@ -628,7 +726,7 @@ std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
// }
static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::string *suffix)
static bool parseForLinkLocalUrl(const std::string &url, std::string *prefix, std::string *suffix)
{
auto iter = url.begin();
auto end = url.end();
@@ -636,7 +734,8 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
// proto:
while (true)
{
if (iter == end) return false;
if (iter == end)
return false;
if (*iter == ':')
{
++iter;
@@ -646,17 +745,20 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
}
// //
if (iter == end || *iter != '/') return false;
if (iter == end || *iter != '/')
return false;
++iter;
if (iter == end || *iter != '/') return false;
if (iter == end || *iter != '/')
return false;
++iter;
// (user@)? of id: http://user@[::1]/
if (*iter != '[') {
if (*iter != '[')
{
while (true)
{
if (iter == end || *iter == '/') return false;
if (iter == end || *iter == '/')
return false;
if (*iter == '@')
{
++iter;
@@ -674,23 +776,30 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
++iter;
// fe80: ...
if (iter == end || !(*iter == 'f' || *iter == 'F')) return false;
if (iter == end || !(*iter == 'f' || *iter == 'F'))
return false;
++iter;
if (iter == end || !(*iter == 'e' || *iter == 'E')) return false;
if (iter == end || !(*iter == 'e' || *iter == 'E'))
return false;
++iter;
if (iter == end || *iter != '8' ) return false;
if (iter == end || *iter != '8')
return false;
++iter;
if (iter == end || *iter != '0' ) return false;
if (iter == end || *iter != '0')
return false;
++iter;
if (iter == end || *iter != ':' ) return false;
if (iter == end || *iter != ':')
return false;
++iter;
while (true)
{
if (iter == end) break;
if (iter == end)
break;
char c = *iter;
bool valid = (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c >= '0' && c <= '9' || c == ':';
if (!valid) break;
if (!valid)
break;
++iter;
}
// potentially a scope ID
@@ -698,38 +807,40 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
auto endOfAddress = iter;
if (iter != end && *iter == '%')
{
while (true) {
if (iter == end) return false;
if (*iter == ']') break;
while (true)
{
if (iter == end)
return false;
if (*iter == ']')
break;
++iter;
}
}
// made it!
if (iter == end || *iter != ']') return false;
if (iter == end || *iter != ']')
return false;
++iter;
auto startOfRest = iter;
*prefix = std::string(url.begin(),endOfPrefix);
*suffix = std::string(startOfRest,url.end());
*prefix = std::string(url.begin(), endOfPrefix);
*suffix = std::string(startOfRest, url.end());
return true;
}
bool pipedal::RemapLinkLocalUrl(
const boost::asio::ip::address &boostAddress,
const std::string&url,std::string*outputUrl)
const std::string &url, std::string *outputUrl)
{
std::string prefix, suffix;
if (!parseForLinkLocalUrl(url,&prefix,&suffix)) return false;
if (!parseForLinkLocalUrl(url, &prefix, &suffix))
return false;
boost::asio::ip::address_v6 addressV6 = boostAddress.to_v6();
std::string directAddress;
directAddress = ::GetNonLinkLocalAddress(addressV6);
*outputUrl = SS(prefix << directAddress << suffix);
*outputUrl = SS(prefix << directAddress << suffix);
return true;
}

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