diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx
index 8f24139..8dd200a 100644
--- a/react/src/Lv2Plugin.tsx
+++ b/react/src/Lv2Plugin.tsx
@@ -204,6 +204,42 @@ export class UiPropertyNotification {
plugin: string = "";
protocol: string = "";
+};
+
+export class UiFrequencyPlot {
+ deserialize(input: any): UiFrequencyPlot
+ {
+ this.patchProperty = input.patchProperty;
+ this.index = input.index;
+ this.portGroup = input.portGroup;
+ this.xLeft = input.xLeft;
+ this.xRight = input.xRight;
+ this.xLog = input.xLog;
+ this.yTop = input.yTop;
+ this.yBottom = input.yBottom;
+ this.width = input.width;
+ return this;
+ }
+ static deserialize_array(input: any): UiFrequencyPlot[]
+ {
+ let result: UiFrequencyPlot[] = [];
+ for (let i = 0; i < input.length; ++i)
+ {
+ result[i] = new UiFrequencyPlot().deserialize(input[i]);
+ }
+ return result;
+ }
+
+ patchProperty: string = "";
+ index: number = -1;
+ portGroup: string = "";
+ xLeft: number = -1;
+ xRight: number = -1;
+ xLog: boolean = true;
+ yTop: number = -1;
+ yBottom: number = -1;
+ width: number = -1;
+
};
export class UiFileProperty {
deserialize(input: any): UiFileProperty
@@ -253,6 +289,10 @@ export class UiFileProperty {
{
return true;
}
+ if (fileType.fileExtension === "*" || fileType.fileExtension === "")
+ {
+ return true;
+ }
}
return false;
}
@@ -270,6 +310,8 @@ export class Lv2Plugin implements Deserializable {
{
this.uri = input.uri;
this.name = input.name;
+ this.brand = input.name? input.name: "";
+ this.label = input.label? input.label: this.name;
this.plugin_class = input.plugin_class;
this.supported_features = input.supported_features;
this.required_features = input.required_features;
@@ -285,6 +327,13 @@ export class Lv2Plugin implements Deserializable {
} else {
this.fileProperties = [];
}
+ if (input.frequencyPlots)
+ {
+ this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots)
+ } else {
+ this.frequencyPlots = [];
+ }
+
if (input.uiPortNotifications)
{
this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications);
@@ -297,6 +346,8 @@ export class Lv2Plugin implements Deserializable {
uri: string = "";
name: string = "";
+ brand: string = "";
+ label: string = "";
plugin_class: string = "";
supported_features: string[] = Lv2Plugin.EmptyFeatures;
required_features: string[] = Lv2Plugin.EmptyFeatures;
@@ -306,7 +357,8 @@ export class Lv2Plugin implements Deserializable {
comment: string = "";
ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = [];
- fileProperties: UiFileProperty[] = [];
+ fileProperties: UiFileProperty[] = [];
+ frequencyPlots: UiFrequencyPlot[] = [];
uiPortNotifications: UiPropertyNotification[] = [];
}
@@ -375,7 +427,19 @@ export enum PluginType {
WaveshaperPlugin = "WaveshaperPlugin",
// psuedo plugin type for the Amps node of the filter dialog.
- PiPedalAmpsNode = "PiPedalAmpsNode"
+ PiPedalAmpsNode = "PiPedalAmpsNode",
+
+ // pseudo plugin types for splitter.
+ SplitA = "SplitA",
+ SplitB = "SplitB", //"img/fx_split_b.svg";
+ SplitMix = "SplitMix", //SplitMix; //"img/fx_dial.svg";
+ SplitLR = "SplitLR", //"img/fx_dial.svg";
+
+
+
+ // psuedo plugin type for misc icons.
+ ErrorPlugin = "ErrorPlugin",
+ Terminal = "Terminal", //"img/fx_terminal.svg";
}
@@ -656,6 +720,8 @@ export class UiPlugin implements Deserializable {
{
this.uri = input.uri;
this.name = input.name;
+ this.brand = input.brand ? input.brand: "";
+ this.label = input.label? input.label: this.name;
this.plugin_type = input.plugin_type as PluginType;
this.plugin_display_type = input.plugin_display_type;
this.author_name = input.author_name;
@@ -673,6 +739,12 @@ export class UiPlugin implements Deserializable {
} else {
this.fileProperties = [];
}
+ if (input.frequencyPlots)
+ {
+ this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots)
+ } else {
+ this.frequencyPlots = [];
+ }
this.is_vst3 = input.is_vst3;
return this;
@@ -726,6 +798,8 @@ export class UiPlugin implements Deserializable {
uri: string = "";
name: string = "";
+ brand: string = "";
+ label: string = "";
plugin_type: PluginType = PluginType.InvalidPlugin;
plugin_display_type: string = "";
author_name: string = "";
@@ -737,7 +811,8 @@ export class UiPlugin implements Deserializable {
description: string = "";
controls: UiControl[] = [];
port_groups: PortGroup[] = [];
- fileProperties: UiFileProperty[] = [];
+ fileProperties: UiFileProperty[] = [];
+ frequencyPlots: UiFrequencyPlot[] = [];
is_vst3 : boolean = false;
}
diff --git a/react/src/MainPage.tsx b/react/src/MainPage.tsx
index baee5ab..6dd2b4c 100644
--- a/react/src/MainPage.tsx
+++ b/react/src/MainPage.tsx
@@ -45,7 +45,9 @@ import PluginInfoDialog from './PluginInfoDialog';
import { GetControlView } from './ControlViewFactory';
import MidiBindingsDialog from './MidiBindingsDialog';
import PluginPresetSelector from './PluginPresetSelector';
-
+import {ReactComponent as OldDeleteIcon} from "./svg/old_delete_outline_24dp.svg";
+import {ReactComponent as MidiIcon} from "./svg/ic_midi.svg";
+import isDarkMode from './DarkMode';
const SPLIT_CONTROLBAR_THRESHHOLD = 650;
@@ -461,7 +463,7 @@ export const MainPage =
{ this.onAddClick(e) }} size="large">
-
+
@@ -493,7 +495,8 @@ export const MainPage =
onClick={this.onLoadClick}
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
startIcon={
}
- style={{ textTransform: "none" }}
+ style={{ textTransform: "none",
+ background: (isDarkMode() ? "#6750A4": undefined) }}
>
Load
@@ -502,7 +505,7 @@ export const MainPage =
{ this.handleMidiConfiguration(instanceId); }}
size="large">
-
+
diff --git a/react/src/PedalboardView.tsx b/react/src/PedalboardView.tsx
index 4ff409d..212f3f4 100644
--- a/react/src/PedalboardView.tsx
+++ b/react/src/PedalboardView.tsx
@@ -26,13 +26,15 @@ import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { PluginType } from './Lv2Plugin';
import ButtonBase from '@mui/material/ButtonBase';
import Typography from '@mui/material/Typography';
-import { SelectIcon } from './PluginIcon';
+import PluginIcon, {SelectIconUri} from './PluginIcon';
import { SelectHoverBackground } from './SelectHoverBackground';
import SvgPathBuilder from './SvgPathBuilder';
import Draggable from './Draggable'
import Rect from './Rect';
import {PiPedalStateError} from './PiPedalError';
-import Utility from './Utility'
+import Utility from './Utility';
+import isDarkMode from './DarkMode';
+
import {
Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType
} from './Pedalboard';
@@ -44,8 +46,9 @@ const END_CONTROL = Pedalboard.END_CONTROL;
const START_PEDALBOARD_ITEM_URI = Pedalboard.START_PEDALBOARD_ITEM_URI;
const END_PEDALBOARD_ITEM_URI = Pedalboard.END_PEDALBOARD_ITEM_URI;
-const ENABLED_CONNECTOR_COLOR = "#666";
-const DISABLED_CONNECTOR_COLOR = "#CCC";
+const ENABLED_CONNECTOR_COLOR = isDarkMode() ? "#CCC": "#666";
+const DISABLED_CONNECTOR_COLOR = isDarkMode() ? "#666": "#CCC";
+
const CELL_WIDTH: number = 96;
@@ -129,14 +132,14 @@ const pedalboardStyles = (theme: Theme) => createStyles({
alignItems: "center",
justifyContent: "center",
- background: theme.palette.background.paper,
+ background: theme.palette.background.default,
marginLeft: (CELL_WIDTH - FRAME_SIZE) / 2,
marginRight: (CELL_WIDTH - FRAME_SIZE) / 2,
marginTop: (CELL_HEIGHT - FRAME_SIZE) / 2,
marginBottom: (CELL_HEIGHT - FRAME_SIZE) / 2,
width: FRAME_SIZE,
height: FRAME_SIZE,
- border: "1pt #666 solid",
+ border: isDarkMode()? "1pt #AAA solid": "1pt #666 solid",
borderRadius: 6
},
borderlessIconFrame: {
@@ -237,6 +240,7 @@ class PedalLayout {
static Start(): PedalLayout {
let t: PedalLayout = new PedalLayout();
t.uri = START_PEDALBOARD_ITEM_URI;
+ t.pluginType = PluginType.Terminal;
t.iconUrl = TERMINAL_ICON_URL;
t.numberOfInputs = 0;
t.numberOfOutputs = 2;
@@ -244,7 +248,7 @@ class PedalLayout {
}
static End(): PedalLayout {
let t: PedalLayout = new PedalLayout();
- t.pluginType = PluginType.UtilityPlugin;
+ t.pluginType = PluginType.Terminal;
t.uri = END_PEDALBOARD_ITEM_URI;
t.iconUrl = TERMINAL_ICON_URL;
t.numberOfInputs = 2;
@@ -272,7 +276,7 @@ class PedalLayout {
} else if (pedalItem.isEmpty()) {
- this.pluginType = PluginType.UtilityPlugin;
+ this.pluginType = PluginType.None;
this.iconUrl = EMPTY_ICON_URL;
this.numberOfInputs = 2;
this.numberOfOutputs = 2;
@@ -293,13 +297,13 @@ class PedalLayout {
let uiPlugin = model.getUiPlugin(pedalItem.uri);
if (uiPlugin != null) {
this.pluginType = uiPlugin.plugin_type;
- this.iconUrl = SelectIcon(uiPlugin.plugin_type,uiPlugin.uri);
- this.name = uiPlugin.name;
+ this.iconUrl = SelectIconUri(uiPlugin.plugin_type);
+ this.name = uiPlugin.label;
this.numberOfInputs = Math.max(uiPlugin.audio_inputs,2);
this.numberOfOutputs = Math.max(uiPlugin.audio_outputs,2);
} else {
// default to empty plugin.
- this.pluginType = PluginType.UtilityPlugin;
+ this.pluginType = PluginType.ErrorPlugin;
this.name = pedalItem.pluginName??"#error";
this.iconUrl = ERROR_ICON_URL;
this.numberOfInputs = 2;
@@ -525,22 +529,22 @@ const PedalboardView =
}
}
- getSplitterIcon(layoutItem: PedalLayout) {
+ getSplitterIcon(layoutItem: PedalLayout): PluginType {
if (layoutItem.pedalItem === undefined) {
throw new Error("Invalid splitter");
}
let split = layoutItem.pedalItem as PedalboardSplitItem;
if (split.getSplitType() === SplitType.Ab) {
if (split.isASelected()) {
- return "img/fx_split_a.svg";
+ return PluginType.SplitA;
} else {
- return "img/fx_split_b.svg";
+ return PluginType.SplitB;
}
} else if (split.getSplitType() === SplitType.Mix) {
- return "img/fx_dial.svg";
+ return PluginType.SplitMix; //"img/fx_dial.svg";
} else {
- return "img/fx_lr.svg";
+ return PluginType.SplitLR; //"img/fx_lr.svg";
}
}
@@ -675,7 +679,7 @@ const PedalboardView =
event.preventDefault();
event.stopPropagation();
- if (!Utility.isTouchDevice()) {
+ if (!Utility.needsZoomedControls()) {
if (this.props.onDoubleClick && instanceId) {
this.props.onDoubleClick(instanceId);
}
@@ -903,7 +907,7 @@ const PedalboardView =
throw new PiPedalStateError("scroll container not found.");
}
- pedalButton(instanceId: number, iconUrl: string, draggable: boolean, enabled: boolean,hasBorder: boolean = true): ReactNode {
+ pedalButton(instanceId: number, iconType: PluginType, draggable: boolean, enabled: boolean,hasBorder: boolean = true): ReactNode {
let classes = this.props.classes;
return (
{ e.preventDefault(); }}>
@@ -917,8 +921,7 @@ const PedalboardView =
this.getScrollContainer()}
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
>
-
+
@@ -982,7 +985,7 @@ const PedalboardView =
result.push(
- {this.pedalButton(START_CONTROL, item.iconUrl, false,true,false)}
+ {this.pedalButton(START_CONTROL, item.pluginType,false,true,false)}
);
break;
@@ -990,7 +993,7 @@ const PedalboardView =
result.push(
- {this.pedalButton(END_CONTROL, item.iconUrl, false,true,false)}
+ {this.pedalButton(END_CONTROL, item.pluginType, false,true,false)}
);
break;
@@ -1011,11 +1014,11 @@ const PedalboardView =
}}>
{item.pedalItem?.pluginName}
+ >{item.name}
)
result.push(
- {this.pedalButton(item.pedalItem?.instanceId ?? -1, item.iconUrl, !item.isEmpty(), item.pedalItem?.isEnabled ?? false)}
+ {this.pedalButton(item.pedalItem?.instanceId ?? -1, item.pluginType, !item.isEmpty(), item.pedalItem?.isEnabled ?? false)}
);
diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx
index a4e0c79..c25b27d 100644
--- a/react/src/PiPedalModel.tsx
+++ b/react/src/PiPedalModel.tsx
@@ -38,7 +38,12 @@ import GovernorSettings from './GovernorSettings';
import WifiChannel from './WifiChannel';
import AlsaDeviceInfo from './AlsaDeviceInfo';
import { AndroidHostInterface, FakeAndroidHost } from './AndroidHost';
+import isDarkMode, {setDarkMode} from './DarkMode';
+export enum ColorTheme {
+ Light,
+ Dark
+};
export enum State {
Loading,
@@ -470,8 +475,11 @@ export class PiPedalModel //implements PiPedalModel
let value = body as number;
this._setInputVolume(value,false);
} else if (message === "onLv2StateChanged") {
- let instanceId = body as number;
- this.onLv2StateChanged(instanceId);
+
+ let instanceId = body.instanceId as number;
+ let state = body.state as [boolean,any];
+
+ this.onLv2StateChanged(instanceId,state);
} else if (message === "onVst3ControlChanged") {
let controlChangedBody = body as Vst3ControlChangedBody;
this._setVst3PedalboardControlValue(
@@ -665,6 +673,10 @@ export class PiPedalModel //implements PiPedalModel
this.getWebSocket().request
("hello")
.then(clientId => {
this.clientId = clientId;
+ return this.getWebSocket().request("plugins");
+ })
+ .then(data => {
+ this.ui_plugins.set(UiPlugin.deserialize_array(data));
return this.getWebSocket().request("currentPedalboard");
})
.then(data => {
@@ -1116,7 +1128,9 @@ export class PiPedalModel //implements PiPedalModel
}
}
- private onLv2StateChanged(instanceId: number): void {
+ private onLv2StateChanged(instanceId: number, state: [boolean,any]): void {
+ let item = this.pedalboard.get().getItem(instanceId);
+ item.lv2State = state;
for (let item of this.stateChangedListeners) {
if (item.instanceId === instanceId) {
item.onStateChanged(instanceId);
@@ -2531,6 +2545,27 @@ export class PiPedalModel //implements PiPedalModel
return nullCast(this.webSocket).request("newPreset");
}
+ getTheme(): ColorTheme {
+ return isDarkMode() ? ColorTheme.Dark: ColorTheme.Light;
+ }
+
+ setTheme(value: ColorTheme) {
+ if (this.getTheme() !== value)
+ {
+ setDarkMode(value === ColorTheme.Dark);
+ this.reloadPage();
+ }
+ }
+
+ reloadRequested: boolean = false;
+
+ reloadPage() {
+ this.reloadRequested = true;
+ // eslint-disable-next-line no-restricted-globals
+ location.reload();
+ }
+
+
};
let instance: PiPedalModel | undefined = undefined;
diff --git a/react/src/PluginControl.tsx b/react/src/PluginControl.tsx
index 38feddd..de6e453 100644
--- a/react/src/PluginControl.tsx
+++ b/react/src/PluginControl.tsx
@@ -1,4 +1,4 @@
- // Copyright (c) 2022 Robin Davies
+// 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
@@ -30,7 +30,7 @@ import Switch from '@mui/material/Switch';
import Utility, {nullCast} from './Utility';
import MenuItem from '@mui/material/MenuItem';
import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
-
+import {ReactComponent as DialIcon} from './svg/fx_dial.svg';
const MIN_ANGLE = -135;
const MAX_ANGLE = 135;
@@ -72,8 +72,8 @@ const styles = (theme: Theme) => createStyles({
right: 0,
bottom: 4,
textAlign: "center",
- background: "white",
- color: "#666",
+ background: theme.mainBackground,
+ color: theme.palette.text.secondary,
// zIndex: -1,
}
});
@@ -98,7 +98,7 @@ const PluginControl =
{
frameRef: React.RefObject;
- imgRef: React.RefObject;
+ imgRef: React.RefObject;
inputRef: React.RefObject;
selectRef: React.RefObject;
@@ -137,7 +137,7 @@ const PluginControl =
this.onInputKeyPress = this.onInputKeyPress.bind(this);
}
isTouchDevice(): boolean {
- return Utility.isTouchDevice();
+ return Utility.needsZoomedControls();
}
showZoomedControl()
@@ -243,7 +243,7 @@ const PluginControl =
e.preventDefault();
}
- isValidPointer(e: PointerEvent): boolean {
+ isValidPointer(e: PointerEvent): boolean {
if (e.pointerType === "mouse") {
return e.button === 0;
} else if (e.pointerType === "pen") {
@@ -257,9 +257,9 @@ const PluginControl =
touchDown: boolean = false;
touchIdentifier: number = -1;
- onTouchStart(e: TouchEvent) {
+ onTouchStart(e: TouchEvent) {
}
- onTouchMove(e: TouchEvent) {
+ onTouchMove(e: TouchEvent) {
// e.preventDefault();
e.stopPropagation(); // cancels scroll!!!
@@ -282,7 +282,7 @@ const PluginControl =
pointerId: number = 0;
pointerType: string = "";
- isCapturedPointer(e: PointerEvent): boolean {
+ isCapturedPointer(e: PointerEvent): boolean {
return this.mouseDown
&& e.pointerId === this.pointerId
&& e.pointerType === this.pointerType;
@@ -294,9 +294,9 @@ const PluginControl =
dRange: number = 0;
pointersDown: number = 0;
- captureElement?: HTMLElement = undefined;
+ captureElement?: SVGSVGElement = undefined;
- onPointerDown(e: PointerEvent): void {
+ onPointerDown(e: PointerEvent): void {
if (!this.mouseDown && this.isValidPointer(e)) {
e.preventDefault();
e.stopPropagation();
@@ -351,7 +351,7 @@ const PluginControl =
}
- onPointerLostCapture(e: PointerEvent) {
+ onPointerLostCapture(e: PointerEvent) {
if (this.isCapturedPointer(e)) {
--this.pointersDown;
@@ -361,7 +361,7 @@ const PluginControl =
}
- updateRange(e: PointerEvent): number {
+ updateRange(e: PointerEvent): number {
let ultraHigh = false;
let high = false;
@@ -396,7 +396,7 @@ const PluginControl =
this.lastX = e.clientX;
return this.dRange;
}
- onPointerUp(e: PointerEvent) {
+ onPointerUp(e: PointerEvent) {
if (this.isCapturedPointer(e)) {
--this.pointersDown;
@@ -414,7 +414,7 @@ const PluginControl =
}
}
- releaseCapture(e: PointerEvent)
+ releaseCapture(e: PointerEvent)
{
let img = this.imgRef.current;
@@ -437,7 +437,7 @@ const PluginControl =
}
- onPointerMove(e: PointerEvent): void {
+ onPointerMove(e: PointerEvent): void {
if (this.isCapturedPointer(e)) {
e.preventDefault();
let dRange = this.updateRange(e)
@@ -547,7 +547,7 @@ const PluginControl =
);
} else {
return (
-