diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 998791f..033e617 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -7,6 +7,7 @@ "${workspaceFolder}", "${workspaceFolder}/src", "/usr/include/lilv-0", + "/usr/lib", "~/src/vst3sdk" ], "compilerPath": "/usr/bin/gcc", diff --git a/.vscode/launch.json b/.vscode/launch.json index b9b5079..b110eb1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,6 +6,7 @@ "configurations": [ + { "name": "(gdb) generic", "type": "cppdbg", @@ -21,11 +22,6 @@ // it gets resolved by CMake Tools: "name": "PATH", "value": "$PATH:${command:cmake.launchTargetDirectory}" - }, - { - "name": "ASAN_OPTIONS", - "value": "detect_leaks=0" - } ], "externalConsole": false, @@ -54,11 +50,6 @@ // it gets resolved by CMake Tools: "name": "PATH", "value": "$PATH:${command:cmake.launchTargetDirectory}" - }, - { - "name": "ASAN_OPTIONS", - "value": "detect_leaks=0" - } ], "externalConsole": false, @@ -80,8 +71,8 @@ "program": "${command:cmake.launchTargetPath}", "args": [ - //"[Dev]" - "[json_variants]" + //"[Dev]" -- all dev-machine tests. + "[json_variants]" // subtest of your choice, or none to run all of the tests. ], "stopAtEntry": false, @@ -108,6 +99,44 @@ } ] }, + { + "name": "(gdb) jsonTest Launch", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + + "args": [ + //"[Dev]" -- all dev-machine tests. + //"[promise]" // subtest of your choice, or none to run all of the tests. + "[atom_converter]" + ], + + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + // add the directory where our target was built to the PATHs + // it gets resolved by CMake Tools: + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { "name": "(gdb) pipedal_latency_test Launch", diff --git a/react/CMakeLists.txt b/react/CMakeLists.txt index 57c5e3a..467075d 100644 --- a/react/CMakeLists.txt +++ b/react/CMakeLists.txt @@ -60,9 +60,9 @@ add_custom_command( src/PiPedalError.tsx src/IControlViewFactory.tsx src/Lv2Plugin.tsx - src/PedalBoard.tsx + src/Pedalboard.tsx src/AndroidHost.tsx - src/PedalBoardView.tsx + src/PedalboardView.tsx src/PresetDialog.tsx src/AppThemed.tsx src/ZoomedDial.tsx diff --git a/react/src/AboutDialog.tsx b/react/src/AboutDialog.tsx index b3531a3..11ec5c4 100644 --- a/react/src/AboutDialog.tsx +++ b/react/src/AboutDialog.tsx @@ -162,15 +162,18 @@ const AboutDialog = withStyles(styles, { withTheme: true })( } componentDidMount() { + super.componentDidMount?.(); this.mounted = true; this.updateNotifications(); this.startFossRequest(); } componentWillUnmount() { + super.componentWillUnmount?.(); this.mounted = false; this.updateNotifications(); } - componentDidUpdate() { + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot: any): void { + super.componentDidUpdate?.(prevProps,prevState,snapshot); this.updateNotifications(); } @@ -196,7 +199,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })( > - + About @@ -208,28 +211,28 @@ const AboutDialog = withStyles(styles, { withTheme: true })( }} >
- + PiPedal {(this.model.serverVersion ? this.model.serverVersion.serverVersion : "") + (this.model.serverVersion?.debug ? " (Debug)" : "")} - + Copyright © 2022 Robin Davies. {this.model.isAndroidHosted() && ( - + {this.model.getAndroidHostVersion()} )} {this.model.isAndroidHosted() && ( - + Copyright © 2022 Robin Davies. )} - + ADDRESSES
diff --git a/react/src/AppThemed.tsx b/react/src/AppThemed.tsx index be4c6e3..e0fbbbd 100644 --- a/react/src/AppThemed.tsx +++ b/react/src/AppThemed.tsx @@ -398,7 +398,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< } - handDisplayOnboarding() { + handleDisplayOnboarding() { this.setState({ isDrawerOpen: false, isSettingsDialogOpen: true, @@ -502,7 +502,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_); this.model_.state.addOnChangedHandler(this.stateChangeHandler_); - this.model_.pedalBoard.addOnChangedHandler(this.presetChangedHandler); + this.model_.pedalboard.addOnChangedHandler(this.presetChangedHandler); this.model_.alertMessage.addOnChangedHandler(this.alertMessageChangedHandler); this.model_.banks.addOnChangedHandler(this.banksChangedHandler); this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler); @@ -529,7 +529,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< super.componentWillUnmount(); this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_); this.model_.state.removeOnChangedHandler(this.stateChangeHandler_); - this.model_.pedalBoard.removeOnChangedHandler(this.presetChangedHandler); + this.model_.pedalboard.removeOnChangedHandler(this.presetChangedHandler); this.model_.banks.removeOnChangedHandler(this.banksChangedHandler); this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler); @@ -575,7 +575,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< this.setState({ errorMessage: message }); } - onboardingShown: boolean = false; setDisplayState(newState: State): void { this.updateOverscroll(); @@ -586,13 +585,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< }); if (newState === State.Ready) { - if (!this.onboardingShown) + if (this.model_.isOnboarding()) { - this.onboardingShown = true; - if (!this.model_.hasConfiguration()) - { - this.handDisplayOnboarding(); - } + this.handleDisplayOnboarding(); } } } @@ -867,7 +862,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
- + {this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."}
@@ -905,7 +900,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
- + Loading...
diff --git a/react/src/BankDialog.tsx b/react/src/BankDialog.tsx index 6a548ea..3824983 100644 --- a/react/src/BankDialog.tsx +++ b/react/src/BankDialog.tsx @@ -298,7 +298,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
- + {bankEntry.name}
@@ -410,7 +410,7 @@ const BankDialog = withStyles(styles, { withTheme: true })( > - + Banks this.showActionBar(true)} > @@ -433,7 +433,7 @@ const BankDialog = withStyles(styles, { withTheme: true })( )} - + Banks {(this.state.banks.getEntry(this.state.selectedItem) != null) diff --git a/react/src/ControlViewFactory.tsx b/react/src/ControlViewFactory.tsx index c1daf0b..55c7449 100644 --- a/react/src/ControlViewFactory.tsx +++ b/react/src/ControlViewFactory.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; -import {PedalBoardItem, PedalBoardSplitItem} from './PedalBoard'; +import {PedalboardItem, PedalboardSplitItem} from './Pedalboard'; import PluginControlView from './PluginControlView'; import SplitControlView from './SplitControlView'; import Typography from '@mui/material/Typography'; @@ -47,38 +47,38 @@ let pluginFactories: IControlViewFactory[] = [ ]; -export function GetControlView(pedalBoardItem?: PedalBoardItem| null): React.ReactNode +export function GetControlView(pedalboardItem?: PedalboardItem| null): React.ReactNode { let model: PiPedalModel = PiPedalModelFactory.getInstance(); - if (!pedalBoardItem) { + if (!pedalboardItem) { return (
); } - if (pedalBoardItem.isSplit()) + if (pedalboardItem.isSplit()) { return ( - ); } else { for (let i = 0; i < pluginFactories.length; ++i) { let factory = pluginFactories[i]; - if (factory.uri === pedalBoardItem.uri) + if (factory.uri === pedalboardItem.uri) { - return factory.Create(model,pedalBoardItem); + return factory.Create(model,pedalboardItem); } } - let uiPlugin = model.getUiPlugin(pedalBoardItem.uri); + let uiPlugin = model.getUiPlugin(pedalboardItem.uri); if (!uiPlugin) {
Missing plugin. - The plugin '{pedalBoardItem.pluginName}' ({pedalBoardItem.uri}) is not currently installed. + The plugin '{pedalboardItem.pluginName}' ({pedalboardItem.uri}) is not currently installed.
} else { return ( - + ) } } diff --git a/react/src/DialogEx.tsx b/react/src/DialogEx.tsx index 9d9244f..3288327 100644 --- a/react/src/DialogEx.tsx +++ b/react/src/DialogEx.tsx @@ -47,12 +47,9 @@ function peekDialogStack(): string { return ""; } -let ignoreNextPop = false; - -function popDialogStack(ignoreNextPop_: boolean): void { +function popDialogStack(): void { if (dialogStack.length !== 0) { - ignoreNextPop = ignoreNextPop_; dialogStack.splice(dialogStack.length-1,1); } } @@ -76,27 +73,23 @@ class DialogEx extends React.Component { mounted: boolean = false; hasHooks: boolean = false; - stateWasPopped: boolean = false; - handlePopState(e: any): any { + + handlePopState(e: any): any + { let shouldClose = (peekDialogStack() === this.props.tag); if (shouldClose) { - if (ignoreNextPop) - { - ignoreNextPop = false; - } else { - if (!this.stateWasPopped) - { - this.stateWasPopped = true; - if (this.props.onClose) - { - popDialogStack(false); - this.props.onClose(e,"backdropClick"); - } + if (!this.stateWasPopped) + { + this.stateWasPopped = true; + popDialogStack(); + if (this.props.open) { + this.props.onClose?.(e,"backdropClick"); } } + window.removeEventListener("popstate",this.handlePopState); e.stopPropagation(); } } @@ -111,49 +104,35 @@ class DialogEx extends React.Component { { this.stateWasPopped = false; window.addEventListener("popstate",this.handlePopState); - // eslint-disable-next-line no-restricted-globals - let state = history.state; - if (!state) - { - state = {}; - } - state[this.props.tag] = true; - + pushDialogStack(this.props.tag); + let state: {tag: string} = {tag: this.props.tag}; // eslint-disable-next-line no-restricted-globals history.pushState( state, "", - "" //"#" + this.props.tag + "#" + this.props.tag ); } else { if (!this.stateWasPopped) { - this.stateWasPopped = true; - popDialogStack(true); // eslint-disable-next-line no-restricted-globals history.back(); } - window.removeEventListener("popstate",this.handlePopState); } } } componentDidMount() { - if (super.componentDidMount) - { - super.componentDidMount(); - } + super.componentDidMount?.(); + this.mounted = true; this.updateHooks(); } componentWillUnmount() { - if (super.componentWillUnmount) - { - super.componentWillUnmount(); - } + super.componentWillUnmount?.(); this.mounted = false; this.updateHooks(); } @@ -162,12 +141,15 @@ class DialogEx extends React.Component { { this.updateHooks(); } - + myOnClose(event:{}, reason: "backdropClick" | "escapeKeyDown") + { + if (this.props.onClose) this.props.onClose(event,reason); + } render() { - let { tag, ...extra} = this.props; + let { tag,onClose, ...extra} = this.props; return ( - + { this.myOnClose(event,reason);}}> {this.props.children} ); diff --git a/react/src/FilePropertyControl.tsx b/react/src/FilePropertyControl.tsx index 7ab5f86..a48b308 100644 --- a/react/src/FilePropertyControl.tsx +++ b/react/src/FilePropertyControl.tsx @@ -22,33 +22,20 @@ * SOFTWARE. */ -import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react'; +import React, { 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 { PiPedalFileProperty } 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 { PiPedalModel, PiPedalModelFactory, ListenHandle, StateChangedHandle } 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 } @@ -88,13 +75,12 @@ const styles = (theme: Theme) => createStyles({ export interface FilePropertyControlProps extends WithStyles { instanceId: number; fileProperty: PiPedalFileProperty; - value: string; - onFileClick: (fileProperty: PiPedalFileProperty,value: string) => void; + onFileClick: (fileProperty: PiPedalFileProperty, value: string) => void; theme: Theme; } type FilePropertyControlState = { error: boolean; - showDialog: boolean; + value: string; }; const FilePropertyControl = @@ -110,13 +96,75 @@ const FilePropertyControl = this.state = { error: false, - showDialog: false + value: "", }; this.model = PiPedalModelFactory.getInstance(); + if (this.props.fileProperty.patchProperty !== "") { + this.subscribeToPropertyGet(); + } } - componentWillUnmount() { + private propertyGetHandle?: ListenHandle; + + refreshPatchProperty() + { + this.model.getPatchProperty(this.props.instanceId, this.props.fileProperty.patchProperty) + .then( + (json: any) => { + if (json && json.otype_ === "Path") { + let path = json.value as string; + this.setState({ value: path }); + } + } + ) + .catch( + (error: Error) => { + this.model.showAlert(error.toString()); + } + ); + } + + stateChangedHandle?: StateChangedHandle; + + subscribeToPropertyGet() { + // this.propertyGetHandle = this.model.listenForAtomOutput(this.props.instanceId,(instanceId,atomOutput)=>{ + // // PARSE THE OBJECT! + // // this.setState({value: atomOutput?.value as string}); + // }); + if (this.stateChangedHandle) + { + this.model.removeLv2StateChangedListener(this.stateChangedHandle); + } + this.refreshPatchProperty(); + this.stateChangedHandle = this.model.addLv2StateChangedListener( + this.props.instanceId, + () => { + this.refreshPatchProperty(); + }); + } + unsubscribeToPropertyGet() { + if (this.stateChangedHandle) + { + this.model.removeLv2StateChangedListener(this.stateChangedHandle); + } + } + componentDidMount() { + this.subscribeToPropertyGet(); + } + componentWillUnmount() { + this.unsubscribeToPropertyGet(); + } + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + if (prevProps.fileProperty.patchProperty !== this.props.fileProperty.patchProperty + || prevProps.instanceId !== this.props.instanceId) { + this.setState({ value: "" }); + this.unsubscribeToPropertyGet(); + this.subscribeToPropertyGet(); + } + } + + inputChanged: boolean = false; onDrag(e: SyntheticEvent) { @@ -124,40 +172,36 @@ const FilePropertyControl = } onFileClick() { - this.props.onFileClick(this.props.fileProperty,this.props.value); + this.props.onFileClick(this.props.fileProperty, this.state.value); } private fileNameOnly(path: string): string { let slashPos = path.lastIndexOf('/'); - if (slashPos < 0) - { + if (slashPos < 0) { slashPos = 0; } else { ++slashPos; } let extPos = path.lastIndexOf('.'); - if (extPos < 0 || extPos < slashPos) - { + if (extPos < 0 || extPos < slashPos) { extPos = path.length; - } + } - return path.substring(slashPos,extPos); + return path.substring(slashPos, extPos); - } + } render() { - let classes = this.props.classes; + //let classes = this.props.classes; let fileProperty = this.props.fileProperty; - let value = this.props.value; - if (!value || value.length === 0) - { + let value = this.fileNameOnly(this.state.value); + if (!value || value.length === 0) { value = "\u00A0"; } - value = this.fileNameOnly(value); let item_width = 264; return ( -
+
{/* TITLE SECTION */}
- {this.onFileClick()}} > -
+ { this.onFileClick() }} > +
- {value} - + >{value} +
 
diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index 5ca8465..c6debb2 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -23,35 +23,27 @@ 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 ArrowBackIcon from '@mui/icons-material/ArrowBack'; -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 { PiPedalFileProperty } 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 + onCancel: () => void }; export interface FilePropertyDialogState { @@ -82,7 +74,23 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent { if (this.mounted) @@ -108,6 +116,11 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent, prevState: Readonly, snapshot?: any): void { + super.componentDidUpdate?.(prevProps,prevState,snapshot); + if (prevProps.fileProperty !== this.props.fileProperty) + { + this.requestFiles() + } } private fileNameOnly(path: string): string { @@ -142,29 +155,39 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }} + this.mapKey = 0; + return this.props.open && + ( + this.props.onCancel()} 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.props.onCancel();}} aria-label="back" + > + + + {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)}
@@ -181,14 +204,14 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent
diff --git a/react/src/FullScreenIME.tsx b/react/src/FullScreenIME.tsx index f18c2b6..22e1539 100644 --- a/react/src/FullScreenIME.tsx +++ b/react/src/FullScreenIME.tsx @@ -203,7 +203,7 @@ const FullScreenIME = justifyContent: "center", alignItems: "center" }} > - {this.props.caption} + {this.props.caption} createStyles({ interface GxTunerProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; isToobTuner: boolean; } @@ -115,8 +115,8 @@ const GxTunerView = export class GxTunerViewFactory implements IControlViewFactory { uri: string = GXTUNER_URI; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } @@ -126,8 +126,8 @@ export class GxTunerViewFactory implements IControlViewFactory { export class ToobTunerViewFactory implements IControlViewFactory { uri: string = TOOBTUNER_URI; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/IControlViewFactory.tsx b/react/src/IControlViewFactory.tsx index d3f1be1..c49f588 100644 --- a/react/src/IControlViewFactory.tsx +++ b/react/src/IControlViewFactory.tsx @@ -19,13 +19,13 @@ import React from 'react'; import { PiPedalModel } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; interface IControlViewFactory { uri: string; - Create(model: PiPedalModel,pedalBoardItem: PedalBoardItem): React.ReactNode; + Create(model: PiPedalModel,pedalboardItem: PedalboardItem): React.ReactNode; } diff --git a/react/src/JackServerSettings.tsx b/react/src/JackServerSettings.tsx index a967217..9ece894 100644 --- a/react/src/JackServerSettings.tsx +++ b/react/src/JackServerSettings.tsx @@ -22,6 +22,7 @@ export default class JackServerSettings { deserialize(input: any) : JackServerSettings{ this.valid = input.valid; + this.isOnboarding = input.isOnboarding; this.isJackAudio = input.isJackAudio; this.rebootRequired = input.rebootRequired; this.alsaDevice = input.alsaDevice?? ""; @@ -44,6 +45,7 @@ export default class JackServerSettings { return new JackServerSettings().deserialize(this); } valid: boolean = false; + isOnboarding: boolean = true; rebootRequired = false; isJackAudio = false; alsaDevice: string = ""; @@ -54,7 +56,7 @@ export default class JackServerSettings { getSummaryText() { if (this.valid) { let device = this.alsaDevice; - if (device.startsWith("hw:")) device = device.substr(3); + if (device.startsWith("hw:")) device = device.substring(3); return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers; } else { return "Not configured"; diff --git a/react/src/JackServerSettingsDialog.tsx b/react/src/JackServerSettingsDialog.tsx index f8cac15..9e38d53 100644 --- a/react/src/JackServerSettingsDialog.tsx +++ b/react/src/JackServerSettingsDialog.tsx @@ -134,8 +134,8 @@ const JackServerSettingsDialog = withStyles(styles)( } if (result.sampleRate === 0) result.sampleRate = 48000; - if (result.bufferSize === 0) result.bufferSize = 64; - if (result.numberOfBuffers === 0) result.numberOfBuffers = 2; + if (result.bufferSize === 0) result.bufferSize = 16; + if (result.numberOfBuffers === 0) result.numberOfBuffers = 3; result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate); result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize); result.valid = true; diff --git a/react/src/JsonAtom.tsx b/react/src/JsonAtom.tsx new file mode 100644 index 0000000..18fd260 --- /dev/null +++ b/react/src/JsonAtom.tsx @@ -0,0 +1,137 @@ +// 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. + + +// Utility class for constructing Json atoms. +class JsonAtom { + static Property(value: string): any + { + return { + "otype_": "Property", + "value": value + }; + } + static Bool(value: boolean): any + { + return { + "otype_": "Bool", + "value": value + }; + } + static Int(value: number): any + { + return { + "otype_": "Int", + "value": value + }; + } + static Long(value: number): any + { + return { + "otype_": "Long", + "value": value + }; + } + static Float(value: number): any + { + return value; + } + static Double(value: number): any + { + return { + "otype_": "Double", + "value": value + }; + } + static String(value: string): any + { + return value; + } + static Path(value: string): any + { + return { + "otype_": "Path", + "value": value + }; + } + static Uri(value: string): any + { + return { + "otype_": "URI", + "value": value + }; + } + static Urid(value: string): any + { + return { + "otype_": "URID", + "value": value + }; + } + static Object(type: string): any + { + return { + "otype_": type + }; + } + static Tuple(values: any[]): any + { + return { + "otype_": "Tuple", + "value": values + }; + } + static IntVector(values: []): any + { + return { + "otype_": "Vector", + "vtype_": "Int", + "value": values + }; + } + static LongVector(values: []): any + { + return { + "otype_": "Vector", + "vtype_": "Long", + "value": values + }; + } + + static FloatVector(values: []): any + { + return { + "otype_": "Vector", + "vtype_": "Float", + "value": values + }; + } + static DoubleVector(values: []): any + { + return { + "otype_": "Vector", + "vtype_": "Float", + "value": values + }; + } + + +}; + +export default JsonAtom; diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx index c8ffd06..855ae5e 100644 --- a/react/src/Lv2Plugin.tsx +++ b/react/src/Lv2Plugin.tsx @@ -123,13 +123,37 @@ export class PiPedalFileType { name: string = ""; fileExtension: string = ""; } + +export class UiPropertyNotification { + deserialize(input: any): UiPropertyNotification + { + this.portIndex = input.portIndex; + this.symbol = input.symbol; + this.plugin = input.plugin; + this.protocol = input.protocol; + return this; + } + static deserialize_array(input: any): UiPropertyNotification[] + { + let result: UiPropertyNotification[] = []; + for (let i = 0; i < input.length; ++i) + { + result[i] = new UiPropertyNotification().deserialize(input[i]); + } + return result; + } + portIndex: number = -1; + symbol: string = ""; + plugin: string = ""; + protocol: 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; this.directory = input.directory; return this; } @@ -147,7 +171,6 @@ export class PiPedalFileProperty { fileTypes: PiPedalFileType[] = []; patchProperty: string = ""; directory: string = ""; - defaultFile: string = ""; }; export class Lv2Plugin implements Deserializable { @@ -170,6 +193,12 @@ export class Lv2Plugin implements Deserializable { } else { this.fileProperties = []; } + if (input.uiPortNotifications) + { + this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications); + } else { + this.uiPortNotifications = []; + } return this; } static EmptyFeatures: string[] = []; @@ -186,6 +215,7 @@ export class Lv2Plugin implements Deserializable { ports: Port[] = Port.EmptyPorts; port_groups: PortGroup[] = []; fileProperties: PiPedalFileProperty[] = []; + uiPortNotifications: UiPropertyNotification[] = []; } diff --git a/react/src/MainPage.tsx b/react/src/MainPage.tsx index ae67584..2677dbe 100644 --- a/react/src/MainPage.tsx +++ b/react/src/MainPage.tsx @@ -24,15 +24,15 @@ import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { - PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType -} from './PedalBoard'; + Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType +} from './Pedalboard'; import Button from '@mui/material/Button'; import InputIcon from '@mui/icons-material/Input'; import LoadPluginDialog from './LoadPluginDialog'; import Switch from '@mui/material/Switch'; import Typography from '@mui/material/Typography'; -import PedalBoardView from './PedalBoardView'; +import PedalboardView from './PedalboardView'; import { PiPedalStateError } from './PiPedalError'; import IconButton from '@mui/material/IconButton'; import AddIcon from '@mui/icons-material/Add'; @@ -58,11 +58,11 @@ const styles = ({ palette }: Theme) => createStyles({ position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap", justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden" }, - pedalBoardScroll: { + pedalboardScroll: { position: "relative", width: "100%", flex: "0 0 auto", overflow: "auto", maxHeight: 220 }, - pedalBoardScrollSmall: { + pedalboardScrollSmall: { position: "relative", width: "100%", flex: "1 1 1px", overflow: "auto" }, @@ -97,7 +97,7 @@ interface MainProps extends WithStyles { interface MainState { selectedPedal: number; loadDialogOpen: boolean; - pedalBoard: PedalBoard; + pedalboard: Pedalboard; addMenuAnchorEl: HTMLElement | null; splitControlBar: boolean; horizontalScrollLayout: boolean; @@ -116,13 +116,13 @@ export const MainPage = constructor(props: MainProps) { super(props); this.model = PiPedalModelFactory.getInstance(); - let pedalboard = this.model.pedalBoard.get(); + let pedalboard = this.model.pedalboard.get(); let selectedPedal = pedalboard.getFirstSelectableItem(); this.state = { selectedPedal: selectedPedal, loadDialogOpen: false, - pedalBoard: pedalboard, + pedalboard: pedalboard, addMenuAnchorEl: null, splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD, horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK, @@ -135,31 +135,31 @@ export const MainPage = this.onLoadClick = this.onLoadClick.bind(this); this.onLoadOk = this.onLoadOk.bind(this); this.onLoadCancel = this.onLoadCancel.bind(this); - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this); } onInsertPedal(instanceId: number) { this.setAddMenuAnchorEl(null); - let newId = this.model.addPedalBoardItem(instanceId, false); + let newId = this.model.addPedalboardItem(instanceId, false); this.setSelection(newId); } onAppendPedal(instanceId: number) { this.setAddMenuAnchorEl(null); - let newId = this.model.addPedalBoardItem(instanceId, true); + let newId = this.model.addPedalboardItem(instanceId, true); this.setSelection(newId); } onInsertSplit(instanceId: number) { this.setAddMenuAnchorEl(null); - let newId = this.model.addPedalBoardSplitItem(instanceId, false); + let newId = this.model.addPedalboardSplitItem(instanceId, false); this.setSelection(newId); } onAppendSplit(instanceId: number) { this.setAddMenuAnchorEl(null); - let newId = this.model.addPedalBoardSplitItem(instanceId, true); + let newId = this.model.addPedalboardSplitItem(instanceId, true); this.setSelection(newId); } @@ -182,16 +182,16 @@ export const MainPage = } handleEnableCurrentItemChanged(event: any): void { let newValue = event.target.checked; - let item = this.getSelectedPedalBoardItem(); + let item = this.getSelectedPedalboardItem(); if (item != null) { - this.model.setPedalBoardItemEnabled(item.getInstanceId(), newValue); + this.model.setPedalboardItemEnabled(item.getInstanceId(), newValue); } } handleSelectPluginPreset(instanceId: number, presetInstanceId: number) { this.model.loadPluginPreset(instanceId, presetInstanceId); } - onPedalBoardChanged(value: PedalBoard) { + onPedalboardChanged(value: Pedalboard) { let selectedItem = -1; if (value.hasItem(this.state.selectedPedal)) { @@ -200,12 +200,12 @@ export const MainPage = selectedItem = value.getFirstSelectableItem(); } this.setState({ - pedalBoard: value, + pedalboard: value, selectedPedal: selectedItem }); } onDeletePedal(instanceId: number): void { - let result = this.model.deletePedalBoardPedal(instanceId); + let result = this.model.deletePedalboardPedal(instanceId); if (result != null) { this.setState({ selectedPedal: result }); } @@ -213,10 +213,10 @@ export const MainPage = componentDidMount() { super.componentDidMount(); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); } componentWillUnmount() { - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); super.componentWillUnmount(); } updateResponsive() { @@ -241,14 +241,14 @@ export const MainPage = } onPedalDoubleClick(selectedId: number): void { this.setSelection(selectedId); - let item = this.getPedalBoardItem(selectedId); + let item = this.getPedalboardItem(selectedId); if (item != null) { if (item.isSplit()) { - let split = item as PedalBoardSplitItem; + let split = item as PedalboardSplitItem; if (split.getSplitType() === SplitType.Ab) { let cv = split.getToggleAbControlValue(); if (split.instanceId === undefined) throw new PiPedalStateError("Split without valid id."); - this.model.setPedalBoardControlValue(split.instanceId, cv.key, cv.value); + this.model.setPedalboardControl(split.instanceId, cv.key, cv.value); } } else { this.setState({ loadDialogOpen: true }); @@ -261,7 +261,7 @@ export const MainPage = onLoadOk(selectedUri: string): void { this.setState({ loadDialogOpen: false }); let itemId = this.state.selectedPedal; - let newSelectedItem = this.model.loadPedalBoardPlugin(itemId, selectedUri); + let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri); this.setState({ selectedPedal: newSelectedItem }); } @@ -269,12 +269,12 @@ export const MainPage = this.setState({ loadDialogOpen: true }); } - getPedalBoardItem(selectedId?: number): PedalBoardItem | null { + getPedalboardItem(selectedId?: number): PedalboardItem | null { if (selectedId === undefined) return null; - let pedalBoard = this.model.pedalBoard.get(); - if (!pedalBoard) return null; - let it = pedalBoard.itemsGenerator(); + let pedalboard = this.model.pedalboard.get(); + if (!pedalboard) return null; + let it = pedalboard.itemsGenerator(); if (!selectedId) return null; while (true) { let v = it.next(); @@ -290,32 +290,32 @@ export const MainPage = } onPedalboardPropertyChanged(instanceId: number, key: string, value: number) { - this.model.setPedalBoardControlValue(instanceId, key, value); + this.model.setPedalboardControl(instanceId, key, value); } - getSelectedPedalBoardItem(): PedalBoardItem | null { - return this.getPedalBoardItem(this.state.selectedPedal); + getSelectedPedalboardItem(): PedalboardItem | null { + return this.getPedalboardItem(this.state.selectedPedal); } getSelectedUri(): string { - let pedalBoardItem = this.getSelectedPedalBoardItem(); - if (pedalBoardItem == null) return ""; - return pedalBoardItem.uri; + let pedalboardItem = this.getSelectedPedalboardItem(); + if (pedalboardItem == null) return ""; + return pedalboardItem.uri; } - titleBar(pedalBoardItem: PedalBoardItem | null): React.ReactNode { + titleBar(pedalboardItem: PedalboardItem | null): React.ReactNode { let title = ""; let author = ""; let pluginUri = ""; let presetsUri = ""; let missing = false; - if (pedalBoardItem) { - if (pedalBoardItem.isEmpty()) { + if (pedalboardItem) { + if (pedalboardItem.isEmpty()) { title = ""; - } else if (pedalBoardItem.isSplit()) { + } else if (pedalboardItem.isSplit()) { title = "Split"; } else { - let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri); + let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri); if (!uiPlugin) { missing = true; - title = pedalBoardItem?.pluginName ?? "Missing plugin"; + title = pedalboardItem?.pluginName ?? "Missing plugin"; } else { title = uiPlugin.name; author = uiPlugin.author_name; @@ -362,7 +362,7 @@ export const MainPage =
-
@@ -373,8 +373,8 @@ export const MainPage = render() { let classes = this.props.classes; - let pedalBoard = this.model.pedalBoard.get(); - let pedalBoardItem = this.getSelectedPedalBoardItem(); + let pedalboard = this.model.pedalboard.get(); + let pedalboardItem = this.getSelectedPedalboardItem(); let uiPlugin = null; let bypassVisible = false; let bypassChecked = false; @@ -384,20 +384,20 @@ export const MainPage = let missing = false; let pluginUri = "#error"; - if (pedalBoardItem) { - canDelete = pedalBoard.canDeleteItem(pedalBoardItem.instanceId); - instanceId = pedalBoardItem.instanceId; - if (pedalBoardItem.isEmpty()) { + if (pedalboardItem) { + canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId); + instanceId = pedalboardItem.instanceId; + if (pedalboardItem.isEmpty()) { canAdd = true; - } else if (pedalBoardItem.isSplit()) { + } else if (pedalboardItem.isSplit()) { canAdd = true; } else { - pluginUri = pedalBoardItem.uri; + pluginUri = pedalboardItem.uri; uiPlugin = this.model.getUiPlugin(pluginUri); canAdd = true; if (uiPlugin) { bypassVisible = true; - bypassChecked = pedalBoardItem.isEnabled; + bypassChecked = pedalboardItem.isEnabled; } else { missing = true; } @@ -407,9 +407,9 @@ export const MainPage = return (
-
-
{ - (!this.state.splitControlBar) && this.titleBar(pedalBoardItem) + (!this.state.splitControlBar) && this.titleBar(pedalboardItem) }
@@ -453,7 +453,7 @@ export const MainPage =
{ this.onDeletePedal(pedalBoardItem?.instanceId ?? -1) }} + onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }} size="large"> Delete @@ -464,7 +464,7 @@ export const MainPage = color="primary" size="small" onClick={this.onLoadClick} - disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalBoardItem()?.isSplit() ?? true)} + disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalboardItem()?.isSplit() ?? true)} startIcon={} style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}} > @@ -485,7 +485,7 @@ export const MainPage = this.state.splitControlBar && (
{ - this.titleBar(pedalBoardItem) + this.titleBar(pedalboardItem) }
) @@ -495,12 +495,12 @@ export const MainPage = missing ? (
Error: Plugin is not installed. - {pluginUri} + {pluginUri}
): ( - GetControlView(pedalBoardItem) + GetControlView(pedalboardItem) ) }
diff --git a/react/src/MidiBindingView.tsx b/react/src/MidiBindingView.tsx index 9753b1f..b9fc6e7 100644 --- a/react/src/MidiBindingView.tsx +++ b/react/src/MidiBindingView.tsx @@ -139,8 +139,8 @@ const MidiBindingView = render() { let classes = this.props.classes; let midiBinding = this.props.midiBinding; - let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); - let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri); + let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); + let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri); if (!uiPlugin) { return (
); } @@ -267,7 +267,7 @@ const MidiBindingView = (canTrigger) && (
- (Trigger) + (Trigger)
) diff --git a/react/src/MidiBindingsDialog.tsx b/react/src/MidiBindingsDialog.tsx index fab5785..63629f1 100644 --- a/react/src/MidiBindingsDialog.tsx +++ b/react/src/MidiBindingsDialog.tsx @@ -123,8 +123,8 @@ export const MidiBindingDialog = { this.cancelListenForControl(); - let pedalBoard = this.model.pedalBoard.get(); - let item = pedalBoard.getItem(instanceId); + let pedalboard = this.model.pedalboard.get(); + let item = pedalboard.getItem(instanceId); if (!item) return; let binding = item.getMidiBinding(symbol); @@ -181,7 +181,7 @@ export const MidiBindingDialog = let classes = this.props.classes; let result: React.ReactNode[] = []; - let pedalboard = this.model.pedalBoard.get(); + let pedalboard = this.model.pedalboard.get(); let iter = pedalboard.itemsGenerator(); while (true) { let v = iter.next(); @@ -285,7 +285,7 @@ export const MidiBindingDialog = size="large"> - + Preset MIDI Bindings diff --git a/react/src/PedalBoard.tsx b/react/src/Pedalboard.tsx similarity index 76% rename from react/src/PedalBoard.tsx rename to react/src/Pedalboard.tsx index 8f640f7..38c222c 100644 --- a/react/src/PedalBoard.tsx +++ b/react/src/Pedalboard.tsx @@ -57,30 +57,9 @@ export class ControlValue implements Deserializable { 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 { - deserializePedalBoardItem(input: any): PedalBoardItem { +export class PedalboardItem implements Deserializable { + deserializePedalboardItem(input: any): PedalboardItem { this.instanceId = input.instanceId ?? -1; this.uri = input.uri; this.pluginName = input.pluginName; @@ -88,24 +67,24 @@ 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 ?? ""; + this.lv2State = input.lv2State; return this; } - deserialize(input: any): PedalBoardItem { - return this.deserializePedalBoardItem(input); + deserialize(input: any): PedalboardItem { + return this.deserializePedalboardItem(input); } - static deserializeArray(input: any): PedalBoardItem[] { - let result: PedalBoardItem[] = []; + static deserializeArray(input: any): PedalboardItem[] { + let result: PedalboardItem[] = []; for (let i = 0; i < input.length; ++i) { let inputItem: any = input[i]; let uri: string = inputItem.uri as string; - let outputItem: PedalBoardItem; + let outputItem: PedalboardItem; if (uri === SPLIT_PEDALBOARD_ITEM_URI) { - outputItem = new PedalBoardSplitItem().deserialize(inputItem); + outputItem = new PedalboardSplitItem().deserialize(inputItem); } else { - outputItem = new PedalBoardItem().deserialize(inputItem); + outputItem = new PedalboardItem().deserialize(inputItem); } result[i] = outputItem; @@ -158,17 +137,6 @@ 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) { @@ -209,16 +177,16 @@ export class PedalBoardItem implements Deserializable { } - static EmptyArray: PedalBoardItem[] = []; + static EmptyArray: PedalboardItem[] = []; instanceId: number = -1; isEnabled: boolean = false; uri: string = ""; pluginName?: string; controlValues: ControlValue[] = ControlValue.EmptyArray; - propertyValues: PropertyValue[] = []; midiBindings: MidiBinding[] = []; vstState: string = ""; + lv2State: any = {}; }; @@ -230,7 +198,7 @@ export enum SplitType { -export class PedalBoardSplitItem extends PedalBoardItem { +export class PedalboardSplitItem extends PedalboardItem { static PANL_KEY: string = "panL"; static PANR_KEY: string = "panR"; static VOLL_KEY: string = "volL"; @@ -241,15 +209,15 @@ export class PedalBoardSplitItem extends PedalBoardItem { static SELECT_KEY: string = "select"; - deserialize(input: any): PedalBoardSplitItem { - this.deserializePedalBoardItem(input); - this.topChain = PedalBoardItem.deserializeArray(input.topChain); - this.bottomChain = PedalBoardItem.deserializeArray(input.bottomChain); + deserialize(input: any): PedalboardSplitItem { + this.deserializePedalboardItem(input); + this.topChain = PedalboardItem.deserializeArray(input.topChain); + this.bottomChain = PedalboardItem.deserializeArray(input.bottomChain); return this; } getSplitType(): SplitType { - let rawValue = this.getControlValue(PedalBoardSplitItem.TYPE_KEY); + let rawValue = this.getControlValue(PedalboardSplitItem.TYPE_KEY); if (rawValue < 1) return SplitType.Ab; if (rawValue < 2) return SplitType.Mix; @@ -263,43 +231,43 @@ export class PedalBoardSplitItem extends PedalBoardItem { } getMixControl(): ControlValue { - return this.getControl(PedalBoardSplitItem.MIX_KEY); + return this.getControl(PedalboardSplitItem.MIX_KEY); } getMix(): number { - return this.getControlValue(PedalBoardSplitItem.MIX_KEY); + return this.getControlValue(PedalboardSplitItem.MIX_KEY); } isASelected(): boolean { - return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalBoardSplitItem.SELECT_KEY) === 0; + return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) === 0; } isBSelected(): boolean { - return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalBoardSplitItem.SELECT_KEY) !== 0; + return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) !== 0; } - topChain: PedalBoardItem[] = PedalBoardItem.EmptyArray; - bottomChain: PedalBoardItem[] = PedalBoardItem.EmptyArray; + topChain: PedalboardItem[] = PedalboardItem.EmptyArray; + bottomChain: PedalboardItem[] = PedalboardItem.EmptyArray; } -export class PedalBoard implements Deserializable { +export class Pedalboard implements Deserializable { - deserialize(input: any): PedalBoard { + deserialize(input: any): Pedalboard { this.name = input.name; - this.items = PedalBoardItem.deserializeArray(input.items); + this.items = PedalboardItem.deserializeArray(input.items); this.nextInstanceId = input.nextInstanceId ?? -1; return this; } - clone(): PedalBoard { - return new PedalBoard().deserialize(this); + clone(): Pedalboard { + return new Pedalboard().deserialize(this); } name: string = ""; - items: PedalBoardItem[] = []; + items: PedalboardItem[] = []; nextInstanceId: number = -1; - *itemsGenerator(): Generator { + *itemsGenerator(): Generator { let it = itemGenerator_(this.items); while (true) { @@ -335,7 +303,7 @@ export class PedalBoard implements Deserializable { } - maybeGetItem(instanceId: number): PedalBoardItem | null{ + maybeGetItem(instanceId: number): PedalboardItem | null{ let it = this.itemsGenerator(); while (true) { @@ -348,7 +316,7 @@ export class PedalBoard implements Deserializable { } return null; } - getItem(instanceId: number): PedalBoardItem { + getItem(instanceId: number): PedalboardItem { let it = this.itemsGenerator(); while (true) { @@ -361,7 +329,7 @@ export class PedalBoard implements Deserializable { } throw new PiPedalArgumentError("Item not found."); } - deleteItem_(instanceId: number,items: PedalBoardItem[]): number | null + deleteItem_(instanceId: number,items: PedalboardItem[]): number | null { for (let i = 0; i < items.length; ++i) { @@ -383,7 +351,7 @@ export class PedalBoard implements Deserializable { } else { if (item.isSplit()) { - let splitItem = item as PedalBoardSplitItem; + let splitItem = item as PedalboardSplitItem; let t = this.deleteItem_(instanceId,splitItem.topChain); if (t != null) return t; @@ -395,7 +363,7 @@ export class PedalBoard implements Deserializable { return null; } - canDeleteItem_(instanceId: number,items: PedalBoardItem[]): boolean + canDeleteItem_(instanceId: number,items: PedalboardItem[]): boolean { for (let i = 0; i < items.length; ++i) { @@ -407,7 +375,7 @@ export class PedalBoard implements Deserializable { } if (item.isSplit()) { - let splitItem = item as PedalBoardSplitItem; + let splitItem = item as PedalboardSplitItem; if (this.canDeleteItem_(instanceId,splitItem.topChain)) { return true; @@ -436,15 +404,15 @@ export class PedalBoard implements Deserializable { if (!item) return false; return item.setMidiBinding(midiBinding); } - addToStart(item: PedalBoardItem) + addToStart(item: PedalboardItem) { this.items.splice(0,0,item); } - addToEnd(item: PedalBoardItem) + addToEnd(item: PedalboardItem) { this.items.splice(this.items.length,0,item); } - static _addRelative(items: PedalBoardItem[],newItem: PedalBoardItem, instanceId: number, addBefore: boolean): boolean + static _addRelative(items: PedalboardItem[],newItem: PedalboardItem, instanceId: number, addBefore: boolean): boolean { for (let i = 0; i < items.length; ++i) { @@ -461,7 +429,7 @@ export class PedalBoard implements Deserializable { } if (item.isSplit()) { - let split = item as PedalBoardSplitItem; + let split = item as PedalboardSplitItem; if (this._addRelative(split.topChain,newItem,instanceId,addBefore)) { return true; @@ -476,19 +444,19 @@ export class PedalBoard implements Deserializable { } - addBefore(item: PedalBoardItem, instanceId: number) + addBefore(item: PedalboardItem, instanceId: number) { if (item.instanceId === instanceId) return; - let result = PedalBoard._addRelative(this.items,item, instanceId, true); + let result = Pedalboard._addRelative(this.items,item, instanceId, true); if (!result) { throw new PiPedalArgumentError("instanceId not found."); } } - addAfter(item: PedalBoardItem, instanceId: number) + addAfter(item: PedalboardItem, instanceId: number) { if (item.instanceId === instanceId) return; - let result = PedalBoard._addRelative(this.items,item, instanceId, false); + let result = Pedalboard._addRelative(this.items,item, instanceId, false); if (!result) { throw new PiPedalArgumentError("instanceId not found."); } @@ -520,8 +488,8 @@ export class PedalBoard implements Deserializable { } } } - createEmptySplit(): PedalBoardSplitItem { - let result: PedalBoardSplitItem = new PedalBoardSplitItem(); + createEmptySplit(): PedalboardSplitItem { + let result: PedalboardSplitItem = new PedalboardSplitItem(); result.uri = SPLIT_PEDALBOARD_ITEM_URI; result.instanceId = ++this.nextInstanceId; result.pluginName = ""; @@ -529,13 +497,13 @@ export class PedalBoard implements Deserializable { result.topChain = [ this.createEmptyItem()]; result.bottomChain = [ this.createEmptyItem()]; result.controlValues = [ - new ControlValue(PedalBoardSplitItem.TYPE_KEY, 0), - new ControlValue(PedalBoardSplitItem.SELECT_KEY,0), - new ControlValue(PedalBoardSplitItem.MIX_KEY,0), - new ControlValue(PedalBoardSplitItem.PANL_KEY,0), - new ControlValue(PedalBoardSplitItem.VOLL_KEY,-3), - new ControlValue(PedalBoardSplitItem.PANR_KEY,0), - new ControlValue(PedalBoardSplitItem.VOLR_KEY,-3) + new ControlValue(PedalboardSplitItem.TYPE_KEY, 0), + new ControlValue(PedalboardSplitItem.SELECT_KEY,0), + new ControlValue(PedalboardSplitItem.MIX_KEY,0), + new ControlValue(PedalboardSplitItem.PANL_KEY,0), + new ControlValue(PedalboardSplitItem.VOLL_KEY,-3), + new ControlValue(PedalboardSplitItem.PANR_KEY,0), + new ControlValue(PedalboardSplitItem.VOLR_KEY,-3) ]; @@ -543,8 +511,8 @@ export class PedalBoard implements Deserializable { } - createEmptyItem(): PedalBoardItem { - let result: PedalBoardItem = new PedalBoardItem(); + createEmptyItem(): PedalboardItem { + let result: PedalboardItem = new PedalboardItem(); result.uri = EMPTY_PEDALBOARD_ITEM_URI; result.instanceId = ++this.nextInstanceId; result.pluginName = ""; @@ -552,7 +520,7 @@ export class PedalBoard implements Deserializable { return result; } - setItemEmpty(item: PedalBoardItem) + setItemEmpty(item: PedalboardItem) { item.uri = EMPTY_PEDALBOARD_ITEM_URI; item.instanceId = ++this.nextInstanceId; @@ -562,7 +530,7 @@ export class PedalBoard implements Deserializable { } - _replaceItem(items: PedalBoardItem[], instanceId: number, newItem: PedalBoardItem): boolean { + _replaceItem(items: PedalboardItem[], instanceId: number, newItem: PedalboardItem): boolean { for (let i = 0; i < items.length; ++i) { let item = items[i]; @@ -573,7 +541,7 @@ export class PedalBoard implements Deserializable { } if (items[i].isSplit()) { - let splitItem = item as PedalBoardSplitItem; + let splitItem = item as PedalboardSplitItem; if (this._replaceItem(splitItem.topChain,instanceId,newItem)) return true; if (this._replaceItem(splitItem.bottomChain,instanceId,newItem)) @@ -585,7 +553,7 @@ export class PedalBoard implements Deserializable { return false; } - replaceItem(instanceId: number, newItem: PedalBoardItem) + replaceItem(instanceId: number, newItem: PedalboardItem) { let result = this._replaceItem(this.items,instanceId,newItem); if (!result) @@ -593,7 +561,7 @@ export class PedalBoard implements Deserializable { throw new PiPedalArgumentError("instanceId not found."); } } - _addItem(items: PedalBoardItem[], newItem: PedalBoardItem, instanceId: number, append: boolean) + _addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean) { for (let i = 0; i < items.length; ++i) { @@ -611,27 +579,27 @@ export class PedalBoard implements Deserializable { } if (item.isSplit()) { - let splitItem = item as PedalBoardSplitItem; + let splitItem = item as PedalboardSplitItem; if (this._addItem(splitItem.topChain,newItem,instanceId,append)) return true; if (this._addItem(splitItem.bottomChain,newItem,instanceId,append)) return true; } } } - addItem(newItem: PedalBoardItem, instanceId: number, append: boolean): void + addItem(newItem: PedalboardItem, instanceId: number, append: boolean): void { this._addItem(this.items,newItem,instanceId,append); } } -function* itemGenerator_(items: PedalBoardItem[]): Generator { +function* itemGenerator_(items: PedalboardItem[]): Generator { for (let i = 0; i < items.length; ++i) { let item = items[i]; yield item; if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) { - let splitItem = item as PedalBoardSplitItem; + let splitItem = item as PedalboardSplitItem; let it = itemGenerator_(splitItem.topChain); while (true) { diff --git a/react/src/PedalBoardView.tsx b/react/src/PedalboardView.tsx similarity index 95% rename from react/src/PedalBoardView.tsx rename to react/src/PedalboardView.tsx index c62c708..59245bf 100644 --- a/react/src/PedalBoardView.tsx +++ b/react/src/PedalboardView.tsx @@ -36,8 +36,8 @@ import Utility from './Utility' import { - PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType, -} from './PedalBoard'; + Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType, +} from './Pedalboard'; const START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start"; const END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End"; @@ -69,7 +69,7 @@ function CalculateConnection(numberOfInputs: number, numberOfOutputs: number) return result; } -const pedalBoardStyles = (theme: Theme) => createStyles({ +const pedalboardStyles = (theme: Theme) => createStyles({ scrollContainer: { }, @@ -177,7 +177,7 @@ const pedalBoardStyles = (theme: Theme) => createStyles({ export type OnSelectHandler = (selectedPedal: number) => void; -interface PedalBoardProps extends WithStyles { +interface PedalboardProps extends WithStyles { theme: Theme; selectedId?: number; onSelectionChanged?: OnSelectHandler; @@ -190,14 +190,14 @@ interface LayoutSize { height: number; } -type PedalBoardState = { - pedalBoard?: PedalBoard; +type PedalboardState = { + pedalboard?: Pedalboard; }; const EMPTY_PEDALS: PedalLayout[] = []; -function makeChain(model: PiPedalModel, uiItems?: PedalBoardItem[]): PedalLayout[] { +function makeChain(model: PiPedalModel, uiItems?: PedalboardItem[]): PedalLayout[] { let result: PedalLayout[] = []; if (uiItems) { for (let i = 0; i < uiItems.length; ++i) { @@ -219,7 +219,7 @@ class PedalLayout { numberOfInputs: number = 2; numberOfOutputs: number = 2; - pedalItem?: PedalBoardItem; + pedalItem?: PedalboardItem; // Split Layout only. topChildren: PedalLayout[] = EMPTY_PEDALS; @@ -245,7 +245,7 @@ class PedalLayout { t.numberOfOutputs = 0; return t; } - constructor(model?: PiPedalModel, pedalItem?: PedalBoardItem) { + constructor(model?: PiPedalModel, pedalItem?: PedalboardItem) { if (model === undefined && pedalItem === undefined) { return; } @@ -255,7 +255,7 @@ class PedalLayout { this.pedalItem = pedalItem; this.uri = pedalItem.uri; if (pedalItem.isSplit()) { - let splitter = pedalItem as PedalBoardSplitItem; + let splitter = pedalItem as PedalboardSplitItem; this.pluginType = PluginType.UtilityPlugin; this.topChildren = makeChain(model, splitter.topChain); @@ -350,9 +350,9 @@ class LayoutParams { } -const PedalBoardView = - withStyles(pedalBoardStyles, { withTheme: true })( - class extends Component +const PedalboardView = + withStyles(pedalboardStyles, { withTheme: true })( + class extends Component { model: PiPedalModel; @@ -360,15 +360,15 @@ const PedalBoardView = scrollRef: React.RefObject; - constructor(props: PedalBoardProps) { + constructor(props: PedalboardProps) { super(props); this.model = PiPedalModelFactory.getInstance(); if (!props.selectedId) props.selectedId = -1; this.state = { - pedalBoard: this.model.pedalBoard.get(), + pedalboard: this.model.pedalboard.get(), }; - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.frameRef = React.createRef(); this.scrollRef = React.createRef(); this.handleTouchStart = this.handleTouchStart.bind(this); @@ -402,11 +402,11 @@ const PedalBoardView = if (item.isSplitter() && item.pedalItem) { if (item.bounds.contains(clientX, clientY)) { if (clientX < item.bounds.x + CELL_WIDTH / 2) { - this.model.movePedalBoardItemBefore(instanceId, item.pedalItem.instanceId); + this.model.movePedalboardItemBefore(instanceId, item.pedalItem.instanceId); this.setSelection(instanceId); return; } else if (clientX > item.bounds.right - CELL_WIDTH / 2) { - this.model.movePedalBoardItemAfter(instanceId, item.pedalItem.instanceId); + this.model.movePedalboardItemAfter(instanceId, item.pedalItem.instanceId); this.setSelection(instanceId); return; @@ -420,7 +420,7 @@ const PedalBoardView = if (clientX < item.topChildren[0].bounds.x) { let topPedalItem = item.topChildren[0].pedalItem; if (topPedalItem) { - this.model.movePedalBoardItemBefore(instanceId, topPedalItem.instanceId); + this.model.movePedalboardItemBefore(instanceId, topPedalItem.instanceId); this.setSelection(instanceId); return; } @@ -428,7 +428,7 @@ const PedalBoardView = let lastTop = item.topChildren[item.topChildren.length - 1]; if (clientX >= lastTop.bounds.right && clientX < item.bounds.right - CELL_WIDTH / 2) { if (lastTop.pedalItem) { - this.model.movePedalBoardItemAfter(instanceId, lastTop.pedalItem.instanceId); + this.model.movePedalboardItemAfter(instanceId, lastTop.pedalItem.instanceId); this.setSelection(instanceId); return; } @@ -441,7 +441,7 @@ const PedalBoardView = if (clientX < item.bottomChildren[0].bounds.x) { let bottomPedalItem = item.bottomChildren[0].pedalItem; if (bottomPedalItem) { - this.model.movePedalBoardItemBefore(instanceId, bottomPedalItem.instanceId); + this.model.movePedalboardItemBefore(instanceId, bottomPedalItem.instanceId); this.setSelection(instanceId); return; } @@ -450,7 +450,7 @@ const PedalBoardView = if (clientX >= lastBottom.bounds.right && clientX < item.bounds.right-CELL_WIDTH/2) { if (lastBottom.pedalItem) { - this.model.movePedalBoardItemAfter(instanceId, lastBottom.pedalItem.instanceId); + this.model.movePedalboardItemAfter(instanceId, lastBottom.pedalItem.instanceId); this.setSelection(instanceId); return; } @@ -461,11 +461,11 @@ const PedalBoardView = } else if (item.bounds.contains(clientX, clientY)) { if (item.isStart()) { - this.model.movePedalBoardItemToStart(instanceId); + this.model.movePedalboardItemToStart(instanceId); this.setSelection(instanceId); return; } else if (item.isEnd()) { - this.model.movePedalBoardItemToEnd(instanceId); + this.model.movePedalboardItemToEnd(instanceId); this.setSelection(instanceId); return; } else { @@ -473,11 +473,11 @@ const PedalBoardView = { let margin = (CELL_WIDTH - FRAME_SIZE) / 2; if (clientX < item.bounds.x + margin) { - this.model.movePedalBoardItemBefore(instanceId, item.pedalItem.instanceId); + this.model.movePedalboardItemBefore(instanceId, item.pedalItem.instanceId); } else if (clientX > item.bounds.right - margin) { - this.model.movePedalBoardItemAfter(instanceId, item.pedalItem.instanceId); + this.model.movePedalboardItemAfter(instanceId, item.pedalItem.instanceId); } else { - this.model.movePedalBoardItem(instanceId, item.pedalItem.instanceId); + this.model.movePedalboardItem(instanceId, item.pedalItem.instanceId); } this.setSelection(instanceId); return; @@ -487,23 +487,23 @@ const PedalBoardView = } } // delete the plugin. - let newId = this.model.setPedalBoardItemEmpty(instanceId); + let newId = this.model.setPedalboardItemEmpty(instanceId); this.setSelection(newId); } - onPedalBoardChanged(value?: PedalBoard) { - this.setState({ pedalBoard: value }); + onPedalboardChanged(value?: Pedalboard) { + this.setState({ pedalboard: value }); } componentDidMount() { this.scrollRef.current!.addEventListener("touchstart",this.handleTouchStart, {passive: false}); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); } componentWillUnmount() { this.scrollRef.current!.removeEventListener("touchstart",this.handleTouchStart); - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); } offsetLayout_(layoutItems: PedalLayout[], offset: number): void { @@ -523,7 +523,7 @@ const PedalBoardView = if (layoutItem.pedalItem === undefined) { throw new Error("Invalid splitter"); } - let split = layoutItem.pedalItem as PedalBoardSplitItem; + let split = layoutItem.pedalItem as PedalboardSplitItem; if (split.getSplitType() === SplitType.Ab) { if (split.isASelected()) { return "img/fx_split_a.svg"; @@ -706,7 +706,7 @@ const PedalBoardView = let yTop = item.topConnectorY; let yBottom = item.bottomConnectorY; //let isStereo = item.stereoOutput; - let split = item.pedalItem as PedalBoardSplitItem; + let split = item.pedalItem as PedalboardSplitItem; let topEnabled = enabled && split.isASelected(); let bottomEnabled = enabled && split.isBSelected(); @@ -889,7 +889,7 @@ const PedalBoardView = // actually not here anymore. :-/ It has a reactive definition in MainPage.tsx now. while (el) { - if (el.id === "pedalBoardScroll") + if (el.id === "pedalboardScroll") { return el as HTMLDivElement; } @@ -930,7 +930,7 @@ const PedalBoardView = for (let i = 0; i < length; ++i) { let item = layoutChain[i]; if (item.isSplitter()) { - let splitter = item.pedalItem as PedalBoardSplitItem; + let splitter = item.pedalItem as PedalboardSplitItem; this.renderSplitConnectors(output, item, enabled, i === length - 1 && shortSplitOutput,); this.renderConnectors(output, item.topChildren, enabled && splitter.isASelected(), false); this.renderConnectors(output, item.bottomChildren, enabled && splitter.isBSelected(), false); @@ -942,15 +942,14 @@ const PedalBoardView = } } renderConnectorFrame(layoutChain: PedalLayout[], layoutSize: LayoutSize): ReactNode { - let output: ReactNode[] = []; - this.renderConnectors(output, layoutChain, true, false); - + let outputs: ReactNode[] = []; + this.renderConnectors(outputs, layoutChain, true, false); return ( { - output + outputs } @@ -1065,7 +1064,7 @@ const PedalBoardView = let topInputs = item.topChildren[0].numberOfInputs; let bottomInputs = item.bottomChildren[0].numberOfInputs; - let splitItem = item.pedalItem as PedalBoardSplitItem; + let splitItem = item.pedalItem as PedalboardSplitItem; if (splitItem.getSplitType() !== SplitType.Lr) { item.numberOfInputs = CalculateConnection(item.numberOfInputs, Math.max(topInputs,bottomInputs)); @@ -1102,7 +1101,7 @@ const PedalBoardView = for (let i = 0; i < layoutChain.length; ++i) { let item = layoutChain[i]; if (item.isSplitter()) { - let splitter = item.pedalItem as PedalBoardSplitItem; + let splitter = item.pedalItem as PedalboardSplitItem; item.numberOfInputs = numberOfInputs; let chainInputs = numberOfInputs; @@ -1156,7 +1155,7 @@ const PedalBoardView = render() { const { classes } = this.props; - let layoutChain = makeChain(this.model, this.state.pedalBoard?.items); + let layoutChain = makeChain(this.model, this.state.pedalboard?.items); let start = PedalLayout.Start(); let end = PedalLayout.End(); if (layoutChain.length !== 0) @@ -1187,4 +1186,4 @@ const PedalBoardView = } ); -export default PedalBoardView \ No newline at end of file +export default PedalboardView \ No newline at end of file diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 479e4c2..da8e1bc 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -22,7 +22,7 @@ import { UiPlugin, UiControl, PluginType, PiPedalFileProperty } from './Lv2Plugi import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError'; import { ObservableProperty } from './ObservableProperty'; -import { PedalBoard, PedalBoardItem, ControlValue } from './PedalBoard' +import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard' import PluginClass from './PluginClass'; import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket'; import { nullCast } from './Utility' @@ -50,7 +50,6 @@ 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; @@ -61,6 +60,19 @@ export interface PluginPresetsChangedHandle { }; +export type StateChangedHandler = (instanceId: number) => void; + +interface StateChangedEntry { + handle: number; + instanceId: number; + onStateChanged: StateChangedHandler; +}; + +export interface StateChangedHandle { + _handle: number; +} + + export interface ZoomedControlInfo { source: HTMLElement; @@ -95,17 +107,6 @@ 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) { @@ -115,15 +116,18 @@ class MidiEventListener { handle: number; callback: (isNote: boolean, noteOrControl: number) => void; }; -class AtomOutputListener { - constructor(handle: number, instanceId: number, callback: (instanceId: number, atomObject: any) => void) { + +export type PatchPropertyListener = (instanceId: number, propertyUri: string, atomObject: any) => void; + +class PatchPropertyListenerItem { + constructor(handle: number, instanceId: number, callback: PatchPropertyListener) { this.handle = handle; this.instanceId = instanceId; this.callback = callback; } handle: number; instanceId: number; - callback: (instanceId: number, atomObject: any) => void; + callback: PatchPropertyListener; }; @@ -301,14 +305,14 @@ interface PresetsChangedBody { clientId: number, presets: PresetIndex } -interface PedalBoardItemEnableBody { +interface PedalboardItemEnableBody { clientId: number, instanceId: number, enabled: boolean } -interface PedalBoardChangedBody { +interface PedalboardChangedBody { clientId: number; - pedalBoard: PedalBoard; + pedalboard: Pedalboard; } interface ControlChangedBody { clientId: number; @@ -316,6 +320,12 @@ interface ControlChangedBody { symbol: string; value: number; }; +interface PatchPropertyChangedBody { + clientId: number; + instanceId: number; + propertyUri: string; + value: any; +}; interface Vst3ControlChangedBody { clientId: number; instanceId: number; @@ -328,152 +338,156 @@ export interface FavoritesList { [url: string]: boolean; } -export interface PiPedalModel { - clientId: number; - countryCodes: Object; - debug: boolean; - serverVersion?: PiPedalVersion; - errorMessage: ObservableProperty; - alertMessage: ObservableProperty; - showStatusMonitor: ObservableProperty; - setShowStatusMonitor(show: boolean): void; - state: ObservableProperty; - visibilityState: ObservableProperty; +// export interface PiPedalModel { +// clientId: number; +// countryCodes: Object; +// debug: boolean; +// serverVersion?: PiPedalVersion; +// errorMessage: ObservableProperty; +// alertMessage: ObservableProperty; +// showStatusMonitor: ObservableProperty; +// setShowStatusMonitor(show: boolean): void; +// state: ObservableProperty; +// visibilityState: ObservableProperty; - ui_plugins: ObservableProperty; - plugin_classes: ObservableProperty; - jackConfiguration: ObservableProperty; - jackSettings: ObservableProperty; - banks: ObservableProperty; - jackServerSettings: ObservableProperty; - wifiConfigSettings: ObservableProperty; - wifiDirectConfigSettings: ObservableProperty; - governorSettings: ObservableProperty; - favorites: ObservableProperty; +// ui_plugins: ObservableProperty; +// plugin_classes: ObservableProperty; +// jackConfiguration: ObservableProperty; +// jackSettings: ObservableProperty; +// banks: ObservableProperty; +// jackServerSettings: ObservableProperty; +// wifiConfigSettings: ObservableProperty; +// wifiDirectConfigSettings: ObservableProperty; +// governorSettings: ObservableProperty; +// favorites: ObservableProperty; - presets: ObservableProperty; - systemMidiBindings: ObservableProperty; +// presets: ObservableProperty; +// systemMidiBindings: ObservableProperty; - zoomedUiControl: ObservableProperty; +// zoomedUiControl: ObservableProperty; - setJackServerSettings(jackServerSettings: JackServerSettings): void; - setMidiBinding(instanceId: number, midiBinding: MidiBinding): void; +// setJackServerSettings(jackServerSettings: JackServerSettings): void; +// setMidiBinding(instanceId: number, midiBinding: MidiBinding): void; - getUiPlugin(uri: string): UiPlugin | null; +// getUiPlugin(uri: string): UiPlugin | null; - pedalBoard: ObservableProperty; - loadPedalBoardPlugin(itemId: number, selectedUri: string): number; +// pedalboard: ObservableProperty; +// 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; - deletePedalBoardPedal(instanceId: number): number | null; +// setPedalboardControl(instanceId: number, key: string, value: number): void; +// setPedalboardItemEnabled(instanceId: number, value: boolean): void; +// previewPedalboardValue(instanceId: number, key: string, value: number): void; +// deletePedalboardPedal(instanceId: number): number | null; - movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void; - movePedalBoardItemToStart(fromInstanceId: number): void; - movePedalBoardItemToEnd(fromInstanceId: number): void; - movePedalBoardItemBefore(fromInstanceId: number, toInstanceId: number): void; - movePedalBoardItemAfter(fromInstanceId: number, toInstanceId: number): void; +// movePedalboardItem(fromInstanceId: number, toInstanceId: number): void; +// movePedalboardItemToStart(fromInstanceId: number): void; +// movePedalboardItemToEnd(fromInstanceId: number): void; +// movePedalboardItemBefore(fromInstanceId: number, toInstanceId: number): void; +// movePedalboardItemAfter(fromInstanceId: number, toInstanceId: number): void; - addPedalBoardItem(instanceId: number, append: boolean): number; - addPedalBoardSplitItem(instanceId: number, append: boolean): number; - setPedalBoardItemEmpty(instanceId: number): number; +// addPedalboardItem(instanceId: number, append: boolean): number; +// addPedalboardSplitItem(instanceId: number, append: boolean): number; +// setPedalboardItemEmpty(instanceId: number): number; - saveCurrentPreset(): void; - saveCurrentPresetAs(newName: string): void; +// saveCurrentPreset(): void; +// saveCurrentPresetAs(newName: string): void; - saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): void; +// saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): void; - loadPreset(instanceId: number): void; - updatePresets(presets: PresetIndex): Promise; - deletePresetItem(instanceId: number): Promise; - renamePresetItem(instanceId: number, name: string): Promise; - copyPreset(fromId: number, toId: number): Promise; - duplicatePreset(instanceId: number): Promise; +// loadPreset(instanceId: number): void; +// updatePresets(presets: PresetIndex): Promise; +// deletePresetItem(instanceId: number): Promise; +// renamePresetItem(instanceId: number, name: string): Promise; +// copyPreset(fromId: number, toId: number): Promise; +// duplicatePreset(instanceId: number): Promise; - moveBank(from: number, to: number): Promise; - deleteBankItem(instanceId: number): Promise; +// moveBank(from: number, to: number): Promise; +// deleteBankItem(instanceId: number): Promise; - showAlert(message: string): void; +// showAlert(message: string): void; - setJackSettings(jackSettings: JackChannelSelection): void; +// setJackSettings(jackSettings: JackChannelSelection): void; - svgImgUrl(svgImage: string): string; +// svgImgUrl(svgImage: string): string; - removeVuSubscription(handle: VuSubscriptionHandle): void; - addVuSubscription(instanceId: number, vuChangedHandler: VuChangedHandler): VuSubscriptionHandle; +// removeVuSubscription(handle: VuSubscriptionHandle): void; +// addVuSubscription(instanceId: number, vuChangedHandler: VuChangedHandler): VuSubscriptionHandle; - monitorPort(instanceid: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle; - unmonitorPort(handle: MonitorPortHandle): void; +// monitorPort(instanceid: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle; +// unmonitorPort(handle: MonitorPortHandle): void; - getLv2Parameter(instanceId: number, uri: string): Promise; +// getPatchProperty(instanceId: number, uri: string): Promise; +// setPatchProperty(instanceId: number, uri: string, value: any): Promise; - renameBank(instanceId: number, newName: string): Promise; - saveBankAs(instanceId: number, newName: string): Promise; - openBank(bankId: number): Promise; +// renameBank(instanceId: number, newName: string): Promise; +// saveBankAs(instanceId: number, newName: string): Promise; +// openBank(bankId: number): Promise; - getJackStatus(): Promise; +// getJackStatus(): Promise; - getPluginPresets(uri: string): Promise; - loadPluginPreset(pluginInstanceId: number, presetInstanceId: number): void; - updatePluginPresets(uri: string, presets: PluginUiPresets): Promise; - duplicatePluginPreset(uri: string, instanceId: number): Promise; +// getPluginPresets(uri: string): Promise; +// loadPluginPreset(pluginInstanceId: number, presetInstanceId: number): void; +// updatePluginPresets(uri: string, presets: PluginUiPresets): Promise; +// duplicatePluginPreset(uri: string, instanceId: number): Promise; - setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void; +// setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void; - shutdown(): Promise; - restart(): Promise; +// shutdown(): Promise; +// restart(): Promise; - listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle; - cancelListenForMidiEvent(listenHandle: ListenHandle): void; +// listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle; +// cancelListenForMidiEvent(listenHandle: ListenHandle): void; - addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle - removeControlValueChangeListener(handle: ControlValueChangedHandle): void; +// addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle +// removeControlValueChangeListener(handle: ControlValueChangedHandle): void; - addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle; - removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void; +// addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle; +// removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void; - listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle; - cancelListenForAtomOutput(listenHandle: ListenHandle): void; +// listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle; +// cancelListenForAtomOutput(listenHandle: ListenHandle): void; - download(targetType: string, isntanceId: number | string): void; +// download(targetType: string, isntanceId: number | string): void; - uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise; - uploadBank(file: File, uploadAfter: number): Promise; +// uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise; +// uploadBank(file: File, uploadAfter: number): Promise; - setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise; - setWifiDirectConfigSettings(wifiDirectConfigSettings: WifiDirectConfigSettings): Promise; - setGovernorSettings(governor: string): Promise; +// setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise; +// setWifiDirectConfigSettings(wifiDirectConfigSettings: WifiDirectConfigSettings): Promise; +// setGovernorSettings(governor: string): Promise; - getWifiChannels(countryIso3661: string): Promise; +// getWifiChannels(countryIso3661: string): Promise; - getAlsaDevices(): Promise; +// getAlsaDevices(): Promise; - zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void; +// zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void; - onPreviousZoomedControl(): void; - onNextZoomedControl(): void; - clearZoomedControl(): void; +// onPreviousZoomedControl(): void; +// onNextZoomedControl(): void; +// clearZoomedControl(): void; - setFavorite(pluginUrl: string, isFavorite: boolean): void; +// setFavorite(pluginUrl: string, isFavorite: boolean): void; - isAndroidHosted(): boolean; - showAndroidDonationActivity(): void; - getAndroidHostVersion(): string; - chooseNewDevice(): void; +// isAndroidHosted(): boolean; +// showAndroidDonationActivity(): void; +// getAndroidHostVersion(): string; +// chooseNewDevice(): void; - hasConfiguration(): boolean; +// isOnboarding(): boolean; +// setOnboarding(value: boolean): void; - requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise; -}; -class PiPedalModelImpl implements PiPedalModel { + +// requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise; +// }; + +export class PiPedalModel //implements PiPedalModel +{ clientId: number = -1; serverVersion?: PiPedalVersion; @@ -493,7 +507,7 @@ class PiPedalModelImpl implements PiPedalModel { showStatusMonitor: ObservableProperty = new ObservableProperty(true); - pedalBoard: ObservableProperty = new ObservableProperty(new PedalBoard()); + pedalboard: ObservableProperty = new ObservableProperty(new Pedalboard()); plugin_classes: ObservableProperty = new ObservableProperty(new PluginClass()); jackConfiguration: ObservableProperty = new ObservableProperty(new JackConfiguration()); jackSettings: ObservableProperty = new ObservableProperty(new JackChannelSelection()); @@ -586,7 +600,28 @@ class PiPedalModelImpl implements PiPedalModel { if (this.visibilityState.get() === VisibilityState.Hidden) return; let message = header.message; - if (message === "onMonitorPortOutput") { + if (message === "onControlChanged") { + let controlChangedBody = body as ControlChangedBody; + this._setPedalboardControlValue( + controlChangedBody.instanceId, + controlChangedBody.symbol, + controlChangedBody.value, + false // do NOT notify the server of the change. + ); + } else if (message === "onLv2StateChanged") { + let instanceId = body as number; + this.onLv2StateChanged(instanceId); + } else if (message === "onVst3ControlChanged") { + let controlChangedBody = body as Vst3ControlChangedBody; + this._setVst3PedalboardControlValue( + controlChangedBody.instanceId, + controlChangedBody.symbol, + controlChangedBody.value, + controlChangedBody.state, + false // do NOT notify the server of the change. + ); + + } else if (message === "onMonitorPortOutput") { let monitorPortOutputBody = body as MonitorPortOutputBody; for (let i = 0; i < this.monitorPortSubscriptions.length; ++i) { let subscription = this.monitorPortSubscriptions[i]; @@ -624,19 +659,19 @@ class PiPedalModelImpl implements PiPedalModel { let controlValues = ControlValue.deserializeArray(body.controlValues); this.handleOnLoadPluginPreset(instanceId, controlValues); } else if (message === "onItemEnabledChanged") { - let itemEnabledBody = body as PedalBoardItemEnableBody; - this._setPedalBoardItemEnabled( + let itemEnabledBody = body as PedalboardItemEnableBody; + this._setPedalboardItemEnabled( itemEnabledBody.instanceId, itemEnabledBody.enabled, false // No server notification. ); - } else if (message === "onPedalBoardChanged") { - let pedalChangedBody = body as PedalBoardChangedBody; - this.pedalBoard.set(new PedalBoard().deserialize(pedalChangedBody.pedalBoard)); + } else if (message === "onPedalboardChanged") { + let pedalChangedBody = body as PedalboardChangedBody; + this.pedalboard.set(new Pedalboard().deserialize(pedalChangedBody.pedalboard)); } else if (message === "onMidiValueChanged") { let controlChangedBody = body as ControlChangedBody; - this._setPedalBoardControlValue( + this._setPedalboardControlValue( controlChangedBody.instanceId, controlChangedBody.symbol, controlChangedBody.value, @@ -651,32 +686,15 @@ class PiPedalModelImpl implements PiPedalModel { let isNote = body.isNote as boolean; let noteOrControl = body.noteOrControl as number; this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl); - } else if (message === "onNotifyAtomOut") { + } else if (message === "onNotifyPatchProperty") { let clientHandle = body.clientHandle as number; let instanceId = body.instanceId as number; - let atomJson = body.atomJson as string; - this.handleNotifyAtomOutput(clientHandle, instanceId, atomJson); + let propertyUri = body.propertyUri as string; + let atomJson = body.atomJson as any; + this.handleNotifyPatchProperty(clientHandle, instanceId, propertyUri, atomJson); if (header.replyTo) { this.webSocket?.reply(header.replyTo, "onNotifyAtomOut", true); } - } else if (message === "onControlChanged") { - let controlChangedBody = body as ControlChangedBody; - this._setPedalBoardControlValue( - controlChangedBody.instanceId, - controlChangedBody.symbol, - controlChangedBody.value, - false // do NOT notify the server of the change. - ); - } else if (message === "onVst3ControlChanged") { - let controlChangedBody = body as Vst3ControlChangedBody; - this._setVst3PedalBoardControlValue( - controlChangedBody.instanceId, - controlChangedBody.symbol, - controlChangedBody.value, - controlChangedBody.state, - false // do NOT notify the server of the change. - ); - } else if (message === "onJackServerSettingsChanged") { let jackServerSettings = new JackServerSettings().deserialize(body); this.jackServerSettings.set(jackServerSettings); @@ -706,8 +724,7 @@ class PiPedalModelImpl implements PiPedalModel { if (header.replyTo) { this.webSocket?.reply(header.replyTo, "onVuUpdate", true); } - } else if (message === "onSystemMidiBindingsChanged") - { + } else if (message === "onSystemMidiBindingsChanged") { let bindings = MidiBinding.deserialize_array(body); this.systemMidiBindings.set(bindings); } else { @@ -737,7 +754,7 @@ class PiPedalModelImpl implements PiPedalModel { } } - + requestPluginClasses(): Promise { const myRequest = new Request(this.varRequest('plugin_classes.json')); return fetch(myRequest) @@ -782,10 +799,10 @@ class PiPedalModelImpl implements PiPedalModel { this.getWebSocket().request("hello") .then(clientId => { this.clientId = clientId; - return this.getWebSocket().request("currentPedalBoard"); + return this.getWebSocket().request("currentPedalboard"); }) .then(data => { - this.pedalBoard.set(new PedalBoard().deserialize(data)); + this.pedalboard.set(new Pedalboard().deserialize(data)); return this.getWebSocket().request("getShowStatusMonitor"); }) @@ -845,7 +862,7 @@ class PiPedalModelImpl implements PiPedalModel { return this.getWebSocket().request("getSystemMidiBindings"); }) - .then((data)=> { + .then((data) => { let bindings = MidiBinding.deserialize_array(data); this.systemMidiBindings.set(bindings); @@ -946,10 +963,10 @@ class PiPedalModelImpl implements PiPedalModel { .then(data => { this.ui_plugins.set(UiPlugin.deserialize_array(data)); - return this.getWebSocket().request("currentPedalBoard"); + return this.getWebSocket().request("currentPedalboard"); }) .then(data => { - this.pedalBoard.set(new PedalBoard().deserialize(data)); + this.pedalboard.set(new Pedalboard().deserialize(data)); return this.getWebSocket().request("pluginClasses"); }) @@ -1017,10 +1034,10 @@ class PiPedalModelImpl implements PiPedalModel { return this.getWebSocket().request("getSystemMidiBindings"); }) - .then((data)=> { + .then((data) => { let bindings = MidiBinding.deserialize_array(data); this.systemMidiBindings.set(bindings); - + if (this.webSocket) { // MUST not allow reconnect until at least one complete load has finished. this.webSocket.canReconnect = true; @@ -1036,7 +1053,7 @@ class PiPedalModelImpl implements PiPedalModel { ; } - requestCurrentPedalBoard(): Promise { + requestCurrentPedalboard(): Promise { const myRequest = new Request(this.varRequest('current_pedalboard.json')); return fetch(myRequest) .then( @@ -1045,9 +1062,9 @@ class PiPedalModelImpl implements PiPedalModel { } ) .then(data => { - let pedalBoard = new PedalBoard().deserialize(data); - pedalBoard.ensurePedalboardIds(); - this.pedalBoard.set(pedalBoard); + let pedalboard = new Pedalboard().deserialize(data); + pedalboard.ensurePedalboardIds(); + this.pedalboard.set(pedalboard); }); } @@ -1174,11 +1191,11 @@ class PiPedalModelImpl implements PiPedalModel { handleOnLoadPluginPreset(instanceId: number, controlValues: ControlValue[]) { - let pedalBoard = this.pedalBoard.get(); - if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); - let newPedalBoard = pedalBoard.clone(); + let pedalboard = this.pedalboard.get(); + if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready."); + let newPedalboard = pedalboard.clone(); - let item = newPedalBoard.getItem(instanceId); + let item = newPedalboard.getItem(instanceId); let changed = false; for (let i = 0; i < controlValues.length; ++i) { @@ -1186,7 +1203,7 @@ class PiPedalModelImpl implements PiPedalModel { changed = item.setControlValue(controlValue.key, controlValue.value) || changed; } if (changed) { - this.pedalBoard.set(newPedalBoard); + this.pedalboard.set(newPedalboard); } } @@ -1206,23 +1223,40 @@ class PiPedalModelImpl implements PiPedalModel { } } } - private _propertyValueChangeItems: PropertyValueChangeItem[] = []; - addPropertyValueChangeListener(instanceId: number, onValueChanged: PropertyValueChangedHandler): PropertyValueChangedHandle { + private stateChangedListeners: StateChangedEntry[] = []; + + addLv2StateChangedListener(instanceId: number, onStateChanged: StateChangedHandler): StateChangedHandle { let handle = ++this.nextListenHandle; - this._propertyValueChangeItems.push({ handle: handle, instanceId: instanceId, onValueChanged: onValueChanged }); - return { _PropertyValueChangedHandle: handle }; + + let item: StateChangedEntry = { + handle: handle, + instanceId: instanceId, + onStateChanged: onStateChanged + }; + this.stateChangedListeners.push(item); + return { _handle: 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; + removeLv2StateChangedListener(handle: StateChangedHandle): void { + let h = handle._handle; + + for (let i = 0; i < this.stateChangedListeners.length; ++i) { + if (this.stateChangedListeners[i].handle === h) { + this.stateChangedListeners.splice(i, 1); } } } - _pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = []; + private onLv2StateChanged(instanceId: number): void { + for (let item of this.stateChangedListeners) { + if (item.instanceId === instanceId) { + item.onStateChanged(instanceId); + } + } + } + + + private _pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = []; addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle { let handle = ++this.nextListenHandle; @@ -1252,36 +1286,24 @@ 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); - } - } - } - + private _setPedalboardPropertyValue(instanceId: number, propertyUri: string, value: any): void { + let body: PatchPropertyChangedBody = { + clientId: this.clientId, + instanceId: instanceId, + propertyUri: propertyUri, + value: value + }; + this.webSocket?.send("setPatchProperty", body); } - _setPedalBoardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void { - let pedalBoard = this.pedalBoard.get(); - if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); - let newPedalBoard = pedalBoard.clone(); + private _setPedalboardControlValue(instanceId: number, key: string, value: number, 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 item = newPedalboard.getItem(instanceId); let changed = item.setControlValue(key, value); if (changed) { - this.pedalBoard.set(newPedalBoard); + this.pedalboard.set(newPedalboard); if (notifyServer) { this._setServerControl("setControl", instanceId, key, value); } @@ -1294,17 +1316,17 @@ class PiPedalModelImpl implements PiPedalModel { } } - _setVst3PedalBoardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void { - let pedalBoard = this.pedalBoard.get(); - if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready."); - let newPedalBoard = pedalBoard.clone(); + private _setVst3PedalboardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void { + let pedalboard = this.pedalboard.get(); + if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready."); + let newPedalboard = pedalboard.clone(); - let item: PedalBoardItem = newPedalBoard.getItem(instanceId); + let item: PedalboardItem = newPedalboard.getItem(instanceId); let changed = item.setControlValue(key, value); item.vstState = state; if (changed) { - this.pedalBoard.set(newPedalBoard); + this.pedalboard.set(newPedalboard); if (notifyServer) { this._setServerControl("setControl", instanceId, key, value); } @@ -1315,48 +1337,47 @@ 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); - } - 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(); - let item = newPedalBoard.getItem(instanceId); + + + setPedalboardControl(instanceId: number, key: string, value: number): void { + this._setPedalboardControlValue(instanceId, key, value, true); + } + setPedalboardItemEnabled(instanceId: number, value: boolean): void { + this._setPedalboardItemEnabled(instanceId, value, true); + } + 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(); + + let item = newPedalboard.getItem(instanceId); let changed = value !== item.isEnabled; if (changed) { item.isEnabled = value; - this.pedalBoard.set(newPedalBoard); + this.pedalboard.set(newPedalboard); if (notifyServer) { - let body: PedalBoardItemEnableBody = { + let body: PedalboardItemEnableBody = { clientId: this.clientId, instanceId: instanceId, enabled: value } - this.webSocket?.send("setPedalBoardItemEnable", body); + this.webSocket?.send("setPedalboardItemEnable", body); } } } - updateServerPedalBoard(): void { - let body: PedalBoardChangedBody = { + updateServerPedalboard(): void { + let body: PedalboardChangedBody = { clientId: this.clientId, - pedalBoard: this.pedalBoard.get() + pedalboard: this.pedalboard.get() }; - this.webSocket?.send("updateCurrentPedalBoard", body); + this.webSocket?.send("updateCurrentPedalboard", body); } @@ -1364,36 +1385,36 @@ class PiPedalModelImpl implements PiPedalModel { this.webSocket?.send("setShowStatusMonitor", show); } - loadPedalBoardPlugin(itemId: number, selectedUri: string): number { - let pedalBoard = this.pedalBoard.get(); - if (pedalBoard === undefined) throw new PiPedalArgumentError("Can't clone an undefined object."); - let newPedalBoard = pedalBoard.clone(); + loadPedalboardPlugin(itemId: number, selectedUri: string): number { + let pedalboard = this.pedalboard.get(); + if (pedalboard === undefined) throw new PiPedalArgumentError("Can't clone an undefined object."); + let newPedalboard = pedalboard.clone(); let plugin = this.getUiPlugin(selectedUri); if (plugin === null) { throw new PiPedalArgumentError("Plugin not found."); } - let it = newPedalBoard.itemsGenerator(); + let it = newPedalboard.itemsGenerator(); while (true) { let v = it.next(); if (v.done) break; let item = v.value; if (item.instanceId === itemId) { - item.instanceId = ++newPedalBoard.nextInstanceId; + item.instanceId = ++newPedalboard.nextInstanceId; item.uri = selectedUri; item.pluginName = plugin.name; item.controlValues = this.getDefaultValues(item.uri); item.isEnabled = true; - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard() + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard() return item.instanceId; } } throw new PiPedalArgumentError("Pedalboard item not found."); } - _setServerControl(message: string, instanceId: number, key: string, value: number) { + private _setServerControl(message: string, instanceId: number, key: string, value: number) { let body: ControlChangedBody = { clientId: this.clientId, instanceId: instanceId, @@ -1402,178 +1423,179 @@ class PiPedalModelImpl implements PiPedalModel { }; this.webSocket?.send(message, body); } - previewPedalBoardValue(instanceId: number, key: string, value: number): void { + previewPedalboardValue(instanceId: number, key: string, value: number): void { // mouse is down. Don't update EVERYBODY, but we must change // the control on the running audio plugin. + // TODO: respect "expensive" port attribute. this._setServerControl("previewControl", instanceId, key, value); } // returns the next selected instanceId, or null,if no item was deleted. - deletePedalBoardPedal(instanceId: number): number | null { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + deletePedalboardPedal(instanceId: number): number | null { + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let result = newPedalBoard.deleteItem(instanceId); + let result = newPedalboard.deleteItem(instanceId); if (result != null) { - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } return result; } - movePedalBoardItemBefore(fromInstanceId: number, toInstanceId: number): void { + movePedalboardItemBefore(fromInstanceId: number, toInstanceId: number): void { if (fromInstanceId === toInstanceId) return; - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); - this.updateVst3State(newPedalBoard); + this.updateVst3State(newPedalboard); - let fromItem = newPedalBoard.getItem(fromInstanceId); + let fromItem = newPedalboard.getItem(fromInstanceId); if (fromItem === null) { throw new PiPedalArgumentError("fromInstanceId not found."); } - newPedalBoard.deleteItem(fromInstanceId); + newPedalboard.deleteItem(fromInstanceId); - newPedalBoard.addBefore(fromItem, toInstanceId); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + newPedalboard.addBefore(fromItem, toInstanceId); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } - movePedalBoardItemAfter(fromInstanceId: number, toInstanceId: number): void { + movePedalboardItemAfter(fromInstanceId: number, toInstanceId: number): void { if (fromInstanceId === toInstanceId) return; - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); - this.updateVst3State(newPedalBoard); + this.updateVst3State(newPedalboard); - let fromItem = newPedalBoard.getItem(fromInstanceId); + let fromItem = newPedalboard.getItem(fromInstanceId); if (fromItem === null) { throw new PiPedalArgumentError("fromInstanceId not found."); } - newPedalBoard.deleteItem(fromInstanceId); + newPedalboard.deleteItem(fromInstanceId); - newPedalBoard.addAfter(fromItem, toInstanceId); + newPedalboard.addAfter(fromItem, toInstanceId); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } - movePedalBoardItemToStart(instanceId: number): void { + movePedalboardItemToStart(instanceId: number): void { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); - this.updateVst3State(newPedalBoard); + this.updateVst3State(newPedalboard); - let fromItem = newPedalBoard.getItem(instanceId); + let fromItem = newPedalboard.getItem(instanceId); if (fromItem === null) { throw new PiPedalArgumentError("fromInstanceId not found."); } - newPedalBoard.deleteItem(instanceId); + newPedalboard.deleteItem(instanceId); - newPedalBoard.addToStart(fromItem); + newPedalboard.addToStart(fromItem); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } - movePedalBoardItemToEnd(instanceId: number): void { + movePedalboardItemToEnd(instanceId: number): void { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let fromItem = newPedalBoard.getItem(instanceId); + let fromItem = newPedalboard.getItem(instanceId); if (fromItem === null) { throw new PiPedalArgumentError("fromInstanceId not found."); } - newPedalBoard.deleteItem(instanceId); + newPedalboard.deleteItem(instanceId); - newPedalBoard.addToEnd(fromItem); + newPedalboard.addToEnd(fromItem); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } - movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void { + movePedalboardItem(fromInstanceId: number, toInstanceId: number): void { if (fromInstanceId === toInstanceId) return; - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let fromItem = newPedalBoard.getItem(fromInstanceId); - let toItem = newPedalBoard.getItem(toInstanceId); + let fromItem = newPedalboard.getItem(fromInstanceId); + let toItem = newPedalboard.getItem(toInstanceId); if (fromItem === null) { throw new PiPedalArgumentError("fromInstanceId not found."); } if (toItem === null) { throw new PiPedalArgumentError("toInstanceId not found."); } - let emptyItem = newPedalBoard.createEmptyItem(); - newPedalBoard.replaceItem(fromInstanceId, emptyItem); - newPedalBoard.replaceItem(toInstanceId, fromItem); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + let emptyItem = newPedalboard.createEmptyItem(); + newPedalboard.replaceItem(fromInstanceId, emptyItem); + newPedalboard.replaceItem(toInstanceId, fromItem); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); } - addPedalBoardItem(instanceId: number, append: boolean): number { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + addPedalboardItem(instanceId: number, append: boolean): number { + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let item = newPedalBoard.getItem(instanceId); + let item = newPedalboard.getItem(instanceId); if (item === null) { throw new PiPedalArgumentError("instanceId not found."); } - let newItem = newPedalBoard.createEmptyItem(); - newPedalBoard.addItem(newItem, instanceId, append); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + let newItem = newPedalboard.createEmptyItem(); + newPedalboard.addItem(newItem, instanceId, append); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); return newItem.instanceId; } - addPedalBoardSplitItem(instanceId: number, append: boolean): number { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + addPedalboardSplitItem(instanceId: number, append: boolean): number { + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let item = newPedalBoard.getItem(instanceId); + let item = newPedalboard.getItem(instanceId); if (item === null) { throw new PiPedalArgumentError("instanceId not found."); } - let newItem = newPedalBoard.createEmptySplit(); - newPedalBoard.addItem(newItem, instanceId, append); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + let newItem = newPedalboard.createEmptySplit(); + newPedalboard.addItem(newItem, instanceId, append); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); return newItem.instanceId; } - setPedalBoardItemEmpty(instanceId: number): number { - let pedalBoard = this.pedalBoard.get(); - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); + setPedalboardItemEmpty(instanceId: number): number { + let pedalboard = this.pedalboard.get(); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); - let item = newPedalBoard.getItem(instanceId); + let item = newPedalboard.getItem(instanceId); if (item === null) { throw new PiPedalArgumentError("instanceId not found."); } - newPedalBoard.setItemEmpty(item); + newPedalboard.setItemEmpty(item); - this.pedalBoard.set(newPedalBoard); - this.updateServerPedalBoard(); + this.pedalboard.set(newPedalboard); + this.updateServerPedalboard(); return item.instanceId; } @@ -1583,6 +1605,18 @@ class PiPedalModelImpl implements PiPedalModel { this.webSocket?.send("saveCurrentPreset", this.clientId); } + isOnboarding(): boolean { + var settings = this.jackServerSettings.get(); + return settings.isOnboarding; + } + + setOnboarding(value: boolean): void { + nullCast(this.webSocket) + .request("setOnboarding", value); + } + + + saveCurrentPresetAs(newName: string, saveAfterInstanceId = -1): Promise { // default behaviour is to save after the currently selected preset. if (saveAfterInstanceId === -1) { @@ -1603,10 +1637,9 @@ class PiPedalModelImpl implements PiPedalModel { }); } - requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise - { + requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise { return nullCast(this.webSocket) - .request('requestFileList',piPedalFileProperty); + .request('requestFileList', piPedalFileProperty); } saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise { @@ -1785,16 +1818,36 @@ class PiPedalModelImpl implements PiPedalModel { } - getLv2Parameter(instanceId: number, uri: string): Promise { + setPatchProperty(instanceId: number, uri: string, value: any): Promise { + let result = new Promise((resolve, reject) => { + if (this.webSocket) { + this.webSocket.request( + "setPatchProperty", + { instanceId: instanceId, propertyUri: uri, value: value } + ).then((result) => { + resolve(true); + }).catch((message) => { + reject(message); + }); + } + else { + reject("Socket closed."); + } + }); + return result; + } + getPatchProperty(instanceId: number, uri: string): Promise { let result = new Promise((resolve, reject) => { if (!this.webSocket) { reject("Socket closed."); } else { - this.webSocket.request("getLv2Parameter", { instanceId: instanceId, uri: uri }) + this.webSocket.request("getPatchProperty", { instanceId: instanceId, propertyUri: uri }) .then((data) => { resolve(data); }) - .catch(error => reject(error)); + .catch((error) => { + reject(error + " (" + uri + ")"); + }); } }); return result; @@ -1926,8 +1979,8 @@ class PiPedalModelImpl implements PiPedalModel { }); } - updateVst3State(pedalBoard: PedalBoard) { - // let it = pedalBoard.itemsGenerator(); + updateVst3State(pedalboard: Pedalboard) { + // let it = pedalboard.itemsGenerator(); // while (true) { // let v = it.next(); // if (v.done) break; @@ -1936,17 +1989,19 @@ class PiPedalModelImpl implements PiPedalModel { } setMidiBinding(instanceId: number, midiBinding: MidiBinding): void { - let pedalBoard = this.pedalBoard.get(); - if (!pedalBoard) { + let pedalboard = this.pedalboard.get(); + if (!pedalboard) { throw new PiPedalStateError("Pedalboard not loaded."); } - let newPedalBoard = pedalBoard.clone(); - this.updateVst3State(newPedalBoard); - if (newPedalBoard.setMidiBinding(instanceId, midiBinding)) { - this.pedalBoard.set(newPedalBoard); + let newPedalboard = pedalboard.clone(); + this.updateVst3State(newPedalboard); + if (newPedalboard.setMidiBinding(instanceId, midiBinding)) { + this.pedalboard.set(newPedalboard); + + // notify the server. // TODO: a more efficient notification. - this.updateServerPedalBoard() + this.updateServerPedalboard() } } @@ -1955,20 +2010,18 @@ class PiPedalModelImpl implements PiPedalModel { let result: MidiBinding[] = []; - for (var binding of currentBindings) - { - if (binding.symbol === midiBinding.symbol) - { + for (var binding of currentBindings) { + if (binding.symbol === midiBinding.symbol) { result.push(midiBinding); } else { result.push(binding); } } this.systemMidiBindings.set(result); - this.webSocket?.send("setSystemMidiBindings",result); + this.webSocket?.send("setSystemMidiBindings", result); } - midiListeners: MidiEventListener[] = []; - atomOutputListeners: AtomOutputListener[] = []; + private midiListeners: MidiEventListener[] = []; + private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = []; nextListenHandle = 1; listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle { @@ -1983,23 +2036,36 @@ class PiPedalModelImpl implements PiPedalModel { } - listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle { + monitorPatchProperty( + instanceId: number, + propertyUri: string, + onReceived: PatchPropertyListener + ): ListenHandle { let handle = this.nextListenHandle++; - this.atomOutputListeners.push(new AtomOutputListener(handle, instanceId, onComplete)); + this.monitorPatchPropertyListeners.push(new PatchPropertyListenerItem(handle, instanceId, onReceived)); - this.webSocket?.send("listenForAtomOutput", { instanceId: instanceId, handle: handle }); + this.webSocket?.send("monitorPatchProperty", { clientHandle: handle, instanceId: instanceId, propertyUri: propertyUri, }); return { _handle: handle }; } + cancelMonitorPatchProperty(listenHandle: ListenHandle): void { + for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) { + if (this.monitorPatchPropertyListeners[i].handle === listenHandle._handle) { + this.monitorPatchPropertyListeners.splice(i, 1); + break; + } + } + this.webSocket?.send("cancelMonitorPatchProperty", listenHandle._handle); + } - handleNotifyAtomOutput(clientHandle: number, instanceId: number, atomJson: string) { - let jsonObject: any = JSON.parse(atomJson); - for (let i = 0; i < this.atomOutputListeners.length; ++i) { - let listener = this.atomOutputListeners[i]; + + private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) { + for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) { + let listener = this.monitorPatchPropertyListeners[i]; if (listener.handle === clientHandle && listener.instanceId === instanceId) { - listener.callback(instanceId, jsonObject); + listener.callback(instanceId, propertyUri, jsonObject); } } } @@ -2028,15 +2094,6 @@ class PiPedalModelImpl implements PiPedalModel { } this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle); } - cancelListenForAtomOutput(listenHandle: ListenHandle): void { - for (let i = 0; i < this.midiListeners.length; ++i) { - if (this.midiListeners[i].handle === listenHandle._handle) { - this.midiListeners.splice(i, 1); - break; - } - } - this.webSocket?.send("cancelListenForAtomOutput", listenHandle._handle); - } download(targetType: string, instanceId: number | string): void { if (instanceId === -1) return; @@ -2288,7 +2345,7 @@ class PiPedalModelImpl implements PiPedalModel { zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void { let name = uiControl.name; if (uiControl.port_group !== "") { - let pedalboard = this.pedalBoard.get(); + let pedalboard = this.pedalboard.get(); if (pedalboard) { let plugin = pedalboard.getItem(instanceId); let uiPlugin = this.getUiPlugin(plugin.uri); @@ -2310,7 +2367,7 @@ class PiPedalModelImpl implements PiPedalModel { let currentSymbol = currentUiControl.uiControl.symbol; - let pedalboard = this.pedalBoard.get(); + let pedalboard = this.pedalboard.get(); if (!pedalboard) return; let pedalboardItem = pedalboard.getItem(currentUiControl.instanceId); @@ -2339,7 +2396,7 @@ class PiPedalModelImpl implements PiPedalModel { let currentSymbol = currentUiControl.uiControl.symbol; - let pedalboard = this.pedalBoard.get(); + let pedalboard = this.pedalboard.get(); if (!pedalboard) return; let pedalboardItem = pedalboard.getItem(currentUiControl.instanceId); @@ -2414,12 +2471,6 @@ class PiPedalModelImpl implements PiPedalModel { } } - hasConfiguration(): boolean { - var jackConfig = this.jackConfiguration.get(); - return jackConfig.isValid; - } - - }; @@ -2430,7 +2481,7 @@ let instance: PiPedalModel | undefined = undefined; export class PiPedalModelFactory { static getInstance(): PiPedalModel { if (instance === undefined) { - let impl: PiPedalModelImpl = new PiPedalModelImpl(); + let impl: PiPedalModel = new PiPedalModel(); instance = impl; impl.initialize(); diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index a75b2f7..8010040 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -23,10 +23,10 @@ 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, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin'; +import { UiPlugin, UiControl, PiPedalFileProperty} from './Lv2Plugin'; import { - PedalBoard, PedalBoardItem, ControlValue,PropertyValue -} from './PedalBoard'; + Pedalboard, PedalboardItem, ControlValue +} from './Pedalboard'; import PluginControl from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import VuMeter from './VuMeter'; @@ -36,6 +36,7 @@ import Typography from '@mui/material/Typography'; import FullScreenIME from './FullScreenIME'; import FilePropertyControl from './FilePropertyControl'; import FilePropertyDialog from './FilePropertyDialog'; +import JsonAtom from './JsonAtom'; export const StandardItemSize = { width: 80, height: 110 }; @@ -188,7 +189,7 @@ export interface ControlViewCustomization { export interface PluginControlViewProps extends WithStyles { theme: Theme; instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; customization?: ControlViewCustomization; customizationId?: number; } @@ -224,35 +225,32 @@ const PluginControlView = dialogFileValue: "" } - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this); } onPreviewChange(key: string, value: number): void { - this.model.previewPedalBoardValue(this.props.instanceId, key, value); + this.model.previewPedalboardValue(this.props.instanceId, key, value); } 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); + this.model.setPedalboardControl(this.props.instanceId, key, value); } - onPedalBoardChanged(value?: PedalBoard) { - //let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId); - //this.setState({ pedalBoardItem: item }); + onPedalboardChanged(value?: Pedalboard) { + //let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId); + //this.setState({ pedalboardItem: item }); } componentDidMount() { super.componentDidMount(); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); } componentWillUnmount() { - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); super.componentWillUnmount(); } @@ -285,22 +283,10 @@ 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; - } + makeFilePropertyUI(fileProperty: PiPedalFileProperty): ReactNode { return (( - { this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile}); @@ -333,7 +319,7 @@ const PluginControlView = } - getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[],propertyValues: PropertyValue[]): ControlNodes { + getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes { let result: ControlNodes = []; for (let i = 0; i < plugin.controls.length; ++i) { @@ -350,7 +336,7 @@ const PluginControlView = pluginControl = plugin.controls[i]; if (!pluginControl.not_on_gui) { groupControls.push( - this.makeStandardControl(pluginControl, controlValues) + this.makeStandardControl(pluginControl,controlValues) ) } } @@ -359,7 +345,7 @@ const PluginControlView = ) } else { result.push( - this.makeStandardControl(pluginControl, controlValues) + this.makeStandardControl(pluginControl,controlValues) ); } } @@ -367,7 +353,7 @@ const PluginControlView = for (let i = 0; i < plugin.fileProperties.length; ++i) { let fileProperty = plugin.fileProperties[i]; result.push( - this.makeFilePropertyUI(fileProperty, propertyValues) + this.makeFilePropertyUI(fileProperty) ); } return result; @@ -383,7 +369,7 @@ const PluginControlView = } onImeValueChange(key: string, value: number) { - this.model.setPedalBoardControlValue(this.props.instanceId, key, value); + this.model.setPedalboardControl(this.props.instanceId, key, value); this.onImeClose(); } onImeClose() { @@ -427,7 +413,7 @@ const PluginControlView = result.push((
- {controlGroup.name} + {controlGroup.name}
{ @@ -452,16 +438,15 @@ const PluginControlView = render(): ReactNode { let classes = this.props.classes; - let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); + let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); - if (!pedalBoardItem) + if (!pedalboardItem) return (
); - let controlValues = pedalBoardItem.controlValues; - let propertyValues = pedalBoardItem.propertyValues; + let controlValues = pedalboardItem.controlValues; - let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri)); + let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri)); controlValues = this.filterNotOnGui(controlValues, plugin); @@ -472,7 +457,7 @@ const PluginControlView = let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let controlNodes: ControlNodes; - controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues); + controlNodes = this.getStandardControlNodes(plugin, controlValues); if (this.props.customization) { // allow wrapper class to insert/remove/rebuild controls. @@ -484,10 +469,10 @@ const PluginControlView = return (
- +
- +
{ @@ -503,9 +488,21 @@ const PluginControlView = { this.setState({ showFileDialog: false});}} + onCancel={()=> { this.setState({ showFileDialog: false});}} onOk={(fileProperty,selectedFile)=> { - this.model.setPedalBoardPropertyValue(this.props.instanceId,fileProperty.patchProperty,selectedFile) + + this.model.setPatchProperty( + this.props.instanceId, + fileProperty.patchProperty, + JsonAtom.Path(selectedFile) + ) + .then( () => { + + }) + .catch((error) => + { + this.model.showAlert("setPatchProperty failed: " +error); + }); this.setState({ showFileDialog: false});} } /> diff --git a/react/src/PluginPresetsDialog.tsx b/react/src/PluginPresetsDialog.tsx index 8135dc6..1ccd4d1 100644 --- a/react/src/PluginPresetsDialog.tsx +++ b/react/src/PluginPresetsDialog.tsx @@ -259,7 +259,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
- + noWrap {presetEntry.label}
@@ -312,8 +312,8 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })( this.setState({ renameOpen: false }); } getPluginUri() : string { - let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); - return pedalBoardItem.uri; + let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); + return pedalboardItem.uri; } handleCopy() { let item = this.props.presets.getItem(this.state.selectedItem); @@ -359,7 +359,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })( > - + {title} this.showActionBar(true)} > diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index e23dfd7..024e910 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -497,6 +497,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( } + onOnboardingContinue() { + this.model.setOnboarding(false); + this.props.onClose(); + } render() { let classes = this.props.classes; let isConfigValid = this.state.jackConfiguration.isValid; @@ -544,40 +548,41 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( }} >
- {this.props.onboarding ? + {this.props.onboarding && (
- + Select and configure an audio device. You may optionally configure MIDI inputs, and set up a Wi-Fi Direct Hotspot now as well. - + Access and modify these settings later by selecting the Settings menu item on the main menu. -
- ): - ( -
- - - STATUS - - {(!isConfigValid) ? - ( -
- Status: Not configured. - Governor: -
- ) : - ( -
- {JackHostStatus.getDisplayView("", this.state.jackStatus)} - {JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)} -
- ) - } +
)} +
+ + STATUS + + {(!isConfigValid) ? + ( +
+ Status: Not configured. + {(!this.props.onboarding) && ( + Governor: + )} +
+ ) : + ( +
+ {JackHostStatus.getDisplayView("", this.state.jackStatus)} + { (!this.props.onboarding) && JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)} +
+ ) + } +
+ {this.state.jackConfiguration.errorState !== "" && (
@@ -782,7 +787,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
- +
) diff --git a/react/src/SplitControlView.tsx b/react/src/SplitControlView.tsx index 7a0fc45..b8fbf01 100644 --- a/react/src/SplitControlView.tsx +++ b/react/src/SplitControlView.tsx @@ -25,8 +25,8 @@ import withStyles from '@mui/styles/withStyles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { UiPlugin } from './Lv2Plugin'; import { - PedalBoard, PedalBoardSplitItem, ControlValue, -} from './PedalBoard'; + Pedalboard, PedalboardSplitItem, ControlValue, +} from './Pedalboard'; import PluginControl from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import VuMeter from './VuMeter'; @@ -97,7 +97,7 @@ const styles = (theme: Theme) => createStyles({ interface SplitControlViewProps extends WithStyles { theme: Theme; instanceId: number; - item: PedalBoardSplitItem; + item: PedalboardSplitItem; } type SplitControlViewState = { landscapeGrid: boolean; @@ -117,32 +117,32 @@ const SplitControlView = landscapeGrid: false } - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onValueChanged = this.onValueChanged.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this); } onPreviewChange(key: string, value: number): void { - this.model.previewPedalBoardValue(this.props.instanceId, key, value); + this.model.previewPedalboardValue(this.props.instanceId, key, value); } onValueChanged(key: string, value: number): void { - this.model.setPedalBoardControlValue(this.props.instanceId, key, value); + this.model.setPedalboardControl(this.props.instanceId, key, value); } - onPedalBoardChanged(value?: PedalBoard) { - //let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId); - //this.setState({ pedalBoardItem: item }); + onPedalboardChanged(value?: Pedalboard) { + //let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId); + //this.setState({ pedalboardItem: item }); } componentDidMount() { super.componentDidMount(); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); } componentWillUnmount() { - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); super.componentWillUnmount(); } @@ -162,29 +162,29 @@ const SplitControlView = render(): ReactNode { let classes = this.props.classes; - let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); + let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); - if (!pedalBoardItem) + if (!pedalboardItem) return (
); let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid; let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; - let typeValue = pedalBoardItem.getControl(PedalBoardSplitItem.TYPE_KEY); - let mixValue = pedalBoardItem.getControl(PedalBoardSplitItem.MIX_KEY); - let selectValue = pedalBoardItem.getControl(PedalBoardSplitItem.SELECT_KEY); - let panLValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANL_KEY); - let volumeLValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLL_KEY); - let panRValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANR_KEY); - let volumeRValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLR_KEY); + let typeValue = pedalboardItem.getControl(PedalboardSplitItem.TYPE_KEY); + let mixValue = pedalboardItem.getControl(PedalboardSplitItem.MIX_KEY); + let selectValue = pedalboardItem.getControl(PedalboardSplitItem.SELECT_KEY); + let panLValue = pedalboardItem.getControl(PedalboardSplitItem.PANL_KEY); + let volumeLValue = pedalboardItem.getControl(PedalboardSplitItem.VOLL_KEY); + let panRValue = pedalboardItem.getControl(PedalboardSplitItem.PANR_KEY); + let volumeRValue = pedalboardItem.getControl(PedalboardSplitItem.VOLR_KEY); return (
- +
- +
diff --git a/react/src/SplitUiControls.tsx b/react/src/SplitUiControls.tsx index 508e7e1..0dc28e0 100644 --- a/react/src/SplitUiControls.tsx +++ b/react/src/SplitUiControls.tsx @@ -18,13 +18,13 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import {UiControl,ScalePoint} from './Lv2Plugin'; -import {PedalBoardSplitItem} from './PedalBoard'; +import {PedalboardSplitItem} from './Pedalboard'; import Units from './Units' export const SplitTypeControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.TYPE_KEY, + symbol: PedalboardSplitItem.TYPE_KEY, name: "Type", index: 0, min_value: 0, @@ -47,7 +47,7 @@ export const SplitTypeControl: UiControl = new UiControl().deserialize({ }); export const SplitAbControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.SELECT_KEY, + symbol: PedalboardSplitItem.SELECT_KEY, name: "Select", index: 1, min_value: 0, @@ -70,7 +70,7 @@ export const SplitAbControl: UiControl = new UiControl().deserialize({ export const SplitMixControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.MIX_KEY, + symbol: PedalboardSplitItem.MIX_KEY, name: "Mix", index: 2, min_value: -1, @@ -87,7 +87,7 @@ export const SplitMixControl: UiControl = new UiControl().deserialize({ }); export const SplitPanLeftControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.PANL_KEY, + symbol: PedalboardSplitItem.PANL_KEY, name: "Pan Top", index: 3, min_value: -1, @@ -104,7 +104,7 @@ export const SplitPanLeftControl: UiControl = new UiControl().deserialize({ }); export const SplitVolLeftControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.VOLL_KEY, + symbol: PedalboardSplitItem.VOLL_KEY, name: "Vol Top", index: 4, min_value: -60, @@ -128,7 +128,7 @@ export const SplitVolLeftControl: UiControl = new UiControl().deserialize({ export const SplitPanRightControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.PANR_KEY, + symbol: PedalboardSplitItem.PANR_KEY, name: "Pan Bottom", index: 5, min_value: -1, @@ -145,7 +145,7 @@ export const SplitPanRightControl: UiControl = new UiControl().deserialize({ }); export const SplitVolRightControl: UiControl = new UiControl().deserialize({ - symbol: PedalBoardSplitItem.VOLR_KEY, + symbol: PedalboardSplitItem.VOLR_KEY, name: "Vol Bottom", index: 6, min_value: -60, diff --git a/react/src/SvgPathBuilder.tsx b/react/src/SvgPathBuilder.tsx index 623e49b..d568890 100644 --- a/react/src/SvgPathBuilder.tsx +++ b/react/src/SvgPathBuilder.tsx @@ -20,7 +20,7 @@ import StringBuilder from './StringBuilder'; class SvgPathBuilder { - _sb: StringBuilder = new StringBuilder(); + private _sb: StringBuilder = new StringBuilder(); moveTo(x: number, y: number): SvgPathBuilder { @@ -37,7 +37,7 @@ class SvgPathBuilder { this._sb.append('C').append(x1).append(',').append(y1) .append(' ').append(x2).append(',').append(y2) .append(' ').append(x).append(',').append(y).append(' '); - + return this; } closePath(): SvgPathBuilder { this._sb.append("Z "); diff --git a/react/src/ToobCabSimView.tsx b/react/src/ToobCabSimView.tsx index d2be3d8..02dda9e 100644 --- a/react/src/ToobCabSimView.tsx +++ b/react/src/ToobCabSimView.tsx @@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView'; @@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({ interface ToobCabSimProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobCabSimState { @@ -83,8 +83,8 @@ const ToobCabSimView = class ToobCabSimViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-cab-sim"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobFrequencyResponseView.tsx b/react/src/ToobFrequencyResponseView.tsx index 619b6a3..b007efa 100644 --- a/react/src/ToobFrequencyResponseView.tsx +++ b/react/src/ToobFrequencyResponseView.tsx @@ -30,14 +30,15 @@ import Utility from './Utility'; import SvgPathBuilder from './SvgPathBuilder'; + const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector"; -const PLOT_WIDTH = StandardItemSize.width*2-16; -const PLOT_HEIGHT = StandardItemSize.height-12; +const PLOT_WIDTH = StandardItemSize.width * 2 - 16; +const PLOT_HEIGHT = StandardItemSize.height - 12; const styles = (theme: Theme) => createStyles({ frame: { - width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444", + width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444", borderRadius: 6, marginTop: 12, marginLeft: 8, marginRight: 8, boxShadow: "1px 4px 8px #000 inset" @@ -73,33 +74,31 @@ const ToobFrequencyResponseView = }; this.pathRef = React.createRef(); - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this); } - onStateChanged() - { - if (this.model.state.get() === State.Ready) - { + onStateChanged() { + if (this.model.state.get() === State.Ready) { this.requestDeferred = false; this.requestOutstanding = false; this.updateAllWaveShapes(); // after a reconnect. } } - onPedalBoardChanged() - { + onPedalboardChanged() { this.updateAllWaveShapes(); } - componentDidMount() - { + private mounted: boolean = false; + componentDidMount() { + this.mounted = true; this.model.state.addOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); this.updateAllWaveShapes(); } - componentWillUnmount() - { + componentWillUnmount() { + this.mounted = false; this.model.state.removeOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); } componentDidUpdate() { @@ -113,24 +112,23 @@ const ToobFrequencyResponseView = dbTickSpacingOpt: number = 10; - MIN_DB_AF: number = Math.pow(10,-192/20); + MIN_DB_AF: number = Math.pow(10, -192 / 20); fMin: number = 30; fMax: number = 20000; logMin: number = Math.log(this.fMin); logMax: number = Math.log(this.fMax); - + // Size of the SVG element. xMin: number = 0; - xMax: number = PLOT_WIDTH+4; + xMax: number = PLOT_WIDTH + 4; yMin: number = 0; yMax: number = PLOT_HEIGHT; dbTickSpacing: number = this.dbTickSpacingOpt; - - toX(frequency: number): number - { + + toX(frequency: number): number { var logV = Math.log(frequency); - return (this.xMax-this.xMin) * (logV-this.logMin)/(this.logMax-this.logMin) + this.xMin; - + return (this.xMax - this.xMin) * (logV - this.logMin) / (this.logMax - this.logMin) + this.xMin; + } toY(value: number): number { value = Math.abs(value); @@ -139,24 +137,23 @@ const ToobFrequencyResponseView = if (value < this.MIN_DB_AF) { db = -192.0; } else { - db = 20*Math.log10(value); + db = 20 * Math.log10(value); } - var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin) + this.yMin; + var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin) + this.yMin; return y; } onFrequencyResponseUpdated(data: number[]) { + if (!this.mounted) return; let pathBuilder = new SvgPathBuilder(); - if (data.length > 2) - { - pathBuilder.moveTo(this.toX(data[0]),this.toY(data[1])) - for (let i = 2; i < data.length; i += 2) - { - pathBuilder.lineTo(this.toX(data[i]),this.toY(data[i+1])); + if (data.length > 2) { + pathBuilder.moveTo(this.toX(data[0]), this.toY(data[1])) + for (let i = 2; i < data.length; i += 2) { + pathBuilder.lineTo(this.toX(data[i]), this.toY(data[i + 1])); } } this.currentPath = pathBuilder.toString(); - this.setState({path: this.currentPath}); + this.setState({ path: this.currentPath }); } @@ -166,21 +163,23 @@ const ToobFrequencyResponseView = return; } this.requestOutstanding = true; - this.model.getLv2Parameter(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI) - .then((data) => { - this.onFrequencyResponseUpdated(data); - if (this.requestDeferred) { - Utility.delay(10) // take breath - .then( - () => { - this.requestOutstanding = false; - this.requestDeferred = false; - this.updateAllWaveShapes(); - } + this.model.getPatchProperty(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI) + .then((json) => { + if (json && json.otype_ === "Vector" && json.vtype_ === "Float") { + this.onFrequencyResponseUpdated(json.value as number[]); + if (this.requestDeferred) { + Utility.delay(10) // take breath + .then( + () => { + this.requestOutstanding = false; + this.requestDeferred = false; + this.updateAllWaveShapes(); + } - ); - } else { - this.requestOutstanding = false; + ); + } else { + this.requestOutstanding = false; + } } }).catch(error => { // assume the connection was lost. We'll get saved by a reconnect. @@ -201,42 +200,36 @@ const ToobFrequencyResponseView = grid(): React.ReactNode[] { let result: React.ReactNode[] = []; - - for (var db = Math.ceil(this.dbMax/this.dbTickSpacing)*this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing ) - { - var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin); - if (db === 0) - { - result.push( - this.majorGridLine(this.xMin,y,this.xMax,y) - ); + + for (var db = Math.ceil(this.dbMax / this.dbTickSpacing) * this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing) { + var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin); + if (db === 0) { + result.push( + this.majorGridLine(this.xMin, y, this.xMax, y) + ); } else { - result.push( - this.gridLine(this.xMin,y,this.xMax,y) - ); + result.push( + this.gridLine(this.xMin, y, this.xMax, y) + ); } } - let decade0 = Math.pow(10,Math.floor(Math.log10(this.fMin))); - for (var decade = decade0; decade < this.fMax; decade *= 10) - { - for (var i = 1; i <= 10; ++i) - { - var f = decade*i; - if (f > this.fMin && f < this.fMax) - { + let decade0 = Math.pow(10, Math.floor(Math.log10(this.fMin))); + for (var decade = decade0; decade < this.fMax; decade *= 10) { + for (var i = 1; i <= 10; ++i) { + var f = decade * i; + if (f > this.fMin && f < this.fMax) { var x = this.toX(f); - if (i === 10) - { - result.push(this.majorGridLine(x,this.yMin,x,this.yMax)); + if (i === 10) { + result.push(this.majorGridLine(x, this.yMin, x, this.yMax)); } else { - result.push(this.gridLine(x,this.yMin,x,this.yMax)); + result.push(this.gridLine(x, this.yMin, x, this.yMax)); } } } } - return result; + return result; } dbMin: number = 5; @@ -244,20 +237,20 @@ const ToobFrequencyResponseView = render() { // deliberately reversed to flip up and down. - this.dbMax = this.props.minDb ?? this.dbMinDefault; + this.dbMax = this.props.minDb ?? this.dbMinDefault; this.dbMin = this.props.maxDb ?? this.dbMaxDefault; this.fMin = this.props.minFrequency ?? 30; this.fMax = this.props.maxFrequency ?? 20000; this.logMin = Math.log(this.fMin); this.logMax = Math.log(this.fMax); - - + + let classes = this.props.classes; return (
- { this.grid() } + {this.grid()} diff --git a/react/src/ToobInputStageView.tsx b/react/src/ToobInputStageView.tsx index 9245d9c..e9894f9 100644 --- a/react/src/ToobInputStageView.tsx +++ b/react/src/ToobInputStageView.tsx @@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView'; @@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({ interface ToobInputStageProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobInputStageState { @@ -84,8 +84,8 @@ const ToobInputStageView = class ToobInputStageViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-input_stage"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobMLView.tsx b/react/src/ToobMLView.tsx index 4500731..2798758 100644 --- a/react/src/ToobMLView.tsx +++ b/react/src/ToobMLView.tsx @@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel,MonitorPortHandle } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView'; @@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({ interface ToobMLProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobMLState { @@ -135,8 +135,8 @@ const ToobMLView = class ToobMLViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-ml"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobPowerStage2View.tsx b/react/src/ToobPowerStage2View.tsx index 8e1e3f6..9fe1641 100644 --- a/react/src/ToobPowerStage2View.tsx +++ b/react/src/ToobPowerStage2View.tsx @@ -26,18 +26,19 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView'; +let POWERSTAGE_UI_URI = "http://two-play.com/plugins/toob-power-stage-2#uiState"; const styles = (theme: Theme) => createStyles({ }); interface ToobPowerstage2Props extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobPowerstage2State { @@ -60,18 +61,11 @@ const ToobPowerstage2View = uiState: [0,0,0,0,0,0,0,0] } this.onStateChanged = this.onStateChanged.bind(this); - this.onAtomOutput = this.onAtomOutput.bind(this); } listeningForOutput: boolean = false; atomOutputHandle?: ListenHandle; - onAtomOutput(instanceId: number, atomOutput: any): void { - if (atomOutput.lv2Type === "http://two-play.com/plugins/toob-power-stage-2#uiState" ) - { - this.setState({uiState: atomOutput.data as number[]}); - } - } maybeListenForAtomOutput(): void { let listenForOutput = this.isReady && this.isControlMounted; @@ -80,11 +74,22 @@ const ToobPowerstage2View = this.listeningForOutput = listenForOutput; if (listenForOutput) { - this.atomOutputHandle = this.model.listenForAtomOutput(this.props.instanceId,this.onAtomOutput); + this.atomOutputHandle = this.model.monitorPatchProperty( + this.props.instanceId, + POWERSTAGE_UI_URI, + (instanceId: number, propertyUri: string, json: any) => { + if (json.otype_ === "Vector") + { + this.setState({uiState: json.value as number[]}); + } + }); + + // discard the output as we will be getting it through monitorPatchProperty as well. + this.model.getPatchProperty(this.props.instanceId,POWERSTAGE_UI_URI); } else { if (this.atomOutputHandle) { - this.model.cancelListenForAtomOutput(this.atomOutputHandle); + this.model.cancelMonitorPatchProperty(this.atomOutputHandle); this.atomOutputHandle = undefined; } @@ -180,8 +185,8 @@ const ToobPowerstage2View = class ToobPowerstage2ViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-power-stage-2"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobSpectrumAnalyzerView.tsx b/react/src/ToobSpectrumAnalyzerView.tsx index 2c2ec0d..9391f9a 100644 --- a/react/src/ToobSpectrumAnalyzerView.tsx +++ b/react/src/ToobSpectrumAnalyzerView.tsx @@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobSpectrumResponseView from './ToobSpectrumResponseView'; @@ -38,7 +38,7 @@ const styles = (theme: Theme) => createStyles({ interface ToobSpectrumAnalyzerProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobSpectrumAnalyzerState { @@ -84,8 +84,8 @@ const ToobSpectrumAnalyzerView = class ToobSpectrumAnalyzerViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-spectrum"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobSpectrumResponseView.tsx b/react/src/ToobSpectrumResponseView.tsx index 8d6a973..424f97d 100644 --- a/react/src/ToobSpectrumResponseView.tsx +++ b/react/src/ToobSpectrumResponseView.tsx @@ -25,22 +25,30 @@ import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; -import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel"; +import { PiPedalModelFactory, PiPedalModel, State, ListenHandle } from "./PiPedalModel"; import { StandardItemSize } from './PluginControlView'; -import Utility from './Utility'; -// import { setInterval,clearInterval } from 'timers'; // no longer requires polyfill (webpack >= 5) -const SPECTRUM_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#spectrumResponse"; -const PLOT_WIDTH = StandardItemSize.width*3-16; -const PLOT_HEIGHT = StandardItemSize.height-12; + +import SvgPathBuilder from './SvgPathBuilder' + +const SPECTRUM_RESPONSE_URI = "http://two-play.com/plugins/toob#spectrumResponse"; +const SPECTRUM_ENABLE_URI = "http://two-play.com/plugins/toob#spectrumEnable"; + +const PLOT_WIDTH = StandardItemSize.width * 3 - 16; +const PLOT_HEIGHT = StandardItemSize.height - 12; const styles = (theme: Theme) => createStyles({ frame: { - width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444", + width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#333", borderRadius: 6, marginTop: 12, marginLeft: 8, marginRight: 8, + overflow: "hidden" + }, + frameShadow: { + width: PLOT_WIDTH, height: PLOT_HEIGHT, + borderRadius: 6, boxShadow: "1px 4px 8px #000 inset" }, }); @@ -51,6 +59,7 @@ interface ToobSpectrumResponseProps extends WithStyles { } interface ToobSpectrumResponseState { path: string; + holdPath: string; minF: number; maxF: number; } @@ -72,9 +81,8 @@ const ToobSpectrumResponseView = let minF = 60; let maxF = 18000; - let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); - if (plugin) - { + let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId); + if (plugin) { minF = plugin.getControlValue("minF"); maxF = plugin.getControlValue("maxF"); } @@ -82,62 +90,59 @@ const ToobSpectrumResponseView = this.state = { path: "", + holdPath: "", minF: minF, maxF: maxF }; - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this); } - onStateChanged() - { - if (this.model.state.get() === State.Ready) - { + onStateChanged() { + if (this.model.state.get() === State.Ready) { this.requestDeferred = false; this.requestOutstanding = false; - let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); - if (plugin) - { + let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId); + if (plugin) { let minF = plugin.getControlValue("minF"); let maxF = plugin.getControlValue("maxF"); - this.setState({minF: minF, maxF: maxF}); + this.setState({ minF: minF, maxF: maxF }); } - + } this.isReady = this.model.state.get() === State.Ready; - this.maybeStartClock(); // after a reconnect. + this.maybeStartMonitoring(); // after a reconnect. } - onPedalBoardChanged() - { - this.maybeStartClock(); - let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); - if (plugin) - { + onPedalboardChanged() { + // rebind our monitoring hooks. + this.maybeStartMonitoring(false); + this.maybeStartMonitoring(true); + + let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId); + if (plugin) { let minF = plugin.getControlValue("minF"); let maxF = plugin.getControlValue("maxF"); - this.setState({minF: minF, maxF: maxF}); + this.setState({ minF: minF, maxF: maxF }); } } mounted: boolean = false; isReady: boolean = false; - componentDidMount() - { + componentDidMount() { this.model.state.addOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); this.mounted = true; this.isReady = this.model.state.get() === State.Ready; - this.maybeStartClock(); + this.maybeStartMonitoring(); } - componentWillUnmount() - { + componentWillUnmount() { this.mounted = false; - this.maybeStartClock(); + this.maybeStartMonitoring(); this.model.state.removeOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); } componentDidUpdate() { @@ -147,77 +152,50 @@ const ToobSpectrumResponseView = // Size of the SVG element. xMin: number = 0; - xMax: number = PLOT_WIDTH+4; + xMax: number = PLOT_WIDTH + 4; yMin: number = 0; yMax: number = PLOT_HEIGHT; - onSpectrumResponseUpdated(svgPath: string) { - this.setState({path: svgPath}); + onSpectrumResponseUpdated(svgPath: string, holdSvgPath: string) { + this.setState({ path: svgPath, holdPath: holdSvgPath }); } - isTicking : boolean = false; - hClock?: NodeJS.Timeout; - hAtomOutput?: ListenHandle; + private isMonitoring: boolean = false; + private hClock?: NodeJS.Timeout; + private hAtomOutput?: ListenHandle; - maybeStartClock() { - let shouldTick = this.isReady && this.mounted; + private maybeStartMonitoring(wantsMonitoring: boolean = true) { + let shouldMonitor = this.isReady && this.mounted && wantsMonitoring; - if (shouldTick !== this.isTicking) - { - this.isTicking = shouldTick; - if (shouldTick) - { - this.hAtomOutput = this.model.listenForAtomOutput(this.props.instanceId, - (instanceId,atomOutput)=> { - this.onSpectrumResponseUpdated(atomOutput.value as string); - if (this.requestDeferred) { - Utility.delay(30) // take breath - .then( - () => { - this.requestOutstanding = false; - this.requestDeferred = false; - this.requestSpectrum(); - } - - ); - } else { - this.requestOutstanding = false; + if (shouldMonitor !== this.isMonitoring) { + this.isMonitoring = shouldMonitor; + if (shouldMonitor) { + this.hAtomOutput = this.model.monitorPatchProperty( + this.props.instanceId, + SPECTRUM_RESPONSE_URI, + (instanceId, propertyUri, json) => { + if (propertyUri === SPECTRUM_RESPONSE_URI && json.otype_ === "Tuple") { + let values = json.value as string[]; + this.onSpectrumResponseUpdated(values[0], values[1]); } - - - }); - this.hClock = setInterval(()=> { - this.requestSpectrum(); - }, - 1000/15); + } + ); + this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, true) + .catch((error)=> {}); } else { - if (this.hAtomOutput) - { - this.model.cancelListenForAtomOutput(this.hAtomOutput); + + this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, false) + .catch((error)=> {}); // e.g. plugin not found. + + + + if (this.hAtomOutput) { + this.model.cancelMonitorPatchProperty(this.hAtomOutput); this.hAtomOutput = undefined; } - if (this.hClock) - { - clearInterval(this.hClock); - this.hClock = undefined; - } } } - - - } - requestSpectrum() { - if (this.requestOutstanding) { // throttling. - this.requestDeferred = true; - return; - } - this.requestOutstanding = true; - this.model.getLv2Parameter(this.props.instanceId, SPECTRUM_RESPONSE_VECTOR_URI) - .then((data) => { - }).catch(error => { - // assume the connection was lost. We'll get saved by a reconnect. - }); } numPoints = 200; @@ -228,22 +206,21 @@ const ToobSpectrumResponseView = logMaxF = Math.log(this.maxF); toX(f: number): number { - let width = PLOT_WIDTH-8; - return (Math.log(f)-this.logMinF)*width/(this.logMaxF-this.logMinF); + let width = PLOT_WIDTH - 8; + return (Math.log(f) - this.logMinF) * width / (this.logMaxF - this.logMinF); } - grid(): React.ReactNode[] { - let result: React.ReactNode[] = []; - let width = PLOT_WIDTH-8; - let height = PLOT_HEIGHT-8; - for (let db = -20; db >= -100; db -= 20) - { - let y = height*db/-100; - result.push( - ( - - ) - ); + private minorGrid(): string { + + let ss = new SvgPathBuilder(); + + let width = PLOT_WIDTH; + let height = PLOT_HEIGHT; + + for (let db = -20; db >= -100; db -= 20) { + let y = height * db / -100; + ss.moveTo(0,y); + ss.lineTo(width,y); } let minF = this.state.minF; @@ -252,54 +229,86 @@ const ToobSpectrumResponseView = this.maxF = maxF; this.logMinF = Math.log(this.minF); this.logMaxF = Math.log(this.maxF); - - for (let decade = 10; decade < 100000; decade *= 10) - { - for (let minorTick = 1; minorTick <= 5; ++ minorTick) - { - let f = decade*minorTick; - if (f > minF && f < maxF) - { + + for (let decade = 10; decade < 100000; decade *= 10) { + for (let minorTick = 1; minorTick <= 5; ++minorTick) { + let f = decade * minorTick; + if (f > minF && f < maxF) { let x = this.toX(f); - if (minorTick === 1) - { - result.push( - ( - - ) - ); + if (minorTick === 1) { + ss.moveTo(x,0); + ss.lineTo(x,height); } else { - result.push( - ( - - ) - ); + ss.moveTo(x,0); + ss.lineTo(x,height); } - + } } } - - return result; + return ss.toString(); } + private majorGrid(): string { + + let ss = new SvgPathBuilder(); + + let height = PLOT_HEIGHT; + + let minF = this.state.minF; + let maxF = this.state.maxF; + this.minF = minF; + this.maxF = maxF; + this.logMinF = Math.log(this.minF); + this.logMaxF = Math.log(this.maxF); + + for (let decade = 10; decade < 100000; decade *= 10) { + if (decade > minF && decade < maxF) + { + let x = this.toX(decade); + ss.moveTo(x,0); + ss.lineTo(x,height); + } + } + return ss.toString(); + } + + render() { let classes = this.props.classes; return ( -
-
- - {this.grid()} +
+ {/*
+ + + +
*/} + +
+ +
-
- - +
+ +
+
+ + + +
+
+
+
); } diff --git a/react/src/ToobToneStackView.tsx b/react/src/ToobToneStackView.tsx index c966d5b..f66e21d 100644 --- a/react/src/ToobToneStackView.tsx +++ b/react/src/ToobToneStackView.tsx @@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles'; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel,ControlValueChangedHandle } from "./PiPedalModel"; -import { PedalBoardItem } from './PedalBoard'; +import { PedalboardItem } from './Pedalboard'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView'; @@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({ interface ToobToneStackProps extends WithStyles { instanceId: number; - item: PedalBoardItem; + item: PedalboardItem; } interface ToobToneStackState { @@ -117,8 +117,8 @@ const ToobToneStackView = class ToobToneStackViewFactory implements IControlViewFactory { uri: string = "http://two-play.com/plugins/toob-tone-stack"; - Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { - return (); + Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode { + return (); } diff --git a/react/src/ToobWaveShapeView.tsx b/react/src/ToobWaveShapeView.tsx index 4cfb762..715291c 100644 --- a/react/src/ToobWaveShapeView.tsx +++ b/react/src/ToobWaveShapeView.tsx @@ -128,7 +128,7 @@ const ToobWaveShapeView = data: [] }; - this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this); this.isReady = this.model.state.get() === State.Ready; @@ -147,7 +147,7 @@ const ToobWaveShapeView = } } } - onPedalBoardChanged() { + onPedalboardChanged() { this.updateWaveShape(); } _valueChangedHandle?: ControlValueChangedHandle; @@ -158,7 +158,7 @@ const ToobWaveShapeView = componentDidMount() { this.mounted = true; this.model.state.addOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); this._valueChangedHandle = this.model.addControlValueChangeListener( this.props.instanceId, this.onControlValueChanged); @@ -180,7 +180,7 @@ const ToobWaveShapeView = this._valueChangedHandle = undefined; } this.model.state.removeOnChangedHandler(this.onStateChanged); - this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); } onControlValueChanged(key: string, value: number) { @@ -230,21 +230,27 @@ const ToobWaveShapeView = return; } this.requestOutstanding = true; - this.model.getLv2Parameter(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber) - .then((data) => { - this.onWaveShapeUpdated(data); - if (this.requestDeferred) { - Utility.delay(10) // take breath - .then( - () => { - this.requestOutstanding = false; - this.requestDeferred = false; - this.updateWaveShape(); - } + this.model.getPatchProperty(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber) + .then((json) => { + if (!this.mounted) return; + + if (json.otype_ === "Vector" && json.vtype_ === "Float") + { + let data = json.value; + this.onWaveShapeUpdated(data); + if (this.requestDeferred) { + Utility.delay(10) // take breath + .then( + () => { + this.requestOutstanding = false; + this.requestDeferred = false; + this.updateWaveShape(); + } - ); - } else { - this.requestOutstanding = false; + ); + } else { + this.requestOutstanding = false; + } } }).catch(error => { // assume the connection was lost. We'll get saved by a reconnect. diff --git a/react/src/ZoomedDial.tsx b/react/src/ZoomedDial.tsx index 3870043..97e436b 100644 --- a/react/src/ZoomedDial.tsx +++ b/react/src/ZoomedDial.tsx @@ -170,8 +170,8 @@ const ZoomedDial = withStyles(styles, { withTheme: true })( let uiControl = this.props.controlInfo.uiControl; let instanceId = this.props.controlInfo.instanceId; if (instanceId === -1) return 0; - let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId); - let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0; + let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId); + let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0; this.defaultValue = value; return value; } diff --git a/react/src/ZoomedUiControl.tsx b/react/src/ZoomedUiControl.tsx index dd17d8e..99220f7 100644 --- a/react/src/ZoomedUiControl.tsx +++ b/react/src/ZoomedUiControl.tsx @@ -94,8 +94,8 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })( let uiControl = this.props.controlInfo.uiControl; let instanceId = this.props.controlInfo.instanceId; if (instanceId === -1) return 0; - let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId); - let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0; + let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId); + let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0; return value; } } @@ -105,7 +105,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })( let v = Number.parseFloat(val.toString()); this.setState({ value: v }); if (this.props.controlInfo) { - this.model.setPedalBoardControlValue( + this.model.setPedalboardControl( this.props.controlInfo.instanceId, this.props.controlInfo.uiControl.symbol, v); @@ -116,7 +116,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })( let v = checked ? 1: 0; this.setState({ value: v }); if (this.props.controlInfo) { - this.model.setPedalBoardControlValue( + this.model.setPedalboardControl( this.props.controlInfo.instanceId, this.props.controlInfo.uiControl.symbol, v); @@ -247,7 +247,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })( onSetValue={(v) => { this.setState({ value: v }); if (this.props.controlInfo) { - this.model.setPedalBoardControlValue( + this.model.setPedalboardControl( this.props.controlInfo.instanceId, this.props.controlInfo.uiControl.symbol, v); diff --git a/react/tmp.txt b/react/tmp.txt index 247c309..610ee0c 100644 --- a/react/tmp.txt +++ b/react/tmp.txt @@ -31,8 +31,8 @@ src/MidiBinding.tsx src/PiPedalError.tsx src/IControlViewFactory.tsx src/Lv2Plugin.tsx -src/PedalBoard.tsx -src/PedalBoardView.tsx +src/Pedalboard.tsx +src/PedalboardView.tsx src/PresetDialog.tsx src/AppThemed.tsx src/ZoomedDial.tsx diff --git a/src/AdminMain.cpp b/src/AdminMain.cpp index 35b403b..65b17ce 100644 --- a/src/AdminMain.cpp +++ b/src/AdminMain.cpp @@ -206,7 +206,7 @@ bool setJackConfiguration(JackServerSettings serverSettings) #if JACK_HOST - serverSettings.Write(); + serverSettings.WriteDaemonConfig(); silentSysExec("/usr/bin/systemctl unmask jack"); silentSysExec("/usr/bin/systemctl enable jack"); diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 24d818f..4ef8711 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -23,6 +23,7 @@ */ #include "pch.h" +#include "util.hpp" #include #include #include "ss.hpp" @@ -1367,6 +1368,7 @@ namespace pipedal } void AudioThread() { + SetThreadName("alsaDriver"); try { #if defined(__WIN32) diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 160071d..73f3a2d 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -92,8 +92,8 @@ public: #endif - oscillator.Init(440,jackConfiguration.GetSampleRate()); - latencyMonitor.Init(jackConfiguration.GetSampleRate()); + oscillator.Init(440,jackConfiguration.sampleRate()); + latencyMonitor.Init(jackConfiguration.sampleRate()); audioDriver->Open(serverSettings,channelSelection); inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()]; @@ -107,7 +107,7 @@ public: if (testType == TestType::LatencyMonitor) { auto latency = this->latencyMonitor.GetLatency(); - double ms = 1000.0*latency/jackConfiguration.GetSampleRate(); + double ms = 1000.0*latency/jackConfiguration.sampleRate(); cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl; } diff --git a/src/AtomBuffer.hpp b/src/AtomBuffer.hpp new file mode 100644 index 0000000..bf0fa70 --- /dev/null +++ b/src/AtomBuffer.hpp @@ -0,0 +1,54 @@ +/* + * 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 "lv2/atom.lv2/atom.h" + +namespace pipedal +{ + class AtomBuffer + { + public: + AtomBuffer() { } + AtomBuffer(const LV2_Atom *atom) + { + size_t size = atom->size + sizeof(LV2_Atom); + data.resize(size); + uint8_t *p = (uint8_t *)atom; + for (size_t i = 0; i < size; ++i) + { + data[i] = p[i]; + } + } + const LV2_Atom *AsAtom() const + { + return (const LV2_Atom *)&(data[0]); + } + + private: + std::vector data; + }; +} diff --git a/src/AtomConverter.cpp b/src/AtomConverter.cpp new file mode 100644 index 0000000..be3f8d1 --- /dev/null +++ b/src/AtomConverter.cpp @@ -0,0 +1,548 @@ +/* + * 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 "AtomConverter.hpp" +#include +#include +#include +#include "json.hpp" +#include "ss.hpp" +#include "lv2/atom.lv2/util.h" + +using namespace pipedal; + +const std::string AtomConverter::ID_TAG {"id_"}; +const std::string AtomConverter::OTYPE_TAG {"otype_"}; +const std::string AtomConverter::VTYPE_TAG {"vtype_"}; + +#define SHORT_ATOM__Bool "Bool" +#define SHORT_ATOM__Float "Float" +#define SHORT_ATOM__Int "Int" +#define SHORT_ATOM__Long "Long" +#define SHORT_ATOM__Double "Double" +#define SHORT_ATOM__Vector "Vector" +#define SHORT_ATOM__Object "Object" +#define SHORT_ATOM__Tuple "Tuple" +#define SHORT_ATOM__URI "URI" +#define SHORT_ATOM__URID "URID" +#define SHORT_ATOM__String "String" +#define SHORT_ATOM__Path "Path" + + + +AtomConverter::AtomConverter(MapFeature &map) + : map(map) +{ + InitUrids(); + if (prototype) + { + this->prototypeBuffer.resize(prototype->size+sizeof(LV2_Atom)); + std::memcpy(&(this->prototypeBuffer[0]), prototype, this->prototypeBuffer.size()); + this->prototype = (LV2_Atom*)&(prototype[0]); + } + lv2_atom_forge_init(&outputForge, map.GetMap()); +} + +json_variant AtomConverter::ToJson(const LV2_Atom *atom) +{ + json_variant variant = ToVariant(const_cast(atom)); + return std::move(variant); +} +LV2_Atom*AtomConverter::ToAtom(const json_variant&json) +{ + if (outputBuffer.size() == 0) + { + outputBuffer.resize(512); + } + + while (true) + { + try { + LV2_Atom *result = (LV2_Atom*)(&outputBuffer[0]); + result->size = outputBuffer.size(); + lv2_atom_forge_set_buffer(&outputForge,(uint8_t*)&(outputBuffer[0]),outputBuffer.size()); + ToForge(json); + return (LV2_Atom*)(&outputBuffer[0]); + } catch (const BufferOverflowException&) + { + } + + if (outputBuffer.size() >= 1024*1024) + { + throw std::logic_error("Atom is too large."); + } + outputBuffer.resize(outputBuffer.size()*2); + } +} + + +static inline void *AtomContent(LV2_Atom*atom,size_t offset = 0) +{ + // always % sizeof(LV2_Atom) + return (void*)((char*)atom + sizeof(LV2_Atom)+offset); +} +static inline LV2_Atom*NextAtom(LV2_Atom*atom) +{ + size_t size = sizeof(LV2_Atom) + (atom->size +(sizeof(LV2_Atom)-1))/sizeof(LV2_Atom)*sizeof(LV2_Atom); + return (LV2_Atom*)((char*)atom + size); +} + +json_variant AtomConverter::ToVariant(LV2_Atom *atom) +{ + if (atom->type == urids.ATOM__Float) + { + LV2_Atom_Float*pVal = (LV2_Atom_Float*)atom; + return json_variant((double)pVal->body); + } + if (atom->type == urids.ATOM__Bool) + { + LV2_Atom_Bool *pVal = (LV2_Atom_Bool*)atom; + return json_variant(pVal->body != 0); + } + if (atom->type == urids.ATOM__Int) + { + LV2_Atom_Int *pVal = (LV2_Atom_Int*)atom; + return TypedProperty(SHORT_ATOM__Int,(double)pVal->body); + } + if (atom->type == urids.ATOM__Long) + { + LV2_Atom_Long *pVal = (LV2_Atom_Long*)atom; + return TypedProperty(SHORT_ATOM__Long,(double)pVal->body); + } + if (atom->type == urids.ATOM__Double) + { + LV2_Atom_Double*pVal = (LV2_Atom_Double*)atom; + return TypedProperty(SHORT_ATOM__Double,(double)pVal->body); + } + if (atom->type == urids.ATOM__URID) + { + LV2_Atom_URID*pVal = (LV2_Atom_URID*)atom; + const char*strValue = map.UridToString(pVal->body); + return TypedProperty(SHORT_ATOM__URID,std::string(strValue)); + } + if (atom->type == urids.ATOM__String) + { + const char*p = (const char*)atom +sizeof(LV2_Atom); + size_t size = atom->size; + while (size > 0 && p[size-1] == '\0') + { + --size; + } + return json_variant(std::string(p,size)); + } else if (atom->type == urids.ATOM__Path) + { + const char*p = (const char*)atom +sizeof(LV2_Atom); + size_t size = atom->size; + while (size > 0 && p[size-1] == '\0') + { + --size; + } + return TypedProperty(SHORT_ATOM__Path,std::string(p,size)); + + } else if (atom->type == urids.ATOM__URI) + { + const char*p = (const char*)atom +sizeof(LV2_Atom); + size_t size = atom->size; + while (size > 0 && p[size-1] == '\0') + { + --size; + } + return TypedProperty(SHORT_ATOM__URI,std::string(p,size)); + + } + else if (atom->type == urids.ATOM__Tuple) + { + json_array array; + LV2_Atom*current = (LV2_Atom*)AtomContent(atom,0); + LV2_Atom*end = (LV2_Atom*)AtomContent(atom,atom->size); + while (current < end) + { + array.push_back(ToVariant(current)); + current = NextAtom(current); + } + json_variant vArray { std::move(array)}; + return TypedProperty(SHORT_ATOM__Tuple,std::move(vArray)); + } + if (atom->type == urids.ATOM__URID) + { + LV2_Atom_URID *pVal = (LV2_Atom_URID*)atom; + return TypedProperty(SHORT_ATOM__URID,std::string(map.UridToString(pVal->body))); + } + if (atom->type == urids.ATOM__Vector) + { + json_array array; + LV2_Atom_Vector *pVal = (LV2_Atom_Vector*)atom; + + size_t n = (atom->size-sizeof(LV2_Atom_Vector_Body))/pVal->body.child_size; + void *vectorData = AtomContent(atom,sizeof(LV2_Atom_Vector_Body)); + if (pVal->body.child_type == urids.ATOM__Float) + { + float*p = (float*)vectorData; + for (size_t i = 0; i < n; ++i) + { + array.push_back(json_variant((double)p[i])); + } + } + else if (pVal->body.child_type == urids.ATOM__Int) + { + auto p = (int32_t*)vectorData; + for (size_t i = 0; i < n; ++i) + { + array.push_back(json_variant((double)p[i])); + } + } + else if (pVal->body.child_type == urids.ATOM__Bool) + { + auto p = (int32_t*)vectorData; + for (size_t i = 0; i < n; ++i) + { + array.push_back(json_variant(p[i] != 0)); + } + } + else if (pVal->body.child_type == urids.ATOM__Long) + { + auto p = (int64_t*)vectorData; + for (size_t i = 0; i < n; ++i) + { + array.push_back(json_variant((double)(p[i]))); + } + } + else if (pVal->body.child_type == urids.ATOM__Double) + { + auto p = (double*)vectorData; + for (size_t i = 0; i < n; ++i) + { + array.push_back(json_variant((double)p[i])); + } + } else { + std::string dataType = map.UridToString(pVal->body.child_type); + throw std::logic_error("AtomConverter: Vector dataype not supported. (" + dataType + ") Please contact support if you get this message."); + } + json_variant vArray {std::move(array)}; + json_variant object = json_variant::make_object(); + object[OTYPE_TAG] = json_variant(std::string(SHORT_ATOM__Vector)); + object[VTYPE_TAG] = json_variant(std::string(TypeUridToString(pVal->body.child_type))); + object["value"] = std::move(vArray); + + return std::move(object); + } else if (atom->type == urids.ATOM__Property) + { + throw std::logic_error("Not implemented."); + } else if (atom->type == urids.ATOM__Object) + { + LV2_Atom_Object *pVal = (LV2_Atom_Object*)atom; + + json_variant result = json_variant::make_object(); + if (pVal->body.id != 0) + { + + result[ ID_TAG] = map.UridToString(pVal->body.id); + } + if (pVal->body.otype != 0) + { + + result[OTYPE_TAG] = map.UridToString(pVal->body.otype); + } + LV2_Atom_Property_Body *current = (LV2_Atom_Property_Body *)AtomContent(atom,sizeof(LV2_Atom_Object_Body)); + LV2_Atom_Property_Body *end = (LV2_Atom_Property_Body *)AtomContent(atom,atom->size); + + while (current < end) + { + std::string key = map.UridToString(current->key); + json_variant value = ToVariant(¤t->value); + result[key] = std::move(value); + current = (LV2_Atom_Property_Body*)(NextAtom(&(current->value))); + } + + return result; + } + throw std::logic_error( + SS("AtomConverter: Datatype not supported. (" + << map.UridToString(atom->type) + << ") Please contact support if you get this message.")); + + + +} + + +bool AtomConverter::AreTypesTheSame(LV2_Atom*left, LV2_Atom *right) const +{ + if (left->type != right->type) return false; + if (left->type == urids.ATOM__Object) + { + LV2_Atom_Object *l = (LV2_Atom_Object*)left; + LV2_Atom_Object *r = (LV2_Atom_Object*)right; + return l->body.id == r->body.id && l->body.otype == r->body.otype; + } + return true; +} + +void AtomConverter::ObjectToForge(const json_variant&json) +{ + LV2_URID bodyId = 0; + assert(json.is_object()); + std::string oType = json[OTYPE_TAG].as_string(); + if (oType == SHORT_ATOM__Int) + { + lv2_atom_forge_int(&this->outputForge,(int32_t)json["value"].as_number()); + } + else if (oType == SHORT_ATOM__Long) + { + lv2_atom_forge_long(&this->outputForge,(int64_t)json["value"].as_number()); + } else if (oType == SHORT_ATOM__Double) + { + lv2_atom_forge_double(&this->outputForge,(double)json["value"].as_number()); + } + else if (oType == SHORT_ATOM__Path) + { + std::string value = json["value"].as_string().c_str(); + + lv2_atom_forge_path(&this->outputForge,value.c_str(),value.length()+1); + } + else if (oType == SHORT_ATOM__URI) + { + std::string value = json["value"].as_string().c_str(); + + lv2_atom_forge_uri(&this->outputForge,value.c_str(),value.length()+1); + } + else if (oType == SHORT_ATOM__URID) + { + LV2_URID urid = map.GetUrid(json["value"].as_string().c_str()); + lv2_atom_forge_urid(&outputForge,urid); + + } + else if (oType == SHORT_ATOM__Tuple) + { + TupleToForge(json); + } else if (oType == SHORT_ATOM__Vector) + { + VectorToForge(json); + } else + { + LV2_URID id = 0; + if (json.contains(ID_TAG)) + { + id = map.GetUrid(json[ID_TAG].as_string().c_str()); + } + LV2_URID oType = 0; + if (json.contains(OTYPE_TAG)) + { + oType = map.GetUrid(json[OTYPE_TAG].as_string().c_str()); + } + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(&outputForge,&frame,id,oType); + const auto& obj = json.as_object(); + for (auto member: *obj.get()) + { + const std::string& property = member.first; + if (property != OTYPE_TAG && property != ID_TAG) + { + LV2_URID property = map.GetUrid(member.first.c_str()); + lv2_atom_forge_key(&outputForge,property); + ToForge(member.second); + } + } + lv2_atom_forge_pop(&outputForge,&frame); + } +} +void AtomConverter::ToForge(const json_variant&json) +{ + if (json.is_number()) + { + CheckResult(lv2_atom_forge_float(&outputForge,(float)json.as_number())); + } + else if (json.is_bool()) + { + CheckResult(lv2_atom_forge_bool(&outputForge,json.as_bool())); + } + else if (json.is_string()) + { + const std::string&str = json.as_string(); + CheckResult(lv2_atom_forge_string(&outputForge,str.c_str(),str.size()+1)); + } else if (json.is_object()) + { + return ObjectToForge(json); + } else { + throw std::logic_error("Malformed json atom."); + } +} + +void AtomConverter::VectorToForge(const json_variant&json) +{ + const json_array& array = *(json["value"].as_array().get()); + size_t size = array.size(); + + + LV2_Atom_Forge_Frame frame; + LV2_URID childType = GetTypeUrid(json[VTYPE_TAG].as_string().c_str()); + + if (childType == urids.ATOM__Float) + { + using T = float; + CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType)); + + for (size_t i = 0; i < size; ++i) + { + T value = (T)(array[i].as_number()); + CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value))); + } + // does this pad the frame? + lv2_atom_forge_pop(&outputForge,&frame); + lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float)); + + } + else if (childType == urids.ATOM__Int) + { + using T = int32_t; + CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType)); + + for (size_t i = 0; i < size; ++i) + { + T value = (T)(array[i].as_number()); + CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value))); + } + // does this pad the frame? + lv2_atom_forge_pop(&outputForge,&frame); + lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float)); + } + else if (childType == urids.ATOM__Bool) + { + using T = int32_t; + CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType)); + + for (size_t i = 0; i < size; ++i) + { + T value = (T)(array[i].as_bool()); + CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value))); + } + // does this pad the frame? + lv2_atom_forge_pop(&outputForge,&frame); + lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float)); + } + else if (childType == urids.ATOM__Long) + { + using T = int64_t; + CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType)); + + for (size_t i = 0; i < size; ++i) + { + T value = (T)(array[i].as_number()); + CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value))); + } + // does this pad the frame? + lv2_atom_forge_pop(&outputForge,&frame); + lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float)); + } + else if (childType == urids.ATOM__Double) + { + using T = double; + CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType)); + + for (size_t i = 0; i < size; ++i) + { + T value = (T)(array[i].as_number()); + CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value))); + } + // does this pad the frame? + lv2_atom_forge_pop(&outputForge,&frame); + lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float)); + } else { + std::string dataType = map.UridToString(childType); + throw std::logic_error("AtomConverter: Vector dataype not supported. (" + dataType + ") Please contact support if you get this message."); + } +} + +void AtomConverter::TupleToForge(const json_variant&json) +{ + LV2_Atom_Forge_Frame frame; + const json_array&array = *(json["value"].as_array().get()); + + lv2_atom_forge_tuple(&outputForge,&frame); + { + for (const json_variant&v: array) + { + ToForge(v); + } + } + lv2_atom_forge_pop(&outputForge,&frame); +} + + +LV2_URID AtomConverter::GetTypeUrid(const std::string uri) +{ + if (stringToTypeUrid.find(uri) != stringToTypeUrid.end()) + { + return stringToTypeUrid[uri]; + } + return map.GetUrid(uri.c_str()); +} + +LV2_URID AtomConverter::InitUrid(const char*uri, const char*shortUri ) +{ + LV2_URID urid = map.GetUrid(uri); + stringToTypeUrid[uri] = urid; + stringToTypeUrid[shortUri] = urid; + typeUridToString[urid] = shortUri; + return urid; +} + +std::string AtomConverter::TypeUridToString(LV2_URID urid) +{ + if (typeUridToString.find(urid) != typeUridToString.end()) + { + return typeUridToString[urid]; + } + return map.UridToString(urid); +} + +void AtomConverter::InitUrids() +{ +#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName) + ATOM_INIT(ATOM__Bool,Bool); + ATOM_INIT(ATOM__Chunk,Chunk); + ATOM_INIT(ATOM__Double,Double); + ATOM_INIT(ATOM__Event,Event); + ATOM_INIT(ATOM__Float,Float); + ATOM_INIT(ATOM__Int,Int); + ATOM_INIT(ATOM__Literal,Literal); + ATOM_INIT(ATOM__Long,Long); + ATOM_INIT(ATOM__Number,Number); + ATOM_INIT(ATOM__Object,Object); + ATOM_INIT(ATOM__Path,Path); + ATOM_INIT(ATOM__Property,Property); + //ATOM_INIT(ATOM__Resource,Resource); + //ATOM_INIT(ATOM__Sequence,Sequence); + //ATOM_INIT(ATOM__Sound,Sound); + ATOM_INIT(ATOM__String,String); + ATOM_INIT(ATOM__Tuple,Tuple); + ATOM_INIT(ATOM__Vector,Vector); + ATOM_INIT(ATOM__URI,URI); + ATOM_INIT(ATOM__URID,URID); + ATOM_INIT(ATOM__Vector,Vector); + //ATOM_INIT(ATOM__beatTime,beatTime); + //ATOM_INIT(ATOM__frameTime,frameTime); + //ATOM_INIT(ATOM__timeUnit,timeUnit); +#undef ATOM_INIT +} \ No newline at end of file diff --git a/src/AtomConverter.hpp b/src/AtomConverter.hpp new file mode 100644 index 0000000..c6adfe8 --- /dev/null +++ b/src/AtomConverter.hpp @@ -0,0 +1,239 @@ +/* + * 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 "MapFeature.hpp" +#include "lv2/atom.lv2/atom.h" +#include "lv2/atom.lv2/forge.h" +#include "json_variant.hpp" + +namespace pipedal +{ + /** @brief Roud-trippable Conversion of atoms to json, and json to atoms. + @remarks + This class implements round-trippable conversion of atoms to json and + vice-versa. + + Json objects are used for atom objects with non-trivial types; and + the "otype_" of a json object resolves the type of the corresponding + atom object. For the sake of density and readability, the otype_ of + basic LV2_ATOM__xxx types are represented using only portion of the + ATOM uri following the '#'. + + Augmented semantics are required to preserve typed atoms in json. For + example, an ATOM_Patch__Get atom would be reprsented as follows. + + + { + "otype_": "http://lv2plug.in/ns/ext/patch#Set", + "http://lv2plug.in/ns/ext/patch#property": { + "otype_": "URID", + "value": "http://something.com/custom#prop" + }, + "http://lv2plug.in/ns/ext/patch#value": { + "otype_": "Path", + "value": "/var/pipdeal/test.wav" + } + + LV2_ATOM__String's are represented as strings in the corresponding json. + + LV2_ATOM__Float's are represented as json numbers. + + LV2_ATOM__Bool's are represented as unadorned boolean values in json. + + Remaining Atom types require augmented json in order to preserve type information. + + LV2_ATOM__Int: + + {"otype_": "Int","value": 1} + + LV2_ATOM__Long + + {"otype_": "Int","value": 1} + + LV2_ATOM__Double + + {"otype_": "Double","value": 1.234} + + LV2_ATOM__Path + + {"otype_": "Path", "value": "/var/pipdeal/test.wav"} + + LV2_ATOM__URID + + {"otype_": "URID", "value": "http://something.com/custom#value"} + + The value is transmitted as an LV2_URID when translated to an atom, and un-mapped + when transmitted in json. + + LV2_ATOM__URI + + {"otype_": "URI", "value": "http://something.com/custom#prop"} + + The value is a string in both json and in atoms. + + LV2_ATOM_TUPLE + + {"otype_": "Tuple","value": [ ... arbitrary atoms ... ]} + + LV2_ATOM_VECTOR + + { "otype_": "Vector", "vtype_": "Int", "value": [ 1, 2, 3, 4 ] } + + Json values are co-erced to the type specified in "vtype_" and do not require + type annotation. vtype_ can be "Bool", "Int", "Long", "Float", or "Double". + + LV2_ATOM_OBJECT + + { "otype_": "http://lv2plug.in/ns/ext/patch#Set", + "http://lv2plug.in/ns/ext/patch#property": { + "otype_": "URID", + "value": "http://something.com/custom#prop" + }, + "http://two-play.com/ConvolutionReverb#volume": 3.5 + } + + An object has an "otype_" that specifies the type of the object, and contains + a list of propertys of the form "propertyname": . Property names + are strings, but are converted to LV2_URID's in the object body. By usual Atom + convention, the are URIs, but they could be simple strings as well. + **/ + + class AtomConverter + { + public: + /// @brief Constructor + /// @brief map used to map LV2_URIDs. Must have a lifetime longer than the AtomConverter instance. + + AtomConverter(MapFeature &map); + + /// @brief Convert the supplied atom to json. + /// @param atom The atom to convert. + /// @return The atom data in json format. + json_variant ToJson(const LV2_Atom *atom); + + /// @brief Convert a json variant to an atom. + /// @param json Json variant that matches the structure of the prototype. + /// @return An atom. + /// @remarks + /// The json variant must match the structure of the LV2_Atom prototype supplied to + /// the constructor. + LV2_Atom*ToAtom(const json_variant& json); + private: + static const std::string OTYPE_TAG; + static const std::string VTYPE_TAG; + static const std::string ID_TAG; + + std::map stringToTypeUrid; + std::map typeUridToString; + + LV2_URID GetTypeUrid(const std::string uri); + std::string TypeUridToString(LV2_URID urid); + + + void InitUrids(); + LV2_URID InitUrid(const char*uri, const char*shortUri); + + template + json_variant TypedProperty(const std::string type,U value) + { + json_variant result = json_variant::make_object(); + result[OTYPE_TAG] = type; + result["value"] = json_variant(value); + return result; + } + json_variant TypedProperty(const std::string type,const json_variant&& value) + { + json_variant result = json_variant::make_object(); + result[OTYPE_TAG] = type; + result["value"] = std::move(value); + return result; + } + bool AreTypesTheSame(LV2_Atom*left, LV2_Atom *right) const; + + json_variant ToVariant(LV2_Atom *atom); + void ToForge(const json_variant&json); + void VectorToForge(const json_variant&json); + void TupleToForge(const json_variant&json); + void ObjectToForge(const json_variant&json); + + + class BufferOverflowException: public std::exception { + public: + BufferOverflowException() { } + virtual const char*what() const noexcept override { + return "Buffer overflow"; + } + }; + inline static void CheckResult(LV2_Atom_Forge_Ref ref) + { + if (ref == 0) + { + throw BufferOverflowException(); + } + } + + private: + class Urids + { + public: + LV2_URID ATOM__Bool; + LV2_URID ATOM__Chunk; + LV2_URID ATOM__Double; + LV2_URID ATOM__Event; + LV2_URID ATOM__Float; + LV2_URID ATOM__Int; + LV2_URID ATOM__Literal; + LV2_URID ATOM__Long; + LV2_URID ATOM__Number; + LV2_URID ATOM__Object; + LV2_URID ATOM__Path; + LV2_URID ATOM__Property; + LV2_URID ATOM__Resource; + LV2_URID ATOM__Sequence; + LV2_URID ATOM__Sound; + LV2_URID ATOM__String; + LV2_URID ATOM__Tuple; + LV2_URID ATOM__Vector; + LV2_URID ATOM__URI; + LV2_URID ATOM__URID; + LV2_URID ATOM__atomTransfer; + LV2_URID ATOM__beatTime; + LV2_URID ATOM__frameTime; + LV2_URID ATOM__supports; + LV2_URID ATOM__timeUnit; + }; + Urids urids; + MapFeature ↦ + std::vector prototypeBuffer; + std::vector outputBuffer; + LV2_Atom_Forge outputForge; + + LV2_Atom * prototype = nullptr; + }; + +} \ No newline at end of file diff --git a/src/AtomConverterTest.cpp b/src/AtomConverterTest.cpp new file mode 100644 index 0000000..3751a32 --- /dev/null +++ b/src/AtomConverterTest.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "pch.h" +#include "catch.hpp" +#include +#include +#include +#include + +#include "json.hpp" +#include "json_variant.hpp" +#include "AtomConverter.hpp" +#include "lv2/atom.lv2/util.h" +#include "lv2/patch.lv2/patch.h" +#include "MapFeature.hpp" +#include + +using namespace pipedal; +using namespace std; + +MapFeature mapFeature; + +using AtomBuffer = std::vector; + +static void RoundTripTest(const AtomBuffer &buffer) +{ + LV2_Atom *prototype = (LV2_Atom *)&(buffer[0]); + + AtomConverter converter(mapFeature); + + json_variant variant = converter.ToJson(prototype); + cout << " " << variant.to_string() << endl; + + LV2_Atom *atomOut = converter.ToAtom(variant); + REQUIRE(atomOut->size == prototype->size); + size_t size = atomOut->size + sizeof(LV2_Atom); + REQUIRE(std::memcmp(atomOut, prototype, size) == 0); +} + +AtomBuffer MakeSimpleAtom() +{ + AtomBuffer atomBuffer(512); + LV2_Atom_Forge t; + LV2_Atom_Forge *forge = &t; + lv2_atom_forge_init(forge, mapFeature.GetMap()); + lv2_atom_forge_set_buffer(forge, &(atomBuffer[0]), atomBuffer.size()); + lv2_atom_forge_string(forge, "abcde\0", 6); + return atomBuffer; +} +AtomBuffer MakeTestTupleAtom() +{ + AtomBuffer atomBuffer(512); + LV2_Atom_Forge t; + LV2_Atom_Forge *forge = &t; + lv2_atom_forge_init(forge, mapFeature.GetMap()); + lv2_atom_forge_set_buffer(forge, &(atomBuffer[0]), atomBuffer.size()); + + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_tuple(forge, &frame); + { + lv2_atom_forge_bool(forge, true); + lv2_atom_forge_int(forge, 1); + lv2_atom_forge_float(forge, 2.1f); + lv2_atom_forge_double(forge, 1.23456789); + lv2_atom_forge_string(forge, "abcd\0", 5); + float vectorValues[] = {1.1f, 2.2f, 3.3f, 4.4f}; + lv2_atom_forge_vector(forge, sizeof(float), mapFeature.GetUrid(LV2_ATOM__Float), 4, vectorValues); + + { + LV2_Atom_Forge_Frame objFrame; + lv2_atom_forge_object(forge, &objFrame, 0, mapFeature.GetUrid(LV2_PATCH__Set)); + lv2_atom_forge_key(forge,mapFeature.GetUrid(LV2_PATCH__property)); + lv2_atom_forge_urid(forge,mapFeature.GetUrid("http://something.com/custom#prop")); + lv2_atom_forge_key(forge,mapFeature.GetUrid(LV2_PATCH__value)); + std::string path = "/var/pipdeal/test.wav"; + lv2_atom_forge_path(forge, path.c_str(),path.size()+1); + + lv2_atom_forge_pop(forge, &objFrame); + } + } + lv2_atom_forge_pop(forge, &frame); + + lv2_atom_forge_string(forge, "abcde\0", 6); + return atomBuffer; +} + +TEST_CASE("AtomConverter", "[atom_converter][Build][Dev]") +{ + cout << "=== AtomConverter Test ====" << endl; + + cout << " testTuple" << endl; + RoundTripTest(MakeTestTupleAtom()); + + cout << " simpleAtom" << endl; + RoundTripTest(MakeSimpleAtom()); +} diff --git a/src/AudioConfig.hpp b/src/AudioConfig.hpp index 7b57e1f..0acb67c 100644 --- a/src/AudioConfig.hpp +++ b/src/AudioConfig.hpp @@ -24,8 +24,13 @@ #pragma once +#ifndef JACK_HOST #define JACK_HOST 0 +#endif + +#ifndef ALSA_HOST #define ALSA_HOST 1 +#endif #if JACK_HOST && ALSA_HOST #error Choose either JACK or ALSA diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index cf1a42d..c893ad7 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -18,11 +18,13 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "AudioHost.hpp" +#include "util.hpp" #include "Lv2Log.hpp" #include "JackDriver.hpp" #include "AlsaDriver.hpp" +#include "AtomConverter.hpp" using namespace pipedal; @@ -177,6 +179,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost { private: IHost *pHost = nullptr; + LV2_Atom_Forge inputWriterForge; + + + std::mutex atomConverterMutex; + AtomConverter atomConverter; static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024; @@ -277,9 +284,9 @@ private: JackChannelSelection channelSelection; bool active = false; - std::shared_ptr currentPedalBoard; - std::vector> activePedalBoards; // pedalboards that have been sent to the audio queue. - Lv2PedalBoard *realtimeActivePedalBoard = nullptr; + std::shared_ptr currentPedalboard; + std::vector> activePedalboards; // pedalboards that have been sent to the audio queue. + Lv2Pedalboard *realtimeActivePedalboard = nullptr; std::vector midiLv2Buffers; @@ -297,18 +304,19 @@ private: { throw std::invalid_argument("Not an Lv2 Object"); } - return pHost->Lv2UriudToString(pAtom->body.otype); + return pHost->Lv2UridToString(pAtom->body.otype); } - void WriteAtom(json_writer &writer, LV2_Atom *pAtom); - std::string AtomToJson(uint8_t *pData) + std::string AtomToJson(const LV2_Atom *pAtom) { + std::lock_guard lock(atomConverterMutex); + json_variant vAtom = atomConverter.ToJson(pAtom); + std::stringstream s; json_writer writer(s); - LV2_Atom *pAtom = (LV2_Atom *)pData; - - WriteAtom(writer, pAtom); + + writer.write(vAtom); return s.str(); } @@ -325,7 +333,6 @@ private: return; isOpen = false; - StopReaderThread(); if (realtimeMonitorPortSubscriptions != nullptr) { @@ -341,9 +348,13 @@ private: audioDriver->Close(); + StopReaderThread(); + pHost->GetHostWorkerThread()->Close(); + + // release any pdealboards owned by the process thread. - this->activePedalBoards.resize(0); - this->realtimeActivePedalBoard = nullptr; + this->activePedalboards.resize(0); + this->realtimeActivePedalboard = nullptr; // clean up any realtime buffers that may have been lost in transit. // TODO: These should be lists, really. There may be multiple items in flight.. @@ -365,6 +376,7 @@ private: delete[] midiLv2Buffers[i]; } midiLv2Buffers.resize(0); + } void ZeroBuffer(float *buffer, size_t nframes) @@ -442,7 +454,7 @@ private: portSubscription.samplesToNextCallback += portSubscription.sampleRate; if (!portSubscription.waitingForAck) { - float value = realtimeActivePedalBoard->GetControlOutputValue( + float value = realtimeActivePedalboard->GetControlOutputValue( portSubscription.instanceIndex, portSubscription.portIndex); if (value != portSubscription.lastValue) @@ -459,7 +471,7 @@ private: } } - RealtimeParameterRequest *pParameterRequests = nullptr; + RealtimePatchPropertyRequest *pParameterRequests = nullptr; bool reEntered = false; void ProcessInputCommands() @@ -486,12 +498,12 @@ private: { SetControlValueBody body; realtimeReader.readComplete(&body); - this->realtimeActivePedalBoard->SetControlValue(body.effectIndex, body.controlIndex, body.value); + this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value); break; } case RingBufferCommand::ParameterRequest: { - RealtimeParameterRequest *pRequest = nullptr; + RealtimePatchPropertyRequest *pRequest = nullptr; realtimeReader.readComplete(&pRequest); // link to the list of parameter requests. @@ -562,7 +574,7 @@ private: { SetBypassBody body; realtimeReader.readComplete(&body); - this->realtimeActivePedalBoard->SetBypass(body.effectIndex, body.enabled); + this->realtimeActivePedalboard->SetBypass(body.effectIndex, body.enabled); break; } case RingBufferCommand::ReplaceEffect: @@ -572,8 +584,8 @@ private: if (body.effect != nullptr) { - auto oldValue = this->realtimeActivePedalBoard; - this->realtimeActivePedalBoard = body.effect; + auto oldValue = this->realtimeActivePedalboard; + this->realtimeActivePedalboard = body.effect; realtimeWriter.EffectReplaced(oldValue); @@ -612,7 +624,7 @@ private: { eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); - this->realtimeActivePedalBoard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged); + this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged); if (listenForMidiEvent) { if (event.size >= 3) @@ -756,8 +768,8 @@ private: bool processed = false; - Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard; - if (pedalBoard != nullptr) + Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard; + if (pedalboard != nullptr) { ProcessMidiInput(); float *inputBuffers[4]; @@ -789,15 +801,15 @@ private: if (buffersValid) { - pedalBoard->ResetAtomBuffers(); - pedalBoard->ProcessParameterRequests(pParameterRequests); + pedalboard->ResetAtomBuffers(); + pedalboard->ProcessParameterRequests(pParameterRequests); - processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); + processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); if (processed) { if (this->realtimeVuBuffers != nullptr) { - pedalBoard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes); + pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes); vuSamplesRemaining -= nframes; if (vuSamplesRemaining <= 0) @@ -811,14 +823,10 @@ private: processMonitorPortSubscriptions(nframes); } } - pedalBoard->GatherParameterRequests(pParameterRequests); + pedalboard->GatherPatchProperties(pParameterRequests); } } - // in = jack_port_get_buffer(input_port, nframes); - // out = jack_port_get_buffer(output_port, nframes); - // memcpy(out, in, - // sizeof(jack_default_audio_sample_t) * nframes); if (!processed) { ZeroOutputBuffers(nframes); @@ -841,7 +849,6 @@ private: throw; } } - public: AudioHostImpl(IHost *pHost) : inputRingBuffer(RING_BUFFER_SIZE), @@ -852,8 +859,11 @@ public: hostWriter(&this->inputRingBuffer), eventBufferUrids(pHost), pHost(pHost), - uris(pHost) + uris(pHost), + atomConverter(pHost->GetMapFeature()) { + realtimeAtomBuffer.resize(32*1024); + lv2_atom_forge_init(&inputWriterForge,pHost->GetMapFeature().GetMap()); #if JACK_HOST audioDriver = CreateJackDriver(this); @@ -870,6 +880,7 @@ public: delete audioDriver; } + virtual JackConfiguration GetServerConfiguration() { JackConfiguration result; @@ -893,6 +904,7 @@ public: realtimeWriter.AudioStopped(); } std::vector atomBuffer; + std::vector realtimeAtomBuffer; bool terminateThread; void ThreadProc() @@ -917,6 +929,7 @@ public: { Lv2Log::debug("Service thread priority successfully boosted."); } + SetThreadName("aout"); #else xxx; // TODO! #endif @@ -924,44 +937,41 @@ public: try { - struct timespec ts; - // ever 30 seconds, timeout check for and log any overruns. - int pollRateS = 30; - if (clock_gettime(CLOCK_REALTIME, &ts) == -1) - { - Lv2Log::error("clock_gettime failed!"); - return; - } - ts.tv_sec += pollRateS; uint64_t lastUnderrunCount = this->underruns; + using clock = std::chrono::steady_clock; + using clock_time = std::chrono::steady_clock::time_point; + using clock_duration = clock_time::duration; + + // check for overruns every 30 seconds. + clock_duration waitPeriod = + std::chrono::duration_cast(std::chrono::seconds(30)); + clock_time waitTime = std::chrono::steady_clock::now(); + while (true) { // wait for an event. // 0 -> ready. -1: timed out. -2: closing. - int result = hostReader.wait(ts); - if (result == -2) + auto result = hostReader.wait_until(sizeof(RingBufferCommand), waitTime); + if (result == RingBufferStatus::Closed) { return; } - else if (result == -1) + else if (result == RingBufferStatus::TimedOut) { // timeout. - ts.tv_sec += pollRateS; - uint64_t underruns = this->underruns; if (underruns != lastUnderrunCount) { if (underrunMessagesGiven < 60) // limit how much log file clutter we generate. { - Lv2Log::info("Jack - Underrun count: %lu", (unsigned long)underruns); + Lv2Log::info("Audio underrun count: %lu", (unsigned long)underruns); lastUnderrunCount = underruns; ++underrunMessagesGiven; } } - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += pollRateS; + waitTime += waitPeriod; } else { @@ -996,32 +1006,42 @@ public: } else if (command == RingBufferCommand::ParameterRequestComplete) { - RealtimeParameterRequest *pRequest = nullptr; + RealtimePatchPropertyRequest *pRequest = nullptr; hostReader.read(&pRequest); - std::shared_ptr currentpedalBoard; + std::shared_ptr currentpedalboard; { std::lock_guard guard(mutex); - currentPedalBoard = this->currentPedalBoard; + currentPedalboard = this->currentPedalboard; } while (pRequest != nullptr) { auto pNext = pRequest->pNext; - if (pRequest->errorMessage == nullptr && pRequest->responseLength != 0) + if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { - IEffect *pEffect = currentPedalBoard->GetEffect(pRequest->instanceId); - if (pEffect == nullptr) + if (pRequest->errorMessage == nullptr) { - pRequest->errorMessage = "Effect no longer available."; - } - else - { - pRequest->jsonResponse = AtomToJson(pRequest->response); + if (pRequest->GetSize() != 0) { + IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId); + if (pEffect == nullptr) + { + pRequest->errorMessage = "Effect no longer available."; + } + else + { + pRequest->jsonResponse = AtomToJson((LV2_Atom*)pRequest->GetBuffer()); + } + } else { + pRequest->errorMessage = "Plugin did not respond."; + } } } - pRequest->onJackRequestComplete(pRequest); + if (pRequest->onPatchRequestComplete) + { + pRequest->onPatchRequestComplete(pRequest); + } pRequest = pNext; } } @@ -1047,6 +1067,12 @@ public: } this->hostWriter.AckVuUpdate(); // please sir, can I have some more? } + else if (command == RingBufferCommand::Lv2StateChanged) + { + uint64_t instanceId; + hostReader.read(&instanceId); + this->pNotifyCallbacks->OnNotifyLv2StateChanged(instanceId); + } else if (command == RingBufferCommand::AtomOutput) { uint64_t instanceId; @@ -1059,12 +1085,35 @@ public: } hostReader.read(extraBytes, &(atomBuffer[0])); - IEffect *pEffect = currentPedalBoard->GetEffect(instanceId); + IEffect *pEffect = currentPedalboard->GetEffect(instanceId); if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput) { - std::string atomType = GetAtomObjectType(&atomBuffer[0]); - auto json = AtomToJson(&(atomBuffer[0])); - this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId, atomType, json); + LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0]; + if (atom->type == uris.atom_Object) + { + LV2_Atom_Object *obj = (LV2_Atom_Object*)atom; + if (obj->body.otype == uris.patch_Set) + { + // Get the property and value of the set message + const LV2_Atom *property = nullptr; + const LV2_Atom *value = nullptr; + + lv2_atom_object_get( + obj, + uris.patch_property, &property, + uris.patch_value, &value, + 0); + if (property != nullptr && value != nullptr && property->type == uris.atom_URID) + { + LV2_URID propertyUrid = ((LV2_Atom_URID*)property)->body; + if (pNotifyCallbacks->WantsAtomOutput(instanceId,propertyUrid)) + { + auto json = AtomToJson(value); + this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,propertyUrid,json); + } + } + } + } } } else if (command == RingBufferCommand::FreeVuSubscriptions) @@ -1083,7 +1132,7 @@ public: { EffectReplacedBody body; hostReader.read(&body); - OnActivePedalBoardReleased(body.oldEffect); + OnActivePedalboardReleased(body.oldEffect); } else if (command == RingBufferCommand::AudioStopped) { @@ -1218,45 +1267,45 @@ public: { pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest); } - void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard) + void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard) { - if (pPedalBoard) + if (pPedalboard) { - pPedalBoard->Deactivate(); + pPedalboard->Deactivate(); std::lock_guard guard(mutex); - for (auto it = activePedalBoards.begin(); it != activePedalBoards.end(); ++it) + for (auto it = activePedalboards.begin(); it != activePedalboards.end(); ++it) { - if ((*it).get() == pPedalBoard) + if ((*it).get() == pPedalboard) { // erase it, relinquishing shared_ptr ownership, usually deleting the object. - activePedalBoards.erase(it); + activePedalboards.erase(it); return; } } } } - virtual void SetPedalBoard(const std::shared_ptr &pedalBoard) + virtual void SetPedalboard(const std::shared_ptr &pedalboard) { std::lock_guard guard(mutex); - this->currentPedalBoard = pedalBoard; + this->currentPedalboard = pedalboard; if (active) { - pedalBoard->Activate(); - this->activePedalBoards.push_back(pedalBoard); - hostWriter.ReplaceEffect(pedalBoard.get()); + pedalboard->Activate(); + this->activePedalboards.push_back(pedalboard); + hostWriter.ReplaceEffect(pedalboard.get()); } } virtual void SetBypass(uint64_t instanceId, bool enabled) { std::lock_guard guard(mutex); - if (active && this->currentPedalBoard) + if (active && this->currentPedalboard) { // use indices not instance ids, so we can just do a straight array index on the audio thread. - auto index = currentPedalBoard->GetIndexOfInstanceId(instanceId); + auto index = currentPedalboard->GetIndexOfInstanceId(instanceId); if (index >= 0) { hostWriter.SetBypass((uint32_t)index, enabled); @@ -1267,15 +1316,15 @@ public: virtual void SetPluginPreset(uint64_t instanceId, const std::vector &values) { std::lock_guard guard(mutex); - if (active && this->currentPedalBoard) + if (active && this->currentPedalboard) { - auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId); + auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId); if (effectIndex != -1) { for (size_t i = 0; i < values.size(); ++i) { const ControlValue &value = values[i]; - int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, value.key()); + int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, value.key()); if (controlIndex != -1 && effectIndex != -1) { hostWriter.SetControlValue(effectIndex, controlIndex, value.value()); @@ -1288,11 +1337,11 @@ public: void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) { std::lock_guard guard(mutex); - if (active && this->currentPedalBoard) + if (active && this->currentPedalboard) { // use indices not instance ids, so we can just do a straight array index on the audio thread. - int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, symbol); - auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId); + int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, symbol); + auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId); if (controlIndex != -1 && effectIndex != -1) { @@ -1301,11 +1350,14 @@ public: } } + + + virtual void SetVuSubscriptions(const std::vector &instanceIds) { std::lock_guard guard(mutex); - if (active && this->currentPedalBoard) + if (active && this->currentPedalboard) { if (instanceIds.size() == 0) @@ -1319,13 +1371,13 @@ public: for (size_t i = 0; i < instanceIds.size(); ++i) { int64_t instanceId = instanceIds[i]; - auto effect = this->currentPedalBoard->GetEffect(instanceId); + auto effect = this->currentPedalboard->GetEffect(instanceId); if (!effect) { throw PiPedalStateException("Effect not found."); } - int index = this->currentPedalBoard->GetIndexOfInstanceId(instanceIds[i]); + int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]); vuConfig->enabledIndexes.push_back(index); VuUpdate v; v.instanceId_ = instanceId; @@ -1346,8 +1398,8 @@ public: { RealtimeMonitorPortSubscription result; result.subscriptionHandle = subscription.subscriptionHandle; - result.instanceIndex = this->currentPedalBoard->GetIndexOfInstanceId(subscription.instanceid); - IEffect *pEffect = this->currentPedalBoard->GetEffect(subscription.instanceid); + result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid); + IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid); result.portIndex = pEffect->GetControlIndex(subscription.key); result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval); @@ -1360,7 +1412,7 @@ public: { if (!active) return; - if (this->currentPedalBoard == nullptr) + if (this->currentPedalboard == nullptr) return; if (subscriptions.size() == 0) { @@ -1372,7 +1424,7 @@ public: for (size_t i = 0; i < subscriptions.size(); ++i) { - if (this->currentPedalBoard->GetEffect(subscriptions[i].instanceid) != nullptr) + if (this->currentPedalboard->GetEffect(subscriptions[i].instanceid) != nullptr) { pSubscriptions->subscriptions.push_back( MakeRealtimeSubscription(subscriptions[i])); @@ -1383,6 +1435,10 @@ public: } private: + + virtual void UpdatePluginStates(Pedalboard& pedalboard); + void UpdatePluginState(PedalboardItem &pedalboardItem); + class RestartThread { AudioHostImpl *this_; @@ -1478,12 +1534,12 @@ public: } } - virtual void getRealtimeParameter(RealtimeParameterRequest *pParameterRequest) + virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) { if (!active) { pParameterRequest->errorMessage = "Not active."; - pParameterRequest->onJackRequestComplete(pParameterRequest); + pParameterRequest->onPatchRequestComplete(pParameterRequest); return; } this->hostWriter.ParameterRequest(pParameterRequest); @@ -1560,145 +1616,6 @@ static std::string UriToFieldName(const std::string &uri) return uri.substr(pos + 1); } -void AudioHostImpl::WriteAtom(json_writer &writer, LV2_Atom *pAtom) -{ - if (pAtom->type == uris.atom_Blank) - { - writer.write_raw("null"); - } - else if (pAtom->type == uris.atom_float) - { - writer.write( - ((LV2_Atom_Float *)pAtom)->body); - } - else if (pAtom->type == uris.atom_Int) - { - writer.write( - ((LV2_Atom_Int *)pAtom)->body); - } - else if (pAtom->type == uris.atom_Long) - { - writer.write( - ((LV2_Atom_Long *)pAtom)->body); - } - else if (pAtom->type == uris.atom_Double) - { - writer.write( - ((LV2_Atom_Double *)pAtom)->body); - } - else if (pAtom->type == uris.atom_Bool) - { - writer.write( - ((LV2_Atom_Bool *)pAtom)->body); - } - else if (pAtom->type == uris.atom_String) - { - const char *p = (((const char *)pAtom) + sizeof(LV2_Atom_String)); - writer.write( - p); - } - else if (pAtom->type == uris.atom_Vector) - { - LV2_Atom_Vector *pVector = (LV2_Atom_Vector *)pAtom; - writer.start_array(); - { - size_t n = (pAtom->size - sizeof(pVector->body)) / pVector->body.child_size; - char *pItems = ((char *)pAtom) + sizeof(LV2_Atom_Vector); - if (pVector->body.child_type == uris.atom_float) - { - float *p = (float *)pItems; - for (size_t i = 0; i < n; ++i) - { - if (i != 0) - writer.write_raw(","); - writer.write(*p++); - } - } - else if (pVector->body.child_type == uris.atom_Int) - { - int32_t *p = (int32_t *)pItems; - for (size_t i = 0; i < n; ++i) - { - if (i != 0) - writer.write_raw(","); - writer.write(*p++); - } - } - else if (pVector->body.child_type == uris.atom_Long) - { - int64_t *p = (int64_t *)pItems; - for (size_t i = 0; i < n; ++i) - { - if (i != 0) - writer.write_raw(","); - writer.write(*p++); - } - } - else if (pVector->body.child_type == uris.atom_Double) - { - double *p = (double *)pItems; - for (size_t i = 0; i < n; ++i) - { - if (i != 0) - writer.write_raw(","); - writer.write(*p++); - } - } - else if (pVector->body.child_type == uris.atom_Bool) - { - bool *p = (bool *)pItems; - for (size_t i = 0; i < n; ++i) - { - if (i != 0) - writer.write_raw(","); - writer.write(*p++); - } - } - } - writer.end_array(); - } - else if (pAtom->type == uris.atom_Object) - { - writer.start_object(); - - const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom; - bool firstMember = true; - if (obj->body.id != 0) - { - std::string id = pHost->Lv2UriudToString(obj->body.id); - writer.write_member("id", id.c_str()); - firstMember = false; - } - - if (obj->body.otype != 0) - { - std::string type = pHost->Lv2UriudToString(obj->body.otype); - writer.write_member("lv2Type", type.c_str()); - if (!firstMember) - { - writer.write_raw(","); - } - firstMember = false; - } - - LV2_ATOM_OBJECT_FOREACH(obj, prop) - { - if (!firstMember) - { - writer.write_raw(","); - } - firstMember = false; - std::string key = pHost->Lv2UriudToString(prop->key); - key = UriToFieldName(key); - writer.write(key); - writer.write_raw(": "); - LV2_Atom *value = &(prop->value); - WriteAtom(writer, value); - } - writer.end_object(); - } -} - void AudioHostImpl::SetSystemMidiBindings(const std::vector&bindings) { for (auto i = bindings.begin(); i != bindings.end(); ++i) @@ -1719,6 +1636,30 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost) return new AudioHostImpl(pHost); } +void AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard) +{ + auto pedalboardItems = pedalboard.GetAllPlugins(); + for (PedalboardItem* item : pedalboardItems) + { + if (!item->isSplit()) + { + UpdatePluginState(*item); + } + } + +} +void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem) +{ + IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId()); + if (effect != nullptr) + { + effect->GetLv2State(const_cast(&pedalboardItem.lv2State())); + } +} + + + + JSON_MAP_BEGIN(JackHostStatus) JSON_MAP_REFERENCE(JackHostStatus, active) JSON_MAP_REFERENCE(JackHostStatus, errorMessage) diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index 616e1cd..7350f91 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -21,13 +21,15 @@ #include "JackConfiguration.hpp" -#include "Lv2PedalBoard.hpp" +#include "Lv2Pedalboard.hpp" #include "VuUpdate.hpp" #include "json.hpp" #include "AudioHost.hpp" #include "JackServerSettings.hpp" #include #include "PiPedalAlsa.hpp" +#include "Promise.hpp" +#include "json_variant.hpp" namespace pipedal { @@ -45,39 +47,86 @@ public: int64_t subscriptionHandle; float value; }; -class RealtimeParameterRequest { +class RealtimePatchPropertyRequest { public: int64_t clientId; int64_t instanceId; LV2_URID uridUri; - std::function onJackRequestComplete; + enum class RequestType { + PatchGet, + PatchSet + }; + RequestType requestType; + + std::function onPatchRequestComplete; std::function onSuccess; std::function onError; const char*errorMessage = nullptr; - int responseLength = 0; - uint8_t response[2048]; std::string jsonResponse; - RealtimeParameterRequest *pNext = nullptr; + RealtimePatchPropertyRequest *pNext = nullptr; + + void SetSize(size_t size) + { + responseLength = size; + if (responseLength > sizeof(atomBuffer)) + { + longAtomBuffer.resize(size); + } + } + size_t GetSize() const { return responseLength; } + uint8_t *GetBuffer() { + if (responseLength > sizeof(atomBuffer)) + { + return &(longAtomBuffer[0]); + } + return atomBuffer; + } +private: + int responseLength = 0; + uint8_t atomBuffer[2048]; + std::vector longAtomBuffer; public: - RealtimeParameterRequest( - std::function onJackRequestComplete_, + RealtimePatchPropertyRequest( + std::function onPatchRequestcomplete_, int64_t clientId_, int64_t instanceId_, LV2_URID uridUri_, std::function onSuccess_, - std::function onError_) - : onJackRequestComplete(onJackRequestComplete_), + std::function onError_ + ) + : onPatchRequestComplete(onPatchRequestcomplete_), clientId(clientId_), instanceId(instanceId_), uridUri(uridUri_), onSuccess(onSuccess_), onError(onError_) { - + requestType = RequestType::PatchGet; + } + RealtimePatchPropertyRequest( + std::function onPatchRequestcomplete_, + int64_t clientId_, + int64_t instanceId_, + LV2_URID uridUri_, + LV2_Atom*atomValue, + std::function onSuccess_, + std::function onError_ + ) + : onPatchRequestComplete(onPatchRequestcomplete_), + clientId(clientId_), + instanceId(instanceId_), + uridUri(uridUri_), + onSuccess(onSuccess_), + onError(onError_) + { + requestType = RequestType::PatchSet; + size_t size = atomValue->size+ sizeof(LV2_Atom); + SetSize(size); + memcpy(GetBuffer(),atomValue,size); } }; @@ -95,11 +144,13 @@ public: class IAudioHostCallbacks { public: + virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0; virtual void OnNotifyVusSubscription(const std::vector & updates) = 0; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0; virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0; - virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0; + virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0; + virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0; virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0; virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0; @@ -143,6 +194,8 @@ public: virtual void SetListenForMidiEvent(bool listen) = 0; virtual void SetListenForAtomOutput(bool listen) = 0; + virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0; + virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0; virtual void Close() = 0; @@ -151,7 +204,7 @@ public: virtual JackConfiguration GetServerConfiguration() = 0; - virtual void SetPedalBoard(const std::shared_ptr &pedalBoard) = 0; + virtual void SetPedalboard(const std::shared_ptr &pedalboard) = 0; virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0; virtual void SetPluginPreset(uint64_t instanceId, const std::vector & values) = 0; @@ -164,12 +217,11 @@ public: virtual void SetSystemMidiBindings(const std::vector&bindings) = 0; - virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0; + virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest*pParameterRequest) = 0; virtual void AckMidiProgramRequest(uint64_t requestId) = 0; virtual JackHostStatus getJackStatus() = 0; - }; diff --git a/src/AutoLilvNode.cpp b/src/AutoLilvNode.cpp index ff8d1b8..00dd7e3 100644 --- a/src/AutoLilvNode.cpp +++ b/src/AutoLilvNode.cpp @@ -23,7 +23,7 @@ */ #include "AutoLilvNode.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" using namespace pipedal; diff --git a/src/AutoLilvNode.hpp b/src/AutoLilvNode.hpp index e002f56..684f48d 100644 --- a/src/AutoLilvNode.hpp +++ b/src/AutoLilvNode.hpp @@ -32,24 +32,23 @@ 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(const LilvNode *node) + : node(const_cast(node)) + { + isConst = true; + } AutoLilvNode(LilvNode *node) : node(node) { + isConst = false; } - ~AutoLilvNode() { Free(); } - operator const LilvNode *() { return this->node; @@ -58,18 +57,30 @@ namespace pipedal { return this->node != nullptr; } - LilvNode*&Get() { return node; } + const LilvNode *Get() { return node; } - AutoLilvNode&operator=(LilvNode*node) { - Free(); - this->node = node; - return *this; - } - AutoLilvNode&operator=(AutoLilvNode&&other) { - std::swap(this->node,other.node); + AutoLilvNode &operator=(LilvNode *node) + { + Free(); + this->node = node; return *this; } + operator LilvNode**() { + Free(); + this->isConst = false; + return &node; + } + operator const LilvNode**() { + Free(); + this->isConst = true; + return (const LilvNode**)&node; + } + AutoLilvNode &operator=(AutoLilvNode &&other) + { + std::swap(this->node, other.node); + return *this; + } float AsFloat(float defaultValue = 0); int AsInt(int defaultValue = 0); @@ -79,11 +90,13 @@ namespace pipedal void Free() { - if (node != nullptr) + if (node != nullptr && !isConst) lilv_node_free(node); node = nullptr; } - + private: + LilvNode *node = nullptr; + bool isConst = true; }; }; \ No newline at end of file diff --git a/src/Banks.hpp b/src/Banks.hpp index e21da97..def9ab1 100644 --- a/src/Banks.hpp +++ b/src/Banks.hpp @@ -20,7 +20,7 @@ #pragma once #include "json.hpp" -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include "PiPedalException.hpp" namespace pipedal { @@ -75,7 +75,7 @@ public: class BankFileEntry { int64_t instanceId_; - PedalBoard preset_; + Pedalboard preset_; public: GETTER_SETTER(instanceId); GETTER_SETTER_REF(preset); @@ -122,7 +122,7 @@ public: } this->nextInstanceId_ = t; } - int64_t addPreset(const PedalBoard&preset, int64_t afterItem = -1) + int64_t addPreset(const Pedalboard&preset, int64_t afterItem = -1) { if (hasName(preset.name())) { @@ -213,8 +213,8 @@ public: } else { // zero length? We can never have a zero-length bank. // Add a default preset and make it the selected preset. - PedalBoard pedalBoard = PedalBoard::MakeDefault(); - this->addPreset(pedalBoard); + Pedalboard pedalboard = Pedalboard::MakeDefault(); + this->addPreset(pedalboard); newSelection = presets_[0]->instanceId(); } if (instanceId == this->selectedPreset_) diff --git a/src/Base64.hpp b/src/Base64.hpp new file mode 100644 index 0000000..c356861 --- /dev/null +++ b/src/Base64.hpp @@ -0,0 +1,144 @@ +#ifndef _MACARON_BASE64_H_ +#define _MACARON_BASE64_H_ + +/** + * The MIT License (MIT) + * Copyright (c) 2016 tomykaira + * + * 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. + */ + +/* + Converted to take uint8_t* input/output instead of std::string. +*/ +#include +#include +#include +#include + +namespace macaron +{ + + class Base64 + { + public: + static std::string Encode(const std::vector &data) + { + return Encode(data.size(),&(data[0])); + } + static std::string Encode(size_t size, const uint8_t *data) + { + static constexpr char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/'}; + + size_t in_len = size; + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = const_cast(ret.c_str()); + + for (i = 0; i < in_len - 2; i += 3) + { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)]; + *p++ = sEncodingTable[data[i + 2] & 0x3F]; + } + if (i < in_len) + { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + if (i == (in_len - 1)) + { + *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; + *p++ = '='; + } + else + { + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + return ret; + } + + static std::vector Decode(const std::string &input) + { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; + + size_t in_len = input.size(); + if (in_len % 4 != 0) + throw std::invalid_argument("Input data size is not a multiple of 4"); + + size_t out_len = in_len / 4 * 3; + if (input[in_len - 1] == '=') + out_len--; + if (input[in_len - 2] == '=') + out_len--; + + std::vector out(out_len); + + for (size_t i = 0, j = 0; i < in_len;) + { + uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) + out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 0 * 8) & 0xFF; + } + return out; + } + }; + +} + +#endif /* _MACARON_BASE64_H_ */ \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88c86e3..bfdbfef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,10 @@ include(FindPkgConfig) # Disabled, pending approval of Steinberg VST3 License. # Do not enable unless you have non-GPL3 access to the Steinberg # SDK. +# +# Plus, there don't seem to be any VST3 plugins for linux, as it +# turns out. :-/ Status: not completely implemented due to lack +# of test targets. Deprecated, and non-functional. ################################################################# set (ENABLE_VST3 0) @@ -137,10 +141,20 @@ else() endif() set (PIPEDAL_SOURCES - AutoLilvNode.hpp - AutoLilvNode.cpp + util.hpp util.cpp PiPedalUI.hpp PiPedalUI.cpp + MapPathFeature.hpp + MapPathFeature.cpp + IHost.hpp + StateInterface.hpp + StateInterface.cpp + AtomConverter.hpp + AtomConverter.cpp + AtomBuffer.hpp + + AutoLilvNode.hpp + AutoLilvNode.cpp RtInversionGuard.hpp CpuUse.hpp CpuUse.cpp P2pConfigFiles.hpp @@ -162,13 +176,13 @@ set (PIPEDAL_SOURCES RequestHandler.hpp json.cpp json.hpp json_variant.hpp json_variant.cpp - Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp + Scratch.cpp PluginHost.hpp PluginHost.cpp PluginType.hpp PluginType.cpp Lv2Log.hpp Lv2Log.cpp PiPedalSocket.hpp PiPedalSocket.cpp PiPedalVersion.hpp PiPedalVersion.cpp PiPedalModel.hpp PiPedalModel.cpp - PedalBoard.hpp PedalBoard.cpp + Pedalboard.hpp Pedalboard.cpp Presets.hpp Presets.cpp Storage.hpp Storage.cpp Banks.hpp Banks.cpp @@ -176,7 +190,7 @@ set (PIPEDAL_SOURCES JackConfiguration.hpp JackConfiguration.cpp defer.hpp Lv2Effect.cpp Lv2Effect.hpp - Lv2PedalBoard.cpp Lv2PedalBoard.hpp + Lv2Pedalboard.cpp Lv2Pedalboard.hpp BufferPool.hpp SplitEffect.hpp SplitEffect.cpp RingBufferReader.hpp @@ -219,7 +233,7 @@ include_directories( ${pipedald_SOURCE_DIR}/. ../build/src) set (PIPEDAL_INCLUDES ${LV2DEV_INCLUDE_DIRS} - ${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS} + ${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${VST3_INCLUDES} . ) @@ -261,7 +275,10 @@ target_link_libraries(pipedald PRIVATE ################################# add_executable(pipedaltest testMain.cpp jsonTest.cpp - AlsaDriverTest.cpp + json_variant.cpp + json_variant.hpp + + AlsaDriverTest.cpp AvahiServiceTest.cpp WifiChannelsTest.cpp PiPedalAlsaTest.cpp @@ -277,6 +294,36 @@ add_executable(pipedaltest testMain.cpp MemDebug.hpp ) +target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES} +) + +add_executable(jsonTest + testMain.cpp + jsonTest.cpp + json.hpp + json.cpp + json_variant.cpp + json_variant.hpp + MapFeature.cpp + MapFeature.hpp + HtmlHelper.cpp + HtmlHelper.hpp + AtomConverter.hpp + AtomConverter.cpp + AtomConverterTest.cpp + AtomBuffer.hpp + Promise.hpp + PromiseTest.cpp +) +target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES} + ) + +target_link_libraries(jsonTest PRIVATE pthread) +add_test(NAME jsonTest COMMAND jsonTest) + + + + include_directories( ${pipedald_SOURCE_DIR}/. ../build/src) if(${USE_PCH}) @@ -539,6 +586,8 @@ target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs ) add_executable(pipedal_latency_test + util.hpp + util.cpp PrettyPrinter.hpp CommandLineParser.hpp PiLatencyMain.cpp diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 2b1f4da..e1bcba4 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -191,7 +191,7 @@ void StartService(bool excludeShutdownService = false) } #if INSTALL_JACK_SERVICE JackServerSettings serverSettings; - serverSettings.ReadJackConfiguration(); + serverSettings.ReadJackDaemonConfiguration(); if (serverSettings.IsValid()) { if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS) diff --git a/src/IEffect.hpp b/src/IEffect.hpp index 9472b91..fd82b68 100644 --- a/src/IEffect.hpp +++ b/src/IEffect.hpp @@ -20,10 +20,13 @@ #pragma once #include +#include + namespace pipedal { - class RealtimeParameterRequest; + class RealtimePatchPropertyRequest; class RealtimeRingBufferWriter; + class Lv2PluginState; class IEffect { public: @@ -35,15 +38,11 @@ namespace pipedal { virtual void SetBypass(bool enable) = 0; virtual float GetOutputControlValue(int controlIndex) const = 0; - - virtual int GetNumberOfInputAudioPorts() const = 0; virtual int GetNumberOfOutputAudioPorts() const = 0; virtual float *GetAudioInputBuffer(int index) const = 0; virtual float *GetAudioOutputBuffer(int index) const = 0; virtual void ResetAtomBuffers() = 0; - virtual void RequestParameter(LV2_URID uridUri) = 0; - virtual void GatherParameter(RealtimeParameterRequest*pRequest) = 0; //virtual std::string AtomToJson(uint8_t*pAtom) = 0; //virtual std::string GetAtomObjectType(uint8_t*pData) = 0; @@ -59,5 +58,6 @@ namespace pipedal { virtual void Deactivate() = 0; virtual bool IsVst3() const = 0; + virtual bool GetLv2State(Lv2PluginState*state) = 0; }; } //namespace \ No newline at end of file diff --git a/src/IHost.hpp b/src/IHost.hpp new file mode 100644 index 0000000..39ef5d1 --- /dev/null +++ b/src/IHost.hpp @@ -0,0 +1,53 @@ +// 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. +#pragma once + +#include + +namespace pipedal { + class MapFeature; + class PedalboardItem; + class Lv2PluginInfo; + class IEffect; + class HostWorkerThread; + + class IHost + { + public: + virtual LilvWorld *getWorld() = 0; + virtual LV2_URID_Map *GetLv2UridMap() = 0; + virtual MapFeature&GetMapFeature() = 0; + virtual LV2_URID GetLv2Urid(const char *uri) = 0; + virtual std::string Lv2UridToString(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 std::shared_ptr GetHostWorkerThread() = 0; + + virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0; + }; +} \ No newline at end of file diff --git a/src/JackConfiguration.cpp b/src/JackConfiguration.cpp index be4f12c..06939ac 100644 --- a/src/JackConfiguration.cpp +++ b/src/JackConfiguration.cpp @@ -297,6 +297,7 @@ JSON_MAP_END() JSON_MAP_BEGIN(JackConfiguration) JSON_MAP_REFERENCE(JackConfiguration,isValid) + JSON_MAP_REFERENCE(JackConfiguration,isOnboarding) JSON_MAP_REFERENCE(JackConfiguration,isRestarting) JSON_MAP_REFERENCE(JackConfiguration,errorStatus) JSON_MAP_REFERENCE(JackConfiguration,sampleRate) diff --git a/src/JackConfiguration.hpp b/src/JackConfiguration.hpp index 10b9225..d247219 100644 --- a/src/JackConfiguration.hpp +++ b/src/JackConfiguration.hpp @@ -37,6 +37,7 @@ namespace pipedal std::string errorStatus_; bool isRestarting_ = false; + bool isOnboarding_ = true; uint32_t sampleRate_ = 48000; size_t blockLength_ = 1024; @@ -54,13 +55,16 @@ namespace pipedal void JackInitialize(); // from jack server instance. ~JackConfiguration(); bool isValid() const { return isValid_;} + bool isOnboarding() const { return isOnboarding_;} + void isOnboarding(bool value) { isOnboarding_ = value; } bool isRestarting() const { return isRestarting_; } - void SetIsRestarting(bool value) { isRestarting_ = value; } - uint32_t GetSampleRate() const { return sampleRate_; } - size_t GetBlockLength() const { return blockLength_; } - size_t GetMidiBufferSize() const { return midiBufferSize_;} - double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; } - void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; } + void isRestarting(bool value) { isRestarting_ = value; } + + uint32_t sampleRate() const { return sampleRate_; } + size_t blockLength() const { return blockLength_; } + size_t midiBufferSize() const { return midiBufferSize_;} + double maxAllowedMidiDelta() const { return maxAllowedMidiDelta_; } + void setErrorStatus(const std::string&message) { this->errorStatus_ = message; } const std::vector &GetInputAudioPorts() const { return inputAudioPorts_; } const std::vector &GetOutputAudioPorts() const { return outputAudioPorts_; } diff --git a/src/JackServerSettings.cpp b/src/JackServerSettings.cpp index 15115d7..93c2de1 100644 --- a/src/JackServerSettings.cpp +++ b/src/JackServerSettings.cpp @@ -108,8 +108,11 @@ static std::int32_t GetJackArg(const std::vector &args, const std:: } -void JackServerSettings::ReadJackConfiguration() +void JackServerSettings::ReadJackDaemonConfiguration() { + #if !JACK_HOST + return; + #endif this->valid_ = false; std::string lastLine; @@ -155,7 +158,7 @@ void JackServerSettings::ReadJackConfiguration() } } -void JackServerSettings::Write() +void JackServerSettings::WriteDaemonConfig() { #if JACK_HOST this->valid_ = false; @@ -240,6 +243,7 @@ void JackServerSettings::Write() JSON_MAP_BEGIN(JackServerSettings) JSON_MAP_REFERENCE(JackServerSettings, valid) +JSON_MAP_REFERENCE(JackServerSettings, isOnboarding) JSON_MAP_REFERENCE(JackServerSettings, rebootRequired) JSON_MAP_REFERENCE(JackServerSettings, isJackAudio) JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index 61b31aa..1e052e0 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -28,6 +28,7 @@ namespace pipedal class JackServerSettings { bool valid_ = false; + bool isOnboarding_ = true; bool isJackAudio_ = JACK_HOST ? true : false; bool rebootRequired_ = false; std::string alsaDevice_; @@ -44,7 +45,8 @@ namespace pipedal alsaDevice_(alsaInputDevice), sampleRate_(sampleRate), bufferSize_(bufferSize), - numberOfBuffers_(numberOfBuffers) + numberOfBuffers_(numberOfBuffers), + isOnboarding_(false) { } @@ -53,23 +55,28 @@ namespace pipedal uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; } const std::string &GetAlsaInputDevice() const { return alsaDevice_; } - void ReadJackConfiguration(); + void ReadJackDaemonConfiguration(); bool IsValid() const { return valid_; } - JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers) - { - this->valid_ = true; - this->rebootRequired_ = true; - this->sampleRate_ = sampleRate; - this->bufferSize_ = bufferSize; - this->numberOfBuffers_ = numberOfBuffers; - } - void Write(); // requires root perms. + // JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers) + // { + // this->valid_ = true; + // this->rebootRequired_ = true; + // this->sampleRate_ = sampleRate; + // this->bufferSize_ = bufferSize; + // this->numberOfBuffers_ = numberOfBuffers; + // } + void WriteDaemonConfig(); // requires root perms. void SetRebootRequired(bool value) { rebootRequired_ = value; } + void SetIsOnboarding(bool value) + { + isOnboarding_ = value; + } + bool Equals(const JackServerSettings &other) { return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index f172e16..5a63047 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -32,6 +32,7 @@ #include "lv2/log.lv2/logger.h" #include "lv2/uri-map.lv2/uri-map.h" #include "lv2/atom.lv2/forge.h" +#include "lv2/state.lv2/state.h" #include "lv2/worker.lv2/worker.h" #include "lv2/patch.lv2/patch.h" #include "lv2/parameters.lv2/parameters.h" @@ -48,14 +49,14 @@ const float BYPASS_TIME_S = 0.1f; Lv2Effect::Lv2Effect( IHost *pHost_, const std::shared_ptr &info_, - const PedalBoardItem &pedalBoardItem) - : pHost(pHost_), pInstance(nullptr), info(info_), uris(pHost) + const PedalboardItem &pedalboardItem) + : pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost) { auto pWorld = pHost_->getWorld(); this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S); - this->bypass = pedalBoardItem.isEnabled(); + this->bypass = pedalboardItem.isEnabled(); // initialize the atom forge used on the realtime thread. LV2_URID_Map *map = this->pHost->GetLv2UridMap(); @@ -64,7 +65,7 @@ Lv2Effect::Lv2Effect( const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld); - auto uriNode = lilv_new_uri(pWorld, pedalBoardItem.uri().c_str()); + auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str()); const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode); lilv_node_free(uriNode); @@ -76,7 +77,7 @@ Lv2Effect::Lv2Effect( } this->work_schedule_feature = nullptr; - if (info_->hasExtension(LV2_WORKER__interface)) + if (true) //info_->hasExtension(LV2_WORKER__interface)) { // insane implementation. :-( LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule)); @@ -99,29 +100,36 @@ Lv2Effect::Lv2Effect( } catch (const std::exception &e) { this->pInstance = nullptr; - throw PiPedalException(SS("Plugin threw an exception: " << e.what())); + throw PiPedalException(SS("Plugin threw an exception: " << e.what() << " '" << info_->name() << "'")); } this->pInstance = pInstance; if (this->pInstance == nullptr) { - throw PiPedalException("Failed to create plugin."); + throw PiPedalException(SS("Failed to create plugin \'" << info_->name() << "\'.")); } - if (info_->hasExtension(LV2_WORKER__interface)) + const LV2_Worker_Interface *worker_interface = + (const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance, + LV2_WORKER__interface); + if (worker_interface) { + this->worker = std::make_unique(pHost->GetHostWorkerThread(), pInstance, worker_interface); + } + const LV2_State_Interface*state_interface = + (const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance, + LV2_STATE__interface); + + if (state_interface) { - const LV2_Worker_Interface *worker_interface = - (const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance, - LV2_WORKER__interface); - this->worker = std::make_unique(pInstance, worker_interface); + this->stateInterface = std::make_unique(pHost,pInstance,state_interface); } - this->instanceId = pedalBoardItem.instanceId(); + this->instanceId = pedalboardItem.instanceId(); this->controlValues.resize(info->ports().size()); // Copy default pedalboard settings. - for (auto i = pedalBoardItem.controlValues().begin(); i != pedalBoardItem.controlValues().end(); ++i) + for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i) { auto &v = (*i); int index = GetControlIndex(v.key()); @@ -132,6 +140,16 @@ Lv2Effect::Lv2Effect( } PreparePortIndices(); ConnectControlPorts(); + + RestoreState(pedalboardItem); +} +void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem) +{ + // Restore state if present. + if (this->stateInterface) + { + this->stateInterface->Restore(pedalboardItem.lv2State()); + } } void Lv2Effect::ConnectControlPorts() @@ -262,6 +280,7 @@ Lv2Effect::~Lv2Effect() { if (worker) { + worker->Close(); worker = nullptr; // delete the worker first! } if (pInstance) @@ -281,8 +300,11 @@ void Lv2Effect::Activate() this->AssignUnconnectedPorts(); lilv_instance_activate(pInstance); this->BypassTo(this->bypass ? 1.0f : 0.0f); + + } + void Lv2Effect::AssignUnconnectedPorts() { for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i) @@ -332,6 +354,10 @@ void Lv2Effect::AssignUnconnectedPorts() } void Lv2Effect::Deactivate() { + if (worker) + { + worker->Close(); + } lilv_instance_deactivate(pInstance); } @@ -441,7 +467,7 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe } } - RelayOutputMessages(this->instanceId,realtimeRingBufferWriter); + RelayPatchSetMessages(this->instanceId,realtimeRingBufferWriter); } LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle, @@ -463,13 +489,13 @@ void Lv2Effect::ResetInputAtomBuffer(char *data) { BufferHeader *header = (BufferHeader *)data; header->size = sizeof(LV2_Atom_Sequence_Body); - header->type = uris.atom_Sequence; + header->type = urids.atom__Sequence; } void Lv2Effect::ResetOutputAtomBuffer(char *data) { BufferHeader *header = (BufferHeader *)data; header->size = pHost->GetAtomBufferSize() - 8; - header->type = uris.atom_Chunk; + header->type = urids.atom__Chunk; } void Lv2Effect::BypassTo(float targetValue) @@ -509,24 +535,41 @@ void Lv2Effect::ResetAtomBuffers() // Start a sequence in the notify input port. - lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, uris.unitsFrame); + lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, urids.units__frame); } } -void Lv2Effect::RequestParameter(LV2_URID uridUri) +void Lv2Effect::RequestPatchProperty(LV2_URID uridUri) { lv2_atom_forge_frame_time(&inputForgeRt, 0); LV2_Atom_Forge_Frame objectFrame; LV2_Atom_Forge_Ref set = - lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, uris.patch_Get); + lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Get); - lv2_atom_forge_key(&inputForgeRt, uris.patch_property); + lv2_atom_forge_key(&inputForgeRt, urids.patch__property); lv2_atom_forge_urid(&inputForgeRt, uridUri); lv2_atom_forge_pop(&inputForgeRt, &objectFrame); } +void Lv2Effect::SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value) +{ + lv2_atom_forge_frame_time(&inputForgeRt, 0); -void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) + LV2_Atom_Forge_Frame objectFrame; + LV2_Atom_Forge_Ref set = + lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Set); + { + lv2_atom_forge_key(&inputForgeRt, urids.patch__property); + lv2_atom_forge_urid(&inputForgeRt, uridUri); + lv2_atom_forge_key(&inputForgeRt, urids.patch__value); + lv2_atom_forge_write(&inputForgeRt,value,size); + } + + lv2_atom_forge_pop(&inputForgeRt, &objectFrame); +} + + +void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) { LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); if (controlOutput == nullptr) @@ -534,6 +577,7 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter return; } + bool stateChanged = false; LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev) { @@ -542,63 +586,73 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) { const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; - if (obj->body.otype != uris.patch_Set) // patch_Set is handled elsewhere. + if (obj->body.otype == urids.state__StateChanged) + { + stateChanged = true; + } else if (obj->body.otype == urids.patch__Set) // patch_Set is handled elsewhere. { realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj); } } + if (stateChanged) + { + realtimeRingBufferWriter->Lv2StateChanged(instanceId); + } } } -void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest) +void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) { - LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); - LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev) + if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { - - // frame_offset = ev->time.frames; // not really interested. - - if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) + LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); + if (controlInput == nullptr) { - const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; - if (obj->body.otype == uris.patch_Set) + return; + } + LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev) + { + + // frame_offset = ev->time.frames; // not really interested. + + if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) { - // Get the property and value of the set message - const LV2_Atom *property = NULL; - const LV2_Atom *value = NULL; + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + if (obj->body.otype == urids.patch__Set) + { + // Get the property and value of the set message + const LV2_Atom *property = NULL; + const LV2_Atom *value = NULL; - lv2_atom_object_get( - obj, - uris.patch_property, &property, - uris.patch_value, &value, - 0); + lv2_atom_object_get( + obj, + urids.patch__property, &property, + urids.patch__value, &value, + 0); - if (!property) - { - } - else if (property->type != uris.atom_URID) - { - } - else - { - LV2_URID key = ((const LV2_Atom_URID *)property)->body; - if (key == pRequest->uridUri) + if (property && property->type == urids.atom__URID && value) { - int atom_size = value->size + sizeof(LV2_Atom); - if (atom_size > sizeof(pRequest->response)) + LV2_URID key = ((const LV2_Atom_URID *)property)->body; + if (key == pRequest->uridUri) { - pRequest->errorMessage = "Response is too large."; - } else { - pRequest->responseLength = atom_size; - memcpy(pRequest->response,value,atom_size); + int atom_size = value->size + sizeof(LV2_Atom); + pRequest->SetSize(atom_size); + memcpy(pRequest->GetBuffer(),value,atom_size); + break; } - break; } } } } } } +bool Lv2Effect::GetLv2State(Lv2PluginState*state) +{ + if (!this->stateInterface) return false; + + *state = this->stateInterface->Save(); + return true; +} diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index 97e64cc..99ce8fa 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -20,8 +20,8 @@ #pragma once -#include "PiPedalHost.hpp" -#include "PedalBoard.hpp" +#include "PluginHost.hpp" +#include "Pedalboard.hpp" #include #include "BufferPool.hpp" @@ -32,6 +32,8 @@ #include "lv2/log.lv2/logger.h" #include "lv2/lv2plug.in/ns/extensions/units/units.h" #include "lv2/atom.lv2/forge.h" +#include "AtomBuffer.hpp" +#include "StateInterface.hpp" namespace pipedal @@ -42,6 +44,12 @@ namespace pipedal class Lv2Effect : public IEffect { private: + + std::unique_ptr stateInterface; + void RestoreState(const PedalboardItem&pedalboardItem); + + std::map patchPropertyPrototypes; + IHost *pHost = nullptr; int numberOfInputs = 0; int numberOfOutputs = 0; @@ -73,6 +81,7 @@ namespace pipedal virtual std::string GetUri() const { return info->uri(); } std::vector realtimePortInfo; + void PreparePortIndices(); void ConnectControlPorts(); void AssignUnconnectedPorts(); @@ -83,68 +92,62 @@ namespace pipedal LV2_Atom_Forge outputForgeRt; - class Uris + class Urids { public: - Uris(IHost *pHost) + Urids(IHost *pHost) { - atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank); - atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path); - atom_float = pHost->GetLv2Urid(LV2_ATOM__Float); - atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double); - atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int); - atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long); - atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool); - atom_String = pHost->GetLv2Urid(LV2_ATOM__String); - atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector); - atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object); + atom__Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk); + atom__Path = pHost->GetLv2Urid(LV2_ATOM__Path); + atom__Float = pHost->GetLv2Urid(LV2_ATOM__Float); + atom__Double = pHost->GetLv2Urid(LV2_ATOM__Double); + atom__Int = pHost->GetLv2Urid(LV2_ATOM__Int); + atom__Long = pHost->GetLv2Urid(LV2_ATOM__Long); + atom__Bool = pHost->GetLv2Urid(LV2_ATOM__Bool); + atom__String = pHost->GetLv2Urid(LV2_ATOM__String); + atom__Vector = pHost->GetLv2Urid(LV2_ATOM__Vector); + atom__Object = pHost->GetLv2Urid(LV2_ATOM__Object); - atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence); - atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk); - atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID); - atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer); - patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get); - patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set); - patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put); - patch_body = pHost->GetLv2Urid(LV2_PATCH__body); - patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject); - patch_property = pHost->GetLv2Urid(LV2_PATCH__property); - //patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept); - patch_value = pHost->GetLv2Urid(LV2_PATCH__value); - unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame); + atom__Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence); + atom__URID = pHost->GetLv2Urid(LV2_ATOM__URID); + patch__Get = pHost->GetLv2Urid(LV2_PATCH__Get); + patch__Set = pHost->GetLv2Urid(LV2_PATCH__Set); + patch__Put = pHost->GetLv2Urid(LV2_PATCH__Put); + patch__body = pHost->GetLv2Urid(LV2_PATCH__body); + patch__subject = pHost->GetLv2Urid(LV2_PATCH__subject); + patch__property = pHost->GetLv2Urid(LV2_PATCH__property); + patch__value = pHost->GetLv2Urid(LV2_PATCH__value); + units__frame = pHost->GetLv2Urid(LV2_UNITS__frame); + state__StateChanged = pHost->GetLv2Urid(LV2_STATE__StateChanged); } - //LV2_URID patch_accept; - - LV2_URID unitsFrame; + LV2_URID atom__Chunk; + LV2_URID units__frame; LV2_URID pluginUri; - LV2_URID atom_Blank; - LV2_URID atom_Bool; - LV2_URID atom_float; - LV2_URID atom_Double; - LV2_URID atom_Int; - LV2_URID atom_Long; - LV2_URID atom_String; - LV2_URID atom_Object; - LV2_URID atom_Vector; - LV2_URID atom_Path; - LV2_URID atom_Sequence; - LV2_URID atom_Chunk; - LV2_URID atom_URID; - LV2_URID atom_eventTransfer; - LV2_URID midi_Event; - LV2_URID patch_Get; - LV2_URID patch_Set; - LV2_URID patch_Put; - LV2_URID patch_body; - LV2_URID patch_subject; - LV2_URID patch_property; - LV2_URID patch_value; - LV2_URID param_uiState; + LV2_URID atom__Bool; + LV2_URID atom__Float; + LV2_URID atom__Double; + LV2_URID atom__Int; + LV2_URID atom__Long; + LV2_URID atom__String; + LV2_URID atom__Object; + LV2_URID atom__Vector; + LV2_URID atom__Path; + LV2_URID atom__Sequence; + LV2_URID atom__URID; + LV2_URID midi__Event; + LV2_URID patch__Get; + LV2_URID patch__Set; + LV2_URID patch__Put; + LV2_URID patch__body; + LV2_URID patch__subject; + LV2_URID patch__property; + LV2_URID patch__value; + LV2_URID state__StateChanged; }; - Uris uris; + Urids urids; uint64_t instanceId; BufferPool bufferPool; @@ -166,11 +169,13 @@ namespace pipedal void BypassTo(float value); - - virtual void RequestParameter(LV2_URID uridUri); - virtual void GatherParameter(RealtimeParameterRequest*pRequest); + public: + virtual bool GetLv2State(Lv2PluginState*state); + virtual void RequestPatchProperty(LV2_URID uridUri); + virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value); + virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest); virtual bool IsVst3() const { return false; } - virtual void RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter); + virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter); virtual uint8_t*GetAtomInputBuffer() { if (this->inputAtomBuffers.size() == 0) return nullptr; @@ -188,7 +193,7 @@ namespace pipedal Lv2Effect( IHost *pHost, const std::shared_ptr &info, - const PedalBoardItem &pedalBoardItem); + const PedalboardItem &pedalboardItem); ~Lv2Effect(); @@ -228,6 +233,7 @@ namespace pipedal controlValues[index] = value; } } + virtual float GetControlValue(int index) const { if (index == -1) { diff --git a/src/Lv2EventBufferWriter.cpp b/src/Lv2EventBufferWriter.cpp index 7027687..8a83699 100644 --- a/src/Lv2EventBufferWriter.cpp +++ b/src/Lv2EventBufferWriter.cpp @@ -19,7 +19,7 @@ #include "pch.h" #include "Lv2EventBufferWriter.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" using namespace pipedal; diff --git a/src/Lv2HostLeakTest.cpp b/src/Lv2HostLeakTest.cpp index 9ae8646..d5738b6 100644 --- a/src/Lv2HostLeakTest.cpp +++ b/src/Lv2HostLeakTest.cpp @@ -24,17 +24,17 @@ #include -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include "MemDebug.hpp" using namespace pipedal; -TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) { +TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) { MemStats initialMemory = GetMemStats(); { - PiPedalHost host; + PluginHost host; host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2"); } diff --git a/src/Lv2PedalBoard.cpp b/src/Lv2Pedalboard.cpp similarity index 79% rename from src/Lv2PedalBoard.cpp rename to src/Lv2Pedalboard.cpp index eda156a..68f7bdf 100644 --- a/src/Lv2PedalBoard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -18,7 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" -#include "Lv2PedalBoard.hpp" +#include "Lv2Pedalboard.hpp" #include "Lv2Effect.hpp" @@ -27,15 +27,16 @@ #include "VuUpdate.hpp" #include "AudioHost.hpp" #include "Lv2EventBufferWriter.hpp" +#include "Lv2Log.hpp" using namespace pipedal; -float *Lv2PedalBoard::CreateNewAudioBuffer() +float *Lv2Pedalboard::CreateNewAudioBuffer() { return bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize()); } -std::vector Lv2PedalBoard::AllocateAudioBuffers(int nChannels) +std::vector Lv2Pedalboard::AllocateAudioBuffers(int nChannels) { std::vector result; for (int i = 0; i < nChannels; ++i) @@ -45,7 +46,7 @@ std::vector Lv2PedalBoard::AllocateAudioBuffers(int nChannels) return result; } -int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbol) +int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbol) { for (int i = 0; i < realtimeEffects.size(); ++i) { @@ -57,8 +58,8 @@ int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbo } return -1; } -std::vector Lv2PedalBoard::PrepareItems( - std::vector &items, +std::vector Lv2Pedalboard::PrepareItems( + std::vector &items, std::vector inputBuffers) { for (int i = 0; i < items.size(); ++i) @@ -105,7 +106,14 @@ std::vector Lv2PedalBoard::PrepareItems( else { - auto pLv2Effect = this->pHost->CreateEffect(item); + IEffect* pLv2Effect = nullptr; + try { + pLv2Effect = this->pHost->CreateEffect(item); + } catch (const std::exception &e) + { + Lv2Log::warning(SS(e.what())); + } + if (pLv2Effect) { pEffect = pLv2Effect; @@ -198,57 +206,57 @@ std::vector Lv2PedalBoard::PrepareItems( return inputBuffers; } -void Lv2PedalBoard::Prepare(IHost *pHost, PedalBoard &pedalBoard) +void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard) { this->pHost = pHost; for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i) { - this->pedalBoardInputBuffers.push_back(bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize())); + this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize())); } - auto outputs = PrepareItems(pedalBoard.items(), this->pedalBoardInputBuffers); + auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers); int nOutputs = pHost->GetNumberOfOutputAudioChannels(); if (nOutputs == 1) { - this->pedalBoardOutputBuffers.push_back(outputs[0]); + this->pedalboardOutputBuffers.push_back(outputs[0]); } else { if (outputs.size() == 1) { - this->pedalBoardOutputBuffers.push_back(outputs[0]); - this->pedalBoardOutputBuffers.push_back(outputs[0]); + this->pedalboardOutputBuffers.push_back(outputs[0]); + this->pedalboardOutputBuffers.push_back(outputs[0]); } else { - this->pedalBoardOutputBuffers.push_back(outputs[0]); - this->pedalBoardOutputBuffers.push_back(outputs[1]); + this->pedalboardOutputBuffers.push_back(outputs[0]); + this->pedalboardOutputBuffers.push_back(outputs[1]); } } - PrepareMidiMap(pedalBoard); + PrepareMidiMap(pedalboard); } -void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem) +void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem) { - if (pedalBoardItem.midiBindings().size() != 0) + if (pedalboardItem.midiBindings().size() != 0) { - auto pluginInfo = pHost->GetPluginInfo(pedalBoardItem.uri()); + auto pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri()); const Lv2PluginInfo *pPluginInfo; - if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) + if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPluginInfo = GetSplitterPluginInfo(); } else { pPluginInfo = pluginInfo.get(); } - int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId()); + int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId()); if (pluginInfo && effectIndex != -1) { - for (size_t bindingIndex = 0; bindingIndex < pedalBoardItem.midiBindings().size(); ++bindingIndex) + for (size_t bindingIndex = 0; bindingIndex < pedalboardItem.midiBindings().size(); ++bindingIndex) { - auto &binding = pedalBoardItem.midiBindings()[bindingIndex]; + auto &binding = pedalboardItem.midiBindings()[bindingIndex]; { const Lv2PortInfo*pPortInfo; int controlIndex; @@ -259,7 +267,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem) } else { try { pPortInfo = &pluginInfo->getPort(binding.symbol()); - controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol()); + controlIndex = this->GetControlIndex(pedalboardItem.instanceId(), binding.symbol()); } catch (const std::exception&ignored) { continue; @@ -274,7 +282,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem) mapping.effectIndex = effectIndex; mapping.controlIndex = controlIndex; mapping.midiBinding = binding; - mapping.instanceId = pedalBoardItem.instanceId(); + mapping.instanceId = pedalboardItem.instanceId(); if (pPortInfo->IsSwitch()) { mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary; @@ -303,20 +311,20 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem) } } } - for (size_t i = 0; i < pedalBoardItem.topChain().size(); ++i) + for (size_t i = 0; i < pedalboardItem.topChain().size(); ++i) { - PrepareMidiMap(pedalBoardItem.topChain()[i]); + PrepareMidiMap(pedalboardItem.topChain()[i]); } - for (size_t i = 0; i < pedalBoardItem.bottomChain().size(); ++i) + for (size_t i = 0; i < pedalboardItem.bottomChain().size(); ++i) { - PrepareMidiMap(pedalBoardItem.bottomChain()[i]); + PrepareMidiMap(pedalboardItem.bottomChain()[i]); } } -void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard) +void Lv2Pedalboard::PrepareMidiMap(const Pedalboard &pedalboard) { - for (size_t i = 0; i < pedalBoard.items().size(); ++i) + for (size_t i = 0; i < pedalboard.items().size(); ++i) { - auto &item = pedalBoard.items()[i]; + auto &item = pedalboard.items()[i]; PrepareMidiMap(item); auto pluginInfo = pHost->GetPluginInfo(item.uri()); @@ -335,14 +343,14 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard) { return left.key < right.key; }); } } -void Lv2PedalBoard::Activate() +void Lv2Pedalboard::Activate() { for (int i = 0; i < this->effects.size(); ++i) { this->realtimeEffects[i]->Activate(); } } -void Lv2PedalBoard::Deactivate() +void Lv2Pedalboard::Deactivate() { for (int i = 0; i < this->effects.size(); ++i) { @@ -357,46 +365,46 @@ static void Copy(float *input, float *output, uint32_t samples) output[i] = input[i]; } } -bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter) +bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter) { this->ringBufferWriter = ringBufferWriter; - for (int i = 0; i < this->pedalBoardInputBuffers.size(); ++i) + for (int i = 0; i < this->pedalboardInputBuffers.size(); ++i) { if (inputBuffers[i] == nullptr) return false; - Copy(inputBuffers[i], this->pedalBoardInputBuffers[i], samples); + Copy(inputBuffers[i], this->pedalboardInputBuffers[i], samples); } for (int i = 0; i < this->processActions.size(); ++i) { processActions[i](samples); } - for (int i = 0; i < this->pedalBoardOutputBuffers.size(); ++i) + for (int i = 0; i < this->pedalboardOutputBuffers.size(); ++i) { if (outputBuffers[i] == nullptr) return false; - Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples); + Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples); } return true; } -float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex) +float Lv2Pedalboard::GetControlOutputValue(int effectIndex, int portIndex) { auto effect = realtimeEffects[effectIndex]; return effect->GetOutputControlValue(portIndex); } -void Lv2PedalBoard::SetControlValue(int effectIndex, int index, float value) +void Lv2Pedalboard::SetControlValue(int effectIndex, int index, float value) { auto effect = realtimeEffects[effectIndex]; effect->SetControl(index, value); } -void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled) +void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled) { auto effect = realtimeEffects[effectIndex]; effect->SetBypass(enabled); } -void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples) +void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples) { for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i) { @@ -429,7 +437,7 @@ void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp } } -void Lv2PedalBoard::ResetAtomBuffers() +void Lv2Pedalboard::ResetAtomBuffers() { for (size_t i = 0; i < this->effects.size(); ++i) { @@ -438,7 +446,7 @@ void Lv2PedalBoard::ResetAtomBuffers() } } -void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests) +void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests) { while (pParameterRequests != nullptr) { @@ -446,34 +454,54 @@ void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParamete if (pEffect == nullptr) { pParameterRequests->errorMessage = "No such effect."; - } - else + + } else if (pEffect->IsVst3()) { - pEffect->RequestParameter(pParameterRequests->uridUri); + pParameterRequests->errorMessage = "Not supported for VST3 plugins"; + } else { + Lv2Effect *pLv2Effect = dynamic_cast(pEffect); + + if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) + { + pLv2Effect->RequestPatchProperty(pParameterRequests->uridUri); + } else if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchSet) { + pLv2Effect->SetPatchProperty( + pParameterRequests->uridUri, + pParameterRequests->GetSize(), + (LV2_Atom*)pParameterRequests->GetBuffer() + ); + } } pParameterRequests = pParameterRequests->pNext; } } -void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests) +void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests) { while (pParameterRequests != nullptr) { - IEffect *effect = this->GetEffect(pParameterRequests->instanceId); - if (effect == nullptr) + if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) { - pParameterRequests->errorMessage = "No such effect."; - } - else - { - effect->GatherParameter(pParameterRequests); + IEffect *effect = this->GetEffect(pParameterRequests->instanceId); + if (effect == nullptr) + { + pParameterRequests->errorMessage = "No such effect."; + } else if (effect->IsVst3()) + { + pParameterRequests->errorMessage = "Not supported for VST3"; + } + else + { + Lv2Effect *pLv2Effect = dynamic_cast(effect); + pLv2Effect->GatherPatchProperties(pParameterRequests); + } } pParameterRequests = pParameterRequests->pNext; } } -void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message, +void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, void *callbackHandle, MidiCallbackFn *pfnCallback) diff --git a/src/Lv2PedalBoard.hpp b/src/Lv2Pedalboard.hpp similarity index 84% rename from src/Lv2PedalBoard.hpp rename to src/Lv2Pedalboard.hpp index d3d8a14..8ab01e4 100644 --- a/src/Lv2PedalBoard.hpp +++ b/src/Lv2Pedalboard.hpp @@ -18,8 +18,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #pragma once -#include "PedalBoard.hpp" -#include "PiPedalHost.hpp" +#include "Pedalboard.hpp" +#include "PluginHost.hpp" #include "Lv2Effect.hpp" #include "BufferPool.hpp" #include @@ -31,15 +31,15 @@ namespace pipedal { class RealtimeVuBuffers; -class RealtimeParameterRequest; +class RealtimePatchPropertyRequest; class RealtimeRingBufferWriter; -class Lv2PedalBoard { +class Lv2Pedalboard { IHost *pHost = nullptr; BufferPool bufferPool; - std::vector pedalBoardInputBuffers; - std::vector pedalBoardOutputBuffers; + std::vector pedalboardInputBuffers; + std::vector pedalboardOutputBuffers; std::vector > effects; std::vector realtimeEffects; // std::shared_ptr is not thread-safe!! @@ -81,21 +81,21 @@ class Lv2PedalBoard { std::vector PrepareItems( - std::vector & items, + std::vector & items, std::vector inputBuffers ); - void PrepareMidiMap(const PedalBoard&pedalBoard); - void PrepareMidiMap(const PedalBoardItem&pedalBoardItem); + void PrepareMidiMap(const Pedalboard&pedalboard); + void PrepareMidiMap(const PedalboardItem&pedalboardItem); std::vector AllocateAudioBuffers(int nChannels); - int CalculateChainInputs(const std::vector &inputBuffers, const std::vector &items); + int CalculateChainInputs(const std::vector &inputBuffers, const std::vector &items); void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter); public: - Lv2PedalBoard() { } - ~Lv2PedalBoard() { } + Lv2Pedalboard() { } + ~Lv2Pedalboard() { } - void Prepare(IHost *pHost,PedalBoard&pedalBoard); + void Prepare(IHost *pHost,Pedalboard&pedalboard); std::vector GetEffects() { return realtimeEffects; } @@ -122,12 +122,12 @@ public: void ResetAtomBuffers(); - void ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests); - void GatherParameterRequests(RealtimeParameterRequest *pParameterRequests); + void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests); + void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests); - std::vector &GetInputBuffers() { return this->pedalBoardInputBuffers;} - std::vector &GetoutputBuffers() { return this->pedalBoardOutputBuffers;} + std::vector &GetInputBuffers() { return this->pedalboardInputBuffers;} + std::vector &GetoutputBuffers() { return this->pedalboardOutputBuffers;} diff --git a/src/MapFileFeature.h b/src/MapFileFeature.h new file mode 100644 index 0000000..e69de29 diff --git a/src/MapPathFeature.cpp b/src/MapPathFeature.cpp new file mode 100644 index 0000000..b47b081 --- /dev/null +++ b/src/MapPathFeature.cpp @@ -0,0 +1,92 @@ +// 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. + +#include "MapPathFeature.hpp" +#include +#include +#include "json.hpp" +#include + + +using namespace pipedal; + +MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath) +:storagePath(storagePath) +{ + lv2_state_map_path.handle = (LV2_State_Map_Path_Handle *)this; + lv2_state_map_path.absolute_path = FnAbsolutePath; + lv2_state_map_path.abstract_path = FnAbstractPath; + + lv2_state_make_path.handle = (LV2_State_Make_Path_Handle*)this; + lv2_state_make_path.path = FnAbsolutePath; + + mapPathFeature.URI = LV2_STATE__mapPath; + mapPathFeature.data = (void*)&lv2_state_map_path; + makePathFeature.URI = LV2_STATE__makePath; + makePathFeature.data = (void*)&lv2_state_make_path; +} + +void MapPathFeature::Prepare(MapFeature* map) +{ + +} + + +/*static*/ char *MapPathFeature::FnAbsolutePath( + LV2_State_Map_Path_Handle handle, + const char *abstract_path) +{ + return ((MapPathFeature *)handle)->AbsolutePath(abstract_path); +} + +char *MapPathFeature::AbsolutePath(const char *abstract_path) +{ + std::filesystem::path t (abstract_path); + if (t.is_absolute()) { + return strdup(abstract_path); + } + std::filesystem::path result = storagePath / t; + return strdup(result.c_str()); +} + +/*static*/ +char *MapPathFeature::FnAbstractPath( + LV2_State_Map_Path_Handle handle, + const char *absolute_path) +{ + return ((MapPathFeature*)handle)->AbstractPath(absolute_path); +} + +char *MapPathFeature::AbstractPath(const char *absolute_path) +{ + if (strncmp(storagePath.c_str(),absolute_path,storagePath.length()) == 0) + { + const char*result = absolute_path + storagePath.length(); + if (*result == '/') + { + ++result; + return strdup(result); + } + if (*result == '0') { + return strdup(result); + } + } + return strdup(absolute_path); +} + diff --git a/src/MapPathFeature.hpp b/src/MapPathFeature.hpp new file mode 100644 index 0000000..10df165 --- /dev/null +++ b/src/MapPathFeature.hpp @@ -0,0 +1,54 @@ +// 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. + +#pragma once +#include "lv2/state.lv2/state.h" +#include +#include "MapFeature.hpp" + +namespace pipedal +{ + class MapPathFeature + { + public: + MapPathFeature(const std::filesystem::path &storagePath); + void Prepare(MapFeature* map); + void SetPluginStoragePath(const std::filesystem::path&path) { storagePath = path;} + const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;} + const LV2_Feature*GetMakePathFeature() { return &makePathFeature;} + private: + char *AbsolutePath(const char *abstract_path); + static char *FnAbsolutePath(LV2_State_Map_Path_Handle handle, + const char *abstract_path); + LV2_State_Map_Path lv2_state_map_path; + + LV2_State_Make_Path lv2_state_make_path; + + LV2_Feature mapPathFeature; + LV2_Feature makePathFeature; + + char *AbstractPath(const char *abstract_path); + static char * FnAbstractPath( + LV2_State_Map_Path_Handle handle, + const char *absolute_path); + + private: + std::string storagePath; + }; +} \ No newline at end of file diff --git a/src/PedalBoard.cpp b/src/Pedalboard.cpp similarity index 64% rename from src/PedalBoard.cpp rename to src/Pedalboard.cpp index cee2230..93b0b33 100644 --- a/src/PedalBoard.cpp +++ b/src/Pedalboard.cpp @@ -18,54 +18,63 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" using namespace pipedal; -static const PedalBoardItem* GetItem_(const std::vector&items,long pedalBoardItemId) +static const PedalboardItem* GetItem_(const std::vector&items,long pedalboardItemId) { for (size_t i = 0; i < items.size(); ++i) { auto &item = items[i]; - if (items[i].instanceId() == pedalBoardItemId) + if (items[i].instanceId() == pedalboardItemId) { return &(items[i]); } if (item.isSplit()) { - const PedalBoardItem* t = GetItem_(item.topChain(),pedalBoardItemId); + const PedalboardItem* t = GetItem_(item.topChain(),pedalboardItemId); if (t != nullptr) return t; - t = GetItem_(item.bottomChain(),pedalBoardItemId); + t = GetItem_(item.bottomChain(),pedalboardItemId); if (t != nullptr) return t; } } return nullptr; } -const PedalBoardItem*PedalBoard::GetItem(long pedalItemId) const +static void GetAllItems(std::vector & result, std::vector&items) +{ + for (auto& item: items) + { + if (item.isSplit()) + { + GetAllItems(result,item.topChain()); + GetAllItems(result,item.bottomChain()); + } + result.push_back(&item); + } +} +std::vector Pedalboard::GetAllPlugins() +{ + std::vector result; + GetAllItems(result,this->items()); + return result; +} + + +const PedalboardItem*Pedalboard::GetItem(long pedalItemId) const { return GetItem_(this->items(),pedalItemId); } -PedalBoardItem*PedalBoard::GetItem(long pedalItemId) +PedalboardItem*Pedalboard::GetItem(long pedalItemId) { - return const_cast(GetItem_(this->items(),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) +ControlValue* PedalboardItem::GetControlValue(const std::string&symbol) { for (size_t i = 0; i < this->controlValues().size(); ++i) { @@ -77,9 +86,9 @@ ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol) return nullptr; } -bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled) +bool Pedalboard::SetItemEnabled(long pedalItemId, bool enabled) { - PedalBoardItem*item = GetItem(pedalItemId); + PedalboardItem*item = GetItem(pedalItemId); if (!item) return false; if (item->isEnabled() != enabled) { @@ -91,9 +100,9 @@ bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled) } -bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, float value) +bool Pedalboard::SetControlValue(long pedalItemId, const std::string &symbol, float value) { - PedalBoardItem*item = GetItem(pedalItemId); + PedalboardItem*item = GetItem(pedalItemId); if (!item) return false; ControlValue*controlValue = item->GetControlValue(symbol); if (controlValue == nullptr) return false; @@ -106,11 +115,11 @@ bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, fl } -PedalBoardItem PedalBoard::MakeEmptyItem() +PedalboardItem Pedalboard::MakeEmptyItem() { uint64_t instanceId = NextInstanceId(); - PedalBoardItem result; + PedalboardItem result; result.instanceId(instanceId); result.uri(EMPTY_PEDALBOARD_ITEM_URI); result.pluginName(""); @@ -119,11 +128,11 @@ PedalBoardItem PedalBoard::MakeEmptyItem() } -PedalBoardItem PedalBoard::MakeSplit() +PedalboardItem Pedalboard::MakeSplit() { uint64_t instanceId = NextInstanceId(); - PedalBoardItem result; + PedalboardItem result; result.instanceId(instanceId); result.uri(SPLIT_PEDALBOARD_ITEM_URI); result.pluginName(""); @@ -151,10 +160,10 @@ PedalBoardItem PedalBoard::MakeSplit() -PedalBoard PedalBoard::MakeDefault() +Pedalboard Pedalboard::MakeDefault() { // copy insanity. but it happens so rarely. - PedalBoard result; + Pedalboard result; auto split = result.MakeSplit(); split.topChain().push_back(result.MakeEmptyItem()); @@ -176,7 +185,7 @@ PedalBoard PedalBoard::MakeDefault() } -bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector&value) +bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector&value) { return self->uri() == SPLIT_PEDALBOARD_ITEM_URI; } @@ -192,29 +201,24 @@ 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_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,pluginName) + JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,topChain,IsPedalboardSplitItem) + JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,bottomChain,&IsPedalboardSplitItem) + JSON_MAP_REFERENCE(PedalboardItem,midiBindings) + JSON_MAP_REFERENCE(PedalboardItem,lv2State) JSON_MAP_END() - -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) - JSON_MAP_REFERENCE(PedalBoardItem,midiBindings) -JSON_MAP_END() - - -JSON_MAP_BEGIN(PedalBoard) - JSON_MAP_REFERENCE(PedalBoard,name) - JSON_MAP_REFERENCE(PedalBoard,items) - JSON_MAP_REFERENCE(PedalBoard,nextInstanceId) +JSON_MAP_BEGIN(Pedalboard) + JSON_MAP_REFERENCE(Pedalboard,name) + JSON_MAP_REFERENCE(Pedalboard,items) + JSON_MAP_REFERENCE(Pedalboard,nextInstanceId) JSON_MAP_END() diff --git a/src/PedalBoard.hpp b/src/Pedalboard.hpp similarity index 79% rename from src/PedalBoard.hpp rename to src/Pedalboard.hpp index 6676f6e..631ecdb 100644 --- a/src/PedalBoard.hpp +++ b/src/Pedalboard.hpp @@ -22,6 +22,7 @@ #include "json.hpp" #include "json_variant.hpp" #include "MidiBinding.hpp" +#include "StateInterface.hpp" namespace pipedal { @@ -73,55 +74,34 @@ public: }; -class PropertyValue { -private: - std::string propertyUri_; - json_variant value_; -public: - PropertyValue() - { - } - 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 { +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 topChain_; + std::vector bottomChain_; std::vector midiBindings_; std::string vstState_; + Lv2PluginState lv2State_; public: ControlValue*GetControlValue(const std::string&symbol); - PropertyValue*GetPropertyValue(const std::string&propertyUri); + bool hasLv2State() const { + return lv2State_.values_.size() != 0; + } 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) + GETTER_SETTER_REF(lv2State) bool isSplit() const @@ -137,7 +117,6 @@ public: writer.write_member("uri",uri_); writer.write_member("pluginName",pluginName_); writer.write_member("isEnabled",isEnabled_); - writer.write_member("controlValues",controlValues_); if (isSplit()) { writer.write_member("topChain",topChain_); @@ -145,13 +124,13 @@ public: } } - DECLARE_JSON_MAP(PedalBoardItem); + DECLARE_JSON_MAP(PedalboardItem); }; -class PedalBoard { +class Pedalboard { std::string name_; - std::vector items_; + std::vector items_; uint64_t nextInstanceId_ = 0; uint64_t NextInstanceId() { return ++nextInstanceId_; } @@ -159,8 +138,9 @@ public: bool SetControlValue(long pedalItemId, const std::string &symbol, float value); bool SetItemEnabled(long pedalItemId, bool enabled); - PedalBoardItem*GetItem(long pedalItemId); - const PedalBoardItem*GetItem(long pedalItemId) const; + PedalboardItem*GetItem(long pedalItemId); + const PedalboardItem*GetItem(long pedalItemId) const; + std::vectorGetAllPlugins(); bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; } @@ -168,13 +148,13 @@ public: GETTER_SETTER_VEC(items) - DECLARE_JSON_MAP(PedalBoard); + DECLARE_JSON_MAP(Pedalboard); - PedalBoardItem MakeEmptyItem(); - PedalBoardItem MakeSplit(); + PedalboardItem MakeEmptyItem(); + PedalboardItem MakeSplit(); - static PedalBoard MakeDefault(); + static Pedalboard MakeDefault(); }; #undef GETTER_SETTER_REF diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index 11c0b36..0a5b658 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -198,7 +198,7 @@ public: audioDriver = CreateAlsaDriver(this); - latencyMonitor.Init(jackConfiguration.GetSampleRate()); + latencyMonitor.Init(jackConfiguration.sampleRate()); audioDriver->Open(serverSettings, channelSelection); inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()]; diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 5e9c1fd..052336c 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -60,10 +60,13 @@ static std::string BytesToHex(const std::vector &bytes) } PiPedalModel::PiPedalModel() + : lv2Host(), + atomConverter(lv2Host.GetMapFeature()) { - this->pedalBoard = PedalBoard::MakeDefault(); + this->pedalboard = Pedalboard::MakeDefault(); #if JACK_HOST - this->jackServerSettings.ReadJackConfiguration(); + this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag. + this->jackServerSettings.ReadJackDaemonConfiguration(); #else this->jackServerSettings = this->storage.GetJackServerSettings(); #endif @@ -71,7 +74,7 @@ PiPedalModel::PiPedalModel() void PiPedalModel::Close() { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; @@ -106,7 +109,7 @@ PiPedalModel::~PiPedalModel() { CurrentPreset currentPreset; currentPreset.modified_ = this->hasPresetChanged; - currentPreset.preset_ = this->pedalBoard; + currentPreset.preset_ = this->pedalboard; storage.SaveCurrentPreset(currentPreset); } catch (...) @@ -134,10 +137,11 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration) storage.SetConfigRoot(configuration.GetDocRoot()); storage.SetDataRoot(configuration.GetLocalStoragePath()); storage.Initialize(); + lv2Host.SetPluginStoragePath(storage.GetPluginStorageDirectory()); this->systemMidiBindings = storage.GetSystemMidiBindings(); - this->jackServerSettings.ReadJackConfiguration(); + this->jackServerSettings.ReadJackDaemonConfiguration(); } void PiPedalModel::LoadLv2PluginInfo() @@ -184,16 +188,21 @@ void PiPedalModel::Load() // lv2Host.Load(configuration.GetLv2Path().c_str()); - this->pedalBoard = storage.GetCurrentPreset(); // the current *saved* preset. + this->pedalboard = storage.GetCurrentPreset(); // the current *saved* preset. // the current edited preset, saved only across orderly shutdowns. CurrentPreset currentPreset; - if (storage.RestoreCurrentPreset(¤tPreset)) + try { + if (storage.RestoreCurrentPreset(¤tPreset)) + { + this->pedalboard = currentPreset.preset_; + this->hasPresetChanged = currentPreset.modified_; + } + } catch (const std::exception &e) { - this->pedalBoard = currentPreset.preset_; - this->hasPresetChanged = currentPreset.modified_; + Lv2Log::warning(SS("Failed to load current preset. " << e.what())); } - UpdateDefaults(&this->pedalBoard); + UpdateDefaults(&this->pedalboard); std::unique_ptr p{AudioHost::CreateInstance(lv2Host.asIHost())}; this->audioHost = std::move(p); @@ -235,16 +244,18 @@ void PiPedalModel::Load() try { audioHost->Open(this->jackServerSettings, selection); - try { - std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; - this->lv2PedalBoard = lv2PedalBoard; - audioHost->SetPedalBoard(lv2PedalBoard); + try + { + std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)}; + this->lv2Pedalboard = lv2Pedalboard; + audioHost->SetPedalboard(lv2Pedalboard); } catch (const std::exception &e) { - Lv2Log::error("Failed to load initial plugin. (%s)", e.what()); + Lv2Log::error("Failed to load initial plugin. %s", e.what()); } - } catch (std::exception &e) + } + catch (std::exception &e) { Lv2Log::error("Failed to start audio device. %s", e.what()); } @@ -269,13 +280,13 @@ IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientI void PiPedalModel::AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); this->subscribers.push_back(pSubscriber); } void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber) { { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (auto it = this->subscribers.begin(); it != this->subscribers.end(); ++it) { @@ -304,26 +315,52 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { - IEffect* effect = lv2PedalBoard->GetEffect(pedalItemId); + IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId); if (effect->IsVst3()) { - int index = lv2PedalBoard->GetControlIndex(pedalItemId, symbol); + int index = lv2Pedalboard->GetControlIndex(pedalItemId, symbol); if (index != -1) { - effect->SetControl(index,value);; + effect->SetControl(index, value); + ; } - } else { + } + else + { audioHost->SetControlValue(pedalItemId, symbol, value); } } +void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId) +{ + std::lock_guard lock(mutex); + SetPresetChanged(-1, true); + + { + // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) + IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; + for (size_t i = 0; i < subscribers.size(); ++i) + { + t[i] = this->subscribers[i]; + } + size_t n = this->subscribers.size(); + { + for (size_t i = 0; i < n; ++i) + { + t[i]->OnLv2StateChanged(instanceId); + } + } + delete[] t; + } +} + void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); PreviewControl(clientId, pedalItemId, symbol, value); - this->pedalBoard.SetControlValue(pedalItemId, symbol, value); - IEffect *pEffect = this->lv2PedalBoard->GetEffect(pedalItemId); + this->pedalboard.SetControlValue(pedalItemId, symbol, value); + IEffect *pEffect = this->lv2Pedalboard->GetEffect(pedalItemId); { @@ -379,7 +416,7 @@ void PiPedalModel::FireBanksChanged(int64_t clientId) delete[] t; } -void PiPedalModel::FirePedalBoardChanged(int64_t clientId) +void PiPedalModel::FirePedalboardChanged(int64_t clientId) { // noify subscribers. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; @@ -390,54 +427,54 @@ void PiPedalModel::FirePedalBoardChanged(int64_t clientId) size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { - t[i]->OnPedalBoardChanged(clientId, this->pedalBoard); + t[i]->OnPedalboardChanged(clientId, this->pedalboard); } delete[] t; // notify the audio thread. if (audioHost->IsOpen()) { - std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; - this->lv2PedalBoard = lv2PedalBoard; - audioHost->SetPedalBoard(lv2PedalBoard); + std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)}; + this->lv2Pedalboard = lv2Pedalboard; + audioHost->SetPedalboard(lv2Pedalboard); UpdateRealtimeVuSubscriptions(); UpdateRealtimeMonitorPortSubscriptions(); } } -void PiPedalModel::SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard) +void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard) { { - std::lock_guard guard(mutex); - this->pedalBoard = pedalBoard; - UpdateDefaults(&this->pedalBoard); + std::lock_guard lock(mutex); + this->pedalboard = pedalboard; + UpdateDefaults(&this->pedalboard); - this->FirePedalBoardChanged(clientId); + this->FirePedalboardChanged(clientId); this->SetPresetChanged(clientId, true); } } -void PiPedalModel::UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard) +void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard) { { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); // update vst3 presets if neccessary. // the pedalboard must be a manipualted instance of the current Lv2Pedalboard. - UpdateVst3Settings(pedalBoard); + UpdateVst3Settings(pedalboard); - this->pedalBoard = pedalBoard; - UpdateDefaults(&this->pedalBoard); + this->pedalboard = pedalboard; + UpdateDefaults(&this->pedalboard); - this->FirePedalBoardChanged(clientId); + this->FirePedalboardChanged(clientId); this->SetPresetChanged(clientId, true); } } -void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled) +void PiPedalModel::SetPedalboardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled) { std::lock_guard guard{mutex}; { - this->pedalBoard.SetItemEnabled(pedalItemId, enabled); + this->pedalboard.SetItemEnabled(pedalItemId, enabled); // Notify clients. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; @@ -460,20 +497,20 @@ void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId void PiPedalModel::GetPresets(PresetIndex *pResult) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); this->storage.GetPresetIndex(pResult); pResult->presetChanged(this->hasPresetChanged); } -PedalBoard PiPedalModel::GetPreset(int64_t instanceId) +Pedalboard PiPedalModel::GetPreset(int64_t instanceId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return this->storage.GetPreset(instanceId); } void PiPedalModel::GetBank(int64_t instanceId, BankFile *pResult) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); this->storage.GetBankFile(instanceId, pResult); } @@ -528,16 +565,16 @@ void PiPedalModel::FirePluginPresetsChanged(const std::string &pluginUri) } } -void PiPedalModel::UpdateVst3Settings(PedalBoard &pedalBoard) +void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard) { - // get the vst3 state bundle from lv2Pedalboard for the current pedalBoard. + // get the vst3 state bundle from lv2Pedalboard for the current pedalboard. #if ENABLE_VST3 - PedalBoard pb; - for (IEffect *effect : lv2PedalBoard->GetEffects()) + Pedalboard pb; + for (IEffect *effect : lv2Pedalboard->GetEffects()) { if (effect->IsVst3()) { - PedalBoardItem *item = pedalBoard.GetItem(effect->GetInstanceId()); + PedalboardItem *item = pedalboard.GetItem(effect->GetInstanceId()); if (item) { Vst3Effect *vst3Effect = (Vst3Effect *)effect; @@ -556,8 +593,9 @@ void PiPedalModel::SaveCurrentPreset(int64_t clientId) { std::lock_guard guard{mutex}; - UpdateVst3Settings(this->pedalBoard); - storage.SaveCurrentPreset(this->pedalBoard); + UpdateVst3Settings(this->pedalboard); + this->audioHost->UpdatePluginStates(this->pedalboard); + storage.SaveCurrentPreset(this->pedalboard); this->SetPresetChanged(clientId, false); } @@ -575,7 +613,7 @@ void PiPedalModel::UpdatePluginPresets(const PluginUiPresets &pluginPresets) } int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &name) { - auto *item = this->pedalBoard.GetItem(instanceId); + auto *item = this->pedalboard.GetItem(instanceId); if (!item) { throw PiPedalException("Plugin not found."); @@ -594,8 +632,8 @@ int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &n { std::lock_guard guard{mutex}; - auto pedalboard = this->pedalBoard; - UpdateVst3Settings(pedalBoard); + auto pedalboard = this->pedalboard; + UpdateVst3Settings(pedalboard); pedalboard.name(name); int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId); FirePresetsChanged(clientId); @@ -629,10 +667,11 @@ int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter) return newPreset; } -void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) +void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) { std::lock_guard guard{mutex}; - try { + try + { PresetIndex index; storage.GetPresetIndex(&index); auto currentPresetId = storage.GetCurrentPresetId(); @@ -654,21 +693,26 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest& { if (currentPresetIndex == 0) { - currentPresetIndex = index.presets().size()-1; - } else { + currentPresetIndex = index.presets().size() - 1; + } + else + { --currentPresetIndex; } - } else { + } + else + { ++currentPresetIndex; if (currentPresetIndex >= index.presets().size()) { currentPresetIndex = 0; } } - LoadPreset(-1,index.presets()[currentPresetIndex].instanceId()); - } catch (std::exception&e) + LoadPreset(-1, index.presets()[currentPresetIndex].instanceId()); + } + catch (std::exception &e) { - + Lv2Log::error(e.what()); } if (this->audioHost) @@ -677,15 +721,16 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest& } } - -void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) +void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) { std::lock_guard guard{mutex}; - try { + try + { if (midiProgramRequest.bank >= 0) - { + { int64_t bankId = storage.GetBankByMidiBankNumber(midiProgramRequest.bank); - if (bankId == -1) throw PiPedalException("Bank not found."); + if (bankId == -1) + throw PiPedalException("Bank not found."); if (bankId != this->storage.GetBanks().selectedBank()) { storage.LoadBank(bankId); @@ -693,13 +738,15 @@ void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProg FireBanksChanged(-1); FirePresetsChanged(-1); } - } + } int64_t presetId = storage.GetPresetByProgramNumber(midiProgramRequest.program); - if (presetId == -1) throw PiPedalException("No valid preset."); - LoadPreset(-1,presetId); - } catch (std::exception&e) + if (presetId == -1) + throw PiPedalException("No valid preset."); + LoadPreset(-1, presetId); + } + catch (std::exception &e) { - + Lv2Log::error(e.what()); } if (this->audioHost) @@ -708,19 +755,17 @@ void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProg } } - - void PiPedalModel::LoadPreset(int64_t clientId, int64_t instanceId) { std::lock_guard guard{mutex}; if (storage.LoadPreset(instanceId)) { - this->pedalBoard = storage.GetCurrentPreset(); - UpdateDefaults(&this->pedalBoard); + this->pedalboard = storage.GetCurrentPreset(); + UpdateDefaults(&this->pedalboard); this->hasPresetChanged = false; // no fire. - this->FirePedalBoardChanged(clientId); + this->FirePedalboardChanged(clientId); this->FirePresetsChanged(clientId); // fire now. } } @@ -787,7 +832,7 @@ int64_t PiPedalModel::DeletePreset(int64_t clientId, int64_t instanceId) } bool PiPedalModel::RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); if (storage.RenamePreset(instanceId, name)) { this->FirePresetsChanged(clientId); @@ -802,7 +847,7 @@ bool PiPedalModel::RenamePreset(int64_t clientId, int64_t instanceId, const std: GovernorSettings PiPedalModel::GetGovernorSettings() { { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); GovernorSettings result; result.governor_ = storage.GetGovernorSettings(); result.governors_ = pipedal::GetAvailableGovernors(); @@ -811,7 +856,7 @@ GovernorSettings PiPedalModel::GetGovernorSettings() } void PiPedalModel::SetGovernorSettings(const std::string &governor) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); adminClient.SetGovernorSettings(governor); this->storage.SetGovernorSettings(governor); @@ -833,7 +878,7 @@ void PiPedalModel::SetGovernorSettings(const std::string &governor) void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); adminClient.SetWifiConfig(wifiConfigSettings); @@ -897,7 +942,7 @@ void PiPedalModel::UpdateDnsSd() } void PiPedalModel::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiDirectConfigSettings) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); adminClient.SetWifiDirectConfig(wifiDirectConfigSettings); @@ -926,12 +971,12 @@ void PiPedalModel::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &w WifiConfigSettings PiPedalModel::GetWifiConfigSettings() { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return this->storage.GetWifiConfigSettings(); } WifiDirectConfigSettings PiPedalModel::GetWifiDirectConfigSettings() { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return this->storage.GetWifiDirectConfigSettings(); } @@ -940,7 +985,7 @@ void PiPedalModel::SetShowStatusMonitor(bool show) IPiPedalModelSubscriber **t; size_t n; { - std::lock_guard guard(mutex); // copy atomically. + std::lock_guard lock(mutex); // copy atomically. t = new IPiPedalModelSubscriber *[this->subscribers.size()]; n = this->subscribers.size(); storage.SetShowStatusMonitor(show); @@ -960,13 +1005,13 @@ void PiPedalModel::SetShowStatusMonitor(bool show) } bool PiPedalModel::GetShowStatusMonitor() { - std::lock_guard guard(mutex); // copy atomically. + std::lock_guard lock(mutex); // copy atomically. return storage.GetShowStatusMonitor(); } JackConfiguration PiPedalModel::GetJackConfiguration() { - std::lock_guard guard(mutex); // copy atomically. + std::lock_guard lock(mutex); // copy atomically. return this->jackConfiguration; } @@ -988,7 +1033,7 @@ void PiPedalModel::RestartAudio() // do a complete reload. - this->audioHost->SetPedalBoard(nullptr); + this->audioHost->SetPedalboard(nullptr); this->jackConfiguration.AlsaInitialize(this->jackServerSettings); @@ -999,7 +1044,7 @@ void PiPedalModel::RestartAudio() } else { - jackConfiguration.SetErrorStatus("Error"); + jackConfiguration.setErrorStatus("Error"); } FireJackConfigurationChanged(jackConfiguration); @@ -1021,9 +1066,9 @@ void PiPedalModel::RestartAudio() this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); - std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; - this->lv2PedalBoard = lv2PedalBoard; - audioHost->SetPedalBoard(lv2PedalBoard); + std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)}; + this->lv2Pedalboard = lv2Pedalboard; + audioHost->SetPedalboard(lv2Pedalboard); this->UpdateRealtimeVuSubscriptions(); UpdateRealtimeMonitorPortSubscriptions(); } @@ -1037,7 +1082,7 @@ void PiPedalModel::RestartAudio() void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection) { { - std::lock_guard guard(mutex); // copy atomically. + std::lock_guard lock(mutex); // copy atomically. this->storage.SetJackChannelSelection(channelSelection); this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); @@ -1072,7 +1117,7 @@ void PiPedalModel::FireChannelSelectionChanged(int64_t clientId) JackChannelSelection PiPedalModel::GetJackChannelSelection() { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); JackChannelSelection t = this->storage.GetJackChannelSelection(this->jackConfiguration); if (this->jackConfiguration.isValid()) { @@ -1083,7 +1128,7 @@ JackChannelSelection PiPedalModel::GetJackChannelSelection() int64_t PiPedalModel::AddVuSubscription(int64_t instanceId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeVuSubscriptions.push_back(VuSubscription{subscriptionId, instanceId}); @@ -1093,7 +1138,7 @@ int64_t PiPedalModel::AddVuSubscription(int64_t instanceId) } void PiPedalModel::RemoveVuSubscription(int64_t subscriptionHandle) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i) { if ((*i).subscriptionHandle == subscriptionHandle) @@ -1107,8 +1152,8 @@ void PiPedalModel::RemoveVuSubscription(int64_t subscriptionHandle) void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) { - std::lock_guard guard(mutex); - PedalBoardItem *item = this->pedalBoard.GetItem(instanceId); + std::lock_guard lock(mutex); + PedalboardItem *item = this->pedalboard.GetItem(instanceId); if (item) { const Lv2PluginInfo *pPluginInfo; @@ -1126,7 +1171,7 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f if (portIndex == -1) { // bypass! - this->pedalBoard.SetItemEnabled(instanceId, value != 0); + this->pedalboard.SetItemEnabled(instanceId, value != 0); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) @@ -1152,7 +1197,7 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f { std::string symbol = port->symbol(); - this->pedalBoard.SetControlValue(instanceId, symbol, value); + this->pedalboard.SetControlValue(instanceId, symbol, value); { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) @@ -1177,9 +1222,10 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f } } } + void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (size_t i = 0; i < updates.size(); ++i) { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) @@ -1204,7 +1250,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions() for (int i = 0; i < activeVuSubscriptions.size(); ++i) { auto instanceId = activeVuSubscriptions[i].instanceid; - if (pedalBoard.HasItem(instanceId)) + if (pedalboard.HasItem(instanceId)) { addedInstances.insert(activeVuSubscriptions[i].instanceid); } @@ -1220,7 +1266,7 @@ void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions() int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeMonitorPortSubscriptions.push_back( MonitorPortSubscription{subscriptionId, instanceId, key, updateInterval, onUpdate}); @@ -1231,7 +1277,7 @@ int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, fl } void PiPedalModel::UnmonitorPort(int64_t subscriptionHandle) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { @@ -1246,7 +1292,7 @@ void PiPedalModel::UnmonitorPort(int64_t subscriptionHandle) void PiPedalModel::OnNotifyMonitorPort(const MonitorPortUpdate &update) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { @@ -1260,18 +1306,24 @@ void PiPedalModel::OnNotifyMonitorPort(const MonitorPortUpdate &update) } } -void PiPedalModel::GetLv2Parameter( +void PiPedalModel::SendSetPatchProperty( int64_t clientId, int64_t instanceId, - const std::string uri, - std::function onSuccess, + const std::string propertyUri, + const json_variant &value, + std::function onSuccess, std::function onError) { - std::function onRequestComplete{ - [this](RealtimeParameterRequest *pParameter) + + std::lock_guard lock(mutex); + + LV2_Atom *atomValue = atomConverter.ToAtom(value); + + std::function onRequestComplete{ + [this, onSuccess](RealtimePatchPropertyRequest *pParameter) { { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); bool cancelled = true; for (auto i = this->outstandingParameterRequests.begin(); i != this->outstandingParameterRequests.end(); ++i) @@ -1287,15 +1339,78 @@ void PiPedalModel::GetLv2Parameter( { if (pParameter->errorMessage != nullptr) { - pParameter->onError(pParameter->errorMessage); - } - else if (pParameter->responseLength == 0) - { - pParameter->onError("No response."); + if (pParameter->onError) + { + pParameter->onError(pParameter->errorMessage); + } } else { - pParameter->onSuccess(pParameter->jsonResponse); + if (pParameter->onSuccess) + { + onSuccess(); + } + } + } + delete pParameter; + } + }}; + + LV2_URID urid = this->lv2Host.GetLv2Urid(propertyUri.c_str()); + + RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest( + onRequestComplete, + clientId, instanceId, urid, atomValue, nullptr, onError); + + outstandingParameterRequests.push_back(request); + this->audioHost->sendRealtimeParameterRequest(request); +} + +void PiPedalModel::SendGetPatchProperty( + int64_t clientId, + int64_t instanceId, + const std::string uri, + std::function onSuccess, + std::function onError) +{ + std::function onRequestComplete{ + [this](RealtimePatchPropertyRequest *pParameter) + { + { + std::lock_guard lock(mutex); + bool cancelled = true; + for (auto i = this->outstandingParameterRequests.begin(); + i != this->outstandingParameterRequests.end(); ++i) + { + if ((*i) == pParameter) + { + cancelled = false; + this->outstandingParameterRequests.erase(i); + break; + } + } + if (!cancelled) + { + if (pParameter->errorMessage != nullptr) + { + if (pParameter->onError) + { + pParameter->onError(pParameter->errorMessage); + } + } + else if (pParameter->GetSize() == 0 && pParameter->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) + { + if (pParameter->onError) + { + pParameter->onError("No response."); + } + } + else + { + if (pParameter->onSuccess) + { + pParameter->onSuccess(pParameter->jsonResponse); + } } } delete pParameter; @@ -1303,13 +1418,13 @@ void PiPedalModel::GetLv2Parameter( }}; LV2_URID urid = this->lv2Host.GetLv2Urid(uri.c_str()); - RealtimeParameterRequest *request = new RealtimeParameterRequest( + RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest( onRequestComplete, clientId, instanceId, urid, onSuccess, onError); - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); outstandingParameterRequests.push_back(request); - this->audioHost->getRealtimeParameter(request); + this->audioHost->sendRealtimeParameterRequest(request); } BankIndex PiPedalModel::GetBankIndex() const @@ -1320,14 +1435,14 @@ BankIndex PiPedalModel::GetBankIndex() const void PiPedalModel::RenameBank(int64_t clientId, int64_t bankId, const std::string &newName) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); storage.RenameBank(bankId, newName); FireBanksChanged(clientId); } int64_t PiPedalModel::SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); int64_t newId = storage.SaveBankAs(bankId, newName); FireBanksChanged(clientId); return newId; @@ -1335,26 +1450,33 @@ int64_t PiPedalModel::SaveBankAs(int64_t clientId, int64_t bankId, const std::st void PiPedalModel::OpenBank(int64_t clientId, int64_t bankId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); storage.LoadBank(bankId); FireBanksChanged(clientId); FirePresetsChanged(clientId); - this->pedalBoard = storage.GetCurrentPreset(); + this->pedalboard = storage.GetCurrentPreset(); - UpdateDefaults(&this->pedalBoard); + UpdateDefaults(&this->pedalboard); this->hasPresetChanged = false; - this->FirePedalBoardChanged(clientId); + this->FirePedalboardChanged(clientId); } JackServerSettings PiPedalModel::GetJackServerSettings() { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return this->jackServerSettings; } +void PiPedalModel::SetOnboarding(bool value) +{ + std::unique_lock guard(mutex); + this->jackServerSettings.SetIsOnboarding(value); + SetJackServerSettings(this->jackServerSettings); +} + void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSettings) { std::unique_lock guard(mutex); @@ -1396,7 +1518,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet // save the current (edited) preset now in case the service shutdown isn't clean. CurrentPreset currentPreset; currentPreset.modified_ = this->hasPresetChanged; - currentPreset.preset_ = this->pedalBoard; + currentPreset.preset_ = this->pedalboard; storage.SaveCurrentPreset(currentPreset); this->jackConfiguration.SetIsRestarting(true); @@ -1405,7 +1527,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet jackServerSettings, [this](bool success, const std::string &errorMessage) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); if (!success) { std::stringstream s; @@ -1428,10 +1550,10 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet FireJackConfigurationChanged(this->jackConfiguration); // restart the pedalboard on a new instance. - std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; - this->lv2PedalBoard = lv2PedalBoard; + std::shared_ptr lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)}; + this->lv2Pedalboard = lv2Pedalboard; - audioHost->SetPedalBoard(lv2PedalBoard); + audioHost->SetPedalboard(lv2Pedalboard); UpdateRealtimeVuSubscriptions(); UpdateRealtimeMonitorPortSubscriptions(); #endif @@ -1441,13 +1563,13 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet #endif } -void PiPedalModel::UpdateDefaults(PedalBoardItem *pedalBoardItem) +void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem) { - std::shared_ptr t = lv2Host.GetPluginInfo(pedalBoardItem->uri()); + std::shared_ptr t = lv2Host.GetPluginInfo(pedalboardItem->uri()); const Lv2PluginInfo *pPlugin = t.get(); if (pPlugin == nullptr) { - if (pedalBoardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) + if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPlugin = GetSplitterPluginInfo(); } @@ -1459,76 +1581,61 @@ void PiPedalModel::UpdateDefaults(PedalBoardItem *pedalBoardItem) auto port = pPlugin->ports()[i]; if (port->is_control_port() && port->is_input()) { - ControlValue *pValue = pedalBoardItem->GetControlValue(port->symbol()); + ControlValue *pValue = pedalboardItem->GetControlValue(port->symbol()); if (pValue == nullptr) { // Missing? Set it to default value. - pedalBoardItem->controlValues().push_back( + pedalboardItem->controlValues().push_back( pipedal::ControlValue(port->symbol().c_str(), port->default_value())); } } } - if (pPlugin->piPedalUI()) - { - auto&piPedalUi = pPlugin->piPedalUI(); - for (auto &fileProperty : piPedalUi->fileProperties()) - { - PropertyValue *pValue = pedalBoardItem->GetPropertyValue(fileProperty->patchProperty()); - if (pValue == nullptr) - { - // missing? set it to default value - pedalBoardItem->propertyValues().push_back( - PropertyValue(fileProperty->patchProperty(),fileProperty->defaultFile()) - ); - } - } - } } - for (size_t i = 0; i < pedalBoardItem->topChain().size(); ++i) + for (size_t i = 0; i < pedalboardItem->topChain().size(); ++i) { - UpdateDefaults(&(pedalBoardItem->topChain()[i])); + UpdateDefaults(&(pedalboardItem->topChain()[i])); } - for (size_t i = 0; i < pedalBoardItem->bottomChain().size(); ++i) + for (size_t i = 0; i < pedalboardItem->bottomChain().size(); ++i) { - UpdateDefaults(&(pedalBoardItem->bottomChain()[i])); + UpdateDefaults(&(pedalboardItem->bottomChain()[i])); } } -void PiPedalModel::UpdateDefaults(PedalBoard *pedalBoard) +void PiPedalModel::UpdateDefaults(Pedalboard *pedalboard) { - for (size_t i = 0; i < pedalBoard->items().size(); ++i) + for (size_t i = 0; i < pedalboard->items().size(); ++i) { - UpdateDefaults(&(pedalBoard->items()[i])); + UpdateDefaults(&(pedalboard->items()[i])); } } PluginPresets PiPedalModel::GetPluginPresets(const std::string &pluginUri) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return storage.GetPluginPresets(pluginUri); } PluginUiPresets PiPedalModel::GetPluginUiPresets(const std::string &pluginUri) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); return storage.GetPluginUiPresets(pluginUri); } void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetInstanceId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); - PedalBoardItem *pedalBoardItem = this->pedalBoard.GetItem(pluginInstanceId); - if (pedalBoardItem != nullptr) + PedalboardItem *pedalboardItem = this->pedalboard.GetItem(pluginInstanceId); + if (pedalboardItem != nullptr) { - std::vector controlValues = storage.GetPluginPresetValues(pedalBoardItem->uri(), presetInstanceId); + std::vector controlValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId); audioHost->SetPluginPreset(pluginInstanceId, controlValues); for (size_t i = 0; i < controlValues.size(); ++i) { const ControlValue &value = controlValues[i]; - this->pedalBoard.SetControlValue(pluginInstanceId, value.key(), value.value()); + this->pedalboard.SetControlValue(pluginInstanceId, value.key(), value.value()); } IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; @@ -1547,7 +1654,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (size_t i = 0; i < atomOutputListeners.size(); ++i) { if (atomOutputListeners[i].clientId == clientId) @@ -1561,7 +1668,7 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId) void PiPedalModel::DeleteMidiListeners(int64_t clientId) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { if (midiEventListeners[i].clientId == clientId) @@ -1573,19 +1680,36 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId) audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0); } -void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson) +bool PiPedalModel::WantsAtomOutput(uint64_t instanceId, LV2_URID atomProperty) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); + for (const AtomOutputListener &listener : atomOutputListeners) + { + if (listener.WantsProperty(instanceId, atomProperty)) + { + return true; + } + } + return false; +} +void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) +{ + std::lock_guard lock(mutex); + std::string propertyUri; for (int i = 0; i < atomOutputListeners.size(); ++i) { auto &listener = atomOutputListeners[i]; - if (listener.instanceId == instanceId) + if (listener.WantsProperty(instanceId, outputAtomProperty)) { auto subscriber = this->GetNotificationSubscriber(listener.clientId); if (subscriber) { - subscriber->OnNotifyAtomOutput(listener.clientHandle, instanceId, atomType, atomJson); + if (propertyUri.length() == 0) + { + propertyUri = lv2Host.GetMapFeature().UridToString(outputAtomProperty); + } + subscriber->OnNotifyAtomOutput(listener.clientHandle, instanceId, propertyUri, atomJson); } else { @@ -1599,7 +1723,7 @@ void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &at void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (int i = 0; i < midiEventListeners.size(); ++i) { @@ -1623,23 +1747,15 @@ void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); MidiListener listener{clientId, clientHandle, listenForControlsOnly}; midiEventListeners.push_back(listener); audioHost->SetListenForMidiEvent(true); } -void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId) -{ - std::lock_guard guard(mutex); - AtomOutputListener listener{clientId, clientHandle, instanceId}; - atomOutputListeners.push_back(listener); - audioHost->SetListenForAtomOutput(true); -} - void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { const auto &listener = midiEventListeners[i]; @@ -1654,9 +1770,23 @@ void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHand audioHost->SetListenForMidiEvent(false); } } -void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle) + +void PiPedalModel::MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); + LV2_URID propertyUrid = 0; + if (propertyUri.length() != 0) + { + propertyUrid = lv2Host.GetMapFeature().GetUrid(propertyUri.c_str()); + } + AtomOutputListener listener{clientId, clientHandle, instanceId, propertyUrid}; + atomOutputListeners.push_back(listener); + audioHost->SetListenForAtomOutput(true); +} + +void PiPedalModel::CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle) +{ + std::lock_guard lock(mutex); for (size_t i = 0; i < atomOutputListeners.size(); ++i) { const auto &listener = atomOutputListeners[i]; @@ -1689,7 +1819,7 @@ std::map PiPedalModel::GetFavorites() const } void PiPedalModel::SetFavorites(const std::map &favorites) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); storage.SetFavorites(favorites); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) @@ -1705,13 +1835,14 @@ void PiPedalModel::SetFavorites(const std::map &favorites) t[i]->OnFavoritesChanged(favorites); } } -std::vector PiPedalModel::GetSystemMidiBidings() { - std::lock_guard guard(mutex); +std::vector PiPedalModel::GetSystemMidiBidings() +{ + std::lock_guard lock(mutex); return this->systemMidiBindings; } void PiPedalModel::SetSystemMidiBindings(std::vector &bindings) { - std::lock_guard guard(mutex); + std::lock_guard lock(mutex); this->systemMidiBindings = bindings; storage.SetSystemMidiBindings(bindings); if (this->audioHost) @@ -1730,15 +1861,15 @@ void PiPedalModel::SetSystemMidiBindings(std::vector &bindings) t[i]->OnSystemMidiBindingsChanged(bindings); } delete[] t; - - } -std::vector PiPedalModel::GetFileList(const PiPedalFileProperty&fileProperty) +std::vector PiPedalModel::GetFileList(const UiFileProperty &fileProperty) { - try { - return this->storage.GetFileList(fileProperty); - } catch (const std::exception & e) + try + { + return this->storage.GetFileList(fileProperty); + } + catch (const std::exception &e) { Lv2Log::warning("GetFileList() failed: (%s)", e.what()); return std::vector(); // don't disclose to users what the problem is. diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 7a4e260..18d9e60 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -19,9 +19,9 @@ #pragma once #include -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include "GovernorSettings.hpp" -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include "Storage.hpp" #include "Banks.hpp" #include "JackConfiguration.hpp" @@ -37,6 +37,8 @@ #include "AdminClient.hpp" #include "AvahiService.hpp" #include +#include "Promise.hpp" +#include "AtomConverter.hpp" namespace pipedal { @@ -50,8 +52,9 @@ namespace pipedal virtual int64_t GetClientId() = 0; virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0; virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0; + virtual void OnLv2StateChanged(int64_t pedalItemId) = 0; virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0; - virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0; + virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0; virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0; virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0; virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0; @@ -62,19 +65,21 @@ namespace pipedal virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector &controlValues) = 0; virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0; virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0; - virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) = 0; + virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0; virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0; virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0; virtual void OnGovernorSettingsChanged(const std::string &governor) = 0; virtual void OnFavoritesChanged(const std::map &favorites) = 0; virtual void OnShowStatusMonitorChanged(bool show) = 0; virtual void OnSystemMidiBindingsChanged(const std::vector&bindings) = 0; + virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0; virtual void Close() = 0; }; class PiPedalModel : private IAudioHostCallbacks { private: + std::unique_ptr pingThread; std::vector systemMidiBindings; @@ -97,9 +102,14 @@ namespace pipedal class AtomOutputListener { public: + bool WantsProperty(uint64_t instanceId,LV2_URID patchProperty) const { + if (instanceId != this->instanceId) return false; + return this->propertyUrid == 0 || patchProperty == this->propertyUrid; + } int64_t clientId; int64_t clientHandle; uint64_t instanceId; + LV2_URID propertyUrid; }; void DeleteMidiListeners(int64_t clientId); void DeleteAtomOutputListeners(int64_t clientId); @@ -108,27 +118,29 @@ namespace pipedal std::vector atomOutputListeners; JackServerSettings jackServerSettings; - PiPedalHost lv2Host; - PedalBoard pedalBoard; + PluginHost lv2Host; + AtomConverter atomConverter; // must be AFTER lv2Host! + + Pedalboard pedalboard; Storage storage; bool hasPresetChanged = false; std::unique_ptr audioHost; JackConfiguration jackConfiguration; - std::shared_ptr lv2PedalBoard; + std::shared_ptr lv2Pedalboard; std::filesystem::path webRoot; std::vector subscribers; void SetPresetChanged(int64_t clientId, bool value); void FirePresetsChanged(int64_t clientId); void FirePluginPresetsChanged(const std::string &pluginUri); - void FirePedalBoardChanged(int64_t clientId); + void FirePedalboardChanged(int64_t clientId); void FireChannelSelectionChanged(int64_t clientId); void FireBanksChanged(int64_t clientId); void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration); - void UpdateDefaults(PedalBoardItem *pedalBoardItem); - void UpdateDefaults(PedalBoard *pedalBoard); + void UpdateDefaults(PedalboardItem *pedalboardItem); + void UpdateDefaults(Pedalboard *pedalboard); class VuSubscription { @@ -147,20 +159,23 @@ namespace pipedal void RestartAudio(); - std::vector outstandingParameterRequests; + std::vector outstandingParameterRequests; IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId); private: // IAudioHostCallbacks - virtual void OnNotifyVusSubscription(const std::vector &updates); - virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update); - virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value); - virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl); - virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson); - virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest); - virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request); + virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override; + virtual void OnNotifyVusSubscription(const std::vector &updates) override; + virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override; + virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override; + virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override; + virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override; + virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) override; +; + virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override; + virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override; - void UpdateVst3Settings(PedalBoard &pedalBoard); + void UpdateVst3Settings(Pedalboard &pedalboard); PiPedalConfiguration configuration; @@ -171,6 +186,8 @@ namespace pipedal uint16_t GetWebPort() const { return webPort; } void Close(); + void SetOnboarding(bool value); + void UpdateDnsSd(); AdminClient &GetAdminClient() { return adminClient; } @@ -180,11 +197,11 @@ namespace pipedal void LoadLv2PluginInfo(); void Load(); - const PiPedalHost &GetLv2Host() const { return lv2Host; } - PedalBoard GetCurrentPedalBoardCopy() + const PluginHost &GetLv2Host() const { return lv2Host; } + Pedalboard GetCurrentPedalboardCopy() { std::lock_guard guard(mutex); - return pedalBoard; // can return a referece because we'd lose mutex protection + return pedalboard; // can return a referece because we'd lose mutex protection } PluginUiPresets GetPluginUiPresets(const std::string &pluginUri); PluginPresets GetPluginPresets(const std::string &pluginUri); @@ -194,15 +211,16 @@ namespace pipedal void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber); void RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber); - void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled); + void SetPedalboardItemEnable(int64_t clientId, int64_t instanceId, bool enabled); void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value); void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value); - void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard); - void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard); + + void SetPedalboard(int64_t clientId, Pedalboard &pedalboard); + void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard); void GetPresets(PresetIndex *pResult); - PedalBoard GetPreset(int64_t instanceId); + Pedalboard GetPreset(int64_t instanceId); void GetBank(int64_t instanceId, BankFile *pBank); int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1); @@ -251,13 +269,22 @@ namespace pipedal int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate); void UnmonitorPort(int64_t subscriptionHandle); - void GetLv2Parameter( + void SendGetPatchProperty( int64_t clientId, int64_t instanceId, const std::string uri, std::function onSuccess, std::function onError); + void SendSetPatchProperty( + int64_t clientId, + int64_t instanceId, + const std::string uri, + const json_variant&value, + std::function onSuccess, + std::function onError); + + BankIndex GetBankIndex() const; void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName); int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName); @@ -273,8 +300,8 @@ namespace pipedal void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly); void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle); - void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId); - void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle); + void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri); + void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle); std::vector GetAlsaDevices(); const std::filesystem::path &GetWebRoot() const; @@ -282,7 +309,7 @@ namespace pipedal std::map GetFavorites() const; void SetFavorites(const std::map &favorites); - std::vector GetFileList(const PiPedalFileProperty&fileProperty); + std::vector GetFileList(const UiFileProperty&fileProperty); }; } // namespace pipedal. \ No newline at end of file diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index b53609d..79e55b3 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -30,6 +30,7 @@ #include #include #include "Ipv6Helpers.hpp" +#include "Promise.hpp" #include "AdminClient.hpp" #include "WifiConfigSettings.hpp" @@ -42,6 +43,33 @@ using namespace std; using namespace pipedal; + +class GetPatchPropertyBody { +public: + uint64_t instanceId_; + std::string propertyUri_; + DECLARE_JSON_MAP(GetPatchPropertyBody); +}; + +JSON_MAP_BEGIN(GetPatchPropertyBody) +JSON_MAP_REFERENCE(GetPatchPropertyBody, instanceId) +JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri) +JSON_MAP_END() + +class SetPatchPropertyBody { +public: + uint64_t instanceId_; + std::string propertyUri_; + json_variant value_; + DECLARE_JSON_MAP(SetPatchPropertyBody); +}; + +JSON_MAP_BEGIN(SetPatchPropertyBody) +JSON_MAP_REFERENCE(SetPatchPropertyBody, instanceId) +JSON_MAP_REFERENCE(SetPatchPropertyBody, propertyUri) +JSON_MAP_REFERENCE(SetPatchPropertyBody, value) +JSON_MAP_END() + class NotifyMidiListenerBody { public: @@ -61,14 +89,15 @@ class NotifyAtomOutputBody public: int64_t clientHandle_; uint64_t instanceId_; - - std::string atomJson_; + std::string propertyUri_; + raw_json_string atomJson_; DECLARE_JSON_MAP(NotifyAtomOutputBody); }; JSON_MAP_BEGIN(NotifyAtomOutputBody) JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle) JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId) +JSON_MAP_REFERENCE(NotifyAtomOutputBody, propertyUri) JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson) JSON_MAP_END() @@ -85,17 +114,19 @@ JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly) JSON_MAP_REFERENCE(ListenForMidiEventBody, handle) JSON_MAP_END() -class ListenForAtomOutputBody +class MonitorPatchPropertyBody { public: uint64_t instanceId_; - int64_t handle_; - DECLARE_JSON_MAP(ListenForAtomOutputBody); + int64_t clientHandle_; + std::string propertyUri_; + DECLARE_JSON_MAP(MonitorPatchPropertyBody); }; -JSON_MAP_BEGIN(ListenForAtomOutputBody) -JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId) -JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle) +JSON_MAP_BEGIN(MonitorPatchPropertyBody) +JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId) +JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle) +JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri) JSON_MAP_END() class OnLoadPluginPresetBody @@ -151,18 +182,6 @@ JSON_MAP_REFERENCE(MonitorResultBody, subscriptionHandle) JSON_MAP_REFERENCE(MonitorResultBody, value) JSON_MAP_END() -class GetLv2ParameterBody -{ -public: - int64_t instanceId_; - std::string uri_; - - DECLARE_JSON_MAP(GetLv2ParameterBody); -}; -JSON_MAP_BEGIN(GetLv2ParameterBody) -JSON_MAP_REFERENCE(GetLv2ParameterBody, instanceId) -JSON_MAP_REFERENCE(GetLv2ParameterBody, uri) -JSON_MAP_END() class MonitorPortBody { @@ -262,33 +281,33 @@ JSON_MAP_REFERENCE(CopyPluginPresetBody, pluginUri) JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId) JSON_MAP_END() -class PedalBoardItemEnabledBody +class PedalboardItemEnabledBody { public: int64_t clientId_ = -1; int64_t instanceId_ = -1; bool enabled_ = true; - DECLARE_JSON_MAP(PedalBoardItemEnabledBody); + DECLARE_JSON_MAP(PedalboardItemEnabledBody); }; -JSON_MAP_BEGIN(PedalBoardItemEnabledBody) -JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, clientId) -JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId) -JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled) +JSON_MAP_BEGIN(PedalboardItemEnabledBody) +JSON_MAP_REFERENCE(PedalboardItemEnabledBody, clientId) +JSON_MAP_REFERENCE(PedalboardItemEnabledBody, instanceId) +JSON_MAP_REFERENCE(PedalboardItemEnabledBody, enabled) JSON_MAP_END() -class UpdateCurrentPedalBoardBody +class UpdateCurrentPedalboardBody { public: int64_t clientId_ = -1; - PedalBoard pedalBoard_; + Pedalboard pedalboard_; - DECLARE_JSON_MAP(UpdateCurrentPedalBoardBody); + DECLARE_JSON_MAP(UpdateCurrentPedalboardBody); }; -JSON_MAP_BEGIN(UpdateCurrentPedalBoardBody) -JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, clientId) -JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, pedalBoard) +JSON_MAP_BEGIN(UpdateCurrentPedalboardBody) +JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId) +JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard) JSON_MAP_END() class ChannelSelectionChangedBody @@ -335,6 +354,24 @@ JSON_MAP_REFERENCE(ControlChangedBody, symbol) JSON_MAP_REFERENCE(ControlChangedBody, value) JSON_MAP_END() +class PatchPropertyChangedBody +{ +public: + int64_t clientId_; + int64_t instanceId_; + std::string propertyUri_; + json_variant value_; + + DECLARE_JSON_MAP(PatchPropertyChangedBody); +}; + +JSON_MAP_BEGIN(PatchPropertyChangedBody) +JSON_MAP_REFERENCE(PatchPropertyChangedBody, clientId) +JSON_MAP_REFERENCE(PatchPropertyChangedBody, instanceId) +JSON_MAP_REFERENCE(PatchPropertyChangedBody, propertyUri) +JSON_MAP_REFERENCE(PatchPropertyChangedBody, value) +JSON_MAP_END() + class Vst3ControlChangedBody { public: @@ -420,14 +457,19 @@ public: std::stringstream imageList; const std::filesystem::path &webRoot = model.GetWebRoot() / "img"; bool firstTime = true; - for (const auto &entry : std::filesystem::directory_iterator(webRoot)) - { - if (!firstTime) + try { + for (const auto &entry : std::filesystem::directory_iterator(webRoot)) { - imageList << ";"; + if (!firstTime) + { + imageList << ";"; + } + firstTime = false; + imageList << entry.path().filename().string(); } - firstTime = false; - imageList << entry.path().filename().string(); + } catch (const std::exception&) + { + Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str()); } this->imageList = imageList.str(); } @@ -778,17 +820,17 @@ public: } return; } - if (message == "previewControl") - { - ControlChangedBody message; - pReader->read(&message); - this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_); - } - else if (message == "setControl") + if (message == "setControl") { ControlChangedBody message; pReader->read(&message); this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_); + } + else if (message == "previewControl") + { + ControlChangedBody message; + pReader->read(&message); + this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_); } else if (message == "listenForMidiEvent") { @@ -802,17 +844,17 @@ public: pReader->read(&handle); this->model.CancelListenForMidiEvent(this->clientId, handle); } - else if (message == "listenForAtomOutput") + else if (message == "monitorPatchProperty") { - ListenForAtomOutputBody body; + MonitorPatchPropertyBody body; pReader->read(&body); - this->model.ListenForAtomOutputs(this->clientId, body.handle_, body.instanceId_); + this->model.MonitorPatchProperty(this->clientId, body.clientHandle_, body.instanceId_, body.propertyUri_); } - else if (message == "cancelListenForAtomOutput") + else if (message == "cancelMonitorPatchProperty") { int64_t handle; pReader->read(&handle); - this->model.CancelListenForMidiEvent(this->clientId, handle); + this->model.CancelMonitorPatchProperty(this->clientId, handle); } else if (message == "getJackStatus") @@ -954,25 +996,25 @@ public: this->model.GetPresets(&presets); Reply(replyTo, "getPresets", presets); } - else if (message == "setPedalBoardItemEnable") + else if (message == "setPedalboardItemEnable") { - PedalBoardItemEnabledBody body; + PedalboardItemEnabledBody body; pReader->read(&body); - model.SetPedalBoardItemEnable(body.clientId_, body.instanceId_, body.enabled_); + model.SetPedalboardItemEnable(body.clientId_, body.instanceId_, body.enabled_); } - else if (message == "updateCurrentPedalBoard") + else if (message == "updateCurrentPedalboard") { { - UpdateCurrentPedalBoardBody body; + UpdateCurrentPedalboardBody body; pReader->read(&body); - this->model.UpdateCurrentPedalBoard(body.clientId_, body.pedalBoard_); + this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_); } } - else if (message == "currentPedalBoard") + else if (message == "currentPedalboard") { - auto pedalBoard = model.GetCurrentPedalBoardCopy(); - Reply(replyTo, "currentPedalBoard", pedalBoard); + auto pedalboard = model.GetCurrentPedalboardCopy(); + Reply(replyTo, "currentPedalboard", pedalboard); } else if (message == "plugins") { @@ -1142,18 +1184,35 @@ public: uint64_t result = model.CopyPluginPreset(body.pluginUri_, body.instanceId_); this->Reply(replyTo, "copyPluginPreset", result); } - else if (message == "getLv2Parameter") + else if (message == "setPatchProperty") { - GetLv2ParameterBody body; + SetPatchPropertyBody body; + pReader->read(&body); + model.SendSetPatchProperty(clientId,body.instanceId_,body.propertyUri_,body.value_, + [this, replyTo] () { + this->JsonReply(replyTo, "setPatchProperty", "true"); + + }, + [this, replyTo] (const std::string&error) { + this->SendError(replyTo, error.c_str()); + + } + ); + + } + + else if (message == "getPatchProperty") + { + GetPatchPropertyBody body; pReader->read(&body); - model.GetLv2Parameter( + model.SendGetPatchProperty( this->clientId, body.instanceId_, - body.uri_, + body.propertyUri_, [this, replyTo](const std::string &jsonResult) { - this->JsonReply(replyTo, "getLv2Parameter", jsonResult.c_str()); + this->JsonReply(replyTo, "getPatchProperty", jsonResult.c_str()); }, [this, replyTo](const std::string &error) { @@ -1248,10 +1307,17 @@ public: } else if (message == "requestFileList") { - PiPedalFileProperty fileProperty; + UiFileProperty fileProperty; + pReader->read(&fileProperty); std::vector list = this->model.GetFileList(fileProperty); this->Reply(replyTo,"requestFileList",list); } + else if (message == "setOnboarding") + { + bool value; + pReader->read(&value); + this->model.SetOnboarding(value); + } else { Lv2Log::error("Unknown message received: %s", message.c_str()); @@ -1260,8 +1326,7 @@ public: } protected: - virtual void - onReceive(const std::string_view &text) + virtual void onReceive(const std::string_view &text) { view_istream s(text); @@ -1324,7 +1389,22 @@ protected: } } -public: +private: + virtual void OnLv2StateChanged(int64_t instanceId) + { + Send("onLv2StateChanged",instanceId); + + } + virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) + { + PatchPropertyChangedBody body; + body.clientId_ = clientId; + body.instanceId_ = instanceId; + body.propertyUri_ = propertyUri; + body.value_ = value; + Send("onPatchPropertyChanged",body); + } + virtual void OnSystemMidiBindingsChanged(const std::vector&bindings) { Send("onSystemMidiBindingsChanged",bindings); } @@ -1499,13 +1579,13 @@ public: public: int64_t clientHandle; uint64_t instanceId; - std::string atomType; + std::string propertyUri; std::string json; }; std::vector pendingNotifyAtomOutputs; - void OnAckNotifyAtomOutput() + void OnAckNotifyPatchProperty() { std::lock_guard guard(subscriptionMutex); if (--outstandingNotifyAtomOutputs <= 0) @@ -1518,19 +1598,24 @@ public: OnNotifyAtomOutput( t.clientHandle, t.instanceId, - t.atomType, + t.propertyUri, t.json); } } } - virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) + virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomProperty, const std::string &atomJson) { NotifyAtomOutputBody body; body.clientHandle_ = clientHandle; body.instanceId_ = instanceId; - body.atomJson_ = atomJson; + body.propertyUri_ = atomProperty; + body.atomJson_.Set(atomJson); + // flow control. We can only have one in-flight NotifyAtomOutput at a time. + // Subsequent notifications are held until we receive an ack. + // + // If a duplicate atomProperty is queued, overwrite the previous value. { std::lock_guard guard(subscriptionMutex); @@ -1539,23 +1624,24 @@ public: for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i) { auto &output = pendingNotifyAtomOutputs[i]; - if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.atomType == atomType) + if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty) { - output.json = atomJson; - return; + // better to erase than overwrite, since it provides better idempotence. + pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i); + break; } } - pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomType, atomJson}); + pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomProperty, atomJson}); return; } ++outstandingNotifyAtomOutputs; } Request( - "onNotifyAtomOut", body, + "onNotifyPatchProperty", body, [this](const bool &value) { - this->OnAckNotifyAtomOutput(); + this->OnAckNotifyPatchProperty(); }, [](const std::exception &e) { @@ -1594,17 +1680,17 @@ public: Send("onGovernorSettingsChanged", governor); } - virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) + virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) { - UpdateCurrentPedalBoardBody body; + UpdateCurrentPedalboardBody body; body.clientId_ = clientId; - body.pedalBoard_ = pedalBoard; - Send("onPedalBoardChanged", body); + body.pedalboard_ = pedalboard; + Send("onPedalboardChanged", body); } virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) { - PedalBoardItemEnabledBody body; + PedalboardItemEnabledBody body; body.clientId_ = clientId; body.instanceId_ = pedalItemId; body.enabled_ = enabled; diff --git a/src/PiPedalUI.cpp b/src/PiPedalUI.cpp index 6f0d18a..5a53093 100644 --- a/src/PiPedalUI.cpp +++ b/src/PiPedalUI.cpp @@ -23,11 +23,12 @@ */ #include "PiPedalUI.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" +#include "ss.hpp" using namespace pipedal; -PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode) +PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::filesystem::path &resourcePath) { auto pWorld = pHost->getWorld(); @@ -37,18 +38,35 @@ PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode) const LilvNode *fileNode = lilv_nodes_get(fileNodes, i); try { - PiPedalFileProperty::ptr fileUI = std::make_shared(pHost, fileNode); + UiFileProperty::ptr fileUI = std::make_shared(pHost, fileNode, resourcePath); this->fileProperites_.push_back(std::move(fileUI)); } catch (const std::exception &e) { - pHost->LogError(e.what()); + pHost->LogWarning(SS("Failed to read pipedalui::fileProperties. " << e.what())); } } + + LilvNodes *portNotifications = lilv_world_find_nodes(pWorld, uiNode, pHost->lilvUris.ui__portNotification, nullptr); + LILV_FOREACH(nodes, i, portNotifications) + { + const LilvNode *portNotificationNode = lilv_nodes_get(portNotifications, i); + try + { + UiPortNotification::ptr portNotification = std::make_shared(pHost, portNotificationNode); + this->portNotifications_.push_back(std::move(portNotification)); + } + catch (const std::exception &e) + { + pHost->LogWarning(SS("Failed to read ui:portNotifications. " << e.what())); + } + } + lilv_nodes_free(fileNodes); } -PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) { +PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node) +{ auto pWorld = pHost->getWorld(); AutoLilvNode name = lilv_world_get( @@ -77,9 +95,8 @@ PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) { { throw std::logic_error("pipedal_ui:fileType is missing fileExtension property."); } - } -PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *node) +UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath) { auto pWorld = pHost->getWorld(); @@ -110,9 +127,9 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod throw std::logic_error("Pipedal FileProperty::directory must have only alpha-numeric characters."); } } - else + if (directory_.length() == 0) { - throw std::logic_error("PiPedal FileProperty is missing a pipedalui:directory value."); + throw std::logic_error("PipedalUI::fileProperty: must specify at least a directory."); } AutoLilvNode patchProperty = lilv_world_get( @@ -123,24 +140,19 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod if (patchProperty) { this->patchProperty_ = patchProperty.AsUri(); - } else { + } + 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); + this->fileTypes_ = PiPedalFileType::GetArray(pHost, node, pHost->lilvUris.pipedalUI__fileTypes); } -std::vector PiPedalFileType::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri) +std::vector PiPedalFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri) { std::vector result; - LilvWorld* pWorld = pHost->getWorld(); + LilvWorld *pWorld = pHost->getWorld(); LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr); LILV_FOREACH(nodes, i, fileTypeNodes) @@ -160,35 +172,33 @@ std::vector PiPedalFileType::GetArray(PiPedalHost*pHost, const return result; } -bool pipedal::IsAlphaNumeric(const std::string&value) +bool pipedal::IsAlphaNumeric(const std::string &value) { - for (char c:value) + for (char c : value) { if ( - (c >= '0' && c <= '9') - || (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c == '_') + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') - ) { + ) + { continue; } return false; - } return true; } -bool PiPedalFileProperty::IsDirectoryNameValid(const std::string&value) +bool UiFileProperty::IsDirectoryNameValid(const std::string &value) { - if (value.length() == 0) return false; + if (value.length() == 0) + return false; return IsAlphaNumeric(value); } -bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const +bool UiFileProperty::IsValidExtension(const std::string &extension) const { - for (auto&fileType: fileTypes_) + for (auto &fileType : fileTypes_) { if (fileType.fileExtension() == extension) { @@ -198,15 +208,69 @@ bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const return false; } +UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node) +{ + // ui:portNotification + // [ + // ui:portIndex 3; + // ui:plugin ; + // ui:protocol ui:floatProtocol; + // // pipedal_ui:style pipedal_ui:text ; + // // pipedal_ui:redLevel 0; + // // pipedal_ui:yellowLevel -12; + // ] + LilvWorld *pWorld = pHost->getWorld(); -JSON_MAP_BEGIN(PiPedalFileType) - JSON_MAP_REFERENCE(PiPedalFileType,name) - JSON_MAP_REFERENCE(PiPedalFileType,fileExtension) + AutoLilvNode portIndex = lilv_world_get(pWorld,node,pHost->lilvUris.ui__portIndex,nullptr); + if (!portIndex) + { + this->portIndex_ = -1; + } else { + this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex); + } + AutoLilvNode symbol = lilv_world_get(pWorld,node,pHost->lilvUris.lv2__symbol,nullptr); + if (!symbol) + { + this->symbol_ = ""; + } else { + this->symbol_ = symbol.AsString(); + } + AutoLilvNode plugin = lilv_world_get(pWorld,node,pHost->lilvUris.ui__plugin,nullptr); + if (!plugin) + { + this->plugin_ = ""; + } else { + this->plugin_ = plugin.AsUri(); + } + AutoLilvNode protocol = lilv_world_get(pWorld,node,pHost->lilvUris.ui__protocol,nullptr); + if (!protocol) + { + this->protocol_ = ""; + } else { + this->protocol_ = protocol.AsUri(); + } + if (this->portIndex_ == -1 &&this->symbol_ == "") + { + pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol."); + } + +} + +JSON_MAP_BEGIN(UiPortNotification) +JSON_MAP_REFERENCE(UiPortNotification, portIndex) +JSON_MAP_REFERENCE(UiPortNotification, symbol) +JSON_MAP_REFERENCE(UiPortNotification, plugin) +JSON_MAP_REFERENCE(UiPortNotification, protocol) 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_BEGIN(PiPedalFileType) +JSON_MAP_REFERENCE(PiPedalFileType, name) +JSON_MAP_REFERENCE(PiPedalFileType, fileExtension) +JSON_MAP_END() + +JSON_MAP_BEGIN(UiFileProperty) +JSON_MAP_REFERENCE(UiFileProperty, name) +JSON_MAP_REFERENCE(UiFileProperty, directory) +JSON_MAP_REFERENCE(UiFileProperty, fileTypes) +JSON_MAP_REFERENCE(UiFileProperty, patchProperty) JSON_MAP_END() \ No newline at end of file diff --git a/src/PiPedalUI.hpp b/src/PiPedalUI.hpp index ff776fa..be16944 100644 --- a/src/PiPedalUI.hpp +++ b/src/PiPedalUI.hpp @@ -28,6 +28,7 @@ #include #include #include "json.hpp" +#include #define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui" @@ -38,7 +39,6 @@ #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" @@ -53,7 +53,7 @@ namespace pipedal { - class PiPedalHost; + class PluginHost; class PiPedalFileType { private: @@ -61,9 +61,9 @@ namespace pipedal { std::string fileExtension_; public: PiPedalFileType() { } - PiPedalFileType(PiPedalHost*pHost, const LilvNode*node); + PiPedalFileType(PluginHost*pHost, const LilvNode*node); - static std::vector GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri); + static std::vector GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri); const std::string& name() const { return name_;} const std::string &fileExtension() const { return fileExtension_; } @@ -73,22 +73,38 @@ namespace pipedal { }; - class PiPedalFileProperty { + + + class UiPortNotification { + private: + int32_t portIndex_; + std::string symbol_; + std::string plugin_; + std::string protocol_; + public: + using ptr = std::shared_ptr; + + UiPortNotification() { } + UiPortNotification(PluginHost*pHost, const LilvNode*node); + + public: + DECLARE_JSON_MAP(UiPortNotification); + + }; + class UiFileProperty { 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); + using ptr = std::shared_ptr; + UiFileProperty() { } + UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath); 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_; } @@ -97,19 +113,35 @@ namespace pipedal { static bool IsDirectoryNameValid(const std::string&value); public: - DECLARE_JSON_MAP(PiPedalFileProperty); + DECLARE_JSON_MAP(UiFileProperty); }; class PiPedalUI { public: using ptr = std::shared_ptr; - PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode); - const std::vector& fileProperties() const + PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath); + const std::vector& fileProperties() const { return fileProperites_; } + + const std::vector &portNotifications() const { return portNotifications_; } + + const UiFileProperty*GetFileProperty(const std::string &propertyUri) const + { + for (const auto&fileProperty : fileProperties()) + { + if (fileProperty->patchProperty() == propertyUri) + { + return fileProperty.get(); + } + } + return nullptr; + } + private: - std::vector fileProperites_; + std::vector fileProperites_; + std::vector portNotifications_; }; // Utiltities for validating file paths received via PiPedalFileProperty-related APIs. diff --git a/src/PiPedalHost.cpp b/src/PluginHost.cpp similarity index 70% rename from src/PiPedalHost.cpp rename to src/PluginHost.cpp index 1887bd9..21244c0 100644 --- a/src/PiPedalHost.cpp +++ b/src/PluginHost.cpp @@ -18,19 +18,20 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include #include #include #include #include "Lv2Log.hpp" #include -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include "Lv2Effect.hpp" -#include "Lv2PedalBoard.hpp" +#include "Lv2Pedalboard.hpp" #include "OptionsFeature.hpp" #include "JackConfiguration.hpp" #include "lv2/urid.lv2/urid.h" +#include "lv2/ui.lv2/ui.h" #include "lv2.h" #include "lv2/atom.lv2/atom.h" #include "lv2/time/time.h" @@ -86,27 +87,37 @@ namespace pipedal static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent"; static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation"; - class PiPedalHost::Urids + class PluginHost::Urids { public: Urids(MapFeature &mapFeature) { atom_Float = mapFeature.GetUrid(LV2_ATOM__Float); + ui__portNotification = mapFeature.GetUrid(LV2_UI__portNotification); + ui__plugin = mapFeature.GetUrid(LV2_UI__plugin); + ui__protocol = mapFeature.GetUrid(LV2_UI__protocol); + ui__floatProtocol = mapFeature.GetUrid(LV2_UI__protocol); + ui__peakProtocol = mapFeature.GetUrid(LV2_UI__peakProtocol); } LV2_URID atom_Float; + LV2_URID ui__portNotification; + LV2_URID ui__plugin; + LV2_URID ui__protocol; + LV2_URID ui__floatProtocol; + LV2_URID ui__peakProtocol; }; } -void PiPedalHost::SetConfiguration(const PiPedalConfiguration &configuration) +void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration) { this->vst3CachePath = std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json"; this->vst3Enabled = configuration.IsVst3Enabled(); } -void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld) +void PluginHost::LilvUris::Initialize(LilvWorld *pWorld) { - rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI); + rdfsComment = lilv_new_uri(pWorld, PluginHost::RDFS_COMMENT_URI); logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC); display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY); range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS); @@ -126,15 +137,35 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld) 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__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__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); + + + ui__portNotification = lilv_new_uri(pWorld, LV2_UI__portNotification); + ui__plugin = lilv_new_uri(pWorld, LV2_UI__plugin); + ui__protocol = lilv_new_uri(pWorld, LV2_UI__protocol); + ui__floatProtocol = lilv_new_uri(pWorld, LV2_UI__protocol); + ui__peakProtocol = lilv_new_uri(pWorld, LV2_UI__peakProtocol); + ui__portIndex = lilv_new_uri(pWorld,LV2_UI__portIndex); + lv2__symbol = lilv_new_uri(pWorld,LV2_CORE__symbol); + + + + // ui:portNotification + // [ + // ui:portIndex 3; + // ui:plugin ; + // ui:protocol ui:floatProtocol; + // // pipedal_ui:style pipedal_ui:text ; + // // pipedal_ui:redLevel 0; + // // pipedal_ui:yellowLevel -12; + // ] time_Position = lilv_new_uri(pWorld, LV2_TIME__Position); time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat); @@ -144,7 +175,7 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld) appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo); } -void PiPedalHost::LilvUris::Free() +void PluginHost::LilvUris::Free() { rdfsComment.Free(); logarithic_uri.Free(); @@ -214,45 +245,56 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0) return default_; } -PiPedalHost::PiPedalHost() +void PluginHost::SetPluginStoragePath(const std::filesystem::path &path) +{ + mapPathFeature.SetPluginStoragePath(path); +} + +PluginHost::PluginHost() + : mapPathFeature("") { pWorld = nullptr; LV2_Feature **features = new LV2_Feature *[10]; - features[0] = const_cast(mapFeature.GetMapFeature()); - features[1] = const_cast(mapFeature.GetUnmapFeature()); + lv2Features.push_back(mapFeature.GetMapFeature()); + lv2Features.push_back(mapFeature.GetUnmapFeature()); logFeature.Prepare(&mapFeature); - features[2] = const_cast(logFeature.GetFeature()); + lv2Features.push_back(logFeature.GetFeature()); optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize()); - features[3] = const_cast(optionsFeature.GetFeature()); - features[4] = nullptr; - this->lv2Features = features; + mapPathFeature.Prepare(&mapFeature); + lv2Features.push_back(mapPathFeature.GetMapPathFeature()); + lv2Features.push_back(mapPathFeature.GetMakePathFeature()); + lv2Features.push_back(optionsFeature.GetFeature()); + + lv2Features.push_back(nullptr); + this->urids = new Urids(mapFeature); + + pHostWorkerThread = std::make_shared(); } -void PiPedalHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings) +void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings) { - this->sampleRate = configuration.GetSampleRate(); + this->sampleRate = configuration.sampleRate(); if (configuration.isValid()) { this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size(); this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size(); - this->maxBufferSize = configuration.GetBlockLength(); - optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize()); + this->maxBufferSize = configuration.blockLength(); + optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize()); } } -PiPedalHost::~PiPedalHost() +PluginHost::~PluginHost() { - delete[] lv2Features; lilvUris.Free(); free_world(); delete urids; } -void PiPedalHost::free_world() +void PluginHost::free_world() { if (pWorld) { @@ -260,7 +302,7 @@ void PiPedalHost::free_world() pWorld = nullptr; } } -std::shared_ptr PiPedalHost::GetPluginClass(const std::string &uri) const +std::shared_ptr PluginHost::GetPluginClass(const std::string &uri) const { auto it = this->classesMap.find(uri); if (it == this->classesMap.end()) @@ -268,7 +310,7 @@ std::shared_ptr PiPedalHost::GetPluginClass(const std::string &u return (*it).second; } -void PiPedalHost::AddJsonClassesToMap(std::shared_ptr pluginClass) +void PluginHost::AddJsonClassesToMap(std::shared_ptr pluginClass) { this->classesMap[pluginClass->uri()] = pluginClass; for (int i = 0; i < pluginClass->children_.size(); ++i) @@ -278,7 +320,7 @@ void PiPedalHost::AddJsonClassesToMap(std::shared_ptr pluginClas child->parent_ = pluginClass.get(); } } -void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile) +void PluginHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile) { std::ifstream f(jsonFile); json_reader reader(f); @@ -292,7 +334,7 @@ void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile) MAP_CHECK(); } -std::shared_ptr PiPedalHost::GetPluginClass(const LilvPluginClass *pClass) +std::shared_ptr PluginHost::GetPluginClass(const LilvPluginClass *pClass) { MAP_CHECK(); @@ -300,7 +342,7 @@ std::shared_ptr PiPedalHost::GetPluginClass(const LilvPluginClas std::shared_ptr pResult = this->classesMap[uri]; return pResult; } -std::shared_ptr PiPedalHost::MakePluginClass(const LilvPluginClass *pClass) +std::shared_ptr PluginHost::MakePluginClass(const LilvPluginClass *pClass) { std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass)); auto t = this->classesMap.find(uri); @@ -320,7 +362,7 @@ std::shared_ptr PiPedalHost::MakePluginClass(const LilvPluginCla return result; } -void PiPedalHost::LoadPluginClassesFromLilv() +void PluginHost::LoadPluginClassesFromLilv() { const auto classes = lilv_world_get_plugin_classes(pWorld); @@ -338,19 +380,19 @@ void PiPedalHost::LoadPluginClassesFromLilv() if (lv2Class && lv2Class->parent_uri().size() != 0) { - std::shared_ptr parentClass = GetPluginClass(lv2Class->parent_uri()); - if (parentClass) - { - Lv2PluginClass *ccClass = const_cast(lv2Class.get()); - ccClass->set_parent(parentClass); - Lv2PluginClass *ccParentClass = parentClass.get(); - ccParentClass->add_child(lv2Class); - } + std::shared_ptr parentClass = GetPluginClass(lv2Class->parent_uri()); + if (parentClass) + { + Lv2PluginClass *ccClass = const_cast(lv2Class.get()); + ccClass->set_parent(parentClass); + Lv2PluginClass *ccParentClass = parentClass.get(); + ccParentClass->add_child(lv2Class); + } } } } -void PiPedalHost::Load(const char *lv2Path) +void PluginHost::Load(const char *lv2Path) { this->plugins_.clear(); @@ -388,30 +430,30 @@ void PiPedalHost::Load(const char *lv2Path) if (pluginInfo->hasCvPorts()) { - Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); + Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); } #if !SUPPORT_MIDI else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN) { - Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); + Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); } #endif else if (!pluginInfo->is_valid()) { - auto &ports = pluginInfo->ports(); - for (int i = 0; i < ports.size(); ++i) - { - auto &port = ports[i]; - if (!port->is_valid()) + auto &ports = pluginInfo->ports(); + for (int i = 0; i < ports.size(); ++i) { - Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str()); + auto &port = ports[i]; + if (!port->is_valid()) + { + Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str()); + } } - } - Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); + Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str()); } else { - this->plugins_.push_back(pluginInfo); + this->plugins_.push_back(pluginInfo); } } auto messages = stdoutCapture.GetOutputLines(); @@ -440,28 +482,28 @@ void PiPedalHost::Load(const char *lv2Path) if (plugin->is_valid()) { #if SUPPORT_MIDI - if (info.audio_inputs() == 0 && !info.has_midi_input()) - { - Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str()); - } - else if (info.audio_outputs() == 0 && !info.has_midi_output()) - { - Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str()); - } + if (info.audio_inputs() == 0 && !info.has_midi_input()) + { + Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str()); + } + else if (info.audio_outputs() == 0 && !info.has_midi_output()) + { + Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str()); + } #else - if (info.audio_inputs() == 0) - { - Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str()); - } - else if (info.audio_outputs() == 0) - { - Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str()); - } + if (info.audio_inputs() == 0) + { + Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str()); + } + else if (info.audio_outputs() == 0) + { + Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str()); + } #endif - else - { - ui_plugins_.push_back(std::move(info)); - } + else + { + ui_plugins_.push_back(std::move(info)); + } } } @@ -476,8 +518,8 @@ void PiPedalHost::Load(const char *lv2Path) const auto &vst3PluginList = this->vst3Host->getPluginList(); for (const auto &vst3Plugin : vst3PluginList) { - // copy not move! - ui_plugins_.push_back(vst3Plugin->pluginInfo_); + // copy not move! + ui_plugins_.push_back(vst3Plugin->pluginInfo_); } auto ui_compare = [&collation]( Lv2PluginUiInfo &left, @@ -532,10 +574,10 @@ static std::vector nodeAsStringArray(const LilvNodes *nodes) return result; } -const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#" - "comment"; +const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#" + "comment"; -LilvNode *PiPedalHost::get_comment(const std::string &uri) +LilvNode *PluginHost::get_comment(const std::string &uri) { AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str()); LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr); @@ -547,7 +589,7 @@ static bool ports_sort_compare(std::shared_ptr &p1, const std::shar return p1->index() < p2->index(); } -bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin) +bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin) { NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset); bool result = false; @@ -559,8 +601,21 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl return result; } -Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin) +Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin) { + + AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin); + if (!bundleUriNode) + throw std::logic_error("Invalid bundle uri."); + + std::string bundleUri = bundleUriNode.AsUri(); + + std::string bundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr); + if (bundlePath.length() == 0) + throw std::logic_error("Bundle uri is not a file uri."); + + this->bundle_path_ = bundlePath; + this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin); this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin)); @@ -604,15 +659,15 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv std::shared_ptr portInfo = std::make_shared(lv2Host, pPlugin, pPort); if (!portInfo->is_valid()) { - isValid = false; + isValid = false; } const auto &portGroup = portInfo->port_group(); if (portGroup.size() != 0) { - if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end()) - { - portGroups.push_back(portGroup); - } + if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end()) + { + portGroups.push_back(portGroup); + } } ports_.push_back(std::move(portInfo)); } @@ -626,8 +681,8 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv { if (!this->IsSupportedFeature(this->required_features_[i])) { - Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str()); - isValid = false; + Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str()); + isValid = false; } } @@ -642,7 +697,7 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv nullptr); if (pipedalUINode) { - this->piPedalUI_ = std::make_shared(lv2Host, pipedalUINode); + this->piPedalUI_ = std::make_shared(lv2Host, pipedalUINode, std::filesystem::path(bundlePath)); } // xxx lilv_world_get(pWorld,pluginUri,); // for (auto&portInfo: ports_) @@ -666,7 +721,12 @@ std::vector supportedFeatures = { LV2_BUF_SIZE__fixedBlockLength, LV2_BUF_SIZE__powerOf2BlockLength, LV2_CORE__isLive, - LV2_STATE__loadDefaultState + LV2_STATE__loadDefaultState, + LV2_STATE__makePath, + LV2_STATE__mapPath, + + // UI features that we can ignore, since we won't load their ui. + "http://lv2plug.in/ns/extensions/ui#makeResident", }; @@ -675,7 +735,7 @@ bool Lv2PluginInfo::IsSupportedFeature(const std::string &feature) const for (int i = 0; i < supportedFeatures.size(); ++i) { if (supportedFeatures[i] == feature) - return true; + return true; } return false; } @@ -683,7 +743,7 @@ Lv2PluginInfo::~Lv2PluginInfo() { } -bool Lv2PortInfo::is_a(PiPedalHost *lv2Plugins, const char *classUri) +bool Lv2PortInfo::is_a(PluginHost *lv2Plugins, const char *classUri) { return classes_.is_a(lv2Plugins, classUri); } @@ -705,7 +765,7 @@ static bool scale_points_sort_compare(const Lv2ScalePoint &v1, const Lv2ScalePoi { return v1.value() < v2.value(); } -Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const LilvPort *pPort) +Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvPort *pPort) { auto pWorld = host->pWorld; index_ = lilv_port_get_index(plugin, pPort); @@ -720,7 +780,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv min_value_ = 0; max_value_ = 1; default_value_ = 0; - lilv_port_get_range(plugin, pPort, &defaultNode.Get(), &minNode.Get(), &maxNode.Get()); + lilv_port_get_range(plugin, pPort, defaultNode, minNode, maxNode); if (defaultNode) { default_value_ = getFloat(defaultNode); @@ -748,7 +808,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv auto priority_node = lilv_nodes_get_first(priority_nodes); if (priority_node) { - this->display_priority_ = lilv_node_as_int(priority_node); + this->display_priority_ = lilv_node_as_int(priority_node); } } @@ -759,7 +819,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv auto range_steps_node = lilv_nodes_get_first(range_steps_nodes); if (range_steps_node) { - this->range_steps_ = lilv_node_as_int(range_steps_node); + this->range_steps_ = lilv_node_as_int(range_steps_node); } } this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.integer_property_uri); @@ -819,7 +879,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv ((is_input_ || is_output_) && (is_audio_port_ || is_atom_port_ || is_cv_port_)); } -Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort) +Lv2PluginClasses PluginHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort) { std::vector result; const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort); @@ -827,9 +887,9 @@ Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, c { LILV_FOREACH(nodes, iNode, nodes) { - const LilvNode *node = lilv_nodes_get(nodes, iNode); - std::string classUri = nodeAsString(node); - result.push_back(classUri); + const LilvNode *node = lilv_nodes_get(nodes, iNode); + std::string classUri = nodeAsString(node); + result.push_back(classUri); } } return Lv2PluginClasses(result); @@ -851,20 +911,20 @@ bool Lv2PluginClass::is_a(const std::string &classUri) const } return false; } -bool Lv2PluginClasses::is_a(PiPedalHost *lv2Plugins, const char *classUri) const +bool Lv2PluginClasses::is_a(PluginHost *lv2Plugins, const char *classUri) const { std::string classUri_(classUri); for (auto i : classes_) { if (lv2Plugins->is_a(classUri_, i)) { - return true; + return true; } } return false; } -bool PiPedalHost::is_a(const std::string &class_, const std::string &target_class) +bool PluginHost::is_a(const std::string &class_, const std::string &target_class) { if (class_ == target_class) return true; @@ -876,7 +936,7 @@ bool PiPedalHost::is_a(const std::string &class_, const std::string &target_clas return false; } -Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin) +Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin) : uri_(plugin->uri()), name_(plugin->name()), author_name_(plugin->author_name()), @@ -898,38 +958,38 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin { PLUGIN_MAP_CHECK(); this->plugin_display_type_ = "Plugin"; - Lv2Log::error("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str()); + Lv2Log::warning("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str()); } for (auto port : plugin->ports()) { if (port->is_input()) { - if (port->is_control_port() && port->is_input()) - { - controls_.push_back(Lv2PluginUiControlPort(plugin, port.get())); - } - else if (port->is_atom_port()) - { - if (port->supports_midi()) + if (port->is_control_port() && port->is_input()) { - has_midi_input_ = true; + controls_.push_back(Lv2PluginUiControlPort(plugin, port.get())); + } + else if (port->is_atom_port()) + { + if (port->supports_midi()) + { + has_midi_input_ = true; + } + } + else if (port->is_audio_port()) + { + ++audio_inputs_; } - } - else if (port->is_audio_port()) - { - ++audio_inputs_; - } } else if (port->is_output()) { - if (port->is_atom_port() && port->supports_midi()) - { - this->has_midi_output_ = true; - } - else if (port->is_audio_port()) - { - ++audio_outputs_; - } + if (port->is_atom_port() && port->supports_midi()) + { + this->has_midi_output_ = true; + } + else if (port->is_audio_port()) + { + ++audio_outputs_; + } } } for (auto &portGroup : plugin->port_groups()) @@ -941,55 +1001,56 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin if (piPedalUI) { this->fileProperties_ = piPedalUI->fileProperties(); + this->uiPortNotifications_ = piPedalUI->portNotifications(); } } -std::shared_ptr PiPedalHost::GetLv2PluginClass() const +std::shared_ptr PluginHost::GetLv2PluginClass() const { return this->GetPluginClass(LV2_PLUGIN); } -std::shared_ptr PiPedalHost::GetPluginInfo(const std::string &uri) const +std::shared_ptr PluginHost::GetPluginInfo(const std::string &uri) const { for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i) { if ((*i)->uri() == uri) { - return (*i); + return (*i); } } return nullptr; } -Lv2PedalBoard *PiPedalHost::CreateLv2PedalBoard(PedalBoard &pedalBoard) +Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard) { - Lv2PedalBoard *pPedalBoard = new Lv2PedalBoard(); + Lv2Pedalboard *pPedalboard = new Lv2Pedalboard(); try { - pPedalBoard->Prepare(this, pedalBoard); - return pPedalBoard; + pPedalboard->Prepare(this, pedalboard); + return pPedalboard; } - catch (const std::exception &) + catch (const std::exception &e) { - delete pPedalBoard; + delete pPedalboard; throw; } } struct StateCallbackData { - PiPedalHost *pHost; + PluginHost *pHost; std::vector *pResult; }; -void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol, - void *user_data, - const void *value, - uint32_t size, - uint32_t type) +void PluginHost::fn_LilvSetPortValueFunc(const char *port_symbol, + void *user_data, + const void *value, + uint32_t size, + uint32_t type) { StateCallbackData *pData = (StateCallbackData *)user_data; - PiPedalHost *pHost = pData->pHost; + PluginHost *pHost = pData->pHost; std::vector *pResult = pData->pResult; LV2_URID valueType = (LV2_URID)type; if (valueType != pHost->urids->atom_Float) @@ -999,15 +1060,15 @@ void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol, pResult->push_back(ControlValue(port_symbol, *(float *)value)); } -std::vector PiPedalHost::LoadFactoryPluginPreset( - PedalBoardItem *pedalBoardItem, const std::string &presetUri) +std::vector PluginHost::LoadFactoryPluginPreset( + PedalboardItem *pedalboardItem, const std::string &presetUri) { std::vector result; const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld); - AutoLilvNode 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); @@ -1027,27 +1088,27 @@ std::vector PiPedalHost::LoadFactoryPluginPreset( if (presetUri == thisPresetUri) { - /*********************************/ + /*********************************/ - // AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str()); + // AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str()); - auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset); + auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset); - if (lilvState == nullptr) - { - throw PiPedalStateException("Preset not found."); - } + if (lilvState == nullptr) + { + throw PiPedalStateException("Preset not found."); + } - StateCallbackData cbData; - cbData.pHost = this; - cbData.pResult = &result; - auto n = lilv_state_get_num_properties(lilvState); - lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData); + StateCallbackData cbData; + cbData.pHost = this; + cbData.pResult = &result; + auto n = lilv_state_get_num_properties(lilvState); + lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData); - lilv_state_free(lilvState); - break; + lilv_state_free(lilvState); + break; - /*********************************/ + /*********************************/ } } lilv_nodes_free(presets); @@ -1057,15 +1118,15 @@ std::vector PiPedalHost::LoadFactoryPluginPreset( struct PresetCallbackState { - PiPedalHost *pHost; + PluginHost *pHost; std::map *values; bool failed = false; }; -void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type) +void PluginHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type) { PresetCallbackState *pState = static_cast(user_data); - PiPedalHost *pHost = pState->pHost; + PluginHost *pHost = pState->pHost; if (type == pHost->urids->atom_Float) { @@ -1076,7 +1137,7 @@ void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const v pState->failed = true; } } -PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri) +PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri) { const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld); @@ -1102,36 +1163,36 @@ PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri) LilvState *state = lilv_state_new_from_world(pWorld, this->mapFeature.GetMap(), preset); if (state != nullptr) { - std::string label = lilv_state_get_label(state); - std::map controlValues; - PresetCallbackState cbData{this, &controlValues, false}; - lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData); - lilv_state_free(state); + std::string label = lilv_state_get_label(state); + std::map controlValues; + PresetCallbackState cbData{this, &controlValues, false}; + lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData); + lilv_state_free(state); - if (!cbData.failed) - { - result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues))); - } + if (!cbData.failed) + { + result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues))); + } } } lilv_nodes_free(presets); return result; } -IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem) +IEffect *PluginHost::CreateEffect(PedalboardItem &pedalboardItem) { - if (pedalBoardItem.uri().starts_with("vst3:")) + if (pedalboardItem.uri().starts_with("vst3:")) { #if ENABLE_VST3 try { - Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this); - return vst3Plugin.release(); + Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalboardItem, this); + return vst3Plugin.release(); } catch (const std::exception &e) { - Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what()); - throw; + Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what()); + throw; } #else Lv2Log::error(std::string("VST3 support not enabled at compile time.")); @@ -1141,15 +1202,15 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem) } else { - auto info = this->GetPluginInfo(pedalBoardItem.uri()); + auto info = this->GetPluginInfo(pedalboardItem.uri()); if (!info) - return nullptr; + return nullptr; - return new Lv2Effect(this, info, pedalBoardItem); + return new Lv2Effect(this, info, pedalboardItem); } } -Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri) +Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri) { LilvWorld *pWorld = lv2Host->pWorld; @@ -1161,11 +1222,15 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri) name_ = nodeAsString(nameNode); } -void PiPedalHostLogError(const std::string&errror) +std::shared_ptr PluginHost::GetHostWorkerThread() { - + return pHostWorkerThread; } - + +// void PiPedalHostLogError(const std::string &error) +// { +// Lv2Log::error("%s",error.c_str()); +// } #define MAP_REF(class, name) \ json_map::reference(#name, &class ::name##_) @@ -1231,6 +1296,7 @@ json_map::storage_type Lv2PortGroup::jmap{{ }}; json_map::storage_type Lv2PluginInfo::jmap{{ + json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_), json_map::reference("uri", &Lv2PluginInfo::uri_), json_map::reference("name", &Lv2PluginInfo::name_), json_map::reference("plugin_class", &Lv2PluginInfo::plugin_class_), @@ -1290,20 +1356,22 @@ json_map::storage_type Lv2PluginUiControlPort::jmap{{ }}; -json_map::storage_type Lv2PluginUiInfo::jmap{{ - json_map::reference("uri", &Lv2PluginUiInfo::uri_), - json_map::reference("name", &Lv2PluginUiInfo::name_), - json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()), - json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_), - json_map::reference("author_name", &Lv2PluginUiInfo::author_name_), - json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_), - json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_), - json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_), - json_map::reference("description", &Lv2PluginUiInfo::description_), - json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_), - json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_), - 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_) -}}; +json_map::storage_type + Lv2PluginUiInfo::jmap{ + {json_map::reference("uri", &Lv2PluginUiInfo::uri_), + json_map::reference("name", &Lv2PluginUiInfo::name_), + json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()), + json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_), + json_map::reference("author_name", &Lv2PluginUiInfo::author_name_), + json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_), + json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_), + json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_), + json_map::reference("description", &Lv2PluginUiInfo::description_), + json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_), + json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_), + 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_), + json_map::reference("uiPortNotifications", &Lv2PluginUiInfo::uiPortNotifications_), + }}; diff --git a/src/PiPedalHost.hpp b/src/PluginHost.hpp similarity index 89% rename from src/PiPedalHost.hpp rename to src/PluginHost.hpp index f135454..e3e6b90 100644 --- a/src/PiPedalHost.hpp +++ b/src/PluginHost.hpp @@ -23,7 +23,7 @@ #include #include "json.hpp" #include "PluginType.hpp" -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include #include "MapFeature.hpp" #include "LogFeature.hpp" @@ -31,6 +31,7 @@ #include #include #include +#include "IHost.hpp" #include "lv2.h" #include "Units.hpp" @@ -40,14 +41,15 @@ #include "PiPedalConfiguration.hpp" #include "AutoLilvNode.hpp" #include "PiPedalUI.hpp" +#include "MapPathFeature.hpp" namespace pipedal { // forward declarations class Lv2Effect; - class Lv2PedalBoard; - class PiPedalHost; + class Lv2Pedalboard; + class PluginHost; class JackConfiguration; class JackChannelSelection; @@ -82,7 +84,7 @@ namespace pipedal class Lv2PluginClass { public: - friend class PiPedalHost; + friend class PluginHost; private: Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED! @@ -92,7 +94,7 @@ namespace pipedal PluginType plugin_type_; std::vector> children_; - friend class ::pipedal::PiPedalHost; + friend class ::pipedal::PluginHost; // hide copy constructor. Lv2PluginClass(const Lv2PluginClass &other) { @@ -151,7 +153,7 @@ namespace pipedal { return classes_; } - bool is_a(PiPedalHost *lv2Plugins, const char *classUri) const; + bool is_a(PluginHost *lv2Plugins, const char *classUri) const; static json_map::storage_type jmap; }; @@ -186,7 +188,7 @@ namespace pipedal class Lv2PortInfo { public: - Lv2PortInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort); + Lv2PortInfo(PluginHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort); private: friend class Lv2PluginInfo; @@ -303,7 +305,7 @@ namespace pipedal public: Lv2PortInfo() {} ~Lv2PortInfo() = default; - bool is_a(PiPedalHost *lv2Plugins, const char *classUri); + bool is_a(PluginHost *lv2Plugins, const char *classUri); static json_map::storage_type jmap; }; @@ -321,7 +323,7 @@ namespace pipedal LV2_PROPERTY_GETSET(name); Lv2PortGroup() {} - Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri); + Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri); static json_map::storage_type jmap; }; @@ -329,14 +331,15 @@ namespace pipedal class Lv2PluginInfo { private: - friend class PiPedalHost; + friend class PluginHost; public: - Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); + Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo() {} private: - bool HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin); + bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin); + std::string bundle_path_; std::string uri_; std::string name_; std::string plugin_class_; @@ -358,6 +361,7 @@ namespace pipedal bool IsSupportedFeature(const std::string &feature) const; public: + LV2_PROPERTY_GETSET(bundle_path) LV2_PROPERTY_GETSET(uri) LV2_PROPERTY_GETSET(name) LV2_PROPERTY_GETSET(plugin_class) @@ -537,7 +541,7 @@ namespace pipedal { public: Lv2PluginUiInfo() {} - Lv2PluginUiInfo(PiPedalHost *pPlugins, const Lv2PluginInfo *plugin); + Lv2PluginUiInfo(PluginHost *pPlugins, const Lv2PluginInfo *plugin); private: std::string uri_; @@ -555,7 +559,8 @@ namespace pipedal std::vector controls_; std::vector port_groups_; - std::vector fileProperties_; + std::vector fileProperties_; + std::vector uiPortNotifications_; public: LV2_PROPERTY_GETSET(uri) @@ -573,32 +578,11 @@ namespace pipedal LV2_PROPERTY_GETSET(port_groups) LV2_PROPERTY_GETSET_SCALAR(is_vst3) LV2_PROPERTY_GETSET(fileProperties) + LV2_PROPERTY_GETSET(uiPortNotifications) 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; - }; - } #if ENABLE_VST3 @@ -608,7 +592,7 @@ namespace pipedal namespace pipedal { - class PiPedalHost : private IHost + class PluginHost : private IHost { private: #if ENABLE_VST3 @@ -618,6 +602,7 @@ namespace pipedal friend class pipedal::AutoLilvNode; friend class pipedal::PiPedalUI; static const char *RDFS_COMMENT_URI; + public: class LilvUris { @@ -648,7 +633,6 @@ namespace pipedal AutoLilvNode pipedalUI__fileProperties; AutoLilvNode pipedalUI__directory; AutoLilvNode pipedalUI__patchProperty; - AutoLilvNode pipedalUI__defaultFile; AutoLilvNode pipedalUI__fileProperty; @@ -665,11 +649,18 @@ namespace pipedal AutoLilvNode appliesTo; AutoLilvNode isA; + + AutoLilvNode ui__portNotification; + AutoLilvNode ui__plugin; + AutoLilvNode ui__protocol; + AutoLilvNode ui__floatProtocol; + AutoLilvNode ui__peakProtocol; + AutoLilvNode ui__portIndex; + AutoLilvNode lv2__symbol; }; LilvUris lilvUris; private: - bool vst3Enabled = true; LilvNode *get_comment(const std::string &uri); @@ -683,10 +674,11 @@ namespace pipedal std::string vst3CachePath; - LV2_Feature *const *lv2Features = nullptr; + std::vector lv2Features; MapFeature mapFeature; LogFeature logFeature; OptionsFeature optionsFeature; + MapPathFeature mapPathFeature; static void fn_LilvSetPortValueFunc(const char *port_symbol, void *user_data, @@ -714,21 +706,21 @@ namespace pipedal // IHost implementation public: - void LogError(const std::string&message) + void LogError(const std::string &message) { - logFeature.LogError("%s",message.c_str()); + logFeature.LogError("%s", message.c_str()); } - void LogWarning(const std::string&message) + void LogWarning(const std::string &message) { - logFeature.LogWarning("%s",message.c_str()); + logFeature.LogWarning("%s", message.c_str()); } - void LogNote(const std::string&message) + void LogNote(const std::string &message) { - logFeature.LogNote("%s",message.c_str()); + logFeature.LogNote("%s", message.c_str()); } - void LogTrace(const std::string&message) + void LogTrace(const std::string &message) { - logFeature.LogTrace("%s",message.c_str()); + logFeature.LogTrace("%s", message.c_str()); } virtual LilvWorld *getWorld() { @@ -736,33 +728,41 @@ namespace pipedal } private: + std::shared_ptr pHostWorkerThread; + // IHost implementation. 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_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); } + virtual std::shared_ptr GetHostWorkerThread(); + + public: + virtual MapFeature &GetMapFeature() { return this->mapFeature; } + + private: 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); + virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem); void LoadPluginClassesFromLilv(); void AddJsonClassesToMap(std::shared_ptr pluginClass); public: - PiPedalHost(); - + PluginHost(); + void SetPluginStoragePath(const std::filesystem::path &path); void SetConfiguration(const PiPedalConfiguration &configuration); - virtual ~PiPedalHost(); + virtual ~PluginHost(); IHost *asIHost() { return this; } - virtual Lv2PedalBoard *CreateLv2PedalBoard(PedalBoard &pedalBoard); + virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard); void setSampleRate(double sampleRate) { @@ -792,19 +792,19 @@ namespace pipedal 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); + void Load(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH); virtual LV2_URID GetLv2Urid(const char *uri) { return this->mapFeature.GetUrid(uri); } - virtual std::string Lv2UriudToString(LV2_URID urid) + virtual std::string Lv2UridToString(LV2_URID urid) { return this->mapFeature.UridToString(urid); } PluginPresets GetFactoryPluginPresets(const std::string &pluginUri); - std::vector LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem, + std::vector LoadFactoryPluginPreset(PedalboardItem *pedalboardItem, const std::string &presetUri); }; diff --git a/src/PluginPreset.cpp b/src/PluginPreset.cpp index 9927a3e..bc28cd9 100644 --- a/src/PluginPreset.cpp +++ b/src/PluginPreset.cpp @@ -27,6 +27,7 @@ JSON_MAP_BEGIN(PluginPreset) JSON_MAP_REFERENCE(PluginPreset,instanceId) JSON_MAP_REFERENCE(PluginPreset,label) JSON_MAP_REFERENCE(PluginPreset,controlValues) + JSON_MAP_REFERENCE(PluginPreset,state) JSON_MAP_END() JSON_MAP_BEGIN(PluginPresets) diff --git a/src/PluginPreset.hpp b/src/PluginPreset.hpp index ba07734..2244cf1 100644 --- a/src/PluginPreset.hpp +++ b/src/PluginPreset.hpp @@ -23,6 +23,8 @@ #include "PiPedalException.hpp" #include #include +#include +#include "StateInterface.hpp" namespace pipedal { @@ -87,6 +89,7 @@ public: uint64_t instanceId_; std::string label_; std::map controlValues_; + Lv2PluginState state_; DECLARE_JSON_MAP(PluginPreset); }; diff --git a/src/Presets.cpp b/src/Presets.cpp index 406013d..29090b3 100644 --- a/src/Presets.cpp +++ b/src/Presets.cpp @@ -27,17 +27,17 @@ JSON_MAP_BEGIN(PedalPreset) JSON_MAP_REFERENCE(PedalPreset,values) JSON_MAP_END() -JSON_MAP_BEGIN(PedalBoardPreset) - JSON_MAP_REFERENCE(PedalBoardPreset,instanceId) - JSON_MAP_REFERENCE(PedalBoardPreset,displayName) - JSON_MAP_REFERENCE(PedalBoardPreset,values) +JSON_MAP_BEGIN(PedalboardPreset) + JSON_MAP_REFERENCE(PedalboardPreset,instanceId) + JSON_MAP_REFERENCE(PedalboardPreset,displayName) + JSON_MAP_REFERENCE(PedalboardPreset,values) JSON_MAP_END() -JSON_MAP_BEGIN(PedalBoardPresets) - JSON_MAP_REFERENCE(PedalBoardPresets,nextInstanceId) - JSON_MAP_REFERENCE(PedalBoardPresets,currentPreset) - JSON_MAP_REFERENCE(PedalBoardPresets,presets) +JSON_MAP_BEGIN(PedalboardPresets) + JSON_MAP_REFERENCE(PedalboardPresets,nextInstanceId) + JSON_MAP_REFERENCE(PedalboardPresets,currentPreset) + JSON_MAP_REFERENCE(PedalboardPresets,presets) JSON_MAP_END() diff --git a/src/Presets.hpp b/src/Presets.hpp index cf57a2d..018ba7b 100644 --- a/src/Presets.hpp +++ b/src/Presets.hpp @@ -19,7 +19,7 @@ #pragma once -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include "json.hpp" namespace pipedal { @@ -32,21 +32,21 @@ public: DECLARE_JSON_MAP(PedalPreset); }; -class PedalBoardPreset { +class PedalboardPreset { uint64_t instanceId_; std::string displayName_; std::vector > values_; public: - DECLARE_JSON_MAP(PedalBoardPreset); + DECLARE_JSON_MAP(PedalboardPreset); }; -class PedalBoardPresets { +class PedalboardPresets { long nextInstanceId_ = 0; long currentPreset_ = 0; - std::vector > presets_; + std::vector > presets_; public: - DECLARE_JSON_MAP(PedalBoardPresets); + DECLARE_JSON_MAP(PedalboardPresets); }; diff --git a/src/Promise.hpp b/src/Promise.hpp new file mode 100644 index 0000000..cb97407 --- /dev/null +++ b/src/Promise.hpp @@ -0,0 +1,493 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +namespace pipedal +{ + + // thread-safe javascript-like future. + + template + class Promise; + + template + class PromiseInner; + + + + template + using ResolveFunction = std::function; + + using RejectFunction = std::function; + + template + using WorkerFunction = std::function, RejectFunction reject)>; + using CatchFunction = std::function; + + class PromiseInnerBase + { + public: + static int64_t allocationCount; // not thread-safe. for testing purposes only. + + public: + virtual void Cancel() = 0; + + + protected: + + virtual ~PromiseInnerBase() + { + if (this->inputPromise) + { + this->inputPromise->ReleaseRef(); + } + } + void AddRef() + { + std::lock_guard lock{mutex}; + ++referenceCount; + } + void ReleaseRef() + { + bool deleteMe = false; + { + std::lock_guard lock{mutex}; + if (referenceCount == 0) + { + throw std::logic_error("Reference count blown."); + } + --referenceCount; + deleteMe = (referenceCount == 0); // can't do it while holding the mutex. + } + if (deleteMe) + { + delete this; + } + } + void SetInputPromise(PromiseInnerBase *promise) + { + + PromiseInnerBase *oldPromise; + { + std::lock_guard lock{mutex}; + oldPromise = this->inputPromise; + this->inputPromise = promise; + } + if (promise) + { + promise->AddRef(); + } + if (oldPromise) + { + oldPromise->ReleaseRef(); + } + } + + protected: + PromiseInnerBase *inputPromise = nullptr; + int referenceCount = 0; + std::mutex mutex; + }; + inline int64_t PromiseInnerBase::allocationCount = 0; + + template + class PromiseInner : PromiseInnerBase + { + private: + friend class Promise; + using ResolveFunction = pipedal::ResolveFunction; + using WorkerFunction = pipedal::WorkerFunction; + using ThenFunction = std::function; + + PromiseInner() + { + ++allocationCount; + referenceCount = 1; // one for the owner, none for the worker (yet) + } + void Work(WorkerFunction work) + { + { + std::lock_guard lock{mutex}; + if (cancelled) + return; + ++referenceCount; + this->worker = work; + this->hasWorker = true; + } + try + { + worker( + [this](const T &value) + { this->Resolve(value); }, + [this](const std::string &message) + { this->Reject(message); }); + } + catch (const std::exception &e) + { + Reject(e.what()); + } + } + PromiseInner(WorkerFunction work) + { + ++allocationCount; + referenceCount = 2; // one for the owner, one for the worker. + this->worker = work; + this->hasWorker = true; + try + { + worker( + [this](const T &value) + { this->Resolve(value); }, + [this](const std::string &message) + { this->Reject(message); }); + } + catch (const std::exception &e) + { + Reject(e.what()); + } + } + ~PromiseInner() + { + delete conditionVariable; + --allocationCount; + } + + T Get() + { + while (true) + { + std::unique_lock lock{mutex}; + if (this->resolved || this->cancelled) + break; + if (conditionVariable != nullptr) + { + conditionVariable = new std::condition_variable(); + } + conditionVariable->wait(lock); + } + ReleaseFunctions(); + if (cancelled) + { + throw std::logic_error("Cancelled."); + } + if (rejected) + { + throw std::logic_error(catchMessage); + } + return resolvedValue; + } + + void Resolve(const T &value) + { + resolvedValue = value; + bool fireResult = false; + std::condition_variable *conditionVariable = nullptr; + bool releaseRef = false; + { + std::lock_guard lock{mutex}; + if (!resolved) + { + resolved = true; + releaseRef = true; + if (hasThenFunction && !cancelled) + { + fireResult = true; + } + conditionVariable = this->conditionVariable; + } + } + if (fireResult) + { + thenFunction(resolvedValue); + ReleaseFunctions(); + thenFunction = nullptr; + catchFunction = nullptr; + } + if (conditionVariable) + { + conditionVariable->notify_all(); + } + if (releaseRef) + { + ReleaseRef(); + } + } + void Reject(const std::string &message) + { + bool fireCatch = false; + bool releaseRef = false; + std::condition_variable *conditionVariable = nullptr; + + { + std::lock_guard lock{mutex}; + if (!resolved) + { + releaseRef = hasWorker; + resolved = true; + rejected = true; + this->catchMessage = message; + + if (hasCatchFunction && !cancelled) + { + fireCatch = true; + } + conditionVariable = this->conditionVariable; + resolved = true; + } + } + if (fireCatch) + { + catchFunction(this->catchMessage); + ReleaseFunctions(); + } + if (conditionVariable) + { + conditionVariable->notify_all(); + } + if (releaseRef) + { + ReleaseRef(); + } + } + virtual void Cancel() + { + std::condition_variable *conditionVariable = nullptr; + { + std::lock_guard lock{mutex}; + if (!cancelled) + { + cancelled = true; + thenFunction = nullptr; + catchFunction = nullptr; + conditionVariable = this->conditionVariable; + if (inputPromise) + { + inputPromise->Cancel(); + SetInputPromise(nullptr); + } + } + } + ReleaseFunctions(); + if (conditionVariable) + { + conditionVariable->notify_all(); + } + } + bool IsCancelled() + { + std::lock_guard lock{mutex}; + return cancelled; + } + void Then(ThenFunction &&handler) + { + bool fireThen; + { + std::lock_guard lock{mutex}; + if (hasThenFunction) + { + throw std::logic_error("Then handler already set."); + } + hasThenFunction = true; + thenFunction = std::move(handler); + fireThen = resolved && (!rejected) && (!cancelled); + } + if (fireThen) + { + thenFunction(resolvedValue); + ReleaseFunctions(); + } + } + void Then(const ThenFunction &handler) + { + ThenFunction fn = handler; + Then(std::move(fn)); + } + + void Catch(CatchFunction handler) + { + bool fireCatch; + { + std::lock_guard lock{mutex}; + if (hasCatchFunction) + { + throw std::logic_error("Catch handler already set."); + } + if (!hasThenFunction) + { + ++referenceCount; + } + hasCatchFunction = true; + catchFunction = handler; + + fireCatch = resolved && rejected && !cancelled; + } + if (fireCatch) + { + catchFunction(this->catchMessage); + + ReleaseFunctions(); + + + } + } + bool IsReady() const + { + std::lock_guard lock{mutex}; + return this->resolved; + } + + private: + void ReleaseFunctions() + { + // Most importantly, release capture variables that reference a promise. + thenFunction = nullptr; + catchFunction = nullptr; + worker = nullptr; + SetInputPromise(nullptr); + } + + private: + bool hasWorker = false; + bool cancelled = false; + bool resolved = false; + bool rejected = false; + WorkerFunction worker; + std::condition_variable *conditionVariable = nullptr; + bool hasThenFunction = false; + ThenFunction thenFunction; + bool hasCatchFunction = false; + CatchFunction catchFunction; + + T resolvedValue; + std::string catchMessage; + }; + + template + class Promise + { + public: + + using ResolveFunction = PromiseInner::ResolveFunction; + using RejectFunction = pipedal::RejectFunction; + using WorkerFunction = PromiseInner::WorkerFunction; + using ThenFunction = PromiseInner::ThenFunction; + using CatchFunction = pipedal::CatchFunction; + + Promise() { p = new PromiseInner(); } + + Promise(const Promise &other) + { + p = other.p; + p->AddRef(); + } + Promise(Promise &&other) + { + this->p = other.p; + other.p = nullptr; + } + Promise(WorkerFunction worker) { p = new PromiseInner(worker); } + ~Promise() + { + if (p) + p->ReleaseRef(); + } + + Promise(Promise &other) + { + other.p->AddRef(); + this->p = other.p; + } + Promise Then(std::function &&thenFn) + { + p->Then(std::move(thenFn)); + return Promise(*this); + } + Promise Then(const std::function &thenFn) + { + std::function t(thenFn); + p->Then(std::move(t)); + return Promise(*this); + } + + template + Promise Then(std::function result, RejectFunction reject)> thenFn) + { + + Promise t{}; + t.SetInputPromise(this->p); + this->Then([t, thenFn](const T &value) mutable + { t.Work([thenFn, value](pipedal::ResolveFunction resolveU, RejectFunction rejectU) mutable + { thenFn(value, resolveU, rejectU); }); }) + .Catch([t](const std::string &message) mutable + { t.Reject(message); }); + return t; + } + + Promise Catch(const CatchFunction &catchFn) + { + p->Catch(catchFn); + return Promise(*this); + } + + Promise &operator=(Promise &&other) + { + std::swap(this->p, other.p); + return *this; + } + Promise &operator=(const Promise &other) + { + this->p = other.p; + p->AddRef(); + return *this; + } + T Get() + { + return p->Get(); + } + + class Test + { + // not thread-safe. for testing purposes only. + int GetAllocationCount() { return PromiseInnerBase::allocationCount; } + }; + + void Work(std::function workFunction) + { + p->Work(workFunction); + } + void Reject(const std::string &message) + { + p->Reject(message); + } + + void SetInputPromise(PromiseInnerBase *inputPromise) + { + p->SetInputPromise(inputPromise); + } + private: + PromiseInner *p = nullptr; + }; + +} \ No newline at end of file diff --git a/src/PromiseTest.cpp b/src/PromiseTest.cpp new file mode 100644 index 0000000..2422a14 --- /dev/null +++ b/src/PromiseTest.cpp @@ -0,0 +1,261 @@ +// 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. + +#include "Promise.hpp" +#include "catch.hpp" +#include +#include +#include + +using namespace pipedal; + +TEST_CASE("Promise", "[promise][Build][Dev]") +{ + { + // synchronous then->get + Promise p1 = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + resolve(1.0); + }); + Promise p2 = p1 + .Then([](double value, ResolveFunction resolve, RejectFunction reject) + { resolve((int)(value + 3)); }); + int result = p2.Get(); + REQUIRE(result == 4.0); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + + //////////////////////// + { + // synchronous resolve. + double result = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + resolve(1.0); + }) + .Get(); + REQUIRE(result == 1.0); + } + { + // async resolve (Get) + double result = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + auto _ = std::async( + std::launch::async, + [resolve]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + resolve(2.0); + }); + }) + .Get(); + REQUIRE(result == 2.0); + } + { + // async resolve (Then) + std::mutex mutex; + std::condition_variable cv; + bool ready = false; + + Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + auto _ = std::async( + std::launch::async, + [resolve]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + resolve(3.0); + }); + }) + .Then([&cv, &mutex, &ready](double result) + { + REQUIRE(result == 3.0); + { + std::lock_guard lock { mutex}; + ready = true; + } + cv.notify_all(); }) + .Catch([](const std::string &message) + { + REQUIRE(true == false); // should not get here. + }); + while (true) + { + std::unique_lock lock{mutex}; + if (ready) + break; + cv.wait(lock); + } + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + { + // async resolve (Then, Then) + + std::mutex mutex; + std::condition_variable cv; + bool ready = false; + + Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + auto _ = std::async( + std::launch::async, + [resolve]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + resolve(3.0); + }); + }) + .Then([](double value, ResolveFunction resolve, RejectFunction reject) + { + REQUIRE(value == 3.0); + auto _ = std::async( + std::launch::async, + [resolve, value]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + resolve((int32_t)(value + 10)); + }); + }) + .Then([&cv, &mutex, &ready](int32_t result) + { + REQUIRE(result == 13); + { + std::lock_guard lock { mutex}; + ready = true; + } + cv.notify_all(); }); + while (true) + { + std::unique_lock lock{mutex}; + if (ready) + break; + cv.wait(lock); + } + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + + { + // synchronous then->get + int result = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + resolve(1.0); + }) + .Then([](double value, ResolveFunction resolve, RejectFunction reject) + { resolve((int)(value + 3)); }) + .Get(); + REQUIRE(result == 4.0); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + + { + // synchronous reject + Promise promise = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + reject("Rejected"); + }); + REQUIRE_THROWS(promise.Get()); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + { + // async reject (Get) + Promise promise = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + auto _ = std::async( + std::launch::async, + [reject]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + reject("Rejected"); + }); + }); + REQUIRE_THROWS(promise.Get()); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + { + // synchronous then then/reject->get + Promise promise = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + resolve(1.0); + }) + .Then( + [](double value, ResolveFunction resolve, RejectFunction reject) + { + reject("reject"); + }); + REQUIRE_THROWS(promise.Get()); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + { + // synchronous then reject->then->get + Promise promise = Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + reject("reject"); + }) + .Then([](double value, ResolveFunction resolve, RejectFunction reject) + { resolve((int)(value + 3)); }); + REQUIRE_THROWS(promise.Get()); + } + REQUIRE(PromiseInnerBase::allocationCount == 0); + + { + // async resolve (Catch) + std::mutex mutex; + std::condition_variable cv; + bool ready = false; + + Promise( + [](ResolveFunction resolve, RejectFunction reject) + { + auto _ = std::async( + std::launch::async, + [reject]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + reject("rejected"); + }); + }) + .Then([&cv, &mutex, &ready](double result) + { REQUIRE(true == false); }) + .Catch([&cv, &mutex, &ready](const std::string &message) + { + { + std::lock_guard lock(mutex); + ready = true; + } + cv.notify_all(); }); + while (true) + { + std::unique_lock lock{mutex}; + if (ready) + break; + cv.wait(lock); + } + } + + REQUIRE(PromiseInnerBase::allocationCount == 0); +} diff --git a/src/RingBuffer.hpp b/src/RingBuffer.hpp index c9aa2b7..ab1b32a 100644 --- a/src/RingBuffer.hpp +++ b/src/RingBuffer.hpp @@ -22,28 +22,35 @@ #include "PiPedalException.hpp" #include #include -#include - +#include #ifndef NO_MLOCK #include #endif /* NO_MLOCK */ - namespace pipedal { + + enum class RingBufferStatus + { + Ready, + TimedOut, + Closed + }; template - class RingBuffer { + class RingBuffer + { char *buffer; bool mlocked = false; size_t ringBufferSize; size_t ringBufferMask; volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition - std::mutex write_mutex; + std::mutex mutex; + std::mutex writeMutex; - sem_t readSemaphore; - bool semaphore_open = false; + bool is_open = true; + std::condition_variable cvRead; size_t nextPowerOfTwo(size_t size) { @@ -54,205 +61,352 @@ namespace pipedal } return v; } + public: - RingBuffer(size_t ringBufferSize = 65536, bool mLock = true) + RingBuffer(size_t ringBufferSize = 65536, bool mLock = true) { this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize); - ringBufferMask = ringBufferSize-1; + ringBufferMask = ringBufferSize - 1; buffer = new char[ringBufferSize]; - if (SEMAPHORE_READER) { - sem_init(&readSemaphore,0,0); - semaphore_open = true; - } - - #ifndef NO_MLOCK +#ifndef NO_MLOCK if (mLock) { - if (mlock (buffer, ringBufferSize)) { - throw PiPedalStateException("Mlock failed."); + if (mlock(buffer, ringBufferSize)) + { + throw PiPedalStateException("Mlock failed."); } this->mlocked = true; } - #endif +#endif } - void reset() { + void reset() + { this->readPosition = 0; this->writePosition = 0; - if (SEMAPHORE_READER) - { - sem_destroy(&readSemaphore); - sem_init(&readSemaphore,0,0); - this->semaphore_open = true; - } + cvRead.notify_all(); } - void close() { + void close() + { if (SEMAPHORE_READER) { - this->semaphore_open = false; - sem_post(&readSemaphore); - } - } - // 0 -> ready. -1: timed out. -2: closing. - int readWait(const struct timespec& timeoutMs) { - if (SEMAPHORE_READER) - { - int result = sem_timedwait(&readSemaphore,&timeoutMs); - if (!semaphore_open) return -2; - return (result == 0) ? 0: -1; - } else { - throw PiPedalStateException("SEMAPHORE_READER is not set to true."); + this->is_open = false; + cvRead.notify_all(); } } - bool readWait() { - if (SEMAPHORE_READER) + template + RingBufferStatus readWait_for(const std::chrono::duration &timeout) + { + while (true) { - sem_wait(&readSemaphore); - return semaphore_open; - } else { - throw PiPedalStateException("SEMAPHORE_READER is not set to true."); + if (SEMAPHORE_READER) + { + std::unique_lock lock(mutex); + if (isReadReady_()) + { + return RingBufferStatus::Ready; + } + if (!is_open) + return RingBufferStatus::Closed; + + auto status = cvRead.wait_for(lock, timeout); + if (status == std::cv_status::timeout) + { + return RingBufferStatus::TimedOut; + } + } + else + { + static_assert("SEMAPHORE_READER is not set to true."); + } } } - size_t writeSpace() { - // at most ringBufferSize-1 in order to + + template + RingBufferStatus readWait_until(const std::chrono::time_point &time_point) + { + while (true) + { + if (SEMAPHORE_READER) + { + std::unique_lock lock(mutex); + if (isReadReady_()) + { + return RingBufferStatus::Ready; + } + if (!is_open) + return RingBufferStatus::Closed; + + auto status = cvRead.wait_until(lock, time_point); + if (status == std::cv_status::timeout) + { + return RingBufferStatus::TimedOut; + } + } + else + { + static_assert("SEMAPHORE_READER is not set to true."); + } + } + } + template + RingBufferStatus readWait_until(size_t size,const std::chrono::time_point &time_point) + { + while (true) + { + if (SEMAPHORE_READER) + { + std::unique_lock lock(mutex); + size_t available = readSpace_(); + if (available >= size) + { + return RingBufferStatus::Ready; + } + if (!is_open) + return RingBufferStatus::Closed; + + auto status = cvRead.wait_until(lock, time_point); + if (status == std::cv_status::timeout) + { + return RingBufferStatus::TimedOut; + } + } + else + { + static_assert("SEMAPHORE_READER is not set to true."); + } + } + } + + bool readWait() + { + if (SEMAPHORE_READER) + { + while (true) + { + std::unique_lock lock(mutex); + if (isReadReady_()) + { + return true; + } + + if (!is_open) + return false; + cvRead.wait(lock); + } + } + else + { + static_assert("SEMAPHORE_READER is not set to true."); + } + } + size_t writeSpace() + { + // at most ringBufferSize-1 in order to // to distinguish the empty buffer from the full buffer. - int64_t size = readPosition-1-writePosition; - if (size < 0) size += this->ringBufferSize; + std::unique_lock lock(mutex); + + int64_t size = readPosition - 1 - writePosition; + if (size < 0) + size += this->ringBufferSize; return (size_t)size; } - size_t readSpace() { - int64_t size = writePosition-readPosition; - if (size < 0) size += this->ringBufferSize; - return size_t(size); + + + size_t readSpace() + { + std::unique_lock lock(mutex); + return readSpace_(); } bool write(size_t bytes, uint8_t *data) { if (MULTI_WRITER) { - std::lock_guard guard(write_mutex); - if (writeSpace() < bytes) { + std::lock_guard writeLock{writeMutex}; + if (writeSpace() < bytes + sizeof(bytes)) + { return false; } size_t index = this->writePosition; for (size_t i = 0; i < bytes; ++i) { - buffer[(index+i) & ringBufferMask] = data[i]; + buffer[(index + i) & ringBufferMask] = data[i]; + } + { + std::lock_guard lock(mutex); + this->writePosition = (index + bytes) & ringBufferMask; } - this->writePosition = (index+bytes) & ringBufferMask; if (SEMAPHORE_READER) { - sem_post(&readSemaphore); + cvRead.notify_all(); } return true; - } else { - if (writeSpace() < bytes) { + } + else + { + if (writeSpace() < sizeof(bytes) + bytes) + { return false; } size_t index = this->writePosition; for (size_t i = 0; i < bytes; ++i) { - buffer[(index+i) & ringBufferMask] = data[i]; + buffer[(index + i) & ringBufferMask] = data[i]; + } + { + std::lock_guard lock{mutex}; + this->writePosition = (index + bytes) & ringBufferMask; } - this->writePosition = (index+bytes) & ringBufferMask; if (SEMAPHORE_READER) { - sem_post(&readSemaphore); + cvRead.notify_all(); } return true; } } // Write two disjoint areas of memory atomically. - bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t*data2) + bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t *data2) { if (MULTI_WRITER) { - std::lock_guard guard(write_mutex); - if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { + std::lock_guard guard(writeMutex); + if (writeSpace() <= sizeof(bytes) + bytes +bytes2) + { return false; } size_t index = this->writePosition; for (size_t i = 0; i < bytes; ++i) { - buffer[(index+i) & ringBufferMask] = data[i]; + buffer[(index + i) & ringBufferMask] = data[i]; } - index = (index+bytes) & ringBufferMask; - + index = (index + bytes) & ringBufferMask; + for (size_t i = 0; i < sizeof(bytes2); ++i) { - buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i]; + buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i]; } - index = (index+sizeof(bytes2)) & ringBufferMask; + index = (index + sizeof(bytes2)) & ringBufferMask; for (size_t i = 0; i < bytes2; ++i) { - buffer[(index+i) & ringBufferMask] = data2[i]; + buffer[(index + i) & ringBufferMask] = data2[i]; + } + { + std::lock_guard lock{mutex}; + this->writePosition = (index + bytes2) & ringBufferMask; } - this->writePosition = (index+bytes2) & ringBufferMask; if (SEMAPHORE_READER) { - sem_post(&readSemaphore); + cvRead.notify_all(); } return true; - } else { - if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { + } + else + { + if (writeSpace() <= sizeof(bytes2) + bytes + bytes2) + { return false; } size_t index = this->writePosition; for (size_t i = 0; i < bytes; ++i) { - buffer[(index+i) & ringBufferMask] = data[i]; + buffer[(index + i) & ringBufferMask] = data[i]; } - index = (index+bytes) & ringBufferMask; - + index = (index + bytes) & ringBufferMask; + for (size_t i = 0; i < sizeof(bytes2); ++i) { - buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i]; + buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i]; } - index = (index+sizeof(bytes2)) & ringBufferMask; + index = (index + sizeof(bytes2)) & ringBufferMask; for (size_t i = 0; i < bytes2; ++i) { - buffer[(index+i) & ringBufferMask] = data2[i]; + buffer[(index + i) & ringBufferMask] = data2[i]; } - this->writePosition = (index+bytes2) & ringBufferMask; - + { + std::lock_guard lock{mutex}; + this->writePosition = (index + bytes2) & ringBufferMask; + } + if (SEMAPHORE_READER) { - sem_post(&readSemaphore); + cvRead.notify_all(); } return true; } } - bool read(size_t bytes, uint8_t*data) + bool read(size_t bytes, uint8_t *data) { - if (readSpace() < bytes) return false; + if (readSpace() < bytes) + return false; int64_t readPosition = this->readPosition; for (size_t i = 0; i < bytes; ++i) { - data[i] = this->buffer[(readPosition+i) & this->ringBufferMask]; + data[i] = this->buffer[(readPosition + i) & this->ringBufferMask]; + } + { + std::lock_guard lock{mutex}; + this->readPosition = (readPosition + bytes) & this->ringBufferMask; } - this->readPosition = (readPosition + bytes) & this->ringBufferMask; return true; } - ~RingBuffer() + ~RingBuffer() { - #ifdef USE_MLOCK - if (this->mlocked) - { - munlock(buffer,ringBufferSize); - } - #endif - if (SEMAPHORE_READER) +#ifdef USE_MLOCK + if (this->mlocked) { - sem_destroy(&this->readSemaphore); + munlock(buffer, ringBufferSize); } +#endif delete[] buffer; } + bool isReadReady() { + std::lock_guard lock(mutex); + if (isReadReady_()) return true; + return !this->is_open; + } + bool isReadReady(size_t size) { + size_t available = readSpace(); + return available >= size; + } + + private: + + size_t readSpace_() + { + int64_t size = writePosition - readPosition; + if (size < 0) + size += this->ringBufferSize; + return size_t(size); + } + + uint32_t peekSize() + { + volatile uint32_t result; + uint8_t *p = (uint8_t*)&result; + size_t ix = this->readPosition; + for (size_t i = 0; i < sizeof(result); ++i) + { + *p++ = this->buffer[(ix++) & ringBufferMask]; + } + return result; + } + bool isReadReady_() + { + size_t available = readSpace_(); + if (available < sizeof(uint32_t)) return false; + // peak to get the size! + uint32_t packetSize = peekSize(); + return packetSize+sizeof(uint32_t) <= available; + } + + }; -}; +}; diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index a01f495..3c2d5f0 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -23,6 +23,8 @@ #include "Lv2Log.hpp" #include "VuUpdate.hpp" #include "AudioHost.hpp" +#include "lv2/atom.lv2/atom.h" +#include namespace pipedal { @@ -56,6 +58,8 @@ namespace pipedal NextMidiProgram = 20, + Lv2StateChanged = 21, + }; struct RealtimeNextMidiProgramRequest { @@ -151,17 +155,17 @@ namespace pipedal float value; }; - class Lv2PedalBoard; + class Lv2Pedalboard; class ReplaceEffectBody { public: - Lv2PedalBoard *effect; + Lv2Pedalboard *effect; }; class EffectReplacedBody { public: - Lv2PedalBoard *oldEffect; + Lv2Pedalboard *oldEffect; }; template @@ -182,8 +186,20 @@ namespace pipedal } // 0 -> ready. -1: timed out. -2: closing. - int wait(struct timespec &timeout) { - return ringBuffer->readWait(timeout); + template + RingBufferStatus wait_for(const std::chrono::duration& timeout) { + return ringBuffer->readWait_for(timeout); + } + + template + RingBufferStatus wait_until(std::chrono::time_point&time_point) + { + return ringBuffer->readWait_until(time_point); + } + template + RingBufferStatus wait_until(size_t size,std::chrono::time_point&time_point) + { + return ringBuffer->readWait_until(size,time_point); } bool wait() { @@ -196,9 +212,6 @@ namespace pipedal template bool read(T *output) { - size_t available = readSpace(); - if (available < sizeof(T)) - return false; if (!ringBuffer->read(sizeof(T),(uint8_t*)output)) { throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); @@ -294,15 +307,23 @@ namespace pipedal } } + void Lv2StateChanged(uint64_t instanceId) + { + write(RingBufferCommand::Lv2StateChanged,instanceId); + } void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data) { write(RingBufferCommand::AtomOutput,instanceId,bytes,data); } - void ParameterRequest(RealtimeParameterRequest *pRequest) + void AtomOutput(uint64_t instanceId, const LV2_Atom*atom) + { + write(RingBufferCommand::AtomOutput,instanceId,atom->size+sizeof(LV2_Atom),(uint8_t*)atom); + } + void ParameterRequest(RealtimePatchPropertyRequest *pRequest) { write(RingBufferCommand::ParameterRequest,pRequest); } - void ParameterRequestComplete(RealtimeParameterRequest *pRequest) + void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest) { write(RingBufferCommand::ParameterRequestComplete,pRequest); } @@ -407,9 +428,9 @@ namespace pipedal write(RingBufferCommand::SetBypass,body); } - void ReplaceEffect(Lv2PedalBoard *pedalBoard) + void ReplaceEffect(Lv2Pedalboard *pedalboard) { - write(RingBufferCommand::ReplaceEffect, pedalBoard); + write(RingBufferCommand::ReplaceEffect, pedalboard); } void AudioStopped() @@ -418,9 +439,9 @@ namespace pipedal write(RingBufferCommand::AudioStopped, body); } - void EffectReplaced(Lv2PedalBoard *pedalBoard) + void EffectReplaced(Lv2Pedalboard *pedalboard) { - write(RingBufferCommand::EffectReplaced, pedalBoard); + write(RingBufferCommand::EffectReplaced, pedalboard); } }; diff --git a/src/SplitEffect.cpp b/src/SplitEffect.cpp index 4ad5524..f4d1d13 100644 --- a/src/SplitEffect.cpp +++ b/src/SplitEffect.cpp @@ -20,8 +20,8 @@ #include "pch.h" #include "SplitEffect.hpp" -#include "PiPedalHost.hpp" -#include "PedalBoard.hpp" +#include "PluginHost.hpp" +#include "Pedalboard.hpp" using namespace pipedal; diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp index cc51c5a..6fadb6e 100644 --- a/src/SplitEffect.hpp +++ b/src/SplitEffect.hpp @@ -291,10 +291,11 @@ namespace pipedal virtual uint8_t *GetAtomInputBuffer() { return nullptr; } virtual uint8_t *GetAtomOutputBuffer() { return nullptr; } - virtual void RequestParameter(LV2_URID uridUri) {} - virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} + virtual void RequestPatchProperty(LV2_URID uridUri) {} + virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} virtual std::string AtomToJson(uint8_t *pAtom) { return ""; } virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";} + virtual bool GetLv2State(Lv2PluginState*state) { return false; } virtual bool IsVst3() const { return false; } @@ -670,7 +671,6 @@ namespace pipedal } } } - void PostMix(uint32_t frames) { if (this->outputBuffers.size() == 1) diff --git a/src/StateInterface.cpp b/src/StateInterface.cpp new file mode 100644 index 0000000..8edae1e --- /dev/null +++ b/src/StateInterface.cpp @@ -0,0 +1,289 @@ +// 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. + +#include "StateInterface.hpp" +#include "ss.hpp" +#include "Base64.hpp" +#include "json.hpp" +#include + +using namespace pipedal; + +LV2_State_Status StateInterface::FnStateStoreFunction( + LV2_State_Handle handle, + uint32_t key, + const void *value, + size_t size, + uint32_t type, + uint32_t flags) +{ + SaveCallState *pCallState = (SaveCallState *)handle; + + pCallState->status = pCallState->pThis->StateStoreFunction( + pCallState, + key, + value, + size, + type, + flags + + ); + return pCallState->status; +} + +LV2_State_Status StateInterface::StateStoreFunction( + SaveCallState *pCallState, + uint32_t key, + const void *value, + size_t size, + uint32_t type, + uint32_t flags) +{ + Lv2PluginState &state = pCallState->state; + + std::string strKey = map.UridToString(key); + Lv2PluginStateEntry& entry = state.values_[strKey]; + + std::string atomType = map.UridToString(type); + entry.atomType_ = atomType; + entry.flags_ = flags; + entry.value_.resize(size); + uint8_t *p = (uint8_t*)value; + + for (size_t i = 0; i < size; ++i) + { + entry.value_[i] = p[i]; + } + return LV2_State_Status::LV2_STATE_SUCCESS; +} + +static void CheckState(LV2_State_Status status) +{ + const char *lv2Error = nullptr; + + switch (status) + { + case LV2_State_Status::LV2_STATE_SUCCESS: + return; + default: + lv2Error = "Unknown error."; + break; + case LV2_State_Status::LV2_STATE_ERR_BAD_TYPE: + lv2Error = "Invalid type."; + break; + case LV2_State_Status::LV2_STATE_ERR_BAD_FLAGS: + lv2Error = "Unsupported flags."; + break; + case LV2_State_Status::LV2_STATE_ERR_NO_FEATURE: + lv2Error = "Feature not supported."; + break; + case LV2_State_Status::LV2_STATE_ERR_NO_PROPERTY: + lv2Error = "No such property."; + break; + case LV2_State_Status::LV2_STATE_ERR_NO_SPACE: + lv2Error = "Insufficient memory."; + break; + } + throw std::logic_error(lv2Error); +} +Lv2PluginState StateInterface::Save() +{ + SaveCallState callState; + callState.pThis = this; + callState.status = LV2_State_Status::LV2_STATE_SUCCESS; + + LV2_Feature **features = &(this->features[0]); + LV2_Handle instanceHandle = lilv_instance_get_handle(pInstance); + try + { + LV2_State_Status status = + pluginStateInterface->save( + instanceHandle, + FnStateStoreFunction, + (LV2_State_Handle)&callState, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, + features); + CheckState(status); + CheckState(callState.status); + } + catch (const std::exception &e) + { + throw std::logic_error(SS("State save failed. " << e.what())); + } + + return std::move(callState.state); +} + +void StateInterface::Restore(const Lv2PluginState &state) +{ + RestoreCallState callState; + callState.pThis = this; + callState.pState = &state; + callState.status = LV2_State_Status::LV2_STATE_SUCCESS; + + LV2_Feature **features = &(this->features[0]); + LV2_Handle instanceHandle = lilv_instance_get_handle(pInstance); + try + { + LV2_State_Status status = + pluginStateInterface->restore( + instanceHandle, + FnStateRetreiveFunction, + (LV2_State_Handle)&callState, + LV2_STATE_IS_POD, + features); + CheckState(status); + CheckState(callState.status); + } + catch (const std::exception &e) + { + throw std::logic_error(SS("State save failed. " << e.what())); + } +} +/*static*/ const void *StateInterface::FnStateRetreiveFunction( + LV2_State_Handle handle, + uint32_t key, + size_t *size, + uint32_t *type, + uint32_t *flags) +{ + RestoreCallState*pCallState = (RestoreCallState*)handle; + return pCallState->pThis->StateRetrieveFunction( + pCallState, + key, + size, + type, + flags); + +} +const void *StateInterface::StateRetrieveFunction( + RestoreCallState *pCallState, + uint32_t key, + size_t *size, + uint32_t *type, + uint32_t *flags) +{ + std::string strKey = map.UridToString(key); + auto & values = pCallState->pState->values_; + auto iEntry = values.find(strKey); + if (iEntry == values.end()) + { + *size = 0; + *type = urids.atom__Atom; + return nullptr; + } + + const Lv2PluginStateEntry& entry = iEntry->second; + *size = entry.value_.size(); + *type = map.GetUrid(entry.atomType_.c_str()); + *flags = entry.flags_; + return (void*)&entry.value_[0]; + +} + +void Lv2PluginState::write_json(json_writer &writer) const +{ + writer.write(values_); +} +void Lv2PluginState::read_json(json_reader &reader) { + reader.read(&values_); +} + + +void Lv2PluginStateEntry::write_json(json_writer &writer) const +{ + writer.start_object(); + writer.write_member("flags",flags_); + writer.write_raw(","); + writer.write_member("atomType",atomType_); + writer.write_raw(","); + if (atomType_ == LV2_ATOM__String || atomType_ == LV2_ATOM__Path || atomType_ == LV2_ATOM__URI) + { + const char *p = (const char*)&(value_[0]); + size_t size = value_.size(); + while (size > 0 && p[size-1] == '\0') + { + --size; + } + std::string value { p,size}; + writer.write_member("value",value); + } else if (atomType_ == LV2_ATOM__Float) + { + if (value_.size() != sizeof(float)) + { + throw std::logic_error("Invalid float property in LV2PluginState"); + } + writer.write_member("value",*(float*)&(value_[0])); + } else { + std::string base64 = macaron::Base64::Encode(value_); + writer.write_member("value",base64); + } + writer.end_object(); +} +void Lv2PluginStateEntry::read_json(json_reader &reader) +{ + reader.start_object(); + reader.read_member("flags",&flags_); + reader.consume(','); + reader.read_member("atomType",&atomType_); + reader.consume(','); + if (atomType_ == LV2_ATOM__String || atomType_ == LV2_ATOM__Path || atomType_ == LV2_ATOM__URI) + { + std::string v; + reader.read_member("value",&v); + value_.resize(v.length()); + for (size_t i = 0; i < v.length(); ++i) + { + value_[i] = (uint8_t)v[i]; + } + } else if (atomType_ == LV2_ATOM__Float) + { + float v; + reader.read_member("value",&v); + value_.resize(sizeof(float)); + float *pVal = (float*)&(value_[0]); + *pVal = v; + } else { + std::string v; + reader.read_member("value",&v); + value_ = macaron::Base64::Decode(v); + } + reader.end_object(); +} + +StateInterface::StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface) + : map(host->GetMapFeature()), + pInstance(pInstance), + pluginStateInterface(pluginStateInterface) +{ + auto hostFeatures = host->GetLv2Features(); + while (*hostFeatures != nullptr) + { + features.push_back((LV2_Feature*)(*hostFeatures)); + ++hostFeatures; + } + features.push_back(nullptr); +} + +std::string Lv2PluginState::ToString() const { + std::stringstream ss; + json_writer writer(ss); + writer.write(*this); + return ss.str(); +} \ No newline at end of file diff --git a/src/StateInterface.hpp b/src/StateInterface.hpp new file mode 100644 index 0000000..ea582c4 --- /dev/null +++ b/src/StateInterface.hpp @@ -0,0 +1,123 @@ +// 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. + +#pragma once + +#include "lilv/lilv.h" +#include "lv2/state.lv2/state.h" +#include +#include "json_variant.hpp" +#include "MapFeature.hpp" +#include "IHost.hpp" + +namespace pipedal +{ + class Lv2PluginStateEntry: public JsonSerializable { + public: + std::int32_t flags_ = 0; + std::string atomType_; + std::vector value_; + private: + virtual void write_json(json_writer &writer) const; + virtual void read_json(json_reader &reader); + + }; + class Lv2PluginState: public JsonSerializable + { + public: + std::map values_; + + std::string ToString() const; + private: + virtual void write_json(json_writer &writer) const; + virtual void read_json(json_reader &reader); + + }; + + + // state callback interface for plugin's LV2_State + class StateInterface + { + public: + StateInterface(IHost *host, LilvInstance *pInstance, const LV2_State_Interface *pluginStateInterface); + + public: + Lv2PluginState Save(); + void Restore(const Lv2PluginState &state); + + private: + struct Urids { + public: + void Init(MapFeature&map) + { + atom__Atom = map.GetUrid(LV2_ATOM__Atom); + atom__String = map.GetUrid(LV2_ATOM__String); + atom__Float = map.GetUrid(LV2_ATOM__Float); + } + LV2_URID atom__Atom; + LV2_URID atom__String; + LV2_URID atom__Float; + }; + Urids urids; + struct SaveCallState + { + StateInterface *pThis; + LV2_State_Status status; + Lv2PluginState state; + }; + struct RestoreCallState + { + StateInterface *pThis; + LV2_State_Status status; + const Lv2PluginState *pState; + }; + static LV2_State_Status FnStateStoreFunction( + LV2_State_Handle handle, + uint32_t key, + const void *value, + size_t size, + uint32_t type, + uint32_t flags); + LV2_State_Status StateStoreFunction( + SaveCallState *pCallState, + uint32_t key, + const void *value, + size_t size, + uint32_t type, + uint32_t flags); + static const void *FnStateRetreiveFunction( + LV2_State_Handle handle, + uint32_t key, + size_t *size, + uint32_t *type, + uint32_t *flags); + const void *StateRetrieveFunction( + RestoreCallState*pCallState, + uint32_t key, + size_t *size, + uint32_t *type, + uint32_t *flags); + + private: + MapFeature ↦ + LilvInstance *pInstance; + const LV2_State_Interface *pluginStateInterface; + std::vector features; + }; +} \ No newline at end of file diff --git a/src/Storage.cpp b/src/Storage.cpp index 6b55303..3b47ce3 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -28,6 +28,8 @@ #include #include #include "PiPedalUI.hpp" +#include "PluginHost.hpp" +#include "ss.hpp" using namespace pipedal; @@ -233,13 +235,20 @@ void Storage::LoadBank(int64_t instanceId) { auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); - LoadBankFile(indexEntry.name(), &(this->currentBank)); - if (this->bankIndex.selectedBank() != instanceId) + try { - this->bankIndex.selectedBank(instanceId); - SaveBankIndex(); + LoadBankFile(indexEntry.name(), &(this->currentBank)); + if (this->bankIndex.selectedBank() != instanceId) + { + this->bankIndex.selectedBank(instanceId); + SaveBankIndex(); + } + this->LoadPreset(this->currentBank.selectedPreset()); + } + catch (const std::exception &e) + { + throw std::logic_error(SS("Bank file corrupted. " << e.what() << "(" << GetBankFileName(indexEntry.name()) << ")")); } - this->LoadPreset(this->currentBank.selectedPreset()); } void Storage::LoadCurrentBank() @@ -255,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const { return this->dataRoot / "plugin_presets"; } -std::filesystem::path Storage::GetAudioFilesDirectory() const +std::filesystem::path Storage::GetPluginStorageDirectory() const { return this->dataRoot / "audio_uploads"; } @@ -297,8 +306,8 @@ void Storage::LoadBankIndex() if (bankIndex.entries().size() == 0) { currentBank.clear(); - PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); - int64_t instanceId = currentBank.addPreset(defaultPedalBoard); + Pedalboard defaultPedalboard = Pedalboard::MakeDefault(); + int64_t instanceId = currentBank.addPreset(defaultPedalboard); currentBank.selectedPreset(instanceId); std::string name = "Default Bank"; @@ -378,7 +387,7 @@ void Storage::ReIndex() void Storage::CreateBank(const std::string &name) { BankFile bankFile; - PedalBoard defaultPreset = PedalBoard::MakeDefault(); + Pedalboard defaultPreset = Pedalboard::MakeDefault(); defaultPreset.name(std::string("Default Preset")); bankFile.addPreset(defaultPreset); @@ -449,7 +458,7 @@ void Storage::SaveCurrentBank() SaveBankFile(indexEntry.name(), this->currentBank); } -const PedalBoard &Storage::GetCurrentPreset() +const Pedalboard &Storage::GetCurrentPreset() { auto &item = currentBank.getItem(currentBank.selectedPreset()); return item.preset(); @@ -466,18 +475,18 @@ bool Storage::LoadPreset(int64_t instanceId) } return true; } -void Storage::SaveCurrentPreset(const PedalBoard &pedalBoard) +void Storage::SaveCurrentPreset(const Pedalboard &pedalboard) { auto &item = currentBank.getItem(currentBank.selectedPreset()); - item.preset(pedalBoard); + item.preset(pedalboard); SaveCurrentBank(); } -int64_t Storage::SaveCurrentPresetAs(const PedalBoard &pedalBoard, const std::string &name, int64_t saveAfterInstanceId) +int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, const std::string &name, int64_t saveAfterInstanceId) { - PedalBoard newPedalBoard = pedalBoard; - newPedalBoard.name(name); + Pedalboard newPedalboard = pedalboard; + newPedalboard.name(name); - int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId); + int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId); currentBank.selectedPreset(newInstanceId); SaveCurrentBank(); return newInstanceId; @@ -530,17 +539,18 @@ void Storage::GetPresetIndex(PresetIndex *pResult) pResult->presets().push_back(entry); } } -int64_t Storage::GetPresetByProgramNumber(uint8_t program)const +int64_t Storage::GetPresetByProgramNumber(uint8_t program) const { if (program >= currentBank.presets().size()) { - if (currentBank.presets().size() == 0) return -1; - program = (uint8_t)currentBank.presets().size()-1; + if (currentBank.presets().size() == 0) + return -1; + program = (uint8_t)currentBank.presets().size() - 1; } return currentBank.presets()[program]->instanceId(); } -PedalBoard Storage::GetPreset(int64_t instanceId) const +Pedalboard Storage::GetPreset(int64_t instanceId) const { for (size_t i = 0; i < currentBank.presets().size(); ++i) { @@ -630,10 +640,10 @@ int64_t Storage::CopyPreset(int64_t fromId, int64_t toId) auto &fromItem = this->currentBank.getItem(fromId); if (toId == -1) { - PedalBoard newPedalBoard = fromItem.preset(); + Pedalboard newPedalboard = fromItem.preset(); std::string name = GetPresetCopyName(fromItem.preset().name()); - newPedalBoard.name(name); - return this->currentBank.addPreset(newPedalBoard, fromId); + newPedalboard.name(name); + return this->currentBank.addPreset(newPedalboard, fromId); } else { @@ -753,13 +763,14 @@ void Storage::MoveBank(int from, int to) this->SaveBankIndex(); } - -int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) { +int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) +{ auto &entries = this->bankIndex.entries(); if (bankNumber >= entries.size()) { - if (entries.size() == 0) return -1; - bankNumber = (uint8_t)(entries.size()-1); + if (entries.size() == 0) + return -1; + bankNumber = (uint8_t)(entries.size() - 1); } return entries[bankNumber].instanceId(); } @@ -792,8 +803,8 @@ int64_t Storage::DeleteBank(int64_t bankId) BankIndexEntry newEntry; BankFile defaultBank; - PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); - int64_t instanceId = defaultBank.addPreset(defaultPedalBoard); + Pedalboard defaultPedalboard = Pedalboard::MakeDefault(); + int64_t instanceId = defaultBank.addPreset(defaultPedalboard); defaultBank.selectedPreset(instanceId); std::string name = "Default Bank"; @@ -832,7 +843,7 @@ int64_t Storage::UploadPreset(const BankFile &bankFile, int64_t uploadAfter) } for (size_t i = 0; i < bankFile.presets().size(); ++i) { - PedalBoard preset = bankFile.presets()[i]->preset(); + Pedalboard preset = bankFile.presets()[i]->preset(); int n = 2; std::string baseName = preset.name(); @@ -1063,8 +1074,11 @@ bool Storage::RestoreCurrentPreset(CurrentPreset *pResult) reader.read(pResult); std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown. } - catch (const std::exception &) + catch (const std::exception &e) { + Lv2Log::warning(SS("Failed to restore current preset. " << e.what())); + + std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown. return false; } return true; @@ -1330,9 +1344,6 @@ void Storage::SetFavorites(const std::map &favorites) pipedal::JackServerSettings Storage::GetJackServerSettings() { JackServerSettings result; -#if JACK_HOST - result.Initialize(); -#else std::filesystem::path fileName = this->dataRoot / "AudioConfig.json"; std::ifstream f; f.open(fileName); @@ -1341,14 +1352,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings() json_reader reader(f); reader.read(&result); } +#if JACK_HOST + result.Initialize(); #endif + return result; } void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration) { -#if JACK_HOST -#error IMPLEMENT ME -#else std::filesystem::path fileName = this->dataRoot / "AudioConfig.json"; std::ofstream f; f.open(fileName); @@ -1357,10 +1368,12 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi json_writer writer(f); writer.write(jackConfiguration); } +#if JACK_HOST + jackConfiguration.Write(); #endif } -void Storage::SetSystemMidiBindings(const std::vector&bindings) +void Storage::SetSystemMidiBindings(const std::vector &bindings) { std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json"; std::ofstream f; @@ -1382,63 +1395,85 @@ std::vector Storage::GetSystemMidiBindings() { json_reader reader(f); reader.read(&result); - } else { + } + else + { result.push_back(MidiBinding::SystemBinding("prevProgram")); result.push_back(MidiBinding::SystemBinding("nextProgram")); } return result; } -static bool containsDotDot(const std::string&value) +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) +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 + 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 void ThrowPermissionDeniedError() +static void ThrowPermissionDeniedError() { throw std::logic_error("Permission denied."); } -std::vector Storage::GetFileList(const PiPedalFileProperty&fileProperty) + +class LexicographicCompare { - if (!PiPedalFileProperty::IsDirectoryNameValid(fileProperty.directory())) +public: + bool operator()(const std::string &left, const std::string &right) + { + return std::lexicographical_compare(left.begin(), left.end(), right.begin(), right.end()); + } +} lexicographicCompare; + +std::vector Storage::GetFileList(const UiFileProperty &fileProperty) +{ + if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory())) { ThrowPermissionDeniedError(); } std::vector result; - std::filesystem::path audioFileDirectory = this->GetAudioFilesDirectory() / fileProperty.directory(); - try { - for (auto const&dir_entry: std::filesystem::directory_iterator(audioFileDirectory)) + // if fileProperty has a user-accessible directory, push the entire file path. + if (fileProperty.directory().size() != 0) + { + std::filesystem::path audioFileDirectory = this->GetPluginStorageDirectory() / fileProperty.directory(); + try { - if (dir_entry.is_regular_file()) + for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory)) { - auto &path = dir_entry.path(); - if (fileProperty.IsValidExtension(path.extension().string())) + if (dir_entry.is_regular_file()) { - result.push_back(fileProperty.directory() / path.filename()); + auto &path = dir_entry.path(); + if (fileProperty.IsValidExtension(path.extension().string())) + { + // a relative path! + result.push_back(path); + } } } } - } catch(const std::exception&error) - { - throw std::logic_error("Directory not found: " + audioFileDirectory.string()); + catch (const std::exception &error) + { + throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string()); + } } + // sort lexicographically + + std::sort(result.begin(), result.end(), lexicographicCompare); return result; } - JSON_MAP_BEGIN(UserSettings) JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, showStatusMonitor) diff --git a/src/Storage.hpp b/src/Storage.hpp index 9476b2d..ecafc68 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -20,7 +20,7 @@ #pragma once #include #include -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" #include "Presets.hpp" #include "PluginPreset.hpp" #include "Banks.hpp" @@ -33,12 +33,13 @@ namespace pipedal { -class PiPedalFileProperty; +class UiFileProperty; +class Lv2PluginInfo; class CurrentPreset { public: bool modified_ = false; - PedalBoard preset_; + Pedalboard preset_; DECLARE_JSON_MAP(CurrentPreset); }; @@ -63,12 +64,12 @@ private: PluginPresetIndex pluginPresetIndex; private: + void MaybeCopyDefaultPresets(); static std::string SafeEncodeName(const std::string& name); 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(); @@ -99,7 +100,9 @@ public: void SetDataRoot(const std::filesystem::path& path); void SetConfigRoot(const std::filesystem::path& path); - std::vector GetPedalBoards(); + std::filesystem::path GetPluginStorageDirectory() const; + + std::vector GetPedalboards(); const BankIndex & GetBanks() const { return bankIndex; } @@ -112,13 +115,13 @@ public: void SaveUserSettings(); void LoadBank(int64_t instanceId); int64_t GetBankByMidiBankNumber(uint8_t bankNumber); - const PedalBoard& GetCurrentPreset(); - void SaveCurrentPreset(const PedalBoard&pedalBoard); - int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1); + const Pedalboard& GetCurrentPreset(); + void SaveCurrentPreset(const Pedalboard&pedalboard); + int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, const std::string&namne,int64_t saveAfterInstanceId = -1); int64_t GetCurrentPresetId() const; void GetPresetIndex(PresetIndex*pResult); void SetPresetIndex(const PresetIndex &presetIndex); - PedalBoard GetPreset(int64_t instanceId) const; + Pedalboard GetPreset(int64_t instanceId) const; int64_t GetPresetByProgramNumber(uint8_t program) const; void GetBankFile(int64_t instanceId,BankFile*pResult) const; int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter); @@ -136,7 +139,7 @@ public: void MoveBank(int from, int to); int64_t DeleteBank(int64_t bankId); - std::vector GetFileList(const PiPedalFileProperty&fileProperty); + std::vector GetFileList(const UiFileProperty&fileProperty); void SetJackChannelSelection(const JackChannelSelection&channelSelection); @@ -156,6 +159,8 @@ public: void SaveCurrentPreset(const CurrentPreset ¤tPreset); bool RestoreCurrentPreset(CurrentPreset*pResult); + //std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path); + private: bool pluginPresetIndexChanged = false; void LoadPluginPresetIndex(); diff --git a/src/Vst3Effect.cpp b/src/Vst3Effect.cpp index 6769e4f..f9b743c 100644 --- a/src/Vst3Effect.cpp +++ b/src/Vst3Effect.cpp @@ -24,7 +24,7 @@ #include "ss.hpp" #include -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include "vst3/Vst3Host.hpp" #include "Lv2Log.hpp" @@ -60,7 +60,7 @@ #include "Vst3MidiToEvent.hpp" #include "vst3/Vst3EffectImpl.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" using namespace pipedal; diff --git a/src/Vst3Host.cpp b/src/Vst3Host.cpp index 03dae19..bd0e725 100644 --- a/src/Vst3Host.cpp +++ b/src/Vst3Host.cpp @@ -23,7 +23,7 @@ */ #include "ss.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include "vst3/Vst3Host.hpp" #include "Lv2Log.hpp" #include @@ -86,7 +86,7 @@ namespace pipedal const Vst3Host::PluginList &getPluginList() override { return pluginList; } std::unique_ptr CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override; - std::unique_ptr CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost) override; + std::unique_ptr CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost) override; private: Lv2PluginUiInfo *GetPluginInfo(const std::string &uri); @@ -615,22 +615,22 @@ static std::vector HexToByteArray(const std::string &hexState) } return result; } -std::unique_ptr Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost) +std::unique_ptr Vst3HostImpl::CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost) { - std::unique_ptr result = CreatePlugin(pedalBoardItem.instanceId(), pedalBoardItem.uri(), pHost); - auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri()); + std::unique_ptr result = CreatePlugin(pedalboardItem.instanceId(), pedalboardItem.uri(), pHost); + auto pluginInfo = this->GetPluginInfo(pedalboardItem.uri()); if (!pluginInfo) { - throw Vst3Exception(SS("Plugin " << pedalBoardItem.pluginName() << " not found.")); + throw Vst3Exception(SS("Plugin " << pedalboardItem.pluginName() << " not found.")); } - if (pedalBoardItem.vstState().length() != 0) + if (pedalboardItem.vstState().length() != 0) { - std::vector state = HexToByteArray(pedalBoardItem.vstState()); + std::vector state = HexToByteArray(pedalboardItem.vstState()); result->SetState(state); } else { - for (const ControlValue &controlValue : pedalBoardItem.controlValues()) + for (const ControlValue &controlValue : pedalboardItem.controlValues()) { int32_t index = -1; for (size_t i = 0; i < pluginInfo->controls().size(); ++i) @@ -647,7 +647,7 @@ std::unique_ptr Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar } } } - for (ControlValue &controlValue : pedalBoardItem.controlValues()) + for (ControlValue &controlValue : pedalboardItem.controlValues()) { int32_t index = -1; for (size_t i = 0; i < pluginInfo->controls().size(); ++i) @@ -667,7 +667,7 @@ std::unique_ptr Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar } controlValue.value(t); } else { - Lv2Log::warning(SS(pedalBoardItem.pluginName() << ": Control key not found. key=" << controlValue.key() )); + Lv2Log::warning(SS(pedalboardItem.pluginName() << ": Control key not found. key=" << controlValue.key() )); } } diff --git a/src/Vst3test.cpp b/src/Vst3test.cpp index 183490b..3de3db9 100644 --- a/src/Vst3test.cpp +++ b/src/Vst3test.cpp @@ -22,7 +22,7 @@ * SOFTWARE. */ -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include "vst3/Vst3Host.hpp" #include @@ -75,7 +75,7 @@ void RunVsts() cout << "Scanning" << endl; const auto & plugins = vst3Host->RescanPlugins(); - PiPedalHost host; + PluginHost host; IHost *pHost = host.asIHost(); host.setSampleRate(44100); diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 2e7dbc5..9add1cb 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -17,6 +17,7 @@ #include #include #include "Ipv6Helpers.hpp" +#include "util.hpp" #include "WebServer.hpp" @@ -582,6 +583,7 @@ namespace pipedal { try { + SetThreadName("webMain"); // The io_context is required for all I/O boost::asio::io_service ioc{threads}; //********************************* @@ -632,8 +634,9 @@ namespace pipedal v.reserve(threads - 1); for (auto i = threads - 1; i > 0; --i) v.emplace_back( - [&ioc] + [&ioc,i] { + SetThreadName(SS("web_" << i)); ioc.run(); }); diff --git a/src/Worker.cpp b/src/Worker.cpp index 7227091..047400b 100644 --- a/src/Worker.cpp +++ b/src/Worker.cpp @@ -1,3 +1,20 @@ +/* + Copyright 2007-2012 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +// (Borrows heavily from worker.c by David Robillard.) + // Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -17,97 +34,160 @@ // 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. -/* - Borrows heavily from worker.c by David Robillard. - - Copyright 2007-2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - #include "Worker.hpp" #include #include #include "Lv2Log.hpp" #include #include // for nice() +#include "util.hpp" using namespace pipedal; -const int RING_BUFFER_SIZE = 16 * 1024; +const int RING_BUFFER_SIZE = 64 * 1024; -Worker::Worker(LilvInstance *lilvInstance_, const LV2_Worker_Interface *workerInterface_) +Worker::Worker(const std::shared_ptr &pHostWorker, LilvInstance *lilvInstance_, const LV2_Worker_Interface *workerInterface_) : lilvInstance(lilvInstance_), - requestRingBuffer(RING_BUFFER_SIZE), + pHostWorker(pHostWorker), responseRingBuffer(RING_BUFFER_SIZE), workerInterface(workerInterface_) { - responseBuffer = (char *)malloc(RING_BUFFER_SIZE); - - StartWorkerThread(); + responseBuffer.resize(16 * 1024); } +void Worker::Close() +{ + { + std::lock_guard lock(outstandingRequestMutex); + if (closed) + return; + closed = true; + exiting = true; + } + WaitForAllResponses(); +} Worker::~Worker() { - StopWorkerThread(); - sem_destroy(&requestSemaphore); - free(responseBuffer); + Close(); } LV2_Worker_Status Worker::worker_respond_fn(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data) { Worker *this_ = (Worker *)handle; - return this_->WorkerResponse(size, data); + return this_->WorkerRespond(size, data); } -LV2_Worker_Status Worker::WorkerResponse(uint32_t size, const void *data) +LV2_Worker_Status Worker::WorkerRespond(uint32_t size, const void *data) { - if (responseRingBuffer.writeSpace() < sizeof(size)+size) { + std::lock_guard lock(outstandingRequestMutex); + ++outstandingRequests; + } + LV2_Worker_Status status; + if (responseRingBuffer.writeSpace() < sizeof(size) + size) + { + { + std::lock_guard lock(outstandingRequestMutex); + --outstandingRequests; + cvOutstandingRequests.notify_all(); + } return LV2_WORKER_ERR_NO_SPACE; } - if (!responseRingBuffer.write(sizeof(size), (uint8_t *)&size)) + else { - return LV2_WORKER_ERR_NO_SPACE; + if (!responseRingBuffer.write(sizeof(size), (uint8_t *)&size)) + { + throw std::logic_error("Response queue sync lost."); + } + if (!responseRingBuffer.write(size, (uint8_t *)data)) + { + throw std::logic_error("Response queue sync lost."); + } + return LV2_WORKER_SUCCESS; } - if (!responseRingBuffer.write(size, (uint8_t *)data)) - { - return LV2_WORKER_ERR_NO_SPACE; - } - return LV2_WORKER_SUCCESS; } -void Worker::EmitResponses() +bool Worker::EmitResponses() +{ + bool emitted = false; + while (true) + { + if (!responseRingBuffer.isReadReady()) + { + break; + } + + emitted = true; + uint32_t size; + responseRingBuffer.read(sizeof(size), (uint8_t *)&size); + if (size > responseBuffer.size()) + { + responseBuffer.resize(size); + } + uint8_t *pResponse = &(responseBuffer[0]); + + responseRingBuffer.read(size, pResponse); + + workerInterface->work_response(lilvInstance->lv2_handle, size, pResponse); + { + std::lock_guard lock(outstandingRequestMutex); + --outstandingRequests; + } + cvOutstandingRequests.notify_all(); + } + return emitted; +} + +void Worker::WaitForAllResponses() { while (true) { - uint32_t available = responseRingBuffer.readSpace(); - uint32_t size = 0; - if (available <= sizeof(size)) // i.e. we need a size AND a response. - break; - - responseRingBuffer.read(sizeof(size), (uint8_t *)&size); - responseRingBuffer.read(size, (uint8_t *)responseBuffer); - - workerInterface->work_response(lilvInstance->lv2_handle, size, responseBuffer); + bool gotResponse = EmitResponses(); + { + std::unique_lock lock(outstandingRequestMutex); + if (outstandingRequests == 0) + { + break; + } + if (!gotResponse) + { + cvOutstandingRequests.wait(lock); + } + } } } -void Worker::ThreadProc() + +LV2_Worker_Status Worker::ScheduleWork( + uint32_t size, + const void *data) { - // run nice +1 (priority -1 on Windows) + { + std::lock_guard lock(outstandingRequestMutex); + ++outstandingRequests; + if (exiting) + { + return LV2_WORKER_ERR_NO_SPACE; + } + } + LV2_Worker_Status status = this->pHostWorker->ScheduleWork(this, size, data); + if (status != LV2_Worker_Status::LV2_WORKER_SUCCESS) + { + { + std::lock_guard lock(outstandingRequestMutex); + --outstandingRequests; + } + cvOutstandingRequests.notify_all(); + } + return status; +} + +void HostWorkerThread::ThreadProc() noexcept +{ + // run nice +2 (priority -2 on Windows) + SetThreadName("lv2_worker"); errno = 0; - nice(1); + nice(2); if (errno != 0) { std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl; @@ -122,69 +202,117 @@ void Worker::ThreadProc() return; } uint32_t size; - while (requestRingBuffer.readSpace() > sizeof(size)) + + Worker *pWorker; + + if (!requestRingBuffer.read(sizeof(size), (uint8_t *)&size)) { - if (!requestRingBuffer.read(sizeof(size), (uint8_t *)&size)) - { - throw PiPedalStateException("Working ringbuffer read failed."); - } - void *data = malloc(size); - if (!requestRingBuffer.read(size, (uint8_t *)data)) - { - throw PiPedalStateException("Working ringbuffer read failed."); - } - workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data); - free(data); + throw PiPedalStateException("Working ringbuffer read failed."); } + + if (!requestRingBuffer.read(sizeof(pWorker), (uint8_t *)&pWorker)) + { + throw PiPedalStateException("Worker ringbuffer read failed."); + } + size -= sizeof(pWorker); + + if (size > dataBuffer.size()) + { + dataBuffer.resize(size); + } + uint8_t *pData = &(dataBuffer[0]); + if (!requestRingBuffer.read(size, pData)) + { + throw PiPedalStateException("Worker ringbuffer read failed."); + } + if (pWorker == nullptr) // signals close. + { + break; + } + pWorker->RunBackgroundTask(size, pData); } } catch (const std::exception &e) { Lv2Log::error("Lv2 Worker thread proc exited abnormally. (%s)", e.what()); } + requestRingBuffer.close(); } -void Worker::StopWorkerThread() +HostWorkerThread::HostWorkerThread() +{ + this->dataBuffer.resize(16*1024); + pThread = std::make_unique([this]() + { this->ThreadProc(); }); +} + +void HostWorkerThread::Close() +{ + + bool sendClose = false; + { + std::lock_guard lock{submitMutex}; + if (!closed) + { + closed = true; + sendClose = true; + } + } + if (sendClose) + { + ScheduleWork(nullptr, 0, nullptr); + } +} +HostWorkerThread::~HostWorkerThread() { if (pThread) { - auto pThread = this->pThread; - this->pThread = nullptr; - exiting = true; - requestRingBuffer.close(); + // ask worker thread to terminate. + Close(); pThread->join(); - delete pThread; + pThread = nullptr; } } -void Worker::StartWorkerThread() -{ - auto fn = [this]() - { this->ThreadProc(); }; - this->pThread = new std::thread(fn); -} -LV2_Worker_Status Worker::ScheduleWork( - uint32_t size, - const void *data) +LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, const void *data) { - if (!exiting) + std::lock_guard lock(submitMutex); + + if (exiting) { - size_t space = requestRingBuffer.writeSpace(); - if (space < sizeof(size) + size) - { - return LV2_WORKER_ERR_NO_SPACE; - } - if (!requestRingBuffer.write(sizeof(size), (uint8_t *)&size)) - { - Lv2Log::debug("Not enough space in Worker ring buffer. Request failed."); - return LV2_WORKER_ERR_NO_SPACE; - } - if (!requestRingBuffer.write(size, (uint8_t *)data)) - { - // probably not going to survive. :-( - Lv2Log::error("Not enough space in Worker ring buffer. Request ring buffer probably lost sync."); - return LV2_WORKER_ERR_NO_SPACE; - } + return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE; } - return LV2_WORKER_ERR_NO_SPACE; + if (worker == nullptr) + { + exiting = true; + } + + if (requestRingBuffer.writeSpace() < sizeof(worker) + sizeof(size) + size) + { + return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE; + } + size_t packetSizeL = (size + sizeof(worker)); + uint32_t packetSize = (uint32_t)(packetSizeL); + if (packetSizeL != packetSize) + { + return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE; + } + + requestRingBuffer.write(sizeof(packetSize), (uint8_t *)&packetSize); + requestRingBuffer.write(sizeof(worker), (uint8_t *)&worker); + requestRingBuffer.write(size, (uint8_t *)data); + + return LV2_Worker_Status::LV2_WORKER_SUCCESS; +} + +void Worker::RunBackgroundTask(size_t size, uint8_t *data) +{ + workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data); + + bool notify = false; + { + std::lock_guard lock(outstandingRequestMutex); + --outstandingRequests; + } + cvOutstandingRequests.notify_all(); } diff --git a/src/Worker.hpp b/src/Worker.hpp index 33d8266..42ce6a7 100644 --- a/src/Worker.hpp +++ b/src/Worker.hpp @@ -32,48 +32,76 @@ #include "lv2/urid.lv2/urid.h" #include "lv2/atom.lv2/atom.h" #include "lv2/worker.lv2/worker.h" +#include "condition_variable" #include #include #include #include #include "RingBuffer.hpp" +#include namespace pipedal { + + class Worker; + + class HostWorkerThread { + public: + HostWorkerThread(); + ~HostWorkerThread(); + + void Close(); + LV2_Worker_Status ScheduleWork(Worker*worker, size_t size, const void*data); + private: + bool closed = false; + std::unique_ptr pThread; + void ThreadProc() noexcept; + + RingBuffer requestRingBuffer; + bool exiting = false; + std::mutex submitMutex; + + std::vector dataBuffer; + + + + }; + class Worker { private: + std::shared_ptr pHostWorker = nullptr; LilvInstance*lilvInstance; const LV2_Worker_Interface*workerInterface; - sem_t requestSemaphore; + bool closed = false; bool exiting = false; - RingBuffer requestRingBuffer; RingBuffer responseRingBuffer; - char *responseBuffer; - - std::thread* pThread = nullptr; - - void ThreadProc(); - void StartWorkerThread(); - void StopWorkerThread(); + std::vector responseBuffer; static LV2_Worker_Status worker_respond_fn(LV2_Worker_Respond_Handle handle, uint32_t size, const void* data); - LV2_Worker_Status WorkerResponse(uint32_t size,const void*data); + LV2_Worker_Status WorkerRespond(uint32_t size,const void*data); + std::mutex outstandingRequestMutex; + std::condition_variable cvOutstandingRequests; + int64_t outstandingRequests = 0; + void WaitForAllResponses(); public: - Worker(LilvInstance *instance, const LV2_Worker_Interface *iface); + Worker(const std::shared_ptr& pHostWorker,LilvInstance *instance, const LV2_Worker_Interface *iface); ~Worker(); + void Close(); LV2_Worker_Status ScheduleWork( uint32_t size, const void *data); - void EmitResponses(); + void RunBackgroundTask(size_t size, uint8_t*data); + + bool EmitResponses(); }; diff --git a/src/defaultFavorites.json b/src/defaultFavorites.json index 56b1991..e485228 100644 --- a/src/defaultFavorites.json +++ b/src/defaultFavorites.json @@ -10,4 +10,5 @@ "http://two-play.com/plugins/toob-tone-stack": 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.hpp b/src/json.hpp index 9748e0a..8f270bd 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #define DECLARE_JSON_MAP(CLASSNAME) \ static json_map::storage_type jmap @@ -277,7 +278,7 @@ namespace pipedal const char *CRLF; private: - bool allowNaN_ = true; + bool allowNaN_ = false; std::ostream &os; int indent_level; bool compressed; @@ -311,7 +312,7 @@ namespace pipedal os << text; } using string_view = boost::string_view; - json_writer(std::ostream &os, bool compressed = true, bool allowNaN = true) + json_writer(std::ostream &os, bool compressed = true, bool allowNaN = false) : os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0) { this->CRLF = compressed ? "" : "\r\n"; @@ -377,9 +378,14 @@ namespace pipedal } void write(float f) { - if (allowNaN_ && (std::isnan(f) || std::isinf(f))) + if ((std::isnan(f) || std::isinf(f))) { - os << "NaN"; + if (allowNaN_) + { + os << "NaN"; + } else { + os << std::numeric_limits::max(); + } } else { @@ -388,9 +394,14 @@ namespace pipedal } void write(double f) { - if (allowNaN_ && (std::isnan(f) || std::isinf(f))) + if ((std::isnan(f) || std::isinf(f))) { - os << "NaN"; + if (allowNaN_) + { + os << "NaN"; + } else { + os << std::numeric_limits::max(); + } } else { @@ -440,6 +451,23 @@ namespace pipedal os << "]"; } } + void write(const std::vector &value) + { + // 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 << "]"; + } + template < class Category, class T, @@ -561,6 +589,14 @@ namespace pipedal } } + template + requires IsJsonSerializable + void write(T*obj) + { + writeRawWritable(*obj); + } + + template void write(T *obj) { @@ -575,6 +611,13 @@ namespace pipedal } } + template + requires IsJsonSerializable + void write(T&obj) + { + writeRawWritable(obj); + } + template requires IsJsonSerializable void write(const T&obj) @@ -678,6 +721,8 @@ namespace pipedal } public: + void start_object() { consume('{');} + void end_object() { consume('}');} void consume(char expected); void consumeToken(const char *expectedToken, const char *errorMessage); int peek() @@ -685,7 +730,16 @@ namespace pipedal skip_whitespace(); return is_.peek(); } + template + void read_member(const std::string&name,U *value) + { + std::string v; + read(&v); + if (v != name) throw std::logic_error("Expecting property '" + name + "'"); + consume(':'); + read(value); + } public: std::string read_string(); diff --git a/src/jsonTest.cpp b/src/jsonTest.cpp index 5550ab6..e71a6c6 100644 --- a/src/jsonTest.cpp +++ b/src/jsonTest.cpp @@ -23,7 +23,6 @@ #include #include - #include "json.hpp" #include "json_variant.hpp" #include @@ -31,60 +30,53 @@ using namespace pipedal; -class JsonTestTarget { +class JsonTestTarget +{ private: - JsonTestTarget(const JsonTestTarget&) {} // hide copy constructor. - JsonTestTarget& operator=(const JsonTestTarget&) { return *this;} // hide assignment. -public: + JsonTestTarget(const JsonTestTarget &) {} // hide copy constructor. + JsonTestTarget &operator=(const JsonTestTarget &) { return *this; } // hide assignment. +public: JsonTestTarget() // make sure reading works without a default cosntructor. { } bool operator==(const JsonTestTarget &other) const { - return this->int_ == other.int_ - && this->float_ == other.float_ - && this->double_ == other.double_ - && this->string_ == other.string_ - && std::equal(this->ints_.begin(),this->ints_.end(), other.ints_.begin(),other.ints_.end()) - && std::equal(this->strings_.begin(),this->strings_.end(), other.strings_.begin(),other.strings_.end()) - ; - + return this->int_ == other.int_ && this->float_ == other.float_ && this->double_ == other.double_ && this->string_ == other.string_ && std::equal(this->ints_.begin(), this->ints_.end(), other.ints_.begin(), other.ints_.end()) && std::equal(this->strings_.begin(), this->strings_.end(), other.strings_.begin(), other.strings_.end()); } + public: int int_ = 1; float float_ = 3; double double_ = 4; - + public: std::string string_{"5"}; - std::vector ints_ { 1,2,3,4,5}; - std::vector strings_ { "a","b","c","d"}; - std::vector > compound_array_ { { 1,2},{3},{4,5,6},{9}}; + std::vector ints_{1, 2, 3, 4, 5}; + std::vector strings_{"a", "b", "c", "d"}; + std::vector> compound_array_{{1, 2}, {3}, {4, 5, 6}, {9}}; public: static json_map::storage_type jmap; - public: - JsonTestTarget(json_reader& reader) + JsonTestTarget(json_reader &reader) { - #ifdef JUNK +#ifdef JUNK while (true) { - const char*name = reader.read_member_name(); - if (name == nullptr) + const char *name = reader.read_member_name(); + if (name == nullptr) { break; } } - #endif +#endif } - }; -json_map::storage_type JsonTestTarget::jmap {{ - //json_map::reference("char_", &JsonTestTarget::char_), +json_map::storage_type JsonTestTarget::jmap{{ + // json_map::reference("char_", &JsonTestTarget::char_), json_map::reference("int", &JsonTestTarget::int_), json_map::reference("floatt", &JsonTestTarget::float_), json_map::reference("double", &JsonTestTarget::double_), @@ -94,12 +86,11 @@ json_map::storage_type JsonTestTarget::jmap {{ json_map::reference("compoundarray", &JsonTestTarget::compound_array_), }}; - - -TEST_CASE( "json write", "[json_write_test]" ) { +TEST_CASE("json write", "[json_write_test]") +{ std::cout << "== json write ==" << std::endl; std::stringstream os; - json_writer writer { os }; + json_writer writer{os}; JsonTestTarget testTarget; @@ -111,20 +102,19 @@ TEST_CASE( "json write", "[json_write_test]" ) { static std::string get_json() { std::stringstream os; - json_writer writer { os }; + json_writer writer{os}; JsonTestTarget testTarget; writer.write(testTarget); return os.str(); - } - -TEST_CASE( "json read", "[json_read_test]" ) { +TEST_CASE("json read", "[json_read_test]") +{ JsonTestTarget source; - source.ints_ = { 1,7,4}; + source.ints_ = {1, 7, 4}; source.string_ = "xyz"; source.double_ = 99483.1837; @@ -133,57 +123,53 @@ TEST_CASE( "json read", "[json_read_test]" ) { writer.write(source); std::string json = os.str(); - std::stringstream input (json); - json_reader reader { input }; + std::stringstream input(json); + json_reader reader{input}; JsonTestTarget dest; reader.read(&dest); REQUIRE(reader.is_complete()); - REQUIRE( source == dest); - - + REQUIRE(source == dest); + JsonTestTarget *pDest; std::stringstream input2(json); - json_reader reader2 { input2}; + json_reader reader2{input2}; reader2.read(&pDest); - - - //JsonTestTarget *testTarget = reader.read_object(); + // JsonTestTarget *testTarget = reader.read_object(); } -TEST_CASE( "json smart ptrs", "[json_smart_ptrs][Build][Dev]" ) { - std::string json =get_json(); - +TEST_CASE("json smart ptrs", "[json_smart_ptrs][Build][Dev]") +{ + std::string json = get_json(); { std::unique_ptr uniquePtr; std::stringstream input(json); - json_reader reader { input }; - reader.read(&uniquePtr); + json_reader reader{input}; + reader.read(&uniquePtr); } { std::shared_ptr sharedPtr; std::stringstream input(json); - json_reader reader { input }; - reader.read(&sharedPtr); + json_reader reader{input}; + reader.read(&sharedPtr); } - } template void TestVariantRoundTrip(const T &value) { json_variant variant(value); - T out = variant.get(); + T out = variant.as(); REQUIRE(out == value); std::string output; { std::stringstream s; json_writer writer(s); - writer.write(variant); + writer.write(variant); output = s.str(); } std::cout << output << std::endl; @@ -191,11 +177,10 @@ void TestVariantRoundTrip(const T &value) { std::stringstream s(output); json_reader reader(s); - + json_variant outputVariant; reader.read(&outputVariant); REQUIRE(outputVariant == variant); - } } void TestVariantRoundTrip(json_variant &value) @@ -207,7 +192,7 @@ void TestVariantRoundTrip(json_variant &value) { std::stringstream s; json_writer writer(s); - writer.write(variant); + writer.write(variant); output = s.str(); } std::cout << output << std::endl; @@ -223,14 +208,12 @@ void TestVariantRoundTrip(json_variant &value) } } - - -class X{ +class X +{ public: - - template - requires std::derived_from - bool write(T &v) + template + requires std::derived_from bool + write(T &v) { (void)v; return true; @@ -243,52 +226,55 @@ public: } }; - - void TestVariantSFINAE() { X x; - json_variant v; - dynamic_cast(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); - +TEST_CASE("json variants", "[json_variants][Build][Dev]") +{ { - json_object obj; - obj["a"] = json_null(); - obj["b"] = 0.25; - obj["c"] = std::move(variantArray); - json_variant variantObj { std::move(obj)}; - TestVariantRoundTrip(variantObj); + TestVariantSFINAE(); - variantObj["a"] = std::string("abc"); - TestVariantRoundTrip(variantObj); + TestVariantRoundTrip(0.0); + TestVariantRoundTrip(std::string("abc")); + TestVariantRoundTrip(json_null()); + + 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)}; + REQUIRE(array.size() == 0); // did it get moved property? + TestVariantRoundTrip(variantArray); + + { + json_object obj; + obj["a"] = json_null(); + obj["b"] = 0.25; + obj["c"] = std::move(variantArray); + json_variant variantObj{std::move(obj)}; + REQUIRE((variantArray.is_null() || variantArray.size() == 0) == true); // did move happen? + REQUIRE(obj.size() == 0); // did move happen? + TestVariantRoundTrip(variantObj); + + variantObj["a"] = std::string("abc"); + TestVariantRoundTrip(variantObj); + } + { + json_variant x = json_variant::make_array(); + x.resize(3); + x[0] = "def"; + } } - { - json_variant x = json_variant::MakeArray(); - x.resize(3); - x[0] = "def"; - } -} \ No newline at end of file + REQUIRE(json_object::allocation_count() == 0); + REQUIRE(json_array::allocation_count() == 0); +} diff --git a/src/json_variant.cpp b/src/json_variant.cpp index f1205c9..2c53e6b 100644 --- a/src/json_variant.cpp +++ b/src/json_variant.cpp @@ -1,18 +1,18 @@ /* * 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 @@ -26,19 +26,534 @@ #include #include #include +#include using namespace pipedal; -void concrete_json_variant_base::write_double_value(json_writer &writer,double value) const +json_variant::~json_variant() { - if (value < std::numeric_limits::max() && value > std::numeric_limits::min()) + free(); +} + +void json_variant::free() +{ + // in-place deletion. + switch (content_type) { - double frac = value-(int32_t)value; - if (value == 0) - { - writer.write((int32_t)value); - return; - } + case ContentType::String: + memString().std::string::~string(); + break; + case ContentType::Object: + memObject().std::shared_ptr::~shared_ptr(); + break; + case ContentType::Array: + memArray().std::shared_ptr::~shared_ptr(); + break; } + content_type = ContentType::Null; +} + + +json_variant::json_variant(json_variant &&other) +{ + this->content_type = ContentType::Null; + switch (other.content_type) + { + case ContentType::Null: + break; + case ContentType::Bool: + this->content.bool_value = other.content.bool_value; + break; + case ContentType::Number: + this->content.double_value = other.content.double_value; + break; + case ContentType::String: + new (content.mem) std::string(std::move(other.memString())); + this->content_type = ContentType::String; + return; + case ContentType::Object: + new (content.mem) std::shared_ptr{std::move(other.memObject())}; + this->content_type = ContentType::Object; + return; + case ContentType::Array: + new (content.mem) std::shared_ptr{std::move(other.memArray())}; + this->content_type = ContentType::Array; + return; + } + this->content_type = other.content_type; + other.content_type = ContentType::Null; +} + +json_variant &json_variant::operator=(const json_variant &other) +{ + free(); + switch (other.content_type) + { + case ContentType::Null: + break; + case ContentType::Bool: + this->content.bool_value = other.content.bool_value; + break; + case ContentType::Number: + this->content.double_value = other.content.double_value; + break; + case ContentType::String: + new (content.mem) std::string(other.memString()); + break; + case ContentType::Object: + new (content.mem) std::shared_ptr{other.memObject()}; + break; + case ContentType::Array: + new (content.mem) std::shared_ptr{other.memArray()}; + this->content_type = ContentType::Array; + break; + } + this->content_type = other.content_type; + return *this; + +} + +json_variant::json_variant(const json_variant &other) +{ + this->content_type = ContentType::Null; + switch (other.content_type) + { + case ContentType::Null: + break; + case ContentType::Bool: + this->content.bool_value = other.content.bool_value; + break; + case ContentType::Number: + this->content.double_value = other.content.double_value; + break; + case ContentType::String: + new (content.mem) std::string(other.memString()); + this->content_type = ContentType::String; + return; + case ContentType::Object: + new (content.mem) std::shared_ptr{other.memObject()}; + this->content_type = ContentType::Object; + return; + case ContentType::Array: + new (content.mem) std::shared_ptr{other.memArray()}; + this->content_type = ContentType::Array; + return; + } + this->content_type = other.content_type; +} + +void json_variant::write_float_value(json_writer &writer, double value) const +{ writer.write(value); } + +void json_variant::write_double_value(json_writer &writer, double value) const +{ + writer.write(value); +} + +void json_variant::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; + } +} +void json_variant::write_json(json_writer &writer) const +{ + switch (content_type) + { + case ContentType::Null: + writer.write_raw("null"); + break; + case ContentType::Bool: + writer.write(as_bool()); + break; + case ContentType::Number: + write_double_value(writer, as_number()); + break; + case ContentType::String: + writer.write(as_string()); + break; + case ContentType::Object: + writer.write(as_object()); + break; + case ContentType::Array: + writer.write(as_array()); + break; + default: + throw std::logic_error("Invalid variant type"); + } +} + +bool json_array::operator==(const json_array &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; +} + +void json_array::read_json(json_reader &reader) +{ + reader.read(&(this->values)); +} + +void json_array::write_json(json_writer &writer) const +{ + writer.start_array(); + bool first = true; + for (auto &value : values) + { + if (!first) + writer.write_raw(","); + first = false; + writer.write(value); + } + writer.end_array(); +} + + +json_object::iterator json_object::find(const std::string& key) +{ + for (auto i = begin(); i != end(); ++i) + { + if (i->first == key) + { + return i; + } + } + return end(); +} +json_object::const_iterator json_object::find(const std::string&key) const +{ + for (auto i = begin(); i != end(); ++i) + { + if (i->first == key) + { + return i; + } + } + return end(); + +} + + + +bool json_object::operator==(const json_object &other) const +{ + + for (const auto &pair : this->values) + { + auto index = other.find(pair.first); + if (index == other.end()) + return false; + if (!(index->second == pair.second)) + return false; + } + for (const auto &pair : other.values) + { + auto index = this->find(pair.first); + if (index == this->end()) + return false; + if (!(index->second == pair.second)) + return false; + } + return true; +} + + + +void json_object::read_json(json_reader &reader) +{ + reader.start_object(); + + while (true) + { + if (reader.peek() == '}') break; + + std::string key; + json_variant value; + reader.read(&key); + reader.consume(':'); + reader.read(&value); + + (*this)[key] = value; + if (reader.peek() == ',') + { + reader.consume(','); + } + } + + reader.end_object(); +} +void json_object::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(); +} + +json_variant &json_array::at(size_t index) +{ + check_index(index); + return values.at(index); +} +const json_variant &json_array::at(size_t index) const +{ + check_index(index); + return values.at(index); +} + +size_t json_variant::size() const +{ + if (content_type == ContentType::Array) + return as_array()->size(); + if (content_type == ContentType::Object) + { + return as_object()->size(); + } + throw std::logic_error("Not supported."); +} + +json_variant &json_object::at(const std::string &index) { + for (auto&entry: values) + { + if (entry.first == index) + { + return entry.second; + } + } + throw std::logic_error("Not found."); + values.push_back(std::pair(index,json_variant())); + return values[values.size()-1].second; +} +const json_variant &json_object::at(const std::string &index) const { + for (const auto&entry: values) + { + if (entry.first == index) + { + return entry.second; + } + } + throw std::logic_error("Not found."); +} + +json_variant &json_object::operator[](const std::string &index) +{ + for (auto&entry: values) + { + if (entry.first == index) + { + return entry.second; + } + } + values.push_back(std::pair(index,json_variant())); + return values[values.size()-1].second; +} +const json_variant &json_object::operator[](const std::string &index) const +{ + return at(index); +} +bool json_object::contains(const std::string &index) const +{ + return find(index) != end(); +} + +json_variant &json_variant::operator=(bool value) +{ + free(); + this->content_type = ContentType::Bool; + this->content.bool_value = value; + return *this; +} + +json_variant &json_variant::operator=(double value) +{ + free(); + this->content_type = ContentType::Number; + this->content.double_value = value; + return *this; +} +json_variant &json_variant::operator=(const std::string &value) +{ + free(); + this->content_type = ContentType::String; + new (this->content.mem) std::string(value); // in-place constructor. + return *this; +} +json_variant &json_variant::operator=(std::string &&value) +{ + free(); + this->content_type = ContentType::String; + new (this->content.mem) std::string(std::move(value)); // in-place constructor. + return *this; +} +json_variant &json_variant::operator=(json_object &&value) +{ + free(); + new (this->content.mem) std::shared_ptr{new json_object(std::move(value))}; + this->content_type = ContentType::Object; + return *this; +} +json_variant &json_variant::operator=(json_array &&value) +{ + free(); + this->content_type = ContentType::Array; + new (this->content.mem) std::shared_ptr{new json_array(std::move(value))}; + return *this; +} +json_variant &json_variant::operator=(json_variant &&value) +{ + if (this->content_type == value.content_type) + { + switch (this->content_type) + { + case ContentType::String: + std::swap(this->memString(), value.memString()); + return *this; + case ContentType::Array: + std::swap(this->memArray(), value.memArray()); + return *this; + case ContentType::Object: + std::swap(this->memObject(), value.memObject()); + return *this; + } + } + free(); + switch (value.content_type) + { + case ContentType::String: + new (content.mem) std::string(std::move(value.memString())); + value.memString().std::string::~string(); + break; + case ContentType::Object: + new (content.mem) std::shared_ptr(std::move(value.memObject())); + value.memObject().std::shared_ptr::~shared_ptr(); + break; + case ContentType::Array: + new (content.mem) std::shared_ptr(std::move(value.memArray())); + value.memArray().std::shared_ptr::~shared_ptr(); + break; + default: + // undifferentiated copy of POD types. + *(uint64_t *)(this->content.mem) = *(uint64_t *)(value.content.mem); + break; + } + this->content_type = value.content_type; + value.content_type = ContentType::Null; + return *this; +} + +bool json_variant::operator==(const json_variant &other) const +{ + if (this->content_type != other.content_type) + return false; + + switch (this->content_type) + { + case ContentType::Null: + return true; + case ContentType::Bool: + return as_bool() == other.as_bool(); + break; + case ContentType::Number: + return as_number() == other.as_number(); + case ContentType::String: + return as_string() == other.as_string(); + case ContentType::Array: + return *(as_array().get()) == *(other.as_array().get()); + case ContentType::Object: + return *(as_object().get()) == *(other.as_object().get());; + default: + throw std::logic_error("Invalid content_type."); + } +} +json_array::json_array(json_array&&other) +: values(std::move(other.values)) +{ + ++allocation_count_; +} +json_object::json_object(json_object&&other) +:values(std::move(other.values)) +{ + ++allocation_count_; +} + +bool json_variant::contains(const std::string &index) const +{ + if (is_object()) + { + return as_object()->contains(index); + } + return false; +} + +std::string json_variant::to_string() const +{ + std::stringstream ss; + json_writer writer(ss); + writer.write(*this); + return ss.str(); +} + +json_variant::json_variant(const char*sz) +: json_variant(std::string(sz)) +{ + +} + + + +/*static*/ json_null json_null::instance; +/*static*/ int64_t json_array::allocation_count_ = 0; // strictly for testing purposes. not thread safe. +/*static*/ int64_t json_object::allocation_count_ = 0; // strictly for testing purposes. not thread safe. + diff --git a/src/json_variant.hpp b/src/json_variant.hpp index 4d797a0..88d43e0 100644 --- a/src/json_variant.hpp +++ b/src/json_variant.hpp @@ -26,8 +26,10 @@ #include #include #include + #include #include +#include #include "json.hpp" namespace pipedal @@ -35,291 +37,514 @@ namespace pipedal class json_null { public: - bool operator==(const json_null&other) const { return true;} + static json_null instance; + bool operator==(const json_null &other) const { return true; } + bool operator!=(const json_null &other) const { return (!((*this) == other)); } + private: int value = 0; }; + class json_object; + class json_array; - template // avoid ordering problem in declarations. - class json_object_base: public JsonSerializable + class json_variant + : 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 + enum class ContentType { - 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; - } + Null, + Bool, + Number, + String, + Object, + Array + }; + using object_ptr = std::shared_ptr; + using array_ptr = std::shared_ptr; + 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() {} + ~json_variant(); + json_variant(); + json_variant(json_variant &&); + json_variant(const json_variant &); - 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(); - } + json_variant(json_null value); + json_variant(bool value); + json_variant(double value); + json_variant(const std::string &value); + json_variant(std::shared_ptr &&value); + json_variant(const std::shared_ptr &value); + json_variant(std::shared_ptr &&value); + json_variant(const std::shared_ptr &value); + json_variant(json_array &&array); + json_variant(json_object &&object); + json_variant(const char*sz); - void check_index(size_t size) const + json_variant(const void*) = delete; // do NOT allow implicit conversion of pointers to bool + + json_variant &operator=(json_variant &&value); + json_variant &operator=(const json_variant &value); + json_variant &operator=(bool value); + json_variant &operator=(double value); + json_variant &operator=(const std::string &value); + json_variant &operator=(std::string &&value); + json_variant &operator=(json_object &&value); + json_variant &operator=(json_array &&value); + + json_variant &operator=(const char*sz) { return (*this) = std::string(sz); } + + json_variant &operator=(void*) = delete; // do NOT allow implicit conversion of pointers to bool + + void require_type(ContentType content_type) const { - if (size >= values.size()) + if (this->content_type != content_type) { - throw std::out_of_range("index out of range."); + throw std::logic_error("Content type is not valid."); } } - std::vector values; - }; + bool is_null() const { return content_type == ContentType::Null; } + bool is_bool() const { return content_type == ContentType::Bool; } + bool is_number() const { return content_type == ContentType::Number; } + bool is_string() const { return content_type == ContentType::String; } + bool is_object() const { return content_type == ContentType::Object; } + bool is_array() const { return content_type == ContentType::Array; } + const json_null &as_null() const + { + require_type(ContentType::Bool); + return json_null::instance; + } + json_null &as_null() + { + require_type(ContentType::Null); + return json_null::instance; + } - class concrete_json_variant_base { - protected: - void write_double_value(json_writer &writer,double value) const; - }; + bool as_bool() const + { + require_type(ContentType::Bool); + return content.bool_value; + } + bool &as_bool() + { + require_type(ContentType::Bool); + return content.bool_value; + } - 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; + double as_number() const + { + require_type(ContentType::Number); + return content.double_value; + } + double &as_number() + { + require_type(ContentType::Number); + return content.double_value; + } + const std::string &as_string() const; + std::string &as_string(); - json_variant_base(json_null value) - :base(value) - { + const std::shared_ptr &as_object() const; + std::shared_ptr &as_object(); - } - 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)) - { - } + const std::shared_ptr &as_array() const; + std::shared_ptr &as_array(); 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(); } + U &as() { static_assert("Invalid type."); } // 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()};}; + static json_variant make_object(); + static json_variant make_array(); - void resize(size_t size) { AsArray().resize(size); } - size_t size() const { return AsArray().size(); } + void resize(size_t size); + size_t size() const; - json_variant&operator[](size_t index) { return AsArray()[index];} - const json_variant&operator[](size_t index) const { return AsArray()[index];} + bool contains(const std::string &index) const; - const json_variant&operator[](const std::string& index) const { return AsObject()[index];} - json_variant&operator[](const std::string& index) { return AsObject()[index];} + json_variant &at(size_t index); + const json_variant &at(size_t index) const; + json_variant &operator[](size_t index); + const json_variant &operator[](size_t index) const; + + json_variant &operator[](const std::string &index); + const json_variant &operator[](const std::string &index) const; + + bool operator==(const json_variant &other) const; + bool operator!=(const json_variant &other) const; + + std::string to_string() const; + private: + void free(); + void write_double_value(json_writer &writer, double value) const; + void write_float_value(json_writer &writer, double value) const; + virtual void read_json(json_reader &reader); + virtual void write_json(json_writer &writer) const; + + static constexpr size_t stringSize = sizeof(std::string); + static constexpr size_t objectSize = sizeof(std::shared_ptr); + static constexpr size_t memSize = stringSize > objectSize ? stringSize : objectSize; + union Content + { + bool bool_value; + double double_value; + float float_value; + int32_t int32_value; + uint8_t mem[memSize]; + }; + + ContentType content_type = ContentType::Null; + Content content; + + std::string &memString(); + object_ptr &memObject(); + array_ptr &memArray(); + + const std::string &memString() const; + const object_ptr &memObject() const; + const array_ptr &memArray() const; + }; + class json_array : public JsonSerializable + { + private: + json_array(const json_array&) { } // deleted. + public: + using ptr = std::shared_ptr; + + json_array() { ++allocation_count_; } + json_array(json_array&&other); + ~json_array() { --allocation_count_; } + + json_variant &at(size_t index); + const json_variant &at(size_t index) const; + + json_variant &operator[](size_t index); + const json_variant &operator[](size_t &index) const; + + void resize(size_t size) { values.resize(size); } + size_t size() const { return values.size(); } + void push_back(json_variant &&value) { values.push_back(std::move(value)); } + template + void push_back(U &&value) { values.push_back(value); } + void push_back(double value) { values.push_back(json_variant{value}); } + void push_back(const std::string &value) { values.push_back(json_variant{value}); } + void push_back(bool value) { values.push_back(json_variant{value}); } + void push_back(const std::shared_ptr &value) { values.push_back(json_variant(value)); } + void push_back(const std::shared_ptr &value) { values.push_back(json_variant(value)); } + + bool operator==(const json_array &other) const; + bool operator!=(const json_array &other) const { return (!((*this) == other)); } + + // Strictly for testing purposes. Not thread-safe. + static int64_t allocation_count() + { + return allocation_count_; + } + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + iterator begin() { return values.begin(); } + iterator end() { return values.end(); } + const_iterator begin() const { return values.begin(); } + const_iterator end() const { return values.end(); } 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(); + static int64_t allocation_count_; // strictly for testing purposes. not thread safe. - } else if (v == 't' || v == 'f') - { - bool b; - reader.read(&b); - (*this) = b; + virtual void read_json(json_reader &reader); + virtual void write_json(json_writer &writer) const; - } else { - // it's a number. - double v; - reader.read(&v); - (*this) = v; - } - } - virtual void write_json(json_writer&writer) const + void check_index(size_t size) const; + + std::vector values; + }; + class json_object : public JsonSerializable + { + private: + json_object(const json_object&) { } // deleted. + public: + using ptr = std::shared_ptr; + + json_object() { ++ allocation_count_;} + json_object(json_object&&other); + ~json_object() { --allocation_count_;} + + size_t size() const { return values.size(); } + json_variant &at(const std::string &index); + const json_variant &at(const std::string &index) const; + + json_variant &operator[](const std::string &index); + const json_variant &operator[](const std::string &index) const; + + bool operator==(const json_object &other) const; + bool operator!=(const json_object &other) const { return (!((*this) == other)); } + bool contains(const std::string &index) const; + + + using values_t = std::vector< std::pair >; + using iterator = values_t::iterator; + using const_iterator = values_t::const_iterator; + + iterator begin() { return values.begin(); } + iterator end() { return values.end(); } + const_iterator begin() const { return values.begin(); } + const_iterator end() const { return values.end(); } + + iterator find(const std::string& key); + const_iterator find(const std::string& key) const; + + + // strictly for testing purposes. Not thread-safe. + static int64_t allocation_count() { - 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"); - } + return allocation_count_; } + private: + virtual void read_json(json_reader &reader); + virtual void write_json(json_writer &writer) const; + + static int64_t allocation_count_; + values_t values; }; - using json_variant = json_variant_base; - using json_object = json_variant::json_object; - using json_array = json_variant::json_array; + //////////////////////////////////////////////// + + inline std::string &json_variant::memString() { return *(std::string *)content.mem; } + inline const std::string &json_variant::memString() const { return *(const std::string *)content.mem; } + + template <> + inline json_null &json_variant::as() { return as_null(); } + + template <> + inline bool &json_variant::as() { return as_bool(); } + + template <> + inline double &json_variant::as() { return as_number(); } + + template <> + inline std::string &json_variant::as() { return as_string(); } + + template <> + inline std::shared_ptr &json_variant::as>() { return as_object(); } + + template <> + inline std::shared_ptr &json_variant::as>() { return as_array(); } + + template <> + inline json_variant &json_variant::as() { return *this; } + + inline json_variant::object_ptr &json_variant::memObject() + { + return *(object_ptr *)content.mem; + } + inline json_variant::array_ptr &json_variant::memArray() + { + return *(array_ptr *)content.mem; + } + + inline const json_variant::object_ptr &json_variant::memObject() const + { + return *(const object_ptr *)content.mem; + } + inline const json_variant::array_ptr &json_variant::memArray() const + { + return *(const array_ptr *)content.mem; + } + + inline json_variant::json_variant(json_array &&array) + { + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr{new json_array(std::move(array))}; + this->content_type = ContentType::Array; + } + inline json_variant::json_variant(json_object &&object) + { + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr{new json_object(std::move(object))}; + this->content_type = ContentType::Object; + } + + inline json_variant::json_variant(const std::string &value) + { + this->content_type = ContentType::Null; + new (content.mem) std::string(value); // placement new. + content_type = ContentType::String; + } + + inline json_variant::json_variant(std::shared_ptr &&value) + { + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr(std::move(value)); // placement new. + content_type = ContentType::Object; + } + inline json_variant::json_variant(const std::shared_ptr &value) + { + // don't deep copy! + std::shared_ptr t = const_cast &>(value); + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr(t); // placement new. + content_type = ContentType::Object; + } + + inline json_variant::json_variant(const std::shared_ptr &value) + { + // Make sure we don't deep copy! + std::shared_ptr t = const_cast &>(value); + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr(t); // placement new. + content_type = ContentType::Array; + } + inline json_variant::json_variant(array_ptr &&value) + { + this->content_type = ContentType::Null; + new (content.mem) std::shared_ptr(std::move(value)); // placement new. + content_type = ContentType::Array; + } + inline json_variant::json_variant() + { + content_type = ContentType::Null; + } + inline json_variant::json_variant(json_null value) + { + content_type = ContentType::Null; + } + inline json_variant::json_variant(bool value) + { + content_type = ContentType::Bool; + content.bool_value = value; + } + + inline json_variant::json_variant(double value) + { + content_type = ContentType::Number; + content.double_value = value; + } + inline std::string &json_variant::as_string() + { + require_type(ContentType::String); + return memString(); + } + + inline const std::string &json_variant::as_string() const + { + require_type(ContentType::String); + return memString(); + } + + inline const json_variant::object_ptr &json_variant::as_object() const + { + require_type(ContentType::Object); + return memObject(); + } + + inline json_variant::object_ptr &json_variant::as_object() + { + require_type(ContentType::Object); + return memObject(); + } + + inline const json_variant::array_ptr &json_variant::as_array() const + { + require_type(ContentType::Array); + return memArray(); + } + + inline json_variant::array_ptr &json_variant::as_array() + { + require_type(ContentType::Array); + return memArray(); + } + + inline /*static*/ json_variant json_variant::make_object() + { + return json_variant{std::make_shared()}; + }; + inline /*static */ json_variant json_variant::make_array() + { + return json_variant{std::make_shared()}; + }; + + inline void json_variant::resize(size_t size) + { + as_array()->resize(size); + } + + inline json_variant &json_variant::at(size_t index) + { + return as_array()->at(index); + } + inline const json_variant &json_variant::at(size_t index) const + { + return as_array()->at(index); + } + + inline json_variant &json_variant::operator[](size_t index) + { + return as_array()->at(index); + } + inline const json_variant &json_variant::operator[](size_t index) const + { + return as_array()->at(index); + } + + inline const json_variant &json_variant::operator[](const std::string &index) const + { + return (*as_object())[index]; + } + inline json_variant &json_variant::operator[](const std::string &index) + { + return (*as_object())[index]; + } + + inline const json_variant &json_array::operator[](size_t &index) const + { + check_index(index); + return values[index]; + } + + inline json_variant &json_array::operator[](size_t index) + { + return at(index); + } + + inline void json_array::check_index(size_t size) const + { + if (size >= values.size()) + { + throw std::out_of_range("index out of range."); + } + } + + inline bool json_variant::operator!=(const json_variant &other) const + { + return !(*this == other); + } + + + // Holds a string but is json_read and json_written as an unqoted json object. + class raw_json_string: public JsonSerializable { + public: + raw_json_string() { } + raw_json_string(const std::string &value) : value(value) {} + const std::string& as_string() const { return value; } + + void Set(const std::string&value) { this->value = value; } + + private: + + virtual void write_json(json_writer &writer) const { + writer.write_raw(value.c_str()); + } + virtual void read_json(json_reader &reader) { + throw std::logic_error("Not implemented."); + } + + std::string value; + }; } // namespace pipedal \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 5162c8b..4ed5c8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" +#include "util.hpp" #include "AudioConfig.hpp" #include "WebServer.hpp" @@ -27,7 +28,7 @@ #include "AvahiService.hpp" #include "PiPedalSocket.hpp" -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include #include #include "PiPedalConfiguration.hpp" @@ -38,6 +39,8 @@ #include #include "HtmlHelper.hpp" #include "Ipv6Helpers.hpp" +#include +#include #include #include @@ -50,6 +53,12 @@ using namespace pipedal; #define BANK_EXTENSION ".piBank" #define PLUGIN_PRESETS_EXTENSION ".piPluginPresets" + +#ifdef __ARM_ARCH_ISA_A64 +#define AARCH64 +#endif + + sem_t signalSemaphore; bool HasAlsaDevice(const std::vector devices, const std::string &deviceId) @@ -70,6 +79,8 @@ public: }; static volatile bool g_SigBreak = false; +std::atomic ga_SigBreak { false }; + void sig_handler(int signo) { if (!g_SigBreak) @@ -78,6 +89,11 @@ void sig_handler(int signo) sem_post(&signalSemaphore); } } + +void throwSystemError(int error) +{ + +} using namespace boost::system; class DownloadIntercept : public RequestHandler @@ -156,19 +172,19 @@ public: { std::string strInstanceId = request_uri.query("id"); int64_t instanceId = std::stol(strInstanceId); - auto pedalBoard = model->GetPreset(instanceId); + auto pedalboard = model->GetPreset(instanceId); // a certain elegance to using same file format for banks and presets. BankFile file; - file.name(pedalBoard.name()); - int64_t newInstanceId = file.addPreset(pedalBoard); + file.name(pedalboard.name()); + int64_t newInstanceId = file.addPreset(pedalboard); file.selectedPreset(newInstanceId); std::stringstream s; json_writer writer(s); writer.write(file); *pContent = s.str(); - *pName = pedalBoard.name(); + *pName = pedalboard.name(); } void GetBank(const uri &request_uri, std::string *pName, std::string *pContent) { @@ -501,7 +517,6 @@ int main(int argc, char *argv[]) sem_init(&signalSemaphore, 0, 0); - signal(SIGINT, sig_handler); #ifndef WIN32 umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction. @@ -570,8 +585,8 @@ int main(int argc, char *argv[]) { Lv2Log::set_logger(MakeLv2SystemdLogger()); } - signal(SIGTERM, sig_handler); - signal(SIGUSR1, sig_handler); + SetThreadName("main"); + std::filesystem::path doc_root = parser.Arguments()[0]; std::filesystem::path web_root = doc_root; @@ -627,7 +642,6 @@ int main(int argc, char *argv[]) try { PiPedalModel model; - model.Init(configuration); // Get heavy IO out of the way before letting dependent (Jack/ALSA) services run. @@ -731,15 +745,56 @@ int main(int argc, char *argv[]) server->AddRequestHandler(downloadIntercept); { - server->RunInBackground(SIGUSR1); + server->RunInBackground(-1); + SetThreadName("avahi"); model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement. + SetThreadName("main"); + + // AARCH64 sem_wait pins CPU 100%. + // static_assert(std::atomic::is_always_lock_free); + + // while (true) + // { + // auto sigBreak = ga_SigBreak.load(); + // if (sigBreak) + // { + // break; + // } + // std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // } - sem_wait(&signalSemaphore); - if (systemd) { - sd_notify(0, "STOPPING=1"); + int sig; + sigset_t sigSet; + int s; + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGINT); + sigaddset(&sigSet, SIGTERM); + sigaddset(&sigSet, SIGUSR1); + + s = pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + if (s != 0) + { + throw std::logic_error("pthread_sigmask failed."); + } + if (s != 0) + { + throwSystemError(s); + } + + //signal(SIGINT, sig_handler); + //signal(SIGTERM, sig_handler); + //signal(SIGUSR1, sig_handler); + + sigwait(&sigSet,&sig); + + if (systemd) + { + sd_notify(0, "STOPPING=1"); + } } } @@ -753,7 +808,7 @@ int main(int argc, char *argv[]) catch (const std::exception &e) { Lv2Log::error(e.what()); + return EXIT_FAILURE; } - Lv2Log::info("PiPedal terminating."); return EXIT_SUCCESS; // only exit in response to a signal. } diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..7a55525 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Robin E. R. Davies + * All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "util.hpp" + +#include +#include + +#include // for gettid() + +using namespace pipedal; + + +void pipedal::SetThreadName(const std::string &name) +{ + std::string threadName = "ppdl_" + name; + if (threadName.length () > 15) + { + threadName = threadName.substr(0,15); + } + pthread_t pid = pthread_self(); + pthread_setname_np(pid,threadName.c_str()); +} \ No newline at end of file diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..952ea44 --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Robin E. R. Davies + * All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once +#include +namespace pipedal { + + inline bool endsWith(const std::string& str, const std::string& suffix) + { + return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix); + } + void SetThreadName(const std::string &name); +} diff --git a/src/vst3/Vst3Effect.hpp b/src/vst3/Vst3Effect.hpp index fcc160b..91df22d 100644 --- a/src/vst3/Vst3Effect.hpp +++ b/src/vst3/Vst3Effect.hpp @@ -24,7 +24,7 @@ #pragma once -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" #include #include "json.hpp" diff --git a/src/vst3/Vst3EffectImpl.hpp b/src/vst3/Vst3EffectImpl.hpp index abf4d82..dc9469f 100644 --- a/src/vst3/Vst3EffectImpl.hpp +++ b/src/vst3/Vst3EffectImpl.hpp @@ -86,7 +86,7 @@ namespace pipedal virtual void CheckSync(); - //- PiPedalHost Interfaces. + //- PluginHost Interfaces. virtual void SetControl(int index, float value); virtual float GetControlValue(int index) const { @@ -130,7 +130,7 @@ namespace pipedal virtual float *GetAudioOutputBuffer(int index) const { return buffers.outputs[index]; } virtual void ResetAtomBuffers() {} virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent. - virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} // no vst equivalent. + virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} // no vst equivalent. virtual void SetAudioInputBuffer(int index, float *buffer) { diff --git a/src/vst3/Vst3Host.hpp b/src/vst3/Vst3Host.hpp index 1217725..4f2ea96 100644 --- a/src/vst3/Vst3Host.hpp +++ b/src/vst3/Vst3Host.hpp @@ -32,7 +32,7 @@ #include "public.sdk/source/vst/utility/uid.h" #include "vst3/Vst3Effect.hpp" -#include "PedalBoard.hpp" +#include "Pedalboard.hpp" namespace Steinberg @@ -67,7 +67,7 @@ namespace Steinberg virtual std::unique_ptr CreatePlugin(long instanceId,const std::string&url, IHost*pHost) = 0; - virtual std::unique_ptr CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost) = 0; + virtual std::unique_ptr CreatePlugin(PedalboardItem&pedalboardItem, IHost*pHost) = 0; static Ptr CreateInstance(const std::string &cacheFilePath = ""); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b3e9e4a..adcd8f8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -48,7 +48,7 @@ add_executable(piddletest main.cpp pch.h UriTest.cpp JsonTest.cpp ../src/Lv2Log.hpp ../src/Lv2Log.cpp ../src/PluginType.hpp ../src/PluginType.cpp ../src/Lv2Effect.hpp ../src/Lv2Effect.cpp - ../src/Lv2PedalBoard.hpp ../src/Lv2PedalBoard.cpp + ../src/Lv2Pedalboard.hpp ../src/Lv2Pedalboard.cpp ../src/MapFeature.hpp ../src/MapFeature.cpp ../src/LogFeature.hpp ../src/LogFeature.cpp diff --git a/test/Lv2Test.cpp b/test/Lv2Test.cpp index e89d014..7a70ded 100644 --- a/test/Lv2Test.cpp +++ b/test/Lv2Test.cpp @@ -19,7 +19,7 @@ #include "pch.h" #include "catch.hpp" #include -#include "PiPedalHost.hpp" +#include "PluginHost.hpp" using namespace piddle;