MIDI plugins -- partial implemetnation, temporarily disabled.
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#define DECLARE_JSON_MAP(CLASSNAME) \
|
||||
static pipedal::json_map::storage_type<CLASSNAME> jmap
|
||||
@@ -592,6 +593,17 @@ namespace pipedal
|
||||
write(obj.get());
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void write(const std::optional<T> &obj)
|
||||
{
|
||||
if (!obj) {
|
||||
write_raw("null");
|
||||
} else {
|
||||
write(obj.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void write(const std::weak_ptr<T> &obj)
|
||||
{
|
||||
@@ -883,6 +895,20 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void read(std::optional<T> *pOptional)
|
||||
{
|
||||
if (peek() == 'n')
|
||||
{
|
||||
consumeToken("null", "Expecting '{' or 'null'.");
|
||||
*pOptional = std::optional<T>();
|
||||
|
||||
} else {
|
||||
T value;
|
||||
read(&value);
|
||||
*pOptional = std::move(value);
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void read(std::weak_ptr<T> *pUniquePtr)
|
||||
{
|
||||
throw std::domain_error("Can't read std::weak_ptr");
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import Divider from '@mui/material/Divider';
|
||||
//import TextFieldEx from './TextFieldEx';
|
||||
|
||||
|
||||
export interface ChannelBindingHelpDialogProps {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
export interface ChannelBindingHelpDialogState {
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
export default class ChannelBindingHelpDialog extends ResizeResponsiveComponent<ChannelBindingHelpDialogProps, ChannelBindingHelpDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement>;
|
||||
|
||||
constructor(props: ChannelBindingHelpDialogProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
fullScreen: false
|
||||
};
|
||||
this.refText = React.createRef<HTMLInputElement>();
|
||||
}
|
||||
mounted: boolean = false;
|
||||
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void
|
||||
{
|
||||
this.setState({fullScreen: height < 200})
|
||||
}
|
||||
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
super.componentWillUnmount();
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate()
|
||||
{
|
||||
}
|
||||
render() {
|
||||
let props = this.props;
|
||||
let { open, onClose } = props;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="bindingHelp" open={open} fullWidth maxWidth="sm"
|
||||
onClose={handleClose}
|
||||
aria-labelledby="Rename-dialog-title"
|
||||
style={{userSelect: "none"}}
|
||||
fullwidth
|
||||
onEnterKey={()=>{}}
|
||||
>
|
||||
<DialogContent style={{minHeight: 96}}>
|
||||
<Typography variant="h5" style={{marginBottom: 8}}>
|
||||
MIDI Control Binding Priority
|
||||
</Typography>
|
||||
<div style={{fontSize: "0.9em", lineHeight: "18pt" }}>
|
||||
<p style={{lineHeight: "1.4em"}}>MIDI Channel Filters determine which MIDI messages get sent to a plugin that accepts MIDI messages. Note that the
|
||||
MIDI Channel Filters do NOT affect system MIDI bindings or control bindings.
|
||||
</p>
|
||||
<p>
|
||||
MIDI message processing occurs in the following order
|
||||
</p>
|
||||
<ul>
|
||||
<li>If the message is a Program Change message, the message is first offered to any MIDI plugins in the currently loaded preset (Message Filters permitting).
|
||||
The message is sent to each MIDI plugin in the current preset that wants it. If any MIDI plugin accepts the program change,
|
||||
no futher processing occurs. Specifically, the program change will not change the currently selected PiPedal preset.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
If the message is a Program Change message, and no MIDI plugin has accepted the message, PiPedal selects the PiPedal preset that corresponds
|
||||
to the requested program.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
The message is then checked to see if it has been bound to a Pipedal feature using the System Midi Bindings settings. If it has,
|
||||
Pipedal processes the message, and it is not forwarded to MIDI plugins.
|
||||
<br/>
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, the message is sent to each MIDI plugin in the currently loaded Pipedal preset (channel filters permitting).)
|
||||
<br/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
<Divider/>
|
||||
<DialogActions style={{flexShrink: 1}}>
|
||||
<Button onClick={handleClose} variant="dialogPrimary" >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2024 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
export enum MidiDeviceSelection {
|
||||
DeviceAny = 0,
|
||||
DeviceNone = 1,
|
||||
DeviceList = 2
|
||||
}
|
||||
|
||||
export default class MidiChannelBinding {
|
||||
deserialize(input: any) : MidiChannelBinding {
|
||||
this.deviceSelection = input.deviceSelection;
|
||||
this.midiDevices = input.midiDevices;
|
||||
|
||||
this.channel = input.channel;
|
||||
this.acceptProgramChanges = input.acceptProgramChanges;
|
||||
this.acceptCommonMessages = input.acceptCommonMessages;
|
||||
return this;
|
||||
}
|
||||
|
||||
clone() { return new MidiChannelBinding().deserialize(this);}
|
||||
|
||||
deviceSelection: number = MidiDeviceSelection.DeviceAny as number;
|
||||
midiDevices: string[] = [];
|
||||
channel: number = -1;
|
||||
acceptProgramChanges: boolean = true;
|
||||
acceptCommonMessages: boolean = true;
|
||||
|
||||
|
||||
static CreateMissingValue() : MidiChannelBinding {
|
||||
let result = new MidiChannelBinding();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2024 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Component } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { pluginControlStyles } from './PluginControl';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import { ReactComponent as MidiIcon } from "./svg/ic_midi.svg";
|
||||
import MidiChannelBindingDialog from './MidiChannelBindingDialog';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
|
||||
|
||||
export interface MidiChannelBindingControlProps extends WithStyles<typeof pluginControlStyles> {
|
||||
midiChannelBinding: MidiChannelBinding
|
||||
theme: Theme;
|
||||
onChange: (result: MidiChannelBinding) => void;
|
||||
}
|
||||
type MidiChannelBindingControlState = {
|
||||
dialogOpen: boolean;
|
||||
};
|
||||
|
||||
const MidiChannelBindingControl =
|
||||
withStyles(pluginControlStyles, { withTheme: true })(
|
||||
class extends Component<MidiChannelBindingControlProps, MidiChannelBindingControlState> {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: MidiChannelBindingControlProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dialogOpen: false
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let item_width = 80;
|
||||
return (
|
||||
<div
|
||||
className={classes.controlFrame}
|
||||
style={{ width: item_width }}
|
||||
>
|
||||
{/* TITLE SECTION */}
|
||||
<div className={this.props.classes.titleSection}
|
||||
style={
|
||||
{
|
||||
alignSelf: "stretch"
|
||||
|
||||
}}>
|
||||
<Tooltip title={(
|
||||
<React.Fragment>
|
||||
<Typography variant="caption">title</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption">Controls which MIDI messages get forward to the instrument.</Typography>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
placement="top-start" arrow enterDelay={1500} enterNextDelay={1500}
|
||||
>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%",
|
||||
textAlign: "center"
|
||||
}}> MIDI</Typography>
|
||||
</Tooltip>
|
||||
|
||||
</div>
|
||||
{/* CONTROL SECTION */}
|
||||
|
||||
<div className={this.props.classes.midSection}>
|
||||
<ButtonBase style={{ width: 48, height: 48, borderRadius: 9999 }}
|
||||
onClick={()=> {
|
||||
this.setState({dialogOpen: true});
|
||||
}}
|
||||
sx={{
|
||||
':hover': {
|
||||
bgcolor: 'action.hover', // theme.palette.primary.main
|
||||
color: 'white',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MidiIcon fill={this.props.theme.palette.text.secondary} style={{ width: 36, height: 36 }} />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
<div className={this.props.classes.editSection}
|
||||
style={{ display: "flex", flexFlow: "column nowrap", justifyContent: "center" }}
|
||||
>
|
||||
<Typography variant="caption" display="block" noWrap
|
||||
style={{ width: "100%", textAlign: "center" }}
|
||||
>OMNI</Typography>
|
||||
</div>
|
||||
{this.state.dialogOpen&&(
|
||||
<MidiChannelBindingDialog
|
||||
open={this.state.dialogOpen}
|
||||
midiChannelBinding={this.props.midiChannelBinding}
|
||||
onClose={() =>{ this.setState({dialogOpen: false});}}
|
||||
onChanged={(midiChannelBinding: MidiChannelBinding)=> {}}
|
||||
midiDevices={[]}
|
||||
|
||||
/>
|
||||
|
||||
)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default MidiChannelBindingControl;
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// Copyright (c) 2024 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { useState } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import ChannelBindingHelpDialog from './ChannelBindingsHelpDialog';
|
||||
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo';
|
||||
import DialogEx from './DialogEx';
|
||||
import MidiChannelBinding, { MidiDeviceSelection } from './MidiChannelBinding';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { styled, Theme } from '@mui/material/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
|
||||
|
||||
export interface MidiChannelBindingDialogProps {
|
||||
open: boolean;
|
||||
midiChannelBinding: MidiChannelBinding;
|
||||
onClose: () => void;
|
||||
onChanged: (midiChannelBinding: MidiChannelBinding) => void;
|
||||
midiDevices: AlsaMidiDeviceInfo[];
|
||||
}
|
||||
|
||||
|
||||
function isChecked(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo): boolean {
|
||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||
if (selectedChannels[i].equals(channel)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function addPort(availableChannels: AlsaMidiDeviceInfo[], selectedChannels: AlsaMidiDeviceInfo[], newChannel: AlsaMidiDeviceInfo)
|
||||
: AlsaMidiDeviceInfo[] {
|
||||
let result: AlsaMidiDeviceInfo[] = [];
|
||||
for (let i = 0; i < availableChannels.length; ++i) {
|
||||
let channel = availableChannels[i];
|
||||
if (isChecked(selectedChannels, channel) || channel.equals(newChannel)) {
|
||||
result.push(channel);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function removePort(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo)
|
||||
: AlsaMidiDeviceInfo[] {
|
||||
let result: AlsaMidiDeviceInfo[] = [];
|
||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||
if (!selectedChannels[i].equals(channel)) {
|
||||
result.push(selectedChannels[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const usesStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
icon: {
|
||||
fill: theme.palette.text.primary,
|
||||
opacity: 0.6
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
function MidiChannelBindingDialog(props: MidiChannelBindingDialogProps) {
|
||||
const classes = usesStyles();
|
||||
const { onClose, midiChannelBinding, open, onChanged } = props;
|
||||
|
||||
const [currentChannel, setCurrentChannel] = useState(midiChannelBinding.channel);
|
||||
const [allowProgramChanges, setAllowProgramChanges] = useState(midiChannelBinding.acceptCommonMessages);
|
||||
const [helpDialog, setHelpDialog] = useState(false);
|
||||
|
||||
const model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||
|
||||
const handleClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
const handleOk = (): void => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const generateDeviceSelect = () => {
|
||||
let result: React.ReactElement[] = [
|
||||
(<MenuItem key={0} value={0}>Any</MenuItem>),
|
||||
(<MenuItem key={1} value={1}>None</MenuItem>)
|
||||
];
|
||||
let i = 2;
|
||||
for (let midiDevice of model.jackConfiguration.get().inputMidiDevices) {
|
||||
result.push((<MenuItem key={i} value={i}>{midiDevice.description}</MenuItem>));
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getDefaultDeviceSelect = (): number => {
|
||||
if (midiChannelBinding.deviceSelection === MidiDeviceSelection.DeviceAny as number) {
|
||||
return 0;
|
||||
}
|
||||
if (midiChannelBinding.deviceSelection === MidiDeviceSelection.DeviceNone as number) {
|
||||
return 1;
|
||||
}
|
||||
if (midiChannelBinding.midiDevices.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
let selectedDevice = midiChannelBinding.midiDevices[0];
|
||||
|
||||
let ix = 2;
|
||||
for (let midiDevice of model.jackConfiguration.get().inputMidiDevices) {
|
||||
if (midiDevice.name === selectedDevice) {
|
||||
return ix;
|
||||
}
|
||||
++ix;
|
||||
}
|
||||
return 0; // any.
|
||||
}
|
||||
|
||||
const [currentDeviceSelect, setCurrentDeviceSelect] = useState(getDefaultDeviceSelect())
|
||||
|
||||
|
||||
const handleDeviceSelecChange = (event: SelectChangeEvent) => {
|
||||
setCurrentDeviceSelect(parseInt(event.target.value));
|
||||
};
|
||||
const handleChannelChange = (event: SelectChangeEvent) => {
|
||||
setCurrentChannel(parseInt(event.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="midiChannelBinding" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
||||
onEnterKey={()=>{}} fullWidth maxWidth="xs"
|
||||
>
|
||||
<DialogTitle id="simple-dialog-title">MIDI Channel Filters</DialogTitle>
|
||||
<DialogContent>
|
||||
<div style={{
|
||||
display: "grid", alignItems: "center", columnGap: 24, rowGap: 24,
|
||||
gridTemplateColumns: "1fr"
|
||||
}}>
|
||||
<div >
|
||||
<Typography display="block" variant="caption">MIDI Devices</Typography>
|
||||
<Select variant='standard' value={currentDeviceSelect.toString()} fullWidth
|
||||
onChange={handleDeviceSelecChange} >
|
||||
{
|
||||
generateDeviceSelect()
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography display="block" variant="caption">Channels</Typography>
|
||||
<Select variant='standard' label="Channels" value={currentChannel.toString()} fullWidth
|
||||
onChange={handleChannelChange}>
|
||||
<MenuItem value={-1}>Omni</MenuItem>
|
||||
<MenuItem value={0}>Channel 1</MenuItem>
|
||||
<MenuItem value={1}>Channel 2</MenuItem>
|
||||
<MenuItem value={2}>Channel 3</MenuItem>
|
||||
<MenuItem value={3}>Channel 4</MenuItem>
|
||||
<MenuItem value={4}>Channel 5</MenuItem>
|
||||
<MenuItem value={5}>Channel 6</MenuItem>
|
||||
<MenuItem value={6}>Channel 7</MenuItem>
|
||||
<MenuItem value={7}>Channel 8</MenuItem>
|
||||
<MenuItem value={8}>Channel 9</MenuItem>
|
||||
<MenuItem value={9}>Channel 10</MenuItem>
|
||||
<MenuItem value={10}>Channel 11</MenuItem>
|
||||
<MenuItem value={11}>Channel 12</MenuItem>
|
||||
<MenuItem value={12}>Channel 13</MenuItem>
|
||||
<MenuItem value={13}>Channel 14</MenuItem>
|
||||
<MenuItem value={14}>Channel 15</MenuItem>
|
||||
<MenuItem value={15}>Channel 16</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "center" }}>
|
||||
<FormControlLabel control={
|
||||
<Checkbox checked={allowProgramChanges}
|
||||
onChange={(event) => {
|
||||
setAllowProgramChanges(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
} label="Allow Program Changes"
|
||||
/>
|
||||
{false&&( // wait until the implementation stabilizes before exposing the help dialog.
|
||||
<IconButton
|
||||
onClick={() => { setHelpDialog(true); }}
|
||||
size="large">
|
||||
<InfoOutlinedIcon color='inherit'
|
||||
className={classes.icon}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleOk} variant="dialogPrimary" >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
<ChannelBindingHelpDialog open={helpDialog} onClose={()=>setHelpDialog(false)}
|
||||
/>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
|
||||
export default MidiChannelBindingDialog;
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { PiPedalArgumentError } from './PiPedalError';
|
||||
import MidiBinding from './MidiBinding';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
|
||||
|
||||
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
||||
@@ -64,6 +65,12 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
this.pluginName = input.pluginName;
|
||||
this.isEnabled = input.isEnabled;
|
||||
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
|
||||
if (input.midiChannelBinding)
|
||||
{
|
||||
this.midiChannelBinding = input.midiChannelBinding;
|
||||
} else {
|
||||
this.midiChannelBinding = null;
|
||||
}
|
||||
|
||||
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||
this.vstState = input.vstState ?? "";
|
||||
@@ -197,6 +204,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
pluginName?: string;
|
||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||
midiBindings: MidiBinding[] = [];
|
||||
midiChannelBinding: MidiChannelBinding | null = null;
|
||||
vstState: string = "";
|
||||
stateUpdateCount: number = 0;
|
||||
lv2State: [boolean,any] = [false,{}];
|
||||
|
||||
@@ -52,7 +52,7 @@ export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
export const pluginControlStyles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
position: "relative",
|
||||
margin: "12px"
|
||||
@@ -90,13 +90,13 @@ const styles = (theme: Theme) => createStyles({
|
||||
flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center"
|
||||
},
|
||||
editSection: {
|
||||
flex: "0 0 0", position: "relative", width: 60, height: 28,minHeight: 28
|
||||
flex: "0 0 0", display: "flex", flexFlow: "column nowrap", justifyContent: "center",position: "relative", width: 60, height: 28,minHeight: 28
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
export interface PluginControlProps extends WithStyles<typeof styles> {
|
||||
export interface PluginControlProps extends WithStyles<typeof pluginControlStyles> {
|
||||
uiControl?: UiControl;
|
||||
instanceId: number;
|
||||
value: number;
|
||||
@@ -110,7 +110,7 @@ type PluginControlState = {
|
||||
};
|
||||
|
||||
const PluginControl =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
withStyles(pluginControlStyles, { withTheme: true })(
|
||||
class extends Component<PluginControlProps, PluginControlState> {
|
||||
|
||||
frameRef: React.RefObject<HTMLDivElement>;
|
||||
@@ -802,8 +802,8 @@ const PluginControl =
|
||||
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
|
||||
(
|
||||
(isAbSwitch) ? (
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%", textAlign: "center"
|
||||
<Typography variant="caption" display="block" textAlign="center" noWrap style={{
|
||||
width: "100%"
|
||||
}}> {switchText} </Typography>
|
||||
) : (
|
||||
<div>
|
||||
|
||||
+100
-31
@@ -41,6 +41,8 @@ import PluginOutputControl from './PluginOutputControl';
|
||||
import Units from './Units';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import MidiChannelBindingControl from './MidiChannelBindingControl';
|
||||
import MidiChannelBinding from './MidiChannelBinding';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 110 };
|
||||
@@ -90,35 +92,57 @@ const styles = (theme: Theme) => createStyles({
|
||||
paddingTop: "8px",
|
||||
paddingBottom: "0px",
|
||||
height: "100%",
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden"
|
||||
},
|
||||
frameScrollLandscape: {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
left: 0, top: 0, right: 0, bottom:0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "nowrap",
|
||||
paddingTop: "0px",
|
||||
paddingBottom: "0px",
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden"
|
||||
},
|
||||
frameScrollPortrait: {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
left: 0, top: 0, right: 0, bottom:0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "nowrap",
|
||||
paddingTop: "0px",
|
||||
paddingBottom: "0px",
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto"
|
||||
},
|
||||
vuMeterL: {
|
||||
position: "fixed",
|
||||
position: "absolute",
|
||||
left: 0, top: 0,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 24,
|
||||
left: 0,
|
||||
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
},
|
||||
vuMeterR: {
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
marginRight: 22,
|
||||
position: "absolute",
|
||||
right: 0, top: 0,
|
||||
marginRight: 20, // has to potentially clear a scrollbar.
|
||||
paddingLeft: 4,
|
||||
paddingBottom: 24,
|
||||
paddingBottom: 12,
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
},
|
||||
vuMeterRLandscape: {
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
paddingRight: 22,
|
||||
position: "absolute",
|
||||
right: 0, top: 0,
|
||||
paddingRight: 6,
|
||||
paddingLeft: 12,
|
||||
paddingBottom: 24,
|
||||
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||
background: theme.mainBackground,
|
||||
zIndex: 3
|
||||
|
||||
@@ -126,21 +150,28 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
normalGrid: {
|
||||
position: "relative",
|
||||
paddingLeft: 25,
|
||||
paddingRight: 34,
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 8,
|
||||
flex: "1 1 auto",
|
||||
display: "flex", flexDirection: "row", flexWrap: "wrap",
|
||||
justifyContent: "flex-start", alignItems: "flex_start",
|
||||
rowGap: 10
|
||||
rowGap: 14,
|
||||
height: "fit-content"
|
||||
|
||||
|
||||
|
||||
},
|
||||
landscapeGrid: {
|
||||
paddingLeft: 40,
|
||||
// marginRight: 40, : bug in chrome layout engine wrt/ right margin/padding. See the spacer div added after all controls in render() with provides the same effect.
|
||||
paddingLeft: 40, paddingRight: 40, paddingTop: 8,
|
||||
// marginRight: 40, : bug in chrome layout engine wrt/ right margin/padding.
|
||||
// See the spacer div added after all controls in render() with provides the same effect.
|
||||
display: "flex", flexDirection: "row", flexWrap: "nowrap",
|
||||
justifyContent: "flex-start", alignItems: "flex_start",
|
||||
flex: "0 0 auto"
|
||||
justifyContent: "flex-start", alignItems: "flex-start",
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden",
|
||||
flex: "0 0 auto",
|
||||
width: "fit-content"
|
||||
},
|
||||
portgroupControlPadding: {
|
||||
flex: "0 0 auto",
|
||||
@@ -198,10 +229,12 @@ const styles = (theme: Theme) => createStyles({
|
||||
border: "2pt #AAA solid",
|
||||
borderRadius: 8,
|
||||
elevation: 12,
|
||||
display: "flex",
|
||||
display: "inline-flex",
|
||||
textOverflow: "ellipsis",
|
||||
flexDirection: "row", flexWrap: "nowrap",
|
||||
flex: "0 0 auto"
|
||||
flex: "0 0 auto",
|
||||
width: "fit-content",
|
||||
minWidth: "max-content"
|
||||
},
|
||||
portGroupTitle: {
|
||||
position: "absolute",
|
||||
@@ -219,6 +252,13 @@ const styles = (theme: Theme) => createStyles({
|
||||
flexDirection: "row", flexWrap: "wrap",
|
||||
paddingTop: 6,
|
||||
paddingBottom: 8
|
||||
},
|
||||
portGroupControlsLandscape: {
|
||||
display: "flex",
|
||||
flexFlow: "row nowrap",
|
||||
width: "fit-content",
|
||||
paddingTop: 6,
|
||||
paddingBottom: 8
|
||||
}
|
||||
|
||||
|
||||
@@ -600,7 +640,8 @@ const PluginControlView =
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={classes.portGroupControls} >
|
||||
<div className={
|
||||
this.state.landscapeGrid ? classes.portGroupControlsLandscape : classes.portGroupControls} >
|
||||
{
|
||||
controls
|
||||
}
|
||||
@@ -626,6 +667,20 @@ const PluginControlView =
|
||||
static endPluginInfo: UiPlugin =
|
||||
makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI);
|
||||
|
||||
midiBindingControl(pedalboardItem: PedalboardItem): ReactNode {
|
||||
if (!pedalboardItem.midiChannelBinding) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<MidiChannelBindingControl key="channelBindingCtl" midiChannelBinding={pedalboardItem.midiChannelBinding}
|
||||
onChange={(result)=> {
|
||||
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
this.controlKeyIndex = 0;
|
||||
@@ -663,6 +718,7 @@ const PluginControlView =
|
||||
|
||||
|
||||
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
|
||||
let scrollClass = this.state.landscapeGrid ? classes.frameScrollLandscape : classes.frameScrollPortrait;
|
||||
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||
let controlNodes: ControlNodes;
|
||||
|
||||
@@ -675,6 +731,16 @@ const PluginControlView =
|
||||
|
||||
let nodes = this.controlNodesToNodes(controlNodes);
|
||||
|
||||
if (plugin.has_midi_input && !pedalboardItem.midiChannelBinding)
|
||||
{
|
||||
pedalboardItem.midiChannelBinding = MidiChannelBinding.CreateMissingValue();
|
||||
|
||||
}
|
||||
if (pedalboardItem.midiChannelBinding) {
|
||||
nodes.push(this.midiBindingControl(pedalboardItem));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={classes.frame}>
|
||||
<div className={classes.vuMeterL}>
|
||||
@@ -683,16 +749,19 @@ const PluginControlView =
|
||||
<div className={vuMeterRClass}>
|
||||
<VuMeter displayText={true} display="output" instanceId={pedalboardItem.instanceId} />
|
||||
</div>
|
||||
<div className={gridClass} >
|
||||
{
|
||||
nodes
|
||||
}
|
||||
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
||||
{
|
||||
(!this.state.landscapeGrid) && (
|
||||
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
||||
)
|
||||
}
|
||||
<div className={scrollClass}>
|
||||
<div className={gridClass} >
|
||||
{
|
||||
nodes
|
||||
}
|
||||
{/* Extra space to allow scrolling right to the end in lascape especially */}
|
||||
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
||||
{
|
||||
(!this.state.landscapeGrid) && (
|
||||
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showFileDialog && (
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ import { ReactComponent as FxEmptyIcon } from './svg/fx_empty.svg';
|
||||
import { ReactComponent as FxTerminalIcon } from './svg/fx_terminal.svg';
|
||||
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { Color } from '@mui/material';
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-2
@@ -1862,8 +1862,9 @@ public:
|
||||
VuUpdate v;
|
||||
v.instanceId_ = instanceId;
|
||||
// Display mono VUs if a stereo device is being fed identical L/R inputs.
|
||||
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
|
||||
v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2
|
||||
&& effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2;
|
||||
|
||||
vuConfig->vuUpdateWorkingData.push_back(v);
|
||||
vuConfig->vuUpdateResponseData.push_back(v);
|
||||
|
||||
+4
-1
@@ -41,7 +41,7 @@ namespace macaron
|
||||
public:
|
||||
static std::string Encode(const std::vector<uint8_t> &data)
|
||||
{
|
||||
return Encode(data.size(),&(data[0]));
|
||||
return Encode(data.size(),data.data());
|
||||
}
|
||||
static std::string Encode(size_t size, const uint8_t *data)
|
||||
{
|
||||
@@ -112,6 +112,9 @@ namespace macaron
|
||||
if (in_len % 4 != 0)
|
||||
throw std::invalid_argument("Input data size is not a multiple of 4");
|
||||
|
||||
if (in_len < 4) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
size_t out_len = in_len / 4 * 3;
|
||||
if (input[in_len - 1] == '=')
|
||||
out_len--;
|
||||
|
||||
+5
-2
@@ -49,8 +49,11 @@ namespace pipedal {
|
||||
virtual void SetBypass(bool enable) = 0;
|
||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||
|
||||
virtual int GetNumberOfInputAudioPorts() const = 0;
|
||||
virtual int GetNumberOfOutputAudioPorts() const = 0;
|
||||
virtual int GetNumberOfInputAudioPorts() const = 0; // as declared
|
||||
virtual int GetNumberOfOutputAudioPorts() const = 0; // as declared.
|
||||
|
||||
virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
|
||||
virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
|
||||
virtual float *GetAudioInputBuffer(int index) const = 0;
|
||||
virtual float *GetAudioOutputBuffer(int index) const = 0;
|
||||
virtual void ResetAtomBuffers() = 0;
|
||||
|
||||
+4
-4
@@ -414,12 +414,12 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
||||
|
||||
void Lv2Effect::SetAudioInputBuffer(float *left)
|
||||
{
|
||||
if (GetNumberOfInputAudioPorts() > 1)
|
||||
if (GetNumberOfInputAudioBuffers() > 1)
|
||||
{
|
||||
SetAudioInputBuffer(0, left);
|
||||
SetAudioInputBuffer(1, left);
|
||||
}
|
||||
else if (GetNumberOfInputAudioPorts() != 0) /// yyx MIXING!
|
||||
else if (GetNumberOfInputAudioBuffers() != 0)
|
||||
{
|
||||
SetAudioInputBuffer(0, left);
|
||||
}
|
||||
@@ -427,11 +427,11 @@ void Lv2Effect::SetAudioInputBuffer(float *left)
|
||||
|
||||
void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
|
||||
{
|
||||
if (GetNumberOfInputAudioPorts() == 1)
|
||||
if (GetNumberOfInputAudioBuffers() == 1)
|
||||
{
|
||||
SetAudioInputBuffer(0, left);
|
||||
}
|
||||
else if (GetNumberOfInputAudioPorts() > 1)
|
||||
else if (GetNumberOfInputAudioBuffers() > 1)
|
||||
{
|
||||
SetAudioInputBuffer(0, left);
|
||||
SetAudioInputBuffer(1, right);
|
||||
|
||||
@@ -252,16 +252,22 @@ namespace pipedal
|
||||
virtual uint64_t GetInstanceId() const { return instanceId; }
|
||||
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
|
||||
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
|
||||
|
||||
virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
|
||||
|
||||
virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
|
||||
virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
|
||||
virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
|
||||
|
||||
|
||||
|
||||
virtual int GetNumberOfInputAudioBuffers() const { return this->inputAudioBuffers.size(); }
|
||||
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); }
|
||||
|
||||
virtual void SetAudioInputBuffer(int index, float *buffer);
|
||||
virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
|
||||
|
||||
|
||||
virtual void SetAudioInputBuffer(float *buffer);
|
||||
virtual void SetAudioInputBuffers(float *left, float *right);
|
||||
|
||||
|
||||
+10
-32
@@ -136,11 +136,11 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
|
||||
if (inputBuffers.size() == 1)
|
||||
{
|
||||
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
|
||||
if (pLv2Effect->GetNumberOfInputAudioBuffers() == 1)
|
||||
{
|
||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||
}
|
||||
else
|
||||
else if (pLv2Effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||
{
|
||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[0]);
|
||||
@@ -148,13 +148,13 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
|
||||
if (pLv2Effect->GetNumberOfInputAudioBuffers() == 1)
|
||||
{
|
||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||
|
||||
auto inputBuffer = inputBuffers[0];
|
||||
}
|
||||
else
|
||||
else if (pLv2Effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||
{
|
||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[1]);
|
||||
@@ -178,37 +178,15 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
|
||||
std::vector<float *> effectOutput;
|
||||
|
||||
#ifdef RECYCLE_AUDIO_BUFFERS
|
||||
// can't do this anymore if we're going to do pop-less bypbass.
|
||||
if (pEffect->GetNumberOfOutputAudioPorts() == 1)
|
||||
{
|
||||
float *pLeft = inputBuffers[0];
|
||||
effectOutput.push_back(pLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inputBuffers.size() == 1)
|
||||
{
|
||||
effectOutput.push_back(inputBuffers[0]);
|
||||
effectOutput.push_back(CreateNewAudioBuffer());
|
||||
}
|
||||
else
|
||||
{
|
||||
effectOutput.push_back(inputBuffers[0]);
|
||||
effectOutput.push_back(inputBuffers[1]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (pEffect->GetNumberOfOutputAudioPorts() == 1)
|
||||
if (pEffect->GetNumberOfOutputAudioBuffers() == 1)
|
||||
{
|
||||
effectOutput.push_back(CreateNewAudioBuffer());
|
||||
}
|
||||
else
|
||||
else if (pEffect->GetNumberOfOutputAudioBuffers() >= 2)
|
||||
{
|
||||
effectOutput.push_back(CreateNewAudioBuffer());
|
||||
effectOutput.push_back(CreateNewAudioBuffer());
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i < effectOutput.size(); ++i)
|
||||
{
|
||||
pEffect->SetAudioOutputBuffer(i, effectOutput[i]);
|
||||
@@ -475,21 +453,21 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
|
||||
{
|
||||
auto effect = this->realtimeEffects[index];
|
||||
|
||||
if (effect->GetNumberOfInputAudioPorts() == 1)
|
||||
if (effect->GetNumberOfInputAudioBuffers() == 1)
|
||||
{
|
||||
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
|
||||
}
|
||||
else
|
||||
else if (effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||
{
|
||||
pUpdate->AccumulateInputs(
|
||||
effect->GetAudioInputBuffer(0),
|
||||
effect->GetAudioInputBuffer(1), samples);
|
||||
}
|
||||
if (effect->GetNumberOfOutputAudioPorts() == 1)
|
||||
if (effect->GetNumberOfOutputAudioBuffers() == 1)
|
||||
{
|
||||
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
|
||||
}
|
||||
else
|
||||
else if (effect->GetNumberOfOutputAudioBuffers() >= 2)
|
||||
{
|
||||
pUpdate->AccumulateOutputs(
|
||||
effect->GetAudioOutputBuffer(0),
|
||||
|
||||
+30
-1
@@ -17,12 +17,41 @@
|
||||
// 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.
|
||||
|
||||
#include "pch.h"
|
||||
#include "MidiBinding.hpp"
|
||||
#include "MapFeature.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
MidiChannelBinding MidiChannelBinding::DefaultForMissingValue()
|
||||
{
|
||||
MidiChannelBinding result {};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void MidiChannelBinding::Prepare(MapFeature &mapFeature)
|
||||
{
|
||||
midiDeviceUrids.resize(0);
|
||||
midiDeviceUrids.reserve(midiDevices_.size());
|
||||
|
||||
for (const auto&midiDevice: midiDevices_)
|
||||
{
|
||||
midiDeviceUrids.push_back(mapFeature.GetUrid(midiDevice.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(MidiChannelBinding)
|
||||
JSON_MAP_REFERENCE(MidiChannelBinding,deviceSelection)
|
||||
JSON_MAP_REFERENCE(MidiChannelBinding,midiDevices)
|
||||
JSON_MAP_REFERENCE(MidiChannelBinding,channel)
|
||||
JSON_MAP_REFERENCE(MidiChannelBinding,acceptProgramChanges)
|
||||
JSON_MAP_REFERENCE(MidiChannelBinding,acceptCommonMessages)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(MidiBinding)
|
||||
JSON_MAP_REFERENCE(MidiBinding,channel)
|
||||
JSON_MAP_REFERENCE(MidiBinding,symbol)
|
||||
|
||||
@@ -20,10 +20,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <lv2/urid/urid.h>
|
||||
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class MapFeature;
|
||||
|
||||
#define GETTER_SETTER_REF(name) \
|
||||
const decltype(name##_)& name() const { return name##_;} \
|
||||
@@ -33,6 +39,9 @@ namespace pipedal {
|
||||
decltype(name##_)& name() { return name##_;} \
|
||||
const decltype(name##_)& name() const { return name##_;}
|
||||
|
||||
//void name(decltype(const name##_) &value) { name##_ = value; }
|
||||
//void name(decltype(const name##_) &&value) { name##_ = std::move(value); }
|
||||
|
||||
|
||||
|
||||
#define GETTER_SETTER(name) \
|
||||
@@ -102,6 +111,85 @@ public:
|
||||
|
||||
};
|
||||
|
||||
|
||||
enum class MidiDeviceSelection {
|
||||
DeviceAny = 0,
|
||||
DeviceNone = 1,
|
||||
DeviceList = 2
|
||||
};
|
||||
|
||||
class MidiChannelBinding {
|
||||
public:
|
||||
static constexpr std::int32_t CHANNEL_OMNI = -1;
|
||||
private:
|
||||
int32_t deviceSelection_ = (int32_t)(MidiDeviceSelection::DeviceAny);
|
||||
|
||||
std::vector<std::string> midiDevices_;
|
||||
|
||||
std::vector<LV2_URID> midiDeviceUrids;
|
||||
|
||||
std::int32_t channel_ = CHANNEL_OMNI;
|
||||
bool acceptProgramChanges_ = true;
|
||||
bool acceptCommonMessages_ = true;
|
||||
public:
|
||||
MidiDeviceSelection deviceSelection() const { return (MidiDeviceSelection)deviceSelection_; }
|
||||
void deviceSelection(MidiDeviceSelection value) { deviceSelection_ = (int32_t)value; }
|
||||
GETTER_SETTER_VEC(midiDevices);
|
||||
GETTER_SETTER(channel);
|
||||
GETTER_SETTER(acceptProgramChanges);
|
||||
GETTER_SETTER(acceptCommonMessages);
|
||||
|
||||
void Prepare(MapFeature &mapFeature);
|
||||
|
||||
static MidiChannelBinding DefaultForMissingValue();
|
||||
|
||||
bool WantsMidiMessage(uint8_t midi_cc0,uint8_t midi_cc1, uint8_t midi_cc2);
|
||||
bool WantProgramChange(uint8_t midi_cc0,uint8_t midi_cc1);
|
||||
|
||||
// must call Prepare first.
|
||||
bool WantsDevice(LV2_URID deviceUrid);
|
||||
|
||||
|
||||
DECLARE_JSON_MAP(MidiChannelBinding);
|
||||
|
||||
};
|
||||
///////////////////////////////////////
|
||||
|
||||
inline bool MidiChannelBinding::WantsMidiMessage(uint8_t midi_cc0,uint8_t midi_cc1, uint8_t midi_cc2)
|
||||
{
|
||||
if (midi_cc0 < 0xF0)
|
||||
{
|
||||
if (channel_ < 0) return true;
|
||||
|
||||
return midi_cc0 & 0x0F == channel_;
|
||||
} else {
|
||||
return acceptCommonMessages_;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool MidiChannelBinding::WantProgramChange(uint8_t midi_cc0,uint8_t midi_cc1)
|
||||
{
|
||||
if (!acceptProgramChanges_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (channel_ < 0) return true;
|
||||
return midi_cc0 & 0x0F == channel_;
|
||||
}
|
||||
|
||||
inline bool MidiChannelBinding::WantsDevice(LV2_URID deviceUrid)
|
||||
{
|
||||
if (this->midiDeviceUrids.size() == 0) return true;
|
||||
for (LV2_URID urid: midiDeviceUrids)
|
||||
{
|
||||
if (urid == deviceUrid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
|
||||
#undef GETTER_SETTER_REF
|
||||
#undef GETTER_SETTER_VEC
|
||||
#undef GETTER_SETTER
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
std::vector<PedalboardItem> topChain_;
|
||||
std::vector<PedalboardItem> bottomChain_;
|
||||
std::vector<MidiBinding> midiBindings_;
|
||||
std::optional<MidiChannelBinding> midiChannelBinding_;
|
||||
std::string vstState_;
|
||||
uint32_t stateUpdateCount_ = 0;
|
||||
Lv2PluginState lv2State_;
|
||||
@@ -122,6 +123,7 @@ public:
|
||||
GETTER_SETTER_VEC(topChain)
|
||||
GETTER_SETTER_VEC(bottomChain)
|
||||
GETTER_SETTER_VEC(midiBindings)
|
||||
GETTER_SETTER_REF(midiChannelBinding)
|
||||
GETTER_SETTER(stateUpdateCount)
|
||||
GETTER_SETTER_REF(lv2State)
|
||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||
|
||||
@@ -1876,6 +1876,18 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
|
||||
}
|
||||
if (pPlugin)
|
||||
{
|
||||
if (pPlugin->hasMidiInput())
|
||||
{
|
||||
if (!pedalboardItem->midiChannelBinding())
|
||||
{
|
||||
pedalboardItem->midiChannelBinding(MidiChannelBinding::DefaultForMissingValue());
|
||||
}
|
||||
} else {
|
||||
if (pedalboardItem->midiChannelBinding())
|
||||
{
|
||||
pedalboardItem->midiChannelBinding(std::optional<MidiChannelBinding>()); // clear it.
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < pPlugin->ports().size(); ++i)
|
||||
{
|
||||
auto port = pPlugin->ports()[i];
|
||||
|
||||
+4
-3
@@ -495,6 +495,10 @@ void PluginHost::Load(const char *lv2Path)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio i/o.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
|
||||
} else if (info.audio_inputs() == 0)
|
||||
{
|
||||
// temporarily disable this feature.
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
#elif SUPPORT_MIDI
|
||||
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
||||
@@ -520,9 +524,6 @@ void PluginHost::Load(const char *lv2Path)
|
||||
if (info.audio_inputs() == 0) {
|
||||
Lv2Log::debug("************* ZERO INPUTS: %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
if (info.audio_outputs() == 0) {
|
||||
Lv2Log::debug("************* ZERO OUTPUTS: %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
ui_plugins_.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +432,7 @@ namespace pipedal
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool hasMidiOutput() const
|
||||
{
|
||||
|
||||
@@ -381,10 +381,15 @@ namespace pipedal
|
||||
{
|
||||
return (int)this->inputs.size();
|
||||
}
|
||||
virtual int GetNumberOfInputAudioBuffers() const { return this->inputs.size(); }
|
||||
|
||||
virtual float *GetAudioInputBuffer(int index) const
|
||||
{
|
||||
return inputs[index];
|
||||
}
|
||||
|
||||
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputBuffers.size(); }
|
||||
|
||||
virtual float *GetAudioOutputBuffer(int index) const
|
||||
{
|
||||
return this->outputBuffers[index];
|
||||
|
||||
@@ -199,7 +199,7 @@ const void *StateInterface::StateRetrieveFunction(
|
||||
*size = entry.value_.size();
|
||||
*type = map.GetUrid(entry.atomType_.c_str());
|
||||
*flags = entry.flags_;
|
||||
return (void*)&entry.value_[0];
|
||||
return entry.value_.data();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -126,8 +126,21 @@ namespace pipedal
|
||||
{
|
||||
return info.pluginInfo_.audio_outputs();
|
||||
}
|
||||
|
||||
virtual int GetNumberOfInputAudioBuffers() const { return buffers.inputs.size(); }
|
||||
virtual int GetNumberOfOutputAudioPorts() const {return buffers.outputs.size(); }
|
||||
|
||||
virtual float *GetAudioInputBuffer(int index) const { return buffers.inputs[index]; }
|
||||
virtual float *GetAudioOutputBuffer(int index) const { return buffers.outputs[index]; }
|
||||
|
||||
virtual int GetNumberOfInputAudioBuffers() const {
|
||||
return buffers.inputs.size();
|
||||
}
|
||||
virtual int GetNumberOfInputAudioBuffers() const {
|
||||
return buffers.outputs.size();
|
||||
}
|
||||
|
||||
|
||||
virtual void ResetAtomBuffers() {}
|
||||
virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent.
|
||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} // no vst equivalent.
|
||||
|
||||
Reference in New Issue
Block a user