File browser compatibility, NAM
This commit is contained in:
Vendored
+2
-1
@@ -91,7 +91,8 @@
|
||||
"string_span": "cpp",
|
||||
"util": "cpp",
|
||||
"charconv": "cpp",
|
||||
"valarray": "cpp"
|
||||
"valarray": "cpp",
|
||||
"hash_map": "cpp"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Guitarix",
|
||||
|
||||
@@ -86,7 +86,7 @@ cabir:impulseFile3
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment "TooB amplifier cabinet simulator." ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -59,7 +59,7 @@ inputStage:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Input Stage";
|
||||
|
||||
@@ -67,7 +67,7 @@ pstage:stage3
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
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.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
A straighforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss BF-2 Flanger*.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger*.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
ToobFreeverb is an Lv2 implementation of the famous Freeverb reverb effect.
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ toobml:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
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.
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 14 ;
|
||||
lv2:microVersion 15 ;
|
||||
rdfs:comment """
|
||||
A chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
Binary file not shown.
@@ -25,7 +25,7 @@ tpset:toob-convolution-reverb-preset-0
|
||||
pset:value 1.5 ;
|
||||
] ;
|
||||
state:state [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/St. Margaret\u0027s Church.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/St.%20Margaret\u0027s%20Church.wav> ;
|
||||
] .
|
||||
|
||||
tpset:toob-convolution-reverb-preset-1
|
||||
@@ -49,7 +49,7 @@ tpset:toob-convolution-reverb-preset-1
|
||||
pset:value 1.5 ;
|
||||
] ;
|
||||
state:state [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Jack Lyons Hall, University of York.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Jack%20Lyons%20Hall,%20University%20of%20York.wav> ;
|
||||
] .
|
||||
|
||||
tpset:toob-convolution-reverb-preset-2
|
||||
@@ -73,7 +73,7 @@ tpset:toob-convolution-reverb-preset-2
|
||||
pset:value 1.5 ;
|
||||
] ;
|
||||
state:state [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Arthur Sykes Rymer Auditorium.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Arthur%20Sykes%20Rymer%20Auditorium.wav> ;
|
||||
] .
|
||||
|
||||
tpset:toob-convolution-reverb-preset-3
|
||||
@@ -97,6 +97,6 @@ tpset:toob-convolution-reverb-preset-3
|
||||
pset:value 1.5 ;
|
||||
] ;
|
||||
state:state [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Genesis%206%20Studio%20Live%20Room.wav> ;
|
||||
] .
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ tpset:toob-convolution-reverb-stereo-preset-0
|
||||
pset:value 1.0 ;
|
||||
] ;
|
||||
state:state [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/St. Margaret\u0027s Church.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/St.%20Margaret\u0027s%20Church.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 [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Jack Lyons Hall, University of York.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Jack%20Lyons%20Hall,%20University%20of%20York.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 [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Arthur Sykes Rymer Auditorium.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Arthur%20Sykes%20Rymer%20Auditorium.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 [
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav" ;
|
||||
<http://two-play.com/plugins/toob-impulse#impulseFile> <impulseFiles/reverb/Genesis%206%20Studio%20Live%20Room.wav> ;
|
||||
] .
|
||||
|
||||
|
||||
@@ -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<FilePropertyControlProps>, prevState: Readonly<FilePropertyControlState>, snapshot?: any): void {
|
||||
@@ -163,8 +142,8 @@ const FilePropertyControl =
|
||||
|| prevProps.pedalboardItem.stateUpdateCount !== this.props.pedalboardItem.stateUpdateCount
|
||||
|
||||
) {
|
||||
this.unsubscribeToPropertyGet();
|
||||
this.subscribeToPropertyGet();
|
||||
this.unsubscribeToPatchProperty();
|
||||
this.subscribeToPatchProperty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "lv2/atom/atom.h"
|
||||
#include "lv2/atom/forge.h"
|
||||
#include "json_variant.hpp"
|
||||
#include "atom_object.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
+35
-14
@@ -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<Lv2PluginState*>(&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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-4
@@ -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<VuUpdate> & 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: <http://rerdavies.github.io/pipedal/TBD XXX.html> for more details.
|
||||
|
||||
@{
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -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 <vector>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <string.h>
|
||||
#include <stdexcept>
|
||||
#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<std::string> &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<std::string> installedFiles;
|
||||
std::unordered_set<std::string> installedFileMap;
|
||||
};
|
||||
};
|
||||
|
||||
inline uint32_t BrowserFilesVersionInfo::Version() const { return version; }
|
||||
inline void BrowserFilesVersionInfo::Version(uint32_t value) { version = value; }
|
||||
|
||||
inline const std::vector<std::string> &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<LV2_URID_Map *>(lv2Map),const_cast<LV2_Log_Log *>(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;
|
||||
}
|
||||
@@ -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 <filesystem>
|
||||
#include <lv2/core/lv2.h>
|
||||
#include <lv2/log/logger.h>
|
||||
|
||||
|
||||
|
||||
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
|
||||
);
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -50,5 +50,7 @@ namespace pipedal {
|
||||
|
||||
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
|
||||
|
||||
virtual std::string GetPluginStoragePath() const = 0;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
+90
-60
@@ -24,7 +24,7 @@
|
||||
#include <lilv/lilv.h>
|
||||
#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<Worker>(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<StateInterface>(pHost,pInstance,state_interface);
|
||||
this->stateInterface = std::make_unique<StateInterface>(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; }
|
||||
|
||||
+8
-2
@@ -24,6 +24,7 @@
|
||||
#include "Pedalboard.hpp"
|
||||
#include <lilv/lilv.h>
|
||||
#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> stateInterface;
|
||||
void RestoreState(const PedalboardItem&pedalboardItem);
|
||||
void RestoreState(PedalboardItem&pedalboardItem);
|
||||
LogFeature logFeature;
|
||||
std::map<std::string,AtomBuffer> 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);
|
||||
|
||||
@@ -545,6 +545,7 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
||||
pParameterRequests->uridUri,
|
||||
pParameterRequests->GetSize(),
|
||||
(LV2_Atom *)pParameterRequests->GetBuffer());
|
||||
pLv2Effect->SetRequestStateChangedNotification(true);
|
||||
}
|
||||
}
|
||||
pParameterRequests = pParameterRequests->pNext;
|
||||
|
||||
@@ -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;}
|
||||
|
||||
@@ -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<std::string,atom_object>;
|
||||
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
|
||||
{
|
||||
|
||||
+93
-65
@@ -32,6 +32,7 @@
|
||||
#include "CpuGovernor.hpp"
|
||||
#include "RingBufferReader.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "atom_object.hpp"
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
@@ -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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard,errorMessages)};
|
||||
std::shared_ptr<Lv2Pedalboard> 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<std::recursive_mutex> lock(mutex);
|
||||
|
||||
@@ -2046,7 +2077,4 @@ void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId,const std::string
|
||||
t[i]->OnErrorMessage(error);
|
||||
}
|
||||
delete[] t;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-5
@@ -68,14 +68,14 @@ namespace pipedal
|
||||
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &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<std::string, bool> &favorites) = 0;
|
||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&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<VuUpdate> &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();
|
||||
|
||||
+11
-11
@@ -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<MidiBinding>&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;
|
||||
|
||||
@@ -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("")
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -497,6 +497,10 @@ namespace pipedal
|
||||
PostMixStereo(frames);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool GetRequestStateChangedNotification() const { return false; }
|
||||
virtual void SetRequestStateChangedNotification(bool value) {}
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
+23
-7
@@ -22,6 +22,7 @@
|
||||
#include "Base64.hpp"
|
||||
#include "json.hpp"
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
+11
-1
@@ -34,6 +34,8 @@ namespace pipedal
|
||||
std::int32_t flags_ = 0;
|
||||
std::string atomType_;
|
||||
std::vector<uint8_t> 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();
|
||||
|
||||
+29
-25
@@ -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<std::string, bool> &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<MidiBinding> &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<std::string> 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)
|
||||
|
||||
+15
-3
@@ -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<websocketpp::uri> 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());
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 <memory.h>
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
json_variant::~json_variant()
|
||||
{
|
||||
free();
|
||||
|
||||
+6
-3
@@ -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)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
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;}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user