diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 726c9b9..47d1c3a 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -1657,7 +1657,7 @@ namespace pipedal Lv2Log::error(e.what()); Lv2Log::error("ALSA audio thread terminated abnormally."); } - this->driverHost->OnAudioStopped(); + this->driverHost->OnAlsaDriverStopped(); // if we terminated abnormally, pump messages until we have been terminated. if (!terminateAudio()) diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 8c3e4c2..29b1f2c 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -212,7 +212,7 @@ class AlsaTester: private AudioDriverHost { } - virtual void OnAudioStopped() { + virtual void OnAlsaDriverStopped() { } virtual void OnProcess(size_t nFrames) { if (testType == TestType::NullTest) return; diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp index 0d45658..6660761 100644 --- a/src/AudioDriver.hpp +++ b/src/AudioDriver.hpp @@ -48,7 +48,7 @@ namespace pipedal { public: virtual void OnProcess(size_t nFrames) = 0; virtual void OnUnderrun() = 0; - virtual void OnAudioStopped() = 0; + virtual void OnAlsaDriverStopped() = 0; virtual void OnAudioTerminated() = 0; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 2e6d1d2..36c62aa 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -183,7 +183,8 @@ namespace pipedal reader.read(&vProperty); if (!vProperty.is_null()) { - try { + try + { vProperty = pluginHost.MapPath(vProperty); // now to atom format (what we want on the rt thread0) @@ -194,7 +195,8 @@ namespace pipedal pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom)); memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size()); this->pathPatchProperties.push_back(std::move(pathPatchProperty)); - } catch (const std::exception &e) + } + catch (const std::exception &e) { Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what())); } @@ -207,7 +209,7 @@ namespace pipedal void ApplyValues(IEffect *effect) { - effect->SetBypass(this->enabled); + effect->SetBypass(this->enabled); if (effect != pEffect) { throw std::runtime_error("Wrong effect"); @@ -907,7 +909,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) { - //eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); + // eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged); if (listenForMidiEvent) @@ -1100,10 +1102,12 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW { return this->active && (!this->audioStopped) && !this->isDummyAudioDriver; } - virtual void OnAudioStopped() override + virtual void OnAlsaDriverStopped() override { + this->audioStopped = true; Lv2Log::info("Audio stopped."); + this->realtimeWriter.AudioTerminatedAbnormally(); } virtual void OnAudioTerminated() override { @@ -1256,12 +1260,14 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW return this->sampleRate; } - void OnAudioComplete() + void HandleAudioTerminatedAbnormally() { - // there is actually no compelling circumstance in which this should ever happen. - Lv2Log::error("Audio processing terminated unexpectedly."); - realtimeWriter.AudioStopped(); + + if (pNotifyCallbacks) + { + pNotifyCallbacks->OnAlsaDriverTerminatedAbnormally(); + } } std::vector atomBuffer; std::vector realtimeAtomBuffer; @@ -1271,7 +1277,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW { SetThreadName("rtsvc"); SetThreadPriority(SchedulerPriority::AudioService); - + int underrunMessagesGiven = 0; try { @@ -1456,7 +1462,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW { LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body; this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value); - //this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); + // this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); } } } @@ -1492,11 +1498,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW hostReader.read(&buffer); OnPathPropertyReceived(buffer); } - else if (command == RingBufferCommand::AudioStopped) + else if (command == RingBufferCommand::AudioTerminatedAbnormally) { AudioStoppedBody body; hostReader.read(&body); - OnAudioComplete(); + HandleAudioTerminatedAbnormally(); return; } else if (command == RingBufferCommand:: @@ -1621,7 +1627,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW if (jackServerSettings.IsDummyAudioDevice()) { this->isDummyAudioDriver = true; - this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice())); + this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice())); } else { @@ -1927,7 +1933,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW } private: - //virtual bool UpdatePluginStates(Pedalboard &pedalboard) override; + // virtual bool UpdatePluginStates(Pedalboard &pedalboard) override; virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override; class RestartThread @@ -2051,7 +2057,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW result.msSinceLastUnderrun_ = (uint64_t)dt; - result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC()*1000)); + result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC() * 1000)); result.active_ = IsAudioRunning(); result.restarting_ = this->restarting; @@ -2065,7 +2071,9 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW if (result.hasCpuGovernor_) { result.governor_ = GetGovernor(); - } else { + } + else + { result.governor_ = ""; } @@ -2110,7 +2118,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector &bindin else if (i->symbol() == "prevBank") { this->prevBankMidiBinding.SetBinding(*i); - } + } else if (i->symbol() == "nextProgram") { this->nextPresetMidiBinding.SetBinding(*i); diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index cbaf7d2..9942042 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -170,6 +170,8 @@ namespace pipedal virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0; virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0; virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0; + + virtual void OnAlsaDriverTerminatedAbnormally() = 0; }; class JackHostStatus diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 58a1acc..0c2155c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -343,6 +343,7 @@ target_link_libraries(pipedal_kconfig PRIVATE ftxui::dom PRIVATE ftxui::component # Not needed for this example. PRIVATE PiPedalCommon + systemd ) ################################# diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index b6f12e6..97c8a73 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -350,7 +350,7 @@ class AlsaTester : private AudioDriverHost Oscillator oscillator; LatencyMonitor latencyMonitor; - virtual void OnAudioStopped() + virtual void OnAlsaDriverStopped() { } virtual void OnAudioTerminated() diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 260e7ea..dc1950b 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -138,6 +138,8 @@ void PiPedalModel::Close() } closed = true; + CancelAudioRetry(); + if (avahiService) { this->avahiService = nullptr; // and close. @@ -1301,6 +1303,10 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver) { try { + if (useDummyAudioDriver) + { + CancelAudioRetry(); + } if (this->audioHost->IsOpen()) { @@ -1380,6 +1386,8 @@ void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSe this->storage.SetJackChannelSelection(channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); + + CancelAudioRetry(); } RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications.. @@ -1791,6 +1799,8 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet FireJackConfigurationChanged(this->jackConfiguration); + CancelAudioRetry(); + guard.unlock(); RestartAudio(); @@ -2735,4 +2745,62 @@ std::map PiPedalModel::GetWifiRegulatoryDomains() Lv2Log::warning(SS("Unable to query Wifi Regulatory domains. " << e.what())); } return result; -} \ No newline at end of file +} + +void PiPedalModel::CancelAudioRetry() { + std::lock_guard lock(mutex); + if (audioRetryPostHandle) + { + // don't think this can ever happen, but if it did, it would be bad. + this->CancelPost(audioRetryPostHandle); + audioRetryPostHandle = 0; + } + +} +void PiPedalModel::OnAlsaDriverTerminatedAbnormally() { + // notification from the realtime thread, via the audiohost that the + // ALSA stream has broken. We want to restart. + + // get off the service thread as promptly as possible + this->Post([&]() { + std::lock_guard lock(mutex); + if (closed) return; + + auto now = clock::now(); + clock::duration timeSinceLastRetry = now-this->lastRestartTime; + this->lastRestartTime = now; + if (timeSinceLastRetry > std::chrono::duration_cast(std::chrono::milliseconds(1000))) { + audioRestartRetries = 0; + } + CancelAudioRetry(); + + if (audioRestartRetries == 0) + { + this->audioRetryPostHandle = this->Post( + // No lock to avoid deadlocks! + [this]() { + Lv2Log::info("Restarting audio."); + this->RestartAudio(); + }); + } else if (audioRestartRetries < 3) + { + this->audioRetryPostHandle = this->PostDelayed( + std::chrono::milliseconds(100 * audioRestartRetries), + [this]() { + if (closed) { + return; + } + Lv2Log::info(SS("Restarting audio. (retry " << audioRestartRetries << ")")); + + RestartAudio(); + }); + } else { + this->audioRetryPostHandle = this->Post( + // No lock to avoid deadlocks! + [this]() { + Lv2Log::info(SS("Switching to dummy driver.")); + RestartAudio(true); // switch to the dummy driver. + }); + } + }); +} diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index ac5428c..f544b99 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -109,6 +109,12 @@ namespace pipedal using NetworkChangedListener = std::function; private: + + void CancelAudioRetry(); + clock::time_point lastRestartTime = clock::time_point::min(); + int audioRestartRetries = 0; + PostHandle audioRetryPostHandle = 0; + bool hasWifi = false; void SetHasWifi(bool hasWifi); @@ -230,6 +236,7 @@ namespace pipedal virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override; virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override; virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override; + virtual void OnAlsaDriverTerminatedAbnormally() override; void OnNotifyPathPatchPropertyReceived( int64_t instanceId, diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index 30f0181..25e6dd0 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -38,7 +38,8 @@ namespace pipedal EffectReplaced, SetValue, SetBypass, - AudioStopped, + //AudioStopped, + AudioTerminatedAbnormally, // specifically for an ALSA loss of connection. SetVuSubscriptions, FreeVuSubscriptions, @@ -507,10 +508,10 @@ namespace pipedal write(RingBufferCommand::ReplaceEffect, pedalboard); } - void AudioStopped() + void AudioTerminatedAbnormally() { AudioStoppedBody body; - write(RingBufferCommand::AudioStopped, body); + write(RingBufferCommand::AudioTerminatedAbnormally, body); } void EffectReplaced(Lv2Pedalboard *pedalboard) diff --git a/src/kconfigMain.cpp b/src/kconfigMain.cpp index 937a79e..f7f7982 100644 --- a/src/kconfigMain.cpp +++ b/src/kconfigMain.cpp @@ -75,7 +75,7 @@ std::string spinnerText(const Clock::time_point &startTime) int64_t position = (ms * MAX_LENGTH / 3000) % (MAX_LENGTH + N_DOTS); - char result[MAX_LENGTH - 1]; + char result[MAX_LENGTH + 1]; for (int64_t i = 0; i < MAX_LENGTH; ++i) { if (i >= position && i < position + N_DOTS)