Sync
This commit is contained in:
@@ -1,55 +0,0 @@
|
||||
|
||||
import { ControlValue } from "../pipedal/Pedalboard.tsx";
|
||||
|
||||
|
||||
export class ChannelMixerStrip {
|
||||
inputVu: number = -100.0;
|
||||
inputVolume: number = 0;
|
||||
outputVu: number = -100.0;
|
||||
outputVolume: number = 0;
|
||||
};
|
||||
|
||||
export default class ChannelMixerSettings {
|
||||
configured: boolean = false;
|
||||
|
||||
guitarInputChannels: number[] = [1,1];
|
||||
|
||||
guitarOutputChannels: number[] = [0,1];
|
||||
|
||||
guitarMixerSettings: ChannelMixerStrip = new ChannelMixerStrip();
|
||||
|
||||
|
||||
auxInputChannels: number[] = [0,0];
|
||||
auxOutputChannels: number[] = [-1,-1];
|
||||
|
||||
auxMixerSettings: ChannelMixerStrip = new ChannelMixerStrip();
|
||||
|
||||
controls: ControlValue[] = [];
|
||||
// Inserts...
|
||||
|
||||
getControlValue(symbol: string): number | null {
|
||||
for (let control of this.controls) {
|
||||
if (control.key === symbol) {
|
||||
return control.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
setControlValue(symbol: string, value: number): boolean {
|
||||
for (let control of this.controls) {
|
||||
if (control.key === symbol) {
|
||||
if (control.value === value) {
|
||||
return false;
|
||||
}
|
||||
control.value = value;
|
||||
this.controls = this.controls.slice(); // trigger observers.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let newValue = new ControlValue(symbol,value);
|
||||
this.controls.push(newValue);
|
||||
this.controls = this.controls.slice(); // trigger observers.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,524 +0,0 @@
|
||||
// Copyright (c) 2026 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 { useState } from 'react';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
import VuMeter from './VuMeter';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Button from '@mui/material/Button';
|
||||
import useWindowSize from "./UseWindowSize";
|
||||
import DialIcon from './svg/fx_dial.svg?react';
|
||||
import Select from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ChannelMixerSettingsHelpDialog from './ChannelMixerSettingsHelpDialog';
|
||||
import PluginControl from './PluginControl';
|
||||
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ChannelMixerSettings from './ChannelMixerSettings';
|
||||
import { makeChanelMixerUiPlugin, CHANNEL_MIXER_INSTANCE_ID } from './ChannelMixerUiControl';
|
||||
import { ControlValue } from './Pedalboard';
|
||||
|
||||
import DialogEx from './DialogEx';
|
||||
|
||||
import { PiPedalModelFactory } from './PiPedalModel';
|
||||
|
||||
export interface ChannelMixerSettingsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let channelMixerUiPlugin = makeChanelMixerUiPlugin();
|
||||
|
||||
enum RouteType {
|
||||
Main,
|
||||
Aux
|
||||
}
|
||||
let cellSectionHead: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 12,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
|
||||
let cellPortraitControlStrip: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellLeft: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellLeftIndent: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 4,
|
||||
margin: "0px",
|
||||
textAlign: "right",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellLeftH: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
|
||||
|
||||
function MakeChannelMenu(channelCount: number): React.ReactElement[] {
|
||||
let items: React.ReactElement[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
|
||||
if (channelCount === 0) {
|
||||
return items;
|
||||
}
|
||||
if (channelCount === 2) {
|
||||
items.push((<MenuItem key={0} value={0}>Left</MenuItem>));
|
||||
items.push((<MenuItem key={1} value={1}>Right</MenuItem>));
|
||||
return items;
|
||||
}
|
||||
for (let i = 0; i < channelCount; ++i) {
|
||||
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ChannelMixerSettingsDialog(props: ChannelMixerSettingsDialogProps) {
|
||||
//const classes = useStyles();
|
||||
const { open, onClose } = props;
|
||||
let model = PiPedalModelFactory.getInstance();
|
||||
let config = model.jackConfiguration.get();
|
||||
|
||||
|
||||
const [settings, setSettings] = useState<ChannelMixerSettings>(model.channelMixerSettings.get());
|
||||
const [controlValues,setControlValues] = useState<ControlValue[]>(model.channelMixerControlValues.get());
|
||||
const [showHelp, setShowHelp] = useState<boolean>(false);
|
||||
|
||||
if (settings) {
|
||||
}
|
||||
if (controlValues) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
let handleSettingsChanged = () => {
|
||||
setSettings(model.channelMixerSettings.get());
|
||||
};
|
||||
model.channelMixerSettings.addOnChangedHandler(handleSettingsChanged);
|
||||
let handleControlsChanged = () => {
|
||||
setControlValues(model.channelMixerControlValues.get());
|
||||
};
|
||||
model.channelMixerControlValues.addOnChangedHandler(handleControlsChanged);
|
||||
return () => {
|
||||
model.channelMixerSettings.removeOnChangedHandler(handleSettingsChanged);
|
||||
model.channelMixerControlValues.removeOnChangedHandler(handleControlsChanged);
|
||||
}
|
||||
} else {
|
||||
return () => { };
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
let [windowSize] = useWindowSize();
|
||||
let landscape = windowSize.width > windowSize.height;
|
||||
let fullScreen = windowSize.width < 450 || windowSize.height < 600;
|
||||
|
||||
let Dial = (symbol: string) => {
|
||||
let instanceId = CHANNEL_MIXER_INSTANCE_ID;
|
||||
let control = channelMixerUiPlugin.getControl(symbol);
|
||||
let value = settings.getControlValue(symbol);
|
||||
if (value === null) {
|
||||
value = control?.default_value ?? 0;
|
||||
}
|
||||
|
||||
return (<PluginControl
|
||||
instanceId={instanceId}
|
||||
uiControl={control}
|
||||
onPreviewChange={(value: number) => {
|
||||
model.previewPedalboardValue(instanceId, symbol, value);
|
||||
}}
|
||||
onChange={(value: number) => {
|
||||
model.setPedalboardControl(instanceId, symbol, value);
|
||||
}}
|
||||
requestIMEEdit={() => { }}
|
||||
value={value}
|
||||
/>);
|
||||
|
||||
return <DialIcon width={32} height={32} style={{ verticalAlign: "middle", fill: "white" }} />;
|
||||
}
|
||||
let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean) => {
|
||||
let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
|
||||
|
||||
let value: number;
|
||||
|
||||
switch (routeType) {
|
||||
case RouteType.Main:
|
||||
value = input ? settings.guitarInputChannels[channelIndex] : settings.guitarOutputChannels[channelIndex];
|
||||
break;
|
||||
case RouteType.Aux:
|
||||
value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex];
|
||||
break;
|
||||
}
|
||||
if (value >= channelCount) {
|
||||
value = -1;
|
||||
}
|
||||
return (
|
||||
<Select variant="standard" style={{ width: 80 }} value={value}
|
||||
onChange={(event) => {
|
||||
let newValue = event.target.value as number;
|
||||
let newSettings = Object.assign(new ChannelMixerSettings(), settings);
|
||||
switch (routeType) {
|
||||
case RouteType.Main:
|
||||
if (input) {
|
||||
newSettings.guitarInputChannels[channelIndex] = newValue;
|
||||
} else {
|
||||
newSettings.guitarOutputChannels[channelIndex] = newValue;
|
||||
}
|
||||
break;
|
||||
case RouteType.Aux:
|
||||
if (input) {
|
||||
newSettings.auxInputChannels[channelIndex] = newValue;
|
||||
} else {
|
||||
newSettings.auxOutputChannels[channelIndex] = newValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
model.channelMixerSettings.set(newSettings);
|
||||
}}
|
||||
>
|
||||
{MakeChannelMenu(channelCount)}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
let Vu = () => {
|
||||
return (
|
||||
<div style={{ }} >
|
||||
<VuMeter instanceId={CHANNEL_MIXER_INSTANCE_ID} display={"input"} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let LandscapeView = () => {
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%", flexGrow: 1, fontSize: "14px" }} >
|
||||
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th style={cellLeftH}><Typography variant="caption" >In</Typography></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th style={cellLeftH}><Typography variant="caption" >Out</Typography></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLeft}>
|
||||
<Typography variant="body2" color="textSecondary">Main</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("mainIn")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("mainOut")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLeft}>
|
||||
<Typography variant="body2" color="textSecondary">Aux</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("auxIn")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("auxOut")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, false))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
let PortraitView = () => {
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%", flexGrow: 1, fontSize: "14px" }} >
|
||||
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "100%" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td colSpan={4} style={cellSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Main</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, true)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={3}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Dial("mainIn")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Dial("mainOut")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer */}
|
||||
<tr><td colSpan={4} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td colSpan={4} style={cellSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Aux</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={3}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Dial("auxIn")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Dial("auxOut")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 0, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 1, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="channelMixerSettings" onClose={handleClose} aria-labelledby="select-channel_mixer_settings"
|
||||
open={open}
|
||||
fullWidth maxWidth="sm"
|
||||
onEnterKey={handleClose}
|
||||
fullScreen={fullScreen}
|
||||
style={{ maxWidth: 1200 }}
|
||||
>
|
||||
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 8 }}>
|
||||
<Toolbar style={{ padding: 0, margin: 0 }}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Channel Routing
|
||||
</Typography>
|
||||
|
||||
<IconButtonEx tooltip="Help"
|
||||
edge="end"
|
||||
aria-label="help"
|
||||
onClick={() => { setShowHelp(true); }}
|
||||
>
|
||||
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
|
||||
</Toolbar>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{landscape ?
|
||||
LandscapeView()
|
||||
:
|
||||
PortraitView()
|
||||
}
|
||||
</DialogContent>
|
||||
{showHelp && (
|
||||
<ChannelMixerSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
|
||||
)}
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelMixerSettingsDialog;
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
|
||||
|
||||
|
||||
|
||||
|
||||
export default class ChannelRouterSettings {
|
||||
configured: boolean = false;
|
||||
channelRouterPresetId: number = -1;
|
||||
|
||||
mainInputChannels: number[] = [1,1];
|
||||
mainOutputChannels: number[] = [0,1];
|
||||
mainInserts: Pedalboard = new Pedalboard();
|
||||
|
||||
auxInputChannels: number[] = [0,0];
|
||||
auxOutputChannels: number[] = [-1,-1];
|
||||
auxInserts: Pedalboard = new Pedalboard();
|
||||
|
||||
sendInputChannels: number[] = [-1,-1];
|
||||
sendOutputChannels: number[] = [-1,-1];
|
||||
|
||||
controlValues: ControlValue[] = [];
|
||||
// Inserts...
|
||||
|
||||
deserialize(obj: any): ChannelRouterSettings {
|
||||
this.configured = obj.configured;
|
||||
this.channelRouterPresetId = obj.channelRouterPresetId ? obj.channelRouterPresetId : -1;
|
||||
this.mainInputChannels = obj.mainInputChannels.slice();
|
||||
this.mainOutputChannels = obj.mainOutputChannels.slice();
|
||||
this.mainInserts = new Pedalboard().deserialize(obj.mainInserts);
|
||||
this.auxInputChannels = obj.auxInputChannels.slice();
|
||||
this.auxOutputChannels = obj.auxOutputChannels.slice();
|
||||
this.auxInserts = new Pedalboard().deserialize(obj.auxInserts);
|
||||
this.sendInputChannels = obj.sendInputChannels.slice();
|
||||
this.sendOutputChannels = obj.sendOutputChannels.slice();
|
||||
this.controlValues = ControlValue.deserializeArray(obj.controlValues);
|
||||
return this;
|
||||
}
|
||||
getControlValue(symbol: string): number | null {
|
||||
for (let control of this.controlValues) {
|
||||
if (control.key === symbol) {
|
||||
return control.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
setControlValue(symbol: string, value: number): boolean {
|
||||
for (let control of this.controlValues) {
|
||||
if (control.key === symbol) {
|
||||
if (control.value === value) {
|
||||
return false;
|
||||
}
|
||||
control.value = value;
|
||||
this.controlValues = this.controlValues.slice(); // trigger observers.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let newValue = new ControlValue(symbol,value);
|
||||
this.controlValues.push(newValue);
|
||||
this.controlValues = this.controlValues.slice(); // trigger observers.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,853 @@
|
||||
// Copyright (c) 2026 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 { useState } from 'react';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Button from '@mui/material/Button';
|
||||
import useWindowSize from "./UseWindowSize";
|
||||
import Select from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ChannelRouterSettingsHelpDialog from './ChannelRouterSettingsHelpDialog';
|
||||
import SpeakerIcon from '@mui/icons-material/Speaker';
|
||||
import MicIcon from '@mui/icons-material/Mic';
|
||||
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
import { Pedalboard, ControlValue } from './Pedalboard';
|
||||
|
||||
import DialogEx from './DialogEx';
|
||||
|
||||
import { PiPedalModelFactory } from './PiPedalModel';
|
||||
|
||||
export interface ChannelRouterSettingsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
enum RouteType {
|
||||
Main,
|
||||
Aux,
|
||||
Send
|
||||
}
|
||||
|
||||
interface ChannelRouterSettingsPreset {
|
||||
id: number;
|
||||
name: string;
|
||||
settings: ChannelRouterSettings;
|
||||
}
|
||||
|
||||
let LEFT_LEFT_PRESET_ID = -2;
|
||||
let RIGHT_STEREO_PRESET_ID = -3;
|
||||
|
||||
let channelRouterPresets: ChannelRouterSettingsPreset[] = [
|
||||
{
|
||||
id: -2, name: "Left -> Left", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -2,
|
||||
mainInputChannels: [0, 0],
|
||||
mainOutputChannels: [0, 0],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, -1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [-1, -1],
|
||||
sendOutputChannels: [-1, -1],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -3, name: "Left -> Stereo", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -3,
|
||||
mainInputChannels: [0, 0],
|
||||
mainOutputChannels: [0, 1],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, -1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [-1, -1],
|
||||
sendOutputChannels: [-1, -1],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -4, name: "Right -> Stereo", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -4,
|
||||
mainInputChannels: [1, 1],
|
||||
mainOutputChannels: [0, 1],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, -1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [-1, -1],
|
||||
sendOutputChannels: [-1, -1],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -5, name: "Right -> Left + re-amp", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -5,
|
||||
mainInputChannels: [1, 1],
|
||||
mainOutputChannels: [0, 0],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [1, 1],
|
||||
auxOutputChannels: [1, 1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [-1, - 1],
|
||||
sendOutputChannels: [-1, -1],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -6, name: "Left -> Left + send", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -6,
|
||||
mainInputChannels: [0, 0],
|
||||
mainOutputChannels: [0, 0],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, - 1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [1, 1],
|
||||
sendOutputChannels: [1, 1],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -7, name: "Right -> Right + send", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -7,
|
||||
mainInputChannels: [1, 1],
|
||||
mainOutputChannels: [1, 1],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, - 1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [0, 0],
|
||||
sendOutputChannels: [0, 0],
|
||||
controlValues: [],
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -8, name: "Left -> Stereo + re-amp", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -8,
|
||||
mainInputChannels: [0, 0],
|
||||
mainOutputChannels: [0, 1],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [0, 0],
|
||||
auxOutputChannels: [2, 2],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [-1, -1],
|
||||
sendOutputChannels: [-1, -1],
|
||||
controlValues: [],
|
||||
|
||||
})
|
||||
},
|
||||
{
|
||||
id: -9, name: "Left -> Stereo + stereo send", settings: new ChannelRouterSettings().deserialize({
|
||||
configured: true,
|
||||
channelRouterPresetId: -9,
|
||||
mainInputChannels: [0, 0],
|
||||
mainOutputChannels: [0, 1],
|
||||
mainInserts: new Pedalboard(),
|
||||
auxInputChannels: [-1, -1],
|
||||
auxOutputChannels: [-1, - 1],
|
||||
auxInserts: new Pedalboard(),
|
||||
sendInputChannels: [2, 3],
|
||||
sendOutputChannels: [2, 3],
|
||||
controlValues: [],
|
||||
|
||||
})
|
||||
},
|
||||
];
|
||||
|
||||
let cellPortraitInOutDiv: React.CSSProperties = {
|
||||
width: 80, display: "flex", columnGap: 4, flexDirection: "row", alignItems: "center", justifyContent: "right",
|
||||
};
|
||||
let cellPortraitSectionHead: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 12,
|
||||
width: 50,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle",
|
||||
};
|
||||
|
||||
let cellPortraitControlStrip: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellLeft: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 4,
|
||||
paddingRight: 12,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
whiteSpace: "nowrap",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellLandscapeTitle: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 4,
|
||||
paddingRight: 20,
|
||||
margin: "0px",
|
||||
textAlign: "left",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
let cellPortraitInOut: React.CSSProperties = {
|
||||
border: "0px",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
margin: "0px",
|
||||
width: 80,
|
||||
textAlign: "right",
|
||||
verticalAlign: "middle"
|
||||
};
|
||||
|
||||
function MakeChannelMenu(channelCount: number): React.ReactElement[] {
|
||||
let items: React.ReactElement[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
|
||||
if (channelCount === 0) {
|
||||
return items;
|
||||
}
|
||||
if (channelCount === 2) {
|
||||
items.push((<MenuItem key={0} value={0}>Left</MenuItem>));
|
||||
items.push((<MenuItem key={1} value={1}>Right</MenuItem>));
|
||||
return items;
|
||||
}
|
||||
for (let i = 0; i < channelCount; ++i) {
|
||||
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
|
||||
//const classes = useStyles();
|
||||
const { open, onClose } = props;
|
||||
let model = PiPedalModelFactory.getInstance();
|
||||
let config = model.jackConfiguration.get();
|
||||
|
||||
|
||||
const [settings, setSettings] = useState<ChannelRouterSettings>(model.channelRouterSettings.get());
|
||||
const [controlValues, setControlValues] = useState<ControlValue[]>(model.channelRouterControlValues.get());
|
||||
const [showHelp, setShowHelp] = useState<boolean>(false);
|
||||
|
||||
if (settings) {
|
||||
}
|
||||
if (controlValues) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
let handleSettingsChanged = () => {
|
||||
setSettings(model.channelRouterSettings.get());
|
||||
};
|
||||
model.channelRouterSettings.addOnChangedHandler(handleSettingsChanged);
|
||||
let handleControlsChanged = () => {
|
||||
setControlValues(model.channelRouterControlValues.get());
|
||||
};
|
||||
model.channelRouterControlValues.addOnChangedHandler(handleControlsChanged);
|
||||
return () => {
|
||||
model.channelRouterSettings.removeOnChangedHandler(handleSettingsChanged);
|
||||
model.channelRouterControlValues.removeOnChangedHandler(handleControlsChanged);
|
||||
}
|
||||
} else {
|
||||
return () => { };
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
let [windowSize] = useWindowSize();
|
||||
let landscape = windowSize.width > windowSize.height;
|
||||
let fullScreen = windowSize.width < 450 || windowSize.height < 600;
|
||||
|
||||
let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean, icon: boolean = true) => {
|
||||
let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
|
||||
|
||||
let value: number;
|
||||
|
||||
switch (routeType) {
|
||||
case RouteType.Main:
|
||||
value = input ? settings.mainInputChannels[channelIndex] : settings.mainOutputChannels[channelIndex];
|
||||
break;
|
||||
case RouteType.Aux:
|
||||
value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex];
|
||||
break;
|
||||
case RouteType.Send:
|
||||
value = input ? settings.sendInputChannels[channelIndex] : settings.sendOutputChannels[channelIndex];
|
||||
break;
|
||||
}
|
||||
if (value >= channelCount) {
|
||||
value = -1;
|
||||
}
|
||||
return (
|
||||
<Select variant="standard" style={{ width: 90 }} value={value}
|
||||
startAdornment={
|
||||
icon ? (
|
||||
input ? (<MicIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) :
|
||||
(<SpeakerIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />)
|
||||
) : undefined
|
||||
}
|
||||
onChange={(event) => {
|
||||
let newValue = event.target.value as number;
|
||||
let newSettings = Object.assign(new ChannelRouterSettings(), settings);
|
||||
switch (routeType) {
|
||||
case RouteType.Main:
|
||||
if (input) {
|
||||
newSettings.mainInputChannels[channelIndex] = newValue;
|
||||
} else {
|
||||
newSettings.mainOutputChannels[channelIndex] = newValue;
|
||||
}
|
||||
break;
|
||||
case RouteType.Aux:
|
||||
if (input) {
|
||||
newSettings.auxInputChannels[channelIndex] = newValue;
|
||||
} else {
|
||||
newSettings.auxOutputChannels[channelIndex] = newValue;
|
||||
}
|
||||
break;
|
||||
case RouteType.Send:
|
||||
if (input) {
|
||||
newSettings.sendInputChannels[channelIndex] = newValue;
|
||||
|
||||
} else {
|
||||
newSettings.sendOutputChannels[channelIndex] = newValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
newSettings.channelRouterPresetId = -1; // custom.
|
||||
newSettings.configured = true;
|
||||
model.setChannelRouterSettings(newSettings);
|
||||
}}
|
||||
>
|
||||
{MakeChannelMenu(channelCount)}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
let Vu = () => {
|
||||
return (
|
||||
<div style={{}} >
|
||||
<div style={{ width: 8, height: 48, background: "black" }}></div>
|
||||
{/* <VuMeter instanceId={Pedalboard.CHANNEL_ROUTER_MAIN_INSERT_ID} display={"input"} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let ApplyPresetId = (presetId: number | string | null | undefined) => {
|
||||
if (typeof presetId !== 'number') {
|
||||
return;
|
||||
}
|
||||
for (let preset of channelRouterPresets) {
|
||||
if (preset.id === presetId) {
|
||||
model.setChannelRouterSettings(preset.settings);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let CanUsePreset = (preset: ChannelRouterSettingsPreset): boolean => {
|
||||
let inputChannels = config.inputAudioPorts.length;
|
||||
let outputChannels = config.outputAudioPorts.length;
|
||||
return (
|
||||
(preset.settings.mainInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
|
||||
(preset.settings.mainOutputChannels.every((ch) => ch < outputChannels || ch === -1)) &&
|
||||
(preset.settings.auxInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
|
||||
(preset.settings.auxOutputChannels.every((ch) => ch < outputChannels || ch === -1)) &&
|
||||
(preset.settings.sendInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
|
||||
(preset.settings.sendOutputChannels.every((ch) => ch < outputChannels || ch === -1))
|
||||
);
|
||||
}
|
||||
|
||||
let GeneratePresetMenuItems = (): React.ReactElement[] => {
|
||||
let items: React.ReactElement[] = [];
|
||||
|
||||
for (let preset of channelRouterPresets) {
|
||||
if (CanUsePreset(preset))
|
||||
items.push(
|
||||
<MenuItem key={preset.id} value={preset.id}>
|
||||
{preset.name}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (settings.channelRouterPresetId === -1) {
|
||||
items.push(
|
||||
(<MenuItem key={-1} value={-1} disabled={true}>
|
||||
<span style={{ opacity: 0.6 }}>(Custom)</span></MenuItem>)
|
||||
)
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
let PresetSelect = (width: number | string | undefined) => {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row", marginTop: 2, alignItems: "center" }}>
|
||||
<Select variant="standard" style={{ width: width }} value={settings.channelRouterPresetId}
|
||||
onChange={(event) => {
|
||||
ApplyPresetId(event.target.value);
|
||||
}}
|
||||
>
|
||||
{GeneratePresetMenuItems()}
|
||||
</Select>
|
||||
<IconButtonEx tooltip="Presets" aria-label="more-presets">
|
||||
<MoreVertIcon style={{ opacity: 0.6 }} onClick={() => { }} />
|
||||
</IconButtonEx>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let LandscapeView = () => {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
|
||||
width: "100%", height: "100%", flexGrow: 1, fontSize: "14px"
|
||||
}} >
|
||||
<table style={{ borderCollapse: "collapse" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Main</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux ----------------------*/}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Aux</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, false))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Send ---------------- */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Send</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, false))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
{/* No button */}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, true))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, true))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
let PortraitView = () => {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
|
||||
width: "100%", flexGrow: 0, fontSize: "14px"
|
||||
}} >
|
||||
<table style={{ borderCollapse: "collapse" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Main</Typography>
|
||||
</td>
|
||||
<td colSpan={4}>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, true, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellLeft}>
|
||||
|
||||
{ChannelSelect(RouteType.Main, 0, false, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Aux</Typography>
|
||||
</td>
|
||||
<td colSpan={4}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true, false))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 0, false, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Send */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Send</Typography>
|
||||
</td>
|
||||
<td colSpan={4}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Send</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, false, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, false, false))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8, visibility: "hidden" }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Return</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Send, 0, true, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Send, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="channelRouterSettings"
|
||||
onClose={handleClose}
|
||||
aria-labelledby="select-channel_mixer_settings"
|
||||
open={open}
|
||||
fullWidth
|
||||
maxWidth={landscape ? "sm" : "xs"}
|
||||
onEnterKey={handleClose}
|
||||
fullScreen={fullScreen}
|
||||
>
|
||||
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 0 }}>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", flexWrap: "nowrap", alignItems: "center",
|
||||
marginTop: 8
|
||||
|
||||
}}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{}}>
|
||||
Channel Routing
|
||||
</Typography>
|
||||
{
|
||||
landscape ? (
|
||||
<>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
<div style={{ flexShrink: 1, maxWidth: 440, display: "relative", marginLeft: 16, marginRight: 16 }}>
|
||||
{PresetSelect(200)}
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
</>
|
||||
|
||||
) : (
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
)
|
||||
}
|
||||
|
||||
<IconButtonEx tooltip="Help"
|
||||
edge="end"
|
||||
aria-label="help"
|
||||
onClick={() => { setShowHelp(true); }}
|
||||
>
|
||||
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
</div>
|
||||
</DialogTitle>
|
||||
{!landscape && (
|
||||
<>
|
||||
<div style={{ marginLeft: 16 + 32 + 8, marginBottom: 8 }}>
|
||||
{PresetSelect("60%")}
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DialogContent>
|
||||
{landscape ?
|
||||
LandscapeView()
|
||||
:
|
||||
PortraitView()
|
||||
}
|
||||
</DialogContent>
|
||||
{
|
||||
showHelp && (
|
||||
<ChannelRouterSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
|
||||
)
|
||||
}
|
||||
</DialogEx >
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelRouterSettingsDialog;
|
||||
+14
-7
@@ -18,33 +18,30 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
|
||||
|
||||
export interface ChannelMixerSettingsHelpDialogProps {
|
||||
export interface ChannelRouterSettingsHelpDialogProps {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
export interface ChannelMixerSettingsHelpDialogState {
|
||||
export interface ChannelRouterSettingsHelpDialogState {
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
export default class ChannelMixerSettingsHelpDialog extends ResizeResponsiveComponent<ChannelMixerSettingsHelpDialogProps, ChannelMixerSettingsHelpDialogState> {
|
||||
export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveComponent<ChannelRouterSettingsHelpDialogProps, ChannelRouterSettingsHelpDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
constructor(props: ChannelMixerSettingsHelpDialogProps) {
|
||||
constructor(props: ChannelRouterSettingsHelpDialogProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
fullScreen: false
|
||||
@@ -137,6 +134,16 @@ export default class ChannelMixerSettingsHelpDialog extends ResizeResponsiveComp
|
||||
channels to both the Main and Aux routes. If you have a 2x4 or 4x4 audio interface with two guitar input channels
|
||||
you can also pass through stereo dry signals by configuring Main and Aux input and output channels appropriately.
|
||||
</p>
|
||||
<p>The Send route, in conjunction with the TooB Send plugin allows you to send and return audio signals to an external physical device
|
||||
from within a PiPedal preset. This allows you to integrate external hardware effects into your PiPedal presets.
|
||||
Connect a cable from the audio interface output <i>Send</i> channel(s) selected here to the input of your
|
||||
hardware effect, and a cable from the output of your hardware effect to the audio interface's <i>Return</i> channel
|
||||
assigned here. Then insert the TooB Send plugin into your PiPedal preset. The input of the
|
||||
TooB Send plugin will be sent to the external hardware effect, and the output the external hardware
|
||||
effect will be returned to PiPedal at the output of the TooB Send plugin. PiPedal currently supports
|
||||
only one Send plugin instance per preset; and the Send and Return channels may not be shared with the Main
|
||||
or Aux routes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
+2
-3
@@ -19,13 +19,12 @@
|
||||
import Units from './Units';
|
||||
import {UiPlugin, UiControl, PluginType, ControlType, ScalePoint} from './Lv2Plugin';
|
||||
|
||||
export const ChannelMixerUiUri = "uri://two-play/pipedal/ppChannelMixer";
|
||||
export const CHANNEL_MIXER_INSTANCE_ID = -4;
|
||||
export const ChannelRouterUiUri = "uri://two-play/pipedal/ppChannelRouter";
|
||||
|
||||
export function makeChanelMixerUiPlugin(): UiPlugin {
|
||||
|
||||
return new UiPlugin().deserialize({
|
||||
uri: ChannelMixerUiUri,
|
||||
uri: ChannelRouterUiUri,
|
||||
name: "Channel Mixer",
|
||||
brand: "",
|
||||
label: "",
|
||||
@@ -357,14 +357,36 @@ export class PedalboardSplitItem extends PedalboardItem {
|
||||
}
|
||||
|
||||
|
||||
let TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value.
|
||||
let TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards.
|
||||
let MAX_INSTANCE_ID = TWO_POWER_48;
|
||||
|
||||
|
||||
|
||||
export enum InstanceType {
|
||||
MainPedalboard,
|
||||
MainInsert,
|
||||
AuxInsert
|
||||
}
|
||||
|
||||
|
||||
export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
|
||||
static readonly CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Main Inserts.
|
||||
static readonly CHANNEL_ROUTER_MAIN_INSERT_ID = -5; // Reserved Instance ID for Router Main Inserts.
|
||||
static readonly CHANNEL_ROUTER_AUX_INSERT_ID = -6; // Reserved Instance ID for Router Aux inserts.
|
||||
|
||||
static readonly MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID;
|
||||
static readonly AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID;
|
||||
|
||||
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
|
||||
static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume.
|
||||
static readonly CHANNEL_MIXER_INSTANCE_ID = -4; // Synthetic instance ID for the channel routing mixer.
|
||||
static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
|
||||
static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
|
||||
static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
|
||||
static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
|
||||
|
||||
|
||||
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
||||
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
||||
|
||||
@@ -373,7 +395,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
this.input_volume_db = input.input_volume_db;
|
||||
this.output_volume_db = input.output_volume_db;
|
||||
this.items = PedalboardItem.deserializeArray(input.items);
|
||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||
this.nextInstanceId = input.nextInstanceId ?? 0;
|
||||
this.snapshots = input.snapshots ? Snapshot.deserializeArray(input.snapshots): [];
|
||||
this.selectedSnapshot = input.selectedSnapshot;
|
||||
this.pathProperties = input.pathProperties;
|
||||
@@ -381,6 +403,25 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static getInstanceTypeFromInstanceId(instanceId: number): InstanceType {
|
||||
if (instanceId >= Pedalboard.MAIN_INSERT_INSTANCE_BASE) {
|
||||
return InstanceType.MainInsert;
|
||||
}
|
||||
if (instanceId >= Pedalboard.AUX_INSERT_INSTANCE_BASE) {
|
||||
return InstanceType.AuxInsert;
|
||||
}
|
||||
return InstanceType.MainPedalboard;
|
||||
}
|
||||
getInstanceType(): InstanceType {
|
||||
if (this.nextInstanceId >= Pedalboard.MAIN_INSERT_INSTANCE_BASE) {
|
||||
return InstanceType.MainInsert;
|
||||
}
|
||||
if (this.nextInstanceId >= Pedalboard.AUX_INSERT_INSTANCE_BASE) {
|
||||
return InstanceType.AuxInsert;
|
||||
}
|
||||
return InstanceType.MainPedalboard;
|
||||
}
|
||||
|
||||
clone(): Pedalboard {
|
||||
return new Pedalboard().deserialize(this);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import AudioFileMetadata from './AudioFileMetadata';
|
||||
import { pathFileName } from './FileUtils';
|
||||
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||
import { getDefaultModGuiPreference } from './ModGuiHost';
|
||||
import ChannelMixerSettings from './ChannelMixerSettings';
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
|
||||
export enum State {
|
||||
Loading,
|
||||
@@ -530,13 +530,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
jackServerSettings: ObservableProperty<JackServerSettings>
|
||||
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
|
||||
|
||||
channelMixerControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||
channelMixerSettings: ObservableProperty<ChannelMixerSettings> = new ObservableProperty<ChannelMixerSettings>(new ChannelMixerSettings());
|
||||
channelRouterControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||
channelRouterSettings: ObservableProperty<ChannelRouterSettings> = new ObservableProperty<ChannelRouterSettings>(new ChannelRouterSettings());
|
||||
|
||||
setChannelMixerSettings(settings: ChannelMixerSettings) {
|
||||
this.channelMixerSettings.set(settings);
|
||||
this.channelMixerControlValues.set(settings.controls);
|
||||
}
|
||||
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
|
||||
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
|
||||
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
|
||||
@@ -1255,6 +1251,11 @@ export class PiPedalModel //implements PiPedalModel
|
||||
await this.getWebSocket().request<any>("getJackServerSettings")
|
||||
)
|
||||
);
|
||||
this.channelRouterSettings.set(
|
||||
new ChannelRouterSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getChannelRouterSettings")
|
||||
)
|
||||
)
|
||||
this.jackConfiguration.set(new JackConfiguration().deserialize(
|
||||
await this.getWebSocket().request<JackConfiguration>("getJackConfiguration")
|
||||
));
|
||||
@@ -1651,15 +1652,15 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
private lastControlMessageWasSentbyMe = false;
|
||||
|
||||
private _setChannelMixerControlValue(key: string, value: number, notifyServer: boolean) : void {
|
||||
let channelMixerSettings = this.channelMixerSettings.get();
|
||||
let changed = channelMixerSettings.setControlValue(key, value);
|
||||
private _setChannelRouterControlValue(key: string, value: number, notifyServer: boolean) : void {
|
||||
let channelRouterSettings = this.channelRouterSettings.get();
|
||||
let changed = channelRouterSettings.setControlValue(key, value);
|
||||
if (changed)
|
||||
{
|
||||
this.channelMixerControlValues.set(this.channelMixerSettings.get().controls);
|
||||
this.channelRouterControlValues.set(this.channelRouterSettings.get().controlValues);
|
||||
if (notifyServer) {
|
||||
this.lastControlMessageWasSentbyMe = true;
|
||||
this._setServerControl("setControl", Pedalboard.CHANNEL_MIXER_INSTANCE_ID, key, value);
|
||||
this._setServerControl("setControl", Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1667,8 +1668,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let pedalboard = this.pedalboard.get();
|
||||
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
||||
|
||||
if (instanceId == Pedalboard.CHANNEL_MIXER_INSTANCE_ID) {
|
||||
this._setChannelMixerControlValue(key,value,notifyServer);
|
||||
if (instanceId == Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID) {
|
||||
this._setChannelRouterControlValue(key,value,notifyServer);
|
||||
return;
|
||||
} else {
|
||||
let changed: boolean;
|
||||
@@ -2316,12 +2317,6 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
setJackSettings(jackSettings: JackChannelSelection): void {
|
||||
this.expectDisconnect(ReconnectReason.LoadingSettings);
|
||||
this.webSocket?.send("setJackSettings", jackSettings);
|
||||
}
|
||||
|
||||
|
||||
monitorPortSubscriptions: MonitorPortHandleImpl[] = [];
|
||||
|
||||
monitorPort(instanceId: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle {
|
||||
@@ -3310,7 +3305,6 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (this.webSocket) {
|
||||
this.webSocket.send("setFavorites", newFavorites);
|
||||
}
|
||||
// stub: update server.
|
||||
}
|
||||
|
||||
|
||||
@@ -3680,6 +3674,15 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
this.removeInvalidSidechains(pedalboard);
|
||||
}
|
||||
|
||||
setChannelRouterSettings(settings: ChannelRouterSettings) {
|
||||
this.channelRouterSettings.set(settings);
|
||||
this.channelRouterControlValues.set(settings.controlValues);
|
||||
if (this.webSocket) {
|
||||
this.webSocket.send("setChannelRouterSettings", settings);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let instance: PiPedalModel | undefined = undefined;
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
import React, { SyntheticEvent, Component } from 'react';
|
||||
import Switch from "@mui/material/Switch";
|
||||
import ChannelMixerSettings from './ChannelMixerSettings';
|
||||
import ChannelMixerSettingsDialog from './ChannelMixerSettingsDialog';
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
import ChannelRouterSettingsDialog from './ChannelRouterSettingsDialog';
|
||||
import OkCancelDialog from './OkCancelDialog';
|
||||
import RadioSelectDialog from './RadioSelectDialog';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
@@ -78,7 +78,7 @@ interface SettingsDialogState {
|
||||
showStatusMonitorDialog: boolean;
|
||||
jackConfiguration: JackConfiguration;
|
||||
jackSettings: JackChannelSelection | null;
|
||||
channelMixerSettings: ChannelMixerSettings | null;
|
||||
channelRouterSettings: ChannelRouterSettings | null;
|
||||
jackServerSettings: JackServerSettings;
|
||||
alsaSequencerConfiguration: AlsaSequencerConfiguration;
|
||||
keepScreenOn: boolean;
|
||||
@@ -101,7 +101,7 @@ interface SettingsDialogState {
|
||||
showMidiSelectDialog: boolean;
|
||||
showThemeSelectDialog: boolean;
|
||||
showJackServerSettingsDialog: boolean;
|
||||
showChannelMixerSettingsDialog: boolean;
|
||||
showChannelRouterSettingsDialog: boolean;
|
||||
shuttingDown: boolean;
|
||||
restarting: boolean;
|
||||
isAndroidHosted: boolean;
|
||||
@@ -194,7 +194,7 @@ const SettingsDialog = withStyles(
|
||||
showStatusMonitorDialog: false,
|
||||
|
||||
jackServerSettings: this.model.jackServerSettings.get(),
|
||||
channelMixerSettings: this.model.channelMixerSettings.get(),
|
||||
channelRouterSettings: this.model.channelRouterSettings.get(),
|
||||
jackConfiguration: this.model.jackConfiguration.get(),
|
||||
jackStatus: undefined,
|
||||
jackSettings: this.model.jackSettings.get(),
|
||||
@@ -215,7 +215,7 @@ const SettingsDialog = withStyles(
|
||||
showMidiSelectDialog: false,
|
||||
showThemeSelectDialog: false,
|
||||
showJackServerSettingsDialog: false,
|
||||
showChannelMixerSettingsDialog: false,
|
||||
showChannelRouterSettingsDialog: false,
|
||||
shuttingDown: false,
|
||||
restarting: false,
|
||||
showShutdownOkDialog: false,
|
||||
@@ -228,7 +228,7 @@ const SettingsDialog = withStyles(
|
||||
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
|
||||
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
||||
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
|
||||
this.handleChannelMixerSettingsChanged = this.handleChannelMixerSettingsChanged.bind(this);
|
||||
this.handleChannelRouterSettingsChanged = this.handleChannelRouterSettingsChanged.bind(this);
|
||||
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
|
||||
this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this);
|
||||
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
|
||||
@@ -330,10 +330,10 @@ const SettingsDialog = withStyles(
|
||||
continueDisabled: !this.model.jackServerSettings.get().valid
|
||||
});
|
||||
}
|
||||
handleChannelMixerSettingsChanged(): void {
|
||||
handleChannelRouterSettingsChanged(): void {
|
||||
this.setState({
|
||||
channelMixerSettings: this.model.channelMixerSettings.get(),
|
||||
continueDisabled: !this.model.channelMixerSettings.get().configured
|
||||
channelRouterSettings: this.model.channelRouterSettings.get(),
|
||||
continueDisabled: !this.model.channelRouterSettings.get().configured
|
||||
});
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ const SettingsDialog = withStyles(
|
||||
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
|
||||
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||
this.model.channelMixerSettings.addOnChangedHandler(this.handleChannelMixerSettingsChanged);
|
||||
this.model.channelRouterSettings.addOnChangedHandler(this.handleChannelRouterSettingsChanged);
|
||||
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||
@@ -401,7 +401,7 @@ const SettingsDialog = withStyles(
|
||||
this.handleJackSettingsChanged();
|
||||
this.handleShowStatusMonitorChanged();
|
||||
this.handleJackServerSettingsChanged();
|
||||
this.handleChannelMixerSettingsChanged();
|
||||
this.handleChannelRouterSettingsChanged();
|
||||
this.handleWifiConfigSettingsChanged();
|
||||
this.handleWifiDirectConfigSettingsChanged();
|
||||
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
|
||||
@@ -420,7 +420,7 @@ const SettingsDialog = withStyles(
|
||||
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
|
||||
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||
this.model.channelMixerSettings.removeOnChangedHandler(this.handleChannelMixerSettingsChanged);
|
||||
this.model.channelRouterSettings.removeOnChangedHandler(this.handleChannelRouterSettingsChanged);
|
||||
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||
@@ -720,10 +720,10 @@ const SettingsDialog = withStyles(
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
{this.state.showChannelMixerSettingsDialog && (
|
||||
<ChannelMixerSettingsDialog
|
||||
open={this.state.showChannelMixerSettingsDialog}
|
||||
onClose={() => this.setState({ showChannelMixerSettingsDialog: false })}
|
||||
{this.state.showChannelRouterSettingsDialog && (
|
||||
<ChannelRouterSettingsDialog
|
||||
open={this.state.showChannelRouterSettingsDialog}
|
||||
onClose={() => this.setState({ showChannelRouterSettingsDialog: false })}
|
||||
/>
|
||||
)}
|
||||
{this.state.showJackServerSettingsDialog && (
|
||||
@@ -744,7 +744,7 @@ const SettingsDialog = withStyles(
|
||||
<ButtonBase className={classes.setting}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
showChannelMixerSettingsDialog: true
|
||||
showChannelRouterSettingsDialog: true
|
||||
});
|
||||
}}
|
||||
disabled={!hasAudioConfig}
|
||||
|
||||
@@ -372,7 +372,7 @@ const ToobParametricEqView =
|
||||
])}
|
||||
{panel("", [
|
||||
controls.gain
|
||||
])}
|
||||
])}
|
||||
{divider()}
|
||||
{panel("Low",
|
||||
[controls.low_level,
|
||||
|
||||
Reference in New Issue
Block a user