MIDI plugins -- partial implemetnation, temporarily disabled.
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#define DECLARE_JSON_MAP(CLASSNAME) \
|
#define DECLARE_JSON_MAP(CLASSNAME) \
|
||||||
static pipedal::json_map::storage_type<CLASSNAME> jmap
|
static pipedal::json_map::storage_type<CLASSNAME> jmap
|
||||||
@@ -592,6 +593,17 @@ namespace pipedal
|
|||||||
write(obj.get());
|
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>
|
template <typename T>
|
||||||
void write(const std::weak_ptr<T> &obj)
|
void write(const std::weak_ptr<T> &obj)
|
||||||
{
|
{
|
||||||
@@ -883,6 +895,20 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <typename T>
|
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)
|
void read(std::weak_ptr<T> *pUniquePtr)
|
||||||
{
|
{
|
||||||
throw std::domain_error("Can't read std::weak_ptr");
|
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 { PiPedalArgumentError } from './PiPedalError';
|
||||||
import MidiBinding from './MidiBinding';
|
import MidiBinding from './MidiBinding';
|
||||||
|
import MidiChannelBinding from './MidiChannelBinding';
|
||||||
|
|
||||||
|
|
||||||
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
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.pluginName = input.pluginName;
|
||||||
this.isEnabled = input.isEnabled;
|
this.isEnabled = input.isEnabled;
|
||||||
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
|
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.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||||
this.vstState = input.vstState ?? "";
|
this.vstState = input.vstState ?? "";
|
||||||
@@ -197,6 +204,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
pluginName?: string;
|
pluginName?: string;
|
||||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||||
midiBindings: MidiBinding[] = [];
|
midiBindings: MidiBinding[] = [];
|
||||||
|
midiChannelBinding: MidiChannelBinding | null = null;
|
||||||
vstState: string = "";
|
vstState: string = "";
|
||||||
stateUpdateCount: number = 0;
|
stateUpdateCount: number = 0;
|
||||||
lv2State: [boolean,any] = [false,{}];
|
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: {
|
frame: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
margin: "12px"
|
margin: "12px"
|
||||||
@@ -90,13 +90,13 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center"
|
flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center"
|
||||||
},
|
},
|
||||||
editSection: {
|
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;
|
uiControl?: UiControl;
|
||||||
instanceId: number;
|
instanceId: number;
|
||||||
value: number;
|
value: number;
|
||||||
@@ -110,7 +110,7 @@ type PluginControlState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PluginControl =
|
const PluginControl =
|
||||||
withStyles(styles, { withTheme: true })(
|
withStyles(pluginControlStyles, { withTheme: true })(
|
||||||
class extends Component<PluginControlProps, PluginControlState> {
|
class extends Component<PluginControlProps, PluginControlState> {
|
||||||
|
|
||||||
frameRef: React.RefObject<HTMLDivElement>;
|
frameRef: React.RefObject<HTMLDivElement>;
|
||||||
@@ -802,8 +802,8 @@ const PluginControl =
|
|||||||
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
|
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
|
||||||
(
|
(
|
||||||
(isAbSwitch) ? (
|
(isAbSwitch) ? (
|
||||||
<Typography variant="caption" display="block" noWrap style={{
|
<Typography variant="caption" display="block" textAlign="center" noWrap style={{
|
||||||
width: "100%", textAlign: "center"
|
width: "100%"
|
||||||
}}> {switchText} </Typography>
|
}}> {switchText} </Typography>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+100
-31
@@ -41,6 +41,8 @@ import PluginOutputControl from './PluginOutputControl';
|
|||||||
import Units from './Units';
|
import Units from './Units';
|
||||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import MidiChannelBindingControl from './MidiChannelBindingControl';
|
||||||
|
import MidiChannelBinding from './MidiChannelBinding';
|
||||||
|
|
||||||
|
|
||||||
export const StandardItemSize = { width: 80, height: 110 };
|
export const StandardItemSize = { width: 80, height: 110 };
|
||||||
@@ -90,35 +92,57 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
paddingTop: "8px",
|
paddingTop: "8px",
|
||||||
paddingBottom: "0px",
|
paddingBottom: "0px",
|
||||||
height: "100%",
|
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",
|
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"
|
overflowY: "auto"
|
||||||
},
|
},
|
||||||
vuMeterL: {
|
vuMeterL: {
|
||||||
position: "fixed",
|
position: "absolute",
|
||||||
|
left: 0, top: 0,
|
||||||
paddingLeft: 6,
|
paddingLeft: 6,
|
||||||
paddingRight: 4,
|
paddingRight: 4,
|
||||||
paddingBottom: 24,
|
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||||
left: 0,
|
|
||||||
background: theme.mainBackground,
|
background: theme.mainBackground,
|
||||||
zIndex: 3
|
zIndex: 3
|
||||||
|
|
||||||
},
|
},
|
||||||
vuMeterR: {
|
vuMeterR: {
|
||||||
position: "fixed",
|
position: "absolute",
|
||||||
right: 0,
|
right: 0, top: 0,
|
||||||
marginRight: 22,
|
marginRight: 20, // has to potentially clear a scrollbar.
|
||||||
paddingLeft: 4,
|
paddingLeft: 4,
|
||||||
paddingBottom: 24,
|
paddingBottom: 12,
|
||||||
background: theme.mainBackground,
|
background: theme.mainBackground,
|
||||||
zIndex: 3
|
zIndex: 3
|
||||||
|
|
||||||
},
|
},
|
||||||
vuMeterRLandscape: {
|
vuMeterRLandscape: {
|
||||||
position: "fixed",
|
position: "absolute",
|
||||||
right: 0,
|
right: 0, top: 0,
|
||||||
paddingRight: 22,
|
paddingRight: 6,
|
||||||
paddingLeft: 12,
|
paddingLeft: 12,
|
||||||
paddingBottom: 24,
|
paddingBottom: 12, // cover bottom line of a portgroup in landscape.
|
||||||
background: theme.mainBackground,
|
background: theme.mainBackground,
|
||||||
zIndex: 3
|
zIndex: 3
|
||||||
|
|
||||||
@@ -126,21 +150,28 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
|
|
||||||
normalGrid: {
|
normalGrid: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
paddingLeft: 25,
|
paddingLeft: 30,
|
||||||
paddingRight: 34,
|
paddingRight: 30,
|
||||||
|
paddingTop: 8,
|
||||||
flex: "1 1 auto",
|
flex: "1 1 auto",
|
||||||
display: "flex", flexDirection: "row", flexWrap: "wrap",
|
display: "flex", flexDirection: "row", flexWrap: "wrap",
|
||||||
justifyContent: "flex-start", alignItems: "flex_start",
|
justifyContent: "flex-start", alignItems: "flex_start",
|
||||||
rowGap: 10
|
rowGap: 14,
|
||||||
|
height: "fit-content"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
landscapeGrid: {
|
landscapeGrid: {
|
||||||
paddingLeft: 40,
|
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.
|
// 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",
|
display: "flex", flexDirection: "row", flexWrap: "nowrap",
|
||||||
justifyContent: "flex-start", alignItems: "flex_start",
|
justifyContent: "flex-start", alignItems: "flex-start",
|
||||||
flex: "0 0 auto"
|
overflowX: "hidden",
|
||||||
|
overflowY: "hidden",
|
||||||
|
flex: "0 0 auto",
|
||||||
|
width: "fit-content"
|
||||||
},
|
},
|
||||||
portgroupControlPadding: {
|
portgroupControlPadding: {
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
@@ -198,10 +229,12 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
border: "2pt #AAA solid",
|
border: "2pt #AAA solid",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
elevation: 12,
|
elevation: 12,
|
||||||
display: "flex",
|
display: "inline-flex",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
flexDirection: "row", flexWrap: "nowrap",
|
flexDirection: "row", flexWrap: "nowrap",
|
||||||
flex: "0 0 auto"
|
flex: "0 0 auto",
|
||||||
|
width: "fit-content",
|
||||||
|
minWidth: "max-content"
|
||||||
},
|
},
|
||||||
portGroupTitle: {
|
portGroupTitle: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -219,6 +252,13 @@ const styles = (theme: Theme) => createStyles({
|
|||||||
flexDirection: "row", flexWrap: "wrap",
|
flexDirection: "row", flexWrap: "wrap",
|
||||||
paddingTop: 6,
|
paddingTop: 6,
|
||||||
paddingBottom: 8
|
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>
|
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.portGroupControls} >
|
<div className={
|
||||||
|
this.state.landscapeGrid ? classes.portGroupControlsLandscape : classes.portGroupControls} >
|
||||||
{
|
{
|
||||||
controls
|
controls
|
||||||
}
|
}
|
||||||
@@ -626,6 +667,20 @@ const PluginControlView =
|
|||||||
static endPluginInfo: UiPlugin =
|
static endPluginInfo: UiPlugin =
|
||||||
makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI);
|
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 {
|
render(): ReactNode {
|
||||||
this.controlKeyIndex = 0;
|
this.controlKeyIndex = 0;
|
||||||
@@ -663,6 +718,7 @@ const PluginControlView =
|
|||||||
|
|
||||||
|
|
||||||
let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid;
|
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 vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||||
let controlNodes: ControlNodes;
|
let controlNodes: ControlNodes;
|
||||||
|
|
||||||
@@ -675,6 +731,16 @@ const PluginControlView =
|
|||||||
|
|
||||||
let nodes = this.controlNodesToNodes(controlNodes);
|
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 (
|
return (
|
||||||
<div className={classes.frame}>
|
<div className={classes.frame}>
|
||||||
<div className={classes.vuMeterL}>
|
<div className={classes.vuMeterL}>
|
||||||
@@ -683,16 +749,19 @@ const PluginControlView =
|
|||||||
<div className={vuMeterRClass}>
|
<div className={vuMeterRClass}>
|
||||||
<VuMeter displayText={true} display="output" instanceId={pedalboardItem.instanceId} />
|
<VuMeter displayText={true} display="output" instanceId={pedalboardItem.instanceId} />
|
||||||
</div>
|
</div>
|
||||||
<div className={gridClass} >
|
<div className={scrollClass}>
|
||||||
{
|
<div className={gridClass} >
|
||||||
nodes
|
{
|
||||||
}
|
nodes
|
||||||
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
}
|
||||||
{
|
{/* Extra space to allow scrolling right to the end in lascape especially */}
|
||||||
(!this.state.landscapeGrid) && (
|
<div style={{ flex: "0 0 40px", width: 40, height: 40 }} />
|
||||||
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
{
|
||||||
)
|
(!this.state.landscapeGrid) && (
|
||||||
}
|
<div style={{ flex: "0 1 100%", width: "0px", height: 40 }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.showFileDialog && (
|
{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 { ReactComponent as FxTerminalIcon } from './svg/fx_terminal.svg';
|
||||||
|
|
||||||
import {isDarkMode} from './DarkMode';
|
import {isDarkMode} from './DarkMode';
|
||||||
import { Color } from '@mui/material';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -1862,8 +1862,9 @@ public:
|
|||||||
VuUpdate v;
|
VuUpdate v;
|
||||||
v.instanceId_ = instanceId;
|
v.instanceId_ = instanceId;
|
||||||
// Display mono VUs if a stereo device is being fed identical L/R inputs.
|
// 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.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2
|
||||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
|
&& effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||||
|
v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2;
|
||||||
|
|
||||||
vuConfig->vuUpdateWorkingData.push_back(v);
|
vuConfig->vuUpdateWorkingData.push_back(v);
|
||||||
vuConfig->vuUpdateResponseData.push_back(v);
|
vuConfig->vuUpdateResponseData.push_back(v);
|
||||||
|
|||||||
+4
-1
@@ -41,7 +41,7 @@ namespace macaron
|
|||||||
public:
|
public:
|
||||||
static std::string Encode(const std::vector<uint8_t> &data)
|
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)
|
static std::string Encode(size_t size, const uint8_t *data)
|
||||||
{
|
{
|
||||||
@@ -112,6 +112,9 @@ namespace macaron
|
|||||||
if (in_len % 4 != 0)
|
if (in_len % 4 != 0)
|
||||||
throw std::invalid_argument("Input data size is not a multiple of 4");
|
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;
|
size_t out_len = in_len / 4 * 3;
|
||||||
if (input[in_len - 1] == '=')
|
if (input[in_len - 1] == '=')
|
||||||
out_len--;
|
out_len--;
|
||||||
|
|||||||
+5
-2
@@ -49,8 +49,11 @@ namespace pipedal {
|
|||||||
virtual void SetBypass(bool enable) = 0;
|
virtual void SetBypass(bool enable) = 0;
|
||||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||||
|
|
||||||
virtual int GetNumberOfInputAudioPorts() const = 0;
|
virtual int GetNumberOfInputAudioPorts() const = 0; // as declared
|
||||||
virtual int GetNumberOfOutputAudioPorts() const = 0;
|
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 *GetAudioInputBuffer(int index) const = 0;
|
||||||
virtual float *GetAudioOutputBuffer(int index) const = 0;
|
virtual float *GetAudioOutputBuffer(int index) const = 0;
|
||||||
virtual void ResetAtomBuffers() = 0;
|
virtual void ResetAtomBuffers() = 0;
|
||||||
|
|||||||
+4
-4
@@ -414,12 +414,12 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
|||||||
|
|
||||||
void Lv2Effect::SetAudioInputBuffer(float *left)
|
void Lv2Effect::SetAudioInputBuffer(float *left)
|
||||||
{
|
{
|
||||||
if (GetNumberOfInputAudioPorts() > 1)
|
if (GetNumberOfInputAudioBuffers() > 1)
|
||||||
{
|
{
|
||||||
SetAudioInputBuffer(0, left);
|
SetAudioInputBuffer(0, left);
|
||||||
SetAudioInputBuffer(1, left);
|
SetAudioInputBuffer(1, left);
|
||||||
}
|
}
|
||||||
else if (GetNumberOfInputAudioPorts() != 0) /// yyx MIXING!
|
else if (GetNumberOfInputAudioBuffers() != 0)
|
||||||
{
|
{
|
||||||
SetAudioInputBuffer(0, left);
|
SetAudioInputBuffer(0, left);
|
||||||
}
|
}
|
||||||
@@ -427,11 +427,11 @@ void Lv2Effect::SetAudioInputBuffer(float *left)
|
|||||||
|
|
||||||
void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
|
void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
|
||||||
{
|
{
|
||||||
if (GetNumberOfInputAudioPorts() == 1)
|
if (GetNumberOfInputAudioBuffers() == 1)
|
||||||
{
|
{
|
||||||
SetAudioInputBuffer(0, left);
|
SetAudioInputBuffer(0, left);
|
||||||
}
|
}
|
||||||
else if (GetNumberOfInputAudioPorts() > 1)
|
else if (GetNumberOfInputAudioBuffers() > 1)
|
||||||
{
|
{
|
||||||
SetAudioInputBuffer(0, left);
|
SetAudioInputBuffer(0, left);
|
||||||
SetAudioInputBuffer(1, right);
|
SetAudioInputBuffer(1, right);
|
||||||
|
|||||||
@@ -252,16 +252,22 @@ namespace pipedal
|
|||||||
virtual uint64_t GetInstanceId() const { return instanceId; }
|
virtual uint64_t GetInstanceId() const { return instanceId; }
|
||||||
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
|
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
|
||||||
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
|
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
|
||||||
|
|
||||||
virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
|
virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
|
||||||
|
|
||||||
virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
|
virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
|
||||||
virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
|
virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
|
||||||
virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.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 void SetAudioInputBuffer(int index, float *buffer);
|
||||||
virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
|
virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
|
||||||
|
|
||||||
|
|
||||||
virtual void SetAudioInputBuffer(float *buffer);
|
virtual void SetAudioInputBuffer(float *buffer);
|
||||||
virtual void SetAudioInputBuffers(float *left, float *right);
|
virtual void SetAudioInputBuffers(float *left, float *right);
|
||||||
|
|
||||||
|
|||||||
+10
-32
@@ -136,11 +136,11 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
|
|
||||||
if (inputBuffers.size() == 1)
|
if (inputBuffers.size() == 1)
|
||||||
{
|
{
|
||||||
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
|
if (pLv2Effect->GetNumberOfInputAudioBuffers() == 1)
|
||||||
{
|
{
|
||||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||||
}
|
}
|
||||||
else
|
else if (pLv2Effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||||
{
|
{
|
||||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||||
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[0]);
|
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[0]);
|
||||||
@@ -148,13 +148,13 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (pLv2Effect->GetNumberOfInputAudioPorts() == 1)
|
if (pLv2Effect->GetNumberOfInputAudioBuffers() == 1)
|
||||||
{
|
{
|
||||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||||
|
|
||||||
auto inputBuffer = inputBuffers[0];
|
auto inputBuffer = inputBuffers[0];
|
||||||
}
|
}
|
||||||
else
|
else if (pLv2Effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||||
{
|
{
|
||||||
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]);
|
||||||
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[1]);
|
pLv2Effect->SetAudioInputBuffer(1, inputBuffers[1]);
|
||||||
@@ -178,37 +178,15 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
|
|
||||||
std::vector<float *> effectOutput;
|
std::vector<float *> effectOutput;
|
||||||
|
|
||||||
#ifdef RECYCLE_AUDIO_BUFFERS
|
if (pEffect->GetNumberOfOutputAudioBuffers() == 1)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
effectOutput.push_back(CreateNewAudioBuffer());
|
effectOutput.push_back(CreateNewAudioBuffer());
|
||||||
}
|
}
|
||||||
else
|
else if (pEffect->GetNumberOfOutputAudioBuffers() >= 2)
|
||||||
{
|
{
|
||||||
effectOutput.push_back(CreateNewAudioBuffer());
|
effectOutput.push_back(CreateNewAudioBuffer());
|
||||||
effectOutput.push_back(CreateNewAudioBuffer());
|
effectOutput.push_back(CreateNewAudioBuffer());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
for (size_t i = 0; i < effectOutput.size(); ++i)
|
for (size_t i = 0; i < effectOutput.size(); ++i)
|
||||||
{
|
{
|
||||||
pEffect->SetAudioOutputBuffer(i, effectOutput[i]);
|
pEffect->SetAudioOutputBuffer(i, effectOutput[i]);
|
||||||
@@ -475,21 +453,21 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
|
|||||||
{
|
{
|
||||||
auto effect = this->realtimeEffects[index];
|
auto effect = this->realtimeEffects[index];
|
||||||
|
|
||||||
if (effect->GetNumberOfInputAudioPorts() == 1)
|
if (effect->GetNumberOfInputAudioBuffers() == 1)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
|
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
|
||||||
}
|
}
|
||||||
else
|
else if (effect->GetNumberOfInputAudioBuffers() >= 2)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateInputs(
|
pUpdate->AccumulateInputs(
|
||||||
effect->GetAudioInputBuffer(0),
|
effect->GetAudioInputBuffer(0),
|
||||||
effect->GetAudioInputBuffer(1), samples);
|
effect->GetAudioInputBuffer(1), samples);
|
||||||
}
|
}
|
||||||
if (effect->GetNumberOfOutputAudioPorts() == 1)
|
if (effect->GetNumberOfOutputAudioBuffers() == 1)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
|
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
|
||||||
}
|
}
|
||||||
else
|
else if (effect->GetNumberOfOutputAudioBuffers() >= 2)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateOutputs(
|
pUpdate->AccumulateOutputs(
|
||||||
effect->GetAudioOutputBuffer(0),
|
effect->GetAudioOutputBuffer(0),
|
||||||
|
|||||||
+30
-1
@@ -17,12 +17,41 @@
|
|||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// 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.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
#include "MidiBinding.hpp"
|
#include "MidiBinding.hpp"
|
||||||
|
#include "MapFeature.hpp"
|
||||||
|
|
||||||
|
|
||||||
using namespace pipedal;
|
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_BEGIN(MidiBinding)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,channel)
|
JSON_MAP_REFERENCE(MidiBinding,channel)
|
||||||
JSON_MAP_REFERENCE(MidiBinding,symbol)
|
JSON_MAP_REFERENCE(MidiBinding,symbol)
|
||||||
|
|||||||
@@ -20,10 +20,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <lv2/urid/urid.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
|
class MapFeature;
|
||||||
|
|
||||||
#define GETTER_SETTER_REF(name) \
|
#define GETTER_SETTER_REF(name) \
|
||||||
const decltype(name##_)& name() const { return name##_;} \
|
const decltype(name##_)& name() const { return name##_;} \
|
||||||
@@ -33,6 +39,9 @@ namespace pipedal {
|
|||||||
decltype(name##_)& name() { return name##_;} \
|
decltype(name##_)& name() { return name##_;} \
|
||||||
const decltype(name##_)& name() const { 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) \
|
#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_REF
|
||||||
#undef GETTER_SETTER_VEC
|
#undef GETTER_SETTER_VEC
|
||||||
#undef GETTER_SETTER
|
#undef GETTER_SETTER
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public:
|
|||||||
std::vector<PedalboardItem> topChain_;
|
std::vector<PedalboardItem> topChain_;
|
||||||
std::vector<PedalboardItem> bottomChain_;
|
std::vector<PedalboardItem> bottomChain_;
|
||||||
std::vector<MidiBinding> midiBindings_;
|
std::vector<MidiBinding> midiBindings_;
|
||||||
|
std::optional<MidiChannelBinding> midiChannelBinding_;
|
||||||
std::string vstState_;
|
std::string vstState_;
|
||||||
uint32_t stateUpdateCount_ = 0;
|
uint32_t stateUpdateCount_ = 0;
|
||||||
Lv2PluginState lv2State_;
|
Lv2PluginState lv2State_;
|
||||||
@@ -122,6 +123,7 @@ public:
|
|||||||
GETTER_SETTER_VEC(topChain)
|
GETTER_SETTER_VEC(topChain)
|
||||||
GETTER_SETTER_VEC(bottomChain)
|
GETTER_SETTER_VEC(bottomChain)
|
||||||
GETTER_SETTER_VEC(midiBindings)
|
GETTER_SETTER_VEC(midiBindings)
|
||||||
|
GETTER_SETTER_REF(midiChannelBinding)
|
||||||
GETTER_SETTER(stateUpdateCount)
|
GETTER_SETTER(stateUpdateCount)
|
||||||
GETTER_SETTER_REF(lv2State)
|
GETTER_SETTER_REF(lv2State)
|
||||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||||
|
|||||||
@@ -1876,6 +1876,18 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
|
|||||||
}
|
}
|
||||||
if (pPlugin)
|
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)
|
for (size_t i = 0; i < pPlugin->ports().size(); ++i)
|
||||||
{
|
{
|
||||||
auto port = pPlugin->ports()[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());
|
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
|
#elif SUPPORT_MIDI
|
||||||
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
||||||
@@ -520,9 +524,6 @@ void PluginHost::Load(const char *lv2Path)
|
|||||||
if (info.audio_inputs() == 0) {
|
if (info.audio_inputs() == 0) {
|
||||||
Lv2Log::debug("************* ZERO INPUTS: %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
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));
|
ui_plugins_.push_back(std::move(info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,6 +432,7 @@ namespace pipedal
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
bool hasMidiOutput() const
|
bool hasMidiOutput() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -381,10 +381,15 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
return (int)this->inputs.size();
|
return (int)this->inputs.size();
|
||||||
}
|
}
|
||||||
|
virtual int GetNumberOfInputAudioBuffers() const { return this->inputs.size(); }
|
||||||
|
|
||||||
virtual float *GetAudioInputBuffer(int index) const
|
virtual float *GetAudioInputBuffer(int index) const
|
||||||
{
|
{
|
||||||
return inputs[index];
|
return inputs[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputBuffers.size(); }
|
||||||
|
|
||||||
virtual float *GetAudioOutputBuffer(int index) const
|
virtual float *GetAudioOutputBuffer(int index) const
|
||||||
{
|
{
|
||||||
return this->outputBuffers[index];
|
return this->outputBuffers[index];
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ const void *StateInterface::StateRetrieveFunction(
|
|||||||
*size = entry.value_.size();
|
*size = entry.value_.size();
|
||||||
*type = map.GetUrid(entry.atomType_.c_str());
|
*type = map.GetUrid(entry.atomType_.c_str());
|
||||||
*flags = entry.flags_;
|
*flags = entry.flags_;
|
||||||
return (void*)&entry.value_[0];
|
return entry.value_.data();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,8 +126,21 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
return info.pluginInfo_.audio_outputs();
|
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 *GetAudioInputBuffer(int index) const { return buffers.inputs[index]; }
|
||||||
virtual float *GetAudioOutputBuffer(int index) const { return buffers.outputs[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 ResetAtomBuffers() {}
|
||||||
virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent.
|
virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent.
|
||||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} // no vst equivalent.
|
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {} // no vst equivalent.
|
||||||
|
|||||||
Reference in New Issue
Block a user