Interim commit for COnvolutionReverb
This commit is contained in:
@@ -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" }}> </div>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
{/* LABEL/EDIT SECTION*/}
|
||||
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default FilePropertyControl;
|
||||
@@ -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" }}> </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" }}> </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" }}> </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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = "";
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user