Merge branch 'dev_routing' of https://github.com/rerdavies/pipedal into dev_tone3000
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
|
||||
import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
|
||||
import JackServerSettings from "./JackServerSettings.tsx";
|
||||
import JackConfiguration from "./Jack.tsx";
|
||||
|
||||
|
||||
function isActiveChannel(channels: number[]): boolean {
|
||||
if (channels.length < 2) return false;
|
||||
return channels[1] >= 0 || channels[0] >= 0;
|
||||
}
|
||||
|
||||
function chName(ch: number): string {
|
||||
if (ch === -1) {
|
||||
return "None";
|
||||
}
|
||||
return "Ch " + (ch + 1);
|
||||
}
|
||||
function channelPairName(channels: number[], maxChannels: number): string {
|
||||
if (channels.length !== 2) {
|
||||
return "Invalid";
|
||||
}
|
||||
if (channels[0] === -1 && channels[1] === -1) {
|
||||
return "None";
|
||||
}
|
||||
|
||||
if (maxChannels === 2) {
|
||||
if (channels[0] === 0 && channels[1] === 1) {
|
||||
return "Stereo";
|
||||
}
|
||||
if (channels[0] === 0 && (channels[0] === channels[1] || channels[1] === -1)) {
|
||||
return "Left";
|
||||
}
|
||||
if (channels[1] === 1 && (channels[0] === channels[1])) {
|
||||
return "Right";
|
||||
}
|
||||
if (channels[0] === 1 && channels[1] === 0) {
|
||||
return "Right,Left";
|
||||
}
|
||||
}
|
||||
return chName(channels[0]) + "," + chName(channels[1]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
getDescription(jackConfiguration: JackConfiguration): string {
|
||||
if (!this.configured) {
|
||||
return "Not configured";
|
||||
}
|
||||
if (!jackConfiguration.isValid)
|
||||
{
|
||||
return "Not configured"
|
||||
}
|
||||
if (!this.isValid(jackConfiguration)) {
|
||||
return "Invalid configuration";
|
||||
}
|
||||
let nInputs = jackConfiguration.inputAudioPorts.length;
|
||||
let nOutputs = jackConfiguration.outputAudioPorts.length;
|
||||
let description = channelPairName(this.mainInputChannels, nInputs) + " -> " +
|
||||
channelPairName(this.mainOutputChannels, nOutputs);
|
||||
if (isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels))
|
||||
{
|
||||
description += " " + "+ re-amp: " + channelPairName(this.auxInputChannels, nInputs) + " -> " +
|
||||
channelPairName(this.auxOutputChannels, nOutputs);
|
||||
}
|
||||
if (isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels)) {
|
||||
description += " " + "+ send: " +channelPairName(this.sendOutputChannels, nOutputs)
|
||||
+ " -> " + channelPairName(this.sendInputChannels, nInputs);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
canEdit(jackConfiguration: JackConfiguration): boolean {
|
||||
return jackConfiguration.isValid;
|
||||
}
|
||||
|
||||
isValid(jackConfiguration: JackConfiguration): boolean {
|
||||
if (!this.configured) {
|
||||
return false;
|
||||
}
|
||||
let maxInputChannels = jackConfiguration.inputAudioPorts.length;
|
||||
let maxOutputChannels = jackConfiguration.outputAudioPorts.length;
|
||||
|
||||
let hasInput = false;
|
||||
let hasOutput = false;
|
||||
for (let ch of this.mainInputChannels) {
|
||||
if (ch >= 0) {
|
||||
hasInput = true;
|
||||
if (ch >= maxInputChannels) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasInput) {
|
||||
return false;
|
||||
}
|
||||
for (let ch of this.mainOutputChannels) {
|
||||
if (ch >= 0) {
|
||||
hasOutput = true;
|
||||
if (ch >= maxOutputChannels) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasOutput) {
|
||||
return false;
|
||||
}
|
||||
for (let ch of this.auxInputChannels) {
|
||||
if (ch >= maxInputChannels) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (let ch of this.auxOutputChannels) {
|
||||
if (ch >= maxOutputChannels) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (let ch of this.sendInputChannels) {
|
||||
if (ch >= maxInputChannels) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,868 @@
|
||||
// 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';
|
||||
|
||||
|
||||
let debugInputChannels: number | null = 2;
|
||||
let debugOutputChannels: number | null = 4;
|
||||
|
||||
|
||||
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;
|
||||
if (input && debugInputChannels != null) {
|
||||
channelCount = debugInputChannels;
|
||||
}
|
||||
if (!input && debugOutputChannels != null) {
|
||||
channelCount = debugOutputChannels;
|
||||
}
|
||||
|
||||
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 = new ChannelRouterSettings().deserialize(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) {
|
||||
let newPreset = Object.assign(new ChannelRouterSettings(), preset.settings);
|
||||
// mark the presets as inserts.
|
||||
newPreset.mainInserts.nextInstanceId = Pedalboard.MAIN_INSERT_INSTANCE_BASE;
|
||||
newPreset.auxInserts.nextInstanceId = Pedalboard.AUX_INSERT_INSTANCE_BASE;
|
||||
model.setChannelRouterSettings(newPreset);
|
||||
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, true, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="channelRouterSettings"
|
||||
onClose={handleClose}
|
||||
aria-labelledby="select-channel_mixer_settings"
|
||||
open={open}
|
||||
fullWidth
|
||||
maxWidth={landscape ? "sm" : "xs"}
|
||||
onEnterKey={handleClose}
|
||||
fullScreen={fullScreen}
|
||||
>
|
||||
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 0 }}>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", flexWrap: "nowrap", alignItems: "center",
|
||||
marginTop: 8
|
||||
|
||||
}}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{}}>
|
||||
Channel Routing
|
||||
</Typography>
|
||||
{
|
||||
landscape ? (
|
||||
<>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
<div style={{ flexShrink: 1, maxWidth: 440, display: "relative", marginLeft: 16, marginRight: 16 }}>
|
||||
{PresetSelect(200)}
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
</>
|
||||
|
||||
) : (
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
)
|
||||
}
|
||||
|
||||
<IconButtonEx tooltip="Help"
|
||||
edge="end"
|
||||
aria-label="help"
|
||||
onClick={() => { setShowHelp(true); }}
|
||||
>
|
||||
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
</div>
|
||||
</DialogTitle>
|
||||
{!landscape && (
|
||||
<>
|
||||
<div style={{ marginLeft: 16 + 32 + 8, marginBottom: 8 }}>
|
||||
{PresetSelect("60%")}
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DialogContent>
|
||||
{landscape ?
|
||||
LandscapeView()
|
||||
:
|
||||
PortraitView()
|
||||
}
|
||||
</DialogContent>
|
||||
{
|
||||
showHelp && (
|
||||
<ChannelRouterSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
|
||||
)
|
||||
}
|
||||
</DialogEx >
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelRouterSettingsDialog;
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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 DialogEx from './DialogEx';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
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 ChannelRouterSettingsHelpDialogProps {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
export interface ChannelRouterSettingsHelpDialogState {
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveComponent<ChannelRouterSettingsHelpDialogProps, ChannelRouterSettingsHelpDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
constructor(props: ChannelRouterSettingsHelpDialogProps) {
|
||||
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>
|
||||
<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>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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 ChannelRouterUiUri = "uri://two-play/pipedal/ppChannelRouter";
|
||||
|
||||
export function makeChanelMixerUiPlugin(): UiPlugin {
|
||||
|
||||
return new UiPlugin().deserialize({
|
||||
uri: ChannelRouterUiUri,
|
||||
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,
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1120,6 +1120,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export function makeSplitUiPlugin(): UiPlugin {
|
||||
|
||||
return new UiPlugin().deserialize({
|
||||
@@ -1138,7 +1140,7 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
has_midi_output: 0,
|
||||
description: "",
|
||||
controls: [
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "splitType",
|
||||
name: "Type",
|
||||
index: 0,
|
||||
@@ -1159,7 +1161,7 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
connection_optional: false,
|
||||
|
||||
}),
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "select",
|
||||
name: "Select",
|
||||
index: 1,
|
||||
@@ -1182,7 +1184,7 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
|
||||
}),
|
||||
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "mix",
|
||||
name: "Mix",
|
||||
index: 2,
|
||||
@@ -1195,9 +1197,10 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
is_program_controller: false,
|
||||
custom_units: "",
|
||||
connection_optional: false,
|
||||
scale_points: [],
|
||||
|
||||
}),
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "panL",
|
||||
name: "Pan Top",
|
||||
index: 3,
|
||||
@@ -1210,10 +1213,12 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
is_program_controller: false,
|
||||
custom_units: "",
|
||||
connection_optional: false,
|
||||
scale_points: [],
|
||||
|
||||
}),
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "volL",
|
||||
name: "Vol Top",
|
||||
index: 4,
|
||||
is_input: true,
|
||||
min_value: -60.0,
|
||||
@@ -1224,9 +1229,10 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
is_program_controller: false,
|
||||
custom_units: "",
|
||||
connection_optional: false,
|
||||
scale_points: [],
|
||||
|
||||
}),
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "panR",
|
||||
name: "Pan Bottom",
|
||||
index: 5,
|
||||
@@ -1239,9 +1245,10 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
is_program_controller: false,
|
||||
custom_units: "",
|
||||
connection_optional: false,
|
||||
scale_points: [],
|
||||
|
||||
}),
|
||||
new UiControl().deserialize({
|
||||
new UiControl().applyProperties({
|
||||
symbol: "volR",
|
||||
name: "Vol Bottom",
|
||||
index: 6,
|
||||
@@ -1254,6 +1261,8 @@ export function makeSplitUiPlugin(): UiPlugin {
|
||||
is_program_controller: false,
|
||||
custom_units: "",
|
||||
connection_optional: false,
|
||||
scale_points: [],
|
||||
|
||||
})
|
||||
],
|
||||
port_groups: [],
|
||||
|
||||
@@ -357,13 +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 MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID;
|
||||
static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
|
||||
static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID
|
||||
static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
|
||||
|
||||
|
||||
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
||||
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
||||
|
||||
@@ -372,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;
|
||||
@@ -380,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);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import AudioFileMetadata from './AudioFileMetadata';
|
||||
import { pathFileName } from './FileUtils';
|
||||
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||
import { getDefaultModGuiPreference } from './ModGuiHost';
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
import Tone3000DownloadProgress from './Tone3000DownloadProgress';
|
||||
|
||||
export enum State {
|
||||
@@ -433,9 +434,9 @@ interface MonitorPortOutputBody {
|
||||
}
|
||||
|
||||
|
||||
interface ChannelSelectionChangedBody {
|
||||
interface ChannelRouterSettingsChangedBody {
|
||||
clientId: number;
|
||||
jackChannelSelection: JackChannelSelection;
|
||||
channelRouterSettings: ChannelRouterSettings;
|
||||
}
|
||||
interface RenamePresetBody {
|
||||
clientId: number;
|
||||
@@ -533,6 +534,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
jackServerSettings: ObservableProperty<JackServerSettings>
|
||||
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
|
||||
|
||||
channelRouterControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||
channelRouterSettings: ObservableProperty<ChannelRouterSettings> = new ObservableProperty<ChannelRouterSettings>(new ChannelRouterSettings());
|
||||
|
||||
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
|
||||
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
|
||||
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
|
||||
@@ -817,10 +821,11 @@ export class PiPedalModel //implements PiPedalModel
|
||||
} else if (message === "onShowStatusMonitorChanged") {
|
||||
let value = body as boolean;
|
||||
this.showStatusMonitor.set(value);
|
||||
} else if (message === "onChannelSelectionChanged") {
|
||||
let channelSelectionBody = body as ChannelSelectionChangedBody;
|
||||
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection);
|
||||
this.jackSettings.set(channelSelection);
|
||||
} else if (message === "onChannelRouterSettingsChanged") {
|
||||
let channelRouterSettingChangedBody = body as ChannelRouterSettingsChangedBody;
|
||||
let channelRouterSettings = new ChannelRouterSettings().deserialize(
|
||||
channelRouterSettingChangedBody.channelRouterSettings);
|
||||
this.channelRouterSettings.set(channelRouterSettings);
|
||||
} else if (message === "onSnapshotModified") {
|
||||
let { snapshotIndex, modified } = (body as { snapshotIndex: number, modified: boolean });
|
||||
let snapshots = this.pedalboard.get().snapshots;
|
||||
@@ -1385,6 +1390,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")
|
||||
));
|
||||
@@ -1778,35 +1788,52 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
private lastControlMessageWasSentbyMe = false;
|
||||
|
||||
private _setChannelRouterControlValue(key: string, value: number, notifyServer: boolean) : void {
|
||||
let channelRouterSettings = this.channelRouterSettings.get();
|
||||
let changed = channelRouterSettings.setControlValue(key, value);
|
||||
if (changed)
|
||||
{
|
||||
this.channelRouterControlValues.set(this.channelRouterSettings.get().controlValues);
|
||||
if (notifyServer) {
|
||||
this.lastControlMessageWasSentbyMe = true;
|
||||
this._setServerControl("setControl", Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
||||
let newPedalboard = pedalboard.clone();
|
||||
|
||||
if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") {
|
||||
this._setInputVolume(value, notifyServer);
|
||||
if (instanceId == Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID) {
|
||||
this._setChannelRouterControlValue(key,value,notifyServer);
|
||||
return;
|
||||
} else if (instanceId === Pedalboard.END_CONTROL) {
|
||||
this._setOutputVolume(value, notifyServer);
|
||||
return;
|
||||
}
|
||||
let item = newPedalboard.getItem(instanceId);
|
||||
let changed = item.setControlValue(key, value);
|
||||
} else {
|
||||
let changed: boolean;
|
||||
let newPedalboard = pedalboard.clone();
|
||||
|
||||
if (changed) {
|
||||
if (notifyServer) {
|
||||
this.lastControlMessageWasSentbyMe = true;
|
||||
this._setServerControl("setControl", instanceId, key, value);
|
||||
if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") {
|
||||
this._setInputVolume(value, notifyServer);
|
||||
return;
|
||||
} else if (instanceId === Pedalboard.END_CONTROL) {
|
||||
this._setOutputVolume(value, notifyServer);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
let item = newPedalboard.getItem(instanceId);
|
||||
changed = item.setControlValue(key, value);
|
||||
if (changed) {
|
||||
if (notifyServer) {
|
||||
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 {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
@@ -2426,12 +2453,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 {
|
||||
@@ -3420,7 +3441,6 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (this.webSocket) {
|
||||
this.webSocket.send("setFavorites", newFavorites);
|
||||
}
|
||||
// stub: update server.
|
||||
}
|
||||
|
||||
|
||||
@@ -3790,6 +3810,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;
|
||||
|
||||
@@ -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
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
|
||||
import React, { SyntheticEvent, Component } from 'react';
|
||||
import Switch from "@mui/material/Switch";
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
import ChannelRouterSettingsDialog from './ChannelRouterSettingsDialog';
|
||||
import OkCancelDialog from './OkCancelDialog';
|
||||
import RadioSelectDialog from './RadioSelectDialog';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||
import { ColorTheme } from './DarkMode';
|
||||
import ButtonBase from "@mui/material/ButtonBase";
|
||||
@@ -75,7 +77,8 @@ interface SettingsDialogState {
|
||||
showStatusMonitor: boolean;
|
||||
showStatusMonitorDialog: boolean;
|
||||
jackConfiguration: JackConfiguration;
|
||||
jackSettings: JackChannelSelection;
|
||||
jackSettings: JackChannelSelection | null;
|
||||
channelRouterSettings: ChannelRouterSettings | null;
|
||||
jackServerSettings: JackServerSettings;
|
||||
alsaSequencerConfiguration: AlsaSequencerConfiguration;
|
||||
keepScreenOn: boolean;
|
||||
@@ -98,6 +101,7 @@ interface SettingsDialogState {
|
||||
showMidiSelectDialog: boolean;
|
||||
showThemeSelectDialog: boolean;
|
||||
showJackServerSettingsDialog: boolean;
|
||||
showChannelRouterSettingsDialog: boolean;
|
||||
shuttingDown: boolean;
|
||||
restarting: boolean;
|
||||
isAndroidHosted: boolean;
|
||||
@@ -190,6 +194,7 @@ const SettingsDialog = withStyles(
|
||||
showStatusMonitorDialog: false,
|
||||
|
||||
jackServerSettings: this.model.jackServerSettings.get(),
|
||||
channelRouterSettings: this.model.channelRouterSettings.get(),
|
||||
jackConfiguration: this.model.jackConfiguration.get(),
|
||||
jackStatus: undefined,
|
||||
jackSettings: this.model.jackSettings.get(),
|
||||
@@ -210,6 +215,7 @@ const SettingsDialog = withStyles(
|
||||
showMidiSelectDialog: false,
|
||||
showThemeSelectDialog: false,
|
||||
showJackServerSettingsDialog: false,
|
||||
showChannelRouterSettingsDialog: false,
|
||||
shuttingDown: false,
|
||||
restarting: false,
|
||||
showShutdownOkDialog: false,
|
||||
@@ -222,6 +228,7 @@ const SettingsDialog = withStyles(
|
||||
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
|
||||
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
|
||||
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.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);
|
||||
@@ -323,6 +330,12 @@ const SettingsDialog = withStyles(
|
||||
continueDisabled: !this.model.jackServerSettings.get().valid
|
||||
});
|
||||
}
|
||||
handleChannelRouterSettingsChanged(): void {
|
||||
this.setState({
|
||||
channelRouterSettings: this.model.channelRouterSettings.get(),
|
||||
continueDisabled: !this.model.channelRouterSettings.get().configured
|
||||
});
|
||||
}
|
||||
|
||||
handleJackConfigurationChanged(): void {
|
||||
this.setState({
|
||||
@@ -365,6 +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.channelRouterSettings.addOnChangedHandler(this.handleChannelRouterSettingsChanged);
|
||||
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||
@@ -387,6 +401,7 @@ const SettingsDialog = withStyles(
|
||||
this.handleJackSettingsChanged();
|
||||
this.handleShowStatusMonitorChanged();
|
||||
this.handleJackServerSettingsChanged();
|
||||
this.handleChannelRouterSettingsChanged();
|
||||
this.handleWifiConfigSettingsChanged();
|
||||
this.handleWifiDirectConfigSettingsChanged();
|
||||
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
|
||||
@@ -405,6 +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.channelRouterSettings.removeOnChangedHandler(this.handleChannelRouterSettingsChanged);
|
||||
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
|
||||
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
|
||||
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
|
||||
@@ -503,28 +519,10 @@ const SettingsDialog = withStyles(
|
||||
}
|
||||
|
||||
handleSelectChannelsDialogResult(channels: string[] | null): void {
|
||||
if (channels) {
|
||||
let newSelection = this.state.jackSettings.clone();
|
||||
if (this.state.showInputSelectDialog) {
|
||||
newSelection.inputAudioPorts = channels;
|
||||
} else {
|
||||
newSelection.outputAudioPorts = channels;
|
||||
}
|
||||
this.setState({
|
||||
jackSettings: newSelection,
|
||||
showInputSelectDialog: false,
|
||||
showOutputSelectDialog: false
|
||||
|
||||
});
|
||||
this.model.setJackSettings(newSelection);
|
||||
} else {
|
||||
this.setState({
|
||||
showInputSelectDialog: false,
|
||||
showOutputSelectDialog: false
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
this.setState({
|
||||
showInputSelectDialog: false,
|
||||
showOutputSelectDialog: false
|
||||
});
|
||||
}
|
||||
|
||||
midiSummary(): string {
|
||||
@@ -595,10 +593,16 @@ const SettingsDialog = withStyles(
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
|
||||
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 canKeepScreenOn = this.model.canKeepScreenOn;
|
||||
let hasAudioConfig = isConfigValid
|
||||
&& this.state.jackConfiguration.inputAudioPorts.length >= 1
|
||||
&& this.state.jackConfiguration.outputAudioPorts.length >= 1;
|
||||
|
||||
|
||||
return (
|
||||
<DialogEx tag="settings" fullScreen open={this.props.open}
|
||||
@@ -663,8 +667,8 @@ const SettingsDialog = withStyles(
|
||||
{(!isConfigValid) ?
|
||||
(
|
||||
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status:
|
||||
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status:
|
||||
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
||||
{(!this.props.onboarding) && (
|
||||
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
||||
)}
|
||||
@@ -693,47 +697,82 @@ const SettingsDialog = withStyles(
|
||||
</Typography>
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography display="block" variant="body2" noWrap>Audio device</Typography>
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
<JackServerSettingsDialog
|
||||
open={this.state.showJackServerSettingsDialog}
|
||||
jackServerSettings={this.state.jackServerSettings}
|
||||
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
||||
onApply={(jackServerSettings) => {
|
||||
{this.state.showChannelRouterSettingsDialog && (
|
||||
<ChannelRouterSettingsDialog
|
||||
open={this.state.showChannelRouterSettingsDialog}
|
||||
onClose={() => this.setState({ showChannelRouterSettingsDialog: false })}
|
||||
/>
|
||||
)}
|
||||
{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({
|
||||
jackServerSettings: jackServerSettings
|
||||
showChannelRouterSettingsDialog: true
|
||||
});
|
||||
this.model.setJackServerSettings(jackServerSettings);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
|
||||
disabled={!hasAudioConfig}
|
||||
style={{ opacity: (!hasAudioConfig) ? 0.6 : 1.0 }}
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<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>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<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="body2" noWrap>Channel Routing</Typography>
|
||||
<Typography display="block" variant="caption"
|
||||
color={
|
||||
this.state.channelRouterSettings?.isValid(this.state.jackConfiguration) ?? false
|
||||
? "textSecondary": "error"} noWrap>{
|
||||
this.state.channelRouterSettings?.getDescription(this.state.jackConfiguration)??""}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
|
||||
|
||||
{/* Old Input and Output selection
|
||||
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography display="block" variant="body2" noWrap>Input channels</Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
|
||||
this.state.jackSettings == null ? "" :
|
||||
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
|
||||
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
|
||||
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
<Typography display="block" variant="body2" noWrap>Output channels</Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
|
||||
this.state.jackSettings == null ? "" :
|
||||
this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
*/}
|
||||
<Divider />
|
||||
<div >
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">MIDI</Typography>
|
||||
|
||||
@@ -372,7 +372,7 @@ const ToobParametricEqView =
|
||||
])}
|
||||
{panel("", [
|
||||
controls.gain
|
||||
])}
|
||||
])}
|
||||
{divider()}
|
||||
{panel("Low",
|
||||
[controls.low_level,
|
||||
|
||||
Reference in New Issue
Block a user