// Copyright (c) 2022-2023 Robin 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 "AudioHost.hpp" #include "util.hpp" #include #include "Lv2Log.hpp" #include "JackDriver.hpp" #include "AlsaDriver.hpp" #include "DummyAudioDriver.hpp" #include "AtomConverter.hpp" #include #include "PluginHost.hpp" #include "PatchPropertyWriter.hpp" using namespace pipedal; #include "AudioConfig.hpp" #include #include #include #include #include #include "VuUpdate.hpp" #include "CpuGovernor.hpp" #include "RingBuffer.hpp" #include "RingBufferReader.hpp" #include "PiPedalException.hpp" #include "pthread.h" #include "sched.h" #include #include #include #include "Lv2EventBufferWriter.hpp" #include "InheritPriorityMutex.hpp" #include #ifdef __linux__ #include #include #include #endif #define JACK_SESSION_CALLBACKS 0 #include "AdminClient.hpp" const double VU_UPDATE_RATE_S = 1.0 / 30; const double OVERRUN_GRACE_PERIOD_S = 15; using namespace pipedal; const int MIDI_LV2_BUFFER_SIZE = 16 * 1024; static void GetCpuFrequency(uint64_t *freqMin, uint64_t *freqMax) { uint64_t fMax = 0; uint64_t fMin = UINT64_MAX; char deviceName[128]; try { for (int i = 0; true; ++i) { snprintf(deviceName, sizeof(deviceName), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i); std::ifstream f(deviceName); if (!f) { break; } uint64_t freq; f >> freq; if (!f) break; if (freq < fMin) fMin = freq; if (freq > fMax) fMax = freq; } } catch (const std::exception &) { } if (fMax == 0) fMin = 0; *freqMin = fMin; *freqMax = fMax; } static std::string GetGovernor() { return pipedal::GetCpuGovernor(); } namespace pipedal { struct PathPatchProperty { LV2_URID propertyUrid = 0; std::vector atomBuffer; }; class IndexedSnapshotValue { private: struct InputControlEntry { bool isInputControl = false; float value = 0; }; public: IndexedSnapshotValue(IEffect *effect, SnapshotValue *snapshotValue, PluginHost &pluginHost) : pEffect(effect) { auto maxInputControl = effect->GetMaxInputControl(); inputControlValues.resize(maxInputControl); this->enabled = true; for (uint64_t i = 0; i < maxInputControl; ++i) { bool isInputControl = effect->IsInputControl(i); if (isInputControl) { inputControlValues[i] = InputControlEntry{isInputControl : true, value : effect->GetDefaultInputControlValue(i)}; } else { inputControlValues[i] = InputControlEntry{isInputControl : false, value : 0}; } } if (snapshotValue) { this->enabled = snapshotValue->isEnabled_; for (auto &controlValue : snapshotValue->controlValues_) { auto index = effect->GetControlIndex(controlValue.key()); if (index >= 0 && index < inputControlValues.size()) { inputControlValues[index].value = controlValue.value(); } } this->lv2State = snapshotValue->lv2State_; if (effect->IsLv2Effect()) { Lv2Effect *lv2Effect = (Lv2Effect *)effect; for (auto &pathProperty : snapshotValue->pathProperties_) { // only transmit changed path patch properties. if (lv2Effect->GetPathPatchProperty(pathProperty.first) != pathProperty.second) { lv2Effect->SetPathPatchProperty(pathProperty.first, pathProperty.second); PathPatchProperty pathPatchProperty; pathPatchProperty.propertyUrid = pluginHost.GetLv2Urid(pathProperty.first.c_str()); // convert to json variant so we do a mappath operation. json_variant vProperty; std::istringstream ss(pathProperty.second); json_reader reader(ss); reader.read(&vProperty); if (!vProperty.is_null()) { try { vProperty = pluginHost.MapPath(vProperty); // now to atom format (what we want on the rt thread0) AtomConverter atomConverter(pluginHost.GetMapFeature()); LV2_Atom *atomValue = atomConverter.ToAtom(vProperty); size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4; pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom)); memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size()); this->pathPatchProperties.push_back(std::move(pathPatchProperty)); } catch (const std::exception &e) { Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what())); } } } } } } } void ApplyValues(IEffect *effect) { effect->SetBypass(this->enabled); if (effect != pEffect) { throw std::runtime_error("Wrong effect"); } for (size_t i = 0; i < inputControlValues.size(); ++i) { InputControlEntry &e = inputControlValues[i]; if (e.isInputControl) { effect->SetControl((int)i, e.value); } } for (const auto &patchProperty : pathPatchProperties) { effect->SetPatchProperty( patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data()); } // effect->SetLv2State(lv2State); } private: IEffect *pEffect; Lv2PluginState lv2State; bool enabled; std::vector inputControlValues; std::vector pathPatchProperties; }; class IndexedSnapshot { public: IndexedSnapshot(Snapshot *snapshot, std::shared_ptr currentPedalboard, PluginHost &pluginHost) { std::unordered_map index; for (auto &value : snapshot->values_) { index[value.instanceId_] = &value; } std::vector &effects = currentPedalboard->GetEffects(); snapshotValues.reserve(effects.size()); for (size_t i = 0; i < effects.size(); ++i) { auto &effect = effects[i]; SnapshotValue *snapshotValue = getSnapshotValue(index, effect->GetInstanceId()); snapshotValues.push_back(IndexedSnapshotValue(effect, snapshotValue, pluginHost)); } } static SnapshotValue *getSnapshotValue(std::unordered_map &index, uint64_t instanceId) { auto iter = index.find(instanceId); if (iter == index.end()) { return nullptr; } return iter->second; } void Apply(std::vector &effects) { if (effects.size() != snapshotValues.size()) { throw std::runtime_error("Effects and values don't match"); } for (size_t i = 0; i < snapshotValues.size(); ++i) { snapshotValues[i].ApplyValues(effects[i]); } } ~IndexedSnapshot() { } private: std::vector snapshotValues; }; } 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); }; bool SystemMidiBinding::IsTriggered(const MidiEvent &event) { if (!IsMatch(event)) { return false; } 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; } return false; } break; default: return false; } } 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: return false; } } class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchWriterCallback { private: void OnWritePatchPropertyBuffer( PatchPropertyWriter::Buffer *); IHost *pHost = nullptr; LV2_Atom_Forge inputWriterForge; std::mutex atomConverterMutex; AtomConverter atomConverter; static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024; uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE]; size_t deferredMidiMessageCount = 0; bool midiProgramChangePending = false; bool midiSnapshotRequestPending = false; int64_t snapshotRequestId = 0; int selectedBank = -1; int64_t midiProgramChangeId = 0; class Uris { public: Uris(IHost *pHost) { atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank); atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path); atom_float = pHost->GetLv2Urid(LV2_ATOM__Float); atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double); atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int); atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long); atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool); atom_String = pHost->GetLv2Urid(LV2_ATOM__String); atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector); atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object); atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence); atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk); atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID); atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer); patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get); patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set); patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put); patch_body = pHost->GetLv2Urid(LV2_PATCH__body); patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject); patch_property = pHost->GetLv2Urid(LV2_PATCH__property); // patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept); patch_value = pHost->GetLv2Urid(LV2_PATCH__value); unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame); } // LV2_URID patch_accept; LV2_URID unitsFrame; LV2_URID pluginUri; LV2_URID atom_Blank; LV2_URID atom_Bool; LV2_URID atom_float; LV2_URID atom_Double; LV2_URID atom_Int; LV2_URID atom_Long; LV2_URID atom_String; LV2_URID atom_Object; LV2_URID atom_Vector; LV2_URID atom_Path; LV2_URID atom_Sequence; LV2_URID atom_Chunk; LV2_URID atom_URID; LV2_URID atom_eventTransfer; LV2_URID midi_Event; LV2_URID patch_Get; LV2_URID patch_Set; LV2_URID patch_Put; LV2_URID patch_body; LV2_URID patch_subject; LV2_URID patch_property; LV2_URID patch_value; LV2_URID param_uiState; }; Uris uris; std::unique_ptr audioDriver; std::recursive_mutex mutex; int64_t overrunGracePeriodSamples = 0; IAudioHostCallbacks *pNotifyCallbacks = nullptr; virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) { this->pNotifyCallbacks = pNotifyCallbacks; } const size_t RING_BUFFER_SIZE = 64 * 1024; RingBuffer inputRingBuffer; RingBuffer outputRingBuffer; RingBufferWriter x; RealtimeRingBufferReader realtimeReader; RealtimeRingBufferWriter realtimeWriter; HostRingBufferReader hostReader; HostRingBufferWriter hostWriter; SystemMidiBinding prevBankMidiBinding; SystemMidiBinding nextBankMidiBinding; SystemMidiBinding nextPresetMidiBinding; SystemMidiBinding prevPresetMidiBinding; SystemMidiBinding snapshot1MidiBinding; SystemMidiBinding snapshot2MidiBinding; SystemMidiBinding snapshot3MidiBinding; SystemMidiBinding snapshot4MidiBinding; SystemMidiBinding snapshot5MidiBinding; SystemMidiBinding snapshot6MidiBinding; SystemMidiBinding startHotspotMidiBinding; SystemMidiBinding stopHotspotMidiBinding; SystemMidiBinding rebootMidiBinding; SystemMidiBinding shutdownMidiBinding; JackChannelSelection channelSelection; 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. Lv2Pedalboard *realtimeActivePedalboard = nullptr; uint32_t sampleRate = 0; uint64_t currentSample = 0; std::atomic underruns = 0; std::atomic lastUnderrunTime = std::chrono::system_clock::from_time_t(0); std::string GetAtomObjectType(uint8_t *pData) { LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData; if (pAtom->atom.type != uris.atom_Object) { throw std::invalid_argument("Not an Lv2 Object"); } return pHost->Lv2UridToString(pAtom->body.otype); } virtual std::string AtomToJson(const LV2_Atom *pAtom) override { std::lock_guard lock(atomConverterMutex); json_variant vAtom = atomConverter.ToJson(pAtom); std::stringstream s; json_writer writer(s); writer.write(vAtom); return s.str(); } virtual void OnUnderrun() { ++this->underruns; this->lastUnderrunTime = std::chrono::system_clock ::now(); } virtual void Close() { { std::lock_guard guard{mutex}; if (!isOpen) return; isOpen = false; } if (active) { audioDriver->Deactivate(); active = false; } audioDriver->Close(); StopReaderThread(); // delete any leaked snapshots. CleanUpSnapshots(); // release any pdealboards owned by the process thread. this->activePedalboards.resize(0); this->realtimeActivePedalboard = nullptr; // clean up any realtime buffers that may have been lost in transit. // TODO: These should be lists, really. There may be multiple items in flight.. if (realtimeVuBuffers != nullptr) { delete realtimeVuBuffers; realtimeVuBuffers = nullptr; } if (realtimeMonitorPortSubscriptions != nullptr) { delete realtimeMonitorPortSubscriptions; realtimeMonitorPortSubscriptions = nullptr; } this->inputRingBuffer.reset(); this->outputRingBuffer.reset(); audioDriver = nullptr; } void ZeroBuffer(float *buffer, size_t nframes) { for (size_t i = 0; i < nframes; ++i) { buffer[i] = 0; } } Lv2EventBufferUrids eventBufferUrids; void ZeroOutputBuffers(size_t nframes) { for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i) { float *out = (float *)audioDriver->GetOutputBuffer(i); if (out) { ZeroBuffer(out, nframes); } } } RealtimeVuBuffers *realtimeVuBuffers = nullptr; size_t vuSamplesPerUpdate = 0; int64_t vuSamplesRemaining = 0; void freeRealtimeVuConfiguration() { if (this->realtimeVuBuffers != nullptr) { realtimeWriter.FreeVuSubscriptions(this->realtimeVuBuffers); this->realtimeVuBuffers = nullptr; } } RealtimeMonitorPortSubscriptions *realtimeMonitorPortSubscriptions = nullptr; void freeRealtimeMonitorPortSubscriptions() { if (this->realtimeMonitorPortSubscriptions != nullptr) { realtimeWriter.FreeMonitorPortSubscriptions(this->realtimeMonitorPortSubscriptions); this->realtimeMonitorPortSubscriptions = nullptr; } } virtual void SetSystemMidiBindings(const std::vector &bindings); void writeVu() { // throttling: we send one; but won't send another until the host thread // acknowledges receipt. if (!realtimeVuBuffers->waitingForAcknowledge) { auto pResult = realtimeVuBuffers->GetResult(currentSample); this->realtimeWriter.SendVuUpdate(pResult); realtimeVuBuffers->waitingForAcknowledge = true; } } void processMonitorPortSubscriptions(uint32_t nframes) { for (size_t i = 0; i < this->realtimeMonitorPortSubscriptions->subscriptions.size(); ++i) { auto &portSubscription = realtimeMonitorPortSubscriptions->subscriptions[i]; portSubscription.samplesToNextCallback -= portSubscription.sampleRate; if (portSubscription.samplesToNextCallback < 0) { portSubscription.samplesToNextCallback += portSubscription.sampleRate; if (!portSubscription.waitingForAck) { float value = realtimeActivePedalboard->GetControlOutputValue( portSubscription.instanceIndex, portSubscription.portIndex); if (value != portSubscription.lastValue) { portSubscription.waitingForAck = true; portSubscription.lastValue = value; this->realtimeWriter.SendMonitorPortUpdate( portSubscription.callbackPtr, portSubscription.subscriptionHandle, value); } } } } } RealtimePatchPropertyRequest *pParameterRequests = nullptr; void cancelParameterRequests() { return; // auto p = this->pParameterRequests; // while (pParameterRequests != nullptr) // { // auto nextParameterRequest = p->pNext; // if (p->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { // p->errorMessage = "Effect unloaded."; // } // p = nextParameterRequest; // } // this->realtimeWriter.ParameterRequestComplete(pParameterRequests); // this->pParameterRequests = nullptr; } bool reEntered = false; // returns false if the current effect has been replaced. bool ProcessInputCommands() { if (reEntered) { throw PiPedalStateException("Rentry of process command."); } reEntered = true; while (true) { RingBufferCommand command; size_t space = realtimeReader.readSpace(); if (space <= sizeof(RingBufferCommand)) // RingBufferCommand + at least 1 more byte for the body. break; if (!realtimeReader.read(&command)) { break; } switch (command) { case RingBufferCommand::SetValue: { SetControlValueBody body; realtimeReader.readComplete(&body); this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value); break; } case RingBufferCommand::SetInputVolume: { SetVolumeBody body; realtimeReader.readComplete(&body); this->realtimeActivePedalboard->SetInputVolume(body.value); break; } case RingBufferCommand::SetOutputVolume: { SetVolumeBody body; realtimeReader.readComplete(&body); this->realtimeActivePedalboard->SetOutputVolume(body.value); break; } case RingBufferCommand::ParameterRequest: { RealtimePatchPropertyRequest *pRequest = nullptr; realtimeReader.readComplete(&pRequest); // link to the list of parameter requests. pRequest->pNext = pParameterRequests; pParameterRequests = pRequest; break; } case RingBufferCommand::AckVuUpdate: { bool dummy; realtimeReader.readComplete(&dummy); if (this->realtimeVuBuffers) { this->realtimeVuBuffers->waitingForAcknowledge = false; } break; } case RingBufferCommand::AckMonitorPortUpdate: { int64_t subscriptionHandle = 0; realtimeReader.readComplete(&subscriptionHandle); if (this->realtimeMonitorPortSubscriptions != nullptr) { for (size_t i = 0; i < this->realtimeMonitorPortSubscriptions->subscriptions.size(); ++i) { if (this->realtimeMonitorPortSubscriptions->subscriptions[i].subscriptionHandle == subscriptionHandle) { this->realtimeMonitorPortSubscriptions->subscriptions[i].waitingForAck = false; } } } break; } case RingBufferCommand::SetMonitorPortSubscription: { RealtimeMonitorPortSubscriptions *pSubscriptions; realtimeReader.readComplete(&pSubscriptions); this->freeRealtimeMonitorPortSubscriptions(); this->realtimeMonitorPortSubscriptions = pSubscriptions; break; } case RingBufferCommand::AckMidiProgramChange: { int64_t requestId; realtimeReader.readComplete(&requestId); if (requestId == this->midiProgramChangeId) { this->midiProgramChangePending = false; } break; } case RingBufferCommand::AckMidiSnapshotRequest: { int64_t requestId; realtimeReader.readComplete(&requestId); if (requestId == this->snapshotRequestId) { this->midiSnapshotRequestPending = false; } break; } case RingBufferCommand::LoadSnapshot: { IndexedSnapshot *snapshot; realtimeReader.readComplete(&snapshot); ApplySnapshot(snapshot); realtimeWriter.FreeSnapshot(snapshot); break; } case RingBufferCommand::SetVuSubscriptions: { RealtimeVuBuffers *configuration; realtimeReader.readComplete(&configuration); this->freeRealtimeVuConfiguration(); this->realtimeVuBuffers = configuration; if (this->realtimeVuBuffers != nullptr) { this->realtimeVuBuffers->waitingForAcknowledge = false; } vuSamplesRemaining = vuSamplesPerUpdate; break; } case RingBufferCommand::SetBypass: { SetBypassBody body; realtimeReader.readComplete(&body); this->realtimeActivePedalboard->SetBypass(body.effectIndex, body.enabled); break; } case RingBufferCommand::ReplaceEffect: { ReplaceEffectBody body; realtimeReader.readComplete(&body); if (body.effect != nullptr) { auto oldValue = this->realtimeActivePedalboard; this->realtimeActivePedalboard = body.effect; realtimeWriter.EffectReplaced(oldValue); // invalidate the possibly no-good subscriptions. Model will update them shortly. freeRealtimeVuConfiguration(); freeRealtimeMonitorPortSubscriptions(); cancelParameterRequests(); if (realtimeActivePedalboard) { realtimeActivePedalboard->ResetAtomBuffers(); // issue patch gets for all writable path properties. for (auto pEffect : realtimeActivePedalboard->GetEffects()) { pEffect->RequestAllPathPatchProperties(); } } } reEntered = false; return false; // signal to caller that the effect has been replaced, and processing needs to start again. } default: throw PiPedalStateException("Unknown Ringbuffer command."); } } reEntered = false; return true; } void ApplySnapshot(IndexedSnapshot *snapshot) { auto &effects = this->realtimeActivePedalboard->GetEffects(); snapshot->Apply(effects); } virtual void AckMidiProgramRequest(uint64_t requestId) { hostWriter.AckMidiProgramRequest(requestId); } virtual void AckSnapshotRequest(uint64_t snapshotRequestId) { hostWriter.AckMidiSnapshotRequest(snapshotRequestId); } void OnMidiValueChanged(uint64_t instanceId, int controlIndex, float value) { realtimeWriter.MidiValueChanged(instanceId, controlIndex, value); } static void fnMidiValueChanged(void *data, uint64_t instanceId, int controlIndex, float value) { ((AudioHostImpl *)data)->OnMidiValueChanged(instanceId, controlIndex, value); } static bool isBankChange(MidiEvent &event) { return (event.size == 3 && event.buffer[0] == 0xB0 && event.buffer[1] == 0x00); } bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) { //eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged); if (listenForMidiEvent) { if (event.size >= 3) { uint8_t cmd = (uint8_t)(event.buffer[0] & 0xF0); bool isNote = cmd == 0x90; bool isControl = cmd == 0xB0; if (isNote || isControl) { realtimeWriter.OnMidiListen(isNote, event.buffer[1]); } } } return true; } void OnSnapshotTriggered(int snapshotIndex) { // midiProgramChangePending = true; this->midiSnapshotRequestPending = true; this->realtimeWriter.OnRealtimeMidiSnapshotRequest(snapshotIndex, ++snapshotRequestId); } void ProcessMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) { uint8_t midiCommand = (uint8_t)(event.buffer[0] & 0xF0); if (midiCommand == 0xC0) // midi program change. { this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; this->realtimeWriter.OnMidiProgramChange(++(this->midiProgramChangeId), selectedBank, event.buffer[1]); } else if (isBankChange(event)) { this->selectedBank = event.buffer[2]; } else if (this->nextBankMidiBinding.IsTriggered(event)) { this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; this->realtimeWriter.OnNextMidiBank(++(this->midiProgramChangeId), 1); } else if (this->prevBankMidiBinding.IsTriggered(event)) { this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; this->realtimeWriter.OnNextMidiBank(++(this->midiProgramChangeId), -1); } else if (nextPresetMidiBinding.IsTriggered(event)) { this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; midiProgramChangePending = true; this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), 1); } else if (prevPresetMidiBinding.IsTriggered(event)) { this->deferredMidiMessageCount = 0; // we can discard previous control changes. midiProgramChangePending = true; this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1); } else if (shutdownMidiBinding.IsTriggered(event)) { this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown); } if (rebootMidiBinding.IsTriggered(event)) { this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot); } if (startHotspotMidiBinding.IsTriggered(event)) { this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot); } if (stopHotspotMidiBinding.IsTriggered(event)) { this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot); } else if (midiProgramChangePending) { // defer the message for processing after the program change has completed discarding messages that don't fit in our buffer. if (event.size > 0 && event.size < 128 && event.size + 2 + this->deferredMidiMessageCount < DEFERRED_MIDI_BUFFER_SIZE) { this->deferredMidiMessages[deferredMidiMessageCount++] = (uint8_t)event.size; for (size_t i = 0; i < event.size; ++i) { this->deferredMidiMessages[deferredMidiMessageCount++] = event.buffer[i]; } } return; } else if (this->snapshot1MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(0); } else if (this->snapshot2MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(1); } else if (this->snapshot3MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(2); } else if (this->snapshot4MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(3); } else if (this->snapshot5MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(4); } else if (this->snapshot6MidiBinding.IsTriggered(event)) { OnSnapshotTriggered(5); } else { onMidiEvent(eventBufferWriter, iterator, event); } } void ProcessDeferredMidiMessages( Lv2EventBufferWriter eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator) { MidiEvent event; // write all deferred midi messages. if (!midiProgramChangePending && !midiSnapshotRequestPending) { for (size_t i = 0; i < deferredMidiMessageCount; /**/) { int8_t deviceIndex = deferredMidiMessages[i++]; int8_t messageCount = deferredMidiMessages[i++]; event.size = messageCount; event.buffer = deferredMidiMessages + i; event.time = 0; ProcessMidiEvent(eventBufferWriter, iterator, event); if (midiProgramChangePending) { break; } i += messageCount; if (midiSnapshotRequestPending) { // consume what has been written so far, leaving what follows for future processing. size_t remaining = deferredMidiMessageCount - i; for (size_t j = 0; j < remaining; ++i) { deferredMidiMessages[j] = deferredMidiMessages[i + j]; } deferredMidiMessageCount = remaining; break; } } } } void ProcessMidiInput() { Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids); Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin(); ProcessDeferredMidiMessages(eventBufferWriter, iterator); { size_t n = audioDriver->GetMidiInputEventCount(); MidiEvent *events = audioDriver->GetMidiEvents(); for (size_t i = 0; i < n; ++i) { ProcessMidiEvent(eventBufferWriter, iterator, events[i]); } } } #define RESET_XRUN_SAMPLES 22050ul // 1/2 a second-ish. std::mutex audioStoppedMutex; bool IsAudioRunning() { return this->active && (!this->audioStopped) && !this->isDummyAudioDriver; } virtual void OnAudioStopped() override { this->audioStopped = true; Lv2Log::info("Audio stopped."); } virtual void OnAudioTerminated() override { this->active = false; Lv2Log::info("Audio thread terminated."); } virtual void OnProcess(size_t nframes) { try { float *in, *out; Lv2Pedalboard *pedalboard = nullptr; pedalboard = this->realtimeActivePedalboard; if (pedalboard) { pedalboard->ResetAtomBuffers(); } while (true) { if (ProcessInputCommands()) { break; } // else a new pedalboard was installed. start again. pedalboard = this->realtimeActivePedalboard; } bool processed = false; if (pedalboard != nullptr) { ProcessMidiInput(); float *inputBuffers[4]; float *outputBuffers[4]; bool buffersValid = true; for (int i = 0; i < audioDriver->InputBufferCount(); ++i) { float *input = (float *)audioDriver->GetInputBuffer(i); if (input == nullptr) { buffersValid = false; break; } inputBuffers[i] = input; } inputBuffers[audioDriver->InputBufferCount()] = nullptr; for (int i = 0; i < audioDriver->OutputBufferCount(); ++i) { float *output = audioDriver->GetOutputBuffer(i); if (output == nullptr) { buffersValid = false; break; } outputBuffers[i] = output; } outputBuffers[audioDriver->OutputBufferCount()] = nullptr; if (buffersValid) { pedalboard->ProcessParameterRequests(pParameterRequests); processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); if (processed) { if (this->realtimeVuBuffers != nullptr) { pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes, inputBuffers, outputBuffers); vuSamplesRemaining -= nframes; if (vuSamplesRemaining <= 0) { writeVu(); vuSamplesRemaining += vuSamplesPerUpdate; } } if (this->realtimeMonitorPortSubscriptions != nullptr) { processMonitorPortSubscriptions(nframes); } } pedalboard->GatherPatchProperties(pParameterRequests); pedalboard->GatherPathPatchProperties(this); } } if (!processed) { ZeroOutputBuffers(nframes); } if (pParameterRequests != nullptr) { this->realtimeWriter.ParameterRequestComplete(pParameterRequests); pParameterRequests = nullptr; } // provide a grace period for undderruns, while spinning up. (15 second-ish) if (currentSample <= this->overrunGracePeriodSamples && currentSample + nframes > this->overrunGracePeriodSamples) { this->underruns = 0; } this->currentSample += nframes; } catch (const std::exception &e) { Lv2Log::error("Fatal error while processing jack audio. (%s)", e.what()); throw; } } public: AudioHostImpl(IHost *pHost) : inputRingBuffer(RING_BUFFER_SIZE), outputRingBuffer(RING_BUFFER_SIZE), realtimeReader(&this->inputRingBuffer), realtimeWriter(&this->outputRingBuffer), hostReader(&this->outputRingBuffer), hostWriter(&this->inputRingBuffer), eventBufferUrids(pHost), pHost(pHost), uris(pHost), atomConverter(pHost->GetMapFeature()) { realtimeAtomBuffer.resize(32 * 1024); lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap()); } virtual ~AudioHostImpl() { Close(); CleanRestartThreads(true); audioDriver = nullptr; } virtual JackConfiguration GetServerConfiguration() { JackConfiguration result; result.JackInitialize(); return result; } virtual uint32_t GetSampleRate() { return this->sampleRate; } void OnAudioComplete() { // there is actually no compelling circumstance in which this should ever happen. Lv2Log::error("Audio processing terminated unexpectedly."); realtimeWriter.AudioStopped(); } std::vector atomBuffer; std::vector realtimeAtomBuffer; bool terminateThread; void ThreadProc() { #if defined(__WIN32) // bump thread prioriy two levels to // ensure that the service thread doesn't // get bogged down by UIwork. Doesn't have to be realtime, but it // MUST run at higher _ than UI threads. xxx; // TO DO. #elif defined(__linux__) int min = sched_get_priority_min(SCHED_RR); int max = sched_get_priority_max(SCHED_RR); struct sched_param param; memset(¶m, 0, sizeof(param)); param.sched_priority = min; int result = sched_setscheduler(0, SCHED_RR, ¶m); if (result == 0) { Lv2Log::debug("Service thread priority successfully boosted."); } SetThreadName("rtsvc"); #else xxx; // TODO! #endif int underrunMessagesGiven = 0; try { uint64_t lastUnderrunCount = this->underruns; using clock = std::chrono::steady_clock; using clock_time = std::chrono::steady_clock::time_point; using clock_duration = clock_time::duration; // check for overruns every 30 seconds. clock_duration waitPeriod = std::chrono::duration_cast(std::chrono::seconds(30)); clock_time waitTime = std::chrono::steady_clock::now(); while (true) { // wait for an event. // 0 -> ready. -1: timed out. -2: closing. auto result = hostReader.wait_until(sizeof(RingBufferCommand), waitTime); if (result == RingBufferStatus::Closed) { return; } else if (result == RingBufferStatus::TimedOut) { // timeout. if (underruns != lastUnderrunCount) { if (underrunMessagesGiven < 60) // limit how much log file clutter we generate. { Lv2Log::info("Audio underrun count: %lu", (unsigned long)underruns); lastUnderrunCount = underruns; ++underrunMessagesGiven; } } waitTime += waitPeriod; } else { while (true) { size_t space = hostReader.readSpace(); if (space <= sizeof(RingBufferCommand)) { break; } RingBufferCommand command; if (hostReader.read(&command)) { if (command == RingBufferCommand::OnMidiListen) { uint16_t msg; hostReader.read(&msg); if (this->pNotifyCallbacks) { pNotifyCallbacks->OnNotifyMidiListen((msg & 0xFF00) != 0, (uint8_t)msg); } } else if (command == RingBufferCommand::MidiValueChanged) { MidiValueChangedBody body; hostReader.read(&body); if (this->pNotifyCallbacks) { this->pNotifyCallbacks->OnNotifyMidiValueChanged(body.instanceId, body.controlIndex, body.value); } } else if (command == RingBufferCommand::ParameterRequestComplete) { RealtimePatchPropertyRequest *pRequest = nullptr; hostReader.read(&pRequest); std::shared_ptr currentpedalboard; { std::lock_guard guard(mutex); currentPedalboard = this->currentPedalboard; } while (pRequest != nullptr) { auto pNext = pRequest->pNext; if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { if (pRequest->errorMessage == nullptr) { if (pRequest->GetSize() != 0) { IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId); if (pEffect == nullptr) { pRequest->errorMessage = "Effect no longer available."; } else { pRequest->jsonResponse = AtomToJson((LV2_Atom *)pRequest->GetBuffer()); } } else { pRequest->errorMessage = "Plugin did not respond."; } } } if (pRequest->onPatchRequestComplete) { pRequest->onPatchRequestComplete(pRequest); } pRequest = pNext; } } else if (command == RingBufferCommand::SendMonitorPortUpdate) { MonitorPortUpdate body; hostReader.read(&body); if (this->pNotifyCallbacks != nullptr) { this->pNotifyCallbacks->OnNotifyMonitorPort(body); } this->hostWriter.AckMonitorPortUpdate(body.subscriptionHandle); // please sir, can I have some more? } else if (command == RingBufferCommand::SendVuUpdate) { const std::vector *updates = nullptr; hostReader.read(&updates); if (this->pNotifyCallbacks) { this->pNotifyCallbacks->OnNotifyVusSubscription(*updates); } this->hostWriter.AckVuUpdate(); // please sir, can I have some more? } else if (command == RingBufferCommand::Lv2StateChanged) { uint64_t instanceId; hostReader.read(&instanceId); this->pNotifyCallbacks->OnNotifyLv2StateChanged(instanceId); } else if (command == RingBufferCommand::MaybeLv2StateChanged) { uint64_t instanceId; hostReader.read(&instanceId); this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); } else if (command == RingBufferCommand::AtomOutput) { uint64_t instanceId; hostReader.read(&instanceId); size_t extraBytes; hostReader.read(&extraBytes); if (atomBuffer.size() < extraBytes) { atomBuffer.resize(extraBytes); } hostReader.read(extraBytes, &(atomBuffer[0])); IEffect *pEffect = currentPedalboard->GetEffect(instanceId); if (pEffect != nullptr && this->pNotifyCallbacks) { LV2_Atom *atom = (LV2_Atom *)&atomBuffer[0]; if (atom->type == uris.atom_Object) { 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; lv2_atom_object_get( obj, uris.patch_property, &property, uris.patch_value, &value, 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); //this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); } } } } } else if (command == RingBufferCommand::FreeVuSubscriptions) { RealtimeVuBuffers *config; hostReader.read(&config); delete config; } else if (command == RingBufferCommand::FreeMonitorPortSubscription) { RealtimeMonitorPortSubscriptions *pSubscriptions; hostReader.read(&pSubscriptions); delete pSubscriptions; } else if (command == RingBufferCommand::EffectReplaced) { EffectReplacedBody body; hostReader.read(&body); OnActivePedalboardReleased(body.oldEffect); } else if (command == RingBufferCommand::FreeSnapshot) { IndexedSnapshot *snapshot; hostReader.read(&snapshot); OnFreeSnapshot(snapshot); } else if (command == RingBufferCommand::SendPathPropertyBuffer) { PatchPropertyWriter::Buffer *buffer = nullptr; hostReader.read(&buffer); OnPathPropertyReceived(buffer); } else if (command == RingBufferCommand::AudioStopped) { AudioStoppedBody body; hostReader.read(&body); OnAudioComplete(); return; } else if (command == RingBufferCommand:: MidiProgramChange) { RealtimeMidiProgramRequest programRequest; hostReader.read(&programRequest); OnMidiProgramRequest(programRequest); } else if (command == RingBufferCommand::NextMidiProgram) { RealtimeNextMidiProgramRequest request; hostReader.read(&request); pNotifyCallbacks->OnNotifyNextMidiProgram(request); } else if (command == RingBufferCommand::NextMidiBank) { RealtimeNextMidiProgramRequest request; hostReader.read(&request); pNotifyCallbacks->OnNotifyNextMidiBank(request); } else if (command == RingBufferCommand::RealtimeMidiEvent) { RealtimeMidiEventRequest request; hostReader.read(&request); pNotifyCallbacks->OnNotifyMidiRealtimeEvent(request.eventType); } else if (command == RingBufferCommand::RealtimeMidiSnapshotRequest) { RealtimeMidiSnapshotRequest request; hostReader.read(&request); pNotifyCallbacks->OnNotifyMidiRealtimeSnapshotRequest( request.snapshotIndex, request.snapshotRequestId); } else if (command == RingBufferCommand::Lv2ErrorMessage) { size_t size; int64_t instanceId; hostReader.read(&instanceId); hostReader.read(&size); if (this->atomBuffer.size() < size + 1) { this->atomBuffer.resize(size + 1); } hostReader.read(size, &(atomBuffer[0])); char *p = (char *)&(atomBuffer[0]); p[size] = 0; std::string message(p); pNotifyCallbacks->OnNotifyLv2RealtimeError(instanceId, message); } else { throw PiPedalStateException("Unrecognized command received from audio thread."); } } } } } } catch (const std::exception &e) { Lv2Log::error("Realtime response thread terminated abnormally. (%s)", e.what()); } } std::thread *readerThread = nullptr; void StopReaderThread() { if (readerThread != nullptr) { this->terminateThread = true; this->outputRingBuffer.close(); readerThread->join(); delete readerThread; readerThread = nullptr; } } void StartReaderThread() { terminateThread = false; auto f = [this]() { this->ThreadProc(); }; this->readerThread = new std::thread(f); } bool isOpen = false; virtual bool IsOpen() const { return isOpen; } uint8_t *AllocateRealtimeBuffer(size_t size) { uint8_t *result = new uint8_t[size]; // populate pages if required. We should have mlocked-ed alread, so they will stay. for (size_t i = 0; i < size; i += 1024) { result[i] = 0; } return result; } virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) { 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,jackServerSettings.GetAlsaInputDevice())); } else { this->isDummyAudioDriver = false; this->audioDriver = std::unique_ptr(CreateAlsaDriver(this)); } if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0) { return; } this->currentSample = 0; this->underruns = 0; this->inputRingBuffer.reset(); this->outputRingBuffer.reset(); this->hostReader.Reset(); this->hostWriter.Reset(); this->realtimeReader.Reset(); this->realtimeWriter.Reset(); this->channelSelection = channelSelection; StartReaderThread(); try { audioDriver->Open(jackServerSettings, this->channelSelection); this->sampleRate = audioDriver->GetSampleRate(); this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate) * OVERRUN_GRACE_PERIOD_S); this->vuSamplesPerUpdate = (size_t)(sampleRate * VU_UPDATE_RATE_S); active = true; audioStopped = false; audioDriver->Activate(); Lv2Log::info(SS("Audio started. " << audioDriver->GetConfigurationDescription())); } catch (const std::exception &e) { Close(); active = false; throw; } } void OnMidiProgramRequest(RealtimeMidiProgramRequest &programRequest) { pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest); } void OnPathPropertyReceived(PatchPropertyWriter::Buffer *buffer) { if (pNotifyCallbacks) { pNotifyCallbacks->OnNotifyPathPatchPropertyReceived( buffer->instanceId, buffer->patchPropertyUrid, (LV2_Atom *)(buffer->memory.data())); } buffer->OnBufferReadComplete(); } void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard) { if (pPedalboard) { pPedalboard->Deactivate(); std::lock_guard guard(mutex); for (auto it = activePedalboards.begin(); it != activePedalboards.end(); ++it) { if ((*it).get() == pPedalboard) { // erase it, relinquishing shared_ptr ownership, usually deleting the object. activePedalboards.erase(it); return; } } } } virtual void SetPedalboard(const std::shared_ptr &pedalboard) { std::lock_guard guard(mutex); this->currentPedalboard = pedalboard; if (active) { pedalboard->Activate(); this->activePedalboards.push_back(pedalboard); hostWriter.ReplaceEffect(pedalboard.get()); } } virtual void SetBypass(uint64_t instanceId, bool enabled) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { // use indices not instance ids, so we can just do a straight array index on the audio thread. auto index = currentPedalboard->GetIndexOfInstanceId(instanceId); if (index >= 0) { hostWriter.SetBypass((uint32_t)index, enabled); } } } virtual void SetPluginPreset(uint64_t instanceId, const std::vector &values) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId); if (effectIndex != -1) { for (size_t i = 0; i < values.size(); ++i) { const ControlValue &value = values[i]; int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, value.key()); if (controlIndex != -1 && effectIndex != -1) { hostWriter.SetControlValue(effectIndex, controlIndex, value.value()); } } } } } void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { // use indices not instance ids, so we can just do a straight array index on the audio thread. int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, symbol); auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId); if (controlIndex != -1 && effectIndex != -1) { hostWriter.SetControlValue(effectIndex, controlIndex, value); } } } virtual void SetInputVolume(float value) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { hostWriter.SetInputVolume(value); } } virtual void SetOutputVolume(float value) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { hostWriter.SetOutputVolume(value); } } std::vector pendingSnapshots; virtual void LoadSnapshot(Snapshot &snapshot, PluginHost &pluginHost) override { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { IndexedSnapshot *indexedSnapshot = new IndexedSnapshot(&snapshot, this->currentPedalboard, pluginHost); pendingSnapshots.push_back(indexedSnapshot); this->hostWriter.LoadSnapshot(indexedSnapshot); } } void OnNotifyPathPatchPropertyReceived( int64_t instanceId, const std::string &pathPatchPropertyUri, const std::string &jsonAtom) override; void OnFreeSnapshot(IndexedSnapshot *snapshot) { { std::lock_guard guard(mutex); for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i) { if (*i == snapshot) { pendingSnapshots.erase(i); break; } } } delete snapshot; } void CleanUpSnapshots() { for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i) { IndexedSnapshot *snapshot = *i; delete snapshot; } pendingSnapshots.clear(); } virtual void SetVuSubscriptions(const std::vector &instanceIds) { std::lock_guard guard(mutex); if (active && this->currentPedalboard) { if (instanceIds.size() == 0) { this->hostWriter.SetVuSubscriptions(nullptr); } else { RealtimeVuBuffers *vuConfig = new RealtimeVuBuffers(); for (size_t i = 0; i < instanceIds.size(); ++i) { int64_t instanceId = instanceIds[i]; auto effect = this->currentPedalboard->GetEffect(instanceId); if (effect) { int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]); vuConfig->enabledIndexes.push_back(index); VuUpdate v; v.instanceId_ = instanceId; // Display mono VUs if a stereo device is being fed identical L/R inputs. v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1); v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1; vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v); } else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) { int index = (int)instanceId; VuUpdate v; vuConfig->enabledIndexes.push_back(index); v.instanceId_ = instanceId; if (instanceId == Pedalboard::INPUT_VOLUME_ID) { v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1; } else { v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfOutputAudioChannels() > 1; } vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v); } } this->hostWriter.SetVuSubscriptions(vuConfig); } } } RealtimeMonitorPortSubscription MakeRealtimeSubscription(const MonitorPortSubscription &subscription) { RealtimeMonitorPortSubscription result; result.subscriptionHandle = subscription.subscriptionHandle; result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid); IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid); result.portIndex = pEffect->GetControlIndex(subscription.key); result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval); result.samplesToNextCallback = result.sampleRate; PortMonitorCallback *ptr = new PortMonitorCallback(subscription.onUpdate); result.callbackPtr = ptr; return result; } virtual void SetMonitorPortSubscriptions(const std::vector &subscriptions) { if (!active) return; if (this->currentPedalboard == nullptr) return; if (subscriptions.size() == 0) { this->hostWriter.SetMonitorPortSubscriptions(nullptr); } else { RealtimeMonitorPortSubscriptions *pSubscriptions = new RealtimeMonitorPortSubscriptions(); for (size_t i = 0; i < subscriptions.size(); ++i) { if (this->currentPedalboard->GetEffect(subscriptions[i].instanceid) != nullptr) { pSubscriptions->subscriptions.push_back( MakeRealtimeSubscription(subscriptions[i])); } } this->hostWriter.SetMonitorPortSubscriptions(pSubscriptions); } } private: //virtual bool UpdatePluginStates(Pedalboard &pedalboard) override; virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override; class RestartThread { AudioHostImpl *this_; JackServerSettings jackServerSettings; std::function onComplete; std::atomic isComplete = false; std::thread *pThread = nullptr; public: RestartThread( AudioHostImpl *host, const JackServerSettings &jackServerSettings_, std::function onComplete_) : this_(host), jackServerSettings(jackServerSettings_), onComplete(onComplete_) { } ~RestartThread() { pThread->join(); delete pThread; } bool IsComplete() const { return isComplete; } void ThreadProc() { this_->restarting = true; // this_->Close(); (JackServerConfiguration now does a service restart.) try { AdminClient client; client.SetJackServerConfiguration(jackServerSettings); // this_->Open(this_->channelSelection); this_->restarting = false; onComplete(true, ""); isComplete = true; } catch (const std::exception &e) { onComplete(false, e.what()); this_->restarting = false; isComplete = true; } } static void ThreadProc_(RestartThread *this_) { this_->ThreadProc(); } void Run() { pThread = new std::thread(ThreadProc_, this); } }; bool restarting = false; std::vector restartThreads; public: std::recursive_mutex restart_mutex; virtual void UpdateServerConfiguration(const JackServerSettings &jackServerSettings, std::function onComplete) { std::lock_guard guard(restart_mutex); RestartThread *pShutdown = new RestartThread(this, jackServerSettings, onComplete); restartThreads.push_back(pShutdown); pShutdown->Run(); } void CleanRestartThreads(bool final) { std::lock_guard guard{restart_mutex}; for (size_t i = 0; i < restartThreads.size(); ++i) { if (final) { delete restartThreads[i]; } else { if (restartThreads[i]->IsComplete()) { delete restartThreads[i]; restartThreads.erase(restartThreads.begin() + i); --i; } } } if (final) { restartThreads.clear(); } } virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) { if (!active) { pParameterRequest->errorMessage = "Not active."; pParameterRequest->onPatchRequestComplete(pParameterRequest); return; } this->hostWriter.ParameterRequest(pParameterRequest); } static int32_t GetRaspberryPiTemperature() { try { std::ifstream f("/sys/class/thermal/thermal_zone0/temp"); int32_t temp; f >> temp; return temp; } catch (std::exception &) { return -1000000; } } virtual JackHostStatus getJackStatus() { CleanRestartThreads(false); using namespace std::chrono; using std::chrono::duration_cast; using std::chrono::milliseconds; std::lock_guard guard(mutex); JackHostStatus result; result.underruns_ = this->underruns; auto dt = duration_cast(std::chrono::system_clock ::now().time_since_epoch()).count() - duration_cast(this->lastUnderrunTime.load().time_since_epoch()).count(); result.msSinceLastUnderrun_ = (uint64_t)dt; result.temperaturemC_ = GetRaspberryPiTemperature(); result.active_ = IsAudioRunning(); result.restarting_ = this->restarting; if (this->audioDriver != nullptr) { result.cpuUsage_ = audioDriver->CpuUse(); } GetCpuFrequency(&result.cpuFreqMax_, &result.cpuFreqMin_); result.hasCpuGovernor_ = HasCpuGovernor(); if (result.hasCpuGovernor_) { result.governor_ = GetGovernor(); } else { result.governor_ = ""; } return result; } volatile bool listenForMidiEvent = false; volatile bool listenForAtomOutput = false; virtual void SetListenForMidiEvent(bool listen) { this->listenForMidiEvent = listen; } virtual void SetListenForAtomOutput(bool listen) { this->listenForAtomOutput = listen; } }; static std::string UriToFieldName(const std::string &uri) { int pos; for (pos = uri.length(); pos >= 0; --pos) { char c = uri[pos]; if (c == '#' || c == '/' || c == ':') { break; } } return uri.substr(pos + 1); } void AudioHostImpl::SetSystemMidiBindings(const std::vector &bindings) { for (auto i = bindings.begin(); i != bindings.end(); ++i) { if (i->symbol() == "nextBank") { this->nextBankMidiBinding.SetBinding(*i); } else if (i->symbol() == "prevBank") { this->prevBankMidiBinding.SetBinding(*i); } else if (i->symbol() == "nextProgram") { this->nextPresetMidiBinding.SetBinding(*i); } else if (i->symbol() == "prevProgram") { this->prevPresetMidiBinding.SetBinding(*i); } else if (i->symbol() == "startHotspot") { this->startHotspotMidiBinding.SetBinding(*i); } else if (i->symbol() == "stopHotspot") { this->stopHotspotMidiBinding.SetBinding(*i); } else if (i->symbol() == "reboot") { this->rebootMidiBinding.SetBinding(*i); } else if (i->symbol() == "shutdown") { this->shutdownMidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot1") { this->snapshot1MidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot2") { this->snapshot2MidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot3") { this->snapshot3MidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot4") { this->snapshot4MidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot5") { this->snapshot5MidiBinding.SetBinding(*i); } else if (i->symbol() == "snapshot6") { this->snapshot6MidiBinding.SetBinding(*i); } else { Lv2Log::error(SS("Invalid system midi binding: " << i->symbol())); } } } AudioHost *AudioHost::CreateInstance(IHost *pHost) { return new AudioHostImpl(pHost); } // Removed because any updates to state have to be sent to clients as well, // so this functionality was moved to PiPedalModel::SyncLv2States(); // bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard) // { // bool changed = false; // auto pedalboardItems = pedalboard.GetAllPlugins(); // for (PedalboardItem *item : pedalboardItems) // { // if (!item->isSplit()) // { // if (UpdatePluginState(*item)) // { // changed = true; // } // } // } // return changed; // } void AudioHostImpl::OnNotifyPathPatchPropertyReceived( int64_t instanceId, const std::string &pathPatchPropertyUri, const std::string &jsonAtom) { if (this->currentPedalboard) { IEffect *effect = this->currentPedalboard->GetEffect(instanceId); if (!effect) { return; } if (effect->IsLv2Effect()) { Lv2Effect *lv2Effect = (Lv2Effect *)effect; lv2Effect->SetPathPatchProperty(pathPatchPropertyUri, jsonAtom); } } } bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) { IEffect *effect = currentPedalboard->GetEffect(pedalboardItem.instanceId()); if (effect != nullptr) { try { Lv2PluginState state; if (!effect->GetLv2State(&state)) { return false; } if (state != pedalboardItem.lv2State()) { pedalboardItem.lv2State(state); return true; } return false; } catch (const std::exception &e) { Lv2Log::warning(SS(pedalboardItem.pluginName() << ": " << e.what())); return false; } } return false; } void AudioHostImpl::OnWritePatchPropertyBuffer( PatchPropertyWriter::Buffer *buffer) { this->realtimeWriter.SendPathPropertyBuffer(buffer); } JSON_MAP_BEGIN(JackHostStatus) JSON_MAP_REFERENCE(JackHostStatus, active) JSON_MAP_REFERENCE(JackHostStatus, errorMessage) JSON_MAP_REFERENCE(JackHostStatus, restarting) JSON_MAP_REFERENCE(JackHostStatus, underruns) JSON_MAP_REFERENCE(JackHostStatus, cpuUsage) JSON_MAP_REFERENCE(JackHostStatus, msSinceLastUnderrun) JSON_MAP_REFERENCE(JackHostStatus, temperaturemC) JSON_MAP_REFERENCE(JackHostStatus, cpuFreqMin) JSON_MAP_REFERENCE(JackHostStatus, cpuFreqMax) JSON_MAP_REFERENCE(JackHostStatus, hasCpuGovernor) JSON_MAP_REFERENCE(JackHostStatus, governor) JSON_MAP_END()