Midi Bindings for split controls.

This commit is contained in:
Robin Davies
2024-08-10 10:24:43 -04:00
parent 2328467ee8
commit 31f1b9a84a
13 changed files with 256 additions and 67 deletions
+144 -2
View File
@@ -520,7 +520,10 @@ export class UiControl implements Deserializable<UiControl> {
}
}
return this;
}
applyProperties(properties: Partial<UiControl>): UiControl
{
return {...this,...properties};
}
private hasScalePoint(value: number): boolean {
for (let scale_point of this.scale_points)
@@ -726,7 +729,7 @@ export class UiControl implements Deserializable<UiControl> {
text += "s";
break;
// Midinote: not handled.
// semitone12TET not handled.
// semitone12TET not handled.
@@ -759,6 +762,7 @@ export class UiControl implements Deserializable<UiControl> {
}
export class UiPlugin implements Deserializable<UiPlugin> {
deserialize(input: any): UiPlugin
{
@@ -802,6 +806,11 @@ export class UiPlugin implements Deserializable<UiPlugin> {
}
return result;
}
isSplit(): boolean {
return this.uri === "uri://two-play/pipedal/pedalboard#Split";
}
getControl(key: string): UiControl | undefined {
for (let i = 0; i < this.controls.length; ++i)
{
@@ -860,3 +869,136 @@ export class UiPlugin implements Deserializable<UiPlugin> {
is_vst3 : boolean = false;
}
export function makeSplitUiPlugin(): UiPlugin
{
return new UiPlugin().deserialize({
uri: "uri://two-play/pipedal/pedalboard#Split",
name: "Split",
brand: "",
label: "",
plugin_type: PluginType.SplitA,
plugin_display_type: "Split",
author_name: "",
author_homepage: "",
audio_inputs: 1,
audio_outputs: 1,
has_midi_input: 0,
has_midi_output: 0,
description: "",
controls: [
new UiControl().applyProperties({
symbol: "splitType",
name: "Type",
index: 0,
is_input: true,
min_value: 0.0,
max_value: 2.0,
enumeration_property: true,
scale_points: [
new ScalePoint().deserialize({value: 0, label: "A/B"}),
new ScalePoint().deserialize({value: 1, label: "mix"}),
new ScalePoint().deserialize({value: 1, label: "L/R"}),
],
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "select",
name: "Select",
index: 1,
is_input: true,
min_value: 0.0,
max_value: 1.0,
enumeration_property: true,
scale_points: [
new ScalePoint().deserialize({value: 0, label: "A"}),
new ScalePoint().deserialize({value: 1, label: "B"}),
],
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "mix",
name: "Mix",
index: 2,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "panL",
name: "Pan Top",
index: 3,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "volL",
name: "Vol Top",
index: 4,
is_input: true,
min_value: -60.0,
max_value: 12.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "panR",
name: "Pan Bottom",
index: 5,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "volR",
name: "Vol Bottom",
index: 6,
is_input: true,
min_value: -60.0,
max_value: 12.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
})
],
port_groups: [],
fileProperties: [],
frequencyPlots: [],
is_vst3 : false,
}
);
}
+8 -3
View File
@@ -32,6 +32,7 @@ import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
import IconButton from '@mui/material/IconButton';
import NumericInput from './NumericInput';
import { UiPlugin } from './Lv2Plugin';
@@ -45,6 +46,7 @@ interface MidiBindingViewProps extends WithStyles<typeof styles> {
instanceId: number;
listen: boolean;
midiBinding: MidiBinding;
uiPlugin: UiPlugin;
onChange: (instanceId: number, newBinding: MidiBinding) => void;
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
}
@@ -139,8 +141,7 @@ const MidiBindingView =
render() {
let classes = this.props.classes;
let midiBinding = this.props.midiBinding;
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
let uiPlugin = this.props.uiPlugin;
if (!uiPlugin) {
return (<div />);
}
@@ -258,7 +259,11 @@ const MidiBindingView =
value={midiBinding.switchControlType}
>
<MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Toggle</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>Momentary</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>{
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) ?
"Note on/off"
: "Control value"
}</MenuItem>
</Select>
</div>
))
+70 -24
View File
@@ -33,6 +33,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';
const styles = (theme: Theme) => createStyles({
dialogAppBar: {
@@ -62,6 +63,10 @@ const styles = (theme: Theme) => createStyles({
},
});
function not_null<T>(value: T | null) {
if (!value) throw Error("Unexpected null value");
return value;
}
export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
open: boolean,
@@ -189,7 +194,43 @@ export const MidiBindingDialog =
let item = v.value;
let plugin = this.model.getUiPlugin(item.uri);
let isSplit = item.uri === "uri://two-play/pipedal/pedalboard#Split";
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;
}
// xxx
}
if (plugin) {
result.push(
<tr>
@@ -200,29 +241,33 @@ export const MidiBindingDialog =
</td>
</tr>
);
result.push(
<tr>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
Bypass
</Typography>
</td>
<td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
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"}
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
/>
</td>
</tr>
);
if (!isSplit)
{
result.push(
<tr>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
Bypass
</Typography>
</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"}
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
/>
</td>
</tr>
);
}
for (let i = 0; i < plugin.controls.length; ++i) {
let control = plugin.controls[i];
@@ -244,6 +289,7 @@ export const MidiBindingDialog =
</td>
<td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId}
uiPlugin={plugin}
midiBinding={item.getMidiBinding(symbol)}
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
+1
View File
@@ -423,6 +423,7 @@ export class PiPedalModel //implements PiPedalModel
//if (retry !== 0) {
if (this.restartExpected) {
this.setState(State.ApplyingChanges);
this.restartExpected = false;
} else {
this.setState(State.Reconnecting);
}