From 3ab431779ac9a2857b70cbfcbc99b5f10ce0ba79 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Tue, 21 Mar 2023 06:55:48 -0400 Subject: [PATCH] Interim commit for COnvolutionReverb --- .vscode/launch.json | 5 +- react/src/FilePropertyControl.tsx | 194 ++++ react/src/FilePropertyDialog.tsx | 191 ++++ react/src/Lv2Plugin.tsx | 62 +- react/src/PedalBoard.tsx | 34 + react/src/PiPedalModel.tsx | 77 +- react/src/PiPedalSocket.tsx | 5 +- react/src/PluginControlView.tsx | 103 +- src/AutoLilvNode.cpp | 85 ++ src/AutoLilvNode.hpp | 89 ++ src/AvahiService.cpp | 2 +- src/AvahiServiceTest.cpp | 2 +- src/CMakeLists.txt | 10 +- src/LogFeature.cpp | 36 + src/LogFeature.hpp | 6 + src/Lv2HostLeakTest.cpp | 4 +- src/PedalBoard.cpp | 26 +- src/PedalBoard.hpp | 30 +- src/PiPedalHost.cpp | 209 ++-- src/PiPedalHost.hpp | 1138 ++++++++++--------- src/PiPedalModel.cpp | 22 +- src/PiPedalModel.hpp | 2 + src/PiPedalSocket.cpp | 6 + src/PiPedalUI.cpp | 170 +++ src/PiPedalUI.hpp | 111 ++ src/Storage.cpp | 54 + src/Storage.hpp | 4 + src/Worker.cpp | 10 + src/defaultFavorites.json | 3 +- src/json.cpp | 6 + src/json.hpp | 1727 +++++++++++++++-------------- src/jsonTest.cpp | 127 ++- src/json_variant.cpp | 44 + src/json_variant.hpp | 325 ++++++ 34 files changed, 3369 insertions(+), 1550 deletions(-) create mode 100644 react/src/FilePropertyControl.tsx create mode 100644 react/src/FilePropertyDialog.tsx create mode 100644 src/AutoLilvNode.cpp create mode 100644 src/AutoLilvNode.hpp create mode 100644 src/PiPedalUI.cpp create mode 100644 src/PiPedalUI.hpp create mode 100644 src/json_variant.cpp create mode 100644 src/json_variant.hpp diff --git a/.vscode/launch.json b/.vscode/launch.json index a2aca1b..b9b5079 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -79,7 +79,10 @@ // Resolved by CMake Tools: "program": "${command:cmake.launchTargetPath}", - "args": [ "[alsa_test]" ], + "args": [ + //"[Dev]" + "[json_variants]" + ], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/react/src/FilePropertyControl.tsx b/react/src/FilePropertyControl.tsx new file mode 100644 index 0000000..7ab5f86 --- /dev/null +++ b/react/src/FilePropertyControl.tsx @@ -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 { + 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 + { + + 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 ( +
+ {/* TITLE SECTION */} +
+ {fileProperty.name} +
+ {/* CONTROL SECTION */} + +
+ + {this.onFileClick()}} > +
+
+ {value} + +
+
 
+
+
+
+ + {/* LABEL/EDIT SECTION*/} +
+
+
+ ); + } + } + ); + +export default FilePropertyControl; \ No newline at end of file diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx new file mode 100644 index 0000000..2c03d46 --- /dev/null +++ b/react/src/FilePropertyDialog.tsx @@ -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 { + + + 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, prevState: Readonly, 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 ( + this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }} + PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }} + > + {this.props.fileProperty.name} +
 
+
+
+ { + 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 ( + this.onSelect(value)} + > +
+
+ + {this.fileNameOnly(value)} +
+ + ); + } + ) + } +
+
+
 
+ +
+ + + + +
 
