Interim commit for COnvolutionReverb

This commit is contained in:
Robin Davies
2023-03-21 06:55:48 -04:00
parent 7741533254
commit 3ab431779a
34 changed files with 3369 additions and 1550 deletions
+4 -1
View File
@@ -79,7 +79,10 @@
// Resolved by CMake Tools: // Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": [ "[alsa_test]" ], "args": [
//"[Dev]"
"[json_variants]"
],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
+194
View File
@@ -0,0 +1,194 @@
/*
* 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.
*/
import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
import Typography from '@mui/material/Typography';
import Input from '@mui/material/Input';
import Select from '@mui/material/Select';
import Switch from '@mui/material/Switch';
import Utility, { nullCast } from './Utility';
import MenuItem from '@mui/material/MenuItem';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import ButtonBase from '@mui/material/ButtonBase'
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
const MIN_ANGLE = -135;
const MAX_ANGLE = 135;
const FONT_SIZE = "0.8em";
const SELECTED_OPACITY = 0.8;
const DEFAULT_OPACITY = 0.6;
const RANGE_SCALE = 120; // 120 pixels to move from 0 to 1.
const FINE_RANGE_SCALE = RANGE_SCALE * 10; // 1200 pixels to move from 0 to 1.
const ULTRA_FINE_RANGE_SCALE = RANGE_SCALE * 50; // 12000 pixels to move from 0 to 1.
export const StandardItemSize = { width: 80, height: 140 }
const styles = (theme: Theme) => createStyles({
frame: {
position: "relative",
margin: "12px"
},
switchTrack: {
height: '100%',
width: '100%',
borderRadius: 14 / 2,
zIndex: -1,
transition: theme.transitions.create(['opacity', 'background-color'], {
duration: theme.transitions.duration.shortest
}),
backgroundColor: theme.palette.secondary.main,
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
},
displayValue: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 4,
textAlign: "center",
background: "white",
color: "#666",
// zIndex: -1,
}
});
export interface FilePropertyControlProps extends WithStyles<typeof styles> {
instanceId: number;
fileProperty: PiPedalFileProperty;
value: string;
onFileClick: (fileProperty: PiPedalFileProperty,value: string) => void;
theme: Theme;
}
type FilePropertyControlState = {
error: boolean;
showDialog: boolean;
};
const FilePropertyControl =
withStyles(styles, { withTheme: true })(
class extends Component<FilePropertyControlProps, FilePropertyControlState>
{
model: PiPedalModel;
constructor(props: FilePropertyControlProps) {
super(props);
this.state = {
error: false,
showDialog: false
};
this.model = PiPedalModelFactory.getInstance();
}
componentWillUnmount() {
}
inputChanged: boolean = false;
onDrag(e: SyntheticEvent) {
e.preventDefault();
}
onFileClick() {
this.props.onFileClick(this.props.fileProperty,this.props.value);
}
private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/');
if (slashPos < 0)
{
slashPos = 0;
} else {
++slashPos;
}
let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos)
{
extPos = path.length;
}
return path.substring(slashPos,extPos);
}
render() {
let classes = this.props.classes;
let fileProperty = this.props.fileProperty;
let value = this.props.value;
if (!value || value.length === 0)
{
value = "\u00A0";
}
value = this.fileNameOnly(value);
let item_width = 264;
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8}}>
{/* TITLE SECTION */}
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
<Typography variant="caption" display="block" noWrap style={{
width: "100%",
textAlign: "start"
}}> {fileProperty.name}</Typography>
</div>
{/* CONTROL SECTION */}
<div style={{ flex: "0 0 auto", width: "100%" }}>
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden",marginTop: 8}} onClick={()=> {this.onFileClick()}} >
<div style={{width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px"}}>
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4,paddingBottom: 4,paddingLeft: 4}}
variant='caption'
>{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center",opacity: 0.5, marginRight: 4 }} />
</div>
<div style={{ height: "1px", width: "100%", background: "black" }}>&nbsp;</div>
</div>
</ButtonBase>
</div>
{/* LABEL/EDIT SECTION*/}
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
</div>
</div >
);
}
}
);
export default FilePropertyControl;
+191
View File
@@ -0,0 +1,191 @@
// Copyright (c) 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.
import React from 'react';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import Button from '@mui/material/Button';
import Radio from '@mui/material/Radio';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import AudioFileIcon from '@mui/icons-material/AudioFile';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
import ButtonBase from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
import DialogEx from './DialogEx';
import { ModelTraining } from '@mui/icons-material';
export interface FilePropertyDialogProps {
open: boolean,
fileProperty: PiPedalFileProperty,
selectedFile: string,
onOk: (fileProperty: PiPedalFileProperty, selectedItem: string) => void,
onClose: () => void
};
export interface FilePropertyDialogState {
fullScreen: boolean;
selectedFile: string;
hasSelection: boolean;
files: string[];
};
export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
constructor(props: FilePropertyDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
selectedFile: props.selectedFile,
hasSelection: false,
files: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
};
this.requestFiles();
}
private mounted: boolean = false;
private model: PiPedalModel;
private requestFiles() {
this.model.requestFileList(this.props.fileProperty);
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void {
}
private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/');
if (slashPos < 0) {
slashPos = 0;
} else {
++slashPos;
}
let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos) {
extPos = path.length;
}
return path.substring(slashPos, extPos);
}
private isFileInList(selectedFile: string) {
let hasSelection = false;
for (var file of this.state.files) {
if (file === selectedFile) {
hasSelection = true;
break;
}
}
return hasSelection;
}
onSelect(selectedFile: string) {
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(selectedFile) })
}
render() {
return (
<DialogEx onClose={() => this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
>
<DialogTitle >{this.props.fileProperty.name}</DialogTitle>
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}>&nbsp;</div>
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start" }}>
{
this.state.files.map(
(value: string, index: number) => {
let selected = value === this.state.selectedFile;
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
return (
<ButtonBase
style={{ width: "320px", flex: "0 0 48px", position: "relative" }}
onClick={() => this.onSelect(value)}
>
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%" }} />
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 4 }} />
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
</div>
</ButtonBase>
);
}
)
}
</div>
</div>
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}>&nbsp;</div>
<DialogActions style={{ justifyContent: "stretch" }}>
<div style={{ display: "flex", width: "100%", flexFlow: "row nowrap" }}>
<Button style={{ flex: "0 0 auto" }} startIcon={<FileUploadIcon />}>
<input hidden accept="audio/x-wav" name="Upload Impuse File" type="file" multiple />
Upload
</Button>
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label">
<DeleteIcon />
</IconButton>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button onClick={() => this.props.onClose()} aria-label="cancel">
Cancel
</Button>
<Button style={{ flex: "0 0 auto" }} onClick={() => this.props.onClose()} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
Select
</Button>
</div>
</DialogActions>
</DialogEx>
);
}
}
+60 -2
View File
@@ -105,6 +105,49 @@ export class PortGroup {
program_list_id: number = -1; program_list_id: number = -1;
}; };
export class PiPedalFileType {
deserialize(input: any): PiPedalFileType {
this.name = input.name;
this.fileExtension = input.fileExtension;
return this;
}
static deserialize_array(input: any): PiPedalFileType[]
{
let result: PiPedalFileType[] = [];
for (let i = 0; i < input.length; ++i)
{
result[i] = new PiPedalFileType().deserialize(input[i]);
}
return result;
}
name: string = "";
fileExtension: string = "";
}
export class PiPedalFileProperty {
deserialize(input: any): PiPedalFileProperty
{
this.name = input.name;
this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes);
this.patchProperty = input.patchProperty;
this.defaultFile = input.defaultFile;
return this;
}
static deserialize_array(input: any): PiPedalFileProperty[]
{
let result: PiPedalFileProperty[] = [];
for (let i = 0; i < input.length; ++i)
{
result[i] = new PiPedalFileProperty().deserialize(input[i]);
}
return result;
}
name: string = "";
fileTypes: PiPedalFileType[] = [];
patchProperty: string = "";
defaultFile: string = "";
};
export class Lv2Plugin implements Deserializable<Lv2Plugin> { export class Lv2Plugin implements Deserializable<Lv2Plugin> {
deserialize(input: any): Lv2Plugin deserialize(input: any): Lv2Plugin
{ {
@@ -119,6 +162,12 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
this.comment = input.comment; this.comment = input.comment;
this.ports= Port.deserialize_array(input.ports); this.ports= Port.deserialize_array(input.ports);
this.port_groups = PortGroup.deserialize_array(input.port_groups); this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
return this; return this;
} }
static EmptyFeatures: string[] = []; static EmptyFeatures: string[] = [];
@@ -133,7 +182,8 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
author_homepage: string = ""; author_homepage: string = "";
comment: string = ""; comment: string = "";
ports: Port[] = Port.EmptyPorts; ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = []; port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = [];
} }
@@ -474,6 +524,13 @@ export class UiPlugin implements Deserializable<UiPlugin> {
this.description = input.description; this.description = input.description;
this.controls = UiControl.deserialize_array(input.controls); this.controls = UiControl.deserialize_array(input.controls);
this.port_groups = PortGroup.deserialize_array(input.port_groups); this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
this.is_vst3 = input.is_vst3; this.is_vst3 = input.is_vst3;
return this; return this;
@@ -523,7 +580,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
has_midi_output: number = 0; has_midi_output: number = 0;
description: string = ""; description: string = "";
controls: UiControl[] = []; controls: UiControl[] = [];
port_groups: PortGroup[] = []; port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = [];
is_vst3 : boolean = false; is_vst3 : boolean = false;
} }
+34
View File
@@ -56,6 +56,27 @@ export class ControlValue implements Deserializable<ControlValue> {
key: string; key: string;
value: number; value: number;
}
export class PropertyValue implements Deserializable<PropertyValue> {
deserialize(input: any): PropertyValue {
this.propertyUri = input.propertyUri;
this.value = input.value;
return this;
}
static deserializeArray(input: any[]): PropertyValue[] {
let result: PropertyValue[] = [];
for (let i = 0; i < input.length; ++i) {
result[i] = new PropertyValue().deserialize(input[i]);
}
return result;
}
setValue(value: number) {
this.value = value;
}
propertyUri: string = "";
value: any = null;
} }
export class PedalBoardItem implements Deserializable<PedalBoardItem> { export class PedalBoardItem implements Deserializable<PedalBoardItem> {
@@ -67,6 +88,7 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings); this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
this.controlValues = ControlValue.deserializeArray(input.controlValues); this.controlValues = ControlValue.deserializeArray(input.controlValues);
this.propertyValues = PropertyValue.deserializeArray(input.propertyValues);
this.vstState = input.vstState ?? ""; this.vstState = input.vstState ?? "";
return this; return this;
} }
@@ -136,6 +158,17 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
} }
return false; return false;
} }
setPropertyValue(propertyUri: string, value: any): boolean {
for (let i = 0; i < this.propertyValues.length; ++i) {
let v = this.propertyValues[i];
if (v.propertyUri === propertyUri) {
if (v.value === value) return false;
v.value = value;
return true;
}
}
return false;
}
setMidiBinding(midiBinding: MidiBinding): boolean { setMidiBinding(midiBinding: MidiBinding): boolean {
if (this.midiBindings) if (this.midiBindings)
{ {
@@ -183,6 +216,7 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
uri: string = ""; uri: string = "";
pluginName?: string; pluginName?: string;
controlValues: ControlValue[] = ControlValue.EmptyArray; controlValues: ControlValue[] = ControlValue.EmptyArray;
propertyValues: PropertyValue[] = [];
midiBindings: MidiBinding[] = []; midiBindings: MidiBinding[] = [];
vstState: string = ""; vstState: string = "";
}; };
+72 -5
View File
@@ -17,7 +17,7 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { UiPlugin, UiControl, PluginType } from './Lv2Plugin'; import { UiPlugin, UiControl, PluginType, PiPedalFileProperty } from './Lv2Plugin';
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError'; import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
@@ -50,6 +50,7 @@ export enum State {
}; };
export type ControlValueChangedHandler = (key: string, value: number) => void; export type ControlValueChangedHandler = (key: string, value: number) => void;
export type PropertyValueChangedHandler = (properyUri: string, value: number) => void;
export type PluginPresetsChangedHandler = (pluginUri: string) => void; export type PluginPresetsChangedHandler = (pluginUri: string) => void;
@@ -94,6 +95,18 @@ interface ControlValueChangeItem {
}; };
export interface PropertyValueChangedHandle {
_PropertyValueChangedHandle: number;
};
interface PropertyValueChangeItem {
handle: number;
instanceId: number;
onValueChanged: PropertyValueChangedHandler;
};
class MidiEventListener { class MidiEventListener {
constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) { constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) {
this.handle = handle; this.handle = handle;
@@ -354,9 +367,9 @@ export interface PiPedalModel {
loadPedalBoardPlugin(itemId: number, selectedUri: string): number; loadPedalBoardPlugin(itemId: number, selectedUri: string): number;
setPedalBoardControlValue(instanceId: number, key: string, value: number): void; setPedalBoardControlValue(instanceId: number, key: string, value: number): void;
setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any): void;
setPedalBoardItemEnabled(instanceId: number, value: boolean): void; setPedalBoardItemEnabled(instanceId: number, value: boolean): void;
previewPedalBoardValue(instanceId: number, key: string, value: number): void; previewPedalBoardValue(instanceId: number, key: string, value: number): void;
setPedalBoardControlValue(instanceId: number, key: string, value: number): void;
deletePedalBoardPedal(instanceId: number): number | null; deletePedalBoardPedal(instanceId: number): number | null;
movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void; movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void;
@@ -456,6 +469,8 @@ export interface PiPedalModel {
chooseNewDevice(): void; chooseNewDevice(): void;
hasConfiguration(): boolean; hasConfiguration(): boolean;
requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise<string[]>;
}; };
class PiPedalModelImpl implements PiPedalModel { class PiPedalModelImpl implements PiPedalModel {
@@ -722,6 +737,7 @@ class PiPedalModelImpl implements PiPedalModel {
} }
} }
requestPluginClasses(): Promise<boolean> { requestPluginClasses(): Promise<boolean> {
const myRequest = new Request(this.varRequest('plugin_classes.json')); const myRequest = new Request(this.varRequest('plugin_classes.json'));
return fetch(myRequest) return fetch(myRequest)
@@ -892,6 +908,10 @@ class PiPedalModelImpl implements PiPedalModel {
); );
return this.webSocket.connect(); return this.webSocket.connect();
}) })
.catch((error) => {
this.setError("Failed to connect to server.");
return false;
})
.then(() => { .then(() => {
const isoRequest = new Request('iso_codes.json'); const isoRequest = new Request('iso_codes.json');
return fetch(isoRequest); return fetch(isoRequest);
@@ -1171,7 +1191,7 @@ class PiPedalModelImpl implements PiPedalModel {
} }
_controlValueChangeItems: ControlValueChangeItem[] = []; private _controlValueChangeItems: ControlValueChangeItem[] = [];
addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle { addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle {
let handle = ++this.nextListenHandle; let handle = ++this.nextListenHandle;
@@ -1186,6 +1206,21 @@ class PiPedalModelImpl implements PiPedalModel {
} }
} }
} }
private _propertyValueChangeItems: PropertyValueChangeItem[] = [];
addPropertyValueChangeListener(instanceId: number, onValueChanged: PropertyValueChangedHandler): PropertyValueChangedHandle {
let handle = ++this.nextListenHandle;
this._propertyValueChangeItems.push({ handle: handle, instanceId: instanceId, onValueChanged: onValueChanged });
return { _PropertyValueChangedHandle: handle };
}
removePropertyValueChangeListener(handle: PropertyValueChangedHandle) {
for (let i = 0; i < this._propertyValueChangeItems.length; ++i) {
if (this._propertyValueChangeItems[i].handle === handle._PropertyValueChangedHandle) {
this._propertyValueChangeItems.splice(i, 1);
return;
}
}
}
_pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = []; _pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = [];
@@ -1217,7 +1252,27 @@ class PiPedalModelImpl implements PiPedalModel {
} }
_setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalBoard = pedalBoard.clone();
let item = newPedalBoard.getItem(instanceId);
let changed = item.setPropertyValue(propertyUri, value);
if (changed) {
this.pedalBoard.set(newPedalBoard);
if (notifyServer) {
// FIX ME!: this._setServerProperty("setProperty", instanceId, propertyUri, value);
}
for (let i = 0; i < this._propertyValueChangeItems.length; ++i) {
let item = this._propertyValueChangeItems[i];
if (instanceId === item.instanceId) {
item.onValueChanged(propertyUri, value);
}
}
}
}
_setPedalBoardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void { _setPedalBoardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get(); let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
@@ -1262,14 +1317,17 @@ class PiPedalModelImpl implements PiPedalModel {
} }
} }
setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any): void
{
this._setPedalBoardPropertyValue(instanceId, propertyUri, value, true);
}
setPedalBoardControlValue(instanceId: number, key: string, value: number): void { setPedalBoardControlValue(instanceId: number, key: string, value: number): void {
this._setPedalBoardControlValue(instanceId, key, value, true); this._setPedalBoardControlValue(instanceId, key, value, true);
} }
setPedalBoardItemEnabled(instanceId: number, value: boolean): void { setPedalBoardItemEnabled(instanceId: number, value: boolean): void {
this._setPedalBoardItemEnabled(instanceId, value, true); this._setPedalBoardItemEnabled(instanceId, value, true);
} }
_setPedalBoardItemEnabled(instanceId: number, value: boolean, notifyServer: boolean): void { private _setPedalBoardItemEnabled(instanceId: number, value: boolean, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get(); let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalBoard = pedalBoard.clone(); let newPedalBoard = pedalBoard.clone();
@@ -1544,6 +1602,13 @@ class PiPedalModelImpl implements PiPedalModel {
return newPresetId; return newPresetId;
}); });
} }
requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise<string[]>
{
return nullCast(this.webSocket)
.request<string[]>('requestFileList',piPedalFileProperty);
}
saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> { saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> {
// default behaviour is to save after the currently selected preset. // default behaviour is to save after the currently selected preset.
let request: any = { let request: any = {
@@ -2354,6 +2419,8 @@ class PiPedalModelImpl implements PiPedalModel {
return jackConfig.isValid; return jackConfig.isValid;
} }
}; };
let instance: PiPedalModel | undefined = undefined; let instance: PiPedalModel | undefined = undefined;
+3 -2
View File
@@ -272,18 +272,19 @@ class PiPedalSocket {
connectInternal_(): Promise<WebSocket> { connectInternal_(): Promise<WebSocket> {
return new Promise<WebSocket>((resolve, reject) => { return new Promise<WebSocket>((resolve, reject) => {
let ws = new WebSocket(this.url); let ws = new WebSocket(this.url);
let self = this; let self = this;
ws.onmessage = this.handleMessage.bind(this); ws.onmessage = this.handleMessage.bind(this);
ws.onclose = (event: Event) => { ws.onclose = (event: Event) => {
ws.onclose = null; ws.onclose = null;
ws.onerror = null; ws.onerror = null;
reject("Can't connect to server."); reject("Connection closed unexpectedly.");
}; };
ws.onerror = (event: Event) => { ws.onerror = (event: Event) => {
ws.onclose = null; ws.onclose = null;
ws.onerror = null; ws.onerror = null;
reject("Can't connect to server."); reject("Failed to connect.");
}; };
ws.onopen = (event: Event) => { ws.onopen = (event: Event) => {
ws.onerror = self.handleError.bind(self); ws.onerror = self.handleError.bind(self);
+75 -28
View File
@@ -23,9 +23,9 @@ import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles'; import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { UiPlugin, UiControl } from './Lv2Plugin'; import { UiPlugin, UiControl, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin';
import { import {
PedalBoard, PedalBoardItem, ControlValue, PedalBoard, PedalBoardItem, ControlValue,PropertyValue
} from './PedalBoard'; } from './PedalBoard';
import PluginControl from './PluginControl'; import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
@@ -34,6 +34,8 @@ import { nullCast } from './Utility'
import { PiPedalStateError } from './PiPedalError'; import { PiPedalStateError } from './PiPedalError';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import FullScreenIME from './FullScreenIME'; import FullScreenIME from './FullScreenIME';
import FilePropertyControl from './FilePropertyControl';
import FilePropertyDialog from './FilePropertyDialog';
export const StandardItemSize = { width: 80, height: 110 }; export const StandardItemSize = { width: 80, height: 110 };
@@ -167,8 +169,7 @@ const styles = (theme: Theme) => createStyles({
}); });
export class ControlGroup { export class ControlGroup {
constructor(name: string, controls: ReactNode[]) constructor(name: string, controls: ReactNode[]) {
{
this.name = name; this.name = name;
this.controls = controls; this.controls = controls;
} }
@@ -197,6 +198,9 @@ type PluginControlViewState = {
imeValue: number; imeValue: number;
imeCaption: string; imeCaption: string;
imeInitialHeight: number; imeInitialHeight: number;
showFileDialog: boolean,
dialogFileProperty: PiPedalFileProperty,
dialogFileValue: string
}; };
const PluginControlView = const PluginControlView =
@@ -214,11 +218,14 @@ const PluginControlView =
imeUiControl: undefined, imeUiControl: undefined,
imeValue: 0, imeValue: 0,
imeCaption: "", imeCaption: "",
imeInitialHeight: 0 imeInitialHeight: 0,
showFileDialog: false,
dialogFileProperty: new PiPedalFileProperty(),
dialogFileValue: ""
} }
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
this.onValueChanged = this.onValueChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this);
this.onPreviewChange = this.onPreviewChange.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this);
} }
@@ -227,9 +234,12 @@ const PluginControlView =
this.model.previewPedalBoardValue(this.props.instanceId, key, value); this.model.previewPedalBoardValue(this.props.instanceId, key, value);
} }
onValueChanged(key: string, value: number): void { onControlValueChanged(key: string, value: number): void {
this.model.setPedalBoardControlValue(this.props.instanceId, key, value); this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
} }
onPropertyValueChanged(propertyUri: string, value: any): void {
this.model.setPedalBoardPropertyValue(this.props.instanceId, propertyUri, value);
}
onPedalBoardChanged(value?: PedalBoard) { onPedalBoardChanged(value?: PedalBoard) {
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId); //let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId);
@@ -274,6 +284,30 @@ const PluginControlView =
}); });
} }
makeFilePropertyUI(fileProperty: PiPedalFileProperty, propertyValues: PropertyValue[]): ReactNode {
let propertyValue: PropertyValue | undefined = undefined;
for (let i = 0; i < propertyValues.length; ++i) {
if (propertyValues[i].propertyUri === fileProperty.patchProperty) {
propertyValue = propertyValues[i];
break;
}
}
if (!propertyValue) {
propertyValue = new PropertyValue();
propertyValue.value = fileProperty.defaultFile;
propertyValue.propertyUri = fileProperty.patchProperty;
}
return ((
<FilePropertyControl instanceId={this.props.instanceId} value={propertyValue.value}
fileProperty={fileProperty}
onFileClick={(fileProperty,selectedFile) => {
this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile});
}}
/>
));
}
makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode { makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode {
let symbol = uiControl.symbol; let symbol = uiControl.symbol;
@@ -288,18 +322,18 @@ const PluginControlView =
throw new PiPedalStateError("Missing control value."); throw new PiPedalStateError("Missing control value.");
} }
return (( return ((
<PluginControl instanceId={this.props.instanceId} uiControl={uiControl} value={controlValue.value}
onChange={(value: number) => { this.onValueChanged(controlValue!.key, value) }}
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
/> <PluginControl instanceId={this.props.instanceId} uiControl={uiControl} value={controlValue.value}
onChange={(value: number) => { this.onControlValueChanged(controlValue!.key, value) }}
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
/>
)); ));
} }
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes { getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[],propertyValues: PropertyValue[]): ControlNodes {
let result: ControlNodes = []; let result: ControlNodes = [];
for (let i = 0; i < plugin.controls.length; ++i) { for (let i = 0; i < plugin.controls.length; ++i) {
@@ -325,11 +359,17 @@ const PluginControlView =
) )
} else { } else {
result.push( result.push(
this.makeStandardControl(pluginControl, controlValues) this.makeStandardControl(pluginControl, controlValues)
); );
} }
} }
} }
for (let i = 0; i < plugin.fileProperties.length; ++i) {
let fileProperty = plugin.fileProperties[i];
result.push(
this.makeFilePropertyUI(fileProperty, propertyValues)
);
}
return result; return result;
} }
@@ -352,10 +392,8 @@ const PluginControlView =
}); });
} }
hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean {
{ for (let i = 0; i < nodes.length; ++i) {
for (let i = 0; i < nodes.length; ++i)
{
let node = nodes[i]; let node = nodes[i];
if (node instanceof ControlGroup) return true; if (node instanceof ControlGroup) return true;
} }
@@ -374,13 +412,12 @@ const PluginControlView =
if (node instanceof ControlGroup) { if (node instanceof ControlGroup) {
let controlGroup = node as ControlGroup; let controlGroup = node as ControlGroup;
let controls: ReactNode[] = []; let controls: ReactNode[] = [];
for (let j = 0; j < controlGroup.controls.length; ++j) for (let j = 0; j < controlGroup.controls.length; ++j) {
{
let item = controlGroup.controls[j]; let item = controlGroup.controls[j];
controls.push( controls.push(
( (
<div className={classes.controlPadding}> <div className={classes.controlPadding}>
{ item } {item}
</div> </div>
) )
@@ -402,7 +439,7 @@ const PluginControlView =
} else { } else {
result.push(( result.push((
<div className={ hasGroups ? classes.portgroupControlPadding: classes.controlPadding } > <div className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
{node as ReactNode} {node as ReactNode}
</div> </div>
)); ));
@@ -421,6 +458,7 @@ const PluginControlView =
return (<div className={classes.frame} ></div>); return (<div className={classes.frame} ></div>);
let controlValues = pedalBoardItem.controlValues; let controlValues = pedalBoardItem.controlValues;
let propertyValues = pedalBoardItem.propertyValues;
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri)); let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri));
@@ -429,14 +467,14 @@ const PluginControlView =
controlValues = this.filterNotOnGui(controlValues, plugin); controlValues = this.filterNotOnGui(controlValues, plugin);
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid; let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
let controlNodes: ControlNodes; let controlNodes: ControlNodes;
controlNodes = this.getStandardControlNodes(plugin, controlValues); controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues);
if (this.props.customization) if (this.props.customization) {
{
// allow wrapper class to insert/remove/rebuild controls. // allow wrapper class to insert/remove/rebuild controls.
controlNodes = this.props.customization.ModifyControls(controlNodes); controlNodes = this.props.customization.ModifyControls(controlNodes);
} }
@@ -462,6 +500,15 @@ const PluginControlView =
) )
} }
</div> </div>
<FilePropertyDialog open={this.state.showFileDialog}
fileProperty={this.state.dialogFileProperty}
selectedFile={this.state.dialogFileValue}
onClose={()=> { this.setState({ showFileDialog: false});}}
onOk={(fileProperty,selectedFile)=> {
this.model.setPedalBoardPropertyValue(this.props.instanceId,fileProperty.patchProperty,selectedFile)
this.setState({ showFileDialog: false});}
}
/>
<FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue} <FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue}
onChange={(key, value) => this.onImeValueChange(key, value)} onChange={(key, value) => this.onImeValueChange(key, value)}
+85
View File
@@ -0,0 +1,85 @@
/*
* 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 "AutoLilvNode.hpp"
#include "PiPedalHost.hpp"
using namespace pipedal;
float AutoLilvNode::AsFloat(float defaultValue)
{
if (node == nullptr)
{
return defaultValue;
}
if (lilv_node_is_float(node))
{
return lilv_node_as_float(node);
}
if (lilv_node_is_int(node))
{
return lilv_node_as_int(node);
}
return defaultValue;
}
int AutoLilvNode::AsInt(int defaultValue)
{
if (node == nullptr)
return defaultValue;
if (lilv_node_is_int(node))
return lilv_node_as_int(node);
return defaultValue;
}
bool AutoLilvNode::AsBool(bool defaultValue)
{
if (node == nullptr)
return defaultValue;
if (lilv_node_is_int(node))
return lilv_node_as_bool(node);
return defaultValue;
}
std::string AutoLilvNode::AsUri()
{
if (node == nullptr)
{
return "";
}
if (lilv_node_is_uri(node))
{
return lilv_node_as_uri(node);
}
return "";
}
std::string AutoLilvNode::AsString()
{
if (node == nullptr)
{
return "";
}
if (lilv_node_is_string(node))
{
return lilv_node_as_string(node);
}
return "";
}
+89
View File
@@ -0,0 +1,89 @@
/*
* 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 <lilv/lilv.h>
#include <cassert>
namespace pipedal
{
class AutoLilvNode
{
// const LilvNode* returns must not be freed, by convention.
private:
LilvNode *node = nullptr;
AutoLilvNode(const LilvNode *node) = delete;
public:
AutoLilvNode()
{
}
AutoLilvNode(LilvNode *node)
: node(node)
{
}
~AutoLilvNode() { Free(); }
operator const LilvNode *()
{
return this->node;
}
operator bool()
{
return this->node != nullptr;
}
LilvNode*&Get() { return node; }
AutoLilvNode&operator=(LilvNode*node) {
Free();
this->node = node;
return *this;
}
AutoLilvNode&operator=(AutoLilvNode&&other) {
std::swap(this->node,other.node);
return *this;
}
float AsFloat(float defaultValue = 0);
int AsInt(int defaultValue = 0);
bool AsBool(bool defaultValue = false);
std::string AsUri();
std::string AsString();
void Free()
{
if (node != nullptr)
lilv_node_free(node);
node = nullptr;
}
};
};
+1 -1
View File
@@ -153,7 +153,7 @@ void AvahiService::create_group(AvahiClient *c)
* because it was reset previously, add our entries. */ * because it was reset previously, add our entries. */
if (avahi_entry_group_is_empty(group)) if (avahi_entry_group_is_empty(group))
{ {
Lv2Log::debug(SS("Adding service '" << name)); Lv2Log::debug(SS("Adding service '" << name << "'"));
std::string instanceTxtRecord = SS("id=" << this->instanceId); std::string instanceTxtRecord = SS("id=" << this->instanceId);
+1 -1
View File
@@ -42,6 +42,6 @@ TEST_CASE("Avahi Service Test", "[avahi_service][dev]")
service.Unannounce(); service.Unannounce();
service.Announce(81, "Test Announcement 2", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); service.Announce(81, "Test Announcement 2", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal");
sleep(10000); sleep(10);
} }
} }
+9 -1
View File
@@ -5,6 +5,8 @@ set (CMAKE_INSTALL_PREFIX "/usr/")
set (USE_PCH 1) set (USE_PCH 1)
set(CXX_STANDARD 20)
include(FindPkgConfig) include(FindPkgConfig)
################################################################# #################################################################
@@ -135,6 +137,10 @@ else()
endif() endif()
set (PIPEDAL_SOURCES set (PIPEDAL_SOURCES
AutoLilvNode.hpp
AutoLilvNode.cpp
PiPedalUI.hpp
PiPedalUI.cpp
RtInversionGuard.hpp RtInversionGuard.hpp
CpuUse.hpp CpuUse.cpp CpuUse.hpp CpuUse.cpp
P2pConfigFiles.hpp P2pConfigFiles.hpp
@@ -154,7 +160,9 @@ set (PIPEDAL_SOURCES
WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp
ConfigUtil.hpp ConfigUtil.cpp ConfigUtil.hpp ConfigUtil.cpp
RequestHandler.hpp json.cpp json.hpp Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp RequestHandler.hpp json.cpp json.hpp
json_variant.hpp json_variant.cpp
Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp
PluginType.hpp PluginType.cpp PluginType.hpp PluginType.cpp
Lv2Log.hpp Lv2Log.cpp Lv2Log.hpp Lv2Log.cpp
PiPedalSocket.hpp PiPedalSocket.cpp PiPedalSocket.hpp PiPedalSocket.cpp
+36
View File
@@ -91,3 +91,39 @@ void LogFeature::Prepare(MapFeature*map)
} }
void LogFeature::LogError(const char*fmt,...)
{
va_list va;
va_start(va, fmt);
vprintf(uris.ridError,fmt,va);
}
void LogFeature::LogWarning(const char*fmt,...)
{
va_list va;
va_start(va, fmt);
vprintf(uris.ridWarning,fmt,va);
}
void LogFeature::LogNote(const char*fmt,...)
{
va_list va;
va_start(va, fmt);
vprintf(uris.ridNote,fmt,va);
}
void LogFeature::LogTrace(const char*fmt,...)
{
{
va_list va;
va_start(va, fmt);
vprintf(uris.ridNote,fmt,va);
}
}
+6
View File
@@ -61,6 +61,12 @@ namespace pipedal {
LogFeature(); LogFeature();
void Prepare(MapFeature* map); void Prepare(MapFeature* map);
void LogError(const char*fmt,...);
void LogWarning(const char*fmt,...);
void LogNote(const char*fmt,...);
void LogTrace(const char*fmt,...);
public: public:
const LV2_Feature* GetFeature() const LV2_Feature* GetFeature()
{ {
+2 -2
View File
@@ -40,9 +40,9 @@ TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) {
} }
MemStats finalMemory = GetMemStats(); MemStats finalMemory = GetMemStats();
// Something lilv leaks a Dublin Core url. // Something in lilv leaks a Dublin Core url.
// Acceptable. // Acceptable.
const int ACCEPTABLE_ALLOCATION_LEAKS = 4; const int ACCEPTABLE_ALLOCATION_LEAKS = 6;
const int ACCEPTABLE_MEMORY_LEAK = 400; const int ACCEPTABLE_MEMORY_LEAK = 400;
if (finalMemory.allocations > initialMemory.allocations + ACCEPTABLE_ALLOCATION_LEAKS if (finalMemory.allocations > initialMemory.allocations + ACCEPTABLE_ALLOCATION_LEAKS
+22 -4
View File
@@ -53,6 +53,18 @@ PedalBoardItem*PedalBoard::GetItem(long pedalItemId)
return const_cast<PedalBoardItem*>(GetItem_(this->items(),pedalItemId)); return const_cast<PedalBoardItem*>(GetItem_(this->items(),pedalItemId));
} }
PropertyValue*PedalBoardItem::GetPropertyValue(const std::string&propertyUri)
{
for (auto&propertyValue: this->propertyValues_)
{
if (propertyValue.propertyUri() == propertyUri)
{
return &propertyValue;
}
}
return nullptr;
}
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol) ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
{ {
for (size_t i = 0; i < this->controlValues().size(); ++i) for (size_t i = 0; i < this->controlValues().size(); ++i)
@@ -164,6 +176,10 @@ PedalBoard PedalBoard::MakeDefault()
} }
bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector<PedalBoardItem>&value)
{
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
}
@@ -176,17 +192,19 @@ JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,value) JSON_MAP_REFERENCE(ControlValue,value)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PropertyValue)
JSON_MAP_REFERENCE(PropertyValue,propertyUri)
JSON_MAP_REFERENCE(PropertyValue,value)
JSON_MAP_END()
bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector<PedalBoardItem>&value)
{
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
}
JSON_MAP_BEGIN(PedalBoardItem) JSON_MAP_BEGIN(PedalBoardItem)
JSON_MAP_REFERENCE(PedalBoardItem,instanceId) JSON_MAP_REFERENCE(PedalBoardItem,instanceId)
JSON_MAP_REFERENCE(PedalBoardItem,uri) JSON_MAP_REFERENCE(PedalBoardItem,uri)
JSON_MAP_REFERENCE(PedalBoardItem,isEnabled) JSON_MAP_REFERENCE(PedalBoardItem,isEnabled)
JSON_MAP_REFERENCE(PedalBoardItem,controlValues) JSON_MAP_REFERENCE(PedalBoardItem,controlValues)
JSON_MAP_REFERENCE(PedalBoardItem,propertyValues)
JSON_MAP_REFERENCE(PedalBoardItem,pluginName) JSON_MAP_REFERENCE(PedalBoardItem,pluginName)
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,topChain,IsPedalBoardSplitItem) JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,topChain,IsPedalBoardSplitItem)
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,bottomChain,&IsPedalBoardSplitItem) JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,bottomChain,&IsPedalBoardSplitItem)
+29 -1
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include "json.hpp" #include "json.hpp"
#include "json_variant.hpp"
#include "MidiBinding.hpp" #include "MidiBinding.hpp"
namespace pipedal { namespace pipedal {
@@ -72,25 +73,52 @@ public:
}; };
class PropertyValue {
private:
std::string propertyUri_;
json_variant value_;
public:
PropertyValue()
{
class PedalBoardItem: public JsonWritable { }
template <typename T>
PropertyValue(const std::string&propertyUri, T value)
:propertyUri_(propertyUri)
, value_(value)
{
}
GETTER_SETTER_REF(propertyUri)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(PropertyValue);
};
class PedalBoardItem: public JsonMemberWritable {
int64_t instanceId_ = 0; int64_t instanceId_ = 0;
std::string uri_; std::string uri_;
std::string pluginName_; std::string pluginName_;
bool isEnabled_ = true; bool isEnabled_ = true;
std::vector<ControlValue> controlValues_; std::vector<ControlValue> controlValues_;
std::vector<PropertyValue> propertyValues_;
std::vector<PedalBoardItem> topChain_; std::vector<PedalBoardItem> topChain_;
std::vector<PedalBoardItem> bottomChain_; std::vector<PedalBoardItem> bottomChain_;
std::vector<MidiBinding> midiBindings_; std::vector<MidiBinding> midiBindings_;
std::string vstState_; std::string vstState_;
public: public:
ControlValue*GetControlValue(const std::string&symbol); ControlValue*GetControlValue(const std::string&symbol);
PropertyValue*GetPropertyValue(const std::string&propertyUri);
GETTER_SETTER(instanceId) GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri) GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(vstState); GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName) GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled) GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues) GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(propertyValues)
GETTER_SETTER_VEC(topChain) GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain) GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings) GETTER_SETTER_VEC(midiBindings)
+103 -106
View File
@@ -34,6 +34,7 @@
#include "lv2.h" #include "lv2.h"
#include "lv2/atom.lv2/atom.h" #include "lv2/atom.lv2/atom.h"
#include "lv2/time/time.h" #include "lv2/time/time.h"
#include "lv2/state/state.h"
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
#include "lv2/lv2plug.in/ns/ext/presets/presets.h" #include "lv2/lv2plug.in/ns/ext/presets/presets.h"
#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" #include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
@@ -103,7 +104,6 @@ void PiPedalHost::SetConfiguration(const PiPedalConfiguration &configuration)
this->vst3Enabled = configuration.IsVst3Enabled(); this->vst3Enabled = configuration.IsVst3Enabled();
} }
void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld) void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
{ {
rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI); rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI);
@@ -124,6 +124,18 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
symbolUri = lilv_new_uri(pWorld, LV2_CORE__symbol); symbolUri = lilv_new_uri(pWorld, LV2_CORE__symbol);
nameUri = lilv_new_uri(pWorld, LV2_CORE__name); nameUri = lilv_new_uri(pWorld, LV2_CORE__name);
lv2Core__name = lilv_new_uri(pWorld, LV2_CORE__name);
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
pipedalUI__fileProperties = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperties);
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
pipedalUI__directory = lilv_new_uri(pWorld,PIPEDAL_UI__directory);
pipedalUI__fileTypes = lilv_new_uri(pWorld,PIPEDAL_UI__fileTypes);
pipedalUI__fileProperty = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperty);
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text);
pipedalUI__defaultFile = lilv_new_uri(pWorld, PIPEDAL_UI__defaultFile);
time_Position = lilv_new_uri(pWorld, LV2_TIME__Position); time_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat); time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
time_beatsPerMinute = lilv_new_uri(pWorld, LV2_TIME__beatsPerMinute); time_beatsPerMinute = lilv_new_uri(pWorld, LV2_TIME__beatsPerMinute);
@@ -202,54 +214,6 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0)
return default_; return default_;
} }
class NodeAutoFree : public LilvNodePtr
{
// const LilvNode* returns must not be freed, by convention.
private:
NodeAutoFree(const LilvNode *node)
{
}
public:
NodeAutoFree()
{
}
NodeAutoFree(LilvNode *node)
: LilvNodePtr(node)
{
}
float as_float(float defaultValue = 0)
{
return nodeAsFloat(this->node, defaultValue);
}
int as_int()
{
if (node == nullptr)
return 0;
if (lilv_node_is_int(node))
return lilv_node_as_int(node);
return 0;
}
bool as_bool()
{
if (node == nullptr)
return false;
if (lilv_node_is_int(node))
return lilv_node_as_bool(node);
return false;
}
std::string as_string()
{
return nodeAsString(this->node);
}
~NodeAutoFree()
{
}
};
PiPedalHost::PiPedalHost() PiPedalHost::PiPedalHost()
{ {
pWorld = nullptr; pWorld = nullptr;
@@ -418,7 +382,7 @@ void PiPedalHost::Load(const char *lv2Path)
{ {
const LilvPlugin *lilvPlugin = lilv_plugins_get(plugins, iPlugin); const LilvPlugin *lilvPlugin = lilv_plugins_get(plugins, iPlugin);
std::shared_ptr<Lv2PluginInfo> pluginInfo = std::make_shared<Lv2PluginInfo>(this, lilvPlugin); std::shared_ptr<Lv2PluginInfo> pluginInfo = std::make_shared<Lv2PluginInfo>(this, pWorld, lilvPlugin);
Lv2Log::debug("Plugin: " + pluginInfo->name()); Lv2Log::debug("Plugin: " + pluginInfo->name());
@@ -573,7 +537,7 @@ const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schem
LilvNode *PiPedalHost::get_comment(const std::string &uri) LilvNode *PiPedalHost::get_comment(const std::string &uri)
{ {
NodeAutoFree uriNode = lilv_new_uri(pWorld, uri.c_str()); AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr); LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
return result; return result;
} }
@@ -595,21 +559,19 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl
return result; return result;
} }
Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin) Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
{ {
const LilvNode *pluginUri = lilv_plugin_get_uri(pPlugin);
this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin); this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin);
this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin)); this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin));
NodeAutoFree name = (lilv_plugin_get_name(pPlugin)); AutoLilvNode name = (lilv_plugin_get_name(pPlugin));
this->name_ = nodeAsString(name); this->name_ = nodeAsString(name);
NodeAutoFree author_name = (lilv_plugin_get_author_name(pPlugin)); AutoLilvNode author_name = (lilv_plugin_get_author_name(pPlugin));
this->author_name_ = nodeAsString(author_name); this->author_name_ = nodeAsString(author_name);
NodeAutoFree author_homepage = (lilv_plugin_get_author_homepage(pPlugin)); AutoLilvNode author_homepage = (lilv_plugin_get_author_homepage(pPlugin));
this->author_homepage_ = nodeAsString(author_homepage); this->author_homepage_ = nodeAsString(author_homepage);
const LilvPluginClass *pClass = lilv_plugin_get_class(pPlugin); const LilvPluginClass *pClass = lilv_plugin_get_class(pPlugin);
@@ -627,7 +589,7 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin)
NodesAutoFree extensions = lilv_plugin_get_extension_data(pPlugin); NodesAutoFree extensions = lilv_plugin_get_extension_data(pPlugin);
this->extensions_ = nodeAsStringArray(extensions); this->extensions_ = nodeAsStringArray(extensions);
NodeAutoFree comment = lv2Host->get_comment(this->uri_); AutoLilvNode comment = lv2Host->get_comment(this->uri_);
this->comment_ = nodeAsString(comment); this->comment_ = nodeAsString(comment);
uint32_t ports = lilv_plugin_get_num_ports(pPlugin); uint32_t ports = lilv_plugin_get_num_ports(pPlugin);
@@ -671,6 +633,26 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin)
std::sort(ports_.begin(), ports_.end(), ports_sort_compare); std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
// Fetch pipedal plugin UI
AutoLilvNode pipedalUINode = lilv_world_get(
pWorld,
lilv_plugin_get_uri(pPlugin),
lv2Host->lilvUris.pipedalUI__ui,
nullptr);
if (pipedalUINode)
{
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode);
}
// xxx lilv_world_get(pWorld,pluginUri,);
// for (auto&portInfo: ports_)
// {
// if (portInfo->is_control_port() && portInfo->is_output())
// {
// std::cout << "Dbg: " << "Has an output control port. " << this->uri_ << std::endl;
// }
// }
this->is_valid_ = isValid; this->is_valid_ = isValid;
} }
@@ -684,6 +666,7 @@ std::vector<std::string> supportedFeatures = {
LV2_BUF_SIZE__fixedBlockLength, LV2_BUF_SIZE__fixedBlockLength,
LV2_BUF_SIZE__powerOf2BlockLength, LV2_BUF_SIZE__powerOf2BlockLength,
LV2_CORE__isLive, LV2_CORE__isLive,
LV2_STATE__loadDefaultState
}; };
@@ -728,16 +711,16 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
index_ = lilv_port_get_index(plugin, pPort); index_ = lilv_port_get_index(plugin, pPort);
symbol_ = nodeAsString(lilv_port_get_symbol(plugin, pPort)); symbol_ = nodeAsString(lilv_port_get_symbol(plugin, pPort));
NodeAutoFree name = lilv_port_get_name(plugin, pPort); AutoLilvNode name = lilv_port_get_name(plugin, pPort);
name_ = nodeAsString(name); name_ = nodeAsString(name);
classes_ = host->GetPluginPortClass(plugin, pPort); classes_ = host->GetPluginPortClass(plugin, pPort);
NodeAutoFree minNode, maxNode, defaultNode; AutoLilvNode minNode, maxNode, defaultNode;
min_value_ = 0; min_value_ = 0;
max_value_ = 1; max_value_ = 1;
default_value_ = 0; default_value_ = 0;
lilv_port_get_range(plugin, pPort, &defaultNode, &minNode, &maxNode); lilv_port_get_range(plugin, pPort, &defaultNode.Get(), &minNode.Get(), &maxNode.Get());
if (defaultNode) if (defaultNode)
{ {
default_value_ = getFloat(defaultNode); default_value_ = getFloat(defaultNode);
@@ -812,19 +795,19 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midiEventNode); supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midiEventNode);
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position); supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position);
NodeAutoFree designationValue = lilv_port_get(plugin, pPort, host->lilvUris.designationNode); AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris.designationNode);
designation_ = nodeAsString(designationValue); designation_ = nodeAsString(designationValue);
NodeAutoFree portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portGroupUri); AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portGroupUri);
port_group_ = nodeAsString(portGroup_value); port_group_ = nodeAsString(portGroup_value);
NodeAutoFree unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.unitsUri); AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.unitsUri);
this->units_ = UriToUnits(nodeAsString(unitsValueUri)); this->units_ = UriToUnits(nodeAsString(unitsValueUri));
NodeAutoFree commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment); AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment);
this->comment_ = nodeAsString(commentNode); this->comment_ = nodeAsString(commentNode);
NodeAutoFree bufferType = lilv_port_get(plugin, pPort, host->lilvUris.bufferType_uri); AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris.bufferType_uri);
this->buffer_type_ = ""; this->buffer_type_ = "";
if (bufferType) if (bufferType)
@@ -953,6 +936,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
{ {
this->port_groups_.push_back(Lv2PluginUiPortGroup(portGroup.get())); this->port_groups_.push_back(Lv2PluginUiPortGroup(portGroup.get()));
} }
auto &piPedalUI = plugin->piPedalUI();
if (piPedalUI)
{
this->fileProperties_ = piPedalUI->fileProperties();
}
} }
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetLv2PluginClass() const std::shared_ptr<Lv2PluginClass> PiPedalHost::GetLv2PluginClass() const
@@ -1018,7 +1007,7 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld); const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
NodeAutoFree uriNode = lilv_new_uri(pWorld, pedalBoardItem->uri().c_str()); AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalBoardItem->uri().c_str());
lilv_world_load_resource(pWorld, uriNode); lilv_world_load_resource(pWorld, uriNode);
@@ -1040,7 +1029,7 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
/*********************************/ /*********************************/
// NodeAutoFree uriNode = lilv_new_uri(pWorld, presetUri.c_str()); // AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset); auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
@@ -1091,7 +1080,7 @@ PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
{ {
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld); const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
NodeAutoFree uriNode = lilv_new_uri(pWorld, pluginUri.c_str()); AutoLilvNode uriNode = lilv_new_uri(pWorld, pluginUri.c_str());
lilv_world_load_resource(pWorld, uriNode); lilv_world_load_resource(pWorld, uriNode);
@@ -1133,7 +1122,7 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
{ {
if (pedalBoardItem.uri().starts_with("vst3:")) if (pedalBoardItem.uri().starts_with("vst3:"))
{ {
#if ENABLE_VST3 #if ENABLE_VST3
try try
{ {
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this); Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this);
@@ -1144,11 +1133,11 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what()); Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
throw; throw;
} }
#else #else
Lv2Log::error(std::string("VST3 support not enabled at compile time.")); Lv2Log::error(std::string("VST3 support not enabled at compile time."));
throw PiPedalException("VST3 support not enabled at compile time, SEE ENABLE_VST3 in CMakeList.txt."); throw PiPedalException("VST3 support not enabled at compile time, SEE ENABLE_VST3 in CMakeList.txt.");
#endif #endif
} }
else else
{ {
@@ -1165,13 +1154,19 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
LilvWorld *pWorld = lv2Host->pWorld; LilvWorld *pWorld = lv2Host->pWorld;
this->uri_ = groupUri; this->uri_ = groupUri;
NodeAutoFree uri = lilv_new_uri(pWorld, groupUri.c_str()); AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.symbolUri, nullptr); LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.symbolUri, nullptr);
symbol_ = nodeAsString(symbolNode); symbol_ = nodeAsString(symbolNode);
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.nameUri, nullptr); LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.nameUri, nullptr);
name_ = nodeAsString(nameNode); name_ = nodeAsString(nameNode);
} }
void PiPedalHostLogError(const std::string&errror)
{
}
#define MAP_REF(class, name) \ #define MAP_REF(class, name) \
json_map::reference(#name, &class ::name##_) json_map::reference(#name, &class ::name##_)
@@ -1188,45 +1183,47 @@ json_map::storage_type<Lv2ScalePoint> Lv2ScalePoint::jmap{{
json_map::reference("label", &Lv2ScalePoint::label_), json_map::reference("label", &Lv2ScalePoint::label_),
}}; }};
json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{{json_map::reference("index", &Lv2PortInfo::index_), json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
json_map::reference("symbol", &Lv2PortInfo::symbol_), {json_map::reference("index", &Lv2PortInfo::index_),
json_map::reference("index", &Lv2PortInfo::index_),
json_map::reference("name", &Lv2PortInfo::name_),
json_map::reference("min_value", &Lv2PortInfo::min_value_), json_map::reference("symbol", &Lv2PortInfo::symbol_),
json_map::reference("max_value", &Lv2PortInfo::max_value_), json_map::reference("index", &Lv2PortInfo::index_),
json_map::reference("default_value", &Lv2PortInfo::default_value_), json_map::reference("name", &Lv2PortInfo::name_),
json_map::reference("classes", &Lv2PortInfo::classes_),
json_map::reference("scale_points", &Lv2PortInfo::scale_points_), json_map::reference("min_value", &Lv2PortInfo::min_value_),
json_map::reference("max_value", &Lv2PortInfo::max_value_),
json_map::reference("default_value", &Lv2PortInfo::default_value_),
json_map::reference("classes", &Lv2PortInfo::classes_),
json_map::reference("is_input", &Lv2PortInfo::is_input_), json_map::reference("scale_points", &Lv2PortInfo::scale_points_),
json_map::reference("is_output", &Lv2PortInfo::is_output_),
json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_), json_map::reference("is_input", &Lv2PortInfo::is_input_),
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_), json_map::reference("is_output", &Lv2PortInfo::is_output_),
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
json_map::reference("is_valid", &Lv2PortInfo::is_valid_), json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_),
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
json_map::reference("supports_midi", &Lv2PortInfo::supports_midi_), json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
json_map::reference("supports_time_position", &Lv2PortInfo::supports_time_position_),
json_map::reference("port_group", &Lv2PortInfo::port_group_),
json_map::reference("is_logarithmic", &Lv2PortInfo::is_logarithmic_),
MAP_REF(Lv2PortInfo, is_logarithmic), json_map::reference("supports_midi", &Lv2PortInfo::supports_midi_),
MAP_REF(Lv2PortInfo, display_priority), json_map::reference("supports_time_position", &Lv2PortInfo::supports_time_position_),
MAP_REF(Lv2PortInfo, range_steps), json_map::reference("port_group", &Lv2PortInfo::port_group_),
MAP_REF(Lv2PortInfo, trigger), json_map::reference("is_logarithmic", &Lv2PortInfo::is_logarithmic_),
MAP_REF(Lv2PortInfo, integer_property),
MAP_REF(Lv2PortInfo, enumeration_property), MAP_REF(Lv2PortInfo, is_logarithmic),
MAP_REF(Lv2PortInfo, toggled_property), MAP_REF(Lv2PortInfo, display_priority),
MAP_REF(Lv2PortInfo, not_on_gui), MAP_REF(Lv2PortInfo, range_steps),
MAP_REF(Lv2PortInfo, buffer_type), MAP_REF(Lv2PortInfo, trigger),
MAP_REF(Lv2PortInfo, port_group), MAP_REF(Lv2PortInfo, integer_property),
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()), MAP_REF(Lv2PortInfo, enumeration_property),
MAP_REF(Lv2PortInfo, comment)}}; MAP_REF(Lv2PortInfo, toggled_property),
MAP_REF(Lv2PortInfo, not_on_gui),
MAP_REF(Lv2PortInfo, buffer_type),
MAP_REF(Lv2PortInfo, port_group),
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
MAP_REF(Lv2PortInfo, comment)}};
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
MAP_REF(Lv2PortGroup, uri), MAP_REF(Lv2PortGroup, uri),
@@ -1308,5 +1305,5 @@ json_map::storage_type<Lv2PluginUiInfo> Lv2PluginUiInfo::jmap{{
json_map::reference("controls", &Lv2PluginUiInfo::controls_), json_map::reference("controls", &Lv2PluginUiInfo::controls_),
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_), json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_), json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
json_map::reference("fileProperties",&Lv2PluginUiInfo::fileProperties_)
}}; }};
+584 -554
View File
File diff suppressed because it is too large Load Diff
+21 -1
View File
@@ -1467,6 +1467,21 @@ void PiPedalModel::UpdateDefaults(PedalBoardItem *pedalBoardItem)
} }
} }
} }
if (pPlugin->piPedalUI())
{
auto&piPedalUi = pPlugin->piPedalUI();
for (auto &fileProperty : piPedalUi->fileProperties())
{
PropertyValue *pValue = pedalBoardItem->GetPropertyValue(fileProperty->patchProperty());
if (pValue == nullptr)
{
// missing? set it to default value
pedalBoardItem->propertyValues().push_back(
PropertyValue(fileProperty->patchProperty(),fileProperty->defaultFile())
);
}
}
}
} }
for (size_t i = 0; i < pedalBoardItem->topChain().size(); ++i) for (size_t i = 0; i < pedalBoardItem->topChain().size(); ++i)
{ {
@@ -1716,4 +1731,9 @@ void PiPedalModel::SetSystemMidiBindings(std::vector<MidiBinding> &bindings)
delete[] t; delete[] t;
} }
std::vector<std::string> PiPedalModel::GetFileList(const PiPedalFileProperty&fileProperty)
{
return this->storage.GetFileList(PiPedalFilesProperty&fileProperty);
}
+2
View File
@@ -281,6 +281,8 @@ namespace pipedal
std::map<std::string, bool> GetFavorites() const; std::map<std::string, bool> GetFavorites() const;
void SetFavorites(const std::map<std::string, bool> &favorites); void SetFavorites(const std::map<std::string, bool> &favorites);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
}; };
} // namespace pipedal. } // namespace pipedal.
+6
View File
@@ -1246,6 +1246,12 @@ public:
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings(); std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
this->Reply(replyTo,"getSystemMidiBindings",bindings); this->Reply(replyTo,"getSystemMidiBindings",bindings);
} }
else if (message == "requestFileList")
{
PiPedalFileProperty fileProperty;
std::vector<std::string> list = this->model.GetFileList(fileProperty);
this->Reply(replyTo,"requestFileList",list);
}
else else
{ {
Lv2Log::error("Unknown message received: %s", message.c_str()); Lv2Log::error("Unknown message received: %s", message.c_str());
+170
View File
@@ -0,0 +1,170 @@
/*
* 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 "PiPedalUI.hpp"
#include "PiPedalHost.hpp"
using namespace pipedal;
PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
{
auto pWorld = pHost->getWorld();
LilvNodes *fileNodes = lilv_world_find_nodes(pWorld, uiNode, pHost->lilvUris.pipedalUI__fileProperties, nullptr);
LILV_FOREACH(nodes, i, fileNodes)
{
const LilvNode *fileNode = lilv_nodes_get(fileNodes, i);
try
{
PiPedalFileProperty::ptr fileUI = std::make_shared<PiPedalFileProperty>(pHost, fileNode);
this->fileProperites_.push_back(std::move(fileUI));
}
catch (const std::exception &e)
{
pHost->LogError(e.what());
}
}
lilv_nodes_free(fileNodes);
}
PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) {
auto pWorld = pHost->getWorld();
AutoLilvNode name = lilv_world_get(
pWorld,
node,
pHost->lilvUris.lv2Core__name,
nullptr);
if (name)
{
this->name_ = name.AsString();
}
else
{
throw std::logic_error("pipedal_ui:fileType is missing name property.");
}
AutoLilvNode fileExtension = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__fileExtension,
nullptr);
if (fileExtension)
{
this->fileExtension_ = fileExtension.AsString();
}
else
{
throw std::logic_error("pipedal_ui:fileType is missing fileExtension property.");
}
}
PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *node)
{
auto pWorld = pHost->getWorld();
AutoLilvNode name = lilv_world_get(
pWorld,
node,
pHost->lilvUris.lv2Core__name,
nullptr);
if (name)
{
this->name_ = name.AsString();
}
else
{
this->name_ = "File";
}
AutoLilvNode directory = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__directory,
nullptr);
if (directory)
{
this->directory_ = name.AsString();
}
else
{
throw std::logic_error("PiPedal FileProperty is missing a pipedalui:directory value.");
}
AutoLilvNode patchProperty = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__patchProperty,
nullptr);
if (patchProperty)
{
this->patchProperty_ = patchProperty.AsUri();
} else {
throw std::logic_error("PiPedal FileProperty is missing pipedalui:patchProperty value.");
}
AutoLilvNode defaultFile = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__defaultFile,
nullptr);
this->defaultFile_ = defaultFile.AsString();
this->fileTypes_ = PiPedalFileType::GetArray(pHost,node,pHost->lilvUris.pipedalUI__fileTypes);
}
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri)
{
std::vector<PiPedalFileType> result;
LilvWorld* pWorld = pHost->getWorld();
LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr);
LILV_FOREACH(nodes, i, fileTypeNodes)
{
const LilvNode *fileTypeNode = lilv_nodes_get(fileTypeNodes, i);
try
{
PiPedalFileType fileType = PiPedalFileType(pHost, fileTypeNode);
result.push_back(std::move(fileType));
}
catch (const std::exception &e)
{
pHost->LogError(e.what());
}
}
lilv_nodes_free(fileTypeNodes);
return result;
}
JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType,name)
JSON_MAP_REFERENCE(PiPedalFileType,fileExtension)
JSON_MAP_END()
JSON_MAP_BEGIN(PiPedalFileProperty)
JSON_MAP_REFERENCE(PiPedalFileProperty,patchProperty)
JSON_MAP_REFERENCE(PiPedalFileProperty,name)
JSON_MAP_REFERENCE(PiPedalFileProperty,defaultFile)
JSON_MAP_REFERENCE(PiPedalFileProperty,fileTypes)
JSON_MAP_END()
+111
View File
@@ -0,0 +1,111 @@
/*
* 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 <string>
#include <memory>
#include <lilv/lilv.h>
#include "json.hpp"
#define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui"
#define PIPEDAL_UI_PREFIX PIPEDAL_UI "#"
#define PIPEDAL_UI__ui PIPEDAL_UI_PREFIX "ui"
#define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties"
#define PIPEDAL_UI__fileProperty PIPEDAL_UI_PREFIX "fileProperty"
#define PIPEDAL_UI__defaultFile PIPEDAL_UI_PREFIX "defaultFile"
#define PIPEDAL_UI__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
#define PIPEDAL_UI__fileType PIPEDAL_UI_PREFIX "fileType"
#define PIPEDAL_UI__fileExtension PIPEDAL_UI_PREFIX "fileExtension"
#define PIPEDAL_UI__outputPorts PIPEDAL_UI_PREFIX "outputPorts"
#define PIPEDAL_UI__text PIPEDAL_UI_PREFIX "text"
namespace pipedal {
class PiPedalHost;
class PiPedalFileType {
private:
std::string name_;
std::string fileExtension_;
public:
PiPedalFileType() { }
PiPedalFileType(PiPedalHost*pHost, const LilvNode*node);
static std::vector<PiPedalFileType> GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri);
const std::string& name() const { return name_;}
const std::string &fileExtension() const { return fileExtension_; }
public:
DECLARE_JSON_MAP(PiPedalFileType);
};
class PiPedalFileProperty {
private:
std::string name_;
std::string directory_;
std::vector<PiPedalFileType> fileTypes_;
std::string patchProperty_;
std::string defaultFile_;
public:
using ptr = std::shared_ptr<PiPedalFileProperty>;
PiPedalFileProperty() { }
PiPedalFileProperty(PiPedalHost*pHost, const LilvNode*node);
const std::string &name() const { return name_; }
const std::string &directory() const { return directory_; }
const std::string &defaultFile() const { return defaultFile_; }
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
const std::string &patchProperty() const { return patchProperty_; }
public:
DECLARE_JSON_MAP(PiPedalFileProperty);
};
class PiPedalUI {
public:
using ptr = std::shared_ptr<PiPedalUI>;
PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode);
const std::vector<PiPedalFileProperty::ptr>& fileProperties() const
{
return fileProperites_;
}
private:
std::vector<PiPedalFileProperty::ptr> fileProperites_;
};
};
+54
View File
@@ -27,6 +27,7 @@
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <map> #include <map>
#include <sys/stat.h> #include <sys/stat.h>
#include "PiPedalUI.hpp"
using namespace pipedal; using namespace pipedal;
@@ -254,6 +255,11 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{ {
return this->dataRoot / "plugin_presets"; return this->dataRoot / "plugin_presets";
} }
std::filesystem::path Storage::GetAudioFilesDirectory() const
{
return this->dataRoot / "audio_uploads";
}
std::filesystem::path Storage::GetCurrentPresetPath() const std::filesystem::path Storage::GetCurrentPresetPath() const
{ {
return this->dataRoot / "currentPreset.json"; return this->dataRoot / "currentPreset.json";
@@ -1383,6 +1389,54 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
return result; return result;
} }
static bool containsDotDot(const std::string&value)
{
std::size_t offset = value.find("..");
return offset != std::string::npos;
}
static bool containsDirectorySeparator(const std::string&value)
{
if (value.find("/") != std::string::npos) return true; //linux
if (value.find("\\") != std::string::npos) return true; // windows
if (value.find("::") != std::string::npos) return true; // mac
return false;
}
static bool containsNonAlphaNumericCharacter(const std::string&value)
{
for (char c:value)
{
if (
(c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z)
|| (c == '_')
) {
continue;
}
return false;
}
return true;
}
static void sanityCheckDirectory(const std::string&directory)
{
// we can afford to be highly restrictive here. Alpha-numeric only.
if (containsNonAlphaNumericCharacter(directory))
{
throw std::logic_error("Invalid directory name.");
}
}
std::vector<std::string> Storage::GetFileList(const PiPedalFileProperty&fileProperty)
{
sanityCheckDirectory(fileProperty.directory());
std::filesystem::path path = this->GetAudioFilesDirectory() / fileProperty.directory();
}
JSON_MAP_BEGIN(UserSettings) JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, governor)
+4
View File
@@ -33,6 +33,7 @@
namespace pipedal { namespace pipedal {
class PiPedalFileProperty;
class CurrentPreset { class CurrentPreset {
public: public:
@@ -67,6 +68,7 @@ private:
static std::string SafeDecodeName(const std::string& name); static std::string SafeDecodeName(const std::string& name);
std::filesystem::path GetPresetsDirectory() const; std::filesystem::path GetPresetsDirectory() const;
std::filesystem::path GetPluginPresetsDirectory() const; std::filesystem::path GetPluginPresetsDirectory() const;
std::filesystem::path GetAudioFilesDirectory() const
std::filesystem::path GetIndexFileName() const; std::filesystem::path GetIndexFileName() const;
std::filesystem::path GetBankFileName(const std::string & name) const; std::filesystem::path GetBankFileName(const std::string & name) const;
std::filesystem::path GetChannelSelectionFileName(); std::filesystem::path GetChannelSelectionFileName();
@@ -134,6 +136,8 @@ public:
void MoveBank(int from, int to); void MoveBank(int from, int to);
int64_t DeleteBank(int64_t bankId); int64_t DeleteBank(int64_t bankId);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
void SetJackChannelSelection(const JackChannelSelection&channelSelection); void SetJackChannelSelection(const JackChannelSelection&channelSelection);
const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration); const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration);
+10
View File
@@ -39,6 +39,8 @@
#include <mutex> #include <mutex>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h> #include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <iostream>
#include <unistd.h> // for nice()
using namespace pipedal; using namespace pipedal;
@@ -103,6 +105,14 @@ void Worker::EmitResponses()
} }
void Worker::ThreadProc() void Worker::ThreadProc()
{ {
// run nice +1 (priority -1 on Windows)
errno = 0;
nice(1);
if (errno != 0)
{
std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl;
}
try try
{ {
while (true) while (true)
+2 -1
View File
@@ -8,5 +8,6 @@
"http://two-play.com/plugins/toob-power-stage-2": true, "http://two-play.com/plugins/toob-power-stage-2": true,
"http://two-play.com/plugins/toob-spectrum": true, "http://two-play.com/plugins/toob-spectrum": true,
"http://two-play.com/plugins/toob-tone-stack": true, "http://two-play.com/plugins/toob-tone-stack": true,
"http://two-play.com/plugins/toob-tuner": true "http://two-play.com/plugins/toob-tuner": true,
"http://two-play.com/plugins/toob-convolution-reverb": true
} }
+6
View File
@@ -22,6 +22,7 @@
#include <string_view> #include <string_view>
#include <cctype> #include <cctype>
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
#include "json_variant.hpp"
using namespace pipedal; using namespace pipedal;
@@ -631,3 +632,8 @@ void json_reader::throw_format_error(const char*error)
throw PiPedalException(message); throw PiPedalException(message);
} }
// void json_writer::write(const json_variant &value)
// {
// ((JsonSerializable *)&value)->write_json(*this);
// }
+888 -839
View File
File diff suppressed because it is too large Load Diff
+126 -1
View File
@@ -25,6 +25,9 @@
#include "json.hpp" #include "json.hpp"
#include "json_variant.hpp"
#include <concepts>
#include <type_traits>
using namespace pipedal; using namespace pipedal;
@@ -94,6 +97,7 @@ json_map::storage_type<JsonTestTarget> JsonTestTarget::jmap {{
TEST_CASE( "json write", "[json_write_test]" ) { TEST_CASE( "json write", "[json_write_test]" ) {
std::cout << "== json write ==" << std::endl;
std::stringstream os; std::stringstream os;
json_writer writer { os }; json_writer writer { os };
@@ -101,7 +105,7 @@ TEST_CASE( "json write", "[json_write_test]" ) {
writer.write(testTarget); writer.write(testTarget);
std::cout << os.str(); std::cout << os.str() << std::endl;
} }
static std::string get_json() static std::string get_json()
@@ -166,4 +170,125 @@ TEST_CASE( "json smart ptrs", "[json_smart_ptrs][Build][Dev]" ) {
reader.read(&sharedPtr); reader.read(&sharedPtr);
} }
}
template <typename T>
void TestVariantRoundTrip(const T &value)
{
json_variant variant(value);
T out = variant.get<T>();
REQUIRE(out == value);
std::string output;
{
std::stringstream s;
json_writer writer(s);
writer.write(variant);
output = s.str();
}
std::cout << output << std::endl;
{
std::stringstream s(output);
json_reader reader(s);
json_variant outputVariant;
reader.read(&outputVariant);
REQUIRE(outputVariant == variant);
}
}
void TestVariantRoundTrip(json_variant &value)
{
json_variant variant(value);
REQUIRE(variant == value);
std::string output;
{
std::stringstream s;
json_writer writer(s);
writer.write(variant);
output = s.str();
}
std::cout << output << std::endl;
json_variant outputVariant;
{
std::stringstream s(output);
json_reader reader(s);
json_variant outputVariant;
reader.read(&outputVariant);
REQUIRE(outputVariant == variant);
}
}
class X{
public:
template<typename T>
requires std::derived_from<T,JsonSerializable>
bool write(T &v)
{
(void)v;
return true;
}
template <typename T>
bool write(T &v)
{
(void)v;
return false;
}
};
void TestVariantSFINAE()
{
X x;
json_variant v;
dynamic_cast<JsonSerializable&>(v);
REQUIRE(x.write(v) == true);
int i;
REQUIRE(x.write(i) == false);
}
TEST_CASE( "json variants", "[json_variants][Build][Dev]" ) {
TestVariantSFINAE();
json_variant v(0);
TestVariantRoundTrip(json_null());
TestVariantRoundTrip(0.0);
TestVariantRoundTrip(std::string("abc"));
json_array array;
array.push_back(json_null());
array.push_back(3.25E19);
array.push_back(std::string("abc"));
json_variant variantArray { std::move(array) };
TestVariantRoundTrip(variantArray);
{
json_object obj;
obj["a"] = json_null();
obj["b"] = 0.25;
obj["c"] = std::move(variantArray);
json_variant variantObj { std::move(obj)};
TestVariantRoundTrip(variantObj);
variantObj["a"] = std::string("abc");
TestVariantRoundTrip(variantObj);
}
{
json_variant x = json_variant::MakeArray();
x.resize(3);
x[0] = "def";
}
} }
+44
View File
@@ -0,0 +1,44 @@
/*
* 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 "json_variant.hpp"
#include <limits>
#include <cmath>
#include <cstddef>
using namespace pipedal;
void concrete_json_variant_base::write_double_value(json_writer &writer,double value) const
{
if (value < std::numeric_limits<int32_t>::max() && value > std::numeric_limits<int32_t>::min())
{
double frac = value-(int32_t)value;
if (value == 0)
{
writer.write((int32_t)value);
return;
}
}
writer.write(value);
}
+325
View File
@@ -0,0 +1,325 @@
/*
* 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 <vector>
#include <variant>
#include <map>
#include <string>
#include <stdexcept>
#include "json.hpp"
namespace pipedal
{
class json_null
{
public:
bool operator==(const json_null&other) const { return true;}
private:
int value = 0;
};
template <class T> // avoid ordering problem in declarations.
class json_object_base: public JsonSerializable
{
public:
using json_variant = T;
json_object_base() {}
T &operator[](const std::string &index) { return values[index]; }
const T &operator[](const std::string &index) const { return values[index]; }
public:
bool operator==(const json_object_base<T> &other) const
{
for (const auto &pair: this->values)
{
auto index = other.values.find(pair.first);
if (index == other.values.end()) return false;
if (!(index->second == pair.second)) return false;
}
for (const auto &pair: other.values)
{
auto index = this->values.find(pair.first);
if (index == this->values.end()) return false;
if (!(index->second == pair.second)) return false;
}
return true;
}
private:
virtual void read_json(json_reader&reader) {
reader.read(&(this->values));
}
virtual void write_json(json_writer&writer) const {
writer.start_object();
bool first = true;
for (auto&value: values)
{
if (!first)
{
writer.write_raw(",");
}
first = false;
writer.write(value.first);
writer.write_raw(": ");
writer.writeRawWritable(value.second);
}
writer.end_object();
}
std::map<std::string, T> values;
};
template <class T> // avoid ordering problem in declarations.
class json_array_base: public JsonSerializable
{
public:
json_array_base() {}
T &operator[](size_t index) {
check_index(index);
return values[index]; }
const T &operator[](size_t &index) const {
check_index(index);
return values[index];
}
void resize(size_t size)
{
values.resize(size);
}
size_t size() const { return values.size(); }
template <typename U>
void push_back(const U&value) { values.push_back(value); }
template <typename U>
void push_back(U&&value) { values.push_back(value); }
bool operator==(const json_array_base<T>&other) const
{
if (!(this->size() == other.size())) return false;
for (size_t i = 0; i < this->size(); ++i)
{
if (!((*this)[i] == other[i])) return false;
}
return true;
}
private:
virtual void read_json(json_reader&reader) {
reader.read(&(this->values));
}
virtual void write_json(json_writer&writer) const {
writer.start_array();
bool first = true;
for (auto&value: values)
{
if (!first) writer.write_raw(",");
first = false;
writer.writeRawWritable(value);
}
writer.end_array();
}
void check_index(size_t size) const
{
if (size >= values.size())
{
throw std::out_of_range("index out of range.");
}
}
std::vector<T> values;
};
class concrete_json_variant_base {
protected:
void write_double_value(json_writer &writer,double value) const;
};
template <typename DUMMY = void>
class json_variant_base
: public std::variant<json_null, bool, double, std::string, json_object_base<json_variant_base<DUMMY>>, json_array_base<json_variant_base<DUMMY>>>,
public JsonSerializable,
private concrete_json_variant_base
{
public:
using base = std::variant<json_null, bool, double, std::string, json_object_base<json_variant_base<DUMMY>>, json_array_base<json_variant_base<DUMMY>>>;
using json_object = json_object_base<json_variant_base<DUMMY>>;
using json_array = json_array_base<json_variant_base<DUMMY>>;
using json_variant = json_variant_base<void>;
json_variant_base(json_null value)
:base(value)
{
}
json_variant_base(double value)
: base(value)
{
}
json_variant_base(int value)
: base((double)value)
{
}
json_variant_base(const std::string &value)
: base(value)
{
}
json_variant_base(const char*value)
:base(std::string(value))
{
}
json_variant_base()
: base(json_null())
{
}
json_variant_base(json_object &&value)
: base(std::forward<json_object>(value))
{
}
json_variant_base(json_array &&value)
: base(std::forward<json_array>(value))
{
}
template <typename U>
bool holds_alternative() const { return std::holds_alternative<U>(*this);}
bool IsNull() const { return holds_alternative<json_null>(); }
bool IsBool() const { return holds_alternative<bool>(); }
bool IsNumber() const { return holds_alternative<double>(); }
bool IsString() const { return holds_alternative<std::string>(); }
bool IsObject() const { return holds_alternative<json_object>(); }
bool IsArray() const { return holds_alternative<json_array>(); }
template <typename U>
const U &get() const
{
return std::get<U>(*this);
}
template <typename U>
U &get()
{
return std::get<U>(*this);
}
bool &AsBool() { return get<bool>(); }
bool AsBool() const { return get<bool>(); }
double &AsNumber() { return get<double>(); }
double AsNumber() const { return get<double>(); }
std::string &AsString() { return get<std::string>(); }
const std::string &AsString() const { return get<std::string>(); }
json_object &AsObject() { return get<json_object>(); }
const json_object &AsObject() const { return get<json_object>(); }
std::vector<float> AsFloatArray() { return get<json_object>().AsFloatArray(); }
std::vector<double> AsDoubleArray() { return get<json_object>().AsDoubleArray(); }
json_array &AsArray() { return get<json_array>(); }
const json_array &AsArray() const { return get<json_array>(); }
// convenience methods for object and array manipulation.
static json_variant MakeObject() { return json_variant{ json_object()};};
static json_variant MakeArray() { return json_variant{ json_array()};};
void resize(size_t size) { AsArray().resize(size); }
size_t size() const { return AsArray().size(); }
json_variant&operator[](size_t index) { return AsArray()[index];}
const json_variant&operator[](size_t index) const { return AsArray()[index];}
const json_variant&operator[](const std::string& index) const { return AsObject()[index];}
json_variant&operator[](const std::string& index) { return AsObject()[index];}
private:
virtual void read_json(json_reader &reader)
{
int v = reader.peek();
if (v == '[')
{
json_array array;
reader.read(&array);
(*this) = std::move(array);
} else if (v == '{')
{
json_object object;
reader.read(&object);
(*this) = std::move(object);
}
else if (v == '\"') {
std::string s;
reader.read(&s);
(*this) = std::move(s);
} else if (v == 'n')
{
reader.read_null();
(*this) = json_null();
} else if (v == 't' || v == 'f')
{
bool b;
reader.read(&b);
(*this) = b;
} else {
// it's a number.
double v;
reader.read(&v);
(*this) = v;
}
}
virtual void write_json(json_writer&writer) const
{
switch (this->index())
{
case 0:
writer.write_raw("null");
break;
case 1:
writer.write(this->get<bool>());
break;
case 2:
write_double_value(writer,this->get<double>());
break;
case 3:
writer.write(get<std::string>());
break;
case 4:
writer.writeRawWritable(get<json_object>());
break;
case 5:
writer.writeRawWritable(get<json_array>());
break;
default:
throw std::logic_error("Invalid variant index");
}
}
};
using json_variant = json_variant_base<void>;
using json_object = json_variant::json_object;
using json_array = json_variant::json_array;
} // namespace pipedal