Routing, in progress.
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,524 @@
|
|||||||
|
// 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,146 @@
|
|||||||
|
// Copyright (c) 2022 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// 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 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 {
|
||||||
|
open: boolean,
|
||||||
|
onClose: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ChannelMixerSettingsHelpDialogState {
|
||||||
|
fullScreen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ChannelMixerSettingsHelpDialog extends ResizeResponsiveComponent<ChannelMixerSettingsHelpDialogProps, ChannelMixerSettingsHelpDialogState> {
|
||||||
|
|
||||||
|
refText: React.RefObject<HTMLInputElement | null>;
|
||||||
|
|
||||||
|
constructor(props: ChannelMixerSettingsHelpDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
fullScreen: false
|
||||||
|
};
|
||||||
|
this.refText = React.createRef<HTMLInputElement>();
|
||||||
|
}
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
this.setState({ fullScreen: height < 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
this.mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
let props = this.props;
|
||||||
|
let { open, onClose } = props;
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogEx tag="bindingHelp" open={open} fullWidth maxWidth="sm"
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="mixer-settings-dialog-title"
|
||||||
|
style={{ userSelect: "none" }}
|
||||||
|
fullwidth
|
||||||
|
onEnterKey={() => { }}
|
||||||
|
>
|
||||||
|
<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 Dialog Help
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent style={{ minHeight: 96 }}>
|
||||||
|
<div style={{ fontSize: "0.9em", lineHeight: "18pt" }}>
|
||||||
|
<p> The Mixer Routing Dialog determines how audio signals are processed and routed between input and output audio channels.
|
||||||
|
</p>
|
||||||
|
<p>Guitar effects processing occurs on the <i>Main</i> route only. Plugins in the main PiPedal window are
|
||||||
|
applied before any insert plugins that have been added to the <i>Main</i> route. Main insert plugins are
|
||||||
|
applied globally, regardless of the selected preset or effects in the main PiPedal window. You might
|
||||||
|
want to add an EQ or reverb plugin here, to allow your presets to be globally adjusted to suit the room
|
||||||
|
in which you are currently performing.
|
||||||
|
</p>
|
||||||
|
<p>Plugins in the main PiPedal window are not applied to the <i>Aux</i> route. The Aux route is intended to be
|
||||||
|
used for
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Passing through a dry (unprocessed) guitar signal that can be recorded by a DAW and
|
||||||
|
re-amped later during mixing.
|
||||||
|
</li>
|
||||||
|
<li>Passing through a vocal mic signal from one of the input channels, perhaps with compression, EQ or
|
||||||
|
other processing performed by plugins in Aux inserts.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Passing through a backing track or other external audio signal from one of the input channels, and then either
|
||||||
|
mixing it with the processed guitar signal, or passing it out on a dedicated output channel.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>although you may find other creative uses for the Aux channel as well.</p>
|
||||||
|
<p>If the Main and Aux routes share output channels, then the results of the Main and Output signals are
|
||||||
|
summed together on those output channels. For stereo mixing, assign the same left and right output
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
// 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 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 function makeChanelMixerUiPlugin(): UiPlugin {
|
||||||
|
|
||||||
|
return new UiPlugin().deserialize({
|
||||||
|
uri: ChannelMixerUiUri,
|
||||||
|
name: "Channel Mixer",
|
||||||
|
brand: "",
|
||||||
|
label: "",
|
||||||
|
plugin_type: PluginType.MixerPlugin,
|
||||||
|
plugin_display_type: "Mixer",
|
||||||
|
author_name: "",
|
||||||
|
author_homepage: "",
|
||||||
|
audio_inputs: 1,
|
||||||
|
audio_side_chain_inputs: 0,
|
||||||
|
audio_outputs: 1,
|
||||||
|
has_midi_input: false,
|
||||||
|
has_midi_output: false,
|
||||||
|
description: "",
|
||||||
|
controls: [
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "mainIn",
|
||||||
|
name: "Main In",
|
||||||
|
index: 0,
|
||||||
|
is_input: true,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
is_bypass: false,
|
||||||
|
units: Units.db,
|
||||||
|
controlType: ControlType.Dial,
|
||||||
|
is_program_controller: false,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
scale_points: [
|
||||||
|
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "mainOut",
|
||||||
|
name: "Main Out",
|
||||||
|
index: 1,
|
||||||
|
is_input: true,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
is_bypass: false,
|
||||||
|
units: Units.db,
|
||||||
|
controlType: ControlType.Dial,
|
||||||
|
is_program_controller: false,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
scale_points: [
|
||||||
|
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
|
||||||
|
],
|
||||||
|
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "auxIn",
|
||||||
|
name: "Aux In",
|
||||||
|
index: 2,
|
||||||
|
is_input: true,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
is_bypass: false,
|
||||||
|
units: Units.db,
|
||||||
|
controlType: ControlType.Dial,
|
||||||
|
is_program_controller: false,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
scale_points: [
|
||||||
|
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "auxOut",
|
||||||
|
name: "Aux Out",
|
||||||
|
index: 3,
|
||||||
|
is_input: true,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
is_bypass: false,
|
||||||
|
units: Units.db,
|
||||||
|
controlType: ControlType.Dial,
|
||||||
|
is_program_controller: false,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
scale_points: [
|
||||||
|
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
|
||||||
|
],
|
||||||
|
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "mainInVu",
|
||||||
|
name: "In",
|
||||||
|
index: 4,
|
||||||
|
is_input: false,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
controlType: ControlType.Vu,
|
||||||
|
is_bypass: false,
|
||||||
|
is_program_controller: false,
|
||||||
|
units: Units.db,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "mainInVu",
|
||||||
|
name: "In",
|
||||||
|
index: 5,
|
||||||
|
is_input: false,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
controlType: ControlType.Vu,
|
||||||
|
is_bypass: false,
|
||||||
|
is_program_controller: false,
|
||||||
|
units: Units.db,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "mainOutVu",
|
||||||
|
name: "Out",
|
||||||
|
index: 6,
|
||||||
|
is_input: false,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
controlType: ControlType.Vu,
|
||||||
|
is_bypass: false,
|
||||||
|
is_program_controller: false,
|
||||||
|
units: Units.db,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
}),
|
||||||
|
new UiControl().applyProperties({
|
||||||
|
symbol: "auxOutVu",
|
||||||
|
name: "Out",
|
||||||
|
index: 7,
|
||||||
|
is_input: false,
|
||||||
|
min_value: -60.0,
|
||||||
|
max_value: 20.0,
|
||||||
|
default_value: 0.0,
|
||||||
|
controlType: ControlType.Vu,
|
||||||
|
is_bypass: false,
|
||||||
|
is_program_controller: false,
|
||||||
|
units: Units.db,
|
||||||
|
custom_units: "",
|
||||||
|
connection_optional: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
port_groups: [],
|
||||||
|
fileProperties: [],
|
||||||
|
frequencyPlots: [],
|
||||||
|
is_vst3: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -364,6 +364,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
|
|
||||||
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
|
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
|
||||||
static readonly END_CONTROL = -3; // synthetic PedalboardItem for output 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 START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
||||||
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import AudioFileMetadata from './AudioFileMetadata';
|
|||||||
import { pathFileName } from './FileUtils';
|
import { pathFileName } from './FileUtils';
|
||||||
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||||
import { getDefaultModGuiPreference } from './ModGuiHost';
|
import { getDefaultModGuiPreference } from './ModGuiHost';
|
||||||
|
import ChannelMixerSettings from './ChannelMixerSettings';
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
Loading,
|
Loading,
|
||||||
@@ -529,6 +530,13 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
jackServerSettings: ObservableProperty<JackServerSettings>
|
jackServerSettings: ObservableProperty<JackServerSettings>
|
||||||
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
|
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
|
||||||
|
|
||||||
|
channelMixerControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||||
|
channelMixerSettings: ObservableProperty<ChannelMixerSettings> = new ObservableProperty<ChannelMixerSettings>(new ChannelMixerSettings());
|
||||||
|
|
||||||
|
setChannelMixerSettings(settings: ChannelMixerSettings) {
|
||||||
|
this.channelMixerSettings.set(settings);
|
||||||
|
this.channelMixerControlValues.set(settings.controls);
|
||||||
|
}
|
||||||
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
|
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
|
||||||
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
|
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
|
||||||
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
|
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
|
||||||
@@ -1643,35 +1651,52 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
private lastControlMessageWasSentbyMe = false;
|
private lastControlMessageWasSentbyMe = false;
|
||||||
|
|
||||||
|
private _setChannelMixerControlValue(key: string, value: number, notifyServer: boolean) : void {
|
||||||
|
let channelMixerSettings = this.channelMixerSettings.get();
|
||||||
|
let changed = channelMixerSettings.setControlValue(key, value);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
this.channelMixerControlValues.set(this.channelMixerSettings.get().controls);
|
||||||
|
if (notifyServer) {
|
||||||
|
this.lastControlMessageWasSentbyMe = true;
|
||||||
|
this._setServerControl("setControl", Pedalboard.CHANNEL_MIXER_INSTANCE_ID, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
|
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
|
||||||
let pedalboard = this.pedalboard.get();
|
let pedalboard = this.pedalboard.get();
|
||||||
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
||||||
let newPedalboard = pedalboard.clone();
|
|
||||||
|
|
||||||
if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") {
|
if (instanceId == Pedalboard.CHANNEL_MIXER_INSTANCE_ID) {
|
||||||
this._setInputVolume(value, notifyServer);
|
this._setChannelMixerControlValue(key,value,notifyServer);
|
||||||
return;
|
return;
|
||||||
} else if (instanceId === Pedalboard.END_CONTROL) {
|
} else {
|
||||||
this._setOutputVolume(value, notifyServer);
|
let changed: boolean;
|
||||||
return;
|
let newPedalboard = pedalboard.clone();
|
||||||
}
|
|
||||||
let item = newPedalboard.getItem(instanceId);
|
|
||||||
let changed = item.setControlValue(key, value);
|
|
||||||
|
|
||||||
if (changed) {
|
if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") {
|
||||||
if (notifyServer) {
|
this._setInputVolume(value, notifyServer);
|
||||||
this.lastControlMessageWasSentbyMe = true;
|
return;
|
||||||
this._setServerControl("setControl", instanceId, key, value);
|
} else if (instanceId === Pedalboard.END_CONTROL) {
|
||||||
|
this._setOutputVolume(value, notifyServer);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.setModelPedalboard(newPedalboard);
|
let item = newPedalboard.getItem(instanceId);
|
||||||
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
changed = item.setControlValue(key, value);
|
||||||
let item = this._controlValueChangeItems[i];
|
if (changed) {
|
||||||
if (instanceId === item.instanceId) {
|
if (notifyServer) {
|
||||||
item.onValueChanged(key, value);
|
this.lastControlMessageWasSentbyMe = true;
|
||||||
|
this._setServerControl("setControl", instanceId, key, value);
|
||||||
|
}
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
|
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||||
|
let item = this._controlValueChangeItems[i];
|
||||||
|
if (instanceId === item.instanceId) {
|
||||||
|
item.onValueChanged(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
private _setVst3PedalboardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void {
|
private _setVst3PedalboardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void {
|
||||||
let pedalboard = this.pedalboard.get();
|
let pedalboard = this.pedalboard.get();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Robin Davies
|
// Copyright (c) 2026 Robin Davies
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
// 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
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
|
|
||||||
import React, { SyntheticEvent, Component } from 'react';
|
import React, { SyntheticEvent, Component } from 'react';
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
|
import ChannelMixerSettings from './ChannelMixerSettings';
|
||||||
|
import ChannelMixerSettingsDialog from './ChannelMixerSettingsDialog';
|
||||||
import OkCancelDialog from './OkCancelDialog';
|
import OkCancelDialog from './OkCancelDialog';
|
||||||
import RadioSelectDialog from './RadioSelectDialog';
|
import RadioSelectDialog from './RadioSelectDialog';
|
||||||
import IconButtonEx from './IconButtonEx';
|
import IconButtonEx from './IconButtonEx';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import {isDarkMode} from './DarkMode';
|
import { isDarkMode } from './DarkMode';
|
||||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||||
import { ColorTheme } from './DarkMode';
|
import { ColorTheme } from './DarkMode';
|
||||||
import ButtonBase from "@mui/material/ButtonBase";
|
import ButtonBase from "@mui/material/ButtonBase";
|
||||||
@@ -75,7 +77,8 @@ interface SettingsDialogState {
|
|||||||
showStatusMonitor: boolean;
|
showStatusMonitor: boolean;
|
||||||
showStatusMonitorDialog: boolean;
|
showStatusMonitorDialog: boolean;
|
||||||
jackConfiguration: JackConfiguration;
|
jackConfiguration: JackConfiguration;
|
||||||
jackSettings: JackChannelSelection;
|
jackSettings: JackChannelSelection | null;
|
||||||
|
channelMixerSettings: ChannelMixerSettings | null;
|
||||||
jackServerSettings: JackServerSettings;
|
jackServerSettings: JackServerSettings;
|
||||||
alsaSequencerConfiguration: AlsaSequencerConfiguration;
|
alsaSequencerConfiguration: AlsaSequencerConfiguration;
|
||||||
keepScreenOn: boolean;
|
keepScreenOn: boolean;
|
||||||
@@ -98,6 +101,7 @@ interface SettingsDialogState {
|
|||||||
showMidiSelectDialog: boolean;
|
showMidiSelectDialog: boolean;
|
||||||
showThemeSelectDialog: boolean;
|
showThemeSelectDialog: boolean;
|
||||||
showJackServerSettingsDialog: boolean;
|
showJackServerSettingsDialog: boolean;
|
||||||
|
showChannelMixerSettingsDialog: boolean;
|
||||||
shuttingDown: boolean;
|
shuttingDown: boolean;
|
||||||
restarting: boolean;
|
restarting: boolean;
|
||||||
isAndroidHosted: boolean;
|
isAndroidHosted: boolean;
|
||||||
@@ -190,6 +194,7 @@ const SettingsDialog = withStyles(
|
|||||||
showStatusMonitorDialog: false,
|
showStatusMonitorDialog: false,
|
||||||
|
|
||||||
jackServerSettings: this.model.jackServerSettings.get(),
|
jackServerSettings: this.model.jackServerSettings.get(),
|
||||||
|
channelMixerSettings: this.model.channelMixerSettings.get(),
|
||||||
jackConfiguration: this.model.jackConfiguration.get(),
|
jackConfiguration: this.model.jackConfiguration.get(),
|
||||||
jackStatus: undefined,
|
jackStatus: undefined,
|
||||||
jackSettings: this.model.jackSettings.get(),
|
jackSettings: this.model.jackSettings.get(),
|
||||||
@@ -210,6 +215,7 @@ const SettingsDialog = withStyles(
|
|||||||
showMidiSelectDialog: false,
|
showMidiSelectDialog: false,
|
||||||
showThemeSelectDialog: false,
|
showThemeSelectDialog: false,
|
||||||
showJackServerSettingsDialog: false,
|
showJackServerSettingsDialog: false,
|
||||||
|
showChannelMixerSettingsDialog: false,
|
||||||
shuttingDown: false,
|
shuttingDown: false,
|
||||||
restarting: false,
|
restarting: false,
|
||||||
showShutdownOkDialog: false,
|
showShutdownOkDialog: false,
|
||||||
@@ -222,6 +228,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
|
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
|
||||||
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
||||||
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
|
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
|
||||||
|
this.handleChannelMixerSettingsChanged = this.handleChannelMixerSettingsChanged.bind(this);
|
||||||
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
|
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
|
||||||
this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this);
|
this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this);
|
||||||
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
|
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
|
||||||
@@ -323,6 +330,12 @@ const SettingsDialog = withStyles(
|
|||||||
continueDisabled: !this.model.jackServerSettings.get().valid
|
continueDisabled: !this.model.jackServerSettings.get().valid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
handleChannelMixerSettingsChanged(): void {
|
||||||
|
this.setState({
|
||||||
|
channelMixerSettings: this.model.channelMixerSettings.get(),
|
||||||
|
continueDisabled: !this.model.channelMixerSettings.get().configured
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleJackConfigurationChanged(): void {
|
handleJackConfigurationChanged(): void {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -365,6 +378,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
|
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
|
||||||
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||||
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
|
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||||
|
this.model.channelMixerSettings.addOnChangedHandler(this.handleChannelMixerSettingsChanged);
|
||||||
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||||
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||||
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
|
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||||
@@ -387,6 +401,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.handleJackSettingsChanged();
|
this.handleJackSettingsChanged();
|
||||||
this.handleShowStatusMonitorChanged();
|
this.handleShowStatusMonitorChanged();
|
||||||
this.handleJackServerSettingsChanged();
|
this.handleJackServerSettingsChanged();
|
||||||
|
this.handleChannelMixerSettingsChanged();
|
||||||
this.handleWifiConfigSettingsChanged();
|
this.handleWifiConfigSettingsChanged();
|
||||||
this.handleWifiDirectConfigSettingsChanged();
|
this.handleWifiDirectConfigSettingsChanged();
|
||||||
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
|
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
|
||||||
@@ -405,6 +420,7 @@ const SettingsDialog = withStyles(
|
|||||||
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
|
||||||
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
|
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
|
||||||
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
|
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
|
||||||
|
this.model.channelMixerSettings.removeOnChangedHandler(this.handleChannelMixerSettingsChanged);
|
||||||
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||||
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||||
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
|
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||||
@@ -503,6 +519,7 @@ const SettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSelectChannelsDialogResult(channels: string[] | null): void {
|
handleSelectChannelsDialogResult(channels: string[] | null): void {
|
||||||
|
if (this.state.jackSettings === null) { return; }
|
||||||
if (channels) {
|
if (channels) {
|
||||||
let newSelection = this.state.jackSettings.clone();
|
let newSelection = this.state.jackSettings.clone();
|
||||||
if (this.state.showInputSelectDialog) {
|
if (this.state.showInputSelectDialog) {
|
||||||
@@ -595,10 +612,14 @@ const SettingsDialog = withStyles(
|
|||||||
const classes = withStyles.getClasses(this.props);
|
const classes = withStyles.getClasses(this.props);
|
||||||
|
|
||||||
let isConfigValid = this.state.jackConfiguration.isValid;
|
let isConfigValid = this.state.jackConfiguration.isValid;
|
||||||
let selectedChannels: string[] = this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts;
|
let selectedChannels: string[] =
|
||||||
|
this.state.jackSettings === null ? [] :
|
||||||
|
this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts;
|
||||||
let disableShutdown = this.state.shuttingDown || this.state.restarting;
|
let disableShutdown = this.state.shuttingDown || this.state.restarting;
|
||||||
|
|
||||||
let canKeepScreenOn = this.model.canKeepScreenOn;
|
let canKeepScreenOn = this.model.canKeepScreenOn;
|
||||||
|
let hasAudioConfig = isConfigValid && this.state.jackConfiguration.outputAudioPorts.length >= 1
|
||||||
|
&& this.state.jackConfiguration.inputAudioPorts.length >= 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogEx tag="settings" fullScreen open={this.props.open}
|
<DialogEx tag="settings" fullScreen open={this.props.open}
|
||||||
@@ -664,7 +685,7 @@ const SettingsDialog = withStyles(
|
|||||||
(
|
(
|
||||||
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||||
<Typography display="block" variant="caption" color="textSecondary">Status:
|
<Typography display="block" variant="caption" color="textSecondary">Status:
|
||||||
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
||||||
{(!this.props.onboarding) && (
|
{(!this.props.onboarding) && (
|
||||||
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
||||||
)}
|
)}
|
||||||
@@ -693,23 +714,51 @@ const SettingsDialog = withStyles(
|
|||||||
</Typography>
|
</Typography>
|
||||||
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
|
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
|
||||||
>
|
>
|
||||||
<SelectHoverBackground selected={false} showHover={true} />
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography display="block" variant="body2" noWrap>Audio device</Typography>
|
<Typography display="block" variant="body2" noWrap>Audio device</Typography>
|
||||||
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
|
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<JackServerSettingsDialog
|
{this.state.showChannelMixerSettingsDialog && (
|
||||||
open={this.state.showJackServerSettingsDialog}
|
<ChannelMixerSettingsDialog
|
||||||
jackServerSettings={this.state.jackServerSettings}
|
open={this.state.showChannelMixerSettingsDialog}
|
||||||
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
onClose={() => this.setState({ showChannelMixerSettingsDialog: false })}
|
||||||
onApply={(jackServerSettings) => {
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.showJackServerSettingsDialog && (
|
||||||
|
|
||||||
|
<JackServerSettingsDialog
|
||||||
|
open={this.state.showJackServerSettingsDialog}
|
||||||
|
jackServerSettings={this.state.jackServerSettings}
|
||||||
|
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
||||||
|
onApply={(jackServerSettings) => {
|
||||||
|
this.setState({
|
||||||
|
jackServerSettings: jackServerSettings
|
||||||
|
});
|
||||||
|
this.model.setJackServerSettings(jackServerSettings);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonBase className={classes.setting}
|
||||||
|
onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
jackServerSettings: jackServerSettings
|
showChannelMixerSettingsDialog: true
|
||||||
});
|
});
|
||||||
this.model.setJackServerSettings(jackServerSettings);
|
|
||||||
}}
|
}}
|
||||||
/>
|
disabled={!hasAudioConfig}
|
||||||
|
style={{ opacity: (!hasAudioConfig) ? 0.6 : 1.0 }}
|
||||||
|
>
|
||||||
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
|
<div style={{ width: "100%" }}>
|
||||||
|
<Typography display="block" variant="body2" noWrap>Channel Routing</Typography>
|
||||||
|
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
|
||||||
|
this.state.jackSettings == null ? "" :
|
||||||
|
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||||
|
</div>
|
||||||
|
</ButtonBase>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -721,7 +770,9 @@ const SettingsDialog = withStyles(
|
|||||||
<SelectHoverBackground selected={false} showHover={true} />
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography display="block" variant="body2" noWrap>Input channels</Typography>
|
<Typography display="block" variant="body2" noWrap>Input channels</Typography>
|
||||||
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
|
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
|
||||||
|
this.state.jackSettings == null ? "" :
|
||||||
|
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
||||||
@@ -731,7 +782,9 @@ const SettingsDialog = withStyles(
|
|||||||
<SelectHoverBackground selected={false} showHover={true} />
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography display="block" variant="body2" noWrap>Output channels</Typography>
|
<Typography display="block" variant="body2" noWrap>Output channels</Typography>
|
||||||
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
|
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
|
||||||
|
this.state.jackSettings == null ? "" :
|
||||||
|
this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
Reference in New Issue
Block a user