+ + +
+
+ + ); + } +} \ No newline at end of file diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx index 4e0ecce..2ff03a8 100644 --- a/react/src/Lv2Plugin.tsx +++ b/react/src/Lv2Plugin.tsx @@ -105,6 +105,49 @@ export class PortGroup { 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 { deserialize(input: any): Lv2Plugin { @@ -119,6 +162,12 @@ export class Lv2Plugin implements Deserializable { this.comment = input.comment; this.ports= Port.deserialize_array(input.ports); this.port_groups = PortGroup.deserialize_array(input.port_groups); + if (input.fileProperties) + { + this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties) + } else { + this.fileProperties = []; + } return this; } static EmptyFeatures: string[] = []; @@ -133,7 +182,8 @@ export class Lv2Plugin implements Deserializable { author_homepage: string = ""; comment: string = ""; ports: Port[] = Port.EmptyPorts; - port_groups: PortGroup[] = []; + port_groups: PortGroup[] = []; + fileProperties: PiPedalFileProperty[] = []; } @@ -474,6 +524,13 @@ export class UiPlugin implements Deserializable { this.description = input.description; this.controls = UiControl.deserialize_array(input.controls); 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; return this; @@ -523,7 +580,8 @@ export class UiPlugin implements Deserializable { has_midi_output: number = 0; description: string = ""; controls: UiControl[] = []; - port_groups: PortGroup[] = []; + port_groups: PortGroup[] = []; + fileProperties: PiPedalFileProperty[] = []; is_vst3 : boolean = false; } diff --git a/react/src/PedalBoard.tsx b/react/src/PedalBoard.tsx index 41deef0..8f640f7 100644 --- a/react/src/PedalBoard.tsx +++ b/react/src/PedalBoard.tsx @@ -56,6 +56,27 @@ export class ControlValue implements Deserializable { key: string; value: number; +} +export class PropertyValue implements Deserializable { + 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 { @@ -67,6 +88,7 @@ export class PedalBoardItem implements Deserializable { this.midiBindings = MidiBinding.deserialize_array(input.midiBindings); this.controlValues = ControlValue.deserializeArray(input.controlValues); + this.propertyValues = PropertyValue.deserializeArray(input.propertyValues); this.vstState = input.vstState ?? ""; return this; } @@ -136,6 +158,17 @@ export class PedalBoardItem implements Deserializable { } 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 { if (this.midiBindings) { @@ -183,6 +216,7 @@ export class PedalBoardItem implements Deserializable { uri: string = ""; pluginName?: string; controlValues: ControlValue[] = ControlValue.EmptyArray; + propertyValues: PropertyValue[] = []; midiBindings: MidiBinding[] = []; vstState: string = ""; }; diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 49606af..479e4c2 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -17,7 +17,7 @@ // 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 { UiPlugin, UiControl, PluginType } from './Lv2Plugin'; +import { UiPlugin, UiControl, PluginType, PiPedalFileProperty } from './Lv2Plugin'; import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError'; @@ -50,6 +50,7 @@ export enum State { }; export type ControlValueChangedHandler = (key: string, value: number) => void; +export type PropertyValueChangedHandler = (properyUri: string, value: number) => 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 { constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) { this.handle = handle; @@ -354,9 +367,9 @@ export interface PiPedalModel { loadPedalBoardPlugin(itemId: number, selectedUri: string): number; setPedalBoardControlValue(instanceId: number, key: string, value: number): void; + setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any): void; setPedalBoardItemEnabled(instanceId: number, value: boolean): void; previewPedalBoardValue(instanceId: number, key: string, value: number): void; - setPedalBoardControlValue(instanceId: number, key: string, value: number): void; deletePedalBoardPedal(instanceId: number): number | null; movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void; @@ -456,6 +469,8 @@ export interface PiPedalModel { chooseNewDevice(): void; hasConfiguration(): boolean; + + requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise; }; class PiPedalModelImpl implements PiPedalModel { @@ -722,6 +737,7 @@ class PiPedalModelImpl implements PiPedalModel { } } + requestPluginClasses(): Promise { const myRequest = new Request(this.varRequest('plugin_classes.json')); return fetch(myRequest) @@ -892,6 +908,10 @@ class PiPedalModelImpl implements PiPedalModel { ); return this.webSocket.connect(); }) + .catch((error) => { + this.setError("Failed to connect to server."); + return false; + }) .then(() => { const isoRequest = new Request('iso_codes.json'); return fetch(isoRequest); @@ -1171,7 +1191,7 @@ class PiPedalModelImpl implements PiPedalModel { } - _controlValueChangeItems: ControlValueChangeItem[] = []; + private _controlValueChangeItems: ControlValueChangeItem[] = []; addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle { 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[] = []; @@ -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 { let pedalBoard = this.pedalBoard.get(); 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 { this._setPedalBoardControlValue(instanceId, key, value, true); } setPedalBoardItemEnabled(instanceId: number, value: boolean): void { 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(); if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); let newPedalBoard = pedalBoard.clone(); @@ -1544,6 +1602,13 @@ class PiPedalModelImpl implements PiPedalModel { return newPresetId; }); } + + requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise + { + return nullCast(this.webSocket) + .request('requestFileList',piPedalFileProperty); + } + saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise { // default behaviour is to save after the currently selected preset. let request: any = { @@ -2354,6 +2419,8 @@ class PiPedalModelImpl implements PiPedalModel { return jackConfig.isValid; } + + }; let instance: PiPedalModel | undefined = undefined; diff --git a/react/src/PiPedalSocket.tsx b/react/src/PiPedalSocket.tsx index 511bed2..e388dcc 100644 --- a/react/src/PiPedalSocket.tsx +++ b/react/src/PiPedalSocket.tsx @@ -272,18 +272,19 @@ class PiPedalSocket { connectInternal_(): Promise { return new Promise((resolve, reject) => { let ws = new WebSocket(this.url); + let self = this; ws.onmessage = this.handleMessage.bind(this); ws.onclose = (event: Event) => { ws.onclose = null; ws.onerror = null; - reject("Can't connect to server."); + reject("Connection closed unexpectedly."); }; ws.onerror = (event: Event) => { ws.onclose = null; ws.onerror = null; - reject("Can't connect to server."); + reject("Failed to connect."); }; ws.onopen = (event: Event) => { ws.onerror = self.handleError.bind(self); diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index 6e6301e..a75b2f7 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -23,9 +23,9 @@ import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; -import { UiPlugin, UiControl } from './Lv2Plugin'; +import { UiPlugin, UiControl, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin'; import { - PedalBoard, PedalBoardItem, ControlValue, + PedalBoard, PedalBoardItem, ControlValue,PropertyValue } from './PedalBoard'; import PluginControl from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; @@ -34,6 +34,8 @@ import { nullCast } from './Utility' import { PiPedalStateError } from './PiPedalError'; import Typography from '@mui/material/Typography'; import FullScreenIME from './FullScreenIME'; +import FilePropertyControl from './FilePropertyControl'; +import FilePropertyDialog from './FilePropertyDialog'; export const StandardItemSize = { width: 80, height: 110 }; @@ -167,8 +169,7 @@ const styles = (theme: Theme) => createStyles({ }); export class ControlGroup { - constructor(name: string, controls: ReactNode[]) - { + constructor(name: string, controls: ReactNode[]) { this.name = name; this.controls = controls; } @@ -197,6 +198,9 @@ type PluginControlViewState = { imeValue: number; imeCaption: string; imeInitialHeight: number; + showFileDialog: boolean, + dialogFileProperty: PiPedalFileProperty, + dialogFileValue: string }; const PluginControlView = @@ -214,11 +218,14 @@ const PluginControlView = imeUiControl: undefined, imeValue: 0, imeCaption: "", - imeInitialHeight: 0 + imeInitialHeight: 0, + showFileDialog: false, + dialogFileProperty: new PiPedalFileProperty(), + dialogFileValue: "" } this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); - this.onValueChanged = this.onValueChanged.bind(this); + this.onControlValueChanged = this.onControlValueChanged.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this); } @@ -227,9 +234,12 @@ const PluginControlView = 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); } + onPropertyValueChanged(propertyUri: string, value: any): void { + this.model.setPedalBoardPropertyValue(this.props.instanceId, propertyUri, value); + } onPedalBoardChanged(value?: PedalBoard) { //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 (( + + { + this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile}); + }} + /> + )); + } makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode { let symbol = uiControl.symbol; @@ -288,18 +322,18 @@ const PluginControlView = throw new PiPedalStateError("Missing control value."); } return (( - - { this.onValueChanged(controlValue!.key, value) }} - onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }} - requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)} - /> + { 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 = []; for (let i = 0; i < plugin.controls.length; ++i) { @@ -325,11 +359,17 @@ const PluginControlView = ) } else { 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; } @@ -352,10 +392,8 @@ const PluginControlView = }); } - hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean - { - for (let i = 0; i < nodes.length; ++i) - { + hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean { + for (let i = 0; i < nodes.length; ++i) { let node = nodes[i]; if (node instanceof ControlGroup) return true; } @@ -374,13 +412,12 @@ const PluginControlView = if (node instanceof ControlGroup) { let controlGroup = node as ControlGroup; 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]; controls.push( (
- { item } + {item}
) @@ -402,7 +439,7 @@ const PluginControlView = } else { result.push(( -
+
{node as ReactNode}
)); @@ -421,6 +458,7 @@ const PluginControlView = return (
); let controlValues = pedalBoardItem.controlValues; + let propertyValues = pedalBoardItem.propertyValues; let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri)); @@ -429,14 +467,14 @@ const PluginControlView = controlValues = this.filterNotOnGui(controlValues, plugin); + let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid; let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let controlNodes: ControlNodes; - controlNodes = this.getStandardControlNodes(plugin, controlValues); - - if (this.props.customization) - { + controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues); + + if (this.props.customization) { // allow wrapper class to insert/remove/rebuild controls. controlNodes = this.props.customization.ModifyControls(controlNodes); } @@ -462,6 +500,15 @@ const PluginControlView = ) }
+ { this.setState({ showFileDialog: false});}} + onOk={(fileProperty,selectedFile)=> { + this.model.setPedalBoardPropertyValue(this.props.instanceId,fileProperty.patchProperty,selectedFile) + this.setState({ showFileDialog: false});} + } + /> this.onImeValueChange(key, value)} diff --git a/src/AutoLilvNode.cpp b/src/AutoLilvNode.cpp new file mode 100644 index 0000000..ff8d1b8 --- /dev/null +++ b/src/AutoLilvNode.cpp @@ -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 ""; +} diff --git a/src/AutoLilvNode.hpp b/src/AutoLilvNode.hpp new file mode 100644 index 0000000..e002f56 --- /dev/null +++ b/src/AutoLilvNode.hpp @@ -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 +#include + +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; + } + + + }; +}; \ No newline at end of file diff --git a/src/AvahiService.cpp b/src/AvahiService.cpp index 7991242..e0eefbf 100644 --- a/src/AvahiService.cpp +++ b/src/AvahiService.cpp @@ -153,7 +153,7 @@ void AvahiService::create_group(AvahiClient *c) * because it was reset previously, add our entries. */ 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); diff --git a/src/AvahiServiceTest.cpp b/src/AvahiServiceTest.cpp index 890279b..6cec4da 100644 --- a/src/AvahiServiceTest.cpp +++ b/src/AvahiServiceTest.cpp @@ -42,6 +42,6 @@ TEST_CASE("Avahi Service Test", "[avahi_service][dev]") service.Unannounce(); service.Announce(81, "Test Announcement 2", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); - sleep(10000); + sleep(10); } } \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f91a996..88c86e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ set (CMAKE_INSTALL_PREFIX "/usr/") set (USE_PCH 1) +set(CXX_STANDARD 20) + include(FindPkgConfig) ################################################################# @@ -135,6 +137,10 @@ else() endif() set (PIPEDAL_SOURCES + AutoLilvNode.hpp + AutoLilvNode.cpp + PiPedalUI.hpp + PiPedalUI.cpp RtInversionGuard.hpp CpuUse.hpp CpuUse.cpp P2pConfigFiles.hpp @@ -154,7 +160,9 @@ set (PIPEDAL_SOURCES WifiDirectConfigSettings.hpp WifiDirectConfigSettings.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 Lv2Log.hpp Lv2Log.cpp PiPedalSocket.hpp PiPedalSocket.cpp diff --git a/src/LogFeature.cpp b/src/LogFeature.cpp index d5f4db8..776a3cc 100644 --- a/src/LogFeature.cpp +++ b/src/LogFeature.cpp @@ -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); + +} + +} diff --git a/src/LogFeature.hpp b/src/LogFeature.hpp index 0511940..2db4f18 100644 --- a/src/LogFeature.hpp +++ b/src/LogFeature.hpp @@ -61,6 +61,12 @@ namespace pipedal { LogFeature(); 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: const LV2_Feature* GetFeature() { diff --git a/src/Lv2HostLeakTest.cpp b/src/Lv2HostLeakTest.cpp index 6f86217..9ae8646 100644 --- a/src/Lv2HostLeakTest.cpp +++ b/src/Lv2HostLeakTest.cpp @@ -40,9 +40,9 @@ TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) { } MemStats finalMemory = GetMemStats(); - // Something lilv leaks a Dublin Core url. + // Something in lilv leaks a Dublin Core url. // Acceptable. - const int ACCEPTABLE_ALLOCATION_LEAKS = 4; + const int ACCEPTABLE_ALLOCATION_LEAKS = 6; const int ACCEPTABLE_MEMORY_LEAK = 400; if (finalMemory.allocations > initialMemory.allocations + ACCEPTABLE_ALLOCATION_LEAKS diff --git a/src/PedalBoard.cpp b/src/PedalBoard.cpp index 6e3b52c..cee2230 100644 --- a/src/PedalBoard.cpp +++ b/src/PedalBoard.cpp @@ -53,6 +53,18 @@ PedalBoardItem*PedalBoard::GetItem(long pedalItemId) return const_cast(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) { 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&value) +{ + return self->uri() == SPLIT_PEDALBOARD_ITEM_URI; +} @@ -176,17 +192,19 @@ JSON_MAP_BEGIN(ControlValue) JSON_MAP_REFERENCE(ControlValue,value) 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&value) -{ - return self->uri() == SPLIT_PEDALBOARD_ITEM_URI; -} JSON_MAP_BEGIN(PedalBoardItem) JSON_MAP_REFERENCE(PedalBoardItem,instanceId) JSON_MAP_REFERENCE(PedalBoardItem,uri) JSON_MAP_REFERENCE(PedalBoardItem,isEnabled) JSON_MAP_REFERENCE(PedalBoardItem,controlValues) + JSON_MAP_REFERENCE(PedalBoardItem,propertyValues) JSON_MAP_REFERENCE(PedalBoardItem,pluginName) JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,topChain,IsPedalBoardSplitItem) JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,bottomChain,&IsPedalBoardSplitItem) diff --git a/src/PedalBoard.hpp b/src/PedalBoard.hpp index 9075d16..6676f6e 100644 --- a/src/PedalBoard.hpp +++ b/src/PedalBoard.hpp @@ -20,6 +20,7 @@ #pragma once #include "json.hpp" +#include "json_variant.hpp" #include "MidiBinding.hpp" namespace pipedal { @@ -72,25 +73,52 @@ public: }; +class PropertyValue { +private: + std::string propertyUri_; + json_variant value_; +public: + PropertyValue() + { -class PedalBoardItem: public JsonWritable { + } + template + 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; std::string uri_; std::string pluginName_; bool isEnabled_ = true; std::vector controlValues_; + std::vector propertyValues_; std::vector topChain_; std::vector bottomChain_; std::vector midiBindings_; std::string vstState_; public: ControlValue*GetControlValue(const std::string&symbol); + PropertyValue*GetPropertyValue(const std::string&propertyUri); + GETTER_SETTER(instanceId) GETTER_SETTER_REF(uri) GETTER_SETTER_REF(vstState); GETTER_SETTER_REF(pluginName) GETTER_SETTER(isEnabled) GETTER_SETTER_VEC(controlValues) + GETTER_SETTER_VEC(propertyValues) GETTER_SETTER_VEC(topChain) GETTER_SETTER_VEC(bottomChain) GETTER_SETTER_VEC(midiBindings) diff --git a/src/PiPedalHost.cpp b/src/PiPedalHost.cpp index f7ab849..1887bd9 100644 --- a/src/PiPedalHost.cpp +++ b/src/PiPedalHost.cpp @@ -34,6 +34,7 @@ #include "lv2.h" #include "lv2/atom.lv2/atom.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/presets/presets.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(); } - void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld) { 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); 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_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat); time_beatsPerMinute = lilv_new_uri(pWorld, LV2_TIME__beatsPerMinute); @@ -202,54 +214,6 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0) 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() { pWorld = nullptr; @@ -418,7 +382,7 @@ void PiPedalHost::Load(const char *lv2Path) { const LilvPlugin *lilvPlugin = lilv_plugins_get(plugins, iPlugin); - std::shared_ptr pluginInfo = std::make_shared(this, lilvPlugin); + std::shared_ptr pluginInfo = std::make_shared(this, pWorld, lilvPlugin); 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) { - 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); return result; } @@ -595,21 +559,19 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl 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->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); - NodeAutoFree author_name = (lilv_plugin_get_author_name(pPlugin)); + AutoLilvNode author_name = (lilv_plugin_get_author_name(pPlugin)); 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); 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); this->extensions_ = nodeAsStringArray(extensions); - NodeAutoFree comment = lv2Host->get_comment(this->uri_); + AutoLilvNode comment = lv2Host->get_comment(this->uri_); this->comment_ = nodeAsString(comment); 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); + // 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(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; } @@ -684,6 +666,7 @@ std::vector supportedFeatures = { LV2_BUF_SIZE__fixedBlockLength, LV2_BUF_SIZE__powerOf2BlockLength, 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); 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); classes_ = host->GetPluginPortClass(plugin, pPort); - NodeAutoFree minNode, maxNode, defaultNode; + AutoLilvNode minNode, maxNode, defaultNode; min_value_ = 0; max_value_ = 1; 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) { 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_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); - 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); - 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)); - NodeAutoFree commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment); + AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment); 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_ = ""; if (bufferType) @@ -953,6 +936,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin { this->port_groups_.push_back(Lv2PluginUiPortGroup(portGroup.get())); } + auto &piPedalUI = plugin->piPedalUI(); + + if (piPedalUI) + { + this->fileProperties_ = piPedalUI->fileProperties(); + } } std::shared_ptr PiPedalHost::GetLv2PluginClass() const @@ -1018,7 +1007,7 @@ std::vector PiPedalHost::LoadFactoryPluginPreset( 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); @@ -1040,7 +1029,7 @@ std::vector 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); @@ -1091,7 +1080,7 @@ PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri) { 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); @@ -1133,7 +1122,7 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem) { if (pedalBoardItem.uri().starts_with("vst3:")) { - #if ENABLE_VST3 +#if ENABLE_VST3 try { 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()); throw; } - #else +#else 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."); - #endif +#endif } else { @@ -1165,13 +1154,19 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri) LilvWorld *pWorld = lv2Host->pWorld; 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); symbol_ = nodeAsString(symbolNode); LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.nameUri, nullptr); name_ = nodeAsString(nameNode); } +void PiPedalHostLogError(const std::string&errror) +{ + +} + + #define MAP_REF(class, name) \ json_map::reference(#name, &class ::name##_) @@ -1188,45 +1183,47 @@ json_map::storage_type Lv2ScalePoint::jmap{{ json_map::reference("label", &Lv2ScalePoint::label_), }}; -json_map::storage_type Lv2PortInfo::jmap{{json_map::reference("index", &Lv2PortInfo::index_), - json_map::reference("symbol", &Lv2PortInfo::symbol_), - json_map::reference("index", &Lv2PortInfo::index_), - json_map::reference("name", &Lv2PortInfo::name_), +json_map::storage_type Lv2PortInfo::jmap{ + {json_map::reference("index", &Lv2PortInfo::index_), - 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("symbol", &Lv2PortInfo::symbol_), + json_map::reference("index", &Lv2PortInfo::index_), + json_map::reference("name", &Lv2PortInfo::name_), - 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("is_output", &Lv2PortInfo::is_output_), + json_map::reference("scale_points", &Lv2PortInfo::scale_points_), - 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("is_input", &Lv2PortInfo::is_input_), + json_map::reference("is_output", &Lv2PortInfo::is_output_), - 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("supports_time_position", &Lv2PortInfo::supports_time_position_), - json_map::reference("port_group", &Lv2PortInfo::port_group_), - json_map::reference("is_logarithmic", &Lv2PortInfo::is_logarithmic_), + json_map::reference("is_valid", &Lv2PortInfo::is_valid_), - MAP_REF(Lv2PortInfo, is_logarithmic), - MAP_REF(Lv2PortInfo, display_priority), - MAP_REF(Lv2PortInfo, range_steps), - MAP_REF(Lv2PortInfo, trigger), - MAP_REF(Lv2PortInfo, integer_property), - MAP_REF(Lv2PortInfo, enumeration_property), - 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::reference("supports_midi", &Lv2PortInfo::supports_midi_), + 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), + MAP_REF(Lv2PortInfo, display_priority), + MAP_REF(Lv2PortInfo, range_steps), + MAP_REF(Lv2PortInfo, trigger), + MAP_REF(Lv2PortInfo, integer_property), + MAP_REF(Lv2PortInfo, enumeration_property), + 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::jmap{{ MAP_REF(Lv2PortGroup, uri), @@ -1308,5 +1305,5 @@ json_map::storage_type Lv2PluginUiInfo::jmap{{ json_map::reference("controls", &Lv2PluginUiInfo::controls_), json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_), json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_), - + json_map::reference("fileProperties",&Lv2PluginUiInfo::fileProperties_) }}; diff --git a/src/PiPedalHost.hpp b/src/PiPedalHost.hpp index b29a0b0..f135454 100644 --- a/src/PiPedalHost.hpp +++ b/src/PiPedalHost.hpp @@ -38,374 +38,399 @@ #include "IEffect.hpp" #include "PiPedalConfiguration.hpp" +#include "AutoLilvNode.hpp" +#include "PiPedalUI.hpp" +namespace pipedal +{ -namespace pipedal { - - -// forward declarations -class Lv2Effect; -class Lv2PedalBoard; -class PiPedalHost; -class JackConfiguration; -class JackChannelSelection; + // forward declarations + class Lv2Effect; + class Lv2PedalBoard; + class PiPedalHost; + class JackConfiguration; + class JackChannelSelection; #ifndef LV2_PROPERTY_GETSET -#define LV2_PROPERTY_GETSET(name) \ - const decltype(name##_) & name() const { return name##_; }; \ - decltype(name##_) & name() { return name##_; }; \ - void name(const decltype(name##_) &value) { name##_ = value; }; +#define LV2_PROPERTY_GETSET(name) \ + const decltype(name##_) &name() const \ + { \ + return name##_; \ + }; \ + decltype(name##_) &name() \ + { \ + return name##_; \ + }; \ + void name(const decltype(name##_) &value) \ + { \ + name##_ = value; \ + }; #endif - #ifndef LV2_PROPERTY_GETSET_SCALAR #define LV2_PROPERTY_GETSET_SCALAR(name) \ - decltype(name##_) name() const { return name##_;}; \ - void name(decltype(name##_) value) { name##_ = value; }; + decltype(name##_) name() const \ + { \ + return name##_; \ + }; \ + void name(decltype(name##_) value) \ + { \ + name##_ = value; \ + }; #endif - - - - -class Lv2PluginClass { -public: - friend class PiPedalHost; -private: - Lv2PluginClass* parent_ = nullptr; // NOT SERIALIZED! - std::string parent_uri_; - std::string display_name_; - std::string uri_; - PluginType plugin_type_; - std::vector> children_; - - - friend class ::pipedal::PiPedalHost; - // hide copy constructor. - Lv2PluginClass(const Lv2PluginClass &other) + class Lv2PluginClass { + public: + friend class PiPedalHost; - } - void set_parent(std::shared_ptr &parent) - { - this->parent_ = parent.get(); - } - void add_child(std::shared_ptr &child) { - for (size_t i = 0; i < children_.size(); ++i) + private: + Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED! + std::string parent_uri_; + std::string display_name_; + std::string uri_; + PluginType plugin_type_; + std::vector> children_; + + friend class ::pipedal::PiPedalHost; + // hide copy constructor. + Lv2PluginClass(const Lv2PluginClass &other) { - if (children_[i]->uri_ == child->uri_) + } + void set_parent(std::shared_ptr &parent) + { + this->parent_ = parent.get(); + } + void add_child(std::shared_ptr &child) + { + for (size_t i = 0; i < children_.size(); ++i) { - return; + if (children_[i]->uri_ == child->uri_) + { + return; + } + } + children_.push_back(child); + } + + public: + Lv2PluginClass( + const char *display_name, const char *uri, const char *parent_uri) + : parent_uri_(parent_uri), display_name_(display_name), uri_(uri), plugin_type_(uri_to_plugin_type(uri)) + { + } + Lv2PluginClass() {} + LV2_PROPERTY_GETSET(uri) + LV2_PROPERTY_GETSET(display_name) + LV2_PROPERTY_GETSET(parent_uri) + + const Lv2PluginClass *parent() { return parent_; } + + bool operator==(const Lv2PluginClass &other) + { + return uri_ == other.uri_; + } + bool is_a(const std::string &classUri) const; + + static json_map::storage_type jmap; + }; + class Lv2PluginClasses + { + private: + std::vector classes_; + + public: + Lv2PluginClasses() + { + } + Lv2PluginClasses(std::vector classes) + : classes_(classes) + { + } + const std::vector &classes() const + { + return classes_; + } + bool is_a(PiPedalHost *lv2Plugins, const char *classUri) const; + + static json_map::storage_type jmap; + }; + + class Lv2ScalePoint + { + private: + float value_; + std::string label_; + + public: + Lv2ScalePoint() {} + Lv2ScalePoint(float value, std::string label) + : value_(value), label_(label) + { + } + + LV2_PROPERTY_GETSET_SCALAR(value); + LV2_PROPERTY_GETSET(label); + + static json_map::storage_type jmap; + }; + + enum class Lv2BufferType + { + None, + Event, + Sequence, + Unknown + }; + + class Lv2PortInfo + { + public: + Lv2PortInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort); + + private: + friend class Lv2PluginInfo; + + uint32_t index_; + std::string symbol_; + std::string name_; + float min_value_, max_value_, default_value_; + Lv2PluginClasses classes_; + std::vector scale_points_; + bool is_input_ = false; + bool is_output_ = false; + + bool is_control_port_ = false; + bool is_audio_port_ = false; + bool is_atom_port_ = false; + bool is_cv_port_ = false; + + bool is_valid_ = false; + bool supports_midi_ = false; + bool supports_time_position_ = false; + bool is_logarithmic_ = false; + int display_priority_ = -1; + int range_steps_ = 0; + bool trigger_; + bool integer_property_; + bool enumeration_property_; + bool toggled_property_; + bool not_on_gui_; + std::string buffer_type_; + std::string port_group_; + + std::string designation_; + Units units_ = Units::none; + std::string comment_; + PiPedalUI::ptr piPedalUI_; + + public: + bool IsSwitch() const + { + return min_value_ == 0 && max_value_ == 1 && (integer_property_ || toggled_property_ || enumeration_property_); + } + const PiPedalUI::ptr &piPedalUI() const { return piPedalUI_; } + float rangeToValue(float range) const + { + float value; + if (is_logarithmic_) + { + value = std::pow(max_value_ / min_value_, range) * min_value_; + } + else + { + value = (max_value_ - min_value_) * range + min_value_; + } + if (integer_property_ || enumeration_property_) + { + value = std::round(value); + } + else if (range_steps_ >= 2) + { + value = std::round(value * (range_steps_ - 1)) / (range_steps_ - 1); + } + if (toggled_property_) + { + value = value == 0 ? 0 : max_value_; + } + if (value > max_value_) + value = max_value_; + if (value < min_value_) + value = min_value_; + return value; + } + LV2_PROPERTY_GETSET(symbol); + LV2_PROPERTY_GETSET_SCALAR(index); + LV2_PROPERTY_GETSET(name); + LV2_PROPERTY_GETSET(classes); + LV2_PROPERTY_GETSET(scale_points); + LV2_PROPERTY_GETSET_SCALAR(min_value); + LV2_PROPERTY_GETSET_SCALAR(max_value); + LV2_PROPERTY_GETSET_SCALAR(default_value); + + LV2_PROPERTY_GETSET_SCALAR(is_input); + LV2_PROPERTY_GETSET_SCALAR(is_output); + LV2_PROPERTY_GETSET_SCALAR(is_control_port); + LV2_PROPERTY_GETSET_SCALAR(is_audio_port); + LV2_PROPERTY_GETSET_SCALAR(is_atom_port); + LV2_PROPERTY_GETSET_SCALAR(is_cv_port); + LV2_PROPERTY_GETSET_SCALAR(is_valid); + LV2_PROPERTY_GETSET_SCALAR(supports_midi); + LV2_PROPERTY_GETSET_SCALAR(supports_time_position); + LV2_PROPERTY_GETSET_SCALAR(is_logarithmic); + LV2_PROPERTY_GETSET_SCALAR(display_priority); + LV2_PROPERTY_GETSET_SCALAR(range_steps); + LV2_PROPERTY_GETSET_SCALAR(trigger); + LV2_PROPERTY_GETSET_SCALAR(integer_property); + LV2_PROPERTY_GETSET_SCALAR(enumeration_property); + LV2_PROPERTY_GETSET_SCALAR(toggled_property); + LV2_PROPERTY_GETSET_SCALAR(not_on_gui); + LV2_PROPERTY_GETSET(port_group); + LV2_PROPERTY_GETSET(comment); + LV2_PROPERTY_GETSET_SCALAR(units); + + LV2_PROPERTY_GETSET(buffer_type); + + Lv2BufferType GetBufferType() + { + if (buffer_type_ == "") + return Lv2BufferType::None; + if (buffer_type_ == LV2_ATOM__Sequence) + return Lv2BufferType::Sequence; + return Lv2BufferType::Unknown; + } + + public: + Lv2PortInfo() {} + ~Lv2PortInfo() = default; + bool is_a(PiPedalHost *lv2Plugins, const char *classUri); + + static json_map::storage_type jmap; + }; + + class Lv2PortGroup + { + private: + std::string uri_; + std::string symbol_; + std::string name_; + + public: + LV2_PROPERTY_GETSET(uri); + LV2_PROPERTY_GETSET(symbol); + LV2_PROPERTY_GETSET(name); + + Lv2PortGroup() {} + Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri); + + static json_map::storage_type jmap; + }; + + class Lv2PluginInfo + { + private: + friend class PiPedalHost; + + public: + Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); + Lv2PluginInfo() {} + + private: + bool HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin); + std::string uri_; + std::string name_; + std::string plugin_class_; + std::vector supported_features_; + std::vector required_features_; + std::vector optional_features_; + std::vector extensions_; + bool has_factory_presets_ = false; + + std::string author_name_; + std::string author_homepage_; + + std::string comment_; + std::vector> ports_; + std::vector> port_groups_; + bool is_valid_ = false; + PiPedalUI::ptr piPedalUI_; + + bool IsSupportedFeature(const std::string &feature) const; + + public: + LV2_PROPERTY_GETSET(uri) + LV2_PROPERTY_GETSET(name) + LV2_PROPERTY_GETSET(plugin_class) + LV2_PROPERTY_GETSET(supported_features) + LV2_PROPERTY_GETSET(required_features) + LV2_PROPERTY_GETSET(optional_features) + LV2_PROPERTY_GETSET(author_name) + LV2_PROPERTY_GETSET(author_homepage) + LV2_PROPERTY_GETSET(comment) + LV2_PROPERTY_GETSET(extensions) + LV2_PROPERTY_GETSET(ports) + LV2_PROPERTY_GETSET(is_valid) + LV2_PROPERTY_GETSET(port_groups) + LV2_PROPERTY_GETSET(has_factory_presets) + LV2_PROPERTY_GETSET(piPedalUI) + + const Lv2PortInfo &getPort(const std::string &symbol) + { + for (size_t i = 0; i < ports_.size(); ++i) + { + if (ports_[i]->symbol() == symbol) + { + return *(ports_[i].get()); + } + } + throw PiPedalArgumentException("Port not found."); + } + bool hasExtension(const std::string &uri) + { + for (int i = 0; i < extensions_.size(); ++i) + { + if (extensions_[i] == uri) + return true; + } + return false; + } + bool hasCvPorts() const + { + for (size_t i = 0; i < ports_.size(); ++i) + { + if (ports_[i]->is_cv_port()) + { + return true; + } + } + return false; + } + bool hasMidiInput() const + { + for (size_t i = 0; i < ports_.size(); ++i) + { + if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_input()) + { + return true; + } } } - children_.push_back(child); - } -public: - Lv2PluginClass( - const char*display_name, const char*uri, const char*parent_uri) - : parent_uri_(parent_uri) - , display_name_(display_name) - , uri_(uri) - , plugin_type_(uri_to_plugin_type(uri)) - { - - } - Lv2PluginClass() { } - LV2_PROPERTY_GETSET(uri) - LV2_PROPERTY_GETSET(display_name) - LV2_PROPERTY_GETSET(parent_uri) - - const Lv2PluginClass *parent() { return parent_;} - - bool operator==(const Lv2PluginClass &other) - { - return uri_ == other.uri_; - } - bool is_a(const std::string& classUri) const; - - static json_map::storage_type jmap; - -}; -class Lv2PluginClasses { -private: - std::vector classes_; -public: - Lv2PluginClasses() - { - - } - Lv2PluginClasses(std::vector classes) - :classes_(classes) - { - - } - const std::vector & classes() const - { - return classes_; - } - bool is_a(PiPedalHost*lv2Plugins,const char*classUri) const; - - static json_map::storage_type jmap; - -}; - - -class Lv2ScalePoint { -private: - float value_; - std::string label_; -public: - Lv2ScalePoint() { } - Lv2ScalePoint(float value, std::string label) - : value_(value) - , label_(label) - { - - } - - LV2_PROPERTY_GETSET_SCALAR(value); - LV2_PROPERTY_GETSET(label); - - static json_map::storage_type jmap; -}; - - -enum class Lv2BufferType { - None, - Event, - Sequence, - Unknown -}; - -class Lv2PortInfo { -public: - Lv2PortInfo(PiPedalHost*lv2Host,const LilvPlugin*pPlugin,const LilvPort *pPort); -private: - friend class Lv2PluginInfo; - - uint32_t index_; - std::string symbol_; - std::string name_; - float min_value_, max_value_, default_value_; - Lv2PluginClasses classes_; - std::vector scale_points_; - bool is_input_ = false; - bool is_output_ = false; - - bool is_control_port_ = false; - bool is_audio_port_ = false; - bool is_atom_port_ = false; - bool is_cv_port_ = false; - - bool is_valid_ = false; - bool supports_midi_ = false; - bool supports_time_position_ = false; - bool is_logarithmic_ = false; - int display_priority_ = -1; - int range_steps_ = 0; - bool trigger_; - bool integer_property_; - bool enumeration_property_; - bool toggled_property_; - bool not_on_gui_; - std::string buffer_type_; - std::string port_group_; - - std::string designation_; - Units units_ = Units::none; - std::string comment_; -public: - bool IsSwitch() const { - return min_value_ == 0 && max_value_ == 1 - && (integer_property_ || toggled_property_ || enumeration_property_); - } - float rangeToValue(float range) const - { - float value; - if (is_logarithmic_) + bool hasMidiOutput() const { - value = std::pow(max_value_/min_value_,range)*min_value_; - } else { - value = (max_value_-min_value_)*range+min_value_; - } - if (integer_property_ || enumeration_property_) - { - value = std::round(value); - } else if (range_steps_ >= 2) - { - value = std::round(value*(range_steps_-1))/(range_steps_-1); - } - if (toggled_property_) - { - value = value == 0 ? 0: max_value_; - } - if (value > max_value_) value = max_value_; - if (value < min_value_) value = min_value_; - return value; - } - LV2_PROPERTY_GETSET(symbol); - LV2_PROPERTY_GETSET_SCALAR(index); - LV2_PROPERTY_GETSET(name); - LV2_PROPERTY_GETSET(classes); - LV2_PROPERTY_GETSET(scale_points); - LV2_PROPERTY_GETSET_SCALAR(min_value); - LV2_PROPERTY_GETSET_SCALAR(max_value); - LV2_PROPERTY_GETSET_SCALAR(default_value); - - LV2_PROPERTY_GETSET_SCALAR(is_input); - LV2_PROPERTY_GETSET_SCALAR(is_output); - LV2_PROPERTY_GETSET_SCALAR(is_control_port); - LV2_PROPERTY_GETSET_SCALAR(is_audio_port); - LV2_PROPERTY_GETSET_SCALAR(is_atom_port); - LV2_PROPERTY_GETSET_SCALAR(is_cv_port); - LV2_PROPERTY_GETSET_SCALAR(is_valid); - LV2_PROPERTY_GETSET_SCALAR(supports_midi); - LV2_PROPERTY_GETSET_SCALAR(supports_time_position); - LV2_PROPERTY_GETSET_SCALAR(is_logarithmic); - LV2_PROPERTY_GETSET_SCALAR(display_priority); - LV2_PROPERTY_GETSET_SCALAR(range_steps); - LV2_PROPERTY_GETSET_SCALAR(trigger); - LV2_PROPERTY_GETSET_SCALAR(integer_property); - LV2_PROPERTY_GETSET_SCALAR(enumeration_property); - LV2_PROPERTY_GETSET_SCALAR(toggled_property); - LV2_PROPERTY_GETSET_SCALAR(not_on_gui); - LV2_PROPERTY_GETSET(port_group); - LV2_PROPERTY_GETSET(comment); - LV2_PROPERTY_GETSET_SCALAR(units); - - LV2_PROPERTY_GETSET(buffer_type); - - Lv2BufferType GetBufferType() { - if (buffer_type_ == "") return Lv2BufferType::None; - if (buffer_type_ == LV2_ATOM__Sequence) return Lv2BufferType::Sequence; - return Lv2BufferType::Unknown; - } - -public: - Lv2PortInfo() { } - ~Lv2PortInfo() = default; - bool is_a(PiPedalHost*lv2Plugins,const char*classUri); - - static json_map::storage_type jmap; - -}; - -class Lv2PortGroup { -private: - std::string uri_; - std::string symbol_; - std::string name_; - -public: - LV2_PROPERTY_GETSET(uri); - LV2_PROPERTY_GETSET(symbol); - LV2_PROPERTY_GETSET(name); - - Lv2PortGroup() { } - Lv2PortGroup(PiPedalHost*lv2Host,const std::string &groupUri); - - static json_map::storage_type jmap; -}; - -class Lv2PluginInfo { -private: - friend class PiPedalHost; -public: - Lv2PluginInfo(PiPedalHost*lv2Host,const LilvPlugin*); - Lv2PluginInfo() { } -private: - bool HasFactoryPresets(PiPedalHost*lv2Host, const LilvPlugin*plugin); - std::string uri_; - std::string name_; - std::string plugin_class_; - std::vector supported_features_; - std::vector required_features_; - std::vector optional_features_; - std::vector extensions_; - bool has_factory_presets_ = false; - - std::string author_name_; - std::string author_homepage_; - - std::string comment_; - std::vector > ports_; - std::vector > port_groups_; - bool is_valid_ = false; - - bool IsSupportedFeature(const std::string &feature) const; - -public: - LV2_PROPERTY_GETSET(uri) - LV2_PROPERTY_GETSET(name) - LV2_PROPERTY_GETSET(plugin_class) - LV2_PROPERTY_GETSET(supported_features) - LV2_PROPERTY_GETSET(required_features) - LV2_PROPERTY_GETSET(optional_features) - LV2_PROPERTY_GETSET(author_name) - LV2_PROPERTY_GETSET(author_homepage) - LV2_PROPERTY_GETSET(comment) - LV2_PROPERTY_GETSET(extensions) - LV2_PROPERTY_GETSET(ports) - LV2_PROPERTY_GETSET(is_valid) - LV2_PROPERTY_GETSET(port_groups) - LV2_PROPERTY_GETSET(has_factory_presets) - - const Lv2PortInfo& getPort(const std::string&symbol) - { - for (size_t i = 0; i < ports_.size(); ++i) - { - if (ports_[i]->symbol() == symbol) + for (size_t i = 0; i < ports_.size(); ++i) { - return *(ports_[i].get()); + if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_output()) + { + return true; + } } } - throw PiPedalArgumentException("Port not found."); - } - bool hasExtension(const std::string&uri) - { - for (int i = 0; i < extensions_.size(); ++i) - { - if (extensions_[i] == uri) return true; - } - return false; - } - bool hasCvPorts() const { - for (size_t i = 0; i < ports_.size(); ++i) - { - if (ports_[i]->is_cv_port()) - { - return true; - } - } - return false; - } - bool hasMidiInput() const { - for (size_t i = 0; i < ports_.size(); ++i) - { - if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_input()) - { - return true; - } - } - } - bool hasMidiOutput() const { - for (size_t i = 0; i < ports_.size(); ++i) - { - if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_output()) - { - return true; - } - } - } + public: + virtual ~Lv2PluginInfo(); -public: - - virtual ~Lv2PluginInfo(); - - static json_map::storage_type jmap; - -}; + static json_map::storage_type jmap; + }; class Lv2PluginUiPortGroup { @@ -430,9 +455,8 @@ public: Lv2PluginUiPortGroup( const std::string &symbol, const std::string &name, const std::string &parent_group, int32_t programListId) - : symbol_(symbol),name_(name),parent_group_(parent_group),program_list_id_(programListId) + : symbol_(symbol), name_(name), parent_group_(parent_group), program_list_id_(programListId) { - } public: @@ -531,6 +555,7 @@ public: std::vector controls_; std::vector port_groups_; + std::vector fileProperties_; public: LV2_PROPERTY_GETSET(uri) @@ -547,61 +572,32 @@ public: LV2_PROPERTY_GETSET(controls) LV2_PROPERTY_GETSET(port_groups) LV2_PROPERTY_GETSET_SCALAR(is_vst3) + LV2_PROPERTY_GETSET(fileProperties) static json_map::storage_type jmap; }; - - -class IHost { -public: - virtual LilvWorld*getWorld() = 0; - virtual LV2_URID_Map* GetLv2UridMap() = 0; - virtual LV2_URID GetLv2Urid(const char*uri) = 0; - virtual std::string Lv2UriudToString(LV2_URID urid) = 0; - - virtual LV2_Feature*const*GetLv2Features() const = 0; - virtual double GetSampleRate() const = 0; - virtual void SetMaxAudioBufferSize(size_t size) = 0; - virtual size_t GetMaxAudioBufferSize() const = 0; - virtual size_t GetAtomBufferSize() const = 0; - - virtual bool HasMidiInputChannel() const = 0; - virtual int GetNumberOfInputAudioChannels() const = 0; - virtual int GetNumberOfOutputAudioChannels() const = 0; - virtual std::shared_ptr GetPluginInfo(const std::string&uri) const = 0; - - - virtual IEffect*CreateEffect(PedalBoardItem &pedalBoard) = 0; -}; - - -class LilvNodePtr { -protected: - LilvNode *node = nullptr; - LilvNodePtr(const LilvNodePtr&) {}; // no copy. - LilvNodePtr(const LilvNode*) {}; // const LilvNodes are owned by lilv by convention. -public: - LilvNodePtr() { } - LilvNodePtr(LilvNode*node) { this->node = node; } - ~LilvNodePtr() { Free(); } - void Free() { if (node != nullptr) lilv_node_free(node); node = nullptr;} - - operator const LilvNode *() + class IHost { - return this->node; - } + public: + virtual LilvWorld *getWorld() = 0; + virtual LV2_URID_Map *GetLv2UridMap() = 0; + virtual LV2_URID GetLv2Urid(const char *uri) = 0; + virtual std::string Lv2UriudToString(LV2_URID urid) = 0; - LilvNode **operator&() - { - return &(this->node); - } - operator bool() - { - return this->node != nullptr; - } - LilvNodePtr&operator=(LilvNode*node) { Free(); this->node = node; return *this;} -}; + virtual LV2_Feature *const *GetLv2Features() const = 0; + virtual double GetSampleRate() const = 0; + virtual void SetMaxAudioBufferSize(size_t size) = 0; + virtual size_t GetMaxAudioBufferSize() const = 0; + virtual size_t GetAtomBufferSize() const = 0; + + virtual bool HasMidiInputChannel() const = 0; + virtual int GetNumberOfInputAudioChannels() const = 0; + virtual int GetNumberOfOutputAudioChannels() const = 0; + virtual std::shared_ptr GetPluginInfo(const std::string &uri) const = 0; + + virtual IEffect *CreateEffect(PedalBoardItem &pedalBoard) = 0; + }; } @@ -609,174 +605,208 @@ public: #include "vst3/Vst3Host.hpp" #endif +namespace pipedal +{ -namespace pipedal{ - - - - -class PiPedalHost: private IHost { -private: + class PiPedalHost : private IHost + { + private: #if ENABLE_VST3 - Vst3Host::Ptr vst3Host; + Vst3Host::Ptr vst3Host; #endif - static const char *RDFS_COMMENT_URI; - class LilvUris { + friend class pipedal::AutoLilvNode; + friend class pipedal::PiPedalUI; + static const char *RDFS_COMMENT_URI; public: - void Initialize(LilvWorld*pWorld); - void Free(); + class LilvUris + { + public: + void Initialize(LilvWorld *pWorld); + void Free(); - LilvNodePtr rdfsComment; - LilvNodePtr logarithic_uri; - LilvNodePtr display_priority_uri; - LilvNodePtr range_steps_uri; - LilvNodePtr integer_property_uri; - LilvNodePtr enumeration_property_uri; - LilvNodePtr toggle_property_uri; - LilvNodePtr not_on_gui_property_uri; - LilvNodePtr midiEventNode; - LilvNodePtr designationNode; - LilvNodePtr portGroupUri; - LilvNodePtr unitsUri; - LilvNodePtr bufferType_uri; - LilvNodePtr pset_Preset; - LilvNodePtr rdfs_label; - LilvNodePtr symbolUri; - LilvNodePtr nameUri; + AutoLilvNode rdfsComment; + AutoLilvNode logarithic_uri; + AutoLilvNode display_priority_uri; + AutoLilvNode range_steps_uri; + AutoLilvNode integer_property_uri; + AutoLilvNode enumeration_property_uri; + AutoLilvNode toggle_property_uri; + AutoLilvNode not_on_gui_property_uri; + AutoLilvNode midiEventNode; + AutoLilvNode designationNode; + AutoLilvNode portGroupUri; + AutoLilvNode unitsUri; + AutoLilvNode bufferType_uri; + AutoLilvNode pset_Preset; + AutoLilvNode rdfs_label; + AutoLilvNode symbolUri; + AutoLilvNode nameUri; - LilvNodePtr time_Position; - LilvNodePtr time_barBeat; - LilvNodePtr time_beatsPerMinute; - LilvNodePtr time_speed; + AutoLilvNode lv2Core__name; + AutoLilvNode pipedalUI__ui; + AutoLilvNode pipedalUI__fileProperties; + AutoLilvNode pipedalUI__directory; + AutoLilvNode pipedalUI__patchProperty; + AutoLilvNode pipedalUI__defaultFile; - LilvNodePtr appliesTo; - LilvNodePtr isA; + AutoLilvNode pipedalUI__fileProperty; + AutoLilvNode pipedalUI__fileTypes; + AutoLilvNode pipedalUI__fileExtension; + AutoLilvNode pipedalUI__outputPorts; + AutoLilvNode pipedalUI__text; + + AutoLilvNode time_Position; + AutoLilvNode time_barBeat; + AutoLilvNode time_beatsPerMinute; + AutoLilvNode time_speed; + + AutoLilvNode appliesTo; + AutoLilvNode isA; + }; + LilvUris lilvUris; + + private: + + bool vst3Enabled = true; + + LilvNode *get_comment(const std::string &uri); + + size_t maxBufferSize = 1024; + size_t maxAtomBufferSize = 16 * 1024; + bool hasMidiInputChannel; + int numberOfAudioInputChannels = 2; + int numberOfAudioOutputChannels = 2; + double sampleRate = 0; + + std::string vst3CachePath; + + LV2_Feature *const *lv2Features = nullptr; + MapFeature mapFeature; + LogFeature logFeature; + OptionsFeature optionsFeature; + + static void fn_LilvSetPortValueFunc(const char *port_symbol, + void *user_data, + const void *value, + uint32_t size, + uint32_t type); + + LilvWorld *pWorld; + void free_world(); + + std::vector> plugins_; + std::vector ui_plugins_; + + std::map> classesMap; + + friend class Lv2PluginInfo; + friend class Lv2PortInfo; + friend class Lv2PortGroup; + + std::shared_ptr GetPluginClass(const LilvPluginClass *pClass); + std::shared_ptr MakePluginClass(const LilvPluginClass *pClass); + Lv2PluginClasses GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort); + + bool classesLoaded = false; + // IHost implementation + + public: + void LogError(const std::string&message) + { + logFeature.LogError("%s",message.c_str()); + } + void LogWarning(const std::string&message) + { + logFeature.LogWarning("%s",message.c_str()); + } + void LogNote(const std::string&message) + { + logFeature.LogNote("%s",message.c_str()); + } + void LogTrace(const std::string&message) + { + logFeature.LogTrace("%s",message.c_str()); + } + virtual LilvWorld *getWorld() + { + return pWorld; + } + + private: + virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; } + virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; } + virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; } + virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; } + virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; } + virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; } + virtual LV2_Feature *const *GetLv2Features() const { return this->lv2Features; } + virtual LV2_URID_Map *GetLv2UridMap() + { + return this->mapFeature.GetMap(); + } + static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type); + + virtual IEffect *CreateEffect(PedalBoardItem &pedalBoardItem); + void LoadPluginClassesFromLilv(); + void AddJsonClassesToMap(std::shared_ptr pluginClass); + + public: + PiPedalHost(); + + void SetConfiguration(const PiPedalConfiguration &configuration); + + virtual ~PiPedalHost(); + + IHost *asIHost() { return this; } + + virtual Lv2PedalBoard *CreateLv2PedalBoard(PedalBoard &pedalBoard); + + void setSampleRate(double sampleRate) + { + this->sampleRate = sampleRate; + } + double GetSampleRate() const + { + return sampleRate; + } + + class Urids; + + Urids *urids; + + void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings); + + std::shared_ptr GetPluginClass(const std::string &uri) const; + bool is_a(const std::string &class_, const std::string &target_class); + + std::shared_ptr GetLv2PluginClass() const; + + std::vector> GetPlugins() const { return plugins_; } + const std::vector &GetUiPlugins() const { return ui_plugins_; } + + virtual std::shared_ptr GetPluginInfo(const std::string &uri) const; + + static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2"; + + void LoadPluginClassesFromJson(std::filesystem::path jsonFile); + void Load(const char *lv2Path = PiPedalHost::DEFAULT_LV2_PATH); + + virtual LV2_URID GetLv2Urid(const char *uri) + { + return this->mapFeature.GetUrid(uri); + } + virtual std::string Lv2UriudToString(LV2_URID urid) + { + return this->mapFeature.UridToString(urid); + } + + PluginPresets GetFactoryPluginPresets(const std::string &pluginUri); + std::vector LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem, + const std::string &presetUri); }; - bool vst3Enabled = true; - - LilvUris lilvUris; - - LilvNode *get_comment(const std::string &uri); - - - size_t maxBufferSize = 1024; - size_t maxAtomBufferSize = 16*1024; - bool hasMidiInputChannel; - int numberOfAudioInputChannels = 2; - int numberOfAudioOutputChannels = 2; - double sampleRate = 0; - - std::string vst3CachePath; - - LV2_Feature*const*lv2Features = nullptr; - MapFeature mapFeature; - LogFeature logFeature; - OptionsFeature optionsFeature; - - static void fn_LilvSetPortValueFunc(const char* port_symbol, - void* user_data, - const void* value, - uint32_t size, - uint32_t type); - - LilvWorld *pWorld; - void free_world(); - - - std::vector > plugins_; - std::vector ui_plugins_; - - std::map > classesMap; - - friend class Lv2PluginInfo; - friend class Lv2PortInfo; - friend class Lv2PortGroup; - - std::shared_ptr GetPluginClass(const LilvPluginClass*pClass); - std::shared_ptr MakePluginClass(const LilvPluginClass*pClass); - Lv2PluginClasses GetPluginPortClass(const LilvPlugin*lilvPlugin,const LilvPort *lilvPort); - - bool classesLoaded = false; - -private: - // IHost implementation - virtual LilvWorld*getWorld() { - return pWorld; - } - virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; } - virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; } - virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; } - virtual bool HasMidiInputChannel() const { return hasMidiInputChannel;} - virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; } - virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels;} - virtual LV2_Feature*const* GetLv2Features() const { return this->lv2Features; } - virtual LV2_URID_Map* GetLv2UridMap() { - return this->mapFeature.GetMap(); - } - static void PortValueCallback(const char*symbol,void*user_data,const void* value,uint32_t size, uint32_t type); - - virtual IEffect*CreateEffect(PedalBoardItem &pedalBoardItem); - void LoadPluginClassesFromLilv(); - void AddJsonClassesToMap(std::shared_ptr pluginClass); - -public: - PiPedalHost(); - - void SetConfiguration(const PiPedalConfiguration&configuration); - - virtual ~PiPedalHost(); - - IHost* asIHost() { return this;} - - virtual Lv2PedalBoard * CreateLv2PedalBoard(PedalBoard& pedalBoard); - - void setSampleRate(double sampleRate) - { - this->sampleRate = sampleRate; - } - double GetSampleRate() const { - return sampleRate; - } - - class Urids; - - Urids *urids; - - void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings); - - std::shared_ptr GetPluginClass(const std::string&uri) const; - bool is_a(const std::string&class_, const std::string & target_class); - - std::shared_ptr GetLv2PluginClass() const; - - std::vector > GetPlugins() const { return plugins_; } - const std::vector &GetUiPlugins() const { return ui_plugins_; } - - virtual std::shared_ptr GetPluginInfo(const std::string&uri) const; - - - static constexpr const char * DEFAULT_LV2_PATH="/usr/lib/lv2"; - - - void LoadPluginClassesFromJson(std::filesystem::path jsonFile); - void Load(const char*lv2Path = PiPedalHost::DEFAULT_LV2_PATH); - - virtual LV2_URID GetLv2Urid(const char*uri) { - return this->mapFeature.GetUrid(uri); - } - virtual std::string Lv2UriudToString(LV2_URID urid) { - return this->mapFeature.UridToString(urid); - } - - PluginPresets GetFactoryPluginPresets(const std::string &pluginUri); - std::vector LoadFactoryPluginPreset(PedalBoardItem*pedalBoardItem, - const std::string&presetUri); - - -}; #undef LV2_PROPERTY_GETSET #undef LV2_PROPERTY_GETSET_SCALAR diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 075838e..fd0050e 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -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) { @@ -1716,4 +1731,9 @@ void PiPedalModel::SetSystemMidiBindings(std::vector &bindings) delete[] t; -} \ No newline at end of file +} + +std::vector PiPedalModel::GetFileList(const PiPedalFileProperty&fileProperty) +{ + return this->storage.GetFileList(PiPedalFilesProperty&fileProperty); +} diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 787fed8..7a4e260 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -281,6 +281,8 @@ namespace pipedal std::map GetFavorites() const; void SetFavorites(const std::map &favorites); + + std::vector GetFileList(const PiPedalFileProperty&fileProperty); }; } // namespace pipedal. \ No newline at end of file diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 5f06e58..b53609d 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -1246,6 +1246,12 @@ public: std::vector bindings = this->model.GetSystemMidiBidings(); this->Reply(replyTo,"getSystemMidiBindings",bindings); } + else if (message == "requestFileList") + { + PiPedalFileProperty fileProperty; + std::vector list = this->model.GetFileList(fileProperty); + this->Reply(replyTo,"requestFileList",list); + } else { Lv2Log::error("Unknown message received: %s", message.c_str()); diff --git a/src/PiPedalUI.cpp b/src/PiPedalUI.cpp new file mode 100644 index 0000000..99c0c12 --- /dev/null +++ b/src/PiPedalUI.cpp @@ -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(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::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri) +{ + std::vector 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() \ No newline at end of file diff --git a/src/PiPedalUI.hpp b/src/PiPedalUI.hpp new file mode 100644 index 0000000..3f03bdd --- /dev/null +++ b/src/PiPedalUI.hpp @@ -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 +#include +#include +#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 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 fileTypes_; + std::string patchProperty_; + std::string defaultFile_; + public: + using ptr = std::shared_ptr; + 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 &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(PiPedalHost*pHost, const LilvNode*uiNode); + const std::vector& fileProperties() const + { + return fileProperites_; + } + private: + std::vector fileProperites_; + }; +}; \ No newline at end of file diff --git a/src/Storage.cpp b/src/Storage.cpp index 417c7d3..d5f2d95 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -27,6 +27,7 @@ #include "Lv2Log.hpp" #include #include +#include "PiPedalUI.hpp" using namespace pipedal; @@ -254,6 +255,11 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const { return this->dataRoot / "plugin_presets"; } +std::filesystem::path Storage::GetAudioFilesDirectory() const +{ + return this->dataRoot / "audio_uploads"; +} + std::filesystem::path Storage::GetCurrentPresetPath() const { return this->dataRoot / "currentPreset.json"; @@ -1383,6 +1389,54 @@ std::vector Storage::GetSystemMidiBindings() 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 Storage::GetFileList(const PiPedalFileProperty&fileProperty) +{ + sanityCheckDirectory(fileProperty.directory()); + std::filesystem::path path = this->GetAudioFilesDirectory() / fileProperty.directory(); + +} + JSON_MAP_BEGIN(UserSettings) JSON_MAP_REFERENCE(UserSettings, governor) diff --git a/src/Storage.hpp b/src/Storage.hpp index 2e287e8..60630c3 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -33,6 +33,7 @@ namespace pipedal { +class PiPedalFileProperty; class CurrentPreset { public: @@ -67,6 +68,7 @@ private: static std::string SafeDecodeName(const std::string& name); std::filesystem::path GetPresetsDirectory() const; std::filesystem::path GetPluginPresetsDirectory() const; + std::filesystem::path GetAudioFilesDirectory() const std::filesystem::path GetIndexFileName() const; std::filesystem::path GetBankFileName(const std::string & name) const; std::filesystem::path GetChannelSelectionFileName(); @@ -134,6 +136,8 @@ public: void MoveBank(int from, int to); int64_t DeleteBank(int64_t bankId); + std::vector GetFileList(const PiPedalFileProperty&fileProperty); + void SetJackChannelSelection(const JackChannelSelection&channelSelection); const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration); diff --git a/src/Worker.cpp b/src/Worker.cpp index bf30db2..7227091 100644 --- a/src/Worker.cpp +++ b/src/Worker.cpp @@ -39,6 +39,8 @@ #include #include #include "Lv2Log.hpp" +#include +#include // for nice() using namespace pipedal; @@ -103,6 +105,14 @@ void Worker::EmitResponses() } 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 { while (true) diff --git a/src/defaultFavorites.json b/src/defaultFavorites.json index c70b0ef..56b1991 100644 --- a/src/defaultFavorites.json +++ b/src/defaultFavorites.json @@ -8,5 +8,6 @@ "http://two-play.com/plugins/toob-power-stage-2": true, "http://two-play.com/plugins/toob-spectrum": 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 } \ No newline at end of file diff --git a/src/json.cpp b/src/json.cpp index 247c6e8..06c9d6d 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -22,6 +22,7 @@ #include #include #include "PiPedalException.hpp" +#include "json_variant.hpp" using namespace pipedal; @@ -631,3 +632,8 @@ void json_reader::throw_format_error(const char*error) throw PiPedalException(message); } + +// void json_writer::write(const json_variant &value) +// { +// ((JsonSerializable *)&value)->write_json(*this); +// } diff --git a/src/json.hpp b/src/json.hpp index c0c6de6..9748e0a 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -30,946 +30,995 @@ #include #include "PiPedalException.hpp" #include - - +#include +#include #define DECLARE_JSON_MAP(CLASSNAME) \ - static json_map::storage_type jmap + static json_map::storage_type jmap -#define JSON_MAP_BEGIN(CLASSNAME) \ - json_map::storage_type CLASSNAME::jmap { { +#define JSON_MAP_BEGIN(CLASSNAME) \ + json_map::storage_type CLASSNAME::jmap \ + { \ + { -#define JSON_MAP_REFERENCE(class,name) \ - json_map::reference(#name,&class::name##_), +#define JSON_MAP_REFERENCE(class, name) \ + json_map::reference(#name, &class ::name##_), -#define JSON_MAP_REFERENCE_CONDITIONAL(class,name, conditionFn) \ - json_map::conditional_reference(#name,&class::name##_,conditionFn) , - +#define JSON_MAP_REFERENCE_CONDITIONAL(class, name, conditionFn) \ + json_map::conditional_reference(#name, &class ::name##_, conditionFn), #define JSON_MAP_END() \ -}}; + } \ + } \ + ; namespace pipedal { -// a function pointer of type bool (*)(const CLASS*,const MEMBER_TYPE&value) (because pre-C++ 20 templated function pointers are *hard*) -template class JsonConditionFunction { -public: - using Pointer = bool (*)(const CLASS*self,const MEMBER_TYPE&value); -}; - -//---------------------------------------------------------------------------------- - - -class json_reader; -class json_writer; - -class JsonWritable { -public: - virtual void write_members(json_writer&writer) const = 0; -}; - -template -class json_member_reference_base { -private: - const char*name_; -protected: - json_member_reference_base(const char*name) - :name_(name) + // a function pointer of type bool (*)(const CLASS*,const MEMBER_TYPE&value) (because pre-C++ 20 templated function pointers are *hard*) + template + class JsonConditionFunction { - } -public: - virtual ~json_member_reference_base() {} - const char*name() { return this->name_; } - virtual bool canWrite(const CLASS *self) { return true; } - virtual void read_value(json_reader&reader, CLASS*self) = 0; - virtual void write_value(json_writer&writer, const CLASS*self) = 0; -}; -template -class json_member_reference: public json_member_reference_base { -public: - virtual ~json_member_reference() {} - json_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer) - : json_member_reference_base(name) - , member_pointer(member_pointer) - { - } - MEMBER_TYPE CLASS::*member_pointer; - - void read_value(json_reader&reader, CLASS*self); - void write_value(json_writer&writer, const CLASS*self); - - -}; -template -class json_conditional_member_reference: public json_member_reference_base { -public: - virtual ~json_conditional_member_reference() {} - - json_conditional_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction::Pointer condition) - : json_member_reference_base(name) - , member_pointer(member_pointer) - , condition_(condition) - { - } - MEMBER_TYPE CLASS::*member_pointer; - - typename JsonConditionFunction::Pointer condition_; - - virtual bool canWrite(const CLASS *self); - - void read_value(json_reader&reader, CLASS*self); - void write_value(json_writer&writer, const CLASS*self); -}; - - - -template -class json_enum_converter { -public: - virtual CLASS fromString(const std::string&value) const = 0; - virtual const std::string& toString(CLASS value) const = 0; -}; - -template -class json_enum_member_reference: public json_member_reference_base { -public: - json_enum_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer, const json_enum_converter*converter) - : json_member_reference_base(name) - , member_pointer(member_pointer) - , converter_(converter) - { - } - MEMBER_TYPE CLASS::*member_pointer; - const json_enum_converter *converter_; - - - void read_value(json_reader&reader, CLASS*self); - void write_value(json_writer&writer, const CLASS*self); - - -}; - - -//---------------------------------------------------------------------------------- -class json_map_base { - -}; - -template -class json_map_impl: public json_map_base { -private: - std::vector* > members_; - json_map_impl(const json_map_impl&) { } // disable copy constructor. -public: - using members_t = std::vector*>; - - json_map_impl(const members_t &members) - : members_(members) - { - - } - json_map_impl(members_t &&members) - : members_(std::forward(members)) - { - - } - virtual ~json_map_impl() - { - for (auto member: members_) - { - delete member; - } - members_.resize(0); - } - void read_property(json_reader* reader,const char*memberName,CLASS *pObject); - void write_members(json_writer* writer, const CLASS*pObject); - - -}; - - - -class json_map { -public: - - template class storage_type : public json_map_impl { public: + using Pointer = bool (*)(const CLASS *self, const MEMBER_TYPE &value); + }; - using members_t = std::vector*>; + //---------------------------------------------------------------------------------- - storage_type(const members_t &members) - : json_map_impl(members) + class json_reader; + class json_writer; + + class JsonSerializable + { + public: + virtual void write_json(json_writer &writer) const = 0; + virtual void read_json(json_reader &reader) = 0; + }; + template + concept IsJsonSerializable = std::derived_from; + + + class JsonMemberWritable + { + public: + virtual void write_members(json_writer &writer) const = 0; + }; + + template + class json_member_reference_base + { + private: + const char *name_; + + protected: + json_member_reference_base(const char *name) + : name_(name) { } - storage_type(members_t &&members) - : json_map_impl(std::forward(members)) + + public: + virtual ~json_member_reference_base() {} + const char *name() { return this->name_; } + virtual bool canWrite(const CLASS *self) { return true; } + virtual void read_value(json_reader &reader, CLASS *self) = 0; + virtual void write_value(json_writer &writer, const CLASS *self) = 0; + }; + template + class json_member_reference : public json_member_reference_base + { + public: + virtual ~json_member_reference() {} + json_member_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer) + : json_member_reference_base(name), member_pointer(member_pointer) { } + MEMBER_TYPE CLASS::*member_pointer; + + void read_value(json_reader &reader, CLASS *self); + void write_value(json_writer &writer, const CLASS *self); }; - template - static json_member_reference * - reference(const char*name,MEMBER_TYPE CLASS::*member_pointer) + template + class json_conditional_member_reference : public json_member_reference_base { - return new json_member_reference(name,member_pointer); - }; - template - static json_enum_member_reference * - enum_reference( - const char*name, - MEMBER_TYPE CLASS::*member_pointer, - const ENUM_CONVERTER*converter) - { - return new json_enum_member_reference(name,member_pointer,converter); + public: + virtual ~json_conditional_member_reference() {} + + json_conditional_member_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, typename JsonConditionFunction::Pointer condition) + : json_member_reference_base(name), member_pointer(member_pointer), condition_(condition) + { + } + MEMBER_TYPE CLASS::*member_pointer; + + typename JsonConditionFunction::Pointer condition_; + + virtual bool canWrite(const CLASS *self); + + void read_value(json_reader &reader, CLASS *self); + void write_value(json_writer &writer, const CLASS *self); }; - template - static json_conditional_member_reference * - conditional_reference(const char*name,MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction::Pointer condition ) + template + class json_enum_converter { - return new json_conditional_member_reference(name,member_pointer,condition); + public: + virtual CLASS fromString(const std::string &value) const = 0; + virtual const std::string &toString(CLASS value) const = 0; }; -}; - -//---------------------------------------------------------------------------------- - -template -class HasJsonPropertyMap { - - template - static std::true_type test (decltype(TYPE::jmap) *) { return std::true_type();}; - - template - static std::false_type test (...); - -public: - static constexpr bool value = decltype(test(nullptr))::value; -}; - - -//---------------------------------------------------------------------------------- - - - -class json_writer; - - -class json_writer { -public: - const char*CRLF; -private: - bool allowNaN_ = true; - std::ostream &os; - int indent_level; - bool compressed; - const int TAB_SIZE = 2; - - const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; - const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; - const uint16_t UTF16_SURROGATE_MASK = 0x3FFFU; - - static const uint32_t UTF8_ONE_BYTE_MASK = 0x80; - static const uint32_t UTF8_ONE_BYTE_BITS = 0; - static const uint32_t UTF8_TWO_BYTES_MASK = 0xE0; - static const uint32_t UTF8_TWO_BYTES_BITS = 0xC0; - static const uint32_t UTF8_THREE_BYTES_MASK = 0xF0; - static const uint32_t UTF8_THREE_BYTES_BITS = 0xE0; - static const uint32_t UTF8_FOUR_BYTES_MASK = 0xF8; - static const uint32_t UTF8_FOUR_BYTES_BITS = 0xF0; - static const uint32_t UTF8_CONTINUATION_MASK = 0xC0; - static const uint32_t UTF8_CONTINUATION_BITS = 0x80; - - -public: - static std::string encode_string(const std::string &text) + template + class json_enum_member_reference : public json_member_reference_base { - std::stringstream s; - json_writer writer(s); - writer.write(text); - return s.str(); - } - void write_raw(const char*text) { - os << text; - } - using string_view = boost::string_view; - json_writer(std::ostream &os, bool compressed = true, bool allowNaN = true) - : os(os) - , compressed(compressed) - , allowNaN_(allowNaN) - , indent_level(0) - { - this->CRLF = compressed? "" : "\r\n"; - } - bool allowNaN() const { return allowNaN_; } - void allowNaN(bool allow) { allowNaN_ = allow; } + public: + json_enum_member_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, const json_enum_converter *converter) + : json_member_reference_base(name), member_pointer(member_pointer), converter_(converter) + { + } + MEMBER_TYPE CLASS::*member_pointer; + const json_enum_converter *converter_; - void write(long long value) - { - os << value; - } - void write(unsigned long long value) - { - os << value; - } - void write(long value) - { - os << value; - } - void write(unsigned long value) - { - os << value; - } - void write(int value) - { - os << value; - } - void write(unsigned int value) - { - os << value; - } + void read_value(json_reader &reader, CLASS *self); + void write_value(json_writer &writer, const CLASS *self); + }; + //---------------------------------------------------------------------------------- + class json_map_base + { + }; + template + class json_map_impl : public json_map_base + { + private: + std::vector *> members_; + json_map_impl(const json_map_impl &) {} // disable copy constructor. + public: + using members_t = std::vector *>; -private: - static void throw_encoding_error(); + json_map_impl(const members_t &members) + : members_(members) + { + } + json_map_impl(members_t &&members) + : members_(std::forward(members)) + { + } + virtual ~json_map_impl() + { + for (auto member : members_) + { + delete member; + } + members_.resize(0); + } + void read_property(json_reader *reader, const char *memberName, CLASS *pObject); + void write_members(json_writer *writer, const CLASS *pObject); + }; - static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end); - void write_utf16_char(uint16_t uc); + class json_map + { + public: + template + class storage_type : public json_map_impl + { + public: + using members_t = std::vector *>; -public: - void indent(); - std::ostream & output_stream() { return os; } + storage_type(const members_t &members) + : json_map_impl(members) + { + } + storage_type(members_t &&members) + : json_map_impl(std::forward(members)) + { + } + }; + template + static json_member_reference * + reference(const char *name, MEMBER_TYPE CLASS::*member_pointer) + { + return new json_member_reference(name, member_pointer); + }; + template + static json_enum_member_reference * + enum_reference( + const char *name, + MEMBER_TYPE CLASS::*member_pointer, + const ENUM_CONVERTER *converter) + { + return new json_enum_member_reference(name, member_pointer, converter); + }; - void write(bool value) { - os << (value? "true": "false"); - } - void write( - string_view v, - bool enforceValidUtf8 // throw errors for illegal (non-minimal) UTF-8 sequences. + template + static json_conditional_member_reference * + conditional_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, typename JsonConditionFunction::Pointer condition) + { + return new json_conditional_member_reference(name, member_pointer, condition); + }; + }; + + //---------------------------------------------------------------------------------- + + template + class HasJsonPropertyMap + { + + template + static std::true_type test(decltype(TYPE::jmap) *) { return std::true_type(); }; + + template + static std::false_type test(...); + + public: + static constexpr bool value = decltype(test(nullptr))::value; + }; + + template + class HasExplicitJsonWrite + { + + template + static std::true_type test() { return std::true_type(); }; + + template + static std::false_type test(...); + + public: + static constexpr bool value = decltype(test(nullptr))::value; + }; + + //---------------------------------------------------------------------------------- + + class json_writer; + + class json_writer + { + public: + const char *CRLF; + + private: + bool allowNaN_ = true; + std::ostream &os; + int indent_level; + bool compressed; + const int TAB_SIZE = 2; + + const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; + const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; + const uint16_t UTF16_SURROGATE_MASK = 0x3FFFU; + + static const uint32_t UTF8_ONE_BYTE_MASK = 0x80; + static const uint32_t UTF8_ONE_BYTE_BITS = 0; + static const uint32_t UTF8_TWO_BYTES_MASK = 0xE0; + static const uint32_t UTF8_TWO_BYTES_BITS = 0xC0; + static const uint32_t UTF8_THREE_BYTES_MASK = 0xF0; + static const uint32_t UTF8_THREE_BYTES_BITS = 0xE0; + static const uint32_t UTF8_FOUR_BYTES_MASK = 0xF8; + static const uint32_t UTF8_FOUR_BYTES_BITS = 0xF0; + static const uint32_t UTF8_CONTINUATION_MASK = 0xC0; + static const uint32_t UTF8_CONTINUATION_BITS = 0x80; + + public: + static std::string encode_string(const std::string &text) + { + std::stringstream s; + json_writer writer(s); + writer.write(text); + return s.str(); + } + void write_raw(const char *text) + { + os << text; + } + using string_view = boost::string_view; + json_writer(std::ostream &os, bool compressed = true, bool allowNaN = true) + : os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0) + { + this->CRLF = compressed ? "" : "\r\n"; + } + bool allowNaN() const { return allowNaN_; } + void allowNaN(bool allow) { allowNaN_ = allow; } + + void write(long long value) + { + os << value; + } + void write(unsigned long long value) + { + os << value; + } + void write(long value) + { + os << value; + } + void write(unsigned long value) + { + os << value; + } + void write(int value) + { + os << value; + } + void write(unsigned int value) + { + os << value; + } + + private: + static void throw_encoding_error(); + + static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end); + void write_utf16_char(uint16_t uc); + + public: + void indent(); + std::ostream &output_stream() { return os; } + + void write(bool value) + { + os << (value ? "true" : "false"); + } + void write( + string_view v, + bool enforceValidUtf8 // throw errors for illegal (non-minimal) UTF-8 sequences. ); - void write(string_view v) - { - write(v,true); - } - void write(const char*p) - { - write(string_view(p)); - } - - void write(const std::string&s) - { - write(string_view(s.c_str())); - } - void write(float f) - { - if (allowNaN_ && (std::isnan(f) || std::isinf(f))) + void write(string_view v) { - os << "NaN"; - } else { - os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format + write(v, true); } - } - void write(double f) - { - if (allowNaN_ && (std::isnan(f) || std::isinf(f))) + void write(const char *p) { - os << "NaN"; - } else { - os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format + write(string_view(p)); } - } - template void write(const std::vector &value) - { - if (std::is_fundamental() || std::is_assignable() || value.size() == 0) + void write(const std::string &s) { - // simple types: all on same line. - os << "[ "; - - if (value.size() >= 1) + write(string_view(s.c_str())); + } + void write(float f) + { + if (allowNaN_ && (std::isnan(f) || std::isinf(f))) { - write(value[0]); + os << "NaN"; } - for (size_t i = 1; i < value.size(); ++i) + else { - os << ","; - write(value[i]); + os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format } - os << "]"; - } else { - // complex types: one line per entry. - os << "[" << CRLF; - indent_level += TAB_SIZE; + } + void write(double f) + { + if (allowNaN_ && (std::isnan(f) || std::isinf(f))) + { + os << "NaN"; + } + else + { + os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format + } + } + + + template + void write(const std::vector &value) + { + if (std::is_fundamental() || std::is_assignable() || value.size() == 0) + { + // simple types: all on same line. + os << "[ "; + + if (value.size() >= 1) + { + write(value[0]); + } + for (size_t i = 1; i < value.size(); ++i) + { + os << ","; + write(value[i]); + } + os << "]"; + } + else + { + // complex types: one line per entry. + os << "[" << CRLF; + indent_level += TAB_SIZE; + bool first = true; + for (size_t i = 0; i < value.size(); ++i) + { + if (!first) + { + os << ',' << CRLF; + } + first = false; + indent(); + write(value[i]); + } + indent_level -= TAB_SIZE; + os << CRLF; + indent(); + os << "]"; + } + } + template < + class Category, + class T, + class Distance = std::ptrdiff_t, + class Pointer = T *, + class Reference = T &> + void write(std::iterator &it) + { + start_array(); bool first = true; - for (size_t i = 0; i < value.size(); ++i) + for (Reference ref : it) { if (!first) { - os << ',' << CRLF; + os << ", "; + os << CRLF; } - first = false; indent(); - write(value[i]); + os << ref; } - indent_level -= TAB_SIZE; - os << CRLF; - indent(); - os << "]"; - + end_array(); } - } - template< - class Category, - class T, - class Distance = std::ptrdiff_t, - class Pointer = T*, - class Reference = T& - > void write(std::iterator &it) { - start_array(); - bool first = true; - for( Reference ref: it) + + void write_json_member(const char *name, const char *json_text) { - if (!first) + write(name); + os << ": "; + os << json_text; + } + + template + void write_member(const char *name, const T &value) + { + write(name); + os << ": "; + write(value); + } + void start_object(); + void end_object(); + void start_array(); + void end_array(); + + /* + void write(const json_writable *obj) { - os << ", "; - os << CRLF; + if (obj == nullptr) + { + os << "null"; + } else { + start_object(); + obj->write(*this); + end_object(); + } } - indent(); - os << ref; - } - end_array(); - } - - void write_json_member(const char*name, const char*json_text) - { - write(name); - os << ": "; - os << json_text; - } - - template void write_member(const char*name,const T&value) - { - write(name); - os << ": "; - write(value); - } - void start_object(); - void end_object(); - void start_array(); - void end_array(); - - -/* - void write(const json_writable *obj) - { - if (obj == nullptr) + void write(const json_writable &obj) + { + start_object(); + obj.write(*this); + end_object(); + } + */ + private: + template + json_map::storage_type &get_object_map() + { + // static_assert(HasJsonPropertyMap::value,"Type does not have a Json propery map name jmap."); + // Type T must have public static propery jmap. + return T::jmap; + } + + public: + void write(const JsonSerializable *pWritable) + { + pWritable->write_json(*this); + } + void write(const JsonSerializable &writable) + { + writable.write_json(*this); + } + + void writeRawWritable(const JsonSerializable &writable) + { + writable.write_json(*this); + } + void write(const JsonMemberWritable *pWritable) { - os << "null"; - } else { start_object(); - obj->write(*this); + pWritable->write_members(*this); end_object(); } - } - void write(const json_writable &obj) - { - start_object(); - obj.write(*this); - end_object(); - } -*/ -private: - template - json_map::storage_type& get_object_map() - { - //static_assert(HasJsonPropertyMap::value,"Type does not have a Json propery map name jmap."); - // Type T must have public static propery jmap. - return T::jmap; - } -public: - - void write(const JsonWritable*pWritable) - { - start_object(); - pWritable->write_members(*this); - end_object(); - } - template - void write(const std::shared_ptr &obj) - { - write(obj.get()); - } - template - void write(const std::weak_ptr &obj) - { - auto p = obj.lock(); - write(p.get()); - } - template - void write(const std::unique_ptr &obj) - { - write(obj.get()); - } - - template - void write(const T*obj) - { - static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); - if (obj == nullptr) + template + void write(const std::shared_ptr &obj) { - write_raw("null"); - } else { - write(*obj); + write(obj.get()); + } + template + void write(const std::weak_ptr &obj) + { + auto p = obj.lock(); + write(p.get()); + } + template + void write(const std::unique_ptr &obj) + { + write(obj.get()); } - } - template - void write(T*obj) - { - static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); - if (obj == nullptr) + template + void write(const T *obj) { - write_raw("null"); - } else { - write(*obj); - } - - } - - template - void write(const T&obj) - { - static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); - - auto &map = get_object_map(); - start_object(); - - map.write_members(this,&obj); - - end_object(); - } - template - void write(const std::map &map) - { - start_object(); - - bool firstTime = true; - for (const auto&v : map) - { - if (!firstTime) + static_assert(HasJsonPropertyMap::value, "Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); + if (obj == nullptr) { - write_raw(","); - write_raw(CRLF); + write_raw("null"); } - indent(); - firstTime = false; - write_member(v.first.c_str(),v.second); - } - end_object(); - } - - - - - -}; - - -class json_reader { -private: - std::istream &is_; - - const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; - const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; - const uint16_t UTF16_SURROGATE_MASK = 0x3FFU; - bool allowNaN_ = true; - - - -public: - json_reader(std::istream &input, bool allowNaN = true) - : is_(input) - { - this->allowNaN_ = allowNaN; - } - - bool allowNaN() const { return allowNaN_;} - void allowNaN(bool allow) { allowNaN_ = allow; } - -private: - void throw_format_error(const char*error); - - void throw_format_error() - { - throw_format_error("Invalid file format"); - } - - - - inline bool is_whitespace(char c) - { - switch (c) - { - case 0x20: - case 0x0A: - case 0x0D: - case 0x09: - return true; - default: - return false; - } - - } - char get() { - int ic = is_.get(); - if (ic == -1) throw_format_error("Unexpected end of file"); - return (char)ic; - } - - void skip_whitespace(); - - void read_object_start(); - uint16_t read_hex(); - uint16_t read_u_escape(); - - template - json_map::storage_type& get_object_map() - { - // Type T must have public static propery map. - return T::jmap; - } -public: - void consume(char expected); - void consumeToken(const char*expectedToken, const char*errorMessage); - int peek() - { - skip_whitespace(); - return is_.peek(); - } -public: - std::string read_string(); - -public: - bool is_complete(); - - template - void read(T*pObject) - { - char c; - consume('{'); - - auto &map = get_object_map(); - while (true) - { - c = peek(); - if (c == '}') + else { - c = get(); - break; + write(*obj); } - std::string memberName = read_string(); + } - consume(':'); + template + void write(T *obj) + { + static_assert(HasJsonPropertyMap::value, "Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); + if (obj == nullptr) + { + write_raw("null"); + } + else + { + write(*obj); + } + } + + template + requires IsJsonSerializable + void write(const T&obj) + { + writeRawWritable(obj); + } + + template + void write(const T &obj) + { + static_assert(HasJsonPropertyMap::value, "Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); + + auto &map = get_object_map(); + start_object(); + + map.write_members(this, &obj); + + end_object(); + } + template + void write(const std::map &map) + { + start_object(); + + bool firstTime = true; + for (const auto &v : map) + { + if (!firstTime) + { + write_raw(","); + write_raw(CRLF); + } + indent(); + firstTime = false; + write_member(v.first.c_str(), v.second); + } + end_object(); + } + }; + + class json_reader + { + private: + std::istream &is_; + + const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; + const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; + const uint16_t UTF16_SURROGATE_MASK = 0x3FFU; + bool allowNaN_ = true; + + public: + json_reader(std::istream &input, bool allowNaN = true) + : is_(input) + { + this->allowNaN_ = allowNaN; + } + + bool allowNaN() const { return allowNaN_; } + void allowNaN(bool allow) { allowNaN_ = allow; } + + private: + void throw_format_error(const char *error); + + void throw_format_error() + { + throw_format_error("Invalid file format"); + } + + inline bool is_whitespace(char c) + { + switch (c) + { + case 0x20: + case 0x0A: + case 0x0D: + case 0x09: + return true; + default: + return false; + } + } + char get() + { + int ic = is_.get(); + if (ic == -1) + throw_format_error("Unexpected end of file"); + return (char)ic; + } + + void skip_whitespace(); + + void read_object_start(); + uint16_t read_hex(); + uint16_t read_u_escape(); + + template + json_map::storage_type &get_object_map() + { + // Type T must have public static propery map. + return T::jmap; + } + + public: + void consume(char expected); + void consumeToken(const char *expectedToken, const char *errorMessage); + int peek() + { skip_whitespace(); - - map.read_property(this,memberName.c_str(),pObject); - + return is_.peek(); + } + + public: + std::string read_string(); + + public: + bool is_complete(); + + template + requires IsJsonSerializable + void read(T*pObject) + { + dynamic_cast(pObject)->read_json(*this); + } + template + void read(T *pObject) + { + char c; + consume('{'); + + auto &map = get_object_map(); + while (true) + { + c = peek(); + if (c == '}') + { + c = get(); + break; + } + std::string memberName = read_string(); + + consume(':'); + skip_whitespace(); + + map.read_property(this, memberName.c_str(), pObject); + + skip_whitespace(); + if (is_.peek() == ',') + { + c = get(); + } + } + } + template + void read(T **pObject) + { + int c = peek(); + if (c != '{') + { + if (c == 'n') + { + consumeToken("null", "Expecting '{' or 'null'."); + *pObject = nullptr; + return; + } + } + *pObject = new T(); + read(*pObject); + } + + template + void read(std::map *pMap) + { + char c; + consume('{'); + + while (true) + { + c = peek(); + if (c == '}') + { + c = get(); + break; + } + std::string key = read_string(); + + consume(':'); + skip_whitespace(); + + U u; + this->read(&u); + (*pMap)[key] = std::move(u); + skip_whitespace(); + + if (peek() == ',') + { + c = get(); + } + } + } + + template + void read(std::unique_ptr *pUniquePtr) + { + std::unique_ptr p = std::make_unique(); + + read(p.get()); + *pUniquePtr = std::move(p); + } + template + void read(std::shared_ptr *pUniquePtr) + { + std::shared_ptr p = std::make_shared(); + + read(p.get()); + (*pUniquePtr) = std::move(p); + } + template + void read(std::weak_ptr *pUniquePtr) + { + throw std::domain_error("Can't read std::weak_ptr"); + } + + private: + void skip_string(); + void skip_number(); + void skip_array(); + void skip_object(); + std::string readToken(); + bool read_boolean(); + void skip_token(); + public: + void read_null(); + public: + void skip_property(); + + void read(std::string *value) + { skip_whitespace(); - if (is_.peek() == ',') - { - c = get(); - } + *value = read_string(); } - } - template - void read(T**pObject) - { - int c = peek(); - if (c != '{') - { - if (c == 'n') - { - consumeToken("null", "Expecting '{' or 'null'."); - *pObject = nullptr; - return; - } - } - *pObject = new T(); - read(*pObject); - } - template - void read(std::map *pMap) - { - char c; - consume('{'); - - while (true) + void read(bool *value) { - c = peek(); - if (c == '}') - { - c = get(); - break; - } - std::string key = read_string(); - - consume(':'); skip_whitespace(); - - U u; - this->read(&u); - (*pMap)[key] = u; + *value = read_boolean(); + } + void read(int *value) + { skip_whitespace(); - - if (peek() == ',') - { - c = get(); - } + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); } - } - - - template - void read(std::unique_ptr *pUniquePtr) - { - std::unique_ptr p = std::make_unique(); - - read(p.get()); - *pUniquePtr = std::move(p); - } - template - void read(std::shared_ptr *pUniquePtr) - { - std::shared_ptr p = std::make_shared(); - - read(p.get()); - (*pUniquePtr) = std::move(p); - } - template - void read(std::weak_ptr *pUniquePtr) - { - throw std::domain_error("Can't read std::weak_ptr"); - } - -private: - void skip_string(); - void skip_number(); - void skip_array(); - void skip_object(); - std::string readToken(); - bool read_boolean(); - void read_null(); - void skip_token(); -public: - void skip_property(); - - void read(std::string*value) { - skip_whitespace(); - *value = read_string(); - } - - void read(bool *value) - { - skip_whitespace(); - *value = read_boolean(); - } - void read(int *value) { - skip_whitespace(); - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(long*value) - { - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(long long*value) { - skip_whitespace(); - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(unsigned int*value) - { - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(unsigned long*value) - { - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(unsigned long long*value) { - skip_whitespace(); - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - - void read(float*value) { - skip_whitespace(); - if (allowNaN_) + void read(long *value) { - if (peek() == 'N') - { - consumeToken("NaN","Expecting a number."); - *value = std::nanf(""); - return; - } + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); } - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } - void read(double*value) { - skip_whitespace(); - if (allowNaN_) + void read(long long *value) { - if (peek() == 'N') - { - consumeToken("NaN","Expecting a number."); - - *value = std::nan(""); - return; - } + skip_whitespace(); + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); + } + void read(unsigned int *value) + { + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); + } + void read(unsigned long *value) + { + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); + } + void read(unsigned long long *value) + { + skip_whitespace(); + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); } + void read(float *value) + { + skip_whitespace(); + if (allowNaN_) + { + if (peek() == 'N') + { + consumeToken("NaN", "Expecting a number."); + *value = std::nanf(""); + return; + } + } + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); + } + void read(double *value) + { + skip_whitespace(); + if (allowNaN_) + { + if (peek() == 'N') + { + consumeToken("NaN", "Expecting a number."); + + *value = std::nan(""); + return; + } + } + + is_ >> *value; + if (is_.fail()) + throw PiPedalException("Invalid format."); + } + template + void read(std::vector *value) + { + char c; + std::vector result; + + consume('['); + while (true) + { + if (peek() == ']') + { + c = get(); + break; + } + T item; + read(&item); + result.push_back(std::move(item)); + if (peek() == ',') + { + c = get(); + } + } + *value = std::move(result); + } + }; - is_ >> *value; - if (is_.fail()) throw PiPedalException("Invalid format."); - } template - void read(std::vector*value) + class HasJsonRead { - char c; - std::vector result; - consume('['); - while (true) + template + static std::true_type test(decltype(json_reader(std::cin).read((ARG *)nullptr)) *v) { return std::true_type(); }; + + template + static std::false_type test(...); + + public: + static constexpr bool value = decltype(test(nullptr))::value; + }; + + template + void json_member_reference::read_value(json_reader &reader, CLASS *self) + { + static_assert(HasJsonRead::value, "Type not supported for Json serialization."); + MEMBER_TYPE *ref = &((*self).*member_pointer); + reader.read(ref); + } + template + void json_member_reference::write_value(json_writer &writer, const CLASS *self) + { + const MEMBER_TYPE &ref = (*self).*member_pointer; + writer.write_member(this->name(), ref); + } + + template + void json_enum_member_reference::read_value(json_reader &reader, CLASS *self) + { + MEMBER_TYPE *ref = &((*self).*member_pointer); + std::string val = reader.read_string(); + *ref = converter_->fromString(val); + } + + template + void json_enum_member_reference::write_value(json_writer &writer, const CLASS *self) + { + const MEMBER_TYPE &ref = (*self).*member_pointer; + std::string val = converter_->toString(ref); + writer.write_member(this->name(), val); + } + + template + void json_map_impl::read_property(json_reader *reader, const char *memberName, CLASS *pObject) + { + for (json_member_reference_base *member : members_) { - if (peek() == ']') + if (strcmp(member->name(), memberName) == 0) { - c = get(); - break; - } - T item; - read(&item); - result.push_back(std::move(item)); - if (peek() == ',') - { - c = get(); + member->read_value(*reader, pObject); + return; } } - *value = std::move(result); + reader->skip_property(); } - -}; - -template -class HasJsonRead { - - - template - static std::true_type test (decltype(json_reader(std::cin).read((ARG*)nullptr))*v) { return std::true_type();}; - - template - static std::false_type test (...); - -public: - static constexpr bool value = decltype(test(nullptr))::value; -}; - - - -template -void json_member_reference::read_value(json_reader&reader, CLASS*self) { - static_assert(HasJsonRead::value,"Type not supported for Json serialization."); - MEMBER_TYPE *ref = &((*self).*member_pointer); - reader.read(ref); -} -template -void json_member_reference::write_value(json_writer&writer, const CLASS*self) { - const MEMBER_TYPE &ref = (*self).*member_pointer; - writer.write_member(this->name(),ref); -} - -template -void json_enum_member_reference::read_value(json_reader&reader, CLASS*self) { - MEMBER_TYPE *ref = &((*self).*member_pointer); - std::string val = reader.read_string(); - *ref = converter_->fromString(val); -} - -template -void json_enum_member_reference::write_value(json_writer&writer, const CLASS*self) { - const MEMBER_TYPE &ref = (*self).*member_pointer; - std::string val = converter_->toString(ref); - writer.write_member(this->name(),val); -} - - -template -void json_map_impl::read_property(json_reader* reader,const char*memberName,CLASS *pObject) -{ - for (json_member_reference_base*member: members_) + template + void json_conditional_member_reference::read_value(json_reader &reader, CLASS *self) { - if (strcmp(member->name(),memberName) == 0) - { - member->read_value(*reader,pObject); - return; - } - + static_assert(HasJsonRead::value, "Type not supported for Json serialization."); + MEMBER_TYPE *ref = &((*self).*member_pointer); + reader.read(ref); } - reader->skip_property(); -} -template -void json_conditional_member_reference::read_value(json_reader&reader, CLASS*self) { - static_assert(HasJsonRead::value,"Type not supported for Json serialization."); - MEMBER_TYPE *ref = &((*self).*member_pointer); - reader.read(ref); -} - -template -bool json_conditional_member_reference::canWrite(const CLASS*self) { - const MEMBER_TYPE &ref = (*self).*member_pointer; - return this->condition_(self,ref); - -} - - -template -void json_conditional_member_reference::write_value(json_writer&writer, const CLASS*self) { - const MEMBER_TYPE &ref = (*self).*member_pointer; - writer.write_member(this->name(),ref); -} - - -template -void json_map_impl::write_members(json_writer* writer, const CLASS*pObject) -{ - bool first = true; - for (json_member_reference_base*member: members_) + template + bool json_conditional_member_reference::canWrite(const CLASS *self) { - if (member->canWrite(pObject)) + const MEMBER_TYPE &ref = (*self).*member_pointer; + return this->condition_(self, ref); + } + + template + void json_conditional_member_reference::write_value(json_writer &writer, const CLASS *self) + { + const MEMBER_TYPE &ref = (*self).*member_pointer; + writer.write_member(this->name(), ref); + } + + template + void json_map_impl::write_members(json_writer *writer, const CLASS *pObject) + { + bool first = true; + for (json_member_reference_base *member : members_) { - if (!first) + if (member->canWrite(pObject)) { - writer->output_stream() << ','; - writer->output_stream() << writer->CRLF; + if (!first) + { + writer->output_stream() << ','; + writer->output_stream() << writer->CRLF; + } + first = false; + writer->indent(); + member->write_value(*writer, pObject); } - first = false; - writer->indent(); - member->write_value(*writer,pObject); } } - -} - - - - - } // namespace pipedal \ No newline at end of file diff --git a/src/jsonTest.cpp b/src/jsonTest.cpp index 33ecee5..5550ab6 100644 --- a/src/jsonTest.cpp +++ b/src/jsonTest.cpp @@ -25,6 +25,9 @@ #include "json.hpp" +#include "json_variant.hpp" +#include +#include using namespace pipedal; @@ -94,6 +97,7 @@ json_map::storage_type JsonTestTarget::jmap {{ TEST_CASE( "json write", "[json_write_test]" ) { + std::cout << "== json write ==" << std::endl; std::stringstream os; json_writer writer { os }; @@ -101,7 +105,7 @@ TEST_CASE( "json write", "[json_write_test]" ) { writer.write(testTarget); - std::cout << os.str(); + std::cout << os.str() << std::endl; } static std::string get_json() @@ -166,4 +170,125 @@ TEST_CASE( "json smart ptrs", "[json_smart_ptrs][Build][Dev]" ) { reader.read(&sharedPtr); } +} + +template +void TestVariantRoundTrip(const T &value) +{ + json_variant variant(value); + T out = variant.get(); + 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 + requires std::derived_from + bool write(T &v) + { + (void)v; + return true; + } + template + bool write(T &v) + { + (void)v; + return false; + } +}; + + + +void TestVariantSFINAE() +{ + X x; + + + json_variant v; + dynamic_cast(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"; + } } \ No newline at end of file diff --git a/src/json_variant.cpp b/src/json_variant.cpp new file mode 100644 index 0000000..f1205c9 --- /dev/null +++ b/src/json_variant.cpp @@ -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 +#include +#include + +using namespace pipedal; + +void concrete_json_variant_base::write_double_value(json_writer &writer,double value) const +{ + if (value < std::numeric_limits::max() && value > std::numeric_limits::min()) + { + double frac = value-(int32_t)value; + if (value == 0) + { + writer.write((int32_t)value); + return; + } + } + writer.write(value); +} diff --git a/src/json_variant.hpp b/src/json_variant.hpp new file mode 100644 index 0000000..4d797a0 --- /dev/null +++ b/src/json_variant.hpp @@ -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 +#include +#include +#include +#include +#include "json.hpp" + +namespace pipedal +{ + class json_null + { + public: + bool operator==(const json_null&other) const { return true;} + private: + int value = 0; + }; + + + template // 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 &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 values; + }; + template // 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 + void push_back(const U&value) { values.push_back(value); } + template + void push_back(U&&value) { values.push_back(value); } + bool operator==(const json_array_base&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 values; + }; + + + class concrete_json_variant_base { + protected: + void write_double_value(json_writer &writer,double value) const; + }; + + template + class json_variant_base + : public std::variant>, json_array_base>>, + public JsonSerializable, + private concrete_json_variant_base + { + public: + using base = std::variant>, json_array_base>>; + using json_object = json_object_base>; + using json_array = json_array_base>; + using json_variant = json_variant_base; + + + 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(value)) + { + } + json_variant_base(json_array &&value) + : base(std::forward(value)) + { + } + + template + bool holds_alternative() const { return std::holds_alternative(*this);} + + bool IsNull() const { return holds_alternative(); } + bool IsBool() const { return holds_alternative(); } + bool IsNumber() const { return holds_alternative(); } + bool IsString() const { return holds_alternative(); } + bool IsObject() const { return holds_alternative(); } + bool IsArray() const { return holds_alternative(); } + + template + const U &get() const + { + return std::get(*this); + } + template + U &get() + { + return std::get(*this); + } + + bool &AsBool() { return get(); } + bool AsBool() const { return get(); } + + double &AsNumber() { return get(); } + double AsNumber() const { return get(); } + std::string &AsString() { return get(); } + const std::string &AsString() const { return get(); } + + json_object &AsObject() { return get(); } + const json_object &AsObject() const { return get(); } + + std::vector AsFloatArray() { return get().AsFloatArray(); } + std::vector AsDoubleArray() { return get().AsDoubleArray(); } + + json_array &AsArray() { return get(); } + const json_array &AsArray() const { return get(); } + + // 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()); + break; + case 2: + write_double_value(writer,this->get()); + break; + case 3: + writer.write(get()); + break; + case 4: + writer.writeRawWritable(get()); + break; + case 5: + writer.writeRawWritable(get()); + break; + default: + throw std::logic_error("Invalid variant index"); + } + } + }; + + using json_variant = json_variant_base; + using json_object = json_variant::json_object; + using json_array = json_variant::json_array; + +} // namespace pipedal \ No newline at end of file