Interim checking, Convolution Reverb

This commit is contained in:
Robin Davies
2023-04-05 03:00:51 -04:00
parent ed1dc75c53
commit b83ba7ca94
115 changed files with 7643 additions and 3055 deletions
+1
View File
@@ -7,6 +7,7 @@
"${workspaceFolder}",
"${workspaceFolder}/src",
"/usr/include/lilv-0",
"/usr/lib",
"~/src/vst3sdk"
],
"compilerPath": "/usr/bin/gcc",
+41 -12
View File
@@ -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",
+2 -2
View File
@@ -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
+10 -7
View File
@@ -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<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps,prevState,snapshot);
this.updateNotifications();
}
@@ -196,7 +199,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.dialogTitle}>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
About
</Typography>
</Toolbar>
@@ -208,28 +211,28 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
}}
>
<div style={{ margin: 24 }}>
<Typography display="block" variant="h6" color="textPrimary">
<Typography noWrap display="block" variant="h6" color="textPrimary">
PiPedal <span style={{ fontSize: "0.7em" }}>
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
<Typography display="block" variant="body2" style={{ marginBottom: 12 }} >
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022 Robin Davies.
</Typography>
{this.model.isAndroidHosted() && (
<Typography display="block" variant="body2" style={{ marginBottom: 0 }} >
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 0 }} >
{this.model.getAndroidHostVersion()}
</Typography>
)}
{this.model.isAndroidHosted() && (
<Typography display="block" variant="body2" style={{ marginBottom: 12 }} >
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022 Robin Davies.
</Typography>
)}
<Divider />
<Typography display="block" variant="caption" >
<Typography noWrap display="block" variant="caption" >
ADDRESSES
</Typography>
<div style={{marginBottom: 16}}>
+7 -12
View File
@@ -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<
<div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography variant="body2" className={classes.progressText}>
<Typography noWrap variant="body2" className={classes.progressText}>
{this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."}
</Typography>
</div>
@@ -905,7 +900,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography variant="body2" className={classes.loadingBoxItem}>
<Typography noWrap variant="body2" className={classes.loadingBoxItem}>
Loading...
</Typography>
</div>
+3 -3
View File
@@ -298,7 +298,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
<img src="img/ic_bank.svg" className={classes.itemIcon} alt="" />
</div>
<div className={classes.itemLabel}>
<Typography>
<Typography noWrap>
{bankEntry.name}
</Typography>
</div>
@@ -410,7 +410,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.dialogTitle}>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
Banks
</Typography>
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} >
@@ -433,7 +433,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
</IconButton>
)}
<Typography variant="h6" className={classes.dialogTitle}>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
Banks
</Typography>
{(this.state.banks.getEntry(this.state.selectedItem) != null)
+10 -10
View File
@@ -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 (<div/>);
}
if (pedalBoardItem.isSplit())
if (pedalboardItem.isSplit())
{
return (
<SplitControlView item={pedalBoardItem as PedalBoardSplitItem} instanceId={pedalBoardItem!.instanceId}
<SplitControlView item={pedalboardItem as PedalboardSplitItem} instanceId={pedalboardItem!.instanceId}
/>
);
} 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)
{
<div style={{paddingLeft: 40, paddingRight: 40}}>
<Typography color="error" variant="h6" >Missing plugin.</Typography>
<Typography>The plugin '{pedalBoardItem.pluginName}' ({pedalBoardItem.uri}) is not currently installed.</Typography>
<Typography>The plugin '{pedalboardItem.pluginName}' ({pedalboardItem.uri}) is not currently installed.</Typography>
</div>
} else {
return (
<PluginControlView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />
<PluginControlView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />
)
}
}
+23 -41
View File
@@ -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<DialogExProps,DialogExState> {
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<DialogExProps,DialogExState> {
{
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<DialogExProps,DialogExState> {
{
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 (
<Dialog {...extra}>
<Dialog {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
{this.props.children}
</Dialog>
);
+84 -40
View File
@@ -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<typeof styles> {
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<FilePropertyControlProps>, prevState: Readonly<FilePropertyControlState>, 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 (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8}}>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8 }}>
{/* TITLE SECTION */}
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
<Typography variant="caption" display="block" noWrap style={{
@@ -169,13 +213,13 @@ const FilePropertyControl =
<div style={{ flex: "0 0 auto", width: "100%" }}>
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden",marginTop: 8}} onClick={()=> {this.onFileClick()}} >
<div style={{width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px"}}>
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden", marginTop: 8 }} onClick={() => { this.onFileClick() }} >
<div style={{ width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px" }}>
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4,paddingBottom: 4,paddingLeft: 4}}
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4, paddingBottom: 4, paddingLeft: 4 }}
variant='caption'
>{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center",opacity: 0.5, marginRight: 4 }} />
>{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center", opacity: 0.5, marginRight: 4 }} />
</div>
<div style={{ height: "1px", width: "100%", background: "black" }}>&nbsp;</div>
</div>
+47 -24
View File
@@ -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<FilePr
private mounted: boolean = false;
private model: PiPedalModel;
private lastFileProperty: PiPedalFileProperty = new PiPedalFileProperty();
private requestFiles() {
if (!this.props.open)
{
return;
}
if (this.props.fileProperty.directory === "")
{
return;
}
if (this.lastFileProperty === this.props.fileProperty)
{
return;
}
this.lastFileProperty = this.props.fileProperty;
this.model.requestFileList(this.props.fileProperty)
.then((files) => {
if (this.mounted)
@@ -108,6 +116,11 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
this.mounted = false;
}
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, 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<FilePr
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.files,selectedFile) })
}
mapKey: number = 0;
render() {
return (
<DialogEx onClose={() => this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
this.mapKey = 0;
return this.props.open &&
(
<DialogEx onClose={() => this.props.onCancel()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
>
<DialogTitle >{this.props.fileProperty.name}</DialogTitle>
<DialogTitle >
<div>
<IconButton edge="start" color="inherit" onClick={()=> {this.props.onCancel();}} aria-label="back"
>
<ArrowBackIcon fontSize="small" style={{opacity: "0.6"}} />
</IconButton>
<Typography display="inline" >{this.props.fileProperty.name}</Typography>
</div>
</DialogTitle>
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}>&nbsp;</div>
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start" }}>
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start",paddingLeft: 16,paddingTop:16 }}>
{
this.state.files.map(
(value: string, index: number) => {
let selected = value === this.state.selectedFile;
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
return (
<ButtonBase
style={{ width: "320px", flex: "0 0 48px", position: "relative" }}
<ButtonBase key={ this.mapKey++ }
style={{ width: "320px", flex: "0 0 48px", position: "relative"}}
onClick={() => this.onSelect(value)}
>
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%" }} />
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 4 }} />
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
</div>
</ButtonBase>
@@ -181,14 +204,14 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
<input hidden accept="audio/x-wav" name="Upload Impuse File" type="file" multiple />
Upload
</Button>
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label">
<DeleteIcon />
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label" color="primary" >
<DeleteIcon fontSize='small' />
</IconButton>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button onClick={() => this.props.onClose()} aria-label="cancel">
<Button onClick={() => {this.props.onCancel();}} aria-label="cancel">
Cancel
</Button>
<Button style={{ flex: "0 0 auto" }} onClick={() => this.props.onClose()} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
<Button style={{ flex: "0 0 auto" }} onClick={() => {this.props.onOk(this.props.fileProperty,this.state.selectedFile);}} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
Select
</Button>
</div>
+1 -1
View File
@@ -203,7 +203,7 @@ const FullScreenIME =
justifyContent: "center", alignItems: "center"
}}
>
<Typography variant="body2">{this.props.caption}</Typography>
<Typography noWrap variant="body2">{this.props.caption}</Typography>
<Input key={value}
type="number"
defaultValue={control.formatShortValue(value)}
+6 -6
View File
@@ -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 GxTunerControl from './GxTunerControl';
@@ -40,7 +40,7 @@ const styles = (theme: Theme) => createStyles({
interface GxTunerProps extends WithStyles<typeof styles> {
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 (<GxTunerView isToobTuner={false} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<GxTunerView isToobTuner={false} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
@@ -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 (<GxTunerView isToobTuner={true} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<GxTunerView isToobTuner={true} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+2 -2
View File
@@ -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;
}
+3 -1
View File
@@ -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";
+2 -2
View File
@@ -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;
+137
View File
@@ -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;
+32 -2
View File
@@ -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<Lv2Plugin> {
@@ -170,6 +193,12 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
} 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<Lv2Plugin> {
ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = [];
uiPortNotifications: UiPropertyNotification[] = [];
}
+58 -58
View File
@@ -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<typeof styles> {
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 =
<PluginInfoDialog plugin_uri={pluginUri} />
</div>
<div style={{ flex: "0 0 auto" }}>
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalBoardItem?.instanceId ?? 0}
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
/>
</div>
</div>
@@ -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 (
<div className={classes.frame}>
<div id="pedalBoardScroll" className={horizontalScrollLayout ? classes.pedalBoardScrollSmall : classes.pedalBoardScroll}
<div id="pedalboardScroll" className={horizontalScrollLayout ? classes.pedalboardScrollSmall : classes.pedalboardScroll}
style={{ maxHeight: horizontalScrollLayout ? undefined : this.state.screenHeight / 2 }}>
<PedalBoardView key={pluginUri} selectedId={this.state.selectedPedal}
<PedalboardView key={pluginUri} selectedId={this.state.selectedPedal}
onSelectionChanged={this.onSelectionChanged}
onDoubleClick={this.onPedalDoubleClick}
hasTinyToolBar={this.props.hasTinyToolBar}
@@ -427,7 +427,7 @@ export const MainPage =
</div>
</div>
{
(!this.state.splitControlBar) && this.titleBar(pedalBoardItem)
(!this.state.splitControlBar) && this.titleBar(pedalboardItem)
}
<div style={{ flex: "1 1 1px" }}>
@@ -453,7 +453,7 @@ export const MainPage =
</div>
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
<IconButton
onClick={() => { this.onDeletePedal(pedalBoardItem?.instanceId ?? -1) }}
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
size="large">
<img src="/img/old_delete_outline_black_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
</IconButton>
@@ -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={<InputIcon />}
style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}}
>
@@ -485,7 +485,7 @@ export const MainPage =
this.state.splitControlBar && (
<div className={classes.splitControlBar}>
{
this.titleBar(pedalBoardItem)
this.titleBar(pedalboardItem)
}
</div>
)
@@ -495,12 +495,12 @@ export const MainPage =
missing ? (
<div style={{marginLeft: 100,marginTop: 20}}>
<Typography variant="body1" paragraph={true}>Error: Plugin is not installed.</Typography>
<Typography variant="body2" paragraph={true}>{pluginUri}</Typography>
<Typography noWrap variant="body2" paragraph={true}>{pluginUri}</Typography>
</div>
):
(
GetControlView(pedalBoardItem)
GetControlView(pedalboardItem)
)
}
</div>
+3 -3
View File
@@ -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 (<div />);
}
@@ -267,7 +267,7 @@ const MidiBindingView =
(canTrigger) &&
(
<div className={classes.controlDiv2} >
<Typography style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography>
<Typography noWrap style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography>
</div>
)
+4 -4
View File
@@ -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">
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.dialogTitle}>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
Preset MIDI Bindings
</Typography>
</Toolbar>
@@ -57,30 +57,9 @@ export class ControlValue implements Deserializable<ControlValue> {
value: number;
}
export class PropertyValue implements Deserializable<PropertyValue> {
deserialize(input: any): PropertyValue {
this.propertyUri = input.propertyUri;
this.value = input.value;
return this;
}
static deserializeArray(input: any[]): PropertyValue[] {
let result: PropertyValue[] = [];
for (let i = 0; i < input.length; ++i) {
result[i] = new PropertyValue().deserialize(input[i]);
}
return result;
}
setValue(value: number) {
this.value = value;
}
propertyUri: string = "";
value: any = null;
}
export class PedalBoardItem implements Deserializable<PedalBoardItem> {
deserializePedalBoardItem(input: any): PedalBoardItem {
export class PedalboardItem implements Deserializable<PedalboardItem> {
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<PedalBoardItem> {
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<PedalBoardItem> {
}
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<PedalBoardItem> {
}
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<PedalBoard> {
export class Pedalboard implements Deserializable<Pedalboard> {
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<PedalBoardItem, void, undefined> {
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
let it = itemGenerator_(this.items);
while (true)
{
@@ -335,7 +303,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
}
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<PedalBoard> {
}
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<PedalBoard> {
}
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<PedalBoard> {
} 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<PedalBoard> {
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<PedalBoard> {
}
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<PedalBoard> {
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<PedalBoard> {
}
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<PedalBoard> {
}
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<PedalBoard> {
}
}
}
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<PedalBoard> {
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<PedalBoard> {
}
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<PedalBoard> {
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<PedalBoard> {
}
_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<PedalBoard> {
}
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<PedalBoard> {
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<PedalBoard> {
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<PedalBoard> {
}
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<PedalBoardItem, void, undefined> {
function* itemGenerator_(items: PedalboardItem[]): Generator<PedalboardItem, void, undefined> {
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) {
@@ -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<typeof pedalBoardStyles> {
interface PedalboardProps extends WithStyles<typeof pedalboardStyles> {
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<PedalBoardProps, PedalBoardState>
const PedalboardView =
withStyles(pedalboardStyles, { withTheme: true })(
class extends Component<PedalboardProps, PedalboardState>
{
model: PiPedalModel;
@@ -360,15 +360,15 @@ const PedalBoardView =
scrollRef: React.RefObject<HTMLDivElement>;
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 (
<svg width={layoutSize.width} height={layoutSize.height}
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
<g fill="none">
{
output
outputs
}
</g>
@@ -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
export default PedalboardView
+423 -372
View File
File diff suppressed because it is too large Load Diff
+42 -45
View File
@@ -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<typeof styles> {
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 ((
<FilePropertyControl instanceId={this.props.instanceId} value={propertyValue.value}
<FilePropertyControl instanceId={this.props.instanceId}
fileProperty={fileProperty}
onFileClick={(fileProperty,selectedFile) => {
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((
<div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
<div className={classes.portGroupTitle}>
<Typography variant="caption">{controlGroup.name}</Typography>
<Typography noWrap variant="caption">{controlGroup.name}</Typography>
</div>
<div className={classes.portGroupControls} >
{
@@ -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 (<div className={classes.frame} ></div>);
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 (
<div className={classes.frame}>
<div className={classes.vuMeterL}>
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} />
<VuMeter display="input" instanceId={pedalboardItem.instanceId} />
</div>
<div className={vuMeterRClass}>
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} />
<VuMeter display="output" instanceId={pedalboardItem.instanceId} />
</div>
<div className={gridClass} >
{
@@ -503,9 +488,21 @@ const PluginControlView =
<FilePropertyDialog open={this.state.showFileDialog}
fileProperty={this.state.dialogFileProperty}
selectedFile={this.state.dialogFileValue}
onClose={()=> { 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});}
}
/>
+4 -4
View File
@@ -259,7 +259,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
<img src="img/ic_pluginpreset2.svg" className={classes.itemIcon} alt="" />
</div>
<div className={classes.itemLabel}>
<Typography>
<Typography> noWrap
{presetEntry.label}
</Typography>
</div>
@@ -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 })(
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.dialogTitle}>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
{title}
</Typography>
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} >
+31 -26
View File
@@ -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 })(
}}
>
<div >
{this.props.onboarding ?
{this.props.onboarding &&
(
<div>
<Typography display="block" variant="caption" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
<Typography display="block" variant="body1" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
Select and configure an audio device. You may optionally configure MIDI inputs, and set up a Wi-Fi Direct Hotspot now as well.
</Typography>
<Typography display="block" variant="caption" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
<Typography display="block" variant="body1" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
Access and modify these settings later by selecting the <i>Settings</i> menu item on the main menu.
</Typography>
</div>
):
(
<div>
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
STATUS
</Typography>
{(!isConfigValid) ?
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
<Typography display="block" variant="caption" color="textSecondary">Governor: </Typography>
</div>
) :
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
{JackHostStatus.getDisplayView("", this.state.jackStatus)}
{JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)}
</div>
)
}
<Divider />
</div>
)}
<div>
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
STATUS
</Typography>
{(!isConfigValid) ?
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
{(!this.props.onboarding) && (
<Typography display="block" variant="caption" color="textSecondary">Governor: </Typography>
)}
</div>
) :
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
{JackHostStatus.getDisplayView("", this.state.jackStatus)}
{ (!this.props.onboarding) && JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)}
</div>
)
}
</div>
{this.state.jackConfiguration.errorState !== "" &&
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
@@ -782,7 +787,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Divider/>
<div style={{display: "grid", placeContent: "end", maxWidth: 550, marginLeft: 16,marginRight: 16,
marginTop: 16,marginBottom: 16}}>
<Button variant="outlined" disabled={this.state.continueDisabled} style={{width: 120}}>Continue</Button>
<Button variant="outlined" onClick={()=> {this.onOnboardingContinue()}} disabled={this.state.continueDisabled} style={{width: 120}}>Continue</Button>
</div>
</div>
)
+22 -22
View File
@@ -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<typeof styles> {
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 (<div className={classes.frame}></div>);
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 (
<div className={classes.frame}>
<div className={classes.vuMeterL}>
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} />
<VuMeter display="input" instanceId={pedalboardItem.instanceId} />
</div>
<div className={vuMeterRClass}>
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} />
<VuMeter display="output" instanceId={pedalboardItem.instanceId} />
</div>
<div className={gridClass} >
<div style={{ flex: "0 0 auto" }}>
+8 -8
View File
@@ -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,
+2 -2
View File
@@ -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 ");
+4 -4
View File
@@ -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<typeof styles> {
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 (<ToobCabSimView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobCabSimView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+69 -76
View File
@@ -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<number[]>(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<any>(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 (
<div className={classes.frame} >
<svg width={PLOT_WIDTH} height={PLOT_HEIGHT} viewBox={"0 0 " + PLOT_WIDTH + " " + PLOT_HEIGHT} stroke="#0F8" fill="none" strokeWidth="2.5" opacity="0.6">
{ this.grid() }
{this.grid()}
<path d={this.state.path} ref={this.pathRef} />
</svg>
+4 -4
View File
@@ -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<typeof styles> {
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 (<ToobInputStageView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobInputStageView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+4 -4
View File
@@ -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<typeof styles> {
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 (<ToobMLView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobMLView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+18 -13
View File
@@ -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<typeof styles> {
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 (<ToobPowerstage2View instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobPowerstage2View instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+4 -4
View File
@@ -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<typeof styles> {
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 (<ToobSpectrumAnalyzerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobSpectrumAnalyzerView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+148 -139
View File
@@ -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<typeof styles> {
}
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<string>(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(
(
<line x1={0} y1={y} x2={width} y2={y} key={"db" + db} />
)
);
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(
(
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} strokeWidth={1}/>
)
);
if (minorTick === 1) {
ss.moveTo(x,0);
ss.lineTo(x,height);
} else {
result.push(
(
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} />
)
);
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 (
<div className={classes.frame} style={{position: "relative"}} >
<div style={{position: "absolute", left: 4, top: 4}}>
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} stroke="#888" strokeWidth="0.5" opacity="1"
>
{this.grid()}
<div className={classes.frame} style={{ position: "relative" }} >
{/* <div style={{ position: "absolute", left: 0, top: 0 }}>
<svg fill="#660" width={PLOT_WIDTH } height={PLOT_HEIGHT} preserveAspectRatio='none'
viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" strokeWidth="2.5" opacity="1.0"
>
<path d={this.state.holdPath} />
</svg>
</div> */}
<div style={{ position: "absolute", left: 0, top: 0 }}>
<svg fill="#0C4" width={PLOT_WIDTH } height={PLOT_HEIGHT } preserveAspectRatio='none'
viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" strokeWidth="2.5" opacity="1.0"
>
<path d={this.state.path} />
</svg>
</div>
<div style={{position: "absolute", left: 4, top: 4}}>
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} preserveAspectRatio='none' viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" fill="#0FC" strokeWidth="2.5" opacity="0.6"
>
<path d={this.state.path} />
<div style={{ position: "absolute", left: 0, top: 0 }}>
<svg width={PLOT_WIDTH } height={PLOT_HEIGHT} stroke="#FFF" strokeWidth="0.9" opacity="0.3"
>
<path d={this.minorGrid()} />
</svg>
</div>
<div style={{ position: "absolute", left: 0, top: 0 }}>
<svg width={PLOT_WIDTH} height={PLOT_HEIGHT} stroke="#FFF" strokeWidth="0.9" opacity="0.5"
>
<path d={this.majorGrid()} />
</svg>
</div>
<div className={classes.frameShadow} style={{ position: "absolute", left: 0, top: 0, right: 0, bottom: 0 }}>
</div>
</div>);
}
+4 -4
View File
@@ -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<typeof styles> {
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 (<ToobToneStackView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobToneStackView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
}
+24 -18
View File
@@ -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<number[]>(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<any>(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.
+2 -2
View File
@@ -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;
}
+5 -5
View File
@@ -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);
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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");
+2
View File
@@ -23,6 +23,7 @@
*/
#include "pch.h"
#include "util.hpp"
#include <bit>
#include <memory>
#include "ss.hpp"
@@ -1367,6 +1368,7 @@ namespace pipedal
}
void AudioThread()
{
SetThreadName("alsaDriver");
try
{
#if defined(__WIN32)
+3 -3
View File
@@ -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;
}
+54
View File
@@ -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 <vector>
#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<uint8_t> data;
};
}
+548
View File
@@ -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 <cstddef>
#include <cstring>
#include <sstream>
#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<LV2_Atom*>(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(&current->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
}
+239
View File
@@ -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 <cstddef>
#include <vector>
#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": <atom_value>. 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<std::string, LV2_URID> stringToTypeUrid;
std::map<LV2_URID,std::string> 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 <typename U>
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 &map;
std::vector<uint8_t> prototypeBuffer;
std::vector<uint8_t> outputBuffer;
LV2_Atom_Forge outputForge;
LV2_Atom * prototype = nullptr;
};
}
+113
View File
@@ -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 <sstream>
#include <cstdint>
#include <string>
#include <iostream>
#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 <cstring>
using namespace pipedal;
using namespace std;
MapFeature mapFeature;
using AtomBuffer = std::vector<uint8_t>;
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());
}
+5
View File
@@ -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
+175 -234
View File
@@ -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<Lv2PedalBoard> currentPedalBoard;
std::vector<std::shared_ptr<Lv2PedalBoard>> activePedalBoards; // pedalboards that have been sent to the audio queue.
Lv2PedalBoard *realtimeActivePedalBoard = nullptr;
std::shared_ptr<Lv2Pedalboard> currentPedalboard;
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
std::vector<uint8_t *> 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<uint8_t> atomBuffer;
std::vector<uint8_t> 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<clock_duration>(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<Lv2PedalBoard> currentpedalBoard;
std::shared_ptr<Lv2Pedalboard> 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<Lv2PedalBoard> &pedalBoard)
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &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<ControlValue> &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<int64_t> &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<MidiBinding>&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<Lv2PluginState*>(&pedalboardItem.lv2State()));
}
}
JSON_MAP_BEGIN(JackHostStatus)
JSON_MAP_REFERENCE(JackHostStatus, active)
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
+67 -15
View File
@@ -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 <functional>
#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<void (RealtimeParameterRequest*)> onJackRequestComplete;
enum class RequestType {
PatchGet,
PatchSet
};
RequestType requestType;
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestComplete;
std::function<void(const std::string &jsonResjult)> onSuccess;
std::function<void(const std::string &error)> 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<uint8_t> longAtomBuffer;
public:
RealtimeParameterRequest(
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete_,
RealtimePatchPropertyRequest(
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
int64_t clientId_,
int64_t instanceId_,
LV2_URID uridUri_,
std::function<void(const std::string &jsonResjult)> onSuccess_,
std::function<void(const std::string &error)> onError_)
: onJackRequestComplete(onJackRequestComplete_),
std::function<void(const std::string &error)> onError_
)
: onPatchRequestComplete(onPatchRequestcomplete_),
clientId(clientId_),
instanceId(instanceId_),
uridUri(uridUri_),
onSuccess(onSuccess_),
onError(onError_)
{
requestType = RequestType::PatchGet;
}
RealtimePatchPropertyRequest(
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
int64_t clientId_,
int64_t instanceId_,
LV2_URID uridUri_,
LV2_Atom*atomValue,
std::function<void(const std::string &jsonResjult)> onSuccess_,
std::function<void(const std::string &error)> 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<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
virtual 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<Lv2PedalBoard> &pedalBoard) = 0;
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> & values) = 0;
@@ -164,12 +217,11 @@ public:
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&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;
};
+1 -1
View File
@@ -23,7 +23,7 @@
*/
#include "AutoLilvNode.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
+30 -17
View File
@@ -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<LilvNode*>(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;
};
};
+5 -5
View File
@@ -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_)
+144
View File
@@ -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 <string>
#include <cstddef>
#include <vector>
#include <stdexcept>
namespace macaron
{
class Base64
{
public:
static std::string Encode(const std::vector<uint8_t> &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<char *>(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<uint8_t> 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<uint8_t> out(out_len);
for (size_t i = 0, j = 0; i < in_len;)
{
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(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_ */
+56 -7
View File
@@ -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
+1 -1
View File
@@ -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)
+5 -5
View File
@@ -20,10 +20,13 @@
#pragma once
#include <lv2/urid.lv2/urid.h>
#include <lv2/atom.lv2/atom.h>
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
+53
View File
@@ -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 <lilv/lilv.h>
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<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread() = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
};
}
+1
View File
@@ -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)
+10 -6
View File
@@ -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<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
+6 -2
View File
@@ -108,8 +108,11 @@ static std::int32_t GetJackArg(const std::vector<std::string> &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)
+18 -11
View File
@@ -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_;
+111 -57
View File
@@ -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<Lv2PluginInfo> &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<Worker>(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<Worker>(pInstance, worker_interface);
this->stateInterface = std::make_unique<StateInterface>(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;
}
+65 -59
View File
@@ -20,8 +20,8 @@
#pragma once
#include "PiPedalHost.hpp"
#include "PedalBoard.hpp"
#include "PluginHost.hpp"
#include "Pedalboard.hpp"
#include <lilv/lilv.h>
#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> stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
std::map<std::string,AtomBuffer> 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<const Lv2PortInfo *> 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<Lv2PluginInfo> &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)
{
+1 -1
View File
@@ -19,7 +19,7 @@
#include "pch.h"
#include "Lv2EventBufferWriter.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
using namespace pipedal;
+3 -3
View File
@@ -24,17 +24,17 @@
#include <string>
#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");
}
+85 -57
View File
@@ -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<float>(pHost->GetMaxAudioBufferSize());
}
std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
std::vector<float *> Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
{
std::vector<float *> result;
for (int i = 0; i < nChannels; ++i)
@@ -45,7 +46,7 @@ std::vector<float *> 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<float *> Lv2PedalBoard::PrepareItems(
std::vector<PedalBoardItem> &items,
std::vector<float *> Lv2Pedalboard::PrepareItems(
std::vector<PedalboardItem> &items,
std::vector<float *> inputBuffers)
{
for (int i = 0; i < items.size(); ++i)
@@ -105,7 +106,14 @@ std::vector<float *> 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<float *> 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<float>(pHost->GetMaxAudioBufferSize()));
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(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<Lv2Effect*>(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<Lv2Effect*>(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)
+17 -17
View File
@@ -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 <functional>
@@ -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<float*> pedalBoardInputBuffers;
std::vector<float*> pedalBoardOutputBuffers;
std::vector<float*> pedalboardInputBuffers;
std::vector<float*> pedalboardOutputBuffers;
std::vector<std::shared_ptr<IEffect> > effects;
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
@@ -81,21 +81,21 @@ class Lv2PedalBoard {
std::vector<float*> PrepareItems(
std::vector<PedalBoardItem> & items,
std::vector<PedalboardItem> & items,
std::vector<float*> inputBuffers
);
void PrepareMidiMap(const PedalBoard&pedalBoard);
void PrepareMidiMap(const PedalBoardItem&pedalBoardItem);
void PrepareMidiMap(const Pedalboard&pedalboard);
void PrepareMidiMap(const PedalboardItem&pedalboardItem);
std::vector<float*> AllocateAudioBuffers(int nChannels);
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalBoardItem> &items);
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &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<IEffect* > 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<float*> &GetInputBuffers() { return this->pedalBoardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalBoardOutputBuffers;}
std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalboardOutputBuffers;}
View File
+92
View File
@@ -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 <string.h>
#include <algorithm>
#include "json.hpp"
#include <sstream>
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);
}
+54
View File
@@ -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 <filesystem>
#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;
};
}
+56 -52
View File
@@ -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<PedalBoardItem>&items,long pedalBoardItemId)
static const PedalboardItem* GetItem_(const std::vector<PedalboardItem>&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<PedalboardItem*> & result, std::vector<PedalboardItem>&items)
{
for (auto& item: items)
{
if (item.isSplit())
{
GetAllItems(result,item.topChain());
GetAllItems(result,item.bottomChain());
}
result.push_back(&item);
}
}
std::vector<PedalboardItem*> Pedalboard::GetAllPlugins()
{
std::vector<PedalboardItem*> 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<PedalBoardItem*>(GetItem_(this->items(),pedalItemId));
return const_cast<PedalboardItem*>(GetItem_(this->items(),pedalItemId));
}
PropertyValue*PedalBoardItem::GetPropertyValue(const std::string&propertyUri)
{
for (auto&propertyValue: this->propertyValues_)
{
if (propertyValue.propertyUri() == propertyUri)
{
return &propertyValue;
}
}
return nullptr;
}
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
{
for (size_t i = 0; i < this->controlValues().size(); ++i)
{
@@ -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<PedalBoardItem>&value)
bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector<PedalboardItem>&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()
+19 -39
View File
@@ -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 <typename T>
PropertyValue(const std::string&propertyUri, T value)
:propertyUri_(propertyUri)
, value_(value)
{
}
GETTER_SETTER_REF(propertyUri)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(PropertyValue);
};
class PedalBoardItem: public JsonMemberWritable {
class PedalboardItem: public JsonMemberWritable {
int64_t instanceId_ = 0;
std::string uri_;
std::string pluginName_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
std::vector<PropertyValue> propertyValues_;
std::vector<PedalBoardItem> topChain_;
std::vector<PedalBoardItem> bottomChain_;
std::vector<PedalboardItem> topChain_;
std::vector<PedalboardItem> bottomChain_;
std::vector<MidiBinding> 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<PedalBoardItem> items_;
std::vector<PedalboardItem> 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::vector<PedalboardItem*>GetAllPlugins();
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
+1 -1
View File
@@ -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()];
+329 -198
View File
File diff suppressed because it is too large Load Diff
+57 -30
View File
@@ -19,9 +19,9 @@
#pragma once
#include <mutex>
#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 <thread>
#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<ControlValue> &controlValues) = 0;
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &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<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
virtual void Close() = 0;
};
class PiPedalModel : private IAudioHostCallbacks
{
private:
std::unique_ptr<std::jthread> pingThread;
std::vector<MidiBinding> 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<AtomOutputListener> 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> audioHost;
JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard;
std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber *> 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<RealtimeParameterRequest *> outstandingParameterRequests;
std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
private: // IAudioHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &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<VuUpdate> &updates) override;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override;
virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) override;
;
virtual void 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<std::recursive_mutex> 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<void(const std::string &jsonResjult)> onSuccess,
std::function<void(const std::string &error)> onError);
void SendSetPatchProperty(
int64_t clientId,
int64_t instanceId,
const std::string uri,
const json_variant&value,
std::function<void()> onSuccess,
std::function<void(const std::string &error)> 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<AlsaDeviceInfo> GetAlsaDevices();
const std::filesystem::path &GetWebRoot() const;
@@ -282,7 +309,7 @@ namespace pipedal
std::map<std::string, bool> GetFavorites() const;
void SetFavorites(const std::map<std::string, bool> &favorites);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
};
} // namespace pipedal.
+170 -84
View File
@@ -30,6 +30,7 @@
#include <future>
#include <atomic>
#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<std::string> 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<char> 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<MidiBinding>&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<PendingNotifyAtomOutput> pendingNotifyAtomOutputs;
void OnAckNotifyAtomOutput()
void OnAckNotifyPatchProperty()
{
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<bool>(
"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;
+104 -40
View File
@@ -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<PiPedalFileProperty>(pHost, fileNode);
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(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<UiPortNotification>(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> PiPedalFileType::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri)
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
{
std::vector<PiPedalFileType> 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> 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 <http://two-play.com/plugins/toob-convolution-reverb>;
// 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()
+46 -14
View File
@@ -28,6 +28,7 @@
#include <memory>
#include <lilv/lilv.h>
#include "json.hpp"
#include <filesystem>
#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<PiPedalFileType> GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri);
static std::vector<PiPedalFileType> 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() { }
UiPortNotification(PluginHost*pHost, const LilvNode*node);
public:
DECLARE_JSON_MAP(UiPortNotification);
};
class UiFileProperty {
private:
std::string name_;
std::string directory_;
std::vector<PiPedalFileType> fileTypes_;
std::string patchProperty_;
std::string defaultFile_;
public:
using ptr = std::shared_ptr<PiPedalFileProperty>;
PiPedalFileProperty() { }
PiPedalFileProperty(PiPedalHost*pHost, const LilvNode*node);
using ptr = std::shared_ptr<UiFileProperty>;
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<PiPedalFileType> &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>;
PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode);
const std::vector<PiPedalFileProperty::ptr>& fileProperties() const
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
const std::vector<UiFileProperty::ptr>& fileProperties() const
{
return fileProperites_;
}
const std::vector<UiPortNotification::ptr> &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<PiPedalFileProperty::ptr> fileProperites_;
std::vector<UiFileProperty::ptr> fileProperites_;
std::vector<UiPortNotification::ptr> portNotifications_;
};
// Utiltities for validating file paths received via PiPedalFileProperty-related APIs.
+270 -202
View File
@@ -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 <lilv/lilv.h>
#include <stdexcept>
#include <locale>
#include <string_view>
#include "Lv2Log.hpp"
#include <functional>
#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 <http://two-play.com/plugins/toob-convolution-reverb>;
// 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<LV2_Feature *>(mapFeature.GetMapFeature());
features[1] = const_cast<LV2_Feature *>(mapFeature.GetUnmapFeature());
lv2Features.push_back(mapFeature.GetMapFeature());
lv2Features.push_back(mapFeature.GetUnmapFeature());
logFeature.Prepare(&mapFeature);
features[2] = const_cast<LV2_Feature *>(logFeature.GetFeature());
lv2Features.push_back(logFeature.GetFeature());
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
features[3] = const_cast<LV2_Feature *>(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<HostWorkerThread>();
}
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<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &uri) const
std::shared_ptr<Lv2PluginClass> 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<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &u
return (*it).second;
}
void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
void PluginHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> 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<Lv2PluginClass> 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<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClass *pClass)
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const LilvPluginClass *pClass)
{
MAP_CHECK();
@@ -300,7 +342,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClas
std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri];
return pResult;
}
std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginClass *pClass)
std::shared_ptr<Lv2PluginClass> 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<Lv2PluginClass> 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<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
if (parentClass)
{
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
ccClass->set_parent(parentClass);
Lv2PluginClass *ccParentClass = parentClass.get();
ccParentClass->add_child(lv2Class);
}
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
if (parentClass)
{
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(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<std::string> 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<Lv2PortInfo> &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<Lv2PortInfo> portInfo = std::make_shared<Lv2PortInfo>(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<PiPedalUI>(lv2Host, pipedalUINode);
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
}
// xxx lilv_world_get(pWorld,pluginUri,);
// for (auto&portInfo: ports_)
@@ -666,7 +721,12 @@ std::vector<std::string> 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<std::string> 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<Lv2PluginClass> PiPedalHost::GetLv2PluginClass() const
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
{
return this->GetPluginClass(LV2_PLUGIN);
}
std::shared_ptr<Lv2PluginInfo> PiPedalHost::GetPluginInfo(const std::string &uri) const
std::shared_ptr<Lv2PluginInfo> 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<ControlValue> *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<ControlValue> *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<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
PedalBoardItem *pedalBoardItem, const std::string &presetUri)
std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
PedalboardItem *pedalboardItem, const std::string &presetUri)
{
std::vector<ControlValue> 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<ControlValue> 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<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
struct PresetCallbackState
{
PiPedalHost *pHost;
PluginHost *pHost;
std::map<std::string, float> *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<PresetCallbackState *>(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<std::string, float> 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<std::string, float> 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<HostWorkerThread> 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> Lv2PortGroup::jmap{{
}};
json_map::storage_type<Lv2PluginInfo> 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> Lv2PluginUiControlPort::jmap{{
}};
json_map::storage_type<Lv2PluginUiInfo> 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>
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_),
}};
+57 -57
View File
@@ -23,7 +23,7 @@
#include <memory>
#include "json.hpp"
#include "PluginType.hpp"
#include "PedalBoard.hpp"
#include "Pedalboard.hpp"
#include <lilv/lilv.h>
#include "MapFeature.hpp"
#include "LogFeature.hpp"
@@ -31,6 +31,7 @@
#include <filesystem>
#include <cmath>
#include <string>
#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<std::shared_ptr<Lv2PluginClass>> 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<Lv2PluginClasses> 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<Lv2PortInfo> 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<Lv2PortGroup> 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<Lv2PluginUiControlPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<PiPedalFileProperty::ptr> fileProperties_;
std::vector<UiFileProperty::ptr> fileProperties_;
std::vector<UiPortNotification::ptr> 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<Lv2PluginUiInfo> 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<Lv2PluginInfo> 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<const LV2_Feature *> 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<HostWorkerThread> 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<HostWorkerThread> 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<Lv2PluginClass> 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<ControlValue> LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem,
std::vector<ControlValue> LoadFactoryPluginPreset(PedalboardItem *pedalboardItem,
const std::string &presetUri);
};
+1
View File
@@ -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)
+3
View File
@@ -23,6 +23,8 @@
#include "PiPedalException.hpp"
#include <utility>
#include <map>
#include <memory>
#include "StateInterface.hpp"
namespace pipedal {
@@ -87,6 +89,7 @@ public:
uint64_t instanceId_;
std::string label_;
std::map<std::string,float> controlValues_;
Lv2PluginState state_;
DECLARE_JSON_MAP(PluginPreset);
};
+8 -8
View File
@@ -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()
+6 -6
View File
@@ -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<std::unique_ptr<PedalPreset> > values_;
public:
DECLARE_JSON_MAP(PedalBoardPreset);
DECLARE_JSON_MAP(PedalboardPreset);
};
class PedalBoardPresets {
class PedalboardPresets {
long nextInstanceId_ = 0;
long currentPreset_ = 0;
std::vector<std::unique_ptr<PedalBoardPreset> > presets_;
std::vector<std::unique_ptr<PedalboardPreset> > presets_;
public:
DECLARE_JSON_MAP(PedalBoardPresets);
DECLARE_JSON_MAP(PedalboardPresets);
};
+493
View File
@@ -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 <functional>
#include <mutex>
#include <stdexcept>
#include <condition_variable>
namespace pipedal
{
// thread-safe javascript-like future.
template <typename T>
class Promise;
template <typename T>
class PromiseInner;
template <typename T>
using ResolveFunction = std::function<void(const T &value)>;
using RejectFunction = std::function<void(const std::string &message)>;
template <typename T>
using WorkerFunction = std::function<void(ResolveFunction<T>, RejectFunction reject)>;
using CatchFunction = std::function<void(const std::string &message)>;
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 <typename T>
class PromiseInner : PromiseInnerBase
{
private:
friend class Promise<T>;
using ResolveFunction = pipedal::ResolveFunction<T>;
using WorkerFunction = pipedal::WorkerFunction<T>;
using ThenFunction = std::function<void(const T &value)>;
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 <typename T>
class Promise
{
public:
using ResolveFunction = PromiseInner<T>::ResolveFunction;
using RejectFunction = pipedal::RejectFunction;
using WorkerFunction = PromiseInner<T>::WorkerFunction;
using ThenFunction = PromiseInner<T>::ThenFunction;
using CatchFunction = pipedal::CatchFunction;
Promise() { p = new PromiseInner<T>(); }
Promise(const Promise<T> &other)
{
p = other.p;
p->AddRef();
}
Promise(Promise<T> &&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<T> Then(std::function<void(const T &v)> &&thenFn)
{
p->Then(std::move(thenFn));
return Promise<T>(*this);
}
Promise<T> Then(const std::function<void(const T &v)> &thenFn)
{
std::function<void(const T &v)> t(thenFn);
p->Then(std::move(t));
return Promise<T>(*this);
}
template <typename U>
Promise<U> Then(std::function<void(const T &value, pipedal::ResolveFunction<U> result, RejectFunction reject)> thenFn)
{
Promise<U> t{};
t.SetInputPromise(this->p);
this->Then([t, thenFn](const T &value) mutable
{ t.Work([thenFn, value](pipedal::ResolveFunction<U> resolveU, RejectFunction rejectU) mutable
{ thenFn(value, resolveU, rejectU); }); })
.Catch([t](const std::string &message) mutable
{ t.Reject(message); });
return t;
}
Promise<T> Catch(const CatchFunction &catchFn)
{
p->Catch(catchFn);
return Promise<T>(*this);
}
Promise<T> &operator=(Promise<T> &&other)
{
std::swap(this->p, other.p);
return *this;
}
Promise<T> &operator=(const Promise<T> &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<void(ResolveFunction resolve, RejectFunction reject)> workFunction)
{
p->Work(workFunction);
}
void Reject(const std::string &message)
{
p->Reject(message);
}
void SetInputPromise(PromiseInnerBase *inputPromise)
{
p->SetInputPromise(inputPromise);
}
private:
PromiseInner<T> *p = nullptr;
};
}
+261
View File
@@ -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 <future>
#include <thread>
#include <iostream>
using namespace pipedal;
TEST_CASE("Promise", "[promise][Build][Dev]")
{
{
// synchronous then->get
Promise<double> p1 = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
});
Promise<int> p2 = p1
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
{ resolve((int)(value + 3)); });
int result = p2.Get();
REQUIRE(result == 4.0);
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
////////////////////////
{
// synchronous resolve.
double result = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Get();
REQUIRE(result == 1.0);
}
{
// async resolve (Get)
double result = Promise<double>(
[](ResolveFunction<double> 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<double>(
[](ResolveFunction<double> 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<int32_t>)
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
auto _ = std::async(
std::launch::async,
[resolve]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
resolve(3.0);
});
})
.Then<int32_t>([](double value, ResolveFunction<int32_t> 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<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
{ resolve((int)(value + 3)); })
.Get();
REQUIRE(result == 4.0);
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous reject
Promise<double> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
reject("Rejected");
});
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// async reject (Get)
Promise<double> promise = Promise<double>(
[](ResolveFunction<double> 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<int>/reject->get
Promise<int> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
resolve(1.0);
})
.Then<int>(
[](double value, ResolveFunction<int> resolve, RejectFunction reject)
{
reject("reject");
});
REQUIRE_THROWS(promise.Get());
}
REQUIRE(PromiseInnerBase::allocationCount == 0);
{
// synchronous then reject->then<int>->get
Promise<int> promise = Promise<double>(
[](ResolveFunction<double> resolve, RejectFunction reject)
{
reject("reject");
})
.Then<int>([](double value, ResolveFunction<int> 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<double>(
[](ResolveFunction<double> 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);
}
+253 -99
View File
@@ -22,28 +22,35 @@
#include "PiPedalException.hpp"
#include <atomic>
#include <mutex>
#include <semaphore.h>
#include <condition_variable>
#ifndef NO_MLOCK
#include <sys/mman.h>
#endif /* NO_MLOCK */
namespace pipedal
{
enum class RingBufferStatus
{
Ready,
TimedOut,
Closed
};
template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false>
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 <class Rep, class Period>
RingBufferStatus readWait_for(const std::chrono::duration<Rep, Period> &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 <class Clock, class Duration>
RingBufferStatus readWait_until(const std::chrono::time_point<Clock, Duration> &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 <class Clock, class Duration>
RingBufferStatus readWait_until(size_t size,const std::chrono::time_point<Clock, Duration> &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;
}
};
};
};
+35 -14
View File
@@ -23,6 +23,8 @@
#include "Lv2Log.hpp"
#include "VuUpdate.hpp"
#include "AudioHost.hpp"
#include "lv2/atom.lv2/atom.h"
#include <chrono>
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 <bool MULTI_WRITE, bool SEMAPHORE_READ>
@@ -182,8 +186,20 @@ namespace pipedal
}
// 0 -> ready. -1: timed out. -2: closing.
int wait(struct timespec &timeout) {
return ringBuffer->readWait(timeout);
template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) {
return ringBuffer->readWait_for(timeout);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(std::chrono::time_point<Clock,Duration>&time_point)
{
return ringBuffer->readWait_until(time_point);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(size_t size,std::chrono::time_point<Clock,Duration>&time_point)
{
return ringBuffer->readWait_until(size,time_point);
}
bool wait() {
@@ -196,9 +212,6 @@ namespace pipedal
template <typename T>
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);
}
};
+2 -2
View File
@@ -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;
+3 -3
View File
@@ -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)
+289
View File
@@ -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 <sstream>
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();
}
+123
View File
@@ -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 <cstddef>
#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<uint8_t> value_;
private:
virtual void write_json(json_writer &writer) const;
virtual void read_json(json_reader &reader);
};
class Lv2PluginState: public JsonSerializable
{
public:
std::map<std::string,Lv2PluginStateEntry> 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 &map;
LilvInstance *pInstance;
const LV2_State_Interface *pluginStateInterface;
std::vector<LV2_Feature *> features;
};
}
+95 -60
View File
@@ -28,6 +28,8 @@
#include <map>
#include <sys/stat.h>
#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<std::string, bool> &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<MidiBinding>&bindings)
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
{
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::ofstream f;
@@ -1382,63 +1395,85 @@ std::vector<MidiBinding> 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<std::string> 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<std::string> Storage::GetFileList(const UiFileProperty &fileProperty)
{
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
{
ThrowPermissionDeniedError();
}
std::vector<std::string> 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)
+15 -10
View File
@@ -20,7 +20,7 @@
#pragma once
#include <filesystem>
#include <iostream>
#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<std::string> GetPedalBoards();
std::filesystem::path GetPluginStorageDirectory() const;
std::vector<std::string> 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<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
void SetJackChannelSelection(const JackChannelSelection&channelSelection);
@@ -156,6 +159,8 @@ public:
void SaveCurrentPreset(const CurrentPreset &currentPreset);
bool RestoreCurrentPreset(CurrentPreset*pResult);
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
private:
bool pluginPresetIndexChanged = false;
void LoadPluginPresetIndex();
+2 -2
View File
@@ -24,7 +24,7 @@
#include "ss.hpp"
#include <assert.h>
#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;
+11 -11
View File
@@ -23,7 +23,7 @@
*/
#include "ss.hpp"
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp"
#include <unordered_map>
@@ -86,7 +86,7 @@ namespace pipedal
const Vst3Host::PluginList &getPluginList() override { return pluginList; }
std::unique_ptr<Vst3Effect> CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override;
std::unique_ptr<Vst3Effect> CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost) override;
std::unique_ptr<Vst3Effect> CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost) override;
private:
Lv2PluginUiInfo *GetPluginInfo(const std::string &uri);
@@ -615,22 +615,22 @@ static std::vector<uint8_t> HexToByteArray(const std::string &hexState)
}
return result;
}
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost)
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost)
{
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalBoardItem.instanceId(), pedalBoardItem.uri(), pHost);
auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri());
std::unique_ptr<Vst3Effect> 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<uint8_t> state = HexToByteArray(pedalBoardItem.vstState());
std::vector<uint8_t> 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<Vst3Effect> 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<Vst3Effect> 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() ));
}
}
+2 -2
View File
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
#include "PiPedalHost.hpp"
#include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp"
#include <iostream>
@@ -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);
+4 -1
View File
@@ -17,6 +17,7 @@
#include <set>
#include <strings.h>
#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();
});

Some files were not shown because too many files have changed in this diff Show More