// 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, { SyntheticEvent } from 'react'; import DialogEx from './DialogEx'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel'; import Typography from '@mui/material/Typography'; import { Theme } from '@mui/material/styles'; import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import IconButton from '@mui/material/IconButton'; import MidiBinding from './MidiBinding'; import MidiBindingView from './MidiBindingView'; import Snackbar from '@mui/material/Snackbar'; const styles = (theme: Theme) => createStyles({ dialogAppBar: { position: 'relative', top: 0, left: 0 }, dialogTitle: { marginLeft: theme.spacing(2), flex: "1 1 auto", }, pluginTable: { border: "collapse", width: "100%", }, pluginHead: { borderTop: "1pt #DDD solid", paddingLeft: 22, paddingRight: 22 }, bindingTd: { verticalAlign: "top", paddingLeft: 12, paddingBottom: 8 }, nameTd: { paddingLeft: 48, verticalAlign: "top", paddingTop: 12 }, }); export interface MidiBindingDialogProps extends WithStyles { open: boolean, onClose: () => void } export interface MidiBindingDialogState { listenInstanceId: number; listenSymbol: string; listenSnackbarOpen: boolean; } export const MidiBindingDialog = withStyles(styles, { withTheme: true })( class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: MidiBindingDialogProps) { super(props); this.state = { listenInstanceId: -2, listenSymbol: "", listenSnackbarOpen: false }; this.model = PiPedalModelFactory.getInstance(); this.handleClose = this.handleClose.bind(this); } mounted: boolean = false; hasHooks: boolean = false; handleClose() { this.props.onClose(); } listenTimeoutHandle?: NodeJS.Timeout; listenHandle?: ListenHandle; cancelListenForControl() { if (this.listenTimeoutHandle) { clearTimeout(this.listenTimeoutHandle); this.listenTimeoutHandle = undefined; } if (this.listenHandle) { this.model.cancelListenForMidiEvent(this.listenHandle) this.listenHandle = undefined; } this.setState({ listenInstanceId: -2, listenSymbol: "" }); } handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number) { this.listenHandle = undefined; // (one-shot event) this.cancelListenForControl(); let pedalBoard = this.model.pedalBoard.get(); let item = pedalBoard.getItem(instanceId); if (!item) return; let binding = item.getMidiBinding(symbol); let newBinding = binding.clone(); if (isNote) { newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE; newBinding.note = noteOrControl; } else { newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL; newBinding.control = noteOrControl; } this.model.setMidiBinding(instanceId,newBinding); } handleListenForControl(instanceId: number, symbol: string, listenForControl: boolean): void { this.cancelListenForControl(); this.setState({ listenInstanceId: instanceId, listenSymbol: symbol, listenSnackbarOpen: true }); this.listenTimeoutHandle = setTimeout(() => { this.cancelListenForControl(); }, 8000); this.listenHandle = this.model.listenForMidiEvent(listenForControl, (isNote: boolean, noteOrControl: number) => { this.handleListenSucceeded(instanceId,symbol,isNote, noteOrControl); }); } onWindowSizeChanged(width: number, height: number): void { } componentDidMount() { super.componentDidMount(); this.mounted = true; } componentWillUnmount() { super.componentWillUnmount(); this.mounted = false; } componentDidUpdate() { } handleItemChanged(instanceId: number, newBinding: MidiBinding) { this.model.setMidiBinding(instanceId, newBinding); } generateTable(): React.ReactNode[] { let classes = this.props.classes; let result: React.ReactNode[] = []; let pedalboard = this.model.pedalBoard.get(); let iter = pedalboard.itemsGenerator(); while (true) { let v = iter.next(); if (v.done) break; let item = v.value; let plugin = this.model.getUiPlugin(item.uri); if (plugin) { result.push( {plugin.name} ); result.push( Bypass { if (instanceId === -2) { this.cancelListenForControl(); } else { this.handleListenForControl(instanceId, symbol, listenForControl); } }} listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"} onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)} /> ); for (let i = 0; i < plugin.controls.length; ++i) { let control = plugin.controls[i]; let symbol = control.symbol; result.push( {control.name} { this.handleListenForControl(instanceId, symbol, listenForControl); }} onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)} /> ); } } } return result; } supressDefault(e: SyntheticEvent) { //e.preventDefault(); //e.stopPropagation(); } render() { let props = this.props; let { open, classes } = props; if (!open) { return (
); } return (
Preset MIDI Bindings
{this.generateTable()}
this.setState({ listenSnackbarOpen: false })} message="Listening for MIDI input" />
); } }); export default MidiBindingDialog;