// 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. #pragma once #include "PiPedalException.hpp" #include "Lv2Log.hpp" #include "VuUpdate.hpp" #include "AudioHost.hpp" #include "lv2/atom/atom.h" #include "RealtimeMidiEventType.hpp" #include namespace pipedal { class IndexedSnapshot; enum class RingBufferCommand : int64_t { Invalid = 0, ReplaceEffect, EffectReplaced, SetValue, SetBypass, //AudioStopped, AudioTerminatedAbnormally, // specifically for an ALSA loss of connection. SetVuSubscriptions, FreeVuSubscriptions, LoadSnapshot, FreeSnapshot, SendVuUpdate, AckVuUpdate, SetMonitorPortSubscription, FreeMonitorPortSubscription, SendMonitorPortUpdate, AckMonitorPortUpdate, ParameterRequest, ParameterRequestComplete, MidiValueChanged, OnMidiListen, AtomOutput, MidiProgramChange, // program change requested via midi. AckMidiProgramChange, AckMidiSnapshotRequest, NextMidiProgram, NextMidiBank, Lv2StateChanged, MaybeLv2StateChanged, SetInputVolume, SetOutputVolume, Lv2ErrorMessage, RealtimeMidiEvent, RealtimeMidiSnapshotRequest, SendPathPropertyBuffer, }; struct RealtimeMidiEventRequest { RealtimeMidiEventType eventType; }; struct RealtimeMidiSnapshotRequest { int32_t snapshotIndex; int64_t snapshotRequestId; }; struct RealtimeNextMidiProgramRequest { int64_t requestId; int32_t direction; }; struct RealtimeMidiProgramRequest { int64_t requestId; int8_t bank; uint8_t program; }; class RealtimeMonitorPortSubscription { public: RealtimeMonitorPortSubscription() { delete callbackPtr; } int64_t subscriptionHandle; int instanceIndex = 0; int portIndex = 0; PortMonitorCallback *callbackPtr = nullptr; int sampleRate = 0; int samplesToNextCallback = 0; bool waitingForAck = false; float lastValue = -1E30; }; class RealtimeMonitorPortSubscriptions { public: std::vector subscriptions; }; struct RealtimeVuBuffers { RealtimeVuBuffers() { Reset(); } bool waitingForAcknowledge = false; const std::vector *GetResult(size_t currentSample) { for (size_t i = 0; i < vuUpdateWorkingData.size(); ++i) { vuUpdateResponseData[i] = vuUpdateWorkingData[i]; vuUpdateResponseData[i].sampleTime_ = currentSample; vuUpdateWorkingData[i].reset(); } return &vuUpdateResponseData; } std::vector enabledIndexes; std::vector vuUpdateWorkingData; std::vector vuUpdateResponseData; void Reset() { for (int i = 0; i < vuUpdateWorkingData.size(); ++i) { vuUpdateWorkingData[i].reset(); } } }; class AudioStoppedBody { bool dummy = true; }; class SetBypassBody { public: int effectIndex; bool enabled; }; class MidiValueChangedBody { public: int64_t instanceId; int controlIndex; float value; }; class SetControlValueBody { public: int effectIndex; int controlIndex; float value; }; class SetVolumeBody { public: float value; }; class Lv2Pedalboard; class ReplaceEffectBody { public: Lv2Pedalboard *effect; }; class EffectReplacedBody { public: Lv2Pedalboard *oldEffect; }; template class RingBufferReader { private: RingBuffer *ringBuffer = nullptr; public: RingBufferReader() : ringBuffer(nullptr) { } RingBufferReader(RingBuffer *ringBuffer) : ringBuffer(ringBuffer) { } void Reset() { ringBuffer->reset(); } // 0 -> ready. -1: timed out. -2: closing. template RingBufferStatus wait_for(const std::chrono::duration &timeout) { return ringBuffer->readWait_for(timeout); } template RingBufferStatus wait_until(std::chrono::time_point &time_point) { return ringBuffer->readWait_until(time_point); } template RingBufferStatus wait_until(size_t size, std::chrono::time_point &time_point) { return ringBuffer->readWait_until(size, time_point); } bool wait() { return ringBuffer->readWait(); } size_t readSpace() const { return ringBuffer->readSpace(); } template bool read(T *output) { if (!ringBuffer->read(sizeof(T), (uint8_t *)output)) { throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); } return true; } bool read(size_t size, uint8_t *data) { if (!ringBuffer->read(size, data)) { throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); } return true; } template void readComplete(T *output) { if (!ringBuffer->read(sizeof(T), (uint8_t *)output)) { throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); } } }; template class CommandBuffer { public: CommandBuffer(RingBufferCommand command) : command(command) { } CommandBuffer(RingBufferCommand command, const T &value) : command(command), value(value) { } RingBufferCommand command; size_t size() const { return sizeof(RingBufferCommand) + sizeof(T); } T value; }; template class RingBufferWriter { private: RingBuffer *ringBuffer; public: RingBufferWriter() : ringBuffer(nullptr) { } RingBufferWriter(RingBuffer *ringBuffer) : ringBuffer(ringBuffer) { } void Reset() { ringBuffer->reset(); } template void write(RingBufferCommand command, const T &value) { // the goal: to atomically write the command and associated data. CommandBuffer buffer(command, value); if (!ringBuffer->write(buffer.size(), (uint8_t *)&buffer)) { Lv2Log::error("No space in audio service ringbuffer."); return; } } template void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t *variableData) { // the goal: to atomically write the command and associated data. CommandBuffer buffer(command, value); if (!ringBuffer->write(buffer.size(), (uint8_t *)&buffer, dataLength, variableData)) { Lv2Log::error("No space in audio service ringbuffer."); return; } } void Lv2StateChanged(uint64_t instanceId) { write(RingBufferCommand::Lv2StateChanged, instanceId); } void MaybeLv2StateChanged(uint64_t instanceId) { write(RingBufferCommand::MaybeLv2StateChanged, instanceId); } void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t *data) { write(RingBufferCommand::AtomOutput, instanceId, bytes, data); } void AtomOutput(uint64_t instanceId, const LV2_Atom *atom) { write(RingBufferCommand::AtomOutput, instanceId, atom->size + sizeof(LV2_Atom), (uint8_t *)atom); } void ParameterRequest(RealtimePatchPropertyRequest *pRequest) { write(RingBufferCommand::ParameterRequest, pRequest); } void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest) { write(RingBufferCommand::ParameterRequestComplete, pRequest); } void MidiValueChanged(int64_t instanceId, int controlIndex, float value) { MidiValueChangedBody body; body.instanceId = instanceId; body.controlIndex = controlIndex; body.value = value; write(RingBufferCommand::MidiValueChanged, body); } void OnMidiListen(bool isNote, uint8_t noteOrControl) { uint16_t msg = noteOrControl; if (isNote) msg |= 0x100; write(RingBufferCommand::OnMidiListen, msg); } /** * @brief Notify host of a midi program change request. * * @param bank MIDI bank number, or -1 for current bank. * @param program MIDI program number. */ void OnMidiProgramChange(int64_t _requestId, int8_t _bank, uint8_t _program) { RealtimeMidiProgramRequest msg{requestId : _requestId, bank : _bank, program : _program}; write(RingBufferCommand::MidiProgramChange, msg); } void OnRealtimeMidiEvent(RealtimeMidiEventType eventType) { RealtimeMidiEventRequest msg{eventType}; write(RingBufferCommand::RealtimeMidiEvent, msg); } void OnRealtimeMidiSnapshotRequest(int32_t snapshotIndex, int64_t snapshotRequestId) { RealtimeMidiSnapshotRequest msg{snapshotIndex, snapshotRequestId}; write(RingBufferCommand::RealtimeMidiSnapshotRequest, msg); } void OnNextMidiProgram(int64_t requestId, int32_t direction) { RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction}; write(RingBufferCommand::NextMidiProgram, msg); } void OnNextMidiBank(int64_t requestId, int32_t direction) { RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction}; write(RingBufferCommand::NextMidiBank, msg); } void SetControlValue(int effectIndex, int controlIndex, float value) { SetControlValueBody body; body.effectIndex = effectIndex; body.controlIndex = controlIndex; body.value = value; write(RingBufferCommand::SetValue, body); } void SetInputVolume(float value) { SetVolumeBody body; body.value = value; write(RingBufferCommand::SetInputVolume, body); } void SetOutputVolume(float value) { SetVolumeBody body; body.value = value; write(RingBufferCommand::SetOutputVolume, body); } void FreeVuSubscriptions(RealtimeVuBuffers *configuration) { write(RingBufferCommand::FreeVuSubscriptions, configuration); } void FreeMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions) { write(RingBufferCommand::FreeMonitorPortSubscription, subscriptions); } void SendMonitorPortUpdate( PortMonitorCallback *callback, int64_t subscriptionHandle, float value) { MonitorPortUpdate body{callback, subscriptionHandle, value}; write(RingBufferCommand::SendMonitorPortUpdate, body); } void SendVuUpdate(const std::vector *pUpdates) { write(RingBufferCommand::SendVuUpdate, pUpdates); } void AckVuUpdate() { bool value = true; write(RingBufferCommand::AckVuUpdate, value); } void AckMonitorPortUpdate(int64_t subscriptionHandle) { // we assume no padding between the command and the data, so we can do an atomic write. write(RingBufferCommand::AckMonitorPortUpdate, subscriptionHandle); } void SetVuSubscriptions(RealtimeVuBuffers *configuration) { write(RingBufferCommand::SetVuSubscriptions, configuration); } void LoadSnapshot(IndexedSnapshot *snapshot) { write(RingBufferCommand::LoadSnapshot, snapshot); } void AckMidiProgramRequest(int64_t requestId) { write(RingBufferCommand::AckMidiProgramChange, requestId); } void AckMidiSnapshotRequest(uint64_t snapshotRequestId) { write(RingBufferCommand::AckMidiSnapshotRequest, snapshotRequestId); } void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions) { write(RingBufferCommand::SetMonitorPortSubscription, subscriptions); } void SetBypass(int effectIndex, bool enabled) { SetBypassBody body; body.effectIndex = effectIndex; body.enabled = enabled; write(RingBufferCommand::SetBypass, body); } void ReplaceEffect(Lv2Pedalboard *pedalboard) { write(RingBufferCommand::ReplaceEffect, pedalboard); } void AudioTerminatedAbnormally() { AudioStoppedBody body; write(RingBufferCommand::AudioTerminatedAbnormally, body); } void EffectReplaced(Lv2Pedalboard *pedalboard) { write(RingBufferCommand::EffectReplaced, pedalboard); } void FreeSnapshot(IndexedSnapshot *snapshot) { write(RingBufferCommand::FreeSnapshot, snapshot); } void WriteLv2ErrorMessage(int64_t instanceId, const char *message) { size_t length = strlen(message); write(RingBufferCommand::Lv2ErrorMessage, instanceId, length, (uint8_t *)message); } void SendPathPropertyBuffer(PatchPropertyWriter::Buffer *buffer) { write(RingBufferCommand::SendPathPropertyBuffer, buffer); } }; typedef RingBufferReader RealtimeRingBufferReader; typedef RingBufferReader HostRingBufferReader; typedef RingBufferWriter HostRingBufferWriter; // cures a forward-declaration problem. class RealtimeRingBufferWriter : public RingBufferWriter { public: RealtimeRingBufferWriter() { } RealtimeRingBufferWriter(RingBuffer *ringBuffer) : RingBufferWriter(ringBuffer) { } }; } // namespace