File browser compatibility, NAM

This commit is contained in:
Robin Davies
2023-06-24 13:49:11 -04:00
parent 38d021a6af
commit 7e0ef9ceb1
57 changed files with 1101 additions and 269 deletions
+2 -1
View File
@@ -91,7 +91,8 @@
"string_span": "cpp",
"util": "cpp",
"charconv": "cpp",
"valarray": "cpp"
"valarray": "cpp",
"hash_map": "cpp"
},
"cSpell.words": [
"Guitarix",
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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*.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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> ;
] .
+21 -42
View File
@@ -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();
}
}
+1 -1
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+3
View File
@@ -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
+138
View File
@@ -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
+336
View File
@@ -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;
}
+77
View File
@@ -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
View File
@@ -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);
+3
View File
@@ -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;
+2
View File
@@ -50,5 +50,7 @@ namespace pipedal {
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
virtual std::string GetPluginStoragePath() const = 0;
};
}
+7
View File
@@ -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)
{
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -545,6 +545,7 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
pParameterRequests->uridUri,
pParameterRequests->GetSize(),
(LV2_Atom *)pParameterRequests->GetBuffer());
pLv2Effect->SetRequestStateChangedNotification(true);
}
}
pParameterRequests = pParameterRequests->pNext;
+2 -1
View File
@@ -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;}
+7
View File
@@ -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
View File
@@ -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(&currentPreset))
{
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
View File
@@ -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
View File
@@ -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;
+5
View File
@@ -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("")
{
+2
View File
@@ -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();
+5
View File
@@ -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);
+4
View File
@@ -497,6 +497,10 @@ namespace pipedal
PostMixStereo(frames);
}
}
virtual bool GetRequestStateChangedNotification() const { return false; }
virtual void SetRequestStateChangedNotification(bool value) {}
};
} // namespace
+23 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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(&copyToSave);
}
@@ -1078,7 +1077,7 @@ void Storage::SaveCurrentPreset(const CurrentPreset &currentPreset)
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
View File
@@ -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());
+1
View File
@@ -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";
};
+32
View File
@@ -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);
}
+81
View File
@@ -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;
}
}
+1
View File
@@ -30,6 +30,7 @@
using namespace pipedal;
json_variant::~json_variant()
{
free();
+6 -3
View File
@@ -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)
{
+3
View File
@@ -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;}
}