groups in MIDI bindings and info dialog. Momentary swtiches, Progress ctl,, TooB Looper and Record plugins.

This commit is contained in:
Robin E. R. Davies
2025-03-12 20:52:56 -04:00
parent eb7a7f3a48
commit 28f4aeafe2
32 changed files with 3526 additions and 264 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import CircularProgress from '@mui/material/CircularProgress';
import { type OnChangedHandler } from './ObservableProperty';
import ErrorOutlineIcon from '@mui/icons-material/Error';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import Button from '@mui/material/Button';
import PresetSelector from './PresetSelector';
+1 -1
View File
@@ -197,7 +197,7 @@ const GxTunerControl =
let nameIndex = noteNumber -octave* tet;
if (nameIndex < 0) nameIndex += tet;
name = names[ nameIndex ] + (octave-2);
name = names[ nameIndex ] + (octave-1);
valid = true;
fraction = note-noteNumber;
if (fraction >= 0) {
+29 -3
View File
@@ -451,9 +451,12 @@ export enum ControlType {
ABSwitch,
Select,
Trigger,
Momentary,
MomentaryOnByDefault,
Tuner,
Vu,
Progress,
DbVu,
OutputText
}
@@ -494,6 +497,8 @@ deserialize(input: any): UiControl {
this.display_priority = input.display_priority;
this.range_steps = input.range_steps;
this.integer_property = input.integer_property;
this.mod_momentaryOffByDefault = input.mod_momentaryOffByDefault;
this.mod_momentaryOnByDefault = input.mod_momentaryOnByDefault;
this.enumeration_property = input.enumeration_property;
this.toggled_property = input.toggled_property;
this.trigger_property = input.trigger_property;
@@ -522,6 +527,8 @@ deserialize(input: any): UiControl {
} else if (this.units === Units.db) {
this.controlType = ControlType.DbVu;
} else if (this.units === Units.pc) {
this.controlType = ControlType.Progress;
} else if (this.enumeration_property) {
this.controlType = ControlType.OutputText;
} else if (displayUnitAsText(this.units))
@@ -533,7 +540,7 @@ deserialize(input: any): UiControl {
}
if (this.isValidEnumeration()) {
this.controlType = ControlType.Select;
if (this.scale_points.length === 2) {
if (this.scale_points.length === 2 && this.min_value === 0 && this.max_value === 1) {
this.controlType = ControlType.ABSwitch;
}
} else {
@@ -541,9 +548,15 @@ deserialize(input: any): UiControl {
this.controlType = ControlType.OnOffSwitch;
}
}
if (this.is_input && this.trigger_property)
if (this.is_input)
{
this.controlType = ControlType.Trigger;
if (this.mod_momentaryOnByDefault) {
this.controlType = ControlType.MomentaryOnByDefault;
} else if (this.mod_momentaryOffByDefault) {
this.controlType = ControlType.Momentary;
} else if (this.trigger_property) {
this.controlType = ControlType.Trigger;
}
}
return this;
}
@@ -591,6 +604,8 @@ deserialize(input: any): UiControl {
display_priority: number = -1;
range_steps: number = 0;
integer_property: boolean = false;
mod_momentaryOffByDefault: boolean = false;
mod_momentaryOnByDefault: boolean = false;
enumeration_property: boolean = false;
trigger_property: boolean = false;
not_on_gui: boolean = false;
@@ -640,6 +655,14 @@ deserialize(input: any): UiControl {
isTrigger(): boolean {
return this.controlType === ControlType.Trigger;
}
isMomentary(): boolean {
return this.controlType === ControlType.Momentary;
}
isMomentaryOnByDefault(): boolean {
return this.controlType === ControlType.MomentaryOnByDefault;
}
isOutputText(): boolean {
return !this.is_input && this.controlType === ControlType.OutputText;
}
@@ -662,6 +685,9 @@ deserialize(input: any): UiControl {
isDbVu(): boolean {
return this.controlType === ControlType.DbVu;
}
isProgress(): boolean {
return this.controlType === ControlType.Progress;
}
valueToRange(value: number): number {
if (this.toggled_property) return value === 0 ? 0 : 1;
+13 -4
View File
@@ -48,10 +48,11 @@ const styles = (theme: Theme) => createStyles({
enum MidiControlType {
None,
Select,
Dial,
Toggle,
Trigger,
Dial,
Select,
MomentarySwitch
}
interface MidiBindingViewProps extends WithStyles<typeof styles> {
@@ -149,7 +150,7 @@ const MidiBindingView =
for (let i = 0; i < 127; ++i) {
result.push(
<MenuItem value={i}>{Utility.midiNoteName(i)}</MenuItem>
<MenuItem key={"k" + i} value={i}>{Utility.midiNoteName(i)}</MenuItem>
)
}
@@ -188,6 +189,12 @@ const MidiBindingView =
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;
}
@@ -230,7 +237,9 @@ const MidiBindingView =
value={midiBinding.bindingType}
>
<MenuItem value={0}>None</MenuItem>
{(controlType === MidiControlType.Toggle || controlType === MidiControlType.Trigger) && (
{(controlType === MidiControlType.Toggle
|| controlType === MidiControlType.Trigger
|| controlType === MidiControlType.MomentarySwitch) && (
<MenuItem value={1}>Note</MenuItem>
)}
<MenuItem value={2}>Control</MenuItem>
+114 -100
View File
@@ -24,7 +24,7 @@ import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel'
import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles';
import {createStyles} from './WithStyles';
import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui";
import AppBar from '@mui/material/AppBar';
@@ -34,7 +34,7 @@ import IconButton from '@mui/material/IconButton';
import MidiBinding from './MidiBinding';
import MidiBindingView from './MidiBindingView';
import Snackbar from '@mui/material/Snackbar';
import { UiPlugin,makeSplitUiPlugin } from './Lv2Plugin';
import { PortGroup, UiPlugin, makeSplitUiPlugin } from './Lv2Plugin';
import { css } from '@emotion/react';
const styles = (theme: Theme) => createStyles({
@@ -54,6 +54,11 @@ const styles = (theme: Theme) => createStyles({
pluginHead: css({
borderTop: "1pt #DDD solid", paddingLeft: 22, paddingRight: 22
}),
groupHead: css({
borderBottom: "1px solid " + theme.palette.divider,
marginLeft: 0,
marginBottom: 0
}),
bindingTd: css({
verticalAlign: "top",
paddingLeft: 12, paddingBottom: 8
@@ -65,7 +70,7 @@ const styles = (theme: Theme) => createStyles({
}),
});
function not_null<T>(value: T | null) {
function not_null<T>(value: T | null) {
if (!value) throw Error("Unexpected null value");
return value;
}
@@ -116,8 +121,7 @@ export const MidiBindingDialog =
clearTimeout(this.listenTimeoutHandle);
this.listenTimeoutHandle = undefined;
}
if (this.listenHandle)
{
if (this.listenHandle) {
this.model.cancelListenForMidiEvent(this.listenHandle)
this.listenHandle = undefined;
}
@@ -126,10 +130,9 @@ export const MidiBindingDialog =
}
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
{
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;
@@ -144,7 +147,7 @@ export const MidiBindingDialog =
newBinding.control = noteOrControl;
}
this.model.setMidiBinding(instanceId,newBinding);
this.model.setMidiBinding(instanceId, newBinding);
}
@@ -157,9 +160,9 @@ export const MidiBindingDialog =
this.listenHandle = this.model.listenForMidiEvent(listenForControl,
(isNote: boolean, noteOrControl: number) => {
this.handleListenSucceeded(instanceId,symbol,isNote, noteOrControl);
this.handleListenSucceeded(instanceId, symbol, isNote, noteOrControl);
});
}
@@ -190,6 +193,7 @@ export const MidiBindingDialog =
let pedalboard = this.model.pedalboard.get();
let iter = pedalboard.itemsGenerator();
let keyIx = 1;
while (true) {
let v = iter.next();
if (v.done) break;
@@ -197,45 +201,43 @@ export const MidiBindingDialog =
let isSplit = item.uri === "uri://two-play/pipedal/pedalboard#Split";
let plugin : UiPlugin | null = this.model.getUiPlugin(item.uri);
if (plugin === null && isSplit)
{
let plugin: UiPlugin | null = this.model.getUiPlugin(item.uri);
if (plugin === null && isSplit) {
plugin = makeSplitUiPlugin();
let splitType = item.getControlValue("splitType");
not_null(plugin.getControl("splitType")).not_on_gui = true;
switch (splitType)
{
case 0: // A/B
not_null(plugin.getControl("select")).not_on_gui = false;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 1: //mixer
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = false;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 2: // L/R
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = false;
not_null(plugin.getControl("panL")).not_on_gui = false;
not_null(plugin.getControl("volR")).not_on_gui = false;
not_null(plugin.getControl("panR")).not_on_gui = false;
break;
switch (splitType) {
case 0: // A/B
not_null(plugin.getControl("select")).not_on_gui = false;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 1: //mixer
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = false;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 2: // L/R
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = false;
not_null(plugin.getControl("panL")).not_on_gui = false;
not_null(plugin.getControl("volR")).not_on_gui = false;
not_null(plugin.getControl("panR")).not_on_gui = false;
break;
}
// xxx
}
if (plugin) {
result.push(
<tr>
<tr key={"k" + keyIx++}>
<td colSpan={2} className={classes.pluginHead}>
<Typography variant="caption" color="textSecondary" noWrap>
{plugin.name}
@@ -243,10 +245,9 @@ export const MidiBindingDialog =
</td>
</tr>
);
if (!isSplit)
{
if (!isSplit) {
result.push(
<tr>
<tr key={"k" + keyIx++}>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
Bypass
@@ -256,8 +257,7 @@ export const MidiBindingDialog =
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
uiPlugin={plugin}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
if (instanceId === -2)
{
if (instanceId === -2) {
this.cancelListenForControl();
} else {
this.handleListenForControl(instanceId, symbol, listenForControl);
@@ -271,21 +271,35 @@ export const MidiBindingDialog =
);
}
let lastPortGroup: PortGroup | null = null;
for (let i = 0; i < plugin.controls.length; ++i) {
let control = plugin.controls[i];
if (control.isHidden())
{
if (!control.is_input) {
continue;
}
if (!control.is_input)
{
continue;
let portGroup = plugin.getPortGroupBySymbol(control.port_group);
if (portGroup != lastPortGroup) {
lastPortGroup = portGroup;
if (portGroup != null) {
result.push(
<tr key={"k" + keyIx++}>
<td colSpan={2} className={classes.nameTd} >
<div className={classes.groupHead}>
<Typography variant="caption" color="textSecondary" noWrap>
{portGroup?.name}
</Typography>
</div>
</td>
</tr>
);
}
}
let symbol = control.symbol;
result.push(
<tr>
<tr key={"k" + keyIx++}>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "middle", maxWidth: 120 }} >
<Typography noWrap style={{ verticalAlign: "middle", maxWidth: 120, marginLeft: portGroup ? 24:0 }} >
{control.name}
</Typography>
</td>
@@ -319,66 +333,66 @@ export const MidiBindingDialog =
render() {
let props = this.props;
let { open} = props;
let { open } = props;
const classes = withStyles.getClasses(props);
if (!open) {
return (<div />);
}
return (
<DialogEx tag="midiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{userSelect: "none"}}
onEnterKey={()=>{}}
<DialogEx tag="midiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{ userSelect: "none" }}
onEnterKey={() => { }}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} >
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={this.handleClose}
aria-label="back"
size="large">
<ArrowBackIcon />
</IconButton>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
Preset MIDI Bindings
</Typography>
</Toolbar>
</AppBar>
</div>
<div style={{ overflow: "auto", flex: "1 1 auto", width: "100%" }}>
<table className={classes.pluginTable} >
<colgroup>
<col style={{ width: "auto" }} />
<col style={{ width: "100%" }} />
</colgroup>
<tbody>
{this.generateTable()}
</tbody>
</table>
</div>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} >
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={this.handleClose}
aria-label="back"
size="large">
<ArrowBackIcon />
</IconButton>
<Typography noWrap variant="h6" className={classes.dialogTitle}>
Preset MIDI Bindings
</Typography>
</Toolbar>
</AppBar>
</div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.listenSnackbarOpen}
autoHideDuration={1500}
onClose={() => this.setState({ listenSnackbarOpen: false })}
message="Listening for MIDI input"
/>
<div style={{ overflow: "auto", flex: "1 1 auto", width: "100%" }}>
<table className={classes.pluginTable} >
<colgroup>
<col style={{ width: "auto" }} />
<col style={{ width: "100%" }} />
</colgroup>
<tbody>
{this.generateTable()}
</tbody>
</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 >
);
}
},
styles);
styles);
export default MidiBindingDialog;
+2 -2
View File
@@ -2297,12 +2297,12 @@ export class PiPedalModel //implements PiPedalModel
private monitorPatchPropertyListeners: PatchPropertyListenerItem[] = [];
nextListenHandle = 1;
listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle {
listenForMidiEvent(listenForControl: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle {
let handle = this.nextListenHandle++;
this.midiListeners.push(new MidiEventListener(handle, onComplete));
this.webSocket?.send("listenForMidiEvent", { listenForControlsOnly: listenForControlsOnly, handle: handle });
this.webSocket?.send("listenForMidiEvent", { listenForControls: listenForControl, handle: handle });
return {
_handle: handle
};
+123 -32
View File
@@ -21,8 +21,8 @@ import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent }
import { css } from '@emotion/react';
import Button from '@mui/material/Button';
import { Theme } from '@mui/material/styles';
import WithStyles, {withTheme} from './WithStyles';
import {createStyles} from './WithStyles';
import WithStyles, { withTheme } from './WithStyles';
import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui";
import { UiControl, ScalePoint } from './Lv2Plugin';
@@ -37,11 +37,17 @@ import DialIcon from './svg/fx_dial.svg?react';
import { isDarkMode } from './DarkMode';
import ControlTooltip from './ControlTooltip';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import StopIcon from '@mui/icons-material/Stop';
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
const MIN_ANGLE = -135;
const MAX_ANGLE = 135;
const FONT_SIZE = "0.8em";
enum ButtonStyle { None, Trigger, Momentary, MomentaryOnByDefault }
const SELECTED_OPACITY = 0.8;
const DEFAULT_OPACITY = 0.6;
@@ -53,6 +59,20 @@ const ULTRA_FINE_RANGE_SCALE = RANGE_SCALE * 50; // 12000 pixels to move from 0
export const StandardItemSize = { width: 80, height: 140 }
function androidEmoji(text: string) {
// android chrome doesn't support these characters properly.
if (text === "⏹") {
return (<StopIcon fontSize={"medium"} />);
}
if (text === "⏺") {
return (<FiberManualRecordIcon fontSize={"medium"} />)
}
if (text === "⏵")
{
return (<PlayArrowIcon fontSize={"medium"}/>);
}
return text;
}
export const pluginControlStyles = (theme: Theme) => createStyles({
frame: css({
@@ -115,12 +135,12 @@ const PluginControl =
withTheme(withStyles(
class extends Component<PluginControlProps, PluginControlState> {
frameRef: React.RefObject<HTMLDivElement|null>;
imgRef: React.RefObject<SVGSVGElement|null>;
inputRef: React.RefObject<HTMLInputElement|null>;
selectRef: React.RefObject<HTMLSelectElement|null>;
frameRef: React.RefObject<HTMLDivElement | null>;
imgRef: React.RefObject<SVGSVGElement | null>;
inputRef: React.RefObject<HTMLInputElement | null>;
selectRef: React.RefObject<HTMLSelectElement | null>;
displayValueRef: React.RefObject<HTMLDivElement|null>;
displayValueRef: React.RefObject<HTMLDivElement | null>;
model: PiPedalModel;
@@ -498,19 +518,50 @@ const PluginControl =
this.isTap = false;
}
}
}
handleTriggerMouseDown() {
handleButtonMouseLeave(buttonStyle: ButtonStyle) {
}
handleButtonMouseDown(buttonStyle: ButtonStyle) {
let uiControl = this.props.uiControl;
if (uiControl) {
let value = uiControl.max_value;
if (uiControl.max_value === uiControl.default_value) {
value = uiControl.min_value;
switch (buttonStyle) {
case ButtonStyle.Momentary:
this.model.setPedalboardControl(this.props.instanceId, uiControl.symbol,uiControl.max_value);
break;
case ButtonStyle.MomentaryOnByDefault:
this.model.setPedalboardControl(this.props.instanceId, uiControl.symbol,uiControl.min_value);
break;
case ButtonStyle.Trigger:
{
let value = uiControl.max_value;
if (uiControl.max_value === uiControl.default_value) {
value = uiControl.min_value;
}
this.model.sendPedalboardControlTrigger(this.props.instanceId, uiControl.symbol, value);
}
break;
}
this.model.sendPedalboardControlTrigger(this.props.instanceId, uiControl.symbol, value);
}
}
handleTriggerMouseUp() {
// triggers are reset on the audio thread.
handleButtonMouseUp(buttonStyle: ButtonStyle) {
let uiControl = this.props.uiControl;
if (uiControl) {
switch (buttonStyle) {
case ButtonStyle.Momentary:
this.model.setPedalboardControl(this.props.instanceId, uiControl.symbol,uiControl.min_value);
break;
case ButtonStyle.MomentaryOnByDefault:
this.model.setPedalboardControl(this.props.instanceId, uiControl.symbol,uiControl.max_value);
break;
case ButtonStyle.Trigger:
{
// trigger values are reset automatically.
}
break;
}
}
}
previewInputValue(value: number, commitValue: boolean) {
let range = this.valueToRange(value);
@@ -657,8 +708,7 @@ const PluginControl =
}
range = Math.log(value / minValue) / Math.log(uiControl.max_value / minValue);
if (!isFinite(range)) {
if (range < 0)
{
if (range < 0) {
range = 0;
} else {
range = 1.0;
@@ -672,8 +722,7 @@ const PluginControl =
} else {
range = (value - uiControl.min_value) / (uiControl.max_value - uiControl.min_value);
if (!isFinite(range)) {
if (range < 0)
{
if (range < 0) {
range = 0;
} else {
range = 1.0;
@@ -760,14 +809,28 @@ const PluginControl =
let isSelect = control.isSelect();
let isAbSwitch = control.isAbToggle();
let isOnOffSwitch = control.isOnOffSwitch();
let isTrigger = control.isTrigger();
let isButton = false;
let buttonStyle: ButtonStyle = ButtonStyle.None;
if (control.isTrigger()) {
isButton = true;
buttonStyle = ButtonStyle.Trigger;
}
if (control.isMomentary()) {
isButton = true;
buttonStyle = ButtonStyle.Momentary;
}
if (control.isMomentaryOnByDefault()) {
isButton = true;
buttonStyle = ButtonStyle.MomentaryOnByDefault;
}
if (isAbSwitch) {
switchText = control.scale_points[0].value === value ? control.scale_points[0].label : control.scale_points[1].label;
}
let item_width: number | undefined = isSelect ? 160 : 80;
if (isTrigger) {
if (isButton) {
item_width = undefined;
}
@@ -788,24 +851,38 @@ const PluginControl =
<Typography variant="caption" display="block" noWrap style={{
width: "100%",
textAlign: isSelect ? "left" : "center"
}}> {isTrigger ? "\u00A0" : control.name}</Typography>
}}> {isButton ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}>
{isTrigger ?
{isButton ?
(
control.name.length !== 1 ? (
<Button variant="contained" color="primary" size="small"
onMouseDown={
(evt) => { this.handleTriggerMouseDown(); }
onMouseDown={
(evt) => { this.handleButtonMouseDown(buttonStyle); }
}
onMouseUp={
(evt) => { this.handleButtonMouseUp(buttonStyle); }
}
onTouchStart={
(evt) => { evt.preventDefault();
this.handleButtonMouseDown(buttonStyle); }
}
onTouchEnd={
(evt) => {
evt.preventDefault();
this.handleButtonMouseUp(buttonStyle);
}
onMouseUp={
(evt) => { this.handleTriggerMouseUp(); }
}
style={{
}
onMouseLeave={(
(evet) => { this.handleButtonMouseLeave(buttonStyle); }
)}
style={{
textTransform: "none",
background: (isDarkMode() ? "#6750A4" : undefined),
marginLeft: 8, marginRight: 8, minWidth: 60,
@@ -820,11 +897,25 @@ const PluginControl =
) : (
<Button variant="contained" color="primary" size="small"
onMouseDown={
(evt) => { this.handleTriggerMouseDown(); }
(evt) => { this.handleButtonMouseDown(buttonStyle); }
}
onMouseUp={
(evt) => { this.handleTriggerMouseUp(); }
(evt) => { this.handleButtonMouseUp(buttonStyle); }
}
onTouchStart={
(evt) => { evt.preventDefault();
this.handleButtonMouseDown(buttonStyle); }
}
onTouchEnd={
(evt) => {
evt.preventDefault();
this.handleButtonMouseUp(buttonStyle);
}
}
onMouseLeave={(
(evet) => { this.handleButtonMouseLeave(buttonStyle); }
)}
style={{
textTransform: "none",
background: (isDarkMode() ? "#6750A4" : undefined),
@@ -839,7 +930,7 @@ const PluginControl =
}}
>
{control.name}
{androidEmoji(control.name)}
</Button>
)
)
@@ -865,7 +956,7 @@ const PluginControl =
{/* LABEL/EDIT SECTION*/}
<div className={classes.editSection} >
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
{(!(isSelect || isOnOffSwitch || isButton)) &&
(
(isAbSwitch) ? (
<Typography variant="caption" display="block" textAlign="center" noWrap style={{
+7 -1
View File
@@ -155,6 +155,7 @@ const styles = (theme: Theme) => createStyles({
paddingLeft: 30,
paddingRight: 30,
paddingTop: 8,
flex: "1 1 auto",
display: "flex", flexDirection: "row", flexWrap: "wrap",
justifyContent: "flex-start", alignItems: "flex_start",
@@ -170,6 +171,7 @@ const styles = (theme: Theme) => createStyles({
// 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",
overflowX: "hidden",
overflowY: "hidden",
flex: "0 0 auto",
@@ -208,7 +210,7 @@ const styles = (theme: Theme) => createStyles({
marginBottom: 12,
position: "relative",
paddingLeft: 3,
paddingRight: 0,
paddingRight: 8,
paddingTop: 0,
paddingBottom: 0,
border: "2pt #AAA solid",
@@ -216,6 +218,7 @@ const styles = (theme: Theme) => createStyles({
elevation: 12,
display: "flex",
flexDirection: "row", flexWrap: "wrap",
flex: "0 1 auto",
}),
portGroupLandscape: css({
@@ -226,6 +229,7 @@ const styles = (theme: Theme) => createStyles({
position: "relative",
paddingLeft: 0,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0,
border: "2pt #AAA solid",
@@ -253,6 +257,7 @@ const styles = (theme: Theme) => createStyles({
display: "flex",
flexDirection: "row", flexWrap: "wrap",
paddingTop: 6,
rowGap: 16,
paddingBottom: 8
}),
portGroupControlsLandscape: css({
@@ -260,6 +265,7 @@ const styles = (theme: Theme) => createStyles({
flexFlow: "row nowrap",
width: "fit-content",
paddingTop: 6,
rowGap: 16,
paddingBottom: 8
})
+33 -12
View File
@@ -20,8 +20,7 @@
import React from 'react';
import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles';
import {createStyles} from './WithStyles';
import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui";
import Button from '@mui/material/Button';
import DialogEx from './DialogEx';
@@ -34,7 +33,7 @@ import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import { PiPedalModelFactory } from "./PiPedalModel";
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { UiPlugin, UiControl } from './Lv2Plugin';
import { UiPlugin, PortGroup } from './Lv2Plugin';
import PluginIcon from './PluginIcon';
import { Remark } from 'react-remark';
import { css } from '@emotion/react';
@@ -56,6 +55,9 @@ const styles = (theme: Theme) => {
icon: {
fill: theme.palette.text.primary,
opacity: 0.6
},
controlHeader: {
borderBottom: "1px solid " + theme.palette.divider,
}
});
};
@@ -88,7 +90,7 @@ const PluginInfoDialogContent = withStyles(
(theme: Theme) => ({
root: {
padding: theme.spacing(2),
}
}
})
);
@@ -146,7 +148,8 @@ function ioDescription(plugin: UiPlugin): string {
// );
// }
function makeControls(controls: UiControl[]) {
function makeControls(plugin: UiPlugin, controlHeadeClass: string) {
let controls = plugin.controls;
let hasComments = false;
for (let i = 0; i < controls.length; ++i) {
@@ -156,14 +159,32 @@ function makeControls(controls: UiControl[]) {
}
}
hasComments = true;
let lastPortGroup: PortGroup | null = null;
if (hasComments) {
let trs: React.ReactElement[] = [];
for (let i = 0; i < controls.length; ++i) {
let control = controls[i];
if (!(control.not_on_gui) && control.is_input)
if (!(control.not_on_gui) && control.is_input) {
let portGroup = plugin.getPortGroupBySymbol(control.port_group);
if (portGroup !== lastPortGroup) {
if (portGroup !== null)
{
trs.push((
<tr>
<td className={controlHeadeClass} style={{ verticalAlign: "top", paddingTop: 8 }} colSpan={2}>
<Typography variant="body2" style={{ whiteSpace: "nowrap" }}>
{portGroup?.name ?? ""}
</Typography>
</td>
</tr >
));
}
lastPortGroup = portGroup;
}
trs.push((
<tr>
<td style={{ verticalAlign: "top" }}>
<td style={{ verticalAlign: "top", paddingLeft: portGroup ? 24 : 0 }}>
<Typography variant="body2" style={{ whiteSpace: "nowrap" }}>
{control.name}
</Typography>
@@ -175,14 +196,14 @@ function makeControls(controls: UiControl[]) {
</td>
</tr >
));
}
}
return (
<table style={{ paddingLeft: "24px", verticalAlign: "top" }}>
<table style={{ paddingLeft: "24px", verticalAlign: "top" }}><tbody>
{
trs
}
</table>
</tbody></table>
);
} else {
@@ -290,7 +311,7 @@ const PluginInfoDialog = withStyles((props: PluginInfoProps) => {
Controls:
</Typography>
{
makeControls(plugin.controls)
makeControls(plugin, classes.controlHeader)
}
{plugin.description.length > 0 && (
<div>
@@ -338,6 +359,6 @@ const PluginInfoDialog = withStyles((props: PluginInfoProps) => {
</div >
);
},
styles);
styles);
export default PluginInfoDialog;
+57 -2
View File
@@ -130,6 +130,7 @@ const PluginOutputControl =
private model: PiPedalModel;
private vuRef: React.RefObject<HTMLDivElement|null>;
private progressRef: React.RefObject<HTMLDivElement|null>;
private dbVuRef: React.RefObject<HTMLDivElement|null>;
private dbVuTelltaleRef: React.RefObject<HTMLDivElement|null>;
private lampRef: React.RefObject<HTMLDivElement|null>;
@@ -138,6 +139,7 @@ const PluginOutputControl =
constructor(props: PluginOutputControlProps) {
super(props);
this.vuRef = React.createRef<HTMLDivElement>();
this.progressRef = React.createRef<HTMLDivElement>();
this.dbVuRef = React.createRef<HTMLDivElement>();
this.dbVuTelltaleRef = React.createRef<HTMLDivElement>();
this.lampRef = React.createRef<HTMLDivElement>();
@@ -182,6 +184,7 @@ const PluginOutputControl =
}
}
private PROGRESS_WIDTH = 40-4;
private VU_HEIGHT = 60 - 4;
private DB_VU_HEIGHT = 60 - 4;
private animationHandle: number | undefined = undefined;
@@ -257,7 +260,23 @@ const PluginOutputControl =
} else if (this.dbVuRef.current) {
this.dbVuValue = value;
this.requestDbVuAnimation();
}
} else if (this.progressRef.current) {
let control = this.props.uiControl;
let range = (value - control.min_value) / (control.max_value - control.min_value);
let w = this.PROGRESS_WIDTH * range;
if (!this.animationHandle) {
this.animationHandle = requestAnimationFrame(
() => {
if (this.progressRef.current) {
this.progressRef.current.style.width = w + "px";
}
this.animationHandle = undefined;
}
)
}
}
else if (this.vuRef.current) {
let control = this.props.uiControl;
let range = (value - control.min_value) / (control.max_value - control.min_value);
@@ -364,6 +383,43 @@ const PluginOutputControl =
</div >
);
} else if (control.isProgress()) {
item_width = undefined;
return (
<div className={classes.controlFrame}
style={{ width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
style={{ width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography variant="caption" display="block" style={{
width: "100%",
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<div style={{ width: this.PROGRESS_WIDTH+2, height: 12, marginLeft: 8, marginRight: 8, background: "#181818", }}>
<div style={{ height: 8, width: this.PROGRESS_WIDTH, overflow: "hidden", position: "absolute",
margin: "1px 1px 1px 1px", background: "#282828" }}>
<div ref={this.progressRef} style={{ height: 10, width: this.PROGRESS_WIDTH, position: "absolute", marginTop: 0, background: "#0C0" }} />
<div style={{ height: 10, width: this.PROGRESS_WIDTH, position: "absolute",
boxShadow: "inset 0px 2px 4px #000D", background: "transparent"
}} />
</div>
</div>
</div>
<div className={classes.editSectionNoContent}>
</div>
</div >
);
} else if (control.isDbVu()) {
item_width = undefined;
@@ -407,7 +463,6 @@ const PluginOutputControl =
);
}
else if (control.isVu()) {
// yyx: convert this to a horizontal progress bar.
item_width = undefined;
return (
<div className={classes.controlFrame} style={{ width: item_width}}>