diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 033e617..d7afa68 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -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 diff --git a/.vscode/launch.json b/.vscode/launch.json index 3c12bd4..6e6cf77 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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, diff --git a/CMakeLists.txt b/CMakeLists.txt index 49f7bb7..e493246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/PiPedalCommon/src/CMakeLists.txt b/PiPedalCommon/src/CMakeLists.txt index 80e2108..3926a61 100644 --- a/PiPedalCommon/src/CMakeLists.txt +++ b/PiPedalCommon/src/CMakeLists.txt @@ -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) diff --git a/PiPedalCommon/src/RegDb.cpp b/PiPedalCommon/src/RegDb.cpp index 4bf9d81..3b08a40 100644 --- a/PiPedalCommon/src/RegDb.cpp +++ b/PiPedalCommon/src/RegDb.cpp @@ -18,6 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "RegDb.hpp" + #include "ss.hpp" #include #include @@ -30,8 +31,12 @@ #include #include #include +#include "json_variant.hpp" +#include "json.hpp" #include +#include + #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 g_Instance; -RegDb&RegDb::GetInstance() +RegDb &RegDb::GetInstance() { if (!g_Instance) { @@ -49,9 +54,9 @@ RegDb&RegDb::GetInstance() return *g_Instance; } -const WifiRegulations& RegDb::getWifiRegulations(const std::string&countryIso3661) const +const WifiRegulations &RegDb::getWifiRegulations(const std::string &countryIso3661) const { - for (const auto ®ulation: this->regulations) + for (const auto ®ulation : this->regulations) { if (regulation.reg_alpha2 == countryIso3661) { @@ -61,28 +66,31 @@ const WifiRegulations& RegDb::getWifiRegulations(const std::string&countryIso366 throw std::runtime_error("Invalid country code."); } - template T NlSwap(T value) { if constexpr (std::endian::native == std::endian::big) { return value; - } else { - auto value_rep = std::bit_cast,T >(value); + } + else + { + auto value_rep = std::bit_cast, T>(value); std::ranges::reverse(value_rep); return std::bit_cast(value_rep); } } template -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&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 &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; #define BIT(n) (1 << n) template -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 cpu_to_be32(uint32_t value) { @@ -326,196 +329,203 @@ inline BigEndian cpu_to_be16(uint16_t value) return BigEndian(value); } - -inline uint16_t be16_to_cpu(const BigEndian&value) +inline uint16_t be16_to_cpu(const BigEndian &value) { return value.value(); } -inline uint32_t be32_to_cpu(const BigEndian&value) +inline uint32_t be32_to_cpu(const BigEndian &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 load_regdb(u8*pData) +static std::vector 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 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; -} \ No newline at end of file +} + + +static std::map getIsoNamesDict(const std::filesystem::path&filename) +{ + std::map 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 RegDb::getRegulatoryDomains(const std::filesystem::path&isoFilenamesfile) const +{ + std::map keyToNameDict = getIsoNamesDict(isoFilenamesfile); + std::map result; + + for (const auto ®ulation : this->regulations) + { + if (keyToNameDict.contains(regulation.reg_alpha2)) + { + result[regulation.reg_alpha2] = keyToNameDict[regulation.reg_alpha2]; + } + } + return result; +} diff --git a/PiPedalCommon/src/include/RegDb.hpp b/PiPedalCommon/src/include/RegDb.hpp index 6a27b02..01a45ba 100644 --- a/PiPedalCommon/src/include/RegDb.hpp +++ b/PiPedalCommon/src/include/RegDb.hpp @@ -61,6 +61,7 @@ namespace pipedal const WifiRegulations&getWifiRegulations(const std::string&countryIso3661) const; + std::map getRegulatoryDomains(const std::filesystem::path&namesFile="/etc/pipedal/config/iso_codes.json") const; private: bool isValid = false; diff --git a/README.md b/README.md index ac613fc..558c9af 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ -Download: v1.3.66 +Download: v1.3.69 Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html). @@ -15,7 +15,7 @@ Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](htt #### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/). -#### NEW version 1.3.66 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. +#### NEW version 1.3.69 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.   diff --git a/config/iso_codes.json b/config/iso_codes.json new file mode 100644 index 0000000..0e970af --- /dev/null +++ b/config/iso_codes.json @@ -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" +} \ No newline at end of file diff --git a/debian/control b/debian/control index 20ceffd..d8385fd 100644 --- a/debian/control +++ b/debian/control @@ -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 Source: pipedal Package: pipedal -Pre-Depends: hostapd;authbind;dnsmasq Priority: optional Section: sound Version: 1.2.32 diff --git a/docs/Installing.md b/docs/Installing.md index f036a80..05c7088 100644 --- a/docs/Installing.md +++ b/docs/Installing.md @@ -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. diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index d3e1703..e68fbfc 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -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: diff --git a/docs/download.md b/docs/download.md index 719359e..71fc8fe 100644 --- a/docs/download.md +++ b/docs/download.md @@ -4,7 +4,7 @@ Download the most recent Debian (.deb) package for your platform: -- Raspberry Pi OS Bookworm (64-bit) v1.3.66 +- Raspberry Pi OS Bookworm (64-bit) v1.3.69 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. diff --git a/docs/index.md b/docs/index.md index 8adc641..d6a143b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ -v1.3.66 +v1.3.69   @@ -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.   diff --git a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl index b4f1999..4566a8e 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl @@ -93,7 +93,7 @@ cabir:impulseFile3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ TooB Cab IR is a convolution-based guitar cabinet impulse response simulator. diff --git a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl index f773249..2c1acf8 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl @@ -49,7 +49,7 @@ toob:frequencyResponseVector doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; mod:brand "TooB"; mod:label "TooB CabSim"; diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl index 8743ff1..bdac290 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl @@ -53,7 +53,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; 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 diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl index cfdc3fa..b92ebdc 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl @@ -51,7 +51,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ diff --git a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl index 81db695..22f5cec 100644 --- a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl +++ b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl @@ -65,7 +65,7 @@ inputStage:filterGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; mod:brand "TooB"; mod:label "TooB Input"; diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl index c50570a..ab5a4b0 100644 --- a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl +++ b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl @@ -67,7 +67,7 @@ pstage:stage3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 47 ; + lv2:microVersion 53 ; mod:brand "TooB"; mod:label "Power Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl index cfd715a..ab5a4b0 100644 --- a/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl +++ b/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl @@ -67,7 +67,7 @@ pstage:stage3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; mod:brand "TooB"; mod:label "Power Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl index d55d6f2..d809dba 100644 --- a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl @@ -58,7 +58,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment "TooB spectrum analyzer" ; mod:brand "TooB"; diff --git a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl index 451e884..296fb9b 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl @@ -55,7 +55,7 @@ tonestack:eqGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so index a7aa333..964e6cc 100644 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so index 8a63a3e..9c8d490 100644 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl index aece98e..4494dfe 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ Emulation of a Boss CE-2 Chorus. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl index b2330ba..dd9237e 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ A straightforward no-frills digital delay. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl index 88495ec..dafeda2 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl index a3a1dce..c54e093 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ Digital emulation of a Boss BF-2 Flanger. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl index 2212071..51f3e1f 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl index c842055..d883091 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl @@ -65,7 +65,7 @@ toobml:sagGroup doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl index 0d628f8..d4d869f 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl @@ -61,7 +61,7 @@ toobNam:eqGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ A port of Steven Atkinson's Neural Amp Modeler to LV2. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl index e3b1a4b..e185b2e 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 52 ; + lv2:microVersion 53 ; rdfs:comment """ TooB Tuner is a chromatic guitar tuner. """ ; diff --git a/react/public/var/config.json b/react/public/var/config.json index 5711fff..ff7662c 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -5,5 +5,6 @@ "max_upload_size": 536870912, "fakeAndroid": false, "ui_plugins": [], - "enable_auto_update": true + "enable_auto_update": true, + "has_wifi_device": false } \ No newline at end of file diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index 5d79c9a..cd4bb90 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -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 ( - + ); } 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)) } diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 5b188c8..e001630 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -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 = new ObservableProperty(false); onSnapshotModified: ObservableEvent = new ObservableEvent(); ui_plugins: ObservableProperty @@ -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 { 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("hello") - .then(clientId => { - this.clientId = clientId; - return this.getUpdateStatus(); // detects whether server has been upgraded. - }) - .then((updateStatus) => { + this.clientId = await this.getWebSocket().request("hello"); - return this.getWebSocket().request("plugins"); - }) - .then(data => { - this.ui_plugins.set(UiPlugin.deserialize_array(data)); - return this.getWebSocket().request("currentPedalboard"); - }) - .then(data => { - this.setModelPedalboard(new Pedalboard().deserialize(data)); + let newServerVersion = this.serverVersion = await this.getWebSocket().request("version"); + if (newServerVersion.serverVersion !== this.serverVersion.serverVersion) + { + this.reloadPage(); + return; + } - return this.getWebSocket().request("getShowStatusMonitor"); - }) - .then(data => { - this.showStatusMonitor.set(data); - - return this.getWebSocket().request("getWifiConfigSettings"); - }) - .then(data => { - this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); - - - return this.getWebSocket().request("getWifiDirectConfigSettings"); - }) - .then(data => { - this.wifiDirectConfigSettings.set(new WifiDirectConfigSettings().deserialize(data)); - - - return this.getWebSocket().request("getGovernorSettings"); - }) - .then(data => { - this.governorSettings.set(new GovernorSettings().deserialize(data)); - - - return this.getWebSocket().request("getJackServerSettings"); - }) - .then(data => { - this.jackServerSettings.set(new JackServerSettings().deserialize(data)); - - return this.getWebSocket().request("getPresets"); - }) - .then((data) => { - this.presets.set(new PresetIndex().deserialize(data)); - - return this.getWebSocket().request("getJackConfiguration"); - }) - .then((data) => { - // data.isValid = false; - // data.errorState = "Jack Audio server not running." - - this.jackConfiguration.set(new JackConfiguration().deserialize(data)); - - return this.getWebSocket().request("getJackSettings"); - }) - .then((data) => { - this.jackSettings.set(new JackChannelSelection().deserialize(data)); - - return this.getWebSocket().request("getBankIndex"); - }) - .then((data) => { - this.banks.set(new BankIndex().deserialize(data)); - - return this.getWebSocket().request("getFavorites"); - }) - .then((data) => { - this.favorites.set(data); - - return this.getWebSocket().request("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 { - 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 { - 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("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("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("version").then(data => { - this.serverVersion = data; - return this.getWebSocket().request("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("currentPedalboard"); - }) - .then(data => { - this.setModelPedalboard(new Pedalboard().deserialize(data)); + } + try { + this.countryCodes = await this.getWebSocket().request<{ [Name: string]: string }>("getWifiRegulatoryDomains"); - return this.getWebSocket().request("pluginClasses"); - }) - .then((data) => { + this.clientId = (await this.getWebSocket().request("hello")) as number; - this.plugin_classes.set(new PluginClass().deserialize(data)); - this.validatePluginClasses(this.plugin_classes.get()); + this.preloadImages( (await this.getWebSocket().request("imageList"))); + } catch (error) { + this.setError("Failed to establish connection. " + getErrorMessage(error)); + return false; + } + return await this.loadServerState(); + } + async loadServerState() : Promise + { + try + { + this.serverVersion = await this.getWebSocket().request("version"); - return this.getWebSocket().request("getPresets"); - }) - .then((data) => { - this.presets.set(new PresetIndex().deserialize(data)); + this.updateStatus.set(new UpdateStatus().deserialize(await this.getUpdateStatus())); - return this.getWebSocket().request("getWifiConfigSettings"); - }) - .then(data => { - this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); + this.hasWifiDevice.set(await this.getWebSocket().request("getHasWifi")); + + this.ui_plugins.set( + UiPlugin.deserialize_array(await this.getWebSocket().request("plugins")) + ); + this.setModelPedalboard( + new Pedalboard().deserialize( + await this.getWebSocket().request("currentPedalboard") + ) + ); + this.plugin_classes.set(new PluginClass().deserialize( + await this.getWebSocket().request("pluginClasses") + )); + this.validatePluginClasses(this.plugin_classes.get()); - return this.getWebSocket().request("getWifiDirectConfigSettings"); - }) - .then(data => { - this.wifiDirectConfigSettings.set(new WifiDirectConfigSettings().deserialize(data)); + this.presets.set( + new PresetIndex().deserialize( + await this.getWebSocket().request("getPresets") + ) + ); + this.wifiConfigSettings.set( + new WifiConfigSettings().deserialize( + await this.getWebSocket().request("getWifiConfigSettings") + )); + this.wifiDirectConfigSettings.set( + new WifiDirectConfigSettings().deserialize( + await this.getWebSocket().request("getWifiDirectConfigSettings") + )); + this.governorSettings.set(new GovernorSettings().deserialize( + await this.getWebSocket().request("getGovernorSettings") + )); + this.showStatusMonitor.set( + await this.getWebSocket().request("getShowStatusMonitor") + ); + this.jackServerSettings.set( + new JackServerSettings().deserialize( + await this.getWebSocket().request("getJackServerSettings") + ) + ); + this.jackConfiguration.set(new JackConfiguration().deserialize( + await this.getWebSocket().request("getJackConfiguration") + )); + this.jackSettings.set(new JackChannelSelection().deserialize( + await this.getWebSocket().request("getJackSettings") + )); + this.banks.set(new BankIndex().deserialize(await this.getWebSocket().request("getBankIndex"))); - return this.getWebSocket().request("getGovernorSettings"); - }) - .then(data => { - this.governorSettings.set(new GovernorSettings().deserialize(data)); + this.favorites.set(await this.getWebSocket().request("getFavorites")); - return this.getWebSocket().request("getShowStatusMonitor"); - }) - .then(data => { - this.showStatusMonitor.set(data); + this.systemMidiBindings.set(MidiBinding.deserialize_array(await this.getWebSocket().request("getSystemMidiBindings"))); - return this.getWebSocket().request("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("getJackConfiguration"); - }) - .then((data) => { - // data.isValid = false; - // data.errorState = "Jack Audio server not running." - - this.jackConfiguration.set(new JackConfiguration().deserialize(data)); - - return this.getWebSocket().request("getJackSettings"); - }) - .then((data) => { - this.jackSettings.set(new JackChannelSelection().deserialize(data)); - - return this.getWebSocket().request("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("getFavorites"); - }) - .then((data) => { - this.favorites.set(data); - - return this.getWebSocket().request("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; + } } diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index 5620021..9027588 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -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 - { + class extends ResizeResponsiveComponent { 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( ( -
+
{item}
@@ -552,7 +551,7 @@ const PluginControlView = } result.push(( -
+
{controlGroup.name}
@@ -566,7 +565,7 @@ const PluginControlView = } else { result.push(( -
+
{node as ReactNode}
)); @@ -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 = ) }
- { - this.setState({ showFileDialog: false }); - }} - onOk={(fileProperty, selectedFile) => { + {this.state.showFileDialog && ( - this.model.setPatchProperty( - this.props.instanceId, - fileProperty.patchProperty, - JsonAtom.Path(selectedFile) - ) - .then(() => { + { + 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 }); - } - } - /> this.onImeValueChange(key, value)} diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index 0009618..a08b7b5 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -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 })( { this.props.onClose() }} TransitionComponent={Transition} style={{ userSelect: "none" }} - onEnterKey={()=>{}} + onEnterKey={() => { }} >
@@ -651,7 +657,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( - this.handleInputSelection()} + 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 })( {this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}
- this.handleOutputSelection()} + 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 })( ) } - -
- CONNECTION + {(this.state.hasWifiDevice || this.state.isAndroidHosted) && + ( +
+ +
+ CONNECTION - this.handleShowWifiConfigDialog()} > - -
- - Wi-Fi auto-hotspot - - {this.state.wifiConfigSettings.getSummaryText()} - + {this.state.hasWifiDevice && ( + this.handleShowWifiConfigDialog()} > + +
+ + Wi-Fi auto-hotspot + + {this.state.wifiConfigSettings.getSummaryText()} + + +
+
+ + )} + + { + this.state.isAndroidHosted && + ( + this.model.chooseNewDevice()} > + +
+ + Connect to a different device + + + + +
+
+ ) + } + +
- - - { - this.state.isAndroidHosted && - ( - this.model.chooseNewDevice()} > - -
- - Connect to a different device - - - - -
-
- ) - } - -
+ )} {(!this.props.onboarding) ? (
@@ -942,10 +957,15 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( ) } - this.setState({ showWifiDirectConfigDialog: false })} - onOk={(wifiDirectConfigSettings: WifiDirectConfigSettings) => this.handleApplyWifiDirectConfig(wifiDirectConfigSettings)} - /> + { + this.state.showWifiDirectConfigDialog && ( + this.setState({ showWifiDirectConfigDialog: false })} + onOk={(wifiDirectConfigSettings: WifiDirectConfigSettings) => this.handleApplyWifiDirectConfig(wifiDirectConfigSettings)} + /> + + ) + } { 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 }); }} /> - { this.setState({ showSystemMidiBindingsDialog: false }); }} - /> + {this.state.showSystemMidiBindingsDialog && ( + { this.setState({ showSystemMidiBindingsDialog: false }); }} + /> + + )} {this.state.showWindowScaleDialog && ( this.setState({ showWindowScaleDialog: false }))} diff --git a/react/src/WifiConfigDialog.tsx b/react/src/WifiConfigDialog.tsx index 886af57..7e35386 100644 --- a/react/src/WifiConfigDialog.tsx +++ b/react/src/WifiConfigDialog.tsx @@ -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" } }> -
+ { if (value) { this.setState({ countryCode: value.id }) } }} + onChange={(event, value) => { if (value) { this.handleCountryChanged(value.id as string ) } }} options={this.getCountryCodeOptions()} renderInput={(params) => ()} disabled={!enabled} /> - {/* - - */} + {this.state.countryCodeErrorMessage} + + + Channel + + {this.state.channelErrorMessage} -
-
- - Channel - - - -
+
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index e92382a..726c9b9 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -34,6 +34,7 @@ #include "RtInversionGuard.hpp" #include "PiPedalException.hpp" #include "DummyAudioDriver.hpp" +#include "SchedulerPriority.hpp" #include "CpuUse.hpp" @@ -1565,32 +1566,7 @@ namespace pipedal SetThreadName("alsaDriver"); try { -#if defined(__WIN32) - // bump thread prioriy two levels to - // ensure that the service thread doesn't - // get bogged down by UIwork. Doesn't have to be realtime, but it - // MUST run at higher priority than UI threads. - xxx; // TO DO. -#elif defined(__linux__) - int min = sched_get_priority_min(SCHED_RR); - int max = sched_get_priority_max(SCHED_RR); - - struct sched_param param; - memset(¶m, 0, sizeof(param)); - param.sched_priority = RT_THREAD_PRIORITY; - - int result = sched_setscheduler(0, SCHED_RR, ¶m); - if (result == 0) - { - Lv2Log::debug("Service thread priority successfully boosted."); - } - else - { - Lv2Log::error(SS("Failed to set ALSA AudioThread priority. (" << strerror(result) << ")")); - } -#else - xxx; // TODO! -#endif + SetThreadPriority(SchedulerPriority::RealtimeAudio); bool ok = true; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 3f5cce8..5c238aa 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -20,9 +20,11 @@ #include "AudioHost.hpp" #include "util.hpp" #include +#include "SchedulerPriority.hpp" #include "Lv2Log.hpp" +#include "SchedulerPriority.hpp" #include "JackDriver.hpp" #include "AlsaDriver.hpp" #include "DummyAudioDriver.hpp" @@ -1263,30 +1265,9 @@ public: bool terminateThread; void ThreadProc() { - -#if defined(__WIN32) - // bump thread prioriy two levels to - // ensure that the service thread doesn't - // get bogged down by UIwork. Doesn't have to be realtime, but it - // MUST run at higher _ than UI threads. - xxx; // TO DO. -#elif defined(__linux__) - int min = sched_get_priority_min(SCHED_RR); - int max = sched_get_priority_max(SCHED_RR); - - struct sched_param param; - memset(¶m, 0, sizeof(param)); - param.sched_priority = min; - - int result = sched_setscheduler(0, SCHED_RR, ¶m); - if (result == 0) - { - Lv2Log::debug("Service thread priority successfully boosted."); - } SetThreadName("rtsvc"); -#else - xxx; // TODO! -#endif + SetThreadPriority(SchedulerPriority::AudioService); + int underrunMessagesGiven = 0; try { diff --git a/src/AvahiService.cpp b/src/AvahiService.cpp index b56d432..ca7c6fd 100644 --- a/src/AvahiService.cpp +++ b/src/AvahiService.cpp @@ -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); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ccb744..36fc86f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 84e8834..de5b78a 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -25,6 +25,8 @@ #include "SystemConfigFile.hpp" #include "ModFileTypes.hpp" #include "alsaCheck.hpp" +#include "RegDb.hpp" +#include "Locale.hpp" #include #include @@ -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 [] \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::vector 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; diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index 8353c8b..a4b58c5 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -37,6 +37,7 @@ #include #include #include "ss.hpp" +#include "SchedulerPriority.hpp" #include "CpuUse.hpp" @@ -223,32 +224,8 @@ namespace pipedal SetThreadName("dummyAudioDriver"); try { -#if defined(__WIN32) - // bump thread prioriy two levels to - // ensure that the service thread doesn't - // get bogged down by UIwork. Doesn't have to be realtime, but it - // MUST run at higher priority than UI threads. - xxx; // TO DO. -#elif defined(__linux__) - int min = sched_get_priority_min(SCHED_RR); - int max = sched_get_priority_max(SCHED_RR); - struct sched_param param; - memset(¶m, 0, sizeof(param)); - param.sched_priority = RT_THREAD_PRIORITY; - - int result = sched_setscheduler(0, SCHED_RR, ¶m); - if (result == 0) - { - Lv2Log::debug("Service thread priority successfully boosted."); - } - else - { - Lv2Log::error(SS("Failed to set ALSA AudioThread priority. (" << strerror(result) << ")")); - } -#else - xxx; // TODO for your platform. -#endif + SetThreadPriority(SchedulerPriority::RealtimeAudio); bool ok = true; diff --git a/src/FileEntry.cpp b/src/FileEntry.cpp index 2a6da62..3c77073 100644 --- a/src/FileEntry.cpp +++ b/src/FileEntry.cpp @@ -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() diff --git a/src/FileEntry.hpp b/src/FileEntry.hpp index f1dc3ff..c040955 100644 --- a/src/FileEntry.hpp +++ b/src/FileEntry.hpp @@ -52,6 +52,7 @@ namespace pipedal { std::vector files_; bool isProtected_ = false; std::vector breadcrumbs_; + std::string currentDirectory_; DECLARE_JSON_MAP(FileRequestResult); }; diff --git a/src/HotspotManager.cpp b/src/HotspotManager.cpp index 8d9de21..a6ba8db 100644 --- a/src/HotspotManager.cpp +++ b/src/HotspotManager.cpp @@ -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>{{{"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 lock{this->networkChangingListenerMutex}; + this->hasWifiListener = listener; + if (!this->closed && this->hasWifiListener) + { + this->hasWifiListener(this->hasWifi); + } +} +bool HotspotManagerImpl::GetHasWifi() { + std::lock_guard lock{this->networkChangingListenerMutex}; + return hasWifi; +} +void HotspotManagerImpl::SetHasWifi(bool hasWifi) +{ + std::lock_guard 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 lock{this->networkChangingListenerMutex}; @@ -1032,3 +1071,39 @@ std::vector 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 get_wireless_interfaces_sysfs() +{ + std::vector 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()); +} diff --git a/src/HotspotManager.hpp b/src/HotspotManager.hpp index cd4958e..7c0d1e3 100644 --- a/src/HotspotManager.hpp +++ b/src/HotspotManager.hpp @@ -38,6 +38,8 @@ namespace pipedal { using ptr = std::unique_ptr; + 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; + virtual void SetHasWifiListener(HasWifiListener &&listener) = 0; + virtual bool GetHasWifi() = 0; + + using PostHandle = uint64_t; using PostCallback = std::function; diff --git a/src/JackDriver.cpp b/src/JackDriver.cpp index d0b4764..e26880f 100644 --- a/src/JackDriver.cpp +++ b/src/JackDriver.cpp @@ -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 @@ -40,8 +50,6 @@ Jack as a systemd daemon proved to be unsupportable,. #include #include "Lv2Log.hpp" -#if JACK_HOST - namespace pipedal { diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index 9df333e..3f4863d 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -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 &info_, @@ -69,7 +81,6 @@ Lv2Effect::Lv2Effect( this->pathProperties.push_back(filePropertyUrid); this->pathPropertyWriters.push_back(PatchPropertyWriter(instanceId, filePropertyUrid)); - std::vector 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()); diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index a85e5ae..a6d5417 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -27,6 +27,7 @@ #include "FileBrowserFilesFeature.hpp" #include "PatchPropertyWriter.hpp" #include +#include "MapPathFeature.hpp" #include "IEffect.hpp" #include "Worker.hpp" @@ -90,7 +91,7 @@ namespace pipedal std::vector outputAtomBuffers; std::vector features; LV2_Feature *work_schedule_feature = nullptr; - + MapPathFeature mapPathFeature; uint64_t maxInputControlPort = 0; std::vector isInputControlPort; diff --git a/src/MapPathFeature.cpp b/src/MapPathFeature.cpp index 85040cd..aedd4e7 100644 --- a/src/MapPathFeature.cpp +++ b/src/MapPathFeature.cpp @@ -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; diff --git a/src/MapPathFeature.hpp b/src/MapPathFeature.hpp index ab227a4..655c42f 100644 --- a/src/MapPathFeature.hpp +++ b/src/MapPathFeature.hpp @@ -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 &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 resourceFileMappings; }; } \ No newline at end of file diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index 75857c8..b6f12e6 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -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 [] \n\n"; + pp << Indent(2) << "pipedal_latency_test [] \n\n"; + pp << "where 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() diff --git a/src/PiPedalAlsaTest.cpp b/src/PiPedalAlsaTest.cpp index e7cf5d9..4b6e3c0 100644 --- a/src/PiPedalAlsaTest.cpp +++ b/src/PiPedalAlsaTest.cpp @@ -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(); } diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index ace8873..260e7ea 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -31,6 +31,7 @@ #include "AdminClient.hpp" #include "SplitEffect.hpp" #include "CpuGovernor.hpp" +#include "RegDb.hpp" #include "RingBufferReader.hpp" #include "PiPedalUI.hpp" #include "atom_object.hpp" @@ -99,29 +100,33 @@ PiPedalModel::PiPedalModel() { OnNetworkChanging(ethernetConnected, hotspotEnabling); }); + hotspotManager->SetHasWifiListener( + [this](bool hasWifi) + { + this->SetHasWifi(hasWifi); + }); // don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities) } -void PrepareSnapshostsForSave(Pedalboard&pedalboard) +void PrepareSnapshostsForSave(Pedalboard &pedalboard) { - if (pedalboard.selectedSnapshot() != -1) { - auto¤tSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()]; - if (!currentSnapshot || currentSnapshot->isModified_) + if (pedalboard.selectedSnapshot() != -1) + { + auto ¤tSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()]; + if (!currentSnapshot || currentSnapshot->isModified_) { pedalboard.selectedSnapshot(-1); - } } - for (auto &snapshot: pedalboard.snapshots()) + for (auto &snapshot : pedalboard.snapshots()) { - if (snapshot) { + if (snapshot) + { snapshot->isModified_ = false; } } } - - void PiPedalModel::Close() { std::unique_ptr 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 t {subscribers.begin(),subscribers.end()}; - for (auto&subscriber: t) + std::vector 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_ptrGetEffect(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 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 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> &snapshot { std::lock_guard lock(mutex); - UpdateVst3Settings(pedalboard); this->pedalboard.snapshots(std::move(snapshots)); @@ -564,7 +565,7 @@ void PiPedalModel::SetSnapshots(std::vector> &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> &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 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 guard{mutex}; std::vector 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 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 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 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 lock(mutex); + if (this->hasWifi != hasWifi) + { + this->hasWifi = hasWifi; + + std::vector t{subscribers.begin(), subscribers.end()}; + + for (auto &subscriber : t) + { + subscriber->OnHasWifiChanged(hasWifi); + } + } +} +bool PiPedalModel::GetHasWifi() +{ + std::lock_guard lock(mutex); + return hasWifi; +} + +std::map PiPedalModel::GetWifiRegulatoryDomains() +{ + std::map 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; +} \ No newline at end of file diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index e8963d7..ac5428c 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -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; private: + bool hasWifi = false; + + void SetHasWifi(bool hasWifi); std::unique_ptr hotspotManager; std::unique_ptr 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 &&listener); void OnLv2PluginsChanged(); void SetOnboarding(bool value); + std::map GetWifiRegulatoryDomains(); void UpdateDnsSd(); diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 612ee1c..f1358cb 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -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 diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index d987f67..1d0ae47 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -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); diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 2638207..5fa685d 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -757,7 +757,7 @@ namespace pipedal std::vector lv2Features; MapFeature mapFeature; OptionsFeature optionsFeature; - MapPathFeature mapPathFeature; + std::string pluginStoragePath; static void fn_LilvSetPortValueFunc(const char *port_symbol, void *user_data, diff --git a/src/RtInversionGuard.hpp b/src/RtInversionGuard.hpp index 295b9eb..74e0a29 100644 --- a/src/RtInversionGuard.hpp +++ b/src/RtInversionGuard.hpp @@ -24,6 +24,8 @@ #pragma once +#ifdef JUNK //yyx delete me. + #include "ss.hpp" #include "Lv2Log.hpp" @@ -66,4 +68,6 @@ namespace pipedal sched_setscheduler(0, currentScheduler, ¤tPriority); } }; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/SchedulerPriority.cpp b/src/SchedulerPriority.cpp new file mode 100644 index 0000000..5b64d44 --- /dev/null +++ b/src/SchedulerPriority.cpp @@ -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 + +#ifdef __linux__ +#include "unistd.h" // for nice()` +#endif + +using namespace pipedal; + +static constexpr int EXPECTED_RT_THREAD_PRIORITY_MAX = 81; + +static constexpr int RT_AUDIO_THREAD_PRIORITY = 80; +static constexpr int NICE_AUDIO_THREAD_PRIORITY = -19; + +static constexpr int RT_AUDIOSERVICE_THREAD_PRIORITY = 10; +static constexpr int NICE_AUDIOSERVICE_THREAD_PRIORITY = -17; + +static constexpr int RT_LV2SCHEDULER_THREAD_PRIORITY = -1; +static constexpr int NICE_LV2SCHEDULER_THREAD_PRIORITY = 2; // nice(2): don't let plugins screw things up by pinning the CPU + +static constexpr int RT_WEBSERVER_THREAD_PRIORITY = -1; +static constexpr int NICE_WEBSERVER_THREAD_PRIORITY = -2; // don't get bogged down by UI threads. + +bool pipedal::IsRtPreemptKernel(SchedulerPriority priority) +{ + #ifdef __linux__ + struct oldPolicy; + struct sched_param oldParam = {0}; + + struct sched_param param = {0}; + + auto oldPolicy = sched_getscheduler(0); + if (sched_getparam(0,&oldParam) == -1) + { + return false; + } + auto priorityMin = sched_get_priority_min(SCHED_RR); + param.sched_priority = priorityMin; + if (sched_setscheduler(0,SCHED_RR,¶m) == -1) + { + return false; + } + sched_setscheduler(0,oldPolicy,&oldParam); + return true; + + #else + return true; // let SetPrority sort out whether it can or can't. + #endif +} + +static void SetPriority(int realtimePriority, int nicePriority, const char *priorityName) +{ + + if (realtimePriority != -1) + { + int initialPriority = realtimePriority; + int min = sched_get_priority_min(SCHED_RR); + int max = sched_get_priority_max(SCHED_RR); + if (realtimePriority > max) { + realtimePriority = max + EXPECTED_RT_THREAD_PRIORITY_MAX-realtimePriority; + } + if (realtimePriority < min) { + realtimePriority = min; + } + if (initialPriority != realtimePriority) + { + Lv2Log::warning( + SS("Realtime priority adjusted from " << initialPriority << " to " << realtimePriority << ". (" << priorityName << ")") + ); + } + + struct sched_param param; + memset(¶m, 0, sizeof(param)); + param.sched_priority = realtimePriority; + + int result = sched_setscheduler(0, SCHED_RR, ¶m); + if (result == 0) + { + Lv2Log::debug("Service thread priority successfully boosted."); + return; + } + else + { + Lv2Log::debug(SS("Failed to set RT thread priority for " << priorityName << " (" << strerror(result) << ")")); + } + } + // If the RT scheduler isn't working, fall back to using NICE priority. + int result = nice(nicePriority); + if (result == -1) + { + Lv2Log::error(SS("Failed Failed to set thread priority. (" << priorityName << ")")); + } +} + +void pipedal::SetThreadPriority(SchedulerPriority priority) +{ + +#if defined(__linux__) + switch (priority) + { + case SchedulerPriority::RealtimeAudio: + SetPriority(RT_AUDIO_THREAD_PRIORITY, NICE_AUDIO_THREAD_PRIORITY, "RealtimeAudio"); + break; + case SchedulerPriority::AudioService: + SetPriority(RT_AUDIOSERVICE_THREAD_PRIORITY, NICE_AUDIOSERVICE_THREAD_PRIORITY, "AudioService"); + break; + case SchedulerPriority::Lv2Scheduler: + SetPriority(RT_LV2SCHEDULER_THREAD_PRIORITY, NICE_LV2SCHEDULER_THREAD_PRIORITY, "Lv2Scheduler"); + break; + case SchedulerPriority::WebServerThread: + SetPriority(RT_WEBSERVER_THREAD_PRIORITY, NICE_WEBSERVER_THREAD_PRIORITY, "Lv2Scheduler"); + break; + default: + Lv2Log::error("Invalid scheduler priority."); + throw std::runtime_error("Invalid value."); + } +#elif defined(__WIN32) +// Realtime thread priority must be set using MM scheduler. +// Others must run with elevated priority, but not realtime priority (MUST run with higher priority than UI threads). +#error "Realtime scheduling not implmented for WIN32" +#else +#error "Realtime scheduling not implmented for your platform" +#endif +} diff --git a/src/SchedulerPriority.hpp b/src/SchedulerPriority.hpp new file mode 100644 index 0000000..2309eeb --- /dev/null +++ b/src/SchedulerPriority.hpp @@ -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); +} \ No newline at end of file diff --git a/src/Storage.cpp b/src/Storage.cpp index 9f1a96c..f60201f 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -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 &bindings) writer.write(bindings); } } -static bool hasBinding(std::vector &bindings, const std::string&name) +static bool hasBinding(std::vector &bindings, const std::string &name) { - for (auto&binding: bindings) + for (auto &binding : bindings) { if (binding.symbol() == name) { @@ -1534,7 +1546,8 @@ std::vector Storage::GetSystemMidiBindings() std::vector 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 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 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 Storage::GetFileList(const UiFileProperty &fileProperty) { if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory())) @@ -1652,22 +1664,21 @@ std::vector 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 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(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("","Home"); + FilePropertyDirectoryTree::ptr result = std::make_unique("", "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( - 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( - 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(rootDirectory.string(),"Home"); + FilePropertyDirectoryTree::ptr result = std::make_unique(rootDirectory.string(), "Home"); - FillSampleDirectoryTree(result.get(),rootDirectory); + FillSampleDirectoryTree(result.get(), rootDirectory); return result; } - } - const PluginPresetIndex &Storage::GetPluginPresetIndex() { return pluginPresetIndex; diff --git a/src/Storage.hpp b/src/Storage.hpp index 15df9e9..810e128 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -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; diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 5563a4e..d0f2b04 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -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 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() diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index 4097268..59c8337 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -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(); diff --git a/src/WifiChannelsTest.cpp b/src/WifiChannelsTest.cpp index 7ba602c..3c40c7f 100644 --- a/src/WifiChannelsTest.cpp +++ b/src/WifiChannelsTest.cpp @@ -230,8 +230,24 @@ static void DisplayRegulations(const std::string& country) } } +void DisplayRegDomains() +{ + std::cout << "==== Regulatory domains === " << std::endl; + std::filesystem::path dbPath = "test_data/debian_bookworm_regulatory.db"; + RegDb regdb{dbPath}; + REQUIRE(regdb.IsValid()); + + auto regDomains = regdb.getRegulatoryDomains("config/iso_codes.json"); + for (const auto®Domain: regDomains) + { + std::cout << " " << regDomain.first << ": " << regDomain.second << std::endl; + } + +} + TEST_CASE("Wifi Channel Test", "[wifi_channels_test]") { + DisplayRegDomains(); DisplayRegulations("CA"); DisplayRegulations("US"); DisplayRegulations("JP"); diff --git a/src/Worker.cpp b/src/Worker.cpp index 6f14779..1f0361d 100644 --- a/src/Worker.cpp +++ b/src/Worker.cpp @@ -42,6 +42,7 @@ #include // for nice( #include #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 { diff --git a/src/main.cpp b/src/main.cpp index e211bde..cb2dfc7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,10 +44,11 @@ #include #include +#include "SchedulerPriority.hpp" #include -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 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(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. } diff --git a/src/templates/pipedald.service.template b/src/templates/pipedald.service.template index 968b5b2..8b93a57 100644 --- a/src/templates/pipedald.service.template +++ b/src/templates/pipedald.service.template @@ -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] diff --git a/todo.txt b/todo.txt index ffdaf18..4fd65aa 100644 --- a/todo.txt +++ b/todo.txt @@ -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.