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}",
"${workspaceFolder}/src", "${workspaceFolder}/src",
"/usr/include/lilv-0", "/usr/include/lilv-0",
"/usr/lib",
"~/src/vst3sdk" "~/src/vst3sdk"
], ],
"compilerPath": "/usr/bin/gcc", "compilerPath": "/usr/bin/gcc",
+41 -12
View File
@@ -6,6 +6,7 @@
"configurations": [ "configurations": [
{ {
"name": "(gdb) generic", "name": "(gdb) generic",
"type": "cppdbg", "type": "cppdbg",
@@ -21,11 +22,6 @@
// it gets resolved by CMake Tools: // it gets resolved by CMake Tools:
"name": "PATH", "name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}" "value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
} }
], ],
"externalConsole": false, "externalConsole": false,
@@ -54,11 +50,6 @@
// it gets resolved by CMake Tools: // it gets resolved by CMake Tools:
"name": "PATH", "name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}" "value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "ASAN_OPTIONS",
"value": "detect_leaks=0"
} }
], ],
"externalConsole": false, "externalConsole": false,
@@ -80,8 +71,8 @@
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": [ "args": [
//"[Dev]" //"[Dev]" -- all dev-machine tests.
"[json_variants]" "[json_variants]" // subtest of your choice, or none to run all of the tests.
], ],
"stopAtEntry": false, "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", "name": "(gdb) pipedal_latency_test Launch",
+2 -2
View File
@@ -60,9 +60,9 @@ add_custom_command(
src/PiPedalError.tsx src/PiPedalError.tsx
src/IControlViewFactory.tsx src/IControlViewFactory.tsx
src/Lv2Plugin.tsx src/Lv2Plugin.tsx
src/PedalBoard.tsx src/Pedalboard.tsx
src/AndroidHost.tsx src/AndroidHost.tsx
src/PedalBoardView.tsx src/PedalboardView.tsx
src/PresetDialog.tsx src/PresetDialog.tsx
src/AppThemed.tsx src/AppThemed.tsx
src/ZoomedDial.tsx src/ZoomedDial.tsx
+10 -7
View File
@@ -162,15 +162,18 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
} }
componentDidMount() { componentDidMount() {
super.componentDidMount?.();
this.mounted = true; this.mounted = true;
this.updateNotifications(); this.updateNotifications();
this.startFossRequest(); this.startFossRequest();
} }
componentWillUnmount() { componentWillUnmount() {
super.componentWillUnmount?.();
this.mounted = false; this.mounted = false;
this.updateNotifications(); this.updateNotifications();
} }
componentDidUpdate() { componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps,prevState,snapshot);
this.updateNotifications(); this.updateNotifications();
} }
@@ -196,7 +199,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
> >
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
About About
</Typography> </Typography>
</Toolbar> </Toolbar>
@@ -208,28 +211,28 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
}} }}
> >
<div style={{ margin: 24 }}> <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" }}> PiPedal <span style={{ fontSize: "0.7em" }}>
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "") {(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
+ (this.model.serverVersion?.debug ? " (Debug)" : "")} + (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span> </span>
</Typography> </Typography>
<Typography display="block" variant="body2" style={{ marginBottom: 12 }} > <Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022 Robin Davies. Copyright &#169; 2022 Robin Davies.
</Typography> </Typography>
{this.model.isAndroidHosted() && ( {this.model.isAndroidHosted() && (
<Typography display="block" variant="body2" style={{ marginBottom: 0 }} > <Typography noWrap display="block" variant="body2" style={{ marginBottom: 0 }} >
{this.model.getAndroidHostVersion()} {this.model.getAndroidHostVersion()}
</Typography> </Typography>
)} )}
{this.model.isAndroidHosted() && ( {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. Copyright &#169; 2022 Robin Davies.
</Typography> </Typography>
)} )}
<Divider /> <Divider />
<Typography display="block" variant="caption" > <Typography noWrap display="block" variant="caption" >
ADDRESSES ADDRESSES
</Typography> </Typography>
<div style={{marginBottom: 16}}> <div style={{marginBottom: 16}}>
+7 -12
View File
@@ -398,7 +398,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
} }
handDisplayOnboarding() { handleDisplayOnboarding() {
this.setState({ this.setState({
isDrawerOpen: false, isDrawerOpen: false,
isSettingsDialogOpen: true, isSettingsDialogOpen: true,
@@ -502,7 +502,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_); this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_);
this.model_.state.addOnChangedHandler(this.stateChangeHandler_); 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_.alertMessage.addOnChangedHandler(this.alertMessageChangedHandler);
this.model_.banks.addOnChangedHandler(this.banksChangedHandler); this.model_.banks.addOnChangedHandler(this.banksChangedHandler);
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler); this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
@@ -529,7 +529,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentWillUnmount(); super.componentWillUnmount();
this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_); this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_);
this.model_.state.removeOnChangedHandler(this.stateChangeHandler_); 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.banksChangedHandler);
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler); this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
@@ -575,7 +575,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.setState({ errorMessage: message }); this.setState({ errorMessage: message });
} }
onboardingShown: boolean = false;
setDisplayState(newState: State): void { setDisplayState(newState: State): void {
this.updateOverscroll(); this.updateOverscroll();
@@ -586,13 +585,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
}); });
if (newState === State.Ready) if (newState === State.Ready)
{ {
if (!this.onboardingShown) if (this.model_.isOnboarding())
{ {
this.onboardingShown = true; this.handleDisplayOnboarding();
if (!this.model_.hasConfiguration())
{
this.handDisplayOnboarding();
}
} }
} }
} }
@@ -867,7 +862,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}> <div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} /> <CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div> </div>
<Typography variant="body2" className={classes.progressText}> <Typography noWrap variant="body2" className={classes.progressText}>
{this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."} {this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."}
</Typography> </Typography>
</div> </div>
@@ -905,7 +900,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}> <div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} /> <CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div> </div>
<Typography variant="body2" className={classes.loadingBoxItem}> <Typography noWrap variant="body2" className={classes.loadingBoxItem}>
Loading... Loading...
</Typography> </Typography>
</div> </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="" /> <img src="img/ic_bank.svg" className={classes.itemIcon} alt="" />
</div> </div>
<div className={classes.itemLabel}> <div className={classes.itemLabel}>
<Typography> <Typography noWrap>
{bankEntry.name} {bankEntry.name}
</Typography> </Typography>
</div> </div>
@@ -410,7 +410,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
> >
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
Banks Banks
</Typography> </Typography>
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} > <IconButton color="inherit" onClick={(e) => this.showActionBar(true)} >
@@ -433,7 +433,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
</IconButton> </IconButton>
)} )}
<Typography variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
Banks Banks
</Typography> </Typography>
{(this.state.banks.getEntry(this.state.selectedItem) != null) {(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 { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import {PedalBoardItem, PedalBoardSplitItem} from './PedalBoard'; import {PedalboardItem, PedalboardSplitItem} from './Pedalboard';
import PluginControlView from './PluginControlView'; import PluginControlView from './PluginControlView';
import SplitControlView from './SplitControlView'; import SplitControlView from './SplitControlView';
import Typography from '@mui/material/Typography'; 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(); let model: PiPedalModel = PiPedalModelFactory.getInstance();
if (!pedalBoardItem) { if (!pedalboardItem) {
return (<div/>); return (<div/>);
} }
if (pedalBoardItem.isSplit()) if (pedalboardItem.isSplit())
{ {
return ( return (
<SplitControlView item={pedalBoardItem as PedalBoardSplitItem} instanceId={pedalBoardItem!.instanceId} <SplitControlView item={pedalboardItem as PedalboardSplitItem} instanceId={pedalboardItem!.instanceId}
/> />
); );
} else { } else {
for (let i = 0; i < pluginFactories.length; ++i) for (let i = 0; i < pluginFactories.length; ++i)
{ {
let factory = pluginFactories[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) if (!uiPlugin)
{ {
<div style={{paddingLeft: 40, paddingRight: 40}}> <div style={{paddingLeft: 40, paddingRight: 40}}>
<Typography color="error" variant="h6" >Missing plugin.</Typography> <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> </div>
} else { } else {
return ( return (
<PluginControlView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} /> <PluginControlView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />
) )
} }
} }
+22 -40
View File
@@ -47,12 +47,9 @@ function peekDialogStack(): string {
return ""; return "";
} }
let ignoreNextPop = false; function popDialogStack(): void {
function popDialogStack(ignoreNextPop_: boolean): void {
if (dialogStack.length !== 0) if (dialogStack.length !== 0)
{ {
ignoreNextPop = ignoreNextPop_;
dialogStack.splice(dialogStack.length-1,1); dialogStack.splice(dialogStack.length-1,1);
} }
} }
@@ -76,27 +73,23 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
mounted: boolean = false; mounted: boolean = false;
hasHooks: boolean = false; hasHooks: boolean = false;
stateWasPopped: boolean = false; stateWasPopped: boolean = false;
handlePopState(e: any): any {
handlePopState(e: any): any
{
let shouldClose = (peekDialogStack() === this.props.tag); let shouldClose = (peekDialogStack() === this.props.tag);
if (shouldClose) if (shouldClose)
{ {
if (ignoreNextPop)
{
ignoreNextPop = false;
} else {
if (!this.stateWasPopped)
{
this.stateWasPopped = true;
if (this.props.onClose) if (!this.stateWasPopped)
{ {
popDialogStack(false); this.stateWasPopped = true;
this.props.onClose(e,"backdropClick"); popDialogStack();
} if (this.props.open) {
this.props.onClose?.(e,"backdropClick");
} }
} }
window.removeEventListener("popstate",this.handlePopState);
e.stopPropagation(); e.stopPropagation();
} }
} }
@@ -111,49 +104,35 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
{ {
this.stateWasPopped = false; this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState); 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); pushDialogStack(this.props.tag);
let state: {tag: string} = {tag: this.props.tag};
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
history.pushState( history.pushState(
state, state,
"", "",
"" //"#" + this.props.tag "#" + this.props.tag
); );
} else { } else {
if (!this.stateWasPopped) if (!this.stateWasPopped)
{ {
this.stateWasPopped = true;
popDialogStack(true);
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
history.back(); history.back();
} }
window.removeEventListener("popstate",this.handlePopState);
} }
} }
} }
componentDidMount() componentDidMount()
{ {
if (super.componentDidMount) super.componentDidMount?.();
{
super.componentDidMount();
}
this.mounted = true; this.mounted = true;
this.updateHooks(); this.updateHooks();
} }
componentWillUnmount() componentWillUnmount()
{ {
if (super.componentWillUnmount) super.componentWillUnmount?.();
{
super.componentWillUnmount();
}
this.mounted = false; this.mounted = false;
this.updateHooks(); this.updateHooks();
} }
@@ -162,12 +141,15 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
{ {
this.updateHooks(); this.updateHooks();
} }
myOnClose(event:{}, reason: "backdropClick" | "escapeKeyDown")
{
if (this.props.onClose) this.props.onClose(event,reason);
}
render() { render() {
let { tag, ...extra} = this.props; let { tag,onClose, ...extra} = this.props;
return ( return (
<Dialog {...extra}> <Dialog {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
{this.props.children} {this.props.children}
</Dialog> </Dialog>
); );
+82 -38
View File
@@ -22,33 +22,20 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react'; import React, { Component, SyntheticEvent } from 'react';
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles'; import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles'; import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin'; import { PiPedalFileProperty } from './Lv2Plugin';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Input from '@mui/material/Input'; import { PiPedalModel, PiPedalModelFactory, ListenHandle, StateChangedHandle } from './PiPedalModel';
import Select from '@mui/material/Select';
import Switch from '@mui/material/Switch';
import Utility, { nullCast } from './Utility';
import MenuItem from '@mui/material/MenuItem';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import ButtonBase from '@mui/material/ButtonBase' import ButtonBase from '@mui/material/ButtonBase'
import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; 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 } export const StandardItemSize = { width: 80, height: 140 }
@@ -88,13 +75,12 @@ const styles = (theme: Theme) => createStyles({
export interface FilePropertyControlProps extends WithStyles<typeof styles> { export interface FilePropertyControlProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
fileProperty: PiPedalFileProperty; fileProperty: PiPedalFileProperty;
value: string; onFileClick: (fileProperty: PiPedalFileProperty, value: string) => void;
onFileClick: (fileProperty: PiPedalFileProperty,value: string) => void;
theme: Theme; theme: Theme;
} }
type FilePropertyControlState = { type FilePropertyControlState = {
error: boolean; error: boolean;
showDialog: boolean; value: string;
}; };
const FilePropertyControl = const FilePropertyControl =
@@ -110,13 +96,75 @@ const FilePropertyControl =
this.state = { this.state = {
error: false, error: false,
showDialog: false value: "",
}; };
this.model = PiPedalModelFactory.getInstance(); 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; inputChanged: boolean = false;
onDrag(e: SyntheticEvent) { onDrag(e: SyntheticEvent) {
@@ -124,40 +172,36 @@ const FilePropertyControl =
} }
onFileClick() { onFileClick() {
this.props.onFileClick(this.props.fileProperty,this.props.value); this.props.onFileClick(this.props.fileProperty, this.state.value);
} }
private fileNameOnly(path: string): string { private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/'); let slashPos = path.lastIndexOf('/');
if (slashPos < 0) if (slashPos < 0) {
{
slashPos = 0; slashPos = 0;
} else { } else {
++slashPos; ++slashPos;
} }
let extPos = path.lastIndexOf('.'); let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos) if (extPos < 0 || extPos < slashPos) {
{
extPos = path.length; extPos = path.length;
} }
return path.substring(slashPos,extPos); return path.substring(slashPos, extPos);
} }
render() { render() {
let classes = this.props.classes; //let classes = this.props.classes;
let fileProperty = this.props.fileProperty; let fileProperty = this.props.fileProperty;
let value = this.props.value; let value = this.fileNameOnly(this.state.value);
if (!value || value.length === 0) if (!value || value.length === 0) {
{
value = "\u00A0"; value = "\u00A0";
} }
value = this.fileNameOnly(value);
let item_width = 264; let item_width = 264;
return ( 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 */} {/* TITLE SECTION */}
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}> <div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
<Typography variant="caption" display="block" noWrap style={{ <Typography variant="caption" display="block" noWrap style={{
@@ -169,13 +213,13 @@ const FilePropertyControl =
<div style={{ flex: "0 0 auto", width: "100%" }}> <div style={{ flex: "0 0 auto", width: "100%" }}>
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden",marginTop: 8}} onClick={()=> {this.onFileClick()}} > <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={{ width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px" }}>
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}> <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' variant='caption'
>{value}</Typography> >{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center",opacity: 0.5, marginRight: 4 }} /> <MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center", opacity: 0.5, marginRight: 4 }} />
</div> </div>
<div style={{ height: "1px", width: "100%", background: "black" }}>&nbsp;</div> <div style={{ height: "1px", width: "100%", background: "black" }}>&nbsp;</div>
</div> </div>
+47 -24
View File
@@ -23,35 +23,27 @@ import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import Button from '@mui/material/Button'; 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 FileUploadIcon from '@mui/icons-material/FileUpload';
import AudioFileIcon from '@mui/icons-material/AudioFile'; 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 DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin'; import { PiPedalFileProperty } from './Lv2Plugin';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import DialogEx from './DialogEx'; import DialogEx from './DialogEx';
import { ModelTraining } from '@mui/icons-material';
export interface FilePropertyDialogProps { export interface FilePropertyDialogProps {
open: boolean, open: boolean,
fileProperty: PiPedalFileProperty, fileProperty: PiPedalFileProperty,
selectedFile: string, selectedFile: string,
onOk: (fileProperty: PiPedalFileProperty, selectedItem: string) => void, onOk: (fileProperty: PiPedalFileProperty, selectedItem: string) => void,
onClose: () => void onCancel: () => void
}; };
export interface FilePropertyDialogState { export interface FilePropertyDialogState {
@@ -82,7 +74,23 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
private mounted: boolean = false; private mounted: boolean = false;
private model: PiPedalModel; private model: PiPedalModel;
private lastFileProperty: PiPedalFileProperty = new PiPedalFileProperty();
private requestFiles() { 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) this.model.requestFileList(this.props.fileProperty)
.then((files) => { .then((files) => {
if (this.mounted) if (this.mounted)
@@ -108,6 +116,11 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
this.mounted = false; this.mounted = false;
} }
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void { 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 { 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) }) this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.files,selectedFile) })
} }
mapKey: number = 0;
render() { render() {
this.mapKey = 0;
return ( return this.props.open &&
<DialogEx onClose={() => this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }} (
<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" } }} 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: "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 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( this.state.files.map(
(value: string, index: number) => { (value: string, index: number) => {
let selected = value === this.state.selectedFile; let selected = value === this.state.selectedFile;
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)"; let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
return ( return (
<ButtonBase <ButtonBase key={ this.mapKey++ }
style={{ width: "320px", flex: "0 0 48px", position: "relative" }} style={{ width: "320px", flex: "0 0 48px", position: "relative"}}
onClick={() => this.onSelect(value)} 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%" }}> <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> <Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
</div> </div>
</ButtonBase> </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 /> <input hidden accept="audio/x-wav" name="Upload Impuse File" type="file" multiple />
Upload Upload
</Button> </Button>
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label"> <IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label" color="primary" >
<DeleteIcon /> <DeleteIcon fontSize='small' />
</IconButton> </IconButton>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div> <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 Cancel
</Button> </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 Select
</Button> </Button>
</div> </div>
+1 -1
View File
@@ -203,7 +203,7 @@ const FullScreenIME =
justifyContent: "center", alignItems: "center" justifyContent: "center", alignItems: "center"
}} }}
> >
<Typography variant="body2">{this.props.caption}</Typography> <Typography noWrap variant="body2">{this.props.caption}</Typography>
<Input key={value} <Input key={value}
type="number" type="number"
defaultValue={control.formatShortValue(value)} defaultValue={control.formatShortValue(value)}
+6 -6
View File
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
import IControlViewFactory from './IControlViewFactory'; import IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import GxTunerControl from './GxTunerControl'; import GxTunerControl from './GxTunerControl';
@@ -40,7 +40,7 @@ const styles = (theme: Theme) => createStyles({
interface GxTunerProps extends WithStyles<typeof styles> { interface GxTunerProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
isToobTuner: boolean; isToobTuner: boolean;
} }
@@ -115,8 +115,8 @@ const GxTunerView =
export class GxTunerViewFactory implements IControlViewFactory { export class GxTunerViewFactory implements IControlViewFactory {
uri: string = GXTUNER_URI; uri: string = GXTUNER_URI;
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<GxTunerView isToobTuner={false} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); return (<GxTunerView isToobTuner={false} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
} }
@@ -126,8 +126,8 @@ export class GxTunerViewFactory implements IControlViewFactory {
export class ToobTunerViewFactory implements IControlViewFactory { export class ToobTunerViewFactory implements IControlViewFactory {
uri: string = TOOBTUNER_URI; uri: string = TOOBTUNER_URI;
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<GxTunerView isToobTuner={true} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); return (<GxTunerView isToobTuner={true} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
} }
+2 -2
View File
@@ -19,13 +19,13 @@
import React from 'react'; import React from 'react';
import { PiPedalModel } from "./PiPedalModel"; import { PiPedalModel } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
interface IControlViewFactory { interface IControlViewFactory {
uri: string; 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 { export default class JackServerSettings {
deserialize(input: any) : JackServerSettings{ deserialize(input: any) : JackServerSettings{
this.valid = input.valid; this.valid = input.valid;
this.isOnboarding = input.isOnboarding;
this.isJackAudio = input.isJackAudio; this.isJackAudio = input.isJackAudio;
this.rebootRequired = input.rebootRequired; this.rebootRequired = input.rebootRequired;
this.alsaDevice = input.alsaDevice?? ""; this.alsaDevice = input.alsaDevice?? "";
@@ -44,6 +45,7 @@ export default class JackServerSettings {
return new JackServerSettings().deserialize(this); return new JackServerSettings().deserialize(this);
} }
valid: boolean = false; valid: boolean = false;
isOnboarding: boolean = true;
rebootRequired = false; rebootRequired = false;
isJackAudio = false; isJackAudio = false;
alsaDevice: string = ""; alsaDevice: string = "";
@@ -54,7 +56,7 @@ export default class JackServerSettings {
getSummaryText() { getSummaryText() {
if (this.valid) { if (this.valid) {
let device = this.alsaDevice; 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; return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers;
} else { } else {
return "Not configured"; return "Not configured";
+2 -2
View File
@@ -134,8 +134,8 @@ const JackServerSettingsDialog = withStyles(styles)(
} }
if (result.sampleRate === 0) result.sampleRate = 48000; if (result.sampleRate === 0) result.sampleRate = 48000;
if (result.bufferSize === 0) result.bufferSize = 64; if (result.bufferSize === 0) result.bufferSize = 16;
if (result.numberOfBuffers === 0) result.numberOfBuffers = 2; if (result.numberOfBuffers === 0) result.numberOfBuffers = 3;
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate); result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize); result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
result.valid = true; 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 = ""; name: string = "";
fileExtension: 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 { export class PiPedalFileProperty {
deserialize(input: any): PiPedalFileProperty deserialize(input: any): PiPedalFileProperty
{ {
this.name = input.name; this.name = input.name;
this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes); this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes);
this.patchProperty = input.patchProperty; this.patchProperty = input.patchProperty;
this.defaultFile = input.defaultFile;
this.directory = input.directory; this.directory = input.directory;
return this; return this;
} }
@@ -147,7 +171,6 @@ export class PiPedalFileProperty {
fileTypes: PiPedalFileType[] = []; fileTypes: PiPedalFileType[] = [];
patchProperty: string = ""; patchProperty: string = "";
directory: string = ""; directory: string = "";
defaultFile: string = "";
}; };
export class Lv2Plugin implements Deserializable<Lv2Plugin> { export class Lv2Plugin implements Deserializable<Lv2Plugin> {
@@ -170,6 +193,12 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
} else { } else {
this.fileProperties = []; this.fileProperties = [];
} }
if (input.uiPortNotifications)
{
this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications);
} else {
this.uiPortNotifications = [];
}
return this; return this;
} }
static EmptyFeatures: string[] = []; static EmptyFeatures: string[] = [];
@@ -186,6 +215,7 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
ports: Port[] = Port.EmptyPorts; ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = []; port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = []; 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 withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { import {
PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType
} from './PedalBoard'; } from './Pedalboard';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import InputIcon from '@mui/icons-material/Input'; import InputIcon from '@mui/icons-material/Input';
import LoadPluginDialog from './LoadPluginDialog'; import LoadPluginDialog from './LoadPluginDialog';
import Switch from '@mui/material/Switch'; import Switch from '@mui/material/Switch';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import PedalBoardView from './PedalBoardView'; import PedalboardView from './PedalboardView';
import { PiPedalStateError } from './PiPedalError'; import { PiPedalStateError } from './PiPedalError';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
@@ -58,11 +58,11 @@ const styles = ({ palette }: Theme) => createStyles({
position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap", position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap",
justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden" justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden"
}, },
pedalBoardScroll: { pedalboardScroll: {
position: "relative", width: "100%", position: "relative", width: "100%",
flex: "0 0 auto", overflow: "auto", maxHeight: 220 flex: "0 0 auto", overflow: "auto", maxHeight: 220
}, },
pedalBoardScrollSmall: { pedalboardScrollSmall: {
position: "relative", width: "100%", position: "relative", width: "100%",
flex: "1 1 1px", overflow: "auto" flex: "1 1 1px", overflow: "auto"
}, },
@@ -97,7 +97,7 @@ interface MainProps extends WithStyles<typeof styles> {
interface MainState { interface MainState {
selectedPedal: number; selectedPedal: number;
loadDialogOpen: boolean; loadDialogOpen: boolean;
pedalBoard: PedalBoard; pedalboard: Pedalboard;
addMenuAnchorEl: HTMLElement | null; addMenuAnchorEl: HTMLElement | null;
splitControlBar: boolean; splitControlBar: boolean;
horizontalScrollLayout: boolean; horizontalScrollLayout: boolean;
@@ -116,13 +116,13 @@ export const MainPage =
constructor(props: MainProps) { constructor(props: MainProps) {
super(props); super(props);
this.model = PiPedalModelFactory.getInstance(); this.model = PiPedalModelFactory.getInstance();
let pedalboard = this.model.pedalBoard.get(); let pedalboard = this.model.pedalboard.get();
let selectedPedal = pedalboard.getFirstSelectableItem(); let selectedPedal = pedalboard.getFirstSelectableItem();
this.state = { this.state = {
selectedPedal: selectedPedal, selectedPedal: selectedPedal,
loadDialogOpen: false, loadDialogOpen: false,
pedalBoard: pedalboard, pedalboard: pedalboard,
addMenuAnchorEl: null, addMenuAnchorEl: null,
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD, splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK, horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
@@ -135,31 +135,31 @@ export const MainPage =
this.onLoadClick = this.onLoadClick.bind(this); this.onLoadClick = this.onLoadClick.bind(this);
this.onLoadOk = this.onLoadOk.bind(this); this.onLoadOk = this.onLoadOk.bind(this);
this.onLoadCancel = this.onLoadCancel.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); this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this);
} }
onInsertPedal(instanceId: number) { onInsertPedal(instanceId: number) {
this.setAddMenuAnchorEl(null); this.setAddMenuAnchorEl(null);
let newId = this.model.addPedalBoardItem(instanceId, false); let newId = this.model.addPedalboardItem(instanceId, false);
this.setSelection(newId); this.setSelection(newId);
} }
onAppendPedal(instanceId: number) { onAppendPedal(instanceId: number) {
this.setAddMenuAnchorEl(null); this.setAddMenuAnchorEl(null);
let newId = this.model.addPedalBoardItem(instanceId, true); let newId = this.model.addPedalboardItem(instanceId, true);
this.setSelection(newId); this.setSelection(newId);
} }
onInsertSplit(instanceId: number) { onInsertSplit(instanceId: number) {
this.setAddMenuAnchorEl(null); this.setAddMenuAnchorEl(null);
let newId = this.model.addPedalBoardSplitItem(instanceId, false); let newId = this.model.addPedalboardSplitItem(instanceId, false);
this.setSelection(newId); this.setSelection(newId);
} }
onAppendSplit(instanceId: number) { onAppendSplit(instanceId: number) {
this.setAddMenuAnchorEl(null); this.setAddMenuAnchorEl(null);
let newId = this.model.addPedalBoardSplitItem(instanceId, true); let newId = this.model.addPedalboardSplitItem(instanceId, true);
this.setSelection(newId); this.setSelection(newId);
} }
@@ -182,16 +182,16 @@ export const MainPage =
} }
handleEnableCurrentItemChanged(event: any): void { handleEnableCurrentItemChanged(event: any): void {
let newValue = event.target.checked; let newValue = event.target.checked;
let item = this.getSelectedPedalBoardItem(); let item = this.getSelectedPedalboardItem();
if (item != null) { if (item != null) {
this.model.setPedalBoardItemEnabled(item.getInstanceId(), newValue); this.model.setPedalboardItemEnabled(item.getInstanceId(), newValue);
} }
} }
handleSelectPluginPreset(instanceId: number, presetInstanceId: number) { handleSelectPluginPreset(instanceId: number, presetInstanceId: number) {
this.model.loadPluginPreset(instanceId, presetInstanceId); this.model.loadPluginPreset(instanceId, presetInstanceId);
} }
onPedalBoardChanged(value: PedalBoard) { onPedalboardChanged(value: Pedalboard) {
let selectedItem = -1; let selectedItem = -1;
if (value.hasItem(this.state.selectedPedal)) if (value.hasItem(this.state.selectedPedal))
{ {
@@ -200,12 +200,12 @@ export const MainPage =
selectedItem = value.getFirstSelectableItem(); selectedItem = value.getFirstSelectableItem();
} }
this.setState({ this.setState({
pedalBoard: value, pedalboard: value,
selectedPedal: selectedItem selectedPedal: selectedItem
}); });
} }
onDeletePedal(instanceId: number): void { onDeletePedal(instanceId: number): void {
let result = this.model.deletePedalBoardPedal(instanceId); let result = this.model.deletePedalboardPedal(instanceId);
if (result != null) { if (result != null) {
this.setState({ selectedPedal: result }); this.setState({ selectedPedal: result });
} }
@@ -213,10 +213,10 @@ export const MainPage =
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
} }
componentWillUnmount() { componentWillUnmount() {
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
super.componentWillUnmount(); super.componentWillUnmount();
} }
updateResponsive() { updateResponsive() {
@@ -241,14 +241,14 @@ export const MainPage =
} }
onPedalDoubleClick(selectedId: number): void { onPedalDoubleClick(selectedId: number): void {
this.setSelection(selectedId); this.setSelection(selectedId);
let item = this.getPedalBoardItem(selectedId); let item = this.getPedalboardItem(selectedId);
if (item != null) { if (item != null) {
if (item.isSplit()) { if (item.isSplit()) {
let split = item as PedalBoardSplitItem; let split = item as PedalboardSplitItem;
if (split.getSplitType() === SplitType.Ab) { if (split.getSplitType() === SplitType.Ab) {
let cv = split.getToggleAbControlValue(); let cv = split.getToggleAbControlValue();
if (split.instanceId === undefined) throw new PiPedalStateError("Split without valid id."); 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 { } else {
this.setState({ loadDialogOpen: true }); this.setState({ loadDialogOpen: true });
@@ -261,7 +261,7 @@ export const MainPage =
onLoadOk(selectedUri: string): void { onLoadOk(selectedUri: string): void {
this.setState({ loadDialogOpen: false }); this.setState({ loadDialogOpen: false });
let itemId = this.state.selectedPedal; let itemId = this.state.selectedPedal;
let newSelectedItem = this.model.loadPedalBoardPlugin(itemId, selectedUri); let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri);
this.setState({ selectedPedal: newSelectedItem }); this.setState({ selectedPedal: newSelectedItem });
} }
@@ -269,12 +269,12 @@ export const MainPage =
this.setState({ loadDialogOpen: true }); this.setState({ loadDialogOpen: true });
} }
getPedalBoardItem(selectedId?: number): PedalBoardItem | null { getPedalboardItem(selectedId?: number): PedalboardItem | null {
if (selectedId === undefined) return null; if (selectedId === undefined) return null;
let pedalBoard = this.model.pedalBoard.get(); let pedalboard = this.model.pedalboard.get();
if (!pedalBoard) return null; if (!pedalboard) return null;
let it = pedalBoard.itemsGenerator(); let it = pedalboard.itemsGenerator();
if (!selectedId) return null; if (!selectedId) return null;
while (true) { while (true) {
let v = it.next(); let v = it.next();
@@ -290,32 +290,32 @@ export const MainPage =
} }
onPedalboardPropertyChanged(instanceId: number, key: string, value: number) { onPedalboardPropertyChanged(instanceId: number, key: string, value: number) {
this.model.setPedalBoardControlValue(instanceId, key, value); this.model.setPedalboardControl(instanceId, key, value);
} }
getSelectedPedalBoardItem(): PedalBoardItem | null { getSelectedPedalboardItem(): PedalboardItem | null {
return this.getPedalBoardItem(this.state.selectedPedal); return this.getPedalboardItem(this.state.selectedPedal);
} }
getSelectedUri(): string { getSelectedUri(): string {
let pedalBoardItem = this.getSelectedPedalBoardItem(); let pedalboardItem = this.getSelectedPedalboardItem();
if (pedalBoardItem == null) return ""; if (pedalboardItem == null) return "";
return pedalBoardItem.uri; return pedalboardItem.uri;
} }
titleBar(pedalBoardItem: PedalBoardItem | null): React.ReactNode { titleBar(pedalboardItem: PedalboardItem | null): React.ReactNode {
let title = ""; let title = "";
let author = ""; let author = "";
let pluginUri = ""; let pluginUri = "";
let presetsUri = ""; let presetsUri = "";
let missing = false; let missing = false;
if (pedalBoardItem) { if (pedalboardItem) {
if (pedalBoardItem.isEmpty()) { if (pedalboardItem.isEmpty()) {
title = ""; title = "";
} else if (pedalBoardItem.isSplit()) { } else if (pedalboardItem.isSplit()) {
title = "Split"; title = "Split";
} else { } else {
let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri); let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
if (!uiPlugin) { if (!uiPlugin) {
missing = true; missing = true;
title = pedalBoardItem?.pluginName ?? "Missing plugin"; title = pedalboardItem?.pluginName ?? "Missing plugin";
} else { } else {
title = uiPlugin.name; title = uiPlugin.name;
author = uiPlugin.author_name; author = uiPlugin.author_name;
@@ -362,7 +362,7 @@ export const MainPage =
<PluginInfoDialog plugin_uri={pluginUri} /> <PluginInfoDialog plugin_uri={pluginUri} />
</div> </div>
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalBoardItem?.instanceId ?? 0} <PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
/> />
</div> </div>
</div> </div>
@@ -373,8 +373,8 @@ export const MainPage =
render() { render() {
let classes = this.props.classes; let classes = this.props.classes;
let pedalBoard = this.model.pedalBoard.get(); let pedalboard = this.model.pedalboard.get();
let pedalBoardItem = this.getSelectedPedalBoardItem(); let pedalboardItem = this.getSelectedPedalboardItem();
let uiPlugin = null; let uiPlugin = null;
let bypassVisible = false; let bypassVisible = false;
let bypassChecked = false; let bypassChecked = false;
@@ -384,20 +384,20 @@ export const MainPage =
let missing = false; let missing = false;
let pluginUri = "#error"; let pluginUri = "#error";
if (pedalBoardItem) { if (pedalboardItem) {
canDelete = pedalBoard.canDeleteItem(pedalBoardItem.instanceId); canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
instanceId = pedalBoardItem.instanceId; instanceId = pedalboardItem.instanceId;
if (pedalBoardItem.isEmpty()) { if (pedalboardItem.isEmpty()) {
canAdd = true; canAdd = true;
} else if (pedalBoardItem.isSplit()) { } else if (pedalboardItem.isSplit()) {
canAdd = true; canAdd = true;
} else { } else {
pluginUri = pedalBoardItem.uri; pluginUri = pedalboardItem.uri;
uiPlugin = this.model.getUiPlugin(pluginUri); uiPlugin = this.model.getUiPlugin(pluginUri);
canAdd = true; canAdd = true;
if (uiPlugin) { if (uiPlugin) {
bypassVisible = true; bypassVisible = true;
bypassChecked = pedalBoardItem.isEnabled; bypassChecked = pedalboardItem.isEnabled;
} else { } else {
missing = true; missing = true;
} }
@@ -407,9 +407,9 @@ export const MainPage =
return ( return (
<div className={classes.frame}> <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 }}> 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} onSelectionChanged={this.onSelectionChanged}
onDoubleClick={this.onPedalDoubleClick} onDoubleClick={this.onPedalDoubleClick}
hasTinyToolBar={this.props.hasTinyToolBar} hasTinyToolBar={this.props.hasTinyToolBar}
@@ -427,7 +427,7 @@ export const MainPage =
</div> </div>
</div> </div>
{ {
(!this.state.splitControlBar) && this.titleBar(pedalBoardItem) (!this.state.splitControlBar) && this.titleBar(pedalboardItem)
} }
<div style={{ flex: "1 1 1px" }}> <div style={{ flex: "1 1 1px" }}>
@@ -453,7 +453,7 @@ export const MainPage =
</div> </div>
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}> <div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
<IconButton <IconButton
onClick={() => { this.onDeletePedal(pedalBoardItem?.instanceId ?? -1) }} onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
size="large"> size="large">
<img src="/img/old_delete_outline_black_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} /> <img src="/img/old_delete_outline_black_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
</IconButton> </IconButton>
@@ -464,7 +464,7 @@ export const MainPage =
color="primary" color="primary"
size="small" size="small"
onClick={this.onLoadClick} onClick={this.onLoadClick}
disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalBoardItem()?.isSplit() ?? true)} disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
startIcon={<InputIcon />} startIcon={<InputIcon />}
style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}} style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}}
> >
@@ -485,7 +485,7 @@ export const MainPage =
this.state.splitControlBar && ( this.state.splitControlBar && (
<div className={classes.splitControlBar}> <div className={classes.splitControlBar}>
{ {
this.titleBar(pedalBoardItem) this.titleBar(pedalboardItem)
} }
</div> </div>
) )
@@ -495,12 +495,12 @@ export const MainPage =
missing ? ( missing ? (
<div style={{marginLeft: 100,marginTop: 20}}> <div style={{marginLeft: 100,marginTop: 20}}>
<Typography variant="body1" paragraph={true}>Error: Plugin is not installed.</Typography> <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> </div>
): ):
( (
GetControlView(pedalBoardItem) GetControlView(pedalboardItem)
) )
} }
</div> </div>
+3 -3
View File
@@ -139,8 +139,8 @@ const MidiBindingView =
render() { render() {
let classes = this.props.classes; let classes = this.props.classes;
let midiBinding = this.props.midiBinding; let midiBinding = this.props.midiBinding;
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri); let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
if (!uiPlugin) { if (!uiPlugin) {
return (<div />); return (<div />);
} }
@@ -267,7 +267,7 @@ const MidiBindingView =
(canTrigger) && (canTrigger) &&
( (
<div className={classes.controlDiv2} > <div className={classes.controlDiv2} >
<Typography style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography> <Typography noWrap style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography>
</div> </div>
) )
+4 -4
View File
@@ -123,8 +123,8 @@ export const MidiBindingDialog =
{ {
this.cancelListenForControl(); this.cancelListenForControl();
let pedalBoard = this.model.pedalBoard.get(); let pedalboard = this.model.pedalboard.get();
let item = pedalBoard.getItem(instanceId); let item = pedalboard.getItem(instanceId);
if (!item) return; if (!item) return;
let binding = item.getMidiBinding(symbol); let binding = item.getMidiBinding(symbol);
@@ -181,7 +181,7 @@ export const MidiBindingDialog =
let classes = this.props.classes; let classes = this.props.classes;
let result: React.ReactNode[] = []; let result: React.ReactNode[] = [];
let pedalboard = this.model.pedalBoard.get(); let pedalboard = this.model.pedalboard.get();
let iter = pedalboard.itemsGenerator(); let iter = pedalboard.itemsGenerator();
while (true) { while (true) {
let v = iter.next(); let v = iter.next();
@@ -285,7 +285,7 @@ export const MidiBindingDialog =
size="large"> size="large">
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
Preset MIDI Bindings Preset MIDI Bindings
</Typography> </Typography>
</Toolbar> </Toolbar>
@@ -57,30 +57,9 @@ export class ControlValue implements Deserializable<ControlValue> {
value: number; value: number;
} }
export class PropertyValue implements Deserializable<PropertyValue> {
deserialize(input: any): PropertyValue {
this.propertyUri = input.propertyUri;
this.value = input.value;
return this;
}
static deserializeArray(input: any[]): PropertyValue[] {
let result: PropertyValue[] = [];
for (let i = 0; i < input.length; ++i) {
result[i] = new PropertyValue().deserialize(input[i]);
}
return result;
}
setValue(value: number) {
this.value = value;
}
propertyUri: string = ""; export class PedalboardItem implements Deserializable<PedalboardItem> {
value: any = null; deserializePedalboardItem(input: any): PedalboardItem {
}
export class PedalBoardItem implements Deserializable<PedalBoardItem> {
deserializePedalBoardItem(input: any): PedalBoardItem {
this.instanceId = input.instanceId ?? -1; this.instanceId = input.instanceId ?? -1;
this.uri = input.uri; this.uri = input.uri;
this.pluginName = input.pluginName; this.pluginName = input.pluginName;
@@ -88,24 +67,24 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings); this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
this.controlValues = ControlValue.deserializeArray(input.controlValues); this.controlValues = ControlValue.deserializeArray(input.controlValues);
this.propertyValues = PropertyValue.deserializeArray(input.propertyValues);
this.vstState = input.vstState ?? ""; this.vstState = input.vstState ?? "";
this.lv2State = input.lv2State;
return this; return this;
} }
deserialize(input: any): PedalBoardItem { deserialize(input: any): PedalboardItem {
return this.deserializePedalBoardItem(input); return this.deserializePedalboardItem(input);
} }
static deserializeArray(input: any): PedalBoardItem[] { static deserializeArray(input: any): PedalboardItem[] {
let result: PedalBoardItem[] = []; let result: PedalboardItem[] = [];
for (let i = 0; i < input.length; ++i) { for (let i = 0; i < input.length; ++i) {
let inputItem: any = input[i]; let inputItem: any = input[i];
let uri: string = inputItem.uri as string; let uri: string = inputItem.uri as string;
let outputItem: PedalBoardItem; let outputItem: PedalboardItem;
if (uri === SPLIT_PEDALBOARD_ITEM_URI) { if (uri === SPLIT_PEDALBOARD_ITEM_URI) {
outputItem = new PedalBoardSplitItem().deserialize(inputItem); outputItem = new PedalboardSplitItem().deserialize(inputItem);
} else { } else {
outputItem = new PedalBoardItem().deserialize(inputItem); outputItem = new PedalboardItem().deserialize(inputItem);
} }
result[i] = outputItem; result[i] = outputItem;
@@ -158,17 +137,6 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
} }
return false; return false;
} }
setPropertyValue(propertyUri: string, value: any): boolean {
for (let i = 0; i < this.propertyValues.length; ++i) {
let v = this.propertyValues[i];
if (v.propertyUri === propertyUri) {
if (v.value === value) return false;
v.value = value;
return true;
}
}
return false;
}
setMidiBinding(midiBinding: MidiBinding): boolean { setMidiBinding(midiBinding: MidiBinding): boolean {
if (this.midiBindings) if (this.midiBindings)
{ {
@@ -209,16 +177,16 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
} }
static EmptyArray: PedalBoardItem[] = []; static EmptyArray: PedalboardItem[] = [];
instanceId: number = -1; instanceId: number = -1;
isEnabled: boolean = false; isEnabled: boolean = false;
uri: string = ""; uri: string = "";
pluginName?: string; pluginName?: string;
controlValues: ControlValue[] = ControlValue.EmptyArray; controlValues: ControlValue[] = ControlValue.EmptyArray;
propertyValues: PropertyValue[] = [];
midiBindings: MidiBinding[] = []; midiBindings: MidiBinding[] = [];
vstState: string = ""; vstState: string = "";
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 PANL_KEY: string = "panL";
static PANR_KEY: string = "panR"; static PANR_KEY: string = "panR";
static VOLL_KEY: string = "volL"; static VOLL_KEY: string = "volL";
@@ -241,15 +209,15 @@ export class PedalBoardSplitItem extends PedalBoardItem {
static SELECT_KEY: string = "select"; static SELECT_KEY: string = "select";
deserialize(input: any): PedalBoardSplitItem { deserialize(input: any): PedalboardSplitItem {
this.deserializePedalBoardItem(input); this.deserializePedalboardItem(input);
this.topChain = PedalBoardItem.deserializeArray(input.topChain); this.topChain = PedalboardItem.deserializeArray(input.topChain);
this.bottomChain = PedalBoardItem.deserializeArray(input.bottomChain); this.bottomChain = PedalboardItem.deserializeArray(input.bottomChain);
return this; return this;
} }
getSplitType(): SplitType { getSplitType(): SplitType {
let rawValue = this.getControlValue(PedalBoardSplitItem.TYPE_KEY); let rawValue = this.getControlValue(PedalboardSplitItem.TYPE_KEY);
if (rawValue < 1) return SplitType.Ab; if (rawValue < 1) return SplitType.Ab;
if (rawValue < 2) return SplitType.Mix; if (rawValue < 2) return SplitType.Mix;
@@ -263,43 +231,43 @@ export class PedalBoardSplitItem extends PedalBoardItem {
} }
getMixControl(): ControlValue { getMixControl(): ControlValue {
return this.getControl(PedalBoardSplitItem.MIX_KEY); return this.getControl(PedalboardSplitItem.MIX_KEY);
} }
getMix(): number { getMix(): number {
return this.getControlValue(PedalBoardSplitItem.MIX_KEY); return this.getControlValue(PedalboardSplitItem.MIX_KEY);
} }
isASelected(): boolean { 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 { 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; topChain: PedalboardItem[] = PedalboardItem.EmptyArray;
bottomChain: 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.name = input.name;
this.items = PedalBoardItem.deserializeArray(input.items); this.items = PedalboardItem.deserializeArray(input.items);
this.nextInstanceId = input.nextInstanceId ?? -1; this.nextInstanceId = input.nextInstanceId ?? -1;
return this; return this;
} }
clone(): PedalBoard { clone(): Pedalboard {
return new PedalBoard().deserialize(this); return new Pedalboard().deserialize(this);
} }
name: string = ""; name: string = "";
items: PedalBoardItem[] = []; items: PedalboardItem[] = [];
nextInstanceId: number = -1; nextInstanceId: number = -1;
*itemsGenerator(): Generator<PedalBoardItem, void, undefined> { *itemsGenerator(): Generator<PedalboardItem, void, undefined> {
let it = itemGenerator_(this.items); let it = itemGenerator_(this.items);
while (true) 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(); let it = this.itemsGenerator();
while (true) while (true)
{ {
@@ -348,7 +316,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
return null; return null;
} }
getItem(instanceId: number): PedalBoardItem { getItem(instanceId: number): PedalboardItem {
let it = this.itemsGenerator(); let it = this.itemsGenerator();
while (true) while (true)
{ {
@@ -361,7 +329,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
throw new PiPedalArgumentError("Item not found."); 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) for (let i = 0; i < items.length; ++i)
{ {
@@ -383,7 +351,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} else { } else {
if (item.isSplit()) if (item.isSplit())
{ {
let splitItem = item as PedalBoardSplitItem; let splitItem = item as PedalboardSplitItem;
let t = this.deleteItem_(instanceId,splitItem.topChain); let t = this.deleteItem_(instanceId,splitItem.topChain);
if (t != null) return t; if (t != null) return t;
@@ -395,7 +363,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
return null; return null;
} }
canDeleteItem_(instanceId: number,items: PedalBoardItem[]): boolean canDeleteItem_(instanceId: number,items: PedalboardItem[]): boolean
{ {
for (let i = 0; i < items.length; ++i) for (let i = 0; i < items.length; ++i)
{ {
@@ -407,7 +375,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
if (item.isSplit()) if (item.isSplit())
{ {
let splitItem = item as PedalBoardSplitItem; let splitItem = item as PedalboardSplitItem;
if (this.canDeleteItem_(instanceId,splitItem.topChain)) if (this.canDeleteItem_(instanceId,splitItem.topChain))
{ {
return true; return true;
@@ -436,15 +404,15 @@ export class PedalBoard implements Deserializable<PedalBoard> {
if (!item) return false; if (!item) return false;
return item.setMidiBinding(midiBinding); return item.setMidiBinding(midiBinding);
} }
addToStart(item: PedalBoardItem) addToStart(item: PedalboardItem)
{ {
this.items.splice(0,0,item); this.items.splice(0,0,item);
} }
addToEnd(item: PedalBoardItem) addToEnd(item: PedalboardItem)
{ {
this.items.splice(this.items.length,0,item); 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) for (let i = 0; i < items.length; ++i)
{ {
@@ -461,7 +429,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
if (item.isSplit()) if (item.isSplit())
{ {
let split = item as PedalBoardSplitItem; let split = item as PedalboardSplitItem;
if (this._addRelative(split.topChain,newItem,instanceId,addBefore)) if (this._addRelative(split.topChain,newItem,instanceId,addBefore))
{ {
return true; 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; 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) { if (!result) {
throw new PiPedalArgumentError("instanceId not found."); throw new PiPedalArgumentError("instanceId not found.");
} }
} }
addAfter(item: PedalBoardItem, instanceId: number) addAfter(item: PedalboardItem, instanceId: number)
{ {
if (item.instanceId === instanceId) return; 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) { if (!result) {
throw new PiPedalArgumentError("instanceId not found."); throw new PiPedalArgumentError("instanceId not found.");
} }
@@ -520,8 +488,8 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
} }
} }
createEmptySplit(): PedalBoardSplitItem { createEmptySplit(): PedalboardSplitItem {
let result: PedalBoardSplitItem = new PedalBoardSplitItem(); let result: PedalboardSplitItem = new PedalboardSplitItem();
result.uri = SPLIT_PEDALBOARD_ITEM_URI; result.uri = SPLIT_PEDALBOARD_ITEM_URI;
result.instanceId = ++this.nextInstanceId; result.instanceId = ++this.nextInstanceId;
result.pluginName = ""; result.pluginName = "";
@@ -529,13 +497,13 @@ export class PedalBoard implements Deserializable<PedalBoard> {
result.topChain = [ this.createEmptyItem()]; result.topChain = [ this.createEmptyItem()];
result.bottomChain = [ this.createEmptyItem()]; result.bottomChain = [ this.createEmptyItem()];
result.controlValues = [ result.controlValues = [
new ControlValue(PedalBoardSplitItem.TYPE_KEY, 0), new ControlValue(PedalboardSplitItem.TYPE_KEY, 0),
new ControlValue(PedalBoardSplitItem.SELECT_KEY,0), new ControlValue(PedalboardSplitItem.SELECT_KEY,0),
new ControlValue(PedalBoardSplitItem.MIX_KEY,0), new ControlValue(PedalboardSplitItem.MIX_KEY,0),
new ControlValue(PedalBoardSplitItem.PANL_KEY,0), new ControlValue(PedalboardSplitItem.PANL_KEY,0),
new ControlValue(PedalBoardSplitItem.VOLL_KEY,-3), new ControlValue(PedalboardSplitItem.VOLL_KEY,-3),
new ControlValue(PedalBoardSplitItem.PANR_KEY,0), new ControlValue(PedalboardSplitItem.PANR_KEY,0),
new ControlValue(PedalBoardSplitItem.VOLR_KEY,-3) new ControlValue(PedalboardSplitItem.VOLR_KEY,-3)
]; ];
@@ -543,8 +511,8 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
createEmptyItem(): PedalBoardItem { createEmptyItem(): PedalboardItem {
let result: PedalBoardItem = new PedalBoardItem(); let result: PedalboardItem = new PedalboardItem();
result.uri = EMPTY_PEDALBOARD_ITEM_URI; result.uri = EMPTY_PEDALBOARD_ITEM_URI;
result.instanceId = ++this.nextInstanceId; result.instanceId = ++this.nextInstanceId;
result.pluginName = ""; result.pluginName = "";
@@ -552,7 +520,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
return result; return result;
} }
setItemEmpty(item: PedalBoardItem) setItemEmpty(item: PedalboardItem)
{ {
item.uri = EMPTY_PEDALBOARD_ITEM_URI; item.uri = EMPTY_PEDALBOARD_ITEM_URI;
item.instanceId = ++this.nextInstanceId; 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) for (let i = 0; i < items.length; ++i)
{ {
let item = items[i]; let item = items[i];
@@ -573,7 +541,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
if (items[i].isSplit()) if (items[i].isSplit())
{ {
let splitItem = item as PedalBoardSplitItem; let splitItem = item as PedalboardSplitItem;
if (this._replaceItem(splitItem.topChain,instanceId,newItem)) if (this._replaceItem(splitItem.topChain,instanceId,newItem))
return true; return true;
if (this._replaceItem(splitItem.bottomChain,instanceId,newItem)) if (this._replaceItem(splitItem.bottomChain,instanceId,newItem))
@@ -585,7 +553,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
return false; return false;
} }
replaceItem(instanceId: number, newItem: PedalBoardItem) replaceItem(instanceId: number, newItem: PedalboardItem)
{ {
let result = this._replaceItem(this.items,instanceId,newItem); let result = this._replaceItem(this.items,instanceId,newItem);
if (!result) if (!result)
@@ -593,7 +561,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
throw new PiPedalArgumentError("instanceId not found."); 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) for (let i = 0; i < items.length; ++i)
{ {
@@ -611,27 +579,27 @@ export class PedalBoard implements Deserializable<PedalBoard> {
} }
if (item.isSplit()) 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.topChain,newItem,instanceId,append)) return true;
if (this._addItem(splitItem.bottomChain,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); 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) { for (let i = 0; i < items.length; ++i) {
let item = items[i]; let item = items[i];
yield item; yield item;
if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) { if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) {
let splitItem = item as PedalBoardSplitItem; let splitItem = item as PedalboardSplitItem;
let it = itemGenerator_(splitItem.topChain); let it = itemGenerator_(splitItem.topChain);
while (true) { while (true) {
@@ -36,8 +36,8 @@ import Utility from './Utility'
import { import {
PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType, Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType,
} from './PedalBoard'; } from './Pedalboard';
const START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start"; const START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
const END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End"; const END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
@@ -69,7 +69,7 @@ function CalculateConnection(numberOfInputs: number, numberOfOutputs: number)
return result; return result;
} }
const pedalBoardStyles = (theme: Theme) => createStyles({ const pedalboardStyles = (theme: Theme) => createStyles({
scrollContainer: { scrollContainer: {
}, },
@@ -177,7 +177,7 @@ const pedalBoardStyles = (theme: Theme) => createStyles({
export type OnSelectHandler = (selectedPedal: number) => void; export type OnSelectHandler = (selectedPedal: number) => void;
interface PedalBoardProps extends WithStyles<typeof pedalBoardStyles> { interface PedalboardProps extends WithStyles<typeof pedalboardStyles> {
theme: Theme; theme: Theme;
selectedId?: number; selectedId?: number;
onSelectionChanged?: OnSelectHandler; onSelectionChanged?: OnSelectHandler;
@@ -190,14 +190,14 @@ interface LayoutSize {
height: number; height: number;
} }
type PedalBoardState = { type PedalboardState = {
pedalBoard?: PedalBoard; pedalboard?: Pedalboard;
}; };
const EMPTY_PEDALS: PedalLayout[] = []; const EMPTY_PEDALS: PedalLayout[] = [];
function makeChain(model: PiPedalModel, uiItems?: PedalBoardItem[]): PedalLayout[] { function makeChain(model: PiPedalModel, uiItems?: PedalboardItem[]): PedalLayout[] {
let result: PedalLayout[] = []; let result: PedalLayout[] = [];
if (uiItems) { if (uiItems) {
for (let i = 0; i < uiItems.length; ++i) { for (let i = 0; i < uiItems.length; ++i) {
@@ -219,7 +219,7 @@ class PedalLayout {
numberOfInputs: number = 2; numberOfInputs: number = 2;
numberOfOutputs: number = 2; numberOfOutputs: number = 2;
pedalItem?: PedalBoardItem; pedalItem?: PedalboardItem;
// Split Layout only. // Split Layout only.
topChildren: PedalLayout[] = EMPTY_PEDALS; topChildren: PedalLayout[] = EMPTY_PEDALS;
@@ -245,7 +245,7 @@ class PedalLayout {
t.numberOfOutputs = 0; t.numberOfOutputs = 0;
return t; return t;
} }
constructor(model?: PiPedalModel, pedalItem?: PedalBoardItem) { constructor(model?: PiPedalModel, pedalItem?: PedalboardItem) {
if (model === undefined && pedalItem === undefined) { if (model === undefined && pedalItem === undefined) {
return; return;
} }
@@ -255,7 +255,7 @@ class PedalLayout {
this.pedalItem = pedalItem; this.pedalItem = pedalItem;
this.uri = pedalItem.uri; this.uri = pedalItem.uri;
if (pedalItem.isSplit()) { if (pedalItem.isSplit()) {
let splitter = pedalItem as PedalBoardSplitItem; let splitter = pedalItem as PedalboardSplitItem;
this.pluginType = PluginType.UtilityPlugin; this.pluginType = PluginType.UtilityPlugin;
this.topChildren = makeChain(model, splitter.topChain); this.topChildren = makeChain(model, splitter.topChain);
@@ -350,9 +350,9 @@ class LayoutParams {
} }
const PedalBoardView = const PedalboardView =
withStyles(pedalBoardStyles, { withTheme: true })( withStyles(pedalboardStyles, { withTheme: true })(
class extends Component<PedalBoardProps, PedalBoardState> class extends Component<PedalboardProps, PedalboardState>
{ {
model: PiPedalModel; model: PiPedalModel;
@@ -360,15 +360,15 @@ const PedalBoardView =
scrollRef: React.RefObject<HTMLDivElement>; scrollRef: React.RefObject<HTMLDivElement>;
constructor(props: PedalBoardProps) { constructor(props: PedalboardProps) {
super(props); super(props);
this.model = PiPedalModelFactory.getInstance(); this.model = PiPedalModelFactory.getInstance();
if (!props.selectedId) props.selectedId = -1; if (!props.selectedId) props.selectedId = -1;
this.state = { 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.frameRef = React.createRef();
this.scrollRef = React.createRef(); this.scrollRef = React.createRef();
this.handleTouchStart = this.handleTouchStart.bind(this); this.handleTouchStart = this.handleTouchStart.bind(this);
@@ -402,11 +402,11 @@ const PedalBoardView =
if (item.isSplitter() && item.pedalItem) { if (item.isSplitter() && item.pedalItem) {
if (item.bounds.contains(clientX, clientY)) { if (item.bounds.contains(clientX, clientY)) {
if (clientX < item.bounds.x + CELL_WIDTH / 2) { 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); this.setSelection(instanceId);
return; return;
} else if (clientX > item.bounds.right - CELL_WIDTH / 2) { } 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); this.setSelection(instanceId);
return; return;
@@ -420,7 +420,7 @@ const PedalBoardView =
if (clientX < item.topChildren[0].bounds.x) { if (clientX < item.topChildren[0].bounds.x) {
let topPedalItem = item.topChildren[0].pedalItem; let topPedalItem = item.topChildren[0].pedalItem;
if (topPedalItem) { if (topPedalItem) {
this.model.movePedalBoardItemBefore(instanceId, topPedalItem.instanceId); this.model.movePedalboardItemBefore(instanceId, topPedalItem.instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} }
@@ -428,7 +428,7 @@ const PedalBoardView =
let lastTop = item.topChildren[item.topChildren.length - 1]; let lastTop = item.topChildren[item.topChildren.length - 1];
if (clientX >= lastTop.bounds.right && clientX < item.bounds.right - CELL_WIDTH / 2) { if (clientX >= lastTop.bounds.right && clientX < item.bounds.right - CELL_WIDTH / 2) {
if (lastTop.pedalItem) { if (lastTop.pedalItem) {
this.model.movePedalBoardItemAfter(instanceId, lastTop.pedalItem.instanceId); this.model.movePedalboardItemAfter(instanceId, lastTop.pedalItem.instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} }
@@ -441,7 +441,7 @@ const PedalBoardView =
if (clientX < item.bottomChildren[0].bounds.x) { if (clientX < item.bottomChildren[0].bounds.x) {
let bottomPedalItem = item.bottomChildren[0].pedalItem; let bottomPedalItem = item.bottomChildren[0].pedalItem;
if (bottomPedalItem) { if (bottomPedalItem) {
this.model.movePedalBoardItemBefore(instanceId, bottomPedalItem.instanceId); this.model.movePedalboardItemBefore(instanceId, bottomPedalItem.instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} }
@@ -450,7 +450,7 @@ const PedalBoardView =
if (clientX >= lastBottom.bounds.right && clientX < item.bounds.right-CELL_WIDTH/2) if (clientX >= lastBottom.bounds.right && clientX < item.bounds.right-CELL_WIDTH/2)
{ {
if (lastBottom.pedalItem) { if (lastBottom.pedalItem) {
this.model.movePedalBoardItemAfter(instanceId, lastBottom.pedalItem.instanceId); this.model.movePedalboardItemAfter(instanceId, lastBottom.pedalItem.instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} }
@@ -461,11 +461,11 @@ const PedalBoardView =
} else if (item.bounds.contains(clientX, clientY)) { } else if (item.bounds.contains(clientX, clientY)) {
if (item.isStart()) { if (item.isStart()) {
this.model.movePedalBoardItemToStart(instanceId); this.model.movePedalboardItemToStart(instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} else if (item.isEnd()) { } else if (item.isEnd()) {
this.model.movePedalBoardItemToEnd(instanceId); this.model.movePedalboardItemToEnd(instanceId);
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
} else { } else {
@@ -473,11 +473,11 @@ const PedalBoardView =
{ {
let margin = (CELL_WIDTH - FRAME_SIZE) / 2; let margin = (CELL_WIDTH - FRAME_SIZE) / 2;
if (clientX < item.bounds.x + margin) { 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) { } else if (clientX > item.bounds.right - margin) {
this.model.movePedalBoardItemAfter(instanceId, item.pedalItem.instanceId); this.model.movePedalboardItemAfter(instanceId, item.pedalItem.instanceId);
} else { } else {
this.model.movePedalBoardItem(instanceId, item.pedalItem.instanceId); this.model.movePedalboardItem(instanceId, item.pedalItem.instanceId);
} }
this.setSelection(instanceId); this.setSelection(instanceId);
return; return;
@@ -487,23 +487,23 @@ const PedalBoardView =
} }
} }
// delete the plugin. // delete the plugin.
let newId = this.model.setPedalBoardItemEmpty(instanceId); let newId = this.model.setPedalboardItemEmpty(instanceId);
this.setSelection(newId); this.setSelection(newId);
} }
onPedalBoardChanged(value?: PedalBoard) { onPedalboardChanged(value?: Pedalboard) {
this.setState({ pedalBoard: value }); this.setState({ pedalboard: value });
} }
componentDidMount() { componentDidMount() {
this.scrollRef.current!.addEventListener("touchstart",this.handleTouchStart, {passive: false}); this.scrollRef.current!.addEventListener("touchstart",this.handleTouchStart, {passive: false});
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
} }
componentWillUnmount() { componentWillUnmount() {
this.scrollRef.current!.removeEventListener("touchstart",this.handleTouchStart); 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 { offsetLayout_(layoutItems: PedalLayout[], offset: number): void {
@@ -523,7 +523,7 @@ const PedalBoardView =
if (layoutItem.pedalItem === undefined) { if (layoutItem.pedalItem === undefined) {
throw new Error("Invalid splitter"); throw new Error("Invalid splitter");
} }
let split = layoutItem.pedalItem as PedalBoardSplitItem; let split = layoutItem.pedalItem as PedalboardSplitItem;
if (split.getSplitType() === SplitType.Ab) { if (split.getSplitType() === SplitType.Ab) {
if (split.isASelected()) { if (split.isASelected()) {
return "img/fx_split_a.svg"; return "img/fx_split_a.svg";
@@ -706,7 +706,7 @@ const PedalBoardView =
let yTop = item.topConnectorY; let yTop = item.topConnectorY;
let yBottom = item.bottomConnectorY; let yBottom = item.bottomConnectorY;
//let isStereo = item.stereoOutput; //let isStereo = item.stereoOutput;
let split = item.pedalItem as PedalBoardSplitItem; let split = item.pedalItem as PedalboardSplitItem;
let topEnabled = enabled && split.isASelected(); let topEnabled = enabled && split.isASelected();
let bottomEnabled = enabled && split.isBSelected(); let bottomEnabled = enabled && split.isBSelected();
@@ -889,7 +889,7 @@ const PedalBoardView =
// actually not here anymore. :-/ It has a reactive definition in MainPage.tsx now. // actually not here anymore. :-/ It has a reactive definition in MainPage.tsx now.
while (el) while (el)
{ {
if (el.id === "pedalBoardScroll") if (el.id === "pedalboardScroll")
{ {
return el as HTMLDivElement; return el as HTMLDivElement;
} }
@@ -930,7 +930,7 @@ const PedalBoardView =
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
let item = layoutChain[i]; let item = layoutChain[i];
if (item.isSplitter()) { 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.renderSplitConnectors(output, item, enabled, i === length - 1 && shortSplitOutput,);
this.renderConnectors(output, item.topChildren, enabled && splitter.isASelected(), false); this.renderConnectors(output, item.topChildren, enabled && splitter.isASelected(), false);
this.renderConnectors(output, item.bottomChildren, enabled && splitter.isBSelected(), false); this.renderConnectors(output, item.bottomChildren, enabled && splitter.isBSelected(), false);
@@ -942,15 +942,14 @@ const PedalBoardView =
} }
} }
renderConnectorFrame(layoutChain: PedalLayout[], layoutSize: LayoutSize): ReactNode { renderConnectorFrame(layoutChain: PedalLayout[], layoutSize: LayoutSize): ReactNode {
let output: ReactNode[] = []; let outputs: ReactNode[] = [];
this.renderConnectors(output, layoutChain, true, false); this.renderConnectors(outputs, layoutChain, true, false);
return ( return (
<svg width={layoutSize.width} height={layoutSize.height} <svg width={layoutSize.width} height={layoutSize.height}
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}> xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
<g fill="none"> <g fill="none">
{ {
output outputs
} }
</g> </g>
@@ -1065,7 +1064,7 @@ const PedalBoardView =
let topInputs = item.topChildren[0].numberOfInputs; let topInputs = item.topChildren[0].numberOfInputs;
let bottomInputs = item.bottomChildren[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) if (splitItem.getSplitType() !== SplitType.Lr)
{ {
item.numberOfInputs = CalculateConnection(item.numberOfInputs, Math.max(topInputs,bottomInputs)); item.numberOfInputs = CalculateConnection(item.numberOfInputs, Math.max(topInputs,bottomInputs));
@@ -1102,7 +1101,7 @@ const PedalBoardView =
for (let i = 0; i < layoutChain.length; ++i) { for (let i = 0; i < layoutChain.length; ++i) {
let item = layoutChain[i]; let item = layoutChain[i];
if (item.isSplitter()) { if (item.isSplitter()) {
let splitter = item.pedalItem as PedalBoardSplitItem; let splitter = item.pedalItem as PedalboardSplitItem;
item.numberOfInputs = numberOfInputs; item.numberOfInputs = numberOfInputs;
let chainInputs = numberOfInputs; let chainInputs = numberOfInputs;
@@ -1156,7 +1155,7 @@ const PedalBoardView =
render() { render() {
const { classes } = this.props; 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 start = PedalLayout.Start();
let end = PedalLayout.End(); let end = PedalLayout.End();
if (layoutChain.length !== 0) if (layoutChain.length !== 0)
@@ -1187,4 +1186,4 @@ const PedalBoardView =
} }
); );
export default PedalBoardView export default PedalboardView
+421 -370
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 createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { UiPlugin, UiControl, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin'; import { UiPlugin, UiControl, PiPedalFileProperty} from './Lv2Plugin';
import { import {
PedalBoard, PedalBoardItem, ControlValue,PropertyValue Pedalboard, PedalboardItem, ControlValue
} from './PedalBoard'; } from './Pedalboard';
import PluginControl from './PluginControl'; import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import VuMeter from './VuMeter'; import VuMeter from './VuMeter';
@@ -36,6 +36,7 @@ import Typography from '@mui/material/Typography';
import FullScreenIME from './FullScreenIME'; import FullScreenIME from './FullScreenIME';
import FilePropertyControl from './FilePropertyControl'; import FilePropertyControl from './FilePropertyControl';
import FilePropertyDialog from './FilePropertyDialog'; import FilePropertyDialog from './FilePropertyDialog';
import JsonAtom from './JsonAtom';
export const StandardItemSize = { width: 80, height: 110 }; export const StandardItemSize = { width: 80, height: 110 };
@@ -188,7 +189,7 @@ export interface ControlViewCustomization {
export interface PluginControlViewProps extends WithStyles<typeof styles> { export interface PluginControlViewProps extends WithStyles<typeof styles> {
theme: Theme; theme: Theme;
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
customization?: ControlViewCustomization; customization?: ControlViewCustomization;
customizationId?: number; customizationId?: number;
} }
@@ -224,35 +225,32 @@ const PluginControlView =
dialogFileValue: "" dialogFileValue: ""
} }
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
this.onControlValueChanged = this.onControlValueChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this);
this.onPreviewChange = this.onPreviewChange.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this);
} }
onPreviewChange(key: string, value: number): void { 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 { onControlValueChanged(key: string, value: number): void {
this.model.setPedalBoardControlValue(this.props.instanceId, key, value); this.model.setPedalboardControl(this.props.instanceId, key, value);
}
onPropertyValueChanged(propertyUri: string, value: any): void {
this.model.setPedalBoardPropertyValue(this.props.instanceId, propertyUri, value);
} }
onPedalBoardChanged(value?: PedalBoard) { onPedalboardChanged(value?: Pedalboard) {
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId); //let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId);
//this.setState({ pedalBoardItem: item }); //this.setState({ pedalboardItem: item });
} }
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
} }
componentWillUnmount() { componentWillUnmount() {
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
super.componentWillUnmount(); super.componentWillUnmount();
} }
@@ -285,22 +283,10 @@ const PluginControlView =
}); });
} }
makeFilePropertyUI(fileProperty: PiPedalFileProperty, propertyValues: PropertyValue[]): ReactNode { makeFilePropertyUI(fileProperty: PiPedalFileProperty): ReactNode {
let propertyValue: PropertyValue | undefined = undefined;
for (let i = 0; i < propertyValues.length; ++i) {
if (propertyValues[i].propertyUri === fileProperty.patchProperty) {
propertyValue = propertyValues[i];
break;
}
}
if (!propertyValue) {
propertyValue = new PropertyValue();
propertyValue.value = fileProperty.defaultFile;
propertyValue.propertyUri = fileProperty.patchProperty;
}
return (( return ((
<FilePropertyControl instanceId={this.props.instanceId} value={propertyValue.value} <FilePropertyControl instanceId={this.props.instanceId}
fileProperty={fileProperty} fileProperty={fileProperty}
onFileClick={(fileProperty,selectedFile) => { onFileClick={(fileProperty,selectedFile) => {
this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: 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 = []; let result: ControlNodes = [];
for (let i = 0; i < plugin.controls.length; ++i) { for (let i = 0; i < plugin.controls.length; ++i) {
@@ -350,7 +336,7 @@ const PluginControlView =
pluginControl = plugin.controls[i]; pluginControl = plugin.controls[i];
if (!pluginControl.not_on_gui) { if (!pluginControl.not_on_gui) {
groupControls.push( groupControls.push(
this.makeStandardControl(pluginControl, controlValues) this.makeStandardControl(pluginControl,controlValues)
) )
} }
} }
@@ -359,7 +345,7 @@ const PluginControlView =
) )
} else { } else {
result.push( 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) { for (let i = 0; i < plugin.fileProperties.length; ++i) {
let fileProperty = plugin.fileProperties[i]; let fileProperty = plugin.fileProperties[i];
result.push( result.push(
this.makeFilePropertyUI(fileProperty, propertyValues) this.makeFilePropertyUI(fileProperty)
); );
} }
return result; return result;
@@ -383,7 +369,7 @@ const PluginControlView =
} }
onImeValueChange(key: string, value: number) { onImeValueChange(key: string, value: number) {
this.model.setPedalBoardControlValue(this.props.instanceId, key, value); this.model.setPedalboardControl(this.props.instanceId, key, value);
this.onImeClose(); this.onImeClose();
} }
onImeClose() { onImeClose() {
@@ -427,7 +413,7 @@ const PluginControlView =
result.push(( result.push((
<div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}> <div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
<div className={classes.portGroupTitle}> <div className={classes.portGroupTitle}>
<Typography variant="caption">{controlGroup.name}</Typography> <Typography noWrap variant="caption">{controlGroup.name}</Typography>
</div> </div>
<div className={classes.portGroupControls} > <div className={classes.portGroupControls} >
{ {
@@ -452,16 +438,15 @@ const PluginControlView =
render(): ReactNode { render(): ReactNode {
let classes = this.props.classes; 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>); return (<div className={classes.frame} ></div>);
let controlValues = pedalBoardItem.controlValues; let controlValues = pedalboardItem.controlValues;
let propertyValues = pedalBoardItem.propertyValues;
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri)); let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri));
controlValues = this.filterNotOnGui(controlValues, plugin); controlValues = this.filterNotOnGui(controlValues, plugin);
@@ -472,7 +457,7 @@ const PluginControlView =
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
let controlNodes: ControlNodes; let controlNodes: ControlNodes;
controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues); controlNodes = this.getStandardControlNodes(plugin, controlValues);
if (this.props.customization) { if (this.props.customization) {
// allow wrapper class to insert/remove/rebuild controls. // allow wrapper class to insert/remove/rebuild controls.
@@ -484,10 +469,10 @@ const PluginControlView =
return ( return (
<div className={classes.frame}> <div className={classes.frame}>
<div className={classes.vuMeterL}> <div className={classes.vuMeterL}>
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} /> <VuMeter display="input" instanceId={pedalboardItem.instanceId} />
</div> </div>
<div className={vuMeterRClass}> <div className={vuMeterRClass}>
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} /> <VuMeter display="output" instanceId={pedalboardItem.instanceId} />
</div> </div>
<div className={gridClass} > <div className={gridClass} >
{ {
@@ -503,9 +488,21 @@ const PluginControlView =
<FilePropertyDialog open={this.state.showFileDialog} <FilePropertyDialog open={this.state.showFileDialog}
fileProperty={this.state.dialogFileProperty} fileProperty={this.state.dialogFileProperty}
selectedFile={this.state.dialogFileValue} selectedFile={this.state.dialogFileValue}
onClose={()=> { this.setState({ showFileDialog: false});}} onCancel={()=> { this.setState({ showFileDialog: false});}}
onOk={(fileProperty,selectedFile)=> { 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});} 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="" /> <img src="img/ic_pluginpreset2.svg" className={classes.itemIcon} alt="" />
</div> </div>
<div className={classes.itemLabel}> <div className={classes.itemLabel}>
<Typography> <Typography> noWrap
{presetEntry.label} {presetEntry.label}
</Typography> </Typography>
</div> </div>
@@ -312,8 +312,8 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
this.setState({ renameOpen: false }); this.setState({ renameOpen: false });
} }
getPluginUri() : string { getPluginUri() : string {
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId); let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
return pedalBoardItem.uri; return pedalboardItem.uri;
} }
handleCopy() { handleCopy() {
let item = this.props.presets.getItem(this.state.selectedItem); let item = this.props.presets.getItem(this.state.selectedItem);
@@ -359,7 +359,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
> >
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.dialogTitle}> <Typography noWrap variant="h6" className={classes.dialogTitle}>
{title} {title}
</Typography> </Typography>
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} > <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() { render() {
let classes = this.props.classes; let classes = this.props.classes;
let isConfigValid = this.state.jackConfiguration.isValid; let isConfigValid = this.state.jackConfiguration.isValid;
@@ -544,40 +548,41 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
}} }}
> >
<div > <div >
{this.props.onboarding ? {this.props.onboarding &&
( (
<div> <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. 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>
<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. Access and modify these settings later by selecting the <i>Settings</i> menu item on the main menu.
</Typography> </Typography>
</div> <Divider />
):
(
<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>
)
}
</div> </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 !== "" && {this.state.jackConfiguration.errorState !== "" &&
( (
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}> <div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
@@ -782,7 +787,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Divider/> <Divider/>
<div style={{display: "grid", placeContent: "end", maxWidth: 550, marginLeft: 16,marginRight: 16, <div style={{display: "grid", placeContent: "end", maxWidth: 550, marginLeft: 16,marginRight: 16,
marginTop: 16,marginBottom: 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>
</div> </div>
) )
+22 -22
View File
@@ -25,8 +25,8 @@ import withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { UiPlugin } from './Lv2Plugin'; import { UiPlugin } from './Lv2Plugin';
import { import {
PedalBoard, PedalBoardSplitItem, ControlValue, Pedalboard, PedalboardSplitItem, ControlValue,
} from './PedalBoard'; } from './Pedalboard';
import PluginControl from './PluginControl'; import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import VuMeter from './VuMeter'; import VuMeter from './VuMeter';
@@ -97,7 +97,7 @@ const styles = (theme: Theme) => createStyles({
interface SplitControlViewProps extends WithStyles<typeof styles> { interface SplitControlViewProps extends WithStyles<typeof styles> {
theme: Theme; theme: Theme;
instanceId: number; instanceId: number;
item: PedalBoardSplitItem; item: PedalboardSplitItem;
} }
type SplitControlViewState = { type SplitControlViewState = {
landscapeGrid: boolean; landscapeGrid: boolean;
@@ -117,32 +117,32 @@ const SplitControlView =
landscapeGrid: false landscapeGrid: false
} }
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
this.onValueChanged = this.onValueChanged.bind(this); this.onValueChanged = this.onValueChanged.bind(this);
this.onPreviewChange = this.onPreviewChange.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this);
} }
onPreviewChange(key: string, value: number): void { 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 { 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) { onPedalboardChanged(value?: Pedalboard) {
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId); //let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId);
//this.setState({ pedalBoardItem: item }); //this.setState({ pedalboardItem: item });
} }
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
} }
componentWillUnmount() { componentWillUnmount() {
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
super.componentWillUnmount(); super.componentWillUnmount();
} }
@@ -162,29 +162,29 @@ const SplitControlView =
render(): ReactNode { render(): ReactNode {
let classes = this.props.classes; 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>); return (<div className={classes.frame}></div>);
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid; let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
let typeValue = pedalBoardItem.getControl(PedalBoardSplitItem.TYPE_KEY); let typeValue = pedalboardItem.getControl(PedalboardSplitItem.TYPE_KEY);
let mixValue = pedalBoardItem.getControl(PedalBoardSplitItem.MIX_KEY); let mixValue = pedalboardItem.getControl(PedalboardSplitItem.MIX_KEY);
let selectValue = pedalBoardItem.getControl(PedalBoardSplitItem.SELECT_KEY); let selectValue = pedalboardItem.getControl(PedalboardSplitItem.SELECT_KEY);
let panLValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANL_KEY); let panLValue = pedalboardItem.getControl(PedalboardSplitItem.PANL_KEY);
let volumeLValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLL_KEY); let volumeLValue = pedalboardItem.getControl(PedalboardSplitItem.VOLL_KEY);
let panRValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANR_KEY); let panRValue = pedalboardItem.getControl(PedalboardSplitItem.PANR_KEY);
let volumeRValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLR_KEY); let volumeRValue = pedalboardItem.getControl(PedalboardSplitItem.VOLR_KEY);
return ( return (
<div className={classes.frame}> <div className={classes.frame}>
<div className={classes.vuMeterL}> <div className={classes.vuMeterL}>
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} /> <VuMeter display="input" instanceId={pedalboardItem.instanceId} />
</div> </div>
<div className={vuMeterRClass}> <div className={vuMeterRClass}>
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} /> <VuMeter display="output" instanceId={pedalboardItem.instanceId} />
</div> </div>
<div className={gridClass} > <div className={gridClass} >
<div style={{ flex: "0 0 auto" }}> <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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import {UiControl,ScalePoint} from './Lv2Plugin'; import {UiControl,ScalePoint} from './Lv2Plugin';
import {PedalBoardSplitItem} from './PedalBoard'; import {PedalboardSplitItem} from './Pedalboard';
import Units from './Units' import Units from './Units'
export const SplitTypeControl: UiControl = new UiControl().deserialize({ export const SplitTypeControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.TYPE_KEY, symbol: PedalboardSplitItem.TYPE_KEY,
name: "Type", name: "Type",
index: 0, index: 0,
min_value: 0, min_value: 0,
@@ -47,7 +47,7 @@ export const SplitTypeControl: UiControl = new UiControl().deserialize({
}); });
export const SplitAbControl: UiControl = new UiControl().deserialize({ export const SplitAbControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.SELECT_KEY, symbol: PedalboardSplitItem.SELECT_KEY,
name: "Select", name: "Select",
index: 1, index: 1,
min_value: 0, min_value: 0,
@@ -70,7 +70,7 @@ export const SplitAbControl: UiControl = new UiControl().deserialize({
export const SplitMixControl: UiControl = new UiControl().deserialize({ export const SplitMixControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.MIX_KEY, symbol: PedalboardSplitItem.MIX_KEY,
name: "Mix", name: "Mix",
index: 2, index: 2,
min_value: -1, min_value: -1,
@@ -87,7 +87,7 @@ export const SplitMixControl: UiControl = new UiControl().deserialize({
}); });
export const SplitPanLeftControl: UiControl = new UiControl().deserialize({ export const SplitPanLeftControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.PANL_KEY, symbol: PedalboardSplitItem.PANL_KEY,
name: "Pan Top", name: "Pan Top",
index: 3, index: 3,
min_value: -1, min_value: -1,
@@ -104,7 +104,7 @@ export const SplitPanLeftControl: UiControl = new UiControl().deserialize({
}); });
export const SplitVolLeftControl: UiControl = new UiControl().deserialize({ export const SplitVolLeftControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.VOLL_KEY, symbol: PedalboardSplitItem.VOLL_KEY,
name: "Vol Top", name: "Vol Top",
index: 4, index: 4,
min_value: -60, min_value: -60,
@@ -128,7 +128,7 @@ export const SplitVolLeftControl: UiControl = new UiControl().deserialize({
export const SplitPanRightControl: UiControl = new UiControl().deserialize({ export const SplitPanRightControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.PANR_KEY, symbol: PedalboardSplitItem.PANR_KEY,
name: "Pan Bottom", name: "Pan Bottom",
index: 5, index: 5,
min_value: -1, min_value: -1,
@@ -145,7 +145,7 @@ export const SplitPanRightControl: UiControl = new UiControl().deserialize({
}); });
export const SplitVolRightControl: UiControl = new UiControl().deserialize({ export const SplitVolRightControl: UiControl = new UiControl().deserialize({
symbol: PedalBoardSplitItem.VOLR_KEY, symbol: PedalboardSplitItem.VOLR_KEY,
name: "Vol Bottom", name: "Vol Bottom",
index: 6, index: 6,
min_value: -60, min_value: -60,
+2 -2
View File
@@ -20,7 +20,7 @@
import StringBuilder from './StringBuilder'; import StringBuilder from './StringBuilder';
class SvgPathBuilder { class SvgPathBuilder {
_sb: StringBuilder = new StringBuilder(); private _sb: StringBuilder = new StringBuilder();
moveTo(x: number, y: number): SvgPathBuilder moveTo(x: number, y: number): SvgPathBuilder
{ {
@@ -37,7 +37,7 @@ class SvgPathBuilder {
this._sb.append('C').append(x1).append(',').append(y1) this._sb.append('C').append(x1).append(',').append(y1)
.append(' ').append(x2).append(',').append(y2) .append(' ').append(x2).append(',').append(y2)
.append(' ').append(x).append(',').append(y).append(' '); .append(' ').append(x).append(',').append(y).append(' ');
return this;
} }
closePath(): SvgPathBuilder { closePath(): SvgPathBuilder {
this._sb.append("Z "); this._sb.append("Z ");
+4 -4
View File
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
import IControlViewFactory from './IControlViewFactory'; import IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobFrequencyResponseView from './ToobFrequencyResponseView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView';
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
interface ToobCabSimProps extends WithStyles<typeof styles> { interface ToobCabSimProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobCabSimState { interface ToobCabSimState {
@@ -83,8 +83,8 @@ const ToobCabSimView =
class ToobCabSimViewFactory implements IControlViewFactory { class ToobCabSimViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-cab-sim"; uri: string = "http://two-play.com/plugins/toob-cab-sim";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobCabSimView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); return (<ToobCabSimView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
} }
+59 -66
View File
@@ -30,10 +30,11 @@ import Utility from './Utility';
import SvgPathBuilder from './SvgPathBuilder'; import SvgPathBuilder from './SvgPathBuilder';
const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector"; const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector";
const PLOT_WIDTH = StandardItemSize.width*2-16; const PLOT_WIDTH = StandardItemSize.width * 2 - 16;
const PLOT_HEIGHT = StandardItemSize.height-12; const PLOT_HEIGHT = StandardItemSize.height - 12;
const styles = (theme: Theme) => createStyles({ const styles = (theme: Theme) => createStyles({
frame: { frame: {
@@ -73,33 +74,31 @@ const ToobFrequencyResponseView =
}; };
this.pathRef = React.createRef(); this.pathRef = React.createRef();
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
this.onStateChanged = this.onStateChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this);
} }
onStateChanged() onStateChanged() {
{ if (this.model.state.get() === State.Ready) {
if (this.model.state.get() === State.Ready)
{
this.requestDeferred = false; this.requestDeferred = false;
this.requestOutstanding = false; this.requestOutstanding = false;
this.updateAllWaveShapes(); // after a reconnect. this.updateAllWaveShapes(); // after a reconnect.
} }
} }
onPedalBoardChanged() onPedalboardChanged() {
{
this.updateAllWaveShapes(); this.updateAllWaveShapes();
} }
componentDidMount() private mounted: boolean = false;
{ componentDidMount() {
this.mounted = true;
this.model.state.addOnChangedHandler(this.onStateChanged); this.model.state.addOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
this.updateAllWaveShapes(); this.updateAllWaveShapes();
} }
componentWillUnmount() componentWillUnmount() {
{ this.mounted = false;
this.model.state.removeOnChangedHandler(this.onStateChanged); this.model.state.removeOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
} }
componentDidUpdate() { componentDidUpdate() {
@@ -113,7 +112,7 @@ const ToobFrequencyResponseView =
dbTickSpacingOpt: number = 10; dbTickSpacingOpt: number = 10;
MIN_DB_AF: number = Math.pow(10,-192/20); MIN_DB_AF: number = Math.pow(10, -192 / 20);
fMin: number = 30; fMin: number = 30;
fMax: number = 20000; fMax: number = 20000;
logMin: number = Math.log(this.fMin); logMin: number = Math.log(this.fMin);
@@ -121,15 +120,14 @@ const ToobFrequencyResponseView =
// Size of the SVG element. // Size of the SVG element.
xMin: number = 0; xMin: number = 0;
xMax: number = PLOT_WIDTH+4; xMax: number = PLOT_WIDTH + 4;
yMin: number = 0; yMin: number = 0;
yMax: number = PLOT_HEIGHT; yMax: number = PLOT_HEIGHT;
dbTickSpacing: number = this.dbTickSpacingOpt; dbTickSpacing: number = this.dbTickSpacingOpt;
toX(frequency: number): number toX(frequency: number): number {
{
var logV = Math.log(frequency); 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 { toY(value: number): number {
@@ -139,24 +137,23 @@ const ToobFrequencyResponseView =
if (value < this.MIN_DB_AF) { if (value < this.MIN_DB_AF) {
db = -192.0; db = -192.0;
} else { } 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; return y;
} }
onFrequencyResponseUpdated(data: number[]) { onFrequencyResponseUpdated(data: number[]) {
if (!this.mounted) return;
let pathBuilder = new SvgPathBuilder(); let pathBuilder = new SvgPathBuilder();
if (data.length > 2) if (data.length > 2) {
{ pathBuilder.moveTo(this.toX(data[0]), this.toY(data[1]))
pathBuilder.moveTo(this.toX(data[0]),this.toY(data[1])) for (let i = 2; i < data.length; i += 2) {
for (let i = 2; i < data.length; i += 2) pathBuilder.lineTo(this.toX(data[i]), this.toY(data[i + 1]));
{
pathBuilder.lineTo(this.toX(data[i]),this.toY(data[i+1]));
} }
} }
this.currentPath = pathBuilder.toString(); this.currentPath = pathBuilder.toString();
this.setState({path: this.currentPath}); this.setState({ path: this.currentPath });
} }
@@ -166,21 +163,23 @@ const ToobFrequencyResponseView =
return; return;
} }
this.requestOutstanding = true; this.requestOutstanding = true;
this.model.getLv2Parameter<number[]>(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI) this.model.getPatchProperty<any>(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI)
.then((data) => { .then((json) => {
this.onFrequencyResponseUpdated(data); if (json && json.otype_ === "Vector" && json.vtype_ === "Float") {
if (this.requestDeferred) { this.onFrequencyResponseUpdated(json.value as number[]);
Utility.delay(10) // take breath if (this.requestDeferred) {
.then( Utility.delay(10) // take breath
() => { .then(
this.requestOutstanding = false; () => {
this.requestDeferred = false; this.requestOutstanding = false;
this.updateAllWaveShapes(); this.requestDeferred = false;
} this.updateAllWaveShapes();
}
); );
} else { } else {
this.requestOutstanding = false; this.requestOutstanding = false;
}
} }
}).catch(error => { }).catch(error => {
// assume the connection was lost. We'll get saved by a reconnect. // assume the connection was lost. We'll get saved by a reconnect.
@@ -202,36 +201,30 @@ const ToobFrequencyResponseView =
let result: React.ReactNode[] = []; let result: React.ReactNode[] = [];
for (var db = Math.ceil(this.dbMax/this.dbTickSpacing)*this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing ) 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);
var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin); if (db === 0) {
if (db === 0)
{
result.push( result.push(
this.majorGridLine(this.xMin,y,this.xMax,y) this.majorGridLine(this.xMin, y, this.xMax, y)
); );
} else { } else {
result.push( result.push(
this.gridLine(this.xMin,y,this.xMax,y) this.gridLine(this.xMin, y, this.xMax, y)
); );
} }
} }
let decade0 = Math.pow(10,Math.floor(Math.log10(this.fMin))); let decade0 = Math.pow(10, Math.floor(Math.log10(this.fMin)));
for (var decade = decade0; decade < this.fMax; decade *= 10) for (var decade = decade0; decade < this.fMax; decade *= 10) {
{ for (var i = 1; i <= 10; ++i) {
for (var i = 1; i <= 10; ++i) var f = decade * i;
{ if (f > this.fMin && f < this.fMax) {
var f = decade*i;
if (f > this.fMin && f < this.fMax)
{
var x = this.toX(f); var x = this.toX(f);
if (i === 10) if (i === 10) {
{ result.push(this.majorGridLine(x, this.yMin, x, this.yMax));
result.push(this.majorGridLine(x,this.yMin,x,this.yMax));
} else { } else {
result.push(this.gridLine(x,this.yMin,x,this.yMax)); result.push(this.gridLine(x, this.yMin, x, this.yMax));
} }
} }
} }
@@ -244,7 +237,7 @@ const ToobFrequencyResponseView =
render() { render() {
// deliberately reversed to flip up and down. // 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.dbMin = this.props.maxDb ?? this.dbMaxDefault;
this.fMin = this.props.minFrequency ?? 30; this.fMin = this.props.minFrequency ?? 30;
@@ -257,7 +250,7 @@ const ToobFrequencyResponseView =
return ( return (
<div className={classes.frame} > <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"> <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} /> <path d={this.state.path} ref={this.pathRef} />
</svg> </svg>
+4 -4
View File
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
import IControlViewFactory from './IControlViewFactory'; import IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobFrequencyResponseView from './ToobFrequencyResponseView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView';
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
interface ToobInputStageProps extends WithStyles<typeof styles> { interface ToobInputStageProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobInputStageState { interface ToobInputStageState {
@@ -84,8 +84,8 @@ const ToobInputStageView =
class ToobInputStageViewFactory implements IControlViewFactory { class ToobInputStageViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-input_stage"; uri: string = "http://two-play.com/plugins/toob-input_stage";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobInputStageView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); 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 IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel,MonitorPortHandle } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel,MonitorPortHandle } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobFrequencyResponseView from './ToobFrequencyResponseView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView';
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
interface ToobMLProps extends WithStyles<typeof styles> { interface ToobMLProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobMLState { interface ToobMLState {
@@ -135,8 +135,8 @@ const ToobMLView =
class ToobMLViewFactory implements IControlViewFactory { class ToobMLViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-ml"; uri: string = "http://two-play.com/plugins/toob-ml";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobMLView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); 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 IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView'; import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView';
let POWERSTAGE_UI_URI = "http://two-play.com/plugins/toob-power-stage-2#uiState";
const styles = (theme: Theme) => createStyles({ const styles = (theme: Theme) => createStyles({
}); });
interface ToobPowerstage2Props extends WithStyles<typeof styles> { interface ToobPowerstage2Props extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobPowerstage2State { interface ToobPowerstage2State {
@@ -60,18 +61,11 @@ const ToobPowerstage2View =
uiState: [0,0,0,0,0,0,0,0] uiState: [0,0,0,0,0,0,0,0]
} }
this.onStateChanged = this.onStateChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this);
this.onAtomOutput = this.onAtomOutput.bind(this);
} }
listeningForOutput: boolean = false; listeningForOutput: boolean = false;
atomOutputHandle?: ListenHandle; 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 { maybeListenForAtomOutput(): void {
let listenForOutput = this.isReady && this.isControlMounted; let listenForOutput = this.isReady && this.isControlMounted;
@@ -80,11 +74,22 @@ const ToobPowerstage2View =
this.listeningForOutput = listenForOutput; this.listeningForOutput = listenForOutput;
if (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 { } else {
if (this.atomOutputHandle) if (this.atomOutputHandle)
{ {
this.model.cancelListenForAtomOutput(this.atomOutputHandle); this.model.cancelMonitorPatchProperty(this.atomOutputHandle);
this.atomOutputHandle = undefined; this.atomOutputHandle = undefined;
} }
@@ -180,8 +185,8 @@ const ToobPowerstage2View =
class ToobPowerstage2ViewFactory implements IControlViewFactory { class ToobPowerstage2ViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-power-stage-2"; uri: string = "http://two-play.com/plugins/toob-power-stage-2";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobPowerstage2View instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); 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 IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobSpectrumResponseView from './ToobSpectrumResponseView'; import ToobSpectrumResponseView from './ToobSpectrumResponseView';
@@ -38,7 +38,7 @@ const styles = (theme: Theme) => createStyles({
interface ToobSpectrumAnalyzerProps extends WithStyles<typeof styles> { interface ToobSpectrumAnalyzerProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobSpectrumAnalyzerState { interface ToobSpectrumAnalyzerState {
@@ -84,8 +84,8 @@ const ToobSpectrumAnalyzerView =
class ToobSpectrumAnalyzerViewFactory implements IControlViewFactory { class ToobSpectrumAnalyzerViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-spectrum"; uri: string = "http://two-play.com/plugins/toob-spectrum";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobSpectrumAnalyzerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); return (<ToobSpectrumAnalyzerView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
} }
+145 -136
View File
@@ -25,22 +25,30 @@ import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles'; import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel, State, ListenHandle } from "./PiPedalModel";
import { StandardItemSize } from './PluginControlView'; 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({ const styles = (theme: Theme) => createStyles({
frame: { frame: {
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444", width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#333",
borderRadius: 6, borderRadius: 6,
marginTop: 12, marginLeft: 8, marginRight: 8, marginTop: 12, marginLeft: 8, marginRight: 8,
overflow: "hidden"
},
frameShadow: {
width: PLOT_WIDTH, height: PLOT_HEIGHT,
borderRadius: 6,
boxShadow: "1px 4px 8px #000 inset" boxShadow: "1px 4px 8px #000 inset"
}, },
}); });
@@ -51,6 +59,7 @@ interface ToobSpectrumResponseProps extends WithStyles<typeof styles> {
} }
interface ToobSpectrumResponseState { interface ToobSpectrumResponseState {
path: string; path: string;
holdPath: string;
minF: number; minF: number;
maxF: number; maxF: number;
} }
@@ -72,9 +81,8 @@ const ToobSpectrumResponseView =
let minF = 60; let minF = 60;
let maxF = 18000; let maxF = 18000;
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
if (plugin) if (plugin) {
{
minF = plugin.getControlValue("minF"); minF = plugin.getControlValue("minF");
maxF = plugin.getControlValue("maxF"); maxF = plugin.getControlValue("maxF");
} }
@@ -82,62 +90,59 @@ const ToobSpectrumResponseView =
this.state = { this.state = {
path: "", path: "",
holdPath: "",
minF: minF, minF: minF,
maxF: maxF maxF: maxF
}; };
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
this.onStateChanged = this.onStateChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this);
} }
onStateChanged() onStateChanged() {
{ if (this.model.state.get() === State.Ready) {
if (this.model.state.get() === State.Ready)
{
this.requestDeferred = false; this.requestDeferred = false;
this.requestOutstanding = false; this.requestOutstanding = false;
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
if (plugin) if (plugin) {
{
let minF = plugin.getControlValue("minF"); let minF = plugin.getControlValue("minF");
let maxF = plugin.getControlValue("maxF"); 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.isReady = this.model.state.get() === State.Ready;
this.maybeStartClock(); // after a reconnect. this.maybeStartMonitoring(); // after a reconnect.
} }
onPedalBoardChanged() onPedalboardChanged() {
{ // rebind our monitoring hooks.
this.maybeStartClock(); this.maybeStartMonitoring(false);
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId); this.maybeStartMonitoring(true);
if (plugin)
{ let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
if (plugin) {
let minF = plugin.getControlValue("minF"); let minF = plugin.getControlValue("minF");
let maxF = plugin.getControlValue("maxF"); let maxF = plugin.getControlValue("maxF");
this.setState({minF: minF, maxF: maxF}); this.setState({ minF: minF, maxF: maxF });
} }
} }
mounted: boolean = false; mounted: boolean = false;
isReady: boolean = false; isReady: boolean = false;
componentDidMount() componentDidMount() {
{
this.model.state.addOnChangedHandler(this.onStateChanged); this.model.state.addOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
this.mounted = true; this.mounted = true;
this.isReady = this.model.state.get() === State.Ready; this.isReady = this.model.state.get() === State.Ready;
this.maybeStartClock(); this.maybeStartMonitoring();
} }
componentWillUnmount() componentWillUnmount() {
{
this.mounted = false; this.mounted = false;
this.maybeStartClock(); this.maybeStartMonitoring();
this.model.state.removeOnChangedHandler(this.onStateChanged); this.model.state.removeOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
} }
componentDidUpdate() { componentDidUpdate() {
@@ -147,77 +152,50 @@ const ToobSpectrumResponseView =
// Size of the SVG element. // Size of the SVG element.
xMin: number = 0; xMin: number = 0;
xMax: number = PLOT_WIDTH+4; xMax: number = PLOT_WIDTH + 4;
yMin: number = 0; yMin: number = 0;
yMax: number = PLOT_HEIGHT; yMax: number = PLOT_HEIGHT;
onSpectrumResponseUpdated(svgPath: string) { onSpectrumResponseUpdated(svgPath: string, holdSvgPath: string) {
this.setState({path: svgPath}); this.setState({ path: svgPath, holdPath: holdSvgPath });
} }
isTicking : boolean = false; private isMonitoring: boolean = false;
hClock?: NodeJS.Timeout; private hClock?: NodeJS.Timeout;
hAtomOutput?: ListenHandle; private hAtomOutput?: ListenHandle;
maybeStartClock() { private maybeStartMonitoring(wantsMonitoring: boolean = true) {
let shouldTick = this.isReady && this.mounted; let shouldMonitor = this.isReady && this.mounted && wantsMonitoring;
if (shouldTick !== this.isTicking) if (shouldMonitor !== this.isMonitoring) {
{ this.isMonitoring = shouldMonitor;
this.isTicking = shouldTick; if (shouldMonitor) {
if (shouldTick) this.hAtomOutput = this.model.monitorPatchProperty(
{ this.props.instanceId,
this.hAtomOutput = this.model.listenForAtomOutput(this.props.instanceId, SPECTRUM_RESPONSE_URI,
(instanceId,atomOutput)=> { (instanceId, propertyUri, json) => {
this.onSpectrumResponseUpdated(atomOutput.value as string); if (propertyUri === SPECTRUM_RESPONSE_URI && json.otype_ === "Tuple") {
if (this.requestDeferred) { let values = json.value as string[];
Utility.delay(30) // take breath this.onSpectrumResponseUpdated(values[0], values[1]);
.then(
() => {
this.requestOutstanding = false;
this.requestDeferred = false;
this.requestSpectrum();
}
);
} else {
this.requestOutstanding = false;
} }
}
);
}); this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, true)
this.hClock = setInterval(()=> { .catch((error)=> {});
this.requestSpectrum();
},
1000/15);
} else { } else {
if (this.hAtomOutput)
{ this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, false)
this.model.cancelListenForAtomOutput(this.hAtomOutput); .catch((error)=> {}); // e.g. plugin not found.
if (this.hAtomOutput) {
this.model.cancelMonitorPatchProperty(this.hAtomOutput);
this.hAtomOutput = undefined; 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; numPoints = 200;
@@ -228,22 +206,21 @@ const ToobSpectrumResponseView =
logMaxF = Math.log(this.maxF); logMaxF = Math.log(this.maxF);
toX(f: number): number { toX(f: number): number {
let width = PLOT_WIDTH-8; let width = PLOT_WIDTH - 8;
return (Math.log(f)-this.logMinF)*width/(this.logMaxF-this.logMinF); 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) private minorGrid(): string {
{
let y = height*db/-100; let ss = new SvgPathBuilder();
result.push(
( let width = PLOT_WIDTH;
<line x1={0} y1={y} x2={width} y2={y} key={"db" + db} /> 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; let minF = this.state.minF;
@@ -253,53 +230,85 @@ const ToobSpectrumResponseView =
this.logMinF = Math.log(this.minF); this.logMinF = Math.log(this.minF);
this.logMaxF = Math.log(this.maxF); this.logMaxF = Math.log(this.maxF);
for (let decade = 10; decade < 100000; decade *= 10) for (let decade = 10; decade < 100000; decade *= 10) {
{ for (let minorTick = 1; minorTick <= 5; ++minorTick) {
for (let minorTick = 1; minorTick <= 5; ++ minorTick) let f = decade * minorTick;
{ if (f > minF && f < maxF) {
let f = decade*minorTick;
if (f > minF && f < maxF)
{
let x = this.toX(f); let x = this.toX(f);
if (minorTick === 1) if (minorTick === 1) {
{ ss.moveTo(x,0);
result.push( ss.lineTo(x,height);
(
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} strokeWidth={1}/>
)
);
} else { } else {
result.push( ss.moveTo(x,0);
( ss.lineTo(x,height);
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} />
)
);
} }
} }
} }
} }
return ss.toString();
return result;
} }
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() { render() {
let classes = this.props.classes; let classes = this.props.classes;
return ( return (
<div className={classes.frame} style={{position: "relative"}} > <div className={classes.frame} style={{ position: "relative" }} >
<div style={{position: "absolute", left: 4, top: 4}}> {/* <div style={{ position: "absolute", left: 0, top: 0 }}>
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} stroke="#888" strokeWidth="0.5" opacity="1" <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"
{this.grid()} >
<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> </svg>
</div> </div>
<div style={{position: "absolute", left: 4, top: 4}}> <div style={{ position: "absolute", left: 0, top: 0 }}>
<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" <svg width={PLOT_WIDTH } height={PLOT_HEIGHT} stroke="#FFF" strokeWidth="0.9" opacity="0.3"
> >
<path d={this.state.path} /> <path d={this.minorGrid()} />
</svg> </svg>
</div> </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>); </div>);
} }
+4 -4
View File
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
import IControlViewFactory from './IControlViewFactory'; import IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel,ControlValueChangedHandle } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel,ControlValueChangedHandle } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobFrequencyResponseView from './ToobFrequencyResponseView'; import ToobFrequencyResponseView from './ToobFrequencyResponseView';
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
interface ToobToneStackProps extends WithStyles<typeof styles> { interface ToobToneStackProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
item: PedalBoardItem; item: PedalboardItem;
} }
interface ToobToneStackState { interface ToobToneStackState {
@@ -117,8 +117,8 @@ const ToobToneStackView =
class ToobToneStackViewFactory implements IControlViewFactory { class ToobToneStackViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-tone-stack"; uri: string = "http://two-play.com/plugins/toob-tone-stack";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
return (<ToobToneStackView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />); return (<ToobToneStackView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
} }
+24 -18
View File
@@ -128,7 +128,7 @@ const ToobWaveShapeView =
data: [] data: []
}; };
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
this.onStateChanged = this.onStateChanged.bind(this); this.onStateChanged = this.onStateChanged.bind(this);
this.onControlValueChanged = this.onControlValueChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this);
this.isReady = this.model.state.get() === State.Ready; this.isReady = this.model.state.get() === State.Ready;
@@ -147,7 +147,7 @@ const ToobWaveShapeView =
} }
} }
} }
onPedalBoardChanged() { onPedalboardChanged() {
this.updateWaveShape(); this.updateWaveShape();
} }
_valueChangedHandle?: ControlValueChangedHandle; _valueChangedHandle?: ControlValueChangedHandle;
@@ -158,7 +158,7 @@ const ToobWaveShapeView =
componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
this.model.state.addOnChangedHandler(this.onStateChanged); this.model.state.addOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
this._valueChangedHandle = this.model.addControlValueChangeListener( this._valueChangedHandle = this.model.addControlValueChangeListener(
this.props.instanceId, this.props.instanceId,
this.onControlValueChanged); this.onControlValueChanged);
@@ -180,7 +180,7 @@ const ToobWaveShapeView =
this._valueChangedHandle = undefined; this._valueChangedHandle = undefined;
} }
this.model.state.removeOnChangedHandler(this.onStateChanged); this.model.state.removeOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
} }
onControlValueChanged(key: string, value: number) { onControlValueChanged(key: string, value: number) {
@@ -230,21 +230,27 @@ const ToobWaveShapeView =
return; return;
} }
this.requestOutstanding = true; this.requestOutstanding = true;
this.model.getLv2Parameter<number[]>(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber) this.model.getPatchProperty<any>(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber)
.then((data) => { .then((json) => {
this.onWaveShapeUpdated(data); if (!this.mounted) return;
if (this.requestDeferred) {
Utility.delay(10) // take breath
.then(
() => {
this.requestOutstanding = false;
this.requestDeferred = false;
this.updateWaveShape();
}
); if (json.otype_ === "Vector" && json.vtype_ === "Float")
} else { {
this.requestOutstanding = false; 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;
}
} }
}).catch(error => { }).catch(error => {
// assume the connection was lost. We'll get saved by a reconnect. // 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 uiControl = this.props.controlInfo.uiControl;
let instanceId = this.props.controlInfo.instanceId; let instanceId = this.props.controlInfo.instanceId;
if (instanceId === -1) return 0; if (instanceId === -1) return 0;
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId); let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId);
let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0; let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0;
this.defaultValue = value; this.defaultValue = value;
return value; return value;
} }
+5 -5
View File
@@ -94,8 +94,8 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
let uiControl = this.props.controlInfo.uiControl; let uiControl = this.props.controlInfo.uiControl;
let instanceId = this.props.controlInfo.instanceId; let instanceId = this.props.controlInfo.instanceId;
if (instanceId === -1) return 0; if (instanceId === -1) return 0;
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId); let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId);
let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0; let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0;
return value; return value;
} }
} }
@@ -105,7 +105,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
let v = Number.parseFloat(val.toString()); let v = Number.parseFloat(val.toString());
this.setState({ value: v }); this.setState({ value: v });
if (this.props.controlInfo) { if (this.props.controlInfo) {
this.model.setPedalBoardControlValue( this.model.setPedalboardControl(
this.props.controlInfo.instanceId, this.props.controlInfo.instanceId,
this.props.controlInfo.uiControl.symbol, this.props.controlInfo.uiControl.symbol,
v); v);
@@ -116,7 +116,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
let v = checked ? 1: 0; let v = checked ? 1: 0;
this.setState({ value: v }); this.setState({ value: v });
if (this.props.controlInfo) { if (this.props.controlInfo) {
this.model.setPedalBoardControlValue( this.model.setPedalboardControl(
this.props.controlInfo.instanceId, this.props.controlInfo.instanceId,
this.props.controlInfo.uiControl.symbol, this.props.controlInfo.uiControl.symbol,
v); v);
@@ -247,7 +247,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
onSetValue={(v) => { onSetValue={(v) => {
this.setState({ value: v }); this.setState({ value: v });
if (this.props.controlInfo) { if (this.props.controlInfo) {
this.model.setPedalBoardControlValue( this.model.setPedalboardControl(
this.props.controlInfo.instanceId, this.props.controlInfo.instanceId,
this.props.controlInfo.uiControl.symbol, this.props.controlInfo.uiControl.symbol,
v); v);
+2 -2
View File
@@ -31,8 +31,8 @@ src/MidiBinding.tsx
src/PiPedalError.tsx src/PiPedalError.tsx
src/IControlViewFactory.tsx src/IControlViewFactory.tsx
src/Lv2Plugin.tsx src/Lv2Plugin.tsx
src/PedalBoard.tsx src/Pedalboard.tsx
src/PedalBoardView.tsx src/PedalboardView.tsx
src/PresetDialog.tsx src/PresetDialog.tsx
src/AppThemed.tsx src/AppThemed.tsx
src/ZoomedDial.tsx src/ZoomedDial.tsx
+1 -1
View File
@@ -206,7 +206,7 @@ bool setJackConfiguration(JackServerSettings serverSettings)
#if JACK_HOST #if JACK_HOST
serverSettings.Write(); serverSettings.WriteDaemonConfig();
silentSysExec("/usr/bin/systemctl unmask jack"); silentSysExec("/usr/bin/systemctl unmask jack");
silentSysExec("/usr/bin/systemctl enable jack"); silentSysExec("/usr/bin/systemctl enable jack");
+2
View File
@@ -23,6 +23,7 @@
*/ */
#include "pch.h" #include "pch.h"
#include "util.hpp"
#include <bit> #include <bit>
#include <memory> #include <memory>
#include "ss.hpp" #include "ss.hpp"
@@ -1367,6 +1368,7 @@ namespace pipedal
} }
void AudioThread() void AudioThread()
{ {
SetThreadName("alsaDriver");
try try
{ {
#if defined(__WIN32) #if defined(__WIN32)
+3 -3
View File
@@ -92,8 +92,8 @@ public:
#endif #endif
oscillator.Init(440,jackConfiguration.GetSampleRate()); oscillator.Init(440,jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.GetSampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection); audioDriver->Open(serverSettings,channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
@@ -107,7 +107,7 @@ public:
if (testType == TestType::LatencyMonitor) if (testType == TestType::LatencyMonitor)
{ {
auto latency = this->latencyMonitor.GetLatency(); 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; 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 #pragma once
#ifndef JACK_HOST
#define JACK_HOST 0 #define JACK_HOST 0
#endif
#ifndef ALSA_HOST
#define ALSA_HOST 1 #define ALSA_HOST 1
#endif
#if JACK_HOST && ALSA_HOST #if JACK_HOST && ALSA_HOST
#error Choose either JACK or ALSA #error Choose either JACK or ALSA
+174 -233
View File
@@ -18,11 +18,13 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "AudioHost.hpp" #include "AudioHost.hpp"
#include "util.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include "JackDriver.hpp" #include "JackDriver.hpp"
#include "AlsaDriver.hpp" #include "AlsaDriver.hpp"
#include "AtomConverter.hpp"
using namespace pipedal; using namespace pipedal;
@@ -177,6 +179,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost
{ {
private: private:
IHost *pHost = nullptr; IHost *pHost = nullptr;
LV2_Atom_Forge inputWriterForge;
std::mutex atomConverterMutex;
AtomConverter atomConverter;
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024; static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
@@ -277,9 +284,9 @@ private:
JackChannelSelection channelSelection; JackChannelSelection channelSelection;
bool active = false; bool active = false;
std::shared_ptr<Lv2PedalBoard> currentPedalBoard; std::shared_ptr<Lv2Pedalboard> currentPedalboard;
std::vector<std::shared_ptr<Lv2PedalBoard>> activePedalBoards; // pedalboards that have been sent to the audio queue. std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2PedalBoard *realtimeActivePedalBoard = nullptr; Lv2Pedalboard *realtimeActivePedalboard = nullptr;
std::vector<uint8_t *> midiLv2Buffers; std::vector<uint8_t *> midiLv2Buffers;
@@ -297,18 +304,19 @@ private:
{ {
throw std::invalid_argument("Not an Lv2 Object"); 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; std::stringstream s;
json_writer writer(s); json_writer writer(s);
LV2_Atom *pAtom = (LV2_Atom *)pData;
WriteAtom(writer, pAtom); writer.write(vAtom);
return s.str(); return s.str();
} }
@@ -325,7 +333,6 @@ private:
return; return;
isOpen = false; isOpen = false;
StopReaderThread();
if (realtimeMonitorPortSubscriptions != nullptr) if (realtimeMonitorPortSubscriptions != nullptr)
{ {
@@ -341,9 +348,13 @@ private:
audioDriver->Close(); audioDriver->Close();
StopReaderThread();
pHost->GetHostWorkerThread()->Close();
// release any pdealboards owned by the process thread. // release any pdealboards owned by the process thread.
this->activePedalBoards.resize(0); this->activePedalboards.resize(0);
this->realtimeActivePedalBoard = nullptr; this->realtimeActivePedalboard = nullptr;
// clean up any realtime buffers that may have been lost in transit. // 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.. // TODO: These should be lists, really. There may be multiple items in flight..
@@ -365,6 +376,7 @@ private:
delete[] midiLv2Buffers[i]; delete[] midiLv2Buffers[i];
} }
midiLv2Buffers.resize(0); midiLv2Buffers.resize(0);
} }
void ZeroBuffer(float *buffer, size_t nframes) void ZeroBuffer(float *buffer, size_t nframes)
@@ -442,7 +454,7 @@ private:
portSubscription.samplesToNextCallback += portSubscription.sampleRate; portSubscription.samplesToNextCallback += portSubscription.sampleRate;
if (!portSubscription.waitingForAck) if (!portSubscription.waitingForAck)
{ {
float value = realtimeActivePedalBoard->GetControlOutputValue( float value = realtimeActivePedalboard->GetControlOutputValue(
portSubscription.instanceIndex, portSubscription.instanceIndex,
portSubscription.portIndex); portSubscription.portIndex);
if (value != portSubscription.lastValue) if (value != portSubscription.lastValue)
@@ -459,7 +471,7 @@ private:
} }
} }
RealtimeParameterRequest *pParameterRequests = nullptr; RealtimePatchPropertyRequest *pParameterRequests = nullptr;
bool reEntered = false; bool reEntered = false;
void ProcessInputCommands() void ProcessInputCommands()
@@ -486,12 +498,12 @@ private:
{ {
SetControlValueBody body; SetControlValueBody body;
realtimeReader.readComplete(&body); realtimeReader.readComplete(&body);
this->realtimeActivePedalBoard->SetControlValue(body.effectIndex, body.controlIndex, body.value); this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
break; break;
} }
case RingBufferCommand::ParameterRequest: case RingBufferCommand::ParameterRequest:
{ {
RealtimeParameterRequest *pRequest = nullptr; RealtimePatchPropertyRequest *pRequest = nullptr;
realtimeReader.readComplete(&pRequest); realtimeReader.readComplete(&pRequest);
// link to the list of parameter requests. // link to the list of parameter requests.
@@ -562,7 +574,7 @@ private:
{ {
SetBypassBody body; SetBypassBody body;
realtimeReader.readComplete(&body); realtimeReader.readComplete(&body);
this->realtimeActivePedalBoard->SetBypass(body.effectIndex, body.enabled); this->realtimeActivePedalboard->SetBypass(body.effectIndex, body.enabled);
break; break;
} }
case RingBufferCommand::ReplaceEffect: case RingBufferCommand::ReplaceEffect:
@@ -572,8 +584,8 @@ private:
if (body.effect != nullptr) if (body.effect != nullptr)
{ {
auto oldValue = this->realtimeActivePedalBoard; auto oldValue = this->realtimeActivePedalboard;
this->realtimeActivePedalBoard = body.effect; this->realtimeActivePedalboard = body.effect;
realtimeWriter.EffectReplaced(oldValue); realtimeWriter.EffectReplaced(oldValue);
@@ -612,7 +624,7 @@ private:
{ {
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); 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 (listenForMidiEvent)
{ {
if (event.size >= 3) if (event.size >= 3)
@@ -756,8 +768,8 @@ private:
bool processed = false; bool processed = false;
Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard; Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard;
if (pedalBoard != nullptr) if (pedalboard != nullptr)
{ {
ProcessMidiInput(); ProcessMidiInput();
float *inputBuffers[4]; float *inputBuffers[4];
@@ -789,15 +801,15 @@ private:
if (buffersValid) if (buffersValid)
{ {
pedalBoard->ResetAtomBuffers(); pedalboard->ResetAtomBuffers();
pedalBoard->ProcessParameterRequests(pParameterRequests); pedalboard->ProcessParameterRequests(pParameterRequests);
processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
if (processed) if (processed)
{ {
if (this->realtimeVuBuffers != nullptr) if (this->realtimeVuBuffers != nullptr)
{ {
pedalBoard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes); pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
vuSamplesRemaining -= nframes; vuSamplesRemaining -= nframes;
if (vuSamplesRemaining <= 0) if (vuSamplesRemaining <= 0)
@@ -811,14 +823,10 @@ private:
processMonitorPortSubscriptions(nframes); 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) if (!processed)
{ {
ZeroOutputBuffers(nframes); ZeroOutputBuffers(nframes);
@@ -841,7 +849,6 @@ private:
throw; throw;
} }
} }
public: public:
AudioHostImpl(IHost *pHost) AudioHostImpl(IHost *pHost)
: inputRingBuffer(RING_BUFFER_SIZE), : inputRingBuffer(RING_BUFFER_SIZE),
@@ -852,8 +859,11 @@ public:
hostWriter(&this->inputRingBuffer), hostWriter(&this->inputRingBuffer),
eventBufferUrids(pHost), eventBufferUrids(pHost),
pHost(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 #if JACK_HOST
audioDriver = CreateJackDriver(this); audioDriver = CreateJackDriver(this);
@@ -870,6 +880,7 @@ public:
delete audioDriver; delete audioDriver;
} }
virtual JackConfiguration GetServerConfiguration() virtual JackConfiguration GetServerConfiguration()
{ {
JackConfiguration result; JackConfiguration result;
@@ -893,6 +904,7 @@ public:
realtimeWriter.AudioStopped(); realtimeWriter.AudioStopped();
} }
std::vector<uint8_t> atomBuffer; std::vector<uint8_t> atomBuffer;
std::vector<uint8_t> realtimeAtomBuffer;
bool terminateThread; bool terminateThread;
void ThreadProc() void ThreadProc()
@@ -917,6 +929,7 @@ public:
{ {
Lv2Log::debug("Service thread priority successfully boosted."); Lv2Log::debug("Service thread priority successfully boosted.");
} }
SetThreadName("aout");
#else #else
xxx; // TODO! xxx; // TODO!
#endif #endif
@@ -924,44 +937,41 @@ public:
try 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; 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) while (true)
{ {
// wait for an event. // wait for an event.
// 0 -> ready. -1: timed out. -2: closing. // 0 -> ready. -1: timed out. -2: closing.
int result = hostReader.wait(ts); auto result = hostReader.wait_until(sizeof(RingBufferCommand), waitTime);
if (result == -2) if (result == RingBufferStatus::Closed)
{ {
return; return;
} }
else if (result == -1) else if (result == RingBufferStatus::TimedOut)
{ {
// timeout. // timeout.
ts.tv_sec += pollRateS;
uint64_t underruns = this->underruns;
if (underruns != lastUnderrunCount) if (underruns != lastUnderrunCount)
{ {
if (underrunMessagesGiven < 60) // limit how much log file clutter we generate. 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; lastUnderrunCount = underruns;
++underrunMessagesGiven; ++underrunMessagesGiven;
} }
} }
clock_gettime(CLOCK_REALTIME, &ts); waitTime += waitPeriod;
ts.tv_sec += pollRateS;
} }
else else
{ {
@@ -996,32 +1006,42 @@ public:
} }
else if (command == RingBufferCommand::ParameterRequestComplete) else if (command == RingBufferCommand::ParameterRequestComplete)
{ {
RealtimeParameterRequest *pRequest = nullptr; RealtimePatchPropertyRequest *pRequest = nullptr;
hostReader.read(&pRequest); hostReader.read(&pRequest);
std::shared_ptr<Lv2PedalBoard> currentpedalBoard; std::shared_ptr<Lv2Pedalboard> currentpedalboard;
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
currentPedalBoard = this->currentPedalBoard; currentPedalboard = this->currentPedalboard;
} }
while (pRequest != nullptr) while (pRequest != nullptr)
{ {
auto pNext = pRequest->pNext; auto pNext = pRequest->pNext;
if (pRequest->errorMessage == nullptr && pRequest->responseLength != 0) if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
{ {
IEffect *pEffect = currentPedalBoard->GetEffect(pRequest->instanceId); if (pRequest->errorMessage == nullptr)
if (pEffect == nullptr)
{ {
pRequest->errorMessage = "Effect no longer available."; if (pRequest->GetSize() != 0) {
} IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId);
else if (pEffect == nullptr)
{ {
pRequest->jsonResponse = AtomToJson(pRequest->response); 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; pRequest = pNext;
} }
} }
@@ -1047,6 +1067,12 @@ public:
} }
this->hostWriter.AckVuUpdate(); // please sir, can I have some more? 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) else if (command == RingBufferCommand::AtomOutput)
{ {
uint64_t instanceId; uint64_t instanceId;
@@ -1059,12 +1085,35 @@ public:
} }
hostReader.read(extraBytes, &(atomBuffer[0])); hostReader.read(extraBytes, &(atomBuffer[0]));
IEffect *pEffect = currentPedalBoard->GetEffect(instanceId); IEffect *pEffect = currentPedalboard->GetEffect(instanceId);
if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput) if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput)
{ {
std::string atomType = GetAtomObjectType(&atomBuffer[0]); LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0];
auto json = AtomToJson(&(atomBuffer[0])); if (atom->type == uris.atom_Object)
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId, atomType, json); {
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) else if (command == RingBufferCommand::FreeVuSubscriptions)
@@ -1083,7 +1132,7 @@ public:
{ {
EffectReplacedBody body; EffectReplacedBody body;
hostReader.read(&body); hostReader.read(&body);
OnActivePedalBoardReleased(body.oldEffect); OnActivePedalboardReleased(body.oldEffect);
} }
else if (command == RingBufferCommand::AudioStopped) else if (command == RingBufferCommand::AudioStopped)
{ {
@@ -1218,45 +1267,45 @@ public:
{ {
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest); pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
} }
void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard) void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
{ {
if (pPedalBoard) if (pPedalboard)
{ {
pPedalBoard->Deactivate(); pPedalboard->Deactivate();
std::lock_guard guard(mutex); 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. // erase it, relinquishing shared_ptr ownership, usually deleting the object.
activePedalBoards.erase(it); activePedalboards.erase(it);
return; return;
} }
} }
} }
} }
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard) virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
this->currentPedalBoard = pedalBoard; this->currentPedalboard = pedalboard;
if (active) if (active)
{ {
pedalBoard->Activate(); pedalboard->Activate();
this->activePedalBoards.push_back(pedalBoard); this->activePedalboards.push_back(pedalboard);
hostWriter.ReplaceEffect(pedalBoard.get()); hostWriter.ReplaceEffect(pedalboard.get());
} }
} }
virtual void SetBypass(uint64_t instanceId, bool enabled) virtual void SetBypass(uint64_t instanceId, bool enabled)
{ {
std::lock_guard guard(mutex); 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. // 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) if (index >= 0)
{ {
hostWriter.SetBypass((uint32_t)index, enabled); hostWriter.SetBypass((uint32_t)index, enabled);
@@ -1267,15 +1316,15 @@ public:
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values)
{ {
std::lock_guard guard(mutex); 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) if (effectIndex != -1)
{ {
for (size_t i = 0; i < values.size(); ++i) for (size_t i = 0; i < values.size(); ++i)
{ {
const ControlValue &value = values[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) if (controlIndex != -1 && effectIndex != -1)
{ {
hostWriter.SetControlValue(effectIndex, controlIndex, value.value()); hostWriter.SetControlValue(effectIndex, controlIndex, value.value());
@@ -1288,11 +1337,11 @@ public:
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
{ {
std::lock_guard guard(mutex); 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. // 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); int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, symbol);
auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId); auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId);
if (controlIndex != -1 && effectIndex != -1) if (controlIndex != -1 && effectIndex != -1)
{ {
@@ -1301,11 +1350,14 @@ public:
} }
} }
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
if (active && this->currentPedalBoard) if (active && this->currentPedalboard)
{ {
if (instanceIds.size() == 0) if (instanceIds.size() == 0)
@@ -1319,13 +1371,13 @@ public:
for (size_t i = 0; i < instanceIds.size(); ++i) for (size_t i = 0; i < instanceIds.size(); ++i)
{ {
int64_t instanceId = instanceIds[i]; int64_t instanceId = instanceIds[i];
auto effect = this->currentPedalBoard->GetEffect(instanceId); auto effect = this->currentPedalboard->GetEffect(instanceId);
if (!effect) if (!effect)
{ {
throw PiPedalStateException("Effect not found."); throw PiPedalStateException("Effect not found.");
} }
int index = this->currentPedalBoard->GetIndexOfInstanceId(instanceIds[i]); int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index); vuConfig->enabledIndexes.push_back(index);
VuUpdate v; VuUpdate v;
v.instanceId_ = instanceId; v.instanceId_ = instanceId;
@@ -1346,8 +1398,8 @@ public:
{ {
RealtimeMonitorPortSubscription result; RealtimeMonitorPortSubscription result;
result.subscriptionHandle = subscription.subscriptionHandle; result.subscriptionHandle = subscription.subscriptionHandle;
result.instanceIndex = this->currentPedalBoard->GetIndexOfInstanceId(subscription.instanceid); result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid);
IEffect *pEffect = this->currentPedalBoard->GetEffect(subscription.instanceid); IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid);
result.portIndex = pEffect->GetControlIndex(subscription.key); result.portIndex = pEffect->GetControlIndex(subscription.key);
result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval); result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval);
@@ -1360,7 +1412,7 @@ public:
{ {
if (!active) if (!active)
return; return;
if (this->currentPedalBoard == nullptr) if (this->currentPedalboard == nullptr)
return; return;
if (subscriptions.size() == 0) if (subscriptions.size() == 0)
{ {
@@ -1372,7 +1424,7 @@ public:
for (size_t i = 0; i < subscriptions.size(); ++i) 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( pSubscriptions->subscriptions.push_back(
MakeRealtimeSubscription(subscriptions[i])); MakeRealtimeSubscription(subscriptions[i]));
@@ -1383,6 +1435,10 @@ public:
} }
private: private:
virtual void UpdatePluginStates(Pedalboard& pedalboard);
void UpdatePluginState(PedalboardItem &pedalboardItem);
class RestartThread class RestartThread
{ {
AudioHostImpl *this_; AudioHostImpl *this_;
@@ -1478,12 +1534,12 @@ public:
} }
} }
virtual void getRealtimeParameter(RealtimeParameterRequest *pParameterRequest) virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest)
{ {
if (!active) if (!active)
{ {
pParameterRequest->errorMessage = "Not active."; pParameterRequest->errorMessage = "Not active.";
pParameterRequest->onJackRequestComplete(pParameterRequest); pParameterRequest->onPatchRequestComplete(pParameterRequest);
return; return;
} }
this->hostWriter.ParameterRequest(pParameterRequest); this->hostWriter.ParameterRequest(pParameterRequest);
@@ -1560,145 +1616,6 @@ static std::string UriToFieldName(const std::string &uri)
return uri.substr(pos + 1); 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) void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
{ {
for (auto i = bindings.begin(); i != bindings.end(); ++i) for (auto i = bindings.begin(); i != bindings.end(); ++i)
@@ -1719,6 +1636,30 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost)
return new AudioHostImpl(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_BEGIN(JackHostStatus)
JSON_MAP_REFERENCE(JackHostStatus, active) JSON_MAP_REFERENCE(JackHostStatus, active)
JSON_MAP_REFERENCE(JackHostStatus, errorMessage) JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
+67 -15
View File
@@ -21,13 +21,15 @@
#include "JackConfiguration.hpp" #include "JackConfiguration.hpp"
#include "Lv2PedalBoard.hpp" #include "Lv2Pedalboard.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "json.hpp" #include "json.hpp"
#include "AudioHost.hpp" #include "AudioHost.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
#include <functional> #include <functional>
#include "PiPedalAlsa.hpp" #include "PiPedalAlsa.hpp"
#include "Promise.hpp"
#include "json_variant.hpp"
namespace pipedal { namespace pipedal {
@@ -45,39 +47,86 @@ public:
int64_t subscriptionHandle; int64_t subscriptionHandle;
float value; float value;
}; };
class RealtimeParameterRequest { class RealtimePatchPropertyRequest {
public: public:
int64_t clientId; int64_t clientId;
int64_t instanceId; int64_t instanceId;
LV2_URID uridUri; 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 &jsonResjult)> onSuccess;
std::function<void(const std::string &error)> onError; std::function<void(const std::string &error)> onError;
const char*errorMessage = nullptr; const char*errorMessage = nullptr;
int responseLength = 0;
uint8_t response[2048];
std::string jsonResponse; 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: public:
RealtimeParameterRequest( RealtimePatchPropertyRequest(
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete_, std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
int64_t clientId_, int64_t clientId_,
int64_t instanceId_, int64_t instanceId_,
LV2_URID uridUri_, LV2_URID uridUri_,
std::function<void(const std::string &jsonResjult)> onSuccess_, std::function<void(const std::string &jsonResjult)> onSuccess_,
std::function<void(const std::string &error)> onError_) std::function<void(const std::string &error)> onError_
: onJackRequestComplete(onJackRequestComplete_), )
: onPatchRequestComplete(onPatchRequestcomplete_),
clientId(clientId_), clientId(clientId_),
instanceId(instanceId_), instanceId(instanceId_),
uridUri(uridUri_), uridUri(uridUri_),
onSuccess(onSuccess_), onSuccess(onSuccess_),
onError(onError_) 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 { class IAudioHostCallbacks {
public: public:
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0; virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 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 OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0; virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
@@ -143,6 +194,8 @@ public:
virtual void SetListenForMidiEvent(bool listen) = 0; virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(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 Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
virtual void Close() = 0; virtual void Close() = 0;
@@ -151,7 +204,7 @@ public:
virtual JackConfiguration GetServerConfiguration() = 0; 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 SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> & values) = 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 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 void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual JackHostStatus getJackStatus() = 0; virtual JackHostStatus getJackStatus() = 0;
}; };
+1 -1
View File
@@ -23,7 +23,7 @@
*/ */
#include "AutoLilvNode.hpp" #include "AutoLilvNode.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
using namespace pipedal; using namespace pipedal;
+27 -14
View File
@@ -32,24 +32,23 @@ namespace pipedal
class AutoLilvNode class AutoLilvNode
{ {
// const LilvNode* returns must not be freed, by convention.
private:
LilvNode *node = nullptr;
AutoLilvNode(const LilvNode *node) = delete;
public: public:
AutoLilvNode() AutoLilvNode()
{ {
} }
AutoLilvNode(const LilvNode *node)
: node(const_cast<LilvNode*>(node))
{
isConst = true;
}
AutoLilvNode(LilvNode *node) AutoLilvNode(LilvNode *node)
: node(node) : node(node)
{ {
isConst = false;
} }
~AutoLilvNode() { Free(); } ~AutoLilvNode() { Free(); }
operator const LilvNode *() operator const LilvNode *()
{ {
return this->node; return this->node;
@@ -58,19 +57,31 @@ namespace pipedal
{ {
return this->node != nullptr; return this->node != nullptr;
} }
LilvNode*&Get() { return node; } const LilvNode *Get() { return node; }
AutoLilvNode&operator=(LilvNode*node) { AutoLilvNode &operator=(LilvNode *node)
{
Free(); Free();
this->node = node; this->node = node;
return *this; return *this;
} }
AutoLilvNode&operator=(AutoLilvNode&&other) {
std::swap(this->node,other.node); 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; return *this;
} }
float AsFloat(float defaultValue = 0); float AsFloat(float defaultValue = 0);
int AsInt(int defaultValue = 0); int AsInt(int defaultValue = 0);
bool AsBool(bool defaultValue = false); bool AsBool(bool defaultValue = false);
@@ -79,11 +90,13 @@ namespace pipedal
void Free() void Free()
{ {
if (node != nullptr) if (node != nullptr && !isConst)
lilv_node_free(node); lilv_node_free(node);
node = nullptr; node = nullptr;
} }
private:
LilvNode *node = nullptr;
bool isConst = true;
}; };
}; };
+5 -5
View File
@@ -20,7 +20,7 @@
#pragma once #pragma once
#include "json.hpp" #include "json.hpp"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
namespace pipedal { namespace pipedal {
@@ -75,7 +75,7 @@ public:
class BankFileEntry { class BankFileEntry {
int64_t instanceId_; int64_t instanceId_;
PedalBoard preset_; Pedalboard preset_;
public: public:
GETTER_SETTER(instanceId); GETTER_SETTER(instanceId);
GETTER_SETTER_REF(preset); GETTER_SETTER_REF(preset);
@@ -122,7 +122,7 @@ public:
} }
this->nextInstanceId_ = t; 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())) if (hasName(preset.name()))
{ {
@@ -213,8 +213,8 @@ public:
} else { } else {
// zero length? We can never have a zero-length bank. // zero length? We can never have a zero-length bank.
// Add a default preset and make it the selected preset. // Add a default preset and make it the selected preset.
PedalBoard pedalBoard = PedalBoard::MakeDefault(); Pedalboard pedalboard = Pedalboard::MakeDefault();
this->addPreset(pedalBoard); this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId(); newSelection = presets_[0]->instanceId();
} }
if (instanceId == this->selectedPreset_) 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. # Disabled, pending approval of Steinberg VST3 License.
# Do not enable unless you have non-GPL3 access to the Steinberg # Do not enable unless you have non-GPL3 access to the Steinberg
# SDK. # 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) set (ENABLE_VST3 0)
@@ -137,10 +141,20 @@ else()
endif() endif()
set (PIPEDAL_SOURCES set (PIPEDAL_SOURCES
AutoLilvNode.hpp util.hpp util.cpp
AutoLilvNode.cpp
PiPedalUI.hpp PiPedalUI.hpp
PiPedalUI.cpp PiPedalUI.cpp
MapPathFeature.hpp
MapPathFeature.cpp
IHost.hpp
StateInterface.hpp
StateInterface.cpp
AtomConverter.hpp
AtomConverter.cpp
AtomBuffer.hpp
AutoLilvNode.hpp
AutoLilvNode.cpp
RtInversionGuard.hpp RtInversionGuard.hpp
CpuUse.hpp CpuUse.cpp CpuUse.hpp CpuUse.cpp
P2pConfigFiles.hpp P2pConfigFiles.hpp
@@ -162,13 +176,13 @@ set (PIPEDAL_SOURCES
RequestHandler.hpp json.cpp json.hpp RequestHandler.hpp json.cpp json.hpp
json_variant.hpp json_variant.cpp json_variant.hpp json_variant.cpp
Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp Scratch.cpp PluginHost.hpp PluginHost.cpp
PluginType.hpp PluginType.cpp PluginType.hpp PluginType.cpp
Lv2Log.hpp Lv2Log.cpp Lv2Log.hpp Lv2Log.cpp
PiPedalSocket.hpp PiPedalSocket.cpp PiPedalSocket.hpp PiPedalSocket.cpp
PiPedalVersion.hpp PiPedalVersion.cpp PiPedalVersion.hpp PiPedalVersion.cpp
PiPedalModel.hpp PiPedalModel.cpp PiPedalModel.hpp PiPedalModel.cpp
PedalBoard.hpp PedalBoard.cpp Pedalboard.hpp Pedalboard.cpp
Presets.hpp Presets.cpp Presets.hpp Presets.cpp
Storage.hpp Storage.cpp Storage.hpp Storage.cpp
Banks.hpp Banks.cpp Banks.hpp Banks.cpp
@@ -176,7 +190,7 @@ set (PIPEDAL_SOURCES
JackConfiguration.hpp JackConfiguration.cpp JackConfiguration.hpp JackConfiguration.cpp
defer.hpp defer.hpp
Lv2Effect.cpp Lv2Effect.hpp Lv2Effect.cpp Lv2Effect.hpp
Lv2PedalBoard.cpp Lv2PedalBoard.hpp Lv2Pedalboard.cpp Lv2Pedalboard.hpp
BufferPool.hpp BufferPool.hpp
SplitEffect.hpp SplitEffect.cpp SplitEffect.hpp SplitEffect.cpp
RingBufferReader.hpp RingBufferReader.hpp
@@ -219,7 +233,7 @@ include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
set (PIPEDAL_INCLUDES set (PIPEDAL_INCLUDES
${LV2DEV_INCLUDE_DIRS} ${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} ${VST3_INCLUDES}
. .
) )
@@ -261,7 +275,10 @@ target_link_libraries(pipedald PRIVATE
################################# #################################
add_executable(pipedaltest testMain.cpp add_executable(pipedaltest testMain.cpp
jsonTest.cpp jsonTest.cpp
AlsaDriverTest.cpp json_variant.cpp
json_variant.hpp
AlsaDriverTest.cpp
AvahiServiceTest.cpp AvahiServiceTest.cpp
WifiChannelsTest.cpp WifiChannelsTest.cpp
PiPedalAlsaTest.cpp PiPedalAlsaTest.cpp
@@ -277,6 +294,36 @@ add_executable(pipedaltest testMain.cpp
MemDebug.hpp 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) include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
if(${USE_PCH}) if(${USE_PCH})
@@ -539,6 +586,8 @@ target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs
) )
add_executable(pipedal_latency_test add_executable(pipedal_latency_test
util.hpp
util.cpp
PrettyPrinter.hpp PrettyPrinter.hpp
CommandLineParser.hpp CommandLineParser.hpp
PiLatencyMain.cpp PiLatencyMain.cpp
+1 -1
View File
@@ -191,7 +191,7 @@ void StartService(bool excludeShutdownService = false)
} }
#if INSTALL_JACK_SERVICE #if INSTALL_JACK_SERVICE
JackServerSettings serverSettings; JackServerSettings serverSettings;
serverSettings.ReadJackConfiguration(); serverSettings.ReadJackDaemonConfiguration();
if (serverSettings.IsValid()) if (serverSettings.IsValid())
{ {
if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS) if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS)
+5 -5
View File
@@ -20,10 +20,13 @@
#pragma once #pragma once
#include <lv2/urid.lv2/urid.h> #include <lv2/urid.lv2/urid.h>
#include <lv2/atom.lv2/atom.h>
namespace pipedal { namespace pipedal {
class RealtimeParameterRequest; class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter; class RealtimeRingBufferWriter;
class Lv2PluginState;
class IEffect { class IEffect {
public: public:
@@ -35,15 +38,11 @@ namespace pipedal {
virtual void SetBypass(bool enable) = 0; virtual void SetBypass(bool enable) = 0;
virtual float GetOutputControlValue(int controlIndex) const = 0; virtual float GetOutputControlValue(int controlIndex) const = 0;
virtual int GetNumberOfInputAudioPorts() const = 0; virtual int GetNumberOfInputAudioPorts() const = 0;
virtual int GetNumberOfOutputAudioPorts() const = 0; virtual int GetNumberOfOutputAudioPorts() const = 0;
virtual float *GetAudioInputBuffer(int index) const = 0; virtual float *GetAudioInputBuffer(int index) const = 0;
virtual float *GetAudioOutputBuffer(int index) const = 0; virtual float *GetAudioOutputBuffer(int index) const = 0;
virtual void ResetAtomBuffers() = 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 AtomToJson(uint8_t*pAtom) = 0;
//virtual std::string GetAtomObjectType(uint8_t*pData) = 0; //virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
@@ -59,5 +58,6 @@ namespace pipedal {
virtual void Deactivate() = 0; virtual void Deactivate() = 0;
virtual bool IsVst3() const = 0; virtual bool IsVst3() const = 0;
virtual bool GetLv2State(Lv2PluginState*state) = 0;
}; };
} //namespace } //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_BEGIN(JackConfiguration)
JSON_MAP_REFERENCE(JackConfiguration,isValid) JSON_MAP_REFERENCE(JackConfiguration,isValid)
JSON_MAP_REFERENCE(JackConfiguration,isOnboarding)
JSON_MAP_REFERENCE(JackConfiguration,isRestarting) JSON_MAP_REFERENCE(JackConfiguration,isRestarting)
JSON_MAP_REFERENCE(JackConfiguration,errorStatus) JSON_MAP_REFERENCE(JackConfiguration,errorStatus)
JSON_MAP_REFERENCE(JackConfiguration,sampleRate) JSON_MAP_REFERENCE(JackConfiguration,sampleRate)
+10 -6
View File
@@ -37,6 +37,7 @@ namespace pipedal
std::string errorStatus_; std::string errorStatus_;
bool isRestarting_ = false; bool isRestarting_ = false;
bool isOnboarding_ = true;
uint32_t sampleRate_ = 48000; uint32_t sampleRate_ = 48000;
size_t blockLength_ = 1024; size_t blockLength_ = 1024;
@@ -54,13 +55,16 @@ namespace pipedal
void JackInitialize(); // from jack server instance. void JackInitialize(); // from jack server instance.
~JackConfiguration(); ~JackConfiguration();
bool isValid() const { return isValid_;} bool isValid() const { return isValid_;}
bool isOnboarding() const { return isOnboarding_;}
void isOnboarding(bool value) { isOnboarding_ = value; }
bool isRestarting() const { return isRestarting_; } bool isRestarting() const { return isRestarting_; }
void SetIsRestarting(bool value) { isRestarting_ = value; } void isRestarting(bool value) { isRestarting_ = value; }
uint32_t GetSampleRate() const { return sampleRate_; }
size_t GetBlockLength() const { return blockLength_; } uint32_t sampleRate() const { return sampleRate_; }
size_t GetMidiBufferSize() const { return midiBufferSize_;} size_t blockLength() const { return blockLength_; }
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; } size_t midiBufferSize() const { return midiBufferSize_;}
void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; } 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> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; } 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; this->valid_ = false;
std::string lastLine; std::string lastLine;
@@ -155,7 +158,7 @@ void JackServerSettings::ReadJackConfiguration()
} }
} }
void JackServerSettings::Write() void JackServerSettings::WriteDaemonConfig()
{ {
#if JACK_HOST #if JACK_HOST
this->valid_ = false; this->valid_ = false;
@@ -240,6 +243,7 @@ void JackServerSettings::Write()
JSON_MAP_BEGIN(JackServerSettings) JSON_MAP_BEGIN(JackServerSettings)
JSON_MAP_REFERENCE(JackServerSettings, valid) JSON_MAP_REFERENCE(JackServerSettings, valid)
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired) JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio) JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
+18 -11
View File
@@ -28,6 +28,7 @@ namespace pipedal
class JackServerSettings class JackServerSettings
{ {
bool valid_ = false; bool valid_ = false;
bool isOnboarding_ = true;
bool isJackAudio_ = JACK_HOST ? true : false; bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false; bool rebootRequired_ = false;
std::string alsaDevice_; std::string alsaDevice_;
@@ -44,7 +45,8 @@ namespace pipedal
alsaDevice_(alsaInputDevice), alsaDevice_(alsaInputDevice),
sampleRate_(sampleRate), sampleRate_(sampleRate),
bufferSize_(bufferSize), bufferSize_(bufferSize),
numberOfBuffers_(numberOfBuffers) numberOfBuffers_(numberOfBuffers),
isOnboarding_(false)
{ {
} }
@@ -53,23 +55,28 @@ namespace pipedal
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; } uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string &GetAlsaInputDevice() const { return alsaDevice_; } const std::string &GetAlsaInputDevice() const { return alsaDevice_; }
void ReadJackConfiguration(); void ReadJackDaemonConfiguration();
bool IsValid() const { return valid_; } bool IsValid() const { return valid_; }
JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers) // JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
{ // {
this->valid_ = true; // this->valid_ = true;
this->rebootRequired_ = true; // this->rebootRequired_ = true;
this->sampleRate_ = sampleRate; // this->sampleRate_ = sampleRate;
this->bufferSize_ = bufferSize; // this->bufferSize_ = bufferSize;
this->numberOfBuffers_ = numberOfBuffers; // this->numberOfBuffers_ = numberOfBuffers;
} // }
void Write(); // requires root perms. void WriteDaemonConfig(); // requires root perms.
void SetRebootRequired(bool value) void SetRebootRequired(bool value)
{ {
rebootRequired_ = value; rebootRequired_ = value;
} }
void SetIsOnboarding(bool value)
{
isOnboarding_ = value;
}
bool Equals(const JackServerSettings &other) bool Equals(const JackServerSettings &other)
{ {
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_; 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/log.lv2/logger.h"
#include "lv2/uri-map.lv2/uri-map.h" #include "lv2/uri-map.lv2/uri-map.h"
#include "lv2/atom.lv2/forge.h" #include "lv2/atom.lv2/forge.h"
#include "lv2/state.lv2/state.h"
#include "lv2/worker.lv2/worker.h" #include "lv2/worker.lv2/worker.h"
#include "lv2/patch.lv2/patch.h" #include "lv2/patch.lv2/patch.h"
#include "lv2/parameters.lv2/parameters.h" #include "lv2/parameters.lv2/parameters.h"
@@ -48,14 +49,14 @@ const float BYPASS_TIME_S = 0.1f;
Lv2Effect::Lv2Effect( Lv2Effect::Lv2Effect(
IHost *pHost_, IHost *pHost_,
const std::shared_ptr<Lv2PluginInfo> &info_, const std::shared_ptr<Lv2PluginInfo> &info_,
const PedalBoardItem &pedalBoardItem) const PedalboardItem &pedalboardItem)
: pHost(pHost_), pInstance(nullptr), info(info_), uris(pHost) : pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost)
{ {
auto pWorld = pHost_->getWorld(); auto pWorld = pHost_->getWorld();
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S); 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. // initialize the atom forge used on the realtime thread.
LV2_URID_Map *map = this->pHost->GetLv2UridMap(); LV2_URID_Map *map = this->pHost->GetLv2UridMap();
@@ -64,7 +65,7 @@ Lv2Effect::Lv2Effect(
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld); 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); const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
lilv_node_free(uriNode); lilv_node_free(uriNode);
@@ -76,7 +77,7 @@ Lv2Effect::Lv2Effect(
} }
this->work_schedule_feature = nullptr; this->work_schedule_feature = nullptr;
if (info_->hasExtension(LV2_WORKER__interface)) if (true) //info_->hasExtension(LV2_WORKER__interface))
{ {
// insane implementation. :-( // insane implementation. :-(
LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule)); LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule));
@@ -99,29 +100,36 @@ Lv2Effect::Lv2Effect(
} catch (const std::exception &e) } catch (const std::exception &e)
{ {
this->pInstance = nullptr; 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; this->pInstance = pInstance;
if (this->pInstance == nullptr) 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 = this->stateInterface = std::make_unique<StateInterface>(pHost,pInstance,state_interface);
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
LV2_WORKER__interface);
this->worker = std::make_unique<Worker>(pInstance, worker_interface);
} }
this->instanceId = pedalBoardItem.instanceId(); this->instanceId = pedalboardItem.instanceId();
this->controlValues.resize(info->ports().size()); this->controlValues.resize(info->ports().size());
// Copy default pedalboard settings. // 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); auto &v = (*i);
int index = GetControlIndex(v.key()); int index = GetControlIndex(v.key());
@@ -132,6 +140,16 @@ Lv2Effect::Lv2Effect(
} }
PreparePortIndices(); PreparePortIndices();
ConnectControlPorts(); ConnectControlPorts();
RestoreState(pedalboardItem);
}
void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem)
{
// Restore state if present.
if (this->stateInterface)
{
this->stateInterface->Restore(pedalboardItem.lv2State());
}
} }
void Lv2Effect::ConnectControlPorts() void Lv2Effect::ConnectControlPorts()
@@ -262,6 +280,7 @@ Lv2Effect::~Lv2Effect()
{ {
if (worker) if (worker)
{ {
worker->Close();
worker = nullptr; // delete the worker first! worker = nullptr; // delete the worker first!
} }
if (pInstance) if (pInstance)
@@ -281,8 +300,11 @@ void Lv2Effect::Activate()
this->AssignUnconnectedPorts(); this->AssignUnconnectedPorts();
lilv_instance_activate(pInstance); lilv_instance_activate(pInstance);
this->BypassTo(this->bypass ? 1.0f : 0.0f); this->BypassTo(this->bypass ? 1.0f : 0.0f);
} }
void Lv2Effect::AssignUnconnectedPorts() void Lv2Effect::AssignUnconnectedPorts()
{ {
for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i) for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i)
@@ -332,6 +354,10 @@ void Lv2Effect::AssignUnconnectedPorts()
} }
void Lv2Effect::Deactivate() void Lv2Effect::Deactivate()
{ {
if (worker)
{
worker->Close();
}
lilv_instance_deactivate(pInstance); 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, 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; BufferHeader *header = (BufferHeader *)data;
header->size = sizeof(LV2_Atom_Sequence_Body); header->size = sizeof(LV2_Atom_Sequence_Body);
header->type = uris.atom_Sequence; header->type = urids.atom__Sequence;
} }
void Lv2Effect::ResetOutputAtomBuffer(char *data) void Lv2Effect::ResetOutputAtomBuffer(char *data)
{ {
BufferHeader *header = (BufferHeader *)data; BufferHeader *header = (BufferHeader *)data;
header->size = pHost->GetAtomBufferSize() - 8; header->size = pHost->GetAtomBufferSize() - 8;
header->type = uris.atom_Chunk; header->type = urids.atom__Chunk;
} }
void Lv2Effect::BypassTo(float targetValue) void Lv2Effect::BypassTo(float targetValue)
@@ -509,24 +535,41 @@ void Lv2Effect::ResetAtomBuffers()
// Start a sequence in the notify input port. // 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_time(&inputForgeRt, 0);
LV2_Atom_Forge_Frame objectFrame; LV2_Atom_Forge_Frame objectFrame;
LV2_Atom_Forge_Ref set = 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_urid(&inputForgeRt, uridUri);
lv2_atom_forge_pop(&inputForgeRt, &objectFrame); 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(); LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
if (controlOutput == nullptr) if (controlOutput == nullptr)
@@ -534,6 +577,7 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter
return; return;
} }
bool stateChanged = false;
LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev) 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)) if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
{ {
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; 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); 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(); if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
{ {
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
// frame_offset = ev->time.frames; // not really interested. if (controlInput == nullptr)
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
{ {
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; return;
if (obj->body.otype == uris.patch_Set) }
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_Object *obj = (const LV2_Atom_Object *)&ev->body;
const LV2_Atom *property = NULL; if (obj->body.otype == urids.patch__Set)
const LV2_Atom *value = NULL; {
// Get the property and value of the set message
const LV2_Atom *property = NULL;
const LV2_Atom *value = NULL;
lv2_atom_object_get( lv2_atom_object_get(
obj, obj,
uris.patch_property, &property, urids.patch__property, &property,
uris.patch_value, &value, urids.patch__value, &value,
0); 0);
if (!property) if (property && property->type == urids.atom__URID && value)
{
}
else if (property->type != uris.atom_URID)
{
}
else
{
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
if (key == pRequest->uridUri)
{ {
int atom_size = value->size + sizeof(LV2_Atom); LV2_URID key = ((const LV2_Atom_URID *)property)->body;
if (atom_size > sizeof(pRequest->response)) if (key == pRequest->uridUri)
{ {
pRequest->errorMessage = "Response is too large."; int atom_size = value->size + sizeof(LV2_Atom);
} else { pRequest->SetSize(atom_size);
pRequest->responseLength = atom_size; memcpy(pRequest->GetBuffer(),value,atom_size);
memcpy(pRequest->response,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 #pragma once
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include <lilv/lilv.h> #include <lilv/lilv.h>
#include "BufferPool.hpp" #include "BufferPool.hpp"
@@ -32,6 +32,8 @@
#include "lv2/log.lv2/logger.h" #include "lv2/log.lv2/logger.h"
#include "lv2/lv2plug.in/ns/extensions/units/units.h" #include "lv2/lv2plug.in/ns/extensions/units/units.h"
#include "lv2/atom.lv2/forge.h" #include "lv2/atom.lv2/forge.h"
#include "AtomBuffer.hpp"
#include "StateInterface.hpp"
namespace pipedal namespace pipedal
@@ -42,6 +44,12 @@ namespace pipedal
class Lv2Effect : public IEffect class Lv2Effect : public IEffect
{ {
private: private:
std::unique_ptr<StateInterface> stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
std::map<std::string,AtomBuffer> patchPropertyPrototypes;
IHost *pHost = nullptr; IHost *pHost = nullptr;
int numberOfInputs = 0; int numberOfInputs = 0;
int numberOfOutputs = 0; int numberOfOutputs = 0;
@@ -73,6 +81,7 @@ namespace pipedal
virtual std::string GetUri() const { return info->uri(); } virtual std::string GetUri() const { return info->uri(); }
std::vector<const Lv2PortInfo *> realtimePortInfo; std::vector<const Lv2PortInfo *> realtimePortInfo;
void PreparePortIndices(); void PreparePortIndices();
void ConnectControlPorts(); void ConnectControlPorts();
void AssignUnconnectedPorts(); void AssignUnconnectedPorts();
@@ -83,68 +92,62 @@ namespace pipedal
LV2_Atom_Forge outputForgeRt; LV2_Atom_Forge outputForgeRt;
class Uris class Urids
{ {
public: public:
Uris(IHost *pHost) Urids(IHost *pHost)
{ {
atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank); atom__Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path); atom__Path = pHost->GetLv2Urid(LV2_ATOM__Path);
atom_float = pHost->GetLv2Urid(LV2_ATOM__Float); atom__Float = pHost->GetLv2Urid(LV2_ATOM__Float);
atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double); atom__Double = pHost->GetLv2Urid(LV2_ATOM__Double);
atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int); atom__Int = pHost->GetLv2Urid(LV2_ATOM__Int);
atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long); atom__Long = pHost->GetLv2Urid(LV2_ATOM__Long);
atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool); atom__Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
atom_String = pHost->GetLv2Urid(LV2_ATOM__String); atom__String = pHost->GetLv2Urid(LV2_ATOM__String);
atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector); atom__Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object); atom__Object = pHost->GetLv2Urid(LV2_ATOM__Object);
atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence); atom__Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk); atom__URID = pHost->GetLv2Urid(LV2_ATOM__URID);
atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID); patch__Get = pHost->GetLv2Urid(LV2_PATCH__Get);
atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer); patch__Set = pHost->GetLv2Urid(LV2_PATCH__Set);
patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get); patch__Put = pHost->GetLv2Urid(LV2_PATCH__Put);
patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set); patch__body = pHost->GetLv2Urid(LV2_PATCH__body);
patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put); patch__subject = pHost->GetLv2Urid(LV2_PATCH__subject);
patch_body = pHost->GetLv2Urid(LV2_PATCH__body); patch__property = pHost->GetLv2Urid(LV2_PATCH__property);
patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject); patch__value = pHost->GetLv2Urid(LV2_PATCH__value);
patch_property = pHost->GetLv2Urid(LV2_PATCH__property); units__frame = pHost->GetLv2Urid(LV2_UNITS__frame);
//patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept); state__StateChanged = pHost->GetLv2Urid(LV2_STATE__StateChanged);
patch_value = pHost->GetLv2Urid(LV2_PATCH__value);
unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame);
} }
//LV2_URID patch_accept; LV2_URID atom__Chunk;
LV2_URID units__frame;
LV2_URID unitsFrame;
LV2_URID pluginUri; LV2_URID pluginUri;
LV2_URID atom_Blank; LV2_URID atom__Bool;
LV2_URID atom_Bool; LV2_URID atom__Float;
LV2_URID atom_float; LV2_URID atom__Double;
LV2_URID atom_Double; LV2_URID atom__Int;
LV2_URID atom_Int; LV2_URID atom__Long;
LV2_URID atom_Long; LV2_URID atom__String;
LV2_URID atom_String; LV2_URID atom__Object;
LV2_URID atom_Object; LV2_URID atom__Vector;
LV2_URID atom_Vector; LV2_URID atom__Path;
LV2_URID atom_Path; LV2_URID atom__Sequence;
LV2_URID atom_Sequence; LV2_URID atom__URID;
LV2_URID atom_Chunk; LV2_URID midi__Event;
LV2_URID atom_URID; LV2_URID patch__Get;
LV2_URID atom_eventTransfer; LV2_URID patch__Set;
LV2_URID midi_Event; LV2_URID patch__Put;
LV2_URID patch_Get; LV2_URID patch__body;
LV2_URID patch_Set; LV2_URID patch__subject;
LV2_URID patch_Put; LV2_URID patch__property;
LV2_URID patch_body; LV2_URID patch__value;
LV2_URID patch_subject; LV2_URID state__StateChanged;
LV2_URID patch_property;
LV2_URID patch_value;
LV2_URID param_uiState;
}; };
Uris uris; Urids urids;
uint64_t instanceId; uint64_t instanceId;
BufferPool bufferPool; BufferPool bufferPool;
@@ -166,11 +169,13 @@ namespace pipedal
void BypassTo(float value); void BypassTo(float value);
public:
virtual void RequestParameter(LV2_URID uridUri); virtual bool GetLv2State(Lv2PluginState*state);
virtual void GatherParameter(RealtimeParameterRequest*pRequest); 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 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() { virtual uint8_t*GetAtomInputBuffer() {
if (this->inputAtomBuffers.size() == 0) return nullptr; if (this->inputAtomBuffers.size() == 0) return nullptr;
@@ -188,7 +193,7 @@ namespace pipedal
Lv2Effect( Lv2Effect(
IHost *pHost, IHost *pHost,
const std::shared_ptr<Lv2PluginInfo> &info, const std::shared_ptr<Lv2PluginInfo> &info,
const PedalBoardItem &pedalBoardItem); const PedalboardItem &pedalboardItem);
~Lv2Effect(); ~Lv2Effect();
@@ -228,6 +233,7 @@ namespace pipedal
controlValues[index] = value; controlValues[index] = value;
} }
} }
virtual float GetControlValue(int index) const { virtual float GetControlValue(int index) const {
if (index == -1) if (index == -1)
{ {
+1 -1
View File
@@ -19,7 +19,7 @@
#include "pch.h" #include "pch.h"
#include "Lv2EventBufferWriter.hpp" #include "Lv2EventBufferWriter.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
using namespace pipedal; using namespace pipedal;
+3 -3
View File
@@ -24,17 +24,17 @@
#include <string> #include <string>
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "MemDebug.hpp" #include "MemDebug.hpp"
using namespace pipedal; using namespace pipedal;
TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) { TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) {
MemStats initialMemory = GetMemStats(); MemStats initialMemory = GetMemStats();
{ {
PiPedalHost host; PluginHost host;
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2"); 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h" #include "pch.h"
#include "Lv2PedalBoard.hpp" #include "Lv2Pedalboard.hpp"
#include "Lv2Effect.hpp" #include "Lv2Effect.hpp"
@@ -27,15 +27,16 @@
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "AudioHost.hpp" #include "AudioHost.hpp"
#include "Lv2EventBufferWriter.hpp" #include "Lv2EventBufferWriter.hpp"
#include "Lv2Log.hpp"
using namespace pipedal; using namespace pipedal;
float *Lv2PedalBoard::CreateNewAudioBuffer() float *Lv2Pedalboard::CreateNewAudioBuffer()
{ {
return bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()); return bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
} }
std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels) std::vector<float *> Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
{ {
std::vector<float *> result; std::vector<float *> result;
for (int i = 0; i < nChannels; ++i) for (int i = 0; i < nChannels; ++i)
@@ -45,7 +46,7 @@ std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
return result; 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) for (int i = 0; i < realtimeEffects.size(); ++i)
{ {
@@ -57,8 +58,8 @@ int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbo
} }
return -1; return -1;
} }
std::vector<float *> Lv2PedalBoard::PrepareItems( std::vector<float *> Lv2Pedalboard::PrepareItems(
std::vector<PedalBoardItem> &items, std::vector<PedalboardItem> &items,
std::vector<float *> inputBuffers) std::vector<float *> inputBuffers)
{ {
for (int i = 0; i < items.size(); ++i) for (int i = 0; i < items.size(); ++i)
@@ -105,7 +106,14 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
else 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) if (pLv2Effect)
{ {
pEffect = pLv2Effect; pEffect = pLv2Effect;
@@ -198,57 +206,57 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
return inputBuffers; return inputBuffers;
} }
void Lv2PedalBoard::Prepare(IHost *pHost, PedalBoard &pedalBoard) void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
{ {
this->pHost = pHost; this->pHost = pHost;
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i) 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(); int nOutputs = pHost->GetNumberOfOutputAudioChannels();
if (nOutputs == 1) if (nOutputs == 1)
{ {
this->pedalBoardOutputBuffers.push_back(outputs[0]); this->pedalboardOutputBuffers.push_back(outputs[0]);
} }
else else
{ {
if (outputs.size() == 1) 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 else
{ {
this->pedalBoardOutputBuffers.push_back(outputs[0]); this->pedalboardOutputBuffers.push_back(outputs[0]);
this->pedalBoardOutputBuffers.push_back(outputs[1]); 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; const Lv2PluginInfo *pPluginInfo;
if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
{ {
pPluginInfo = GetSplitterPluginInfo(); pPluginInfo = GetSplitterPluginInfo();
} else { } else {
pPluginInfo = pluginInfo.get(); pPluginInfo = pluginInfo.get();
} }
int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId()); int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId());
if (pluginInfo && effectIndex != -1) 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; const Lv2PortInfo*pPortInfo;
int controlIndex; int controlIndex;
@@ -259,7 +267,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
} else { } else {
try { try {
pPortInfo = &pluginInfo->getPort(binding.symbol()); pPortInfo = &pluginInfo->getPort(binding.symbol());
controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol()); controlIndex = this->GetControlIndex(pedalboardItem.instanceId(), binding.symbol());
} catch (const std::exception&ignored) } catch (const std::exception&ignored)
{ {
continue; continue;
@@ -274,7 +282,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
mapping.effectIndex = effectIndex; mapping.effectIndex = effectIndex;
mapping.controlIndex = controlIndex; mapping.controlIndex = controlIndex;
mapping.midiBinding = binding; mapping.midiBinding = binding;
mapping.instanceId = pedalBoardItem.instanceId(); mapping.instanceId = pedalboardItem.instanceId();
if (pPortInfo->IsSwitch()) if (pPortInfo->IsSwitch())
{ {
mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary; 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); PrepareMidiMap(item);
auto pluginInfo = pHost->GetPluginInfo(item.uri()); auto pluginInfo = pHost->GetPluginInfo(item.uri());
@@ -335,14 +343,14 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
{ return left.key < right.key; }); { return left.key < right.key; });
} }
} }
void Lv2PedalBoard::Activate() void Lv2Pedalboard::Activate()
{ {
for (int i = 0; i < this->effects.size(); ++i) for (int i = 0; i < this->effects.size(); ++i)
{ {
this->realtimeEffects[i]->Activate(); this->realtimeEffects[i]->Activate();
} }
} }
void Lv2PedalBoard::Deactivate() void Lv2Pedalboard::Deactivate()
{ {
for (int i = 0; i < this->effects.size(); ++i) 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]; 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; 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) if (inputBuffers[i] == nullptr)
return false; 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) for (int i = 0; i < this->processActions.size(); ++i)
{ {
processActions[i](samples); 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) if (outputBuffers[i] == nullptr)
return false; return false;
Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples); Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples);
} }
return true; return true;
} }
float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex) float Lv2Pedalboard::GetControlOutputValue(int effectIndex, int portIndex)
{ {
auto effect = realtimeEffects[effectIndex]; auto effect = realtimeEffects[effectIndex];
return effect->GetOutputControlValue(portIndex); 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]; auto effect = realtimeEffects[effectIndex];
effect->SetControl(index, value); effect->SetControl(index, value);
} }
void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled) void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
{ {
auto effect = realtimeEffects[effectIndex]; auto effect = realtimeEffects[effectIndex];
effect->SetBypass(enabled); 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) 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) 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) while (pParameterRequests != nullptr)
{ {
@@ -446,34 +454,54 @@ void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParamete
if (pEffect == nullptr) if (pEffect == nullptr)
{ {
pParameterRequests->errorMessage = "No such effect."; 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; pParameterRequests = pParameterRequests->pNext;
} }
} }
void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests) void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
{ {
while (pParameterRequests != nullptr) while (pParameterRequests != nullptr)
{ {
IEffect *effect = this->GetEffect(pParameterRequests->instanceId); if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
if (effect == nullptr)
{ {
pParameterRequests->errorMessage = "No such effect."; IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
} if (effect == nullptr)
else {
{ pParameterRequests->errorMessage = "No such effect.";
effect->GatherParameter(pParameterRequests); } else if (effect->IsVst3())
{
pParameterRequests->errorMessage = "Not supported for VST3";
}
else
{
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect*>(effect);
pLv2Effect->GatherPatchProperties(pParameterRequests);
}
} }
pParameterRequests = pParameterRequests->pNext; pParameterRequests = pParameterRequests->pNext;
} }
} }
void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message, void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
void *callbackHandle, void *callbackHandle,
MidiCallbackFn *pfnCallback) MidiCallbackFn *pfnCallback)
+17 -17
View File
@@ -18,8 +18,8 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once #pragma once
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "Lv2Effect.hpp" #include "Lv2Effect.hpp"
#include "BufferPool.hpp" #include "BufferPool.hpp"
#include <functional> #include <functional>
@@ -31,15 +31,15 @@ namespace pipedal {
class RealtimeVuBuffers; class RealtimeVuBuffers;
class RealtimeParameterRequest; class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter; class RealtimeRingBufferWriter;
class Lv2PedalBoard { class Lv2Pedalboard {
IHost *pHost = nullptr; IHost *pHost = nullptr;
BufferPool bufferPool; BufferPool bufferPool;
std::vector<float*> pedalBoardInputBuffers; std::vector<float*> pedalboardInputBuffers;
std::vector<float*> pedalBoardOutputBuffers; std::vector<float*> pedalboardOutputBuffers;
std::vector<std::shared_ptr<IEffect> > effects; std::vector<std::shared_ptr<IEffect> > effects;
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!! std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
@@ -81,21 +81,21 @@ class Lv2PedalBoard {
std::vector<float*> PrepareItems( std::vector<float*> PrepareItems(
std::vector<PedalBoardItem> & items, std::vector<PedalboardItem> & items,
std::vector<float*> inputBuffers std::vector<float*> inputBuffers
); );
void PrepareMidiMap(const PedalBoard&pedalBoard); void PrepareMidiMap(const Pedalboard&pedalboard);
void PrepareMidiMap(const PedalBoardItem&pedalBoardItem); void PrepareMidiMap(const PedalboardItem&pedalboardItem);
std::vector<float*> AllocateAudioBuffers(int nChannels); 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); void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
public: public:
Lv2PedalBoard() { } Lv2Pedalboard() { }
~Lv2PedalBoard() { } ~Lv2Pedalboard() { }
void Prepare(IHost *pHost,PedalBoard&pedalBoard); void Prepare(IHost *pHost,Pedalboard&pedalboard);
std::vector<IEffect* > GetEffects() { return realtimeEffects; } std::vector<IEffect* > GetEffects() { return realtimeEffects; }
@@ -122,12 +122,12 @@ public:
void ResetAtomBuffers(); void ResetAtomBuffers();
void ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests); void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
void GatherParameterRequests(RealtimeParameterRequest *pParameterRequests); void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
std::vector<float*> &GetInputBuffers() { return this->pedalBoardInputBuffers;} std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
std::vector<float*> &GetoutputBuffers() { return this->pedalBoardOutputBuffers;} 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h" #include "pch.h"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
using namespace pipedal; 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) for (size_t i = 0; i < items.size(); ++i)
{ {
auto &item = items[i]; auto &item = items[i];
if (items[i].instanceId() == pedalBoardItemId) if (items[i].instanceId() == pedalboardItemId)
{ {
return &(items[i]); return &(items[i]);
} }
if (item.isSplit()) if (item.isSplit())
{ {
const PedalBoardItem* t = GetItem_(item.topChain(),pedalBoardItemId); const PedalboardItem* t = GetItem_(item.topChain(),pedalboardItemId);
if (t != nullptr) return t; if (t != nullptr) return t;
t = GetItem_(item.bottomChain(),pedalBoardItemId); t = GetItem_(item.bottomChain(),pedalboardItemId);
if (t != nullptr) return t; if (t != nullptr) return t;
} }
} }
return nullptr; 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); 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) ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
{
for (auto&propertyValue: this->propertyValues_)
{
if (propertyValue.propertyUri() == propertyUri)
{
return &propertyValue;
}
}
return nullptr;
}
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
{ {
for (size_t i = 0; i < this->controlValues().size(); ++i) for (size_t i = 0; i < this->controlValues().size(); ++i)
{ {
@@ -77,9 +86,9 @@ ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
return nullptr; 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) return false;
if (item->isEnabled() != enabled) 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; if (!item) return false;
ControlValue*controlValue = item->GetControlValue(symbol); ControlValue*controlValue = item->GetControlValue(symbol);
if (controlValue == nullptr) return false; 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(); uint64_t instanceId = NextInstanceId();
PedalBoardItem result; PedalboardItem result;
result.instanceId(instanceId); result.instanceId(instanceId);
result.uri(EMPTY_PEDALBOARD_ITEM_URI); result.uri(EMPTY_PEDALBOARD_ITEM_URI);
result.pluginName(""); result.pluginName("");
@@ -119,11 +128,11 @@ PedalBoardItem PedalBoard::MakeEmptyItem()
} }
PedalBoardItem PedalBoard::MakeSplit() PedalboardItem Pedalboard::MakeSplit()
{ {
uint64_t instanceId = NextInstanceId(); uint64_t instanceId = NextInstanceId();
PedalBoardItem result; PedalboardItem result;
result.instanceId(instanceId); result.instanceId(instanceId);
result.uri(SPLIT_PEDALBOARD_ITEM_URI); result.uri(SPLIT_PEDALBOARD_ITEM_URI);
result.pluginName(""); result.pluginName("");
@@ -151,10 +160,10 @@ PedalBoardItem PedalBoard::MakeSplit()
PedalBoard PedalBoard::MakeDefault() Pedalboard Pedalboard::MakeDefault()
{ {
// copy insanity. but it happens so rarely. // copy insanity. but it happens so rarely.
PedalBoard result; Pedalboard result;
auto split = result.MakeSplit(); auto split = result.MakeSplit();
split.topChain().push_back(result.MakeEmptyItem()); 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; return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
} }
@@ -192,29 +201,24 @@ JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,value) JSON_MAP_REFERENCE(ControlValue,value)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PropertyValue)
JSON_MAP_REFERENCE(PropertyValue,propertyUri)
JSON_MAP_REFERENCE(PropertyValue,value) JSON_MAP_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_END()
JSON_MAP_BEGIN(Pedalboard)
JSON_MAP_BEGIN(PedalBoardItem) JSON_MAP_REFERENCE(Pedalboard,name)
JSON_MAP_REFERENCE(PedalBoardItem,instanceId) JSON_MAP_REFERENCE(Pedalboard,items)
JSON_MAP_REFERENCE(PedalBoardItem,uri) JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
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_END() JSON_MAP_END()
+19 -39
View File
@@ -22,6 +22,7 @@
#include "json.hpp" #include "json.hpp"
#include "json_variant.hpp" #include "json_variant.hpp"
#include "MidiBinding.hpp" #include "MidiBinding.hpp"
#include "StateInterface.hpp"
namespace pipedal { namespace pipedal {
@@ -73,55 +74,34 @@ public:
}; };
class PropertyValue {
private:
std::string propertyUri_;
json_variant value_;
public:
PropertyValue()
{
} class PedalboardItem: public JsonMemberWritable {
template <typename T>
PropertyValue(const std::string&propertyUri, T value)
:propertyUri_(propertyUri)
, value_(value)
{
}
GETTER_SETTER_REF(propertyUri)
GETTER_SETTER_REF(value)
DECLARE_JSON_MAP(PropertyValue);
};
class PedalBoardItem: public JsonMemberWritable {
int64_t instanceId_ = 0; int64_t instanceId_ = 0;
std::string uri_; std::string uri_;
std::string pluginName_; std::string pluginName_;
bool isEnabled_ = true; bool isEnabled_ = true;
std::vector<ControlValue> controlValues_; std::vector<ControlValue> controlValues_;
std::vector<PropertyValue> propertyValues_; std::vector<PedalboardItem> topChain_;
std::vector<PedalBoardItem> topChain_; std::vector<PedalboardItem> bottomChain_;
std::vector<PedalBoardItem> bottomChain_;
std::vector<MidiBinding> midiBindings_; std::vector<MidiBinding> midiBindings_;
std::string vstState_; std::string vstState_;
Lv2PluginState lv2State_;
public: public:
ControlValue*GetControlValue(const std::string&symbol); 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(instanceId)
GETTER_SETTER_REF(uri) GETTER_SETTER_REF(uri)
GETTER_SETTER_REF(vstState); GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName) GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled) GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues) GETTER_SETTER_VEC(controlValues)
GETTER_SETTER_VEC(propertyValues)
GETTER_SETTER_VEC(topChain) GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain) GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings) GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER_REF(lv2State)
bool isSplit() const bool isSplit() const
@@ -137,7 +117,6 @@ public:
writer.write_member("uri",uri_); writer.write_member("uri",uri_);
writer.write_member("pluginName",pluginName_); writer.write_member("pluginName",pluginName_);
writer.write_member("isEnabled",isEnabled_); writer.write_member("isEnabled",isEnabled_);
writer.write_member("controlValues",controlValues_);
if (isSplit()) if (isSplit())
{ {
writer.write_member("topChain",topChain_); 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::string name_;
std::vector<PedalBoardItem> items_; std::vector<PedalboardItem> items_;
uint64_t nextInstanceId_ = 0; uint64_t nextInstanceId_ = 0;
uint64_t NextInstanceId() { return ++nextInstanceId_; } uint64_t NextInstanceId() { return ++nextInstanceId_; }
@@ -159,8 +138,9 @@ public:
bool SetControlValue(long pedalItemId, const std::string &symbol, float value); bool SetControlValue(long pedalItemId, const std::string &symbol, float value);
bool SetItemEnabled(long pedalItemId, bool enabled); bool SetItemEnabled(long pedalItemId, bool enabled);
PedalBoardItem*GetItem(long pedalItemId); PedalboardItem*GetItem(long pedalItemId);
const PedalBoardItem*GetItem(long pedalItemId) const; const PedalboardItem*GetItem(long pedalItemId) const;
std::vector<PedalboardItem*>GetAllPlugins();
bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; } bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
@@ -168,13 +148,13 @@ public:
GETTER_SETTER_VEC(items) GETTER_SETTER_VEC(items)
DECLARE_JSON_MAP(PedalBoard); DECLARE_JSON_MAP(Pedalboard);
PedalBoardItem MakeEmptyItem(); PedalboardItem MakeEmptyItem();
PedalBoardItem MakeSplit(); PedalboardItem MakeSplit();
static PedalBoard MakeDefault(); static Pedalboard MakeDefault();
}; };
#undef GETTER_SETTER_REF #undef GETTER_SETTER_REF
+1 -1
View File
@@ -198,7 +198,7 @@ public:
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.GetSampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection); audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
+324 -193
View File
File diff suppressed because it is too large Load Diff
+57 -30
View File
@@ -19,9 +19,9 @@
#pragma once #pragma once
#include <mutex> #include <mutex>
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "GovernorSettings.hpp" #include "GovernorSettings.hpp"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "Storage.hpp" #include "Storage.hpp"
#include "Banks.hpp" #include "Banks.hpp"
#include "JackConfiguration.hpp" #include "JackConfiguration.hpp"
@@ -37,6 +37,8 @@
#include "AdminClient.hpp" #include "AdminClient.hpp"
#include "AvahiService.hpp" #include "AvahiService.hpp"
#include <thread> #include <thread>
#include "Promise.hpp"
#include "AtomConverter.hpp"
namespace pipedal namespace pipedal
{ {
@@ -50,8 +52,9 @@ namespace pipedal
virtual int64_t GetClientId() = 0; virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 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 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 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 OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0; virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 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 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 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 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 OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0; virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0; virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0; virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0; virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 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; virtual void Close() = 0;
}; };
class PiPedalModel : private IAudioHostCallbacks class PiPedalModel : private IAudioHostCallbacks
{ {
private: private:
std::unique_ptr<std::jthread> pingThread; std::unique_ptr<std::jthread> pingThread;
std::vector<MidiBinding> systemMidiBindings; std::vector<MidiBinding> systemMidiBindings;
@@ -97,9 +102,14 @@ namespace pipedal
class AtomOutputListener class AtomOutputListener
{ {
public: 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 clientId;
int64_t clientHandle; int64_t clientHandle;
uint64_t instanceId; uint64_t instanceId;
LV2_URID propertyUrid;
}; };
void DeleteMidiListeners(int64_t clientId); void DeleteMidiListeners(int64_t clientId);
void DeleteAtomOutputListeners(int64_t clientId); void DeleteAtomOutputListeners(int64_t clientId);
@@ -108,27 +118,29 @@ namespace pipedal
std::vector<AtomOutputListener> atomOutputListeners; std::vector<AtomOutputListener> atomOutputListeners;
JackServerSettings jackServerSettings; JackServerSettings jackServerSettings;
PiPedalHost lv2Host; PluginHost lv2Host;
PedalBoard pedalBoard; AtomConverter atomConverter; // must be AFTER lv2Host!
Pedalboard pedalboard;
Storage storage; Storage storage;
bool hasPresetChanged = false; bool hasPresetChanged = false;
std::unique_ptr<AudioHost> audioHost; std::unique_ptr<AudioHost> audioHost;
JackConfiguration jackConfiguration; JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard; std::shared_ptr<Lv2Pedalboard> lv2Pedalboard;
std::filesystem::path webRoot; std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber *> subscribers; std::vector<IPiPedalModelSubscriber *> subscribers;
void SetPresetChanged(int64_t clientId, bool value); void SetPresetChanged(int64_t clientId, bool value);
void FirePresetsChanged(int64_t clientId); void FirePresetsChanged(int64_t clientId);
void FirePluginPresetsChanged(const std::string &pluginUri); void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalBoardChanged(int64_t clientId); void FirePedalboardChanged(int64_t clientId);
void FireChannelSelectionChanged(int64_t clientId); void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId); void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration); void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void UpdateDefaults(PedalBoardItem *pedalBoardItem); void UpdateDefaults(PedalboardItem *pedalboardItem);
void UpdateDefaults(PedalBoard *pedalBoard); void UpdateDefaults(Pedalboard *pedalboard);
class VuSubscription class VuSubscription
{ {
@@ -147,20 +159,23 @@ namespace pipedal
void RestartAudio(); void RestartAudio();
std::vector<RealtimeParameterRequest *> outstandingParameterRequests; std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId); IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
private: // IAudioHostCallbacks private: // IAudioHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates); virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update); virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value); virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl); virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson); virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest); virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request); 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; PiPedalConfiguration configuration;
@@ -171,6 +186,8 @@ namespace pipedal
uint16_t GetWebPort() const { return webPort; } uint16_t GetWebPort() const { return webPort; }
void Close(); void Close();
void SetOnboarding(bool value);
void UpdateDnsSd(); void UpdateDnsSd();
AdminClient &GetAdminClient() { return adminClient; } AdminClient &GetAdminClient() { return adminClient; }
@@ -180,11 +197,11 @@ namespace pipedal
void LoadLv2PluginInfo(); void LoadLv2PluginInfo();
void Load(); void Load();
const PiPedalHost &GetLv2Host() const { return lv2Host; } const PluginHost &GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy() Pedalboard GetCurrentPedalboardCopy()
{ {
std::lock_guard<std::recursive_mutex> guard(mutex); 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); PluginUiPresets GetPluginUiPresets(const std::string &pluginUri);
PluginPresets GetPluginPresets(const std::string &pluginUri); PluginPresets GetPluginPresets(const std::string &pluginUri);
@@ -194,15 +211,16 @@ namespace pipedal
void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber); void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber);
void RemoveNotificationSubsription(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 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 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); void GetPresets(PresetIndex *pResult);
PedalBoard GetPreset(int64_t instanceId); Pedalboard GetPreset(int64_t instanceId);
void GetBank(int64_t instanceId, BankFile *pBank); void GetBank(int64_t instanceId, BankFile *pBank);
int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1); 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); int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
void UnmonitorPort(int64_t subscriptionHandle); void UnmonitorPort(int64_t subscriptionHandle);
void GetLv2Parameter( void SendGetPatchProperty(
int64_t clientId, int64_t clientId,
int64_t instanceId, int64_t instanceId,
const std::string uri, const std::string uri,
std::function<void(const std::string &jsonResjult)> onSuccess, std::function<void(const std::string &jsonResjult)> onSuccess,
std::function<void(const std::string &error)> onError); 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; BankIndex GetBankIndex() const;
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName); 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); 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 ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle); void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId); void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri);
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle); void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
std::vector<AlsaDeviceInfo> GetAlsaDevices(); std::vector<AlsaDeviceInfo> GetAlsaDevices();
const std::filesystem::path &GetWebRoot() const; const std::filesystem::path &GetWebRoot() const;
@@ -282,7 +309,7 @@ namespace pipedal
std::map<std::string, bool> GetFavorites() const; std::map<std::string, bool> GetFavorites() const;
void SetFavorites(const std::map<std::string, bool> &favorites); void SetFavorites(const std::map<std::string, bool> &favorites);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty); std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
}; };
} // namespace pipedal. } // namespace pipedal.
+170 -84
View File
@@ -30,6 +30,7 @@
#include <future> #include <future>
#include <atomic> #include <atomic>
#include "Ipv6Helpers.hpp" #include "Ipv6Helpers.hpp"
#include "Promise.hpp"
#include "AdminClient.hpp" #include "AdminClient.hpp"
#include "WifiConfigSettings.hpp" #include "WifiConfigSettings.hpp"
@@ -42,6 +43,33 @@
using namespace std; using namespace std;
using namespace pipedal; 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 class NotifyMidiListenerBody
{ {
public: public:
@@ -61,14 +89,15 @@ class NotifyAtomOutputBody
public: public:
int64_t clientHandle_; int64_t clientHandle_;
uint64_t instanceId_; uint64_t instanceId_;
std::string propertyUri_;
std::string atomJson_; raw_json_string atomJson_;
DECLARE_JSON_MAP(NotifyAtomOutputBody); DECLARE_JSON_MAP(NotifyAtomOutputBody);
}; };
JSON_MAP_BEGIN(NotifyAtomOutputBody) JSON_MAP_BEGIN(NotifyAtomOutputBody)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle) JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId) JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, propertyUri)
JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson) JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson)
JSON_MAP_END() JSON_MAP_END()
@@ -85,17 +114,19 @@ JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly)
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle) JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
JSON_MAP_END() JSON_MAP_END()
class ListenForAtomOutputBody class MonitorPatchPropertyBody
{ {
public: public:
uint64_t instanceId_; uint64_t instanceId_;
int64_t handle_; int64_t clientHandle_;
DECLARE_JSON_MAP(ListenForAtomOutputBody); std::string propertyUri_;
DECLARE_JSON_MAP(MonitorPatchPropertyBody);
}; };
JSON_MAP_BEGIN(ListenForAtomOutputBody) JSON_MAP_BEGIN(MonitorPatchPropertyBody)
JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId) JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle) JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri)
JSON_MAP_END() JSON_MAP_END()
class OnLoadPluginPresetBody class OnLoadPluginPresetBody
@@ -151,18 +182,6 @@ JSON_MAP_REFERENCE(MonitorResultBody, subscriptionHandle)
JSON_MAP_REFERENCE(MonitorResultBody, value) JSON_MAP_REFERENCE(MonitorResultBody, value)
JSON_MAP_END() 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 class MonitorPortBody
{ {
@@ -262,33 +281,33 @@ JSON_MAP_REFERENCE(CopyPluginPresetBody, pluginUri)
JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId) JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId)
JSON_MAP_END() JSON_MAP_END()
class PedalBoardItemEnabledBody class PedalboardItemEnabledBody
{ {
public: public:
int64_t clientId_ = -1; int64_t clientId_ = -1;
int64_t instanceId_ = -1; int64_t instanceId_ = -1;
bool enabled_ = true; bool enabled_ = true;
DECLARE_JSON_MAP(PedalBoardItemEnabledBody); DECLARE_JSON_MAP(PedalboardItemEnabledBody);
}; };
JSON_MAP_BEGIN(PedalBoardItemEnabledBody) JSON_MAP_BEGIN(PedalboardItemEnabledBody)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, clientId) JSON_MAP_REFERENCE(PedalboardItemEnabledBody, clientId)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId) JSON_MAP_REFERENCE(PedalboardItemEnabledBody, instanceId)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled) JSON_MAP_REFERENCE(PedalboardItemEnabledBody, enabled)
JSON_MAP_END() JSON_MAP_END()
class UpdateCurrentPedalBoardBody class UpdateCurrentPedalboardBody
{ {
public: public:
int64_t clientId_ = -1; int64_t clientId_ = -1;
PedalBoard pedalBoard_; Pedalboard pedalboard_;
DECLARE_JSON_MAP(UpdateCurrentPedalBoardBody); DECLARE_JSON_MAP(UpdateCurrentPedalboardBody);
}; };
JSON_MAP_BEGIN(UpdateCurrentPedalBoardBody) JSON_MAP_BEGIN(UpdateCurrentPedalboardBody)
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, clientId) JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId)
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, pedalBoard) JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
JSON_MAP_END() JSON_MAP_END()
class ChannelSelectionChangedBody class ChannelSelectionChangedBody
@@ -335,6 +354,24 @@ JSON_MAP_REFERENCE(ControlChangedBody, symbol)
JSON_MAP_REFERENCE(ControlChangedBody, value) JSON_MAP_REFERENCE(ControlChangedBody, value)
JSON_MAP_END() 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 class Vst3ControlChangedBody
{ {
public: public:
@@ -420,14 +457,19 @@ public:
std::stringstream imageList; std::stringstream imageList;
const std::filesystem::path &webRoot = model.GetWebRoot() / "img"; const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
bool firstTime = true; bool firstTime = true;
for (const auto &entry : std::filesystem::directory_iterator(webRoot)) try {
{ for (const auto &entry : std::filesystem::directory_iterator(webRoot))
if (!firstTime)
{ {
imageList << ";"; if (!firstTime)
{
imageList << ";";
}
firstTime = false;
imageList << entry.path().filename().string();
} }
firstTime = false; } catch (const std::exception&)
imageList << entry.path().filename().string(); {
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(); this->imageList = imageList.str();
} }
@@ -778,18 +820,18 @@ public:
} }
return; return;
} }
if (message == "previewControl") if (message == "setControl")
{
ControlChangedBody message;
pReader->read(&message);
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
}
else if (message == "setControl")
{ {
ControlChangedBody message; ControlChangedBody message;
pReader->read(&message); pReader->read(&message);
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_); 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") else if (message == "listenForMidiEvent")
{ {
ListenForMidiEventBody body; ListenForMidiEventBody body;
@@ -802,17 +844,17 @@ public:
pReader->read(&handle); pReader->read(&handle);
this->model.CancelListenForMidiEvent(this->clientId, handle); this->model.CancelListenForMidiEvent(this->clientId, handle);
} }
else if (message == "listenForAtomOutput") else if (message == "monitorPatchProperty")
{ {
ListenForAtomOutputBody body; MonitorPatchPropertyBody body;
pReader->read(&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; int64_t handle;
pReader->read(&handle); pReader->read(&handle);
this->model.CancelListenForMidiEvent(this->clientId, handle); this->model.CancelMonitorPatchProperty(this->clientId, handle);
} }
else if (message == "getJackStatus") else if (message == "getJackStatus")
@@ -954,25 +996,25 @@ public:
this->model.GetPresets(&presets); this->model.GetPresets(&presets);
Reply(replyTo, "getPresets", presets); Reply(replyTo, "getPresets", presets);
} }
else if (message == "setPedalBoardItemEnable") else if (message == "setPedalboardItemEnable")
{ {
PedalBoardItemEnabledBody body; PedalboardItemEnabledBody body;
pReader->read(&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); 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(); auto pedalboard = model.GetCurrentPedalboardCopy();
Reply(replyTo, "currentPedalBoard", pedalBoard); Reply(replyTo, "currentPedalboard", pedalboard);
} }
else if (message == "plugins") else if (message == "plugins")
{ {
@@ -1142,18 +1184,35 @@ public:
uint64_t result = model.CopyPluginPreset(body.pluginUri_, body.instanceId_); uint64_t result = model.CopyPluginPreset(body.pluginUri_, body.instanceId_);
this->Reply(replyTo, "copyPluginPreset", result); 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); pReader->read(&body);
model.GetLv2Parameter( model.SendGetPatchProperty(
this->clientId, this->clientId,
body.instanceId_, body.instanceId_,
body.uri_, body.propertyUri_,
[this, replyTo](const std::string &jsonResult) [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) [this, replyTo](const std::string &error)
{ {
@@ -1248,10 +1307,17 @@ public:
} }
else if (message == "requestFileList") else if (message == "requestFileList")
{ {
PiPedalFileProperty fileProperty; UiFileProperty fileProperty;
pReader->read(&fileProperty);
std::vector<std::string> list = this->model.GetFileList(fileProperty); std::vector<std::string> list = this->model.GetFileList(fileProperty);
this->Reply(replyTo,"requestFileList",list); this->Reply(replyTo,"requestFileList",list);
} }
else if (message == "setOnboarding")
{
bool value;
pReader->read(&value);
this->model.SetOnboarding(value);
}
else else
{ {
Lv2Log::error("Unknown message received: %s", message.c_str()); Lv2Log::error("Unknown message received: %s", message.c_str());
@@ -1260,8 +1326,7 @@ public:
} }
protected: protected:
virtual void virtual void onReceive(const std::string_view &text)
onReceive(const std::string_view &text)
{ {
view_istream<char> s(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) { virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
Send("onSystemMidiBindingsChanged",bindings); Send("onSystemMidiBindingsChanged",bindings);
} }
@@ -1499,13 +1579,13 @@ public:
public: public:
int64_t clientHandle; int64_t clientHandle;
uint64_t instanceId; uint64_t instanceId;
std::string atomType; std::string propertyUri;
std::string json; std::string json;
}; };
std::vector<PendingNotifyAtomOutput> pendingNotifyAtomOutputs; std::vector<PendingNotifyAtomOutput> pendingNotifyAtomOutputs;
void OnAckNotifyAtomOutput() void OnAckNotifyPatchProperty()
{ {
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex); std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
if (--outstandingNotifyAtomOutputs <= 0) if (--outstandingNotifyAtomOutputs <= 0)
@@ -1518,19 +1598,24 @@ public:
OnNotifyAtomOutput( OnNotifyAtomOutput(
t.clientHandle, t.clientHandle,
t.instanceId, t.instanceId,
t.atomType, t.propertyUri,
t.json); 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; NotifyAtomOutputBody body;
body.clientHandle_ = clientHandle; body.clientHandle_ = clientHandle;
body.instanceId_ = instanceId; 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); std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
@@ -1539,23 +1624,24 @@ public:
for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i) for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i)
{ {
auto &output = pendingNotifyAtomOutputs[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; // better to erase than overwrite, since it provides better idempotence.
return; pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i);
break;
} }
} }
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomType, atomJson}); pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomProperty, atomJson});
return; return;
} }
++outstandingNotifyAtomOutputs; ++outstandingNotifyAtomOutputs;
} }
Request<bool>( Request<bool>(
"onNotifyAtomOut", body, "onNotifyPatchProperty", body,
[this](const bool &value) [this](const bool &value)
{ {
this->OnAckNotifyAtomOutput(); this->OnAckNotifyPatchProperty();
}, },
[](const std::exception &e) { [](const std::exception &e) {
@@ -1594,17 +1680,17 @@ public:
Send("onGovernorSettingsChanged", governor); 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.clientId_ = clientId;
body.pedalBoard_ = pedalBoard; body.pedalboard_ = pedalboard;
Send("onPedalBoardChanged", body); Send("onPedalboardChanged", body);
} }
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled)
{ {
PedalBoardItemEnabledBody body; PedalboardItemEnabledBody body;
body.clientId_ = clientId; body.clientId_ = clientId;
body.instanceId_ = pedalItemId; body.instanceId_ = pedalItemId;
body.enabled_ = enabled; body.enabled_ = enabled;
+103 -39
View File
@@ -23,11 +23,12 @@
*/ */
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "ss.hpp"
using namespace pipedal; 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(); auto pWorld = pHost->getWorld();
@@ -37,18 +38,35 @@ PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
const LilvNode *fileNode = lilv_nodes_get(fileNodes, i); const LilvNode *fileNode = lilv_nodes_get(fileNodes, i);
try 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)); this->fileProperites_.push_back(std::move(fileUI));
} }
catch (const std::exception &e) 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); lilv_nodes_free(fileNodes);
} }
PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) { PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
{
auto pWorld = pHost->getWorld(); auto pWorld = pHost->getWorld();
AutoLilvNode name = lilv_world_get( 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."); 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(); 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."); 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( AutoLilvNode patchProperty = lilv_world_get(
@@ -123,24 +140,19 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod
if (patchProperty) if (patchProperty)
{ {
this->patchProperty_ = patchProperty.AsUri(); this->patchProperty_ = patchProperty.AsUri();
} else { }
else
{
throw std::logic_error("PiPedal FileProperty is missing pipedalui:patchProperty value."); 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; 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); LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr);
LILV_FOREACH(nodes, i, fileTypeNodes) LILV_FOREACH(nodes, i, fileTypeNodes)
@@ -160,35 +172,33 @@ std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const
return result; 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 ( if (
(c >= '0' && c <= '9') (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c == '_')
) { )
{
continue; continue;
} }
return false; return false;
} }
return true; 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); 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) if (fileType.fileExtension() == extension)
{ {
@@ -198,15 +208,69 @@ bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const
return false; 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();
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(PiPedalFileType) JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType,name) JSON_MAP_REFERENCE(PiPedalFileType, name)
JSON_MAP_REFERENCE(PiPedalFileType,fileExtension) JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PiPedalFileProperty) JSON_MAP_BEGIN(UiFileProperty)
JSON_MAP_REFERENCE(PiPedalFileProperty,patchProperty) JSON_MAP_REFERENCE(UiFileProperty, name)
JSON_MAP_REFERENCE(PiPedalFileProperty,name) JSON_MAP_REFERENCE(UiFileProperty, directory)
JSON_MAP_REFERENCE(PiPedalFileProperty,defaultFile) JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
JSON_MAP_REFERENCE(PiPedalFileProperty,fileTypes) JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
JSON_MAP_END() JSON_MAP_END()
+46 -14
View File
@@ -28,6 +28,7 @@
#include <memory> #include <memory>
#include <lilv/lilv.h> #include <lilv/lilv.h>
#include "json.hpp" #include "json.hpp"
#include <filesystem>
#define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui" #define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui"
@@ -38,7 +39,6 @@
#define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties" #define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties"
#define PIPEDAL_UI__fileProperty PIPEDAL_UI_PREFIX "fileProperty" #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__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory" #define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes" #define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
@@ -53,7 +53,7 @@
namespace pipedal { namespace pipedal {
class PiPedalHost; class PluginHost;
class PiPedalFileType { class PiPedalFileType {
private: private:
@@ -61,9 +61,9 @@ namespace pipedal {
std::string fileExtension_; std::string fileExtension_;
public: public:
PiPedalFileType() { } 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& name() const { return name_;}
const std::string &fileExtension() const { return fileExtension_; } 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: private:
std::string name_; std::string name_;
std::string directory_; std::string directory_;
std::vector<PiPedalFileType> fileTypes_; std::vector<PiPedalFileType> fileTypes_;
std::string patchProperty_; std::string patchProperty_;
std::string defaultFile_;
public: public:
using ptr = std::shared_ptr<PiPedalFileProperty>; using ptr = std::shared_ptr<UiFileProperty>;
PiPedalFileProperty() { } UiFileProperty() { }
PiPedalFileProperty(PiPedalHost*pHost, const LilvNode*node); UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
const std::string &name() const { return name_; } const std::string &name() const { return name_; }
const std::string &directory() const { return directory_; } const std::string &directory() const { return directory_; }
const std::string &defaultFile() const { return defaultFile_; }
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; } const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
@@ -97,19 +113,35 @@ namespace pipedal {
static bool IsDirectoryNameValid(const std::string&value); static bool IsDirectoryNameValid(const std::string&value);
public: public:
DECLARE_JSON_MAP(PiPedalFileProperty); DECLARE_JSON_MAP(UiFileProperty);
}; };
class PiPedalUI { class PiPedalUI {
public: public:
using ptr = std::shared_ptr<PiPedalUI>; using ptr = std::shared_ptr<PiPedalUI>;
PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode); PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
const std::vector<PiPedalFileProperty::ptr>& fileProperties() const const std::vector<UiFileProperty::ptr>& fileProperties() const
{ {
return fileProperites_; 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: 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. // Utiltities for validating file paths received via PiPedalFileProperty-related APIs.
+269 -201
View File
@@ -18,19 +18,20 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h" #include "pch.h"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include <lilv/lilv.h> #include <lilv/lilv.h>
#include <stdexcept> #include <stdexcept>
#include <locale> #include <locale>
#include <string_view> #include <string_view>
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <functional> #include <functional>
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "Lv2Effect.hpp" #include "Lv2Effect.hpp"
#include "Lv2PedalBoard.hpp" #include "Lv2Pedalboard.hpp"
#include "OptionsFeature.hpp" #include "OptionsFeature.hpp"
#include "JackConfiguration.hpp" #include "JackConfiguration.hpp"
#include "lv2/urid.lv2/urid.h" #include "lv2/urid.lv2/urid.h"
#include "lv2/ui.lv2/ui.h"
#include "lv2.h" #include "lv2.h"
#include "lv2/atom.lv2/atom.h" #include "lv2/atom.lv2/atom.h"
#include "lv2/time/time.h" #include "lv2/time/time.h"
@@ -86,27 +87,37 @@ namespace pipedal
static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent"; static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation"; static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
class PiPedalHost::Urids class PluginHost::Urids
{ {
public: public:
Urids(MapFeature &mapFeature) Urids(MapFeature &mapFeature)
{ {
atom_Float = mapFeature.GetUrid(LV2_ATOM__Float); 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 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 = this->vst3CachePath =
std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json"; std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json";
this->vst3Enabled = configuration.IsVst3Enabled(); 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); logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC);
display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY); display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY);
range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS); 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); lv2Core__name = lilv_new_uri(pWorld, LV2_CORE__name);
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui); 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__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
pipedalUI__directory = lilv_new_uri(pWorld,PIPEDAL_UI__directory); pipedalUI__directory = lilv_new_uri(pWorld, PIPEDAL_UI__directory);
pipedalUI__fileTypes = lilv_new_uri(pWorld,PIPEDAL_UI__fileTypes); pipedalUI__fileTypes = lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
pipedalUI__fileProperty = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperty); pipedalUI__fileProperty = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperty);
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension); pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts); pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text); 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_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat); time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
@@ -144,7 +175,7 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo); appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
} }
void PiPedalHost::LilvUris::Free() void PluginHost::LilvUris::Free()
{ {
rdfsComment.Free(); rdfsComment.Free();
logarithic_uri.Free(); logarithic_uri.Free();
@@ -214,45 +245,56 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0)
return default_; return default_;
} }
PiPedalHost::PiPedalHost() void PluginHost::SetPluginStoragePath(const std::filesystem::path &path)
{
mapPathFeature.SetPluginStoragePath(path);
}
PluginHost::PluginHost()
: mapPathFeature("")
{ {
pWorld = nullptr; pWorld = nullptr;
LV2_Feature **features = new LV2_Feature *[10]; LV2_Feature **features = new LV2_Feature *[10];
features[0] = const_cast<LV2_Feature *>(mapFeature.GetMapFeature()); lv2Features.push_back(mapFeature.GetMapFeature());
features[1] = const_cast<LV2_Feature *>(mapFeature.GetUnmapFeature()); lv2Features.push_back(mapFeature.GetUnmapFeature());
logFeature.Prepare(&mapFeature); logFeature.Prepare(&mapFeature);
features[2] = const_cast<LV2_Feature *>(logFeature.GetFeature()); lv2Features.push_back(logFeature.GetFeature());
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize()); 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); 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()) if (configuration.isValid())
{ {
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size(); this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size(); this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
this->maxBufferSize = configuration.GetBlockLength(); this->maxBufferSize = configuration.blockLength();
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize()); optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize());
} }
} }
PiPedalHost::~PiPedalHost() PluginHost::~PluginHost()
{ {
delete[] lv2Features;
lilvUris.Free(); lilvUris.Free();
free_world(); free_world();
delete urids; delete urids;
} }
void PiPedalHost::free_world() void PluginHost::free_world()
{ {
if (pWorld) if (pWorld)
{ {
@@ -260,7 +302,7 @@ void PiPedalHost::free_world()
pWorld = nullptr; 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); auto it = this->classesMap.find(uri);
if (it == this->classesMap.end()) if (it == this->classesMap.end())
@@ -268,7 +310,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &u
return (*it).second; return (*it).second;
} }
void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass) void PluginHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
{ {
this->classesMap[pluginClass->uri()] = pluginClass; this->classesMap[pluginClass->uri()] = pluginClass;
for (int i = 0; i < pluginClass->children_.size(); ++i) 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(); child->parent_ = pluginClass.get();
} }
} }
void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile) void PluginHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
{ {
std::ifstream f(jsonFile); std::ifstream f(jsonFile);
json_reader reader(f); json_reader reader(f);
@@ -292,7 +334,7 @@ void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
MAP_CHECK(); MAP_CHECK();
} }
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClass *pClass) std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const LilvPluginClass *pClass)
{ {
MAP_CHECK(); MAP_CHECK();
@@ -300,7 +342,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClas
std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri]; std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri];
return pResult; 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)); std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
auto t = this->classesMap.find(uri); auto t = this->classesMap.find(uri);
@@ -320,7 +362,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginCla
return result; return result;
} }
void PiPedalHost::LoadPluginClassesFromLilv() void PluginHost::LoadPluginClassesFromLilv()
{ {
const auto classes = lilv_world_get_plugin_classes(pWorld); const auto classes = lilv_world_get_plugin_classes(pWorld);
@@ -338,19 +380,19 @@ void PiPedalHost::LoadPluginClassesFromLilv()
if (lv2Class && lv2Class->parent_uri().size() != 0) if (lv2Class && lv2Class->parent_uri().size() != 0)
{ {
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri()); std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
if (parentClass) if (parentClass)
{ {
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get()); Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
ccClass->set_parent(parentClass); ccClass->set_parent(parentClass);
Lv2PluginClass *ccParentClass = parentClass.get(); Lv2PluginClass *ccParentClass = parentClass.get();
ccParentClass->add_child(lv2Class); ccParentClass->add_child(lv2Class);
} }
} }
} }
} }
void PiPedalHost::Load(const char *lv2Path) void PluginHost::Load(const char *lv2Path)
{ {
this->plugins_.clear(); this->plugins_.clear();
@@ -388,30 +430,30 @@ void PiPedalHost::Load(const char *lv2Path)
if (pluginInfo->hasCvPorts()) 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 #if !SUPPORT_MIDI
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN) 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 #endif
else if (!pluginInfo->is_valid()) else if (!pluginInfo->is_valid())
{ {
auto &ports = pluginInfo->ports(); auto &ports = pluginInfo->ports();
for (int i = 0; i < ports.size(); ++i) for (int i = 0; i < ports.size(); ++i)
{
auto &port = ports[i];
if (!port->is_valid())
{ {
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 else
{ {
this->plugins_.push_back(pluginInfo); this->plugins_.push_back(pluginInfo);
} }
} }
auto messages = stdoutCapture.GetOutputLines(); auto messages = stdoutCapture.GetOutputLines();
@@ -440,28 +482,28 @@ void PiPedalHost::Load(const char *lv2Path)
if (plugin->is_valid()) if (plugin->is_valid())
{ {
#if SUPPORT_MIDI #if SUPPORT_MIDI
if (info.audio_inputs() == 0 && !info.has_midi_input()) 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()); 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()) 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()); Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
} }
#else #else
if (info.audio_inputs() == 0) if (info.audio_inputs() == 0)
{ {
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str()); Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
} }
else if (info.audio_outputs() == 0) else if (info.audio_outputs() == 0)
{ {
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str()); Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
} }
#endif #endif
else else
{ {
ui_plugins_.push_back(std::move(info)); ui_plugins_.push_back(std::move(info));
} }
} }
} }
@@ -476,8 +518,8 @@ void PiPedalHost::Load(const char *lv2Path)
const auto &vst3PluginList = this->vst3Host->getPluginList(); const auto &vst3PluginList = this->vst3Host->getPluginList();
for (const auto &vst3Plugin : vst3PluginList) for (const auto &vst3Plugin : vst3PluginList)
{ {
// copy not move! // copy not move!
ui_plugins_.push_back(vst3Plugin->pluginInfo_); ui_plugins_.push_back(vst3Plugin->pluginInfo_);
} }
auto ui_compare = [&collation]( auto ui_compare = [&collation](
Lv2PluginUiInfo &left, Lv2PluginUiInfo &left,
@@ -532,10 +574,10 @@ static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
return result; return result;
} }
const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#" const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
"comment"; "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()); AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr); LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
@@ -547,7 +589,7 @@ static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shar
return p1->index() < p2->index(); 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); NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
bool result = false; bool result = false;
@@ -559,8 +601,21 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl
return result; 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->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin);
this->uri_ = nodeAsString(lilv_plugin_get_uri(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); std::shared_ptr<Lv2PortInfo> portInfo = std::make_shared<Lv2PortInfo>(lv2Host, pPlugin, pPort);
if (!portInfo->is_valid()) if (!portInfo->is_valid())
{ {
isValid = false; isValid = false;
} }
const auto &portGroup = portInfo->port_group(); const auto &portGroup = portInfo->port_group();
if (portGroup.size() != 0) if (portGroup.size() != 0)
{ {
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end()) if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
{ {
portGroups.push_back(portGroup); portGroups.push_back(portGroup);
} }
} }
ports_.push_back(std::move(portInfo)); 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])) 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()); Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
isValid = false; isValid = false;
} }
} }
@@ -642,7 +697,7 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
nullptr); nullptr);
if (pipedalUINode) 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,); // xxx lilv_world_get(pWorld,pluginUri,);
// for (auto&portInfo: ports_) // for (auto&portInfo: ports_)
@@ -666,7 +721,12 @@ std::vector<std::string> supportedFeatures = {
LV2_BUF_SIZE__fixedBlockLength, LV2_BUF_SIZE__fixedBlockLength,
LV2_BUF_SIZE__powerOf2BlockLength, LV2_BUF_SIZE__powerOf2BlockLength,
LV2_CORE__isLive, LV2_CORE__isLive,
LV2_STATE__loadDefaultState 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) for (int i = 0; i < supportedFeatures.size(); ++i)
{ {
if (supportedFeatures[i] == feature) if (supportedFeatures[i] == feature)
return true; return true;
} }
return false; 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); 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(); 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; auto pWorld = host->pWorld;
index_ = lilv_port_get_index(plugin, pPort); index_ = lilv_port_get_index(plugin, pPort);
@@ -720,7 +780,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
min_value_ = 0; min_value_ = 0;
max_value_ = 1; max_value_ = 1;
default_value_ = 0; default_value_ = 0;
lilv_port_get_range(plugin, pPort, &defaultNode.Get(), &minNode.Get(), &maxNode.Get()); lilv_port_get_range(plugin, pPort, defaultNode, minNode, maxNode);
if (defaultNode) if (defaultNode)
{ {
default_value_ = getFloat(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); auto priority_node = lilv_nodes_get_first(priority_nodes);
if (priority_node) 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); auto range_steps_node = lilv_nodes_get_first(range_steps_nodes);
if (range_steps_node) 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); 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_)); ((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; std::vector<std::string> result;
const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort); 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) LILV_FOREACH(nodes, iNode, nodes)
{ {
const LilvNode *node = lilv_nodes_get(nodes, iNode); const LilvNode *node = lilv_nodes_get(nodes, iNode);
std::string classUri = nodeAsString(node); std::string classUri = nodeAsString(node);
result.push_back(classUri); result.push_back(classUri);
} }
} }
return Lv2PluginClasses(result); return Lv2PluginClasses(result);
@@ -851,20 +911,20 @@ bool Lv2PluginClass::is_a(const std::string &classUri) const
} }
return false; 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); std::string classUri_(classUri);
for (auto i : classes_) for (auto i : classes_)
{ {
if (lv2Plugins->is_a(classUri_, i)) if (lv2Plugins->is_a(classUri_, i))
{ {
return true; return true;
} }
} }
return false; 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) if (class_ == target_class)
return true; return true;
@@ -876,7 +936,7 @@ bool PiPedalHost::is_a(const std::string &class_, const std::string &target_clas
return false; return false;
} }
Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin) Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
: uri_(plugin->uri()), : uri_(plugin->uri()),
name_(plugin->name()), name_(plugin->name()),
author_name_(plugin->author_name()), author_name_(plugin->author_name()),
@@ -898,38 +958,38 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
{ {
PLUGIN_MAP_CHECK(); PLUGIN_MAP_CHECK();
this->plugin_display_type_ = "Plugin"; 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()) for (auto port : plugin->ports())
{ {
if (port->is_input()) if (port->is_input())
{ {
if (port->is_control_port() && 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())
{ {
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()) else if (port->is_output())
{ {
if (port->is_atom_port() && port->supports_midi()) if (port->is_atom_port() && port->supports_midi())
{ {
this->has_midi_output_ = true; this->has_midi_output_ = true;
} }
else if (port->is_audio_port()) else if (port->is_audio_port())
{ {
++audio_outputs_; ++audio_outputs_;
} }
} }
} }
for (auto &portGroup : plugin->port_groups()) for (auto &portGroup : plugin->port_groups())
@@ -941,55 +1001,56 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
if (piPedalUI) if (piPedalUI)
{ {
this->fileProperties_ = piPedalUI->fileProperties(); 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); 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) for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i)
{ {
if ((*i)->uri() == uri) if ((*i)->uri() == uri)
{ {
return (*i); return (*i);
} }
} }
return nullptr; return nullptr;
} }
Lv2PedalBoard *PiPedalHost::CreateLv2PedalBoard(PedalBoard &pedalBoard) Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard)
{ {
Lv2PedalBoard *pPedalBoard = new Lv2PedalBoard(); Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
try try
{ {
pPedalBoard->Prepare(this, pedalBoard); pPedalboard->Prepare(this, pedalboard);
return pPedalBoard; return pPedalboard;
} }
catch (const std::exception &) catch (const std::exception &e)
{ {
delete pPedalBoard; delete pPedalboard;
throw; throw;
} }
} }
struct StateCallbackData struct StateCallbackData
{ {
PiPedalHost *pHost; PluginHost *pHost;
std::vector<ControlValue> *pResult; std::vector<ControlValue> *pResult;
}; };
void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol, void PluginHost::fn_LilvSetPortValueFunc(const char *port_symbol,
void *user_data, void *user_data,
const void *value, const void *value,
uint32_t size, uint32_t size,
uint32_t type) uint32_t type)
{ {
StateCallbackData *pData = (StateCallbackData *)user_data; StateCallbackData *pData = (StateCallbackData *)user_data;
PiPedalHost *pHost = pData->pHost; PluginHost *pHost = pData->pHost;
std::vector<ControlValue> *pResult = pData->pResult; std::vector<ControlValue> *pResult = pData->pResult;
LV2_URID valueType = (LV2_URID)type; LV2_URID valueType = (LV2_URID)type;
if (valueType != pHost->urids->atom_Float) 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)); pResult->push_back(ControlValue(port_symbol, *(float *)value));
} }
std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset( std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
PedalBoardItem *pedalBoardItem, const std::string &presetUri) PedalboardItem *pedalboardItem, const std::string &presetUri)
{ {
std::vector<ControlValue> result; std::vector<ControlValue> result;
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld); 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); lilv_world_load_resource(pWorld, uriNode);
@@ -1027,27 +1088,27 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
if (presetUri == thisPresetUri) 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) if (lilvState == nullptr)
{ {
throw PiPedalStateException("Preset not found."); throw PiPedalStateException("Preset not found.");
} }
StateCallbackData cbData; StateCallbackData cbData;
cbData.pHost = this; cbData.pHost = this;
cbData.pResult = &result; cbData.pResult = &result;
auto n = lilv_state_get_num_properties(lilvState); auto n = lilv_state_get_num_properties(lilvState);
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData); lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
lilv_state_free(lilvState); lilv_state_free(lilvState);
break; break;
/*********************************/ /*********************************/
} }
} }
lilv_nodes_free(presets); lilv_nodes_free(presets);
@@ -1057,15 +1118,15 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
struct PresetCallbackState struct PresetCallbackState
{ {
PiPedalHost *pHost; PluginHost *pHost;
std::map<std::string, float> *values; std::map<std::string, float> *values;
bool failed = false; 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); PresetCallbackState *pState = static_cast<PresetCallbackState *>(user_data);
PiPedalHost *pHost = pState->pHost; PluginHost *pHost = pState->pHost;
if (type == pHost->urids->atom_Float) if (type == pHost->urids->atom_Float)
{ {
@@ -1076,7 +1137,7 @@ void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const v
pState->failed = true; 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); 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); LilvState *state = lilv_state_new_from_world(pWorld, this->mapFeature.GetMap(), preset);
if (state != nullptr) if (state != nullptr)
{ {
std::string label = lilv_state_get_label(state); std::string label = lilv_state_get_label(state);
std::map<std::string, float> controlValues; std::map<std::string, float> controlValues;
PresetCallbackState cbData{this, &controlValues, false}; PresetCallbackState cbData{this, &controlValues, false};
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData); lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
lilv_state_free(state); lilv_state_free(state);
if (!cbData.failed) if (!cbData.failed)
{ {
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues))); result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
} }
} }
} }
lilv_nodes_free(presets); lilv_nodes_free(presets);
return result; 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 #if ENABLE_VST3
try try
{ {
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this); Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalboardItem, this);
return vst3Plugin.release(); return vst3Plugin.release();
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what()); Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
throw; throw;
} }
#else #else
Lv2Log::error(std::string("VST3 support not enabled at compile time.")); Lv2Log::error(std::string("VST3 support not enabled at compile time."));
@@ -1141,15 +1202,15 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
} }
else else
{ {
auto info = this->GetPluginInfo(pedalBoardItem.uri()); auto info = this->GetPluginInfo(pedalboardItem.uri());
if (!info) 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; LilvWorld *pWorld = lv2Host->pWorld;
@@ -1161,11 +1222,15 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
name_ = nodeAsString(nameNode); 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) \ #define MAP_REF(class, name) \
json_map::reference(#name, &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::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_),
json_map::reference("uri", &Lv2PluginInfo::uri_), json_map::reference("uri", &Lv2PluginInfo::uri_),
json_map::reference("name", &Lv2PluginInfo::name_), json_map::reference("name", &Lv2PluginInfo::name_),
json_map::reference("plugin_class", &Lv2PluginInfo::plugin_class_), 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::storage_type<Lv2PluginUiInfo>
json_map::reference("uri", &Lv2PluginUiInfo::uri_), Lv2PluginUiInfo::jmap{
json_map::reference("name", &Lv2PluginUiInfo::name_), {json_map::reference("uri", &Lv2PluginUiInfo::uri_),
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()), json_map::reference("name", &Lv2PluginUiInfo::name_),
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_), json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_), json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_), json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_), json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_), json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
json_map::reference("description", &Lv2PluginUiInfo::description_), json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_), json_map::reference("description", &Lv2PluginUiInfo::description_),
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_), json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
json_map::reference("controls", &Lv2PluginUiInfo::controls_), json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_), json_map::reference("controls", &Lv2PluginUiInfo::controls_),
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_), json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
json_map::reference("fileProperties",&Lv2PluginUiInfo::fileProperties_) 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 <memory>
#include "json.hpp" #include "json.hpp"
#include "PluginType.hpp" #include "PluginType.hpp"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include <lilv/lilv.h> #include <lilv/lilv.h>
#include "MapFeature.hpp" #include "MapFeature.hpp"
#include "LogFeature.hpp" #include "LogFeature.hpp"
@@ -31,6 +31,7 @@
#include <filesystem> #include <filesystem>
#include <cmath> #include <cmath>
#include <string> #include <string>
#include "IHost.hpp"
#include "lv2.h" #include "lv2.h"
#include "Units.hpp" #include "Units.hpp"
@@ -40,14 +41,15 @@
#include "PiPedalConfiguration.hpp" #include "PiPedalConfiguration.hpp"
#include "AutoLilvNode.hpp" #include "AutoLilvNode.hpp"
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "MapPathFeature.hpp"
namespace pipedal namespace pipedal
{ {
// forward declarations // forward declarations
class Lv2Effect; class Lv2Effect;
class Lv2PedalBoard; class Lv2Pedalboard;
class PiPedalHost; class PluginHost;
class JackConfiguration; class JackConfiguration;
class JackChannelSelection; class JackChannelSelection;
@@ -82,7 +84,7 @@ namespace pipedal
class Lv2PluginClass class Lv2PluginClass
{ {
public: public:
friend class PiPedalHost; friend class PluginHost;
private: private:
Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED! Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED!
@@ -92,7 +94,7 @@ namespace pipedal
PluginType plugin_type_; PluginType plugin_type_;
std::vector<std::shared_ptr<Lv2PluginClass>> children_; std::vector<std::shared_ptr<Lv2PluginClass>> children_;
friend class ::pipedal::PiPedalHost; friend class ::pipedal::PluginHost;
// hide copy constructor. // hide copy constructor.
Lv2PluginClass(const Lv2PluginClass &other) Lv2PluginClass(const Lv2PluginClass &other)
{ {
@@ -151,7 +153,7 @@ namespace pipedal
{ {
return classes_; 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; static json_map::storage_type<Lv2PluginClasses> jmap;
}; };
@@ -186,7 +188,7 @@ namespace pipedal
class Lv2PortInfo class Lv2PortInfo
{ {
public: public:
Lv2PortInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort); Lv2PortInfo(PluginHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort);
private: private:
friend class Lv2PluginInfo; friend class Lv2PluginInfo;
@@ -303,7 +305,7 @@ namespace pipedal
public: public:
Lv2PortInfo() {} Lv2PortInfo() {}
~Lv2PortInfo() = default; ~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; static json_map::storage_type<Lv2PortInfo> jmap;
}; };
@@ -321,7 +323,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(name); LV2_PROPERTY_GETSET(name);
Lv2PortGroup() {} Lv2PortGroup() {}
Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri); Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri);
static json_map::storage_type<Lv2PortGroup> jmap; static json_map::storage_type<Lv2PortGroup> jmap;
}; };
@@ -329,14 +331,15 @@ namespace pipedal
class Lv2PluginInfo class Lv2PluginInfo
{ {
private: private:
friend class PiPedalHost; friend class PluginHost;
public: public:
Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {} Lv2PluginInfo() {}
private: private:
bool HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin); bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
std::string bundle_path_;
std::string uri_; std::string uri_;
std::string name_; std::string name_;
std::string plugin_class_; std::string plugin_class_;
@@ -358,6 +361,7 @@ namespace pipedal
bool IsSupportedFeature(const std::string &feature) const; bool IsSupportedFeature(const std::string &feature) const;
public: public:
LV2_PROPERTY_GETSET(bundle_path)
LV2_PROPERTY_GETSET(uri) LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name) LV2_PROPERTY_GETSET(name)
LV2_PROPERTY_GETSET(plugin_class) LV2_PROPERTY_GETSET(plugin_class)
@@ -537,7 +541,7 @@ namespace pipedal
{ {
public: public:
Lv2PluginUiInfo() {} Lv2PluginUiInfo() {}
Lv2PluginUiInfo(PiPedalHost *pPlugins, const Lv2PluginInfo *plugin); Lv2PluginUiInfo(PluginHost *pPlugins, const Lv2PluginInfo *plugin);
private: private:
std::string uri_; std::string uri_;
@@ -555,7 +559,8 @@ namespace pipedal
std::vector<Lv2PluginUiControlPort> controls_; std::vector<Lv2PluginUiControlPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_; std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<PiPedalFileProperty::ptr> fileProperties_; std::vector<UiFileProperty::ptr> fileProperties_;
std::vector<UiPortNotification::ptr> uiPortNotifications_;
public: public:
LV2_PROPERTY_GETSET(uri) LV2_PROPERTY_GETSET(uri)
@@ -573,32 +578,11 @@ namespace pipedal
LV2_PROPERTY_GETSET(port_groups) LV2_PROPERTY_GETSET(port_groups)
LV2_PROPERTY_GETSET_SCALAR(is_vst3) LV2_PROPERTY_GETSET_SCALAR(is_vst3)
LV2_PROPERTY_GETSET(fileProperties) LV2_PROPERTY_GETSET(fileProperties)
LV2_PROPERTY_GETSET(uiPortNotifications)
static json_map::storage_type<Lv2PluginUiInfo> jmap; 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 #if ENABLE_VST3
@@ -608,7 +592,7 @@ namespace pipedal
namespace pipedal namespace pipedal
{ {
class PiPedalHost : private IHost class PluginHost : private IHost
{ {
private: private:
#if ENABLE_VST3 #if ENABLE_VST3
@@ -618,6 +602,7 @@ namespace pipedal
friend class pipedal::AutoLilvNode; friend class pipedal::AutoLilvNode;
friend class pipedal::PiPedalUI; friend class pipedal::PiPedalUI;
static const char *RDFS_COMMENT_URI; static const char *RDFS_COMMENT_URI;
public: public:
class LilvUris class LilvUris
{ {
@@ -648,7 +633,6 @@ namespace pipedal
AutoLilvNode pipedalUI__fileProperties; AutoLilvNode pipedalUI__fileProperties;
AutoLilvNode pipedalUI__directory; AutoLilvNode pipedalUI__directory;
AutoLilvNode pipedalUI__patchProperty; AutoLilvNode pipedalUI__patchProperty;
AutoLilvNode pipedalUI__defaultFile;
AutoLilvNode pipedalUI__fileProperty; AutoLilvNode pipedalUI__fileProperty;
@@ -665,11 +649,18 @@ namespace pipedal
AutoLilvNode appliesTo; AutoLilvNode appliesTo;
AutoLilvNode isA; 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; LilvUris lilvUris;
private: private:
bool vst3Enabled = true; bool vst3Enabled = true;
LilvNode *get_comment(const std::string &uri); LilvNode *get_comment(const std::string &uri);
@@ -683,10 +674,11 @@ namespace pipedal
std::string vst3CachePath; std::string vst3CachePath;
LV2_Feature *const *lv2Features = nullptr; std::vector<const LV2_Feature *> lv2Features;
MapFeature mapFeature; MapFeature mapFeature;
LogFeature logFeature; LogFeature logFeature;
OptionsFeature optionsFeature; OptionsFeature optionsFeature;
MapPathFeature mapPathFeature;
static void fn_LilvSetPortValueFunc(const char *port_symbol, static void fn_LilvSetPortValueFunc(const char *port_symbol,
void *user_data, void *user_data,
@@ -714,21 +706,21 @@ namespace pipedal
// IHost implementation // IHost implementation
public: 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() virtual LilvWorld *getWorld()
{ {
@@ -736,33 +728,41 @@ namespace pipedal
} }
private: private:
std::shared_ptr<HostWorkerThread> pHostWorkerThread;
// IHost implementation.
virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; } virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; }
virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; } virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; }
virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; } virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; }
virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; } virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; }
virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; } virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; }
virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; } 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() virtual LV2_URID_Map *GetLv2UridMap()
{ {
return this->mapFeature.GetMap(); return this->mapFeature.GetMap();
} }
static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type); 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 LoadPluginClassesFromLilv();
void AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass); void AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass);
public: public:
PiPedalHost(); PluginHost();
void SetPluginStoragePath(const std::filesystem::path &path);
void SetConfiguration(const PiPedalConfiguration &configuration); void SetConfiguration(const PiPedalConfiguration &configuration);
virtual ~PiPedalHost(); virtual ~PluginHost();
IHost *asIHost() { return this; } IHost *asIHost() { return this; }
virtual Lv2PedalBoard *CreateLv2PedalBoard(PedalBoard &pedalBoard); virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard);
void setSampleRate(double sampleRate) void setSampleRate(double sampleRate)
{ {
@@ -792,19 +792,19 @@ namespace pipedal
static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2"; static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2";
void LoadPluginClassesFromJson(std::filesystem::path jsonFile); 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) virtual LV2_URID GetLv2Urid(const char *uri)
{ {
return this->mapFeature.GetUrid(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); return this->mapFeature.UridToString(urid);
} }
PluginPresets GetFactoryPluginPresets(const std::string &pluginUri); PluginPresets GetFactoryPluginPresets(const std::string &pluginUri);
std::vector<ControlValue> LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem, std::vector<ControlValue> LoadFactoryPluginPreset(PedalboardItem *pedalboardItem,
const std::string &presetUri); const std::string &presetUri);
}; };
+1
View File
@@ -27,6 +27,7 @@ JSON_MAP_BEGIN(PluginPreset)
JSON_MAP_REFERENCE(PluginPreset,instanceId) JSON_MAP_REFERENCE(PluginPreset,instanceId)
JSON_MAP_REFERENCE(PluginPreset,label) JSON_MAP_REFERENCE(PluginPreset,label)
JSON_MAP_REFERENCE(PluginPreset,controlValues) JSON_MAP_REFERENCE(PluginPreset,controlValues)
JSON_MAP_REFERENCE(PluginPreset,state)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PluginPresets) JSON_MAP_BEGIN(PluginPresets)
+3
View File
@@ -23,6 +23,8 @@
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
#include <utility> #include <utility>
#include <map> #include <map>
#include <memory>
#include "StateInterface.hpp"
namespace pipedal { namespace pipedal {
@@ -87,6 +89,7 @@ public:
uint64_t instanceId_; uint64_t instanceId_;
std::string label_; std::string label_;
std::map<std::string,float> controlValues_; std::map<std::string,float> controlValues_;
Lv2PluginState state_;
DECLARE_JSON_MAP(PluginPreset); DECLARE_JSON_MAP(PluginPreset);
}; };
+8 -8
View File
@@ -27,17 +27,17 @@ JSON_MAP_BEGIN(PedalPreset)
JSON_MAP_REFERENCE(PedalPreset,values) JSON_MAP_REFERENCE(PedalPreset,values)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoardPreset) JSON_MAP_BEGIN(PedalboardPreset)
JSON_MAP_REFERENCE(PedalBoardPreset,instanceId) JSON_MAP_REFERENCE(PedalboardPreset,instanceId)
JSON_MAP_REFERENCE(PedalBoardPreset,displayName) JSON_MAP_REFERENCE(PedalboardPreset,displayName)
JSON_MAP_REFERENCE(PedalBoardPreset,values) JSON_MAP_REFERENCE(PedalboardPreset,values)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(PedalBoardPresets) JSON_MAP_BEGIN(PedalboardPresets)
JSON_MAP_REFERENCE(PedalBoardPresets,nextInstanceId) JSON_MAP_REFERENCE(PedalboardPresets,nextInstanceId)
JSON_MAP_REFERENCE(PedalBoardPresets,currentPreset) JSON_MAP_REFERENCE(PedalboardPresets,currentPreset)
JSON_MAP_REFERENCE(PedalBoardPresets,presets) JSON_MAP_REFERENCE(PedalboardPresets,presets)
JSON_MAP_END() JSON_MAP_END()
+6 -6
View File
@@ -19,7 +19,7 @@
#pragma once #pragma once
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "json.hpp" #include "json.hpp"
namespace pipedal { namespace pipedal {
@@ -32,21 +32,21 @@ public:
DECLARE_JSON_MAP(PedalPreset); DECLARE_JSON_MAP(PedalPreset);
}; };
class PedalBoardPreset { class PedalboardPreset {
uint64_t instanceId_; uint64_t instanceId_;
std::string displayName_; std::string displayName_;
std::vector<std::unique_ptr<PedalPreset> > values_; std::vector<std::unique_ptr<PedalPreset> > values_;
public: public:
DECLARE_JSON_MAP(PedalBoardPreset); DECLARE_JSON_MAP(PedalboardPreset);
}; };
class PedalBoardPresets { class PedalboardPresets {
long nextInstanceId_ = 0; long nextInstanceId_ = 0;
long currentPreset_ = 0; long currentPreset_ = 0;
std::vector<std::unique_ptr<PedalBoardPreset> > presets_; std::vector<std::unique_ptr<PedalboardPreset> > presets_;
public: 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);
}
+247 -93
View File
@@ -22,28 +22,35 @@
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <semaphore.h> #include <condition_variable>
#ifndef NO_MLOCK #ifndef NO_MLOCK
#include <sys/mman.h> #include <sys/mman.h>
#endif /* NO_MLOCK */ #endif /* NO_MLOCK */
namespace pipedal namespace pipedal
{ {
enum class RingBufferStatus
{
Ready,
TimedOut,
Closed
};
template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false> template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false>
class RingBuffer { class RingBuffer
{
char *buffer; char *buffer;
bool mlocked = false; bool mlocked = false;
size_t ringBufferSize; size_t ringBufferSize;
size_t ringBufferMask; size_t ringBufferMask;
volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition
volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition
std::mutex write_mutex; std::mutex mutex;
std::mutex writeMutex;
sem_t readSemaphore; bool is_open = true;
bool semaphore_open = false; std::condition_variable cvRead;
size_t nextPowerOfTwo(size_t size) size_t nextPowerOfTwo(size_t size)
{ {
@@ -54,205 +61,352 @@ namespace pipedal
} }
return v; return v;
} }
public: public:
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true) RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
{ {
this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize); this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize);
ringBufferMask = ringBufferSize-1; ringBufferMask = ringBufferSize - 1;
buffer = new char[ringBufferSize]; buffer = new char[ringBufferSize];
if (SEMAPHORE_READER) { #ifndef NO_MLOCK
sem_init(&readSemaphore,0,0);
semaphore_open = true;
}
#ifndef NO_MLOCK
if (mLock) if (mLock)
{ {
if (mlock (buffer, ringBufferSize)) { if (mlock(buffer, ringBufferSize))
throw PiPedalStateException("Mlock failed."); {
throw PiPedalStateException("Mlock failed.");
} }
this->mlocked = true; this->mlocked = true;
} }
#endif #endif
} }
void reset() { void reset()
{
this->readPosition = 0; this->readPosition = 0;
this->writePosition = 0; this->writePosition = 0;
if (SEMAPHORE_READER) cvRead.notify_all();
{
sem_destroy(&readSemaphore);
sem_init(&readSemaphore,0,0);
this->semaphore_open = true;
}
} }
void close() { void close()
{
if (SEMAPHORE_READER) if (SEMAPHORE_READER)
{ {
this->semaphore_open = false; this->is_open = false;
sem_post(&readSemaphore); cvRead.notify_all();
}
}
// 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.");
} }
} }
bool readWait() { template <class Rep, class Period>
if (SEMAPHORE_READER) RingBufferStatus readWait_for(const std::chrono::duration<Rep, Period> &timeout)
{
while (true)
{ {
sem_wait(&readSemaphore); if (SEMAPHORE_READER)
return semaphore_open; {
} else { std::unique_lock lock(mutex);
throw PiPedalStateException("SEMAPHORE_READER is not set to true."); 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() {
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 // at most ringBufferSize-1 in order to
// to distinguish the empty buffer from the full buffer. // to distinguish the empty buffer from the full buffer.
int64_t size = readPosition-1-writePosition; std::unique_lock lock(mutex);
if (size < 0) size += this->ringBufferSize;
int64_t size = readPosition - 1 - writePosition;
if (size < 0)
size += this->ringBufferSize;
return (size_t)size; return (size_t)size;
} }
size_t readSpace() {
int64_t size = writePosition-readPosition;
if (size < 0) size += this->ringBufferSize; size_t readSpace()
return size_t(size); {
std::unique_lock lock(mutex);
return readSpace_();
} }
bool write(size_t bytes, uint8_t *data) bool write(size_t bytes, uint8_t *data)
{ {
if (MULTI_WRITER) if (MULTI_WRITER)
{ {
std::lock_guard guard(write_mutex); std::lock_guard writeLock{writeMutex};
if (writeSpace() < bytes) { if (writeSpace() < bytes + sizeof(bytes))
{
return false; return false;
} }
size_t index = this->writePosition; size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i) 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) if (SEMAPHORE_READER)
{ {
sem_post(&readSemaphore); cvRead.notify_all();
} }
return true; return true;
} else { }
if (writeSpace() < bytes) { else
{
if (writeSpace() < sizeof(bytes) + bytes)
{
return false; return false;
} }
size_t index = this->writePosition; size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i) 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) if (SEMAPHORE_READER)
{ {
sem_post(&readSemaphore); cvRead.notify_all();
} }
return true; return true;
} }
} }
// Write two disjoint areas of memory atomically. // 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) if (MULTI_WRITER)
{ {
std::lock_guard guard(write_mutex); std::lock_guard guard(writeMutex);
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { if (writeSpace() <= sizeof(bytes) + bytes +bytes2)
{
return false; return false;
} }
size_t index = this->writePosition; size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i) 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) 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) 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) if (SEMAPHORE_READER)
{ {
sem_post(&readSemaphore); cvRead.notify_all();
} }
return true; return true;
} else { }
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { else
{
if (writeSpace() <= sizeof(bytes2) + bytes + bytes2)
{
return false; return false;
} }
size_t index = this->writePosition; size_t index = this->writePosition;
for (size_t i = 0; i < bytes; ++i) 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) 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) 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) if (SEMAPHORE_READER)
{ {
sem_post(&readSemaphore); cvRead.notify_all();
} }
return true; 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; int64_t readPosition = this->readPosition;
for (size_t i = 0; i < bytes; ++i) 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; return true;
} }
~RingBuffer() ~RingBuffer()
{ {
#ifdef USE_MLOCK #ifdef USE_MLOCK
if (this->mlocked) if (this->mlocked)
{
munlock(buffer,ringBufferSize);
}
#endif
if (SEMAPHORE_READER)
{ {
sem_destroy(&this->readSemaphore); munlock(buffer, ringBufferSize);
} }
#endif
delete[] buffer; 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 "Lv2Log.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "AudioHost.hpp" #include "AudioHost.hpp"
#include "lv2/atom.lv2/atom.h"
#include <chrono>
namespace pipedal namespace pipedal
{ {
@@ -56,6 +58,8 @@ namespace pipedal
NextMidiProgram = 20, NextMidiProgram = 20,
Lv2StateChanged = 21,
}; };
struct RealtimeNextMidiProgramRequest { struct RealtimeNextMidiProgramRequest {
@@ -151,17 +155,17 @@ namespace pipedal
float value; float value;
}; };
class Lv2PedalBoard; class Lv2Pedalboard;
class ReplaceEffectBody class ReplaceEffectBody
{ {
public: public:
Lv2PedalBoard *effect; Lv2Pedalboard *effect;
}; };
class EffectReplacedBody class EffectReplacedBody
{ {
public: public:
Lv2PedalBoard *oldEffect; Lv2Pedalboard *oldEffect;
}; };
template <bool MULTI_WRITE, bool SEMAPHORE_READ> template <bool MULTI_WRITE, bool SEMAPHORE_READ>
@@ -182,8 +186,20 @@ namespace pipedal
} }
// 0 -> ready. -1: timed out. -2: closing. // 0 -> ready. -1: timed out. -2: closing.
int wait(struct timespec &timeout) { template <class Rep, class Period>
return ringBuffer->readWait(timeout); 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() { bool wait() {
@@ -196,9 +212,6 @@ namespace pipedal
template <typename T> template <typename T>
bool read(T *output) bool read(T *output)
{ {
size_t available = readSpace();
if (available < sizeof(T))
return false;
if (!ringBuffer->read(sizeof(T),(uint8_t*)output)) if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
{ {
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); 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) void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data)
{ {
write(RingBufferCommand::AtomOutput,instanceId,bytes,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); write(RingBufferCommand::ParameterRequest,pRequest);
} }
void ParameterRequestComplete(RealtimeParameterRequest *pRequest) void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest)
{ {
write(RingBufferCommand::ParameterRequestComplete,pRequest); write(RingBufferCommand::ParameterRequestComplete,pRequest);
} }
@@ -407,9 +428,9 @@ namespace pipedal
write(RingBufferCommand::SetBypass,body); write(RingBufferCommand::SetBypass,body);
} }
void ReplaceEffect(Lv2PedalBoard *pedalBoard) void ReplaceEffect(Lv2Pedalboard *pedalboard)
{ {
write(RingBufferCommand::ReplaceEffect, pedalBoard); write(RingBufferCommand::ReplaceEffect, pedalboard);
} }
void AudioStopped() void AudioStopped()
@@ -418,9 +439,9 @@ namespace pipedal
write(RingBufferCommand::AudioStopped, body); 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 "pch.h"
#include "SplitEffect.hpp" #include "SplitEffect.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
using namespace pipedal; using namespace pipedal;
+3 -3
View File
@@ -291,10 +291,11 @@ namespace pipedal
virtual uint8_t *GetAtomInputBuffer() { return nullptr; } virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; } virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
virtual void RequestParameter(LV2_URID uridUri) {} virtual void RequestPatchProperty(LV2_URID uridUri) {}
virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; } virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";} virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
virtual bool IsVst3() const { return false; } virtual bool IsVst3() const { return false; }
@@ -670,7 +671,6 @@ namespace pipedal
} }
} }
} }
void PostMix(uint32_t frames) void PostMix(uint32_t frames)
{ {
if (this->outputBuffers.size() == 1) 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 <map>
#include <sys/stat.h> #include <sys/stat.h>
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "PluginHost.hpp"
#include "ss.hpp"
using namespace pipedal; using namespace pipedal;
@@ -233,13 +235,20 @@ void Storage::LoadBank(int64_t instanceId)
{ {
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
LoadBankFile(indexEntry.name(), &(this->currentBank)); try
if (this->bankIndex.selectedBank() != instanceId)
{ {
this->bankIndex.selectedBank(instanceId); LoadBankFile(indexEntry.name(), &(this->currentBank));
SaveBankIndex(); 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() void Storage::LoadCurrentBank()
@@ -255,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
{ {
return this->dataRoot / "plugin_presets"; return this->dataRoot / "plugin_presets";
} }
std::filesystem::path Storage::GetAudioFilesDirectory() const std::filesystem::path Storage::GetPluginStorageDirectory() const
{ {
return this->dataRoot / "audio_uploads"; return this->dataRoot / "audio_uploads";
} }
@@ -297,8 +306,8 @@ void Storage::LoadBankIndex()
if (bankIndex.entries().size() == 0) if (bankIndex.entries().size() == 0)
{ {
currentBank.clear(); currentBank.clear();
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
int64_t instanceId = currentBank.addPreset(defaultPedalBoard); int64_t instanceId = currentBank.addPreset(defaultPedalboard);
currentBank.selectedPreset(instanceId); currentBank.selectedPreset(instanceId);
std::string name = "Default Bank"; std::string name = "Default Bank";
@@ -378,7 +387,7 @@ void Storage::ReIndex()
void Storage::CreateBank(const std::string &name) void Storage::CreateBank(const std::string &name)
{ {
BankFile bankFile; BankFile bankFile;
PedalBoard defaultPreset = PedalBoard::MakeDefault(); Pedalboard defaultPreset = Pedalboard::MakeDefault();
defaultPreset.name(std::string("Default Preset")); defaultPreset.name(std::string("Default Preset"));
bankFile.addPreset(defaultPreset); bankFile.addPreset(defaultPreset);
@@ -449,7 +458,7 @@ void Storage::SaveCurrentBank()
SaveBankFile(indexEntry.name(), this->currentBank); SaveBankFile(indexEntry.name(), this->currentBank);
} }
const PedalBoard &Storage::GetCurrentPreset() const Pedalboard &Storage::GetCurrentPreset()
{ {
auto &item = currentBank.getItem(currentBank.selectedPreset()); auto &item = currentBank.getItem(currentBank.selectedPreset());
return item.preset(); return item.preset();
@@ -466,18 +475,18 @@ bool Storage::LoadPreset(int64_t instanceId)
} }
return true; return true;
} }
void Storage::SaveCurrentPreset(const PedalBoard &pedalBoard) void Storage::SaveCurrentPreset(const Pedalboard &pedalboard)
{ {
auto &item = currentBank.getItem(currentBank.selectedPreset()); auto &item = currentBank.getItem(currentBank.selectedPreset());
item.preset(pedalBoard); item.preset(pedalboard);
SaveCurrentBank(); 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; Pedalboard newPedalboard = pedalboard;
newPedalBoard.name(name); newPedalboard.name(name);
int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId); int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId);
currentBank.selectedPreset(newInstanceId); currentBank.selectedPreset(newInstanceId);
SaveCurrentBank(); SaveCurrentBank();
return newInstanceId; return newInstanceId;
@@ -530,17 +539,18 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
pResult->presets().push_back(entry); 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 (program >= currentBank.presets().size())
{ {
if (currentBank.presets().size() == 0) return -1; if (currentBank.presets().size() == 0)
program = (uint8_t)currentBank.presets().size()-1; return -1;
program = (uint8_t)currentBank.presets().size() - 1;
} }
return currentBank.presets()[program]->instanceId(); 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) 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); auto &fromItem = this->currentBank.getItem(fromId);
if (toId == -1) if (toId == -1)
{ {
PedalBoard newPedalBoard = fromItem.preset(); Pedalboard newPedalboard = fromItem.preset();
std::string name = GetPresetCopyName(fromItem.preset().name()); std::string name = GetPresetCopyName(fromItem.preset().name());
newPedalBoard.name(name); newPedalboard.name(name);
return this->currentBank.addPreset(newPedalBoard, fromId); return this->currentBank.addPreset(newPedalboard, fromId);
} }
else else
{ {
@@ -753,13 +763,14 @@ void Storage::MoveBank(int from, int to)
this->SaveBankIndex(); this->SaveBankIndex();
} }
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber)
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) { {
auto &entries = this->bankIndex.entries(); auto &entries = this->bankIndex.entries();
if (bankNumber >= entries.size()) if (bankNumber >= entries.size())
{ {
if (entries.size() == 0) return -1; if (entries.size() == 0)
bankNumber = (uint8_t)(entries.size()-1); return -1;
bankNumber = (uint8_t)(entries.size() - 1);
} }
return entries[bankNumber].instanceId(); return entries[bankNumber].instanceId();
} }
@@ -792,8 +803,8 @@ int64_t Storage::DeleteBank(int64_t bankId)
BankIndexEntry newEntry; BankIndexEntry newEntry;
BankFile defaultBank; BankFile defaultBank;
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
int64_t instanceId = defaultBank.addPreset(defaultPedalBoard); int64_t instanceId = defaultBank.addPreset(defaultPedalboard);
defaultBank.selectedPreset(instanceId); defaultBank.selectedPreset(instanceId);
std::string name = "Default Bank"; 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) 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; int n = 2;
std::string baseName = preset.name(); std::string baseName = preset.name();
@@ -1063,8 +1074,11 @@ bool Storage::RestoreCurrentPreset(CurrentPreset *pResult)
reader.read(pResult); reader.read(pResult);
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown. 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 false;
} }
return true; return true;
@@ -1330,9 +1344,6 @@ void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
pipedal::JackServerSettings Storage::GetJackServerSettings() pipedal::JackServerSettings Storage::GetJackServerSettings()
{ {
JackServerSettings result; JackServerSettings result;
#if JACK_HOST
result.Initialize();
#else
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json"; std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
std::ifstream f; std::ifstream f;
f.open(fileName); f.open(fileName);
@@ -1341,14 +1352,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
json_reader reader(f); json_reader reader(f);
reader.read(&result); reader.read(&result);
} }
#if JACK_HOST
result.Initialize();
#endif #endif
return result; return result;
} }
void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration) void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration)
{ {
#if JACK_HOST
#error IMPLEMENT ME
#else
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json"; std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
std::ofstream f; std::ofstream f;
f.open(fileName); f.open(fileName);
@@ -1357,10 +1368,12 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
json_writer writer(f); json_writer writer(f);
writer.write(jackConfiguration); writer.write(jackConfiguration);
} }
#if JACK_HOST
jackConfiguration.Write();
#endif #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::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::ofstream f; std::ofstream f;
@@ -1382,63 +1395,85 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
{ {
json_reader reader(f); json_reader reader(f);
reader.read(&result); reader.read(&result);
} else { }
else
{
result.push_back(MidiBinding::SystemBinding("prevProgram")); result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram")); result.push_back(MidiBinding::SystemBinding("nextProgram"));
} }
return result; return result;
} }
static bool containsDotDot(const std::string&value) static bool containsDotDot(const std::string &value)
{ {
std::size_t offset = value.find(".."); std::size_t offset = value.find("..");
return offset != std::string::npos; 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)
if (value.find("\\") != std::string::npos) return true; // windows return true; // linux
if (value.find("::") != std::string::npos) return true; // mac if (value.find("\\") != std::string::npos)
return true; // windows
if (value.find("::") != std::string::npos)
return true; // mac
return false; return false;
} }
static void ThrowPermissionDeniedError()
static void ThrowPermissionDeniedError()
{ {
throw std::logic_error("Permission denied."); 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(); ThrowPermissionDeniedError();
} }
std::vector<std::string> result; std::vector<std::string> result;
std::filesystem::path audioFileDirectory = this->GetAudioFilesDirectory() / fileProperty.directory();
try { // if fileProperty has a user-accessible directory, push the entire file path.
for (auto const&dir_entry: std::filesystem::directory_iterator(audioFileDirectory)) 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 (dir_entry.is_regular_file())
if (fileProperty.IsValidExtension(path.extension().string()))
{ {
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) catch (const std::exception &error)
{ {
throw std::logic_error("Directory not found: " + audioFileDirectory.string()); throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
}
} }
// sort lexicographically
std::sort(result.begin(), result.end(), lexicographicCompare);
return result; return result;
} }
JSON_MAP_BEGIN(UserSettings) JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor) JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+15 -10
View File
@@ -20,7 +20,7 @@
#pragma once #pragma once
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include "PedalBoard.hpp" #include "Pedalboard.hpp"
#include "Presets.hpp" #include "Presets.hpp"
#include "PluginPreset.hpp" #include "PluginPreset.hpp"
#include "Banks.hpp" #include "Banks.hpp"
@@ -33,12 +33,13 @@
namespace pipedal { namespace pipedal {
class PiPedalFileProperty; class UiFileProperty;
class Lv2PluginInfo;
class CurrentPreset { class CurrentPreset {
public: public:
bool modified_ = false; bool modified_ = false;
PedalBoard preset_; Pedalboard preset_;
DECLARE_JSON_MAP(CurrentPreset); DECLARE_JSON_MAP(CurrentPreset);
}; };
@@ -63,12 +64,12 @@ private:
PluginPresetIndex pluginPresetIndex; PluginPresetIndex pluginPresetIndex;
private: private:
void MaybeCopyDefaultPresets(); void MaybeCopyDefaultPresets();
static std::string SafeEncodeName(const std::string& name); static std::string SafeEncodeName(const std::string& name);
static std::string SafeDecodeName(const std::string& name); static std::string SafeDecodeName(const std::string& name);
std::filesystem::path GetPresetsDirectory() const; std::filesystem::path GetPresetsDirectory() const;
std::filesystem::path GetPluginPresetsDirectory() const; std::filesystem::path GetPluginPresetsDirectory() const;
std::filesystem::path GetAudioFilesDirectory() const;
std::filesystem::path GetIndexFileName() const; std::filesystem::path GetIndexFileName() const;
std::filesystem::path GetBankFileName(const std::string & name) const; std::filesystem::path GetBankFileName(const std::string & name) const;
std::filesystem::path GetChannelSelectionFileName(); std::filesystem::path GetChannelSelectionFileName();
@@ -99,7 +100,9 @@ public:
void SetDataRoot(const std::filesystem::path& path); void SetDataRoot(const std::filesystem::path& path);
void SetConfigRoot(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; } const BankIndex & GetBanks() const { return bankIndex; }
@@ -112,13 +115,13 @@ public:
void SaveUserSettings(); void SaveUserSettings();
void LoadBank(int64_t instanceId); void LoadBank(int64_t instanceId);
int64_t GetBankByMidiBankNumber(uint8_t bankNumber); int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
const PedalBoard& GetCurrentPreset(); const Pedalboard& GetCurrentPreset();
void SaveCurrentPreset(const PedalBoard&pedalBoard); void SaveCurrentPreset(const Pedalboard&pedalboard);
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1); int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, const std::string&namne,int64_t saveAfterInstanceId = -1);
int64_t GetCurrentPresetId() const; int64_t GetCurrentPresetId() const;
void GetPresetIndex(PresetIndex*pResult); void GetPresetIndex(PresetIndex*pResult);
void SetPresetIndex(const PresetIndex &presetIndex); void SetPresetIndex(const PresetIndex &presetIndex);
PedalBoard GetPreset(int64_t instanceId) const; Pedalboard GetPreset(int64_t instanceId) const;
int64_t GetPresetByProgramNumber(uint8_t program) const; int64_t GetPresetByProgramNumber(uint8_t program) const;
void GetBankFile(int64_t instanceId,BankFile*pResult) const; void GetBankFile(int64_t instanceId,BankFile*pResult) const;
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter); int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
@@ -136,7 +139,7 @@ public:
void MoveBank(int from, int to); void MoveBank(int from, int to);
int64_t DeleteBank(int64_t bankId); int64_t DeleteBank(int64_t bankId);
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty); std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
void SetJackChannelSelection(const JackChannelSelection&channelSelection); void SetJackChannelSelection(const JackChannelSelection&channelSelection);
@@ -156,6 +159,8 @@ public:
void SaveCurrentPreset(const CurrentPreset &currentPreset); void SaveCurrentPreset(const CurrentPreset &currentPreset);
bool RestoreCurrentPreset(CurrentPreset*pResult); bool RestoreCurrentPreset(CurrentPreset*pResult);
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
private: private:
bool pluginPresetIndexChanged = false; bool pluginPresetIndexChanged = false;
void LoadPluginPresetIndex(); void LoadPluginPresetIndex();
+2 -2
View File
@@ -24,7 +24,7 @@
#include "ss.hpp" #include "ss.hpp"
#include <assert.h> #include <assert.h>
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp" #include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
@@ -60,7 +60,7 @@
#include "Vst3MidiToEvent.hpp" #include "Vst3MidiToEvent.hpp"
#include "vst3/Vst3EffectImpl.hpp" #include "vst3/Vst3EffectImpl.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
using namespace pipedal; using namespace pipedal;
+11 -11
View File
@@ -23,7 +23,7 @@
*/ */
#include "ss.hpp" #include "ss.hpp"
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp" #include "vst3/Vst3Host.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <unordered_map> #include <unordered_map>
@@ -86,7 +86,7 @@ namespace pipedal
const Vst3Host::PluginList &getPluginList() override { return pluginList; } 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(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: private:
Lv2PluginUiInfo *GetPluginInfo(const std::string &uri); Lv2PluginUiInfo *GetPluginInfo(const std::string &uri);
@@ -615,22 +615,22 @@ static std::vector<uint8_t> HexToByteArray(const std::string &hexState)
} }
return result; 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); std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalboardItem.instanceId(), pedalboardItem.uri(), pHost);
auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri()); auto pluginInfo = this->GetPluginInfo(pedalboardItem.uri());
if (!pluginInfo) 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); result->SetState(state);
} }
else else
{ {
for (const ControlValue &controlValue : pedalBoardItem.controlValues()) for (const ControlValue &controlValue : pedalboardItem.controlValues())
{ {
int32_t index = -1; int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i) 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; int32_t index = -1;
for (size_t i = 0; i < pluginInfo->controls().size(); ++i) 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); controlValue.value(t);
} else { } 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. * SOFTWARE.
*/ */
#include "PiPedalHost.hpp" #include "PluginHost.hpp"
#include "vst3/Vst3Host.hpp" #include "vst3/Vst3Host.hpp"
#include <iostream> #include <iostream>
@@ -75,7 +75,7 @@ void RunVsts()
cout << "Scanning" << endl; cout << "Scanning" << endl;
const auto & plugins = vst3Host->RescanPlugins(); const auto & plugins = vst3Host->RescanPlugins();
PiPedalHost host; PluginHost host;
IHost *pHost = host.asIHost(); IHost *pHost = host.asIHost();
host.setSampleRate(44100); host.setSampleRate(44100);
+4 -1
View File
@@ -17,6 +17,7 @@
#include <set> #include <set>
#include <strings.h> #include <strings.h>
#include "Ipv6Helpers.hpp" #include "Ipv6Helpers.hpp"
#include "util.hpp"
#include "WebServer.hpp" #include "WebServer.hpp"
@@ -582,6 +583,7 @@ namespace pipedal
{ {
try try
{ {
SetThreadName("webMain");
// The io_context is required for all I/O // The io_context is required for all I/O
boost::asio::io_service ioc{threads}; boost::asio::io_service ioc{threads};
//********************************* //*********************************
@@ -632,8 +634,9 @@ namespace pipedal
v.reserve(threads - 1); v.reserve(threads - 1);
for (auto i = threads - 1; i > 0; --i) for (auto i = threads - 1; i > 0; --i)
v.emplace_back( v.emplace_back(
[&ioc] [&ioc,i]
{ {
SetThreadName(SS("web_" << i));
ioc.run(); ioc.run();
}); });

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