This commit is contained in:
Robin E. R. Davies
2026-01-27 10:10:36 -05:00
parent 9410c0144b
commit 59263ee715
38 changed files with 2127 additions and 979 deletions
-55
View File
@@ -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 }}>&nbsp;</div>
<div style={{ flexShrink: 1, maxWidth: 440, display: "relative", marginLeft: 16, marginRight: 16 }}>
{PresetSelect(200)}
</div>
<div style={{ flexGrow: 1 }}>&nbsp;</div>
</>
) : (
<div style={{ flexGrow: 1 }}>&nbsp;</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;
@@ -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>
@@ -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: "",
+43 -2
View File
@@ -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);
}
+24 -21
View File
@@ -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;
+18 -18
View File
@@ -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}
+1 -1
View File
@@ -372,7 +372,7 @@ const ToobParametricEqView =
])}
{panel("", [
controls.gain
])}
])}
{divider()}
{panel("Low",
[controls.low_level,