diff --git a/.vscode/settings.json b/.vscode/settings.json index e151156..15f1448 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -91,7 +91,8 @@ "string_span": "cpp", "util": "cpp", "charconv": "cpp", - "valarray": "cpp" + "valarray": "cpp", + "hash_map": "cpp" }, "cSpell.words": [ "Guitarix", diff --git a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl index b8a023f..024200b 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl @@ -86,7 +86,7 @@ cabir:impulseFile3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ TooB Cab IR is a convolution-based guitar cabinet impulse response simulator. diff --git a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl index dedce05..83c505b 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl @@ -49,7 +49,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment "TooB amplifier cabinet simulator." ; mod:brand "TooB"; diff --git a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl index cd5ac26..cc3bdc6 100644 --- a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl +++ b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl @@ -59,7 +59,7 @@ inputStage:filterGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; mod:brand "TooB"; mod:label "Input Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl index 11c760c..91d0a2d 100644 --- a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl +++ b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl @@ -67,7 +67,7 @@ pstage:stage3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; mod:brand "TooB"; mod:label "Power Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl index b0ea77d..c1ca5b3 100644 --- a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment "TooB spectrum analyzer" ; mod:brand "TooB"; diff --git a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl index ea177f3..a8373f9 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl @@ -49,7 +49,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ The TooB Tone Stack emulates the tone controls of either a 59' Bassman or a JCM8000 guitar amplifier. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.14 b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.14 deleted file mode 100644 index 37c1de5..0000000 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.14 and /dev/null differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.15 b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.15 index 1a5e855..d42f7ed 100644 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.15 and b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.0.15 differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl index f6fb8ce..5712f46 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ Emulation of a Boss CE-2 Chorus. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl index dc244ca..eaf4eed 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ A straighforward no-frills digital delay. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl index c72fa63..f2c981d 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ Emulation of a Boss BF-2 Flanger*. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl index be9d6e4..1964a2b 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ Digital emulation of a Boss BF-2 Flanger*. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl index 6afa215..70d1206 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ ToobFreeverb is an Lv2 implementation of the famous Freeverb reverb effect. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl index 426261c..a06c218 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl @@ -47,7 +47,7 @@ toobml:filterGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented using artifical intelligence/machine learning modelling of real amplifiers. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl index cd8f957..a5420e4 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl @@ -37,7 +37,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 14 ; + lv2:microVersion 15 ; rdfs:comment """ A chromatic guitar tuner. """ ; diff --git a/lv2/aarch64/ToobAmp.lv2/impulseFiles/CabIR/AK_License.txt b/lv2/aarch64/ToobAmp.lv2/impulseFiles/CabIR/.Licenses.ToobAmp.lv2.txt similarity index 100% rename from lv2/aarch64/ToobAmp.lv2/impulseFiles/CabIR/AK_License.txt rename to lv2/aarch64/ToobAmp.lv2/impulseFiles/CabIR/.Licenses.ToobAmp.lv2.txt diff --git a/lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/Licenses.txt b/lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/.Licenses.ToobAmp.lv2.txt similarity index 100% rename from lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/Licenses.txt rename to lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/.Licenses.ToobAmp.lv2.txt diff --git a/lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/tmp b/lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/tmp deleted file mode 100644 index ad4e0fe..0000000 Binary files a/lv2/aarch64/ToobAmp.lv2/impulseFiles/reverb/tmp and /dev/null differ diff --git a/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-presets.ttl b/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-presets.ttl index 6908af6..9155214 100644 --- a/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-presets.ttl +++ b/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-presets.ttl @@ -25,7 +25,7 @@ tpset:toob-convolution-reverb-preset-0 pset:value 1.5 ; ] ; state:state [ - "ReverbImpulseFiles/St. Margaret\u0027s Church.wav" ; + ; ] . tpset:toob-convolution-reverb-preset-1 @@ -49,7 +49,7 @@ tpset:toob-convolution-reverb-preset-1 pset:value 1.5 ; ] ; state:state [ - "ReverbImpulseFiles/Jack Lyons Hall, University of York.wav" ; + ; ] . tpset:toob-convolution-reverb-preset-2 @@ -73,7 +73,7 @@ tpset:toob-convolution-reverb-preset-2 pset:value 1.5 ; ] ; state:state [ - "ReverbImpulseFiles/Arthur Sykes Rymer Auditorium.wav" ; + ; ] . tpset:toob-convolution-reverb-preset-3 @@ -97,6 +97,6 @@ tpset:toob-convolution-reverb-preset-3 pset:value 1.5 ; ] ; state:state [ - "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav" ; + ; ] . diff --git a/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-stereo-presets.ttl b/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-stereo-presets.ttl index f73fba6..050d4be 100644 --- a/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-stereo-presets.ttl +++ b/lv2/aarch64/ToobAmp.lv2/toob-convolution-reverb-stereo-presets.ttl @@ -28,7 +28,7 @@ tpset:toob-convolution-reverb-stereo-preset-0 pset:value 1.0 ; ] ; state:state [ - "ReverbImpulseFiles/St. Margaret\u0027s Church.wav" ; + ; ] . tpset:toob-convolution-reverb-stereo-preset-1 @@ -55,7 +55,7 @@ tpset:toob-convolution-reverb-stereo-preset-1 pset:value 1.0 ; ] ; state:state [ - "ReverbImpulseFiles/Jack Lyons Hall, University of York.wav" ; + ; ] . tpset:toob-convolution-reverb-stereo-preset-2 @@ -82,7 +82,7 @@ tpset:toob-convolution-reverb-stereo-preset-2 pset:value 1.0 ; ] ; state:state [ - "ReverbImpulseFiles/Arthur Sykes Rymer Auditorium.wav" ; + ; ] . tpset:toob-convolution-reverb-stereo-preset-3 @@ -95,7 +95,7 @@ tpset:toob-convolution-reverb-stereo-preset-3 ] , [ lv2:symbol "loading_state" ; pset:value 0.0 ; - ] , [ + lv2:symbol "predelay" ; pset:value 0.0 ; ] , [ @@ -109,6 +109,6 @@ tpset:toob-convolution-reverb-stereo-preset-3 pset:value 1.0 ; ] ; state:state [ - "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav" ; + ; ] . diff --git a/react/src/FilePropertyControl.tsx b/react/src/FilePropertyControl.tsx index b09cf66..eb2acc6 100644 --- a/react/src/FilePropertyControl.tsx +++ b/react/src/FilePropertyControl.tsx @@ -29,7 +29,7 @@ import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { UiFileProperty } from './Lv2Plugin'; import Typography from '@mui/material/Typography'; -import { PiPedalModel, PiPedalModelFactory, ListenHandle, StateChangedHandle } from './PiPedalModel'; +import { PiPedalModel, PiPedalModelFactory, ListenHandle} from './PiPedalModel'; import ButtonBase from '@mui/material/ButtonBase' import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import {PedalboardItem} from './Pedalboard'; @@ -98,63 +98,42 @@ const FilePropertyControl = value: "", }; this.model = PiPedalModelFactory.getInstance(); - if (this.props.fileProperty.patchProperty !== "") { - this.subscribeToPropertyGet(); - } } private propertyGetHandle?: ListenHandle; - refreshPatchProperty() - { - this.model.getPatchProperty(this.props.pedalboardItem.instanceId, this.props.fileProperty.patchProperty) - .then( - (json: any) => { - if (json && json.otype_ === "Path") { - let path = json.value as string; - this.setState({ value: path, hasValue: true }); - } - } - ) - .catch( - (error: Error) => { - this.model.showAlert(error.toString()); - } - ); + monitorPropertyHandle?: ListenHandle; - } - - stateChangedHandle?: StateChangedHandle; - - subscribeToPropertyGet() { + subscribeToPatchProperty() { // this.propertyGetHandle = this.model.listenForAtomOutput(this.props.instanceId,(instanceId,atomOutput)=>{ // // PARSE THE OBJECT! // // this.setState({value: atomOutput?.value as string}); // }); - if (this.stateChangedHandle) - { - this.model.removeLv2StateChangedListener(this.stateChangedHandle); - } - this.refreshPatchProperty(); - this.stateChangedHandle = this.model.addLv2StateChangedListener( - this.props.pedalboardItem.instanceId, - () => { - this.refreshPatchProperty(); - }); + this.unsubscribeToPatchProperty(); + this.monitorPropertyHandle = this.model.monitorPatchProperty(this.props.pedalboardItem.instanceId, + this.props.fileProperty.patchProperty, + (instanceId,property,propertyValue) => { + if (propertyValue.otype_ === "Path") + { + this.setState({value: propertyValue.value as string,hasValue: true}); + } + } + ); } - unsubscribeToPropertyGet() { - if (this.stateChangedHandle) + unsubscribeToPatchProperty() { + if (this.monitorPropertyHandle !== undefined) { - this.model.removeLv2StateChangedListener(this.stateChangedHandle); + this.model.cancelMonitorPatchProperty(this.monitorPropertyHandle); + this.monitorPropertyHandle = undefined; } } private mounted: boolean = false; componentDidMount() { this.mounted = true; - this.subscribeToPropertyGet(); + this.subscribeToPatchProperty(); } componentWillUnmount() { - this.unsubscribeToPropertyGet(); + this.unsubscribeToPatchProperty(); this.mounted = false; } componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { @@ -163,8 +142,8 @@ const FilePropertyControl = || prevProps.pedalboardItem.stateUpdateCount !== this.props.pedalboardItem.stateUpdateCount ) { - this.unsubscribeToPropertyGet(); - this.subscribeToPropertyGet(); + this.unsubscribeToPatchProperty(); + this.subscribeToPatchProperty(); } } diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index cf378fb..a4e0c79 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -554,7 +554,7 @@ export class PiPedalModel //implements PiPedalModel let atomJson = body.atomJson as any; this.handleNotifyPatchProperty(clientHandle, instanceId, propertyUri, atomJson); if (header.replyTo) { - this.webSocket?.reply(header.replyTo, "onNotifyAtomOut", true); + this.webSocket?.reply(header.replyTo, "onNotifyPatchProperty", true); } } else if (message === "onJackServerSettingsChanged") { let jackServerSettings = new JackServerSettings().deserialize(body); diff --git a/src/AtomConverter.hpp b/src/AtomConverter.hpp index 9ac7a64..205267f 100644 --- a/src/AtomConverter.hpp +++ b/src/AtomConverter.hpp @@ -30,6 +30,7 @@ #include "lv2/atom/atom.h" #include "lv2/atom/forge.h" #include "json_variant.hpp" +#include "atom_object.hpp" namespace pipedal { diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 4979455..57317fa 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -308,7 +308,7 @@ private: } - std::string AtomToJson(const LV2_Atom *pAtom) + virtual std::string AtomToJson(const LV2_Atom *pAtom) override { std::lock_guard lock(atomConverterMutex); json_variant vAtom = atomConverter.ToJson(pAtom); @@ -365,7 +365,7 @@ private: if (realtimeMonitorPortSubscriptions != nullptr) { delete realtimeMonitorPortSubscriptions; - realtimeVuBuffers = nullptr; + realtimeMonitorPortSubscriptions = nullptr; } this->inputRingBuffer.reset(); this->outputRingBuffer.reset(); @@ -1086,6 +1086,12 @@ public: 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; @@ -1099,7 +1105,7 @@ public: hostReader.read(extraBytes, &(atomBuffer[0])); IEffect *pEffect = currentPedalboard->GetEffect(instanceId); - if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput) + if (pEffect != nullptr && this->pNotifyCallbacks) { LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0]; if (atom->type == uris.atom_Object) @@ -1107,6 +1113,7 @@ public: 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; @@ -1119,11 +1126,8 @@ public: if (property != nullptr && value != nullptr && property->type == uris.atom_URID) { LV2_URID propertyUrid = ((LV2_Atom_URID*)property)->body; - if (pNotifyCallbacks->WantsAtomOutput(instanceId,propertyUrid)) - { - auto json = AtomToJson(value); - this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,propertyUrid,json); - } + this->pNotifyCallbacks->OnPatchSetReply(instanceId,propertyUrid,value); + this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); } } } @@ -1496,8 +1500,8 @@ public: private: - virtual void UpdatePluginStates(Pedalboard& pedalboard); - void UpdatePluginState(PedalboardItem &pedalboardItem); + virtual bool UpdatePluginStates(Pedalboard& pedalboard) override; + virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override; class RestartThread { @@ -1696,30 +1700,47 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost) return new AudioHostImpl(pHost); } -void AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard) +bool AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard) { + bool changed = false; auto pedalboardItems = pedalboard.GetAllPlugins(); for (PedalboardItem* item : pedalboardItems) { if (!item->isSplit()) { - UpdatePluginState(*item); + if (UpdatePluginState(*item)) + { + changed = true; + } } } + return true; } -void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) +bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) { IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId()); if (effect != nullptr) { try { - effect->GetLv2State(const_cast(&pedalboardItem.lv2State())); + 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; } diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index ad86f7c..0c189c2 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -145,12 +145,17 @@ public: class IAudioHostCallbacks { public: virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0; + virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0; virtual void OnNotifyVusSubscription(const std::vector & updates) = 0; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0; virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0; - virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0; - virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0; + + + virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) = 0; + + //virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0; + //virtual void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0; virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0; virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0; @@ -195,9 +200,10 @@ public: virtual void SetListenForMidiEvent(bool listen) = 0; virtual void SetListenForAtomOutput(bool listen) = 0; - virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0; - virtual void UpdatePluginState(PedalboardItem& pedalboardItem) = 0; + virtual bool UpdatePluginStates(Pedalboard& pedalboard) = 0; + virtual bool UpdatePluginState(PedalboardItem& pedalboardItem) = 0; + virtual std::string AtomToJson(const LV2_Atom*atom) = 0; virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0; virtual void Close() = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7511b06..986eac4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,9 @@ else() endif() set (PIPEDAL_SOURCES + atom_object.hpp atom_object.cpp + FileBrowserFiles.h + FileBrowserFilesFeature.hpp FileBrowserFilesFeature.cpp MimeTypes.cpp MimeTypes.hpp inverting_mutex.hpp DbDezipper.hpp DbDezipper.cpp diff --git a/src/FileBrowserFiles.h b/src/FileBrowserFiles.h new file mode 100644 index 0000000..a474f1b --- /dev/null +++ b/src/FileBrowserFiles.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023 Robin E. R. Davies + * All rights reserved. + + * 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. + */ + +#ifndef FILEBROWSERFILES_H +#define FILEBROWSERFILES_H + +#define LV2_FILEBROWSER_URI "http://two-play.com/ns/ext/fileBrowser" +#define LV2_FILEBROWSER_PREFIX LV2_FILEBROWSER_URI "#" +#define LV2_FILEBROWSER__files LV2_FILEBROWSER_PREFIX "files" ///< http://two-play.com/ns/ext/fileBrowser#files > + +/** + @defgroup filedialog LV2_FileBrowser_Files + @ingroup lv2 + + A feature for LV2 plugins to provide sample files (or other selectable files) within the plugin's resource bundle to host file selection dialogs. + + Browser directories may contain actual files that typically have been uploaded by users; but may also contain symlinks to files + in other locations. This feature allows plugins to publish files within the plugin's resource bundle to browser directories. For + each published file consists of a symlink in the browser directory that links to a corresponding file in the plugins resource bundle. + + The LV2_FileBrowser_Files.publish_resource_files() method allows plugins to publish entire directories. publish_resource_files() + implements a versioning system, which tracks whether sample files have been deleted by the user. If the symlink for sample files that were installed + by a previous version of the plugin have been deleted, the sample files from the current version are not reinstalled. See LV2_FileBrowser_Files.publish_resource_files. + not re-installed. + + See: for more details. + + @{ +*/ + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LV2_FileBrowser_Status_Success = 0, + LV2_FileBrowser_Status_Err_Filesystem = 1, + LV2_FileBrowser_Status_Err_InvalidParameter = 1, +} LV2_FileBrowser_Status; + +typedef void* + LV2_FileBrowser_Files_Handle; ///< Opaque handle for pipedal:uploadPath feature + + +/** + Feature data for filedialog:files (@ref LV2_FILEBROWSER_file). +*/ +typedef struct { + /** + Opaque host data. + */ + LV2_FileBrowser_Files_Handle handle; + + LV2_FileBrowser_Status (*publish_resource_files)(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory); + + /** + Return a path in which upload files will be stored for this plugin. + @param handle MUST be the `handle` member of this struct. + @param path The sub-directory name in which uploaded files will be stored. + @return The absolute path to use for the new directory. + + This function can be used by plugins to get the path of files or directories + that can be browsed by file dialogs. get_filebrowser_path, unlike map_path() does + not create or modify files in any way. + + The caller must free memory for the returned value with LV2_FileBrowser_Files.free_path(). + + Example: + [code] + TBD + [/code] + */ + char* (*get_upload_path)(LV2_FileBrowser_Files_Handle handle, const char* path); + + /** + Map resource bundle filenames to upload directories. + @param handle MUST be the `handle` member of this struct. + @param path File path name + @param fileBrowserDirectory relative directory of the file browser directory in which to create a link. + @return The absolute pathname of a file in the resource directory. + + The primary purpose of this method is to map file references to files in the plugin's bundle + directory to corresponding files in the a file browser directory. + + If the path is relative, the full path is resolved using the `rootResourceDirectory` path. + + If the full path is not a child of rootResourceDirectory or if the path is relative, + get_upload_path returns the supplied path unmodified. +` + If the filename is a child of rootResourceDirectory, creates a corresponding link to the file in the file browser + directory and returns the name of that link. + + The caller must free memory for the returned value with `LV2_FileBrowser_Files.free_path()`. + + Example: + */ + char* (*map_path)(LV2_FileBrowser_Files_Handle handle, const char* path, const char*rootResourceDirectory, const char*fileBrowserDirectory); + + + /** + Free a path returned by LV2_FileBrowser_Files.get_upload_path() or LV2_FileBrowser_Files.map_path() + @param handle MUST be the `handle` member of this struct. + @param path A path previously returned by a method of `LV2_FileBrowser_Files`. + + */ + void (*free_path)(LV2_FileBrowser_Files_Handle handle, char* path); +} LV2_FileBrowser_Files; + + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // FILEBROWSERFILES_H diff --git a/src/FileBrowserFilesFeature.cpp b/src/FileBrowserFilesFeature.cpp new file mode 100644 index 0000000..45e3991 --- /dev/null +++ b/src/FileBrowserFilesFeature.cpp @@ -0,0 +1,336 @@ +/* + * MIT License + * + * Copyright (c) 2023 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 "FileBrowserFilesFeature.hpp" +#include +#include +#include +#include +#include +#include +#include "util.hpp" + +using namespace pipedal; +using namespace pipedal::implementation; + + +namespace pipedal::implementation { + class BrowserFilesVersionInfo + { + public: + bool Load(std::filesystem::path &path); + void Save(std::filesystem::path &path); + const std::vector &InstalledFiles() const; + bool IsInstalled(const std::string &file) const; + void AddFile(const std::string &file); + uint32_t Version() const; + void Version(uint32_t value); + + private: + uint32_t version = 0; + std::vector installedFiles; + std::unordered_set installedFileMap; + }; +}; + +inline uint32_t BrowserFilesVersionInfo::Version() const { return version; } +inline void BrowserFilesVersionInfo::Version(uint32_t value) { version = value; } + +inline const std::vector &BrowserFilesVersionInfo::InstalledFiles() const { + return installedFiles; +} +inline bool BrowserFilesVersionInfo::IsInstalled(const std::string &file) const +{ + return installedFileMap.contains(file); +} +void BrowserFilesVersionInfo::AddFile(const std::string &file) +{ + if (!IsInstalled(file)) + { + installedFileMap.insert(file); + installedFiles.push_back(file); + } +} + +bool BrowserFilesVersionInfo::Load(std::filesystem::path &path) +{ + std::ifstream f{path}; + if (!f.is_open()) + return false; + f >> version; + std::string str; + std::getline(f, str); + if (f.fail()) + return false; + + while (!f.eof()) + { + std::getline(f, str); + if (f.fail() || str.length() == 0) + break; + AddFile(str); + } + return true; +} + +FileBrowserFilesFeature::FileBrowserFilesFeature() +{ + lv2_log_logger_init(&lv2Logger,nullptr,nullptr); + feature.URI = LV2_FILEBROWSER__files; + feature.data = &(this->featureData); +} +void BrowserFilesVersionInfo::Save(std::filesystem::path &path) +{ + std::ofstream f{path}; + if (!f.is_open()) + return; + f << version << std::endl; + + for (const std::string &file : installedFiles) + { + f << file << std::endl; + } +} + +void FileBrowserFilesFeature::LogError(const char*message) +{ + lv2_log_error(&lv2Logger,"FileBrowserFiles: %s\n",message); +} + + +void FileBrowserFilesFeature::Initialize( + const LV2_URID_Map *lv2Map, + const LV2_Log_Log *lv2Log, + const std::string &bundleDirectory, + const std::string &browserDirectory) +{ + lv2_log_logger_init(&this->lv2Logger,const_cast(lv2Map),const_cast(lv2Log)); + + this->bundleDirectory = bundleDirectory; + this->browserDirectory = browserDirectory; + + + featureData.handle = (void *)this; + featureData.free_path = &FN_free_path; + featureData.get_upload_path = &FN_get_upload_path; + featureData.map_path = &FN_map_path; + featureData.publish_resource_files = &FN_publish_resource_files; +} + +char *FileBrowserFilesFeature::FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char *fileBrowserPath) +{ + return ((FileBrowserFilesFeature *)handle)->GetUploadPath(fileBrowserPath); +} +char *FileBrowserFilesFeature::FN_map_path(LV2_FileBrowser_Files_Handle handle, const char*path, const char*resourcePathBase,const char *fileBrowserPath) +{ + return ((FileBrowserFilesFeature *)handle)->MapPath(path,resourcePathBase,fileBrowserPath); +} +void FileBrowserFilesFeature::FN_free_path(LV2_FileBrowser_Files_Handle handle, char *path) +{ + ((FileBrowserFilesFeature *)handle)->FreePath(path); +} +LV2_FileBrowser_Status FileBrowserFilesFeature::FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle, uint32_t version, const char *resourcePath, const char *uploadDirectory) +{ + return ((FileBrowserFilesFeature *)handle)->PublishResourceFiles(version, resourcePath, uploadDirectory); +} + +char *FileBrowserFilesFeature::GetUploadPath(const char *fileBrowserPath) +{ + std::filesystem::path result = this->browserDirectory / fileBrowserPath; + return strdup(result.c_str()); +} +void FileBrowserFilesFeature::FreePath(char *path) +{ + free(path); +} + +static std::string RelativePath(const std::filesystem::path&root, const std::filesystem::path&path) +{ + std::string strRoot = root.string(); + std::string strPath = path.string(); + if (!strPath.starts_with(strRoot)) + { + throw std::logic_error("Path is not relative to root."); + } + size_t pos = strRoot.length(); + + // if there's no separator, we need to skip the separator in the path. + if (pos != 0) { + if (pos < strPath.length()+1) + { + char c = strPath[pos]; + if (c == std::filesystem::path::preferred_separator) + { + ++pos; + } + } + } + return strPath.substr(pos); +} +static std::string GetPluginTag(const std::filesystem::path bundlePath) +{ + std::string directoryName = bundlePath.filename(); + if (directoryName.length() != 0) + { + return directoryName; + } + return bundlePath.parent_path().filename(); +} + + +char *FileBrowserFilesFeature::MapPath(const char *path,const char*resourcePathBase,const char*fileBrowserPath) +{ + try { + if (strncmp(path,this->bundleDirectory.c_str(),strlen(this->bundleDirectory.c_str()) != 0)) + { + return strdup(path); + } + std::filesystem::path resourceRoot { resourcePathBase}; + + if (resourceRoot.is_relative()) + { + resourceRoot = this->bundleDirectory / resourceRoot; + } + if (strncmp(path,resourceRoot.c_str(),strlen(resourceRoot.c_str())) != 0 ) { + return strdup(path); + } + auto relativePath = RelativePath(resourceRoot,path); + + std::filesystem::path targetDirectory; + if (fileBrowserPath == nullptr || fileBrowserPath[0] == '\0') + { + targetDirectory = GetPluginTag(this->bundleDirectory); + } else { + targetDirectory = fileBrowserPath; + } + targetDirectory = this->browserDirectory / targetDirectory; + std::filesystem::create_directories(targetDirectory); + + std::filesystem::path targetPath = targetDirectory / relativePath; + + if (!std::filesystem::exists(targetPath)) + { + std::filesystem::create_symlink(path,targetPath); + } + return strdup(targetPath.c_str()); + + } catch (const std::exception&e) + { + LogError(e.what()); + return ""; + } + +} + + + + +// throws on error. +void FileBrowserFilesFeature::PublishRecursive( + BrowserFilesVersionInfo &versionInfo, + const std::filesystem::path &rootResourcePath, + const std::filesystem::path &resourcePath, + const std::filesystem::path &browserPath) +{ + for (const std::filesystem::directory_entry directoryEntry : std::filesystem::directory_iterator{resourcePath}) + { + std::string fileName = directoryEntry.path().filename(); + if (directoryEntry.is_directory()) + { + std::filesystem::path targetDirectory = browserPath / fileName; + std::filesystem::create_directories(targetDirectory); + PublishRecursive( + versionInfo, + rootResourcePath, + directoryEntry.path(), + targetDirectory); + } + else + { + std::filesystem::path sourceFile = + resourcePath / fileName; + std::filesystem::path targetFile = + browserPath / fileName; + std::string relativePath = RelativePath(rootResourcePath, sourceFile); + if (!std::filesystem::exists(targetFile)) // only create link if there isn't one. + { + + // If the file has been previously installed but no longer exists, the file + // has been deleted by a user, so don't reinstall it. + if (!versionInfo.IsInstalled(relativePath)) + { + std::filesystem::create_directories(browserPath); + std::filesystem::create_symlink( + sourceFile, targetFile); + } + } + versionInfo.AddFile(relativePath); + } + } +} + +LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t version, const char *resourcePath, const char *uploadDirectory) +{ + BrowserFilesVersionInfo versionInfo; + + static std::string VERSION_FILENAME_PREFIX = ".versionInfo."; + + // generate a filename that will allow multiple plugins to install files into the same directory. + std::filesystem::path versionFileName = this->browserDirectory / uploadDirectory / + (VERSION_FILENAME_PREFIX + GetPluginTag(this->bundleDirectory)); + + bool loaded = versionInfo.Load(versionFileName); + if (loaded && versionInfo.Version() >= version) + { + return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Success; + } + try { + versionInfo.Version(version); + + std::filesystem::path sourcePath = resourcePath; + if (sourcePath.is_relative()) + { + sourcePath = this->bundleDirectory / resourcePath; + } + std::filesystem::path targetDirectory; + if (uploadDirectory == nullptr || uploadDirectory[0] == '\0') + { + targetDirectory = GetPluginTag(this->bundleDirectory); + } else { + targetDirectory = uploadDirectory; + } + if (!targetDirectory.is_relative()) + { + LogError("uploadDirectory must be relative."); + return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Err_InvalidParameter; + } + targetDirectory = this->browserDirectory / targetDirectory; + PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserDirectory / uploadDirectory); + versionInfo.Save(versionFileName); + } catch (const std::exception & e) + { + LogError(e.what()); + return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Err_Filesystem; + } + return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Success; +} diff --git a/src/FileBrowserFilesFeature.hpp b/src/FileBrowserFilesFeature.hpp new file mode 100644 index 0000000..94eb547 --- /dev/null +++ b/src/FileBrowserFilesFeature.hpp @@ -0,0 +1,77 @@ +/* + * MIT License + * + * Copyright (c) 2023 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 "FileBrowserFiles.h" +#include +#include +#include + + + +namespace pipedal { + // Private forward declaration + namespace implementation { + class BrowserFilesVersionInfo; + }; + + class FileBrowserFilesFeature { + public: + FileBrowserFilesFeature(); + void Initialize( + const LV2_URID_Map *lv2Map, + const LV2_Log_Log *lv2Log, + const std::string&bundleDirectory, + const std::string&browserDirectory); + const LV2_Feature*GetFeature() { return &feature; } + private: + + LV2_Feature feature; + const LV2_URID_Map *lv2Map = nullptr; + LV2_Log_Logger lv2Logger = {nullptr,0,0,0,0}; + + std::filesystem::path bundleDirectory; + std::filesystem::path browserDirectory; + LV2_FileBrowser_Files featureData; + + static char* FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char* fileBrowserDirectory); + static char* FN_map_path(LV2_FileBrowser_Files_Handle handle, const char* path, const char *resourcePathBase,const char*fileBrowserDirectory); + static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char* path); + static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory); + + void LogError(const char*message); + char* GetUploadPath(const char* fileBrowserDirectory); + char* MapPath(const char* path,const char*resourcePathBase,const char* fileBrowserDirectory); + void FreePath(char* path); + LV2_FileBrowser_Status PublishResourceFiles(uint32_t version,const char*resourcePath, const char*uploadDirectory); + + void PublishRecursive( + implementation::BrowserFilesVersionInfo& versionInfo, + const std::filesystem::path& rootResourcePath, + const std::filesystem::path& resourcePath, + const std::filesystem::path& browserPath + ); + }; +} \ No newline at end of file diff --git a/src/HtmlHelper.cpp b/src/HtmlHelper.cpp index 34dab8b..89057ef 100644 --- a/src/HtmlHelper.cpp +++ b/src/HtmlHelper.cpp @@ -52,10 +52,10 @@ std::string HtmlHelper::timeToHttpDate(time_t time) } char szBuffer[sizeof("Mon, 01 Jan 1970 00:00:00 GMT+")]; snprintf(szBuffer,sizeof(szBuffer), FORMAT, - INVARIANT_MONTHS[gmTm.tm_wday], + INVARIANT_WEEK_DAYS[gmTm.tm_wday], gmTm.tm_mday, INVARIANT_MONTHS[gmTm.tm_mon], - gmTm.tm_year, + gmTm.tm_year+1900, gmTm.tm_hour, gmTm.tm_min, gmTm.tm_sec); diff --git a/src/IEffect.hpp b/src/IEffect.hpp index c711109..ae4ae12 100644 --- a/src/IEffect.hpp +++ b/src/IEffect.hpp @@ -44,6 +44,9 @@ namespace pipedal { virtual float *GetAudioOutputBuffer(int index) const = 0; virtual void ResetAtomBuffers() = 0; + virtual bool GetRequestStateChangedNotification() const = 0; + virtual void SetRequestStateChangedNotification(bool value) = 0; + //virtual std::string AtomToJson(uint8_t*pAtom) = 0; //virtual std::string GetAtomObjectType(uint8_t*pData) = 0; diff --git a/src/IHost.hpp b/src/IHost.hpp index 0a8444d..0cc7eae 100644 --- a/src/IHost.hpp +++ b/src/IHost.hpp @@ -50,5 +50,7 @@ namespace pipedal { virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0; + virtual std::string GetPluginStoragePath() const = 0; + }; } \ No newline at end of file diff --git a/src/LogFeature.cpp b/src/LogFeature.cpp index aaafd3f..98dbf20 100644 --- a/src/LogFeature.cpp +++ b/src/LogFeature.cpp @@ -61,6 +61,13 @@ int LogFeature::vprintf(LV2_URID type,const char*fmt, va_list va) result = vsnprintf(p, sizeof(buffer)-messagePrefix.length(), fmt, va); buffer[sizeof(buffer)-1] = '\0'; + + // strip trailing \n + size_t len = strlen(buffer); + if (len != 0 && buffer[len-1] == '\n') + { + buffer[len-1] = '\0'; + } if (type == uris.ridError) { diff --git a/src/LogFeature.hpp b/src/LogFeature.hpp index 600d06b..45ec63a 100644 --- a/src/LogFeature.hpp +++ b/src/LogFeature.hpp @@ -43,6 +43,7 @@ namespace pipedal { virtual void OnLogInfo(const char*message) = 0; virtual void OnLogDebug(const char*message) = 0; }; + const LV2_Log_Log*GetLog() const { return &log;} private: LogMessageListener *logMessageListener = nullptr; std::string messagePrefix; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index 52472ee..6f096d5 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -24,7 +24,7 @@ #include #include "lv2/atom/atom.h" #include "lv2/atom/util.h" -//#include "lv2.h" +// #include "lv2.h" #include "lv2/log/log.h" #include "lv2/log/logger.h" #include "lv2/midi/midi.h" @@ -54,7 +54,8 @@ Lv2Effect::Lv2Effect( { auto pWorld = pHost_->getWorld(); - logFeature.Prepare(&(pHost_->GetMapFeature()),info_->name() + ": ",this); + logFeature.Prepare(&pHost_->GetMapFeature(), info_->name() + ": ", this); + this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S); this->bypass = pedalboardItem.isEnabled(); @@ -69,6 +70,19 @@ Lv2Effect::Lv2Effect( auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str()); const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode); lilv_node_free(uriNode); + { + AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin); + char *bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr); + + std::string storagePath = pHost_->GetPluginStoragePath(); + + fileBrowserFilesFeature.Initialize( + pHost_->GetMapFeature().GetMap(), + logFeature.GetLog(), + bundleUriString, + storagePath); + lilv_free(bundleUriString); + } LV2_Feature *const *features = pHost_->GetLv2Features(); @@ -79,10 +93,11 @@ Lv2Effect::Lv2Effect( this->features.push_back(*p); } + this->features.push_back(this->fileBrowserFilesFeature.GetFeature()); + this->work_schedule_feature = nullptr; - if (true) //info_->hasExtension(LV2_WORKER__interface)) + if (true) // info_->hasExtension(LV2_WORKER__interface)) { - // insane implementation. :-( LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule)); schedule->handle = this; schedule->schedule_work = worker_schedule_fn; @@ -98,13 +113,14 @@ Lv2Effect::Lv2Effect( const LV2_Feature **myFeatures = &this->features[0]; LilvInstance *pInstance = nullptr; - try { + try + { pInstance = lilv_plugin_instantiate(pPlugin, pHost->GetSampleRate(), myFeatures); - } catch (const std::exception &e) + } + catch (const std::exception &e) { this->pInstance = nullptr; throw PiPedalException(SS("Plugin threw an exception: " << e.what() << " '" << info_->name() << "'")); - } this->pInstance = pInstance; if (this->pInstance == nullptr) @@ -114,17 +130,18 @@ Lv2Effect::Lv2Effect( const LV2_Worker_Interface *worker_interface = (const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance, - LV2_WORKER__interface); - if (worker_interface) { + LV2_WORKER__interface); + if (worker_interface) + { this->worker = std::make_unique(pHost->GetHostWorkerThread(), pInstance, worker_interface); } - const LV2_State_Interface*state_interface = + const LV2_State_Interface *state_interface = (const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance, - LV2_STATE__interface); + LV2_STATE__interface); if (state_interface) { - this->stateInterface = std::make_unique(pHost,pInstance,state_interface); + this->stateInterface = std::make_unique(pHost, &(this->features[0]), pInstance, state_interface); } this->instanceId = pedalboardItem.instanceId(); @@ -146,9 +163,9 @@ Lv2Effect::Lv2Effect( if (!pedalboardItem.lilvPresetUri().empty()) { - AutoLilvNode presetNode = lilv_new_uri(pWorld,pedalboardItem.lilvPresetUri().c_str()); - lilv_world_load_resource(pWorld,presetNode); - LilvState*pState = lilv_state_new_from_world(pWorld,pHost->GetMapFeature().GetMap(),presetNode); + AutoLilvNode presetNode = lilv_new_uri(pWorld, pedalboardItem.lilvPresetUri().c_str()); + lilv_world_load_resource(pWorld, presetNode); + LilvState *pState = lilv_state_new_from_world(pWorld, pHost->GetMapFeature().GetMap(), presetNode); if (pState) { if (this->stateInterface) @@ -161,21 +178,29 @@ Lv2Effect::Lv2Effect( // Why? because lilv doesn't provide facilities for reading state. pedalboardItem.lv2State(this->stateInterface->Save()); pedalboardItem.lilvPresetUri(""); - } else { + } + else + { RestoreState(pedalboardItem); } } -void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem) +void Lv2Effect::RestoreState(PedalboardItem &pedalboardItem) { // Restore state if present. if (this->stateInterface) { - try { + try + { if (pedalboardItem.lv2State().isValid_) { this->stateInterface->Restore(pedalboardItem.lv2State()); + } else { + // set the state to default state. + auto savedState = this->stateInterface->Save(); + pedalboardItem.lv2State(savedState); } - } catch (const std::exception &e) + } + catch (const std::exception &e) { std::string name = pedalboardItem.pluginName(); Lv2Log::warning(SS(name << ": " << e.what())); @@ -331,11 +356,8 @@ void Lv2Effect::Activate() this->AssignUnconnectedPorts(); lilv_instance_activate(pInstance); this->BypassTo(this->bypass ? 1.0f : 0.0f); - - } - void Lv2Effect::AssignUnconnectedPorts() { for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i) @@ -400,7 +422,7 @@ static inline void CopyBuffer(float *input, float *output, uint32_t frames) } } -void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBufferWriter) +void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter) { // close off the atom input frame. if (this->inputAtomBuffers.size() != 0) @@ -408,12 +430,14 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe lv2_atom_forge_pop(&this->inputForgeRt, &input_frame); } + lilv_instance_run(pInstance, samples); + if (worker) { // relay worker response worker->EmitResponses(); } - lilv_instance_run(pInstance, samples); + // do soft bypass. if (this->bypassSamplesRemaining == 0) @@ -431,7 +455,9 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe { CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples); CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[1], samples); - } else { + } + else + { CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples); CopyBuffer(this->inputAudioBuffers[1], this->outputAudioBuffers[1], samples); } @@ -499,9 +525,8 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe this->currentBypassDx = currentBypassDx; this->bypassSamplesRemaining = bypassSamplesRemaining; } - } - RelayPatchSetMessages(this->instanceId,realtimeRingBufferWriter); + RelayPatchSetMessages(this->instanceId, realtimeRingBufferWriter); } LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle, @@ -585,7 +610,7 @@ void Lv2Effect::RequestPatchProperty(LV2_URID uridUri) lv2_atom_forge_urid(&inputForgeRt, uridUri); lv2_atom_forge_pop(&inputForgeRt, &objectFrame); } -void Lv2Effect::SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value) +void Lv2Effect::SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value) { lv2_atom_forge_frame_time(&inputForgeRt, 0); @@ -596,22 +621,22 @@ void Lv2Effect::SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value) lv2_atom_forge_key(&inputForgeRt, urids.patch__property); lv2_atom_forge_urid(&inputForgeRt, uridUri); lv2_atom_forge_key(&inputForgeRt, urids.patch__value); - lv2_atom_forge_write(&inputForgeRt,value,size); + lv2_atom_forge_write(&inputForgeRt, value, size); } lv2_atom_forge_pop(&inputForgeRt, &objectFrame); + this->requestStateChangedNotification = true; } - -void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) +void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId, RealtimeRingBufferWriter *realtimeRingBufferWriter) { - LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); + LV2_Atom_Sequence *controlOutput = (LV2_Atom_Sequence *)GetAtomOutputBuffer(); if (controlOutput == nullptr) { return; } - bool stateChanged = false; + bool maybeStateChanged = false; LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev) { @@ -622,16 +647,22 @@ void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWrit const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; if (obj->body.otype == urids.state__StateChanged) { - stateChanged = true; - } else if (obj->body.otype == urids.patch__Set) // patch_Set is handled elsewhere. + requestStateChangedNotification = true; + } + else if (obj->body.otype == urids.patch__Set) // patch_Set is handled elsewhere. { - realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj); + maybeStateChanged = true; + realtimeRingBufferWriter->AtomOutput(instanceId, obj->atom.size + sizeof(obj->atom), (uint8_t *)obj); } } - if (stateChanged) - { - realtimeRingBufferWriter->Lv2StateChanged(instanceId); - } + } + if (this->requestStateChangedNotification) + { + requestStateChangedNotification = false; + realtimeRingBufferWriter->Lv2StateChanged(instanceId); + } else if (maybeStateChanged) + { + realtimeRingBufferWriter->MaybeLv2StateChanged(instanceId); } } @@ -639,15 +670,15 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) { if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { - LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); - if (controlInput == nullptr) + LV2_Atom_Sequence *controlInput = (LV2_Atom_Sequence *)GetAtomOutputBuffer(); + if (controlInput == nullptr) { return; } LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev) { - auto frame_offset = ev->time.frames; // not really interested. + auto frame_offset = ev->time.frames; // not really interested. if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) { @@ -671,7 +702,7 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) { int atom_size = value->size + sizeof(LV2_Atom); pRequest->SetSize(atom_size); - memcpy(pRequest->GetBuffer(),value,atom_size); + memcpy(pRequest->GetBuffer(), value, atom_size); break; } } @@ -680,50 +711,49 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) } } } -bool Lv2Effect::GetLv2State(Lv2PluginState*state) +bool Lv2Effect::GetLv2State(Lv2PluginState *state) { - if (!this->stateInterface) return false; - try { + if (!this->stateInterface) + return false; + try + { if (this->stateInterface == nullptr) { state->Erase(); return false; } - + *state = this->stateInterface->Save(); state->isValid_ = true; return true; } - catch (const std::exception&e) + catch (const std::exception &e) { state->Erase(); throw; } } - -void Lv2Effect::OnLogError(const char*message) +void Lv2Effect::OnLogError(const char *message) { // only errors get transmitted to the client. - strncpy(this->errorMessage,message,sizeof(errorMessage)); - errorMessage[sizeof(errorMessage)-1] = '\0'; + strncpy(this->errorMessage, message, sizeof(errorMessage)); + errorMessage[sizeof(errorMessage) - 1] = '\0'; this->hasErrorMessage = true; } -void Lv2Effect::OnLogWarning(const char*message) +void Lv2Effect::OnLogWarning(const char *message) { Lv2Log::warning(message); - } -void Lv2Effect::OnLogInfo(const char*message) +void Lv2Effect::OnLogInfo(const char *message) { Lv2Log::info(message); - } -void Lv2Effect::OnLogDebug(const char*message) +void Lv2Effect::OnLogDebug(const char *message) { Lv2Log::debug(message); - } - +bool Lv2Effect::GetRequestStateChangedNotification() const{ return requestStateChangedNotification; } +void Lv2Effect::SetRequestStateChangedNotification(bool value) { requestStateChangedNotification = value; } diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index 3c69c0c..c4bb19c 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -24,6 +24,7 @@ #include "Pedalboard.hpp" #include #include "BufferPool.hpp" +#include "FileBrowserFilesFeature.hpp" #include "IEffect.hpp" #include "Worker.hpp" @@ -51,9 +52,9 @@ namespace pipedal virtual void OnLogDebug(const char*message); private: - + FileBrowserFilesFeature fileBrowserFilesFeature; std::unique_ptr stateInterface; - void RestoreState(const PedalboardItem&pedalboardItem); + void RestoreState(PedalboardItem&pedalboardItem); LogFeature logFeature; std::map patchPropertyPrototypes; @@ -174,6 +175,8 @@ namespace pipedal double currentBypassDx = 0; uint32_t bypassSamplesRemaining = 0; + bool requestStateChangedNotification = false; + void BypassTo(float value); public: @@ -181,6 +184,9 @@ namespace pipedal virtual bool GetLv2State(Lv2PluginState*state); virtual void RequestPatchProperty(LV2_URID uridUri); virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value); + virtual bool GetRequestStateChangedNotification() const; + virtual void SetRequestStateChangedNotification(bool value); + virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest); virtual bool IsVst3() const { return false; } virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter); diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index be3442f..038b1d4 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -545,6 +545,7 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara pParameterRequests->uridUri, pParameterRequests->GetSize(), (LV2_Atom *)pParameterRequests->GetBuffer()); + pLv2Effect->SetRequestStateChangedNotification(true); } } pParameterRequests = pParameterRequests->pNext; diff --git a/src/MapPathFeature.hpp b/src/MapPathFeature.hpp index e256c72..ab227a4 100644 --- a/src/MapPathFeature.hpp +++ b/src/MapPathFeature.hpp @@ -29,7 +29,8 @@ namespace pipedal public: MapPathFeature(const std::filesystem::path &storagePath); void Prepare(MapFeature* map); - void SetPluginStoragePath(const std::filesystem::path&path) { storagePath = path;} + void SetPluginStoragePath(const std::string&path) { storagePath = path;} + const std::string&GetPluginStoragePath() const { return storagePath; } const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;} const LV2_Feature*GetMakePathFeature() { return &makePathFeature;} const LV2_Feature*GetFreePathFeature() { return &freePathFeature;} diff --git a/src/Pedalboard.hpp b/src/Pedalboard.hpp index af4dda2..a64e22b 100644 --- a/src/Pedalboard.hpp +++ b/src/Pedalboard.hpp @@ -23,6 +23,7 @@ #include "json_variant.hpp" #include "MidiBinding.hpp" #include "StateInterface.hpp" +#include "atom_object.hpp" namespace pipedal { @@ -76,6 +77,8 @@ public: }; class PedalboardItem: public JsonMemberWritable { +public: + using PropertyMap = std::map; int64_t instanceId_ = 0; std::string uri_; std::string pluginName_; @@ -88,6 +91,9 @@ class PedalboardItem: public JsonMemberWritable { uint32_t stateUpdateCount_ = 0; Lv2PluginState lv2State_; std::string lilvPresetUri_; + + // non persistent state. + PropertyMap patchProperties; public: ControlValue*GetControlValue(const std::string&symbol); @@ -108,6 +114,7 @@ public: Lv2PluginState&lv2State() { return lv2State_; } // non-const version. GETTER_SETTER_REF(lilvPresetUri) + PropertyMap&PatchProperties() { return patchProperties; } bool isSplit() const { diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 4f16959..a415a39 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -32,6 +32,7 @@ #include "CpuGovernor.hpp" #include "RingBufferReader.hpp" #include "PiPedalUI.hpp" +#include "atom_object.hpp" #ifndef NO_MLOCK #include @@ -192,13 +193,15 @@ void PiPedalModel::Load() // the current edited preset, saved only across orderly shutdowns. CurrentPreset currentPreset; - try { + try + { if (storage.RestoreCurrentPreset(¤tPreset)) { this->pedalboard = currentPreset.preset_; this->hasPresetChanged = currentPreset.modified_; } - } catch (const std::exception &e) + } + catch (const std::exception &e) { Lv2Log::warning(SS("Failed to load current preset. " << e.what())); } @@ -249,12 +252,12 @@ void PiPedalModel::Load() { Lv2Log::info("Retry succeeded."); break; - } } - } - } else { + } + else + { this->jackConfiguration.AlsaInitialize(this->jackServerSettings); } #endif @@ -335,10 +338,6 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc } } - - - - void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId); @@ -348,7 +347,6 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s if (index != -1) { effect->SetControl(index, value); - ; } } else @@ -359,31 +357,40 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId) { + // a sent PATCH_Set, or an explicit state changed notification. + OnNotifyMaybeLv2StateChanged(instanceId); + this->SetPresetChanged(-1, true); +} + +void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId) +{ + // one or more received PATCH_Sets, which MAY change the state. std::lock_guard lock(mutex); PedalboardItem *item = pedalboard.GetItem(instanceId); if (item != nullptr) { - item->stateUpdateCount(item->stateUpdateCount()+1); - - this->audioHost->UpdatePluginState(*item); - this->FirePedalboardChanged(-1,false); - this->SetPresetChanged(-1, true); - } - { - // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) - IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; - for (size_t i = 0; i < subscribers.size(); ++i) + + bool changed = this->audioHost->UpdatePluginState(*item); + if (changed) { - t[i] = this->subscribers[i]; - } - size_t n = this->subscribers.size(); - { - for (size_t i = 0; i < n; ++i) + + item->stateUpdateCount(item->stateUpdateCount() + 1); + + + IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; + for (size_t i = 0; i < subscribers.size(); ++i) { - t[i]->OnLv2StateChanged(instanceId); + t[i] = this->subscribers[i]; } + size_t n = this->subscribers.size(); + { + for (size_t i = 0; i < n; ++i) + { + t[i]->OnLv2StateChanged(instanceId); + } + } + delete[] t; } - delete[] t; } } @@ -411,7 +418,6 @@ void PiPedalModel::SetInputVolume(float value) this->SetPresetChanged(-1, true); } - } void PiPedalModel::SetOutputVolume(float value) { @@ -436,7 +442,6 @@ void PiPedalModel::SetOutputVolume(float value) this->SetPresetChanged(-1, true); } - } void PiPedalModel::PreviewInputVolume(float value) { @@ -445,7 +450,6 @@ void PiPedalModel::PreviewInputVolume(float value) void PiPedalModel::PreviewOutputVolume(float value) { audioHost->SetOutputVolume(value); - } void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) @@ -458,8 +462,7 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std:: return; } - - PedalboardItem*item = pedalboard.GetItem(pedalItemId); + PedalboardItem *item = pedalboard.GetItem(pedalItemId); // change of split type requires rebuild of the effect // since it can change the number of output channels. @@ -467,7 +470,7 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std:: { this->FirePedalboardChanged(clientId); return; - } + } PreviewControl(clientId, pedalItemId, symbol, value); { @@ -524,7 +527,7 @@ void PiPedalModel::FireBanksChanged(int64_t clientId) delete[] t; } -void PiPedalModel::FirePedalboardChanged(int64_t clientId,bool loadAudioThread) +void PiPedalModel::FirePedalboardChanged(int64_t clientId, bool loadAudioThread) { if (loadAudioThread) { @@ -549,7 +552,6 @@ void PiPedalModel::FirePedalboardChanged(int64_t clientId,bool loadAudioThread) t[i]->OnPedalboardChanged(clientId, this->pedalboard); } delete[] t; - } void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard) { @@ -1741,11 +1743,11 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns // if the plugin has state, we have to rebuild the pedalboard, since setting state is not thread-safe. // Same goes if lilvPresetUri is not empty. - // lilvPresetUri: use lilv to load the preset from the RDF model. Occurs when using a factory preset + // lilvPresetUri: use lilv to load the preset from the RDF model. Occurs when using a factory preset // that has state:state, because lilv doesn't allow us to read this data, but does load it. // This is a transient condition. - for (auto&control : presetValues.controls) + for (auto &control : presetValues.controls) { this->pedalboard.SetControlValue(pluginInstanceId, control.key(), control.value()); } @@ -1765,10 +1767,12 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns t[i]->OnLoadPluginPreset(pluginInstanceId, presetValues.controls); } delete[] t; - } else { + } + else + { pedalboardItem->lv2State(presetValues.state); pedalboardItem->lilvPresetUri(presetValues.lilvPresetUri); - pedalboardItem->stateUpdateCount(oldStateUpdateCount+1); + pedalboardItem->stateUpdateCount(oldStateUpdateCount + 1); FirePedalboardChanged(-1); // does a complete reload of both client and audio server. } this->SetPresetChanged(-1, true); @@ -1803,36 +1807,46 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId) audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0); } -bool PiPedalModel::WantsAtomOutput(uint64_t instanceId, LV2_URID atomProperty) -{ - std::lock_guard lock(mutex); - for (const AtomOutputListener &listener : atomOutputListeners) - { - if (listener.WantsProperty(instanceId, atomProperty)) - { - return true; - } - } - return false; -} -void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) + +void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) { std::lock_guard lock(mutex); - std::string propertyUri; + std::string propertyUri = lv2Host.GetMapFeature().UridToString(patchSetProperty); + + { + PedalboardItem *item = pedalboard.GetItem((int64_t)instanceId); + if (item == nullptr) return; + atom_object atomObject { atomValue }; + + PedalboardItem::PropertyMap& properties = item->PatchProperties(); + if (properties.contains(propertyUri)) + { + if (properties[propertyUri] == atomObject) + { + return; + } + } + properties[propertyUri] = std::move(atomObject); + } + + bool hasAtomJson = false; + std::string atomJson; + for (int i = 0; i < atomOutputListeners.size(); ++i) { auto &listener = atomOutputListeners[i]; - if (listener.WantsProperty(instanceId, outputAtomProperty)) + if (listener.WantsProperty(instanceId, patchSetProperty)) { auto subscriber = this->GetNotificationSubscriber(listener.clientId); if (subscriber) { - if (propertyUri.length() == 0) + if (!hasAtomJson) { - propertyUri = lv2Host.GetMapFeature().UridToString(outputAtomProperty); + atomJson = this->audioHost->AtomToJson(atomValue); + hasAtomJson = true; } - subscriber->OnNotifyAtomOutput(listener.clientHandle, instanceId, propertyUri, atomJson); + subscriber->OnNotifyPatchProperty(listener.clientHandle, instanceId, propertyUri, atomJson); } else { @@ -1905,6 +1919,24 @@ void PiPedalModel::MonitorPatchProperty(int64_t clientId, int64_t clientHandle, AtomOutputListener listener{clientId, clientHandle, instanceId, propertyUrid}; atomOutputListeners.push_back(listener); audioHost->SetListenForAtomOutput(true); + + PedalboardItem*item = this->pedalboard.GetItem(instanceId ); + if (item) + { + auto& map = item->PatchProperties(); + if (map.contains(propertyUri)) + { + const auto&value = map[propertyUri]; + std::string json = this->audioHost->AtomToJson(value.get()); + for (auto &subscriber: this->subscribers) + { + if (subscriber->GetClientId() == clientId) + { + subscriber->OnNotifyPatchProperty(clientHandle,instanceId,propertyUri,json); + } + } + } + } } void PiPedalModel::CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle) @@ -2005,10 +2037,9 @@ void PiPedalModel::DeleteSampleFile(const std::filesystem::path &fileName) storage.DeleteSampleFile(fileName); } - -std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody) +std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, const std::string &fileBody) { - return storage.UploadUserFile(directory,patchProperty,filename,fileBody); + return storage.UploadUserFile(directory, patchProperty, filename, fileBody); } uint64_t PiPedalModel::CreateNewPreset() @@ -2021,7 +2052,7 @@ uint64_t PiPedalModel::CreateNewPreset() bool PiPedalModel::LoadCurrentPedalboard() { Lv2PedalboardErrorList errorMessages; - std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard,errorMessages)}; + std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard, errorMessages)}; this->lv2Pedalboard = lv2Pedalboard; // apply the error messages to the lv2Pedalboard. @@ -2030,7 +2061,7 @@ bool PiPedalModel::LoadCurrentPedalboard() return true; } -void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) +void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) { std::lock_guard lock(mutex); @@ -2046,7 +2077,4 @@ void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId,const std::string t[i]->OnErrorMessage(error); } delete[] t; - } - - diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 2f5e92d..eb82847 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -68,14 +68,14 @@ namespace pipedal virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector &controlValues) = 0; virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0; virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0; - virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0; + virtual void OnNotifyPatchProperty(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0; virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0; virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0; virtual void OnGovernorSettingsChanged(const std::string &governor) = 0; virtual void OnFavoritesChanged(const std::map &favorites) = 0; virtual void OnShowStatusMonitorChanged(bool show) = 0; virtual void OnSystemMidiBindingsChanged(const std::vector&bindings) = 0; - virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0; + //virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0; virtual void OnErrorMessage(const std::string&message) = 0; virtual void Close() = 0; }; @@ -169,13 +169,17 @@ namespace pipedal private: // IAudioHostCallbacks virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override; + virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) override; virtual void OnNotifyVusSubscription(const std::vector &updates) override; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override; virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override; - virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override; - virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) override; -; + virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) override; + + void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson); + + + virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override; virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override; virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) override; @@ -185,6 +189,7 @@ namespace pipedal PiPedalConfiguration configuration; + public: PiPedalModel(); virtual ~PiPedalModel(); diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 1e3450c..4008796 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -1502,15 +1502,15 @@ private: { Send("onErrorMessage",message); } - virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) - { - PatchPropertyChangedBody body; - body.clientId_ = clientId; - body.instanceId_ = instanceId; - body.propertyUri_ = propertyUri; - body.value_ = value; - Send("onPatchPropertyChanged",body); - } + // virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) + // { + // PatchPropertyChangedBody body; + // body.clientId_ = clientId; + // body.instanceId_ = instanceId; + // body.propertyUri_ = propertyUri; + // body.value_ = value; + // Send("onPatchPropertyChanged",body); + // } virtual void OnSystemMidiBindingsChanged(const std::vector&bindings) { Send("onSystemMidiBindingsChanged",bindings); @@ -1712,7 +1712,7 @@ private: { PendingNotifyAtomOutput t = pendingNotifyAtomOutputs[0]; pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()); - OnNotifyAtomOutput( + OnNotifyPatchProperty( t.clientHandle, t.instanceId, t.propertyUri, @@ -1721,7 +1721,7 @@ private: } } - virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomProperty, const std::string &atomJson) + virtual void OnNotifyPatchProperty(int64_t clientHandle, uint64_t instanceId, const std::string &atomProperty, const std::string &atomJson) { NotifyAtomOutputBody body; body.clientHandle_ = clientHandle; diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index d586620..d34036d 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -227,6 +227,11 @@ void PluginHost::SetPluginStoragePath(const std::filesystem::path &path) mapPathFeature.SetPluginStoragePath(path); } +std::string PluginHost::GetPluginStoragePath() const +{ + return mapPathFeature.GetPluginStoragePath(); +} + PluginHost::PluginHost() : mapPathFeature("") { diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index bfbae52..a089ced 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -805,6 +805,8 @@ namespace pipedal public: PluginHost(); void SetPluginStoragePath(const std::filesystem::path &path); + virtual std::string GetPluginStoragePath() const; + void SetConfiguration(const PiPedalConfiguration &configuration); virtual ~PluginHost(); diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index 34996ae..be51fe2 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -60,6 +60,7 @@ namespace pipedal NextMidiProgram, Lv2StateChanged, + MaybeLv2StateChanged, SetInputVolume, SetOutputVolume, Lv2ErrorMessage, @@ -322,6 +323,10 @@ namespace pipedal { 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); diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp index b1cdbc1..22384cb 100644 --- a/src/SplitEffect.hpp +++ b/src/SplitEffect.hpp @@ -497,6 +497,10 @@ namespace pipedal PostMixStereo(frames); } } + + virtual bool GetRequestStateChangedNotification() const { return false; } + virtual void SetRequestStateChangedNotification(bool value) {} + }; } // namespace \ No newline at end of file diff --git a/src/StateInterface.cpp b/src/StateInterface.cpp index 7d27742..ecf8cdb 100644 --- a/src/StateInterface.cpp +++ b/src/StateInterface.cpp @@ -22,6 +22,7 @@ #include "Base64.hpp" #include "json.hpp" #include +#include using namespace pipedal; @@ -281,18 +282,21 @@ void Lv2PluginStateEntry::read_json(json_reader &reader) reader.end_object(); } -StateInterface::StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface) +StateInterface::StateInterface( + IHost *host, + const LV2_Feature** features, + LilvInstance *pInstance, + const LV2_State_Interface *pluginStateInterface) : map(host->GetMapFeature()), pInstance(pInstance), pluginStateInterface(pluginStateInterface) { - auto hostFeatures = host->GetLv2Features(); - while (*hostFeatures != nullptr) + while (*features != nullptr) { - features.push_back((LV2_Feature*)(*hostFeatures)); - ++hostFeatures; + this->features.push_back((LV2_Feature*)(*features)); + ++features; } - features.push_back(nullptr); + this->features.push_back(nullptr); } std::string Lv2PluginState::ToString() const { @@ -300,4 +304,16 @@ std::string Lv2PluginState::ToString() const { json_writer writer(ss); writer.write(*this); return ss.str(); -} \ No newline at end of file +} + + +bool Lv2PluginStateEntry::operator==(const Lv2PluginStateEntry&other) const +{ + return this->atomType_ == other.atomType_ && this->value_ == other.value_; +} + +bool Lv2PluginState::IsEqual(const Lv2PluginState&other) const +{ + if (other.isValid_ != this->isValid_) return false; + return (other.values_ == this->values_); +} diff --git a/src/StateInterface.hpp b/src/StateInterface.hpp index 1b579db..c09cc33 100644 --- a/src/StateInterface.hpp +++ b/src/StateInterface.hpp @@ -34,6 +34,8 @@ namespace pipedal std::int32_t flags_ = 0; std::string atomType_; std::vector value_; + + bool operator==(const Lv2PluginStateEntry&other) const; private: virtual void write_json(json_writer &writer) const; virtual void read_json(json_reader &reader); @@ -48,6 +50,10 @@ namespace pipedal isValid_ = false; values_.clear(); } + bool IsEqual(const Lv2PluginState&other) const ; + + bool operator==(const Lv2PluginState&other) const { return IsEqual(other); } + bool operator!=(const Lv2PluginState&other) const { return !IsEqual(other); } std::string ToString() const; private: @@ -61,7 +67,11 @@ namespace pipedal class StateInterface { public: - StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface); + StateInterface( + IHost *host, + const LV2_Feature** features, + LilvInstance *pInstance, + const LV2_State_Interface *pluginStateInterface); public: Lv2PluginState Save(); diff --git a/src/Storage.cpp b/src/Storage.cpp index c18a4f1..a6b4c43 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -197,7 +197,7 @@ void Storage::MaybeCopyDefaultPresets() } // Obsolete: TooB effects now have correct preset declarations. - //auto pluginDirectory = this->GetPluginPresetsDirectory(); + // auto pluginDirectory = this->GetPluginPresetsDirectory(); // if (!std::filesystem::exists(pluginDirectory / "index.json")) // { // CopyDirectory(this->configRoot / "default_presets" / "plugin_presets", @@ -351,7 +351,7 @@ void Storage::SavePluginPresetIndex() throw PiPedalException(SS("Can't write to " << path)); } - json_writer writer(os,false); + json_writer writer(os, false); writer.write(this->pluginPresetIndex); } } @@ -360,7 +360,7 @@ void Storage::SaveBankIndex() { std::ofstream os; os.open(GetIndexFileName(), std::ios_base::trunc); - json_writer writer(os,false); + json_writer writer(os, false); writer.write(this->bankIndex); } @@ -435,7 +435,7 @@ void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile) { std::ofstream s; s.open(fileName, std::ios_base::trunc); - json_writer writer(s,false); + json_writer writer(s, false); writer.write(bankFile); if (std::filesystem::exists(backupFile)) { @@ -655,8 +655,7 @@ int64_t Storage::CreateNewPreset() } } newPedalboard.name(name); - return this->currentBank.addPreset(newPedalboard,-1); - + return this->currentBank.addPreset(newPedalboard, -1); } int64_t Storage::CopyPreset(int64_t fromId, int64_t toId) @@ -722,7 +721,7 @@ void Storage::SaveChannelSelection() try { std::ofstream s(fileName); - json_writer writer(s,false); + json_writer writer(s, false); writer.write(this->jackChannelSelection); } catch (const std::exception &e) @@ -911,7 +910,7 @@ int64_t Storage::UploadBank(BankFile &bankFile, int64_t uploadAfter) { throw PiPedalException("Can't write to bank file."); } - json_writer writer(f,false); + json_writer writer(f, false); writer.write(bankFile); lastBank = this->bankIndex.addBank(lastBank, bankFile.name()); @@ -933,7 +932,7 @@ void Storage::SaveUserSettings() { throw PiPedalException("Unable to write to " + ((std::string)path)); } - json_writer writer(f,false); + json_writer writer(f, false); writer.write(userSettings); } } @@ -969,7 +968,7 @@ void Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings { throw PiPedalException("Unable to write to " + ((std::string)path)); } - json_writer writer(f,false); + json_writer writer(f, false); writer.write(©ToSave); } @@ -1078,7 +1077,7 @@ void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset) std::filesystem::path path = GetCurrentPresetPath(); std::ofstream f(path); - json_writer writer(f,false); + json_writer writer(f, false); writer.write(currentPreset); } catch (std::exception &) @@ -1158,7 +1157,7 @@ void Storage::SavePluginPresets(const std::string &pluginUri, const PluginPreset { throw PiPedalException(SS("Can't write to " << path)); } - json_writer writer(os,false); + json_writer writer(os, false); writer.write(presets); } if (std::filesystem::exists(path)) @@ -1238,7 +1237,7 @@ uint64_t Storage::SavePluginPreset( const std::string &name, const PedalboardItem &pedalboardItem) { - const std::string&pluginUri = pedalboardItem.uri(); + const std::string &pluginUri = pedalboardItem.uri(); auto presets = GetPluginPresets(pluginUri); uint64_t result = -1; bool existing = false; @@ -1249,7 +1248,7 @@ uint64_t Storage::SavePluginPreset( { existing = true; result = preset.instanceId_; - presets.presets_[i] = PluginPreset(preset.instanceId_,preset.label_,pedalboardItem); + presets.presets_[i] = PluginPreset(preset.instanceId_, preset.label_, pedalboardItem); break; } } @@ -1258,8 +1257,8 @@ uint64_t Storage::SavePluginPreset( result = presets.nextInstanceId_++; presets.presets_.push_back( PluginPreset(result, - name, - pedalboardItem)); + name, + pedalboardItem)); } this->SavePluginPresets(pluginUri, presets); return result; @@ -1400,7 +1399,7 @@ void Storage::SetFavorites(const std::map &favorites) f.open(fileName); if (f.is_open()) { - json_writer writer(f,false); + json_writer writer(f, false); writer.write(favorites); } } @@ -1429,7 +1428,7 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi f.open(fileName); if (f.is_open()) { - json_writer writer(f,false); + json_writer writer(f, false); writer.write(jackConfiguration); } #if JACK_HOST @@ -1444,7 +1443,7 @@ void Storage::SetSystemMidiBindings(const std::vector &bindings) f.open(fileName); if (f.is_open()) { - json_writer writer(f,false); + json_writer writer(f, false); writer.write(bindings); } } @@ -1518,10 +1517,14 @@ std::vector Storage::GetFileList(const UiFileProperty &fileProperty if (dir_entry.is_regular_file()) { auto &path = dir_entry.path(); - if (fileProperty.IsValidExtension(path.extension().string())) + auto name = path.filename().string(); + if (name.length() > 0 && name[0] != '.') // don't show hidden files. { - // a relative path! - result.push_back(path); + if (fileProperty.IsValidExtension(path.extension().string())) + { + // a relative path! + result.push_back(path); + } } } } @@ -1612,6 +1615,7 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str path = this->MakeUserFilePath(directory, filename); } else + { if (patchProperty.length() == 0) { @@ -1632,9 +1636,9 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str return path.string(); } -const PluginPresetIndex& Storage::GetPluginPresetIndex() -{ - return pluginPresetIndex; +const PluginPresetIndex &Storage::GetPluginPresetIndex() +{ + return pluginPresetIndex; } JSON_MAP_BEGIN(UserSettings) diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 2534c01..ba86964 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -147,12 +147,15 @@ mime_type(const std::filesystem::path &path) auto const ext = path.extension(); try { - return extensionsToMimeType.at(ext); + if (extensionsToMimeType.find(ext) != extensionsToMimeType.end()) + { + return extensionsToMimeType.at(ext); + } } catch (const std::exception &) { - return "application/octet-stream"; } + return "application/octet-stream"; } std::string GetFromAddress(const tcp::socket &socket) @@ -436,12 +439,17 @@ namespace pipedal auto &request = con->get_request(); std::string origin = con->get_request_header(HttpField::origin); + if (origin.size() == 0) + { + origin = "*"; + } if (logHttpRequests) { tcp::endpoint fromAddress = con->get_socket().remote_endpoint(); stringstream ss; ss << "http - " << fromAddress << "; " << request.get_method() << "; " << con->get_uri()->str(); + Lv2Log::info(ss.str()); } std::shared_ptr connectionUri = con->get_uri(); @@ -507,7 +515,6 @@ namespace pipedal res.set(HttpField::date, HtmlHelper::timeToHttpDate(time(nullptr))); res.set(HttpField::access_control_allow_origin, origin); - res.set(HttpField::cache_control, "max-age: 31104000"); // cache for a year. requestHandler->get_response(fromAddress,requestUri, req, res, ec); res.keepAlive(req.keepAlive()); @@ -600,6 +607,11 @@ namespace pipedal res.set("Content-Type", mimeType); + if (mimeType.starts_with("image/") || mimeType.starts_with("font/")) + { + res.set(HttpField::cache_control, "public, max-age=864000"); // cache for a ten days. + } + res.set(HttpField::access_control_allow_origin, origin); res.set(HttpField::date, HtmlHelper::timeToHttpDate(time(nullptr))); res.setContentLength(response.length()); diff --git a/src/WebServer.hpp b/src/WebServer.hpp index 19a5087..904be24 100644 --- a/src/WebServer.hpp +++ b/src/WebServer.hpp @@ -62,6 +62,7 @@ public: constexpr static const char* access_control_request_headers = "Access-Control-Request-Headers"; constexpr static const char* origin = "Origin"; constexpr static const char* date = "Date"; + constexpr static const char* referer = "Referer"; }; diff --git a/src/atom_object.cpp b/src/atom_object.cpp new file mode 100644 index 0000000..fd9ca6f --- /dev/null +++ b/src/atom_object.cpp @@ -0,0 +1,32 @@ +// 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 "atom_object.hpp" +#include + + +using namespace pipedal; + + +atom_object::atom_object(const LV2_Atom*atom) +{ + size_t size = atom->size + sizeof(LV2_Atom); + atomData.resize(size); + memcpy(&(atomData[0]),atom,size); +} diff --git a/src/atom_object.hpp b/src/atom_object.hpp new file mode 100644 index 0000000..1ef77f4 --- /dev/null +++ b/src/atom_object.hpp @@ -0,0 +1,81 @@ +// 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 "lv2/atom/atom.h" +#include +#include + +namespace pipedal +{ + // memory-managed binary atom. + class atom_object { + public: + atom_object(); + atom_object(const LV2_Atom*atom); + atom_object(const atom_object&other); + atom_object(atom_object&&other); + + atom_object&operator=(const atom_object&other); + atom_object&operator=(atom_object&&other); + const LV2_Atom*get() const { return (LV2_Atom*)&(atomData[0]); } + bool operator==(const atom_object&other) const; + bool operator!=(const atom_object&other) const; + private: + std::vector atomData; + }; + + /*****/ + + inline atom_object::atom_object() { } + + inline atom_object::atom_object(const atom_object&other) + : atomData(other.atomData) + { + + } + inline atom_object::atom_object(atom_object&&other) + : atomData(std::move(other.atomData)) + { + + } + inline bool atom_object::operator==(const atom_object&other) const + { + return this->atomData == other.atomData; + } + inline bool atom_object::operator!=(const atom_object&other) const + { + return this->atomData != other.atomData; + } + + inline atom_object&atom_object::operator=(const atom_object&other) + { + this->atomData = other.atomData; + return *this; + } + inline atom_object&atom_object::operator=(atom_object&&other) + { + this->atomData = std::move(other.atomData); + return *this; + } + + +} \ No newline at end of file diff --git a/src/json_variant.cpp b/src/json_variant.cpp index 2c53e6b..8c1b3ae 100644 --- a/src/json_variant.cpp +++ b/src/json_variant.cpp @@ -30,6 +30,7 @@ using namespace pipedal; + json_variant::~json_variant() { free(); diff --git a/src/main.cpp b/src/main.cpp index 5abba1d..13aeb02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -692,10 +692,9 @@ int main(int argc, char *argv[]) auto serverSettings = model.GetJackServerSettings(); - if (true || systemd) { - // IF running as a service, wait for selected audio device to be initialized. - // It may take some time for ALSA to publish all available devices. + // 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()) { @@ -718,6 +717,10 @@ int main(int argc, char *argv[]) } if (g_SigBreak) exit(1); + if (systemd) + { + break; + } } if (found) { diff --git a/src/util.hpp b/src/util.hpp index 7f5b80f..9a3fa3c 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -22,6 +22,8 @@ */ #pragma once #include +#include + namespace pipedal { inline bool endsWith(const std::string& str, const std::string& suffix) @@ -32,4 +34,5 @@ namespace pipedal { std::u32string ToUtf32(const std::string &s); + inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;} }