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;