Merge pull request #257 from rerdavies/dev

v1.3.69 dev merge.
This commit is contained in:
Robin Davies
2024-11-22 19:28:36 -05:00
committed by GitHub
72 changed files with 1665 additions and 978 deletions
+19
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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})
+6 -6
View File
@@ -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
View File
@@ -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 &regulation: this->regulations)
for (const auto &regulation : 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 &regulation : this->regulations)
{
if (keyToNameDict.contains(regulation.reg_alpha2))
{
result[regulation.reg_alpha2] = keyToNameDict[regulation.reg_alpha2];
}
}
return result;
}
+1
View File
@@ -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;
+2 -2
View File
@@ -6,7 +6,7 @@
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.66</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.69</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
@@ -15,7 +15,7 @@ Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](htt
#### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/).
#### NEW version 1.3.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.
&nbsp;
+248
View File
@@ -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"
}
+1 -3
View File
@@ -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
View File
@@ -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.
+15
View File
@@ -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
View File
@@ -4,7 +4,7 @@
Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.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
View File
@@ -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>
&nbsp;
@@ -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.
&nbsp;
+1 -1
View File
@@ -93,7 +93,7 @@ cabir:impulseFile3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
rdfs:comment """
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
+1 -1
View File
@@ -49,7 +49,7 @@ toob:frequencyResponseVector
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 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 """
+1 -1
View File
@@ -65,7 +65,7 @@ inputStage:filterGroup
doap:license <https://two-play.com/TooB/licenses/isc> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
mod:brand "TooB";
mod:label "TooB Input";
+1 -1
View File
@@ -67,7 +67,7 @@ pstage:stage3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 47 ;
lv2:microVersion 53 ;
mod:brand "TooB";
mod:label "Power Stage";
+1 -1
View File
@@ -67,7 +67,7 @@ pstage:stage3
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
mod:brand "TooB";
mod:label "Power Stage";
+1 -1
View File
@@ -58,7 +58,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
rdfs:comment "TooB spectrum analyzer" ;
mod:brand "TooB";
+1 -1
View File
@@ -55,7 +55,7 @@ tonestack:eqGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -40,7 +40,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
rdfs:comment """
Emulation of a Boss CE-2 Chorus.
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
rdfs:comment """
A straightforward no-frills digital delay.
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 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.
+1 -1
View File
@@ -41,7 +41,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 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.
+1 -1
View File
@@ -65,7 +65,7 @@ toobml:sagGroup
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 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.
+1 -1
View File
@@ -40,7 +40,7 @@
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 52 ;
lv2:microVersion 53 ;
rdfs:comment """
TooB Tuner is a chromatic guitar tuner.
""" ;
+2 -1
View File
@@ -5,5 +5,6 @@
"max_upload_size": 536870912,
"fakeAndroid": false,
"ui_plugins": [],
"enable_auto_update": true
"enable_auto_update": true,
"has_wifi_device": false
}
+17 -9
View File
@@ -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
View File
@@ -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;
}
}
+32 -29
View File
@@ -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)}
+74 -51
View File
@@ -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 }))}
+61 -36
View File
@@ -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
View File
@@ -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(&param, 0, sizeof(param));
param.sched_priority = RT_THREAD_PRIORITY;
int result = sched_setscheduler(0, SCHED_RR, &param);
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
View File
@@ -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(&param, 0, sizeof(param));
param.sched_priority = min;
int result = sched_setscheduler(0, SCHED_RR, &param);
if (result == 0)
{
Lv2Log::debug("Service thread priority successfully boosted.");
}
SetThreadName("rtsvc");
#else
xxx; // TODO!
#endif
SetThreadPriority(SchedulerPriority::AudioService);
int underrunMessagesGiven = 0;
try
{
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+2 -25
View File
@@ -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(&param, 0, sizeof(param));
param.sched_priority = RT_THREAD_PRIORITY;
int result = sched_setscheduler(0, SCHED_RR, &param);
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;
+1
View File
@@ -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()
+1
View File
@@ -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
View File
@@ -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());
}
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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()
-6
View File
@@ -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
View File
@@ -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&currentSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()];
if (!currentSnapshot || currentSnapshot->isModified_)
if (pedalboard.selectedSnapshot() != -1)
{
auto &currentSnapshot = 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;
}
+9
View File
@@ -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();
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+5 -1
View File
@@ -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, &currentPriority);
}
};
}
}
#endif
+147
View File
@@ -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,&param) == -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(&param, 0, sizeof(param));
param.sched_priority = realtimePriority;
int result = sched_setscheduler(0, SCHED_RR, &param);
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
}
+33
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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()
+2
View File
@@ -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();
+16
View File
@@ -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&regDomain: 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
View File
@@ -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
View File
@@ -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.
}
+1 -3
View File
@@ -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 -24
View File
@@ -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.