Interim checking, Convolution Reverb
This commit is contained in:
Vendored
+1
@@ -7,6 +7,7 @@
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/src",
|
||||
"/usr/include/lilv-0",
|
||||
"/usr/lib",
|
||||
"~/src/vst3sdk"
|
||||
],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
|
||||
Vendored
+41
-12
@@ -6,6 +6,7 @@
|
||||
"configurations": [
|
||||
|
||||
|
||||
|
||||
{
|
||||
"name": "(gdb) generic",
|
||||
"type": "cppdbg",
|
||||
@@ -21,11 +22,6 @@
|
||||
// it gets resolved by CMake Tools:
|
||||
"name": "PATH",
|
||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
||||
},
|
||||
{
|
||||
"name": "ASAN_OPTIONS",
|
||||
"value": "detect_leaks=0"
|
||||
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
@@ -54,11 +50,6 @@
|
||||
// it gets resolved by CMake Tools:
|
||||
"name": "PATH",
|
||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
||||
},
|
||||
{
|
||||
"name": "ASAN_OPTIONS",
|
||||
"value": "detect_leaks=0"
|
||||
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
@@ -80,8 +71,8 @@
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
|
||||
"args": [
|
||||
//"[Dev]"
|
||||
"[json_variants]"
|
||||
//"[Dev]" -- all dev-machine tests.
|
||||
"[json_variants]" // subtest of your choice, or none to run all of the tests.
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
@@ -108,6 +99,44 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) jsonTest Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
|
||||
"args": [
|
||||
//"[Dev]" -- all dev-machine tests.
|
||||
//"[promise]" // subtest of your choice, or none to run all of the tests.
|
||||
"[atom_converter]"
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{
|
||||
// add the directory where our target was built to the PATHs
|
||||
// it gets resolved by CMake Tools:
|
||||
"name": "PATH",
|
||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
||||
},
|
||||
{
|
||||
"name": "OTHER_VALUE",
|
||||
"value": "Something something"
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "(gdb) pipedal_latency_test Launch",
|
||||
|
||||
@@ -60,9 +60,9 @@ add_custom_command(
|
||||
src/PiPedalError.tsx
|
||||
src/IControlViewFactory.tsx
|
||||
src/Lv2Plugin.tsx
|
||||
src/PedalBoard.tsx
|
||||
src/Pedalboard.tsx
|
||||
src/AndroidHost.tsx
|
||||
src/PedalBoardView.tsx
|
||||
src/PedalboardView.tsx
|
||||
src/PresetDialog.tsx
|
||||
src/AppThemed.tsx
|
||||
src/ZoomedDial.tsx
|
||||
|
||||
@@ -162,15 +162,18 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount?.();
|
||||
this.mounted = true;
|
||||
this.updateNotifications();
|
||||
this.startFossRequest();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount?.();
|
||||
this.mounted = false;
|
||||
this.updateNotifications();
|
||||
}
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
|
||||
super.componentDidUpdate?.(prevProps,prevState,snapshot);
|
||||
this.updateNotifications();
|
||||
}
|
||||
|
||||
@@ -196,7 +199,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
About
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
@@ -208,28 +211,28 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
|
||||
}}
|
||||
>
|
||||
<div style={{ margin: 24 }}>
|
||||
<Typography display="block" variant="h6" color="textPrimary">
|
||||
<Typography noWrap display="block" variant="h6" color="textPrimary">
|
||||
PiPedal <span style={{ fontSize: "0.7em" }}>
|
||||
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
|
||||
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
|
||||
</span>
|
||||
</Typography>
|
||||
<Typography display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
Copyright © 2022 Robin Davies.
|
||||
</Typography>
|
||||
{this.model.isAndroidHosted() && (
|
||||
<Typography display="block" variant="body2" style={{ marginBottom: 0 }} >
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 0 }} >
|
||||
{this.model.getAndroidHostVersion()}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{this.model.isAndroidHosted() && (
|
||||
<Typography display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
Copyright © 2022 Robin Davies.
|
||||
</Typography>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography display="block" variant="caption" >
|
||||
<Typography noWrap display="block" variant="caption" >
|
||||
ADDRESSES
|
||||
</Typography>
|
||||
<div style={{marginBottom: 16}}>
|
||||
|
||||
+7
-12
@@ -398,7 +398,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
|
||||
}
|
||||
|
||||
handDisplayOnboarding() {
|
||||
handleDisplayOnboarding() {
|
||||
this.setState({
|
||||
isDrawerOpen: false,
|
||||
isSettingsDialogOpen: true,
|
||||
@@ -502,7 +502,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
|
||||
this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_);
|
||||
this.model_.state.addOnChangedHandler(this.stateChangeHandler_);
|
||||
this.model_.pedalBoard.addOnChangedHandler(this.presetChangedHandler);
|
||||
this.model_.pedalboard.addOnChangedHandler(this.presetChangedHandler);
|
||||
this.model_.alertMessage.addOnChangedHandler(this.alertMessageChangedHandler);
|
||||
this.model_.banks.addOnChangedHandler(this.banksChangedHandler);
|
||||
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
|
||||
@@ -529,7 +529,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
super.componentWillUnmount();
|
||||
this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_);
|
||||
this.model_.state.removeOnChangedHandler(this.stateChangeHandler_);
|
||||
this.model_.pedalBoard.removeOnChangedHandler(this.presetChangedHandler);
|
||||
this.model_.pedalboard.removeOnChangedHandler(this.presetChangedHandler);
|
||||
this.model_.banks.removeOnChangedHandler(this.banksChangedHandler);
|
||||
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
|
||||
|
||||
@@ -575,7 +575,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
this.setState({ errorMessage: message });
|
||||
}
|
||||
|
||||
onboardingShown: boolean = false;
|
||||
|
||||
setDisplayState(newState: State): void {
|
||||
this.updateOverscroll();
|
||||
@@ -586,13 +585,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
});
|
||||
if (newState === State.Ready)
|
||||
{
|
||||
if (!this.onboardingShown)
|
||||
if (this.model_.isOnboarding())
|
||||
{
|
||||
this.onboardingShown = true;
|
||||
if (!this.model_.hasConfiguration())
|
||||
{
|
||||
this.handDisplayOnboarding();
|
||||
}
|
||||
this.handleDisplayOnboarding();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,7 +862,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
<div className={classes.loadingBoxItem}>
|
||||
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
||||
</div>
|
||||
<Typography variant="body2" className={classes.progressText}>
|
||||
<Typography noWrap variant="body2" className={classes.progressText}>
|
||||
{this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -905,7 +900,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
<div className={classes.loadingBoxItem}>
|
||||
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
||||
</div>
|
||||
<Typography variant="body2" className={classes.loadingBoxItem}>
|
||||
<Typography noWrap variant="body2" className={classes.loadingBoxItem}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -298,7 +298,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
||||
<img src="img/ic_bank.svg" className={classes.itemIcon} alt="" />
|
||||
</div>
|
||||
<div className={classes.itemLabel}>
|
||||
<Typography>
|
||||
<Typography noWrap>
|
||||
{bankEntry.name}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -410,7 +410,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
Banks
|
||||
</Typography>
|
||||
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} >
|
||||
@@ -433,7 +433,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
||||
</IconButton>
|
||||
|
||||
)}
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
Banks
|
||||
</Typography>
|
||||
{(this.state.banks.getEntry(this.state.selectedItem) != null)
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from 'react';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
|
||||
|
||||
import {PedalBoardItem, PedalBoardSplitItem} from './PedalBoard';
|
||||
import {PedalboardItem, PedalboardSplitItem} from './Pedalboard';
|
||||
import PluginControlView from './PluginControlView';
|
||||
import SplitControlView from './SplitControlView';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -47,38 +47,38 @@ let pluginFactories: IControlViewFactory[] = [
|
||||
];
|
||||
|
||||
|
||||
export function GetControlView(pedalBoardItem?: PedalBoardItem| null): React.ReactNode
|
||||
export function GetControlView(pedalboardItem?: PedalboardItem| null): React.ReactNode
|
||||
{
|
||||
let model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||
|
||||
if (!pedalBoardItem) {
|
||||
if (!pedalboardItem) {
|
||||
return (<div/>);
|
||||
}
|
||||
if (pedalBoardItem.isSplit())
|
||||
if (pedalboardItem.isSplit())
|
||||
{
|
||||
return (
|
||||
<SplitControlView item={pedalBoardItem as PedalBoardSplitItem} instanceId={pedalBoardItem!.instanceId}
|
||||
<SplitControlView item={pedalboardItem as PedalboardSplitItem} instanceId={pedalboardItem!.instanceId}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < pluginFactories.length; ++i)
|
||||
{
|
||||
let factory = pluginFactories[i];
|
||||
if (factory.uri === pedalBoardItem.uri)
|
||||
if (factory.uri === pedalboardItem.uri)
|
||||
{
|
||||
return factory.Create(model,pedalBoardItem);
|
||||
return factory.Create(model,pedalboardItem);
|
||||
}
|
||||
}
|
||||
let uiPlugin = model.getUiPlugin(pedalBoardItem.uri);
|
||||
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
|
||||
if (!uiPlugin)
|
||||
{
|
||||
<div style={{paddingLeft: 40, paddingRight: 40}}>
|
||||
<Typography color="error" variant="h6" >Missing plugin.</Typography>
|
||||
<Typography>The plugin '{pedalBoardItem.pluginName}' ({pedalBoardItem.uri}) is not currently installed.</Typography>
|
||||
<Typography>The plugin '{pedalboardItem.pluginName}' ({pedalboardItem.uri}) is not currently installed.</Typography>
|
||||
</div>
|
||||
} else {
|
||||
return (
|
||||
<PluginControlView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />
|
||||
<PluginControlView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-41
@@ -47,12 +47,9 @@ function peekDialogStack(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
let ignoreNextPop = false;
|
||||
|
||||
function popDialogStack(ignoreNextPop_: boolean): void {
|
||||
function popDialogStack(): void {
|
||||
if (dialogStack.length !== 0)
|
||||
{
|
||||
ignoreNextPop = ignoreNextPop_;
|
||||
dialogStack.splice(dialogStack.length-1,1);
|
||||
}
|
||||
}
|
||||
@@ -76,27 +73,23 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
mounted: boolean = false;
|
||||
|
||||
hasHooks: boolean = false;
|
||||
|
||||
stateWasPopped: boolean = false;
|
||||
handlePopState(e: any): any {
|
||||
|
||||
handlePopState(e: any): any
|
||||
{
|
||||
let shouldClose = (peekDialogStack() === this.props.tag);
|
||||
if (shouldClose)
|
||||
{
|
||||
if (ignoreNextPop)
|
||||
{
|
||||
ignoreNextPop = false;
|
||||
} else {
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
this.stateWasPopped = true;
|
||||
|
||||
if (this.props.onClose)
|
||||
{
|
||||
popDialogStack(false);
|
||||
this.props.onClose(e,"backdropClick");
|
||||
}
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
this.stateWasPopped = true;
|
||||
popDialogStack();
|
||||
if (this.props.open) {
|
||||
this.props.onClose?.(e,"backdropClick");
|
||||
}
|
||||
}
|
||||
window.removeEventListener("popstate",this.handlePopState);
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -111,49 +104,35 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
{
|
||||
this.stateWasPopped = false;
|
||||
window.addEventListener("popstate",this.handlePopState);
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
let state = history.state;
|
||||
if (!state)
|
||||
{
|
||||
state = {};
|
||||
}
|
||||
state[this.props.tag] = true;
|
||||
|
||||
|
||||
pushDialogStack(this.props.tag);
|
||||
let state: {tag: string} = {tag: this.props.tag};
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
history.pushState(
|
||||
state,
|
||||
"",
|
||||
"" //"#" + this.props.tag
|
||||
"#" + this.props.tag
|
||||
);
|
||||
} else {
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
this.stateWasPopped = true;
|
||||
popDialogStack(true);
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
history.back();
|
||||
}
|
||||
window.removeEventListener("popstate",this.handlePopState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
if (super.componentDidMount)
|
||||
{
|
||||
super.componentDidMount();
|
||||
}
|
||||
super.componentDidMount?.();
|
||||
|
||||
this.mounted = true;
|
||||
this.updateHooks();
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
if (super.componentWillUnmount)
|
||||
{
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
super.componentWillUnmount?.();
|
||||
this.mounted = false;
|
||||
this.updateHooks();
|
||||
}
|
||||
@@ -162,12 +141,15 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
{
|
||||
this.updateHooks();
|
||||
}
|
||||
|
||||
myOnClose(event:{}, reason: "backdropClick" | "escapeKeyDown")
|
||||
{
|
||||
if (this.props.onClose) this.props.onClose(event,reason);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { tag, ...extra} = this.props;
|
||||
let { tag,onClose, ...extra} = this.props;
|
||||
return (
|
||||
<Dialog {...extra}>
|
||||
<Dialog {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
||||
{this.props.children}
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -22,33 +22,20 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react';
|
||||
import React, { Component, SyntheticEvent } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
|
||||
import { PiPedalFileProperty } from './Lv2Plugin';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Input from '@mui/material/Input';
|
||||
import Select from '@mui/material/Select';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Utility, { nullCast } from './Utility';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle, StateChangedHandle } from './PiPedalModel';
|
||||
import ButtonBase from '@mui/material/ButtonBase'
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
|
||||
const MIN_ANGLE = -135;
|
||||
const MAX_ANGLE = 135;
|
||||
const FONT_SIZE = "0.8em";
|
||||
|
||||
|
||||
|
||||
const SELECTED_OPACITY = 0.8;
|
||||
const DEFAULT_OPACITY = 0.6;
|
||||
const RANGE_SCALE = 120; // 120 pixels to move from 0 to 1.
|
||||
const FINE_RANGE_SCALE = RANGE_SCALE * 10; // 1200 pixels to move from 0 to 1.
|
||||
const ULTRA_FINE_RANGE_SCALE = RANGE_SCALE * 50; // 12000 pixels to move from 0 to 1.
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
@@ -88,13 +75,12 @@ const styles = (theme: Theme) => createStyles({
|
||||
export interface FilePropertyControlProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
fileProperty: PiPedalFileProperty;
|
||||
value: string;
|
||||
onFileClick: (fileProperty: PiPedalFileProperty,value: string) => void;
|
||||
onFileClick: (fileProperty: PiPedalFileProperty, value: string) => void;
|
||||
theme: Theme;
|
||||
}
|
||||
type FilePropertyControlState = {
|
||||
error: boolean;
|
||||
showDialog: boolean;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const FilePropertyControl =
|
||||
@@ -110,13 +96,75 @@ const FilePropertyControl =
|
||||
|
||||
this.state = {
|
||||
error: false,
|
||||
showDialog: false
|
||||
value: "",
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
if (this.props.fileProperty.patchProperty !== "") {
|
||||
this.subscribeToPropertyGet();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
private propertyGetHandle?: ListenHandle;
|
||||
|
||||
refreshPatchProperty()
|
||||
{
|
||||
this.model.getPatchProperty(this.props.instanceId, this.props.fileProperty.patchProperty)
|
||||
.then(
|
||||
(json: any) => {
|
||||
if (json && json.otype_ === "Path") {
|
||||
let path = json.value as string;
|
||||
this.setState({ value: path });
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(
|
||||
(error: Error) => {
|
||||
this.model.showAlert(error.toString());
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
stateChangedHandle?: StateChangedHandle;
|
||||
|
||||
subscribeToPropertyGet() {
|
||||
// this.propertyGetHandle = this.model.listenForAtomOutput(this.props.instanceId,(instanceId,atomOutput)=>{
|
||||
// // PARSE THE OBJECT!
|
||||
// // this.setState({value: atomOutput?.value as string});
|
||||
// });
|
||||
if (this.stateChangedHandle)
|
||||
{
|
||||
this.model.removeLv2StateChangedListener(this.stateChangedHandle);
|
||||
}
|
||||
this.refreshPatchProperty();
|
||||
this.stateChangedHandle = this.model.addLv2StateChangedListener(
|
||||
this.props.instanceId,
|
||||
() => {
|
||||
this.refreshPatchProperty();
|
||||
});
|
||||
}
|
||||
unsubscribeToPropertyGet() {
|
||||
if (this.stateChangedHandle)
|
||||
{
|
||||
this.model.removeLv2StateChangedListener(this.stateChangedHandle);
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
this.subscribeToPropertyGet();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeToPropertyGet();
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<FilePropertyControlProps>, prevState: Readonly<FilePropertyControlState>, snapshot?: any): void {
|
||||
if (prevProps.fileProperty.patchProperty !== this.props.fileProperty.patchProperty
|
||||
|| prevProps.instanceId !== this.props.instanceId) {
|
||||
this.setState({ value: "" });
|
||||
this.unsubscribeToPropertyGet();
|
||||
this.subscribeToPropertyGet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inputChanged: boolean = false;
|
||||
|
||||
onDrag(e: SyntheticEvent) {
|
||||
@@ -124,40 +172,36 @@ const FilePropertyControl =
|
||||
}
|
||||
|
||||
onFileClick() {
|
||||
this.props.onFileClick(this.props.fileProperty,this.props.value);
|
||||
this.props.onFileClick(this.props.fileProperty, this.state.value);
|
||||
}
|
||||
private fileNameOnly(path: string): string {
|
||||
let slashPos = path.lastIndexOf('/');
|
||||
if (slashPos < 0)
|
||||
{
|
||||
if (slashPos < 0) {
|
||||
slashPos = 0;
|
||||
} else {
|
||||
++slashPos;
|
||||
}
|
||||
let extPos = path.lastIndexOf('.');
|
||||
if (extPos < 0 || extPos < slashPos)
|
||||
{
|
||||
if (extPos < 0 || extPos < slashPos) {
|
||||
extPos = path.length;
|
||||
}
|
||||
}
|
||||
|
||||
return path.substring(slashPos,extPos);
|
||||
return path.substring(slashPos, extPos);
|
||||
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
//let classes = this.props.classes;
|
||||
let fileProperty = this.props.fileProperty;
|
||||
|
||||
let value = this.props.value;
|
||||
if (!value || value.length === 0)
|
||||
{
|
||||
let value = this.fileNameOnly(this.state.value);
|
||||
if (!value || value.length === 0) {
|
||||
value = "\u00A0";
|
||||
}
|
||||
value = this.fileNameOnly(value);
|
||||
|
||||
let item_width = 264;
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8}}>
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8 }}>
|
||||
{/* TITLE SECTION */}
|
||||
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
@@ -169,13 +213,13 @@ const FilePropertyControl =
|
||||
|
||||
<div style={{ flex: "0 0 auto", width: "100%" }}>
|
||||
|
||||
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden",marginTop: 8}} onClick={()=> {this.onFileClick()}} >
|
||||
<div style={{width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px"}}>
|
||||
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden", marginTop: 8 }} onClick={() => { this.onFileClick() }} >
|
||||
<div style={{ width: "100%", background: "rgba(0,0,0,0.07)", borderRadius: "4px 4px 0px 0px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4,paddingBottom: 4,paddingLeft: 4}}
|
||||
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4, paddingBottom: 4, paddingLeft: 4 }}
|
||||
variant='caption'
|
||||
>{value}</Typography>
|
||||
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center",opacity: 0.5, marginRight: 4 }} />
|
||||
>{value}</Typography>
|
||||
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center", opacity: 0.5, marginRight: 4 }} />
|
||||
</div>
|
||||
<div style={{ height: "1px", width: "100%", background: "black" }}> </div>
|
||||
</div>
|
||||
|
||||
@@ -23,35 +23,27 @@ import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
|
||||
|
||||
import Button from '@mui/material/Button';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
import AudioFileIcon from '@mui/icons-material/AudioFile';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
|
||||
import { PiPedalFileProperty } from './Lv2Plugin';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DialogEx from './DialogEx';
|
||||
import { ModelTraining } from '@mui/icons-material';
|
||||
|
||||
export interface FilePropertyDialogProps {
|
||||
open: boolean,
|
||||
fileProperty: PiPedalFileProperty,
|
||||
selectedFile: string,
|
||||
onOk: (fileProperty: PiPedalFileProperty, selectedItem: string) => void,
|
||||
onClose: () => void
|
||||
onCancel: () => void
|
||||
};
|
||||
|
||||
export interface FilePropertyDialogState {
|
||||
@@ -82,7 +74,23 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
||||
private mounted: boolean = false;
|
||||
private model: PiPedalModel;
|
||||
|
||||
private lastFileProperty: PiPedalFileProperty = new PiPedalFileProperty();
|
||||
|
||||
private requestFiles() {
|
||||
if (!this.props.open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.props.fileProperty.directory === "")
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.lastFileProperty === this.props.fileProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.lastFileProperty = this.props.fileProperty;
|
||||
|
||||
this.model.requestFileList(this.props.fileProperty)
|
||||
.then((files) => {
|
||||
if (this.mounted)
|
||||
@@ -108,6 +116,11 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
||||
this.mounted = false;
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void {
|
||||
super.componentDidUpdate?.(prevProps,prevState,snapshot);
|
||||
if (prevProps.fileProperty !== this.props.fileProperty)
|
||||
{
|
||||
this.requestFiles()
|
||||
}
|
||||
}
|
||||
|
||||
private fileNameOnly(path: string): string {
|
||||
@@ -142,29 +155,39 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
||||
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.files,selectedFile) })
|
||||
}
|
||||
|
||||
mapKey: number = 0;
|
||||
render() {
|
||||
|
||||
return (
|
||||
<DialogEx onClose={() => this.props.onClose()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
|
||||
this.mapKey = 0;
|
||||
return this.props.open &&
|
||||
(
|
||||
<DialogEx onClose={() => this.props.onCancel()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
|
||||
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
|
||||
>
|
||||
<DialogTitle >{this.props.fileProperty.name}</DialogTitle>
|
||||
<DialogTitle >
|
||||
<div>
|
||||
<IconButton edge="start" color="inherit" onClick={()=> {this.props.onCancel();}} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon fontSize="small" style={{opacity: "0.6"}} />
|
||||
</IconButton>
|
||||
<Typography display="inline" >{this.props.fileProperty.name}</Typography>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
||||
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
|
||||
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start" }}>
|
||||
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start",paddingLeft: 16,paddingTop:16 }}>
|
||||
{
|
||||
this.state.files.map(
|
||||
(value: string, index: number) => {
|
||||
let selected = value === this.state.selectedFile;
|
||||
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
|
||||
return (
|
||||
<ButtonBase
|
||||
style={{ width: "320px", flex: "0 0 48px", position: "relative" }}
|
||||
<ButtonBase key={ this.mapKey++ }
|
||||
style={{ width: "320px", flex: "0 0 48px", position: "relative"}}
|
||||
onClick={() => this.onSelect(value)}
|
||||
>
|
||||
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%" }} />
|
||||
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
||||
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 4 }} />
|
||||
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
||||
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
@@ -181,14 +204,14 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
||||
<input hidden accept="audio/x-wav" name="Upload Impuse File" type="file" multiple />
|
||||
Upload
|
||||
</Button>
|
||||
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label">
|
||||
<DeleteIcon />
|
||||
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label" color="primary" >
|
||||
<DeleteIcon fontSize='small' />
|
||||
</IconButton>
|
||||
<div style={{ flex: "1 1 auto" }}> </div>
|
||||
<Button onClick={() => this.props.onClose()} aria-label="cancel">
|
||||
<Button onClick={() => {this.props.onCancel();}} aria-label="cancel">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style={{ flex: "0 0 auto" }} onClick={() => this.props.onClose()} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
|
||||
<Button style={{ flex: "0 0 auto" }} onClick={() => {this.props.onOk(this.props.fileProperty,this.state.selectedFile);}} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
|
||||
Select
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -203,7 +203,7 @@ const FullScreenIME =
|
||||
justifyContent: "center", alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">{this.props.caption}</Typography>
|
||||
<Typography noWrap variant="body2">{this.props.caption}</Typography>
|
||||
<Input key={value}
|
||||
type="number"
|
||||
defaultValue={control.formatShortValue(value)}
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import GxTunerControl from './GxTunerControl';
|
||||
|
||||
@@ -40,7 +40,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface GxTunerProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
isToobTuner: boolean;
|
||||
|
||||
}
|
||||
@@ -115,8 +115,8 @@ const GxTunerView =
|
||||
export class GxTunerViewFactory implements IControlViewFactory {
|
||||
uri: string = GXTUNER_URI;
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<GxTunerView isToobTuner={false} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<GxTunerView isToobTuner={false} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,8 +126,8 @@ export class GxTunerViewFactory implements IControlViewFactory {
|
||||
export class ToobTunerViewFactory implements IControlViewFactory {
|
||||
uri: string = TOOBTUNER_URI;
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<GxTunerView isToobTuner={true} instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<GxTunerView isToobTuner={true} instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
|
||||
import React from 'react';
|
||||
import { PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
|
||||
|
||||
|
||||
interface IControlViewFactory {
|
||||
uri: string;
|
||||
Create(model: PiPedalModel,pedalBoardItem: PedalBoardItem): React.ReactNode;
|
||||
Create(model: PiPedalModel,pedalboardItem: PedalboardItem): React.ReactNode;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
export default class JackServerSettings {
|
||||
deserialize(input: any) : JackServerSettings{
|
||||
this.valid = input.valid;
|
||||
this.isOnboarding = input.isOnboarding;
|
||||
this.isJackAudio = input.isJackAudio;
|
||||
this.rebootRequired = input.rebootRequired;
|
||||
this.alsaDevice = input.alsaDevice?? "";
|
||||
@@ -44,6 +45,7 @@ export default class JackServerSettings {
|
||||
return new JackServerSettings().deserialize(this);
|
||||
}
|
||||
valid: boolean = false;
|
||||
isOnboarding: boolean = true;
|
||||
rebootRequired = false;
|
||||
isJackAudio = false;
|
||||
alsaDevice: string = "";
|
||||
@@ -54,7 +56,7 @@ export default class JackServerSettings {
|
||||
getSummaryText() {
|
||||
if (this.valid) {
|
||||
let device = this.alsaDevice;
|
||||
if (device.startsWith("hw:")) device = device.substr(3);
|
||||
if (device.startsWith("hw:")) device = device.substring(3);
|
||||
return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers;
|
||||
} else {
|
||||
return "Not configured";
|
||||
|
||||
@@ -134,8 +134,8 @@ const JackServerSettingsDialog = withStyles(styles)(
|
||||
|
||||
}
|
||||
if (result.sampleRate === 0) result.sampleRate = 48000;
|
||||
if (result.bufferSize === 0) result.bufferSize = 64;
|
||||
if (result.numberOfBuffers === 0) result.numberOfBuffers = 2;
|
||||
if (result.bufferSize === 0) result.bufferSize = 16;
|
||||
if (result.numberOfBuffers === 0) result.numberOfBuffers = 3;
|
||||
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
||||
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
|
||||
result.valid = true;
|
||||
|
||||
@@ -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
@@ -123,13 +123,37 @@ export class PiPedalFileType {
|
||||
name: string = "";
|
||||
fileExtension: string = "";
|
||||
}
|
||||
|
||||
export class UiPropertyNotification {
|
||||
deserialize(input: any): UiPropertyNotification
|
||||
{
|
||||
this.portIndex = input.portIndex;
|
||||
this.symbol = input.symbol;
|
||||
this.plugin = input.plugin;
|
||||
this.protocol = input.protocol;
|
||||
return this;
|
||||
}
|
||||
static deserialize_array(input: any): UiPropertyNotification[]
|
||||
{
|
||||
let result: UiPropertyNotification[] = [];
|
||||
for (let i = 0; i < input.length; ++i)
|
||||
{
|
||||
result[i] = new UiPropertyNotification().deserialize(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
portIndex: number = -1;
|
||||
symbol: string = "";
|
||||
plugin: string = "";
|
||||
protocol: string = "";
|
||||
|
||||
};
|
||||
export class PiPedalFileProperty {
|
||||
deserialize(input: any): PiPedalFileProperty
|
||||
{
|
||||
this.name = input.name;
|
||||
this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes);
|
||||
this.patchProperty = input.patchProperty;
|
||||
this.defaultFile = input.defaultFile;
|
||||
this.directory = input.directory;
|
||||
return this;
|
||||
}
|
||||
@@ -147,7 +171,6 @@ export class PiPedalFileProperty {
|
||||
fileTypes: PiPedalFileType[] = [];
|
||||
patchProperty: string = "";
|
||||
directory: string = "";
|
||||
defaultFile: string = "";
|
||||
|
||||
};
|
||||
export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
||||
@@ -170,6 +193,12 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
||||
} else {
|
||||
this.fileProperties = [];
|
||||
}
|
||||
if (input.uiPortNotifications)
|
||||
{
|
||||
this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications);
|
||||
} else {
|
||||
this.uiPortNotifications = [];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
static EmptyFeatures: string[] = [];
|
||||
@@ -186,6 +215,7 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
||||
ports: Port[] = Port.EmptyPorts;
|
||||
port_groups: PortGroup[] = [];
|
||||
fileProperties: PiPedalFileProperty[] = [];
|
||||
uiPortNotifications: UiPropertyNotification[] = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
+58
-58
@@ -24,15 +24,15 @@ import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import {
|
||||
PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType
|
||||
} from './PedalBoard';
|
||||
Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType
|
||||
} from './Pedalboard';
|
||||
import Button from '@mui/material/Button';
|
||||
import InputIcon from '@mui/icons-material/Input';
|
||||
import LoadPluginDialog from './LoadPluginDialog';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import PedalBoardView from './PedalBoardView';
|
||||
import PedalboardView from './PedalboardView';
|
||||
import { PiPedalStateError } from './PiPedalError';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@@ -58,11 +58,11 @@ const styles = ({ palette }: Theme) => createStyles({
|
||||
position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden"
|
||||
},
|
||||
pedalBoardScroll: {
|
||||
pedalboardScroll: {
|
||||
position: "relative", width: "100%",
|
||||
flex: "0 0 auto", overflow: "auto", maxHeight: 220
|
||||
},
|
||||
pedalBoardScrollSmall: {
|
||||
pedalboardScrollSmall: {
|
||||
position: "relative", width: "100%",
|
||||
flex: "1 1 1px", overflow: "auto"
|
||||
},
|
||||
@@ -97,7 +97,7 @@ interface MainProps extends WithStyles<typeof styles> {
|
||||
interface MainState {
|
||||
selectedPedal: number;
|
||||
loadDialogOpen: boolean;
|
||||
pedalBoard: PedalBoard;
|
||||
pedalboard: Pedalboard;
|
||||
addMenuAnchorEl: HTMLElement | null;
|
||||
splitControlBar: boolean;
|
||||
horizontalScrollLayout: boolean;
|
||||
@@ -116,13 +116,13 @@ export const MainPage =
|
||||
constructor(props: MainProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
let pedalboard = this.model.pedalBoard.get();
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
let selectedPedal = pedalboard.getFirstSelectableItem();
|
||||
|
||||
this.state = {
|
||||
selectedPedal: selectedPedal,
|
||||
loadDialogOpen: false,
|
||||
pedalBoard: pedalboard,
|
||||
pedalboard: pedalboard,
|
||||
addMenuAnchorEl: null,
|
||||
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
|
||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||
@@ -135,31 +135,31 @@ export const MainPage =
|
||||
this.onLoadClick = this.onLoadClick.bind(this);
|
||||
this.onLoadOk = this.onLoadOk.bind(this);
|
||||
this.onLoadCancel = this.onLoadCancel.bind(this);
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this);
|
||||
}
|
||||
|
||||
onInsertPedal(instanceId: number) {
|
||||
this.setAddMenuAnchorEl(null);
|
||||
let newId = this.model.addPedalBoardItem(instanceId, false);
|
||||
let newId = this.model.addPedalboardItem(instanceId, false);
|
||||
this.setSelection(newId);
|
||||
}
|
||||
onAppendPedal(instanceId: number) {
|
||||
this.setAddMenuAnchorEl(null);
|
||||
let newId = this.model.addPedalBoardItem(instanceId, true);
|
||||
let newId = this.model.addPedalboardItem(instanceId, true);
|
||||
|
||||
this.setSelection(newId);
|
||||
|
||||
}
|
||||
onInsertSplit(instanceId: number) {
|
||||
this.setAddMenuAnchorEl(null);
|
||||
let newId = this.model.addPedalBoardSplitItem(instanceId, false);
|
||||
let newId = this.model.addPedalboardSplitItem(instanceId, false);
|
||||
this.setSelection(newId);
|
||||
|
||||
}
|
||||
onAppendSplit(instanceId: number) {
|
||||
this.setAddMenuAnchorEl(null);
|
||||
let newId = this.model.addPedalBoardSplitItem(instanceId, true);
|
||||
let newId = this.model.addPedalboardSplitItem(instanceId, true);
|
||||
this.setSelection(newId);
|
||||
|
||||
}
|
||||
@@ -182,16 +182,16 @@ export const MainPage =
|
||||
}
|
||||
handleEnableCurrentItemChanged(event: any): void {
|
||||
let newValue = event.target.checked;
|
||||
let item = this.getSelectedPedalBoardItem();
|
||||
let item = this.getSelectedPedalboardItem();
|
||||
if (item != null) {
|
||||
this.model.setPedalBoardItemEnabled(item.getInstanceId(), newValue);
|
||||
this.model.setPedalboardItemEnabled(item.getInstanceId(), newValue);
|
||||
|
||||
}
|
||||
}
|
||||
handleSelectPluginPreset(instanceId: number, presetInstanceId: number) {
|
||||
this.model.loadPluginPreset(instanceId, presetInstanceId);
|
||||
}
|
||||
onPedalBoardChanged(value: PedalBoard) {
|
||||
onPedalboardChanged(value: Pedalboard) {
|
||||
let selectedItem = -1;
|
||||
if (value.hasItem(this.state.selectedPedal))
|
||||
{
|
||||
@@ -200,12 +200,12 @@ export const MainPage =
|
||||
selectedItem = value.getFirstSelectableItem();
|
||||
}
|
||||
this.setState({
|
||||
pedalBoard: value,
|
||||
pedalboard: value,
|
||||
selectedPedal: selectedItem
|
||||
});
|
||||
}
|
||||
onDeletePedal(instanceId: number): void {
|
||||
let result = this.model.deletePedalBoardPedal(instanceId);
|
||||
let result = this.model.deletePedalboardPedal(instanceId);
|
||||
if (result != null) {
|
||||
this.setState({ selectedPedal: result });
|
||||
}
|
||||
@@ -213,10 +213,10 @@ export const MainPage =
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
updateResponsive() {
|
||||
@@ -241,14 +241,14 @@ export const MainPage =
|
||||
}
|
||||
onPedalDoubleClick(selectedId: number): void {
|
||||
this.setSelection(selectedId);
|
||||
let item = this.getPedalBoardItem(selectedId);
|
||||
let item = this.getPedalboardItem(selectedId);
|
||||
if (item != null) {
|
||||
if (item.isSplit()) {
|
||||
let split = item as PedalBoardSplitItem;
|
||||
let split = item as PedalboardSplitItem;
|
||||
if (split.getSplitType() === SplitType.Ab) {
|
||||
let cv = split.getToggleAbControlValue();
|
||||
if (split.instanceId === undefined) throw new PiPedalStateError("Split without valid id.");
|
||||
this.model.setPedalBoardControlValue(split.instanceId, cv.key, cv.value);
|
||||
this.model.setPedalboardControl(split.instanceId, cv.key, cv.value);
|
||||
}
|
||||
} else {
|
||||
this.setState({ loadDialogOpen: true });
|
||||
@@ -261,7 +261,7 @@ export const MainPage =
|
||||
onLoadOk(selectedUri: string): void {
|
||||
this.setState({ loadDialogOpen: false });
|
||||
let itemId = this.state.selectedPedal;
|
||||
let newSelectedItem = this.model.loadPedalBoardPlugin(itemId, selectedUri);
|
||||
let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri);
|
||||
this.setState({ selectedPedal: newSelectedItem });
|
||||
}
|
||||
|
||||
@@ -269,12 +269,12 @@ export const MainPage =
|
||||
this.setState({ loadDialogOpen: true });
|
||||
}
|
||||
|
||||
getPedalBoardItem(selectedId?: number): PedalBoardItem | null {
|
||||
getPedalboardItem(selectedId?: number): PedalboardItem | null {
|
||||
if (selectedId === undefined) return null;
|
||||
|
||||
let pedalBoard = this.model.pedalBoard.get();
|
||||
if (!pedalBoard) return null;
|
||||
let it = pedalBoard.itemsGenerator();
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
if (!pedalboard) return null;
|
||||
let it = pedalboard.itemsGenerator();
|
||||
if (!selectedId) return null;
|
||||
while (true) {
|
||||
let v = it.next();
|
||||
@@ -290,32 +290,32 @@ export const MainPage =
|
||||
}
|
||||
|
||||
onPedalboardPropertyChanged(instanceId: number, key: string, value: number) {
|
||||
this.model.setPedalBoardControlValue(instanceId, key, value);
|
||||
this.model.setPedalboardControl(instanceId, key, value);
|
||||
}
|
||||
getSelectedPedalBoardItem(): PedalBoardItem | null {
|
||||
return this.getPedalBoardItem(this.state.selectedPedal);
|
||||
getSelectedPedalboardItem(): PedalboardItem | null {
|
||||
return this.getPedalboardItem(this.state.selectedPedal);
|
||||
}
|
||||
getSelectedUri(): string {
|
||||
let pedalBoardItem = this.getSelectedPedalBoardItem();
|
||||
if (pedalBoardItem == null) return "";
|
||||
return pedalBoardItem.uri;
|
||||
let pedalboardItem = this.getSelectedPedalboardItem();
|
||||
if (pedalboardItem == null) return "";
|
||||
return pedalboardItem.uri;
|
||||
}
|
||||
titleBar(pedalBoardItem: PedalBoardItem | null): React.ReactNode {
|
||||
titleBar(pedalboardItem: PedalboardItem | null): React.ReactNode {
|
||||
let title = "";
|
||||
let author = "";
|
||||
let pluginUri = "";
|
||||
let presetsUri = "";
|
||||
let missing = false;
|
||||
if (pedalBoardItem) {
|
||||
if (pedalBoardItem.isEmpty()) {
|
||||
if (pedalboardItem) {
|
||||
if (pedalboardItem.isEmpty()) {
|
||||
title = "";
|
||||
} else if (pedalBoardItem.isSplit()) {
|
||||
} else if (pedalboardItem.isSplit()) {
|
||||
title = "Split";
|
||||
} else {
|
||||
let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri);
|
||||
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
|
||||
if (!uiPlugin) {
|
||||
missing = true;
|
||||
title = pedalBoardItem?.pluginName ?? "Missing plugin";
|
||||
title = pedalboardItem?.pluginName ?? "Missing plugin";
|
||||
} else {
|
||||
title = uiPlugin.name;
|
||||
author = uiPlugin.author_name;
|
||||
@@ -362,7 +362,7 @@ export const MainPage =
|
||||
<PluginInfoDialog plugin_uri={pluginUri} />
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalBoardItem?.instanceId ?? 0}
|
||||
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -373,8 +373,8 @@ export const MainPage =
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let pedalBoard = this.model.pedalBoard.get();
|
||||
let pedalBoardItem = this.getSelectedPedalBoardItem();
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
let pedalboardItem = this.getSelectedPedalboardItem();
|
||||
let uiPlugin = null;
|
||||
let bypassVisible = false;
|
||||
let bypassChecked = false;
|
||||
@@ -384,20 +384,20 @@ export const MainPage =
|
||||
let missing = false;
|
||||
let pluginUri = "#error";
|
||||
|
||||
if (pedalBoardItem) {
|
||||
canDelete = pedalBoard.canDeleteItem(pedalBoardItem.instanceId);
|
||||
instanceId = pedalBoardItem.instanceId;
|
||||
if (pedalBoardItem.isEmpty()) {
|
||||
if (pedalboardItem) {
|
||||
canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
|
||||
instanceId = pedalboardItem.instanceId;
|
||||
if (pedalboardItem.isEmpty()) {
|
||||
canAdd = true;
|
||||
} else if (pedalBoardItem.isSplit()) {
|
||||
} else if (pedalboardItem.isSplit()) {
|
||||
canAdd = true;
|
||||
} else {
|
||||
pluginUri = pedalBoardItem.uri;
|
||||
pluginUri = pedalboardItem.uri;
|
||||
uiPlugin = this.model.getUiPlugin(pluginUri);
|
||||
canAdd = true;
|
||||
if (uiPlugin) {
|
||||
bypassVisible = true;
|
||||
bypassChecked = pedalBoardItem.isEnabled;
|
||||
bypassChecked = pedalboardItem.isEnabled;
|
||||
} else {
|
||||
missing = true;
|
||||
}
|
||||
@@ -407,9 +407,9 @@ export const MainPage =
|
||||
|
||||
return (
|
||||
<div className={classes.frame}>
|
||||
<div id="pedalBoardScroll" className={horizontalScrollLayout ? classes.pedalBoardScrollSmall : classes.pedalBoardScroll}
|
||||
<div id="pedalboardScroll" className={horizontalScrollLayout ? classes.pedalboardScrollSmall : classes.pedalboardScroll}
|
||||
style={{ maxHeight: horizontalScrollLayout ? undefined : this.state.screenHeight / 2 }}>
|
||||
<PedalBoardView key={pluginUri} selectedId={this.state.selectedPedal}
|
||||
<PedalboardView key={pluginUri} selectedId={this.state.selectedPedal}
|
||||
onSelectionChanged={this.onSelectionChanged}
|
||||
onDoubleClick={this.onPedalDoubleClick}
|
||||
hasTinyToolBar={this.props.hasTinyToolBar}
|
||||
@@ -427,7 +427,7 @@ export const MainPage =
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
(!this.state.splitControlBar) && this.titleBar(pedalBoardItem)
|
||||
(!this.state.splitControlBar) && this.titleBar(pedalboardItem)
|
||||
}
|
||||
<div style={{ flex: "1 1 1px" }}>
|
||||
|
||||
@@ -453,7 +453,7 @@ export const MainPage =
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
||||
<IconButton
|
||||
onClick={() => { this.onDeletePedal(pedalBoardItem?.instanceId ?? -1) }}
|
||||
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
||||
size="large">
|
||||
<img src="/img/old_delete_outline_black_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButton>
|
||||
@@ -464,7 +464,7 @@ export const MainPage =
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={this.onLoadClick}
|
||||
disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalBoardItem()?.isSplit() ?? true)}
|
||||
disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
||||
startIcon={<InputIcon />}
|
||||
style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}}
|
||||
>
|
||||
@@ -485,7 +485,7 @@ export const MainPage =
|
||||
this.state.splitControlBar && (
|
||||
<div className={classes.splitControlBar}>
|
||||
{
|
||||
this.titleBar(pedalBoardItem)
|
||||
this.titleBar(pedalboardItem)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
@@ -495,12 +495,12 @@ export const MainPage =
|
||||
missing ? (
|
||||
<div style={{marginLeft: 100,marginTop: 20}}>
|
||||
<Typography variant="body1" paragraph={true}>Error: Plugin is not installed.</Typography>
|
||||
<Typography variant="body2" paragraph={true}>{pluginUri}</Typography>
|
||||
<Typography noWrap variant="body2" paragraph={true}>{pluginUri}</Typography>
|
||||
</div>
|
||||
|
||||
):
|
||||
(
|
||||
GetControlView(pedalBoardItem)
|
||||
GetControlView(pedalboardItem)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -139,8 +139,8 @@ const MidiBindingView =
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let midiBinding = this.props.midiBinding;
|
||||
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId);
|
||||
let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri);
|
||||
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
|
||||
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
|
||||
if (!uiPlugin) {
|
||||
return (<div />);
|
||||
}
|
||||
@@ -267,7 +267,7 @@ const MidiBindingView =
|
||||
(canTrigger) &&
|
||||
(
|
||||
<div className={classes.controlDiv2} >
|
||||
<Typography style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography>
|
||||
<Typography noWrap style={{paddingTop: 12, paddingBottom: 12}}>(Trigger)</Typography>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -123,8 +123,8 @@ export const MidiBindingDialog =
|
||||
{
|
||||
this.cancelListenForControl();
|
||||
|
||||
let pedalBoard = this.model.pedalBoard.get();
|
||||
let item = pedalBoard.getItem(instanceId);
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
let item = pedalboard.getItem(instanceId);
|
||||
if (!item) return;
|
||||
|
||||
let binding = item.getMidiBinding(symbol);
|
||||
@@ -181,7 +181,7 @@ export const MidiBindingDialog =
|
||||
let classes = this.props.classes;
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
let pedalboard = this.model.pedalBoard.get();
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
let iter = pedalboard.itemsGenerator();
|
||||
while (true) {
|
||||
let v = iter.next();
|
||||
@@ -285,7 +285,7 @@ export const MidiBindingDialog =
|
||||
size="large">
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
Preset MIDI Bindings
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
|
||||
@@ -57,30 +57,9 @@ export class ControlValue implements Deserializable<ControlValue> {
|
||||
value: number;
|
||||
|
||||
}
|
||||
export class PropertyValue implements Deserializable<PropertyValue> {
|
||||
deserialize(input: any): PropertyValue {
|
||||
this.propertyUri = input.propertyUri;
|
||||
this.value = input.value;
|
||||
return this;
|
||||
}
|
||||
static deserializeArray(input: any[]): PropertyValue[] {
|
||||
let result: PropertyValue[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
result[i] = new PropertyValue().deserialize(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
setValue(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
propertyUri: string = "";
|
||||
value: any = null;
|
||||
|
||||
}
|
||||
|
||||
export class PedalBoardItem implements Deserializable<PedalBoardItem> {
|
||||
deserializePedalBoardItem(input: any): PedalBoardItem {
|
||||
export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
deserializePedalboardItem(input: any): PedalboardItem {
|
||||
this.instanceId = input.instanceId ?? -1;
|
||||
this.uri = input.uri;
|
||||
this.pluginName = input.pluginName;
|
||||
@@ -88,24 +67,24 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
|
||||
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
|
||||
|
||||
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||
this.propertyValues = PropertyValue.deserializeArray(input.propertyValues);
|
||||
this.vstState = input.vstState ?? "";
|
||||
this.lv2State = input.lv2State;
|
||||
return this;
|
||||
}
|
||||
deserialize(input: any): PedalBoardItem {
|
||||
return this.deserializePedalBoardItem(input);
|
||||
deserialize(input: any): PedalboardItem {
|
||||
return this.deserializePedalboardItem(input);
|
||||
}
|
||||
static deserializeArray(input: any): PedalBoardItem[] {
|
||||
let result: PedalBoardItem[] = [];
|
||||
static deserializeArray(input: any): PedalboardItem[] {
|
||||
let result: PedalboardItem[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let inputItem: any = input[i];
|
||||
let uri: string = inputItem.uri as string;
|
||||
let outputItem: PedalBoardItem;
|
||||
let outputItem: PedalboardItem;
|
||||
|
||||
if (uri === SPLIT_PEDALBOARD_ITEM_URI) {
|
||||
outputItem = new PedalBoardSplitItem().deserialize(inputItem);
|
||||
outputItem = new PedalboardSplitItem().deserialize(inputItem);
|
||||
} else {
|
||||
outputItem = new PedalBoardItem().deserialize(inputItem);
|
||||
outputItem = new PedalboardItem().deserialize(inputItem);
|
||||
}
|
||||
result[i] = outputItem;
|
||||
|
||||
@@ -158,17 +137,6 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setPropertyValue(propertyUri: string, value: any): boolean {
|
||||
for (let i = 0; i < this.propertyValues.length; ++i) {
|
||||
let v = this.propertyValues[i];
|
||||
if (v.propertyUri === propertyUri) {
|
||||
if (v.value === value) return false;
|
||||
v.value = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setMidiBinding(midiBinding: MidiBinding): boolean {
|
||||
if (this.midiBindings)
|
||||
{
|
||||
@@ -209,16 +177,16 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
|
||||
}
|
||||
|
||||
|
||||
static EmptyArray: PedalBoardItem[] = [];
|
||||
static EmptyArray: PedalboardItem[] = [];
|
||||
|
||||
instanceId: number = -1;
|
||||
isEnabled: boolean = false;
|
||||
uri: string = "";
|
||||
pluginName?: string;
|
||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||
propertyValues: PropertyValue[] = [];
|
||||
midiBindings: MidiBinding[] = [];
|
||||
vstState: string = "";
|
||||
lv2State: any = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -230,7 +198,7 @@ export enum SplitType {
|
||||
|
||||
|
||||
|
||||
export class PedalBoardSplitItem extends PedalBoardItem {
|
||||
export class PedalboardSplitItem extends PedalboardItem {
|
||||
static PANL_KEY: string = "panL";
|
||||
static PANR_KEY: string = "panR";
|
||||
static VOLL_KEY: string = "volL";
|
||||
@@ -241,15 +209,15 @@ export class PedalBoardSplitItem extends PedalBoardItem {
|
||||
static SELECT_KEY: string = "select";
|
||||
|
||||
|
||||
deserialize(input: any): PedalBoardSplitItem {
|
||||
this.deserializePedalBoardItem(input);
|
||||
this.topChain = PedalBoardItem.deserializeArray(input.topChain);
|
||||
this.bottomChain = PedalBoardItem.deserializeArray(input.bottomChain);
|
||||
deserialize(input: any): PedalboardSplitItem {
|
||||
this.deserializePedalboardItem(input);
|
||||
this.topChain = PedalboardItem.deserializeArray(input.topChain);
|
||||
this.bottomChain = PedalboardItem.deserializeArray(input.bottomChain);
|
||||
|
||||
return this;
|
||||
}
|
||||
getSplitType(): SplitType {
|
||||
let rawValue = this.getControlValue(PedalBoardSplitItem.TYPE_KEY);
|
||||
let rawValue = this.getControlValue(PedalboardSplitItem.TYPE_KEY);
|
||||
|
||||
if (rawValue < 1) return SplitType.Ab;
|
||||
if (rawValue < 2) return SplitType.Mix;
|
||||
@@ -263,43 +231,43 @@ export class PedalBoardSplitItem extends PedalBoardItem {
|
||||
|
||||
}
|
||||
getMixControl(): ControlValue {
|
||||
return this.getControl(PedalBoardSplitItem.MIX_KEY);
|
||||
return this.getControl(PedalboardSplitItem.MIX_KEY);
|
||||
}
|
||||
getMix(): number {
|
||||
return this.getControlValue(PedalBoardSplitItem.MIX_KEY);
|
||||
return this.getControlValue(PedalboardSplitItem.MIX_KEY);
|
||||
}
|
||||
isASelected(): boolean {
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalBoardSplitItem.SELECT_KEY) === 0;
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) === 0;
|
||||
}
|
||||
isBSelected(): boolean {
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalBoardSplitItem.SELECT_KEY) !== 0;
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) !== 0;
|
||||
}
|
||||
|
||||
topChain: PedalBoardItem[] = PedalBoardItem.EmptyArray;
|
||||
bottomChain: PedalBoardItem[] = PedalBoardItem.EmptyArray;
|
||||
topChain: PedalboardItem[] = PedalboardItem.EmptyArray;
|
||||
bottomChain: PedalboardItem[] = PedalboardItem.EmptyArray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
|
||||
deserialize(input: any): PedalBoard {
|
||||
deserialize(input: any): Pedalboard {
|
||||
this.name = input.name;
|
||||
this.items = PedalBoardItem.deserializeArray(input.items);
|
||||
this.items = PedalboardItem.deserializeArray(input.items);
|
||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
clone(): PedalBoard {
|
||||
return new PedalBoard().deserialize(this);
|
||||
clone(): Pedalboard {
|
||||
return new Pedalboard().deserialize(this);
|
||||
}
|
||||
name: string = "";
|
||||
items: PedalBoardItem[] = [];
|
||||
items: PedalboardItem[] = [];
|
||||
nextInstanceId: number = -1;
|
||||
|
||||
*itemsGenerator(): Generator<PedalBoardItem, void, undefined> {
|
||||
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
|
||||
let it = itemGenerator_(this.items);
|
||||
while (true)
|
||||
{
|
||||
@@ -335,7 +303,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
|
||||
}
|
||||
|
||||
maybeGetItem(instanceId: number): PedalBoardItem | null{
|
||||
maybeGetItem(instanceId: number): PedalboardItem | null{
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
@@ -348,7 +316,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getItem(instanceId: number): PedalBoardItem {
|
||||
getItem(instanceId: number): PedalboardItem {
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
@@ -361,7 +329,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
throw new PiPedalArgumentError("Item not found.");
|
||||
}
|
||||
deleteItem_(instanceId: number,items: PedalBoardItem[]): number | null
|
||||
deleteItem_(instanceId: number,items: PedalboardItem[]): number | null
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
@@ -383,7 +351,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
} else {
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalBoardSplitItem;
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
let t = this.deleteItem_(instanceId,splitItem.topChain);
|
||||
if (t != null) return t;
|
||||
|
||||
@@ -395,7 +363,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
return null;
|
||||
}
|
||||
|
||||
canDeleteItem_(instanceId: number,items: PedalBoardItem[]): boolean
|
||||
canDeleteItem_(instanceId: number,items: PedalboardItem[]): boolean
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
@@ -407,7 +375,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalBoardSplitItem;
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this.canDeleteItem_(instanceId,splitItem.topChain))
|
||||
{
|
||||
return true;
|
||||
@@ -436,15 +404,15 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
if (!item) return false;
|
||||
return item.setMidiBinding(midiBinding);
|
||||
}
|
||||
addToStart(item: PedalBoardItem)
|
||||
addToStart(item: PedalboardItem)
|
||||
{
|
||||
this.items.splice(0,0,item);
|
||||
}
|
||||
addToEnd(item: PedalBoardItem)
|
||||
addToEnd(item: PedalboardItem)
|
||||
{
|
||||
this.items.splice(this.items.length,0,item);
|
||||
}
|
||||
static _addRelative(items: PedalBoardItem[],newItem: PedalBoardItem, instanceId: number, addBefore: boolean): boolean
|
||||
static _addRelative(items: PedalboardItem[],newItem: PedalboardItem, instanceId: number, addBefore: boolean): boolean
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
@@ -461,7 +429,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let split = item as PedalBoardSplitItem;
|
||||
let split = item as PedalboardSplitItem;
|
||||
if (this._addRelative(split.topChain,newItem,instanceId,addBefore))
|
||||
{
|
||||
return true;
|
||||
@@ -476,19 +444,19 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
|
||||
}
|
||||
|
||||
addBefore(item: PedalBoardItem, instanceId: number)
|
||||
addBefore(item: PedalboardItem, instanceId: number)
|
||||
{
|
||||
if (item.instanceId === instanceId) return;
|
||||
let result = PedalBoard._addRelative(this.items,item, instanceId, true);
|
||||
let result = Pedalboard._addRelative(this.items,item, instanceId, true);
|
||||
if (!result) {
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
|
||||
}
|
||||
addAfter(item: PedalBoardItem, instanceId: number)
|
||||
addAfter(item: PedalboardItem, instanceId: number)
|
||||
{
|
||||
if (item.instanceId === instanceId) return;
|
||||
let result = PedalBoard._addRelative(this.items,item, instanceId, false);
|
||||
let result = Pedalboard._addRelative(this.items,item, instanceId, false);
|
||||
if (!result) {
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
@@ -520,8 +488,8 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
}
|
||||
}
|
||||
createEmptySplit(): PedalBoardSplitItem {
|
||||
let result: PedalBoardSplitItem = new PedalBoardSplitItem();
|
||||
createEmptySplit(): PedalboardSplitItem {
|
||||
let result: PedalboardSplitItem = new PedalboardSplitItem();
|
||||
result.uri = SPLIT_PEDALBOARD_ITEM_URI;
|
||||
result.instanceId = ++this.nextInstanceId;
|
||||
result.pluginName = "";
|
||||
@@ -529,13 +497,13 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
result.topChain = [ this.createEmptyItem()];
|
||||
result.bottomChain = [ this.createEmptyItem()];
|
||||
result.controlValues = [
|
||||
new ControlValue(PedalBoardSplitItem.TYPE_KEY, 0),
|
||||
new ControlValue(PedalBoardSplitItem.SELECT_KEY,0),
|
||||
new ControlValue(PedalBoardSplitItem.MIX_KEY,0),
|
||||
new ControlValue(PedalBoardSplitItem.PANL_KEY,0),
|
||||
new ControlValue(PedalBoardSplitItem.VOLL_KEY,-3),
|
||||
new ControlValue(PedalBoardSplitItem.PANR_KEY,0),
|
||||
new ControlValue(PedalBoardSplitItem.VOLR_KEY,-3)
|
||||
new ControlValue(PedalboardSplitItem.TYPE_KEY, 0),
|
||||
new ControlValue(PedalboardSplitItem.SELECT_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.MIX_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.PANL_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.VOLL_KEY,-3),
|
||||
new ControlValue(PedalboardSplitItem.PANR_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.VOLR_KEY,-3)
|
||||
];
|
||||
|
||||
|
||||
@@ -543,8 +511,8 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
|
||||
|
||||
}
|
||||
createEmptyItem(): PedalBoardItem {
|
||||
let result: PedalBoardItem = new PedalBoardItem();
|
||||
createEmptyItem(): PedalboardItem {
|
||||
let result: PedalboardItem = new PedalboardItem();
|
||||
result.uri = EMPTY_PEDALBOARD_ITEM_URI;
|
||||
result.instanceId = ++this.nextInstanceId;
|
||||
result.pluginName = "";
|
||||
@@ -552,7 +520,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
|
||||
return result;
|
||||
}
|
||||
setItemEmpty(item: PedalBoardItem)
|
||||
setItemEmpty(item: PedalboardItem)
|
||||
{
|
||||
item.uri = EMPTY_PEDALBOARD_ITEM_URI;
|
||||
item.instanceId = ++this.nextInstanceId;
|
||||
@@ -562,7 +530,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
|
||||
}
|
||||
|
||||
_replaceItem(items: PedalBoardItem[], instanceId: number, newItem: PedalBoardItem): boolean {
|
||||
_replaceItem(items: PedalboardItem[], instanceId: number, newItem: PedalboardItem): boolean {
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
@@ -573,7 +541,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
if (items[i].isSplit())
|
||||
{
|
||||
let splitItem = item as PedalBoardSplitItem;
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this._replaceItem(splitItem.topChain,instanceId,newItem))
|
||||
return true;
|
||||
if (this._replaceItem(splitItem.bottomChain,instanceId,newItem))
|
||||
@@ -585,7 +553,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
return false;
|
||||
}
|
||||
|
||||
replaceItem(instanceId: number, newItem: PedalBoardItem)
|
||||
replaceItem(instanceId: number, newItem: PedalboardItem)
|
||||
{
|
||||
let result = this._replaceItem(this.items,instanceId,newItem);
|
||||
if (!result)
|
||||
@@ -593,7 +561,7 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
}
|
||||
_addItem(items: PedalBoardItem[], newItem: PedalBoardItem, instanceId: number, append: boolean)
|
||||
_addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
@@ -611,27 +579,27 @@ export class PedalBoard implements Deserializable<PedalBoard> {
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalBoardSplitItem;
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this._addItem(splitItem.topChain,newItem,instanceId,append)) return true;
|
||||
if (this._addItem(splitItem.bottomChain,newItem,instanceId,append)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addItem(newItem: PedalBoardItem, instanceId: number, append: boolean): void
|
||||
addItem(newItem: PedalboardItem, instanceId: number, append: boolean): void
|
||||
{
|
||||
this._addItem(this.items,newItem,instanceId,append);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function* itemGenerator_(items: PedalBoardItem[]): Generator<PedalBoardItem, void, undefined> {
|
||||
function* itemGenerator_(items: PedalboardItem[]): Generator<PedalboardItem, void, undefined> {
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
let item = items[i];
|
||||
yield item;
|
||||
if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) {
|
||||
|
||||
let splitItem = item as PedalBoardSplitItem;
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
|
||||
let it = itemGenerator_(splitItem.topChain);
|
||||
while (true) {
|
||||
@@ -36,8 +36,8 @@ import Utility from './Utility'
|
||||
|
||||
|
||||
import {
|
||||
PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType,
|
||||
} from './PedalBoard';
|
||||
Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType,
|
||||
} from './Pedalboard';
|
||||
|
||||
const START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
||||
const END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
||||
@@ -69,7 +69,7 @@ function CalculateConnection(numberOfInputs: number, numberOfOutputs: number)
|
||||
return result;
|
||||
}
|
||||
|
||||
const pedalBoardStyles = (theme: Theme) => createStyles({
|
||||
const pedalboardStyles = (theme: Theme) => createStyles({
|
||||
scrollContainer: {
|
||||
},
|
||||
|
||||
@@ -177,7 +177,7 @@ const pedalBoardStyles = (theme: Theme) => createStyles({
|
||||
|
||||
export type OnSelectHandler = (selectedPedal: number) => void;
|
||||
|
||||
interface PedalBoardProps extends WithStyles<typeof pedalBoardStyles> {
|
||||
interface PedalboardProps extends WithStyles<typeof pedalboardStyles> {
|
||||
theme: Theme;
|
||||
selectedId?: number;
|
||||
onSelectionChanged?: OnSelectHandler;
|
||||
@@ -190,14 +190,14 @@ interface LayoutSize {
|
||||
height: number;
|
||||
}
|
||||
|
||||
type PedalBoardState = {
|
||||
pedalBoard?: PedalBoard;
|
||||
type PedalboardState = {
|
||||
pedalboard?: Pedalboard;
|
||||
};
|
||||
|
||||
const EMPTY_PEDALS: PedalLayout[] = [];
|
||||
|
||||
|
||||
function makeChain(model: PiPedalModel, uiItems?: PedalBoardItem[]): PedalLayout[] {
|
||||
function makeChain(model: PiPedalModel, uiItems?: PedalboardItem[]): PedalLayout[] {
|
||||
let result: PedalLayout[] = [];
|
||||
if (uiItems) {
|
||||
for (let i = 0; i < uiItems.length; ++i) {
|
||||
@@ -219,7 +219,7 @@ class PedalLayout {
|
||||
numberOfInputs: number = 2;
|
||||
numberOfOutputs: number = 2;
|
||||
|
||||
pedalItem?: PedalBoardItem;
|
||||
pedalItem?: PedalboardItem;
|
||||
|
||||
// Split Layout only.
|
||||
topChildren: PedalLayout[] = EMPTY_PEDALS;
|
||||
@@ -245,7 +245,7 @@ class PedalLayout {
|
||||
t.numberOfOutputs = 0;
|
||||
return t;
|
||||
}
|
||||
constructor(model?: PiPedalModel, pedalItem?: PedalBoardItem) {
|
||||
constructor(model?: PiPedalModel, pedalItem?: PedalboardItem) {
|
||||
if (model === undefined && pedalItem === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ class PedalLayout {
|
||||
this.pedalItem = pedalItem;
|
||||
this.uri = pedalItem.uri;
|
||||
if (pedalItem.isSplit()) {
|
||||
let splitter = pedalItem as PedalBoardSplitItem;
|
||||
let splitter = pedalItem as PedalboardSplitItem;
|
||||
|
||||
this.pluginType = PluginType.UtilityPlugin;
|
||||
this.topChildren = makeChain(model, splitter.topChain);
|
||||
@@ -350,9 +350,9 @@ class LayoutParams {
|
||||
}
|
||||
|
||||
|
||||
const PedalBoardView =
|
||||
withStyles(pedalBoardStyles, { withTheme: true })(
|
||||
class extends Component<PedalBoardProps, PedalBoardState>
|
||||
const PedalboardView =
|
||||
withStyles(pedalboardStyles, { withTheme: true })(
|
||||
class extends Component<PedalboardProps, PedalboardState>
|
||||
{
|
||||
model: PiPedalModel;
|
||||
|
||||
@@ -360,15 +360,15 @@ const PedalBoardView =
|
||||
scrollRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
|
||||
constructor(props: PedalBoardProps) {
|
||||
constructor(props: PedalboardProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
if (!props.selectedId) props.selectedId = -1;
|
||||
this.state = {
|
||||
pedalBoard: this.model.pedalBoard.get(),
|
||||
pedalboard: this.model.pedalboard.get(),
|
||||
};
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.frameRef = React.createRef();
|
||||
this.scrollRef = React.createRef();
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
@@ -402,11 +402,11 @@ const PedalBoardView =
|
||||
if (item.isSplitter() && item.pedalItem) {
|
||||
if (item.bounds.contains(clientX, clientY)) {
|
||||
if (clientX < item.bounds.x + CELL_WIDTH / 2) {
|
||||
this.model.movePedalBoardItemBefore(instanceId, item.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemBefore(instanceId, item.pedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
} else if (clientX > item.bounds.right - CELL_WIDTH / 2) {
|
||||
this.model.movePedalBoardItemAfter(instanceId, item.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemAfter(instanceId, item.pedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
|
||||
@@ -420,7 +420,7 @@ const PedalBoardView =
|
||||
if (clientX < item.topChildren[0].bounds.x) {
|
||||
let topPedalItem = item.topChildren[0].pedalItem;
|
||||
if (topPedalItem) {
|
||||
this.model.movePedalBoardItemBefore(instanceId, topPedalItem.instanceId);
|
||||
this.model.movePedalboardItemBefore(instanceId, topPedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
}
|
||||
@@ -428,7 +428,7 @@ const PedalBoardView =
|
||||
let lastTop = item.topChildren[item.topChildren.length - 1];
|
||||
if (clientX >= lastTop.bounds.right && clientX < item.bounds.right - CELL_WIDTH / 2) {
|
||||
if (lastTop.pedalItem) {
|
||||
this.model.movePedalBoardItemAfter(instanceId, lastTop.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemAfter(instanceId, lastTop.pedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
}
|
||||
@@ -441,7 +441,7 @@ const PedalBoardView =
|
||||
if (clientX < item.bottomChildren[0].bounds.x) {
|
||||
let bottomPedalItem = item.bottomChildren[0].pedalItem;
|
||||
if (bottomPedalItem) {
|
||||
this.model.movePedalBoardItemBefore(instanceId, bottomPedalItem.instanceId);
|
||||
this.model.movePedalboardItemBefore(instanceId, bottomPedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
}
|
||||
@@ -450,7 +450,7 @@ const PedalBoardView =
|
||||
if (clientX >= lastBottom.bounds.right && clientX < item.bounds.right-CELL_WIDTH/2)
|
||||
{
|
||||
if (lastBottom.pedalItem) {
|
||||
this.model.movePedalBoardItemAfter(instanceId, lastBottom.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemAfter(instanceId, lastBottom.pedalItem.instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
}
|
||||
@@ -461,11 +461,11 @@ const PedalBoardView =
|
||||
|
||||
} else if (item.bounds.contains(clientX, clientY)) {
|
||||
if (item.isStart()) {
|
||||
this.model.movePedalBoardItemToStart(instanceId);
|
||||
this.model.movePedalboardItemToStart(instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
} else if (item.isEnd()) {
|
||||
this.model.movePedalBoardItemToEnd(instanceId);
|
||||
this.model.movePedalboardItemToEnd(instanceId);
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
} else {
|
||||
@@ -473,11 +473,11 @@ const PedalBoardView =
|
||||
{
|
||||
let margin = (CELL_WIDTH - FRAME_SIZE) / 2;
|
||||
if (clientX < item.bounds.x + margin) {
|
||||
this.model.movePedalBoardItemBefore(instanceId, item.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemBefore(instanceId, item.pedalItem.instanceId);
|
||||
} else if (clientX > item.bounds.right - margin) {
|
||||
this.model.movePedalBoardItemAfter(instanceId, item.pedalItem.instanceId);
|
||||
this.model.movePedalboardItemAfter(instanceId, item.pedalItem.instanceId);
|
||||
} else {
|
||||
this.model.movePedalBoardItem(instanceId, item.pedalItem.instanceId);
|
||||
this.model.movePedalboardItem(instanceId, item.pedalItem.instanceId);
|
||||
}
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
@@ -487,23 +487,23 @@ const PedalBoardView =
|
||||
}
|
||||
}
|
||||
// delete the plugin.
|
||||
let newId = this.model.setPedalBoardItemEmpty(instanceId);
|
||||
let newId = this.model.setPedalboardItemEmpty(instanceId);
|
||||
this.setSelection(newId);
|
||||
|
||||
}
|
||||
|
||||
onPedalBoardChanged(value?: PedalBoard) {
|
||||
this.setState({ pedalBoard: value });
|
||||
onPedalboardChanged(value?: Pedalboard) {
|
||||
this.setState({ pedalboard: value });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scrollRef.current!.addEventListener("touchstart",this.handleTouchStart, {passive: false});
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.scrollRef.current!.removeEventListener("touchstart",this.handleTouchStart);
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
|
||||
offsetLayout_(layoutItems: PedalLayout[], offset: number): void {
|
||||
@@ -523,7 +523,7 @@ const PedalBoardView =
|
||||
if (layoutItem.pedalItem === undefined) {
|
||||
throw new Error("Invalid splitter");
|
||||
}
|
||||
let split = layoutItem.pedalItem as PedalBoardSplitItem;
|
||||
let split = layoutItem.pedalItem as PedalboardSplitItem;
|
||||
if (split.getSplitType() === SplitType.Ab) {
|
||||
if (split.isASelected()) {
|
||||
return "img/fx_split_a.svg";
|
||||
@@ -706,7 +706,7 @@ const PedalBoardView =
|
||||
let yTop = item.topConnectorY;
|
||||
let yBottom = item.bottomConnectorY;
|
||||
//let isStereo = item.stereoOutput;
|
||||
let split = item.pedalItem as PedalBoardSplitItem;
|
||||
let split = item.pedalItem as PedalboardSplitItem;
|
||||
|
||||
let topEnabled = enabled && split.isASelected();
|
||||
let bottomEnabled = enabled && split.isBSelected();
|
||||
@@ -889,7 +889,7 @@ const PedalBoardView =
|
||||
// actually not here anymore. :-/ It has a reactive definition in MainPage.tsx now.
|
||||
while (el)
|
||||
{
|
||||
if (el.id === "pedalBoardScroll")
|
||||
if (el.id === "pedalboardScroll")
|
||||
{
|
||||
return el as HTMLDivElement;
|
||||
}
|
||||
@@ -930,7 +930,7 @@ const PedalBoardView =
|
||||
for (let i = 0; i < length; ++i) {
|
||||
let item = layoutChain[i];
|
||||
if (item.isSplitter()) {
|
||||
let splitter = item.pedalItem as PedalBoardSplitItem;
|
||||
let splitter = item.pedalItem as PedalboardSplitItem;
|
||||
this.renderSplitConnectors(output, item, enabled, i === length - 1 && shortSplitOutput,);
|
||||
this.renderConnectors(output, item.topChildren, enabled && splitter.isASelected(), false);
|
||||
this.renderConnectors(output, item.bottomChildren, enabled && splitter.isBSelected(), false);
|
||||
@@ -942,15 +942,14 @@ const PedalBoardView =
|
||||
}
|
||||
}
|
||||
renderConnectorFrame(layoutChain: PedalLayout[], layoutSize: LayoutSize): ReactNode {
|
||||
let output: ReactNode[] = [];
|
||||
this.renderConnectors(output, layoutChain, true, false);
|
||||
|
||||
let outputs: ReactNode[] = [];
|
||||
this.renderConnectors(outputs, layoutChain, true, false);
|
||||
return (
|
||||
<svg width={layoutSize.width} height={layoutSize.height}
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
|
||||
<g fill="none">
|
||||
{
|
||||
output
|
||||
outputs
|
||||
}
|
||||
</g>
|
||||
|
||||
@@ -1065,7 +1064,7 @@ const PedalBoardView =
|
||||
let topInputs = item.topChildren[0].numberOfInputs;
|
||||
let bottomInputs = item.bottomChildren[0].numberOfInputs;
|
||||
|
||||
let splitItem = item.pedalItem as PedalBoardSplitItem;
|
||||
let splitItem = item.pedalItem as PedalboardSplitItem;
|
||||
if (splitItem.getSplitType() !== SplitType.Lr)
|
||||
{
|
||||
item.numberOfInputs = CalculateConnection(item.numberOfInputs, Math.max(topInputs,bottomInputs));
|
||||
@@ -1102,7 +1101,7 @@ const PedalBoardView =
|
||||
for (let i = 0; i < layoutChain.length; ++i) {
|
||||
let item = layoutChain[i];
|
||||
if (item.isSplitter()) {
|
||||
let splitter = item.pedalItem as PedalBoardSplitItem;
|
||||
let splitter = item.pedalItem as PedalboardSplitItem;
|
||||
item.numberOfInputs = numberOfInputs;
|
||||
|
||||
let chainInputs = numberOfInputs;
|
||||
@@ -1156,7 +1155,7 @@ const PedalBoardView =
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
let layoutChain = makeChain(this.model, this.state.pedalBoard?.items);
|
||||
let layoutChain = makeChain(this.model, this.state.pedalboard?.items);
|
||||
let start = PedalLayout.Start();
|
||||
let end = PedalLayout.End();
|
||||
if (layoutChain.length !== 0)
|
||||
@@ -1187,4 +1186,4 @@ const PedalBoardView =
|
||||
}
|
||||
);
|
||||
|
||||
export default PedalBoardView
|
||||
export default PedalboardView
|
||||
+423
-372
File diff suppressed because it is too large
Load Diff
@@ -23,10 +23,10 @@ import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { UiPlugin, UiControl, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin';
|
||||
import { UiPlugin, UiControl, PiPedalFileProperty} from './Lv2Plugin';
|
||||
import {
|
||||
PedalBoard, PedalBoardItem, ControlValue,PropertyValue
|
||||
} from './PedalBoard';
|
||||
Pedalboard, PedalboardItem, ControlValue
|
||||
} from './Pedalboard';
|
||||
import PluginControl from './PluginControl';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import VuMeter from './VuMeter';
|
||||
@@ -36,6 +36,7 @@ import Typography from '@mui/material/Typography';
|
||||
import FullScreenIME from './FullScreenIME';
|
||||
import FilePropertyControl from './FilePropertyControl';
|
||||
import FilePropertyDialog from './FilePropertyDialog';
|
||||
import JsonAtom from './JsonAtom';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 110 };
|
||||
@@ -188,7 +189,7 @@ export interface ControlViewCustomization {
|
||||
export interface PluginControlViewProps extends WithStyles<typeof styles> {
|
||||
theme: Theme;
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
customization?: ControlViewCustomization;
|
||||
customizationId?: number;
|
||||
}
|
||||
@@ -224,35 +225,32 @@ const PluginControlView =
|
||||
dialogFileValue: ""
|
||||
|
||||
}
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onControlValueChanged = this.onControlValueChanged.bind(this);
|
||||
this.onPreviewChange = this.onPreviewChange.bind(this);
|
||||
}
|
||||
|
||||
|
||||
onPreviewChange(key: string, value: number): void {
|
||||
this.model.previewPedalBoardValue(this.props.instanceId, key, value);
|
||||
this.model.previewPedalboardValue(this.props.instanceId, key, value);
|
||||
}
|
||||
|
||||
onControlValueChanged(key: string, value: number): void {
|
||||
this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
|
||||
}
|
||||
onPropertyValueChanged(propertyUri: string, value: any): void {
|
||||
this.model.setPedalBoardPropertyValue(this.props.instanceId, propertyUri, value);
|
||||
this.model.setPedalboardControl(this.props.instanceId, key, value);
|
||||
}
|
||||
|
||||
onPedalBoardChanged(value?: PedalBoard) {
|
||||
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId);
|
||||
//this.setState({ pedalBoardItem: item });
|
||||
onPedalboardChanged(value?: Pedalboard) {
|
||||
//let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId);
|
||||
//this.setState({ pedalboardItem: item });
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
@@ -285,22 +283,10 @@ const PluginControlView =
|
||||
});
|
||||
}
|
||||
|
||||
makeFilePropertyUI(fileProperty: PiPedalFileProperty, propertyValues: PropertyValue[]): ReactNode {
|
||||
let propertyValue: PropertyValue | undefined = undefined;
|
||||
for (let i = 0; i < propertyValues.length; ++i) {
|
||||
if (propertyValues[i].propertyUri === fileProperty.patchProperty) {
|
||||
propertyValue = propertyValues[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!propertyValue) {
|
||||
propertyValue = new PropertyValue();
|
||||
propertyValue.value = fileProperty.defaultFile;
|
||||
propertyValue.propertyUri = fileProperty.patchProperty;
|
||||
}
|
||||
makeFilePropertyUI(fileProperty: PiPedalFileProperty): ReactNode {
|
||||
return ((
|
||||
|
||||
<FilePropertyControl instanceId={this.props.instanceId} value={propertyValue.value}
|
||||
<FilePropertyControl instanceId={this.props.instanceId}
|
||||
fileProperty={fileProperty}
|
||||
onFileClick={(fileProperty,selectedFile) => {
|
||||
this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile});
|
||||
@@ -333,7 +319,7 @@ const PluginControlView =
|
||||
|
||||
}
|
||||
|
||||
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[],propertyValues: PropertyValue[]): ControlNodes {
|
||||
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes {
|
||||
let result: ControlNodes = [];
|
||||
|
||||
for (let i = 0; i < plugin.controls.length; ++i) {
|
||||
@@ -350,7 +336,7 @@ const PluginControlView =
|
||||
pluginControl = plugin.controls[i];
|
||||
if (!pluginControl.not_on_gui) {
|
||||
groupControls.push(
|
||||
this.makeStandardControl(pluginControl, controlValues)
|
||||
this.makeStandardControl(pluginControl,controlValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -359,7 +345,7 @@ const PluginControlView =
|
||||
)
|
||||
} else {
|
||||
result.push(
|
||||
this.makeStandardControl(pluginControl, controlValues)
|
||||
this.makeStandardControl(pluginControl,controlValues)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -367,7 +353,7 @@ const PluginControlView =
|
||||
for (let i = 0; i < plugin.fileProperties.length; ++i) {
|
||||
let fileProperty = plugin.fileProperties[i];
|
||||
result.push(
|
||||
this.makeFilePropertyUI(fileProperty, propertyValues)
|
||||
this.makeFilePropertyUI(fileProperty)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
@@ -383,7 +369,7 @@ const PluginControlView =
|
||||
}
|
||||
|
||||
onImeValueChange(key: string, value: number) {
|
||||
this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
|
||||
this.model.setPedalboardControl(this.props.instanceId, key, value);
|
||||
this.onImeClose();
|
||||
}
|
||||
onImeClose() {
|
||||
@@ -427,7 +413,7 @@ const PluginControlView =
|
||||
result.push((
|
||||
<div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div className={classes.portGroupTitle}>
|
||||
<Typography variant="caption">{controlGroup.name}</Typography>
|
||||
<Typography noWrap variant="caption">{controlGroup.name}</Typography>
|
||||
</div>
|
||||
<div className={classes.portGroupControls} >
|
||||
{
|
||||
@@ -452,16 +438,15 @@ const PluginControlView =
|
||||
|
||||
render(): ReactNode {
|
||||
let classes = this.props.classes;
|
||||
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId);
|
||||
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
|
||||
|
||||
if (!pedalBoardItem)
|
||||
if (!pedalboardItem)
|
||||
return (<div className={classes.frame} ></div>);
|
||||
|
||||
let controlValues = pedalBoardItem.controlValues;
|
||||
let propertyValues = pedalBoardItem.propertyValues;
|
||||
let controlValues = pedalboardItem.controlValues;
|
||||
|
||||
|
||||
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri));
|
||||
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri));
|
||||
|
||||
|
||||
controlValues = this.filterNotOnGui(controlValues, plugin);
|
||||
@@ -472,7 +457,7 @@ const PluginControlView =
|
||||
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||
let controlNodes: ControlNodes;
|
||||
|
||||
controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues);
|
||||
controlNodes = this.getStandardControlNodes(plugin, controlValues);
|
||||
|
||||
if (this.props.customization) {
|
||||
// allow wrapper class to insert/remove/rebuild controls.
|
||||
@@ -484,10 +469,10 @@ const PluginControlView =
|
||||
return (
|
||||
<div className={classes.frame}>
|
||||
<div className={classes.vuMeterL}>
|
||||
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} />
|
||||
<VuMeter display="input" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={vuMeterRClass}>
|
||||
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} />
|
||||
<VuMeter display="output" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={gridClass} >
|
||||
{
|
||||
@@ -503,9 +488,21 @@ const PluginControlView =
|
||||
<FilePropertyDialog open={this.state.showFileDialog}
|
||||
fileProperty={this.state.dialogFileProperty}
|
||||
selectedFile={this.state.dialogFileValue}
|
||||
onClose={()=> { this.setState({ showFileDialog: false});}}
|
||||
onCancel={()=> { this.setState({ showFileDialog: false});}}
|
||||
onOk={(fileProperty,selectedFile)=> {
|
||||
this.model.setPedalBoardPropertyValue(this.props.instanceId,fileProperty.patchProperty,selectedFile)
|
||||
|
||||
this.model.setPatchProperty(
|
||||
this.props.instanceId,
|
||||
fileProperty.patchProperty,
|
||||
JsonAtom.Path(selectedFile)
|
||||
)
|
||||
.then( () => {
|
||||
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
this.model.showAlert("setPatchProperty failed: " +error);
|
||||
});
|
||||
this.setState({ showFileDialog: false});}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -259,7 +259,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
||||
<img src="img/ic_pluginpreset2.svg" className={classes.itemIcon} alt="" />
|
||||
</div>
|
||||
<div className={classes.itemLabel}>
|
||||
<Typography>
|
||||
<Typography> noWrap
|
||||
{presetEntry.label}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -312,8 +312,8 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
||||
this.setState({ renameOpen: false });
|
||||
}
|
||||
getPluginUri() : string {
|
||||
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId);
|
||||
return pedalBoardItem.uri;
|
||||
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
|
||||
return pedalboardItem.uri;
|
||||
}
|
||||
handleCopy() {
|
||||
let item = this.props.presets.getItem(this.state.selectedItem);
|
||||
@@ -359,7 +359,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
{title}
|
||||
</Typography>
|
||||
<IconButton color="inherit" onClick={(e) => this.showActionBar(true)} >
|
||||
|
||||
@@ -497,6 +497,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
|
||||
}
|
||||
|
||||
onOnboardingContinue() {
|
||||
this.model.setOnboarding(false);
|
||||
this.props.onClose();
|
||||
}
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let isConfigValid = this.state.jackConfiguration.isValid;
|
||||
@@ -544,40 +548,41 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
}}
|
||||
>
|
||||
<div >
|
||||
{this.props.onboarding ?
|
||||
{this.props.onboarding &&
|
||||
(
|
||||
<div>
|
||||
<Typography display="block" variant="caption" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
|
||||
<Typography display="block" variant="body1" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
|
||||
Select and configure an audio device. You may optionally configure MIDI inputs, and set up a Wi-Fi Direct Hotspot now as well.
|
||||
</Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
|
||||
<Typography display="block" variant="body1" color="textSecondary" style={{paddingLeft: 24,paddingBottom: 8 }}>
|
||||
Access and modify these settings later by selecting the <i>Settings</i> menu item on the main menu.
|
||||
</Typography>
|
||||
</div>
|
||||
):
|
||||
(
|
||||
<div>
|
||||
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
|
||||
STATUS
|
||||
</Typography>
|
||||
{(!isConfigValid) ?
|
||||
(
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Governor: </Typography>
|
||||
</div>
|
||||
) :
|
||||
(
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
{JackHostStatus.getDisplayView("", this.state.jackStatus)}
|
||||
{JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<Divider />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
|
||||
STATUS
|
||||
</Typography>
|
||||
{(!isConfigValid) ?
|
||||
(
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
|
||||
{(!this.props.onboarding) && (
|
||||
<Typography display="block" variant="caption" color="textSecondary">Governor: </Typography>
|
||||
)}
|
||||
</div>
|
||||
) :
|
||||
(
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
{JackHostStatus.getDisplayView("", this.state.jackStatus)}
|
||||
{ (!this.props.onboarding) && JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{this.state.jackConfiguration.errorState !== "" &&
|
||||
(
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
@@ -782,7 +787,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
<Divider/>
|
||||
<div style={{display: "grid", placeContent: "end", maxWidth: 550, marginLeft: 16,marginRight: 16,
|
||||
marginTop: 16,marginBottom: 16}}>
|
||||
<Button variant="outlined" disabled={this.state.continueDisabled} style={{width: 120}}>Continue</Button>
|
||||
<Button variant="outlined" onClick={()=> {this.onOnboardingContinue()}} disabled={this.state.continueDisabled} style={{width: 120}}>Continue</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -25,8 +25,8 @@ import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { UiPlugin } from './Lv2Plugin';
|
||||
import {
|
||||
PedalBoard, PedalBoardSplitItem, ControlValue,
|
||||
} from './PedalBoard';
|
||||
Pedalboard, PedalboardSplitItem, ControlValue,
|
||||
} from './Pedalboard';
|
||||
import PluginControl from './PluginControl';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import VuMeter from './VuMeter';
|
||||
@@ -97,7 +97,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
interface SplitControlViewProps extends WithStyles<typeof styles> {
|
||||
theme: Theme;
|
||||
instanceId: number;
|
||||
item: PedalBoardSplitItem;
|
||||
item: PedalboardSplitItem;
|
||||
}
|
||||
type SplitControlViewState = {
|
||||
landscapeGrid: boolean;
|
||||
@@ -117,32 +117,32 @@ const SplitControlView =
|
||||
landscapeGrid: false
|
||||
|
||||
}
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onValueChanged = this.onValueChanged.bind(this);
|
||||
this.onPreviewChange = this.onPreviewChange.bind(this);
|
||||
}
|
||||
|
||||
|
||||
onPreviewChange(key: string, value: number): void {
|
||||
this.model.previewPedalBoardValue(this.props.instanceId, key, value);
|
||||
this.model.previewPedalboardValue(this.props.instanceId, key, value);
|
||||
}
|
||||
|
||||
onValueChanged(key: string, value: number): void {
|
||||
this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
|
||||
this.model.setPedalboardControl(this.props.instanceId, key, value);
|
||||
}
|
||||
|
||||
onPedalBoardChanged(value?: PedalBoard) {
|
||||
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId);
|
||||
//this.setState({ pedalBoardItem: item });
|
||||
onPedalboardChanged(value?: Pedalboard) {
|
||||
//let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId);
|
||||
//this.setState({ pedalboardItem: item });
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
@@ -162,29 +162,29 @@ const SplitControlView =
|
||||
|
||||
render(): ReactNode {
|
||||
let classes = this.props.classes;
|
||||
let pedalBoardItem = this.model.pedalBoard.get().getItem(this.props.instanceId);
|
||||
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
|
||||
|
||||
if (!pedalBoardItem)
|
||||
if (!pedalboardItem)
|
||||
return (<div className={classes.frame}></div>);
|
||||
|
||||
|
||||
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
|
||||
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||
let typeValue = pedalBoardItem.getControl(PedalBoardSplitItem.TYPE_KEY);
|
||||
let mixValue = pedalBoardItem.getControl(PedalBoardSplitItem.MIX_KEY);
|
||||
let selectValue = pedalBoardItem.getControl(PedalBoardSplitItem.SELECT_KEY);
|
||||
let panLValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANL_KEY);
|
||||
let volumeLValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLL_KEY);
|
||||
let panRValue = pedalBoardItem.getControl(PedalBoardSplitItem.PANR_KEY);
|
||||
let volumeRValue = pedalBoardItem.getControl(PedalBoardSplitItem.VOLR_KEY);
|
||||
let typeValue = pedalboardItem.getControl(PedalboardSplitItem.TYPE_KEY);
|
||||
let mixValue = pedalboardItem.getControl(PedalboardSplitItem.MIX_KEY);
|
||||
let selectValue = pedalboardItem.getControl(PedalboardSplitItem.SELECT_KEY);
|
||||
let panLValue = pedalboardItem.getControl(PedalboardSplitItem.PANL_KEY);
|
||||
let volumeLValue = pedalboardItem.getControl(PedalboardSplitItem.VOLL_KEY);
|
||||
let panRValue = pedalboardItem.getControl(PedalboardSplitItem.PANR_KEY);
|
||||
let volumeRValue = pedalboardItem.getControl(PedalboardSplitItem.VOLR_KEY);
|
||||
|
||||
return (
|
||||
<div className={classes.frame}>
|
||||
<div className={classes.vuMeterL}>
|
||||
<VuMeter display="input" instanceId={pedalBoardItem.instanceId} />
|
||||
<VuMeter display="input" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={vuMeterRClass}>
|
||||
<VuMeter display="output" instanceId={pedalBoardItem.instanceId} />
|
||||
<VuMeter display="output" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={gridClass} >
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import {UiControl,ScalePoint} from './Lv2Plugin';
|
||||
import {PedalBoardSplitItem} from './PedalBoard';
|
||||
import {PedalboardSplitItem} from './Pedalboard';
|
||||
import Units from './Units'
|
||||
|
||||
|
||||
|
||||
export const SplitTypeControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.TYPE_KEY,
|
||||
symbol: PedalboardSplitItem.TYPE_KEY,
|
||||
name: "Type",
|
||||
index: 0,
|
||||
min_value: 0,
|
||||
@@ -47,7 +47,7 @@ export const SplitTypeControl: UiControl = new UiControl().deserialize({
|
||||
});
|
||||
|
||||
export const SplitAbControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.SELECT_KEY,
|
||||
symbol: PedalboardSplitItem.SELECT_KEY,
|
||||
name: "Select",
|
||||
index: 1,
|
||||
min_value: 0,
|
||||
@@ -70,7 +70,7 @@ export const SplitAbControl: UiControl = new UiControl().deserialize({
|
||||
|
||||
|
||||
export const SplitMixControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.MIX_KEY,
|
||||
symbol: PedalboardSplitItem.MIX_KEY,
|
||||
name: "Mix",
|
||||
index: 2,
|
||||
min_value: -1,
|
||||
@@ -87,7 +87,7 @@ export const SplitMixControl: UiControl = new UiControl().deserialize({
|
||||
});
|
||||
|
||||
export const SplitPanLeftControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.PANL_KEY,
|
||||
symbol: PedalboardSplitItem.PANL_KEY,
|
||||
name: "Pan Top",
|
||||
index: 3,
|
||||
min_value: -1,
|
||||
@@ -104,7 +104,7 @@ export const SplitPanLeftControl: UiControl = new UiControl().deserialize({
|
||||
});
|
||||
|
||||
export const SplitVolLeftControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.VOLL_KEY,
|
||||
symbol: PedalboardSplitItem.VOLL_KEY,
|
||||
name: "Vol Top",
|
||||
index: 4,
|
||||
min_value: -60,
|
||||
@@ -128,7 +128,7 @@ export const SplitVolLeftControl: UiControl = new UiControl().deserialize({
|
||||
|
||||
|
||||
export const SplitPanRightControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.PANR_KEY,
|
||||
symbol: PedalboardSplitItem.PANR_KEY,
|
||||
name: "Pan Bottom",
|
||||
index: 5,
|
||||
min_value: -1,
|
||||
@@ -145,7 +145,7 @@ export const SplitPanRightControl: UiControl = new UiControl().deserialize({
|
||||
});
|
||||
|
||||
export const SplitVolRightControl: UiControl = new UiControl().deserialize({
|
||||
symbol: PedalBoardSplitItem.VOLR_KEY,
|
||||
symbol: PedalboardSplitItem.VOLR_KEY,
|
||||
name: "Vol Bottom",
|
||||
index: 6,
|
||||
min_value: -60,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import StringBuilder from './StringBuilder';
|
||||
|
||||
class SvgPathBuilder {
|
||||
_sb: StringBuilder = new StringBuilder();
|
||||
private _sb: StringBuilder = new StringBuilder();
|
||||
|
||||
moveTo(x: number, y: number): SvgPathBuilder
|
||||
{
|
||||
@@ -37,7 +37,7 @@ class SvgPathBuilder {
|
||||
this._sb.append('C').append(x1).append(',').append(y1)
|
||||
.append(' ').append(x2).append(',').append(y2)
|
||||
.append(' ').append(x).append(',').append(y).append(' ');
|
||||
|
||||
return this;
|
||||
}
|
||||
closePath(): SvgPathBuilder {
|
||||
this._sb.append("Z ");
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobCabSimProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobCabSimState {
|
||||
@@ -83,8 +83,8 @@ const ToobCabSimView =
|
||||
class ToobCabSimViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-cab-sim";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobCabSimView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobCabSimView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,14 +30,15 @@ import Utility from './Utility';
|
||||
import SvgPathBuilder from './SvgPathBuilder';
|
||||
|
||||
|
||||
|
||||
const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector";
|
||||
|
||||
const PLOT_WIDTH = StandardItemSize.width*2-16;
|
||||
const PLOT_HEIGHT = StandardItemSize.height-12;
|
||||
const PLOT_WIDTH = StandardItemSize.width * 2 - 16;
|
||||
const PLOT_HEIGHT = StandardItemSize.height - 12;
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444",
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444",
|
||||
borderRadius: 6,
|
||||
marginTop: 12, marginLeft: 8, marginRight: 8,
|
||||
boxShadow: "1px 4px 8px #000 inset"
|
||||
@@ -73,33 +74,31 @@ const ToobFrequencyResponseView =
|
||||
};
|
||||
|
||||
this.pathRef = React.createRef();
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
}
|
||||
|
||||
onStateChanged()
|
||||
{
|
||||
if (this.model.state.get() === State.Ready)
|
||||
{
|
||||
onStateChanged() {
|
||||
if (this.model.state.get() === State.Ready) {
|
||||
this.requestDeferred = false;
|
||||
this.requestOutstanding = false;
|
||||
this.updateAllWaveShapes(); // after a reconnect.
|
||||
}
|
||||
}
|
||||
onPedalBoardChanged()
|
||||
{
|
||||
onPedalboardChanged() {
|
||||
this.updateAllWaveShapes();
|
||||
}
|
||||
componentDidMount()
|
||||
{
|
||||
private mounted: boolean = false;
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
this.updateAllWaveShapes();
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -113,24 +112,23 @@ const ToobFrequencyResponseView =
|
||||
dbTickSpacingOpt: number = 10;
|
||||
|
||||
|
||||
MIN_DB_AF: number = Math.pow(10,-192/20);
|
||||
MIN_DB_AF: number = Math.pow(10, -192 / 20);
|
||||
fMin: number = 30;
|
||||
fMax: number = 20000;
|
||||
logMin: number = Math.log(this.fMin);
|
||||
logMax: number = Math.log(this.fMax);
|
||||
|
||||
|
||||
// Size of the SVG element.
|
||||
xMin: number = 0;
|
||||
xMax: number = PLOT_WIDTH+4;
|
||||
xMax: number = PLOT_WIDTH + 4;
|
||||
yMin: number = 0;
|
||||
yMax: number = PLOT_HEIGHT;
|
||||
dbTickSpacing: number = this.dbTickSpacingOpt;
|
||||
|
||||
toX(frequency: number): number
|
||||
{
|
||||
|
||||
toX(frequency: number): number {
|
||||
var logV = Math.log(frequency);
|
||||
return (this.xMax-this.xMin) * (logV-this.logMin)/(this.logMax-this.logMin) + this.xMin;
|
||||
|
||||
return (this.xMax - this.xMin) * (logV - this.logMin) / (this.logMax - this.logMin) + this.xMin;
|
||||
|
||||
}
|
||||
toY(value: number): number {
|
||||
value = Math.abs(value);
|
||||
@@ -139,24 +137,23 @@ const ToobFrequencyResponseView =
|
||||
if (value < this.MIN_DB_AF) {
|
||||
db = -192.0;
|
||||
} else {
|
||||
db = 20*Math.log10(value);
|
||||
db = 20 * Math.log10(value);
|
||||
}
|
||||
var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin) + this.yMin;
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
}
|
||||
|
||||
onFrequencyResponseUpdated(data: number[]) {
|
||||
if (!this.mounted) return;
|
||||
let pathBuilder = new SvgPathBuilder();
|
||||
if (data.length > 2)
|
||||
{
|
||||
pathBuilder.moveTo(this.toX(data[0]),this.toY(data[1]))
|
||||
for (let i = 2; i < data.length; i += 2)
|
||||
{
|
||||
pathBuilder.lineTo(this.toX(data[i]),this.toY(data[i+1]));
|
||||
if (data.length > 2) {
|
||||
pathBuilder.moveTo(this.toX(data[0]), this.toY(data[1]))
|
||||
for (let i = 2; i < data.length; i += 2) {
|
||||
pathBuilder.lineTo(this.toX(data[i]), this.toY(data[i + 1]));
|
||||
}
|
||||
}
|
||||
this.currentPath = pathBuilder.toString();
|
||||
this.setState({path: this.currentPath});
|
||||
this.setState({ path: this.currentPath });
|
||||
|
||||
}
|
||||
|
||||
@@ -166,21 +163,23 @@ const ToobFrequencyResponseView =
|
||||
return;
|
||||
}
|
||||
this.requestOutstanding = true;
|
||||
this.model.getLv2Parameter<number[]>(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI)
|
||||
.then((data) => {
|
||||
this.onFrequencyResponseUpdated(data);
|
||||
if (this.requestDeferred) {
|
||||
Utility.delay(10) // take breath
|
||||
.then(
|
||||
() => {
|
||||
this.requestOutstanding = false;
|
||||
this.requestDeferred = false;
|
||||
this.updateAllWaveShapes();
|
||||
}
|
||||
this.model.getPatchProperty<any>(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI)
|
||||
.then((json) => {
|
||||
if (json && json.otype_ === "Vector" && json.vtype_ === "Float") {
|
||||
this.onFrequencyResponseUpdated(json.value as number[]);
|
||||
if (this.requestDeferred) {
|
||||
Utility.delay(10) // take breath
|
||||
.then(
|
||||
() => {
|
||||
this.requestOutstanding = false;
|
||||
this.requestDeferred = false;
|
||||
this.updateAllWaveShapes();
|
||||
}
|
||||
|
||||
);
|
||||
} else {
|
||||
this.requestOutstanding = false;
|
||||
);
|
||||
} else {
|
||||
this.requestOutstanding = false;
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
// assume the connection was lost. We'll get saved by a reconnect.
|
||||
@@ -201,42 +200,36 @@ const ToobFrequencyResponseView =
|
||||
grid(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
|
||||
for (var db = Math.ceil(this.dbMax/this.dbTickSpacing)*this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing )
|
||||
{
|
||||
var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin);
|
||||
if (db === 0)
|
||||
{
|
||||
result.push(
|
||||
this.majorGridLine(this.xMin,y,this.xMax,y)
|
||||
);
|
||||
|
||||
for (var db = Math.ceil(this.dbMax / this.dbTickSpacing) * this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing) {
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin);
|
||||
if (db === 0) {
|
||||
result.push(
|
||||
this.majorGridLine(this.xMin, y, this.xMax, y)
|
||||
);
|
||||
|
||||
} else {
|
||||
result.push(
|
||||
this.gridLine(this.xMin,y,this.xMax,y)
|
||||
);
|
||||
result.push(
|
||||
this.gridLine(this.xMin, y, this.xMax, y)
|
||||
);
|
||||
}
|
||||
}
|
||||
let decade0 = Math.pow(10,Math.floor(Math.log10(this.fMin)));
|
||||
for (var decade = decade0; decade < this.fMax; decade *= 10)
|
||||
{
|
||||
for (var i = 1; i <= 10; ++i)
|
||||
{
|
||||
var f = decade*i;
|
||||
if (f > this.fMin && f < this.fMax)
|
||||
{
|
||||
let decade0 = Math.pow(10, Math.floor(Math.log10(this.fMin)));
|
||||
for (var decade = decade0; decade < this.fMax; decade *= 10) {
|
||||
for (var i = 1; i <= 10; ++i) {
|
||||
var f = decade * i;
|
||||
if (f > this.fMin && f < this.fMax) {
|
||||
var x = this.toX(f);
|
||||
if (i === 10)
|
||||
{
|
||||
result.push(this.majorGridLine(x,this.yMin,x,this.yMax));
|
||||
if (i === 10) {
|
||||
result.push(this.majorGridLine(x, this.yMin, x, this.yMax));
|
||||
|
||||
} else {
|
||||
result.push(this.gridLine(x,this.yMin,x,this.yMax));
|
||||
result.push(this.gridLine(x, this.yMin, x, this.yMax));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
dbMin: number = 5;
|
||||
@@ -244,20 +237,20 @@ const ToobFrequencyResponseView =
|
||||
|
||||
render() {
|
||||
// deliberately reversed to flip up and down.
|
||||
this.dbMax = this.props.minDb ?? this.dbMinDefault;
|
||||
this.dbMax = this.props.minDb ?? this.dbMinDefault;
|
||||
this.dbMin = this.props.maxDb ?? this.dbMaxDefault;
|
||||
|
||||
this.fMin = this.props.minFrequency ?? 30;
|
||||
this.fMax = this.props.maxFrequency ?? 20000;
|
||||
this.logMin = Math.log(this.fMin);
|
||||
this.logMax = Math.log(this.fMax);
|
||||
|
||||
|
||||
|
||||
|
||||
let classes = this.props.classes;
|
||||
return (
|
||||
<div className={classes.frame} >
|
||||
<svg width={PLOT_WIDTH} height={PLOT_HEIGHT} viewBox={"0 0 " + PLOT_WIDTH + " " + PLOT_HEIGHT} stroke="#0F8" fill="none" strokeWidth="2.5" opacity="0.6">
|
||||
{ this.grid() }
|
||||
{this.grid()}
|
||||
<path d={this.state.path} ref={this.pathRef} />
|
||||
</svg>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobInputStageProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobInputStageState {
|
||||
@@ -84,8 +84,8 @@ const ToobInputStageView =
|
||||
class ToobInputStageViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-input_stage";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobInputStageView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobInputStageView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel,MonitorPortHandle } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobMLProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobMLState {
|
||||
@@ -135,8 +135,8 @@ const ToobMLView =
|
||||
class ToobMLViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-ml";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobMLView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobMLView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,18 +26,19 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView';
|
||||
|
||||
|
||||
let POWERSTAGE_UI_URI = "http://two-play.com/plugins/toob-power-stage-2#uiState";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
interface ToobPowerstage2Props extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobPowerstage2State {
|
||||
@@ -60,18 +61,11 @@ const ToobPowerstage2View =
|
||||
uiState: [0,0,0,0,0,0,0,0]
|
||||
}
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
this.onAtomOutput = this.onAtomOutput.bind(this);
|
||||
}
|
||||
|
||||
listeningForOutput: boolean = false;
|
||||
atomOutputHandle?: ListenHandle;
|
||||
|
||||
onAtomOutput(instanceId: number, atomOutput: any): void {
|
||||
if (atomOutput.lv2Type === "http://two-play.com/plugins/toob-power-stage-2#uiState" )
|
||||
{
|
||||
this.setState({uiState: atomOutput.data as number[]});
|
||||
}
|
||||
}
|
||||
maybeListenForAtomOutput(): void {
|
||||
let listenForOutput = this.isReady && this.isControlMounted;
|
||||
|
||||
@@ -80,11 +74,22 @@ const ToobPowerstage2View =
|
||||
this.listeningForOutput = listenForOutput;
|
||||
if (listenForOutput)
|
||||
{
|
||||
this.atomOutputHandle = this.model.listenForAtomOutput(this.props.instanceId,this.onAtomOutput);
|
||||
this.atomOutputHandle = this.model.monitorPatchProperty(
|
||||
this.props.instanceId,
|
||||
POWERSTAGE_UI_URI,
|
||||
(instanceId: number, propertyUri: string, json: any) => {
|
||||
if (json.otype_ === "Vector")
|
||||
{
|
||||
this.setState({uiState: json.value as number[]});
|
||||
}
|
||||
});
|
||||
|
||||
// discard the output as we will be getting it through monitorPatchProperty as well.
|
||||
this.model.getPatchProperty(this.props.instanceId,POWERSTAGE_UI_URI);
|
||||
} else {
|
||||
if (this.atomOutputHandle)
|
||||
{
|
||||
this.model.cancelListenForAtomOutput(this.atomOutputHandle);
|
||||
this.model.cancelMonitorPatchProperty(this.atomOutputHandle);
|
||||
this.atomOutputHandle = undefined;
|
||||
}
|
||||
|
||||
@@ -180,8 +185,8 @@ const ToobPowerstage2View =
|
||||
class ToobPowerstage2ViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-power-stage-2";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobPowerstage2View instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobPowerstage2View instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobSpectrumResponseView from './ToobSpectrumResponseView';
|
||||
|
||||
@@ -38,7 +38,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobSpectrumAnalyzerProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobSpectrumAnalyzerState {
|
||||
@@ -84,8 +84,8 @@ const ToobSpectrumAnalyzerView =
|
||||
class ToobSpectrumAnalyzerViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-spectrum";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobSpectrumAnalyzerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobSpectrumAnalyzerView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,22 +25,30 @@ import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel";
|
||||
import { PiPedalModelFactory, PiPedalModel, State, ListenHandle } from "./PiPedalModel";
|
||||
import { StandardItemSize } from './PluginControlView';
|
||||
import Utility from './Utility';
|
||||
// import { setInterval,clearInterval } from 'timers'; // no longer requires polyfill (webpack >= 5)
|
||||
|
||||
|
||||
const SPECTRUM_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#spectrumResponse";
|
||||
|
||||
const PLOT_WIDTH = StandardItemSize.width*3-16;
|
||||
const PLOT_HEIGHT = StandardItemSize.height-12;
|
||||
|
||||
import SvgPathBuilder from './SvgPathBuilder'
|
||||
|
||||
const SPECTRUM_RESPONSE_URI = "http://two-play.com/plugins/toob#spectrumResponse";
|
||||
const SPECTRUM_ENABLE_URI = "http://two-play.com/plugins/toob#spectrumEnable";
|
||||
|
||||
const PLOT_WIDTH = StandardItemSize.width * 3 - 16;
|
||||
const PLOT_HEIGHT = StandardItemSize.height - 12;
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444",
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#333",
|
||||
borderRadius: 6,
|
||||
marginTop: 12, marginLeft: 8, marginRight: 8,
|
||||
overflow: "hidden"
|
||||
},
|
||||
frameShadow: {
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT,
|
||||
borderRadius: 6,
|
||||
boxShadow: "1px 4px 8px #000 inset"
|
||||
},
|
||||
});
|
||||
@@ -51,6 +59,7 @@ interface ToobSpectrumResponseProps extends WithStyles<typeof styles> {
|
||||
}
|
||||
interface ToobSpectrumResponseState {
|
||||
path: string;
|
||||
holdPath: string;
|
||||
minF: number;
|
||||
maxF: number;
|
||||
}
|
||||
@@ -72,9 +81,8 @@ const ToobSpectrumResponseView =
|
||||
let minF = 60;
|
||||
let maxF = 18000;
|
||||
|
||||
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin)
|
||||
{
|
||||
let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin) {
|
||||
minF = plugin.getControlValue("minF");
|
||||
maxF = plugin.getControlValue("maxF");
|
||||
}
|
||||
@@ -82,62 +90,59 @@ const ToobSpectrumResponseView =
|
||||
|
||||
this.state = {
|
||||
path: "",
|
||||
holdPath: "",
|
||||
minF: minF,
|
||||
maxF: maxF
|
||||
};
|
||||
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
}
|
||||
|
||||
onStateChanged()
|
||||
{
|
||||
if (this.model.state.get() === State.Ready)
|
||||
{
|
||||
onStateChanged() {
|
||||
if (this.model.state.get() === State.Ready) {
|
||||
this.requestDeferred = false;
|
||||
this.requestOutstanding = false;
|
||||
|
||||
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin)
|
||||
{
|
||||
let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin) {
|
||||
let minF = plugin.getControlValue("minF");
|
||||
let maxF = plugin.getControlValue("maxF");
|
||||
this.setState({minF: minF, maxF: maxF});
|
||||
this.setState({ minF: minF, maxF: maxF });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
this.isReady = this.model.state.get() === State.Ready;
|
||||
|
||||
this.maybeStartClock(); // after a reconnect.
|
||||
this.maybeStartMonitoring(); // after a reconnect.
|
||||
}
|
||||
onPedalBoardChanged()
|
||||
{
|
||||
this.maybeStartClock();
|
||||
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin)
|
||||
{
|
||||
onPedalboardChanged() {
|
||||
// rebind our monitoring hooks.
|
||||
this.maybeStartMonitoring(false);
|
||||
this.maybeStartMonitoring(true);
|
||||
|
||||
let plugin = this.model.pedalboard.get()?.maybeGetItem(this.props.instanceId);
|
||||
if (plugin) {
|
||||
let minF = plugin.getControlValue("minF");
|
||||
let maxF = plugin.getControlValue("maxF");
|
||||
this.setState({minF: minF, maxF: maxF});
|
||||
this.setState({ minF: minF, maxF: maxF });
|
||||
}
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
isReady: boolean = false;
|
||||
componentDidMount()
|
||||
{
|
||||
componentDidMount() {
|
||||
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
this.mounted = true;
|
||||
this.isReady = this.model.state.get() === State.Ready;
|
||||
this.maybeStartClock();
|
||||
this.maybeStartMonitoring();
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
this.maybeStartClock();
|
||||
this.maybeStartMonitoring();
|
||||
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -147,77 +152,50 @@ const ToobSpectrumResponseView =
|
||||
|
||||
// Size of the SVG element.
|
||||
xMin: number = 0;
|
||||
xMax: number = PLOT_WIDTH+4;
|
||||
xMax: number = PLOT_WIDTH + 4;
|
||||
yMin: number = 0;
|
||||
yMax: number = PLOT_HEIGHT;
|
||||
|
||||
onSpectrumResponseUpdated(svgPath: string) {
|
||||
this.setState({path: svgPath});
|
||||
onSpectrumResponseUpdated(svgPath: string, holdSvgPath: string) {
|
||||
this.setState({ path: svgPath, holdPath: holdSvgPath });
|
||||
|
||||
}
|
||||
|
||||
isTicking : boolean = false;
|
||||
hClock?: NodeJS.Timeout;
|
||||
hAtomOutput?: ListenHandle;
|
||||
private isMonitoring: boolean = false;
|
||||
private hClock?: NodeJS.Timeout;
|
||||
private hAtomOutput?: ListenHandle;
|
||||
|
||||
maybeStartClock() {
|
||||
let shouldTick = this.isReady && this.mounted;
|
||||
private maybeStartMonitoring(wantsMonitoring: boolean = true) {
|
||||
let shouldMonitor = this.isReady && this.mounted && wantsMonitoring;
|
||||
|
||||
if (shouldTick !== this.isTicking)
|
||||
{
|
||||
this.isTicking = shouldTick;
|
||||
if (shouldTick)
|
||||
{
|
||||
this.hAtomOutput = this.model.listenForAtomOutput(this.props.instanceId,
|
||||
(instanceId,atomOutput)=> {
|
||||
this.onSpectrumResponseUpdated(atomOutput.value as string);
|
||||
if (this.requestDeferred) {
|
||||
Utility.delay(30) // take breath
|
||||
.then(
|
||||
() => {
|
||||
this.requestOutstanding = false;
|
||||
this.requestDeferred = false;
|
||||
this.requestSpectrum();
|
||||
}
|
||||
|
||||
);
|
||||
} else {
|
||||
this.requestOutstanding = false;
|
||||
if (shouldMonitor !== this.isMonitoring) {
|
||||
this.isMonitoring = shouldMonitor;
|
||||
if (shouldMonitor) {
|
||||
this.hAtomOutput = this.model.monitorPatchProperty(
|
||||
this.props.instanceId,
|
||||
SPECTRUM_RESPONSE_URI,
|
||||
(instanceId, propertyUri, json) => {
|
||||
if (propertyUri === SPECTRUM_RESPONSE_URI && json.otype_ === "Tuple") {
|
||||
let values = json.value as string[];
|
||||
this.onSpectrumResponseUpdated(values[0], values[1]);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
this.hClock = setInterval(()=> {
|
||||
this.requestSpectrum();
|
||||
},
|
||||
1000/15);
|
||||
}
|
||||
);
|
||||
this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, true)
|
||||
.catch((error)=> {});
|
||||
} else {
|
||||
if (this.hAtomOutput)
|
||||
{
|
||||
this.model.cancelListenForAtomOutput(this.hAtomOutput);
|
||||
|
||||
this.model.setPatchProperty(this.props.instanceId, SPECTRUM_ENABLE_URI, false)
|
||||
.catch((error)=> {}); // e.g. plugin not found.
|
||||
|
||||
|
||||
|
||||
if (this.hAtomOutput) {
|
||||
this.model.cancelMonitorPatchProperty(this.hAtomOutput);
|
||||
this.hAtomOutput = undefined;
|
||||
}
|
||||
if (this.hClock)
|
||||
{
|
||||
clearInterval(this.hClock);
|
||||
this.hClock = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
requestSpectrum() {
|
||||
if (this.requestOutstanding) { // throttling.
|
||||
this.requestDeferred = true;
|
||||
return;
|
||||
}
|
||||
this.requestOutstanding = true;
|
||||
this.model.getLv2Parameter<string>(this.props.instanceId, SPECTRUM_RESPONSE_VECTOR_URI)
|
||||
.then((data) => {
|
||||
}).catch(error => {
|
||||
// assume the connection was lost. We'll get saved by a reconnect.
|
||||
});
|
||||
}
|
||||
|
||||
numPoints = 200;
|
||||
@@ -228,22 +206,21 @@ const ToobSpectrumResponseView =
|
||||
logMaxF = Math.log(this.maxF);
|
||||
|
||||
toX(f: number): number {
|
||||
let width = PLOT_WIDTH-8;
|
||||
return (Math.log(f)-this.logMinF)*width/(this.logMaxF-this.logMinF);
|
||||
let width = PLOT_WIDTH - 8;
|
||||
return (Math.log(f) - this.logMinF) * width / (this.logMaxF - this.logMinF);
|
||||
}
|
||||
grid(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
let width = PLOT_WIDTH-8;
|
||||
let height = PLOT_HEIGHT-8;
|
||||
|
||||
for (let db = -20; db >= -100; db -= 20)
|
||||
{
|
||||
let y = height*db/-100;
|
||||
result.push(
|
||||
(
|
||||
<line x1={0} y1={y} x2={width} y2={y} key={"db" + db} />
|
||||
)
|
||||
);
|
||||
private minorGrid(): string {
|
||||
|
||||
let ss = new SvgPathBuilder();
|
||||
|
||||
let width = PLOT_WIDTH;
|
||||
let height = PLOT_HEIGHT;
|
||||
|
||||
for (let db = -20; db >= -100; db -= 20) {
|
||||
let y = height * db / -100;
|
||||
ss.moveTo(0,y);
|
||||
ss.lineTo(width,y);
|
||||
}
|
||||
|
||||
let minF = this.state.minF;
|
||||
@@ -252,54 +229,86 @@ const ToobSpectrumResponseView =
|
||||
this.maxF = maxF;
|
||||
this.logMinF = Math.log(this.minF);
|
||||
this.logMaxF = Math.log(this.maxF);
|
||||
|
||||
for (let decade = 10; decade < 100000; decade *= 10)
|
||||
{
|
||||
for (let minorTick = 1; minorTick <= 5; ++ minorTick)
|
||||
{
|
||||
let f = decade*minorTick;
|
||||
if (f > minF && f < maxF)
|
||||
{
|
||||
|
||||
for (let decade = 10; decade < 100000; decade *= 10) {
|
||||
for (let minorTick = 1; minorTick <= 5; ++minorTick) {
|
||||
let f = decade * minorTick;
|
||||
if (f > minF && f < maxF) {
|
||||
let x = this.toX(f);
|
||||
if (minorTick === 1)
|
||||
{
|
||||
result.push(
|
||||
(
|
||||
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} strokeWidth={1}/>
|
||||
)
|
||||
);
|
||||
if (minorTick === 1) {
|
||||
ss.moveTo(x,0);
|
||||
ss.lineTo(x,height);
|
||||
} else {
|
||||
result.push(
|
||||
(
|
||||
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} />
|
||||
)
|
||||
);
|
||||
ss.moveTo(x,0);
|
||||
ss.lineTo(x,height);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
private majorGrid(): string {
|
||||
|
||||
let ss = new SvgPathBuilder();
|
||||
|
||||
let height = PLOT_HEIGHT;
|
||||
|
||||
let minF = this.state.minF;
|
||||
let maxF = this.state.maxF;
|
||||
this.minF = minF;
|
||||
this.maxF = maxF;
|
||||
this.logMinF = Math.log(this.minF);
|
||||
this.logMaxF = Math.log(this.maxF);
|
||||
|
||||
for (let decade = 10; decade < 100000; decade *= 10) {
|
||||
if (decade > minF && decade < maxF)
|
||||
{
|
||||
let x = this.toX(decade);
|
||||
ss.moveTo(x,0);
|
||||
ss.lineTo(x,height);
|
||||
}
|
||||
}
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
return (
|
||||
<div className={classes.frame} style={{position: "relative"}} >
|
||||
<div style={{position: "absolute", left: 4, top: 4}}>
|
||||
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} stroke="#888" strokeWidth="0.5" opacity="1"
|
||||
>
|
||||
{this.grid()}
|
||||
<div className={classes.frame} style={{ position: "relative" }} >
|
||||
{/* <div style={{ position: "absolute", left: 0, top: 0 }}>
|
||||
<svg fill="#660" width={PLOT_WIDTH } height={PLOT_HEIGHT} preserveAspectRatio='none'
|
||||
viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" strokeWidth="2.5" opacity="1.0"
|
||||
>
|
||||
<path d={this.state.holdPath} />
|
||||
</svg>
|
||||
</div> */}
|
||||
|
||||
<div style={{ position: "absolute", left: 0, top: 0 }}>
|
||||
<svg fill="#0C4" width={PLOT_WIDTH } height={PLOT_HEIGHT } preserveAspectRatio='none'
|
||||
viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" strokeWidth="2.5" opacity="1.0"
|
||||
>
|
||||
<path d={this.state.path} />
|
||||
</svg>
|
||||
</div>
|
||||
<div style={{position: "absolute", left: 4, top: 4}}>
|
||||
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} preserveAspectRatio='none' viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" fill="#0FC" strokeWidth="2.5" opacity="0.6"
|
||||
>
|
||||
<path d={this.state.path} />
|
||||
<div style={{ position: "absolute", left: 0, top: 0 }}>
|
||||
<svg width={PLOT_WIDTH } height={PLOT_HEIGHT} stroke="#FFF" strokeWidth="0.9" opacity="0.3"
|
||||
>
|
||||
<path d={this.minorGrid()} />
|
||||
</svg>
|
||||
</div>
|
||||
<div style={{ position: "absolute", left: 0, top: 0 }}>
|
||||
<svg width={PLOT_WIDTH} height={PLOT_HEIGHT} stroke="#FFF" strokeWidth="0.9" opacity="0.5"
|
||||
>
|
||||
<path d={this.majorGrid()} />
|
||||
</svg>
|
||||
</div>
|
||||
<div className={classes.frameShadow} style={{ position: "absolute", left: 0, top: 0, right: 0, bottom: 0 }}>
|
||||
</div>
|
||||
|
||||
|
||||
</div>);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel,ControlValueChangedHandle } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobToneStackProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobToneStackState {
|
||||
@@ -117,8 +117,8 @@ const ToobToneStackView =
|
||||
class ToobToneStackViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-tone-stack";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobToneStackView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobToneStackView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ const ToobWaveShapeView =
|
||||
data: []
|
||||
};
|
||||
|
||||
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
this.onControlValueChanged = this.onControlValueChanged.bind(this);
|
||||
this.isReady = this.model.state.get() === State.Ready;
|
||||
@@ -147,7 +147,7 @@ const ToobWaveShapeView =
|
||||
}
|
||||
}
|
||||
}
|
||||
onPedalBoardChanged() {
|
||||
onPedalboardChanged() {
|
||||
this.updateWaveShape();
|
||||
}
|
||||
_valueChangedHandle?: ControlValueChangedHandle;
|
||||
@@ -158,7 +158,7 @@ const ToobWaveShapeView =
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
this._valueChangedHandle = this.model.addControlValueChangeListener(
|
||||
this.props.instanceId,
|
||||
this.onControlValueChanged);
|
||||
@@ -180,7 +180,7 @@ const ToobWaveShapeView =
|
||||
this._valueChangedHandle = undefined;
|
||||
}
|
||||
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
}
|
||||
|
||||
onControlValueChanged(key: string, value: number) {
|
||||
@@ -230,21 +230,27 @@ const ToobWaveShapeView =
|
||||
return;
|
||||
}
|
||||
this.requestOutstanding = true;
|
||||
this.model.getLv2Parameter<number[]>(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber)
|
||||
.then((data) => {
|
||||
this.onWaveShapeUpdated(data);
|
||||
if (this.requestDeferred) {
|
||||
Utility.delay(10) // take breath
|
||||
.then(
|
||||
() => {
|
||||
this.requestOutstanding = false;
|
||||
this.requestDeferred = false;
|
||||
this.updateWaveShape();
|
||||
}
|
||||
this.model.getPatchProperty<any>(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber)
|
||||
.then((json) => {
|
||||
if (!this.mounted) return;
|
||||
|
||||
if (json.otype_ === "Vector" && json.vtype_ === "Float")
|
||||
{
|
||||
let data = json.value;
|
||||
this.onWaveShapeUpdated(data);
|
||||
if (this.requestDeferred) {
|
||||
Utility.delay(10) // take breath
|
||||
.then(
|
||||
() => {
|
||||
this.requestOutstanding = false;
|
||||
this.requestDeferred = false;
|
||||
this.updateWaveShape();
|
||||
}
|
||||
|
||||
);
|
||||
} else {
|
||||
this.requestOutstanding = false;
|
||||
);
|
||||
} else {
|
||||
this.requestOutstanding = false;
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
// assume the connection was lost. We'll get saved by a reconnect.
|
||||
|
||||
@@ -170,8 +170,8 @@ const ZoomedDial = withStyles(styles, { withTheme: true })(
|
||||
let uiControl = this.props.controlInfo.uiControl;
|
||||
let instanceId = this.props.controlInfo.instanceId;
|
||||
if (instanceId === -1) return 0;
|
||||
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId);
|
||||
let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0;
|
||||
let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId);
|
||||
let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0;
|
||||
this.defaultValue = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
|
||||
let uiControl = this.props.controlInfo.uiControl;
|
||||
let instanceId = this.props.controlInfo.instanceId;
|
||||
if (instanceId === -1) return 0;
|
||||
let pedalBoardItem = this.model.pedalBoard.get()?.getItem(instanceId);
|
||||
let value: number = pedalBoardItem?.getControlValue(uiControl.symbol) ?? 0;
|
||||
let pedalboardItem = this.model.pedalboard.get()?.getItem(instanceId);
|
||||
let value: number = pedalboardItem?.getControlValue(uiControl.symbol) ?? 0;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
|
||||
let v = Number.parseFloat(val.toString());
|
||||
this.setState({ value: v });
|
||||
if (this.props.controlInfo) {
|
||||
this.model.setPedalBoardControlValue(
|
||||
this.model.setPedalboardControl(
|
||||
this.props.controlInfo.instanceId,
|
||||
this.props.controlInfo.uiControl.symbol,
|
||||
v);
|
||||
@@ -116,7 +116,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
|
||||
let v = checked ? 1: 0;
|
||||
this.setState({ value: v });
|
||||
if (this.props.controlInfo) {
|
||||
this.model.setPedalBoardControlValue(
|
||||
this.model.setPedalboardControl(
|
||||
this.props.controlInfo.instanceId,
|
||||
this.props.controlInfo.uiControl.symbol,
|
||||
v);
|
||||
@@ -247,7 +247,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
|
||||
onSetValue={(v) => {
|
||||
this.setState({ value: v });
|
||||
if (this.props.controlInfo) {
|
||||
this.model.setPedalBoardControlValue(
|
||||
this.model.setPedalboardControl(
|
||||
this.props.controlInfo.instanceId,
|
||||
this.props.controlInfo.uiControl.symbol,
|
||||
v);
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ src/MidiBinding.tsx
|
||||
src/PiPedalError.tsx
|
||||
src/IControlViewFactory.tsx
|
||||
src/Lv2Plugin.tsx
|
||||
src/PedalBoard.tsx
|
||||
src/PedalBoardView.tsx
|
||||
src/Pedalboard.tsx
|
||||
src/PedalboardView.tsx
|
||||
src/PresetDialog.tsx
|
||||
src/AppThemed.tsx
|
||||
src/ZoomedDial.tsx
|
||||
|
||||
+1
-1
@@ -206,7 +206,7 @@ bool setJackConfiguration(JackServerSettings serverSettings)
|
||||
|
||||
#if JACK_HOST
|
||||
|
||||
serverSettings.Write();
|
||||
serverSettings.WriteDaemonConfig();
|
||||
|
||||
silentSysExec("/usr/bin/systemctl unmask jack");
|
||||
silentSysExec("/usr/bin/systemctl enable jack");
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "pch.h"
|
||||
#include "util.hpp"
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include "ss.hpp"
|
||||
@@ -1367,6 +1368,7 @@ namespace pipedal
|
||||
}
|
||||
void AudioThread()
|
||||
{
|
||||
SetThreadName("alsaDriver");
|
||||
try
|
||||
{
|
||||
#if defined(__WIN32)
|
||||
|
||||
@@ -92,8 +92,8 @@ public:
|
||||
#endif
|
||||
|
||||
|
||||
oscillator.Init(440,jackConfiguration.GetSampleRate());
|
||||
latencyMonitor.Init(jackConfiguration.GetSampleRate());
|
||||
oscillator.Init(440,jackConfiguration.sampleRate());
|
||||
latencyMonitor.Init(jackConfiguration.sampleRate());
|
||||
audioDriver->Open(serverSettings,channelSelection);
|
||||
|
||||
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
if (testType == TestType::LatencyMonitor)
|
||||
{
|
||||
auto latency = this->latencyMonitor.GetLatency();
|
||||
double ms = 1000.0*latency/jackConfiguration.GetSampleRate();
|
||||
double ms = 1000.0*latency/jackConfiguration.sampleRate();
|
||||
|
||||
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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(¤t->value);
|
||||
result[key] = std::move(value);
|
||||
current = (LV2_Atom_Property_Body*)(NextAtom(&(current->value)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
throw std::logic_error(
|
||||
SS("AtomConverter: Datatype not supported. ("
|
||||
<< map.UridToString(atom->type)
|
||||
<< ") Please contact support if you get this message."));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool AtomConverter::AreTypesTheSame(LV2_Atom*left, LV2_Atom *right) const
|
||||
{
|
||||
if (left->type != right->type) return false;
|
||||
if (left->type == urids.ATOM__Object)
|
||||
{
|
||||
LV2_Atom_Object *l = (LV2_Atom_Object*)left;
|
||||
LV2_Atom_Object *r = (LV2_Atom_Object*)right;
|
||||
return l->body.id == r->body.id && l->body.otype == r->body.otype;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtomConverter::ObjectToForge(const json_variant&json)
|
||||
{
|
||||
LV2_URID bodyId = 0;
|
||||
assert(json.is_object());
|
||||
std::string oType = json[OTYPE_TAG].as_string();
|
||||
if (oType == SHORT_ATOM__Int)
|
||||
{
|
||||
lv2_atom_forge_int(&this->outputForge,(int32_t)json["value"].as_number());
|
||||
}
|
||||
else if (oType == SHORT_ATOM__Long)
|
||||
{
|
||||
lv2_atom_forge_long(&this->outputForge,(int64_t)json["value"].as_number());
|
||||
} else if (oType == SHORT_ATOM__Double)
|
||||
{
|
||||
lv2_atom_forge_double(&this->outputForge,(double)json["value"].as_number());
|
||||
}
|
||||
else if (oType == SHORT_ATOM__Path)
|
||||
{
|
||||
std::string value = json["value"].as_string().c_str();
|
||||
|
||||
lv2_atom_forge_path(&this->outputForge,value.c_str(),value.length()+1);
|
||||
}
|
||||
else if (oType == SHORT_ATOM__URI)
|
||||
{
|
||||
std::string value = json["value"].as_string().c_str();
|
||||
|
||||
lv2_atom_forge_uri(&this->outputForge,value.c_str(),value.length()+1);
|
||||
}
|
||||
else if (oType == SHORT_ATOM__URID)
|
||||
{
|
||||
LV2_URID urid = map.GetUrid(json["value"].as_string().c_str());
|
||||
lv2_atom_forge_urid(&outputForge,urid);
|
||||
|
||||
}
|
||||
else if (oType == SHORT_ATOM__Tuple)
|
||||
{
|
||||
TupleToForge(json);
|
||||
} else if (oType == SHORT_ATOM__Vector)
|
||||
{
|
||||
VectorToForge(json);
|
||||
} else
|
||||
{
|
||||
LV2_URID id = 0;
|
||||
if (json.contains(ID_TAG))
|
||||
{
|
||||
id = map.GetUrid(json[ID_TAG].as_string().c_str());
|
||||
}
|
||||
LV2_URID oType = 0;
|
||||
if (json.contains(OTYPE_TAG))
|
||||
{
|
||||
oType = map.GetUrid(json[OTYPE_TAG].as_string().c_str());
|
||||
}
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
lv2_atom_forge_object(&outputForge,&frame,id,oType);
|
||||
const auto& obj = json.as_object();
|
||||
for (auto member: *obj.get())
|
||||
{
|
||||
const std::string& property = member.first;
|
||||
if (property != OTYPE_TAG && property != ID_TAG)
|
||||
{
|
||||
LV2_URID property = map.GetUrid(member.first.c_str());
|
||||
lv2_atom_forge_key(&outputForge,property);
|
||||
ToForge(member.second);
|
||||
}
|
||||
}
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
}
|
||||
}
|
||||
void AtomConverter::ToForge(const json_variant&json)
|
||||
{
|
||||
if (json.is_number())
|
||||
{
|
||||
CheckResult(lv2_atom_forge_float(&outputForge,(float)json.as_number()));
|
||||
}
|
||||
else if (json.is_bool())
|
||||
{
|
||||
CheckResult(lv2_atom_forge_bool(&outputForge,json.as_bool()));
|
||||
}
|
||||
else if (json.is_string())
|
||||
{
|
||||
const std::string&str = json.as_string();
|
||||
CheckResult(lv2_atom_forge_string(&outputForge,str.c_str(),str.size()+1));
|
||||
} else if (json.is_object())
|
||||
{
|
||||
return ObjectToForge(json);
|
||||
} else {
|
||||
throw std::logic_error("Malformed json atom.");
|
||||
}
|
||||
}
|
||||
|
||||
void AtomConverter::VectorToForge(const json_variant&json)
|
||||
{
|
||||
const json_array& array = *(json["value"].as_array().get());
|
||||
size_t size = array.size();
|
||||
|
||||
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
LV2_URID childType = GetTypeUrid(json[VTYPE_TAG].as_string().c_str());
|
||||
|
||||
if (childType == urids.ATOM__Float)
|
||||
{
|
||||
using T = float;
|
||||
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
T value = (T)(array[i].as_number());
|
||||
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
|
||||
}
|
||||
// does this pad the frame?
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
|
||||
|
||||
}
|
||||
else if (childType == urids.ATOM__Int)
|
||||
{
|
||||
using T = int32_t;
|
||||
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
T value = (T)(array[i].as_number());
|
||||
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
|
||||
}
|
||||
// does this pad the frame?
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
|
||||
}
|
||||
else if (childType == urids.ATOM__Bool)
|
||||
{
|
||||
using T = int32_t;
|
||||
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
T value = (T)(array[i].as_bool());
|
||||
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
|
||||
}
|
||||
// does this pad the frame?
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
|
||||
}
|
||||
else if (childType == urids.ATOM__Long)
|
||||
{
|
||||
using T = int64_t;
|
||||
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
T value = (T)(array[i].as_number());
|
||||
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
|
||||
}
|
||||
// does this pad the frame?
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
|
||||
}
|
||||
else if (childType == urids.ATOM__Double)
|
||||
{
|
||||
using T = double;
|
||||
CheckResult(lv2_atom_forge_vector_head(&outputForge,&frame,sizeof(T),childType));
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
T value = (T)(array[i].as_number());
|
||||
CheckResult(lv2_atom_forge_raw(&outputForge,&value,sizeof(value)));
|
||||
}
|
||||
// does this pad the frame?
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
lv2_atom_forge_pad(&outputForge,sizeof(LV2_Atom_Vector_Body)+size*sizeof(float));
|
||||
} else {
|
||||
std::string dataType = map.UridToString(childType);
|
||||
throw std::logic_error("AtomConverter: Vector dataype not supported. (" + dataType + ") Please contact support if you get this message.");
|
||||
}
|
||||
}
|
||||
|
||||
void AtomConverter::TupleToForge(const json_variant&json)
|
||||
{
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
const json_array&array = *(json["value"].as_array().get());
|
||||
|
||||
lv2_atom_forge_tuple(&outputForge,&frame);
|
||||
{
|
||||
for (const json_variant&v: array)
|
||||
{
|
||||
ToForge(v);
|
||||
}
|
||||
}
|
||||
lv2_atom_forge_pop(&outputForge,&frame);
|
||||
}
|
||||
|
||||
|
||||
LV2_URID AtomConverter::GetTypeUrid(const std::string uri)
|
||||
{
|
||||
if (stringToTypeUrid.find(uri) != stringToTypeUrid.end())
|
||||
{
|
||||
return stringToTypeUrid[uri];
|
||||
}
|
||||
return map.GetUrid(uri.c_str());
|
||||
}
|
||||
|
||||
LV2_URID AtomConverter::InitUrid(const char*uri, const char*shortUri )
|
||||
{
|
||||
LV2_URID urid = map.GetUrid(uri);
|
||||
stringToTypeUrid[uri] = urid;
|
||||
stringToTypeUrid[shortUri] = urid;
|
||||
typeUridToString[urid] = shortUri;
|
||||
return urid;
|
||||
}
|
||||
|
||||
std::string AtomConverter::TypeUridToString(LV2_URID urid)
|
||||
{
|
||||
if (typeUridToString.find(urid) != typeUridToString.end())
|
||||
{
|
||||
return typeUridToString[urid];
|
||||
}
|
||||
return map.UridToString(urid);
|
||||
}
|
||||
|
||||
void AtomConverter::InitUrids()
|
||||
{
|
||||
#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName)
|
||||
ATOM_INIT(ATOM__Bool,Bool);
|
||||
ATOM_INIT(ATOM__Chunk,Chunk);
|
||||
ATOM_INIT(ATOM__Double,Double);
|
||||
ATOM_INIT(ATOM__Event,Event);
|
||||
ATOM_INIT(ATOM__Float,Float);
|
||||
ATOM_INIT(ATOM__Int,Int);
|
||||
ATOM_INIT(ATOM__Literal,Literal);
|
||||
ATOM_INIT(ATOM__Long,Long);
|
||||
ATOM_INIT(ATOM__Number,Number);
|
||||
ATOM_INIT(ATOM__Object,Object);
|
||||
ATOM_INIT(ATOM__Path,Path);
|
||||
ATOM_INIT(ATOM__Property,Property);
|
||||
//ATOM_INIT(ATOM__Resource,Resource);
|
||||
//ATOM_INIT(ATOM__Sequence,Sequence);
|
||||
//ATOM_INIT(ATOM__Sound,Sound);
|
||||
ATOM_INIT(ATOM__String,String);
|
||||
ATOM_INIT(ATOM__Tuple,Tuple);
|
||||
ATOM_INIT(ATOM__Vector,Vector);
|
||||
ATOM_INIT(ATOM__URI,URI);
|
||||
ATOM_INIT(ATOM__URID,URID);
|
||||
ATOM_INIT(ATOM__Vector,Vector);
|
||||
//ATOM_INIT(ATOM__beatTime,beatTime);
|
||||
//ATOM_INIT(ATOM__frameTime,frameTime);
|
||||
//ATOM_INIT(ATOM__timeUnit,timeUnit);
|
||||
#undef ATOM_INIT
|
||||
}
|
||||
@@ -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 ↦
|
||||
std::vector<uint8_t> prototypeBuffer;
|
||||
std::vector<uint8_t> outputBuffer;
|
||||
LV2_Atom_Forge outputForge;
|
||||
|
||||
LV2_Atom * prototype = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -24,8 +24,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef JACK_HOST
|
||||
#define JACK_HOST 0
|
||||
#endif
|
||||
|
||||
#ifndef ALSA_HOST
|
||||
#define ALSA_HOST 1
|
||||
#endif
|
||||
|
||||
#if JACK_HOST && ALSA_HOST
|
||||
#error Choose either JACK or ALSA
|
||||
|
||||
+175
-234
@@ -18,11 +18,13 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "AudioHost.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
#include "JackDriver.hpp"
|
||||
#include "AlsaDriver.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -177,6 +179,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost
|
||||
{
|
||||
private:
|
||||
IHost *pHost = nullptr;
|
||||
LV2_Atom_Forge inputWriterForge;
|
||||
|
||||
|
||||
std::mutex atomConverterMutex;
|
||||
AtomConverter atomConverter;
|
||||
|
||||
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
|
||||
|
||||
@@ -277,9 +284,9 @@ private:
|
||||
JackChannelSelection channelSelection;
|
||||
bool active = false;
|
||||
|
||||
std::shared_ptr<Lv2PedalBoard> currentPedalBoard;
|
||||
std::vector<std::shared_ptr<Lv2PedalBoard>> activePedalBoards; // pedalboards that have been sent to the audio queue.
|
||||
Lv2PedalBoard *realtimeActivePedalBoard = nullptr;
|
||||
std::shared_ptr<Lv2Pedalboard> currentPedalboard;
|
||||
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
|
||||
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
|
||||
|
||||
std::vector<uint8_t *> midiLv2Buffers;
|
||||
|
||||
@@ -297,18 +304,19 @@ private:
|
||||
{
|
||||
throw std::invalid_argument("Not an Lv2 Object");
|
||||
}
|
||||
return pHost->Lv2UriudToString(pAtom->body.otype);
|
||||
return pHost->Lv2UridToString(pAtom->body.otype);
|
||||
}
|
||||
|
||||
void WriteAtom(json_writer &writer, LV2_Atom *pAtom);
|
||||
|
||||
std::string AtomToJson(uint8_t *pData)
|
||||
std::string AtomToJson(const LV2_Atom *pAtom)
|
||||
{
|
||||
std::lock_guard lock(atomConverterMutex);
|
||||
json_variant vAtom = atomConverter.ToJson(pAtom);
|
||||
|
||||
std::stringstream s;
|
||||
json_writer writer(s);
|
||||
LV2_Atom *pAtom = (LV2_Atom *)pData;
|
||||
|
||||
WriteAtom(writer, pAtom);
|
||||
|
||||
writer.write(vAtom);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
@@ -325,7 +333,6 @@ private:
|
||||
return;
|
||||
|
||||
isOpen = false;
|
||||
StopReaderThread();
|
||||
|
||||
if (realtimeMonitorPortSubscriptions != nullptr)
|
||||
{
|
||||
@@ -341,9 +348,13 @@ private:
|
||||
|
||||
audioDriver->Close();
|
||||
|
||||
StopReaderThread();
|
||||
pHost->GetHostWorkerThread()->Close();
|
||||
|
||||
|
||||
// release any pdealboards owned by the process thread.
|
||||
this->activePedalBoards.resize(0);
|
||||
this->realtimeActivePedalBoard = nullptr;
|
||||
this->activePedalboards.resize(0);
|
||||
this->realtimeActivePedalboard = nullptr;
|
||||
|
||||
// clean up any realtime buffers that may have been lost in transit.
|
||||
// TODO: These should be lists, really. There may be multiple items in flight..
|
||||
@@ -365,6 +376,7 @@ private:
|
||||
delete[] midiLv2Buffers[i];
|
||||
}
|
||||
midiLv2Buffers.resize(0);
|
||||
|
||||
}
|
||||
|
||||
void ZeroBuffer(float *buffer, size_t nframes)
|
||||
@@ -442,7 +454,7 @@ private:
|
||||
portSubscription.samplesToNextCallback += portSubscription.sampleRate;
|
||||
if (!portSubscription.waitingForAck)
|
||||
{
|
||||
float value = realtimeActivePedalBoard->GetControlOutputValue(
|
||||
float value = realtimeActivePedalboard->GetControlOutputValue(
|
||||
portSubscription.instanceIndex,
|
||||
portSubscription.portIndex);
|
||||
if (value != portSubscription.lastValue)
|
||||
@@ -459,7 +471,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
RealtimeParameterRequest *pParameterRequests = nullptr;
|
||||
RealtimePatchPropertyRequest *pParameterRequests = nullptr;
|
||||
|
||||
bool reEntered = false;
|
||||
void ProcessInputCommands()
|
||||
@@ -486,12 +498,12 @@ private:
|
||||
{
|
||||
SetControlValueBody body;
|
||||
realtimeReader.readComplete(&body);
|
||||
this->realtimeActivePedalBoard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
|
||||
this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
|
||||
break;
|
||||
}
|
||||
case RingBufferCommand::ParameterRequest:
|
||||
{
|
||||
RealtimeParameterRequest *pRequest = nullptr;
|
||||
RealtimePatchPropertyRequest *pRequest = nullptr;
|
||||
realtimeReader.readComplete(&pRequest);
|
||||
|
||||
// link to the list of parameter requests.
|
||||
@@ -562,7 +574,7 @@ private:
|
||||
{
|
||||
SetBypassBody body;
|
||||
realtimeReader.readComplete(&body);
|
||||
this->realtimeActivePedalBoard->SetBypass(body.effectIndex, body.enabled);
|
||||
this->realtimeActivePedalboard->SetBypass(body.effectIndex, body.enabled);
|
||||
break;
|
||||
}
|
||||
case RingBufferCommand::ReplaceEffect:
|
||||
@@ -572,8 +584,8 @@ private:
|
||||
|
||||
if (body.effect != nullptr)
|
||||
{
|
||||
auto oldValue = this->realtimeActivePedalBoard;
|
||||
this->realtimeActivePedalBoard = body.effect;
|
||||
auto oldValue = this->realtimeActivePedalboard;
|
||||
this->realtimeActivePedalboard = body.effect;
|
||||
|
||||
realtimeWriter.EffectReplaced(oldValue);
|
||||
|
||||
@@ -612,7 +624,7 @@ private:
|
||||
{
|
||||
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
||||
|
||||
this->realtimeActivePedalBoard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
|
||||
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
|
||||
if (listenForMidiEvent)
|
||||
{
|
||||
if (event.size >= 3)
|
||||
@@ -756,8 +768,8 @@ private:
|
||||
|
||||
bool processed = false;
|
||||
|
||||
Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard;
|
||||
if (pedalBoard != nullptr)
|
||||
Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard;
|
||||
if (pedalboard != nullptr)
|
||||
{
|
||||
ProcessMidiInput();
|
||||
float *inputBuffers[4];
|
||||
@@ -789,15 +801,15 @@ private:
|
||||
|
||||
if (buffersValid)
|
||||
{
|
||||
pedalBoard->ResetAtomBuffers();
|
||||
pedalBoard->ProcessParameterRequests(pParameterRequests);
|
||||
pedalboard->ResetAtomBuffers();
|
||||
pedalboard->ProcessParameterRequests(pParameterRequests);
|
||||
|
||||
processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
||||
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
||||
if (processed)
|
||||
{
|
||||
if (this->realtimeVuBuffers != nullptr)
|
||||
{
|
||||
pedalBoard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
|
||||
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
|
||||
|
||||
vuSamplesRemaining -= nframes;
|
||||
if (vuSamplesRemaining <= 0)
|
||||
@@ -811,14 +823,10 @@ private:
|
||||
processMonitorPortSubscriptions(nframes);
|
||||
}
|
||||
}
|
||||
pedalBoard->GatherParameterRequests(pParameterRequests);
|
||||
pedalboard->GatherPatchProperties(pParameterRequests);
|
||||
}
|
||||
}
|
||||
|
||||
// in = jack_port_get_buffer(input_port, nframes);
|
||||
// out = jack_port_get_buffer(output_port, nframes);
|
||||
// memcpy(out, in,
|
||||
// sizeof(jack_default_audio_sample_t) * nframes);
|
||||
if (!processed)
|
||||
{
|
||||
ZeroOutputBuffers(nframes);
|
||||
@@ -841,7 +849,6 @@ private:
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AudioHostImpl(IHost *pHost)
|
||||
: inputRingBuffer(RING_BUFFER_SIZE),
|
||||
@@ -852,8 +859,11 @@ public:
|
||||
hostWriter(&this->inputRingBuffer),
|
||||
eventBufferUrids(pHost),
|
||||
pHost(pHost),
|
||||
uris(pHost)
|
||||
uris(pHost),
|
||||
atomConverter(pHost->GetMapFeature())
|
||||
{
|
||||
realtimeAtomBuffer.resize(32*1024);
|
||||
lv2_atom_forge_init(&inputWriterForge,pHost->GetMapFeature().GetMap());
|
||||
|
||||
#if JACK_HOST
|
||||
audioDriver = CreateJackDriver(this);
|
||||
@@ -870,6 +880,7 @@ public:
|
||||
delete audioDriver;
|
||||
}
|
||||
|
||||
|
||||
virtual JackConfiguration GetServerConfiguration()
|
||||
{
|
||||
JackConfiguration result;
|
||||
@@ -893,6 +904,7 @@ public:
|
||||
realtimeWriter.AudioStopped();
|
||||
}
|
||||
std::vector<uint8_t> atomBuffer;
|
||||
std::vector<uint8_t> realtimeAtomBuffer;
|
||||
|
||||
bool terminateThread;
|
||||
void ThreadProc()
|
||||
@@ -917,6 +929,7 @@ public:
|
||||
{
|
||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
||||
}
|
||||
SetThreadName("aout");
|
||||
#else
|
||||
xxx; // TODO!
|
||||
#endif
|
||||
@@ -924,44 +937,41 @@ public:
|
||||
try
|
||||
{
|
||||
|
||||
struct timespec ts;
|
||||
// ever 30 seconds, timeout check for and log any overruns.
|
||||
int pollRateS = 30;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
Lv2Log::error("clock_gettime failed!");
|
||||
return;
|
||||
}
|
||||
ts.tv_sec += pollRateS;
|
||||
uint64_t lastUnderrunCount = this->underruns;
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
using clock_time = std::chrono::steady_clock::time_point;
|
||||
using clock_duration = clock_time::duration;
|
||||
|
||||
// check for overruns every 30 seconds.
|
||||
clock_duration waitPeriod =
|
||||
std::chrono::duration_cast<clock_duration>(std::chrono::seconds(30));
|
||||
clock_time waitTime = std::chrono::steady_clock::now();
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
// wait for an event.
|
||||
// 0 -> ready. -1: timed out. -2: closing.
|
||||
|
||||
int result = hostReader.wait(ts);
|
||||
if (result == -2)
|
||||
auto result = hostReader.wait_until(sizeof(RingBufferCommand), waitTime);
|
||||
if (result == RingBufferStatus::Closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (result == -1)
|
||||
else if (result == RingBufferStatus::TimedOut)
|
||||
{
|
||||
// timeout.
|
||||
ts.tv_sec += pollRateS;
|
||||
uint64_t underruns = this->underruns;
|
||||
if (underruns != lastUnderrunCount)
|
||||
{
|
||||
if (underrunMessagesGiven < 60) // limit how much log file clutter we generate.
|
||||
{
|
||||
Lv2Log::info("Jack - Underrun count: %lu", (unsigned long)underruns);
|
||||
Lv2Log::info("Audio underrun count: %lu", (unsigned long)underruns);
|
||||
lastUnderrunCount = underruns;
|
||||
++underrunMessagesGiven;
|
||||
}
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += pollRateS;
|
||||
waitTime += waitPeriod;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -996,32 +1006,42 @@ public:
|
||||
}
|
||||
else if (command == RingBufferCommand::ParameterRequestComplete)
|
||||
{
|
||||
RealtimeParameterRequest *pRequest = nullptr;
|
||||
RealtimePatchPropertyRequest *pRequest = nullptr;
|
||||
hostReader.read(&pRequest);
|
||||
|
||||
std::shared_ptr<Lv2PedalBoard> currentpedalBoard;
|
||||
std::shared_ptr<Lv2Pedalboard> currentpedalboard;
|
||||
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
currentPedalBoard = this->currentPedalBoard;
|
||||
currentPedalboard = this->currentPedalboard;
|
||||
}
|
||||
|
||||
while (pRequest != nullptr)
|
||||
{
|
||||
auto pNext = pRequest->pNext;
|
||||
if (pRequest->errorMessage == nullptr && pRequest->responseLength != 0)
|
||||
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
IEffect *pEffect = currentPedalBoard->GetEffect(pRequest->instanceId);
|
||||
if (pEffect == nullptr)
|
||||
if (pRequest->errorMessage == nullptr)
|
||||
{
|
||||
pRequest->errorMessage = "Effect no longer available.";
|
||||
}
|
||||
else
|
||||
{
|
||||
pRequest->jsonResponse = AtomToJson(pRequest->response);
|
||||
if (pRequest->GetSize() != 0) {
|
||||
IEffect *pEffect = currentPedalboard->GetEffect(pRequest->instanceId);
|
||||
if (pEffect == nullptr)
|
||||
{
|
||||
pRequest->errorMessage = "Effect no longer available.";
|
||||
}
|
||||
else
|
||||
{
|
||||
pRequest->jsonResponse = AtomToJson((LV2_Atom*)pRequest->GetBuffer());
|
||||
}
|
||||
} else {
|
||||
pRequest->errorMessage = "Plugin did not respond.";
|
||||
}
|
||||
}
|
||||
}
|
||||
pRequest->onJackRequestComplete(pRequest);
|
||||
if (pRequest->onPatchRequestComplete)
|
||||
{
|
||||
pRequest->onPatchRequestComplete(pRequest);
|
||||
}
|
||||
pRequest = pNext;
|
||||
}
|
||||
}
|
||||
@@ -1047,6 +1067,12 @@ public:
|
||||
}
|
||||
this->hostWriter.AckVuUpdate(); // please sir, can I have some more?
|
||||
}
|
||||
else if (command == RingBufferCommand::Lv2StateChanged)
|
||||
{
|
||||
uint64_t instanceId;
|
||||
hostReader.read(&instanceId);
|
||||
this->pNotifyCallbacks->OnNotifyLv2StateChanged(instanceId);
|
||||
}
|
||||
else if (command == RingBufferCommand::AtomOutput)
|
||||
{
|
||||
uint64_t instanceId;
|
||||
@@ -1059,12 +1085,35 @@ public:
|
||||
}
|
||||
hostReader.read(extraBytes, &(atomBuffer[0]));
|
||||
|
||||
IEffect *pEffect = currentPedalBoard->GetEffect(instanceId);
|
||||
IEffect *pEffect = currentPedalboard->GetEffect(instanceId);
|
||||
if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput)
|
||||
{
|
||||
std::string atomType = GetAtomObjectType(&atomBuffer[0]);
|
||||
auto json = AtomToJson(&(atomBuffer[0]));
|
||||
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId, atomType, json);
|
||||
LV2_Atom *atom = (LV2_Atom*)&atomBuffer[0];
|
||||
if (atom->type == uris.atom_Object)
|
||||
{
|
||||
LV2_Atom_Object *obj = (LV2_Atom_Object*)atom;
|
||||
if (obj->body.otype == uris.patch_Set)
|
||||
{
|
||||
// Get the property and value of the set message
|
||||
const LV2_Atom *property = nullptr;
|
||||
const LV2_Atom *value = nullptr;
|
||||
|
||||
lv2_atom_object_get(
|
||||
obj,
|
||||
uris.patch_property, &property,
|
||||
uris.patch_value, &value,
|
||||
0);
|
||||
if (property != nullptr && value != nullptr && property->type == uris.atom_URID)
|
||||
{
|
||||
LV2_URID propertyUrid = ((LV2_Atom_URID*)property)->body;
|
||||
if (pNotifyCallbacks->WantsAtomOutput(instanceId,propertyUrid))
|
||||
{
|
||||
auto json = AtomToJson(value);
|
||||
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,propertyUrid,json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (command == RingBufferCommand::FreeVuSubscriptions)
|
||||
@@ -1083,7 +1132,7 @@ public:
|
||||
{
|
||||
EffectReplacedBody body;
|
||||
hostReader.read(&body);
|
||||
OnActivePedalBoardReleased(body.oldEffect);
|
||||
OnActivePedalboardReleased(body.oldEffect);
|
||||
}
|
||||
else if (command == RingBufferCommand::AudioStopped)
|
||||
{
|
||||
@@ -1218,45 +1267,45 @@ public:
|
||||
{
|
||||
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
|
||||
}
|
||||
void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard)
|
||||
void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
|
||||
{
|
||||
if (pPedalBoard)
|
||||
if (pPedalboard)
|
||||
{
|
||||
pPedalBoard->Deactivate();
|
||||
pPedalboard->Deactivate();
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
for (auto it = activePedalBoards.begin(); it != activePedalBoards.end(); ++it)
|
||||
for (auto it = activePedalboards.begin(); it != activePedalboards.end(); ++it)
|
||||
{
|
||||
if ((*it).get() == pPedalBoard)
|
||||
if ((*it).get() == pPedalboard)
|
||||
{
|
||||
// erase it, relinquishing shared_ptr ownership, usually deleting the object.
|
||||
activePedalBoards.erase(it);
|
||||
activePedalboards.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard)
|
||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
this->currentPedalBoard = pedalBoard;
|
||||
this->currentPedalboard = pedalboard;
|
||||
if (active)
|
||||
{
|
||||
pedalBoard->Activate();
|
||||
this->activePedalBoards.push_back(pedalBoard);
|
||||
hostWriter.ReplaceEffect(pedalBoard.get());
|
||||
pedalboard->Activate();
|
||||
this->activePedalboards.push_back(pedalboard);
|
||||
hostWriter.ReplaceEffect(pedalboard.get());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void SetBypass(uint64_t instanceId, bool enabled)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (active && this->currentPedalBoard)
|
||||
if (active && this->currentPedalboard)
|
||||
{
|
||||
// use indices not instance ids, so we can just do a straight array index on the audio thread.
|
||||
auto index = currentPedalBoard->GetIndexOfInstanceId(instanceId);
|
||||
auto index = currentPedalboard->GetIndexOfInstanceId(instanceId);
|
||||
if (index >= 0)
|
||||
{
|
||||
hostWriter.SetBypass((uint32_t)index, enabled);
|
||||
@@ -1267,15 +1316,15 @@ public:
|
||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (active && this->currentPedalBoard)
|
||||
if (active && this->currentPedalboard)
|
||||
{
|
||||
auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId);
|
||||
auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId);
|
||||
if (effectIndex != -1)
|
||||
{
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
const ControlValue &value = values[i];
|
||||
int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, value.key());
|
||||
int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, value.key());
|
||||
if (controlIndex != -1 && effectIndex != -1)
|
||||
{
|
||||
hostWriter.SetControlValue(effectIndex, controlIndex, value.value());
|
||||
@@ -1288,11 +1337,11 @@ public:
|
||||
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (active && this->currentPedalBoard)
|
||||
if (active && this->currentPedalboard)
|
||||
{
|
||||
// use indices not instance ids, so we can just do a straight array index on the audio thread.
|
||||
int controlIndex = this->currentPedalBoard->GetControlIndex(instanceId, symbol);
|
||||
auto effectIndex = currentPedalBoard->GetIndexOfInstanceId(instanceId);
|
||||
int controlIndex = this->currentPedalboard->GetControlIndex(instanceId, symbol);
|
||||
auto effectIndex = currentPedalboard->GetIndexOfInstanceId(instanceId);
|
||||
|
||||
if (controlIndex != -1 && effectIndex != -1)
|
||||
{
|
||||
@@ -1301,11 +1350,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
if (active && this->currentPedalBoard)
|
||||
if (active && this->currentPedalboard)
|
||||
{
|
||||
|
||||
if (instanceIds.size() == 0)
|
||||
@@ -1319,13 +1371,13 @@ public:
|
||||
for (size_t i = 0; i < instanceIds.size(); ++i)
|
||||
{
|
||||
int64_t instanceId = instanceIds[i];
|
||||
auto effect = this->currentPedalBoard->GetEffect(instanceId);
|
||||
auto effect = this->currentPedalboard->GetEffect(instanceId);
|
||||
if (!effect)
|
||||
{
|
||||
throw PiPedalStateException("Effect not found.");
|
||||
}
|
||||
|
||||
int index = this->currentPedalBoard->GetIndexOfInstanceId(instanceIds[i]);
|
||||
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
|
||||
vuConfig->enabledIndexes.push_back(index);
|
||||
VuUpdate v;
|
||||
v.instanceId_ = instanceId;
|
||||
@@ -1346,8 +1398,8 @@ public:
|
||||
{
|
||||
RealtimeMonitorPortSubscription result;
|
||||
result.subscriptionHandle = subscription.subscriptionHandle;
|
||||
result.instanceIndex = this->currentPedalBoard->GetIndexOfInstanceId(subscription.instanceid);
|
||||
IEffect *pEffect = this->currentPedalBoard->GetEffect(subscription.instanceid);
|
||||
result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid);
|
||||
IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid);
|
||||
|
||||
result.portIndex = pEffect->GetControlIndex(subscription.key);
|
||||
result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval);
|
||||
@@ -1360,7 +1412,7 @@ public:
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
if (this->currentPedalBoard == nullptr)
|
||||
if (this->currentPedalboard == nullptr)
|
||||
return;
|
||||
if (subscriptions.size() == 0)
|
||||
{
|
||||
@@ -1372,7 +1424,7 @@ public:
|
||||
|
||||
for (size_t i = 0; i < subscriptions.size(); ++i)
|
||||
{
|
||||
if (this->currentPedalBoard->GetEffect(subscriptions[i].instanceid) != nullptr)
|
||||
if (this->currentPedalboard->GetEffect(subscriptions[i].instanceid) != nullptr)
|
||||
{
|
||||
pSubscriptions->subscriptions.push_back(
|
||||
MakeRealtimeSubscription(subscriptions[i]));
|
||||
@@ -1383,6 +1435,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
virtual void UpdatePluginStates(Pedalboard& pedalboard);
|
||||
void UpdatePluginState(PedalboardItem &pedalboardItem);
|
||||
|
||||
class RestartThread
|
||||
{
|
||||
AudioHostImpl *this_;
|
||||
@@ -1478,12 +1534,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void getRealtimeParameter(RealtimeParameterRequest *pParameterRequest)
|
||||
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest)
|
||||
{
|
||||
if (!active)
|
||||
{
|
||||
pParameterRequest->errorMessage = "Not active.";
|
||||
pParameterRequest->onJackRequestComplete(pParameterRequest);
|
||||
pParameterRequest->onPatchRequestComplete(pParameterRequest);
|
||||
return;
|
||||
}
|
||||
this->hostWriter.ParameterRequest(pParameterRequest);
|
||||
@@ -1560,145 +1616,6 @@ static std::string UriToFieldName(const std::string &uri)
|
||||
return uri.substr(pos + 1);
|
||||
}
|
||||
|
||||
void AudioHostImpl::WriteAtom(json_writer &writer, LV2_Atom *pAtom)
|
||||
{
|
||||
if (pAtom->type == uris.atom_Blank)
|
||||
{
|
||||
writer.write_raw("null");
|
||||
}
|
||||
else if (pAtom->type == uris.atom_float)
|
||||
{
|
||||
writer.write(
|
||||
((LV2_Atom_Float *)pAtom)->body);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Int)
|
||||
{
|
||||
writer.write(
|
||||
((LV2_Atom_Int *)pAtom)->body);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Long)
|
||||
{
|
||||
writer.write(
|
||||
((LV2_Atom_Long *)pAtom)->body);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Double)
|
||||
{
|
||||
writer.write(
|
||||
((LV2_Atom_Double *)pAtom)->body);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Bool)
|
||||
{
|
||||
writer.write(
|
||||
((LV2_Atom_Bool *)pAtom)->body);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_String)
|
||||
{
|
||||
const char *p = (((const char *)pAtom) + sizeof(LV2_Atom_String));
|
||||
writer.write(
|
||||
p);
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Vector)
|
||||
{
|
||||
LV2_Atom_Vector *pVector = (LV2_Atom_Vector *)pAtom;
|
||||
writer.start_array();
|
||||
{
|
||||
size_t n = (pAtom->size - sizeof(pVector->body)) / pVector->body.child_size;
|
||||
char *pItems = ((char *)pAtom) + sizeof(LV2_Atom_Vector);
|
||||
if (pVector->body.child_type == uris.atom_float)
|
||||
{
|
||||
float *p = (float *)pItems;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
writer.write_raw(",");
|
||||
writer.write(*p++);
|
||||
}
|
||||
}
|
||||
else if (pVector->body.child_type == uris.atom_Int)
|
||||
{
|
||||
int32_t *p = (int32_t *)pItems;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
writer.write_raw(",");
|
||||
writer.write(*p++);
|
||||
}
|
||||
}
|
||||
else if (pVector->body.child_type == uris.atom_Long)
|
||||
{
|
||||
int64_t *p = (int64_t *)pItems;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
writer.write_raw(",");
|
||||
writer.write(*p++);
|
||||
}
|
||||
}
|
||||
else if (pVector->body.child_type == uris.atom_Double)
|
||||
{
|
||||
double *p = (double *)pItems;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
writer.write_raw(",");
|
||||
writer.write(*p++);
|
||||
}
|
||||
}
|
||||
else if (pVector->body.child_type == uris.atom_Bool)
|
||||
{
|
||||
bool *p = (bool *)pItems;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
writer.write_raw(",");
|
||||
writer.write(*p++);
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.end_array();
|
||||
}
|
||||
else if (pAtom->type == uris.atom_Object)
|
||||
{
|
||||
writer.start_object();
|
||||
|
||||
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom;
|
||||
bool firstMember = true;
|
||||
if (obj->body.id != 0)
|
||||
{
|
||||
std::string id = pHost->Lv2UriudToString(obj->body.id);
|
||||
writer.write_member("id", id.c_str());
|
||||
firstMember = false;
|
||||
}
|
||||
|
||||
if (obj->body.otype != 0)
|
||||
{
|
||||
std::string type = pHost->Lv2UriudToString(obj->body.otype);
|
||||
writer.write_member("lv2Type", type.c_str());
|
||||
if (!firstMember)
|
||||
{
|
||||
writer.write_raw(",");
|
||||
}
|
||||
firstMember = false;
|
||||
}
|
||||
|
||||
LV2_ATOM_OBJECT_FOREACH(obj, prop)
|
||||
{
|
||||
if (!firstMember)
|
||||
{
|
||||
writer.write_raw(",");
|
||||
}
|
||||
firstMember = false;
|
||||
std::string key = pHost->Lv2UriudToString(prop->key);
|
||||
key = UriToFieldName(key);
|
||||
writer.write(key);
|
||||
writer.write_raw(": ");
|
||||
LV2_Atom *value = &(prop->value);
|
||||
WriteAtom(writer, value);
|
||||
}
|
||||
writer.end_object();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
|
||||
{
|
||||
for (auto i = bindings.begin(); i != bindings.end(); ++i)
|
||||
@@ -1719,6 +1636,30 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost)
|
||||
return new AudioHostImpl(pHost);
|
||||
}
|
||||
|
||||
void AudioHostImpl::UpdatePluginStates(Pedalboard& pedalboard)
|
||||
{
|
||||
auto pedalboardItems = pedalboard.GetAllPlugins();
|
||||
for (PedalboardItem* item : pedalboardItems)
|
||||
{
|
||||
if (!item->isSplit())
|
||||
{
|
||||
UpdatePluginState(*item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
||||
{
|
||||
IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
||||
if (effect != nullptr)
|
||||
{
|
||||
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(JackHostStatus)
|
||||
JSON_MAP_REFERENCE(JackHostStatus, active)
|
||||
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
||||
|
||||
+67
-15
@@ -21,13 +21,15 @@
|
||||
|
||||
#include "JackConfiguration.hpp"
|
||||
|
||||
#include "Lv2PedalBoard.hpp"
|
||||
#include "Lv2Pedalboard.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include "json.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "JackServerSettings.hpp"
|
||||
#include <functional>
|
||||
#include "PiPedalAlsa.hpp"
|
||||
#include "Promise.hpp"
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
@@ -45,39 +47,86 @@ public:
|
||||
int64_t subscriptionHandle;
|
||||
float value;
|
||||
};
|
||||
class RealtimeParameterRequest {
|
||||
class RealtimePatchPropertyRequest {
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t instanceId;
|
||||
LV2_URID uridUri;
|
||||
|
||||
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete;
|
||||
enum class RequestType {
|
||||
PatchGet,
|
||||
PatchSet
|
||||
};
|
||||
RequestType requestType;
|
||||
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestComplete;
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess;
|
||||
std::function<void(const std::string &error)> onError;
|
||||
|
||||
const char*errorMessage = nullptr;
|
||||
int responseLength = 0;
|
||||
uint8_t response[2048];
|
||||
std::string jsonResponse;
|
||||
|
||||
RealtimeParameterRequest *pNext = nullptr;
|
||||
RealtimePatchPropertyRequest *pNext = nullptr;
|
||||
|
||||
void SetSize(size_t size)
|
||||
{
|
||||
responseLength = size;
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
longAtomBuffer.resize(size);
|
||||
}
|
||||
}
|
||||
size_t GetSize() const { return responseLength; }
|
||||
uint8_t *GetBuffer() {
|
||||
if (responseLength > sizeof(atomBuffer))
|
||||
{
|
||||
return &(longAtomBuffer[0]);
|
||||
}
|
||||
return atomBuffer;
|
||||
}
|
||||
private:
|
||||
int responseLength = 0;
|
||||
uint8_t atomBuffer[2048];
|
||||
std::vector<uint8_t> longAtomBuffer;
|
||||
|
||||
public:
|
||||
RealtimeParameterRequest(
|
||||
std::function<void (RealtimeParameterRequest*)> onJackRequestComplete_,
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_)
|
||||
: onJackRequestComplete(onJackRequestComplete_),
|
||||
std::function<void(const std::string &error)> onError_
|
||||
)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
{
|
||||
|
||||
requestType = RequestType::PatchGet;
|
||||
}
|
||||
RealtimePatchPropertyRequest(
|
||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
||||
int64_t clientId_,
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
LV2_Atom*atomValue,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_
|
||||
)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
{
|
||||
requestType = RequestType::PatchSet;
|
||||
size_t size = atomValue->size+ sizeof(LV2_Atom);
|
||||
SetSize(size);
|
||||
memcpy(GetBuffer(),atomValue,size);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -95,11 +144,13 @@ public:
|
||||
|
||||
class IAudioHostCallbacks {
|
||||
public:
|
||||
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0;
|
||||
virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
|
||||
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
|
||||
@@ -143,6 +194,8 @@ public:
|
||||
virtual void SetListenForMidiEvent(bool listen) = 0;
|
||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||
|
||||
virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0;
|
||||
|
||||
|
||||
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
|
||||
virtual void Close() = 0;
|
||||
@@ -151,7 +204,7 @@ public:
|
||||
|
||||
virtual JackConfiguration GetServerConfiguration() = 0;
|
||||
|
||||
virtual void SetPedalBoard(const std::shared_ptr<Lv2PedalBoard> &pedalBoard) = 0;
|
||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||
|
||||
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
|
||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> & values) = 0;
|
||||
@@ -164,12 +217,11 @@ public:
|
||||
|
||||
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
|
||||
|
||||
virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
|
||||
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest*pParameterRequest) = 0;
|
||||
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
||||
|
||||
virtual JackHostStatus getJackStatus() = 0;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
+30
-17
@@ -32,24 +32,23 @@ namespace pipedal
|
||||
class AutoLilvNode
|
||||
{
|
||||
|
||||
// const LilvNode* returns must not be freed, by convention.
|
||||
private:
|
||||
LilvNode *node = nullptr;
|
||||
AutoLilvNode(const LilvNode *node) = delete;
|
||||
|
||||
public:
|
||||
AutoLilvNode()
|
||||
{
|
||||
}
|
||||
AutoLilvNode(const LilvNode *node)
|
||||
: node(const_cast<LilvNode*>(node))
|
||||
{
|
||||
isConst = true;
|
||||
}
|
||||
AutoLilvNode(LilvNode *node)
|
||||
: node(node)
|
||||
{
|
||||
isConst = false;
|
||||
}
|
||||
|
||||
|
||||
~AutoLilvNode() { Free(); }
|
||||
|
||||
|
||||
operator const LilvNode *()
|
||||
{
|
||||
return this->node;
|
||||
@@ -58,18 +57,30 @@ namespace pipedal
|
||||
{
|
||||
return this->node != nullptr;
|
||||
}
|
||||
LilvNode*&Get() { return node; }
|
||||
const LilvNode *Get() { return node; }
|
||||
|
||||
AutoLilvNode&operator=(LilvNode*node) {
|
||||
Free();
|
||||
this->node = node;
|
||||
return *this;
|
||||
}
|
||||
AutoLilvNode&operator=(AutoLilvNode&&other) {
|
||||
std::swap(this->node,other.node);
|
||||
AutoLilvNode &operator=(LilvNode *node)
|
||||
{
|
||||
Free();
|
||||
this->node = node;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator LilvNode**() {
|
||||
Free();
|
||||
this->isConst = false;
|
||||
return &node;
|
||||
}
|
||||
operator const LilvNode**() {
|
||||
Free();
|
||||
this->isConst = true;
|
||||
return (const LilvNode**)&node;
|
||||
}
|
||||
AutoLilvNode &operator=(AutoLilvNode &&other)
|
||||
{
|
||||
std::swap(this->node, other.node);
|
||||
return *this;
|
||||
}
|
||||
|
||||
float AsFloat(float defaultValue = 0);
|
||||
int AsInt(int defaultValue = 0);
|
||||
@@ -79,11 +90,13 @@ namespace pipedal
|
||||
|
||||
void Free()
|
||||
{
|
||||
if (node != nullptr)
|
||||
if (node != nullptr && !isConst)
|
||||
lilv_node_free(node);
|
||||
node = nullptr;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
LilvNode *node = nullptr;
|
||||
bool isConst = true;
|
||||
};
|
||||
};
|
||||
+5
-5
@@ -20,7 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "PiPedalException.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
|
||||
class BankFileEntry {
|
||||
int64_t instanceId_;
|
||||
PedalBoard preset_;
|
||||
Pedalboard preset_;
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER_REF(preset);
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
}
|
||||
this->nextInstanceId_ = t;
|
||||
}
|
||||
int64_t addPreset(const PedalBoard&preset, int64_t afterItem = -1)
|
||||
int64_t addPreset(const Pedalboard&preset, int64_t afterItem = -1)
|
||||
{
|
||||
if (hasName(preset.name()))
|
||||
{
|
||||
@@ -213,8 +213,8 @@ public:
|
||||
} else {
|
||||
// zero length? We can never have a zero-length bank.
|
||||
// Add a default preset and make it the selected preset.
|
||||
PedalBoard pedalBoard = PedalBoard::MakeDefault();
|
||||
this->addPreset(pedalBoard);
|
||||
Pedalboard pedalboard = Pedalboard::MakeDefault();
|
||||
this->addPreset(pedalboard);
|
||||
newSelection = presets_[0]->instanceId();
|
||||
}
|
||||
if (instanceId == this->selectedPreset_)
|
||||
|
||||
+144
@@ -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
@@ -14,6 +14,10 @@ include(FindPkgConfig)
|
||||
# Disabled, pending approval of Steinberg VST3 License.
|
||||
# Do not enable unless you have non-GPL3 access to the Steinberg
|
||||
# SDK.
|
||||
#
|
||||
# Plus, there don't seem to be any VST3 plugins for linux, as it
|
||||
# turns out. :-/ Status: not completely implemented due to lack
|
||||
# of test targets. Deprecated, and non-functional.
|
||||
#################################################################
|
||||
|
||||
set (ENABLE_VST3 0)
|
||||
@@ -137,10 +141,20 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
AutoLilvNode.hpp
|
||||
AutoLilvNode.cpp
|
||||
util.hpp util.cpp
|
||||
PiPedalUI.hpp
|
||||
PiPedalUI.cpp
|
||||
MapPathFeature.hpp
|
||||
MapPathFeature.cpp
|
||||
IHost.hpp
|
||||
StateInterface.hpp
|
||||
StateInterface.cpp
|
||||
AtomConverter.hpp
|
||||
AtomConverter.cpp
|
||||
AtomBuffer.hpp
|
||||
|
||||
AutoLilvNode.hpp
|
||||
AutoLilvNode.cpp
|
||||
RtInversionGuard.hpp
|
||||
CpuUse.hpp CpuUse.cpp
|
||||
P2pConfigFiles.hpp
|
||||
@@ -162,13 +176,13 @@ set (PIPEDAL_SOURCES
|
||||
|
||||
RequestHandler.hpp json.cpp json.hpp
|
||||
json_variant.hpp json_variant.cpp
|
||||
Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp
|
||||
Scratch.cpp PluginHost.hpp PluginHost.cpp
|
||||
PluginType.hpp PluginType.cpp
|
||||
Lv2Log.hpp Lv2Log.cpp
|
||||
PiPedalSocket.hpp PiPedalSocket.cpp
|
||||
PiPedalVersion.hpp PiPedalVersion.cpp
|
||||
PiPedalModel.hpp PiPedalModel.cpp
|
||||
PedalBoard.hpp PedalBoard.cpp
|
||||
Pedalboard.hpp Pedalboard.cpp
|
||||
Presets.hpp Presets.cpp
|
||||
Storage.hpp Storage.cpp
|
||||
Banks.hpp Banks.cpp
|
||||
@@ -176,7 +190,7 @@ set (PIPEDAL_SOURCES
|
||||
JackConfiguration.hpp JackConfiguration.cpp
|
||||
defer.hpp
|
||||
Lv2Effect.cpp Lv2Effect.hpp
|
||||
Lv2PedalBoard.cpp Lv2PedalBoard.hpp
|
||||
Lv2Pedalboard.cpp Lv2Pedalboard.hpp
|
||||
BufferPool.hpp
|
||||
SplitEffect.hpp SplitEffect.cpp
|
||||
RingBufferReader.hpp
|
||||
@@ -219,7 +233,7 @@ include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
|
||||
|
||||
set (PIPEDAL_INCLUDES
|
||||
${LV2DEV_INCLUDE_DIRS}
|
||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS}
|
||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS}
|
||||
${VST3_INCLUDES}
|
||||
.
|
||||
)
|
||||
@@ -261,7 +275,10 @@ target_link_libraries(pipedald PRIVATE
|
||||
#################################
|
||||
add_executable(pipedaltest testMain.cpp
|
||||
jsonTest.cpp
|
||||
AlsaDriverTest.cpp
|
||||
json_variant.cpp
|
||||
json_variant.hpp
|
||||
|
||||
AlsaDriverTest.cpp
|
||||
AvahiServiceTest.cpp
|
||||
WifiChannelsTest.cpp
|
||||
PiPedalAlsaTest.cpp
|
||||
@@ -277,6 +294,36 @@ add_executable(pipedaltest testMain.cpp
|
||||
MemDebug.hpp
|
||||
)
|
||||
|
||||
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
|
||||
)
|
||||
|
||||
add_executable(jsonTest
|
||||
testMain.cpp
|
||||
jsonTest.cpp
|
||||
json.hpp
|
||||
json.cpp
|
||||
json_variant.cpp
|
||||
json_variant.hpp
|
||||
MapFeature.cpp
|
||||
MapFeature.hpp
|
||||
HtmlHelper.cpp
|
||||
HtmlHelper.hpp
|
||||
AtomConverter.hpp
|
||||
AtomConverter.cpp
|
||||
AtomConverterTest.cpp
|
||||
AtomBuffer.hpp
|
||||
Promise.hpp
|
||||
PromiseTest.cpp
|
||||
)
|
||||
target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES}
|
||||
)
|
||||
|
||||
target_link_libraries(jsonTest PRIVATE pthread)
|
||||
add_test(NAME jsonTest COMMAND jsonTest)
|
||||
|
||||
|
||||
|
||||
|
||||
include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
|
||||
|
||||
if(${USE_PCH})
|
||||
@@ -539,6 +586,8 @@ target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs
|
||||
)
|
||||
|
||||
add_executable(pipedal_latency_test
|
||||
util.hpp
|
||||
util.cpp
|
||||
PrettyPrinter.hpp
|
||||
CommandLineParser.hpp
|
||||
PiLatencyMain.cpp
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ void StartService(bool excludeShutdownService = false)
|
||||
}
|
||||
#if INSTALL_JACK_SERVICE
|
||||
JackServerSettings serverSettings;
|
||||
serverSettings.ReadJackConfiguration();
|
||||
serverSettings.ReadJackDaemonConfiguration();
|
||||
if (serverSettings.IsValid())
|
||||
{
|
||||
if (sysExec(SYSTEMCTL_BIN " start " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
||||
|
||||
+5
-5
@@ -20,10 +20,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <lv2/urid.lv2/urid.h>
|
||||
#include <lv2/atom.lv2/atom.h>
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
class RealtimeParameterRequest;
|
||||
class RealtimePatchPropertyRequest;
|
||||
class RealtimeRingBufferWriter;
|
||||
class Lv2PluginState;
|
||||
|
||||
class IEffect {
|
||||
public:
|
||||
@@ -35,15 +38,11 @@ namespace pipedal {
|
||||
virtual void SetBypass(bool enable) = 0;
|
||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||
|
||||
|
||||
|
||||
virtual int GetNumberOfInputAudioPorts() const = 0;
|
||||
virtual int GetNumberOfOutputAudioPorts() const = 0;
|
||||
virtual float *GetAudioInputBuffer(int index) const = 0;
|
||||
virtual float *GetAudioOutputBuffer(int index) const = 0;
|
||||
virtual void ResetAtomBuffers() = 0;
|
||||
virtual void RequestParameter(LV2_URID uridUri) = 0;
|
||||
virtual void GatherParameter(RealtimeParameterRequest*pRequest) = 0;
|
||||
|
||||
//virtual std::string AtomToJson(uint8_t*pAtom) = 0;
|
||||
//virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
|
||||
@@ -59,5 +58,6 @@ namespace pipedal {
|
||||
virtual void Deactivate() = 0;
|
||||
|
||||
virtual bool IsVst3() const = 0;
|
||||
virtual bool GetLv2State(Lv2PluginState*state) = 0;
|
||||
};
|
||||
} //namespace
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -297,6 +297,7 @@ JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(JackConfiguration)
|
||||
JSON_MAP_REFERENCE(JackConfiguration,isValid)
|
||||
JSON_MAP_REFERENCE(JackConfiguration,isOnboarding)
|
||||
JSON_MAP_REFERENCE(JackConfiguration,isRestarting)
|
||||
JSON_MAP_REFERENCE(JackConfiguration,errorStatus)
|
||||
JSON_MAP_REFERENCE(JackConfiguration,sampleRate)
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace pipedal
|
||||
std::string errorStatus_;
|
||||
|
||||
bool isRestarting_ = false;
|
||||
bool isOnboarding_ = true;
|
||||
|
||||
uint32_t sampleRate_ = 48000;
|
||||
size_t blockLength_ = 1024;
|
||||
@@ -54,13 +55,16 @@ namespace pipedal
|
||||
void JackInitialize(); // from jack server instance.
|
||||
~JackConfiguration();
|
||||
bool isValid() const { return isValid_;}
|
||||
bool isOnboarding() const { return isOnboarding_;}
|
||||
void isOnboarding(bool value) { isOnboarding_ = value; }
|
||||
bool isRestarting() const { return isRestarting_; }
|
||||
void SetIsRestarting(bool value) { isRestarting_ = value; }
|
||||
uint32_t GetSampleRate() const { return sampleRate_; }
|
||||
size_t GetBlockLength() const { return blockLength_; }
|
||||
size_t GetMidiBufferSize() const { return midiBufferSize_;}
|
||||
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
|
||||
void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; }
|
||||
void isRestarting(bool value) { isRestarting_ = value; }
|
||||
|
||||
uint32_t sampleRate() const { return sampleRate_; }
|
||||
size_t blockLength() const { return blockLength_; }
|
||||
size_t midiBufferSize() const { return midiBufferSize_;}
|
||||
double maxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
|
||||
void setErrorStatus(const std::string&message) { this->errorStatus_ = message; }
|
||||
|
||||
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
|
||||
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
||||
|
||||
@@ -108,8 +108,11 @@ static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::
|
||||
}
|
||||
|
||||
|
||||
void JackServerSettings::ReadJackConfiguration()
|
||||
void JackServerSettings::ReadJackDaemonConfiguration()
|
||||
{
|
||||
#if !JACK_HOST
|
||||
return;
|
||||
#endif
|
||||
this->valid_ = false;
|
||||
|
||||
std::string lastLine;
|
||||
@@ -155,7 +158,7 @@ void JackServerSettings::ReadJackConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
void JackServerSettings::Write()
|
||||
void JackServerSettings::WriteDaemonConfig()
|
||||
{
|
||||
#if JACK_HOST
|
||||
this->valid_ = false;
|
||||
@@ -240,6 +243,7 @@ void JackServerSettings::Write()
|
||||
|
||||
JSON_MAP_BEGIN(JackServerSettings)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, valid)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
|
||||
|
||||
+18
-11
@@ -28,6 +28,7 @@ namespace pipedal
|
||||
class JackServerSettings
|
||||
{
|
||||
bool valid_ = false;
|
||||
bool isOnboarding_ = true;
|
||||
bool isJackAudio_ = JACK_HOST ? true : false;
|
||||
bool rebootRequired_ = false;
|
||||
std::string alsaDevice_;
|
||||
@@ -44,7 +45,8 @@ namespace pipedal
|
||||
alsaDevice_(alsaInputDevice),
|
||||
sampleRate_(sampleRate),
|
||||
bufferSize_(bufferSize),
|
||||
numberOfBuffers_(numberOfBuffers)
|
||||
numberOfBuffers_(numberOfBuffers),
|
||||
isOnboarding_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -53,23 +55,28 @@ namespace pipedal
|
||||
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
|
||||
const std::string &GetAlsaInputDevice() const { return alsaDevice_; }
|
||||
|
||||
void ReadJackConfiguration();
|
||||
void ReadJackDaemonConfiguration();
|
||||
|
||||
bool IsValid() const { return valid_; }
|
||||
|
||||
JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
||||
{
|
||||
this->valid_ = true;
|
||||
this->rebootRequired_ = true;
|
||||
this->sampleRate_ = sampleRate;
|
||||
this->bufferSize_ = bufferSize;
|
||||
this->numberOfBuffers_ = numberOfBuffers;
|
||||
}
|
||||
void Write(); // requires root perms.
|
||||
// JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
||||
// {
|
||||
// this->valid_ = true;
|
||||
// this->rebootRequired_ = true;
|
||||
// this->sampleRate_ = sampleRate;
|
||||
// this->bufferSize_ = bufferSize;
|
||||
// this->numberOfBuffers_ = numberOfBuffers;
|
||||
// }
|
||||
void WriteDaemonConfig(); // requires root perms.
|
||||
void SetRebootRequired(bool value)
|
||||
{
|
||||
rebootRequired_ = value;
|
||||
}
|
||||
void SetIsOnboarding(bool value)
|
||||
{
|
||||
isOnboarding_ = value;
|
||||
}
|
||||
|
||||
bool Equals(const JackServerSettings &other)
|
||||
{
|
||||
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
|
||||
|
||||
+111
-57
@@ -32,6 +32,7 @@
|
||||
#include "lv2/log.lv2/logger.h"
|
||||
#include "lv2/uri-map.lv2/uri-map.h"
|
||||
#include "lv2/atom.lv2/forge.h"
|
||||
#include "lv2/state.lv2/state.h"
|
||||
#include "lv2/worker.lv2/worker.h"
|
||||
#include "lv2/patch.lv2/patch.h"
|
||||
#include "lv2/parameters.lv2/parameters.h"
|
||||
@@ -48,14 +49,14 @@ const float BYPASS_TIME_S = 0.1f;
|
||||
Lv2Effect::Lv2Effect(
|
||||
IHost *pHost_,
|
||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||
const PedalBoardItem &pedalBoardItem)
|
||||
: pHost(pHost_), pInstance(nullptr), info(info_), uris(pHost)
|
||||
const PedalboardItem &pedalboardItem)
|
||||
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost)
|
||||
{
|
||||
auto pWorld = pHost_->getWorld();
|
||||
|
||||
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
|
||||
|
||||
this->bypass = pedalBoardItem.isEnabled();
|
||||
this->bypass = pedalboardItem.isEnabled();
|
||||
|
||||
// initialize the atom forge used on the realtime thread.
|
||||
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
||||
@@ -64,7 +65,7 @@ Lv2Effect::Lv2Effect(
|
||||
|
||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
||||
|
||||
auto uriNode = lilv_new_uri(pWorld, pedalBoardItem.uri().c_str());
|
||||
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
|
||||
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
||||
lilv_node_free(uriNode);
|
||||
|
||||
@@ -76,7 +77,7 @@ Lv2Effect::Lv2Effect(
|
||||
}
|
||||
|
||||
this->work_schedule_feature = nullptr;
|
||||
if (info_->hasExtension(LV2_WORKER__interface))
|
||||
if (true) //info_->hasExtension(LV2_WORKER__interface))
|
||||
{
|
||||
// insane implementation. :-(
|
||||
LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule));
|
||||
@@ -99,29 +100,36 @@ Lv2Effect::Lv2Effect(
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
this->pInstance = nullptr;
|
||||
throw PiPedalException(SS("Plugin threw an exception: " << e.what()));
|
||||
throw PiPedalException(SS("Plugin threw an exception: " << e.what() << " '" << info_->name() << "'"));
|
||||
|
||||
}
|
||||
this->pInstance = pInstance;
|
||||
if (this->pInstance == nullptr)
|
||||
{
|
||||
throw PiPedalException("Failed to create plugin.");
|
||||
throw PiPedalException(SS("Failed to create plugin \'" << info_->name() << "\'."));
|
||||
}
|
||||
|
||||
if (info_->hasExtension(LV2_WORKER__interface))
|
||||
const LV2_Worker_Interface *worker_interface =
|
||||
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||
LV2_WORKER__interface);
|
||||
if (worker_interface) {
|
||||
this->worker = std::make_unique<Worker>(pHost->GetHostWorkerThread(), pInstance, worker_interface);
|
||||
}
|
||||
const LV2_State_Interface*state_interface =
|
||||
(const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||
LV2_STATE__interface);
|
||||
|
||||
if (state_interface)
|
||||
{
|
||||
const LV2_Worker_Interface *worker_interface =
|
||||
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||
LV2_WORKER__interface);
|
||||
this->worker = std::make_unique<Worker>(pInstance, worker_interface);
|
||||
this->stateInterface = std::make_unique<StateInterface>(pHost,pInstance,state_interface);
|
||||
}
|
||||
|
||||
this->instanceId = pedalBoardItem.instanceId();
|
||||
this->instanceId = pedalboardItem.instanceId();
|
||||
|
||||
this->controlValues.resize(info->ports().size());
|
||||
|
||||
// Copy default pedalboard settings.
|
||||
for (auto i = pedalBoardItem.controlValues().begin(); i != pedalBoardItem.controlValues().end(); ++i)
|
||||
for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i)
|
||||
{
|
||||
auto &v = (*i);
|
||||
int index = GetControlIndex(v.key());
|
||||
@@ -132,6 +140,16 @@ Lv2Effect::Lv2Effect(
|
||||
}
|
||||
PreparePortIndices();
|
||||
ConnectControlPorts();
|
||||
|
||||
RestoreState(pedalboardItem);
|
||||
}
|
||||
void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem)
|
||||
{
|
||||
// Restore state if present.
|
||||
if (this->stateInterface)
|
||||
{
|
||||
this->stateInterface->Restore(pedalboardItem.lv2State());
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::ConnectControlPorts()
|
||||
@@ -262,6 +280,7 @@ Lv2Effect::~Lv2Effect()
|
||||
{
|
||||
if (worker)
|
||||
{
|
||||
worker->Close();
|
||||
worker = nullptr; // delete the worker first!
|
||||
}
|
||||
if (pInstance)
|
||||
@@ -281,8 +300,11 @@ void Lv2Effect::Activate()
|
||||
this->AssignUnconnectedPorts();
|
||||
lilv_instance_activate(pInstance);
|
||||
this->BypassTo(this->bypass ? 1.0f : 0.0f);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Lv2Effect::AssignUnconnectedPorts()
|
||||
{
|
||||
for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i)
|
||||
@@ -332,6 +354,10 @@ void Lv2Effect::AssignUnconnectedPorts()
|
||||
}
|
||||
void Lv2Effect::Deactivate()
|
||||
{
|
||||
if (worker)
|
||||
{
|
||||
worker->Close();
|
||||
}
|
||||
lilv_instance_deactivate(pInstance);
|
||||
}
|
||||
|
||||
@@ -441,7 +467,7 @@ void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBuffe
|
||||
}
|
||||
|
||||
}
|
||||
RelayOutputMessages(this->instanceId,realtimeRingBufferWriter);
|
||||
RelayPatchSetMessages(this->instanceId,realtimeRingBufferWriter);
|
||||
}
|
||||
|
||||
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
|
||||
@@ -463,13 +489,13 @@ void Lv2Effect::ResetInputAtomBuffer(char *data)
|
||||
{
|
||||
BufferHeader *header = (BufferHeader *)data;
|
||||
header->size = sizeof(LV2_Atom_Sequence_Body);
|
||||
header->type = uris.atom_Sequence;
|
||||
header->type = urids.atom__Sequence;
|
||||
}
|
||||
void Lv2Effect::ResetOutputAtomBuffer(char *data)
|
||||
{
|
||||
BufferHeader *header = (BufferHeader *)data;
|
||||
header->size = pHost->GetAtomBufferSize() - 8;
|
||||
header->type = uris.atom_Chunk;
|
||||
header->type = urids.atom__Chunk;
|
||||
}
|
||||
|
||||
void Lv2Effect::BypassTo(float targetValue)
|
||||
@@ -509,24 +535,41 @@ void Lv2Effect::ResetAtomBuffers()
|
||||
|
||||
// Start a sequence in the notify input port.
|
||||
|
||||
lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, uris.unitsFrame);
|
||||
lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, urids.units__frame);
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::RequestParameter(LV2_URID uridUri)
|
||||
void Lv2Effect::RequestPatchProperty(LV2_URID uridUri)
|
||||
{
|
||||
lv2_atom_forge_frame_time(&inputForgeRt, 0);
|
||||
|
||||
LV2_Atom_Forge_Frame objectFrame;
|
||||
LV2_Atom_Forge_Ref set =
|
||||
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, uris.patch_Get);
|
||||
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Get);
|
||||
|
||||
lv2_atom_forge_key(&inputForgeRt, uris.patch_property);
|
||||
lv2_atom_forge_key(&inputForgeRt, urids.patch__property);
|
||||
lv2_atom_forge_urid(&inputForgeRt, uridUri);
|
||||
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
|
||||
}
|
||||
void Lv2Effect::SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value)
|
||||
{
|
||||
lv2_atom_forge_frame_time(&inputForgeRt, 0);
|
||||
|
||||
void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
LV2_Atom_Forge_Frame objectFrame;
|
||||
LV2_Atom_Forge_Ref set =
|
||||
lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, urids.patch__Set);
|
||||
{
|
||||
lv2_atom_forge_key(&inputForgeRt, urids.patch__property);
|
||||
lv2_atom_forge_urid(&inputForgeRt, uridUri);
|
||||
lv2_atom_forge_key(&inputForgeRt, urids.patch__value);
|
||||
lv2_atom_forge_write(&inputForgeRt,value,size);
|
||||
}
|
||||
|
||||
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
|
||||
}
|
||||
|
||||
|
||||
void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
{
|
||||
LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
|
||||
if (controlOutput == nullptr)
|
||||
@@ -534,6 +577,7 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter
|
||||
return;
|
||||
}
|
||||
|
||||
bool stateChanged = false;
|
||||
LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev)
|
||||
{
|
||||
|
||||
@@ -542,63 +586,73 @@ void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter
|
||||
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||
{
|
||||
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
|
||||
if (obj->body.otype != uris.patch_Set) // patch_Set is handled elsewhere.
|
||||
if (obj->body.otype == urids.state__StateChanged)
|
||||
{
|
||||
stateChanged = true;
|
||||
} else if (obj->body.otype == urids.patch__Set) // patch_Set is handled elsewhere.
|
||||
{
|
||||
realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj);
|
||||
}
|
||||
}
|
||||
if (stateChanged)
|
||||
{
|
||||
realtimeRingBufferWriter->Lv2StateChanged(instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest)
|
||||
void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
||||
{
|
||||
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
|
||||
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
|
||||
// frame_offset = ev->time.frames; // not really interested.
|
||||
|
||||
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||
LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer();
|
||||
if (controlInput == nullptr)
|
||||
{
|
||||
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
|
||||
if (obj->body.otype == uris.patch_Set)
|
||||
return;
|
||||
}
|
||||
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||
{
|
||||
|
||||
// frame_offset = ev->time.frames; // not really interested.
|
||||
|
||||
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||
{
|
||||
// Get the property and value of the set message
|
||||
const LV2_Atom *property = NULL;
|
||||
const LV2_Atom *value = NULL;
|
||||
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
|
||||
if (obj->body.otype == urids.patch__Set)
|
||||
{
|
||||
// Get the property and value of the set message
|
||||
const LV2_Atom *property = NULL;
|
||||
const LV2_Atom *value = NULL;
|
||||
|
||||
lv2_atom_object_get(
|
||||
obj,
|
||||
uris.patch_property, &property,
|
||||
uris.patch_value, &value,
|
||||
0);
|
||||
lv2_atom_object_get(
|
||||
obj,
|
||||
urids.patch__property, &property,
|
||||
urids.patch__value, &value,
|
||||
0);
|
||||
|
||||
if (!property)
|
||||
{
|
||||
}
|
||||
else if (property->type != uris.atom_URID)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
|
||||
if (key == pRequest->uridUri)
|
||||
if (property && property->type == urids.atom__URID && value)
|
||||
{
|
||||
int atom_size = value->size + sizeof(LV2_Atom);
|
||||
if (atom_size > sizeof(pRequest->response))
|
||||
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
|
||||
if (key == pRequest->uridUri)
|
||||
{
|
||||
pRequest->errorMessage = "Response is too large.";
|
||||
} else {
|
||||
pRequest->responseLength = atom_size;
|
||||
memcpy(pRequest->response,value,atom_size);
|
||||
int atom_size = value->size + sizeof(LV2_Atom);
|
||||
pRequest->SetSize(atom_size);
|
||||
memcpy(pRequest->GetBuffer(),value,atom_size);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Lv2Effect::GetLv2State(Lv2PluginState*state)
|
||||
{
|
||||
if (!this->stateInterface) return false;
|
||||
|
||||
*state = this->stateInterface->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+65
-59
@@ -20,8 +20,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include <lilv/lilv.h>
|
||||
#include "BufferPool.hpp"
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "lv2/log.lv2/logger.h"
|
||||
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
|
||||
#include "lv2/atom.lv2/forge.h"
|
||||
#include "AtomBuffer.hpp"
|
||||
#include "StateInterface.hpp"
|
||||
|
||||
|
||||
namespace pipedal
|
||||
@@ -42,6 +44,12 @@ namespace pipedal
|
||||
class Lv2Effect : public IEffect
|
||||
{
|
||||
private:
|
||||
|
||||
std::unique_ptr<StateInterface> stateInterface;
|
||||
void RestoreState(const PedalboardItem&pedalboardItem);
|
||||
|
||||
std::map<std::string,AtomBuffer> patchPropertyPrototypes;
|
||||
|
||||
IHost *pHost = nullptr;
|
||||
int numberOfInputs = 0;
|
||||
int numberOfOutputs = 0;
|
||||
@@ -73,6 +81,7 @@ namespace pipedal
|
||||
virtual std::string GetUri() const { return info->uri(); }
|
||||
|
||||
std::vector<const Lv2PortInfo *> realtimePortInfo;
|
||||
|
||||
void PreparePortIndices();
|
||||
void ConnectControlPorts();
|
||||
void AssignUnconnectedPorts();
|
||||
@@ -83,68 +92,62 @@ namespace pipedal
|
||||
LV2_Atom_Forge outputForgeRt;
|
||||
|
||||
|
||||
class Uris
|
||||
class Urids
|
||||
{
|
||||
public:
|
||||
Uris(IHost *pHost)
|
||||
Urids(IHost *pHost)
|
||||
{
|
||||
atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank);
|
||||
atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path);
|
||||
atom_float = pHost->GetLv2Urid(LV2_ATOM__Float);
|
||||
atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double);
|
||||
atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int);
|
||||
atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long);
|
||||
atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
|
||||
atom_String = pHost->GetLv2Urid(LV2_ATOM__String);
|
||||
atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
|
||||
atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object);
|
||||
atom__Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
|
||||
atom__Path = pHost->GetLv2Urid(LV2_ATOM__Path);
|
||||
atom__Float = pHost->GetLv2Urid(LV2_ATOM__Float);
|
||||
atom__Double = pHost->GetLv2Urid(LV2_ATOM__Double);
|
||||
atom__Int = pHost->GetLv2Urid(LV2_ATOM__Int);
|
||||
atom__Long = pHost->GetLv2Urid(LV2_ATOM__Long);
|
||||
atom__Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
|
||||
atom__String = pHost->GetLv2Urid(LV2_ATOM__String);
|
||||
atom__Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
|
||||
atom__Object = pHost->GetLv2Urid(LV2_ATOM__Object);
|
||||
|
||||
|
||||
atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
|
||||
atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
|
||||
atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID);
|
||||
atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer);
|
||||
patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get);
|
||||
patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set);
|
||||
patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put);
|
||||
patch_body = pHost->GetLv2Urid(LV2_PATCH__body);
|
||||
patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject);
|
||||
patch_property = pHost->GetLv2Urid(LV2_PATCH__property);
|
||||
//patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept);
|
||||
patch_value = pHost->GetLv2Urid(LV2_PATCH__value);
|
||||
unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame);
|
||||
atom__Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
|
||||
atom__URID = pHost->GetLv2Urid(LV2_ATOM__URID);
|
||||
patch__Get = pHost->GetLv2Urid(LV2_PATCH__Get);
|
||||
patch__Set = pHost->GetLv2Urid(LV2_PATCH__Set);
|
||||
patch__Put = pHost->GetLv2Urid(LV2_PATCH__Put);
|
||||
patch__body = pHost->GetLv2Urid(LV2_PATCH__body);
|
||||
patch__subject = pHost->GetLv2Urid(LV2_PATCH__subject);
|
||||
patch__property = pHost->GetLv2Urid(LV2_PATCH__property);
|
||||
patch__value = pHost->GetLv2Urid(LV2_PATCH__value);
|
||||
units__frame = pHost->GetLv2Urid(LV2_UNITS__frame);
|
||||
state__StateChanged = pHost->GetLv2Urid(LV2_STATE__StateChanged);
|
||||
|
||||
}
|
||||
//LV2_URID patch_accept;
|
||||
|
||||
LV2_URID unitsFrame;
|
||||
LV2_URID atom__Chunk;
|
||||
LV2_URID units__frame;
|
||||
LV2_URID pluginUri;
|
||||
LV2_URID atom_Blank;
|
||||
LV2_URID atom_Bool;
|
||||
LV2_URID atom_float;
|
||||
LV2_URID atom_Double;
|
||||
LV2_URID atom_Int;
|
||||
LV2_URID atom_Long;
|
||||
LV2_URID atom_String;
|
||||
LV2_URID atom_Object;
|
||||
LV2_URID atom_Vector;
|
||||
LV2_URID atom_Path;
|
||||
LV2_URID atom_Sequence;
|
||||
LV2_URID atom_Chunk;
|
||||
LV2_URID atom_URID;
|
||||
LV2_URID atom_eventTransfer;
|
||||
LV2_URID midi_Event;
|
||||
LV2_URID patch_Get;
|
||||
LV2_URID patch_Set;
|
||||
LV2_URID patch_Put;
|
||||
LV2_URID patch_body;
|
||||
LV2_URID patch_subject;
|
||||
LV2_URID patch_property;
|
||||
LV2_URID patch_value;
|
||||
LV2_URID param_uiState;
|
||||
LV2_URID atom__Bool;
|
||||
LV2_URID atom__Float;
|
||||
LV2_URID atom__Double;
|
||||
LV2_URID atom__Int;
|
||||
LV2_URID atom__Long;
|
||||
LV2_URID atom__String;
|
||||
LV2_URID atom__Object;
|
||||
LV2_URID atom__Vector;
|
||||
LV2_URID atom__Path;
|
||||
LV2_URID atom__Sequence;
|
||||
LV2_URID atom__URID;
|
||||
LV2_URID midi__Event;
|
||||
LV2_URID patch__Get;
|
||||
LV2_URID patch__Set;
|
||||
LV2_URID patch__Put;
|
||||
LV2_URID patch__body;
|
||||
LV2_URID patch__subject;
|
||||
LV2_URID patch__property;
|
||||
LV2_URID patch__value;
|
||||
LV2_URID state__StateChanged;
|
||||
};
|
||||
|
||||
Uris uris;
|
||||
Urids urids;
|
||||
|
||||
uint64_t instanceId;
|
||||
BufferPool bufferPool;
|
||||
@@ -166,11 +169,13 @@ namespace pipedal
|
||||
|
||||
void BypassTo(float value);
|
||||
|
||||
|
||||
virtual void RequestParameter(LV2_URID uridUri);
|
||||
virtual void GatherParameter(RealtimeParameterRequest*pRequest);
|
||||
public:
|
||||
virtual bool GetLv2State(Lv2PluginState*state);
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri);
|
||||
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
|
||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest);
|
||||
virtual bool IsVst3() const { return false; }
|
||||
virtual void RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
|
||||
virtual uint8_t*GetAtomInputBuffer() {
|
||||
if (this->inputAtomBuffers.size() == 0) return nullptr;
|
||||
@@ -188,7 +193,7 @@ namespace pipedal
|
||||
Lv2Effect(
|
||||
IHost *pHost,
|
||||
const std::shared_ptr<Lv2PluginInfo> &info,
|
||||
const PedalBoardItem &pedalBoardItem);
|
||||
const PedalboardItem &pedalboardItem);
|
||||
~Lv2Effect();
|
||||
|
||||
|
||||
@@ -228,6 +233,7 @@ namespace pipedal
|
||||
controlValues[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
virtual float GetControlValue(int index) const {
|
||||
if (index == -1)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "Lv2EventBufferWriter.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -24,17 +24,17 @@
|
||||
#include <string>
|
||||
|
||||
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "MemDebug.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) {
|
||||
TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) {
|
||||
|
||||
MemStats initialMemory = GetMemStats();
|
||||
{
|
||||
PiPedalHost host;
|
||||
PluginHost host;
|
||||
|
||||
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Lv2PedalBoard.hpp"
|
||||
#include "Lv2Pedalboard.hpp"
|
||||
#include "Lv2Effect.hpp"
|
||||
|
||||
|
||||
@@ -27,15 +27,16 @@
|
||||
#include "VuUpdate.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "Lv2EventBufferWriter.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
float *Lv2PedalBoard::CreateNewAudioBuffer()
|
||||
float *Lv2Pedalboard::CreateNewAudioBuffer()
|
||||
{
|
||||
return bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
||||
}
|
||||
|
||||
std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
|
||||
std::vector<float *> Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
|
||||
{
|
||||
std::vector<float *> result;
|
||||
for (int i = 0; i < nChannels; ++i)
|
||||
@@ -45,7 +46,7 @@ std::vector<float *> Lv2PedalBoard::AllocateAudioBuffers(int nChannels)
|
||||
return result;
|
||||
}
|
||||
|
||||
int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
|
||||
int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
|
||||
{
|
||||
for (int i = 0; i < realtimeEffects.size(); ++i)
|
||||
{
|
||||
@@ -57,8 +58,8 @@ int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbo
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
std::vector<float *> Lv2PedalBoard::PrepareItems(
|
||||
std::vector<PedalBoardItem> &items,
|
||||
std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
std::vector<PedalboardItem> &items,
|
||||
std::vector<float *> inputBuffers)
|
||||
{
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
@@ -105,7 +106,14 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
|
||||
else
|
||||
{
|
||||
|
||||
auto pLv2Effect = this->pHost->CreateEffect(item);
|
||||
IEffect* pLv2Effect = nullptr;
|
||||
try {
|
||||
pLv2Effect = this->pHost->CreateEffect(item);
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS(e.what()));
|
||||
}
|
||||
|
||||
if (pLv2Effect)
|
||||
{
|
||||
pEffect = pLv2Effect;
|
||||
@@ -198,57 +206,57 @@ std::vector<float *> Lv2PedalBoard::PrepareItems(
|
||||
return inputBuffers;
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::Prepare(IHost *pHost, PedalBoard &pedalBoard)
|
||||
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
|
||||
{
|
||||
this->pHost = pHost;
|
||||
|
||||
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
|
||||
{
|
||||
this->pedalBoardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
|
||||
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
|
||||
}
|
||||
|
||||
auto outputs = PrepareItems(pedalBoard.items(), this->pedalBoardInputBuffers);
|
||||
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers);
|
||||
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
|
||||
if (nOutputs == 1)
|
||||
{
|
||||
this->pedalBoardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalboardOutputBuffers.push_back(outputs[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (outputs.size() == 1)
|
||||
{
|
||||
this->pedalBoardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalBoardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalboardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalboardOutputBuffers.push_back(outputs[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->pedalBoardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalBoardOutputBuffers.push_back(outputs[1]);
|
||||
this->pedalboardOutputBuffers.push_back(outputs[0]);
|
||||
this->pedalboardOutputBuffers.push_back(outputs[1]);
|
||||
}
|
||||
}
|
||||
PrepareMidiMap(pedalBoard);
|
||||
PrepareMidiMap(pedalboard);
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
|
||||
void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
|
||||
{
|
||||
if (pedalBoardItem.midiBindings().size() != 0)
|
||||
if (pedalboardItem.midiBindings().size() != 0)
|
||||
{
|
||||
auto pluginInfo = pHost->GetPluginInfo(pedalBoardItem.uri());
|
||||
auto pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri());
|
||||
const Lv2PluginInfo *pPluginInfo;
|
||||
if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
|
||||
if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
|
||||
{
|
||||
pPluginInfo = GetSplitterPluginInfo();
|
||||
} else {
|
||||
pPluginInfo = pluginInfo.get();
|
||||
}
|
||||
|
||||
int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId());
|
||||
int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId());
|
||||
|
||||
if (pluginInfo && effectIndex != -1)
|
||||
{
|
||||
for (size_t bindingIndex = 0; bindingIndex < pedalBoardItem.midiBindings().size(); ++bindingIndex)
|
||||
for (size_t bindingIndex = 0; bindingIndex < pedalboardItem.midiBindings().size(); ++bindingIndex)
|
||||
{
|
||||
auto &binding = pedalBoardItem.midiBindings()[bindingIndex];
|
||||
auto &binding = pedalboardItem.midiBindings()[bindingIndex];
|
||||
{
|
||||
const Lv2PortInfo*pPortInfo;
|
||||
int controlIndex;
|
||||
@@ -259,7 +267,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
|
||||
} else {
|
||||
try {
|
||||
pPortInfo = &pluginInfo->getPort(binding.symbol());
|
||||
controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol());
|
||||
controlIndex = this->GetControlIndex(pedalboardItem.instanceId(), binding.symbol());
|
||||
} catch (const std::exception&ignored)
|
||||
{
|
||||
continue;
|
||||
@@ -274,7 +282,7 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
|
||||
mapping.effectIndex = effectIndex;
|
||||
mapping.controlIndex = controlIndex;
|
||||
mapping.midiBinding = binding;
|
||||
mapping.instanceId = pedalBoardItem.instanceId();
|
||||
mapping.instanceId = pedalboardItem.instanceId();
|
||||
if (pPortInfo->IsSwitch())
|
||||
{
|
||||
mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary;
|
||||
@@ -303,20 +311,20 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < pedalBoardItem.topChain().size(); ++i)
|
||||
for (size_t i = 0; i < pedalboardItem.topChain().size(); ++i)
|
||||
{
|
||||
PrepareMidiMap(pedalBoardItem.topChain()[i]);
|
||||
PrepareMidiMap(pedalboardItem.topChain()[i]);
|
||||
}
|
||||
for (size_t i = 0; i < pedalBoardItem.bottomChain().size(); ++i)
|
||||
for (size_t i = 0; i < pedalboardItem.bottomChain().size(); ++i)
|
||||
{
|
||||
PrepareMidiMap(pedalBoardItem.bottomChain()[i]);
|
||||
PrepareMidiMap(pedalboardItem.bottomChain()[i]);
|
||||
}
|
||||
}
|
||||
void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
|
||||
void Lv2Pedalboard::PrepareMidiMap(const Pedalboard &pedalboard)
|
||||
{
|
||||
for (size_t i = 0; i < pedalBoard.items().size(); ++i)
|
||||
for (size_t i = 0; i < pedalboard.items().size(); ++i)
|
||||
{
|
||||
auto &item = pedalBoard.items()[i];
|
||||
auto &item = pedalboard.items()[i];
|
||||
PrepareMidiMap(item);
|
||||
|
||||
auto pluginInfo = pHost->GetPluginInfo(item.uri());
|
||||
@@ -335,14 +343,14 @@ void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard)
|
||||
{ return left.key < right.key; });
|
||||
}
|
||||
}
|
||||
void Lv2PedalBoard::Activate()
|
||||
void Lv2Pedalboard::Activate()
|
||||
{
|
||||
for (int i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
this->realtimeEffects[i]->Activate();
|
||||
}
|
||||
}
|
||||
void Lv2PedalBoard::Deactivate()
|
||||
void Lv2Pedalboard::Deactivate()
|
||||
{
|
||||
for (int i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
@@ -357,46 +365,46 @@ static void Copy(float *input, float *output, uint32_t samples)
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
|
||||
bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
|
||||
{
|
||||
this->ringBufferWriter = ringBufferWriter;
|
||||
for (int i = 0; i < this->pedalBoardInputBuffers.size(); ++i)
|
||||
for (int i = 0; i < this->pedalboardInputBuffers.size(); ++i)
|
||||
{
|
||||
if (inputBuffers[i] == nullptr)
|
||||
return false;
|
||||
Copy(inputBuffers[i], this->pedalBoardInputBuffers[i], samples);
|
||||
Copy(inputBuffers[i], this->pedalboardInputBuffers[i], samples);
|
||||
}
|
||||
for (int i = 0; i < this->processActions.size(); ++i)
|
||||
{
|
||||
processActions[i](samples);
|
||||
}
|
||||
for (int i = 0; i < this->pedalBoardOutputBuffers.size(); ++i)
|
||||
for (int i = 0; i < this->pedalboardOutputBuffers.size(); ++i)
|
||||
{
|
||||
if (outputBuffers[i] == nullptr)
|
||||
return false;
|
||||
Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples);
|
||||
Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex)
|
||||
float Lv2Pedalboard::GetControlOutputValue(int effectIndex, int portIndex)
|
||||
{
|
||||
auto effect = realtimeEffects[effectIndex];
|
||||
return effect->GetOutputControlValue(portIndex);
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::SetControlValue(int effectIndex, int index, float value)
|
||||
void Lv2Pedalboard::SetControlValue(int effectIndex, int index, float value)
|
||||
{
|
||||
auto effect = realtimeEffects[effectIndex];
|
||||
effect->SetControl(index, value);
|
||||
}
|
||||
void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled)
|
||||
void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
|
||||
{
|
||||
auto effect = realtimeEffects[effectIndex];
|
||||
effect->SetBypass(enabled);
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
|
||||
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
|
||||
{
|
||||
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
|
||||
{
|
||||
@@ -429,7 +437,7 @@ void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::ResetAtomBuffers()
|
||||
void Lv2Pedalboard::ResetAtomBuffers()
|
||||
{
|
||||
for (size_t i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
@@ -438,7 +446,7 @@ void Lv2PedalBoard::ResetAtomBuffers()
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests)
|
||||
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
|
||||
{
|
||||
while (pParameterRequests != nullptr)
|
||||
{
|
||||
@@ -446,34 +454,54 @@ void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParamete
|
||||
if (pEffect == nullptr)
|
||||
{
|
||||
pParameterRequests->errorMessage = "No such effect.";
|
||||
}
|
||||
else
|
||||
|
||||
} else if (pEffect->IsVst3())
|
||||
{
|
||||
pEffect->RequestParameter(pParameterRequests->uridUri);
|
||||
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
||||
} else {
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect*>(pEffect);
|
||||
|
||||
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
pLv2Effect->RequestPatchProperty(pParameterRequests->uridUri);
|
||||
} else if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchSet) {
|
||||
pLv2Effect->SetPatchProperty(
|
||||
pParameterRequests->uridUri,
|
||||
pParameterRequests->GetSize(),
|
||||
(LV2_Atom*)pParameterRequests->GetBuffer()
|
||||
);
|
||||
}
|
||||
}
|
||||
pParameterRequests = pParameterRequests->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests)
|
||||
void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
|
||||
{
|
||||
while (pParameterRequests != nullptr)
|
||||
{
|
||||
IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
|
||||
if (effect == nullptr)
|
||||
if (pParameterRequests->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||
{
|
||||
pParameterRequests->errorMessage = "No such effect.";
|
||||
}
|
||||
else
|
||||
{
|
||||
effect->GatherParameter(pParameterRequests);
|
||||
IEffect *effect = this->GetEffect(pParameterRequests->instanceId);
|
||||
if (effect == nullptr)
|
||||
{
|
||||
pParameterRequests->errorMessage = "No such effect.";
|
||||
} else if (effect->IsVst3())
|
||||
{
|
||||
pParameterRequests->errorMessage = "Not supported for VST3";
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect*>(effect);
|
||||
pLv2Effect->GatherPatchProperties(pParameterRequests);
|
||||
}
|
||||
}
|
||||
|
||||
pParameterRequests = pParameterRequests->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback)
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
#include "PedalBoard.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Lv2Effect.hpp"
|
||||
#include "BufferPool.hpp"
|
||||
#include <functional>
|
||||
@@ -31,15 +31,15 @@ namespace pipedal {
|
||||
|
||||
|
||||
class RealtimeVuBuffers;
|
||||
class RealtimeParameterRequest;
|
||||
class RealtimePatchPropertyRequest;
|
||||
class RealtimeRingBufferWriter;
|
||||
|
||||
class Lv2PedalBoard {
|
||||
class Lv2Pedalboard {
|
||||
IHost *pHost = nullptr;
|
||||
|
||||
BufferPool bufferPool;
|
||||
std::vector<float*> pedalBoardInputBuffers;
|
||||
std::vector<float*> pedalBoardOutputBuffers;
|
||||
std::vector<float*> pedalboardInputBuffers;
|
||||
std::vector<float*> pedalboardOutputBuffers;
|
||||
|
||||
std::vector<std::shared_ptr<IEffect> > effects;
|
||||
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
|
||||
@@ -81,21 +81,21 @@ class Lv2PedalBoard {
|
||||
|
||||
|
||||
std::vector<float*> PrepareItems(
|
||||
std::vector<PedalBoardItem> & items,
|
||||
std::vector<PedalboardItem> & items,
|
||||
std::vector<float*> inputBuffers
|
||||
);
|
||||
|
||||
void PrepareMidiMap(const PedalBoard&pedalBoard);
|
||||
void PrepareMidiMap(const PedalBoardItem&pedalBoardItem);
|
||||
void PrepareMidiMap(const Pedalboard&pedalboard);
|
||||
void PrepareMidiMap(const PedalboardItem&pedalboardItem);
|
||||
|
||||
std::vector<float*> AllocateAudioBuffers(int nChannels);
|
||||
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalBoardItem> &items);
|
||||
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
|
||||
void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
|
||||
public:
|
||||
Lv2PedalBoard() { }
|
||||
~Lv2PedalBoard() { }
|
||||
Lv2Pedalboard() { }
|
||||
~Lv2Pedalboard() { }
|
||||
|
||||
void Prepare(IHost *pHost,PedalBoard&pedalBoard);
|
||||
void Prepare(IHost *pHost,Pedalboard&pedalboard);
|
||||
|
||||
std::vector<IEffect* > GetEffects() { return realtimeEffects; }
|
||||
|
||||
@@ -122,12 +122,12 @@ public:
|
||||
void ResetAtomBuffers();
|
||||
|
||||
|
||||
void ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests);
|
||||
void GatherParameterRequests(RealtimeParameterRequest *pParameterRequests);
|
||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
|
||||
|
||||
std::vector<float*> &GetInputBuffers() { return this->pedalBoardInputBuffers;}
|
||||
std::vector<float*> &GetoutputBuffers() { return this->pedalBoardOutputBuffers;}
|
||||
std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
|
||||
std::vector<float*> &GetoutputBuffers() { return this->pedalboardOutputBuffers;}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -18,54 +18,63 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "pch.h"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
static const PedalBoardItem* GetItem_(const std::vector<PedalBoardItem>&items,long pedalBoardItemId)
|
||||
static const PedalboardItem* GetItem_(const std::vector<PedalboardItem>&items,long pedalboardItemId)
|
||||
{
|
||||
for (size_t i = 0; i < items.size(); ++i)
|
||||
{
|
||||
auto &item = items[i];
|
||||
if (items[i].instanceId() == pedalBoardItemId)
|
||||
if (items[i].instanceId() == pedalboardItemId)
|
||||
{
|
||||
return &(items[i]);
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
const PedalBoardItem* t = GetItem_(item.topChain(),pedalBoardItemId);
|
||||
const PedalboardItem* t = GetItem_(item.topChain(),pedalboardItemId);
|
||||
if (t != nullptr) return t;
|
||||
t = GetItem_(item.bottomChain(),pedalBoardItemId);
|
||||
t = GetItem_(item.bottomChain(),pedalboardItemId);
|
||||
if (t != nullptr) return t;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const PedalBoardItem*PedalBoard::GetItem(long pedalItemId) const
|
||||
static void GetAllItems(std::vector<PedalboardItem*> & result, std::vector<PedalboardItem>&items)
|
||||
{
|
||||
for (auto& item: items)
|
||||
{
|
||||
if (item.isSplit())
|
||||
{
|
||||
GetAllItems(result,item.topChain());
|
||||
GetAllItems(result,item.bottomChain());
|
||||
}
|
||||
result.push_back(&item);
|
||||
}
|
||||
}
|
||||
std::vector<PedalboardItem*> Pedalboard::GetAllPlugins()
|
||||
{
|
||||
std::vector<PedalboardItem*> result;
|
||||
GetAllItems(result,this->items());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
const PedalboardItem*Pedalboard::GetItem(long pedalItemId) const
|
||||
{
|
||||
return GetItem_(this->items(),pedalItemId);
|
||||
}
|
||||
PedalBoardItem*PedalBoard::GetItem(long pedalItemId)
|
||||
PedalboardItem*Pedalboard::GetItem(long pedalItemId)
|
||||
{
|
||||
return const_cast<PedalBoardItem*>(GetItem_(this->items(),pedalItemId));
|
||||
return const_cast<PedalboardItem*>(GetItem_(this->items(),pedalItemId));
|
||||
}
|
||||
|
||||
|
||||
PropertyValue*PedalBoardItem::GetPropertyValue(const std::string&propertyUri)
|
||||
{
|
||||
for (auto&propertyValue: this->propertyValues_)
|
||||
{
|
||||
if (propertyValue.propertyUri() == propertyUri)
|
||||
{
|
||||
return &propertyValue;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
|
||||
ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
|
||||
{
|
||||
for (size_t i = 0; i < this->controlValues().size(); ++i)
|
||||
{
|
||||
@@ -77,9 +86,9 @@ ControlValue* PedalBoardItem::GetControlValue(const std::string&symbol)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled)
|
||||
bool Pedalboard::SetItemEnabled(long pedalItemId, bool enabled)
|
||||
{
|
||||
PedalBoardItem*item = GetItem(pedalItemId);
|
||||
PedalboardItem*item = GetItem(pedalItemId);
|
||||
if (!item) return false;
|
||||
if (item->isEnabled() != enabled)
|
||||
{
|
||||
@@ -91,9 +100,9 @@ bool PedalBoard::SetItemEnabled(long pedalItemId, bool enabled)
|
||||
}
|
||||
|
||||
|
||||
bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, float value)
|
||||
bool Pedalboard::SetControlValue(long pedalItemId, const std::string &symbol, float value)
|
||||
{
|
||||
PedalBoardItem*item = GetItem(pedalItemId);
|
||||
PedalboardItem*item = GetItem(pedalItemId);
|
||||
if (!item) return false;
|
||||
ControlValue*controlValue = item->GetControlValue(symbol);
|
||||
if (controlValue == nullptr) return false;
|
||||
@@ -106,11 +115,11 @@ bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, fl
|
||||
}
|
||||
|
||||
|
||||
PedalBoardItem PedalBoard::MakeEmptyItem()
|
||||
PedalboardItem Pedalboard::MakeEmptyItem()
|
||||
{
|
||||
uint64_t instanceId = NextInstanceId();
|
||||
|
||||
PedalBoardItem result;
|
||||
PedalboardItem result;
|
||||
result.instanceId(instanceId);
|
||||
result.uri(EMPTY_PEDALBOARD_ITEM_URI);
|
||||
result.pluginName("");
|
||||
@@ -119,11 +128,11 @@ PedalBoardItem PedalBoard::MakeEmptyItem()
|
||||
}
|
||||
|
||||
|
||||
PedalBoardItem PedalBoard::MakeSplit()
|
||||
PedalboardItem Pedalboard::MakeSplit()
|
||||
{
|
||||
uint64_t instanceId = NextInstanceId();
|
||||
|
||||
PedalBoardItem result;
|
||||
PedalboardItem result;
|
||||
result.instanceId(instanceId);
|
||||
result.uri(SPLIT_PEDALBOARD_ITEM_URI);
|
||||
result.pluginName("");
|
||||
@@ -151,10 +160,10 @@ PedalBoardItem PedalBoard::MakeSplit()
|
||||
|
||||
|
||||
|
||||
PedalBoard PedalBoard::MakeDefault()
|
||||
Pedalboard Pedalboard::MakeDefault()
|
||||
{
|
||||
// copy insanity. but it happens so rarely.
|
||||
PedalBoard result;
|
||||
Pedalboard result;
|
||||
|
||||
auto split = result.MakeSplit();
|
||||
split.topChain().push_back(result.MakeEmptyItem());
|
||||
@@ -176,7 +185,7 @@ PedalBoard PedalBoard::MakeDefault()
|
||||
}
|
||||
|
||||
|
||||
bool IsPedalBoardSplitItem(const PedalBoardItem*self, const std::vector<PedalBoardItem>&value)
|
||||
bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector<PedalboardItem>&value)
|
||||
{
|
||||
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
|
||||
}
|
||||
@@ -192,29 +201,24 @@ JSON_MAP_BEGIN(ControlValue)
|
||||
JSON_MAP_REFERENCE(ControlValue,value)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(PropertyValue)
|
||||
JSON_MAP_REFERENCE(PropertyValue,propertyUri)
|
||||
JSON_MAP_REFERENCE(PropertyValue,value)
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(PedalboardItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,instanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,uri)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,isEnabled)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,controlValues)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,pluginName)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,topChain,IsPedalboardSplitItem)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,bottomChain,&IsPedalboardSplitItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,midiBindings)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(PedalBoardItem)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,instanceId)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,uri)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,isEnabled)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,controlValues)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,propertyValues)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,pluginName)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,topChain,IsPedalBoardSplitItem)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalBoardItem,bottomChain,&IsPedalBoardSplitItem)
|
||||
JSON_MAP_REFERENCE(PedalBoardItem,midiBindings)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(PedalBoard)
|
||||
JSON_MAP_REFERENCE(PedalBoard,name)
|
||||
JSON_MAP_REFERENCE(PedalBoard,items)
|
||||
JSON_MAP_REFERENCE(PedalBoard,nextInstanceId)
|
||||
JSON_MAP_BEGIN(Pedalboard)
|
||||
JSON_MAP_REFERENCE(Pedalboard,name)
|
||||
JSON_MAP_REFERENCE(Pedalboard,items)
|
||||
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "json.hpp"
|
||||
#include "json_variant.hpp"
|
||||
#include "MidiBinding.hpp"
|
||||
#include "StateInterface.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
@@ -73,55 +74,34 @@ public:
|
||||
|
||||
|
||||
};
|
||||
class PropertyValue {
|
||||
private:
|
||||
std::string propertyUri_;
|
||||
json_variant value_;
|
||||
public:
|
||||
PropertyValue()
|
||||
{
|
||||
|
||||
}
|
||||
template <typename T>
|
||||
PropertyValue(const std::string&propertyUri, T value)
|
||||
:propertyUri_(propertyUri)
|
||||
, value_(value)
|
||||
{
|
||||
|
||||
}
|
||||
GETTER_SETTER_REF(propertyUri)
|
||||
GETTER_SETTER_REF(value)
|
||||
|
||||
DECLARE_JSON_MAP(PropertyValue);
|
||||
|
||||
|
||||
};
|
||||
|
||||
class PedalBoardItem: public JsonMemberWritable {
|
||||
class PedalboardItem: public JsonMemberWritable {
|
||||
int64_t instanceId_ = 0;
|
||||
std::string uri_;
|
||||
std::string pluginName_;
|
||||
bool isEnabled_ = true;
|
||||
std::vector<ControlValue> controlValues_;
|
||||
std::vector<PropertyValue> propertyValues_;
|
||||
std::vector<PedalBoardItem> topChain_;
|
||||
std::vector<PedalBoardItem> bottomChain_;
|
||||
std::vector<PedalboardItem> topChain_;
|
||||
std::vector<PedalboardItem> bottomChain_;
|
||||
std::vector<MidiBinding> midiBindings_;
|
||||
std::string vstState_;
|
||||
Lv2PluginState lv2State_;
|
||||
public:
|
||||
ControlValue*GetControlValue(const std::string&symbol);
|
||||
PropertyValue*GetPropertyValue(const std::string&propertyUri);
|
||||
|
||||
bool hasLv2State() const {
|
||||
return lv2State_.values_.size() != 0;
|
||||
}
|
||||
GETTER_SETTER(instanceId)
|
||||
GETTER_SETTER_REF(uri)
|
||||
GETTER_SETTER_REF(vstState);
|
||||
GETTER_SETTER_REF(pluginName)
|
||||
GETTER_SETTER(isEnabled)
|
||||
GETTER_SETTER_VEC(controlValues)
|
||||
GETTER_SETTER_VEC(propertyValues)
|
||||
GETTER_SETTER_VEC(topChain)
|
||||
GETTER_SETTER_VEC(bottomChain)
|
||||
GETTER_SETTER_VEC(midiBindings)
|
||||
GETTER_SETTER_REF(lv2State)
|
||||
|
||||
|
||||
bool isSplit() const
|
||||
@@ -137,7 +117,6 @@ public:
|
||||
writer.write_member("uri",uri_);
|
||||
writer.write_member("pluginName",pluginName_);
|
||||
writer.write_member("isEnabled",isEnabled_);
|
||||
writer.write_member("controlValues",controlValues_);
|
||||
if (isSplit())
|
||||
{
|
||||
writer.write_member("topChain",topChain_);
|
||||
@@ -145,13 +124,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
DECLARE_JSON_MAP(PedalBoardItem);
|
||||
DECLARE_JSON_MAP(PedalboardItem);
|
||||
};
|
||||
|
||||
|
||||
class PedalBoard {
|
||||
class Pedalboard {
|
||||
std::string name_;
|
||||
std::vector<PedalBoardItem> items_;
|
||||
std::vector<PedalboardItem> items_;
|
||||
uint64_t nextInstanceId_ = 0;
|
||||
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
||||
|
||||
@@ -159,8 +138,9 @@ public:
|
||||
bool SetControlValue(long pedalItemId, const std::string &symbol, float value);
|
||||
bool SetItemEnabled(long pedalItemId, bool enabled);
|
||||
|
||||
PedalBoardItem*GetItem(long pedalItemId);
|
||||
const PedalBoardItem*GetItem(long pedalItemId) const;
|
||||
PedalboardItem*GetItem(long pedalItemId);
|
||||
const PedalboardItem*GetItem(long pedalItemId) const;
|
||||
std::vector<PedalboardItem*>GetAllPlugins();
|
||||
|
||||
bool HasItem(long pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
|
||||
|
||||
@@ -168,13 +148,13 @@ public:
|
||||
GETTER_SETTER_VEC(items)
|
||||
|
||||
|
||||
DECLARE_JSON_MAP(PedalBoard);
|
||||
DECLARE_JSON_MAP(Pedalboard);
|
||||
|
||||
PedalBoardItem MakeEmptyItem();
|
||||
PedalBoardItem MakeSplit();
|
||||
PedalboardItem MakeEmptyItem();
|
||||
PedalboardItem MakeSplit();
|
||||
|
||||
|
||||
static PedalBoard MakeDefault();
|
||||
static Pedalboard MakeDefault();
|
||||
};
|
||||
|
||||
#undef GETTER_SETTER_REF
|
||||
@@ -198,7 +198,7 @@ public:
|
||||
|
||||
audioDriver = CreateAlsaDriver(this);
|
||||
|
||||
latencyMonitor.Init(jackConfiguration.GetSampleRate());
|
||||
latencyMonitor.Init(jackConfiguration.sampleRate());
|
||||
audioDriver->Open(serverSettings, channelSelection);
|
||||
|
||||
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
|
||||
|
||||
+329
-198
File diff suppressed because it is too large
Load Diff
+57
-30
@@ -19,9 +19,9 @@
|
||||
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "GovernorSettings.hpp"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "Storage.hpp"
|
||||
#include "Banks.hpp"
|
||||
#include "JackConfiguration.hpp"
|
||||
@@ -37,6 +37,8 @@
|
||||
#include "AdminClient.hpp"
|
||||
#include "AvahiService.hpp"
|
||||
#include <thread>
|
||||
#include "Promise.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -50,8 +52,9 @@ namespace pipedal
|
||||
virtual int64_t GetClientId() = 0;
|
||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnLv2StateChanged(int64_t pedalItemId) = 0;
|
||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
||||
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
|
||||
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
|
||||
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
|
||||
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
|
||||
@@ -62,19 +65,21 @@ namespace pipedal
|
||||
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
|
||||
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
|
||||
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) = 0;
|
||||
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &propertyUri, const std::string &atomJson) = 0;
|
||||
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
|
||||
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
|
||||
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
|
||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
|
||||
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
class PiPedalModel : private IAudioHostCallbacks
|
||||
{
|
||||
private:
|
||||
|
||||
std::unique_ptr<std::jthread> pingThread;
|
||||
|
||||
std::vector<MidiBinding> systemMidiBindings;
|
||||
@@ -97,9 +102,14 @@ namespace pipedal
|
||||
class AtomOutputListener
|
||||
{
|
||||
public:
|
||||
bool WantsProperty(uint64_t instanceId,LV2_URID patchProperty) const {
|
||||
if (instanceId != this->instanceId) return false;
|
||||
return this->propertyUrid == 0 || patchProperty == this->propertyUrid;
|
||||
}
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
uint64_t instanceId;
|
||||
LV2_URID propertyUrid;
|
||||
};
|
||||
void DeleteMidiListeners(int64_t clientId);
|
||||
void DeleteAtomOutputListeners(int64_t clientId);
|
||||
@@ -108,27 +118,29 @@ namespace pipedal
|
||||
std::vector<AtomOutputListener> atomOutputListeners;
|
||||
|
||||
JackServerSettings jackServerSettings;
|
||||
PiPedalHost lv2Host;
|
||||
PedalBoard pedalBoard;
|
||||
PluginHost lv2Host;
|
||||
AtomConverter atomConverter; // must be AFTER lv2Host!
|
||||
|
||||
Pedalboard pedalboard;
|
||||
Storage storage;
|
||||
bool hasPresetChanged = false;
|
||||
|
||||
std::unique_ptr<AudioHost> audioHost;
|
||||
JackConfiguration jackConfiguration;
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
|
||||
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard;
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
std::vector<IPiPedalModelSubscriber *> subscribers;
|
||||
void SetPresetChanged(int64_t clientId, bool value);
|
||||
void FirePresetsChanged(int64_t clientId);
|
||||
void FirePluginPresetsChanged(const std::string &pluginUri);
|
||||
void FirePedalBoardChanged(int64_t clientId);
|
||||
void FirePedalboardChanged(int64_t clientId);
|
||||
void FireChannelSelectionChanged(int64_t clientId);
|
||||
void FireBanksChanged(int64_t clientId);
|
||||
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
|
||||
|
||||
void UpdateDefaults(PedalBoardItem *pedalBoardItem);
|
||||
void UpdateDefaults(PedalBoard *pedalBoard);
|
||||
void UpdateDefaults(PedalboardItem *pedalboardItem);
|
||||
void UpdateDefaults(Pedalboard *pedalboard);
|
||||
|
||||
class VuSubscription
|
||||
{
|
||||
@@ -147,20 +159,23 @@ namespace pipedal
|
||||
|
||||
void RestartAudio();
|
||||
|
||||
std::vector<RealtimeParameterRequest *> outstandingParameterRequests;
|
||||
std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
|
||||
|
||||
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
|
||||
|
||||
private: // IAudioHostCallbacks
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates);
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson);
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest);
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request);
|
||||
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) override;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
|
||||
virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID atomProperty) override;
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson) override;
|
||||
;
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override;
|
||||
|
||||
void UpdateVst3Settings(PedalBoard &pedalBoard);
|
||||
void UpdateVst3Settings(Pedalboard &pedalboard);
|
||||
|
||||
PiPedalConfiguration configuration;
|
||||
|
||||
@@ -171,6 +186,8 @@ namespace pipedal
|
||||
uint16_t GetWebPort() const { return webPort; }
|
||||
void Close();
|
||||
|
||||
void SetOnboarding(bool value);
|
||||
|
||||
void UpdateDnsSd();
|
||||
|
||||
AdminClient &GetAdminClient() { return adminClient; }
|
||||
@@ -180,11 +197,11 @@ namespace pipedal
|
||||
void LoadLv2PluginInfo();
|
||||
void Load();
|
||||
|
||||
const PiPedalHost &GetLv2Host() const { return lv2Host; }
|
||||
PedalBoard GetCurrentPedalBoardCopy()
|
||||
const PluginHost &GetLv2Host() const { return lv2Host; }
|
||||
Pedalboard GetCurrentPedalboardCopy()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
return pedalBoard; // can return a referece because we'd lose mutex protection
|
||||
return pedalboard; // can return a referece because we'd lose mutex protection
|
||||
}
|
||||
PluginUiPresets GetPluginUiPresets(const std::string &pluginUri);
|
||||
PluginPresets GetPluginPresets(const std::string &pluginUri);
|
||||
@@ -194,15 +211,16 @@ namespace pipedal
|
||||
void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber);
|
||||
void RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber);
|
||||
|
||||
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetPedalboardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
|
||||
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
|
||||
|
||||
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||
|
||||
void GetPresets(PresetIndex *pResult);
|
||||
|
||||
PedalBoard GetPreset(int64_t instanceId);
|
||||
Pedalboard GetPreset(int64_t instanceId);
|
||||
void GetBank(int64_t instanceId, BankFile *pBank);
|
||||
|
||||
int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1);
|
||||
@@ -251,13 +269,22 @@ namespace pipedal
|
||||
int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
|
||||
void UnmonitorPort(int64_t subscriptionHandle);
|
||||
|
||||
void GetLv2Parameter(
|
||||
void SendGetPatchProperty(
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess,
|
||||
std::function<void(const std::string &error)> onError);
|
||||
|
||||
void SendSetPatchProperty(
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
const json_variant&value,
|
||||
std::function<void()> onSuccess,
|
||||
std::function<void(const std::string &error)> onError);
|
||||
|
||||
|
||||
BankIndex GetBankIndex() const;
|
||||
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||
@@ -273,8 +300,8 @@ namespace pipedal
|
||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
|
||||
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
|
||||
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri);
|
||||
void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
const std::filesystem::path &GetWebRoot() const;
|
||||
@@ -282,7 +309,7 @@ namespace pipedal
|
||||
std::map<std::string, bool> GetFavorites() const;
|
||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||
|
||||
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
|
||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
+170
-84
@@ -30,6 +30,7 @@
|
||||
#include <future>
|
||||
#include <atomic>
|
||||
#include "Ipv6Helpers.hpp"
|
||||
#include "Promise.hpp"
|
||||
|
||||
#include "AdminClient.hpp"
|
||||
#include "WifiConfigSettings.hpp"
|
||||
@@ -42,6 +43,33 @@
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
class GetPatchPropertyBody {
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
DECLARE_JSON_MAP(GetPatchPropertyBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(GetPatchPropertyBody)
|
||||
JSON_MAP_REFERENCE(GetPatchPropertyBody, instanceId)
|
||||
JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri)
|
||||
JSON_MAP_END()
|
||||
|
||||
class SetPatchPropertyBody {
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
json_variant value_;
|
||||
DECLARE_JSON_MAP(SetPatchPropertyBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(SetPatchPropertyBody)
|
||||
JSON_MAP_REFERENCE(SetPatchPropertyBody, instanceId)
|
||||
JSON_MAP_REFERENCE(SetPatchPropertyBody, propertyUri)
|
||||
JSON_MAP_REFERENCE(SetPatchPropertyBody, value)
|
||||
JSON_MAP_END()
|
||||
|
||||
class NotifyMidiListenerBody
|
||||
{
|
||||
public:
|
||||
@@ -61,14 +89,15 @@ class NotifyAtomOutputBody
|
||||
public:
|
||||
int64_t clientHandle_;
|
||||
uint64_t instanceId_;
|
||||
|
||||
std::string atomJson_;
|
||||
std::string propertyUri_;
|
||||
raw_json_string atomJson_;
|
||||
|
||||
DECLARE_JSON_MAP(NotifyAtomOutputBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(NotifyAtomOutputBody)
|
||||
JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle)
|
||||
JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId)
|
||||
JSON_MAP_REFERENCE(NotifyAtomOutputBody, propertyUri)
|
||||
JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -85,17 +114,19 @@ JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly)
|
||||
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
|
||||
JSON_MAP_END()
|
||||
|
||||
class ListenForAtomOutputBody
|
||||
class MonitorPatchPropertyBody
|
||||
{
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
int64_t handle_;
|
||||
DECLARE_JSON_MAP(ListenForAtomOutputBody);
|
||||
int64_t clientHandle_;
|
||||
std::string propertyUri_;
|
||||
DECLARE_JSON_MAP(MonitorPatchPropertyBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(ListenForAtomOutputBody)
|
||||
JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId)
|
||||
JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle)
|
||||
JSON_MAP_BEGIN(MonitorPatchPropertyBody)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
|
||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri)
|
||||
JSON_MAP_END()
|
||||
|
||||
class OnLoadPluginPresetBody
|
||||
@@ -151,18 +182,6 @@ JSON_MAP_REFERENCE(MonitorResultBody, subscriptionHandle)
|
||||
JSON_MAP_REFERENCE(MonitorResultBody, value)
|
||||
JSON_MAP_END()
|
||||
|
||||
class GetLv2ParameterBody
|
||||
{
|
||||
public:
|
||||
int64_t instanceId_;
|
||||
std::string uri_;
|
||||
|
||||
DECLARE_JSON_MAP(GetLv2ParameterBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(GetLv2ParameterBody)
|
||||
JSON_MAP_REFERENCE(GetLv2ParameterBody, instanceId)
|
||||
JSON_MAP_REFERENCE(GetLv2ParameterBody, uri)
|
||||
JSON_MAP_END()
|
||||
|
||||
class MonitorPortBody
|
||||
{
|
||||
@@ -262,33 +281,33 @@ JSON_MAP_REFERENCE(CopyPluginPresetBody, pluginUri)
|
||||
JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId)
|
||||
JSON_MAP_END()
|
||||
|
||||
class PedalBoardItemEnabledBody
|
||||
class PedalboardItemEnabledBody
|
||||
{
|
||||
public:
|
||||
int64_t clientId_ = -1;
|
||||
int64_t instanceId_ = -1;
|
||||
bool enabled_ = true;
|
||||
|
||||
DECLARE_JSON_MAP(PedalBoardItemEnabledBody);
|
||||
DECLARE_JSON_MAP(PedalboardItemEnabledBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(PedalBoardItemEnabledBody)
|
||||
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, clientId)
|
||||
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled)
|
||||
JSON_MAP_BEGIN(PedalboardItemEnabledBody)
|
||||
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, clientId)
|
||||
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, enabled)
|
||||
JSON_MAP_END()
|
||||
|
||||
class UpdateCurrentPedalBoardBody
|
||||
class UpdateCurrentPedalboardBody
|
||||
{
|
||||
public:
|
||||
int64_t clientId_ = -1;
|
||||
PedalBoard pedalBoard_;
|
||||
Pedalboard pedalboard_;
|
||||
|
||||
DECLARE_JSON_MAP(UpdateCurrentPedalBoardBody);
|
||||
DECLARE_JSON_MAP(UpdateCurrentPedalboardBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(UpdateCurrentPedalBoardBody)
|
||||
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, clientId)
|
||||
JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, pedalBoard)
|
||||
JSON_MAP_BEGIN(UpdateCurrentPedalboardBody)
|
||||
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId)
|
||||
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
|
||||
JSON_MAP_END()
|
||||
|
||||
class ChannelSelectionChangedBody
|
||||
@@ -335,6 +354,24 @@ JSON_MAP_REFERENCE(ControlChangedBody, symbol)
|
||||
JSON_MAP_REFERENCE(ControlChangedBody, value)
|
||||
JSON_MAP_END()
|
||||
|
||||
class PatchPropertyChangedBody
|
||||
{
|
||||
public:
|
||||
int64_t clientId_;
|
||||
int64_t instanceId_;
|
||||
std::string propertyUri_;
|
||||
json_variant value_;
|
||||
|
||||
DECLARE_JSON_MAP(PatchPropertyChangedBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(PatchPropertyChangedBody)
|
||||
JSON_MAP_REFERENCE(PatchPropertyChangedBody, clientId)
|
||||
JSON_MAP_REFERENCE(PatchPropertyChangedBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PatchPropertyChangedBody, propertyUri)
|
||||
JSON_MAP_REFERENCE(PatchPropertyChangedBody, value)
|
||||
JSON_MAP_END()
|
||||
|
||||
class Vst3ControlChangedBody
|
||||
{
|
||||
public:
|
||||
@@ -420,14 +457,19 @@ public:
|
||||
std::stringstream imageList;
|
||||
const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
|
||||
bool firstTime = true;
|
||||
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
|
||||
{
|
||||
if (!firstTime)
|
||||
try {
|
||||
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
|
||||
{
|
||||
imageList << ";";
|
||||
if (!firstTime)
|
||||
{
|
||||
imageList << ";";
|
||||
}
|
||||
firstTime = false;
|
||||
imageList << entry.path().filename().string();
|
||||
}
|
||||
firstTime = false;
|
||||
imageList << entry.path().filename().string();
|
||||
} catch (const std::exception&)
|
||||
{
|
||||
Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str());
|
||||
}
|
||||
this->imageList = imageList.str();
|
||||
}
|
||||
@@ -778,17 +820,17 @@ public:
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (message == "previewControl")
|
||||
{
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
}
|
||||
else if (message == "setControl")
|
||||
if (message == "setControl")
|
||||
{
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
}
|
||||
else if (message == "previewControl")
|
||||
{
|
||||
ControlChangedBody message;
|
||||
pReader->read(&message);
|
||||
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||
}
|
||||
else if (message == "listenForMidiEvent")
|
||||
{
|
||||
@@ -802,17 +844,17 @@ public:
|
||||
pReader->read(&handle);
|
||||
this->model.CancelListenForMidiEvent(this->clientId, handle);
|
||||
}
|
||||
else if (message == "listenForAtomOutput")
|
||||
else if (message == "monitorPatchProperty")
|
||||
{
|
||||
ListenForAtomOutputBody body;
|
||||
MonitorPatchPropertyBody body;
|
||||
pReader->read(&body);
|
||||
this->model.ListenForAtomOutputs(this->clientId, body.handle_, body.instanceId_);
|
||||
this->model.MonitorPatchProperty(this->clientId, body.clientHandle_, body.instanceId_, body.propertyUri_);
|
||||
}
|
||||
else if (message == "cancelListenForAtomOutput")
|
||||
else if (message == "cancelMonitorPatchProperty")
|
||||
{
|
||||
int64_t handle;
|
||||
pReader->read(&handle);
|
||||
this->model.CancelListenForMidiEvent(this->clientId, handle);
|
||||
this->model.CancelMonitorPatchProperty(this->clientId, handle);
|
||||
}
|
||||
|
||||
else if (message == "getJackStatus")
|
||||
@@ -954,25 +996,25 @@ public:
|
||||
this->model.GetPresets(&presets);
|
||||
Reply(replyTo, "getPresets", presets);
|
||||
}
|
||||
else if (message == "setPedalBoardItemEnable")
|
||||
else if (message == "setPedalboardItemEnable")
|
||||
{
|
||||
PedalBoardItemEnabledBody body;
|
||||
PedalboardItemEnabledBody body;
|
||||
pReader->read(&body);
|
||||
model.SetPedalBoardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
|
||||
model.SetPedalboardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
|
||||
}
|
||||
else if (message == "updateCurrentPedalBoard")
|
||||
else if (message == "updateCurrentPedalboard")
|
||||
{
|
||||
{
|
||||
UpdateCurrentPedalBoardBody body;
|
||||
UpdateCurrentPedalboardBody body;
|
||||
|
||||
pReader->read(&body);
|
||||
this->model.UpdateCurrentPedalBoard(body.clientId_, body.pedalBoard_);
|
||||
this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_);
|
||||
}
|
||||
}
|
||||
else if (message == "currentPedalBoard")
|
||||
else if (message == "currentPedalboard")
|
||||
{
|
||||
auto pedalBoard = model.GetCurrentPedalBoardCopy();
|
||||
Reply(replyTo, "currentPedalBoard", pedalBoard);
|
||||
auto pedalboard = model.GetCurrentPedalboardCopy();
|
||||
Reply(replyTo, "currentPedalboard", pedalboard);
|
||||
}
|
||||
else if (message == "plugins")
|
||||
{
|
||||
@@ -1142,18 +1184,35 @@ public:
|
||||
uint64_t result = model.CopyPluginPreset(body.pluginUri_, body.instanceId_);
|
||||
this->Reply(replyTo, "copyPluginPreset", result);
|
||||
}
|
||||
else if (message == "getLv2Parameter")
|
||||
else if (message == "setPatchProperty")
|
||||
{
|
||||
GetLv2ParameterBody body;
|
||||
SetPatchPropertyBody body;
|
||||
pReader->read(&body);
|
||||
model.SendSetPatchProperty(clientId,body.instanceId_,body.propertyUri_,body.value_,
|
||||
[this, replyTo] () {
|
||||
this->JsonReply(replyTo, "setPatchProperty", "true");
|
||||
|
||||
},
|
||||
[this, replyTo] (const std::string&error) {
|
||||
this->SendError(replyTo, error.c_str());
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
else if (message == "getPatchProperty")
|
||||
{
|
||||
GetPatchPropertyBody body;
|
||||
pReader->read(&body);
|
||||
|
||||
model.GetLv2Parameter(
|
||||
model.SendGetPatchProperty(
|
||||
this->clientId,
|
||||
body.instanceId_,
|
||||
body.uri_,
|
||||
body.propertyUri_,
|
||||
[this, replyTo](const std::string &jsonResult)
|
||||
{
|
||||
this->JsonReply(replyTo, "getLv2Parameter", jsonResult.c_str());
|
||||
this->JsonReply(replyTo, "getPatchProperty", jsonResult.c_str());
|
||||
},
|
||||
[this, replyTo](const std::string &error)
|
||||
{
|
||||
@@ -1248,10 +1307,17 @@ public:
|
||||
}
|
||||
else if (message == "requestFileList")
|
||||
{
|
||||
PiPedalFileProperty fileProperty;
|
||||
UiFileProperty fileProperty;
|
||||
pReader->read(&fileProperty);
|
||||
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
||||
this->Reply(replyTo,"requestFileList",list);
|
||||
}
|
||||
else if (message == "setOnboarding")
|
||||
{
|
||||
bool value;
|
||||
pReader->read(&value);
|
||||
this->model.SetOnboarding(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error("Unknown message received: %s", message.c_str());
|
||||
@@ -1260,8 +1326,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void
|
||||
onReceive(const std::string_view &text)
|
||||
virtual void onReceive(const std::string_view &text)
|
||||
{
|
||||
view_istream<char> s(text);
|
||||
|
||||
@@ -1324,7 +1389,22 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
private:
|
||||
virtual void OnLv2StateChanged(int64_t instanceId)
|
||||
{
|
||||
Send("onLv2StateChanged",instanceId);
|
||||
|
||||
}
|
||||
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
|
||||
{
|
||||
PatchPropertyChangedBody body;
|
||||
body.clientId_ = clientId;
|
||||
body.instanceId_ = instanceId;
|
||||
body.propertyUri_ = propertyUri;
|
||||
body.value_ = value;
|
||||
Send("onPatchPropertyChanged",body);
|
||||
}
|
||||
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
|
||||
Send("onSystemMidiBindingsChanged",bindings);
|
||||
}
|
||||
@@ -1499,13 +1579,13 @@ public:
|
||||
public:
|
||||
int64_t clientHandle;
|
||||
uint64_t instanceId;
|
||||
std::string atomType;
|
||||
std::string propertyUri;
|
||||
std::string json;
|
||||
};
|
||||
|
||||
std::vector<PendingNotifyAtomOutput> pendingNotifyAtomOutputs;
|
||||
|
||||
void OnAckNotifyAtomOutput()
|
||||
void OnAckNotifyPatchProperty()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
||||
if (--outstandingNotifyAtomOutputs <= 0)
|
||||
@@ -1518,19 +1598,24 @@ public:
|
||||
OnNotifyAtomOutput(
|
||||
t.clientHandle,
|
||||
t.instanceId,
|
||||
t.atomType,
|
||||
t.propertyUri,
|
||||
t.json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomType, const std::string &atomJson)
|
||||
virtual void OnNotifyAtomOutput(int64_t clientHandle, uint64_t instanceId, const std::string &atomProperty, const std::string &atomJson)
|
||||
{
|
||||
NotifyAtomOutputBody body;
|
||||
body.clientHandle_ = clientHandle;
|
||||
body.instanceId_ = instanceId;
|
||||
body.atomJson_ = atomJson;
|
||||
body.propertyUri_ = atomProperty;
|
||||
body.atomJson_.Set(atomJson);
|
||||
|
||||
// flow control. We can only have one in-flight NotifyAtomOutput at a time.
|
||||
// Subsequent notifications are held until we receive an ack.
|
||||
//
|
||||
// If a duplicate atomProperty is queued, overwrite the previous value.
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
||||
|
||||
@@ -1539,23 +1624,24 @@ public:
|
||||
for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i)
|
||||
{
|
||||
auto &output = pendingNotifyAtomOutputs[i];
|
||||
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.atomType == atomType)
|
||||
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty)
|
||||
{
|
||||
output.json = atomJson;
|
||||
return;
|
||||
// better to erase than overwrite, since it provides better idempotence.
|
||||
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomType, atomJson});
|
||||
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{clientHandle, instanceId, atomProperty, atomJson});
|
||||
return;
|
||||
}
|
||||
++outstandingNotifyAtomOutputs;
|
||||
}
|
||||
|
||||
Request<bool>(
|
||||
"onNotifyAtomOut", body,
|
||||
"onNotifyPatchProperty", body,
|
||||
[this](const bool &value)
|
||||
{
|
||||
this->OnAckNotifyAtomOutput();
|
||||
this->OnAckNotifyPatchProperty();
|
||||
},
|
||||
[](const std::exception &e) {
|
||||
|
||||
@@ -1594,17 +1680,17 @@ public:
|
||||
Send("onGovernorSettingsChanged", governor);
|
||||
}
|
||||
|
||||
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard)
|
||||
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard)
|
||||
{
|
||||
UpdateCurrentPedalBoardBody body;
|
||||
UpdateCurrentPedalboardBody body;
|
||||
body.clientId_ = clientId;
|
||||
body.pedalBoard_ = pedalBoard;
|
||||
Send("onPedalBoardChanged", body);
|
||||
body.pedalboard_ = pedalboard;
|
||||
Send("onPedalboardChanged", body);
|
||||
}
|
||||
|
||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
PedalBoardItemEnabledBody body;
|
||||
PedalboardItemEnabledBody body;
|
||||
body.clientId_ = clientId;
|
||||
body.instanceId_ = pedalItemId;
|
||||
body.enabled_ = enabled;
|
||||
|
||||
+104
-40
@@ -23,11 +23,12 @@
|
||||
*/
|
||||
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
|
||||
PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::filesystem::path &resourcePath)
|
||||
{
|
||||
auto pWorld = pHost->getWorld();
|
||||
|
||||
@@ -37,18 +38,35 @@ PiPedalUI::PiPedalUI(PiPedalHost *pHost, const LilvNode *uiNode)
|
||||
const LilvNode *fileNode = lilv_nodes_get(fileNodes, i);
|
||||
try
|
||||
{
|
||||
PiPedalFileProperty::ptr fileUI = std::make_shared<PiPedalFileProperty>(pHost, fileNode);
|
||||
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(pHost, fileNode, resourcePath);
|
||||
this->fileProperites_.push_back(std::move(fileUI));
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
pHost->LogError(e.what());
|
||||
pHost->LogWarning(SS("Failed to read pipedalui::fileProperties. " << e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
LilvNodes *portNotifications = lilv_world_find_nodes(pWorld, uiNode, pHost->lilvUris.ui__portNotification, nullptr);
|
||||
LILV_FOREACH(nodes, i, portNotifications)
|
||||
{
|
||||
const LilvNode *portNotificationNode = lilv_nodes_get(portNotifications, i);
|
||||
try
|
||||
{
|
||||
UiPortNotification::ptr portNotification = std::make_shared<UiPortNotification>(pHost, portNotificationNode);
|
||||
this->portNotifications_.push_back(std::move(portNotification));
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
pHost->LogWarning(SS("Failed to read ui:portNotifications. " << e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
lilv_nodes_free(fileNodes);
|
||||
}
|
||||
|
||||
PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) {
|
||||
PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
|
||||
{
|
||||
auto pWorld = pHost->getWorld();
|
||||
|
||||
AutoLilvNode name = lilv_world_get(
|
||||
@@ -77,9 +95,8 @@ PiPedalFileType::PiPedalFileType(PiPedalHost*pHost, const LilvNode*node) {
|
||||
{
|
||||
throw std::logic_error("pipedal_ui:fileType is missing fileExtension property.");
|
||||
}
|
||||
|
||||
}
|
||||
PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *node)
|
||||
UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath)
|
||||
{
|
||||
auto pWorld = pHost->getWorld();
|
||||
|
||||
@@ -110,9 +127,9 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod
|
||||
throw std::logic_error("Pipedal FileProperty::directory must have only alpha-numeric characters.");
|
||||
}
|
||||
}
|
||||
else
|
||||
if (directory_.length() == 0)
|
||||
{
|
||||
throw std::logic_error("PiPedal FileProperty is missing a pipedalui:directory value.");
|
||||
throw std::logic_error("PipedalUI::fileProperty: must specify at least a directory.");
|
||||
}
|
||||
|
||||
AutoLilvNode patchProperty = lilv_world_get(
|
||||
@@ -123,24 +140,19 @@ PiPedalFileProperty::PiPedalFileProperty(PiPedalHost *pHost, const LilvNode *nod
|
||||
if (patchProperty)
|
||||
{
|
||||
this->patchProperty_ = patchProperty.AsUri();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("PiPedal FileProperty is missing pipedalui:patchProperty value.");
|
||||
}
|
||||
AutoLilvNode defaultFile = lilv_world_get(
|
||||
pWorld,
|
||||
node,
|
||||
pHost->lilvUris.pipedalUI__defaultFile,
|
||||
nullptr);
|
||||
this->defaultFile_ = defaultFile.AsString();
|
||||
|
||||
|
||||
this->fileTypes_ = PiPedalFileType::GetArray(pHost,node,pHost->lilvUris.pipedalUI__fileTypes);
|
||||
this->fileTypes_ = PiPedalFileType::GetArray(pHost, node, pHost->lilvUris.pipedalUI__fileTypes);
|
||||
}
|
||||
|
||||
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri)
|
||||
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
|
||||
{
|
||||
std::vector<PiPedalFileType> result;
|
||||
LilvWorld* pWorld = pHost->getWorld();
|
||||
LilvWorld *pWorld = pHost->getWorld();
|
||||
|
||||
LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr);
|
||||
LILV_FOREACH(nodes, i, fileTypeNodes)
|
||||
@@ -160,35 +172,33 @@ std::vector<PiPedalFileType> PiPedalFileType::GetArray(PiPedalHost*pHost, const
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pipedal::IsAlphaNumeric(const std::string&value)
|
||||
bool pipedal::IsAlphaNumeric(const std::string &value)
|
||||
{
|
||||
for (char c:value)
|
||||
for (char c : value)
|
||||
{
|
||||
if (
|
||||
(c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'z')
|
||||
|| (c >= 'A' && c <= 'Z')
|
||||
|| (c == '_')
|
||||
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')
|
||||
|
||||
) {
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiPedalFileProperty::IsDirectoryNameValid(const std::string&value)
|
||||
bool UiFileProperty::IsDirectoryNameValid(const std::string &value)
|
||||
{
|
||||
if (value.length() == 0) return false;
|
||||
if (value.length() == 0)
|
||||
return false;
|
||||
return IsAlphaNumeric(value);
|
||||
}
|
||||
|
||||
bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const
|
||||
bool UiFileProperty::IsValidExtension(const std::string &extension) const
|
||||
{
|
||||
|
||||
for (auto&fileType: fileTypes_)
|
||||
for (auto &fileType : fileTypes_)
|
||||
{
|
||||
if (fileType.fileExtension() == extension)
|
||||
{
|
||||
@@ -198,15 +208,69 @@ bool PiPedalFileProperty::IsValidExtension(const std::string&extension) const
|
||||
return false;
|
||||
}
|
||||
|
||||
UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
|
||||
{
|
||||
// ui:portNotification
|
||||
// [
|
||||
// ui:portIndex 3;
|
||||
// ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
|
||||
// ui:protocol ui:floatProtocol;
|
||||
// // pipedal_ui:style pipedal_ui:text ;
|
||||
// // pipedal_ui:redLevel 0;
|
||||
// // pipedal_ui:yellowLevel -12;
|
||||
// ]
|
||||
LilvWorld *pWorld = pHost->getWorld();
|
||||
|
||||
JSON_MAP_BEGIN(PiPedalFileType)
|
||||
JSON_MAP_REFERENCE(PiPedalFileType,name)
|
||||
JSON_MAP_REFERENCE(PiPedalFileType,fileExtension)
|
||||
AutoLilvNode portIndex = lilv_world_get(pWorld,node,pHost->lilvUris.ui__portIndex,nullptr);
|
||||
if (!portIndex)
|
||||
{
|
||||
this->portIndex_ = -1;
|
||||
} else {
|
||||
this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex);
|
||||
}
|
||||
AutoLilvNode symbol = lilv_world_get(pWorld,node,pHost->lilvUris.lv2__symbol,nullptr);
|
||||
if (!symbol)
|
||||
{
|
||||
this->symbol_ = "";
|
||||
} else {
|
||||
this->symbol_ = symbol.AsString();
|
||||
}
|
||||
AutoLilvNode plugin = lilv_world_get(pWorld,node,pHost->lilvUris.ui__plugin,nullptr);
|
||||
if (!plugin)
|
||||
{
|
||||
this->plugin_ = "";
|
||||
} else {
|
||||
this->plugin_ = plugin.AsUri();
|
||||
}
|
||||
AutoLilvNode protocol = lilv_world_get(pWorld,node,pHost->lilvUris.ui__protocol,nullptr);
|
||||
if (!protocol)
|
||||
{
|
||||
this->protocol_ = "";
|
||||
} else {
|
||||
this->protocol_ = protocol.AsUri();
|
||||
}
|
||||
if (this->portIndex_ == -1 &&this->symbol_ == "")
|
||||
{
|
||||
pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(UiPortNotification)
|
||||
JSON_MAP_REFERENCE(UiPortNotification, portIndex)
|
||||
JSON_MAP_REFERENCE(UiPortNotification, symbol)
|
||||
JSON_MAP_REFERENCE(UiPortNotification, plugin)
|
||||
JSON_MAP_REFERENCE(UiPortNotification, protocol)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(PiPedalFileProperty)
|
||||
JSON_MAP_REFERENCE(PiPedalFileProperty,patchProperty)
|
||||
JSON_MAP_REFERENCE(PiPedalFileProperty,name)
|
||||
JSON_MAP_REFERENCE(PiPedalFileProperty,defaultFile)
|
||||
JSON_MAP_REFERENCE(PiPedalFileProperty,fileTypes)
|
||||
JSON_MAP_BEGIN(PiPedalFileType)
|
||||
JSON_MAP_REFERENCE(PiPedalFileType, name)
|
||||
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(UiFileProperty)
|
||||
JSON_MAP_REFERENCE(UiFileProperty, name)
|
||||
JSON_MAP_REFERENCE(UiFileProperty, directory)
|
||||
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
|
||||
JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
|
||||
JSON_MAP_END()
|
||||
+46
-14
@@ -28,6 +28,7 @@
|
||||
#include <memory>
|
||||
#include <lilv/lilv.h>
|
||||
#include "json.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
#define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui"
|
||||
@@ -38,7 +39,6 @@
|
||||
#define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties"
|
||||
|
||||
#define PIPEDAL_UI__fileProperty PIPEDAL_UI_PREFIX "fileProperty"
|
||||
#define PIPEDAL_UI__defaultFile PIPEDAL_UI_PREFIX "defaultFile"
|
||||
#define PIPEDAL_UI__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
|
||||
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
|
||||
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class PiPedalHost;
|
||||
class PluginHost;
|
||||
|
||||
class PiPedalFileType {
|
||||
private:
|
||||
@@ -61,9 +61,9 @@ namespace pipedal {
|
||||
std::string fileExtension_;
|
||||
public:
|
||||
PiPedalFileType() { }
|
||||
PiPedalFileType(PiPedalHost*pHost, const LilvNode*node);
|
||||
PiPedalFileType(PluginHost*pHost, const LilvNode*node);
|
||||
|
||||
static std::vector<PiPedalFileType> GetArray(PiPedalHost*pHost, const LilvNode*node,const LilvNode*uri);
|
||||
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
|
||||
|
||||
const std::string& name() const { return name_;}
|
||||
const std::string &fileExtension() const { return fileExtension_; }
|
||||
@@ -73,22 +73,38 @@ namespace pipedal {
|
||||
|
||||
};
|
||||
|
||||
class PiPedalFileProperty {
|
||||
|
||||
|
||||
class UiPortNotification {
|
||||
private:
|
||||
int32_t portIndex_;
|
||||
std::string symbol_;
|
||||
std::string plugin_;
|
||||
std::string protocol_;
|
||||
public:
|
||||
using ptr = std::shared_ptr<UiPortNotification>;
|
||||
|
||||
UiPortNotification() { }
|
||||
UiPortNotification(PluginHost*pHost, const LilvNode*node);
|
||||
|
||||
public:
|
||||
DECLARE_JSON_MAP(UiPortNotification);
|
||||
|
||||
};
|
||||
class UiFileProperty {
|
||||
private:
|
||||
std::string name_;
|
||||
std::string directory_;
|
||||
std::vector<PiPedalFileType> fileTypes_;
|
||||
std::string patchProperty_;
|
||||
std::string defaultFile_;
|
||||
public:
|
||||
using ptr = std::shared_ptr<PiPedalFileProperty>;
|
||||
PiPedalFileProperty() { }
|
||||
PiPedalFileProperty(PiPedalHost*pHost, const LilvNode*node);
|
||||
using ptr = std::shared_ptr<UiFileProperty>;
|
||||
UiFileProperty() { }
|
||||
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
|
||||
|
||||
|
||||
const std::string &name() const { return name_; }
|
||||
const std::string &directory() const { return directory_; }
|
||||
const std::string &defaultFile() const { return defaultFile_; }
|
||||
|
||||
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
|
||||
|
||||
@@ -97,19 +113,35 @@ namespace pipedal {
|
||||
static bool IsDirectoryNameValid(const std::string&value);
|
||||
|
||||
public:
|
||||
DECLARE_JSON_MAP(PiPedalFileProperty);
|
||||
DECLARE_JSON_MAP(UiFileProperty);
|
||||
};
|
||||
|
||||
class PiPedalUI {
|
||||
public:
|
||||
using ptr = std::shared_ptr<PiPedalUI>;
|
||||
PiPedalUI(PiPedalHost*pHost, const LilvNode*uiNode);
|
||||
const std::vector<PiPedalFileProperty::ptr>& fileProperties() const
|
||||
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
|
||||
const std::vector<UiFileProperty::ptr>& fileProperties() const
|
||||
{
|
||||
return fileProperites_;
|
||||
}
|
||||
|
||||
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
|
||||
|
||||
const UiFileProperty*GetFileProperty(const std::string &propertyUri) const
|
||||
{
|
||||
for (const auto&fileProperty : fileProperties())
|
||||
{
|
||||
if (fileProperty->patchProperty() == propertyUri)
|
||||
{
|
||||
return fileProperty.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<PiPedalFileProperty::ptr> fileProperites_;
|
||||
std::vector<UiFileProperty::ptr> fileProperites_;
|
||||
std::vector<UiPortNotification::ptr> portNotifications_;
|
||||
};
|
||||
|
||||
// Utiltities for validating file paths received via PiPedalFileProperty-related APIs.
|
||||
|
||||
@@ -18,19 +18,20 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "pch.h"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include <lilv/lilv.h>
|
||||
#include <stdexcept>
|
||||
#include <locale>
|
||||
#include <string_view>
|
||||
#include "Lv2Log.hpp"
|
||||
#include <functional>
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "Lv2Effect.hpp"
|
||||
#include "Lv2PedalBoard.hpp"
|
||||
#include "Lv2Pedalboard.hpp"
|
||||
#include "OptionsFeature.hpp"
|
||||
#include "JackConfiguration.hpp"
|
||||
#include "lv2/urid.lv2/urid.h"
|
||||
#include "lv2/ui.lv2/ui.h"
|
||||
#include "lv2.h"
|
||||
#include "lv2/atom.lv2/atom.h"
|
||||
#include "lv2/time/time.h"
|
||||
@@ -86,27 +87,37 @@ namespace pipedal
|
||||
static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
|
||||
static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
|
||||
|
||||
class PiPedalHost::Urids
|
||||
class PluginHost::Urids
|
||||
{
|
||||
public:
|
||||
Urids(MapFeature &mapFeature)
|
||||
{
|
||||
atom_Float = mapFeature.GetUrid(LV2_ATOM__Float);
|
||||
ui__portNotification = mapFeature.GetUrid(LV2_UI__portNotification);
|
||||
ui__plugin = mapFeature.GetUrid(LV2_UI__plugin);
|
||||
ui__protocol = mapFeature.GetUrid(LV2_UI__protocol);
|
||||
ui__floatProtocol = mapFeature.GetUrid(LV2_UI__protocol);
|
||||
ui__peakProtocol = mapFeature.GetUrid(LV2_UI__peakProtocol);
|
||||
}
|
||||
LV2_URID atom_Float;
|
||||
LV2_URID ui__portNotification;
|
||||
LV2_URID ui__plugin;
|
||||
LV2_URID ui__protocol;
|
||||
LV2_URID ui__floatProtocol;
|
||||
LV2_URID ui__peakProtocol;
|
||||
};
|
||||
}
|
||||
|
||||
void PiPedalHost::SetConfiguration(const PiPedalConfiguration &configuration)
|
||||
void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration)
|
||||
{
|
||||
this->vst3CachePath =
|
||||
std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json";
|
||||
this->vst3Enabled = configuration.IsVst3Enabled();
|
||||
}
|
||||
|
||||
void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
{
|
||||
rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI);
|
||||
rdfsComment = lilv_new_uri(pWorld, PluginHost::RDFS_COMMENT_URI);
|
||||
logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC);
|
||||
display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY);
|
||||
range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS);
|
||||
@@ -126,15 +137,35 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
lv2Core__name = lilv_new_uri(pWorld, LV2_CORE__name);
|
||||
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
|
||||
pipedalUI__fileProperties = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperties);
|
||||
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
|
||||
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
|
||||
pipedalUI__directory = lilv_new_uri(pWorld,PIPEDAL_UI__directory);
|
||||
pipedalUI__fileTypes = lilv_new_uri(pWorld,PIPEDAL_UI__fileTypes);
|
||||
pipedalUI__fileProperty = lilv_new_uri(pWorld,PIPEDAL_UI__fileProperty);
|
||||
pipedalUI__directory = lilv_new_uri(pWorld, PIPEDAL_UI__directory);
|
||||
pipedalUI__fileTypes = lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
|
||||
pipedalUI__fileProperty = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperty);
|
||||
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
|
||||
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
|
||||
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text);
|
||||
pipedalUI__defaultFile = lilv_new_uri(pWorld, PIPEDAL_UI__defaultFile);
|
||||
|
||||
|
||||
ui__portNotification = lilv_new_uri(pWorld, LV2_UI__portNotification);
|
||||
ui__plugin = lilv_new_uri(pWorld, LV2_UI__plugin);
|
||||
ui__protocol = lilv_new_uri(pWorld, LV2_UI__protocol);
|
||||
ui__floatProtocol = lilv_new_uri(pWorld, LV2_UI__protocol);
|
||||
ui__peakProtocol = lilv_new_uri(pWorld, LV2_UI__peakProtocol);
|
||||
ui__portIndex = lilv_new_uri(pWorld,LV2_UI__portIndex);
|
||||
lv2__symbol = lilv_new_uri(pWorld,LV2_CORE__symbol);
|
||||
|
||||
|
||||
|
||||
// ui:portNotification
|
||||
// [
|
||||
// ui:portIndex 3;
|
||||
// ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
|
||||
// ui:protocol ui:floatProtocol;
|
||||
// // pipedal_ui:style pipedal_ui:text ;
|
||||
// // pipedal_ui:redLevel 0;
|
||||
// // pipedal_ui:yellowLevel -12;
|
||||
// ]
|
||||
|
||||
time_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
|
||||
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
|
||||
@@ -144,7 +175,7 @@ void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
|
||||
}
|
||||
|
||||
void PiPedalHost::LilvUris::Free()
|
||||
void PluginHost::LilvUris::Free()
|
||||
{
|
||||
rdfsComment.Free();
|
||||
logarithic_uri.Free();
|
||||
@@ -214,45 +245,56 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0)
|
||||
return default_;
|
||||
}
|
||||
|
||||
PiPedalHost::PiPedalHost()
|
||||
void PluginHost::SetPluginStoragePath(const std::filesystem::path &path)
|
||||
{
|
||||
mapPathFeature.SetPluginStoragePath(path);
|
||||
}
|
||||
|
||||
PluginHost::PluginHost()
|
||||
: mapPathFeature("")
|
||||
{
|
||||
pWorld = nullptr;
|
||||
|
||||
LV2_Feature **features = new LV2_Feature *[10];
|
||||
features[0] = const_cast<LV2_Feature *>(mapFeature.GetMapFeature());
|
||||
features[1] = const_cast<LV2_Feature *>(mapFeature.GetUnmapFeature());
|
||||
lv2Features.push_back(mapFeature.GetMapFeature());
|
||||
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
||||
|
||||
logFeature.Prepare(&mapFeature);
|
||||
features[2] = const_cast<LV2_Feature *>(logFeature.GetFeature());
|
||||
lv2Features.push_back(logFeature.GetFeature());
|
||||
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
|
||||
features[3] = const_cast<LV2_Feature *>(optionsFeature.GetFeature());
|
||||
features[4] = nullptr;
|
||||
|
||||
this->lv2Features = features;
|
||||
mapPathFeature.Prepare(&mapFeature);
|
||||
lv2Features.push_back(mapPathFeature.GetMapPathFeature());
|
||||
lv2Features.push_back(mapPathFeature.GetMakePathFeature());
|
||||
lv2Features.push_back(optionsFeature.GetFeature());
|
||||
|
||||
lv2Features.push_back(nullptr);
|
||||
|
||||
this->urids = new Urids(mapFeature);
|
||||
|
||||
pHostWorkerThread = std::make_shared<HostWorkerThread>();
|
||||
}
|
||||
|
||||
void PiPedalHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
|
||||
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
|
||||
{
|
||||
this->sampleRate = configuration.GetSampleRate();
|
||||
this->sampleRate = configuration.sampleRate();
|
||||
if (configuration.isValid())
|
||||
{
|
||||
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
||||
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
|
||||
this->maxBufferSize = configuration.GetBlockLength();
|
||||
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize());
|
||||
this->maxBufferSize = configuration.blockLength();
|
||||
optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize());
|
||||
}
|
||||
}
|
||||
|
||||
PiPedalHost::~PiPedalHost()
|
||||
PluginHost::~PluginHost()
|
||||
{
|
||||
delete[] lv2Features;
|
||||
lilvUris.Free();
|
||||
free_world();
|
||||
delete urids;
|
||||
}
|
||||
|
||||
void PiPedalHost::free_world()
|
||||
void PluginHost::free_world()
|
||||
{
|
||||
if (pWorld)
|
||||
{
|
||||
@@ -260,7 +302,7 @@ void PiPedalHost::free_world()
|
||||
pWorld = nullptr;
|
||||
}
|
||||
}
|
||||
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &uri) const
|
||||
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const std::string &uri) const
|
||||
{
|
||||
auto it = this->classesMap.find(uri);
|
||||
if (it == this->classesMap.end())
|
||||
@@ -268,7 +310,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const std::string &u
|
||||
return (*it).second;
|
||||
}
|
||||
|
||||
void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
|
||||
void PluginHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass)
|
||||
{
|
||||
this->classesMap[pluginClass->uri()] = pluginClass;
|
||||
for (int i = 0; i < pluginClass->children_.size(); ++i)
|
||||
@@ -278,7 +320,7 @@ void PiPedalHost::AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClas
|
||||
child->parent_ = pluginClass.get();
|
||||
}
|
||||
}
|
||||
void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
|
||||
void PluginHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
|
||||
{
|
||||
std::ifstream f(jsonFile);
|
||||
json_reader reader(f);
|
||||
@@ -292,7 +334,7 @@ void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
|
||||
MAP_CHECK();
|
||||
}
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClass *pClass)
|
||||
std::shared_ptr<Lv2PluginClass> PluginHost::GetPluginClass(const LilvPluginClass *pClass)
|
||||
{
|
||||
MAP_CHECK();
|
||||
|
||||
@@ -300,7 +342,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::GetPluginClass(const LilvPluginClas
|
||||
std::shared_ptr<Lv2PluginClass> pResult = this->classesMap[uri];
|
||||
return pResult;
|
||||
}
|
||||
std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginClass *pClass)
|
||||
std::shared_ptr<Lv2PluginClass> PluginHost::MakePluginClass(const LilvPluginClass *pClass)
|
||||
{
|
||||
std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
|
||||
auto t = this->classesMap.find(uri);
|
||||
@@ -320,7 +362,7 @@ std::shared_ptr<Lv2PluginClass> PiPedalHost::MakePluginClass(const LilvPluginCla
|
||||
return result;
|
||||
}
|
||||
|
||||
void PiPedalHost::LoadPluginClassesFromLilv()
|
||||
void PluginHost::LoadPluginClassesFromLilv()
|
||||
{
|
||||
const auto classes = lilv_world_get_plugin_classes(pWorld);
|
||||
|
||||
@@ -338,19 +380,19 @@ void PiPedalHost::LoadPluginClassesFromLilv()
|
||||
|
||||
if (lv2Class && lv2Class->parent_uri().size() != 0)
|
||||
{
|
||||
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
|
||||
if (parentClass)
|
||||
{
|
||||
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
|
||||
ccClass->set_parent(parentClass);
|
||||
Lv2PluginClass *ccParentClass = parentClass.get();
|
||||
ccParentClass->add_child(lv2Class);
|
||||
}
|
||||
std::shared_ptr<Lv2PluginClass> parentClass = GetPluginClass(lv2Class->parent_uri());
|
||||
if (parentClass)
|
||||
{
|
||||
Lv2PluginClass *ccClass = const_cast<Lv2PluginClass *>(lv2Class.get());
|
||||
ccClass->set_parent(parentClass);
|
||||
Lv2PluginClass *ccParentClass = parentClass.get();
|
||||
ccParentClass->add_child(lv2Class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalHost::Load(const char *lv2Path)
|
||||
void PluginHost::Load(const char *lv2Path)
|
||||
{
|
||||
|
||||
this->plugins_.clear();
|
||||
@@ -388,30 +430,30 @@ void PiPedalHost::Load(const char *lv2Path)
|
||||
|
||||
if (pluginInfo->hasCvPorts())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
#if !SUPPORT_MIDI
|
||||
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
#endif
|
||||
else if (!pluginInfo->is_valid())
|
||||
{
|
||||
auto &ports = pluginInfo->ports();
|
||||
for (int i = 0; i < ports.size(); ++i)
|
||||
{
|
||||
auto &port = ports[i];
|
||||
if (!port->is_valid())
|
||||
auto &ports = pluginInfo->ports();
|
||||
for (int i = 0; i < ports.size(); ++i)
|
||||
{
|
||||
Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str());
|
||||
auto &port = ports[i];
|
||||
if (!port->is_valid())
|
||||
{
|
||||
Lv2Log::debug("Plugin port %s:%s is invalid.", pluginInfo->name().c_str(), port->name().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. Not valid.", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->plugins_.push_back(pluginInfo);
|
||||
this->plugins_.push_back(pluginInfo);
|
||||
}
|
||||
}
|
||||
auto messages = stdoutCapture.GetOutputLines();
|
||||
@@ -440,28 +482,28 @@ void PiPedalHost::Load(const char *lv2Path)
|
||||
if (plugin->is_valid())
|
||||
{
|
||||
#if SUPPORT_MIDI
|
||||
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
else if (info.audio_outputs() == 0 && !info.has_midi_output())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
else if (info.audio_outputs() == 0 && !info.has_midi_output())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
#else
|
||||
if (info.audio_inputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
else if (info.audio_outputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
if (info.audio_inputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
else if (info.audio_outputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
ui_plugins_.push_back(std::move(info));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui_plugins_.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,8 +518,8 @@ void PiPedalHost::Load(const char *lv2Path)
|
||||
const auto &vst3PluginList = this->vst3Host->getPluginList();
|
||||
for (const auto &vst3Plugin : vst3PluginList)
|
||||
{
|
||||
// copy not move!
|
||||
ui_plugins_.push_back(vst3Plugin->pluginInfo_);
|
||||
// copy not move!
|
||||
ui_plugins_.push_back(vst3Plugin->pluginInfo_);
|
||||
}
|
||||
auto ui_compare = [&collation](
|
||||
Lv2PluginUiInfo &left,
|
||||
@@ -532,10 +574,10 @@ static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
|
||||
"comment";
|
||||
const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
|
||||
"comment";
|
||||
|
||||
LilvNode *PiPedalHost::get_comment(const std::string &uri)
|
||||
LilvNode *PluginHost::get_comment(const std::string &uri)
|
||||
{
|
||||
AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
|
||||
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
|
||||
@@ -547,7 +589,7 @@ static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shar
|
||||
return p1->index() < p2->index();
|
||||
}
|
||||
|
||||
bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin)
|
||||
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
|
||||
{
|
||||
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
|
||||
bool result = false;
|
||||
@@ -559,8 +601,21 @@ bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *pl
|
||||
return result;
|
||||
}
|
||||
|
||||
Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
||||
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
||||
{
|
||||
|
||||
AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin);
|
||||
if (!bundleUriNode)
|
||||
throw std::logic_error("Invalid bundle uri.");
|
||||
|
||||
std::string bundleUri = bundleUriNode.AsUri();
|
||||
|
||||
std::string bundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr);
|
||||
if (bundlePath.length() == 0)
|
||||
throw std::logic_error("Bundle uri is not a file uri.");
|
||||
|
||||
this->bundle_path_ = bundlePath;
|
||||
|
||||
this->has_factory_presets_ = HasFactoryPresets(lv2Host, pPlugin);
|
||||
|
||||
this->uri_ = nodeAsString(lilv_plugin_get_uri(pPlugin));
|
||||
@@ -604,15 +659,15 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
|
||||
std::shared_ptr<Lv2PortInfo> portInfo = std::make_shared<Lv2PortInfo>(lv2Host, pPlugin, pPort);
|
||||
if (!portInfo->is_valid())
|
||||
{
|
||||
isValid = false;
|
||||
isValid = false;
|
||||
}
|
||||
const auto &portGroup = portInfo->port_group();
|
||||
if (portGroup.size() != 0)
|
||||
{
|
||||
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
|
||||
{
|
||||
portGroups.push_back(portGroup);
|
||||
}
|
||||
if (std::find(portGroups.begin(), portGroups.end(), portGroup) == portGroups.end())
|
||||
{
|
||||
portGroups.push_back(portGroup);
|
||||
}
|
||||
}
|
||||
ports_.push_back(std::move(portInfo));
|
||||
}
|
||||
@@ -626,8 +681,8 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
|
||||
{
|
||||
if (!this->IsSupportedFeature(this->required_features_[i]))
|
||||
{
|
||||
Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
|
||||
isValid = false;
|
||||
Lv2Log::debug("%s (%s) requires feature %s.", this->name_.c_str(), this->uri_.c_str(), this->required_features_[i].c_str());
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,7 +697,7 @@ Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const Lilv
|
||||
nullptr);
|
||||
if (pipedalUINode)
|
||||
{
|
||||
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode);
|
||||
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
|
||||
}
|
||||
// xxx lilv_world_get(pWorld,pluginUri,);
|
||||
// for (auto&portInfo: ports_)
|
||||
@@ -666,7 +721,12 @@ std::vector<std::string> supportedFeatures = {
|
||||
LV2_BUF_SIZE__fixedBlockLength,
|
||||
LV2_BUF_SIZE__powerOf2BlockLength,
|
||||
LV2_CORE__isLive,
|
||||
LV2_STATE__loadDefaultState
|
||||
LV2_STATE__loadDefaultState,
|
||||
LV2_STATE__makePath,
|
||||
LV2_STATE__mapPath,
|
||||
|
||||
// UI features that we can ignore, since we won't load their ui.
|
||||
"http://lv2plug.in/ns/extensions/ui#makeResident",
|
||||
|
||||
};
|
||||
|
||||
@@ -675,7 +735,7 @@ bool Lv2PluginInfo::IsSupportedFeature(const std::string &feature) const
|
||||
for (int i = 0; i < supportedFeatures.size(); ++i)
|
||||
{
|
||||
if (supportedFeatures[i] == feature)
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -683,7 +743,7 @@ Lv2PluginInfo::~Lv2PluginInfo()
|
||||
{
|
||||
}
|
||||
|
||||
bool Lv2PortInfo::is_a(PiPedalHost *lv2Plugins, const char *classUri)
|
||||
bool Lv2PortInfo::is_a(PluginHost *lv2Plugins, const char *classUri)
|
||||
{
|
||||
return classes_.is_a(lv2Plugins, classUri);
|
||||
}
|
||||
@@ -705,7 +765,7 @@ static bool scale_points_sort_compare(const Lv2ScalePoint &v1, const Lv2ScalePoi
|
||||
{
|
||||
return v1.value() < v2.value();
|
||||
}
|
||||
Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
|
||||
Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
|
||||
{
|
||||
auto pWorld = host->pWorld;
|
||||
index_ = lilv_port_get_index(plugin, pPort);
|
||||
@@ -720,7 +780,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
|
||||
min_value_ = 0;
|
||||
max_value_ = 1;
|
||||
default_value_ = 0;
|
||||
lilv_port_get_range(plugin, pPort, &defaultNode.Get(), &minNode.Get(), &maxNode.Get());
|
||||
lilv_port_get_range(plugin, pPort, defaultNode, minNode, maxNode);
|
||||
if (defaultNode)
|
||||
{
|
||||
default_value_ = getFloat(defaultNode);
|
||||
@@ -748,7 +808,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
|
||||
auto priority_node = lilv_nodes_get_first(priority_nodes);
|
||||
if (priority_node)
|
||||
{
|
||||
this->display_priority_ = lilv_node_as_int(priority_node);
|
||||
this->display_priority_ = lilv_node_as_int(priority_node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,7 +819,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
|
||||
auto range_steps_node = lilv_nodes_get_first(range_steps_nodes);
|
||||
if (range_steps_node)
|
||||
{
|
||||
this->range_steps_ = lilv_node_as_int(range_steps_node);
|
||||
this->range_steps_ = lilv_node_as_int(range_steps_node);
|
||||
}
|
||||
}
|
||||
this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.integer_property_uri);
|
||||
@@ -819,7 +879,7 @@ Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const Lilv
|
||||
((is_input_ || is_output_) && (is_audio_port_ || is_atom_port_ || is_cv_port_));
|
||||
}
|
||||
|
||||
Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
|
||||
Lv2PluginClasses PluginHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort);
|
||||
@@ -827,9 +887,9 @@ Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, c
|
||||
{
|
||||
LILV_FOREACH(nodes, iNode, nodes)
|
||||
{
|
||||
const LilvNode *node = lilv_nodes_get(nodes, iNode);
|
||||
std::string classUri = nodeAsString(node);
|
||||
result.push_back(classUri);
|
||||
const LilvNode *node = lilv_nodes_get(nodes, iNode);
|
||||
std::string classUri = nodeAsString(node);
|
||||
result.push_back(classUri);
|
||||
}
|
||||
}
|
||||
return Lv2PluginClasses(result);
|
||||
@@ -851,20 +911,20 @@ bool Lv2PluginClass::is_a(const std::string &classUri) const
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Lv2PluginClasses::is_a(PiPedalHost *lv2Plugins, const char *classUri) const
|
||||
bool Lv2PluginClasses::is_a(PluginHost *lv2Plugins, const char *classUri) const
|
||||
{
|
||||
std::string classUri_(classUri);
|
||||
for (auto i : classes_)
|
||||
{
|
||||
if (lv2Plugins->is_a(classUri_, i))
|
||||
{
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PiPedalHost::is_a(const std::string &class_, const std::string &target_class)
|
||||
bool PluginHost::is_a(const std::string &class_, const std::string &target_class)
|
||||
{
|
||||
if (class_ == target_class)
|
||||
return true;
|
||||
@@ -876,7 +936,7 @@ bool PiPedalHost::is_a(const std::string &class_, const std::string &target_clas
|
||||
return false;
|
||||
}
|
||||
|
||||
Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin)
|
||||
Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
||||
: uri_(plugin->uri()),
|
||||
name_(plugin->name()),
|
||||
author_name_(plugin->author_name()),
|
||||
@@ -898,38 +958,38 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
|
||||
{
|
||||
PLUGIN_MAP_CHECK();
|
||||
this->plugin_display_type_ = "Plugin";
|
||||
Lv2Log::error("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str());
|
||||
Lv2Log::warning("%s: Unknown plugin type: %s", plugin->uri().c_str(), plugin->plugin_class().c_str());
|
||||
}
|
||||
for (auto port : plugin->ports())
|
||||
{
|
||||
if (port->is_input())
|
||||
{
|
||||
if (port->is_control_port() && port->is_input())
|
||||
{
|
||||
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
|
||||
}
|
||||
else if (port->is_atom_port())
|
||||
{
|
||||
if (port->supports_midi())
|
||||
if (port->is_control_port() && port->is_input())
|
||||
{
|
||||
has_midi_input_ = true;
|
||||
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
|
||||
}
|
||||
else if (port->is_atom_port())
|
||||
{
|
||||
if (port->supports_midi())
|
||||
{
|
||||
has_midi_input_ = true;
|
||||
}
|
||||
}
|
||||
else if (port->is_audio_port())
|
||||
{
|
||||
++audio_inputs_;
|
||||
}
|
||||
}
|
||||
else if (port->is_audio_port())
|
||||
{
|
||||
++audio_inputs_;
|
||||
}
|
||||
}
|
||||
else if (port->is_output())
|
||||
{
|
||||
if (port->is_atom_port() && port->supports_midi())
|
||||
{
|
||||
this->has_midi_output_ = true;
|
||||
}
|
||||
else if (port->is_audio_port())
|
||||
{
|
||||
++audio_outputs_;
|
||||
}
|
||||
if (port->is_atom_port() && port->supports_midi())
|
||||
{
|
||||
this->has_midi_output_ = true;
|
||||
}
|
||||
else if (port->is_audio_port())
|
||||
{
|
||||
++audio_outputs_;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &portGroup : plugin->port_groups())
|
||||
@@ -941,55 +1001,56 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin
|
||||
if (piPedalUI)
|
||||
{
|
||||
this->fileProperties_ = piPedalUI->fileProperties();
|
||||
this->uiPortNotifications_ = piPedalUI->portNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> PiPedalHost::GetLv2PluginClass() const
|
||||
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
|
||||
{
|
||||
return this->GetPluginClass(LV2_PLUGIN);
|
||||
}
|
||||
|
||||
std::shared_ptr<Lv2PluginInfo> PiPedalHost::GetPluginInfo(const std::string &uri) const
|
||||
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
|
||||
{
|
||||
for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i)
|
||||
{
|
||||
if ((*i)->uri() == uri)
|
||||
{
|
||||
return (*i);
|
||||
return (*i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Lv2PedalBoard *PiPedalHost::CreateLv2PedalBoard(PedalBoard &pedalBoard)
|
||||
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard)
|
||||
{
|
||||
Lv2PedalBoard *pPedalBoard = new Lv2PedalBoard();
|
||||
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
|
||||
try
|
||||
{
|
||||
pPedalBoard->Prepare(this, pedalBoard);
|
||||
return pPedalBoard;
|
||||
pPedalboard->Prepare(this, pedalboard);
|
||||
return pPedalboard;
|
||||
}
|
||||
catch (const std::exception &)
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
delete pPedalBoard;
|
||||
delete pPedalboard;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
struct StateCallbackData
|
||||
{
|
||||
PiPedalHost *pHost;
|
||||
PluginHost *pHost;
|
||||
std::vector<ControlValue> *pResult;
|
||||
};
|
||||
|
||||
void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
void *user_data,
|
||||
const void *value,
|
||||
uint32_t size,
|
||||
uint32_t type)
|
||||
void PluginHost::fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
void *user_data,
|
||||
const void *value,
|
||||
uint32_t size,
|
||||
uint32_t type)
|
||||
{
|
||||
StateCallbackData *pData = (StateCallbackData *)user_data;
|
||||
PiPedalHost *pHost = pData->pHost;
|
||||
PluginHost *pHost = pData->pHost;
|
||||
std::vector<ControlValue> *pResult = pData->pResult;
|
||||
LV2_URID valueType = (LV2_URID)type;
|
||||
if (valueType != pHost->urids->atom_Float)
|
||||
@@ -999,15 +1060,15 @@ void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
pResult->push_back(ControlValue(port_symbol, *(float *)value));
|
||||
}
|
||||
|
||||
std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
|
||||
PedalBoardItem *pedalBoardItem, const std::string &presetUri)
|
||||
std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
|
||||
PedalboardItem *pedalboardItem, const std::string &presetUri)
|
||||
{
|
||||
|
||||
std::vector<ControlValue> result;
|
||||
|
||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
|
||||
|
||||
AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalBoardItem->uri().c_str());
|
||||
AutoLilvNode uriNode = lilv_new_uri(pWorld, pedalboardItem->uri().c_str());
|
||||
|
||||
lilv_world_load_resource(pWorld, uriNode);
|
||||
|
||||
@@ -1027,27 +1088,27 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
|
||||
if (presetUri == thisPresetUri)
|
||||
{
|
||||
|
||||
/*********************************/
|
||||
/*********************************/
|
||||
|
||||
// AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
|
||||
// AutoLilvNode uriNode = lilv_new_uri(pWorld, presetUri.c_str());
|
||||
|
||||
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
|
||||
auto lilvState = lilv_state_new_from_world(pWorld, GetLv2UridMap(), preset);
|
||||
|
||||
if (lilvState == nullptr)
|
||||
{
|
||||
throw PiPedalStateException("Preset not found.");
|
||||
}
|
||||
if (lilvState == nullptr)
|
||||
{
|
||||
throw PiPedalStateException("Preset not found.");
|
||||
}
|
||||
|
||||
StateCallbackData cbData;
|
||||
cbData.pHost = this;
|
||||
cbData.pResult = &result;
|
||||
auto n = lilv_state_get_num_properties(lilvState);
|
||||
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
|
||||
StateCallbackData cbData;
|
||||
cbData.pHost = this;
|
||||
cbData.pResult = &result;
|
||||
auto n = lilv_state_get_num_properties(lilvState);
|
||||
lilv_state_emit_port_values(lilvState, fn_LilvSetPortValueFunc, &cbData);
|
||||
|
||||
lilv_state_free(lilvState);
|
||||
break;
|
||||
lilv_state_free(lilvState);
|
||||
break;
|
||||
|
||||
/*********************************/
|
||||
/*********************************/
|
||||
}
|
||||
}
|
||||
lilv_nodes_free(presets);
|
||||
@@ -1057,15 +1118,15 @@ std::vector<ControlValue> PiPedalHost::LoadFactoryPluginPreset(
|
||||
|
||||
struct PresetCallbackState
|
||||
{
|
||||
PiPedalHost *pHost;
|
||||
PluginHost *pHost;
|
||||
std::map<std::string, float> *values;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
|
||||
void PluginHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
|
||||
{
|
||||
PresetCallbackState *pState = static_cast<PresetCallbackState *>(user_data);
|
||||
PiPedalHost *pHost = pState->pHost;
|
||||
PluginHost *pHost = pState->pHost;
|
||||
|
||||
if (type == pHost->urids->atom_Float)
|
||||
{
|
||||
@@ -1076,7 +1137,7 @@ void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const v
|
||||
pState->failed = true;
|
||||
}
|
||||
}
|
||||
PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
||||
PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
||||
{
|
||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
|
||||
|
||||
@@ -1102,36 +1163,36 @@ PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
||||
LilvState *state = lilv_state_new_from_world(pWorld, this->mapFeature.GetMap(), preset);
|
||||
if (state != nullptr)
|
||||
{
|
||||
std::string label = lilv_state_get_label(state);
|
||||
std::map<std::string, float> controlValues;
|
||||
PresetCallbackState cbData{this, &controlValues, false};
|
||||
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
|
||||
lilv_state_free(state);
|
||||
std::string label = lilv_state_get_label(state);
|
||||
std::map<std::string, float> controlValues;
|
||||
PresetCallbackState cbData{this, &controlValues, false};
|
||||
lilv_state_emit_port_values(state, PortValueCallback, (void *)&cbData);
|
||||
lilv_state_free(state);
|
||||
|
||||
if (!cbData.failed)
|
||||
{
|
||||
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
|
||||
}
|
||||
if (!cbData.failed)
|
||||
{
|
||||
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
|
||||
}
|
||||
}
|
||||
}
|
||||
lilv_nodes_free(presets);
|
||||
return result;
|
||||
}
|
||||
|
||||
IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
|
||||
IEffect *PluginHost::CreateEffect(PedalboardItem &pedalboardItem)
|
||||
{
|
||||
if (pedalBoardItem.uri().starts_with("vst3:"))
|
||||
if (pedalboardItem.uri().starts_with("vst3:"))
|
||||
{
|
||||
#if ENABLE_VST3
|
||||
try
|
||||
{
|
||||
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this);
|
||||
return vst3Plugin.release();
|
||||
Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalboardItem, this);
|
||||
return vst3Plugin.release();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
|
||||
throw;
|
||||
Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
Lv2Log::error(std::string("VST3 support not enabled at compile time."));
|
||||
@@ -1141,15 +1202,15 @@ IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto info = this->GetPluginInfo(pedalBoardItem.uri());
|
||||
auto info = this->GetPluginInfo(pedalboardItem.uri());
|
||||
if (!info)
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
|
||||
return new Lv2Effect(this, info, pedalBoardItem);
|
||||
return new Lv2Effect(this, info, pedalboardItem);
|
||||
}
|
||||
}
|
||||
|
||||
Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
|
||||
Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
|
||||
{
|
||||
LilvWorld *pWorld = lv2Host->pWorld;
|
||||
|
||||
@@ -1161,11 +1222,15 @@ Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
|
||||
name_ = nodeAsString(nameNode);
|
||||
}
|
||||
|
||||
void PiPedalHostLogError(const std::string&errror)
|
||||
std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread()
|
||||
{
|
||||
|
||||
return pHostWorkerThread;
|
||||
}
|
||||
|
||||
|
||||
// void PiPedalHostLogError(const std::string &error)
|
||||
// {
|
||||
// Lv2Log::error("%s",error.c_str());
|
||||
// }
|
||||
|
||||
#define MAP_REF(class, name) \
|
||||
json_map::reference(#name, &class ::name##_)
|
||||
@@ -1231,6 +1296,7 @@ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||
}};
|
||||
|
||||
json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
||||
json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_),
|
||||
json_map::reference("uri", &Lv2PluginInfo::uri_),
|
||||
json_map::reference("name", &Lv2PluginInfo::name_),
|
||||
json_map::reference("plugin_class", &Lv2PluginInfo::plugin_class_),
|
||||
@@ -1290,20 +1356,22 @@ json_map::storage_type<Lv2PluginUiControlPort> Lv2PluginUiControlPort::jmap{{
|
||||
|
||||
}};
|
||||
|
||||
json_map::storage_type<Lv2PluginUiInfo> Lv2PluginUiInfo::jmap{{
|
||||
json_map::reference("uri", &Lv2PluginUiInfo::uri_),
|
||||
json_map::reference("name", &Lv2PluginUiInfo::name_),
|
||||
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
|
||||
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
|
||||
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
|
||||
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
|
||||
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
|
||||
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
|
||||
json_map::reference("description", &Lv2PluginUiInfo::description_),
|
||||
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
|
||||
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
|
||||
json_map::reference("controls", &Lv2PluginUiInfo::controls_),
|
||||
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
|
||||
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
|
||||
json_map::reference("fileProperties",&Lv2PluginUiInfo::fileProperties_)
|
||||
}};
|
||||
json_map::storage_type<Lv2PluginUiInfo>
|
||||
Lv2PluginUiInfo::jmap{
|
||||
{json_map::reference("uri", &Lv2PluginUiInfo::uri_),
|
||||
json_map::reference("name", &Lv2PluginUiInfo::name_),
|
||||
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
|
||||
json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
|
||||
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
|
||||
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
|
||||
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
|
||||
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
|
||||
json_map::reference("description", &Lv2PluginUiInfo::description_),
|
||||
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
|
||||
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
|
||||
json_map::reference("controls", &Lv2PluginUiInfo::controls_),
|
||||
json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
|
||||
json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
|
||||
json_map::reference("fileProperties", &Lv2PluginUiInfo::fileProperties_),
|
||||
json_map::reference("uiPortNotifications", &Lv2PluginUiInfo::uiPortNotifications_),
|
||||
}};
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <memory>
|
||||
#include "json.hpp"
|
||||
#include "PluginType.hpp"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include <lilv/lilv.h>
|
||||
#include "MapFeature.hpp"
|
||||
#include "LogFeature.hpp"
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <filesystem>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include "IHost.hpp"
|
||||
|
||||
#include "lv2.h"
|
||||
#include "Units.hpp"
|
||||
@@ -40,14 +41,15 @@
|
||||
#include "PiPedalConfiguration.hpp"
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "MapPathFeature.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class Lv2Effect;
|
||||
class Lv2PedalBoard;
|
||||
class PiPedalHost;
|
||||
class Lv2Pedalboard;
|
||||
class PluginHost;
|
||||
class JackConfiguration;
|
||||
class JackChannelSelection;
|
||||
|
||||
@@ -82,7 +84,7 @@ namespace pipedal
|
||||
class Lv2PluginClass
|
||||
{
|
||||
public:
|
||||
friend class PiPedalHost;
|
||||
friend class PluginHost;
|
||||
|
||||
private:
|
||||
Lv2PluginClass *parent_ = nullptr; // NOT SERIALIZED!
|
||||
@@ -92,7 +94,7 @@ namespace pipedal
|
||||
PluginType plugin_type_;
|
||||
std::vector<std::shared_ptr<Lv2PluginClass>> children_;
|
||||
|
||||
friend class ::pipedal::PiPedalHost;
|
||||
friend class ::pipedal::PluginHost;
|
||||
// hide copy constructor.
|
||||
Lv2PluginClass(const Lv2PluginClass &other)
|
||||
{
|
||||
@@ -151,7 +153,7 @@ namespace pipedal
|
||||
{
|
||||
return classes_;
|
||||
}
|
||||
bool is_a(PiPedalHost *lv2Plugins, const char *classUri) const;
|
||||
bool is_a(PluginHost *lv2Plugins, const char *classUri) const;
|
||||
|
||||
static json_map::storage_type<Lv2PluginClasses> jmap;
|
||||
};
|
||||
@@ -186,7 +188,7 @@ namespace pipedal
|
||||
class Lv2PortInfo
|
||||
{
|
||||
public:
|
||||
Lv2PortInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort);
|
||||
Lv2PortInfo(PluginHost *lv2Host, const LilvPlugin *pPlugin, const LilvPort *pPort);
|
||||
|
||||
private:
|
||||
friend class Lv2PluginInfo;
|
||||
@@ -303,7 +305,7 @@ namespace pipedal
|
||||
public:
|
||||
Lv2PortInfo() {}
|
||||
~Lv2PortInfo() = default;
|
||||
bool is_a(PiPedalHost *lv2Plugins, const char *classUri);
|
||||
bool is_a(PluginHost *lv2Plugins, const char *classUri);
|
||||
|
||||
static json_map::storage_type<Lv2PortInfo> jmap;
|
||||
};
|
||||
@@ -321,7 +323,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(name);
|
||||
|
||||
Lv2PortGroup() {}
|
||||
Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri);
|
||||
Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri);
|
||||
|
||||
static json_map::storage_type<Lv2PortGroup> jmap;
|
||||
};
|
||||
@@ -329,14 +331,15 @@ namespace pipedal
|
||||
class Lv2PluginInfo
|
||||
{
|
||||
private:
|
||||
friend class PiPedalHost;
|
||||
friend class PluginHost;
|
||||
|
||||
public:
|
||||
Lv2PluginInfo(PiPedalHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
|
||||
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
|
||||
Lv2PluginInfo() {}
|
||||
|
||||
private:
|
||||
bool HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin);
|
||||
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
||||
std::string bundle_path_;
|
||||
std::string uri_;
|
||||
std::string name_;
|
||||
std::string plugin_class_;
|
||||
@@ -358,6 +361,7 @@ namespace pipedal
|
||||
bool IsSupportedFeature(const std::string &feature) const;
|
||||
|
||||
public:
|
||||
LV2_PROPERTY_GETSET(bundle_path)
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
LV2_PROPERTY_GETSET(name)
|
||||
LV2_PROPERTY_GETSET(plugin_class)
|
||||
@@ -537,7 +541,7 @@ namespace pipedal
|
||||
{
|
||||
public:
|
||||
Lv2PluginUiInfo() {}
|
||||
Lv2PluginUiInfo(PiPedalHost *pPlugins, const Lv2PluginInfo *plugin);
|
||||
Lv2PluginUiInfo(PluginHost *pPlugins, const Lv2PluginInfo *plugin);
|
||||
|
||||
private:
|
||||
std::string uri_;
|
||||
@@ -555,7 +559,8 @@ namespace pipedal
|
||||
|
||||
std::vector<Lv2PluginUiControlPort> controls_;
|
||||
std::vector<Lv2PluginUiPortGroup> port_groups_;
|
||||
std::vector<PiPedalFileProperty::ptr> fileProperties_;
|
||||
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||
std::vector<UiPortNotification::ptr> uiPortNotifications_;
|
||||
|
||||
public:
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
@@ -573,32 +578,11 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(port_groups)
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_vst3)
|
||||
LV2_PROPERTY_GETSET(fileProperties)
|
||||
LV2_PROPERTY_GETSET(uiPortNotifications)
|
||||
|
||||
static json_map::storage_type<Lv2PluginUiInfo> jmap;
|
||||
};
|
||||
|
||||
class IHost
|
||||
{
|
||||
public:
|
||||
virtual LilvWorld *getWorld() = 0;
|
||||
virtual LV2_URID_Map *GetLv2UridMap() = 0;
|
||||
virtual LV2_URID GetLv2Urid(const char *uri) = 0;
|
||||
virtual std::string Lv2UriudToString(LV2_URID urid) = 0;
|
||||
|
||||
virtual LV2_Feature *const *GetLv2Features() const = 0;
|
||||
virtual double GetSampleRate() const = 0;
|
||||
virtual void SetMaxAudioBufferSize(size_t size) = 0;
|
||||
virtual size_t GetMaxAudioBufferSize() const = 0;
|
||||
virtual size_t GetAtomBufferSize() const = 0;
|
||||
|
||||
virtual bool HasMidiInputChannel() const = 0;
|
||||
virtual int GetNumberOfInputAudioChannels() const = 0;
|
||||
virtual int GetNumberOfOutputAudioChannels() const = 0;
|
||||
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
|
||||
|
||||
virtual IEffect *CreateEffect(PedalBoardItem &pedalBoard) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#if ENABLE_VST3
|
||||
@@ -608,7 +592,7 @@ namespace pipedal
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
class PiPedalHost : private IHost
|
||||
class PluginHost : private IHost
|
||||
{
|
||||
private:
|
||||
#if ENABLE_VST3
|
||||
@@ -618,6 +602,7 @@ namespace pipedal
|
||||
friend class pipedal::AutoLilvNode;
|
||||
friend class pipedal::PiPedalUI;
|
||||
static const char *RDFS_COMMENT_URI;
|
||||
|
||||
public:
|
||||
class LilvUris
|
||||
{
|
||||
@@ -648,7 +633,6 @@ namespace pipedal
|
||||
AutoLilvNode pipedalUI__fileProperties;
|
||||
AutoLilvNode pipedalUI__directory;
|
||||
AutoLilvNode pipedalUI__patchProperty;
|
||||
AutoLilvNode pipedalUI__defaultFile;
|
||||
|
||||
AutoLilvNode pipedalUI__fileProperty;
|
||||
|
||||
@@ -665,11 +649,18 @@ namespace pipedal
|
||||
|
||||
AutoLilvNode appliesTo;
|
||||
AutoLilvNode isA;
|
||||
|
||||
AutoLilvNode ui__portNotification;
|
||||
AutoLilvNode ui__plugin;
|
||||
AutoLilvNode ui__protocol;
|
||||
AutoLilvNode ui__floatProtocol;
|
||||
AutoLilvNode ui__peakProtocol;
|
||||
AutoLilvNode ui__portIndex;
|
||||
AutoLilvNode lv2__symbol;
|
||||
};
|
||||
LilvUris lilvUris;
|
||||
|
||||
private:
|
||||
|
||||
bool vst3Enabled = true;
|
||||
|
||||
LilvNode *get_comment(const std::string &uri);
|
||||
@@ -683,10 +674,11 @@ namespace pipedal
|
||||
|
||||
std::string vst3CachePath;
|
||||
|
||||
LV2_Feature *const *lv2Features = nullptr;
|
||||
std::vector<const LV2_Feature *> lv2Features;
|
||||
MapFeature mapFeature;
|
||||
LogFeature logFeature;
|
||||
OptionsFeature optionsFeature;
|
||||
MapPathFeature mapPathFeature;
|
||||
|
||||
static void fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
void *user_data,
|
||||
@@ -714,21 +706,21 @@ namespace pipedal
|
||||
// IHost implementation
|
||||
|
||||
public:
|
||||
void LogError(const std::string&message)
|
||||
void LogError(const std::string &message)
|
||||
{
|
||||
logFeature.LogError("%s",message.c_str());
|
||||
logFeature.LogError("%s", message.c_str());
|
||||
}
|
||||
void LogWarning(const std::string&message)
|
||||
void LogWarning(const std::string &message)
|
||||
{
|
||||
logFeature.LogWarning("%s",message.c_str());
|
||||
logFeature.LogWarning("%s", message.c_str());
|
||||
}
|
||||
void LogNote(const std::string&message)
|
||||
void LogNote(const std::string &message)
|
||||
{
|
||||
logFeature.LogNote("%s",message.c_str());
|
||||
logFeature.LogNote("%s", message.c_str());
|
||||
}
|
||||
void LogTrace(const std::string&message)
|
||||
void LogTrace(const std::string &message)
|
||||
{
|
||||
logFeature.LogTrace("%s",message.c_str());
|
||||
logFeature.LogTrace("%s", message.c_str());
|
||||
}
|
||||
virtual LilvWorld *getWorld()
|
||||
{
|
||||
@@ -736,33 +728,41 @@ namespace pipedal
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<HostWorkerThread> pHostWorkerThread;
|
||||
// IHost implementation.
|
||||
virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; }
|
||||
virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; }
|
||||
virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; }
|
||||
virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; }
|
||||
virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; }
|
||||
virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; }
|
||||
virtual LV2_Feature *const *GetLv2Features() const { return this->lv2Features; }
|
||||
virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); }
|
||||
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread();
|
||||
|
||||
public:
|
||||
virtual MapFeature &GetMapFeature() { return this->mapFeature; }
|
||||
|
||||
private:
|
||||
virtual LV2_URID_Map *GetLv2UridMap()
|
||||
{
|
||||
return this->mapFeature.GetMap();
|
||||
}
|
||||
static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type);
|
||||
|
||||
virtual IEffect *CreateEffect(PedalBoardItem &pedalBoardItem);
|
||||
virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem);
|
||||
void LoadPluginClassesFromLilv();
|
||||
void AddJsonClassesToMap(std::shared_ptr<Lv2PluginClass> pluginClass);
|
||||
|
||||
public:
|
||||
PiPedalHost();
|
||||
|
||||
PluginHost();
|
||||
void SetPluginStoragePath(const std::filesystem::path &path);
|
||||
void SetConfiguration(const PiPedalConfiguration &configuration);
|
||||
|
||||
virtual ~PiPedalHost();
|
||||
virtual ~PluginHost();
|
||||
|
||||
IHost *asIHost() { return this; }
|
||||
|
||||
virtual Lv2PedalBoard *CreateLv2PedalBoard(PedalBoard &pedalBoard);
|
||||
virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard);
|
||||
|
||||
void setSampleRate(double sampleRate)
|
||||
{
|
||||
@@ -792,19 +792,19 @@ namespace pipedal
|
||||
static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2";
|
||||
|
||||
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
|
||||
void Load(const char *lv2Path = PiPedalHost::DEFAULT_LV2_PATH);
|
||||
void Load(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
|
||||
|
||||
virtual LV2_URID GetLv2Urid(const char *uri)
|
||||
{
|
||||
return this->mapFeature.GetUrid(uri);
|
||||
}
|
||||
virtual std::string Lv2UriudToString(LV2_URID urid)
|
||||
virtual std::string Lv2UridToString(LV2_URID urid)
|
||||
{
|
||||
return this->mapFeature.UridToString(urid);
|
||||
}
|
||||
|
||||
PluginPresets GetFactoryPluginPresets(const std::string &pluginUri);
|
||||
std::vector<ControlValue> LoadFactoryPluginPreset(PedalBoardItem *pedalBoardItem,
|
||||
std::vector<ControlValue> LoadFactoryPluginPreset(PedalboardItem *pedalboardItem,
|
||||
const std::string &presetUri);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ JSON_MAP_BEGIN(PluginPreset)
|
||||
JSON_MAP_REFERENCE(PluginPreset,instanceId)
|
||||
JSON_MAP_REFERENCE(PluginPreset,label)
|
||||
JSON_MAP_REFERENCE(PluginPreset,controlValues)
|
||||
JSON_MAP_REFERENCE(PluginPreset,state)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(PluginPresets)
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include "StateInterface.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
@@ -87,6 +89,7 @@ public:
|
||||
uint64_t instanceId_;
|
||||
std::string label_;
|
||||
std::map<std::string,float> controlValues_;
|
||||
Lv2PluginState state_;
|
||||
DECLARE_JSON_MAP(PluginPreset);
|
||||
};
|
||||
|
||||
|
||||
+8
-8
@@ -27,17 +27,17 @@ JSON_MAP_BEGIN(PedalPreset)
|
||||
JSON_MAP_REFERENCE(PedalPreset,values)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(PedalBoardPreset)
|
||||
JSON_MAP_REFERENCE(PedalBoardPreset,instanceId)
|
||||
JSON_MAP_REFERENCE(PedalBoardPreset,displayName)
|
||||
JSON_MAP_REFERENCE(PedalBoardPreset,values)
|
||||
JSON_MAP_BEGIN(PedalboardPreset)
|
||||
JSON_MAP_REFERENCE(PedalboardPreset,instanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardPreset,displayName)
|
||||
JSON_MAP_REFERENCE(PedalboardPreset,values)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(PedalBoardPresets)
|
||||
JSON_MAP_REFERENCE(PedalBoardPresets,nextInstanceId)
|
||||
JSON_MAP_REFERENCE(PedalBoardPresets,currentPreset)
|
||||
JSON_MAP_REFERENCE(PedalBoardPresets,presets)
|
||||
JSON_MAP_BEGIN(PedalboardPresets)
|
||||
JSON_MAP_REFERENCE(PedalboardPresets,nextInstanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardPresets,currentPreset)
|
||||
JSON_MAP_REFERENCE(PedalboardPresets,presets)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
+6
-6
@@ -19,7 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
@@ -32,21 +32,21 @@ public:
|
||||
DECLARE_JSON_MAP(PedalPreset);
|
||||
};
|
||||
|
||||
class PedalBoardPreset {
|
||||
class PedalboardPreset {
|
||||
uint64_t instanceId_;
|
||||
std::string displayName_;
|
||||
std::vector<std::unique_ptr<PedalPreset> > values_;
|
||||
public:
|
||||
DECLARE_JSON_MAP(PedalBoardPreset);
|
||||
DECLARE_JSON_MAP(PedalboardPreset);
|
||||
};
|
||||
|
||||
class PedalBoardPresets {
|
||||
class PedalboardPresets {
|
||||
long nextInstanceId_ = 0;
|
||||
|
||||
long currentPreset_ = 0;
|
||||
std::vector<std::unique_ptr<PedalBoardPreset> > presets_;
|
||||
std::vector<std::unique_ptr<PedalboardPreset> > presets_;
|
||||
public:
|
||||
DECLARE_JSON_MAP(PedalBoardPresets);
|
||||
DECLARE_JSON_MAP(PedalboardPresets);
|
||||
|
||||
};
|
||||
|
||||
|
||||
+493
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2023 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "Promise.hpp"
|
||||
#include "catch.hpp"
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
TEST_CASE("Promise", "[promise][Build][Dev]")
|
||||
{
|
||||
{
|
||||
// synchronous then->get
|
||||
Promise<double> p1 = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
resolve(1.0);
|
||||
});
|
||||
Promise<int> p2 = p1
|
||||
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
|
||||
{ resolve((int)(value + 3)); });
|
||||
int result = p2.Get();
|
||||
REQUIRE(result == 4.0);
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
|
||||
////////////////////////
|
||||
{
|
||||
// synchronous resolve.
|
||||
double result = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
resolve(1.0);
|
||||
})
|
||||
.Get();
|
||||
REQUIRE(result == 1.0);
|
||||
}
|
||||
{
|
||||
// async resolve (Get)
|
||||
double result = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[resolve]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
resolve(2.0);
|
||||
});
|
||||
})
|
||||
.Get();
|
||||
REQUIRE(result == 2.0);
|
||||
}
|
||||
{
|
||||
// async resolve (Then)
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[resolve]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
resolve(3.0);
|
||||
});
|
||||
})
|
||||
.Then([&cv, &mutex, &ready](double result)
|
||||
{
|
||||
REQUIRE(result == 3.0);
|
||||
{
|
||||
std::lock_guard lock { mutex};
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all(); })
|
||||
.Catch([](const std::string &message)
|
||||
{
|
||||
REQUIRE(true == false); // should not get here.
|
||||
});
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock lock{mutex};
|
||||
if (ready)
|
||||
break;
|
||||
cv.wait(lock);
|
||||
}
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
{
|
||||
// async resolve (Then, Then<int32_t>)
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[resolve]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
resolve(3.0);
|
||||
});
|
||||
})
|
||||
.Then<int32_t>([](double value, ResolveFunction<int32_t> resolve, RejectFunction reject)
|
||||
{
|
||||
REQUIRE(value == 3.0);
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[resolve, value]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
resolve((int32_t)(value + 10));
|
||||
});
|
||||
})
|
||||
.Then([&cv, &mutex, &ready](int32_t result)
|
||||
{
|
||||
REQUIRE(result == 13);
|
||||
{
|
||||
std::lock_guard lock { mutex};
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all(); });
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock lock{mutex};
|
||||
if (ready)
|
||||
break;
|
||||
cv.wait(lock);
|
||||
}
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
|
||||
{
|
||||
// synchronous then->get
|
||||
int result = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
resolve(1.0);
|
||||
})
|
||||
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
|
||||
{ resolve((int)(value + 3)); })
|
||||
.Get();
|
||||
REQUIRE(result == 4.0);
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
|
||||
{
|
||||
// synchronous reject
|
||||
Promise<double> promise = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
reject("Rejected");
|
||||
});
|
||||
REQUIRE_THROWS(promise.Get());
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
{
|
||||
// async reject (Get)
|
||||
Promise<double> promise = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[reject]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
reject("Rejected");
|
||||
});
|
||||
});
|
||||
REQUIRE_THROWS(promise.Get());
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
{
|
||||
// synchronous then then<int>/reject->get
|
||||
Promise<int> promise = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
resolve(1.0);
|
||||
})
|
||||
.Then<int>(
|
||||
[](double value, ResolveFunction<int> resolve, RejectFunction reject)
|
||||
{
|
||||
reject("reject");
|
||||
});
|
||||
REQUIRE_THROWS(promise.Get());
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
{
|
||||
// synchronous then reject->then<int>->get
|
||||
Promise<int> promise = Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
reject("reject");
|
||||
})
|
||||
.Then<int>([](double value, ResolveFunction<int> resolve, RejectFunction reject)
|
||||
{ resolve((int)(value + 3)); });
|
||||
REQUIRE_THROWS(promise.Get());
|
||||
}
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
|
||||
{
|
||||
// async resolve (Catch)
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
Promise<double>(
|
||||
[](ResolveFunction<double> resolve, RejectFunction reject)
|
||||
{
|
||||
auto _ = std::async(
|
||||
std::launch::async,
|
||||
[reject]()
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
reject("rejected");
|
||||
});
|
||||
})
|
||||
.Then([&cv, &mutex, &ready](double result)
|
||||
{ REQUIRE(true == false); })
|
||||
.Catch([&cv, &mutex, &ready](const std::string &message)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all(); });
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock lock{mutex};
|
||||
if (ready)
|
||||
break;
|
||||
cv.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(PromiseInnerBase::allocationCount == 0);
|
||||
}
|
||||
+253
-99
@@ -22,28 +22,35 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include <condition_variable>
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
#endif /* NO_MLOCK */
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
enum class RingBufferStatus
|
||||
{
|
||||
Ready,
|
||||
TimedOut,
|
||||
Closed
|
||||
};
|
||||
template <bool MULTI_WRITER = false, bool SEMAPHORE_READER = false>
|
||||
class RingBuffer {
|
||||
class RingBuffer
|
||||
{
|
||||
char *buffer;
|
||||
bool mlocked = false;
|
||||
size_t ringBufferSize;
|
||||
size_t ringBufferMask;
|
||||
volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition
|
||||
volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition
|
||||
std::mutex write_mutex;
|
||||
std::mutex mutex;
|
||||
std::mutex writeMutex;
|
||||
|
||||
sem_t readSemaphore;
|
||||
bool semaphore_open = false;
|
||||
bool is_open = true;
|
||||
std::condition_variable cvRead;
|
||||
|
||||
size_t nextPowerOfTwo(size_t size)
|
||||
{
|
||||
@@ -54,205 +61,352 @@ namespace pipedal
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public:
|
||||
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
|
||||
RingBuffer(size_t ringBufferSize = 65536, bool mLock = true)
|
||||
{
|
||||
this->ringBufferSize = ringBufferSize = nextPowerOfTwo(ringBufferSize);
|
||||
ringBufferMask = ringBufferSize-1;
|
||||
ringBufferMask = ringBufferSize - 1;
|
||||
buffer = new char[ringBufferSize];
|
||||
|
||||
if (SEMAPHORE_READER) {
|
||||
sem_init(&readSemaphore,0,0);
|
||||
semaphore_open = true;
|
||||
}
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#ifndef NO_MLOCK
|
||||
if (mLock)
|
||||
{
|
||||
if (mlock (buffer, ringBufferSize)) {
|
||||
throw PiPedalStateException("Mlock failed.");
|
||||
if (mlock(buffer, ringBufferSize))
|
||||
{
|
||||
throw PiPedalStateException("Mlock failed.");
|
||||
}
|
||||
this->mlocked = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset() {
|
||||
void reset()
|
||||
{
|
||||
this->readPosition = 0;
|
||||
this->writePosition = 0;
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
sem_destroy(&readSemaphore);
|
||||
sem_init(&readSemaphore,0,0);
|
||||
this->semaphore_open = true;
|
||||
}
|
||||
cvRead.notify_all();
|
||||
}
|
||||
void close() {
|
||||
void close()
|
||||
{
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
this->semaphore_open = false;
|
||||
sem_post(&readSemaphore);
|
||||
}
|
||||
}
|
||||
// 0 -> ready. -1: timed out. -2: closing.
|
||||
int readWait(const struct timespec& timeoutMs) {
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
int result = sem_timedwait(&readSemaphore,&timeoutMs);
|
||||
if (!semaphore_open) return -2;
|
||||
return (result == 0) ? 0: -1;
|
||||
} else {
|
||||
throw PiPedalStateException("SEMAPHORE_READER is not set to true.");
|
||||
this->is_open = false;
|
||||
cvRead.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool readWait() {
|
||||
if (SEMAPHORE_READER)
|
||||
template <class Rep, class Period>
|
||||
RingBufferStatus readWait_for(const std::chrono::duration<Rep, Period> &timeout)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
sem_wait(&readSemaphore);
|
||||
return semaphore_open;
|
||||
} else {
|
||||
throw PiPedalStateException("SEMAPHORE_READER is not set to true.");
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (isReadReady_())
|
||||
{
|
||||
return RingBufferStatus::Ready;
|
||||
}
|
||||
if (!is_open)
|
||||
return RingBufferStatus::Closed;
|
||||
|
||||
auto status = cvRead.wait_for(lock, timeout);
|
||||
if (status == std::cv_status::timeout)
|
||||
{
|
||||
return RingBufferStatus::TimedOut;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert("SEMAPHORE_READER is not set to true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t writeSpace() {
|
||||
// at most ringBufferSize-1 in order to
|
||||
|
||||
template <class Clock, class Duration>
|
||||
RingBufferStatus readWait_until(const std::chrono::time_point<Clock, Duration> &time_point)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (isReadReady_())
|
||||
{
|
||||
return RingBufferStatus::Ready;
|
||||
}
|
||||
if (!is_open)
|
||||
return RingBufferStatus::Closed;
|
||||
|
||||
auto status = cvRead.wait_until(lock, time_point);
|
||||
if (status == std::cv_status::timeout)
|
||||
{
|
||||
return RingBufferStatus::TimedOut;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert("SEMAPHORE_READER is not set to true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
template <class Clock, class Duration>
|
||||
RingBufferStatus readWait_until(size_t size,const std::chrono::time_point<Clock, Duration> &time_point)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
size_t available = readSpace_();
|
||||
if (available >= size)
|
||||
{
|
||||
return RingBufferStatus::Ready;
|
||||
}
|
||||
if (!is_open)
|
||||
return RingBufferStatus::Closed;
|
||||
|
||||
auto status = cvRead.wait_until(lock, time_point);
|
||||
if (status == std::cv_status::timeout)
|
||||
{
|
||||
return RingBufferStatus::TimedOut;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert("SEMAPHORE_READER is not set to true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool readWait()
|
||||
{
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (isReadReady_())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_open)
|
||||
return false;
|
||||
cvRead.wait(lock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert("SEMAPHORE_READER is not set to true.");
|
||||
}
|
||||
}
|
||||
size_t writeSpace()
|
||||
{
|
||||
// at most ringBufferSize-1 in order to
|
||||
// to distinguish the empty buffer from the full buffer.
|
||||
int64_t size = readPosition-1-writePosition;
|
||||
if (size < 0) size += this->ringBufferSize;
|
||||
std::unique_lock lock(mutex);
|
||||
|
||||
int64_t size = readPosition - 1 - writePosition;
|
||||
if (size < 0)
|
||||
size += this->ringBufferSize;
|
||||
return (size_t)size;
|
||||
}
|
||||
size_t readSpace() {
|
||||
int64_t size = writePosition-readPosition;
|
||||
if (size < 0) size += this->ringBufferSize;
|
||||
return size_t(size);
|
||||
|
||||
|
||||
size_t readSpace()
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
return readSpace_();
|
||||
}
|
||||
bool write(size_t bytes, uint8_t *data)
|
||||
{
|
||||
if (MULTI_WRITER)
|
||||
{
|
||||
std::lock_guard guard(write_mutex);
|
||||
if (writeSpace() < bytes) {
|
||||
std::lock_guard writeLock{writeMutex};
|
||||
if (writeSpace() < bytes + sizeof(bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
size_t index = this->writePosition;
|
||||
for (size_t i = 0; i < bytes; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data[i];
|
||||
buffer[(index + i) & ringBufferMask] = data[i];
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
this->writePosition = (index + bytes) & ringBufferMask;
|
||||
}
|
||||
this->writePosition = (index+bytes) & ringBufferMask;
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
sem_post(&readSemaphore);
|
||||
cvRead.notify_all();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (writeSpace() < bytes) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (writeSpace() < sizeof(bytes) + bytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
size_t index = this->writePosition;
|
||||
for (size_t i = 0; i < bytes; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data[i];
|
||||
buffer[(index + i) & ringBufferMask] = data[i];
|
||||
}
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
this->writePosition = (index + bytes) & ringBufferMask;
|
||||
}
|
||||
this->writePosition = (index+bytes) & ringBufferMask;
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
sem_post(&readSemaphore);
|
||||
cvRead.notify_all();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Write two disjoint areas of memory atomically.
|
||||
bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t*data2)
|
||||
bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t *data2)
|
||||
{
|
||||
if (MULTI_WRITER)
|
||||
{
|
||||
std::lock_guard guard(write_mutex);
|
||||
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) {
|
||||
std::lock_guard guard(writeMutex);
|
||||
if (writeSpace() <= sizeof(bytes) + bytes +bytes2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
size_t index = this->writePosition;
|
||||
for (size_t i = 0; i < bytes; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data[i];
|
||||
buffer[(index + i) & ringBufferMask] = data[i];
|
||||
}
|
||||
index = (index+bytes) & ringBufferMask;
|
||||
|
||||
index = (index + bytes) & ringBufferMask;
|
||||
|
||||
for (size_t i = 0; i < sizeof(bytes2); ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i];
|
||||
buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i];
|
||||
}
|
||||
index = (index+sizeof(bytes2)) & ringBufferMask;
|
||||
index = (index + sizeof(bytes2)) & ringBufferMask;
|
||||
|
||||
for (size_t i = 0; i < bytes2; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data2[i];
|
||||
buffer[(index + i) & ringBufferMask] = data2[i];
|
||||
}
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
this->writePosition = (index + bytes2) & ringBufferMask;
|
||||
}
|
||||
this->writePosition = (index+bytes2) & ringBufferMask;
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
sem_post(&readSemaphore);
|
||||
cvRead.notify_all();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (writeSpace() <= sizeof(bytes2) + bytes + bytes2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
size_t index = this->writePosition;
|
||||
for (size_t i = 0; i < bytes; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data[i];
|
||||
buffer[(index + i) & ringBufferMask] = data[i];
|
||||
}
|
||||
index = (index+bytes) & ringBufferMask;
|
||||
|
||||
index = (index + bytes) & ringBufferMask;
|
||||
|
||||
for (size_t i = 0; i < sizeof(bytes2); ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i];
|
||||
buffer[(index + i) & ringBufferMask] = ((char *)&bytes2)[i];
|
||||
}
|
||||
index = (index+sizeof(bytes2)) & ringBufferMask;
|
||||
index = (index + sizeof(bytes2)) & ringBufferMask;
|
||||
|
||||
for (size_t i = 0; i < bytes2; ++i)
|
||||
{
|
||||
buffer[(index+i) & ringBufferMask] = data2[i];
|
||||
buffer[(index + i) & ringBufferMask] = data2[i];
|
||||
}
|
||||
this->writePosition = (index+bytes2) & ringBufferMask;
|
||||
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
this->writePosition = (index + bytes2) & ringBufferMask;
|
||||
}
|
||||
|
||||
if (SEMAPHORE_READER)
|
||||
{
|
||||
sem_post(&readSemaphore);
|
||||
cvRead.notify_all();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool read(size_t bytes, uint8_t*data)
|
||||
bool read(size_t bytes, uint8_t *data)
|
||||
{
|
||||
if (readSpace() < bytes) return false;
|
||||
if (readSpace() < bytes)
|
||||
return false;
|
||||
int64_t readPosition = this->readPosition;
|
||||
for (size_t i = 0; i < bytes; ++i)
|
||||
{
|
||||
data[i] = this->buffer[(readPosition+i) & this->ringBufferMask];
|
||||
data[i] = this->buffer[(readPosition + i) & this->ringBufferMask];
|
||||
}
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
this->readPosition = (readPosition + bytes) & this->ringBufferMask;
|
||||
}
|
||||
this->readPosition = (readPosition + bytes) & this->ringBufferMask;
|
||||
return true;
|
||||
}
|
||||
~RingBuffer()
|
||||
~RingBuffer()
|
||||
{
|
||||
#ifdef USE_MLOCK
|
||||
if (this->mlocked)
|
||||
{
|
||||
munlock(buffer,ringBufferSize);
|
||||
}
|
||||
#endif
|
||||
if (SEMAPHORE_READER)
|
||||
#ifdef USE_MLOCK
|
||||
if (this->mlocked)
|
||||
{
|
||||
sem_destroy(&this->readSemaphore);
|
||||
munlock(buffer, ringBufferSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
delete[] buffer;
|
||||
}
|
||||
bool isReadReady() {
|
||||
std::lock_guard lock(mutex);
|
||||
if (isReadReady_()) return true;
|
||||
return !this->is_open;
|
||||
}
|
||||
bool isReadReady(size_t size) {
|
||||
size_t available = readSpace();
|
||||
return available >= size;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
size_t readSpace_()
|
||||
{
|
||||
int64_t size = writePosition - readPosition;
|
||||
if (size < 0)
|
||||
size += this->ringBufferSize;
|
||||
return size_t(size);
|
||||
}
|
||||
|
||||
uint32_t peekSize()
|
||||
{
|
||||
volatile uint32_t result;
|
||||
uint8_t *p = (uint8_t*)&result;
|
||||
size_t ix = this->readPosition;
|
||||
for (size_t i = 0; i < sizeof(result); ++i)
|
||||
{
|
||||
*p++ = this->buffer[(ix++) & ringBufferMask];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool isReadReady_()
|
||||
{
|
||||
size_t available = readSpace_();
|
||||
if (available < sizeof(uint32_t)) return false;
|
||||
// peak to get the size!
|
||||
uint32_t packetSize = peekSize();
|
||||
return packetSize+sizeof(uint32_t) <= available;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
+35
-14
@@ -23,6 +23,8 @@
|
||||
#include "Lv2Log.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "lv2/atom.lv2/atom.h"
|
||||
#include <chrono>
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
@@ -56,6 +58,8 @@ namespace pipedal
|
||||
|
||||
NextMidiProgram = 20,
|
||||
|
||||
Lv2StateChanged = 21,
|
||||
|
||||
};
|
||||
|
||||
struct RealtimeNextMidiProgramRequest {
|
||||
@@ -151,17 +155,17 @@ namespace pipedal
|
||||
float value;
|
||||
};
|
||||
|
||||
class Lv2PedalBoard;
|
||||
class Lv2Pedalboard;
|
||||
|
||||
class ReplaceEffectBody
|
||||
{
|
||||
public:
|
||||
Lv2PedalBoard *effect;
|
||||
Lv2Pedalboard *effect;
|
||||
};
|
||||
class EffectReplacedBody
|
||||
{
|
||||
public:
|
||||
Lv2PedalBoard *oldEffect;
|
||||
Lv2Pedalboard *oldEffect;
|
||||
};
|
||||
|
||||
template <bool MULTI_WRITE, bool SEMAPHORE_READ>
|
||||
@@ -182,8 +186,20 @@ namespace pipedal
|
||||
}
|
||||
|
||||
// 0 -> ready. -1: timed out. -2: closing.
|
||||
int wait(struct timespec &timeout) {
|
||||
return ringBuffer->readWait(timeout);
|
||||
template <class Rep, class Period>
|
||||
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) {
|
||||
return ringBuffer->readWait_for(timeout);
|
||||
}
|
||||
|
||||
template <typename Clock, typename Duration>
|
||||
RingBufferStatus wait_until(std::chrono::time_point<Clock,Duration>&time_point)
|
||||
{
|
||||
return ringBuffer->readWait_until(time_point);
|
||||
}
|
||||
template <typename Clock, typename Duration>
|
||||
RingBufferStatus wait_until(size_t size,std::chrono::time_point<Clock,Duration>&time_point)
|
||||
{
|
||||
return ringBuffer->readWait_until(size,time_point);
|
||||
}
|
||||
|
||||
bool wait() {
|
||||
@@ -196,9 +212,6 @@ namespace pipedal
|
||||
template <typename T>
|
||||
bool read(T *output)
|
||||
{
|
||||
size_t available = readSpace();
|
||||
if (available < sizeof(T))
|
||||
return false;
|
||||
if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
|
||||
{
|
||||
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
|
||||
@@ -294,15 +307,23 @@ namespace pipedal
|
||||
|
||||
}
|
||||
}
|
||||
void Lv2StateChanged(uint64_t instanceId)
|
||||
{
|
||||
write(RingBufferCommand::Lv2StateChanged,instanceId);
|
||||
}
|
||||
void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data)
|
||||
{
|
||||
write(RingBufferCommand::AtomOutput,instanceId,bytes,data);
|
||||
}
|
||||
void ParameterRequest(RealtimeParameterRequest *pRequest)
|
||||
void AtomOutput(uint64_t instanceId, const LV2_Atom*atom)
|
||||
{
|
||||
write(RingBufferCommand::AtomOutput,instanceId,atom->size+sizeof(LV2_Atom),(uint8_t*)atom);
|
||||
}
|
||||
void ParameterRequest(RealtimePatchPropertyRequest *pRequest)
|
||||
{
|
||||
write(RingBufferCommand::ParameterRequest,pRequest);
|
||||
}
|
||||
void ParameterRequestComplete(RealtimeParameterRequest *pRequest)
|
||||
void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest)
|
||||
{
|
||||
write(RingBufferCommand::ParameterRequestComplete,pRequest);
|
||||
}
|
||||
@@ -407,9 +428,9 @@ namespace pipedal
|
||||
write(RingBufferCommand::SetBypass,body);
|
||||
}
|
||||
|
||||
void ReplaceEffect(Lv2PedalBoard *pedalBoard)
|
||||
void ReplaceEffect(Lv2Pedalboard *pedalboard)
|
||||
{
|
||||
write(RingBufferCommand::ReplaceEffect, pedalBoard);
|
||||
write(RingBufferCommand::ReplaceEffect, pedalboard);
|
||||
}
|
||||
|
||||
void AudioStopped()
|
||||
@@ -418,9 +439,9 @@ namespace pipedal
|
||||
write(RingBufferCommand::AudioStopped, body);
|
||||
}
|
||||
|
||||
void EffectReplaced(Lv2PedalBoard *pedalBoard)
|
||||
void EffectReplaced(Lv2Pedalboard *pedalboard)
|
||||
{
|
||||
write(RingBufferCommand::EffectReplaced, pedalBoard);
|
||||
write(RingBufferCommand::EffectReplaced, pedalboard);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@
|
||||
#include "pch.h"
|
||||
#include "SplitEffect.hpp"
|
||||
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PedalBoard.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
+3
-3
@@ -291,10 +291,11 @@ namespace pipedal
|
||||
|
||||
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
|
||||
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
|
||||
virtual void RequestParameter(LV2_URID uridUri) {}
|
||||
virtual void GatherParameter(RealtimeParameterRequest *pRequest) {}
|
||||
virtual void RequestPatchProperty(LV2_URID uridUri) {}
|
||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
|
||||
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
|
||||
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
|
||||
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
|
||||
|
||||
virtual bool IsVst3() const { return false; }
|
||||
|
||||
@@ -670,7 +671,6 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PostMix(uint32_t frames)
|
||||
{
|
||||
if (this->outputBuffers.size() == 1)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 ↦
|
||||
LilvInstance *pInstance;
|
||||
const LV2_State_Interface *pluginStateInterface;
|
||||
std::vector<LV2_Feature *> features;
|
||||
};
|
||||
}
|
||||
+95
-60
@@ -28,6 +28,8 @@
|
||||
#include <map>
|
||||
#include <sys/stat.h>
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -233,13 +235,20 @@ void Storage::LoadBank(int64_t instanceId)
|
||||
{
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
|
||||
|
||||
LoadBankFile(indexEntry.name(), &(this->currentBank));
|
||||
if (this->bankIndex.selectedBank() != instanceId)
|
||||
try
|
||||
{
|
||||
this->bankIndex.selectedBank(instanceId);
|
||||
SaveBankIndex();
|
||||
LoadBankFile(indexEntry.name(), &(this->currentBank));
|
||||
if (this->bankIndex.selectedBank() != instanceId)
|
||||
{
|
||||
this->bankIndex.selectedBank(instanceId);
|
||||
SaveBankIndex();
|
||||
}
|
||||
this->LoadPreset(this->currentBank.selectedPreset());
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
throw std::logic_error(SS("Bank file corrupted. " << e.what() << "(" << GetBankFileName(indexEntry.name()) << ")"));
|
||||
}
|
||||
this->LoadPreset(this->currentBank.selectedPreset());
|
||||
}
|
||||
|
||||
void Storage::LoadCurrentBank()
|
||||
@@ -255,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
|
||||
{
|
||||
return this->dataRoot / "plugin_presets";
|
||||
}
|
||||
std::filesystem::path Storage::GetAudioFilesDirectory() const
|
||||
std::filesystem::path Storage::GetPluginStorageDirectory() const
|
||||
{
|
||||
return this->dataRoot / "audio_uploads";
|
||||
}
|
||||
@@ -297,8 +306,8 @@ void Storage::LoadBankIndex()
|
||||
if (bankIndex.entries().size() == 0)
|
||||
{
|
||||
currentBank.clear();
|
||||
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
|
||||
int64_t instanceId = currentBank.addPreset(defaultPedalBoard);
|
||||
Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
|
||||
int64_t instanceId = currentBank.addPreset(defaultPedalboard);
|
||||
currentBank.selectedPreset(instanceId);
|
||||
|
||||
std::string name = "Default Bank";
|
||||
@@ -378,7 +387,7 @@ void Storage::ReIndex()
|
||||
void Storage::CreateBank(const std::string &name)
|
||||
{
|
||||
BankFile bankFile;
|
||||
PedalBoard defaultPreset = PedalBoard::MakeDefault();
|
||||
Pedalboard defaultPreset = Pedalboard::MakeDefault();
|
||||
defaultPreset.name(std::string("Default Preset"));
|
||||
|
||||
bankFile.addPreset(defaultPreset);
|
||||
@@ -449,7 +458,7 @@ void Storage::SaveCurrentBank()
|
||||
SaveBankFile(indexEntry.name(), this->currentBank);
|
||||
}
|
||||
|
||||
const PedalBoard &Storage::GetCurrentPreset()
|
||||
const Pedalboard &Storage::GetCurrentPreset()
|
||||
{
|
||||
auto &item = currentBank.getItem(currentBank.selectedPreset());
|
||||
return item.preset();
|
||||
@@ -466,18 +475,18 @@ bool Storage::LoadPreset(int64_t instanceId)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void Storage::SaveCurrentPreset(const PedalBoard &pedalBoard)
|
||||
void Storage::SaveCurrentPreset(const Pedalboard &pedalboard)
|
||||
{
|
||||
auto &item = currentBank.getItem(currentBank.selectedPreset());
|
||||
item.preset(pedalBoard);
|
||||
item.preset(pedalboard);
|
||||
SaveCurrentBank();
|
||||
}
|
||||
int64_t Storage::SaveCurrentPresetAs(const PedalBoard &pedalBoard, const std::string &name, int64_t saveAfterInstanceId)
|
||||
int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, const std::string &name, int64_t saveAfterInstanceId)
|
||||
{
|
||||
PedalBoard newPedalBoard = pedalBoard;
|
||||
newPedalBoard.name(name);
|
||||
Pedalboard newPedalboard = pedalboard;
|
||||
newPedalboard.name(name);
|
||||
|
||||
int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId);
|
||||
int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId);
|
||||
currentBank.selectedPreset(newInstanceId);
|
||||
SaveCurrentBank();
|
||||
return newInstanceId;
|
||||
@@ -530,17 +539,18 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
|
||||
pResult->presets().push_back(entry);
|
||||
}
|
||||
}
|
||||
int64_t Storage::GetPresetByProgramNumber(uint8_t program)const
|
||||
int64_t Storage::GetPresetByProgramNumber(uint8_t program) const
|
||||
{
|
||||
if (program >= currentBank.presets().size())
|
||||
{
|
||||
if (currentBank.presets().size() == 0) return -1;
|
||||
program = (uint8_t)currentBank.presets().size()-1;
|
||||
if (currentBank.presets().size() == 0)
|
||||
return -1;
|
||||
program = (uint8_t)currentBank.presets().size() - 1;
|
||||
}
|
||||
return currentBank.presets()[program]->instanceId();
|
||||
}
|
||||
|
||||
PedalBoard Storage::GetPreset(int64_t instanceId) const
|
||||
Pedalboard Storage::GetPreset(int64_t instanceId) const
|
||||
{
|
||||
for (size_t i = 0; i < currentBank.presets().size(); ++i)
|
||||
{
|
||||
@@ -630,10 +640,10 @@ int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
|
||||
auto &fromItem = this->currentBank.getItem(fromId);
|
||||
if (toId == -1)
|
||||
{
|
||||
PedalBoard newPedalBoard = fromItem.preset();
|
||||
Pedalboard newPedalboard = fromItem.preset();
|
||||
std::string name = GetPresetCopyName(fromItem.preset().name());
|
||||
newPedalBoard.name(name);
|
||||
return this->currentBank.addPreset(newPedalBoard, fromId);
|
||||
newPedalboard.name(name);
|
||||
return this->currentBank.addPreset(newPedalboard, fromId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -753,13 +763,14 @@ void Storage::MoveBank(int from, int to)
|
||||
this->SaveBankIndex();
|
||||
}
|
||||
|
||||
|
||||
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) {
|
||||
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber)
|
||||
{
|
||||
auto &entries = this->bankIndex.entries();
|
||||
if (bankNumber >= entries.size())
|
||||
{
|
||||
if (entries.size() == 0) return -1;
|
||||
bankNumber = (uint8_t)(entries.size()-1);
|
||||
if (entries.size() == 0)
|
||||
return -1;
|
||||
bankNumber = (uint8_t)(entries.size() - 1);
|
||||
}
|
||||
return entries[bankNumber].instanceId();
|
||||
}
|
||||
@@ -792,8 +803,8 @@ int64_t Storage::DeleteBank(int64_t bankId)
|
||||
BankIndexEntry newEntry;
|
||||
|
||||
BankFile defaultBank;
|
||||
PedalBoard defaultPedalBoard = PedalBoard::MakeDefault();
|
||||
int64_t instanceId = defaultBank.addPreset(defaultPedalBoard);
|
||||
Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
|
||||
int64_t instanceId = defaultBank.addPreset(defaultPedalboard);
|
||||
defaultBank.selectedPreset(instanceId);
|
||||
|
||||
std::string name = "Default Bank";
|
||||
@@ -832,7 +843,7 @@ int64_t Storage::UploadPreset(const BankFile &bankFile, int64_t uploadAfter)
|
||||
}
|
||||
for (size_t i = 0; i < bankFile.presets().size(); ++i)
|
||||
{
|
||||
PedalBoard preset = bankFile.presets()[i]->preset();
|
||||
Pedalboard preset = bankFile.presets()[i]->preset();
|
||||
|
||||
int n = 2;
|
||||
std::string baseName = preset.name();
|
||||
@@ -1063,8 +1074,11 @@ bool Storage::RestoreCurrentPreset(CurrentPreset *pResult)
|
||||
reader.read(pResult);
|
||||
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
|
||||
}
|
||||
catch (const std::exception &)
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to restore current preset. " << e.what()));
|
||||
|
||||
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1330,9 +1344,6 @@ void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
|
||||
pipedal::JackServerSettings Storage::GetJackServerSettings()
|
||||
{
|
||||
JackServerSettings result;
|
||||
#if JACK_HOST
|
||||
result.Initialize();
|
||||
#else
|
||||
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
|
||||
std::ifstream f;
|
||||
f.open(fileName);
|
||||
@@ -1341,14 +1352,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
}
|
||||
#if JACK_HOST
|
||||
result.Initialize();
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration)
|
||||
{
|
||||
#if JACK_HOST
|
||||
#error IMPLEMENT ME
|
||||
#else
|
||||
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
|
||||
std::ofstream f;
|
||||
f.open(fileName);
|
||||
@@ -1357,10 +1368,12 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
|
||||
json_writer writer(f);
|
||||
writer.write(jackConfiguration);
|
||||
}
|
||||
#if JACK_HOST
|
||||
jackConfiguration.Write();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
|
||||
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
|
||||
{
|
||||
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
|
||||
std::ofstream f;
|
||||
@@ -1382,63 +1395,85 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(MidiBinding::SystemBinding("prevProgram"));
|
||||
result.push_back(MidiBinding::SystemBinding("nextProgram"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool containsDotDot(const std::string&value)
|
||||
static bool containsDotDot(const std::string &value)
|
||||
{
|
||||
std::size_t offset = value.find("..");
|
||||
return offset != std::string::npos;
|
||||
}
|
||||
static bool containsDirectorySeparator(const std::string&value)
|
||||
static bool containsDirectorySeparator(const std::string &value)
|
||||
{
|
||||
if (value.find("/") != std::string::npos) return true; //linux
|
||||
if (value.find("\\") != std::string::npos) return true; // windows
|
||||
if (value.find("::") != std::string::npos) return true; // mac
|
||||
if (value.find("/") != std::string::npos)
|
||||
return true; // linux
|
||||
if (value.find("\\") != std::string::npos)
|
||||
return true; // windows
|
||||
if (value.find("::") != std::string::npos)
|
||||
return true; // mac
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void ThrowPermissionDeniedError()
|
||||
static void ThrowPermissionDeniedError()
|
||||
{
|
||||
throw std::logic_error("Permission denied.");
|
||||
}
|
||||
std::vector<std::string> Storage::GetFileList(const PiPedalFileProperty&fileProperty)
|
||||
|
||||
class LexicographicCompare
|
||||
{
|
||||
if (!PiPedalFileProperty::IsDirectoryNameValid(fileProperty.directory()))
|
||||
public:
|
||||
bool operator()(const std::string &left, const std::string &right)
|
||||
{
|
||||
return std::lexicographical_compare(left.begin(), left.end(), right.begin(), right.end());
|
||||
}
|
||||
} lexicographicCompare;
|
||||
|
||||
std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty)
|
||||
{
|
||||
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
|
||||
{
|
||||
ThrowPermissionDeniedError();
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
std::filesystem::path audioFileDirectory = this->GetAudioFilesDirectory() / fileProperty.directory();
|
||||
|
||||
try {
|
||||
for (auto const&dir_entry: std::filesystem::directory_iterator(audioFileDirectory))
|
||||
// if fileProperty has a user-accessible directory, push the entire file path.
|
||||
if (fileProperty.directory().size() != 0)
|
||||
{
|
||||
std::filesystem::path audioFileDirectory = this->GetPluginStorageDirectory() / fileProperty.directory();
|
||||
try
|
||||
{
|
||||
if (dir_entry.is_regular_file())
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
|
||||
{
|
||||
auto &path = dir_entry.path();
|
||||
if (fileProperty.IsValidExtension(path.extension().string()))
|
||||
if (dir_entry.is_regular_file())
|
||||
{
|
||||
result.push_back(fileProperty.directory() / path.filename());
|
||||
auto &path = dir_entry.path();
|
||||
if (fileProperty.IsValidExtension(path.extension().string()))
|
||||
{
|
||||
// a relative path!
|
||||
result.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(const std::exception&error)
|
||||
{
|
||||
throw std::logic_error("Directory not found: " + audioFileDirectory.string());
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
|
||||
}
|
||||
}
|
||||
|
||||
// sort lexicographically
|
||||
|
||||
std::sort(result.begin(), result.end(), lexicographicCompare);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UserSettings)
|
||||
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
||||
|
||||
+15
-10
@@ -20,7 +20,7 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include "PedalBoard.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include "Presets.hpp"
|
||||
#include "PluginPreset.hpp"
|
||||
#include "Banks.hpp"
|
||||
@@ -33,12 +33,13 @@
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class PiPedalFileProperty;
|
||||
class UiFileProperty;
|
||||
class Lv2PluginInfo;
|
||||
|
||||
class CurrentPreset {
|
||||
public:
|
||||
bool modified_ = false;
|
||||
PedalBoard preset_;
|
||||
Pedalboard preset_;
|
||||
|
||||
DECLARE_JSON_MAP(CurrentPreset);
|
||||
};
|
||||
@@ -63,12 +64,12 @@ private:
|
||||
PluginPresetIndex pluginPresetIndex;
|
||||
|
||||
private:
|
||||
|
||||
void MaybeCopyDefaultPresets();
|
||||
static std::string SafeEncodeName(const std::string& name);
|
||||
static std::string SafeDecodeName(const std::string& name);
|
||||
std::filesystem::path GetPresetsDirectory() const;
|
||||
std::filesystem::path GetPluginPresetsDirectory() const;
|
||||
std::filesystem::path GetAudioFilesDirectory() const;
|
||||
std::filesystem::path GetIndexFileName() const;
|
||||
std::filesystem::path GetBankFileName(const std::string & name) const;
|
||||
std::filesystem::path GetChannelSelectionFileName();
|
||||
@@ -99,7 +100,9 @@ public:
|
||||
void SetDataRoot(const std::filesystem::path& path);
|
||||
void SetConfigRoot(const std::filesystem::path& path);
|
||||
|
||||
std::vector<std::string> GetPedalBoards();
|
||||
std::filesystem::path GetPluginStorageDirectory() const;
|
||||
|
||||
std::vector<std::string> GetPedalboards();
|
||||
|
||||
const BankIndex & GetBanks() const { return bankIndex; }
|
||||
|
||||
@@ -112,13 +115,13 @@ public:
|
||||
void SaveUserSettings();
|
||||
void LoadBank(int64_t instanceId);
|
||||
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
|
||||
const PedalBoard& GetCurrentPreset();
|
||||
void SaveCurrentPreset(const PedalBoard&pedalBoard);
|
||||
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
|
||||
const Pedalboard& GetCurrentPreset();
|
||||
void SaveCurrentPreset(const Pedalboard&pedalboard);
|
||||
int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, const std::string&namne,int64_t saveAfterInstanceId = -1);
|
||||
int64_t GetCurrentPresetId() const;
|
||||
void GetPresetIndex(PresetIndex*pResult);
|
||||
void SetPresetIndex(const PresetIndex &presetIndex);
|
||||
PedalBoard GetPreset(int64_t instanceId) const;
|
||||
Pedalboard GetPreset(int64_t instanceId) const;
|
||||
int64_t GetPresetByProgramNumber(uint8_t program) const;
|
||||
void GetBankFile(int64_t instanceId,BankFile*pResult) const;
|
||||
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
|
||||
@@ -136,7 +139,7 @@ public:
|
||||
void MoveBank(int from, int to);
|
||||
int64_t DeleteBank(int64_t bankId);
|
||||
|
||||
std::vector<std::string> GetFileList(const PiPedalFileProperty&fileProperty);
|
||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
||||
|
||||
|
||||
void SetJackChannelSelection(const JackChannelSelection&channelSelection);
|
||||
@@ -156,6 +159,8 @@ public:
|
||||
void SaveCurrentPreset(const CurrentPreset ¤tPreset);
|
||||
bool RestoreCurrentPreset(CurrentPreset*pResult);
|
||||
|
||||
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
|
||||
|
||||
private:
|
||||
bool pluginPresetIndexChanged = false;
|
||||
void LoadPluginPresetIndex();
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "ss.hpp"
|
||||
#include <assert.h>
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
|
||||
#include "vst3/Vst3Host.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
@@ -60,7 +60,7 @@
|
||||
#include "Vst3MidiToEvent.hpp"
|
||||
|
||||
#include "vst3/Vst3EffectImpl.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
+11
-11
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "ss.hpp"
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "vst3/Vst3Host.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include <unordered_map>
|
||||
@@ -86,7 +86,7 @@ namespace pipedal
|
||||
const Vst3Host::PluginList &getPluginList() override { return pluginList; }
|
||||
|
||||
std::unique_ptr<Vst3Effect> CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override;
|
||||
std::unique_ptr<Vst3Effect> CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost) override;
|
||||
std::unique_ptr<Vst3Effect> CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost) override;
|
||||
|
||||
private:
|
||||
Lv2PluginUiInfo *GetPluginInfo(const std::string &uri);
|
||||
@@ -615,22 +615,22 @@ static std::vector<uint8_t> HexToByteArray(const std::string &hexState)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoardItem, IHost *pHost)
|
||||
std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalboardItem &pedalboardItem, IHost *pHost)
|
||||
{
|
||||
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalBoardItem.instanceId(), pedalBoardItem.uri(), pHost);
|
||||
auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri());
|
||||
std::unique_ptr<Vst3Effect> result = CreatePlugin(pedalboardItem.instanceId(), pedalboardItem.uri(), pHost);
|
||||
auto pluginInfo = this->GetPluginInfo(pedalboardItem.uri());
|
||||
if (!pluginInfo)
|
||||
{
|
||||
throw Vst3Exception(SS("Plugin " << pedalBoardItem.pluginName() << " not found."));
|
||||
throw Vst3Exception(SS("Plugin " << pedalboardItem.pluginName() << " not found."));
|
||||
}
|
||||
if (pedalBoardItem.vstState().length() != 0)
|
||||
if (pedalboardItem.vstState().length() != 0)
|
||||
{
|
||||
std::vector<uint8_t> state = HexToByteArray(pedalBoardItem.vstState());
|
||||
std::vector<uint8_t> state = HexToByteArray(pedalboardItem.vstState());
|
||||
result->SetState(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const ControlValue &controlValue : pedalBoardItem.controlValues())
|
||||
for (const ControlValue &controlValue : pedalboardItem.controlValues())
|
||||
{
|
||||
int32_t index = -1;
|
||||
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
|
||||
@@ -647,7 +647,7 @@ std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ControlValue &controlValue : pedalBoardItem.controlValues())
|
||||
for (ControlValue &controlValue : pedalboardItem.controlValues())
|
||||
{
|
||||
int32_t index = -1;
|
||||
for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
|
||||
@@ -667,7 +667,7 @@ std::unique_ptr<Vst3Effect> Vst3HostImpl::CreatePlugin(PedalBoardItem &pedalBoar
|
||||
}
|
||||
controlValue.value(t);
|
||||
} else {
|
||||
Lv2Log::warning(SS(pedalBoardItem.pluginName() << ": Control key not found. key=" << controlValue.key() ));
|
||||
Lv2Log::warning(SS(pedalboardItem.pluginName() << ": Control key not found. key=" << controlValue.key() ));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "PiPedalHost.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "vst3/Vst3Host.hpp"
|
||||
#include <iostream>
|
||||
|
||||
@@ -75,7 +75,7 @@ void RunVsts()
|
||||
cout << "Scanning" << endl;
|
||||
const auto & plugins = vst3Host->RescanPlugins();
|
||||
|
||||
PiPedalHost host;
|
||||
PluginHost host;
|
||||
|
||||
IHost *pHost = host.asIHost();
|
||||
host.setSampleRate(44100);
|
||||
|
||||
+4
-1
@@ -17,6 +17,7 @@
|
||||
#include <set>
|
||||
#include <strings.h>
|
||||
#include "Ipv6Helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "WebServer.hpp"
|
||||
|
||||
@@ -582,6 +583,7 @@ namespace pipedal
|
||||
{
|
||||
try
|
||||
{
|
||||
SetThreadName("webMain");
|
||||
// The io_context is required for all I/O
|
||||
boost::asio::io_service ioc{threads};
|
||||
//*********************************
|
||||
@@ -632,8 +634,9 @@ namespace pipedal
|
||||
v.reserve(threads - 1);
|
||||
for (auto i = threads - 1; i > 0; --i)
|
||||
v.emplace_back(
|
||||
[&ioc]
|
||||
[&ioc,i]
|
||||
{
|
||||
SetThreadName(SS("web_" << i));
|
||||
ioc.run();
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user