Vendored
+19
@@ -7,6 +7,7 @@
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/src",
|
||||
"/usr/include/lilv-0",
|
||||
"/usr/include/x86_64-linux-gnu",
|
||||
"/usr/lib",
|
||||
"~/src/vst3sdk"
|
||||
],
|
||||
@@ -16,6 +17,24 @@
|
||||
"intelliSenseMode": "linux-gcc-arm64",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
"configurationProvider": "ms-vscode.cmake-tools"
|
||||
},
|
||||
{
|
||||
"name": "Linux x86_64",
|
||||
"includePath": [
|
||||
"${default}",
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/src",
|
||||
"/usr/include/x86_64-linux-gnu",
|
||||
"/usr/include/lilv-0",
|
||||
"/usr/lib"
|
||||
],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "linux-gcc-x64",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
"configurationProvider": "ms-vscode.cmake-tools",
|
||||
"mergeConfigurations": true
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
||||
Vendored
+2
-1
@@ -137,7 +137,8 @@
|
||||
//"[json_variants]" // subtest of your choice, or none to run all of the tests.
|
||||
//"[inverting_mutex_test]"
|
||||
// "[utf8_to_utf32]"
|
||||
"[pipedal_alsa_test]"
|
||||
//"[pipedal_alsa_test]"
|
||||
"[wifi_channels_test]"
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.3.67
|
||||
VERSION 1.3.69
|
||||
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.67-Experimetnal")
|
||||
set (DISPLAY_VERSION "PiPedal v1.3.69-Release")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
@@ -104,7 +104,7 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi"
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, authbind, gpg" )
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, authbind, curl, gpg" )
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
|
||||
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||
|
||||
@@ -27,12 +27,12 @@ endif()
|
||||
# endif()
|
||||
|
||||
# nlgenl-3 library.
|
||||
execute_process(COMMAND ls /usr/include/libnl3/netlink/netlink.h RESULT_VARIABLE LNL3_MISSING OUTPUT_QUIET ERROR_QUIET)
|
||||
if(LNL3_MISSING)
|
||||
message(ERROR " Need to: sudo apt install libnl-3-dev libnl-genl-3-dev ")
|
||||
endif()
|
||||
set(LIBNL3_INCLUDE_DIRS /usr/include/libnl3)
|
||||
set(LIBNL3_LIBRARIES nl-3 nl-genl-3)
|
||||
# execute_process(COMMAND ls /usr/include/libnl3/netlink/netlink.h RESULT_VARIABLE LNL3_MISSING OUTPUT_QUIET ERROR_QUIET)
|
||||
# if(LNL3_MISSING)
|
||||
# message(ERROR " Need to: sudo apt install libnl-3-dev libnl-genl-3-dev ")
|
||||
# endif()
|
||||
# set(LIBNL3_INCLUDE_DIRS /usr/include/libnl3)
|
||||
# set(LIBNL3_LIBRARIES nl-3 nl-genl-3)
|
||||
|
||||
|
||||
|
||||
|
||||
+233
-198
@@ -18,6 +18,7 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "RegDb.hpp"
|
||||
|
||||
#include "ss.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
@@ -30,8 +31,12 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "json_variant.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#define REGDB_MAGIC 0x52474442
|
||||
#define REGDB_VERSION 19 // pre-bookwork
|
||||
#define REGDB_VERSION_2 20 // bookworm and later.
|
||||
@@ -40,7 +45,7 @@ using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
static std::unique_ptr<RegDb> g_Instance;
|
||||
RegDb&RegDb::GetInstance()
|
||||
RegDb &RegDb::GetInstance()
|
||||
{
|
||||
if (!g_Instance)
|
||||
{
|
||||
@@ -49,9 +54,9 @@ RegDb&RegDb::GetInstance()
|
||||
return *g_Instance;
|
||||
}
|
||||
|
||||
const WifiRegulations& RegDb::getWifiRegulations(const std::string&countryIso3661) const
|
||||
const WifiRegulations &RegDb::getWifiRegulations(const std::string &countryIso3661) const
|
||||
{
|
||||
for (const auto ®ulation: this->regulations)
|
||||
for (const auto ®ulation : this->regulations)
|
||||
{
|
||||
if (regulation.reg_alpha2 == countryIso3661)
|
||||
{
|
||||
@@ -61,28 +66,31 @@ const WifiRegulations& RegDb::getWifiRegulations(const std::string&countryIso366
|
||||
throw std::runtime_error("Invalid country code.");
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T NlSwap(T value)
|
||||
{
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return value;
|
||||
} else {
|
||||
auto value_rep = std::bit_cast<std::array<std::uint8_t,sizeof(T)>,T >(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto value_rep = std::bit_cast<std::array<std::uint8_t, sizeof(T)>, T>(value);
|
||||
std::ranges::reverse(value_rep);
|
||||
return std::bit_cast<T>(value_rep);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
class BigEndian
|
||||
{
|
||||
public:
|
||||
BigEndian(): m_value(0) { }
|
||||
explicit BigEndian(T value): m_value(NlSwap(value)) {}
|
||||
T value() const { return NlSwap(m_value);}
|
||||
bool operator==(const BigEndian<T>&other) const { return m_value == other.m_value; }
|
||||
BigEndian() : m_value(0) {}
|
||||
explicit BigEndian(T value) : m_value(NlSwap(value)) {}
|
||||
T value() const { return NlSwap(m_value); }
|
||||
bool operator==(const BigEndian<T> &other) const { return m_value == other.m_value; }
|
||||
explicit operator bool() const { return m_value != 0; }
|
||||
|
||||
private:
|
||||
T m_value;
|
||||
};
|
||||
@@ -141,17 +149,15 @@ struct RulesCollection19
|
||||
{
|
||||
this->ruleCount = NlSwap(this->ruleCount);
|
||||
}
|
||||
Rule*getRule(
|
||||
Rule *getRule(
|
||||
uint32_t nRule,
|
||||
char *pData
|
||||
)
|
||||
char *pData)
|
||||
{
|
||||
uint32_t ruleOffset = NlSwap(ruleOffsets[nRule]);
|
||||
return (Rule*)(pData+ruleOffset);
|
||||
}
|
||||
return (Rule *)(pData + ruleOffset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct FrequencyRange
|
||||
{
|
||||
uint32_t startFrequency; // in khz.
|
||||
@@ -203,7 +209,7 @@ RegDb::RegDb()
|
||||
{
|
||||
}
|
||||
RegDb::RegDb(const std::filesystem::path &path)
|
||||
: pData(nullptr)
|
||||
: pData(nullptr)
|
||||
{
|
||||
ifstream f(path);
|
||||
if (!f.is_open())
|
||||
@@ -255,23 +261,22 @@ void RegDb::Load19()
|
||||
{
|
||||
WifiRegulations wifiRegulations;
|
||||
|
||||
|
||||
CountryHeader countryHeader = pCountryHeader[i];
|
||||
countryHeader.toNs();
|
||||
wifiRegulations.reg_alpha2 = SS(countryHeader.alpha2[0] << countryHeader.alpha2[1]);
|
||||
wifiRegulations.reg_alpha2 = SS(countryHeader.alpha2[0] << countryHeader.alpha2[1]);
|
||||
wifiRegulations.dfs_region = (DfsRegion)(countryHeader.dfsRegion & 0x03);
|
||||
|
||||
RulesCollection19 *pRules = (RulesCollection19 *)(pData + countryHeader.rulesOffset);
|
||||
uint32_t ruleCount = NlSwap(pRules->ruleCount);
|
||||
for (uint32_t nRule = 0; nRule < ruleCount; ++nRule)
|
||||
{
|
||||
Rule *pRule = pRules->getRule(nRule,pData);
|
||||
{
|
||||
Rule *pRule = pRules->getRule(nRule, pData);
|
||||
Rule rule = *pRule;
|
||||
rule.toNs();
|
||||
|
||||
FrequencyRange fr = *(FrequencyRange *)(pData + rule.frequencyRangeOffset);
|
||||
fr.toNs();
|
||||
|
||||
|
||||
PowerRule pr = *(PowerRule *)(pData + rule.powerRuleOffset);
|
||||
pr.toNs();
|
||||
|
||||
@@ -291,7 +296,7 @@ void RegDb::Load19()
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// glue
|
||||
#define IEEE80211_NUM_ACS 4 // linux/ieee80211.h Number of hardware queues?
|
||||
#define IEEE80211_NUM_ACS 4 // linux/ieee80211.h Number of hardware queues?
|
||||
|
||||
#define __aligned(n)
|
||||
#define __packed
|
||||
@@ -305,16 +310,14 @@ using __be16 = BigEndian<uint16_t>;
|
||||
#define BIT(n) (1 << n)
|
||||
|
||||
template <typename T>
|
||||
T ALIGN(T value,int bits)
|
||||
T ALIGN(T value, int bits)
|
||||
{
|
||||
intptr_t v = (intptr_t)value;
|
||||
|
||||
intptr_t mask = (1 << bits)-1;
|
||||
intptr_t mask = (1 << bits) - 1;
|
||||
v = (v + mask) & (~mask);
|
||||
return (T)v;
|
||||
|
||||
}
|
||||
|
||||
|
||||
inline BigEndian<uint32_t> cpu_to_be32(uint32_t value)
|
||||
{
|
||||
@@ -326,196 +329,203 @@ inline BigEndian<uint16_t> cpu_to_be16(uint16_t value)
|
||||
return BigEndian<uint16_t>(value);
|
||||
}
|
||||
|
||||
|
||||
inline uint16_t be16_to_cpu(const BigEndian<uint16_t>&value)
|
||||
inline uint16_t be16_to_cpu(const BigEndian<uint16_t> &value)
|
||||
{
|
||||
return value.value();
|
||||
}
|
||||
inline uint32_t be32_to_cpu(const BigEndian<uint32_t>&value)
|
||||
inline uint32_t be32_to_cpu(const BigEndian<uint32_t> &value)
|
||||
{
|
||||
return value.value();
|
||||
}
|
||||
|
||||
bool regdb_has_valid_signature(const u8 *data, unsigned int size) { return true; }
|
||||
|
||||
#define offsetofend(TYPE,FIELD) ((uint32_t)(offsetof(TYPE,FIELD)+sizeof(((TYPE*)0)->FIELD)))
|
||||
#define offsetofend(TYPE, FIELD) ((uint32_t)(offsetof(TYPE, FIELD) + sizeof(((TYPE *)0)->FIELD)))
|
||||
#define ASSERT_RTNL() // in the original, assert that rtnl is not locked.
|
||||
|
||||
/////////////////////////
|
||||
/////////////////////////
|
||||
// shamelessly lifted from linux net/wireless/reg.c
|
||||
|
||||
struct fwdb_country {
|
||||
u8 alpha2[2];
|
||||
__be16 coll_ptr;
|
||||
/* this struct cannot be extended */
|
||||
struct fwdb_country
|
||||
{
|
||||
u8 alpha2[2];
|
||||
__be16 coll_ptr;
|
||||
/* this struct cannot be extended */
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct fwdb_collection {
|
||||
u8 len;
|
||||
u8 n_rules;
|
||||
u8 dfs_region;
|
||||
/* no optional data yet */
|
||||
/* aligned to 2, then followed by __be16 array of rule pointers */
|
||||
struct fwdb_collection
|
||||
{
|
||||
u8 len;
|
||||
u8 n_rules;
|
||||
u8 dfs_region;
|
||||
/* no optional data yet */
|
||||
/* aligned to 2, then followed by __be16 array of rule pointers */
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct fwdb_header {
|
||||
__be32 magic;
|
||||
__be32 version;
|
||||
struct fwdb_country country[];
|
||||
struct fwdb_header
|
||||
{
|
||||
__be32 magic;
|
||||
__be32 version;
|
||||
struct fwdb_country country[];
|
||||
} __packed __aligned(4);
|
||||
|
||||
|
||||
enum fwdb_flags {
|
||||
FWDB_FLAG_NO_OFDM = BIT(0),
|
||||
FWDB_FLAG_NO_OUTDOOR = BIT(1),
|
||||
FWDB_FLAG_DFS = BIT(2),
|
||||
FWDB_FLAG_NO_IR = BIT(3),
|
||||
FWDB_FLAG_AUTO_BW = BIT(4),
|
||||
enum fwdb_flags
|
||||
{
|
||||
FWDB_FLAG_NO_OFDM = BIT(0),
|
||||
FWDB_FLAG_NO_OUTDOOR = BIT(1),
|
||||
FWDB_FLAG_DFS = BIT(2),
|
||||
FWDB_FLAG_NO_IR = BIT(3),
|
||||
FWDB_FLAG_AUTO_BW = BIT(4),
|
||||
};
|
||||
|
||||
struct fwdb_wmm_ac {
|
||||
u8 ecw;
|
||||
u8 aifsn;
|
||||
__be16 cot;
|
||||
struct fwdb_wmm_ac
|
||||
{
|
||||
u8 ecw;
|
||||
u8 aifsn;
|
||||
__be16 cot;
|
||||
} __packed;
|
||||
|
||||
struct fwdb_wmm_rule {
|
||||
struct fwdb_wmm_ac client[IEEE80211_NUM_ACS];
|
||||
struct fwdb_wmm_ac ap[IEEE80211_NUM_ACS];
|
||||
struct fwdb_wmm_rule
|
||||
{
|
||||
struct fwdb_wmm_ac client[IEEE80211_NUM_ACS];
|
||||
struct fwdb_wmm_ac ap[IEEE80211_NUM_ACS];
|
||||
} __packed;
|
||||
|
||||
struct fwdb_rule {
|
||||
u8 len;
|
||||
u8 flags;
|
||||
__be16 max_eirp;
|
||||
__be32 start, end, max_bw;
|
||||
/* start of optional data */
|
||||
__be16 cac_timeout;
|
||||
__be16 wmm_ptr;
|
||||
struct fwdb_rule
|
||||
{
|
||||
u8 len;
|
||||
u8 flags;
|
||||
__be16 max_eirp;
|
||||
__be32 start, end, max_bw;
|
||||
/* start of optional data */
|
||||
__be16 cac_timeout;
|
||||
__be16 wmm_ptr;
|
||||
} __packed __aligned(4);
|
||||
|
||||
#define FWDB_MAGIC 0x52474442
|
||||
#define FWDB_VERSION 20
|
||||
|
||||
|
||||
static int ecw2cw(int ecw)
|
||||
{
|
||||
return (1 << ecw) - 1;
|
||||
return (1 << ecw) - 1;
|
||||
}
|
||||
|
||||
static bool valid_wmm(struct fwdb_wmm_rule *rule)
|
||||
{
|
||||
struct fwdb_wmm_ac *ac = (struct fwdb_wmm_ac *)rule;
|
||||
int i;
|
||||
struct fwdb_wmm_ac *ac = (struct fwdb_wmm_ac *)rule;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < IEEE80211_NUM_ACS * 2; i++) {
|
||||
u16 cw_min = ecw2cw((ac[i].ecw & 0xf0) >> 4);
|
||||
u16 cw_max = ecw2cw(ac[i].ecw & 0x0f);
|
||||
u8 aifsn = ac[i].aifsn;
|
||||
for (i = 0; i < IEEE80211_NUM_ACS * 2; i++)
|
||||
{
|
||||
u16 cw_min = ecw2cw((ac[i].ecw & 0xf0) >> 4);
|
||||
u16 cw_max = ecw2cw(ac[i].ecw & 0x0f);
|
||||
u8 aifsn = ac[i].aifsn;
|
||||
|
||||
if (cw_min >= cw_max)
|
||||
return false;
|
||||
if (cw_min >= cw_max)
|
||||
return false;
|
||||
|
||||
if (aifsn < 1)
|
||||
return false;
|
||||
}
|
||||
if (aifsn < 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr)
|
||||
{
|
||||
struct fwdb_rule *rule = (fwdb_rule *)(data + (rule_ptr << 2));
|
||||
struct fwdb_rule *rule = (fwdb_rule *)(data + (rule_ptr << 2));
|
||||
|
||||
if ((u8 *)rule + sizeof(rule->len) > data + size)
|
||||
return false;
|
||||
if ((u8 *)rule + sizeof(rule->len) > data + size)
|
||||
return false;
|
||||
|
||||
/* mandatory fields */
|
||||
if (rule->len < offsetofend(struct fwdb_rule, max_bw))
|
||||
return false;
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) {
|
||||
u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
|
||||
struct fwdb_wmm_rule *wmm;
|
||||
/* mandatory fields */
|
||||
if (rule->len < offsetofend(struct fwdb_rule, max_bw))
|
||||
return false;
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
|
||||
{
|
||||
u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
|
||||
struct fwdb_wmm_rule *wmm;
|
||||
|
||||
if (wmm_ptr + sizeof(struct fwdb_wmm_rule) > size)
|
||||
return false;
|
||||
if (wmm_ptr + sizeof(struct fwdb_wmm_rule) > size)
|
||||
return false;
|
||||
|
||||
wmm = (fwdb_wmm_rule *)(data + wmm_ptr);
|
||||
wmm = (fwdb_wmm_rule *)(data + wmm_ptr);
|
||||
|
||||
if (!valid_wmm(wmm))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (!valid_wmm(wmm))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool valid_country(const u8 *data, unsigned int size,
|
||||
const struct fwdb_country *country)
|
||||
const struct fwdb_country *country)
|
||||
{
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (struct fwdb_collection *)(data + ptr);
|
||||
__be16 *rules_ptr;
|
||||
unsigned int i;
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (struct fwdb_collection *)(data + ptr);
|
||||
__be16 *rules_ptr;
|
||||
unsigned int i;
|
||||
|
||||
/* make sure we can read len/n_rules */
|
||||
if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
|
||||
return false;
|
||||
/* make sure we can read len/n_rules */
|
||||
if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
|
||||
return false;
|
||||
|
||||
/* make sure base struct and all rules fit */
|
||||
if ((u8 *)coll + ALIGN(coll->len, 2) +
|
||||
(coll->n_rules * 2) > data + size)
|
||||
return false;
|
||||
/* make sure base struct and all rules fit */
|
||||
if ((u8 *)coll + ALIGN(coll->len, 2) +
|
||||
(coll->n_rules * 2) >
|
||||
data + size)
|
||||
return false;
|
||||
|
||||
/* mandatory fields must exist */
|
||||
if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
|
||||
return false;
|
||||
/* mandatory fields must exist */
|
||||
if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
|
||||
return false;
|
||||
|
||||
rules_ptr = (__be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
rules_ptr = (__be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
|
||||
for (i = 0; i < coll->n_rules; i++) {
|
||||
u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
|
||||
for (i = 0; i < coll->n_rules; i++)
|
||||
{
|
||||
u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
|
||||
|
||||
if (!valid_rule(data, size, rule_ptr))
|
||||
return false;
|
||||
}
|
||||
if (!valid_rule(data, size, rule_ptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool valid_regdb(const u8 *data, unsigned int size)
|
||||
{
|
||||
const struct fwdb_header *hdr = (fwdb_header *)data;
|
||||
const struct fwdb_country *country;
|
||||
const struct fwdb_header *hdr = (fwdb_header *)data;
|
||||
const struct fwdb_country *country;
|
||||
|
||||
if (size < sizeof(*hdr))
|
||||
return false;
|
||||
if (size < sizeof(*hdr))
|
||||
return false;
|
||||
|
||||
if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
|
||||
return false;
|
||||
if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
|
||||
return false;
|
||||
|
||||
if (hdr->version != cpu_to_be32(FWDB_VERSION))
|
||||
return false;
|
||||
if (hdr->version != cpu_to_be32(FWDB_VERSION))
|
||||
return false;
|
||||
|
||||
if (!regdb_has_valid_signature(data, size))
|
||||
return false;
|
||||
if (!regdb_has_valid_signature(data, size))
|
||||
return false;
|
||||
|
||||
country = &hdr->country[0];
|
||||
while ((u8 *)(country + 1) <= data + size) {
|
||||
if (!country->coll_ptr)
|
||||
break;
|
||||
if (!valid_country(data, size, country))
|
||||
return false;
|
||||
country++;
|
||||
}
|
||||
country = &hdr->country[0];
|
||||
while ((u8 *)(country + 1) <= data + size)
|
||||
{
|
||||
if (!country->coll_ptr)
|
||||
break;
|
||||
if (!valid_country(data, size, country))
|
||||
return false;
|
||||
country++;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_wmm_rule(const struct fwdb_header *db,
|
||||
const struct fwdb_country *country,
|
||||
const struct fwdb_rule *rule,
|
||||
WifiRule*rrule)
|
||||
const struct fwdb_country *country,
|
||||
const struct fwdb_rule *rule,
|
||||
WifiRule *rrule)
|
||||
{
|
||||
// not implemented.
|
||||
return;
|
||||
@@ -528,11 +538,11 @@ static void set_wmm_rule(const struct fwdb_header *db,
|
||||
// wmm = (fwdb_wmm_rule *)((u8 *)db + wmm_ptr);
|
||||
|
||||
// if (!valid_wmm(wmm)) {
|
||||
// std::cout
|
||||
// std::cout
|
||||
// << "Invalid regulatory WMM rule "
|
||||
// << be32_to_cpu(rule->start)
|
||||
// << be32_to_cpu(rule->start)
|
||||
// << "-" << be32_to_cpu(rule->end)
|
||||
// << " in domain " << country->alpha2[0] << country->alpha2[1]
|
||||
// << " in domain " << country->alpha2[0] << country->alpha2[1]
|
||||
// << std::endl;
|
||||
// return;
|
||||
// }
|
||||
@@ -553,90 +563,115 @@ static void set_wmm_rule(const struct fwdb_header *db,
|
||||
// rrule->has_wmm = true;
|
||||
// }
|
||||
|
||||
|
||||
static WifiRegulations regdb_load_country(const struct fwdb_header *db,
|
||||
const struct fwdb_country *country)
|
||||
const struct fwdb_country *country)
|
||||
{
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (fwdb_collection *)((u8 *)db + ptr);
|
||||
unsigned int i;
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (fwdb_collection *)((u8 *)db + ptr);
|
||||
unsigned int i;
|
||||
|
||||
WifiRegulations wifiRegulations;
|
||||
|
||||
wifiRegulations.reg_alpha2 = SS(country->alpha2[0] << country->alpha2[1]);
|
||||
wifiRegulations.dfs_region = (DfsRegion)(coll->dfs_region);
|
||||
unsigned int nRules = coll->n_rules;
|
||||
wifiRegulations.reg_alpha2 = SS(country->alpha2[0] << country->alpha2[1]);
|
||||
wifiRegulations.dfs_region = (DfsRegion)(coll->dfs_region);
|
||||
unsigned int nRules = coll->n_rules;
|
||||
|
||||
for (i = 0; i < nRules; i++) {
|
||||
const __be16 *rules_ptr = (const __be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
|
||||
struct fwdb_rule *rule = (fwdb_rule *)((u8 *)db + rule_ptr);
|
||||
for (i = 0; i < nRules; i++)
|
||||
{
|
||||
const __be16 *rules_ptr = (const __be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
|
||||
struct fwdb_rule *rule = (fwdb_rule *)((u8 *)db + rule_ptr);
|
||||
|
||||
WifiRule wifiRule;
|
||||
wifiRule.start_freq_khz = be32_to_cpu(rule->start);
|
||||
wifiRule.end_freq_khz = be32_to_cpu(rule->end);
|
||||
wifiRule.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
|
||||
wifiRule.end_freq_khz = be32_to_cpu(rule->end);
|
||||
wifiRule.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
|
||||
wifiRule.max_antenna_gain = 0;
|
||||
wifiRule.max_eirp = be16_to_cpu(rule->max_eirp);
|
||||
wifiRule.flags = RegRuleFlags::NONE;
|
||||
|
||||
if (rule->flags & FWDB_FLAG_NO_OFDM)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OFDM;
|
||||
if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OUTDOOR;
|
||||
if (rule->flags & FWDB_FLAG_DFS)
|
||||
wifiRule.flags |= RegRuleFlags::DFS;
|
||||
if (rule->flags & FWDB_FLAG_NO_IR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_IR;
|
||||
if (rule->flags & FWDB_FLAG_AUTO_BW)
|
||||
wifiRule.flags |= RegRuleFlags::AUTO_BW;
|
||||
if (rule->flags & FWDB_FLAG_NO_OFDM)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OFDM;
|
||||
if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OUTDOOR;
|
||||
if (rule->flags & FWDB_FLAG_DFS)
|
||||
wifiRule.flags |= RegRuleFlags::DFS;
|
||||
if (rule->flags & FWDB_FLAG_NO_IR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_IR;
|
||||
if (rule->flags & FWDB_FLAG_AUTO_BW)
|
||||
wifiRule.flags |= RegRuleFlags::AUTO_BW;
|
||||
|
||||
wifiRule.dfs_cac_ms = 0;
|
||||
wifiRule.dfs_cac_ms = 0;
|
||||
|
||||
/* handle optional data */
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
|
||||
wifiRule.dfs_cac_ms =
|
||||
1000 * be16_to_cpu(rule->cac_timeout);
|
||||
// if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
|
||||
// set_wmm_rule(db, country, rule, &wifiRule);
|
||||
/* handle optional data */
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
|
||||
wifiRule.dfs_cac_ms =
|
||||
1000 * be16_to_cpu(rule->cac_timeout);
|
||||
// if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
|
||||
// set_wmm_rule(db, country, rule, &wifiRule);
|
||||
wifiRegulations.rules.push_back(std::move(wifiRule));
|
||||
}
|
||||
}
|
||||
|
||||
return wifiRegulations;
|
||||
return wifiRegulations;
|
||||
}
|
||||
|
||||
|
||||
static std::vector<WifiRegulations> load_regdb(u8*pData)
|
||||
static std::vector<WifiRegulations> load_regdb(u8 *pData)
|
||||
{
|
||||
const struct fwdb_header *hdr = (const fwdb_header*)pData;
|
||||
const struct fwdb_country *country;
|
||||
const struct fwdb_header *hdr = (const fwdb_header *)pData;
|
||||
const struct fwdb_country *country;
|
||||
|
||||
ASSERT_RTNL();
|
||||
if (!pData)
|
||||
ASSERT_RTNL();
|
||||
if (!pData)
|
||||
{
|
||||
throw std::runtime_error("Regdb not loaded.");
|
||||
}
|
||||
std::vector<WifiRegulations> result;
|
||||
|
||||
country = &hdr->country[0];
|
||||
while (country->coll_ptr) {
|
||||
country = &hdr->country[0];
|
||||
while (country->coll_ptr)
|
||||
{
|
||||
result.push_back(regdb_load_country(hdr, country));
|
||||
country++;
|
||||
}
|
||||
country++;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void RegDb::Load20()
|
||||
{
|
||||
u8*data = (u8*)ptr();
|
||||
u8 *data = (u8 *)ptr();
|
||||
unsigned int size = (unsigned int)this->data.size();
|
||||
if (!valid_regdb(data,size))
|
||||
if (!valid_regdb(data, size))
|
||||
{
|
||||
throw std::runtime_error("Invalid file format.");
|
||||
|
||||
}
|
||||
this->regulations = load_regdb(data);
|
||||
this->regulations = load_regdb(data);
|
||||
this->isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static std::map<std::string,std::string> getIsoNamesDict(const std::filesystem::path&filename)
|
||||
{
|
||||
std::map<std::string,std::string> result;
|
||||
std::ifstream f(filename);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error("Can't open WiFi regulatory domain names file.");
|
||||
}
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
return result;
|
||||
}
|
||||
std::map<std::string,std::string> RegDb::getRegulatoryDomains(const std::filesystem::path&isoFilenamesfile) const
|
||||
{
|
||||
std::map<std::string,std::string> keyToNameDict = getIsoNamesDict(isoFilenamesfile);
|
||||
std::map<std::string,std::string> result;
|
||||
|
||||
for (const auto ®ulation : this->regulations)
|
||||
{
|
||||
if (keyToNameDict.contains(regulation.reg_alpha2))
|
||||
{
|
||||
result[regulation.reg_alpha2] = keyToNameDict[regulation.reg_alpha2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace pipedal
|
||||
|
||||
const WifiRegulations&getWifiRegulations(const std::string&countryIso3661) const;
|
||||
|
||||
std::map<std::string,std::string> getRegulatoryDomains(const std::filesystem::path&namesFile="/etc/pipedal/config/iso_codes.json") const;
|
||||
private:
|
||||
bool isValid = false;
|
||||
|
||||
|
||||
@@ -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: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.66</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.69</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
@@ -15,7 +15,7 @@ Documentation: [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.66 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.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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"AF": "Afghanistan",
|
||||
"AX": "Aland Islands",
|
||||
"AL": "Albania",
|
||||
"DZ": "Algeria",
|
||||
"AS": "American Samoa",
|
||||
"AD": "Andorra",
|
||||
"AO": "Angola",
|
||||
"AI": "Anguilla",
|
||||
"AQ": "Antarctica",
|
||||
"AG": "Antigua And Barbuda",
|
||||
"AR": "Argentina",
|
||||
"AM": "Armenia",
|
||||
"AW": "Aruba",
|
||||
"AU": "Australia",
|
||||
"AT": "Austria",
|
||||
"AZ": "Azerbaijan",
|
||||
"BS": "Bahamas",
|
||||
"BH": "Bahrain",
|
||||
"BD": "Bangladesh",
|
||||
"BB": "Barbados",
|
||||
"BY": "Belarus",
|
||||
"BE": "Belgium",
|
||||
"BZ": "Belize",
|
||||
"BJ": "Benin",
|
||||
"BM": "Bermuda",
|
||||
"BT": "Bhutan",
|
||||
"BO": "Bolivia",
|
||||
"BA": "Bosnia And Herzegovina",
|
||||
"BW": "Botswana",
|
||||
"BV": "Bouvet Island",
|
||||
"BR": "Brazil",
|
||||
"IO": "British Indian Ocean Territory",
|
||||
"BN": "Brunei Darussalam",
|
||||
"BG": "Bulgaria",
|
||||
"BF": "Burkina Faso",
|
||||
"BI": "Burundi",
|
||||
"KH": "Cambodia",
|
||||
"CM": "Cameroon",
|
||||
"CA": "Canada",
|
||||
"CV": "Cape Verde",
|
||||
"KY": "Cayman Islands",
|
||||
"CF": "Central African Republic",
|
||||
"TD": "Chad",
|
||||
"CL": "Chile",
|
||||
"CN": "China",
|
||||
"CX": "Christmas Island",
|
||||
"CC": "Cocos (Keeling) Islands",
|
||||
"CO": "Colombia",
|
||||
"KM": "Comoros",
|
||||
"CG": "Congo",
|
||||
"CD": "Congo, Democratic Republic",
|
||||
"CK": "Cook Islands",
|
||||
"CR": "Costa Rica",
|
||||
"CI": "Cote D\"Ivoire",
|
||||
"HR": "Croatia",
|
||||
"CU": "Cuba",
|
||||
"CY": "Cyprus",
|
||||
"CZ": "Czech Republic",
|
||||
"DK": "Denmark",
|
||||
"DJ": "Djibouti",
|
||||
"DM": "Dominica",
|
||||
"DO": "Dominican Republic",
|
||||
"EC": "Ecuador",
|
||||
"EG": "Egypt",
|
||||
"SV": "El Salvador",
|
||||
"GQ": "Equatorial Guinea",
|
||||
"ER": "Eritrea",
|
||||
"EE": "Estonia",
|
||||
"ET": "Ethiopia",
|
||||
"FK": "Falkland Islands (Malvinas)",
|
||||
"FO": "Faroe Islands",
|
||||
"FJ": "Fiji",
|
||||
"FI": "Finland",
|
||||
"FR": "France",
|
||||
"GF": "French Guiana",
|
||||
"PF": "French Polynesia",
|
||||
"TF": "French Southern Territories",
|
||||
"GA": "Gabon",
|
||||
"GM": "Gambia",
|
||||
"GE": "Georgia",
|
||||
"DE": "Germany",
|
||||
"GH": "Ghana",
|
||||
"GI": "Gibraltar",
|
||||
"GR": "Greece",
|
||||
"GL": "Greenland",
|
||||
"GD": "Grenada",
|
||||
"GP": "Guadeloupe",
|
||||
"GU": "Guam",
|
||||
"GT": "Guatemala",
|
||||
"GG": "Guernsey",
|
||||
"GN": "Guinea",
|
||||
"GW": "Guinea-Bissau",
|
||||
"GY": "Guyana",
|
||||
"HT": "Haiti",
|
||||
"HM": "Heard Island & Mcdonald Islands",
|
||||
"VA": "Holy See (Vatican City State)",
|
||||
"HN": "Honduras",
|
||||
"HK": "Hong Kong",
|
||||
"HU": "Hungary",
|
||||
"IS": "Iceland",
|
||||
"IN": "India",
|
||||
"ID": "Indonesia",
|
||||
"IR": "Iran, Islamic Republic Of",
|
||||
"IQ": "Iraq",
|
||||
"IE": "Ireland",
|
||||
"IM": "Isle Of Man",
|
||||
"IL": "Israel",
|
||||
"IT": "Italy",
|
||||
"JM": "Jamaica",
|
||||
"JP": "Japan",
|
||||
"JE": "Jersey",
|
||||
"JO": "Jordan",
|
||||
"KZ": "Kazakhstan",
|
||||
"KE": "Kenya",
|
||||
"KI": "Kiribati",
|
||||
"KR": "Korea",
|
||||
"KP": "North Korea",
|
||||
"KW": "Kuwait",
|
||||
"KG": "Kyrgyzstan",
|
||||
"LA": "Lao People\"s Democratic Republic",
|
||||
"LV": "Latvia",
|
||||
"LB": "Lebanon",
|
||||
"LS": "Lesotho",
|
||||
"LR": "Liberia",
|
||||
"LY": "Libyan Arab Jamahiriya",
|
||||
"LI": "Liechtenstein",
|
||||
"LT": "Lithuania",
|
||||
"LU": "Luxembourg",
|
||||
"MO": "Macao",
|
||||
"MK": "Macedonia",
|
||||
"MG": "Madagascar",
|
||||
"MW": "Malawi",
|
||||
"MY": "Malaysia",
|
||||
"MV": "Maldives",
|
||||
"ML": "Mali",
|
||||
"MT": "Malta",
|
||||
"MH": "Marshall Islands",
|
||||
"MQ": "Martinique",
|
||||
"MR": "Mauritania",
|
||||
"MU": "Mauritius",
|
||||
"YT": "Mayotte",
|
||||
"MX": "Mexico",
|
||||
"FM": "Micronesia, Federated States Of",
|
||||
"MD": "Moldova",
|
||||
"MC": "Monaco",
|
||||
"MN": "Mongolia",
|
||||
"ME": "Montenegro",
|
||||
"MS": "Montserrat",
|
||||
"MA": "Morocco",
|
||||
"MZ": "Mozambique",
|
||||
"MM": "Myanmar",
|
||||
"NA": "Namibia",
|
||||
"NR": "Nauru",
|
||||
"NP": "Nepal",
|
||||
"NL": "Netherlands",
|
||||
"AN": "Netherlands Antilles",
|
||||
"NC": "New Caledonia",
|
||||
"NZ": "New Zealand",
|
||||
"NI": "Nicaragua",
|
||||
"NE": "Niger",
|
||||
"NG": "Nigeria",
|
||||
"NU": "Niue",
|
||||
"NF": "Norfolk Island",
|
||||
"MP": "Northern Mariana Islands",
|
||||
"NO": "Norway",
|
||||
"OM": "Oman",
|
||||
"PK": "Pakistan",
|
||||
"PW": "Palau",
|
||||
"PS": "Palestinian Territory, Occupied",
|
||||
"PA": "Panama",
|
||||
"PG": "Papua New Guinea",
|
||||
"PY": "Paraguay",
|
||||
"PE": "Peru",
|
||||
"PH": "Philippines",
|
||||
"PN": "Pitcairn",
|
||||
"PL": "Poland",
|
||||
"PT": "Portugal",
|
||||
"PR": "Puerto Rico",
|
||||
"QA": "Qatar",
|
||||
"RE": "Reunion",
|
||||
"RO": "Romania",
|
||||
"RU": "Russian Federation",
|
||||
"RW": "Rwanda",
|
||||
"BL": "Saint Barthelemy",
|
||||
"SH": "Saint Helena",
|
||||
"KN": "Saint Kitts And Nevis",
|
||||
"LC": "Saint Lucia",
|
||||
"MF": "Saint Martin",
|
||||
"PM": "Saint Pierre And Miquelon",
|
||||
"VC": "Saint Vincent And Grenadines",
|
||||
"WS": "Samoa",
|
||||
"SM": "San Marino",
|
||||
"ST": "Sao Tome And Principe",
|
||||
"SA": "Saudi Arabia",
|
||||
"SN": "Senegal",
|
||||
"RS": "Serbia",
|
||||
"SC": "Seychelles",
|
||||
"SL": "Sierra Leone",
|
||||
"SG": "Singapore",
|
||||
"SK": "Slovakia",
|
||||
"SI": "Slovenia",
|
||||
"SB": "Solomon Islands",
|
||||
"SO": "Somalia",
|
||||
"ZA": "South Africa",
|
||||
"GS": "South Georgia And Sandwich Isl.",
|
||||
"ES": "Spain",
|
||||
"LK": "Sri Lanka",
|
||||
"SD": "Sudan",
|
||||
"SR": "Suriname",
|
||||
"SJ": "Svalbard And Jan Mayen",
|
||||
"SZ": "Swaziland",
|
||||
"SE": "Sweden",
|
||||
"CH": "Switzerland",
|
||||
"SY": "Syrian Arab Republic",
|
||||
"TW": "Taiwan",
|
||||
"TJ": "Tajikistan",
|
||||
"TZ": "Tanzania",
|
||||
"TH": "Thailand",
|
||||
"TL": "Timor-Leste",
|
||||
"TG": "Togo",
|
||||
"TK": "Tokelau",
|
||||
"TO": "Tonga",
|
||||
"TT": "Trinidad And Tobago",
|
||||
"TN": "Tunisia",
|
||||
"TR": "Turkey",
|
||||
"TM": "Turkmenistan",
|
||||
"TC": "Turks And Caicos Islands",
|
||||
"TV": "Tuvalu",
|
||||
"UG": "Uganda",
|
||||
"UA": "Ukraine",
|
||||
"AE": "United Arab Emirates",
|
||||
"GB": "United Kingdom",
|
||||
"US": "United States",
|
||||
"UM": "United States Outlying Islands",
|
||||
"UY": "Uruguay",
|
||||
"UZ": "Uzbekistan",
|
||||
"VU": "Vanuatu",
|
||||
"VE": "Venezuela",
|
||||
"VN": "Vietnam",
|
||||
"VG": "Virgin Islands, British",
|
||||
"VI": "Virgin Islands, U.S.",
|
||||
"WF": "Wallis And Futuna",
|
||||
"EH": "Western Sahara",
|
||||
"YE": "Yemen",
|
||||
"ZM": "Zambia",
|
||||
"ZW": "Zimbabwe"
|
||||
}
|
||||
Vendored
+1
-3
@@ -1,12 +1,10 @@
|
||||
Architecture: arm64
|
||||
Depends: libstdc++6 (>= 9), libasound2 (>= 1.0.17), libc6 (>= 2.34), libgcc-s1 (>= 4.0), libjack-jackd2-0 (>= 1.9.10+20150825) | libjack-0.125, liblilv-0-0 (>= 0.22.0~dfsg0), libnl-3-200 (>= 3.2.7), libnl-genl-3-200 (>= 3.2.7), libstdc++6 (>= 11), libsystemd0, dnsmasq(>= 2.85),lv2-dev(>=1.14)
|
||||
Depends: dnsmasq(>= 2.85),lv2-dev(>=1.14), iw
|
||||
Description: IoT guitar effect for Raspberry Pi
|
||||
IoT guitar effect pedal for Raspberry Pi, with phone-friendly web interface.
|
||||
Homepage: https://github.com/rerdavies/pipedal
|
||||
Maintainer: Robin E. R. Davies<rerdavies@gmail.com>
|
||||
Source: pipedal
|
||||
Package: pipedal
|
||||
Pre-Depends: hostapd;authbind;dnsmasq
|
||||
Priority: optional
|
||||
Section: sound
|
||||
Version: 1.2.32
|
||||
|
||||
+3
-3
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (64-bit) v1.3.66](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [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)
|
||||
|
||||
Version 1.3.66 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
|
||||
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
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install pipedal_1.3.66_arm64.deb
|
||||
sudo apt-get install pipedal_1.3.69_arm64.deb
|
||||
```
|
||||
Adjust accordingly if you have downloaded v1.1.31.
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 1.3.69
|
||||
|
||||
Bug fixes:
|
||||
- Fix file uploads for TooB NAM and TooB Convolution Reverb.
|
||||
- Load actual list of Wi-Fi regulatory domains from regulatory database.
|
||||
- Reload channel list after selecting a new Wi-Fi regulatory domain.
|
||||
- Hotspot configuration dialog: validate country code and channel selection.
|
||||
- Add `--fix-permissions` and `-list-wifi-country-codes options` to `pipedalconfig`
|
||||
- Correct error in `wifi_latency_test` help text.
|
||||
- Cleaned up Debian package dependencies.
|
||||
- Correct bug with mDNS service announcements after a name collision.
|
||||
- Cleaned up build prerequisite documentation for PiPedal and ToobAmp projects.
|
||||
- Clean service shutdown: avoid double-close of web server sockets.
|
||||
- Numerous fixes for Ubuntu 24.04 LTS builds for arm64 and amd64. (experimental)
|
||||
|
||||
## PiPedal 1.3.66 Release
|
||||
|
||||
Bug fixes:
|
||||
|
||||
+2
-2
@@ -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.66/pipedal_1.3.66_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.66</a>
|
||||
- <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>
|
||||
|
||||
|
||||
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.66_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.3.69_arm64.deb
|
||||
```
|
||||
|
||||
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
<img src="GithubBanner.png" width="100%"/>
|
||||
<a href="Installing.html"><i>v1.3.66</i></a>
|
||||
<a href="Installing.html"><i>v1.3.69</i></a>
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ 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/).
|
||||
|
||||
#### NEW version 1.3.66 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
#### NEW version 1.3.69 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
|
||||
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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB Input";
|
||||
|
||||
@@ -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 47 ;
|
||||
lv2:microVersion 53 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
A straightforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger.
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
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.
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
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.
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
A port of Steven Atkinson's Neural Amp Modeler to LV2.
|
||||
|
||||
|
||||
@@ -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 52 ;
|
||||
lv2:microVersion 53 ;
|
||||
rdfs:comment """
|
||||
TooB Tuner is a chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"max_upload_size": 536870912,
|
||||
"fakeAndroid": false,
|
||||
"ui_plugins": [],
|
||||
"enable_auto_update": true
|
||||
"enable_auto_update": true,
|
||||
"has_wifi_device": false
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import Button from '@mui/material/Button';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
import AudioFileIcon from '@mui/icons-material/AudioFile';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
|
||||
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
@@ -105,6 +105,7 @@ export interface FilePropertyDialogState {
|
||||
canDelete: boolean;
|
||||
fileResult: FileRequestResult;
|
||||
navDirectory: string;
|
||||
uploadDirectory: string;
|
||||
isProtectedDirectory: boolean;
|
||||
columns: number;
|
||||
columnWidth: number;
|
||||
@@ -187,12 +188,14 @@ export default withStyles(styles, { withTheme: true })(
|
||||
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
let selectedFile = props.selectedFile;
|
||||
this.state = {
|
||||
fullScreen: this.getFullScreen(),
|
||||
selectedFile: props.selectedFile,
|
||||
selectedFile: selectedFile,
|
||||
selectedFileProtected: true,
|
||||
selectedFileIsDirectory: false,
|
||||
navDirectory: this.getNavDirectoryFromFile(props.selectedFile, props.fileProperty),
|
||||
navDirectory: this.getNavDirectoryFromFile(selectedFile, props.fileProperty),
|
||||
uploadDirectory: "",
|
||||
hasSelection: false,
|
||||
canDelete: false,
|
||||
columns: 0,
|
||||
@@ -256,7 +259,8 @@ export default withStyles(styles, { withTheme: true })(
|
||||
fileResult: filesResult,
|
||||
hasSelection: !!fileEntry,
|
||||
selectedFileProtected: fileEntry ? fileEntry.isProtected: true,
|
||||
navDirectory: navPath
|
||||
navDirectory: navPath,
|
||||
uploadDirectory: filesResult.currentDirectory
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
@@ -300,6 +304,8 @@ export default withStyles(styles, { withTheme: true })(
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
this.requestFiles(this.state.navDirectory)
|
||||
this.requestScroll = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
@@ -310,14 +316,16 @@ export default withStyles(styles, { withTheme: true })(
|
||||
super.componentDidUpdate?.(prevProps, prevState, snapshot);
|
||||
if (prevProps.open !== this.props.open || prevProps.fileProperty !== this.props.fileProperty || prevProps.selectedFile !== this.props.selectedFile) {
|
||||
if (this.props.open) {
|
||||
let navDirectory = this.getNavDirectoryFromFile(this.props.selectedFile, this.props.fileProperty);
|
||||
let selectedFile = this.props.selectedFile;
|
||||
let navDirectory = this.getNavDirectoryFromFile(selectedFile, this.props.fileProperty);
|
||||
this.setState({
|
||||
selectedFile: this.props.selectedFile,
|
||||
selectedFile: selectedFile,
|
||||
selectedFileIsDirectory: false,
|
||||
selectedFileProtected: true,
|
||||
newFolderDialogOpen: false,
|
||||
renameDialogOpen: false,
|
||||
moveDialogOpen: false
|
||||
moveDialogOpen: false,
|
||||
navDirectory: navDirectory
|
||||
});
|
||||
this.requestFiles(navDirectory)
|
||||
this.requestScroll = true;
|
||||
@@ -549,7 +557,7 @@ export default withStyles(styles, { withTheme: true })(
|
||||
}
|
||||
if (fileEntry.isDirectory) {
|
||||
return (
|
||||
<FolderIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
||||
<FolderOutlinedIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
||||
);
|
||||
}
|
||||
if (isAudioFile(fileEntry.pathname)) {
|
||||
@@ -741,7 +749,7 @@ export default withStyles(styles, { withTheme: true })(
|
||||
}
|
||||
}
|
||||
uploadPage={
|
||||
"uploadUserFile?directory=" + encodeURIComponent(this.state.navDirectory)
|
||||
"uploadUserFile?directory=" + encodeURIComponent(this.state.uploadDirectory)
|
||||
+ "&ext="
|
||||
+ encodeURIComponent(this.getFileExtensionList(this.props.fileProperty))
|
||||
}
|
||||
|
||||
+147
-246
@@ -56,6 +56,18 @@ export enum State {
|
||||
HotspotChanging,
|
||||
};
|
||||
|
||||
function getErrorMessage(error: any)
|
||||
{
|
||||
if (error instanceof Error)
|
||||
{
|
||||
return (error as Error).message;
|
||||
}
|
||||
if (!error)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return error.toString();
|
||||
}
|
||||
class UpdatedError extends Error {
|
||||
};
|
||||
export function wantsReloadingScreen(state: State) {
|
||||
@@ -87,6 +99,7 @@ export class FileRequestResult {
|
||||
files: FileEntry[] = [];
|
||||
isProtected: boolean = false;
|
||||
breadcrumbs: BreadcrumbEntry[] = [];
|
||||
currentDirectory: string = "";
|
||||
};
|
||||
|
||||
export type PluginPresetsChangedHandler = (pluginUri: string) => void;
|
||||
@@ -394,7 +407,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
webSocket?: PiPedalSocket;
|
||||
|
||||
|
||||
|
||||
hasWifiDevice: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||
onSnapshotModified: ObservableEvent<SnapshotModifiedEvent> = new ObservableEvent<SnapshotModifiedEvent>();
|
||||
|
||||
ui_plugins: ObservableProperty<UiPlugin[]>
|
||||
@@ -742,6 +755,10 @@ export class PiPedalModel //implements PiPedalModel
|
||||
} else if (message === "onNetworkChanging") {
|
||||
this.onNetworkChanging(body as boolean);
|
||||
}
|
||||
else if (message === "onHasWifiChanged") {
|
||||
let hasWifi = body as boolean;
|
||||
this.hasWifiDevice.set(hasWifi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -912,7 +929,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
}
|
||||
}
|
||||
onSocketReconnected() {
|
||||
async onSocketReconnected(): Promise<void> {
|
||||
this.cancelOnNetworkChanging();
|
||||
this.cancelAndroidReconnectTimer();
|
||||
|
||||
@@ -920,97 +937,20 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.androidHost?.setDisconnected(false);
|
||||
}
|
||||
|
||||
this.expectDisconnect(ReconnectReason.Disconnected); // the next expected disconnect will be an actual disconnect.
|
||||
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||
|
||||
|
||||
// reload state, but not configuration.
|
||||
this.getWebSocket().request<number>("hello")
|
||||
.then(clientId => {
|
||||
this.clientId = clientId;
|
||||
return this.getUpdateStatus(); // detects whether server has been upgraded.
|
||||
})
|
||||
.then((updateStatus) => {
|
||||
this.clientId = await this.getWebSocket().request<number>("hello");
|
||||
|
||||
return this.getWebSocket().request<any>("plugins");
|
||||
})
|
||||
.then(data => {
|
||||
this.ui_plugins.set(UiPlugin.deserialize_array(data));
|
||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||
})
|
||||
.then(data => {
|
||||
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||
let newServerVersion = this.serverVersion = await this.getWebSocket().request<PiPedalVersion>("version");
|
||||
if (newServerVersion.serverVersion !== this.serverVersion.serverVersion)
|
||||
{
|
||||
this.reloadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
return this.getWebSocket().request<boolean>("getShowStatusMonitor");
|
||||
})
|
||||
.then(data => {
|
||||
this.showStatusMonitor.set(data);
|
||||
|
||||
return this.getWebSocket().request<any>("getWifiConfigSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data));
|
||||
|
||||
|
||||
return this.getWebSocket().request<any>("getWifiDirectConfigSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.wifiDirectConfigSettings.set(new WifiDirectConfigSettings().deserialize(data));
|
||||
|
||||
|
||||
return this.getWebSocket().request<any>("getGovernorSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.governorSettings.set(new GovernorSettings().deserialize(data));
|
||||
|
||||
|
||||
return this.getWebSocket().request<any>("getJackServerSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.jackServerSettings.set(new JackServerSettings().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<PresetIndex>("getPresets");
|
||||
})
|
||||
.then((data) => {
|
||||
this.presets.set(new PresetIndex().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<JackConfiguration>("getJackConfiguration");
|
||||
})
|
||||
.then((data) => {
|
||||
// data.isValid = false;
|
||||
// data.errorState = "Jack Audio server not running."
|
||||
|
||||
this.jackConfiguration.set(new JackConfiguration().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<any>("getJackSettings");
|
||||
})
|
||||
.then((data) => {
|
||||
this.jackSettings.set(new JackChannelSelection().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<any>("getBankIndex");
|
||||
})
|
||||
.then((data) => {
|
||||
this.banks.set(new BankIndex().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<FavoritesList>("getFavorites");
|
||||
})
|
||||
.then((data) => {
|
||||
this.favorites.set(data);
|
||||
|
||||
return this.getWebSocket().request<MidiBinding[]>("getSystemMidiBindings");
|
||||
})
|
||||
.then((data) => {
|
||||
let bindings = MidiBinding.deserialize_array(data);
|
||||
this.systemMidiBindings.set(bindings);
|
||||
|
||||
this.setState(State.Ready);
|
||||
})
|
||||
.catch((what) => {
|
||||
if (what instanceof UpdatedError) {
|
||||
// do nothing. a page reload is imminent and unavoidable as soon as we return to the dispatcher.
|
||||
} else {
|
||||
this.onError(what.toString());
|
||||
}
|
||||
})
|
||||
// anything could have changed while we were disconnected.
|
||||
await this.loadServerState();
|
||||
}
|
||||
makeSocketServerUrl(hostName: string, port: number): string {
|
||||
return "ws://" + hostName + ":" + port + "/pipedal";
|
||||
@@ -1026,178 +966,139 @@ export class PiPedalModel //implements PiPedalModel
|
||||
debug: boolean = false;
|
||||
enableAutoUpdate: boolean = false;
|
||||
|
||||
requestConfig(): Promise<boolean> {
|
||||
const myRequest = new Request(this.varRequest('config.json'));
|
||||
return fetch(myRequest)
|
||||
.then(
|
||||
(response) => {
|
||||
return response.json();
|
||||
}
|
||||
)
|
||||
.then(data => {
|
||||
this.enableAutoUpdate = !!data.enable_auto_update;
|
||||
if (data.max_upload_size) {
|
||||
this.maxPresetUploadSize = data.max_upload_size;
|
||||
}
|
||||
if (data.fakeAndroid) {
|
||||
this.androidHost = new FakeAndroidHost();
|
||||
}
|
||||
this.debug = !!data.debug;
|
||||
let { socket_server_port, socket_server_address, max_upload_size } = data;
|
||||
if ((!socket_server_address) || socket_server_address === "*") {
|
||||
socket_server_address = window.location.hostname;
|
||||
}
|
||||
if (!socket_server_port) socket_server_port = 8080;
|
||||
let socket_server = this.makeSocketServerUrl(socket_server_address, socket_server_port);
|
||||
let var_server_url = this.makeVarServerUrl("http", socket_server_address, socket_server_port);
|
||||
|
||||
async requestConfig(): Promise<boolean> {
|
||||
|
||||
this.socketServerUrl = socket_server;
|
||||
this.varServerUrl = var_server_url;
|
||||
this.maxFileUploadSize = parseInt(max_upload_size);
|
||||
try {
|
||||
const myRequest = new Request(this.varRequest('config.json'));
|
||||
let response: Response = await fetch(myRequest);
|
||||
let data = await response.json();
|
||||
|
||||
this.webSocket = new PiPedalSocket(
|
||||
this.socketServerUrl,
|
||||
{
|
||||
onMessageReceived: this.onSocketMessage,
|
||||
onError: this.onSocketError,
|
||||
onConnectionLost: this.onSocketConnectionLost,
|
||||
onReconnect: this.onSocketReconnected,
|
||||
onReconnecting: this.onSocketReconnecting
|
||||
}
|
||||
);
|
||||
return this.webSocket.connect();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setError("Failed to connect to server. (" + this.socketServerUrl + ")");
|
||||
return false;
|
||||
})
|
||||
.then(() => {
|
||||
return this.getUpdateStatus();
|
||||
})
|
||||
.then((updateStatus) => {
|
||||
const isoRequest = new Request('iso_codes.json');
|
||||
return fetch(isoRequest);
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((countryCodes) => {
|
||||
this.countryCodes = countryCodes as { [Name: string]: string };
|
||||
this.enableAutoUpdate = !!data.enable_auto_update;
|
||||
this.hasWifiDevice.set(!!data.has_wifi_device);
|
||||
if (data.max_upload_size) {
|
||||
this.maxPresetUploadSize = data.max_upload_size;
|
||||
}
|
||||
if (data.fakeAndroid) {
|
||||
this.androidHost = new FakeAndroidHost();
|
||||
}
|
||||
this.debug = !!data.debug;
|
||||
let { socket_server_port, socket_server_address, max_upload_size } = data;
|
||||
if ((!socket_server_address) || socket_server_address === "*") {
|
||||
socket_server_address = window.location.hostname;
|
||||
}
|
||||
if (!socket_server_port) socket_server_port = 8080;
|
||||
let socket_server = this.makeSocketServerUrl(socket_server_address, socket_server_port);
|
||||
let var_server_url = this.makeVarServerUrl("http", socket_server_address, socket_server_port);
|
||||
|
||||
this.socketServerUrl = socket_server;
|
||||
this.varServerUrl = var_server_url;
|
||||
this.maxFileUploadSize = parseInt(max_upload_size);
|
||||
} catch (error: any)
|
||||
{
|
||||
this.setError("Can't connect to server. " + getErrorMessage(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.getWebSocket().request<number>("hello");
|
||||
})
|
||||
.then((clientId) => {
|
||||
this.clientId = clientId;
|
||||
this.webSocket = new PiPedalSocket(
|
||||
this.socketServerUrl,
|
||||
{
|
||||
onMessageReceived: this.onSocketMessage,
|
||||
onError: this.onSocketError,
|
||||
onConnectionLost: this.onSocketConnectionLost,
|
||||
onReconnect: this.onSocketReconnected,
|
||||
onReconnecting: this.onSocketReconnecting
|
||||
}
|
||||
);
|
||||
|
||||
return this.getWebSocket().request<string>("imageList");
|
||||
})
|
||||
.then((data) => {
|
||||
this.preloadImages(data);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setError("Failed to connect to server. " + error.toString());
|
||||
return false;
|
||||
})
|
||||
.then((succeeded) => {
|
||||
if (!succeeded) return false;
|
||||
return this.getWebSocket().request<PiPedalVersion>("version").then(data => {
|
||||
this.serverVersion = data;
|
||||
return this.getWebSocket().request<any>("plugins");
|
||||
})
|
||||
.then(data => {
|
||||
this.ui_plugins.set(UiPlugin.deserialize_array(data));
|
||||
try {
|
||||
await this.webSocket.connect();
|
||||
} catch (error) {
|
||||
this.setError("Failed to connect to server. (" + getErrorMessage(error));
|
||||
return false;
|
||||
|
||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||
})
|
||||
.then(data => {
|
||||
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||
}
|
||||
try {
|
||||
this.countryCodes = await this.getWebSocket().request<{ [Name: string]: string }>("getWifiRegulatoryDomains");
|
||||
|
||||
return this.getWebSocket().request<any>("pluginClasses");
|
||||
})
|
||||
.then((data) => {
|
||||
this.clientId = (await this.getWebSocket().request<number>("hello")) as number;
|
||||
|
||||
this.plugin_classes.set(new PluginClass().deserialize(data));
|
||||
this.validatePluginClasses(this.plugin_classes.get());
|
||||
this.preloadImages( (await this.getWebSocket().request<string>("imageList")));
|
||||
} catch (error) {
|
||||
this.setError("Failed to establish connection. " + getErrorMessage(error));
|
||||
return false;
|
||||
}
|
||||
return await this.loadServerState();
|
||||
}
|
||||
async loadServerState() : Promise<boolean>
|
||||
{
|
||||
try
|
||||
{
|
||||
this.serverVersion = await this.getWebSocket().request<PiPedalVersion>("version");
|
||||
|
||||
return this.getWebSocket().request<PresetIndex>("getPresets");
|
||||
})
|
||||
.then((data) => {
|
||||
this.presets.set(new PresetIndex().deserialize(data));
|
||||
this.updateStatus.set(new UpdateStatus().deserialize(await this.getUpdateStatus()));
|
||||
|
||||
return this.getWebSocket().request<any>("getWifiConfigSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data));
|
||||
this.hasWifiDevice.set(await this.getWebSocket().request<boolean>("getHasWifi"));
|
||||
|
||||
this.ui_plugins.set(
|
||||
UiPlugin.deserialize_array(await this.getWebSocket().request<any>("plugins"))
|
||||
);
|
||||
this.setModelPedalboard(
|
||||
new Pedalboard().deserialize(
|
||||
await this.getWebSocket().request<Pedalboard>("currentPedalboard")
|
||||
)
|
||||
);
|
||||
this.plugin_classes.set(new PluginClass().deserialize(
|
||||
await this.getWebSocket().request<any>("pluginClasses")
|
||||
));
|
||||
this.validatePluginClasses(this.plugin_classes.get());
|
||||
|
||||
return this.getWebSocket().request<any>("getWifiDirectConfigSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.wifiDirectConfigSettings.set(new WifiDirectConfigSettings().deserialize(data));
|
||||
this.presets.set(
|
||||
new PresetIndex().deserialize(
|
||||
await this.getWebSocket().request<PresetIndex>("getPresets")
|
||||
)
|
||||
);
|
||||
this.wifiConfigSettings.set(
|
||||
new WifiConfigSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getWifiConfigSettings")
|
||||
));
|
||||
this.wifiDirectConfigSettings.set(
|
||||
new WifiDirectConfigSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getWifiDirectConfigSettings")
|
||||
));
|
||||
this.governorSettings.set(new GovernorSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getGovernorSettings")
|
||||
));
|
||||
this.showStatusMonitor.set(
|
||||
await this.getWebSocket().request<boolean>("getShowStatusMonitor")
|
||||
);
|
||||
this.jackServerSettings.set(
|
||||
new JackServerSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getJackServerSettings")
|
||||
)
|
||||
);
|
||||
this.jackConfiguration.set(new JackConfiguration().deserialize(
|
||||
await this.getWebSocket().request<JackConfiguration>("getJackConfiguration")
|
||||
));
|
||||
this.jackSettings.set(new JackChannelSelection().deserialize(
|
||||
await this.getWebSocket().request<any>("getJackSettings")
|
||||
));
|
||||
this.banks.set(new BankIndex().deserialize(await this.getWebSocket().request<any>("getBankIndex")));
|
||||
|
||||
return this.getWebSocket().request<any>("getGovernorSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.governorSettings.set(new GovernorSettings().deserialize(data));
|
||||
this.favorites.set(await this.getWebSocket().request<FavoritesList>("getFavorites"));
|
||||
|
||||
return this.getWebSocket().request<boolean>("getShowStatusMonitor");
|
||||
})
|
||||
.then(data => {
|
||||
this.showStatusMonitor.set(data);
|
||||
this.systemMidiBindings.set(MidiBinding.deserialize_array(await this.getWebSocket().request<MidiBinding[]>("getSystemMidiBindings")));
|
||||
|
||||
return this.getWebSocket().request<any>("getJackServerSettings");
|
||||
})
|
||||
.then(data => {
|
||||
this.jackServerSettings.set(new JackServerSettings().deserialize(data));
|
||||
// load at lest once before we allow a reconnect.
|
||||
this.getWebSocket().canReconnect = true;
|
||||
|
||||
return this.getWebSocket().request<JackConfiguration>("getJackConfiguration");
|
||||
})
|
||||
.then((data) => {
|
||||
// data.isValid = false;
|
||||
// data.errorState = "Jack Audio server not running."
|
||||
|
||||
this.jackConfiguration.set(new JackConfiguration().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<any>("getJackSettings");
|
||||
})
|
||||
.then((data) => {
|
||||
this.jackSettings.set(new JackChannelSelection().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<any>("getBankIndex");
|
||||
})
|
||||
.then((data) => {
|
||||
this.banks.set(new BankIndex().deserialize(data));
|
||||
if (this.webSocket) {
|
||||
// MUST not allow reconnect until at least one complete load has finished.
|
||||
this.webSocket.canReconnect = true;
|
||||
}
|
||||
|
||||
return this.getWebSocket().request<FavoritesList>("getFavorites");
|
||||
})
|
||||
.then((data) => {
|
||||
this.favorites.set(data);
|
||||
|
||||
return this.getWebSocket().request<MidiBinding[]>("getSystemMidiBindings");
|
||||
})
|
||||
.then((data) => {
|
||||
let bindings = MidiBinding.deserialize_array(data);
|
||||
this.systemMidiBindings.set(bindings);
|
||||
|
||||
if (this.webSocket) {
|
||||
// MUST not allow reconnect until at least one complete load has finished.
|
||||
this.webSocket.canReconnect = true;
|
||||
}
|
||||
this.setState(State.Ready);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setError("Failed to fetch server state.\n\n" + error.toString());
|
||||
return false;
|
||||
});
|
||||
|
||||
})
|
||||
;
|
||||
this.setState(State.Ready);
|
||||
return true;
|
||||
}
|
||||
catch(error) {
|
||||
this.setError("Failed to fetch server state.\n\n" + getErrorMessage(error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { UiPlugin, UiControl, UiFileProperty,UiFrequencyPlot, ScalePoint } from './Lv2Plugin';
|
||||
import { UiPlugin, UiControl, UiFileProperty, UiFrequencyPlot, ScalePoint } from './Lv2Plugin';
|
||||
import {
|
||||
Pedalboard, PedalboardItem, ControlValue
|
||||
} from './Pedalboard';
|
||||
@@ -247,8 +247,7 @@ type PluginControlViewState = {
|
||||
|
||||
const PluginControlView =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState>
|
||||
{
|
||||
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: PluginControlViewProps) {
|
||||
@@ -526,7 +525,7 @@ const PluginControlView =
|
||||
return false;
|
||||
}
|
||||
|
||||
controlKeyIndex : number = 0;
|
||||
controlKeyIndex: number = 0;
|
||||
controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] {
|
||||
let classes = this.props.classes;
|
||||
let isLandscapeGrid = this.state.landscapeGrid;
|
||||
@@ -543,7 +542,7 @@ const PluginControlView =
|
||||
let item = controlGroup.controls[j];
|
||||
controls.push(
|
||||
(
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={classes.controlPadding}>
|
||||
<div key={"ctl" + (this.controlKeyIndex++)} className={classes.controlPadding}>
|
||||
{item}
|
||||
</div>
|
||||
|
||||
@@ -552,7 +551,7 @@ const PluginControlView =
|
||||
}
|
||||
|
||||
result.push((
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div key={"ctl" + (this.controlKeyIndex++)} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div className={classes.portGroupTitle}>
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</div>
|
||||
@@ -566,7 +565,7 @@ const PluginControlView =
|
||||
|
||||
} else {
|
||||
result.push((
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
||||
<div key={"ctl" + (this.controlKeyIndex++)} className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
||||
{node as ReactNode}
|
||||
</div>
|
||||
));
|
||||
@@ -584,7 +583,7 @@ const PluginControlView =
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
this.controlKeyIndex = 0;
|
||||
this.controlKeyIndex = 0;
|
||||
let classes = this.props.classes;
|
||||
let pedalboardItem: PedalboardItem;
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
@@ -650,29 +649,33 @@ const PluginControlView =
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<FilePropertyDialog open={this.state.showFileDialog}
|
||||
fileProperty={this.state.dialogFileProperty}
|
||||
selectedFile={this.state.dialogFileValue}
|
||||
onCancel={() => {
|
||||
this.setState({ showFileDialog: false });
|
||||
}}
|
||||
onOk={(fileProperty, selectedFile) => {
|
||||
{this.state.showFileDialog && (
|
||||
|
||||
this.model.setPatchProperty(
|
||||
this.props.instanceId,
|
||||
fileProperty.patchProperty,
|
||||
JsonAtom.Path(selectedFile)
|
||||
)
|
||||
.then(() => {
|
||||
<FilePropertyDialog open={this.state.showFileDialog}
|
||||
fileProperty={this.state.dialogFileProperty}
|
||||
selectedFile={this.state.dialogFileValue}
|
||||
onCancel={() => {
|
||||
this.setState({ showFileDialog: false });
|
||||
}}
|
||||
onOk={(fileProperty, selectedFile) => {
|
||||
|
||||
this.model.setPatchProperty(
|
||||
this.props.instanceId,
|
||||
fileProperty.patchProperty,
|
||||
JsonAtom.Path(selectedFile)
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert("Unable to complete the operation. Audio is not running." + error);
|
||||
});
|
||||
this.setState({ showFileDialog: false });
|
||||
}
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert("Unable to complete the operation. Audio is not running." + error);
|
||||
});
|
||||
this.setState({ showFileDialog: false });
|
||||
}
|
||||
}
|
||||
/>
|
||||
<FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue}
|
||||
|
||||
onChange={(key, value) => this.onImeValueChange(key, value)}
|
||||
|
||||
@@ -92,6 +92,8 @@ interface SettingsDialogState {
|
||||
showRestartOkDialog: boolean;
|
||||
showShutdownOkDialog: boolean;
|
||||
showSystemMidiBindingsDialog: boolean;
|
||||
|
||||
hasWifiDevice: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -199,10 +201,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
showShutdownOkDialog: false,
|
||||
showRestartOkDialog: false,
|
||||
showSystemMidiBindingsDialog: false,
|
||||
isAndroidHosted: this.model.isAndroidHosted()
|
||||
|
||||
|
||||
|
||||
isAndroidHosted: this.model.isAndroidHosted(),
|
||||
hasWifiDevice: this.model.hasWifiDevice.get()
|
||||
};
|
||||
this.handleJackConfigurationChanged = this.handleJackConfigurationChanged.bind(this);
|
||||
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
||||
@@ -212,10 +212,13 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
|
||||
this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this);
|
||||
this.handleShowStatusMonitorChanged = this.handleShowStatusMonitorChanged.bind(this);
|
||||
|
||||
this.handleHasWifiChanged = this.handleHasWifiChanged.bind(this);
|
||||
|
||||
}
|
||||
|
||||
handleHasWifiChanged(newValue: boolean) {
|
||||
this.setState({hasWifiDevice: newValue});
|
||||
}
|
||||
|
||||
handleShowStatusMonitorChanged(): void {
|
||||
this.setState({ showStatusMonitor: this.model.showStatusMonitor.get() });
|
||||
@@ -323,6 +326,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
if (active !== this.active) {
|
||||
this.active = active;
|
||||
if (active) {
|
||||
this.model.hasWifiDevice.addOnChangedHandler(this.handleHasWifiChanged);
|
||||
this.model.state.addOnChangedHandler(this.handleConnectionStateChanged);
|
||||
this.model.showStatusMonitor.addOnChangedHandler(this.handleShowStatusMonitorChanged);
|
||||
this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged);
|
||||
@@ -351,6 +355,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
this.handleJackServerSettingsChanged();
|
||||
this.handleWifiConfigSettingsChanged();
|
||||
this.handleWifiDirectConfigSettingsChanged();
|
||||
this.setState({hasWifiDevice: this.model.hasWifiDevice.get()});
|
||||
|
||||
this.timerHandle = setInterval(() => this.tick(), 1000);
|
||||
} else {
|
||||
if (this.timerHandle) {
|
||||
@@ -365,7 +371,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||
|
||||
this.model.hasWifiDevice.removeOnChangedHandler(this.handleHasWifiChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,7 +548,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
<DialogEx tag="settings" fullScreen open={this.props.open}
|
||||
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
|
||||
style={{ userSelect: "none" }}
|
||||
onEnterKey={()=>{}}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
|
||||
@@ -651,7 +657,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
|
||||
|
||||
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
|
||||
@@ -662,7 +668,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
>
|
||||
@@ -766,43 +772,52 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
)
|
||||
}
|
||||
|
||||
<Divider />
|
||||
<div >
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
|
||||
{(this.state.hasWifiDevice || this.state.isAndroidHosted) &&
|
||||
(
|
||||
<div>
|
||||
<Divider />
|
||||
<div >
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
|
||||
|
||||
<ButtonBase
|
||||
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
|
||||
onClick={() => this.handleShowWifiConfigDialog()} >
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
|
||||
Wi-Fi auto-hotspot</Typography>
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">
|
||||
{this.state.wifiConfigSettings.getSummaryText()}
|
||||
</Typography>
|
||||
{this.state.hasWifiDevice && (
|
||||
<ButtonBase
|
||||
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
|
||||
onClick={() => this.handleShowWifiConfigDialog()} >
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
|
||||
Wi-Fi auto-hotspot</Typography>
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">
|
||||
{this.state.wifiConfigSettings.getSummaryText()}
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
</ButtonBase>
|
||||
|
||||
)}
|
||||
|
||||
{
|
||||
this.state.isAndroidHosted &&
|
||||
(
|
||||
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
|
||||
onClick={() => this.model.chooseNewDevice()} >
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
|
||||
Connect to a different device</Typography>
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">
|
||||
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
</ButtonBase>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ButtonBase>
|
||||
|
||||
{
|
||||
this.state.isAndroidHosted &&
|
||||
(
|
||||
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
|
||||
onClick={() => this.model.chooseNewDevice()} >
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
|
||||
Connect to a different device</Typography>
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">
|
||||
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
</ButtonBase>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
)}
|
||||
{(!this.props.onboarding) ? (
|
||||
<div >
|
||||
<Divider />
|
||||
@@ -942,10 +957,15 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
|
||||
)
|
||||
}
|
||||
<WifiDirectConfigDialog wifiDirectConfigSettings={this.state.wifiDirectConfigSettings} open={this.state.showWifiDirectConfigDialog}
|
||||
onClose={() => this.setState({ showWifiDirectConfigDialog: false })}
|
||||
onOk={(wifiDirectConfigSettings: WifiDirectConfigSettings) => this.handleApplyWifiDirectConfig(wifiDirectConfigSettings)}
|
||||
/>
|
||||
{
|
||||
this.state.showWifiDirectConfigDialog && (
|
||||
<WifiDirectConfigDialog wifiDirectConfigSettings={this.state.wifiDirectConfigSettings} open={this.state.showWifiDirectConfigDialog}
|
||||
onClose={() => this.setState({ showWifiDirectConfigDialog: false })}
|
||||
onOk={(wifiDirectConfigSettings: WifiDirectConfigSettings) => this.handleApplyWifiDirectConfig(wifiDirectConfigSettings)}
|
||||
/>
|
||||
|
||||
)
|
||||
}
|
||||
<OkCancelDialog text="Are you sure you want to reboot?" okButtonText='Reboot'
|
||||
open={this.state.showRestartOkDialog}
|
||||
onOk={() => { this.setState({ showRestartOkDialog: false }); this.handleRestartOk(); }}
|
||||
@@ -956,10 +976,13 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
onOk={() => { this.setState({ showShutdownOkDialog: false }); this.handleShutdownOk(); }}
|
||||
onClose={() => { this.setState({ showShutdownOkDialog: false }); }}
|
||||
/>
|
||||
<SystemMidiBindingsDialog
|
||||
open={this.state.showSystemMidiBindingsDialog}
|
||||
onClose={() => { this.setState({ showSystemMidiBindingsDialog: false }); }}
|
||||
/>
|
||||
{this.state.showSystemMidiBindingsDialog && (
|
||||
<SystemMidiBindingsDialog
|
||||
open={this.state.showSystemMidiBindingsDialog}
|
||||
onClose={() => { this.setState({ showSystemMidiBindingsDialog: false }); }}
|
||||
/>
|
||||
|
||||
)}
|
||||
{this.state.showWindowScaleDialog && (
|
||||
<OptionsDialog open={this.state.showWindowScaleDialog} options={getWindowScaleOptions()} value={getWindowScale()}
|
||||
onClose={(() => this.setState({ showWindowScaleDialog: false }))}
|
||||
|
||||
@@ -78,6 +78,10 @@ export interface WifiConfigState {
|
||||
homeNetworkSsid: string;
|
||||
homeNetworkError: boolean;
|
||||
homeNetworkErrorMessage: string;
|
||||
countryCodeError: boolean;
|
||||
countryCodeErrorMessage: string;
|
||||
channelError: boolean;
|
||||
channelErrorMessage: string;
|
||||
countryCode: string;
|
||||
channel: string;
|
||||
knownWifiNetworks: string[];
|
||||
@@ -112,6 +116,10 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
|
||||
homeNetworkSsid: this.props.wifiConfigSettings.homeNetwork,
|
||||
homeNetworkError: false,
|
||||
homeNetworkErrorMessage: NBSP,
|
||||
countryCodeError: false,
|
||||
countryCodeErrorMessage: NBSP,
|
||||
channelError: false,
|
||||
channelErrorMessage: NBSP,
|
||||
showWifiWarningDialog: false,
|
||||
showHelpDialog: false,
|
||||
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
|
||||
@@ -257,7 +265,19 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
|
||||
this.setState({ homeNetworkError: true, homeNetworkErrorMessage: "* Required" });
|
||||
hasError = true;
|
||||
}
|
||||
if (this.state.countryCode === "")
|
||||
{
|
||||
this.setState({ countryCodeError: true, countryCodeErrorMessage: "* Required" });
|
||||
hasError = true;
|
||||
}
|
||||
if (this.validChannelSelection() === "")
|
||||
{
|
||||
this.setState({ channelError: true, channelErrorMessage: "* Required" });
|
||||
hasError = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.autoStartMode !== 0 && (!this.props.wifiConfigSettings.hasSavedPassword) && this.state.newPassword.length === 0) {
|
||||
this.setState({ passwordError: true, passwordErrorMessage: "* Required" });
|
||||
hasError = true;
|
||||
@@ -291,12 +311,14 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
|
||||
|
||||
handleChannelChange(e: any) {
|
||||
let value = e.target.value as string;
|
||||
this.setState({ channel: value });
|
||||
this.setState({ channel: value, channelError: false, channelErrorMessage: NBSP });
|
||||
}
|
||||
handleCountryChanged(e: any) {
|
||||
let value = e.target.value as string;
|
||||
handleCountryChanged(value: string) {
|
||||
|
||||
this.setState({ countryCode: value });
|
||||
this.setState({ countryCode: value,
|
||||
countryCodeError: false, countryCodeErrorMessage: NBSP,
|
||||
channelError: false, channelErrorMessage: NBSP
|
||||
});
|
||||
this.requestWifiChannels(value);
|
||||
}
|
||||
handleTogglePasswordVisibility() {
|
||||
@@ -334,6 +356,20 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
|
||||
this.handleOk(true);
|
||||
}
|
||||
|
||||
validChannelSelection() {
|
||||
if (!this.state.wifiChannels)
|
||||
{
|
||||
return ''; // null selector.
|
||||
}
|
||||
for (let wifiChannel of this.state.wifiChannels)
|
||||
{
|
||||
if (wifiChannel.channelId === this.state.channel)
|
||||
{
|
||||
return this.state.channel; // yes, it's a valid selection.
|
||||
}
|
||||
}
|
||||
return ''; // null selector value.
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
@@ -522,46 +558,35 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
|
||||
? { display: "flex", flexFlow: "row nowrap", gap: 24, alignItems: "start" }
|
||||
: { display: "block" }
|
||||
}>
|
||||
<div style={{ display: "flex", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
|
||||
<FormControl variant="standard" style={{ display: "flex", marginBottom: 8, flexGrow: 1, flexBasis: 1 }} >
|
||||
<Autocomplete fullWidth
|
||||
defaultValue={this.getCountryCodeValue(this.state.countryCode)}
|
||||
disableClearable={true}
|
||||
onChange={(event, value) => { if (value) { this.setState({ countryCode: value.id }) } }}
|
||||
onChange={(event, value) => { if (value) { this.handleCountryChanged(value.id as string ) } }}
|
||||
options={this.getCountryCodeOptions()}
|
||||
renderInput={(params) => (<TextField {...params} variant="standard" label="Regulatory Domain" />)}
|
||||
disabled={!enabled}
|
||||
/>
|
||||
{/*
|
||||
<Select variant="standard" label="Regulatory Domain" id="countryCodeSelect"
|
||||
fullWidth value={this.state.countryCode} style={{}}
|
||||
onChange={(event) => this.handleCountryChanged(event)} disabled={!enabled} >
|
||||
{Object.entries(this.model.countryCodes).map(([key, value]) => {
|
||||
return (
|
||||
<MenuItem value={key}>{value}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
*/}
|
||||
<FormHelperText error={this.state.countryCodeError}>{this.state.countryCodeErrorMessage}</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl variant="standard" style={{ display: "flex", marginBottom: 8, flexGrow: 1, flexBasis: 1 }} >
|
||||
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
|
||||
<Select variant="standard" id="channelSelect" value={
|
||||
this.validChannelSelection()}
|
||||
fullWidth
|
||||
disabled={!enabled || this.state.wifiChannels.length === 0}
|
||||
onChange={(e) => {
|
||||
this.handleChannelChange(e);
|
||||
}}>
|
||||
{this.state.wifiChannels.map((channel) => {
|
||||
return (
|
||||
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText error={this.state.channelError}>{this.state.channelErrorMessage}</FormHelperText>
|
||||
|
||||
</div>
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
|
||||
<FormControl style={{ flexGrow: 1 }} >
|
||||
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
|
||||
<Select variant="standard" id="channelSelect" value={this.state.channel}
|
||||
fullWidth
|
||||
disabled={!enabled}
|
||||
onChange={(e) => {
|
||||
this.handleChannelChange(e);
|
||||
}}>
|
||||
{this.state.wifiChannels.map((channel) => {
|
||||
return (
|
||||
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
</div>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
+2
-26
@@ -34,6 +34,7 @@
|
||||
#include "RtInversionGuard.hpp"
|
||||
#include "PiPedalException.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -1565,32 +1566,7 @@ namespace pipedal
|
||||
SetThreadName("alsaDriver");
|
||||
try
|
||||
{
|
||||
#if defined(__WIN32)
|
||||
// bump thread prioriy two levels to
|
||||
// ensure that the service thread doesn't
|
||||
// get bogged down by UIwork. Doesn't have to be realtime, but it
|
||||
// MUST run at higher priority than UI threads.
|
||||
xxx; // TO DO.
|
||||
#elif defined(__linux__)
|
||||
int min = sched_get_priority_min(SCHED_RR);
|
||||
int max = sched_get_priority_max(SCHED_RR);
|
||||
|
||||
struct sched_param param;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.sched_priority = RT_THREAD_PRIORITY;
|
||||
|
||||
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
||||
if (result == 0)
|
||||
{
|
||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error(SS("Failed to set ALSA AudioThread priority. (" << strerror(result) << ")"));
|
||||
}
|
||||
#else
|
||||
xxx; // TODO!
|
||||
#endif
|
||||
SetThreadPriority(SchedulerPriority::RealtimeAudio);
|
||||
|
||||
bool ok = true;
|
||||
|
||||
|
||||
+4
-23
@@ -20,9 +20,11 @@
|
||||
#include "AudioHost.hpp"
|
||||
#include "util.hpp"
|
||||
#include <lv2/atom/atom.h>
|
||||
#include "SchedulerPriority.hpp"
|
||||
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
#include "SchedulerPriority.hpp"
|
||||
#include "JackDriver.hpp"
|
||||
#include "AlsaDriver.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
@@ -1263,30 +1265,9 @@ public:
|
||||
bool terminateThread;
|
||||
void ThreadProc()
|
||||
{
|
||||
|
||||
#if defined(__WIN32)
|
||||
// bump thread prioriy two levels to
|
||||
// ensure that the service thread doesn't
|
||||
// get bogged down by UIwork. Doesn't have to be realtime, but it
|
||||
// MUST run at higher _ than UI threads.
|
||||
xxx; // TO DO.
|
||||
#elif defined(__linux__)
|
||||
int min = sched_get_priority_min(SCHED_RR);
|
||||
int max = sched_get_priority_max(SCHED_RR);
|
||||
|
||||
struct sched_param param;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.sched_priority = min;
|
||||
|
||||
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
||||
if (result == 0)
|
||||
{
|
||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
||||
}
|
||||
SetThreadName("rtsvc");
|
||||
#else
|
||||
xxx; // TODO!
|
||||
#endif
|
||||
SetThreadPriority(SchedulerPriority::AudioService);
|
||||
|
||||
int underrunMessagesGiven = 0;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -301,6 +301,7 @@ collision:
|
||||
n = avahi_alternative_service_name(avahiNameString);
|
||||
avahi_free(avahiNameString);
|
||||
avahiNameString = n;
|
||||
serviceName = avahiNameString;
|
||||
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'"));
|
||||
avahi_entry_group_reset(group);
|
||||
create_group(c);
|
||||
|
||||
+15
-7
@@ -87,13 +87,14 @@ set (LV2DEV_INCLUDE_DIRS ) # use lvt headers fro /usr/include
|
||||
|
||||
set (WEBSOCKETPP_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/modules/websocketpp)
|
||||
message(STATUS "WEBSOCKETPP_INCLUDE_DIRS ${WEBSOCKETPP_INCLUDE_DIRS}" )
|
||||
pkg_check_modules(JACK "jack")
|
||||
if(!JACK_FOUND)
|
||||
message(ERROR "jack package not installed.")
|
||||
else()
|
||||
message(STATUS "JACK_LIBRARIES: ${JACK_LIBRARIES}")
|
||||
message(STATUS "JACK_INCLUDE_DIRS: ${JACK_INCLUDE_DIRS}")
|
||||
endif()
|
||||
|
||||
# pkg_check_modules(JACK "jack")
|
||||
# if(!JACK_FOUND)
|
||||
# message(ERROR "jack package not installed.")
|
||||
# else()
|
||||
# message(STATUS "JACK_LIBRARIES: ${JACK_LIBRARIES}")
|
||||
# message(STATUS "JACK_INCLUDE_DIRS: ${JACK_INCLUDE_DIRS}")
|
||||
# endif()
|
||||
|
||||
|
||||
# specify the C++ standard
|
||||
@@ -157,6 +158,7 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
SchedulerPriority.hpp SchedulerPriority.cpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
PatchPropertyWriter.hpp
|
||||
PresetBundle.cpp PresetBundle.hpp
|
||||
@@ -645,11 +647,16 @@ add_executable(pipedalconfig
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
WifiChannelSelectors.cpp WifiChannelSelectors.hpp
|
||||
Locale.cpp Locale.hpp
|
||||
asan_options.cpp
|
||||
|
||||
)
|
||||
|
||||
target_link_libraries(pipedalconfig PRIVATE PiPedalCommon pthread atomic uuid stdc++fs asound
|
||||
icui18n
|
||||
icuuc
|
||||
icudata
|
||||
|
||||
)
|
||||
|
||||
add_executable(pipedal_latency_test
|
||||
@@ -659,6 +666,7 @@ add_executable(pipedal_latency_test
|
||||
PiPedalAlsa.hpp PiPedalAlsa.cpp
|
||||
asan_options.cpp
|
||||
AlsaDriver.cpp AlsaDriver.hpp
|
||||
SchedulerPriority.cpp SchedulerPriority.hpp
|
||||
DummyAudioDriver.cpp DummyAudioDriver.hpp
|
||||
JackConfiguration.hpp JackConfiguration.cpp
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
|
||||
+73
-13
@@ -25,6 +25,8 @@
|
||||
#include "SystemConfigFile.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
#include "alsaCheck.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include "Locale.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdlib.h>
|
||||
@@ -483,17 +485,25 @@ void InstallLimits()
|
||||
throw std::runtime_error("Failed to create audio service group.");
|
||||
}
|
||||
|
||||
fs::path limitsConfig = "/etc/security/limits.d/audio.conf";
|
||||
// old limits config file bumped limits for members of the audio group, and didn't have a proper priority.
|
||||
// remove it
|
||||
fs::path oldLimitsConfig = "/etc/security/limits.d/audio.conf";
|
||||
if (fs::exists(oldLimitsConfig))
|
||||
{
|
||||
fs::remove(oldLimitsConfig);
|
||||
}
|
||||
|
||||
// bump limits for members of the pipedal_d group.
|
||||
fs::path limitsConfig = "/etc/security/limits.d/90-pipedal.conf";
|
||||
|
||||
if (!fs::exists(limitsConfig))
|
||||
{
|
||||
ofstream output(limitsConfig);
|
||||
output << "# Realtime priority group used by pipedal audio (and also by jack)"
|
||||
<< "\n";
|
||||
output << "@audio - rtprio 95"
|
||||
<< "\n";
|
||||
output << "@audio - memlock unlimited"
|
||||
output << "# Realtime priority limits for the pipedal services"
|
||||
<< "\n";
|
||||
output << "@pipedal_d - rtprio 95" << "\n";
|
||||
output << "@pipedal_d - nice -20" << "\n";
|
||||
output << "@pipedal_d - memlock unlimited" << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,8 +1171,8 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
|
||||
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
||||
|
||||
FixPermissions();
|
||||
ModFileTypes::CreateDefaultDirectories("/var/pipedal/audio_uploads");
|
||||
FixPermissions();
|
||||
|
||||
StopService(false);
|
||||
AvahiInstall();
|
||||
@@ -1288,6 +1298,11 @@ static void PrintHelp()
|
||||
<< "\n\n"
|
||||
<< HangingIndent() << " --list-wifi-channels [<country_code>] \tList valid Wifi channels for the current/specified country."
|
||||
<< "\n\n"
|
||||
<< HangingIndent() << " --list-wifi-country-codes\tList valid country codes."
|
||||
<< "\n\n"
|
||||
|
||||
<< HangingIndent() << " --fix-permissions\t"
|
||||
<< "Set correct permissions on /var/pipedal directories and sub-directories\n\n"
|
||||
|
||||
<< Indent(0) << "Country codes:"
|
||||
<< "\n\n"
|
||||
@@ -1299,11 +1314,10 @@ static void PrintHelp()
|
||||
<< "Without a country code, Wi-Fi must be restricted to channels 1 through 11 "
|
||||
<< "with reduced amplitude and feature sets."
|
||||
<< "\n\n"
|
||||
<< "For the most part, Wi-Fi country codes are taken from the list of ISO 3661 "
|
||||
<< "2-letter country codes; although there are a handful of exceptions for small "
|
||||
<< "countries and islands. See the Alpha-2 code column of "
|
||||
<< "\n\n"
|
||||
<< Indent(8) << "https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes."
|
||||
<< " Wi-Fi country codes are taken from the list of ISO 3661 "
|
||||
<< "2-letter country codes.\n\n"
|
||||
<< "To see a list of countries and their country codes, run: \n\n"
|
||||
<< Indent(8) << "pipedalconfig --list-wifi-country-codes"
|
||||
<< "\n\n";
|
||||
}
|
||||
|
||||
@@ -1353,6 +1367,41 @@ void RequireNetworkManager()
|
||||
}
|
||||
}
|
||||
|
||||
void ListValidCountryCodes()
|
||||
{
|
||||
RegDb regdb;
|
||||
|
||||
auto ccs = regdb.getRegulatoryDomains();
|
||||
|
||||
if (ccs.size() == 0)
|
||||
{
|
||||
cout << "No regulatory domains found." << endl;
|
||||
} else {
|
||||
using pair = std::pair<std::string,std::string>;
|
||||
std::vector<pair> list;
|
||||
for (auto &cc: ccs)
|
||||
{
|
||||
list.push_back(pair(cc.first,cc.second));
|
||||
}
|
||||
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
auto compare = [&collator](
|
||||
pair &left,
|
||||
pair &right)
|
||||
{
|
||||
return collator->Compare(
|
||||
left.second, right.second) < 0;
|
||||
};
|
||||
std::sort(list.begin(), list.end(), compare);
|
||||
|
||||
for (const auto &entry: list)
|
||||
{
|
||||
cout << entry.first << " - " << entry.second << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CommandLineParser parser;
|
||||
@@ -1375,9 +1424,11 @@ int main(int argc, char **argv)
|
||||
std::string portOption;
|
||||
std::string homeNetwork;
|
||||
bool noEthernet = false;
|
||||
bool list_wifi_country_codes = false;
|
||||
bool noWifi = false;
|
||||
|
||||
parser.AddOption("--nosudo", &nosudo); // strictly a debugging aid. Run without sudo, until we fail because of permissions.
|
||||
parser.AddOption("--list-wifi-country-codes",&list_wifi_country_codes);
|
||||
parser.AddOption("--install", &install);
|
||||
parser.AddOption("--uninstall", &uninstall);
|
||||
parser.AddOption("--stop", &stop);
|
||||
@@ -1396,12 +1447,16 @@ int main(int argc, char **argv)
|
||||
parser.AddOption("--no-wifi", &noWifi);
|
||||
parser.AddOption("--alsa-devices", &alsaDevices);
|
||||
parser.AddOption("--alsa-device", &alsaDevice);
|
||||
parser.AddOption("--fix-permissions", &fix_permissions);
|
||||
try
|
||||
{
|
||||
parser.Parse(argc, (const char **)argv);
|
||||
|
||||
int actionCount =
|
||||
(!alsaDevice.empty()) + alsaDevices + help + get_current_port + install + uninstall + stop + start + enable + disable + enable_hotspot + disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
|
||||
(!alsaDevice.empty()) + alsaDevices + help + get_current_port + install +
|
||||
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 > 1)
|
||||
{
|
||||
throw std::runtime_error("Please provide only one action.");
|
||||
@@ -1458,6 +1513,11 @@ int main(int argc, char **argv)
|
||||
PrintHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (list_wifi_country_codes)
|
||||
{
|
||||
ListValidCountryCodes();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (get_current_port)
|
||||
{
|
||||
std::cout << "current port: " << GetCurrentWebServicePort() << std::endl;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <thread>
|
||||
#include <stdexcept>
|
||||
#include "ss.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -223,32 +224,8 @@ namespace pipedal
|
||||
SetThreadName("dummyAudioDriver");
|
||||
try
|
||||
{
|
||||
#if defined(__WIN32)
|
||||
// bump thread prioriy two levels to
|
||||
// ensure that the service thread doesn't
|
||||
// get bogged down by UIwork. Doesn't have to be realtime, but it
|
||||
// MUST run at higher priority than UI threads.
|
||||
xxx; // TO DO.
|
||||
#elif defined(__linux__)
|
||||
int min = sched_get_priority_min(SCHED_RR);
|
||||
int max = sched_get_priority_max(SCHED_RR);
|
||||
|
||||
struct sched_param param;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.sched_priority = RT_THREAD_PRIORITY;
|
||||
|
||||
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
||||
if (result == 0)
|
||||
{
|
||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error(SS("Failed to set ALSA AudioThread priority. (" << strerror(result) << ")"));
|
||||
}
|
||||
#else
|
||||
xxx; // TODO for your platform.
|
||||
#endif
|
||||
SetThreadPriority(SchedulerPriority::RealtimeAudio);
|
||||
|
||||
bool ok = true;
|
||||
|
||||
|
||||
@@ -38,4 +38,5 @@ JSON_MAP_BEGIN(FileRequestResult)
|
||||
JSON_MAP_REFERENCE(FileRequestResult,files)
|
||||
JSON_MAP_REFERENCE(FileRequestResult,isProtected)
|
||||
JSON_MAP_REFERENCE(FileRequestResult,breadcrumbs)
|
||||
JSON_MAP_REFERENCE(FileRequestResult,currentDirectory)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace pipedal {
|
||||
std::vector<FileEntry> files_;
|
||||
bool isProtected_ = false;
|
||||
std::vector<BreadcrumbEntry> breadcrumbs_;
|
||||
std::string currentDirectory_;
|
||||
DECLARE_JSON_MAP(FileRequestResult);
|
||||
|
||||
};
|
||||
|
||||
+79
-4
@@ -33,6 +33,7 @@
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace dbus::networkmanager;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace pipedal::impl
|
||||
{
|
||||
@@ -59,6 +60,7 @@ namespace pipedal::impl
|
||||
virtual bool CancelPost(PostHandle handle) override;
|
||||
|
||||
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) override;
|
||||
virtual void SetHasWifiListener(HasWifiListener &&listener) override;
|
||||
|
||||
private:
|
||||
enum class State
|
||||
@@ -74,6 +76,11 @@ namespace pipedal::impl
|
||||
void SetState(State state);
|
||||
State state = State::Initial;
|
||||
|
||||
bool hasWifi = false;
|
||||
HasWifiListener hasWifiListener;
|
||||
void SetHasWifi(bool hasWifi);
|
||||
virtual bool GetHasWifi() override ;
|
||||
|
||||
void onClose();
|
||||
void onError(const std::string &message);
|
||||
void onInitialize();
|
||||
@@ -223,6 +230,7 @@ void HotspotManagerImpl::onInitialize()
|
||||
}
|
||||
void HotspotManagerImpl::onDisconnect()
|
||||
{
|
||||
SetHasWifi(false);
|
||||
if (state != State::Initial && state != State::WaitingForNetworkManager)
|
||||
{
|
||||
WaitForNetworkManager();
|
||||
@@ -241,6 +249,8 @@ void HotspotManagerImpl::UpdateNetworkManagerStatus()
|
||||
auto ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET);
|
||||
auto wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI);
|
||||
|
||||
SetHasWifi(!!wlanDevice);
|
||||
|
||||
if (ethernetDevice && wlanDevice)
|
||||
{
|
||||
if (state == State::Initial || state == State::WaitingForNetworkManager)
|
||||
@@ -290,6 +300,7 @@ void HotspotManagerImpl::ReleaseNetworkManager()
|
||||
|
||||
this->onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE;
|
||||
this->onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE;
|
||||
SetHasWifi(false);
|
||||
}
|
||||
|
||||
void HotspotManagerImpl::onDevicesChanged()
|
||||
@@ -400,6 +411,8 @@ void HotspotManagerImpl::onStartMonitoring()
|
||||
OnEthernetStateChanged(ethernetDevice->State());
|
||||
OnWlanStateChanged(wlanDevice->State());
|
||||
|
||||
SetHasWifi(true);
|
||||
|
||||
StartScanTimer();
|
||||
onAccessPointChanged();
|
||||
}
|
||||
@@ -862,11 +875,11 @@ void HotspotManagerImpl::StartHotspot()
|
||||
settings["ipv4"]["method"] = "shared";
|
||||
settings["ipv6"]["method"] = "shared";
|
||||
settings["ipv6"]["addr-gen-mode"] = 1; // "stable-privacy";
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Create connection
|
||||
settings["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Create connection
|
||||
settings["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
|
||||
// settings["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
|
||||
|
||||
settings["ipv4"]["address-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{{"address", sdbus::Variant("192.168.60.1")},
|
||||
@@ -1013,6 +1026,32 @@ bool HotspotManagerImpl::CancelPost(PostHandle handle)
|
||||
return dbusDispatcher.CancelPost(handle);
|
||||
}
|
||||
|
||||
void HotspotManagerImpl::SetHasWifiListener(HasWifiListener &&listener)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
||||
this->hasWifiListener = listener;
|
||||
if (!this->closed && this->hasWifiListener)
|
||||
{
|
||||
this->hasWifiListener(this->hasWifi);
|
||||
}
|
||||
}
|
||||
bool HotspotManagerImpl::GetHasWifi() {
|
||||
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
||||
return hasWifi;
|
||||
}
|
||||
void HotspotManagerImpl::SetHasWifi(bool hasWifi)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
||||
if (hasWifi != this->hasWifi)
|
||||
{
|
||||
this->hasWifi = hasWifi;
|
||||
if (this->hasWifiListener)
|
||||
{
|
||||
this->hasWifiListener(this->hasWifi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
||||
@@ -1032,3 +1071,39 @@ std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
|
||||
std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread.
|
||||
return this->knownWifiNetworks;
|
||||
}
|
||||
|
||||
static bool is_wireless_sysfs(const std::string &ifname)
|
||||
{
|
||||
return fs::exists("/sys/class/net/" + ifname + "/wireless");
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_wireless_interfaces_sysfs()
|
||||
{
|
||||
std::vector<std::string> wireless_interfaces;
|
||||
const std::string net_path = "/sys/class/net";
|
||||
|
||||
try
|
||||
{
|
||||
for (const auto &entry : fs::directory_iterator(net_path))
|
||||
{
|
||||
std::string ifname = entry.path().filename();
|
||||
if (is_wireless_sysfs(ifname))
|
||||
{
|
||||
wireless_interfaces.push_back(ifname);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const fs::filesystem_error &e)
|
||||
{
|
||||
std::cerr << "Error accessing " << net_path << ": " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return wireless_interfaces;
|
||||
}
|
||||
|
||||
bool HotspotManager::HasWifiDevice()
|
||||
{
|
||||
// use procfs to decide this, as NetworkManager may not be available yet.
|
||||
|
||||
return !(get_wireless_interfaces_sysfs().empty());
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace pipedal {
|
||||
|
||||
using ptr = std::unique_ptr<HotspotManager>;
|
||||
|
||||
static bool HasWifiDevice();
|
||||
|
||||
static ptr Create();
|
||||
|
||||
virtual ~HotspotManager() noexcept { }
|
||||
@@ -53,6 +55,11 @@ namespace pipedal {
|
||||
|
||||
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) = 0;
|
||||
|
||||
using HasWifiListener = std::function<void(bool hasWifi)>;
|
||||
virtual void SetHasWifiListener(HasWifiListener &&listener) = 0;
|
||||
virtual bool GetHasWifi() = 0;
|
||||
|
||||
|
||||
using PostHandle = uint64_t;
|
||||
using PostCallback = std::function<void()>;
|
||||
|
||||
|
||||
+11
-3
@@ -25,13 +25,23 @@
|
||||
/* Status of Jack support.
|
||||
|
||||
PiPedal was originally written for Jack, but subsequently ported to Alsa because running
|
||||
Jack as a systemd daemon proved to be unsupportable,.
|
||||
Jack as a systemd daemon proved to be unsupportable. (Completely broken when pipewire is
|
||||
installed).
|
||||
|
||||
This was all functional code at one point, but is no longer in sync with the current
|
||||
codebase. It won't work as is. It probably implemenets the driver interface properly.
|
||||
You would have to go through AudioHost.cpp and substitute JackDriver for AlsaDriver
|
||||
as appropriate.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#if JACK_HOST
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "JackDriver.hpp"
|
||||
|
||||
#include <jack/jack.h>
|
||||
@@ -40,8 +50,6 @@ Jack as a systemd daemon proved to be unsupportable,.
|
||||
#include <jack/midiport.h>
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
#if JACK_HOST
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
|
||||
+37
-1
@@ -43,9 +43,21 @@
|
||||
#include "RingBufferReader.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const float BYPASS_TIME_S = 0.1f;
|
||||
|
||||
|
||||
static fs::path makeAbsolutePath(const std::filesystem::path &path, const std::filesystem::path &parentPath)
|
||||
{
|
||||
if (path.is_absolute())
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return parentPath / path;
|
||||
}
|
||||
|
||||
|
||||
Lv2Effect::Lv2Effect(
|
||||
IHost *pHost_,
|
||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||
@@ -69,7 +81,6 @@ Lv2Effect::Lv2Effect(
|
||||
this->pathProperties.push_back(filePropertyUrid);
|
||||
|
||||
this->pathPropertyWriters.push_back(PatchPropertyWriter(instanceId, filePropertyUrid));
|
||||
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||
}
|
||||
}
|
||||
for (auto &pathProperty: pedalboardItem.pathProperties_)
|
||||
@@ -84,8 +95,10 @@ Lv2Effect::Lv2Effect(
|
||||
|
||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
||||
|
||||
// xxx: could we not stash the pPlugin in the plugin info?
|
||||
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
|
||||
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
||||
|
||||
lilv_node_free(uriNode);
|
||||
{
|
||||
AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin);
|
||||
@@ -98,6 +111,25 @@ Lv2Effect::Lv2Effect(
|
||||
logFeature.GetLog(),
|
||||
bundleUriString,
|
||||
storagePath);
|
||||
|
||||
|
||||
mapPathFeature.Prepare(&(pHost_->GetMapFeature()));
|
||||
mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath());
|
||||
if (info->piPedalUI())
|
||||
{
|
||||
const auto &fileProperties = info_->piPedalUI()->fileProperties();
|
||||
for (const auto &fileProperty : fileProperties)
|
||||
{
|
||||
if (!fileProperty->resourceDirectory().empty())
|
||||
{
|
||||
mapPathFeature.AddResourceFileMapping({
|
||||
makeAbsolutePath(fileProperty->resourceDirectory(),bundleUriString),
|
||||
makeAbsolutePath(fileProperty->directory(),pHost_->GetPluginStoragePath())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lilv_free(bundleUriString);
|
||||
}
|
||||
|
||||
@@ -109,6 +141,10 @@ Lv2Effect::Lv2Effect(
|
||||
{
|
||||
this->features.push_back(*p);
|
||||
}
|
||||
this->features.push_back(mapPathFeature.GetMapPathFeature());
|
||||
this->features.push_back(mapPathFeature.GetMakePathFeature());
|
||||
this->features.push_back(mapPathFeature.GetFreePathFeature());
|
||||
|
||||
|
||||
this->features.push_back(this->fileBrowserFilesFeature.GetFeature());
|
||||
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@
|
||||
#include "FileBrowserFilesFeature.hpp"
|
||||
#include "PatchPropertyWriter.hpp"
|
||||
#include <unordered_map>
|
||||
#include "MapPathFeature.hpp"
|
||||
|
||||
#include "IEffect.hpp"
|
||||
#include "Worker.hpp"
|
||||
@@ -90,7 +91,7 @@ namespace pipedal
|
||||
std::vector<char *> outputAtomBuffers;
|
||||
std::vector<const LV2_Feature *> features;
|
||||
LV2_Feature *work_schedule_feature = nullptr;
|
||||
|
||||
MapPathFeature mapPathFeature;
|
||||
|
||||
uint64_t maxInputControlPort = 0;
|
||||
std::vector<bool> isInputControlPort;
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath)
|
||||
:storagePath(storagePath)
|
||||
MapPathFeature::MapPathFeature()
|
||||
{
|
||||
lv2_state_map_path.handle = (LV2_State_Map_Path_Handle *)this;
|
||||
lv2_state_map_path.absolute_path = FnAbsolutePath;
|
||||
|
||||
+13
-1
@@ -24,12 +24,23 @@
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
struct ResourceFileMapping {
|
||||
std::string resourcePath; // absolute path of the resource directory.
|
||||
std::string storagePath; // absolute path of where resource directory files get placed.
|
||||
};
|
||||
class MapPathFeature
|
||||
{
|
||||
public:
|
||||
MapPathFeature(const std::filesystem::path &storagePath);
|
||||
MapPathFeature();
|
||||
void Prepare(MapFeature* map);
|
||||
void SetPluginStoragePath(const std::string&path) { storagePath = path;}
|
||||
|
||||
void AddResourceFileMapping(const ResourceFileMapping &resourceFileMapping)
|
||||
{
|
||||
this->resourceFileMappings.push_back(resourceFileMapping);
|
||||
}
|
||||
|
||||
const std::vector<ResourceFileMapping> &GetResourceFileMappings() const { return resourceFileMappings; }
|
||||
const std::string&GetPluginStoragePath() const { return storagePath; }
|
||||
const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;}
|
||||
const LV2_Feature*GetMakePathFeature() { return &makePathFeature;}
|
||||
@@ -60,5 +71,6 @@ namespace pipedal
|
||||
|
||||
private:
|
||||
std::string storagePath;
|
||||
std::vector<ResourceFileMapping> resourceFileMappings;
|
||||
};
|
||||
}
|
||||
+16
-9
@@ -50,13 +50,15 @@ struct TestResult
|
||||
void PrintHelp()
|
||||
{
|
||||
PrettyPrinter pp;
|
||||
pp.width(160);
|
||||
pp.width(78);
|
||||
|
||||
pp << "PiPedal Latency Tester\n";
|
||||
pp << "Copyright (c) 2022 Robin Davies\n";
|
||||
pp << "\n";
|
||||
pp << Indent(0) << "Syntax\n\n";
|
||||
pp << Indent(4) << "pipedal_latency_test [<options>] <device-name>\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test [<options>] <device-name>\n\n";
|
||||
pp << "where <device-name> is the name of an ALSA device. Typically this should be the name of a hardware "
|
||||
"device (a device name starting with 'hw:').\n\n";
|
||||
pp << Indent(0) << "Options\n\n";
|
||||
pp << Indent(15);
|
||||
|
||||
@@ -73,21 +75,26 @@ void PrintHelp()
|
||||
<< "Display this message.\n\n";
|
||||
|
||||
pp << Indent(0) << "Remarks\n\n";
|
||||
pp << Indent(4);
|
||||
pp << "PiPedal Latency Tester measure actual audio latency from output to input of an ALSA device. "
|
||||
pp << Indent(2);
|
||||
|
||||
pp << "PiPedal Latency Tester measures actual audio latency from output to input of an ALSA device.\n\n"
|
||||
<< "To run a latency test, you must connect an audio cable from left (first) output of the device "
|
||||
"under test to the left (first) input of the device under test.\n\n"
|
||||
|
||||
<< "PiPedal Latency Tester will measure the time it takes for a signal to propagate from output to input.\n\n"
|
||||
|
||||
<< "PiPedal Latency Tester measures internal buffer delays as well as operating system and "
|
||||
<< "signal delays in hardware peripherals. Latency figures will therefore be somewhat higher than "
|
||||
<< "most reported latency figures which typically only include internal buffer delays.\n\n";
|
||||
|
||||
pp
|
||||
<< "The tests run over a variety of buffer sizes. A nominal compute load is provided in order to put some "
|
||||
"stress on the audio system.\n\n"
|
||||
|
||||
<< "You may need to stop the pipedald audio service in order to access the ALSA device:\n\n"
|
||||
<< Indent(8) << "sudo systemctl stop pipedald\n\n";
|
||||
<< Indent(6) << "sudo systemctl stop pipedald\n\n";
|
||||
|
||||
pp << Indent(0) << "Examples\n\n";
|
||||
pp << Indent(4) << "pipedal_latency_test --list\n\n";
|
||||
pp << Indent(4) << "pipedal_latency_test M2\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test --list\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test hw:M2\n\n";
|
||||
}
|
||||
|
||||
void ListDevices()
|
||||
|
||||
@@ -44,16 +44,10 @@ static void DiscoveryTest()
|
||||
|
||||
}
|
||||
|
||||
void ChannelConfigtest()
|
||||
{
|
||||
cout << "--- Channel Config Test" << endl;
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) {
|
||||
DiscoveryTest();
|
||||
|
||||
ChannelConfigTest();
|
||||
|
||||
}
|
||||
|
||||
|
||||
+91
-55
@@ -31,6 +31,7 @@
|
||||
#include "AdminClient.hpp"
|
||||
#include "SplitEffect.hpp"
|
||||
#include "CpuGovernor.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include "RingBufferReader.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "atom_object.hpp"
|
||||
@@ -99,29 +100,33 @@ PiPedalModel::PiPedalModel()
|
||||
{
|
||||
OnNetworkChanging(ethernetConnected, hotspotEnabling);
|
||||
});
|
||||
hotspotManager->SetHasWifiListener(
|
||||
[this](bool hasWifi)
|
||||
{
|
||||
this->SetHasWifi(hasWifi);
|
||||
});
|
||||
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
|
||||
}
|
||||
|
||||
void PrepareSnapshostsForSave(Pedalboard&pedalboard)
|
||||
void PrepareSnapshostsForSave(Pedalboard &pedalboard)
|
||||
{
|
||||
if (pedalboard.selectedSnapshot() != -1) {
|
||||
auto¤tSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()];
|
||||
if (!currentSnapshot || currentSnapshot->isModified_)
|
||||
if (pedalboard.selectedSnapshot() != -1)
|
||||
{
|
||||
auto ¤tSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()];
|
||||
if (!currentSnapshot || currentSnapshot->isModified_)
|
||||
{
|
||||
pedalboard.selectedSnapshot(-1);
|
||||
|
||||
}
|
||||
}
|
||||
for (auto &snapshot: pedalboard.snapshots())
|
||||
for (auto &snapshot : pedalboard.snapshots())
|
||||
{
|
||||
if (snapshot) {
|
||||
if (snapshot)
|
||||
{
|
||||
snapshot->isModified_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PiPedalModel::Close()
|
||||
{
|
||||
std::unique_ptr<AudioHost> oldAudioHost;
|
||||
@@ -139,8 +144,8 @@ void PiPedalModel::Close()
|
||||
}
|
||||
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t {subscribers.begin(),subscribers.end()};
|
||||
for (auto&subscriber: t)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->Close();
|
||||
}
|
||||
@@ -299,11 +304,6 @@ void PiPedalModel::Load()
|
||||
#endif
|
||||
}
|
||||
|
||||
struct sched_param scheduler_params;
|
||||
scheduler_params.sched_priority = 10;
|
||||
memset(&scheduler_params, 0, sizeof(sched_param));
|
||||
sched_setscheduler(0, SCHED_RR, &scheduler_params);
|
||||
|
||||
RestartAudio();
|
||||
}
|
||||
|
||||
@@ -356,6 +356,10 @@ void PiPedalModel::RemoveNotificationSubsription(std::shared_ptr<IPiPedalModelSu
|
||||
void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
||||
{
|
||||
IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId);
|
||||
if (!effect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (effect->IsVst3())
|
||||
{
|
||||
int index = lv2Pedalboard->GetControlIndex(pedalItemId, symbol);
|
||||
@@ -386,7 +390,8 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
||||
PedalboardItem *item = pedalboard.GetItem(instanceId);
|
||||
if (item != nullptr)
|
||||
{
|
||||
if (!audioHost) {
|
||||
if (!audioHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool changed = this->audioHost->UpdatePluginState(*item);
|
||||
@@ -397,7 +402,7 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
||||
|
||||
Lv2PluginState newState = item->lv2State();
|
||||
|
||||
FireLv2StateChanged(instanceId,newState);
|
||||
FireLv2StateChanged(instanceId, newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,7 +488,6 @@ void PiPedalModel::FireJackConfigurationChanged(const JackConfiguration &jackCon
|
||||
{
|
||||
// noify subscribers.
|
||||
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
@@ -533,15 +537,13 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (this->pedalboard.ApplySnapshot(selectedSnapshot, pluginHost))
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto snapshot: this->pedalboard.snapshots())
|
||||
for (auto snapshot : this->pedalboard.snapshots())
|
||||
{
|
||||
if (snapshot)
|
||||
{
|
||||
@@ -556,7 +558,6 @@ void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshot
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
|
||||
this->pedalboard.snapshots(std::move(snapshots));
|
||||
@@ -564,7 +565,7 @@ void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshot
|
||||
if (selectedSnapshot != -1)
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto &snapshot: pedalboard.snapshots())
|
||||
for (auto &snapshot : pedalboard.snapshots())
|
||||
{
|
||||
if (snapshot)
|
||||
{
|
||||
@@ -572,12 +573,11 @@ void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this->FirePedalboardChanged(-1, false); // notify clients (but don't change the running pedalboard, because it's still the same)
|
||||
// this means that all clients get an up-to-date copy of the snapshots AND the currently selected snapshot if that applies
|
||||
// (and a fresh copy of the pedalboard settings as well, which is harmless, since they have not changed)
|
||||
this->SetPresetChanged(-1, true,false);
|
||||
|
||||
this->SetPresetChanged(-1, true, false);
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||
@@ -640,12 +640,13 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
|
||||
{
|
||||
if (changeSnapshotSelect && value && this->pedalboard.selectedSnapshot() != -1)
|
||||
{
|
||||
auto & snapshot = this->pedalboard.snapshots()[pedalboard.selectedSnapshot()];
|
||||
if (snapshot) {
|
||||
auto &snapshot = this->pedalboard.snapshots()[pedalboard.selectedSnapshot()];
|
||||
if (snapshot)
|
||||
{
|
||||
if (!snapshot->isModified_)
|
||||
{
|
||||
snapshot->isModified_ = true;
|
||||
FireSnapshotModified(pedalboard.selectedSnapshot(),true);
|
||||
snapshot->isModified_ = true;
|
||||
FireSnapshotModified(pedalboard.selectedSnapshot(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,10 +667,9 @@ void PiPedalModel::FireSnapshotModified(int64_t snapshotIndex, bool modified)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnSnapshotModified(snapshotIndex,modified);
|
||||
subscriber->OnSnapshotModified(snapshotIndex, modified);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
|
||||
@@ -754,15 +754,15 @@ void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard)
|
||||
#endif
|
||||
}
|
||||
|
||||
void PiPedalModel::FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State) {
|
||||
void PiPedalModel::FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnLv2StateChanged(instanceId,lv2State);
|
||||
subscriber->OnLv2StateChanged(instanceId, lv2State);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// referesh the plugin state for all plugins.
|
||||
@@ -776,13 +776,12 @@ bool PiPedalModel::SyncLv2State()
|
||||
{
|
||||
if (audioHost->UpdatePluginState(*item))
|
||||
{
|
||||
FireLv2StateChanged(item->instanceId(),item->lv2State());
|
||||
FireLv2StateChanged(item->instanceId(), item->lv2State());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
|
||||
}
|
||||
void PiPedalModel::SaveCurrentPreset(int64_t clientId)
|
||||
{
|
||||
@@ -792,7 +791,6 @@ void PiPedalModel::SaveCurrentPreset(int64_t clientId)
|
||||
SyncLv2State();
|
||||
PrepareSnapshostsForSave(pedalboard);
|
||||
|
||||
|
||||
storage.SaveCurrentPreset(this->pedalboard);
|
||||
this->SetPresetChanged(clientId, false);
|
||||
}
|
||||
@@ -821,15 +819,14 @@ int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &
|
||||
return presetId;
|
||||
}
|
||||
|
||||
|
||||
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
|
||||
SyncLv2State();
|
||||
auto pedalboard = this->pedalboard.DeepCopy();
|
||||
PrepareSnapshostsForSave(pedalboard);
|
||||
|
||||
PrepareSnapshostsForSave(pedalboard);
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
pedalboard.name(name);
|
||||
int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId);
|
||||
@@ -875,8 +872,8 @@ void PiPedalModel::NextBank(Direction direction)
|
||||
}
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < bankIndex.entries().size(); ++i)
|
||||
{
|
||||
auto&entry = bankIndex.entries()[i];
|
||||
{
|
||||
auto &entry = bankIndex.entries()[i];
|
||||
if (entry.instanceId() == bankIndex.selectedBank())
|
||||
{
|
||||
index = i;
|
||||
@@ -890,15 +887,19 @@ void PiPedalModel::NextBank(Direction direction)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
index = bankIndex.entries().size()-1;
|
||||
} else {
|
||||
index = bankIndex.entries().size() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
--index;
|
||||
}
|
||||
}
|
||||
this->OpenBank(-1,bankIndex.entries()[index].instanceId());
|
||||
this->OpenBank(-1, bankIndex.entries()[index].instanceId());
|
||||
}
|
||||
void PiPedalModel::NextPreset(Direction direction)
|
||||
{
|
||||
@@ -968,11 +969,13 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId)
|
||||
void PiPedalModel::OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex, int64_t snapshotRequestId)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
SetSnapshot((int64_t)snapshotIndex);
|
||||
} catch (const std::exception&e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("SetSnapshot failed. " << e.what()));
|
||||
}
|
||||
@@ -1008,7 +1011,6 @@ void PiPedalModel::OnNotifyNextMidiBank(const RealtimeNextMidiProgramRequest &re
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
@@ -2056,7 +2058,9 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
|
||||
if (properties.contains(propertyUri) && properties[propertyUri] == atomObject)
|
||||
{
|
||||
// do noting.
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
properties[propertyUri] = std::move(atomObject);
|
||||
}
|
||||
|
||||
@@ -2236,7 +2240,6 @@ std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
|
||||
result.push_back(MakeDummyDeviceInfo(8));
|
||||
#endif
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
const std::filesystem::path &PiPedalModel::GetWebRoot() const
|
||||
@@ -2619,7 +2622,6 @@ void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnecte
|
||||
FireNetworkChanged();
|
||||
}
|
||||
|
||||
|
||||
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
||||
{
|
||||
try
|
||||
@@ -2700,3 +2702,37 @@ void PiPedalModel::RequestShutdown(bool restart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetHasWifi(bool hasWifi)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (this->hasWifi != hasWifi)
|
||||
{
|
||||
this->hasWifi = hasWifi;
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnHasWifiChanged(hasWifi);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool PiPedalModel::GetHasWifi()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
return hasWifi;
|
||||
}
|
||||
|
||||
std::map<std::string,std::string> PiPedalModel::GetWifiRegulatoryDomains()
|
||||
{
|
||||
std::map<std::string,std::string> result;
|
||||
try {
|
||||
auto& regDb = RegDb::GetInstance();
|
||||
result = regDb.getRegulatoryDomains(storage.GetConfigRoot() / "iso_codes.json");
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
Lv2Log::warning(SS("Unable to query Wifi Regulatory domains. " << e.what()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -91,7 +91,9 @@ namespace pipedal
|
||||
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||
virtual void OnErrorMessage(const std::string &message) = 0;
|
||||
virtual void OnLv2PluginsChanging() = 0;
|
||||
|
||||
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
||||
virtual void OnHasWifiChanged(bool hasWifi) = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
@@ -107,6 +109,9 @@ namespace pipedal
|
||||
using NetworkChangedListener = std::function<void(void)>;
|
||||
|
||||
private:
|
||||
bool hasWifi = false;
|
||||
|
||||
void SetHasWifi(bool hasWifi);
|
||||
std::unique_ptr<HotspotManager> hotspotManager;
|
||||
|
||||
std::unique_ptr<Updater> updater;
|
||||
@@ -257,6 +262,9 @@ namespace pipedal
|
||||
Increase,
|
||||
Decrease
|
||||
};
|
||||
|
||||
bool GetHasWifi();
|
||||
|
||||
void NextBank(Direction direction = Direction::Increase);
|
||||
void PreviousBank() { NextBank(Direction::Decrease); }
|
||||
void NextPreset(Direction direction = Direction::Increase);
|
||||
@@ -297,6 +305,7 @@ namespace pipedal
|
||||
void SetRestartListener(std::function<void(void)> &&listener);
|
||||
void OnLv2PluginsChanged();
|
||||
void SetOnboarding(bool value);
|
||||
std::map<std::string,std::string> GetWifiRegulatoryDomains();
|
||||
|
||||
void UpdateDnsSd();
|
||||
|
||||
|
||||
@@ -1080,6 +1080,11 @@ public:
|
||||
UpdateStatus updateStatus = model.GetUpdateStatus();
|
||||
this->Reply(replyTo, "getUpdateStatus", updateStatus);
|
||||
}
|
||||
else if (message == "getHasWifi")
|
||||
{
|
||||
bool result = model.GetHasWifi();
|
||||
this->Reply(replyTo, "getHasWifi",result);
|
||||
}
|
||||
else if (message == "updateNow")
|
||||
{
|
||||
std::string updateUrl;
|
||||
@@ -1635,6 +1640,11 @@ public:
|
||||
pReader->read(&value);
|
||||
this->model.SetOnboarding(value);
|
||||
}
|
||||
else if (message == "getWifiRegulatoryDomains")
|
||||
{
|
||||
auto regulatoryDomains = this->model.GetWifiRegulatoryDomains();
|
||||
this->Reply(replyTo, "getWifiRegulatoryDomains", regulatoryDomains);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error("Unknown message received: %s", message.c_str());
|
||||
@@ -1727,6 +1737,12 @@ private:
|
||||
Send("onLv2PluginsChanging", true);
|
||||
Flush();
|
||||
}
|
||||
virtual void OnHasWifiChanged(bool hasWifi){
|
||||
Send("onHasWifiChanged", hasWifi);
|
||||
Flush();
|
||||
|
||||
}
|
||||
|
||||
virtual void OnNetworkChanging(bool hotspotConnected) override
|
||||
{
|
||||
try
|
||||
|
||||
+3
-8
@@ -243,16 +243,15 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0)
|
||||
|
||||
void PluginHost::SetPluginStoragePath(const std::filesystem::path &path)
|
||||
{
|
||||
mapPathFeature.SetPluginStoragePath(path);
|
||||
pluginStoragePath = path;
|
||||
}
|
||||
|
||||
std::string PluginHost::GetPluginStoragePath() const
|
||||
{
|
||||
return mapPathFeature.GetPluginStoragePath();
|
||||
return pluginStoragePath;
|
||||
}
|
||||
|
||||
PluginHost::PluginHost()
|
||||
: mapPathFeature("")
|
||||
{
|
||||
pWorld = nullptr;
|
||||
|
||||
@@ -260,13 +259,9 @@ PluginHost::PluginHost()
|
||||
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
||||
|
||||
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
|
||||
|
||||
mapPathFeature.Prepare(&mapFeature);
|
||||
lv2Features.push_back(mapPathFeature.GetMapPathFeature());
|
||||
lv2Features.push_back(mapPathFeature.GetMakePathFeature());
|
||||
lv2Features.push_back(mapPathFeature.GetFreePathFeature());
|
||||
lv2Features.push_back(optionsFeature.GetFeature());
|
||||
|
||||
|
||||
lv2Features.push_back(nullptr);
|
||||
|
||||
this->urids = new Urids(mapFeature);
|
||||
|
||||
+1
-1
@@ -757,7 +757,7 @@ namespace pipedal
|
||||
std::vector<const LV2_Feature *> lv2Features;
|
||||
MapFeature mapFeature;
|
||||
OptionsFeature optionsFeature;
|
||||
MapPathFeature mapPathFeature;
|
||||
std::string pluginStoragePath;
|
||||
|
||||
static void fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
void *user_data,
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef JUNK //yyx delete me.
|
||||
|
||||
#include "ss.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
@@ -66,4 +68,6 @@ namespace pipedal
|
||||
sched_setscheduler(0, currentScheduler, ¤tPriority);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 "SchedulerPriority.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include "memory.h"
|
||||
#include "sched.h"
|
||||
#include "ss.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef __linux__
|
||||
#include "unistd.h" // for nice()`
|
||||
#endif
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static constexpr int EXPECTED_RT_THREAD_PRIORITY_MAX = 81;
|
||||
|
||||
static constexpr int RT_AUDIO_THREAD_PRIORITY = 80;
|
||||
static constexpr int NICE_AUDIO_THREAD_PRIORITY = -19;
|
||||
|
||||
static constexpr int RT_AUDIOSERVICE_THREAD_PRIORITY = 10;
|
||||
static constexpr int NICE_AUDIOSERVICE_THREAD_PRIORITY = -17;
|
||||
|
||||
static constexpr int RT_LV2SCHEDULER_THREAD_PRIORITY = -1;
|
||||
static constexpr int NICE_LV2SCHEDULER_THREAD_PRIORITY = 2; // nice(2): don't let plugins screw things up by pinning the CPU
|
||||
|
||||
static constexpr int RT_WEBSERVER_THREAD_PRIORITY = -1;
|
||||
static constexpr int NICE_WEBSERVER_THREAD_PRIORITY = -2; // don't get bogged down by UI threads.
|
||||
|
||||
bool pipedal::IsRtPreemptKernel(SchedulerPriority priority)
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct oldPolicy;
|
||||
struct sched_param oldParam = {0};
|
||||
|
||||
struct sched_param param = {0};
|
||||
|
||||
auto oldPolicy = sched_getscheduler(0);
|
||||
if (sched_getparam(0,&oldParam) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto priorityMin = sched_get_priority_min(SCHED_RR);
|
||||
param.sched_priority = priorityMin;
|
||||
if (sched_setscheduler(0,SCHED_RR,¶m) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
sched_setscheduler(0,oldPolicy,&oldParam);
|
||||
return true;
|
||||
|
||||
#else
|
||||
return true; // let SetPrority sort out whether it can or can't.
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetPriority(int realtimePriority, int nicePriority, const char *priorityName)
|
||||
{
|
||||
|
||||
if (realtimePriority != -1)
|
||||
{
|
||||
int initialPriority = realtimePriority;
|
||||
int min = sched_get_priority_min(SCHED_RR);
|
||||
int max = sched_get_priority_max(SCHED_RR);
|
||||
if (realtimePriority > max) {
|
||||
realtimePriority = max + EXPECTED_RT_THREAD_PRIORITY_MAX-realtimePriority;
|
||||
}
|
||||
if (realtimePriority < min) {
|
||||
realtimePriority = min;
|
||||
}
|
||||
if (initialPriority != realtimePriority)
|
||||
{
|
||||
Lv2Log::warning(
|
||||
SS("Realtime priority adjusted from " << initialPriority << " to " << realtimePriority << ". (" << priorityName << ")")
|
||||
);
|
||||
}
|
||||
|
||||
struct sched_param param;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.sched_priority = realtimePriority;
|
||||
|
||||
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
||||
if (result == 0)
|
||||
{
|
||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::debug(SS("Failed to set RT thread priority for " << priorityName << " (" << strerror(result) << ")"));
|
||||
}
|
||||
}
|
||||
// If the RT scheduler isn't working, fall back to using NICE priority.
|
||||
int result = nice(nicePriority);
|
||||
if (result == -1)
|
||||
{
|
||||
Lv2Log::error(SS("Failed Failed to set thread priority. (" << priorityName << ")"));
|
||||
}
|
||||
}
|
||||
|
||||
void pipedal::SetThreadPriority(SchedulerPriority priority)
|
||||
{
|
||||
|
||||
#if defined(__linux__)
|
||||
switch (priority)
|
||||
{
|
||||
case SchedulerPriority::RealtimeAudio:
|
||||
SetPriority(RT_AUDIO_THREAD_PRIORITY, NICE_AUDIO_THREAD_PRIORITY, "RealtimeAudio");
|
||||
break;
|
||||
case SchedulerPriority::AudioService:
|
||||
SetPriority(RT_AUDIOSERVICE_THREAD_PRIORITY, NICE_AUDIOSERVICE_THREAD_PRIORITY, "AudioService");
|
||||
break;
|
||||
case SchedulerPriority::Lv2Scheduler:
|
||||
SetPriority(RT_LV2SCHEDULER_THREAD_PRIORITY, NICE_LV2SCHEDULER_THREAD_PRIORITY, "Lv2Scheduler");
|
||||
break;
|
||||
case SchedulerPriority::WebServerThread:
|
||||
SetPriority(RT_WEBSERVER_THREAD_PRIORITY, NICE_WEBSERVER_THREAD_PRIORITY, "Lv2Scheduler");
|
||||
break;
|
||||
default:
|
||||
Lv2Log::error("Invalid scheduler priority.");
|
||||
throw std::runtime_error("Invalid value.");
|
||||
}
|
||||
#elif defined(__WIN32)
|
||||
// Realtime thread priority must be set using MM scheduler.
|
||||
// Others must run with elevated priority, but not realtime priority (MUST run with higher priority than UI threads).
|
||||
#error "Realtime scheduling not implmented for WIN32"
|
||||
#else
|
||||
#error "Realtime scheduling not implmented for your platform"
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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
|
||||
|
||||
namespace pipedal {
|
||||
enum class SchedulerPriority {
|
||||
RealtimeAudio, // the audio service thread.
|
||||
AudioService, // non-realtime servicing of AudioThread responses.
|
||||
Lv2Scheduler, // LV2 Scheduler service thread.
|
||||
WebServerThread, // Web server threads.
|
||||
};
|
||||
|
||||
bool IsRtPreemptKernel(SchedulerPriority priority);
|
||||
|
||||
void SetThreadPriority(SchedulerPriority priority);
|
||||
}
|
||||
+171
-122
@@ -43,8 +43,7 @@ const char *BANKS_FILENAME = "index.banks";
|
||||
|
||||
#define USER_SETTINGS_FILENAME "userSettings.json";
|
||||
|
||||
|
||||
static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
|
||||
static bool isSubdirectory(const fs::path &path, const fs::path &basePath)
|
||||
{
|
||||
auto iPath = path.begin();
|
||||
for (auto i = basePath.begin(); i != basePath.end(); ++i)
|
||||
@@ -53,7 +52,7 @@ static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ((*i) != (*iPath))
|
||||
{
|
||||
return false;
|
||||
@@ -74,7 +73,6 @@ static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
|
||||
static bool hasSyntheticModRoot(const UiFileProperty &fileProperty)
|
||||
{
|
||||
return (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory()));
|
||||
|
||||
}
|
||||
|
||||
Storage::Storage()
|
||||
@@ -165,7 +163,7 @@ std::string Storage::SafeEncodeName(const std::string &name)
|
||||
}
|
||||
else
|
||||
{
|
||||
s << '%' << hex[(c >> 4) & 0x0F] << hex[(c)&0x0F];
|
||||
s << '%' << hex[(c >> 4) & 0x0F] << hex[(c) & 0x0F];
|
||||
}
|
||||
}
|
||||
return s.str();
|
||||
@@ -187,7 +185,8 @@ std::filesystem::path ResolveHomePath(const std::filesystem::path &path)
|
||||
{
|
||||
homeDirectory = getenv("USERPROFILE");
|
||||
}
|
||||
if (homeDirectory == nullptr) {
|
||||
if (homeDirectory == nullptr)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
std::filesystem::path result = homeDirectory;
|
||||
@@ -213,6 +212,15 @@ void Storage::SetDataRoot(const std::filesystem::path &path)
|
||||
this->dataRoot = ResolveHomePath(path);
|
||||
}
|
||||
|
||||
const std::filesystem::path &Storage::GetConfigRoot()
|
||||
{
|
||||
return this->configRoot;
|
||||
}
|
||||
const std::filesystem::path &Storage::GetDataRoot()
|
||||
{
|
||||
return this->dataRoot;
|
||||
}
|
||||
|
||||
static void CopyDirectory(const std::filesystem::path &source, const std::filesystem::path &destination)
|
||||
{
|
||||
for (auto &directoryEntry : std::filesystem::directory_iterator(source))
|
||||
@@ -1001,7 +1009,9 @@ bool Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings
|
||||
copyToSave.hasPassword_ = previousValue.hasPassword_;
|
||||
copyToSave.password_ = previousValue.password_;
|
||||
copyToSave.hasSavedPassword_ = previousValue.hasPassword_;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (copyToSave.IsEnabled())
|
||||
{
|
||||
copyToSave.hasSavedPassword_ = copyToSave.hasPassword_;
|
||||
@@ -1012,7 +1022,7 @@ bool Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings
|
||||
this->wifiConfigSettings = copyToSave;
|
||||
this->wifiConfigSettings.hasPassword_ = false;
|
||||
this->wifiConfigSettings.password_ = "";
|
||||
|
||||
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
@@ -1160,19 +1170,21 @@ void Storage::MergePluginPresets(const std::string &pluginUri, const PluginPrese
|
||||
else
|
||||
{
|
||||
path = GetPluginPresetPath(pluginUri);
|
||||
try {
|
||||
try
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(&existingPresets);
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Storage::MergePluginPresets: Can't reading existing plugin presets." << e.what()));
|
||||
}
|
||||
}
|
||||
for (const PluginPreset &preset: presets.presets_)
|
||||
for (const PluginPreset &preset : presets.presets_)
|
||||
{
|
||||
|
||||
existingPresets.MergePreset(preset);
|
||||
@@ -1518,9 +1530,9 @@ void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
|
||||
writer.write(bindings);
|
||||
}
|
||||
}
|
||||
static bool hasBinding(std::vector<MidiBinding> &bindings, const std::string&name)
|
||||
static bool hasBinding(std::vector<MidiBinding> &bindings, const std::string &name)
|
||||
{
|
||||
for (auto&binding: bindings)
|
||||
for (auto &binding : bindings)
|
||||
{
|
||||
if (binding.symbol() == name)
|
||||
{
|
||||
@@ -1534,7 +1546,8 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
|
||||
std::vector<MidiBinding> result;
|
||||
|
||||
std::filesystem::path fileName = this->dataRoot / "config" / "SystemMidiBindings.json";
|
||||
if (!std::filesystem::exists(fileName)) {
|
||||
if (!std::filesystem::exists(fileName))
|
||||
{
|
||||
// pick up from legacy location?
|
||||
fileName = this->configRoot / "config" / "SystemMidiBindings.json";
|
||||
}
|
||||
@@ -1542,7 +1555,8 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
if (result.size() == 2)
|
||||
@@ -1552,23 +1566,23 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
|
||||
result.push_back(MidiBinding::SystemBinding("shutdown"));
|
||||
result.push_back(MidiBinding::SystemBinding("reboot"));
|
||||
}
|
||||
if (!hasBinding(result,"prevBank"))
|
||||
if (!hasBinding(result, "prevBank"))
|
||||
{
|
||||
result.insert(result.begin(),MidiBinding::SystemBinding("nextBank")); // reverse order.
|
||||
result.insert(result.begin(),MidiBinding::SystemBinding("prevBank"));
|
||||
result.insert(result.begin(), MidiBinding::SystemBinding("nextBank")); // reverse order.
|
||||
result.insert(result.begin(), MidiBinding::SystemBinding("prevBank"));
|
||||
}
|
||||
if (!hasBinding(result,"snapshot1"))
|
||||
if (!hasBinding(result, "snapshot1"))
|
||||
{
|
||||
auto position = 4;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
result.insert(result.begin()+position,MidiBinding::SystemBinding(SS("snapshot" << (i+1))));
|
||||
result.insert(result.begin() + position, MidiBinding::SystemBinding(SS("snapshot" << (i + 1))));
|
||||
++position;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception&e)
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Can't read file " << fileName << ". " << e.what()));
|
||||
}
|
||||
@@ -1604,13 +1618,11 @@ static bool containsDirectorySeparator(const std::string &value)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void ThrowPermissionDeniedError()
|
||||
{
|
||||
throw std::logic_error("Permission denied.");
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty)
|
||||
{
|
||||
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
|
||||
@@ -1652,22 +1664,21 @@ std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty
|
||||
// sort lexicographically
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
|
||||
std::sort(result.begin(), result.end(), [&collator](const std::string&left,const std::string&right) {
|
||||
return collator->Compare(left,right) < 0;
|
||||
});
|
||||
std::sort(result.begin(), result.end(), [&collator](const std::string &left, const std::string &right)
|
||||
{ return collator->Compare(left, right) < 0; });
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool ensureNoDotDot(const std::filesystem::path&path)
|
||||
static bool ensureNoDotDot(const std::filesystem::path &path)
|
||||
{
|
||||
for (auto segment_: path)
|
||||
for (auto segment_ : path)
|
||||
{
|
||||
std::string segment = segment_.string();
|
||||
if (segment.starts_with("."))
|
||||
{
|
||||
// the linux rule: any path that consists of all '.'s.
|
||||
bool valid = false;
|
||||
for (auto c: segment)
|
||||
for (auto c : segment)
|
||||
{
|
||||
if (c != '.')
|
||||
{
|
||||
@@ -1679,22 +1690,21 @@ static bool ensureNoDotDot(const std::filesystem::path&path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void AddFilesToResult(
|
||||
FileRequestResult&result,
|
||||
FileRequestResult &result,
|
||||
const UiFileProperty &fileProperty,
|
||||
const fs::path&rootPath)
|
||||
const fs::path &rootPath)
|
||||
{
|
||||
if (!fs::exists(rootPath))
|
||||
{
|
||||
return; // silently without error.
|
||||
}
|
||||
auto & resultFiles = result.files_;
|
||||
auto &resultFiles = result.files_;
|
||||
try
|
||||
{
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
|
||||
@@ -1708,12 +1718,13 @@ static void AddFilesToResult(
|
||||
if (fileProperty.IsValidExtension(path.extension().string()))
|
||||
{
|
||||
resultFiles.push_back(
|
||||
FileEntry(path,name,false,false)
|
||||
);
|
||||
FileEntry(path, name, false, false));
|
||||
}
|
||||
}
|
||||
} else if (dir_entry.is_directory()) {
|
||||
resultFiles.push_back(FileEntry{path,name,true,fs::is_symlink(path)});
|
||||
}
|
||||
else if (dir_entry.is_directory())
|
||||
{
|
||||
resultFiles.push_back(FileEntry{path, name, true, fs::is_symlink(path)});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1726,61 +1737,58 @@ static void AddFilesToResult(
|
||||
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
|
||||
std::sort(resultFiles.begin(), resultFiles.end(),[&collator](const FileEntry&l, const FileEntry&r) {
|
||||
std::sort(resultFiles.begin(), resultFiles.end(), [&collator](const FileEntry &l, const FileEntry &r)
|
||||
{
|
||||
if (l.isDirectory_ != r.isDirectory_)
|
||||
{
|
||||
return l.isDirectory_ > r.isDirectory_;
|
||||
}
|
||||
return collator->Compare(l.displayName_,r.displayName_) < 0;
|
||||
});
|
||||
return collator->Compare(l.displayName_,r.displayName_) < 0; });
|
||||
}
|
||||
FileRequestResult Storage::GetModFileList2(const std::string &relativePath,const UiFileProperty &fileProperty)
|
||||
FileRequestResult Storage::GetModFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
|
||||
{
|
||||
FileRequestResult result;
|
||||
fs::path uploadsDirectory = GetPluginUploadDirectory();
|
||||
|
||||
if (relativePath.empty())
|
||||
{
|
||||
// return the synthetic root.
|
||||
// return the synthetic root.
|
||||
result.isProtected_ = true;
|
||||
|
||||
for (const auto&modDirectory: fileProperty.modDirectories())
|
||||
for (const auto &modDirectory : fileProperty.modDirectories())
|
||||
{
|
||||
const auto directoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
if (directoryInfo)
|
||||
{
|
||||
result.files_.push_back(
|
||||
FileEntry(uploadsDirectory / directoryInfo->pipedalPath,directoryInfo->displayName,true,true)
|
||||
);
|
||||
FileEntry(uploadsDirectory / directoryInfo->pipedalPath, directoryInfo->displayName, true, true));
|
||||
}
|
||||
|
||||
}
|
||||
if (fileProperty.useLegacyModDirectory())
|
||||
{
|
||||
result.files_.push_back(
|
||||
FileEntry(uploadsDirectory / fileProperty.directory(),fs::path(fileProperty.directory()).filename().string(),true,true)
|
||||
);
|
||||
FileEntry(uploadsDirectory / fileProperty.directory(), fs::path(fileProperty.directory()).filename().string(), true, true));
|
||||
}
|
||||
result.breadcrumbs_.push_back({"","Home"});
|
||||
result.breadcrumbs_.push_back({"", "Home"});
|
||||
result.currentDirectory_ = relativePath;
|
||||
return result;
|
||||
}
|
||||
fs::path modDirectoryPath;
|
||||
|
||||
result.breadcrumbs_.push_back({"","Home"});
|
||||
fs::path fsRelativePath { relativePath};
|
||||
result.breadcrumbs_.push_back({"", "Home"});
|
||||
fs::path fsRelativePath{relativePath};
|
||||
|
||||
for (const auto &modDirectory: fileProperty.modDirectories())
|
||||
for (const auto &modDirectory : fileProperty.modDirectories())
|
||||
{
|
||||
const ModFileTypes::ModDirectory*modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
const ModFileTypes::ModDirectory *modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
if (modDirectoryInfo)
|
||||
{
|
||||
if (isSubdirectory(fsRelativePath, uploadsDirectory / modDirectoryInfo->pipedalPath))
|
||||
{
|
||||
modDirectoryPath = uploadsDirectory / modDirectoryInfo->pipedalPath;
|
||||
result.breadcrumbs_.push_back({modDirectoryPath.string(),modDirectoryInfo->displayName});
|
||||
result.breadcrumbs_.push_back({modDirectoryPath.string(), modDirectoryInfo->displayName});
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (modDirectoryPath.empty() && fileProperty.useLegacyModDirectory())
|
||||
@@ -1788,37 +1796,53 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath,const
|
||||
if (isSubdirectory(fsRelativePath, uploadsDirectory / fileProperty.directory()))
|
||||
{
|
||||
modDirectoryPath = uploadsDirectory / fileProperty.directory();
|
||||
result.breadcrumbs_.push_back({modDirectoryPath.string(),fs::path(fileProperty.directory()).filename().string()});
|
||||
} else {
|
||||
result.breadcrumbs_.push_back({modDirectoryPath.string(), fs::path(fileProperty.directory()).filename().string()});
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowPermissionDeniedError();
|
||||
}
|
||||
}
|
||||
|
||||
// add remaing path segements as breadcrumbs.
|
||||
{
|
||||
fs::path modPath {modDirectoryPath};
|
||||
fs::path rp { relativePath};
|
||||
fs::path modPath{modDirectoryPath};
|
||||
fs::path rp{relativePath};
|
||||
auto iRp = rp.begin();
|
||||
// skip past one or more segments in the modDiretoryPath.
|
||||
for (auto iModPath = modPath.begin(); iModPath != modPath.end(); ++iModPath)
|
||||
{
|
||||
if (iRp != rp.end()) {
|
||||
if (iRp != rp.end())
|
||||
{
|
||||
++iRp;
|
||||
}
|
||||
}
|
||||
while (iRp != rp.end())
|
||||
{
|
||||
result.breadcrumbs_.push_back({*iRp,*iRp});
|
||||
result.breadcrumbs_.push_back({*iRp, *iRp});
|
||||
++iRp;
|
||||
}
|
||||
}
|
||||
|
||||
AddFilesToResult(result,fileProperty,relativePath);
|
||||
AddFilesToResult(result, fileProperty, relativePath);
|
||||
result.currentDirectory_ = relativePath;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsChildDirectory(const fs::path& child, const fs::path&parent) {
|
||||
auto iChild = child.begin();
|
||||
for (auto i = parent.begin(); i != parent.end(); ++i)
|
||||
{
|
||||
if (iChild == child.end() || (*i) != *iChild)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iChild;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const UiFileProperty &fileProperty)
|
||||
FileRequestResult Storage::GetFileList2(const std::string &relativePath_, const UiFileProperty &fileProperty)
|
||||
{
|
||||
std::string absolutePath = relativePath_;
|
||||
if (!ensureNoDotDot(absolutePath))
|
||||
@@ -1827,25 +1851,44 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const U
|
||||
}
|
||||
if (hasSyntheticModRoot(fileProperty))
|
||||
{
|
||||
return Storage::GetModFileList2(absolutePath,fileProperty);
|
||||
return Storage::GetModFileList2(absolutePath, fileProperty);
|
||||
}
|
||||
|
||||
FileRequestResult result;
|
||||
|
||||
if (fileProperty.directory().empty())
|
||||
if (fileProperty.directory().empty())
|
||||
{
|
||||
|
||||
throw std::runtime_error("fileProperty.directory() not specified.");
|
||||
}
|
||||
std::filesystem::path pluginRootDirectory = this->GetPluginUploadDirectory() / fileProperty.directory();
|
||||
if (absolutePath == "")
|
||||
if (absolutePath.empty())
|
||||
{
|
||||
absolutePath = pluginRootDirectory.string();
|
||||
}
|
||||
|
||||
// handle resource directories.
|
||||
if (!fileProperty.resourceDirectory().empty())
|
||||
{
|
||||
if (fileProperty.resourceDirectory() == absolutePath)
|
||||
{
|
||||
absolutePath = pluginRootDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsChildDirectory(absolutePath,pluginRootDirectory))
|
||||
{
|
||||
absolutePath = pluginRootDirectory;
|
||||
}
|
||||
|
||||
|
||||
result.currentDirectory_ = absolutePath;
|
||||
|
||||
|
||||
{
|
||||
result.breadcrumbs_.push_back({"","Home"});
|
||||
fs::path fsAbsolutePath {absolutePath};
|
||||
// watch out for resource files!!!
|
||||
result.breadcrumbs_.push_back({"", "Home"});
|
||||
fs::path fsAbsolutePath{absolutePath};
|
||||
auto iAbsolutePath = fsAbsolutePath.begin();
|
||||
for (auto i = pluginRootDirectory.begin(); i != pluginRootDirectory.end(); ++i)
|
||||
{
|
||||
@@ -1860,26 +1903,25 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const U
|
||||
while (iAbsolutePath != fsAbsolutePath.end())
|
||||
{
|
||||
cumulativePath /= (*iAbsolutePath);
|
||||
result.breadcrumbs_.push_back({cumulativePath.string(),iAbsolutePath->string()});
|
||||
result.breadcrumbs_.push_back({cumulativePath.string(), iAbsolutePath->string()});
|
||||
++iAbsolutePath;
|
||||
}
|
||||
}
|
||||
if (!isSubdirectory(absolutePath,pluginRootDirectory))
|
||||
if (!isSubdirectory(absolutePath, pluginRootDirectory))
|
||||
{
|
||||
throw std::runtime_error(SS("Improper location. " << absolutePath));
|
||||
}
|
||||
AddFilesToResult(result,fileProperty,absolutePath);
|
||||
AddFilesToResult(result, fileProperty, absolutePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool Storage::IsValidSampleFileName(const std::filesystem::path &fileName)
|
||||
{
|
||||
if (!fileName.is_absolute())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::filesystem::path audioFilePath = this->GetPluginUploadDirectory();
|
||||
|
||||
std::filesystem::path parentDirectory = fileName.parent_path();
|
||||
@@ -1888,10 +1930,12 @@ bool Storage::IsValidSampleFileName(const std::filesystem::path &fileName)
|
||||
|
||||
for (auto i = audioFilePath.begin(); i != audioFilePath.end(); ++i)
|
||||
{
|
||||
if (iTarget == parentDirectory.end()) {
|
||||
if (iTarget == parentDirectory.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (*i != *iTarget) {
|
||||
if (*i != *iTarget)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iTarget;
|
||||
@@ -1927,14 +1971,17 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||
if (std::filesystem::is_symlink(fileName))
|
||||
{
|
||||
std::filesystem::remove(fileName);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fileName.string().length() > 1) // guard against rm -rf / (bitter experience)
|
||||
{
|
||||
std::filesystem::remove_all(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
std::filesystem::remove(fileName);
|
||||
}
|
||||
}
|
||||
@@ -1958,7 +2005,7 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string Storage::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream&stream, size_t contentLength)
|
||||
std::string Storage::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength)
|
||||
{
|
||||
std::filesystem::path path;
|
||||
if (directory.length() != 0)
|
||||
@@ -1966,7 +2013,7 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
|
||||
path = this->MakeUserFilePath(directory, filename);
|
||||
}
|
||||
else
|
||||
|
||||
|
||||
{
|
||||
if (patchProperty.length() == 0)
|
||||
{
|
||||
@@ -1975,7 +2022,8 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
|
||||
throw std::logic_error("patchProperty directory not implemented.");
|
||||
}
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
|
||||
pipedal::ofstream_synced f(path, std::ios_base::trunc | std::ios_base::binary);
|
||||
@@ -1984,23 +2032,26 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
|
||||
throw std::logic_error(SS("Can't create file " << path << "."));
|
||||
}
|
||||
std::vector<uint8_t> buffer;
|
||||
size_t BUFFER_SIZE = 64*1024;
|
||||
size_t BUFFER_SIZE = 64 * 1024;
|
||||
buffer.resize(BUFFER_SIZE);
|
||||
char *pBuffer= (char*)&(buffer[0]);
|
||||
char *pBuffer = (char *)&(buffer[0]);
|
||||
while (contentLength != 0)
|
||||
{
|
||||
size_t thisTime = std::min(BUFFER_SIZE,contentLength);
|
||||
stream.read(pBuffer,(std::streamsize)thisTime);
|
||||
if (!stream) {
|
||||
size_t thisTime = std::min(BUFFER_SIZE, contentLength);
|
||||
stream.read(pBuffer, (std::streamsize)thisTime);
|
||||
if (!stream)
|
||||
{
|
||||
throw std::runtime_error("Unable to read body input stream.");
|
||||
}
|
||||
f.write(pBuffer,(std::streamsize)thisTime);
|
||||
if (!f) {
|
||||
f.write(pBuffer, (std::streamsize)thisTime);
|
||||
if (!f)
|
||||
{
|
||||
throw std::runtime_error("Failed to write to upload file.");
|
||||
}
|
||||
contentLength -= thisTime;
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Upload failed. " << e.what()));
|
||||
std::filesystem::remove(path);
|
||||
@@ -2010,7 +2061,7 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
|
||||
return path.string();
|
||||
}
|
||||
|
||||
std::string Storage::CreateNewSampleDirectory(const std::string&relativePath, const UiFileProperty&uiFileProperty)
|
||||
std::string Storage::CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty)
|
||||
{
|
||||
if (uiFileProperty.directory().empty())
|
||||
{
|
||||
@@ -2027,12 +2078,11 @@ std::string Storage::CreateNewSampleDirectory(const std::string&relativePath, co
|
||||
}
|
||||
std::filesystem::create_directories(path);
|
||||
return path;
|
||||
|
||||
}
|
||||
std::string Storage::RenameFilePropertyFile(
|
||||
const std::string&oldRelativePath,
|
||||
const std::string&newRelativePath,
|
||||
const UiFileProperty&uiFileProperty)
|
||||
const std::string &oldRelativePath,
|
||||
const std::string &newRelativePath,
|
||||
const UiFileProperty &uiFileProperty)
|
||||
{
|
||||
if (uiFileProperty.directory().empty())
|
||||
{
|
||||
@@ -2058,67 +2108,68 @@ std::string Storage::RenameFilePropertyFile(
|
||||
if (std::filesystem::is_directory(newPath))
|
||||
{
|
||||
throw std::runtime_error("A directory with that name already exists.");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("A file with that name already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::rename(oldPath,newPath);
|
||||
std::filesystem::rename(oldPath, newPath);
|
||||
return newPath;
|
||||
|
||||
}
|
||||
|
||||
void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std::filesystem::path&directory) const
|
||||
void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree *node, const std::filesystem::path &directory) const
|
||||
{
|
||||
for (auto child: std::filesystem::directory_iterator(directory))
|
||||
for (auto child : std::filesystem::directory_iterator(directory))
|
||||
{
|
||||
if (child.is_directory())
|
||||
{
|
||||
const auto& childPath = child.path();
|
||||
const auto &childPath = child.path();
|
||||
FilePropertyDirectoryTree::ptr childTree = std::make_unique<FilePropertyDirectoryTree>(childPath);
|
||||
FillSampleDirectoryTree(childTree.get(),childPath);
|
||||
FillSampleDirectoryTree(childTree.get(), childPath);
|
||||
node->children_.push_back(std::move(childTree));
|
||||
}
|
||||
}
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
std::sort(node->children_.begin(),node->children_.end(),
|
||||
[&collator](const FilePropertyDirectoryTree::ptr&left,const FilePropertyDirectoryTree::ptr&right)
|
||||
{
|
||||
return collator->Compare(left->directoryName_,right->directoryName_) < 0;
|
||||
});
|
||||
std::sort(node->children_.begin(), node->children_.end(),
|
||||
[&collator](const FilePropertyDirectoryTree::ptr &left, const FilePropertyDirectoryTree::ptr &right)
|
||||
{
|
||||
return collator->Compare(left->directoryName_, right->directoryName_) < 0;
|
||||
});
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty)
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty)
|
||||
{
|
||||
fs::path uploadDirectory = this->GetPluginUploadDirectory();
|
||||
fs::path uploadDirectory = this->GetPluginUploadDirectory();
|
||||
|
||||
if (hasSyntheticModRoot(uiFileProperty))
|
||||
{
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>("","Home");
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>("", "Home");
|
||||
result->isProtected_ = true;
|
||||
for (const auto& modDirectory: uiFileProperty.modDirectories())
|
||||
for (const auto &modDirectory : uiFileProperty.modDirectories())
|
||||
{
|
||||
auto modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
if (modDirectoryInfo)
|
||||
{
|
||||
auto childPath = uploadDirectory / modDirectoryInfo->pipedalPath;
|
||||
FilePropertyDirectoryTree::ptr child = std::make_unique<FilePropertyDirectoryTree>(
|
||||
childPath,modDirectoryInfo->displayName
|
||||
);
|
||||
FillSampleDirectoryTree(child.get(),childPath);
|
||||
childPath, modDirectoryInfo->displayName);
|
||||
FillSampleDirectoryTree(child.get(), childPath);
|
||||
result->children_.push_back(std::move(child));
|
||||
}
|
||||
}
|
||||
if (uiFileProperty.useLegacyModDirectory()) {
|
||||
if (uiFileProperty.useLegacyModDirectory())
|
||||
{
|
||||
auto childPath = uploadDirectory / uiFileProperty.directory();
|
||||
FilePropertyDirectoryTree::ptr child = std::make_unique<FilePropertyDirectoryTree>(
|
||||
childPath
|
||||
);
|
||||
FillSampleDirectoryTree(child.get(),childPath);
|
||||
childPath);
|
||||
FillSampleDirectoryTree(child.get(), childPath);
|
||||
result->children_.push_back(std::move(child));
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uiFileProperty.directory().empty())
|
||||
{
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
@@ -2128,15 +2179,13 @@ FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFil
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
}
|
||||
std::filesystem::path rootDirectory = uploadDirectory / uiFileProperty.directory();
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>(rootDirectory.string(),"Home");
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>(rootDirectory.string(), "Home");
|
||||
|
||||
FillSampleDirectoryTree(result.get(),rootDirectory);
|
||||
FillSampleDirectoryTree(result.get(), rootDirectory);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const PluginPresetIndex &Storage::GetPluginPresetIndex()
|
||||
{
|
||||
return pluginPresetIndex;
|
||||
|
||||
@@ -109,6 +109,8 @@ public:
|
||||
|
||||
void SetDataRoot(const std::filesystem::path& path);
|
||||
void SetConfigRoot(const std::filesystem::path& path);
|
||||
const std::filesystem::path&GetConfigRoot();
|
||||
const std::filesystem::path&GetDataRoot();
|
||||
|
||||
std::filesystem::path GetPluginUploadDirectory() const;
|
||||
|
||||
|
||||
+2
-20
@@ -1340,30 +1340,12 @@ namespace pipedal
|
||||
StopListening();
|
||||
|
||||
//linger bit to see of connections will shut down normally
|
||||
if(WaitForAllEndpointsClosed(timeoutMs/2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Lv2Log::warning("WebServer: forcibly closing connections");
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock{m_sessionsMutex};
|
||||
for (auto it = m_connections.begin(); it != m_connections.end(); ++it)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_endpoint.close(*it, websocketpp::close::status::abnormal_close, "Shutting down");
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if(WaitForAllEndpointsClosed(timeoutMs/2))
|
||||
if(WaitForAllEndpointsClosed(timeoutMs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Lv2Log::warning("WebServer: failed to close all connections.");
|
||||
return;
|
||||
|
||||
}
|
||||
virtual void Join()
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "TemporaryFile.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "json.hpp"
|
||||
#include "HotspotManager.hpp"
|
||||
|
||||
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||
#define PRESET_EXTENSION ".piPreset"
|
||||
@@ -701,6 +702,7 @@ public:
|
||||
<< ", \"socket_server_address\": \"" << webSocketAddress
|
||||
<< "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize
|
||||
<< ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true" : "false")
|
||||
<< ", \"has_wifi_device\": " << (HotspotManager::HasWifiDevice() ? " true": "false")
|
||||
<< " }";
|
||||
|
||||
return s.str();
|
||||
|
||||
@@ -230,8 +230,24 @@ static void DisplayRegulations(const std::string& country)
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRegDomains()
|
||||
{
|
||||
std::cout << "==== Regulatory domains === " << std::endl;
|
||||
std::filesystem::path dbPath = "test_data/debian_bookworm_regulatory.db";
|
||||
RegDb regdb{dbPath};
|
||||
REQUIRE(regdb.IsValid());
|
||||
|
||||
auto regDomains = regdb.getRegulatoryDomains("config/iso_codes.json");
|
||||
for (const auto®Domain: regDomains)
|
||||
{
|
||||
std::cout << " " << regDomain.first << ": " << regDomain.second << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Wifi Channel Test", "[wifi_channels_test]")
|
||||
{
|
||||
DisplayRegDomains();
|
||||
DisplayRegulations("CA");
|
||||
DisplayRegulations("US");
|
||||
DisplayRegulations("JP");
|
||||
|
||||
+3
-6
@@ -42,6 +42,7 @@
|
||||
#include <unistd.h> // for nice(
|
||||
#include <utility>
|
||||
#include "util.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -190,12 +191,8 @@ void HostWorkerThread::ThreadProc() noexcept
|
||||
{
|
||||
// run nice +2 (priority -2 on Windows)
|
||||
SetThreadName("lv2_worker");
|
||||
errno = 0;
|
||||
std::ignore = nice(2);
|
||||
if (errno != 0)
|
||||
{
|
||||
std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl;
|
||||
}
|
||||
SetThreadPriority(SchedulerPriority::Lv2Scheduler);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
+10
-8
@@ -44,10 +44,11 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <semaphore.h>
|
||||
#include "SchedulerPriority.hpp"
|
||||
|
||||
#include <systemd/sd-daemon.h>
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace pipedal;
|
||||
|
||||
#ifdef __ARM_ARCH_ISA_A64
|
||||
#define AARCH64
|
||||
@@ -79,13 +80,6 @@ static bool isJackServiceRunning()
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
static void AsanCheck()
|
||||
{
|
||||
char *t = new char[5];
|
||||
t[5] = 'x';
|
||||
delete t;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#if ENABLE_BACKTRACE
|
||||
void segvHandler(int sig) {
|
||||
@@ -244,6 +238,9 @@ int main(int argc, char *argv[])
|
||||
std::shared_ptr<WebServer> server;
|
||||
try
|
||||
{
|
||||
// (web server threads inherit the main thread priority)
|
||||
SetThreadPriority(SchedulerPriority::WebServerThread);
|
||||
|
||||
auto const address = boost::asio::ip::make_address(configuration.GetSocketServerAddress());
|
||||
port = static_cast<uint16_t>(configuration.GetSocketServerPort());
|
||||
|
||||
@@ -394,6 +391,11 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
Lv2Log::info("Shutdown complete.");
|
||||
|
||||
if (systemd)
|
||||
{
|
||||
sd_notify(0, "STOPPING=1");
|
||||
}
|
||||
|
||||
if (g_restart)
|
||||
return EXIT_FAILURE; // indicate to systemd that we want a restart.
|
||||
}
|
||||
|
||||
@@ -16,12 +16,10 @@ User=pipedal_d
|
||||
Group=pipedal_d
|
||||
Restart=always
|
||||
TimeoutStartSec=60
|
||||
NotifyAccess=all
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
WorkingDirectory=/var/pipedal
|
||||
Environment=JACK_PROMISCUOUS_SERVER=audio
|
||||
Environment=JACK_NO_AUDIO_RESERVATION=1
|
||||
|
||||
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -1,28 +1,5 @@
|
||||
Loading shields need to be of Modal type.
|
||||
|
||||
MOD fileTypes
|
||||
|
||||
- audioloop: Audio Loops, meant to be used for looper-style plugins
|
||||
- audiorecording: Audio Recordings, triggered by plugins and stored in the unit
|
||||
- audiosample: One-shot Audio Samples, meant to be used for sampler-style plugins
|
||||
- audiotrack: Audio Tracks, meant to be used as full-performance/song or backtrack
|
||||
- cabsim: Speaker Cabinets, meant as small IR audio files
|
||||
- h2drumkit: Hydrogen Drumkits, must use h2drumkit file extension
|
||||
- ir: Impulse Responses
|
||||
- midiclip: MIDI Clips, to be used in sync with host tempo, must have mid or midi file extension
|
||||
- midisong: MIDI Songs, meant to be used as full-performance/song or backtrack
|
||||
- sf2: SF2 Instruments, must have sf2 or sf3 file extension
|
||||
- sfz: SFZ Instruments, must have sfz file extension
|
||||
|
||||
|
||||
|
||||
Main menu on Pixel 5 client.
|
||||
x1 bank menu (landscape pixel 5) should show current bank.
|
||||
|
||||
Restart discover on network change?
|
||||
|
||||
|
||||
-verify clean removal of dhcpcd and nm_p2p2d
|
||||
- Start and end connectors are missing in PedalboardView when no device selected (Do they have zero outputs?)
|
||||
|
||||
X Review docs changes once we go live.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user