diff --git a/react/public/var/config.json b/react/public/var/config.json index 04d3a08..f808ca9 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -1,5 +1,5 @@ { - "socket_server_port": 80, + "socket_server_port": 8080, "socket_server_address": "*", "debug": true, "max_upload_size": 536870912, diff --git a/react/src/JackHostStatus.tsx b/react/src/JackHostStatus.tsx index adcde7e..359671c 100644 --- a/react/src/JackHostStatus.tsx +++ b/react/src/JackHostStatus.tsx @@ -138,7 +138,7 @@ export default class JackHostStatus { {label} - {status.errorMessage === "" ? "Stopped" : status.errorMessage}   + {status.errorMessage === "" ? "Audio\u00A0Stopped" : status.errorMessage}   { status.temperaturemC > -100000 && diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 09a7966..56d257a 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -239,10 +239,10 @@ namespace pipedal unsigned int periods = 0; - snd_pcm_hw_params_t *captureHwParams; - snd_pcm_sw_params_t *captureSwParams; - snd_pcm_hw_params_t *playbackHwParams; - snd_pcm_sw_params_t *playbackSwParams; + snd_pcm_hw_params_t *captureHwParams = nullptr; + snd_pcm_sw_params_t *captureSwParams = nullptr; + snd_pcm_hw_params_t *playbackHwParams = nullptr; + snd_pcm_sw_params_t *playbackSwParams = nullptr; bool capture_and_playback_not_synced = false; @@ -1469,6 +1469,32 @@ namespace pipedal Lv2Log::error("ALSA audio thread terminated abnormally."); } this->driverHost->OnAudioStopped(); + + // if we terminated abnormally, pump messages until we have been terminated. + if (!terminateAudio()) + { + // zero out input buffers. + for (size_t i = 0; i < this->captureBuffers.size(); ++i) + { + float*pBuffer = captureBuffers[i]; + for (size_t j = 0; j < this->bufferSize; ++j) + { + pBuffer[j] = 0; + } + + } + while(!terminateAudio()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // zero out input buffers. + this->driverHost->OnProcess(this->bufferSize); + + } + } + this->driverHost->OnAudioTerminated(); + + + } bool alsaActive = false; @@ -1947,6 +1973,17 @@ namespace pipedal std::vector &inputAudioPorts, std::vector &outputAudioPorts) { + if (jackServerSettings.IsDummyAudioDevice()) + { + inputAudioPorts.clear(); + inputAudioPorts.push_back("system::capture_0"); + inputAudioPorts.push_back("system::capture_1"); + outputAudioPorts.clear(); + outputAudioPorts.push_back("system::playback_0"); + outputAudioPorts.push_back("system::playback_1"); + + return true; + } snd_pcm_t *playbackHandle = nullptr; snd_pcm_t *captureHandle = nullptr; @@ -1958,7 +1995,7 @@ namespace pipedal try { int err; - for (int retry = 0; retry < 15; ++retry) + for (int retry = 0; retry < 2; ++retry) { err = snd_pcm_open(&playbackHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) // field report of a device that is present, but won't immediately open. @@ -2044,7 +2081,6 @@ namespace pipedal } catch (const std::exception &e) { - Lv2Log::warning(SS("Unable to read ALSA configuration for " << alsaDeviceName << ". " << e.what() << ".")); result = false; throw; } diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 73f3a2d..1d39ad5 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -208,6 +208,9 @@ public: Oscillator oscillator; LatencyMonitor latencyMonitor; + virtual void OnAudioTerminated() { + + } virtual void OnAudioStopped() { } diff --git a/src/AudioConfig.hpp b/src/AudioConfig.hpp index 0acb67c..38ca20b 100644 --- a/src/AudioConfig.hpp +++ b/src/AudioConfig.hpp @@ -24,8 +24,8 @@ #pragma once -#ifndef JACK_HOST -#define JACK_HOST 0 +#ifndef JACK_HOST // jack is no longer supported. +#define JACK_HOST 0 #endif #ifndef ALSA_HOST diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp index 0442ca2..6d8baae 100644 --- a/src/AudioDriver.hpp +++ b/src/AudioDriver.hpp @@ -51,6 +51,7 @@ namespace pipedal { virtual void OnProcess(size_t nFrames) = 0; virtual void OnUnderrun() = 0; virtual void OnAudioStopped() = 0; + virtual void OnAudioTerminated() = 0; }; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index d5ef32f..fb742db 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -24,6 +24,7 @@ #include "JackDriver.hpp" #include "AlsaDriver.hpp" +#include "DummyAudioDriver.hpp" #include "AtomConverter.hpp" using namespace pipedal; @@ -49,6 +50,7 @@ using namespace pipedal; #include #include "Lv2EventBufferWriter.hpp" #include "InheritPriorityMutex.hpp" +#include #ifdef __linux__ #include @@ -105,73 +107,88 @@ static std::string GetGovernor() return pipedal::GetCpuGovernor(); } - -class SystemMidiBinding { +class SystemMidiBinding +{ private: MidiBinding currentBinding; bool controlState = false; -public: - void SetBinding(const MidiBinding&binding) { currentBinding = binding; controlState = false; } - bool IsMatch(const MidiEvent&event); - bool IsTriggered(const MidiEvent&event); +public: + void SetBinding(const MidiBinding &binding) + { + currentBinding = binding; + controlState = false; + } + + bool IsMatch(const MidiEvent &event); + bool IsTriggered(const MidiEvent &event); }; bool SystemMidiBinding::IsTriggered(const MidiEvent &event) { switch (currentBinding.bindingType()) { - case BINDING_TYPE_NOTE: - { - if (event.size != 3) return false; - auto command = event.buffer[0] & 0xF0; - if (command != 0x90) return false; // MIDI note on - if (event.buffer[1] != currentBinding.note()) return false; - return event.buffer[2] != 0; // note-on with velicity of zero is a note-off. - } - break; - case BINDING_TYPE_CONTROL: - { - if (event.size != 3) return false; - auto command = event.buffer[0] & 0xF0; - if (command != 0xB0) return false; - if (event.buffer[1] != currentBinding.control()) return false; - bool state = event.buffer[2] >= 0x64; - if (state != this->controlState) - { - this->controlState = state; - return state; - } + case BINDING_TYPE_NOTE: + { + if (event.size != 3) return false; - } - break; - default: + auto command = event.buffer[0] & 0xF0; + if (command != 0x90) + return false; // MIDI note on + if (event.buffer[1] != currentBinding.note()) return false; + return event.buffer[2] != 0; // note-on with velicity of zero is a note-off. + } + break; + case BINDING_TYPE_CONTROL: + { + if (event.size != 3) + return false; + auto command = event.buffer[0] & 0xF0; + if (command != 0xB0) + return false; + if (event.buffer[1] != currentBinding.control()) + return false; + bool state = event.buffer[2] >= 0x64; + if (state != this->controlState) + { + this->controlState = state; + return state; + } + return false; + } + break; + default: + return false; } } -bool SystemMidiBinding::IsMatch(const MidiEvent&event) +bool SystemMidiBinding::IsMatch(const MidiEvent &event) { switch (currentBinding.bindingType()) { - case BINDING_TYPE_NOTE: - { - if (event.size != 3) return false; - auto command = event.buffer[0] & 0xF0; - if (command != 0x80 && command != 0x90) return false; // note on/note off. - return event.buffer[1] == currentBinding.note(); - } - break; - case BINDING_TYPE_CONTROL: - { - if (event.size != 3) return false; - auto command = event.buffer[0] & 0xF0; - if (command != 0xB0) return false; - return event.buffer[1] == currentBinding.control(); - } - break; - default: + case BINDING_TYPE_NOTE: + { + if (event.size != 3) return false; + auto command = event.buffer[0] & 0xF0; + if (command != 0x80 && command != 0x90) + return false; // note on/note off. + return event.buffer[1] == currentBinding.note(); + } + break; + case BINDING_TYPE_CONTROL: + { + if (event.size != 3) + return false; + auto command = event.buffer[0] & 0xF0; + if (command != 0xB0) + return false; + return event.buffer[1] == currentBinding.control(); + } + break; + default: + return false; } } @@ -181,7 +198,6 @@ private: IHost *pHost = nullptr; LV2_Atom_Forge inputWriterForge; - std::mutex atomConverterMutex; AtomConverter atomConverter; @@ -254,7 +270,7 @@ private: Uris uris; - AudioDriver *audioDriver = nullptr; + std::unique_ptr audioDriver; std::recursive_mutex mutex; int64_t overrunGracePeriodSamples = 0; @@ -282,7 +298,9 @@ private: SystemMidiBinding prevMidiBinding; JackChannelSelection channelSelection; - bool active = false; + std::atomic active = false; + std::atomic audioStopped = false; + std::atomic isDummyAudioDriver = false; std::shared_ptr currentPedalboard; std::vector> activePedalboards; // pedalboards that have been sent to the audio queue. @@ -307,7 +325,6 @@ private: return pHost->Lv2UridToString(pAtom->body.otype); } - virtual std::string AtomToJson(const LV2_Atom *pAtom) override { std::lock_guard lock(atomConverterMutex); @@ -315,7 +332,7 @@ private: std::stringstream s; json_writer writer(s); - + writer.write(vAtom); return s.str(); } @@ -328,7 +345,7 @@ private: virtual void Close() { - std::lock_guard guard {mutex}; + std::lock_guard guard{mutex}; if (!isOpen) return; @@ -350,7 +367,6 @@ private: StopReaderThread(); - // release any pdealboards owned by the process thread. this->activePedalboards.resize(0); this->realtimeActivePedalboard = nullptr; @@ -375,7 +391,7 @@ private: delete[] midiLv2Buffers[i]; } midiLv2Buffers.resize(0); - + audioDriver = nullptr; } void ZeroBuffer(float *buffer, size_t nframes) @@ -424,8 +440,7 @@ private: } } - - virtual void SetSystemMidiBindings(const std::vector&bindings); + virtual void SetSystemMidiBindings(const std::vector &bindings); void writeVu() { @@ -633,7 +648,7 @@ private: return (event.size == 3 && event.buffer[0] == 0xB0 && event.buffer[1] == 0x00); } - bool onMidiEvent(Lv2EventBufferWriter&eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent&event) + bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) { eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); @@ -654,7 +669,6 @@ private: return true; } - void ProcessMidiInput() { Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids); @@ -676,7 +690,6 @@ private: Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin(); MidiEvent event; - // write all deferred midi messages. if (deferredMidiMessageCount != 0 && !midiProgramChangePending) { @@ -687,17 +700,16 @@ private: if (deviceIndex == midiDeviceIx) { event.size = messageCount; - event.buffer = deferredMidiMessages +i; + event.buffer = deferredMidiMessages + i; event.time = 0; - onMidiEvent(eventBufferWriter,iterator,event); + onMidiEvent(eventBufferWriter, iterator, event); } i += messageCount; } } - for (size_t frame = 0; frame < n; ++frame) { if (audioDriver->GetMidiInputEvent(&event, portBuffer, frame)) @@ -708,32 +720,32 @@ private: this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; - this->realtimeWriter.OnMidiProgramChange(++(this->midiProgramChangeId), selectedBank,event.buffer[1]); + this->realtimeWriter.OnMidiProgramChange(++(this->midiProgramChangeId), selectedBank, event.buffer[1]); } else if (isBankChange(event)) { this->selectedBank = event.buffer[2]; - } else if (this->nextMidiBinding.IsMatch(event)) + } + else if (this->nextMidiBinding.IsMatch(event)) { if (nextMidiBinding.IsTriggered(event)) { midiProgramChangePending = true; - this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId),1); + this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), 1); } - } else if (this->prevMidiBinding.IsMatch(event)) + } + else if (this->prevMidiBinding.IsMatch(event)) { if (prevMidiBinding.IsTriggered(event)) { midiProgramChangePending = true; - this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId),-1); + this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1); } } else if (midiProgramChangePending) { // defer the message for processing after the program change has completed. - if (event.size > 0 - && event.size < 128 - && event.size + 2 + this->deferredMidiMessageCount < DEFERRED_MIDI_BUFFER_SIZE) + if (event.size > 0 && event.size < 128 && event.size + 2 + this->deferredMidiMessageCount < DEFERRED_MIDI_BUFFER_SIZE) { this->deferredMidiMessages[deferredMidiMessageCount++] = midiDeviceIx; this->deferredMidiMessages[deferredMidiMessageCount++] = (uint8_t)event.size; @@ -745,7 +757,7 @@ private: } else { - onMidiEvent(eventBufferWriter,iterator,event); + onMidiEvent(eventBufferWriter, iterator, event); } } } @@ -757,18 +769,24 @@ private: std::mutex audioStoppedMutex; - bool IsAudioActive() + + + bool IsAudioRunning() { - std::lock_guard lock{audioStoppedMutex}; - return this->active; + return this->active && (!this->audioStopped) && !this->isDummyAudioDriver; } - virtual void OnAudioStopped() + virtual void OnAudioStopped() override + { + this->audioStopped = true; + Lv2Log::info("Audio stopped."); + } + virtual void OnAudioTerminated() override { - std::lock_guard lock{audioStoppedMutex}; this->active = false; Lv2Log::info("Audio stopped."); } + virtual void OnProcess(size_t nframes) { try @@ -822,7 +840,7 @@ private: { if (this->realtimeVuBuffers != nullptr) { - pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes,inputBuffers,outputBuffers); + pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes, inputBuffers, outputBuffers); vuSamplesRemaining -= nframes; if (vuSamplesRemaining <= 0) @@ -862,6 +880,7 @@ private: throw; } } + public: AudioHostImpl(IHost *pHost) : inputRingBuffer(RING_BUFFER_SIZE), @@ -875,25 +894,17 @@ public: uris(pHost), atomConverter(pHost->GetMapFeature()) { - realtimeAtomBuffer.resize(32*1024); - lv2_atom_forge_init(&inputWriterForge,pHost->GetMapFeature().GetMap()); + realtimeAtomBuffer.resize(32 * 1024); + lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap()); -#if JACK_HOST - audioDriver = CreateJackDriver(this); -#endif -#if ALSA_HOST - audioDriver = CreateAlsaDriver(this); -#endif } virtual ~AudioHostImpl() { Close(); CleanRestartThreads(true); - - delete audioDriver; + audioDriver = nullptr; } - virtual JackConfiguration GetServerConfiguration() { JackConfiguration result; @@ -907,8 +918,6 @@ public: return this->sampleRate; } - - void OnAudioComplete() { // there is actually no compelling circumstance in which this should ever happen. @@ -942,7 +951,7 @@ public: { Lv2Log::debug("Service thread priority successfully boosted."); } - SetThreadName("aout"); + SetThreadName("rtsvc"); #else xxx; // TODO! #endif @@ -957,9 +966,9 @@ public: using clock_duration = clock_time::duration; // check for overruns every 30 seconds. - clock_duration waitPeriod = + clock_duration waitPeriod = std::chrono::duration_cast(std::chrono::seconds(30)); - clock_time waitTime = std::chrono::steady_clock::now(); + clock_time waitTime = std::chrono::steady_clock::now(); while (true) { @@ -1022,7 +1031,7 @@ public: RealtimePatchPropertyRequest *pRequest = nullptr; hostReader.read(&pRequest); - std::shared_ptr currentpedalboard; + std::shared_ptr currentpedalboard; { std::lock_guard guard(mutex); @@ -1036,7 +1045,8 @@ public: { if (pRequest->errorMessage == nullptr) { - if (pRequest->GetSize() != 0) { + if (pRequest->GetSize() != 0) + { IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId); if (pEffect == nullptr) { @@ -1044,9 +1054,11 @@ public: } else { - pRequest->jsonResponse = AtomToJson((LV2_Atom*)pRequest->GetBuffer()); + pRequest->jsonResponse = AtomToJson((LV2_Atom *)pRequest->GetBuffer()); } - } else { + } + else + { pRequest->errorMessage = "Plugin did not respond."; } } @@ -1107,13 +1119,13 @@ public: IEffect *pEffect = currentPedalboard->GetEffect(instanceId); if (pEffect != nullptr && this->pNotifyCallbacks) { - LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0]; + LV2_Atom *atom = (LV2_Atom *)&atomBuffer[0]; if (atom->type == uris.atom_Object) { - LV2_Atom_Object *obj = (LV2_Atom_Object*)atom; + LV2_Atom_Object *obj = (LV2_Atom_Object *)atom; if (obj->body.otype == uris.patch_Set) { - + // Get the property and value of the set message const LV2_Atom *property = nullptr; const LV2_Atom *value = nullptr; @@ -1125,8 +1137,8 @@ public: 0); if (property != nullptr && value != nullptr && property->type == uris.atom_URID) { - LV2_URID propertyUrid = ((LV2_Atom_URID*)property)->body; - this->pNotifyCallbacks->OnPatchSetReply(instanceId,propertyUrid,value); + LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body; + this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value); this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); } } @@ -1157,31 +1169,34 @@ public: hostReader.read(&body); OnAudioComplete(); return; - } else if (command == RingBufferCommand::MidiProgramChange) + } + else if (command == RingBufferCommand::MidiProgramChange) { RealtimeMidiProgramRequest programRequest; hostReader.read(&programRequest); OnMidiProgramRequest(programRequest); - } else if (command == RingBufferCommand::NextMidiProgram) + } + else if (command == RingBufferCommand::NextMidiProgram) { RealtimeNextMidiProgramRequest request; hostReader.read(&request); pNotifyCallbacks->OnNotifyNextMidiProgram(request); - } else if (command == RingBufferCommand::Lv2ErrorMessage) + } + else if (command == RingBufferCommand::Lv2ErrorMessage) { size_t size; int64_t instanceId; hostReader.read(&instanceId); hostReader.read(&size); - if (this->atomBuffer.size() < size+1) + if (this->atomBuffer.size() < size + 1) { - this->atomBuffer.resize(size+1); + this->atomBuffer.resize(size + 1); } hostReader.read(size, &(atomBuffer[0])); - char *p = (char*)&(atomBuffer[0]); + char *p = (char *)&(atomBuffer[0]); p[size] = 0; std::string message(p); - pNotifyCallbacks->OnNotifyLv2RealtimeError(instanceId,message); + pNotifyCallbacks->OnNotifyLv2RealtimeError(instanceId, message); } else { @@ -1245,6 +1260,22 @@ public: { std::lock_guard guard(mutex); + + if (isOpen) + { + throw PiPedalStateException("Already open."); + } + isOpen = true; + + if (jackServerSettings.IsDummyAudioDevice()) + { + this->isDummyAudioDriver = true; + this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this)); + } else { + this->isDummyAudioDriver = false; + this->audioDriver = std::unique_ptr(CreateAlsaDriver(this)); + } + if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0) { return; @@ -1253,11 +1284,6 @@ public: this->currentSample = 0; this->underruns = 0; - if (isOpen) - { - throw PiPedalStateException("Already open."); - } - isOpen = true; this->inputRingBuffer.reset(); this->outputRingBuffer.reset(); @@ -1272,7 +1298,6 @@ public: try { - audioDriver->Open(jackServerSettings, this->channelSelection); this->sampleRate = audioDriver->GetSampleRate(); @@ -1287,12 +1312,12 @@ public: } active = true; + audioStopped = false; audioDriver->Activate(); Lv2Log::info(SS("Audio started. " << audioDriver->GetConfigurationDescription())); } catch (const std::exception &e) { - Lv2Log::error(SS("Failed to start audio. " << e.what())); Close(); active = false; throw; @@ -1368,7 +1393,6 @@ public: } } } - } void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) @@ -1387,22 +1411,22 @@ public: } } - virtual void SetInputVolume(float value) { + virtual void SetInputVolume(float value) + { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { hostWriter.SetInputVolume(value); } - } - virtual void SetOutputVolume(float value) { + virtual void SetOutputVolume(float value) + { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { hostWriter.SetOutputVolume(value); } - } virtual void SetVuSubscriptions(const std::vector &instanceIds) @@ -1436,7 +1460,9 @@ public: vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v); - } else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) { + } + else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) + { int index = (int)instanceId; VuUpdate v; vuConfig->enabledIndexes.push_back(index); @@ -1444,12 +1470,13 @@ public: if (instanceId == Pedalboard::INPUT_VOLUME_ID) { v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1; - } else { - v.isStereoInput_ = v.isStereoOutput_ =this->pHost->GetNumberOfOutputAudioChannels() > 1; + } + else + { + v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfOutputAudioChannels() > 1; } vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v); - } } @@ -1499,8 +1526,7 @@ public: } private: - - virtual bool UpdatePluginStates(Pedalboard& pedalboard) override; + virtual bool UpdatePluginStates(Pedalboard &pedalboard) override; virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override; class RestartThread @@ -1575,7 +1601,7 @@ public: } void CleanRestartThreads(bool final) { - std::lock_guard guard {restart_mutex}; + std::lock_guard guard{restart_mutex}; for (size_t i = 0; i < restartThreads.size(); ++i) { if (final) @@ -1641,7 +1667,7 @@ public: result.temperaturemC_ = GetRaspberryPiTemperature(); - result.active_ = IsAudioActive(); + result.active_ = IsAudioRunning(); result.restarting_ = this->restarting; if (this->audioDriver != nullptr) @@ -1680,31 +1706,31 @@ static std::string UriToFieldName(const std::string &uri) return uri.substr(pos + 1); } -void AudioHostImpl::SetSystemMidiBindings(const std::vector&bindings) +void AudioHostImpl::SetSystemMidiBindings(const std::vector &bindings) { for (auto i = bindings.begin(); i != bindings.end(); ++i) { if (i->symbol() == "nextProgram") { this->nextMidiBinding.SetBinding(*i); - } else if (i->symbol() == "prevProgram") + } + else if (i->symbol() == "prevProgram") { this->prevMidiBinding.SetBinding(*i); } } } - AudioHost *AudioHost::CreateInstance(IHost *pHost) { return new AudioHostImpl(pHost); } -bool AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard) +bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard) { bool changed = false; auto pedalboardItems = pedalboard.GetAllPlugins(); - for (PedalboardItem* item : pedalboardItems) + for (PedalboardItem *item : pedalboardItems) { if (!item->isSplit()) { @@ -1715,14 +1741,14 @@ bool AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard) } } return true; - } bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) { - IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId()); + IEffect *effect = currentPedalboard->GetEffect(pedalboardItem.instanceId()); if (effect != nullptr) { - try { + try + { Lv2PluginState state; if (!effect->GetLv2State(&state)) { @@ -1734,7 +1760,8 @@ bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) return true; } return false; - } catch(const std::exception &e) + } + catch (const std::exception &e) { Lv2Log::warning(SS(pedalboardItem.pluginName() << ": " << e.what())); return false; @@ -1743,9 +1770,6 @@ bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) return false; } - - - JSON_MAP_BEGIN(JackHostStatus) JSON_MAP_REFERENCE(JackHostStatus, active) JSON_MAP_REFERENCE(JackHostStatus, errorMessage) diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index 0c189c2..2455f88 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -166,7 +166,7 @@ public: class JackHostStatus { public: - bool active_; + bool active_ = false; std::string errorMessage_; bool restarting_; uint64_t underruns_; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5bf973f..91ba6cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -220,6 +220,7 @@ set (PIPEDAL_SOURCES JackDriver.cpp JackDriver.hpp AlsaDriver.cpp AlsaDriver.hpp + DummyAudioDriver.cpp DummyAudioDriver.hpp AudioDriver.hpp AudioConfig.hpp diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp new file mode 100644 index 0000000..377aa8d --- /dev/null +++ b/src/DummyAudioDriver.cpp @@ -0,0 +1,564 @@ +/* + * MIT License + * + * 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 "pch.h" +#include "util.hpp" +#include +#include +#include "ss.hpp" +#include "DummyAudioDriver.hpp" +#include "JackServerSettings.hpp" +#include +#include "RtInversionGuard.hpp" +#include "PiPedalException.hpp" +#include +#include +#include + +#include "CpuUse.hpp" + +#include + +#include "Lv2Log.hpp" +#include +#include "ss.hpp" + +#undef ALSADRIVER_CONFIG_DBG + +#ifdef ALSADRIVER_CONFIG_DBG +#include +#endif + +using namespace pipedal; + +namespace pipedal +{ + + [[noreturn]] static void DummyError(const std::string &message) + { + throw PiPedalStateException(message); + } + + + class DummyDriverImpl : public AudioDriver + { + private: + pipedal::CpuUse cpuUse; + + uint32_t sampleRate = 0; + + uint32_t bufferSize; + uint32_t numberOfBuffers; + + int playbackChannels = 2; + int captureChannels = 2; + + + uint32_t playbackSampleSize = 0; + uint32_t captureSampleSize = 0; + uint32_t playbackFrameSize = 0; + uint32_t captureFrameSize = 0; + + + std::vector activeCaptureBuffers; + std::vector activePlaybackBuffers; + + std::vector captureBuffers; + std::vector playbackBuffers; + + uint8_t *rawCaptureBuffer = nullptr; + uint8_t *rawPlaybackBuffer = nullptr; + + AudioDriverHost *driverHost = nullptr; + + public: + DummyDriverImpl(AudioDriverHost *driverHost) + : driverHost(driverHost) + { + } + virtual ~DummyDriverImpl() + { + Close(); + } + + private: + void OnShutdown() + { + Lv2Log::info("Dummy Audio Server has shut down."); + } + + virtual uint32_t GetSampleRate() + { + return this->sampleRate; + } + + JackServerSettings jackServerSettings; + + unsigned int periods = 0; + + + + std::atomic terminateAudio_ = false; + + void terminateAudio(bool terminate) + { + this->terminateAudio_ = terminate; + } + + bool terminateAudio() + { + return this->terminateAudio_; + } + + private: + void DummyCleanup() + { + } + + private: + void AllocateBuffers(std::vector &buffers, size_t n) + { + buffers.resize(n); + for (size_t i = 0; i < n; ++i) + { + buffers[i] = new float[this->bufferSize]; + for (size_t j = 0; j < this->bufferSize; ++j) + { + buffers[i][j] = 0; + } + } + } + + JackChannelSelection channelSelection; + bool open = false; + virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) + { + terminateAudio_ = false; + if (open) + { + throw PiPedalStateException("Already open."); + } + this->jackServerSettings = jackServerSettings; + this->channelSelection = channelSelection; + this->sampleRate = jackServerSettings.GetSampleRate(); + + open = true; + try + { + OpenMidi(jackServerSettings, channelSelection); + OpenAudio(jackServerSettings, channelSelection); + } + catch (const std::exception &e) + { + Close(); + throw; + } + } + virtual std::string GetConfigurationDescription() + { + std::string result = SS( + "DUMMY, " + << "n/a" + << ", " << "Native float" + << ", " << this->sampleRate + << ", " << this->bufferSize << "x" << this->numberOfBuffers + << ", in: " << this->InputBufferCount() << "/" << this->captureChannels + << ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels); + return result; + } + + void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) + { + int err; + + this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers(); + this->bufferSize = jackServerSettings.GetBufferSize(); + AllocateBuffers(captureBuffers, 2); + AllocateBuffers(playbackBuffers, 2); + + } + + std::jthread *audioThread; + bool audioRunning; + + bool block = false; + + void AudioThread() + { + 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 + + bool ok = true; + + while (true) + { + if (terminateAudio()) + { + break; + } + + ssize_t framesRead = this->bufferSize; + this->driverHost->OnProcess(framesRead); + + /// no attempt at realtime. Just as long as we run occasionally. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + + } + } + catch (const std::exception &e) + { + Lv2Log::error(e.what()); + Lv2Log::error("Dummy audio thread terminated abnormally."); + } + this->driverHost->OnAudioTerminated(); + } + + bool alsaActive = false; + + static int IndexFromPortName(const std::string &s) + { + auto pos = s.find_last_of('_'); + if (pos == std::string::npos) + { + throw std::invalid_argument("Bad port name."); + } + const char *p = s.c_str() + (pos + 1); + + int v = atoi(p); + if (v < 0) + { + throw std::invalid_argument("Bad port name."); + } + return v; + } + + bool activated = false; + virtual void Activate() + { + if (activated) + { + throw PiPedalStateException("Already activated."); + } + activated = true; + + this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size()); + + playbackBuffers.resize(2); + + int ix = 0; + for (auto &x : channelSelection.GetInputAudioPorts()) + { + int sourceIndex = IndexFromPortName(x); + if (sourceIndex >= captureBuffers.size()) + { + Lv2Log::error(SS("Invalid audio input port: " << x)); + } + else + { + this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex]; + } + } + + this->activePlaybackBuffers.resize(channelSelection.GetOutputAudioPorts().size()); + + ix = 0; + for (auto &x : channelSelection.GetOutputAudioPorts()) + { + int sourceIndex = IndexFromPortName(x); + if (sourceIndex >= playbackBuffers.size()) + { + Lv2Log::error(SS("Invalid audio output port: " << x)); + } + else + { + this->activePlaybackBuffers[ix++] = this->playbackBuffers[sourceIndex]; + } + } + + audioThread = new std::jthread([this]() + { AudioThread(); }); + } + + virtual void Deactivate() + { + if (!activated) + { + return; + } + activated = false; + terminateAudio(true); + if (audioThread) + { + this->audioThread->join(); + this->audioThread = 0; + } + Lv2Log::debug("Audio thread joined."); + } + + static constexpr size_t MIDI_BUFFER_SIZE = 16 * 1024; + static constexpr size_t MAX_MIDI_EVENT = 4 * 1024; + + public: + class MidiState + { + private: + snd_rawmidi_t *hIn = nullptr; + snd_rawmidi_params_t *hInParams = nullptr; + std::string deviceName; + + // running status state. + uint8_t runningStatus = 0; + int dataLength = 0; + int dataIndex = 0; + size_t statusBytesRemaining = 0; + size_t data0; + size_t data1; + + size_t eventCount = 0; + MidiEvent events[MAX_MIDI_EVENT]; + size_t bufferCount = 0; + uint8_t buffer[MIDI_BUFFER_SIZE]; + + uint8_t readBuffer[1024]; + + ssize_t sysexStartIndex = -1; + + void checkError(int result, const char *message) + { + if (result < 0) + { + throw PiPedalStateException(SS("Unexpected error: " << message << " (" << this->deviceName)); + } + } + + public: + void Open(const AlsaMidiDeviceInfo &device) + { + bufferCount = 0; + eventCount = 0; + sysexStartIndex = -1; + runningStatus = 0; + dataIndex = 0; + dataLength = 0; + + this->deviceName = device.description_; + + } + void Close() + { + } + + + size_t GetMidiInputEventCount() + { + return 0; + } + + bool GetMidiInputEvent(MidiEvent *event, size_t nFrame) + { + return false; + } + + void MidiPut(uint8_t cc, uint8_t d0, uint8_t d1) + { + } + + void FillBuffer() + { + } + + void WriteBuffer(uint8_t *readBuffer, size_t nRead) + { + } + }; + + std::vector midiStates; + + void OpenMidi(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) + { + const auto &devices = channelSelection.GetInputMidiDevices(); + + midiStates.resize(devices.size()); + + for (size_t i = 0; i < devices.size(); ++i) + { + const auto &device = devices[i]; + MidiState *state = new MidiState(); + midiStates[i] = state; + state->Open(device); + } + } + + virtual size_t MidiInputBufferCount() const + { + return this->midiStates.size(); + } + virtual void *GetMidiInputBuffer(size_t channel, size_t nFrames) + { + return (void *)midiStates[channel]; + } + + virtual size_t GetMidiInputEventCount(void *portBuffer) + { + MidiState *state = (MidiState *)portBuffer; + return state->GetMidiInputEventCount(); + } + virtual bool GetMidiInputEvent(MidiEvent *event, void *portBuf, size_t nFrame) + { + MidiState *state = (MidiState *)portBuf; + return state->GetMidiInputEvent(event, nFrame); + } + + virtual void FillMidiBuffers() + { + } + + virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); } + virtual float *GetInputBuffer(size_t channel, size_t nFrames) + { + return activeCaptureBuffers[channel]; + } + + virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); } + virtual float *GetOutputBuffer(size_t channel, size_t nFrames) + { + return activePlaybackBuffers[channel]; + } + + void FreeBuffers(std::vector &buffer) + { + for (size_t i = 0; i < buffer.size(); ++i) + { + // delete[] buffer[i]; + buffer[i] = 0; + } + buffer.clear(); + } + void DeleteBuffers() + { + activeCaptureBuffers.clear(); + activePlaybackBuffers.clear(); + FreeBuffers(this->playbackBuffers); + FreeBuffers(this->captureBuffers); + if (rawCaptureBuffer) + { + delete[] rawCaptureBuffer; + rawCaptureBuffer = nullptr; + } + if (rawPlaybackBuffer) + { + delete[] rawPlaybackBuffer; + rawPlaybackBuffer = nullptr; + } + } + virtual void Close() + { + if (!open) + { + return; + } + open = false; + Deactivate(); + DummyCleanup(); + DeleteBuffers(); + } + + virtual float CpuUse() + { + return 0; + } + + virtual float CpuOverhead() + { + return 0.1; + } + }; + + AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost) + { + return new DummyDriverImpl(driverHost); + } + + bool GetDummyChannels(const JackServerSettings &jackServerSettings, + std::vector &inputAudioPorts, + std::vector &outputAudioPorts) + { + + bool result = false; + + try + { + + unsigned int playbackChannels = 2, captureChannels = 2; + + inputAudioPorts.clear(); + for (unsigned int i = 0; i < captureChannels; ++i) + { + inputAudioPorts.push_back(SS("system::capture_" << i)); + } + + outputAudioPorts.clear(); + for (unsigned int i = 0; i < playbackChannels; ++i) + { + outputAudioPorts.push_back(SS("system::playback_" << i)); + } + + result = true; + } + catch (const std::exception &e) + { + throw; + } + return result; + } + +} // namespace diff --git a/src/DummyAudioDriver.hpp b/src/DummyAudioDriver.hpp new file mode 100644 index 0000000..edfcb22 --- /dev/null +++ b/src/DummyAudioDriver.hpp @@ -0,0 +1,35 @@ +/* + * MIT License + * + * 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 + +#include "AudioDriver.hpp" +#include "JackServerSettings.hpp" + +namespace pipedal { + + AudioDriver* CreateDummyAudioDriver(AudioDriverHost*driverHost); + +} + diff --git a/src/JackConfiguration.cpp b/src/JackConfiguration.cpp index 06939ac..11c4129 100644 --- a/src/JackConfiguration.cpp +++ b/src/JackConfiguration.cpp @@ -112,7 +112,12 @@ void JackConfiguration::AlsaInitialize( this->isValid_ = false; this->errorStatus_ = ""; - this->inputMidiDevices_ = GetAlsaMidiInputDevices(); + if (jackServerSettings.IsDummyAudioDevice()) + { + this->inputMidiDevices_.clear(); + } else { + this->inputMidiDevices_ = GetAlsaMidiInputDevices(); + } if (jackServerSettings.IsValid()) { this->blockLength_ = jackServerSettings.GetBufferSize(); @@ -126,7 +131,7 @@ void JackConfiguration::AlsaInitialize( } } catch (const std::exception& /*ignore*/) { - + throw; } } diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index 6d27282..1477a7b 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -54,6 +54,14 @@ namespace pipedal uint32_t GetBufferSize() const { return bufferSize_; } uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; } const std::string &GetAlsaInputDevice() const { return alsaDevice_; } + void UseDummyAudioDevice() { + this->valid_ = true; + if (sampleRate_ == 0) sampleRate_ = 48000; + this->alsaDevice_ = "__DUMMY_AUDIO__"; + } + bool IsDummyAudioDevice() const { + return this->alsaDevice_ == "__DUMMY_AUDIO__"; + } void ReadJackDaemonConfiguration(); diff --git a/src/Lv2PluginChangeMonitor.cpp b/src/Lv2PluginChangeMonitor.cpp index e9521e9..b4b57f3 100644 --- a/src/Lv2PluginChangeMonitor.cpp +++ b/src/Lv2PluginChangeMonitor.cpp @@ -27,6 +27,7 @@ #include #include #include "PiPedalModel.hpp" +#include "util.hpp" using namespace pipedal; @@ -57,6 +58,7 @@ Lv2PluginChangeMonitor::~Lv2PluginChangeMonitor() void Lv2PluginChangeMonitor::ThreadProc() { + SetThreadName("Lv2Change"); using clock = std::chrono::steady_clock; int inotify_fd = inotify_init(); diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index b2b679e..e5ff1cd 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -74,7 +74,7 @@ PiPedalModel::PiPedalModel() this->jackServerSettings = this->storage.GetJackServerSettings(); #endif updater.SetUpdateListener( - [this](const UpdateStatus&updateStatus) + [this](const UpdateStatus &updateStatus) { this->OnUpdateStatusChanged(updateStatus); }); @@ -247,64 +247,8 @@ void PiPedalModel::Load() scheduler_params.sched_priority = 10; memset(&scheduler_params, 0, sizeof(sched_param)); sched_setscheduler(0, SCHED_RR, &scheduler_params); -#if JACK_HOST - this->jackConfiguration = this->jackConfiguration.JackInitialize(); -#else - this->jackServerSettings = storage.GetJackServerSettings(); - if (jackServerSettings.IsValid()) - { - this->jackConfiguration.AlsaInitialize(this->jackServerSettings); - if (!jackServerSettings.IsValid()) - { - for (size_t retry = 0; retry < 5; ++retry) - { - // retry until the device comes online. - sleep(3); - Lv2Log::info("Retrying AlsaInitialize"); - jackConfiguration.isValid(true); - this->jackConfiguration.AlsaInitialize(this->jackServerSettings); - if (jackConfiguration.isValid()) - { - Lv2Log::info("Retry succeeded."); - break; - } - } - } - } - else - { - this->jackConfiguration.AlsaInitialize(this->jackServerSettings); - } -#endif - if (this->jackConfiguration.isValid()) - { - JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); - selection = selection.RemoveInvalidChannels(jackConfiguration); - this->pluginHost.OnConfigurationChanged(jackConfiguration, selection); - try - { - audioHost->Open(this->jackServerSettings, selection); - bool loadedSuccessfully = false; - try - { - LoadCurrentPedalboard(); - loadedSuccessfully = true; - } - catch (const std::exception &e) - { - Lv2Log::error("Failed to load initial plugin. %s", e.what()); - } - } - catch (std::exception &e) - { - Lv2Log::error("Failed to start audio device. %s", e.what()); - } - } - else - { - Lv2Log::error("Audio device not configured."); - } + RestartAudio(); } IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientId) @@ -391,7 +335,6 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId) item->stateUpdateCount(item->stateUpdateCount() + 1); - Lv2PluginState newState = item->lv2State(); IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) @@ -1143,59 +1086,65 @@ JackConfiguration PiPedalModel::GetJackConfiguration() return this->jackConfiguration; } -void PiPedalModel::RestartAudio() +void PiPedalModel::RestartAudio(bool useDummyAudioDriver) { - if (this->audioHost->IsOpen()) - { - - this->audioHost->Close(); - } - // restarting is a bit dodgy. It was impossible with Jack, but - // now very plausible with the ALSA audio stack. - - // Still bugs wrt/ restarting the circular buffers for the audio thread. - - // for the meantime, just rely on the fact that the admin service will restart - // the process. - //... - - // do a complete reload. - - this->audioHost->SetPedalboard(nullptr); - - this->jackConfiguration.AlsaInitialize(this->jackServerSettings); - - if (this->jackConfiguration.isValid()) - { - JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); - selection = selection.RemoveInvalidChannels(jackConfiguration); - } - else - { - jackConfiguration.setErrorStatus("Error"); - } - - FireJackConfigurationChanged(jackConfiguration); - - if (!jackServerSettings.IsValid() || !jackConfiguration.isValid()) - { - Lv2Log::error("Audio configuration not valid."); - return; - } try { - JackChannelSelection channelSelection = GetJackChannelSelection(); + if (this->audioHost->IsOpen()) + { + + this->audioHost->Close(); + } + // restarting is a bit dodgy. It was impossible with Jack, but + // now very plausible with the ALSA audio stack. + + // Still bugs wrt/ restarting the circular buffers for the audio thread. + + // do a complete reload. + + this->audioHost->SetPedalboard(nullptr); + + auto jackServerSettings = this->jackServerSettings; + if (useDummyAudioDriver) + { + jackServerSettings.UseDummyAudioDevice(); + } + + auto jackConfiguration = this->jackConfiguration; + jackConfiguration.AlsaInitialize(jackServerSettings); + if (jackConfiguration.isValid()) + { + JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); + selection = selection.RemoveInvalidChannels(jackConfiguration); + } + else + { + jackConfiguration.setErrorStatus("Error"); + } + if (!useDummyAudioDriver) + { + this->jackConfiguration = jackConfiguration; + FireJackConfigurationChanged(jackConfiguration); + } + + if (!jackServerSettings.IsValid() || !jackConfiguration.isValid()) + { + throw std::runtime_error("Audio configuration not valid."); + } + + JackChannelSelection channelSelection = this->storage.GetJackChannelSelection(jackConfiguration); + if (this->jackConfiguration.isValid()) + { + channelSelection.RemoveInvalidChannels(this->jackConfiguration); + } if (!channelSelection.isValid()) { - Lv2Log::error("Audio configuration not valid."); - return; + throw std::runtime_error("Audio configuration not valid."); } - this->audioHost->Open(this->jackServerSettings, channelSelection); + this->audioHost->Open(jackServerSettings, channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); - std::vector errorMessages; - LoadCurrentPedalboard(); this->UpdateRealtimeVuSubscriptions(); @@ -1203,8 +1152,19 @@ void PiPedalModel::RestartAudio() } catch (const std::exception &e) { - Lv2Log::error(SS("Failed to start audio. " << e.what())); - throw; + this->audioHost->Close(); + if (useDummyAudioDriver) + { + Lv2Log::error(SS("Failed to start dummy audio driver. " << e.what())); + } + else + { + Lv2Log::error(SS("Failed to start audio. " << e.what())); + } + if (!useDummyAudioDriver) + { + RestartAudio(true); // use the dummy audio driver. + } } } @@ -2178,7 +2138,7 @@ void PiPedalModel::SetRestartListener(std::function &&listener) this->restartListener = std::move(listener); } -void PiPedalModel::OnUpdateStatusChanged(const UpdateStatus&updateStatus) +void PiPedalModel::OnUpdateStatusChanged(const UpdateStatus &updateStatus) { std::lock_guard lock(mutex); @@ -2188,40 +2148,98 @@ void PiPedalModel::OnUpdateStatusChanged(const UpdateStatus&updateStatus) FireUpdateStatusChanged(this->currentUpdateStatus); } } -void PiPedalModel::FireUpdateStatusChanged(const UpdateStatus&updateStatus) +void PiPedalModel::FireUpdateStatusChanged(const UpdateStatus &updateStatus) { std::lock_guard lock(mutex); std::vector t; t.reserve(subscribers.size()); - for (auto subscriber: subscribers) + for (auto subscriber : subscribers) { t.push_back(subscriber); } - for (auto subscriber: t) + for (auto subscriber : t) { subscriber->OnUpdateStatusChanged(updateStatus); } } -UpdateStatus PiPedalModel::GetUpdateStatus() { +UpdateStatus PiPedalModel::GetUpdateStatus() +{ std::lock_guard lock(mutex); return currentUpdateStatus; } -void PiPedalModel::UpdateNow(const std::string&updateUrl) +void PiPedalModel::UpdateNow(const std::string &updateUrl) { std::lock_guard lock(mutex); auto fileName = updater.DownloadUpdate(updateUrl); adminClient.InstallUpdate(fileName); - } -void PiPedalModel::ForceUpdateCheck() { +void PiPedalModel::ForceUpdateCheck() +{ updater.ForceUpdateCheck(); } void PiPedalModel::SetUpdatePolicy(UpdatePolicyT updatePolicy) { updater.SetUpdatePolicy(updatePolicy); } + +static bool HasAlsaDevice(const std::vector devices, const std::string &deviceId) +{ + for (auto &device : devices) + { + if (device.id_ == deviceId) + return true; + } + return false; +} + +void PiPedalModel::WaitForAudioDeviceToComeOnline() +{ + auto serverSettings = this->GetJackServerSettings(); + // Wait for selected audio device to be initialized. + // It may take some time for ALSA to publish all available devices when rebooting. + + if (serverSettings.IsValid()) + { + // wait up to 15 seconds for the midi device to come online. + auto devices = GetAlsaDevices(); + bool found = false; + if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice())) + { + Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); + } + else + { + Lv2Log::info(SS("Waiting for ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); + for (int i = 0; i < 5; ++i) + { + sleep(2); + devices = GetAlsaDevices(); + if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice())) + { + found = true; + break; + } + } + if (found) + { + Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); + } + else + { + Lv2Log::info(SS("ALSA device " << serverSettings.GetAlsaInputDevice() << " not found.")); + } + } + } + else + { + Lv2Log::info("No ALSA device selected."); + } + + // pre-cache device info before we let audio services run. + GetAlsaDevices(); +} diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 1cf8710..06c97b1 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -169,9 +169,8 @@ namespace pipedal void UpdateRealtimeVuSubscriptions(); void UpdateRealtimeMonitorPortSubscriptions(); - void OnVuUpdate(const std::vector &updates); - void RestartAudio(); + void RestartAudio(bool useDummyAudioDriver = false); std::vector outstandingParameterRequests; @@ -204,6 +203,8 @@ namespace pipedal PiPedalModel(); virtual ~PiPedalModel(); + void WaitForAudioDeviceToComeOnline(); + UpdateStatus GetUpdateStatus(); void UpdateNow(const std::string&updateUrl); void FireUpdateStatusChanged(const UpdateStatus&updateStatus); diff --git a/src/Updater.cpp b/src/Updater.cpp index ad2d310..c0a32fb 100644 --- a/src/Updater.cpp +++ b/src/Updater.cpp @@ -18,6 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "Updater.hpp" +#include "util.hpp" #include "json.hpp" #include "config.hpp" #include @@ -165,6 +166,7 @@ void Updater::SetUpdateListener(UpdateListener &&listener) void Updater::ThreadProc() { + SetThreadName("UpdateMonitor"); struct pollfd pfd; pfd.fd = this->event_reader; pfd.events = POLLIN; diff --git a/src/main.cpp b/src/main.cpp index b9d8833..4c44ca8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,15 +54,6 @@ using namespace pipedal; #endif -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 { @@ -250,58 +241,7 @@ int main(int argc, char *argv[]) (unsigned long)getpid()); } - auto serverSettings = model.GetJackServerSettings(); - - { - // Wait for selected audio device to be initialized. - // It may take some time for ALSA to publish all available devices when rebooting. - - if (serverSettings.IsValid()) - { - // wait up to 15 seconds for the midi device to come online. - auto devices = model.GetAlsaDevices(); - bool found = false; - if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice())) - { - Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); - } - else - { - for (int i = 0; i < 5; ++i) - { - sleep(3); - devices = model.GetAlsaDevices(); - if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice())) - { - found = true; - break; - } - if (g_SigBreak) - exit(1); - if (!systemd) - { - break; - } - Lv2Log::info(SS("Waiting for ALSA device " << serverSettings.GetAlsaInputDevice() << " to come online...")); - } - if (found) - { - Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); - } - else - { - Lv2Log::info(SS("ALSA device " << serverSettings.GetAlsaInputDevice() << " not found.")); - } - } - } - else - { - Lv2Log::info("No ALSA device selected."); - } - - // pre-cache device info before we let audio services run. - model.GetAlsaDevices(); - } + model.WaitForAudioDeviceToComeOnline(); // only accept signals on the main thread. int sig;