MIDI plugins -- partial implemetnation, temporarily disabled.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
// 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 TextFieldEx from './TextFieldEx';
|
||||
|
||||
|
||||
export interface ChannelBindingHelpDialogProps {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
export interface ChannelBindingHelpDialogState {
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
export default class ChannelBindingHelpDialog extends ResizeResponsiveComponent<ChannelBindingHelpDialogProps, ChannelBindingHelpDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement>;
|
||||
|
||||
constructor(props: ChannelBindingHelpDialogProps) {
|
||||
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="Rename-dialog-title"
|
||||
style={{userSelect: "none"}}
|
||||
fullwidth
|
||||
onEnterKey={()=>{}}
|
||||
>
|
||||
<DialogContent style={{minHeight: 96}}>
|
||||
<Typography variant="h5" style={{marginBottom: 8}}>
|
||||
MIDI Control Binding Priority
|
||||
</Typography>
|
||||
<div style={{fontSize: "0.9em", lineHeight: "18pt" }}>
|
||||
<p style={{lineHeight: "1.4em"}}>MIDI Channel Filters determine which MIDI messages get sent to a plugin that accepts MIDI messages. Note that the
|
||||
MIDI Channel Filters do NOT affect system MIDI bindings or control bindings.
|
||||
</p>
|
||||
<p>
|
||||
MIDI message processing occurs in the following order
|
||||
</p>
|
||||
<ul>
|
||||
<li>If the message is a Program Change message, the message is first offered to any MIDI plugins in the currently loaded preset (Message Filters permitting).
|
||||
The message is sent to each MIDI plugin in the current preset that wants it. If any MIDI plugin accepts the program change,
|
||||
no futher processing occurs. Specifically, the program change will not change the currently selected PiPedal preset.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
If the message is a Program Change message, and no MIDI plugin has accepted the message, PiPedal selects the PiPedal preset that corresponds
|
||||
to the requested program.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
The message is then checked to see if it has been bound to a Pipedal feature using the System Midi Bindings settings. If it has,
|
||||
Pipedal processes the message, and it is not forwarded to MIDI plugins.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, the message is sent to each MIDI plugin in the currently loaded Pipedal preset (channel filters permitting).)
|
||||
<br/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
<Divider/>
|
||||
<DialogActions style={{flexShrink: 1}}>
|
||||
<Button onClick={handleClose} variant="dialogPrimary" >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2024 Robin E. R. 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.
|
||||
|
||||
|
||||
export enum MidiDeviceSelection {
|
||||
DeviceAny = 0,
|
||||
DeviceNone = 1,
|
||||
DeviceList = 2
|
||||
}
|
||||
|
||||
export default class MidiChannelBinding {
|
||||
deserialize(input: any) : MidiChannelBinding {
|
||||
this.deviceSelection = input.deviceSelection;
|
||||
this.midiDevices = input.midiDevices;
|
||||
|
||||
this.channel = input.channel;
|
||||
this.acceptProgramChanges = input.acceptProgramChanges;
|
||||
this.acceptCommonMessages = input.acceptCommonMessages;
|
||||
return this;
|
||||
}
|
||||
|
||||
clone() { return new MidiChannelBinding().deserialize(this);}
|
||||
|
||||
deviceSelection: number = MidiDeviceSelection.DeviceAny as number;
|
||||
midiDevices: string[] = [];
|
||||
channel: number = -1;
|
||||
acceptProgramChanges: boolean = true;
|
||||
acceptCommonMessages: boolean = true;
|
||||
|
||||
|
||||
static CreateMissingValue() : MidiChannelBinding {
|
||||
let result = new MidiChannelBinding();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2024 Robin E. R. 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 { Component } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { pluginControlStyles } from './PluginControl';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import { ReactComponent as MidiIcon } from "./svg/ic_midi.svg";
|
||||
import MidiChannelBindingDialog from './MidiChannelBindingDialog';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
|
||||
|
||||
export interface MidiChannelBindingControlProps extends WithStyles<typeof pluginControlStyles> {
|
||||
midiChannelBinding: MidiChannelBinding
|
||||
theme: Theme;
|
||||
onChange: (result: MidiChannelBinding) => void;
|
||||
}
|
||||
type MidiChannelBindingControlState = {
|
||||
dialogOpen: boolean;
|
||||
};
|
||||
|
||||
const MidiChannelBindingControl =
|
||||
withStyles(pluginControlStyles, { withTheme: true })(
|
||||
class extends Component<MidiChannelBindingControlProps, MidiChannelBindingControlState> {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: MidiChannelBindingControlProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dialogOpen: false
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let item_width = 80;
|
||||
return (
|
||||
<div
|
||||
className={classes.controlFrame}
|
||||
style={{ width: item_width }}
|
||||
>
|
||||
{/* TITLE SECTION */}
|
||||
<div className={this.props.classes.titleSection}
|
||||
style={
|
||||
{
|
||||
alignSelf: "stretch"
|
||||
|
||||
}}>
|
||||
<Tooltip title={(
|
||||
<React.Fragment>
|
||||
<Typography variant="caption">title</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption">Controls which MIDI messages get forward to the instrument.</Typography>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
placement="top-start" arrow enterDelay={1500} enterNextDelay={1500}
|
||||
>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%",
|
||||
textAlign: "center"
|
||||
}}> MIDI</Typography>
|
||||
</Tooltip>
|
||||
|
||||
</div>
|
||||
{/* CONTROL SECTION */}
|
||||
|
||||
<div className={this.props.classes.midSection}>
|
||||
<ButtonBase style={{ width: 48, height: 48, borderRadius: 9999 }}
|
||||
onClick={()=> {
|
||||
this.setState({dialogOpen: true});
|
||||
}}
|
||||
sx={{
|
||||
':hover': {
|
||||
bgcolor: 'action.hover', // theme.palette.primary.main
|
||||
color: 'white',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MidiIcon fill={this.props.theme.palette.text.secondary} style={{ width: 36, height: 36 }} />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
<div className={this.props.classes.editSection}
|
||||
style={{ display: "flex", flexFlow: "column nowrap", justifyContent: "center" }}
|
||||
>
|
||||
<Typography variant="caption" display="block" noWrap
|
||||
style={{ width: "100%", textAlign: "center" }}
|
||||
>OMNI</Typography>
|
||||
</div>
|
||||
{this.state.dialogOpen&&(
|
||||
<MidiChannelBindingDialog
|
||||
open={this.state.dialogOpen}
|
||||
midiChannelBinding={this.props.midiChannelBinding}
|
||||
onClose={() =>{ this.setState({dialogOpen: false});}}
|
||||
onChanged={(midiChannelBinding: MidiChannelBinding)=> {}}
|
||||
midiDevices={[]}
|
||||
|
||||
/>
|
||||
|
||||
)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default MidiChannelBindingControl;
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// Copyright (c) 2024 Robin E. R. 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 { useState } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import ChannelBindingHelpDialog from './ChannelBindingsHelpDialog';
|
||||
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo';
|
||||
import DialogEx from './DialogEx';
|
||||
import MidiChannelBinding, { MidiDeviceSelection } from './MidiChannelBinding';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { styled, Theme } from '@mui/material/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
|
||||
|
||||
export interface MidiChannelBindingDialogProps {
|
||||
open: boolean;
|
||||
midiChannelBinding: MidiChannelBinding;
|
||||
onClose: () => void;
|
||||
onChanged: (midiChannelBinding: MidiChannelBinding) => void;
|
||||
midiDevices: AlsaMidiDeviceInfo[];
|
||||
}
|
||||
|
||||
|
||||
function isChecked(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo): boolean {
|
||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||
if (selectedChannels[i].equals(channel)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function addPort(availableChannels: AlsaMidiDeviceInfo[], selectedChannels: AlsaMidiDeviceInfo[], newChannel: AlsaMidiDeviceInfo)
|
||||
: AlsaMidiDeviceInfo[] {
|
||||
let result: AlsaMidiDeviceInfo[] = [];
|
||||
for (let i = 0; i < availableChannels.length; ++i) {
|
||||
let channel = availableChannels[i];
|
||||
if (isChecked(selectedChannels, channel) || channel.equals(newChannel)) {
|
||||
result.push(channel);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function removePort(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo)
|
||||
: AlsaMidiDeviceInfo[] {
|
||||
let result: AlsaMidiDeviceInfo[] = [];
|
||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||
if (!selectedChannels[i].equals(channel)) {
|
||||
result.push(selectedChannels[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const usesStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
icon: {
|
||||
fill: theme.palette.text.primary,
|
||||
opacity: 0.6
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
function MidiChannelBindingDialog(props: MidiChannelBindingDialogProps) {
|
||||
const classes = usesStyles();
|
||||
const { onClose, midiChannelBinding, open, onChanged } = props;
|
||||
|
||||
const [currentChannel, setCurrentChannel] = useState(midiChannelBinding.channel);
|
||||
const [allowProgramChanges, setAllowProgramChanges] = useState(midiChannelBinding.acceptCommonMessages);
|
||||
const [helpDialog, setHelpDialog] = useState(false);
|
||||
|
||||
const model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||
|
||||
const handleClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
const handleOk = (): void => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const generateDeviceSelect = () => {
|
||||
let result: React.ReactElement[] = [
|
||||
(<MenuItem key={0} value={0}>Any</MenuItem>),
|
||||
(<MenuItem key={1} value={1}>None</MenuItem>)
|
||||
];
|
||||
let i = 2;
|
||||
for (let midiDevice of model.jackConfiguration.get().inputMidiDevices) {
|
||||
result.push((<MenuItem key={i} value={i}>{midiDevice.description}</MenuItem>));
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getDefaultDeviceSelect = (): number => {
|
||||
if (midiChannelBinding.deviceSelection === MidiDeviceSelection.DeviceAny as number) {
|
||||
return 0;
|
||||
}
|
||||
if (midiChannelBinding.deviceSelection === MidiDeviceSelection.DeviceNone as number) {
|
||||
return 1;
|
||||
}
|
||||
if (midiChannelBinding.midiDevices.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
let selectedDevice = midiChannelBinding.midiDevices[0];
|
||||
|
||||
let ix = 2;
|
||||
for (let midiDevice of model.jackConfiguration.get().inputMidiDevices) {
|
||||
if (midiDevice.name === selectedDevice) {
|
||||
return ix;
|
||||
}
|
||||
++ix;
|
||||
}
|
||||
return 0; // any.
|
||||
}
|
||||
|
||||
const [currentDeviceSelect, setCurrentDeviceSelect] = useState(getDefaultDeviceSelect())
|
||||
|
||||
|
||||
const handleDeviceSelecChange = (event: SelectChangeEvent) => {
|
||||
setCurrentDeviceSelect(parseInt(event.target.value));
|
||||
};
|
||||
const handleChannelChange = (event: SelectChangeEvent) => {
|
||||
setCurrentChannel(parseInt(event.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="midiChannelBinding" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
||||
onEnterKey={()=>{}} fullWidth maxWidth="xs"
|
||||
>
|
||||
<DialogTitle id="simple-dialog-title">MIDI Channel Filters</DialogTitle>
|
||||
<DialogContent>
|
||||
<div style={{
|
||||
display: "grid", alignItems: "center", columnGap: 24, rowGap: 24,
|
||||
gridTemplateColumns: "1fr"
|
||||
}}>
|
||||
<div >
|
||||
<Typography display="block" variant="caption">MIDI Devices</Typography>
|
||||
<Select variant='standard' value={currentDeviceSelect.toString()} fullWidth
|
||||
onChange={handleDeviceSelecChange} >
|
||||
{
|
||||
generateDeviceSelect()
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography display="block" variant="caption">Channels</Typography>
|
||||
<Select variant='standard' label="Channels" value={currentChannel.toString()} fullWidth
|
||||
onChange={handleChannelChange}>
|
||||
<MenuItem value={-1}>Omni</MenuItem>
|
||||
<MenuItem value={0}>Channel 1</MenuItem>
|
||||
<MenuItem value={1}>Channel 2</MenuItem>
|
||||
<MenuItem value={2}>Channel 3</MenuItem>
|
||||
<MenuItem value={3}>Channel 4</MenuItem>
|
||||
<MenuItem value={4}>Channel 5</MenuItem>
|
||||
<MenuItem value={5}>Channel 6</MenuItem>
|
||||
<MenuItem value={6}>Channel 7</MenuItem>
|
||||
<MenuItem value={7}>Channel 8</MenuItem>
|
||||
<MenuItem value={8}>Channel 9</MenuItem>
|
||||
<MenuItem value={9}>Channel 10</MenuItem>
|
||||
<MenuItem value={10}>Channel 11</MenuItem>
|
||||
<MenuItem value={11}>Channel 12</MenuItem>
|
||||
<MenuItem value={12}>Channel 13</MenuItem>
|
||||
<MenuItem value={13}>Channel 14</MenuItem>
|
||||
<MenuItem value={14}>Channel 15</MenuItem>
|
||||
<MenuItem value={15}>Channel 16</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "center" }}>
|
||||
<FormControlLabel control={
|
||||
<Checkbox checked={allowProgramChanges}
|
||||
onChange={(event) => {
|
||||
setAllowProgramChanges(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
} label="Allow Program Changes"
|
||||
/>
|
||||
{false&&( // wait until the implementation stabilizes before exposing the help dialog.
|
||||
<IconButton
|
||||
onClick={() => { setHelpDialog(true); }}
|
||||
size="large">
|
||||
<InfoOutlinedIcon color='inherit'
|
||||
className={classes.icon}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleOk} variant="dialogPrimary" >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
<ChannelBindingHelpDialog open={helpDialog} onClose={()=>setHelpDialog(false)}
|
||||
/>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
|
||||
export default MidiChannelBindingDialog;
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { PiPedalArgumentError } from './PiPedalError';
|
||||
import MidiBinding from './MidiBinding';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
|
||||
|
||||
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
||||
@@ -64,6 +65,12 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
this.pluginName = input.pluginName;
|
||||
this.isEnabled = input.isEnabled;
|
||||
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
|
||||
if (input.midiChannelBinding)
|
||||
{
|
||||
this.midiChannelBinding = input.midiChannelBinding;
|
||||
} else {
|
||||
this.midiChannelBinding = null;
|
||||
}
|
||||
|
||||
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||
this.vstState = input.vstState ?? "";
|
||||
@@ -197,6 +204,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
pluginName?: string;
|
||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||
midiBindings: MidiBinding[] = [];
|
||||
midiChannelBinding: MidiChannelBinding | null = null;
|
||||
vstState: string = "";
|
||||
stateUpdateCount: number = 0;
|
||||
lv2State: [boolean,any] = [false,{}];
|
||||
|
||||
@@ -52,7 +52,7 @@ export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
export const pluginControlStyles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
position: "relative",
|
||||
margin: "12px"
|
||||
@@ -90,13 +90,13 @@ const styles = (theme: Theme) => createStyles({
|
||||
flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center"
|
||||
},
|
||||
editSection: {
|
||||
flex: "0 0 0", position: "relative", width: 60, height: 28,minHeight: 28
|
||||
flex: "0 0 0", display: "flex", flexFlow: "column nowrap", justifyContent: "center",position: "relative", width: 60, height: 28,minHeight: 28
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
export interface PluginControlProps extends WithStyles<typeof styles> {
|
||||
export interface PluginControlProps extends WithStyles<typeof pluginControlStyles> {
|
||||
uiControl?: UiControl;
|
||||
instanceId: number;
|
||||
value: number;
|
||||
@@ -110,7 +110,7 @@ type PluginControlState = {
|
||||
};
|
||||
|
||||
const PluginControl =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
withStyles(pluginControlStyles, { withTheme: true })(
|
||||
class extends Component<PluginControlProps, PluginControlState> {
|
||||
|
||||
frameRef: React.RefObject<HTMLDivElement>;
|
||||
@@ -802,8 +802,8 @@ const PluginControl =
|
||||
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
|
||||
(
|
||||
(isAbSwitch) ? (
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%", textAlign: "center"
|
||||
<Typography variant="caption" display="block" textAlign="center" noWrap style={{
|
||||
width: "100%"
|
||||
}}> {switchText} </Typography>
|
||||
) : (
|
||||
<div>
|
||||
|
||||
+100
-31
@@ -41,6 +41,8 @@ import PluginOutputControl from './PluginOutputControl';
|
||||
import Units from './Units';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import MidiChannelBindingControl from './MidiChannelBindingControl';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 110 };
|
||||
@@ -90,35 +92,57 @@ const styles = (theme: Theme) => createStyles({
|
||||
paddingTop: "8px",
|
||||
paddingBottom: "0px",
|
||||
height: "100%",
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden"
|
||||
},
|
||||
frameScrollLandscape: {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
left: 0, top: 0, right: 0, bottom:0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "nowrap",
|
||||
paddingTop: "0px",
|
||||
paddingBottom: "0px",
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden"
|
||||
},
|
||||
frameScrollPortrait: {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
left: 0, top: 0, right: 0, bottom:0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "nowrap",
|
||||
paddingTop: "0px",
|
||||
paddingBottom: "0px",
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto"
|
||||
},
|
||||
vuMeterL: {
|
||||
position: "fixed",
|
||||
position: "absolute",
|
||||
left: 0, top: 0,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 24,
|
||||
left: 0,
|
||||
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
},
|
||||
vuMeterR: {
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
marginRight: 22,
|
||||
position: "absolute",
|
||||
right: 0, top: 0,
|
||||
marginRight: 20, // has to potentially clear a scrollbar.
|
||||
paddingLeft: 4,
|
||||
paddingBottom: 24,
|
||||
paddingBottom: 12,
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
},
|
||||
vuMeterRLandscape: {
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
paddingRight: 22,
|
||||
position: "absolute",
|
||||
right: 0, top: 0,
|
||||
paddingRight: 6,
|
||||
paddingLeft: 12,
|
||||
paddingBottom: 24,
|
||||
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
@@ -126,21 +150,28 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
normalGrid: {
|
||||
position: "relative",
|
||||
paddingLeft: 25,
|
||||
paddingRight: 34,
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 8,
|
||||
flex: "1 1 auto",
|
||||
display: "flex", flexDirection: "row", flexWrap: "wrap",
|
||||
justifyContent: "flex-start", alignItems: "flex_start",
|
||||
rowGap: 10
|
||||
rowGap: 14,
|
||||
height: "fit-content"
|
||||
|
||||
|
||||
|
||||
},
|
||||
landscapeGrid: {
|
||||
paddingLeft: 40,
|
||||
// marginRight: 40, : bug in chrome layout engine wrt/ right margin/padding. See the spacer div added after all controls in render() with provides the same effect.
|
||||
paddingLeft: 40, paddingRight: 40, paddingTop: 8,
|
||||
// marginRight: 40, : bug in chrome layout engine wrt/ right margin/padding.
|
||||
// See the spacer div added after all controls in render() with provides the same effect.
|
||||
display: "flex", flexDirection: "row", flexWrap: "nowrap",
|
||||
justifyContent: "flex-start", alignItems: "flex_start",
|
||||
flex: "0 0 auto"
|
||||
justifyContent: "flex-start", alignItems: "flex-start",
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden",
|
||||
flex: "0 0 auto",
|
||||
width: "fit-content"
|
||||
},
|
||||
portgroupControlPadding: {
|
||||
flex: "0 0 auto",
|
||||
@@ -198,10 +229,12 @@ const styles = (theme: Theme) => createStyles({
|
||||
border: "2pt #AAA solid",
|
||||
borderRadius: 8,
|
||||
elevation: 12,
|
||||
display: "flex",
|
||||
display: "inline-flex",
|
||||
textOverflow: "ellipsis",
|
||||
flexDirection: "row", flexWrap: "nowrap",
|
||||
flex: "0 0 auto"
|
||||
flex: "0 0 auto",
|
||||
width: "fit-content",
|
||||
minWidth: "max-content"
|
||||
},
|
||||
portGroupTitle: {
|
||||
position: "absolute",
|
||||
@@ -219,6 +252,13 @@ const styles = (theme: Theme) => createStyles({
|
||||
flexDirection: "row", flexWrap: "wrap",
|
||||
paddingTop: 6,
|
||||
paddingBottom: 8
|
||||
},
|
||||
portGroupControlsLandscape: {
|
||||
display: "flex",
|
||||
flexFlow: "row nowrap",
|
||||
width: "fit-content",
|
||||
paddingTop: 6,
|
||||
paddingBottom: 8
|
||||
}
|
||||
|
||||
|
||||
@@ -600,7 +640,8 @@ const PluginControlView =
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={classes.portGroupControls} >
|
||||
<div className={
|
||||
this.state.landscapeGrid ? classes.portGroupControlsLandscape : classes.portGroupControls} >
|
||||
{
|
||||
controls
|
||||
}
|
||||
@@ -626,6 +667,20 @@ const PluginControlView =
|
||||
static endPluginInfo: UiPlugin =
|
||||
makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI);
|
||||
|
||||
midiBindingControl(pedalboardItem: PedalboardItem): ReactNode {
|
||||
if (!pedalboardItem.midiChannelBinding) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<MidiChannelBindingControl key="channelBindingCtl" midiChannelBinding={pedalboardItem.midiChannelBinding}
|
||||
onChange={(result)=> {
|
||||
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
this.controlKeyIndex = 0;
|
||||
@@ -663,6 +718,7 @@ const PluginControlView =
|
||||
|
||||
|
||||
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
|
||||
let scrollClass = this.state.landscapeGrid ? classes.frameScrollLandscape : classes.frameScrollPortrait;
|
||||
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||
let controlNodes: ControlNodes;
|
||||
|
||||
@@ -675,6 +731,16 @@ const PluginControlView =
|
||||
|
||||
let nodes = this.controlNodesToNodes(controlNodes);
|
||||
|
||||
if (plugin.has_midi_input && !pedalboardItem.midiChannelBinding)
|
||||
{
|
||||
pedalboardItem.midiChannelBinding = MidiChannelBinding.CreateMissingValue();
|
||||
|
||||
}
|
||||
if (pedalboardItem.midiChannelBinding) {
|
||||
nodes.push(this.midiBindingControl(pedalboardItem));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={classes.frame}>
|
||||
<div className={classes.vuMeterL}>
|
||||
@@ -683,16 +749,19 @@ const PluginControlView =
|
||||
<div className={vuMeterRClass}>
|
||||
<VuMeter displayText={true} display="output" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={gridClass} >
|
||||
{
|
||||
nodes
|
||||
}
|
||||
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
||||
{
|
||||
(!this.state.landscapeGrid) && (
|
||||
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
||||
)
|
||||
}
|
||||
<div className={scrollClass}>
|
||||
<div className={gridClass} >
|
||||
{
|
||||
nodes
|
||||
}
|
||||
{/* Extra space to allow scrolling right to the end in lascape especially */}
|
||||
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
||||
{
|
||||
(!this.state.landscapeGrid) && (
|
||||
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showFileDialog && (
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ import { ReactComponent as FxEmptyIcon } from './svg/fx_empty.svg';
|
||||
import { ReactComponent as FxTerminalIcon } from './svg/fx_terminal.svg';
|
||||
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { Color } from '@mui/material';
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user