listen for midi binding control range.
This commit is contained in:
@@ -25,6 +25,9 @@ export default class MidiBinding {
|
||||
this.bindingType = input.bindingType;
|
||||
this.note = input.note;
|
||||
this.control = input.control;
|
||||
this.minControlValue = input.minControlValue?? 0;
|
||||
this.maxControlValue = input.maxControlValue?? 127;
|
||||
this.rotaryScale = input.rotaryScale?? 1;
|
||||
this.minValue = input.minValue;
|
||||
this.maxValue = input.maxValue;
|
||||
this.linearControlType = input.linearControlType;
|
||||
@@ -53,6 +56,9 @@ export default class MidiBinding {
|
||||
&& (this.note === other.note)
|
||||
&& (this.control === other.control)
|
||||
&& (this.minValue === other.minValue)
|
||||
&& (this.minControlValue === other.minControlValue)
|
||||
&& (this.maxControlValue === other.maxControlValue)
|
||||
&& (this.rotaryScale === other.rotaryScale)
|
||||
&& (this.maxValue === other.maxValue)
|
||||
&& (this.linearControlType === other.linearControlType)
|
||||
&& (this.switchControlType === other.switchControlType)
|
||||
@@ -73,6 +79,8 @@ export default class MidiBinding {
|
||||
bindingType: number = MidiBinding.BINDING_TYPE_NONE;
|
||||
note: number = 12*4+24; // C4.
|
||||
control: number = 1;
|
||||
minControlValue: number = 0;
|
||||
maxControlValue: number = 127;
|
||||
minValue: number = 0;
|
||||
maxValue: number = 1;
|
||||
rotaryScale: number = 1;
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { Component } from 'react';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { ListenHandle, MidiMessage, PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import {createStyles} from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
|
||||
import { UiPlugin } from './Lv2Plugin';
|
||||
|
||||
@@ -47,7 +48,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
}
|
||||
});
|
||||
|
||||
enum MidiControlType {
|
||||
export enum MidiControlType {
|
||||
None,
|
||||
Select,
|
||||
Dial,
|
||||
@@ -56,18 +57,57 @@ enum MidiControlType {
|
||||
MomentarySwitch
|
||||
}
|
||||
|
||||
export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: string): MidiControlType {
|
||||
if (!uiPlugin) return MidiControlType.None;
|
||||
let port = uiPlugin.getControl(symbol);
|
||||
if (!port) return MidiControlType.None;
|
||||
|
||||
if (symbol === "__bypass") {
|
||||
return MidiControlType.Toggle;
|
||||
}
|
||||
|
||||
|
||||
if (!port) return MidiControlType.None;
|
||||
|
||||
if (port.mod_momentaryOffByDefault || port.mod_momentaryOnByDefault) {
|
||||
return MidiControlType.MomentarySwitch;
|
||||
}
|
||||
if (port.trigger_property) {
|
||||
return MidiControlType.Trigger;
|
||||
}
|
||||
if (port.trigger_property) {
|
||||
return MidiControlType.Trigger;
|
||||
}
|
||||
if (port.isAbToggle() || port.isOnOffSwitch()) {
|
||||
return MidiControlType.Toggle;
|
||||
}
|
||||
if (port.isSelect()) {
|
||||
return MidiControlType.Select;
|
||||
}
|
||||
return MidiControlType.Dial;
|
||||
}
|
||||
|
||||
export function canBindToNote(controlType: MidiControlType): boolean {
|
||||
return controlType === MidiControlType.Toggle
|
||||
|| controlType === MidiControlType.Trigger
|
||||
|| controlType === MidiControlType.MomentarySwitch
|
||||
}
|
||||
|
||||
interface MidiBindingViewProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
listen: boolean;
|
||||
midiBinding: MidiBinding;
|
||||
uiPlugin: UiPlugin;
|
||||
midiControlType: MidiControlType;
|
||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
interface MidiBindingViewState {
|
||||
midiControlType: MidiControlType;
|
||||
listenSymbol: string;
|
||||
listenForRangeSymbol: string;
|
||||
listenForRangeMax: number;
|
||||
listenForRangeMin: number;
|
||||
listenSnackbarOpen: boolean;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +123,11 @@ const MidiBindingView =
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
midiControlType: this.getControlType()
|
||||
listenSymbol: "",
|
||||
listenForRangeSymbol: "",
|
||||
listenForRangeMax: 127,
|
||||
listenForRangeMin: 0,
|
||||
listenSnackbarOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,6 +183,16 @@ const MidiBindingView =
|
||||
newBinding.maxValue = value;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleCtlMinChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.minControlValue = Math.round(value);
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleCtlMaxChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.maxControlValue = Math.round(value);
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleScaleChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.rotaryScale = value;
|
||||
@@ -158,10 +212,23 @@ const MidiBindingView =
|
||||
return result;
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount?.();
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
this.cancelListenForControl();
|
||||
super.componentWillUnmount?.();
|
||||
}
|
||||
validateSwitchControlType(midiBinding: MidiBinding) {
|
||||
// :-(
|
||||
|
||||
let controlType = this.state.midiControlType;
|
||||
let controlType = this.props.midiControlType;
|
||||
if (controlType === MidiControlType.Toggle
|
||||
&& midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) {
|
||||
if (midiBinding.switchControlType !== MidiBinding.TRIGGER_ON_RISING_EDGE // Toggle on Note On.
|
||||
@@ -181,41 +248,154 @@ const MidiBindingView =
|
||||
}
|
||||
|
||||
getControlType(): MidiControlType {
|
||||
|
||||
if (this.props.midiBinding.symbol === "__bypass") {
|
||||
return MidiControlType.Toggle;
|
||||
}
|
||||
if (!this.props.uiPlugin) return MidiControlType.None;
|
||||
|
||||
let port = this.props.uiPlugin.getControl(this.props.midiBinding.symbol);
|
||||
|
||||
if (!port) return MidiControlType.None;
|
||||
if (port.mod_momentaryOffByDefault || port.mod_momentaryOnByDefault) {
|
||||
return MidiControlType.MomentarySwitch;
|
||||
}
|
||||
if (port.trigger_property) {
|
||||
return MidiControlType.Trigger;
|
||||
}
|
||||
if (port.trigger_property) {
|
||||
return MidiControlType.Trigger;
|
||||
}
|
||||
if (port.isAbToggle() || port.isOnOffSwitch()) {
|
||||
return MidiControlType.Toggle;
|
||||
}
|
||||
if (port.isSelect()) {
|
||||
return MidiControlType.Select;
|
||||
}
|
||||
return MidiControlType.Dial;
|
||||
return this.props.midiControlType;
|
||||
}
|
||||
///////////////////////////////////////
|
||||
listenTimeoutHandle?: number;
|
||||
|
||||
listenHandle?: ListenHandle;
|
||||
|
||||
cancelListenForControl() {
|
||||
this.stopListenTimeout();
|
||||
if (this.listenHandle) {
|
||||
this.model.cancelListenForMidiEvent(this.listenHandle)
|
||||
this.listenHandle = undefined;
|
||||
}
|
||||
|
||||
this.setState({ listenSymbol: "", listenForRangeSymbol: "" });
|
||||
|
||||
}
|
||||
|
||||
handleListenForRangeSucceeded(instanceId: number, symbol: string, midiMessage: MidiMessage) {
|
||||
if (!midiMessage.isControl()) return;
|
||||
|
||||
let binding = this.props.midiBinding;
|
||||
if (binding.control != midiMessage.cc1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let value = midiMessage.cc2;
|
||||
let min = this.state.listenForRangeMin;
|
||||
let max = this.state.listenForRangeMax;
|
||||
|
||||
let update = false;
|
||||
if (value < min) {
|
||||
min = value;
|
||||
update = true;
|
||||
}
|
||||
if (value > max) {
|
||||
max = value;
|
||||
update = true;
|
||||
}
|
||||
if (!update) {
|
||||
return; // No change, so ignore.
|
||||
}
|
||||
|
||||
let newBinding = binding.clone();
|
||||
newBinding.minControlValue = min;
|
||||
newBinding.maxControlValue = max;
|
||||
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
this.setState({ listenForRangeMin: min, listenForRangeMax: max });
|
||||
this.startListenTimeout(20000);
|
||||
|
||||
}
|
||||
|
||||
handleListenSucceeded(instanceId: number, symbol: string, midiMessage: MidiMessage) {
|
||||
if (instanceId !== this.props.instanceId) {
|
||||
return;
|
||||
}
|
||||
if (symbol !== this.props.midiBinding.symbol) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let binding = this.props.midiBinding;
|
||||
let newBinding = binding.clone();
|
||||
if (midiMessage.isNote()) {
|
||||
if (!canBindToNote(this.props.midiControlType)) {
|
||||
return;
|
||||
}
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE
|
||||
newBinding.note = midiMessage.cc1;
|
||||
} else if (midiMessage.isControl()) {
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL
|
||||
newBinding.control = midiMessage.cc1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
this.cancelListenForControl();
|
||||
}
|
||||
handleListenForControlRange(instanceId: number, symbol: string): void {
|
||||
if (this.state.listenForRangeSymbol !== "") {
|
||||
this.cancelListenForControl();
|
||||
return;
|
||||
}
|
||||
this.cancelListenForControl();
|
||||
|
||||
this.setState({
|
||||
listenSymbol: "",
|
||||
listenForRangeSymbol: symbol,
|
||||
listenForRangeMin: 127,
|
||||
listenForRangeMax: 0,
|
||||
listenSnackbarOpen: true
|
||||
});
|
||||
this.startListenTimeout(20000);
|
||||
|
||||
this.listenHandle = this.model.listenForMidiEvent(
|
||||
(midiMessage) => {
|
||||
this.handleListenForRangeSucceeded(instanceId, symbol, midiMessage);
|
||||
});
|
||||
}
|
||||
|
||||
stopListenTimeout(): void {
|
||||
if (this.listenTimeoutHandle) {
|
||||
clearTimeout(this.listenTimeoutHandle);
|
||||
}
|
||||
}
|
||||
|
||||
startListenTimeout(timeout: number): void {
|
||||
this.stopListenTimeout();
|
||||
this.listenTimeoutHandle = setTimeout(() => {
|
||||
this.cancelListenForControl();
|
||||
this.listenTimeoutHandle = undefined;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
handleListen(): void {
|
||||
if (this.state.listenSymbol !== "") {
|
||||
this.cancelListenForControl();
|
||||
return;
|
||||
}
|
||||
this.cancelListenForControl();
|
||||
|
||||
this.setState({
|
||||
listenSymbol: this.props.midiBinding.symbol,
|
||||
listenForRangeSymbol: "",
|
||||
listenSnackbarOpen: true
|
||||
});
|
||||
let instanceId = this.props.instanceId;
|
||||
let symbol = this.props.midiBinding.symbol;
|
||||
|
||||
this.startListenTimeout(8000);
|
||||
|
||||
this.listenHandle = this.model.listenForMidiEvent(
|
||||
(midiMessage) => {
|
||||
this.handleListenSucceeded(instanceId, symbol, midiMessage);
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let midiBinding = this.props.midiBinding;
|
||||
let uiPlugin = this.props.uiPlugin;
|
||||
if (!uiPlugin) {
|
||||
if (this.props.midiControlType === MidiControlType.None) {
|
||||
return (<div />);
|
||||
}
|
||||
let controlType = this.state.midiControlType;
|
||||
let controlType = this.props.midiControlType;
|
||||
|
||||
let showLinearRange =
|
||||
controlType === MidiControlType.Dial &&
|
||||
@@ -238,9 +418,7 @@ const MidiBindingView =
|
||||
value={midiBinding.bindingType}
|
||||
>
|
||||
<MenuItem value={0}>None</MenuItem>
|
||||
{(controlType === MidiControlType.Toggle
|
||||
|| controlType === MidiControlType.Trigger
|
||||
|| controlType === MidiControlType.MomentarySwitch) && (
|
||||
{(canBindToNote(this.props.midiControlType)) && (
|
||||
<MenuItem value={1}>Note</MenuItem>
|
||||
)}
|
||||
<MenuItem value={2}>Control</MenuItem>
|
||||
@@ -262,15 +440,15 @@ const MidiBindingView =
|
||||
</Select>
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
if (this.props.listen) {
|
||||
this.props.onListen(-2, "", false)
|
||||
} else {
|
||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
|
||||
}
|
||||
this.handleListen()
|
||||
}}
|
||||
size="large">
|
||||
{this.props.listen ? (
|
||||
{this.state.listenSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
@@ -296,15 +474,15 @@ const MidiBindingView =
|
||||
</Select>
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
if (this.props.listen) {
|
||||
this.props.onListen(-2, "", false)
|
||||
} else {
|
||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, true)
|
||||
}
|
||||
this.handleListen()
|
||||
}}
|
||||
size="large">
|
||||
{this.props.listen ? (
|
||||
{this.state.listenSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
@@ -314,6 +492,27 @@ const MidiBindingView =
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{midiBinding.bindingType === MidiBinding.BINDING_TYPE_NONE &&
|
||||
(
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
this.handleListen()
|
||||
}}
|
||||
size="large">
|
||||
{this.state.listenSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
)}
|
||||
</IconButtonEx>
|
||||
|
||||
)
|
||||
}
|
||||
{
|
||||
((controlType === MidiControlType.Toggle && midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
||||
(
|
||||
@@ -378,25 +577,58 @@ const MidiBindingView =
|
||||
<MenuItem value={MidiBinding.CIRCULAR_CONTROL_TYPE}>Rotary</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
{
|
||||
showLinearRange && (
|
||||
)}
|
||||
{showLinearRange && (
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline">Min: </Typography>
|
||||
<NumericInput defaultValue={midiBinding.minValue} ariaLabel='min'
|
||||
min={0} max={1} onChange={(value) => { this.handleMinChange(value); }}
|
||||
<Typography display="inline" noWrap>Min Ctl: </Typography>
|
||||
<NumericInput integer value={midiBinding.minControlValue} ariaLabel='min ctl val'
|
||||
min={0} max={127} step={1} onChange={(value) => { this.handleCtlMinChange(value); }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline" noWrap>Max Ctl: </Typography>
|
||||
<NumericInput value={midiBinding.maxControlValue} ariaLabel='max ctl val'
|
||||
integer={true}
|
||||
min={0} max={127} step={1} onChange={(value) => { this.handleCtlMaxChange(value); }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IconButtonEx
|
||||
tooltip="Listen for control range"
|
||||
onBlur={() => {
|
||||
this.cancelListenForControl();
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleListenForControlRange(this.props.instanceId, this.props.midiBinding.symbol);
|
||||
}}
|
||||
size="large">
|
||||
{this.state.listenForRangeSymbol !== "" ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
)}
|
||||
</IconButtonEx>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
showLinearRange && (
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline">Max: </Typography>
|
||||
<NumericInput defaultValue={midiBinding.maxValue} ariaLabel='max'
|
||||
min={0} max={1} onChange={(value) => { this.handleMaxChange(value); }} />
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", alignItems: "center" }}>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography noWrap display="inline">Min Val: </Typography>
|
||||
<NumericInput value={midiBinding.minValue} ariaLabel='min'
|
||||
min={0} max={1} onChange={(value) => { this.handleMinChange(value); }}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography noWrap display="inline">Max Val: </Typography>
|
||||
<NumericInput value={midiBinding.maxValue} ariaLabel='max'
|
||||
min={0} max={1} onChange={(value) => { this.handleMaxChange(value); }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -404,11 +636,24 @@ const MidiBindingView =
|
||||
canRotaryScale && (
|
||||
<div className={classes.controlDiv}>
|
||||
<Typography display="inline">Scale: </Typography>
|
||||
<NumericInput defaultValue={midiBinding.maxValue} ariaLabel='scale'
|
||||
<NumericInput value={midiBinding.maxValue} ariaLabel='scale'
|
||||
min={-100} max={100} onChange={(value) => { this.handleScaleChange(value); }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={this.state.listenSnackbarOpen}
|
||||
autoHideDuration={1500}
|
||||
onClose={() => this.setState({ listenSnackbarOpen: false })}
|
||||
message="Listening for MIDI input"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
@@ -32,8 +32,7 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import MidiBinding from './MidiBinding';
|
||||
import MidiBindingView from './MidiBindingView';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import MidiBindingView, { getMidiControlType } from './MidiBindingView';
|
||||
import { PortGroup, UiPlugin, makeSplitUiPlugin } from './Lv2Plugin';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
@@ -81,9 +80,6 @@ export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
|
||||
}
|
||||
|
||||
export interface MidiBindingDialogState {
|
||||
listenInstanceId: number;
|
||||
listenSymbol: string;
|
||||
listenSnackbarOpen: boolean;
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +94,10 @@ export const MidiBindingDialog =
|
||||
this.state = {
|
||||
listenInstanceId: -2,
|
||||
listenSymbol: "",
|
||||
listenSnackbarOpen: false
|
||||
listenForRangeSymbol: "",
|
||||
listenSnackbarOpen: false,
|
||||
listenForRangeMin: 127,
|
||||
listenForRangeMax: 0
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
@@ -108,64 +107,9 @@ export const MidiBindingDialog =
|
||||
hasHooks: boolean = false;
|
||||
|
||||
handleClose() {
|
||||
this.cancelListenForControl();
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
listenTimeoutHandle?: number;
|
||||
|
||||
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.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 {
|
||||
}
|
||||
@@ -176,10 +120,11 @@ export const MidiBindingDialog =
|
||||
this.mounted = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
|
||||
this.mounted = false;
|
||||
|
||||
super.componentWillUnmount();
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -255,15 +200,7 @@ export const MidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
|
||||
uiPlugin={plugin}
|
||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
||||
if (instanceId === -2) {
|
||||
this.cancelListenForControl();
|
||||
} else {
|
||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
||||
}
|
||||
}}
|
||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"}
|
||||
midiControlType={ getMidiControlType(plugin, "__bypass") }
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
@@ -305,13 +242,8 @@ export const MidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<MidiBindingView instanceId={item.instanceId}
|
||||
uiPlugin={plugin}
|
||||
midiControlType={getMidiControlType(plugin, symbol)}
|
||||
midiBinding={item.getMidiBinding(symbol)}
|
||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol}
|
||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
||||
}}
|
||||
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
@@ -379,16 +311,6 @@ export const MidiBindingDialog =
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={this.state.listenSnackbarOpen}
|
||||
autoHideDuration={1500}
|
||||
onClose={() => this.setState({ listenSnackbarOpen: false })}
|
||||
message="Listening for MIDI input"
|
||||
/>
|
||||
</DialogEx >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,14 +35,18 @@ const styles = ({ palette }: Theme) => createStyles({
|
||||
|
||||
interface NumericInputProps extends WithStyles<typeof styles> {
|
||||
ariaLabel: string;
|
||||
defaultValue: number;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
integer?: boolean;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
interface NumericInputState {
|
||||
stateValue: string;
|
||||
error: boolean;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
export const NumericInput =
|
||||
@@ -54,22 +58,28 @@ export const NumericInput =
|
||||
constructor(props: NumericInputProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: false
|
||||
stateValue: this.toDisplayValue(props.value),
|
||||
error: false,
|
||||
focused: false
|
||||
};
|
||||
}
|
||||
|
||||
changed: boolean = false;
|
||||
|
||||
handleChange(event: any): void {
|
||||
handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void {
|
||||
this.changed = true;
|
||||
let strValue = event.target.value;
|
||||
this.setState({ stateValue: strValue });
|
||||
try {
|
||||
let value = Number(strValue);
|
||||
if (isNaN(value))
|
||||
{
|
||||
this.setState({ error: true });
|
||||
} else if (this.props.integer === true && !Number.isInteger(value)) {
|
||||
this.setState({ error: true });
|
||||
} else if (value >= this.props.min && value <= this.props.max) {
|
||||
this.setState({ error: false });
|
||||
this.props.onChange(value);
|
||||
} else {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
@@ -80,6 +90,10 @@ export const NumericInput =
|
||||
}
|
||||
|
||||
toDisplayValue(value: number): string {
|
||||
if (this.props.integer === true)
|
||||
{
|
||||
return Math.round(value).toFixed(0);
|
||||
}
|
||||
if (value <= -1000 || value >= 1000)
|
||||
{
|
||||
return value.toFixed(0);
|
||||
@@ -96,7 +110,7 @@ export const NumericInput =
|
||||
}
|
||||
}
|
||||
|
||||
apply(input: HTMLInputElement) {
|
||||
apply(input: HTMLInputElement| HTMLTextAreaElement) {
|
||||
if (this.changed) {
|
||||
let strValue = input.value;
|
||||
let value = Number(strValue);
|
||||
@@ -104,28 +118,34 @@ export const NumericInput =
|
||||
{
|
||||
if (value < this.props.min) value = this.props.min;
|
||||
if (value > this.props.max) value = this.props.max;
|
||||
if (this.props.integer === true) value = Math.round(value);
|
||||
input.value = this.toDisplayValue(value);
|
||||
this.props.onChange(value);
|
||||
} else {
|
||||
input.value = this.toDisplayValue(this.props.defaultValue);
|
||||
input.value = this.toDisplayValue(this.props.value);
|
||||
this.props.onChange(this.props.value);
|
||||
}
|
||||
this.changed = false;
|
||||
this.setState({ error: false });
|
||||
}
|
||||
}
|
||||
|
||||
handleLostFocus(e: any) {
|
||||
this.apply(e.currentTarget as HTMLInputElement);
|
||||
handleFocus(e: React.FocusEvent<HTMLInputElement| HTMLTextAreaElement>) {
|
||||
this.changed = false;
|
||||
this.setState({ focused: true });
|
||||
e.currentTarget.select();
|
||||
this.setState({ stateValue: this.toDisplayValue(this.props.value) });
|
||||
}
|
||||
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||
handleBlur(e: any) {
|
||||
this.apply(e.currentTarget as HTMLInputElement);
|
||||
this.setState({ focused: false });
|
||||
}
|
||||
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
||||
if (e.key === "Enter")
|
||||
{
|
||||
this.apply(e.currentTarget as HTMLInputElement);
|
||||
} else if (e.key === "Escape")
|
||||
{
|
||||
e.currentTarget.value = this.toDisplayValue(this.props.defaultValue);
|
||||
this.changed = false;
|
||||
}
|
||||
this.setState({ stateValue: this.toDisplayValue(this.props.value) });
|
||||
this.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,11 +155,15 @@ export const NumericInput =
|
||||
inputProps={{
|
||||
'aria-label': this.props.ariaLabel,
|
||||
style: { textAlign: 'right' },
|
||||
"onBlur": (e: any) => this.handleLostFocus(e),
|
||||
"onKeyDown": (e: any) => this.handleKeyDown(e)
|
||||
"min": this.props.min,
|
||||
"max": this.props.max,
|
||||
"step:": this.props.step
|
||||
}}
|
||||
onChange={(event) => this.handleChange(event)}
|
||||
defaultValue={this.toDisplayValue(this.props.defaultValue)}
|
||||
onFocus={(e) => {this.handleFocus(e);}}
|
||||
onBlur= {(e) => { this.handleBlur(e); }}
|
||||
onKeyDown={(e) => {this.handleKeyDown(e);}}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
value={this.state.focused ? this.state.stateValue: this.toDisplayValue(this.props.value)}
|
||||
error={this.state.error}
|
||||
/>
|
||||
|
||||
|
||||
@@ -158,14 +158,31 @@ interface ControlValueChangeItem {
|
||||
|
||||
};
|
||||
|
||||
export class MidiMessage {
|
||||
constructor(cc0: number, cc1: number, cc2: number) {
|
||||
this.cc0 = cc0;
|
||||
this.cc1 = cc1;
|
||||
this.cc2 = cc2;
|
||||
}
|
||||
cc0: number;
|
||||
cc1: number;
|
||||
cc2: number;
|
||||
|
||||
isNote() {
|
||||
return (this.cc0 & 0xF0) == 0x90 && this.cc2 !== 0;
|
||||
}
|
||||
isControl() {
|
||||
return (this.cc0 & 0xF0) == 0xB0;
|
||||
}
|
||||
};
|
||||
|
||||
class MidiEventListener {
|
||||
constructor(handle: number, callback: (isNote: boolean, noteOrControl: number) => void) {
|
||||
constructor(handle: number, callback: (message: MidiMessage) => void) {
|
||||
this.handle = handle;
|
||||
this.callback = callback;
|
||||
}
|
||||
handle: number;
|
||||
callback: (isNote: boolean, noteOrControl: number) => void;
|
||||
callback: (midiMessage: MidiMessage) => void;
|
||||
};
|
||||
|
||||
export type PatchPropertyListener = (instanceId: number, propertyUri: string, atomObject: any) => void;
|
||||
@@ -679,10 +696,13 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
} else if (message === "onNotifyMidiListener") {
|
||||
let clientHandle = body.clientHandle as number;
|
||||
let isNote = body.isNote as boolean;
|
||||
let noteOrControl = body.noteOrControl as number;
|
||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
||||
let notifyBody = body as { clientHandle: number, cc0: number, cc1: number, cc2: number };
|
||||
let clientHandle = notifyBody.clientHandle as number;
|
||||
this.handleNotifyMidiListener(
|
||||
clientHandle,
|
||||
notifyBody.cc0,
|
||||
notifyBody.cc1,
|
||||
notifyBody.cc2);
|
||||
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
||||
let instanceId = body.instanceId as number;
|
||||
let propertyUri = body.propertyUri as string;
|
||||
@@ -2355,12 +2375,12 @@ export class PiPedalModel //implements PiPedalModel
|
||||
private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = [];
|
||||
|
||||
nextListenHandle = 1;
|
||||
listenForMidiEvent(listenForControl: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle {
|
||||
listenForMidiEvent(onComplete: (midiMessage: MidiMessage) => void): ListenHandle {
|
||||
let handle = this.nextListenHandle++;
|
||||
|
||||
this.midiListeners.push(new MidiEventListener(handle, onComplete));
|
||||
|
||||
this.webSocket?.send("listenForMidiEvent", { listenForControls: listenForControl, handle: handle });
|
||||
this.webSocket?.send("listenForMidiEvent", { handle: handle });
|
||||
return {
|
||||
_handle: handle
|
||||
};
|
||||
@@ -2418,12 +2438,16 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
handleNotifyMidiListener(clientHandle: number, isNote: boolean, noteOrControl: number) {
|
||||
handleNotifyMidiListener(clientHandle: number, cc0: number, cc1: number, cc2: number): void {
|
||||
let midiMessage = new MidiMessage(cc0, cc1, cc2);
|
||||
|
||||
if (!midiMessage.isNote() && !midiMessage.isControl()) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.midiListeners.length; ++i) {
|
||||
let listener = this.midiListeners[i];
|
||||
if (listener.handle === clientHandle) {
|
||||
listener.callback(isNote, noteOrControl);
|
||||
|
||||
listener.callback(midiMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2464,7 +2488,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let link = window.document.createElement("A") as HTMLAnchorElement;
|
||||
link.href = url;
|
||||
link.target = "_blank";
|
||||
link.download = "download.piPreset";
|
||||
link.download = "download.piPreset";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -2705,8 +2729,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
});
|
||||
}
|
||||
copyFilePropertyFile(
|
||||
oldRelativePath: string,
|
||||
newRelativePath: string,
|
||||
oldRelativePath: string,
|
||||
newRelativePath: string,
|
||||
uiFileProperty: UiFileProperty,
|
||||
overwrite: boolean
|
||||
): Promise<string> {
|
||||
@@ -3167,7 +3191,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
item.title = title;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
// notify the server.
|
||||
// xxx;
|
||||
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,200 +23,21 @@
|
||||
*/
|
||||
|
||||
|
||||
import { Component } from 'react';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import {createStyles} from './WithStyles';
|
||||
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import MidiBinding from './MidiBinding';
|
||||
import Utility from './Utility';
|
||||
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
|
||||
import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
controlDiv: { flex: "0 0 auto", marginRight: 12, verticalAlign: "center", height: 48, paddingTop: 8, paddingBottom: 8 },
|
||||
controlDiv2: {
|
||||
flex: "0 0 auto", marginRight: 12, verticalAlign: "center",
|
||||
height: 48, paddingTop: 0, paddingBottom: 0, whiteSpace: "nowrap"
|
||||
}
|
||||
});
|
||||
|
||||
interface SystemMidiBindingViewProps extends WithStyles<typeof styles> {
|
||||
import MidiBindingView, {MidiControlType} from './MidiBindingView';
|
||||
interface SystemMidiBindingViewProps {
|
||||
instanceId: number;
|
||||
listen: boolean;
|
||||
midiBinding: MidiBinding;
|
||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
interface SystemMidiBindingViewState {
|
||||
function SystemMidiBindingView(props: SystemMidiBindingViewProps) {
|
||||
return (
|
||||
<MidiBindingView
|
||||
instanceId={props.instanceId}
|
||||
midiBinding={props.midiBinding}
|
||||
onChange={props.onChange}
|
||||
midiControlType={MidiControlType.Trigger}
|
||||
/>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const SystemMidiBindingView =
|
||||
withStyles(
|
||||
class extends Component<SystemMidiBindingViewProps, SystemMidiBindingViewState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: SystemMidiBindingViewProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
|
||||
handleTypeChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.bindingType = newValue;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleNoteChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.note = newValue;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleControlChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.control = newValue;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleLatchControlTypeChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.switchControlType = newValue;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleLinearControlTypeChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.linearControlType = newValue;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleMinChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.minValue = value;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleMaxChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.maxValue = value;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
handleScaleChange(value: number): void {
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
newBinding.rotaryScale = value;
|
||||
this.props.onChange(this.props.instanceId, newBinding);
|
||||
}
|
||||
|
||||
|
||||
generateMidiSelects(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
for (let i = 0; i < 127; ++i) {
|
||||
result.push(
|
||||
<MenuItem value={i}>{Utility.midiNoteName(i)}</MenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
generateControlSelects(): React.ReactNode[] {
|
||||
|
||||
return Utility.validMidiControllers.map((control) => (
|
||||
<MenuItem key={control.value} value={control.value}>{control.displayName}</MenuItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let midiBinding = this.props.midiBinding;
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "left", alignItems: "center", minHeight: 48 }}>
|
||||
<div className={classes.controlDiv} >
|
||||
<Select variant="standard"
|
||||
style={{ width: 80, }}
|
||||
onChange={(e, extra) => this.handleTypeChange(e, extra)}
|
||||
value={midiBinding.bindingType}
|
||||
>
|
||||
<MenuItem value={0}>None</MenuItem>
|
||||
<MenuItem value={1}>Note</MenuItem>
|
||||
<MenuItem value={2}>Control</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
{
|
||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
||||
(
|
||||
<div className={classes.controlDiv} >
|
||||
<Select variant="standard"
|
||||
style={{ width: 80, verticalAlign: 'center' }}
|
||||
onChange={(e, extra) => this.handleNoteChange(e, extra)}
|
||||
value={midiBinding.note}
|
||||
|
||||
>
|
||||
{
|
||||
this.generateMidiSelects()
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_CONTROL) &&
|
||||
(
|
||||
<div className={classes.controlDiv} >
|
||||
|
||||
<Select variant="standard"
|
||||
style={{ width: 80 }}
|
||||
onChange={(e, extra) => this.handleControlChange(e, extra)}
|
||||
value={midiBinding.control}
|
||||
renderValue={(value) => { return "CC-" + value }}
|
||||
>
|
||||
{
|
||||
this.generateControlSelects()
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<IconButtonEx
|
||||
tooltip="Listen for MIDI input"
|
||||
onClick={() => {
|
||||
if (this.props.listen) {
|
||||
this.props.onListen(-2, "", false)
|
||||
} else {
|
||||
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
|
||||
}
|
||||
}}
|
||||
size="large">
|
||||
{this.props.listen ? (
|
||||
<MicOutlinedIcon />
|
||||
) : (
|
||||
<MicNoneOutlinedIcon />
|
||||
)}
|
||||
</IconButtonEx>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
},
|
||||
styles
|
||||
);
|
||||
|
||||
export default SystemMidiBindingView;
|
||||
@@ -27,7 +27,7 @@ import React, { SyntheticEvent } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
@@ -182,66 +182,9 @@ export const SystemMidiBindingDialog =
|
||||
hasHooks: boolean = false;
|
||||
|
||||
handleClose() {
|
||||
this.cancelListenForControl();
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
listenTimeoutHandle?: number;
|
||||
|
||||
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.cancelListenForControl();
|
||||
|
||||
for (var binding of this.state.systemMidiBindings) {
|
||||
if (binding.instanceId === instanceId) {
|
||||
let newBinding = binding.midiBinding.clone();
|
||||
|
||||
if (isNote) {
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE;
|
||||
newBinding.note = noteOrControl;
|
||||
} else {
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL;
|
||||
newBinding.control = noteOrControl;
|
||||
}
|
||||
|
||||
this.model.setSystemMidiBinding(instanceId, newBinding);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
@@ -293,14 +236,6 @@ export const SystemMidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
|
||||
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
|
||||
if (instanceId === -2) {
|
||||
this.cancelListenForControl();
|
||||
} else {
|
||||
this.handleListenForControl(instanceId, symbol, listenForControl);
|
||||
}
|
||||
}}
|
||||
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === item.midiBinding.symbol}
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -48,9 +48,8 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
|
||||
}
|
||||
function stopTimeout() {
|
||||
if (timeout > 0) {
|
||||
startTimeout(0);
|
||||
}
|
||||
setTimeout(0);
|
||||
setTimeoutInstance(0);
|
||||
}
|
||||
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
@@ -60,6 +59,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
stopTimeout();
|
||||
setLongPressLeaving(false);
|
||||
}
|
||||
|
||||
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
@@ -81,7 +81,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
if (valueTooltip === undefined) // no timeout if there's a value tooltip
|
||||
{
|
||||
if (t > 0) {
|
||||
// console.log("ToolTipEx: starting timeout for ", t);
|
||||
console.log("ToolTipEx: starting timeout for ", t);
|
||||
handle = window.setTimeout(() => {
|
||||
setOpen(true);
|
||||
},t);
|
||||
@@ -89,7 +89,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
}
|
||||
return () => {
|
||||
if (handle !== null) {
|
||||
// console.log("ToolTipEx: clearing timeout for ", t);
|
||||
console.log("ToolTipEx: clearing timeout for ", t);
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
@@ -179,41 +179,46 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
stopTimeout(); // Reset hover timeout on click
|
||||
}
|
||||
return (
|
||||
<div
|
||||
<div style={{ display: 'inline-block' }}
|
||||
onClickCapture={(e) => {
|
||||
// console.log("ToolTipEx: onClickCapture");
|
||||
console.log("ToolTipEx: onClickCapture");
|
||||
handleClickCapture(e);
|
||||
setTimeout(0);
|
||||
setOpen(false);
|
||||
setIsLongPress(false);
|
||||
setLongPressLeaving(true);
|
||||
}}
|
||||
|
||||
onMouseEnter={(e)=> {
|
||||
// console.log("ToolTipEx: onMouseEnter");
|
||||
|
||||
handleMouseEnter(e);
|
||||
console.log("ToolTipEx: onMouseEnter");
|
||||
if (!longPressLeaving) {// Don't handle mouse enter if we're in a long press leaving state
|
||||
handleMouseEnter(e);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e)=> {
|
||||
// console.log("ToolTipEx: onMouseLeave");
|
||||
console.log("ToolTipEx: onMouseLeave");
|
||||
handleMouseLeave(e);
|
||||
}}
|
||||
|
||||
onPointerCancelCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerCancelCapture");
|
||||
console.log("ToolTipEx: onPointerCancelCapture");
|
||||
handlePointerCancel(e);
|
||||
}}
|
||||
onContextMenuCapture={(e) => {
|
||||
// console.log("ToolTipEx: onContextMenuCapture");
|
||||
console.log("ToolTipEx: onContextMenuCapture");
|
||||
handleConextMenu(e);
|
||||
return false;
|
||||
}}
|
||||
onPointerDownCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerDownCapture");
|
||||
console.log("ToolTipEx: onPointerDownCapture");
|
||||
handlePointerDownCapture(e);
|
||||
}}
|
||||
onPointerMoveCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerMoveCapture");
|
||||
console.log("ToolTipEx: onPointerMoveCapture");
|
||||
handlePointerMoveCapture(e);
|
||||
}}
|
||||
onPointerUpCapture={(e) => {
|
||||
// console.log("ToolTipEx: onPointerUpCapture");
|
||||
console.log("ToolTipEx: onPointerUpCapture");
|
||||
handlePointerUpCapture(e);
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user