diff --git a/debugConfig/config.json b/debugConfig/config.json index ea7711f..029b9c2 100644 --- a/debugConfig/config.json +++ b/debugConfig/config.json @@ -25,6 +25,6 @@ /* Provide access point capture redirects on this gateway. */ "accessPointGateway": "192.168.0.0/24", - "accessPointServerAddress" : "192.168.0.23:8080" + "accessPointServerAddress" : "192.168.0.26:8080" } \ No newline at end of file diff --git a/react/src/JackServerSettingsDialog.tsx b/react/src/JackServerSettingsDialog.tsx index 494be63..6ab9065 100644 --- a/react/src/JackServerSettingsDialog.tsx +++ b/react/src/JackServerSettingsDialog.tsx @@ -33,7 +33,6 @@ import FormControl from '@material-ui/core/FormControl'; import Select from '@material-ui/core/Select'; import DialogContent from '@material-ui/core/DialogContent'; import MenuItem from '@material-ui/core/MenuItem'; -import { nullCast } from './Utility'; import Typography from '@material-ui/core/Typography'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; @@ -178,7 +177,7 @@ const JackServerSettingsDialog = withStyles(styles)( let settings = this.state.jackServerSettings.clone(); settings.alsaDevice = device; settings.sampleRate = selectedDevice.closestSampleRate(settings.sampleRate); - settings.bufferSize = selectedDevice.closestSampleRate(settings.bufferSize); + settings.bufferSize = selectedDevice.closestBufferSize(settings.bufferSize); this.setState({ jackServerSettings: settings, diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index a8a6a73..3c06648 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -20,7 +20,7 @@ import React, { SyntheticEvent, Component } from 'react'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; -import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; +import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel'; import ButtonBase from "@material-ui/core/ButtonBase"; import { TransitionProps } from '@material-ui/core/transitions/transition'; import Slide from '@material-ui/core/Slide'; @@ -159,9 +159,20 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this); this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this); this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this); + this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this); } + handleConnectionStateChanged() : void { + if (this.model.state.get() === State.Ready) + { + if (this.state.shuttingDown || this.state.restarting) + { + this.setState({shuttingDown: false, restarting: false}); + } + } + } + handleApplyWifiConfig(wifiConfigSettings: WifiConfigSettings): void { this.setState({showWifiConfigDialog: false}); this.model.setWifiConfigSettings(wifiConfigSettings) @@ -218,6 +229,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( if (active !== this.active) { this.active = active; if (active) { + this.model.state.addOnChangedHandler(this.handleConnectionStateChanged); this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged); this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged); @@ -235,15 +247,17 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( jackStatus: undefined }) }); + this.handleConnectionStateChanged(); this.handleJackConfigurationChanged(); this.handleJackSettingsChanged(); this.handleJackServerSettingsChanged(); this.handleWifiConfigSettingsChanged(); - // xxx UNCOMMENT ME! this.timerHandle = setInterval(() => this.tick(), 1000); + this.timerHandle = setInterval(() => this.tick(), 1000); } else { if (this.timerHandle) { clearInterval(this.timerHandle); } + this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged); this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged); this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85d57c8..048924b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -176,6 +176,7 @@ add_executable(pipedalconfig json.cpp json.hpp PiPedalException.hpp ConfigMain.cpp WifiConfigSettings.cpp WifiConfigSettings.hpp + JackServerSettings.hpp JackServerSettings.cpp SetWifiConfig.hpp SetWifiConfig.cpp SystemConfigFile.hpp SystemConfigFile.cpp SysExec.hpp SysExec.cpp diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 170eb05..33b33da 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -30,6 +30,7 @@ #include "SysExec.hpp" #include #include +#include "JackServerSettings.hpp" using namespace std; using namespace pipedal; @@ -116,6 +117,10 @@ void StopService() { cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service."; } + if (SysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS) + { + throw PiPedalException("Failed to stop the " JACK_SERVICE " service."); + } } void Uninstall() { @@ -128,13 +133,23 @@ void Uninstall() { void StartService() { + SilentSysExec("/usr/bin/pulseaudio --kill"); // interferes with Jack audio service startup. + if (SysExec(SYSTEMCTL_BIN " start " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + { + throw PiPedalException("Failed to start the " SHUTDOWN_SERVICE " service."); + } if (SysExec(SYSTEMCTL_BIN " start " NATIVE_SERVICE ".service") != EXIT_SUCCESS) { throw PiPedalException("Failed to start the " NATIVE_SERVICE " service."); } - if (SysExec(SYSTEMCTL_BIN " start " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + JackServerSettings serverSettings; + serverSettings.ReadJackConfiguration(); + if (serverSettings.IsValid()) { - throw PiPedalException("Failed to start the " SHUTDOWN_SERVICE " service."); + if (SysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS) + { + throw PiPedalException("Failed to start the " JACK_SERVICE " service."); + } } } void RestartService() diff --git a/src/JackHost.cpp b/src/JackHost.cpp index 2834d99..85304cb 100644 --- a/src/JackHost.cpp +++ b/src/JackHost.cpp @@ -905,6 +905,8 @@ public: return result; } + static int jackInstanceId; + virtual void Open(const JackChannelSelection &channelSelection) { @@ -929,14 +931,24 @@ public: jack_status_t status; try { + // need a unique instance name every timme. + std::stringstream s; + s << "PiPedal"; + if (jackInstanceId != 0) { + s << jackInstanceId; + } + ++jackInstanceId; - client = jack_client_open("PiPedal", JackNullOption, &status); + std::string instanceName = s.str(); + + client = jack_client_open(instanceName.c_str(), JackNullOption, &status); if (client == nullptr || status & JackFailure) { if (client) { jack_client_close(client); + client = nullptr; } std::string error = GetJackErrorMessage(status); Lv2Log::error(error); @@ -1389,6 +1401,8 @@ public: } }; +int JackHostImpl::jackInstanceId = 0; + JackHost *JackHost::CreateInstance(IHost *pHost) { return new JackHostImpl(pHost); diff --git a/src/JackServerSettings.cpp b/src/JackServerSettings.cpp index c86a71a..4458833 100644 --- a/src/JackServerSettings.cpp +++ b/src/JackServerSettings.cpp @@ -19,7 +19,6 @@ #include "pch.h" -#include "Lv2Log.hpp" #include "JackServerSettings.hpp" #include #include "PiPedalException.hpp" @@ -117,7 +116,6 @@ void JackServerSettings::ReadJackConfiguration() if (!input.is_open()) { - Lv2Log::error("Can't read " DRC_FILENAME); return; } @@ -146,11 +144,11 @@ void JackServerSettings::ReadJackConfiguration() this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods"); this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate"); this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device"); - valid_ = true; + this->valid_ = true; } catch (std::exception &) { - Lv2Log::error("Can't parse " DRC_FILENAME); + //Lv2Log::error("Can't parse " DRC_FILENAME); } } } @@ -168,7 +166,6 @@ void JackServerSettings::Write() if (!input.is_open()) { - Lv2Log::error("Can't read " DRC_FILENAME); return; } @@ -176,7 +173,6 @@ void JackServerSettings::Write() { if (input.eof()) { - Lv2Log::error("Premature end of file in " DRC_FILENAME); break; } std::getline(input,lastLine); @@ -209,8 +205,8 @@ void JackServerSettings::Write() { // jack1 incantation for promiscuous servers. output << "#!/bin/sh" <alsaDevice_ + << " -dalsa -d" << this->alsaDevice_ << " -r" << this->sampleRate_ << " -p" << this->bufferSize_ << " -n" << this->numberOfBuffers_ << " -Xseq" @@ -235,9 +231,6 @@ void JackServerSettings::Write() } catch (const std::exception &e) { - stringstream s; - s << "jack - " << e.what(); - Lv2Log::error(s.str()); } } diff --git a/src/PiPedalAlsa.cpp b/src/PiPedalAlsa.cpp index 477a2ce..d909d4b 100644 --- a/src/PiPedalAlsa.cpp +++ b/src/PiPedalAlsa.cpp @@ -26,12 +26,26 @@ using namespace pipedal; static uint32_t RATES[] = { - 44100, 48000, 44100 * 2, 48000 * 2, 44100 * 4, 48000 * 4}; - + 22050, 24000, 44100, 48000, 44100 * 2, 48000 * 2, 44100 * 4, 48000 * 4}; std::mutex alsaMutex; -std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() +bool PiPedalAlsaDevices::getCachedDevice(const std::string &name, AlsaDeviceInfo *pResult) +{ + auto it = cachedDevices.find(name); + if (it != cachedDevices.end()) + { + *pResult = it->second; + return true; + } + return false; +} +void PiPedalAlsaDevices::cacheDevice(const std::string &name, const AlsaDeviceInfo &deviceInfo) +{ + cachedDevices[name] = deviceInfo; +} + +std::vector PiPedalAlsaDevices::GetAlsaDevices() { std::lock_guard guard{alsaMutex}; @@ -50,7 +64,7 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() if (cardNum < 0) // No more cards break; - + { std::stringstream ss; ss << "hw:" << cardNum; @@ -77,17 +91,17 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() AlsaDeviceInfo info; info.cardId_ = cardNum; info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo); - const char* driver = snd_ctl_card_info_get_driver(alsaInfo); + const char *driver = snd_ctl_card_info_get_driver(alsaInfo); info.name_ = snd_ctl_card_info_get_name(alsaInfo); info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); - snd_pcm_t *hDevice = nullptr; // must support capture AND playback err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0); - if (err == 0) { + if (err == 0) + { snd_pcm_close(hDevice); } if (err == 0) @@ -104,7 +118,7 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() if (err == 0) { unsigned int minRate = 0, maxRate = 0; - snd_pcm_uframes_t minBufferSize,maxBufferSize; + snd_pcm_uframes_t minBufferSize, maxBufferSize; int dir; err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir); if (err == 0) @@ -113,11 +127,11 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() } if (err == 0) { - err = snd_pcm_hw_params_get_buffer_size_min(params,&minBufferSize); + err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize); } - if (err == 0) + if (err == 0) { - err = snd_pcm_hw_params_get_buffer_size_max(params,&maxBufferSize); + err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize); } if (err == 0) { @@ -131,7 +145,8 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() } info.minBufferSize_ = (uint32_t)minBufferSize; info.maxBufferSize_ = (uint32_t)maxBufferSize; - result.push_back(std::move(info)); + cacheDevice(info.name_, info); + result.push_back(info); } } } @@ -139,6 +154,13 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() snd_pcm_hw_params_free(params); snd_pcm_close(hDevice); } + else + { + if (getCachedDevice(info.name_, &info)) + { + result.push_back(info); + } + } } snd_ctl_card_info_free(alsaInfo); snd_ctl_close(hDevice); @@ -148,47 +170,6 @@ std::vector PiPedalAlsaDevices::GetAvailableAlsaDevices() return result; } - -void PiPedalAlsaDevices::PreLoadJackDevice(const std::string&deviceName) -{ - // save the device info before we start the jack server, because - // we won't be able to do it later. - std::vector devices = GetAvailableAlsaDevices(); - this->hasJackDevice = false; - for (auto &device : devices) - { - if (device.id_ == deviceName) { - this->currentJackDevice = device; - this->hasJackDevice = true; - break; - } - } -} - -std::vector PiPedalAlsaDevices::GetAlsaDevices() -{ - std::vector devices = GetAvailableAlsaDevices(); - if (this->hasJackDevice) - { - bool found = false; - for (auto &device: devices) - { - if (device.id_ == this->currentJackDevice.id_) - { - found = true; - break; - } - } - if (!found) - { - devices.push_back(this->currentJackDevice); - } - } - return devices; -} - - - JSON_MAP_BEGIN(AlsaDeviceInfo) JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId) JSON_MAP_REFERENCE(AlsaDeviceInfo, id) diff --git a/src/PiPedalAlsa.hpp b/src/PiPedalAlsa.hpp index 849435f..545cc36 100644 --- a/src/PiPedalAlsa.hpp +++ b/src/PiPedalAlsa.hpp @@ -37,12 +37,12 @@ namespace pipedal { class PiPedalAlsaDevices { - bool hasJackDevice = false; - AlsaDeviceInfo currentJackDevice; - std::vector GetAvailableAlsaDevices(); - public: - void PreLoadJackDevice(const std::string&deviceName); + std::map cachedDevices; + bool getCachedDevice(const std::string&name, AlsaDeviceInfo*pResult); + void cacheDevice(const std::string&name, const AlsaDeviceInfo&deviceInfo); + public: + std::vector GetAlsaDevices(); }; } \ No newline at end of file diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 24ee3ae..e6a03f1 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -47,6 +47,8 @@ std::string AtomToJson(int length, uint8_t *pData) PiPedalModel::PiPedalModel() { this->pedalBoard = PedalBoard::MakeDefault(); + this->jackServerSettings.ReadJackConfiguration(); + } void PiPedalModel::Close() @@ -87,7 +89,6 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration) this->jackServerSettings.ReadJackConfiguration(); - alsaDevices.PreLoadJackDevice(this->jackServerSettings.GetAlsaDevice()); storage.SetDataRoot(configuration.GetLocalStoragePath().c_str()); storage.Initialize(); @@ -862,10 +863,6 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet throw PiPedalException("Can't change server settings when running a debug server."); } - if (this->jackServerSettings.GetAlsaDevice() != jackServerSettings.GetAlsaDevice()) - { - this->alsaDevices.PreLoadJackDevice(jackServerSettings.GetAlsaDevice()); - } this->jackServerSettings = jackServerSettings; diff --git a/src/SysExec.cpp b/src/SysExec.cpp index d916d16..6dcd04d 100644 --- a/src/SysExec.cpp +++ b/src/SysExec.cpp @@ -28,7 +28,6 @@ using namespace pipedal; using namespace std; - // find on path, but ONLY /usr/bin and /usr/sbin static std::filesystem::path findOnSystemPath(const std::string &command) @@ -60,23 +59,42 @@ static std::filesystem::path findOnSystemPath(const std::string &command) throw PiPedalException(s.str()); } +void pipedal::SilentSysExec(const char *szCommand) +{ + std::stringstream s; + s << szCommand << " 2>&1"; + FILE *output = popen(s.str().c_str(), "r"); + char buffer[512]; + if (output) + { + while (!feof(output)) + { + fgets(buffer, sizeof(buffer), output); + } + pclose(output); + } +} -int pipedal::SysExec(const char*szCommand) { - char*args = strdup(szCommand); +int pipedal::SysExec(const char *szCommand) +{ + char *args = strdup(szCommand); int argc; - std::vector argv; + std::vector argv; char *p = args; - while (*p) { + while (*p) + { argv.push_back(p); - while (*p && *p != ' ') { + while (*p && *p != ' ') + { ++p; } - if (*p) { + if (*p) + { *p++ = '\0'; - while (*p && *p == ' ') + while (*p && *p == ' ') { ++p; } @@ -91,12 +109,11 @@ int pipedal::SysExec(const char*szCommand) { } if (!std::filesystem::exists(execPath)) { - throw PiPedalException( SS("Path does not exist: " << execPath << ".")); + throw PiPedalException(SS("Path does not exist: " << execPath << ".")); } - argv[0] = (char*)(execPath.c_str()); - - - char**rawArgv = &argv[0]; + argv[0] = (char *)(execPath.c_str()); + + char **rawArgv = &argv[0]; int pbPid; int returnValue = 0; @@ -107,7 +124,7 @@ int pipedal::SysExec(const char*szCommand) { } else { - free((void*)args); + free((void *)args); waitpid(pbPid, &returnValue, 0); int exitStatus = WEXITSTATUS(returnValue); return exitStatus; diff --git a/src/SysExec.hpp b/src/SysExec.hpp index 2cb8aa9..3dd728b 100644 --- a/src/SysExec.hpp +++ b/src/SysExec.hpp @@ -24,4 +24,7 @@ namespace pipedal { // exec a command, returning the actual exit code (unlike execXX() or system() ) int SysExec(const char*szCommand); +// execute a command, suppressing output. +void SilentSysExec(const char *szCommand); + } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d294b7c..42599ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,6 +37,8 @@ #include #include +#include + using namespace pipedal; using namespace boost::beast; @@ -45,6 +47,16 @@ using namespace boost::beast; sem_t signalSemaphore; +bool HasAlsaDevice(const std::vector devices, const std::string &deviceId) +{ + for (auto &device : devices) + { + if (device.id_ == deviceId) + return true; + } + return false; +} + class application_category : public boost::system::error_category { public: @@ -52,13 +64,14 @@ public: std::string message(int ev) const { return "error message"; } }; +static volatile bool g_SigBreak = false; void sig_handler(int signo) { + g_SigBreak = true; sem_post(&signalSemaphore); } using namespace boost::system; - class DownloadIntercept : public RequestHandler { PiPedalModel *model; @@ -81,7 +94,8 @@ public: std::string strInstanceId = request_uri.query("id"); if (strInstanceId != "") return true; - } else if (request_uri.segment(1) == "uploadPreset") + } + else if (request_uri.segment(1) == "uploadPreset") { return true; } @@ -90,19 +104,19 @@ public: std::string strInstanceId = request_uri.query("id"); if (strInstanceId != "") return true; - } else if (request_uri.segment(1) == "uploadBank") + } + else if (request_uri.segment(1) == "uploadBank") { return true; } return false; } - std::string GetContentDispositionHeader(const std::string &name, const std::string &extension) { - std::string fileName = name.substr(0,64) + extension; + std::string fileName = name.substr(0, 64) + extension; std::stringstream s; - + s << "attachment; filename*=" << HtmlHelper::Rfc5987EncodeFileName(fileName) << "; filename=\"" << HtmlHelper::SafeFileName(fileName) << "\""; std::string result = s.str(); return result; @@ -131,11 +145,10 @@ public: std::string strInstanceId = request_uri.query("id"); int64_t instanceId = std::stol(strInstanceId); BankFile bank; - model->getBank(instanceId,&bank); - + model->getBank(instanceId, &bank); std::stringstream s; - json_writer writer(s,true); // do what we can to reduce the file size. + json_writer writer(s, true); // do what we can to reduce the file size. writer.write(bank); *pContent = s.str(); *pName = bank.name(); @@ -160,7 +173,7 @@ public: res.set(http::field::content_length, content.length()); res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION)); return; - } + } if (request_uri.segment(1) == "downloadBank") { std::string name; @@ -172,7 +185,7 @@ public: res.set(http::field::content_length, content.length()); res.set(http::field::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); return; - } + } throw PiPedalException("Not found."); } catch (const std::exception &e) @@ -207,7 +220,8 @@ public: res.set(http::field::content_length, content.length()); res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION)); res.body() = content; - } else if (request_uri.segment(1) == "downloadBank") + } + else if (request_uri.segment(1) == "downloadBank") { std::string name; std::string content; @@ -218,7 +232,9 @@ public: res.set(http::field::content_length, content.length()); res.set(http::field::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); res.body() = content; - } else { + } + else + { throw PiPedalException("Not found"); } } @@ -258,7 +274,7 @@ public: BankFile bankFile; reader.read(&bankFile); - uint64_t instanceId = model->uploadPreset(bankFile,uploadAfter); + uint64_t instanceId = model->uploadPreset(bankFile, uploadAfter); res.set(http::field::content_type, "application/json"); res.set(http::field::cache_control, "no-cache"); @@ -268,8 +284,8 @@ public: res.set(http::field::content_length, result.length()); res.body() = result; - } - else if (request_uri.segment(1) == "uploadBank" ) + } + else if (request_uri.segment(1) == "uploadBank") { std::string presetBody = req.body(); std::stringstream s(presetBody); @@ -285,7 +301,7 @@ public: BankFile bankFile; reader.read(&bankFile); - uint64_t instanceId = model->uploadBank(bankFile,uploadAfter); + uint64_t instanceId = model->uploadBank(bankFile, uploadAfter); res.set(http::field::content_type, "application/json"); res.set(http::field::cache_control, "no-cache"); @@ -296,7 +312,8 @@ public: res.body() = result; } - else { + else + { throw PiPedalException("Not found"); } } @@ -312,7 +329,6 @@ public: } } } - }; /* When hosting a react app, replace /var/config.json with @@ -326,7 +342,7 @@ private: uint64_t maxUploadSize; public: - InterceptConfig(int portNumber,uint64_t maxUploadSize) + InterceptConfig(int portNumber, uint64_t maxUploadSize) : RequestHandler("/var/config.json"), maxUploadSize(maxUploadSize) { @@ -414,11 +430,17 @@ public: } }; +static bool isJackServiceRunning() +{ + // look for the jack shmem . + std::filesystem::path path = "/dev/shm/jack_default_0"; + return std::filesystem::exists(path); +} + uint16_t g_ShutdownPort = 0; int main(int argc, char *argv[]) { - sem_init(&signalSemaphore, 0, 0); @@ -471,7 +493,7 @@ int main(int argc, char *argv[]) { std::cout << "Usage: pipedal [] [options...]\n\n" << "Options:\n" - << " -systemd: Log to systemd journals.\n" + << " -systemd: Log to systemd journals, wait for jack service.\n" << " -port: Port to listen on e.g. 0.0.0.0:80\n" << " -shutdownPort: Port for the shutdown service.\n" << "Example:\n" @@ -556,13 +578,65 @@ int main(int argc, char *argv[]) { PiPedalModel model; + // Pre-populate ALSA device info,wait for jackd service (daemon only) + if (systemd) + { + auto devices = model.GetAlsaDevices(); + + auto serverSettings = model.GetJackServerSettings(); + if (serverSettings.IsValid()) + { + // wait up to 15 seconds for the midi device to come online. + if (!HasAlsaDevice(devices, serverSettings.GetAlsaDevice())) + { + Lv2Log::info("Waiting for ALSA device " + serverSettings.GetAlsaDevice()); + for (int i = 0; i < 15; ++i) + { + sleep(1); + devices = model.GetAlsaDevices(); + if (HasAlsaDevice(devices, serverSettings.GetAlsaDevice())) + { + break; + } + if (g_SigBreak) + break; + } + } + } + Lv2Log::info("Waiting for jack service."); + + // pre-cache device info. + model.GetAlsaDevices(); + sd_notify(0, "READY=1"); + + if (!isJackServiceRunning()) + { + Lv2Log::info("Waiting for Jack service."); + // wait up to 15 seconds for the jack service to come online. + for (int i = 0; i < 15; ++i) + { + // use the time to prepopulate ALSA device cache before jack + // opens the device and we can't read properties. + model.GetAlsaDevices(); + + sleep(1); + if (isJackServiceRunning()) + { + break; + } + } + Lv2Log::info("Found Jack service."); + } + sleep(3); // jack needs a little time to get up to speed. + } + model.Load(configuration); auto pipedalSocketFactory = MakePiPedalSocketFactory(model); server->AddSocketFactory(pipedalSocketFactory); - std::shared_ptr interceptConfig{new InterceptConfig(port,configuration.GetMaxUploadSize())}; + std::shared_ptr interceptConfig{new InterceptConfig(port, configuration.GetMaxUploadSize())}; server->AddRequestHandler(interceptConfig); std::shared_ptr downloadIntercept = std::make_shared(&model); @@ -572,6 +646,11 @@ int main(int argc, char *argv[]) sem_wait(&signalSemaphore); + if (systemd) + { + sd_notify(0, "STOPPING=1"); + } + Lv2Log::info("Shutting down gracefully."); model.Close(); Lv2Log::info("Stopping web server."); diff --git a/src/template.service b/src/template.service index 98c8aad..0cf5497 100644 --- a/src/template.service +++ b/src/template.service @@ -1,12 +1,14 @@ [Unit] Description=${DESCRIPTION} -After=jack.service +Before=jack.service After=network.target -BindsTo=jack.service +After=sound.target +After=local-fs.target [Service] +Type=notify LimitMEMLOCK=infinity LimitRTPRIO=95 ExecStart=${COMMAND} @@ -16,8 +18,8 @@ Restart=always RestartSec=25 TimeoutStopSec=10 WorkingDirectory=/var/pipedal -Environment=JACK_PROMISCUOUS_SERVER= -Environment=JACK_STJACK_NO_START_SERVER= +Environment=JACK_PROMISCUOUS_SERVER=jack +Environment=JACK_NO_START_SERVER=1 [Install]