Interim commit for COnvolutionReverb

This commit is contained in:
Robin Davies
2023-03-21 06:55:48 -04:00
parent 7741533254
commit 3ab431779a
34 changed files with 3369 additions and 1550 deletions
+194
View File
@@ -0,0 +1,194 @@
/*
* MIT License
*
* Copyright (c) 2023 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
import Typography from '@mui/material/Typography';
import Input from '@mui/material/Input';
import Select from '@mui/material/Select';
import Switch from '@mui/material/Switch';
import Utility, { nullCast } from './Utility';
import MenuItem from '@mui/material/MenuItem';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import ButtonBase from '@mui/material/ButtonBase'
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
const MIN_ANGLE = -135;
const MAX_ANGLE = 135;
const FONT_SIZE = "0.8em";
const SELECTED_OPACITY = 0.8;
const DEFAULT_OPACITY = 0.6;
const RANGE_SCALE = 120; // 120 pixels to move from 0 to 1.
const FINE_RANGE_SCALE = RANGE_SCALE * 10; // 1200 pixels to move from 0 to 1.
const ULTRA_FINE_RANGE_SCALE = RANGE_SCALE * 50; // 12000 pixels to move from 0 to 1.
export const StandardItemSize = { width: 80, height: 140 }
const styles = (theme: Theme) => createStyles({
frame: {
position: "relative",
margin: "12px"
},
switchTrack: {
height: '100%',
width: '100%',
borderRadius: 14 / 2,
zIndex: -1,
transition: theme.transitions.create(['opacity', 'background-color'], {
duration: theme.transitions.duration.shortest
}),
backgroundColor: theme.palette.secondary.main,
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
},
displayValue: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 4,
textAlign: "center",
background: "white",
color: "#666",
// zIndex: -1,
}
});
export interface FilePropertyControlProps extends WithStyles<typeof styles> {
instanceId: number;
fileProperty: PiPedalFileProperty;
value: string;
onFileClick: (fileProperty: PiPedalFileProperty,value: string) => void;
theme: Theme;
}
type FilePropertyControlState = {
error: boolean;
showDialog: boolean;
};
const FilePropertyControl =
withStyles(styles, { withTheme: true })(
class extends Component<FilePropertyControlProps, FilePropertyControlState>
{
model: PiPedalModel;
constructor(props: FilePropertyControlProps) {
super(props);
this.state = {
error: false,
showDialog: false
};
this.model = PiPedalModelFactory.getInstance();
}
componentWillUnmount() {
}
inputChanged: boolean = false;
onDrag(e: SyntheticEvent) {
e.preventDefault();
}
onFileClick() {
this.props.onFileClick(this.props.fileProperty,this.props.value);
}
private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/');
if (slashPos < 0)
{
slashPos = 0;
} else {
++slashPos;
}
let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos)
{
extPos = path.length;
}
return path.substring(slashPos,extPos);
}
render() {
let classes = this.props.classes;
let fileProperty = this.props.fileProperty;
let value = this.props.value;
if (!value || value.length === 0)
{
value = "\u00A0";
}
value = this.fileNameOnly(value);
let item_width = 264;
return (
<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={{
width: "100%",
textAlign: "start"
}}> {fileProperty.name}</Typography>
</div>
{/* CONTROL SECTION */}
<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"}}>
<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}}
variant='caption'
>{value}</Typography>
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center",opacity: 0.5, marginRight: 4 }} />
</div>
<div style={{ height: "1px", width: "100%", background: "black" }}>&nbsp;</div>
</div>
</ButtonBase>
</div>
{/* LABEL/EDIT SECTION*/}
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
</div>
</div >
);
}
}
);
export default FilePropertyControl;
+191
View File
@@ -0,0 +1,191 @@
// Copyright (c) 2023 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import Button from '@mui/material/Button';
import Radio from '@mui/material/Radio';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import AudioFileIcon from '@mui/icons-material/AudioFile';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalFileProperty, PiPedalFileType } from './Lv2Plugin';
import ButtonBase from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
import DialogEx from './DialogEx';
import { ModelTraining } from '@mui/icons-material';
export interface FilePropertyDialogProps {
open: boolean,
fileProperty: PiPedalFileProperty,
selectedFile: string,
onOk: (fileProperty: PiPedalFileProperty, selectedItem: string) => void,
onClose: () => void
};
export interface FilePropertyDialogState {
fullScreen: boolean;
selectedFile: string;
hasSelection: boolean;
files: string[];
};
export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
constructor(props: FilePropertyDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
selectedFile: props.selectedFile,
hasSelection: false,
files: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
};
this.requestFiles();
}
private mounted: boolean = false;
private model: PiPedalModel;
private requestFiles() {
this.model.requestFileList(this.props.fileProperty);
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void {
}
private fileNameOnly(path: string): string {
let slashPos = path.lastIndexOf('/');
if (slashPos < 0) {
slashPos = 0;
} else {
++slashPos;
}
let extPos = path.lastIndexOf('.');
if (extPos < 0 || extPos < slashPos) {
extPos = path.length;
}
return path.substring(slashPos, extPos);
}
private isFileInList(selectedFile: string) {
let hasSelection = false;
for (var file of this.state.files) {
if (file === selectedFile) {
hasSelection = true;
break;
}
}
return hasSelection;
}
onSelect(selectedFile: string) {
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(selectedFile) })
}
render() {
return (
<DialogEx onClose={() => this.props.onClose()} 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>
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}>&nbsp;</div>
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start" }}>
{
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" }}
onClick={() => this.onSelect(value)}
>
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%" }} />
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 4 }} />
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
</div>
</ButtonBase>
);
}
)
}
</div>
</div>
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}>&nbsp;</div>
<DialogActions style={{ justifyContent: "stretch" }}>
<div style={{ display: "flex", width: "100%", flexFlow: "row nowrap" }}>
<Button style={{ flex: "0 0 auto" }} startIcon={<FileUploadIcon />}>
<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>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button onClick={() => this.props.onClose()} aria-label="cancel">
Cancel
</Button>
<Button style={{ flex: "0 0 auto" }} onClick={() => this.props.onClose()} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
Select
</Button>
</div>
</DialogActions>
</DialogEx>
);
}
}
+60 -2
View File
@@ -105,6 +105,49 @@ export class PortGroup {
program_list_id: number = -1;
};
export class PiPedalFileType {
deserialize(input: any): PiPedalFileType {
this.name = input.name;
this.fileExtension = input.fileExtension;
return this;
}
static deserialize_array(input: any): PiPedalFileType[]
{
let result: PiPedalFileType[] = [];
for (let i = 0; i < input.length; ++i)
{
result[i] = new PiPedalFileType().deserialize(input[i]);
}
return result;
}
name: string = "";
fileExtension: string = "";
}
export class PiPedalFileProperty {
deserialize(input: any): PiPedalFileProperty
{
this.name = input.name;
this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes);
this.patchProperty = input.patchProperty;
this.defaultFile = input.defaultFile;
return this;
}
static deserialize_array(input: any): PiPedalFileProperty[]
{
let result: PiPedalFileProperty[] = [];
for (let i = 0; i < input.length; ++i)
{
result[i] = new PiPedalFileProperty().deserialize(input[i]);
}
return result;
}
name: string = "";
fileTypes: PiPedalFileType[] = [];
patchProperty: string = "";
defaultFile: string = "";
};
export class Lv2Plugin implements Deserializable<Lv2Plugin> {
deserialize(input: any): Lv2Plugin
{
@@ -119,6 +162,12 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
this.comment = input.comment;
this.ports= Port.deserialize_array(input.ports);
this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
return this;
}
static EmptyFeatures: string[] = [];
@@ -133,7 +182,8 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
author_homepage: string = "";
comment: string = "";
ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = [];
port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = [];
}
@@ -474,6 +524,13 @@ export class UiPlugin implements Deserializable<UiPlugin> {
this.description = input.description;
this.controls = UiControl.deserialize_array(input.controls);
this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
this.is_vst3 = input.is_vst3;
return this;
@@ -523,7 +580,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
has_midi_output: number = 0;
description: string = "";
controls: UiControl[] = [];
port_groups: PortGroup[] = [];
port_groups: PortGroup[] = [];
fileProperties: PiPedalFileProperty[] = [];
is_vst3 : boolean = false;
}
+34
View File
@@ -56,6 +56,27 @@ export class ControlValue implements Deserializable<ControlValue> {
key: string;
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> {
@@ -67,6 +88,7 @@ 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 ?? "";
return this;
}
@@ -136,6 +158,17 @@ 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)
{
@@ -183,6 +216,7 @@ export class PedalBoardItem implements Deserializable<PedalBoardItem> {
uri: string = "";
pluginName?: string;
controlValues: ControlValue[] = ControlValue.EmptyArray;
propertyValues: PropertyValue[] = [];
midiBindings: MidiBinding[] = [];
vstState: string = "";
};
+72 -5
View File
@@ -17,7 +17,7 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { UiPlugin, UiControl, PluginType } from './Lv2Plugin';
import { UiPlugin, UiControl, PluginType, PiPedalFileProperty } from './Lv2Plugin';
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
@@ -50,6 +50,7 @@ export enum State {
};
export type ControlValueChangedHandler = (key: string, value: number) => void;
export type PropertyValueChangedHandler = (properyUri: string, value: number) => void;
export type PluginPresetsChangedHandler = (pluginUri: string) => void;
@@ -94,6 +95,18 @@ interface ControlValueChangeItem {
};
export interface PropertyValueChangedHandle {
_PropertyValueChangedHandle: number;
};
interface PropertyValueChangeItem {
handle: number;
instanceId: number;
onValueChanged: PropertyValueChangedHandler;
};
class MidiEventListener {
constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) {
this.handle = handle;
@@ -354,9 +367,9 @@ export interface PiPedalModel {
loadPedalBoardPlugin(itemId: number, selectedUri: string): number;
setPedalBoardControlValue(instanceId: number, key: string, value: number): void;
setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any): void;
setPedalBoardItemEnabled(instanceId: number, value: boolean): void;
previewPedalBoardValue(instanceId: number, key: string, value: number): void;
setPedalBoardControlValue(instanceId: number, key: string, value: number): void;
deletePedalBoardPedal(instanceId: number): number | null;
movePedalBoardItem(fromInstanceId: number, toInstanceId: number): void;
@@ -456,6 +469,8 @@ export interface PiPedalModel {
chooseNewDevice(): void;
hasConfiguration(): boolean;
requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise<string[]>;
};
class PiPedalModelImpl implements PiPedalModel {
@@ -722,6 +737,7 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
requestPluginClasses(): Promise<boolean> {
const myRequest = new Request(this.varRequest('plugin_classes.json'));
return fetch(myRequest)
@@ -892,6 +908,10 @@ class PiPedalModelImpl implements PiPedalModel {
);
return this.webSocket.connect();
})
.catch((error) => {
this.setError("Failed to connect to server.");
return false;
})
.then(() => {
const isoRequest = new Request('iso_codes.json');
return fetch(isoRequest);
@@ -1171,7 +1191,7 @@ class PiPedalModelImpl implements PiPedalModel {
}
_controlValueChangeItems: ControlValueChangeItem[] = [];
private _controlValueChangeItems: ControlValueChangeItem[] = [];
addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle {
let handle = ++this.nextListenHandle;
@@ -1186,6 +1206,21 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
}
private _propertyValueChangeItems: PropertyValueChangeItem[] = [];
addPropertyValueChangeListener(instanceId: number, onValueChanged: PropertyValueChangedHandler): PropertyValueChangedHandle {
let handle = ++this.nextListenHandle;
this._propertyValueChangeItems.push({ handle: handle, instanceId: instanceId, onValueChanged: onValueChanged });
return { _PropertyValueChangedHandle: handle };
}
removePropertyValueChangeListener(handle: PropertyValueChangedHandle) {
for (let i = 0; i < this._propertyValueChangeItems.length; ++i) {
if (this._propertyValueChangeItems[i].handle === handle._PropertyValueChangedHandle) {
this._propertyValueChangeItems.splice(i, 1);
return;
}
}
}
_pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = [];
@@ -1217,7 +1252,27 @@ class PiPedalModelImpl implements PiPedalModel {
}
_setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalBoard = pedalBoard.clone();
let item = newPedalBoard.getItem(instanceId);
let changed = item.setPropertyValue(propertyUri, value);
if (changed) {
this.pedalBoard.set(newPedalBoard);
if (notifyServer) {
// FIX ME!: this._setServerProperty("setProperty", instanceId, propertyUri, value);
}
for (let i = 0; i < this._propertyValueChangeItems.length; ++i) {
let item = this._propertyValueChangeItems[i];
if (instanceId === item.instanceId) {
item.onValueChanged(propertyUri, value);
}
}
}
}
_setPedalBoardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
@@ -1262,14 +1317,17 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
setPedalBoardPropertyValue(instanceId: number, propertyUri: string, value: any): void
{
this._setPedalBoardPropertyValue(instanceId, propertyUri, value, true);
}
setPedalBoardControlValue(instanceId: number, key: string, value: number): void {
this._setPedalBoardControlValue(instanceId, key, value, true);
}
setPedalBoardItemEnabled(instanceId: number, value: boolean): void {
this._setPedalBoardItemEnabled(instanceId, value, true);
}
_setPedalBoardItemEnabled(instanceId: number, value: boolean, notifyServer: boolean): void {
private _setPedalBoardItemEnabled(instanceId: number, value: boolean, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalBoard = pedalBoard.clone();
@@ -1544,6 +1602,13 @@ class PiPedalModelImpl implements PiPedalModel {
return newPresetId;
});
}
requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise<string[]>
{
return nullCast(this.webSocket)
.request<string[]>('requestFileList',piPedalFileProperty);
}
saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> {
// default behaviour is to save after the currently selected preset.
let request: any = {
@@ -2354,6 +2419,8 @@ class PiPedalModelImpl implements PiPedalModel {
return jackConfig.isValid;
}
};
let instance: PiPedalModel | undefined = undefined;
+3 -2
View File
@@ -272,18 +272,19 @@ class PiPedalSocket {
connectInternal_(): Promise<WebSocket> {
return new Promise<WebSocket>((resolve, reject) => {
let ws = new WebSocket(this.url);
let self = this;
ws.onmessage = this.handleMessage.bind(this);
ws.onclose = (event: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Can't connect to server.");
reject("Connection closed unexpectedly.");
};
ws.onerror = (event: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Can't connect to server.");
reject("Failed to connect.");
};
ws.onopen = (event: Event) => {
ws.onerror = self.handleError.bind(self);
+75 -28
View File
@@ -23,9 +23,9 @@ import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { UiPlugin, UiControl } from './Lv2Plugin';
import { UiPlugin, UiControl, PiPedalFileProperty,PiPedalFileType } from './Lv2Plugin';
import {
PedalBoard, PedalBoardItem, ControlValue,
PedalBoard, PedalBoardItem, ControlValue,PropertyValue
} from './PedalBoard';
import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
@@ -34,6 +34,8 @@ import { nullCast } from './Utility'
import { PiPedalStateError } from './PiPedalError';
import Typography from '@mui/material/Typography';
import FullScreenIME from './FullScreenIME';
import FilePropertyControl from './FilePropertyControl';
import FilePropertyDialog from './FilePropertyDialog';
export const StandardItemSize = { width: 80, height: 110 };
@@ -167,8 +169,7 @@ const styles = (theme: Theme) => createStyles({
});
export class ControlGroup {
constructor(name: string, controls: ReactNode[])
{
constructor(name: string, controls: ReactNode[]) {
this.name = name;
this.controls = controls;
}
@@ -197,6 +198,9 @@ type PluginControlViewState = {
imeValue: number;
imeCaption: string;
imeInitialHeight: number;
showFileDialog: boolean,
dialogFileProperty: PiPedalFileProperty,
dialogFileValue: string
};
const PluginControlView =
@@ -214,11 +218,14 @@ const PluginControlView =
imeUiControl: undefined,
imeValue: 0,
imeCaption: "",
imeInitialHeight: 0
imeInitialHeight: 0,
showFileDialog: false,
dialogFileProperty: new PiPedalFileProperty(),
dialogFileValue: ""
}
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.onControlValueChanged = this.onControlValueChanged.bind(this);
this.onPreviewChange = this.onPreviewChange.bind(this);
}
@@ -227,9 +234,12 @@ const PluginControlView =
this.model.previewPedalBoardValue(this.props.instanceId, key, value);
}
onValueChanged(key: string, value: number): void {
onControlValueChanged(key: string, value: number): void {
this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
}
onPropertyValueChanged(propertyUri: string, value: any): void {
this.model.setPedalBoardPropertyValue(this.props.instanceId, propertyUri, value);
}
onPedalBoardChanged(value?: PedalBoard) {
//let item = this.model.pedalBoard.get().maybeGetItem(this.props.instanceId);
@@ -274,6 +284,30 @@ const PluginControlView =
});
}
makeFilePropertyUI(fileProperty: PiPedalFileProperty, propertyValues: PropertyValue[]): ReactNode {
let propertyValue: PropertyValue | undefined = undefined;
for (let i = 0; i < propertyValues.length; ++i) {
if (propertyValues[i].propertyUri === fileProperty.patchProperty) {
propertyValue = propertyValues[i];
break;
}
}
if (!propertyValue) {
propertyValue = new PropertyValue();
propertyValue.value = fileProperty.defaultFile;
propertyValue.propertyUri = fileProperty.patchProperty;
}
return ((
<FilePropertyControl instanceId={this.props.instanceId} value={propertyValue.value}
fileProperty={fileProperty}
onFileClick={(fileProperty,selectedFile) => {
this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile});
}}
/>
));
}
makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode {
let symbol = uiControl.symbol;
@@ -288,18 +322,18 @@ const PluginControlView =
throw new PiPedalStateError("Missing control value.");
}
return ((
<PluginControl instanceId={this.props.instanceId} uiControl={uiControl} value={controlValue.value}
onChange={(value: number) => { this.onValueChanged(controlValue!.key, value) }}
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
/>
<PluginControl instanceId={this.props.instanceId} uiControl={uiControl} value={controlValue.value}
onChange={(value: number) => { this.onControlValueChanged(controlValue!.key, value) }}
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }}
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
/>
));
}
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes {
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[],propertyValues: PropertyValue[]): ControlNodes {
let result: ControlNodes = [];
for (let i = 0; i < plugin.controls.length; ++i) {
@@ -325,11 +359,17 @@ const PluginControlView =
)
} else {
result.push(
this.makeStandardControl(pluginControl, controlValues)
this.makeStandardControl(pluginControl, controlValues)
);
}
}
}
for (let i = 0; i < plugin.fileProperties.length; ++i) {
let fileProperty = plugin.fileProperties[i];
result.push(
this.makeFilePropertyUI(fileProperty, propertyValues)
);
}
return result;
}
@@ -352,10 +392,8 @@ const PluginControlView =
});
}
hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean
{
for (let i = 0; i < nodes.length; ++i)
{
hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean {
for (let i = 0; i < nodes.length; ++i) {
let node = nodes[i];
if (node instanceof ControlGroup) return true;
}
@@ -374,13 +412,12 @@ const PluginControlView =
if (node instanceof ControlGroup) {
let controlGroup = node as ControlGroup;
let controls: ReactNode[] = [];
for (let j = 0; j < controlGroup.controls.length; ++j)
{
for (let j = 0; j < controlGroup.controls.length; ++j) {
let item = controlGroup.controls[j];
controls.push(
(
<div className={classes.controlPadding}>
{ item }
{item}
</div>
)
@@ -402,7 +439,7 @@ const PluginControlView =
} else {
result.push((
<div className={ hasGroups ? classes.portgroupControlPadding: classes.controlPadding } >
<div className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
{node as ReactNode}
</div>
));
@@ -421,6 +458,7 @@ const PluginControlView =
return (<div className={classes.frame} ></div>);
let controlValues = pedalBoardItem.controlValues;
let propertyValues = pedalBoardItem.propertyValues;
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalBoardItem.uri));
@@ -429,14 +467,14 @@ const PluginControlView =
controlValues = this.filterNotOnGui(controlValues, plugin);
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
let controlNodes: ControlNodes;
controlNodes = this.getStandardControlNodes(plugin, controlValues);
if (this.props.customization)
{
controlNodes = this.getStandardControlNodes(plugin, controlValues,propertyValues);
if (this.props.customization) {
// allow wrapper class to insert/remove/rebuild controls.
controlNodes = this.props.customization.ModifyControls(controlNodes);
}
@@ -462,6 +500,15 @@ const PluginControlView =
)
}
</div>
<FilePropertyDialog open={this.state.showFileDialog}
fileProperty={this.state.dialogFileProperty}
selectedFile={this.state.dialogFileValue}
onClose={()=> { this.setState({ showFileDialog: false});}}
onOk={(fileProperty,selectedFile)=> {
this.model.setPedalBoardPropertyValue(this.props.instanceId,fileProperty.patchProperty,selectedFile)
this.setState({ showFileDialog: false});}
}
/>
<FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue}
onChange={(key, value) => this.onImeValueChange(key, value)}