- MIDI Program/Bank select.
- MIDI next/previous program.
This commit is contained in:
Robin Davies
2023-01-13 20:59:43 -05:00
parent ee3e264df5
commit ff8ed6b733
31 changed files with 1647 additions and 580 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 1.0.17
VERSION 1.0.18
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
set (DISPLAY_VERSION "v1.0.17")
set (DISPLAY_VERSION "v1.0.18")
set (CMAKE_INSTALL_PREFIX "/usr/")
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2021-2022 Robin Davies
Copyright 2021-2023 Robin Davies
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+1 -1
View File
@@ -1,7 +1,7 @@
<img src='docs/GithubBanner.png' width="100%" /><br/>
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.0.17</a>
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.0.18</a>
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
+1 -1
View File
@@ -9,6 +9,6 @@ Package: pipedal
Pre-Depends: hostapd;authbind
Priority: optional
Section: sound
Version: 1.0.17
Version: 1.0.18
Installed-Size: 15147
+2 -2
View File
@@ -13,14 +13,14 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Ubuntu/Raspberry Pi OS (64-bit) v1.0.17](https://github.com/rerdavies/pipedal/releases/download/v1.0.17/pipedal_1.0.17_arm64.deb)
- [Ubuntu/Raspberry Pi OS (64-bit) v1.0.18](https://github.com/rerdavies/pipedal/releases/download/v1.0.18/pipedal_1.0.18_arm64.deb)
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.0.17_arm64.deb
sudo apt-get install ./pipedal_1.0.18_arm64.deb
```
On Raspberry Pi OS, if you have a graphical desktop installed, you can also install the package by double-clicking on the downloaded package in the File Manager.
+2 -2
View File
@@ -4,14 +4,14 @@
Download the most recent Debian (.deb) package for your platform:
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v1.0.17/pipedal_1.0.17_arm64.deb) v1.0.17
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v1.0.18/pipedal_1.0.18_arm64.deb) v1.0.18
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.0.17_arm64.deb
sudo apt-get install ./pipedal_1.0.18_arm64.deb
```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+1 -1
View File
@@ -1,7 +1,7 @@
<img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.0.17</i></a>
<a href="Installing.html"><i>v1.0.18</i></a>
&nbsp;
+5
View File
@@ -31,6 +31,11 @@ export default class MidiBinding {
this.switchControlType = input.switchControlType;
return this;
}
static systemBinding(symbol: string): MidiBinding {
let result = new MidiBinding();
result.symbol = symbol;
return result;
}
static deserialize_array(input: any): MidiBinding[] {
let result: MidiBinding[] = [];
for (let i = 0; i < input.length; ++i)
+1 -1
View File
@@ -257,7 +257,7 @@ const MidiBindingView =
onChange={(e, extra) => this.handleLatchControlTypeChange(e, extra)}
value={midiBinding.switchControlType}
>
<MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Latch</MenuItem>
<MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Toggle</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>Momentary</MenuItem>
</Select>
</div>
+63 -33
View File
@@ -339,6 +339,7 @@ export interface PiPedalModel {
favorites: ObservableProperty<FavoritesList>;
presets: ObservableProperty<PresetIndex>;
systemMidiBindings: ObservableProperty<MidiBinding[]>;
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined>;
@@ -409,6 +410,8 @@ export interface PiPedalModel {
updatePluginPresets(uri: string, presets: PluginUiPresets): Promise<void>;
duplicatePluginPreset(uri: string, instanceId: number): Promise<number>;
setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
shutdown(): Promise<void>;
restart(): Promise<void>;
@@ -441,8 +444,8 @@ export interface PiPedalModel {
zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void;
onPreviousZoomedControl() : void;
onNextZoomedControl() : void;
onPreviousZoomedControl(): void;
onNextZoomedControl(): void;
clearZoomedControl(): void;
setFavorite(pluginUrl: string, isFavorite: boolean): void;
@@ -495,7 +498,13 @@ class PiPedalModelImpl implements PiPedalModel {
(
new PresetIndex()
);
systemMidiBindings: ObservableProperty<MidiBinding[]> = new ObservableProperty<MidiBinding[]>
(
[
MidiBinding.systemBinding("prevProgram"),
MidiBinding.systemBinding("nextProgram")
]
);
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
svgImgUrl(svgImage: string): string {
@@ -682,7 +691,10 @@ class PiPedalModelImpl implements PiPedalModel {
if (header.replyTo) {
this.webSocket?.reply(header.replyTo, "onVuUpdate", true);
}
} else if (message === "onSystemMidiBindingsChanged")
{
let bindings = MidiBinding.deserialize_array(body);
this.systemMidiBindings.set(bindings);
} else {
throw new PiPedalStateError("Unrecognized message received from server: " + message);
}
@@ -815,6 +827,12 @@ class PiPedalModelImpl implements PiPedalModel {
.then((data) => {
this.favorites.set(data);
return this.getWebSocket().request<MidiBinding[]>("getSystemMidiBindings");
})
.then((data)=> {
let bindings = MidiBinding.deserialize_array(data);
this.systemMidiBindings.set(bindings);
this.setState(State.Ready);
})
.catch((what) => {
@@ -944,7 +962,7 @@ class PiPedalModelImpl implements PiPedalModel {
})
.then(data => {
this.showStatusMonitor.set(data);
return this.getWebSocket().request<any>("getJackServerSettings");
})
.then(data => {
@@ -977,6 +995,12 @@ class PiPedalModelImpl implements PiPedalModel {
.then((data) => {
this.favorites.set(data);
return this.getWebSocket().request<MidiBinding[]>("getSystemMidiBindings");
})
.then((data)=> {
let bindings = MidiBinding.deserialize_array(data);
this.systemMidiBindings.set(bindings);
if (this.webSocket) {
// MUST not allow reconnect until at least one complete load has finished.
this.webSocket.canReconnect = true;
@@ -1215,7 +1239,7 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
_setVst3PedalBoardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean ): void {
_setVst3PedalBoardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void {
let pedalBoard = this.pedalBoard.get();
if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalBoard = pedalBoard.clone();
@@ -1251,7 +1275,7 @@ class PiPedalModelImpl implements PiPedalModel {
let newPedalBoard = pedalBoard.clone();
let item = newPedalBoard.getItem(instanceId);
let changed = value !== item.isEnabled;
if (changed) {
item.isEnabled = value;
@@ -1837,8 +1861,7 @@ class PiPedalModelImpl implements PiPedalModel {
});
}
updateVst3State(pedalBoard: PedalBoard)
{
updateVst3State(pedalBoard: PedalBoard) {
// let it = pedalBoard.itemsGenerator();
// while (true) {
// let v = it.next();
@@ -1862,6 +1885,23 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void {
let currentBindings = this.systemMidiBindings.get();
let result: MidiBinding[] = [];
for (var binding of currentBindings)
{
if (binding.symbol === midiBinding.symbol)
{
result.push(midiBinding);
} else {
result.push(binding);
}
}
this.systemMidiBindings.set(result);
this.webSocket?.send("setSystemMidiBindings",result);
}
midiListeners: MidiEventListener[] = [];
atomOutputListeners: AtomOutputListener[] = [];
@@ -1918,8 +1958,7 @@ class PiPedalModelImpl implements PiPedalModel {
break;
}
}
if (!found)
{
if (!found) {
console.log('cancelListenForMidiEvent: event not found.');
}
this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle);
@@ -2101,7 +2140,7 @@ class PiPedalModelImpl implements PiPedalModel {
wifiDirectConfigSettings = wifiDirectConfigSettings.clone();
if ((!oldSettings.enable) && (!wifiDirectConfigSettings.enable)
&& (oldSettings.hotspotName === wifiDirectConfigSettings.hotspotName)
&& (oldSettings.hotspotName === wifiDirectConfigSettings.hotspotName)
) {
// no effective change.
resolve();
@@ -2183,19 +2222,14 @@ class PiPedalModelImpl implements PiPedalModel {
}
zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void {
let name = uiControl.name;
if (uiControl.port_group !== "")
{
if (uiControl.port_group !== "") {
let pedalboard = this.pedalBoard.get();
if (pedalboard)
{
if (pedalboard) {
let plugin = pedalboard.getItem(instanceId);
let uiPlugin = this.getUiPlugin(plugin.uri);
if (uiPlugin)
{
for (let i = 0; i < uiPlugin.port_groups.length; ++i)
{
if (uiPlugin.port_groups[i].symbol === uiControl.port_group)
{
if (uiPlugin) {
for (let i = 0; i < uiPlugin.port_groups.length; ++i) {
if (uiPlugin.port_groups[i].symbol === uiControl.port_group) {
name = uiPlugin.port_groups[i].name + " / " + name;
break;
}
@@ -2205,8 +2239,7 @@ class PiPedalModelImpl implements PiPedalModel {
}
this.zoomedUiControl.set({ source: sourceElement, name: name, instanceId: instanceId, uiControl: uiControl });
}
onPreviousZoomedControl() : void
{
onPreviousZoomedControl(): void {
let currentUiControl = this.zoomedUiControl.get();
if (!currentUiControl) return;
@@ -2222,8 +2255,7 @@ class PiPedalModelImpl implements PiPedalModel {
let i = 0;
let ix = -1;
for (i = 0; i < uiPlugin.controls.length; ++i)
{
for (i = 0; i < uiPlugin.controls.length; ++i) {
if (uiPlugin.controls[i].symbol === currentSymbol) {
ix = i;
break;
@@ -2234,10 +2266,9 @@ class PiPedalModelImpl implements PiPedalModel {
++ix;
if (ix >= uiPlugin.controls.length) return;
this.zoomUiControl(currentUiControl.source,currentUiControl.instanceId,uiPlugin.controls[ix]);
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
}
onNextZoomedControl() : void
{
onNextZoomedControl(): void {
let currentUiControl = this.zoomedUiControl.get();
if (!currentUiControl) return;
@@ -2253,8 +2284,7 @@ class PiPedalModelImpl implements PiPedalModel {
let i = 0;
let ix = -1;
for (i = 0; i < uiPlugin.controls.length; ++i)
{
for (i = 0; i < uiPlugin.controls.length; ++i) {
if (uiPlugin.controls[i].symbol === currentSymbol) {
ix = i;
break;
@@ -2264,8 +2294,8 @@ class PiPedalModelImpl implements PiPedalModel {
--ix;
if (ix < 0) return;
this.zoomUiControl(currentUiControl.source,currentUiControl.instanceId,uiPlugin.controls[ix]);
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
}
clearZoomedControl(): void {
+21
View File
@@ -44,6 +44,7 @@ import WifiDirectConfigDialog from './WifiDirectConfigDialog';
import DialogEx from './DialogEx'
import GovernorSettings from './GovernorSettings';
import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo';
import SystemMidiBindingsDialog from './SystemMidiBindingsDialog';
import Slide, { SlideProps } from '@mui/material/Slide';
import { createStyles, Theme } from '@mui/material/styles';
@@ -84,6 +85,7 @@ interface SettingsDialogState {
isAndroidHosted: boolean;
showRestartOkDialog: boolean;
showShutdownOkDialog: boolean;
showSystemMidiBindingsDialog: boolean;
};
@@ -182,6 +184,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
restarting: false,
showShutdownOkDialog: false,
showRestartOkDialog: false,
showSystemMidiBindingsDialog: false,
isAndroidHosted: this.model.isAndroidHosted()
@@ -376,6 +379,12 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
showMidiSelectDialog: true
})
}
handleMidiMessageSettings() {
this.setState({
showSystemMidiBindingsDialog: true
});
}
handleJackServerSettings() {
this.setState({
showJackServerSettingsDialog: true,
@@ -634,6 +643,14 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{this.midiSummary()}</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiMessageSettings()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>System MIDI Bindings</Typography>
</div>
</ButtonBase>
</div>
</div>
<Divider />
@@ -840,6 +857,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
onOk={() => { this.setState({ showShutdownOkDialog: false }); this.handleShutdownOk(); }}
onClose={() => { this.setState({ showShutdownOkDialog: false }); }}
/>
<SystemMidiBindingsDialog
open={this.state.showSystemMidiBindingsDialog}
onClose={()=> { this.setState({showSystemMidiBindingsDialog: false});}}
/>
</DialogEx >
);
+218
View File
@@ -0,0 +1,218 @@
/*
* MIT License
*
* Copyright (c) 2022-2023 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 { Component } from 'react';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import withStyles from '@mui/styles/withStyles';
import createStyles from '@mui/styles/createStyles';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import MidiBinding from './MidiBinding';
import Utility from './Utility';
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
import IconButton from '@mui/material/IconButton';
const styles = (theme: Theme) => createStyles({
controlDiv: { flex: "0 0 auto", marginRight: 12, verticalAlign: "center", height: 48, paddingTop: 8, paddingBottom: 8 },
controlDiv2: {
flex: "0 0 auto", marginRight: 12, verticalAlign: "center",
height: 48, paddingTop: 0, paddingBottom: 0, whiteSpace: "nowrap"
}
});
interface SystemMidiBindingViewProps extends WithStyles<typeof styles> {
instanceId: number;
listen: boolean;
midiBinding: MidiBinding;
onChange: (instanceId: number, newBinding: MidiBinding) => void;
onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
}
interface SystemMidiBindingViewState {
}
const SystemMidiBindingView =
withStyles(styles, { withTheme: true })(
class extends Component<SystemMidiBindingViewProps, SystemMidiBindingViewState> {
model: PiPedalModel;
constructor(props: SystemMidiBindingViewProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
};
}
handleTypeChange(e: any, extra: any) {
let newValue = parseInt(e.target.value);
let newBinding = this.props.midiBinding.clone();
newBinding.bindingType = newValue;
this.props.onChange(this.props.instanceId, newBinding);
}
handleNoteChange(e: any, extra: any) {
let newValue = parseInt(e.target.value);
let newBinding = this.props.midiBinding.clone();
newBinding.note = newValue;
this.props.onChange(this.props.instanceId, newBinding);
}
handleControlChange(e: any, extra: any) {
let newValue = parseInt(e.target.value);
let newBinding = this.props.midiBinding.clone();
newBinding.control = newValue;
this.props.onChange(this.props.instanceId, newBinding);
}
handleLatchControlTypeChange(e: any, extra: any) {
let newValue = parseInt(e.target.value);
let newBinding = this.props.midiBinding.clone();
newBinding.switchControlType = newValue;
this.props.onChange(this.props.instanceId, newBinding);
}
handleLinearControlTypeChange(e: any, extra: any) {
let newValue = parseInt(e.target.value);
let newBinding = this.props.midiBinding.clone();
newBinding.linearControlType = newValue;
this.props.onChange(this.props.instanceId, newBinding);
}
handleMinChange(value: number): void {
let newBinding = this.props.midiBinding.clone();
newBinding.minValue = value;
this.props.onChange(this.props.instanceId, newBinding);
}
handleMaxChange(value: number): void {
let newBinding = this.props.midiBinding.clone();
newBinding.maxValue = value;
this.props.onChange(this.props.instanceId, newBinding);
}
handleScaleChange(value: number): void {
let newBinding = this.props.midiBinding.clone();
newBinding.rotaryScale = value;
this.props.onChange(this.props.instanceId, newBinding);
}
generateMidiSelects(): React.ReactNode[] {
let result: React.ReactNode[] = [];
for (let i = 0; i < 127; ++i) {
result.push(
<MenuItem value={i}>{Utility.midiNoteName(i)}</MenuItem>
)
}
return result;
}
generateControlSelects(): React.ReactNode[] {
return Utility.validMidiControllers.map((control) => (
<MenuItem key={control.value} value={control.value}>{control.displayName}</MenuItem>
)
);
}
render() {
let classes = this.props.classes;
let midiBinding = this.props.midiBinding;
return (
<div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "left", alignItems: "center", minHeight: 48 }}>
<div className={classes.controlDiv} >
<Select variant="standard"
style={{ width: 80, }}
onChange={(e, extra) => this.handleTypeChange(e, extra)}
value={midiBinding.bindingType}
>
<MenuItem value={0}>None</MenuItem>
<MenuItem value={1}>Note</MenuItem>
<MenuItem value={2}>Control</MenuItem>
</Select>
</div>
{
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
(
<div className={classes.controlDiv} >
<Select variant="standard"
style={{ width: 80, verticalAlign: 'center' }}
onChange={(e, extra) => this.handleNoteChange(e, extra)}
value={midiBinding.note}
>
{
this.generateMidiSelects()
}
</Select>
</div>
)
}
{
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_CONTROL) &&
(
<div className={classes.controlDiv} >
<Select variant="standard"
style={{ width: 80 }}
onChange={(e, extra) => this.handleControlChange(e, extra)}
value={midiBinding.control}
renderValue={(value) => { return "CC-" + value }}
>
{
this.generateControlSelects()
}
</Select>
</div>
)
}
<IconButton
onClick={() => {
if (this.props.listen) {
this.props.onListen(-2, "", false)
} else {
this.props.onListen(this.props.instanceId, this.props.midiBinding.symbol, false)
}
}}
size="large">
{this.props.listen ? (
<MicOutlinedIcon />
) : (
<MicNoneOutlinedIcon />
)}
</IconButton>
</div>
);
}
});
export default SystemMidiBindingView;
+338
View File
@@ -0,0 +1,338 @@
/*
* MIT License
*
* Copyright (c) 2022-2023 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, { SyntheticEvent } from 'react';
import DialogEx from './DialogEx';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalModel, PiPedalModelFactory, ListenHandle } from './PiPedalModel';
import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButton from '@mui/material/IconButton';
import MidiBinding from './MidiBinding';
import SystemMidiBindingView from './SystemMidiBindingView';
import Snackbar from '@mui/material/Snackbar';
const styles = (theme: Theme) => createStyles({
dialogAppBar: {
position: 'relative',
top: 0, left: 0
},
dialogTitle: {
marginLeft: theme.spacing(2),
flex: "1 1 auto",
},
pluginTable: {
border: "collapse",
width: "100%",
},
pluginHead: {
borderTop: "1pt #DDD solid", paddingLeft: 22, paddingRight: 22
},
bindingTd: {
verticalAlign: "top",
paddingLeft: 12, paddingBottom: 8
},
nameTd: {
paddingLeft: 16,
verticalAlign: "top",
paddingTop: 12
},
});
export interface SystemMidiBindingDialogProps extends WithStyles<typeof styles> {
open: boolean,
onClose: () => void
}
class BindingEntry {
constructor(
displayName: string,
instanceId: number,
midiBinding: MidiBinding,
) {
this.displayName = displayName;
this.instanceId = instanceId;
this.midiBinding = midiBinding;
}
displayName: string;
instanceId: number;
midiBinding: MidiBinding;
}
export interface SystemMidiBindingDialogState {
listenInstanceId: number;
listenSymbol: string;
listenSnackbarOpen: boolean;
systemMidiBindings: BindingEntry[];
}
export const SystemMidiBindingDialog =
withStyles(styles, { withTheme: true })(
class extends ResizeResponsiveComponent<SystemMidiBindingDialogProps, SystemMidiBindingDialogState> {
model: PiPedalModel;
constructor(props: SystemMidiBindingDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
listenInstanceId: -2,
listenSymbol: "",
listenSnackbarOpen: false,
systemMidiBindings: this.createBindings()
};
this.handleClose = this.handleClose.bind(this);
this.onMidiBindingsChanged = this.onMidiBindingsChanged.bind(this);
}
createBindings(): BindingEntry[] {
let result: BindingEntry[] = [];
for (var item of this.model.systemMidiBindings.get())
{
let displayName = "";
let instanceId = -1;
if (item.symbol === "prevProgram")
{
displayName = "Previous Preset";
instanceId = 1;
} else if (item.symbol === "nextProgram")
{
displayName = "Next Preset";
instanceId = 2;
}
if (instanceId !== -1)
{
result.push(new BindingEntry(displayName,instanceId,item));
}
}
return result;
}
mounted: boolean = false;
hasHooks: boolean = false;
handleClose() {
this.cancelListenForControl();
this.props.onClose();
}
listenTimeoutHandle?: NodeJS.Timeout;
listenHandle?: ListenHandle;
cancelListenForControl() {
if (this.listenTimeoutHandle) {
clearTimeout(this.listenTimeoutHandle);
this.listenTimeoutHandle = undefined;
}
if (this.listenHandle)
{
this.model.cancelListenForMidiEvent(this.listenHandle)
this.listenHandle = undefined;
}
this.setState({ listenInstanceId: -2, listenSymbol: "" });
}
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
{
this.cancelListenForControl();
for (var binding of this.state.systemMidiBindings)
{
if (binding.instanceId === instanceId)
{
let newBinding = binding.midiBinding.clone();
if (isNote) {
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE;
newBinding.note = noteOrControl;
} else {
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL;
newBinding.control = noteOrControl;
}
this.model.setSystemMidiBinding(instanceId,newBinding);
return;
}
}
}
handleListenForControl(instanceId: number, symbol: string, listenForControl: boolean): void {
this.cancelListenForControl();
this.setState({ listenInstanceId: instanceId, listenSymbol: symbol, listenSnackbarOpen: true });
this.listenTimeoutHandle = setTimeout(() => {
this.cancelListenForControl();
}, 8000);
this.listenHandle = this.model.listenForMidiEvent(listenForControl,
(isNote: boolean, noteOrControl: number) => {
this.handleListenSucceeded(instanceId,symbol,isNote, noteOrControl);
});
}
onWindowSizeChanged(width: number, height: number): void {
}
onMidiBindingsChanged() {
this.setState({systemMidiBindings: this.createBindings()});
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
this.model.systemMidiBindings.addOnChangedHandler(this.onMidiBindingsChanged);
this.onMidiBindingsChanged();
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
this.model.systemMidiBindings.removeOnChangedHandler(this.onMidiBindingsChanged);
}
componentDidUpdate() {
}
handleItemChanged(instanceId: number, newBinding: MidiBinding) {
this.model.setSystemMidiBinding(instanceId, newBinding);
}
generateTable(): React.ReactNode[] {
let classes = this.props.classes;
let result: React.ReactNode[] = [];
let items = this.state.systemMidiBindings;
for (var item of items) {
result.push(
<tr key={item.instanceId}>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
{item.displayName}
</Typography>
</td>
<td className={classes.bindingTd}>
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
if (instanceId === -2)
{
this.cancelListenForControl();
} else {
this.handleListenForControl(instanceId, symbol, listenForControl);
}
}}
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === item.midiBinding.symbol}
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
/>
</td>
</tr>
);
}
return result;
}
supressDefault(e: SyntheticEvent) {
//e.preventDefault();
//e.stopPropagation();
}
render() {
let props = this.props;
let { open, classes } = props;
if (!open) {
return (<div />);
}
return (
<DialogEx tag="SystemMidiBindingsDialog" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{userSelect: "none"}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} >
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={this.handleClose}
aria-label="back"
size="large">
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.dialogTitle}>
System MIDI Bindings
</Typography>
</Toolbar>
</AppBar>
</div>
<div style={{ overflow: "auto", flex: "1 1 auto", width: "100%" }}>
<table className={classes.pluginTable} >
<colgroup>
<col style={{ width: "auto" }} />
<col style={{ width: "100%" }} />
</colgroup>
<tbody>
{this.generateTable()}
</tbody>
</table>
</div>
</div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.listenSnackbarOpen}
autoHideDuration={1500}
onClose={() => this.setState({ listenSnackbarOpen: false })}
message="Listening for MIDI input"
/>
</DialogEx >
);
}
});
export default SystemMidiBindingDialog;
File diff suppressed because it is too large Load Diff
+15 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2023 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
@@ -24,7 +24,7 @@
#include "Lv2PedalBoard.hpp"
#include "VuUpdate.hpp"
#include "json.hpp"
#include "JackHost.hpp"
#include "AudioHost.hpp"
#include "JackServerSettings.hpp"
#include <functional>
#include "PiPedalAlsa.hpp"
@@ -32,6 +32,8 @@
namespace pipedal {
struct RealtimeMidiProgramRequest;
struct RealtimeNextMidiProgramRequest;
using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
@@ -91,7 +93,7 @@ public:
};
class IJackHostCallbacks {
class IAudioHostCallbacks {
public:
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
@@ -99,6 +101,8 @@ public:
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0;
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
};
@@ -124,17 +128,17 @@ public:
class IHost;
class JackHost {
class AudioHost {
protected:
JackHost() { }
AudioHost() { }
public:
static JackHost*CreateInstance(IHost *pHost);
virtual ~JackHost() { };
static AudioHost*CreateInstance(IHost *pHost);
virtual ~AudioHost() { };
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0;
virtual void SetNotificationCallbacks(IJackHostCallbacks *pNotifyCallbacks) = 0;
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(bool listen) = 0;
@@ -158,7 +162,10 @@ public:
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual JackHostStatus getJackStatus() = 0;
+3 -3
View File
@@ -149,7 +149,7 @@ set (PIPEDAL_SOURCES
CpuGovernor.cpp CpuGovernor.hpp
GovernorSettings.cpp GovernorSettings.hpp
SysExec.cpp SysExec.hpp
BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp
WebServer.cpp WebServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp
WifiConfigSettings.hpp WifiConfigSettings.cpp
WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp
ConfigUtil.hpp ConfigUtil.cpp
@@ -164,7 +164,7 @@ set (PIPEDAL_SOURCES
Presets.hpp Presets.cpp
Storage.hpp Storage.cpp
Banks.hpp Banks.cpp
JackHost.hpp JackHost.cpp
AudioHost.hpp AudioHost.cpp
JackConfiguration.hpp JackConfiguration.cpp
defer.hpp
Lv2Effect.cpp Lv2Effect.hpp
@@ -264,7 +264,7 @@ add_executable(pipedaltest testMain.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
SystemConfigFileTest.cpp
BeastServerTest.cpp
WebServerTest.cpp
MemDebug.cpp
MemDebug.hpp
)
+1 -1
View File
@@ -37,7 +37,7 @@
#include "lv2/parameters.lv2/parameters.h"
#include "lv2/units.lv2/units.h"
#include "lv2/atom.lv2/util.h"
#include "JackHost.hpp"
#include "AudioHost.hpp"
#include <exception>
#include "RingBufferReader.hpp"
+1 -1
View File
@@ -25,7 +25,7 @@
#include "SplitEffect.hpp"
#include "RingBufferReader.hpp"
#include "VuUpdate.hpp"
#include "JackHost.hpp"
#include "AudioHost.hpp"
#include "Lv2EventBufferWriter.hpp"
using namespace pipedal;
+9 -3
View File
@@ -58,15 +58,21 @@ public:
private:
std::string symbol_;
int channel_ = -1;
int bindingType_;
int bindingType_ = BINDING_TYPE_NONE;
int note_ = 12*4+24;
int control_ = 1;
float minValue_ = 0;
float maxValue_ = 1;
float rotaryScale_ = 1;
int linearControlType_ = 0;
int switchControlType_ = 0;
int linearControlType_ = LINEAR_CONTROL_TYPE;
int switchControlType_ = LATCH_CONTROL_TYPE;
public:
static MidiBinding SystemBinding(const std::string&symbol)
{
MidiBinding result;
result.symbol_ = symbol;
return result;
}
GETTER_SETTER(channel);
GETTER_SETTER_REF(symbol);
GETTER_SETTER(bindingType);
+151 -34
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2023 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
@@ -23,13 +23,14 @@
#include "ConfigUtil.hpp"
#include <sched.h>
#include "PiPedalModel.hpp"
#include "JackHost.hpp"
#include "AudioHost.hpp"
#include "Lv2Log.hpp"
#include <set>
#include "PiPedalConfiguration.hpp"
#include "AdminClient.hpp"
#include "SplitEffect.hpp"
#include "CpuGovernor.hpp"
#include "RingBufferReader.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -84,9 +85,9 @@ void PiPedalModel::Close()
}
delete[] t;
if (jackHost)
if (audioHost)
{
jackHost->Close();
audioHost->Close();
}
}
@@ -113,9 +114,9 @@ PiPedalModel::~PiPedalModel()
try
{
if (jackHost)
if (audioHost)
{
jackHost->Close();
audioHost->Close();
}
}
catch (...)
@@ -133,6 +134,8 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
storage.SetDataRoot(configuration.GetLocalStoragePath());
storage.Initialize();
this->systemMidiBindings = storage.GetSystemMidiBindings();
this->jackServerSettings.ReadJackConfiguration();
}
@@ -191,10 +194,14 @@ void PiPedalModel::Load()
}
UpdateDefaults(&this->pedalBoard);
std::unique_ptr<JackHost> p{JackHost::CreateInstance(lv2Host.asIHost())};
this->jackHost = std::move(p);
std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(lv2Host.asIHost())};
this->audioHost = std::move(p);
this->jackHost->SetNotificationCallbacks(this);
this->audioHost->SetNotificationCallbacks(this);
this->systemMidiBindings = storage.GetSystemMidiBindings();
this->audioHost->SetSystemMidiBindings(this->systemMidiBindings);
if (configuration.GetMLock())
{
@@ -226,11 +233,11 @@ void PiPedalModel::Load()
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
try
{
jackHost->Open(this->jackServerSettings, selection);
audioHost->Open(this->jackServerSettings, selection);
try {
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
audioHost->SetPedalBoard(lv2PedalBoard);
}
catch (const std::exception &e)
{
@@ -305,7 +312,7 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s
effect->SetControl(index,value);;
}
} else {
jackHost->SetControlValue(pedalItemId, symbol, value);
audioHost->SetControlValue(pedalItemId, symbol, value);
}
}
@@ -387,11 +394,11 @@ void PiPedalModel::FirePedalBoardChanged(int64_t clientId)
delete[] t;
// notify the audio thread.
if (jackHost->IsOpen())
if (audioHost->IsOpen())
{
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
audioHost->SetPedalBoard(lv2PedalBoard);
UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
}
@@ -446,7 +453,7 @@ void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId
this->SetPresetChanged(clientId, true);
// Notify audo thread.
this->jackHost->SetBypass(pedalItemId, enabled);
this->audioHost->SetBypass(pedalItemId, enabled);
}
}
@@ -621,6 +628,87 @@ int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter)
return newPreset;
}
void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
try {
PresetIndex index;
storage.GetPresetIndex(&index);
auto currentPresetId = storage.GetCurrentPresetId();
size_t currentPresetIndex = 0;
for (size_t i = 0; i < index.presets().size(); ++i)
{
if (index.presets()[i].instanceId() == currentPresetId)
{
currentPresetIndex = i;
break;
}
}
if (index.presets().size() == 0)
{
throw PiPedalException("No presets loaded.");
}
if (request.direction < 0)
{
if (currentPresetIndex == 0)
{
currentPresetIndex = index.presets().size()-1;
} else {
--currentPresetIndex;
}
} else {
++currentPresetIndex;
if (currentPresetIndex >= index.presets().size())
{
currentPresetIndex = 0;
}
}
LoadPreset(-1,index.presets()[currentPresetIndex].instanceId());
} catch (std::exception&e)
{
Lv2Log::error(e.what());
}
if (this->audioHost)
{
this->audioHost->AckMidiProgramRequest(request.requestId);
}
}
void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
try {
if (midiProgramRequest.bank >= 0)
{
int64_t bankId = storage.GetBankByMidiBankNumber(midiProgramRequest.bank);
if (bankId == -1) throw PiPedalException("Bank not found.");
if (bankId != this->storage.GetBanks().selectedBank())
{
storage.LoadBank(bankId);
FireBanksChanged(-1);
FirePresetsChanged(-1);
}
}
int64_t presetId = storage.GetPresetByProgramNumber(midiProgramRequest.program);
if (presetId == -1) throw PiPedalException("No valid preset.");
LoadPreset(-1,presetId);
} catch (std::exception&e)
{
Lv2Log::error(e.what());
}
if (this->audioHost)
{
this->audioHost->AckMidiProgramRequest(midiProgramRequest.requestId);
}
}
void PiPedalModel::LoadPreset(int64_t clientId, int64_t instanceId)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -883,10 +971,10 @@ JackConfiguration PiPedalModel::GetJackConfiguration()
void PiPedalModel::RestartAudio()
{
if (this->jackHost->IsOpen())
if (this->audioHost->IsOpen())
{
this->jackHost->Close();
this->audioHost->Close();
}
// restarting is a bit dodgy. It was impossible with Jack, but
// now very plausible with the ALSA audio stack.
@@ -899,7 +987,7 @@ void PiPedalModel::RestartAudio()
// do a complete reload.
this->jackHost->SetPedalBoard(nullptr);
this->audioHost->SetPedalBoard(nullptr);
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
@@ -928,13 +1016,13 @@ void PiPedalModel::RestartAudio()
Lv2Log::error("Audio configuration not valid.");
return;
}
this->jackHost->Open(this->jackServerSettings, channelSelection);
this->audioHost->Open(this->jackServerSettings, channelSelection);
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
audioHost->SetPedalBoard(lv2PedalBoard);
this->UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
}
@@ -1121,12 +1209,12 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
}
}
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
jackHost->SetVuSubscriptions(instanceids);
audioHost->SetVuSubscriptions(instanceids);
}
void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions()
{
jackHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
audioHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
}
int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate)
@@ -1220,7 +1308,7 @@ void PiPedalModel::GetLv2Parameter(
std::lock_guard<std::recursive_mutex> guard(mutex);
outstandingParameterRequests.push_back(request);
this->jackHost->getRealtimeParameter(request);
this->audioHost->getRealtimeParameter(request);
}
BankIndex PiPedalModel::GetBankIndex() const
@@ -1254,6 +1342,7 @@ void PiPedalModel::OpenBank(int64_t clientId, int64_t bankId)
FirePresetsChanged(clientId);
this->pedalBoard = storage.GetCurrentPreset();
UpdateDefaults(&this->pedalBoard);
this->hasPresetChanged = false;
this->FirePedalBoardChanged(clientId);
@@ -1311,7 +1400,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
this->jackConfiguration.SetIsRestarting(true);
FireJackConfigurationChanged(this->jackConfiguration);
this->jackHost->UpdateServerConfiguration(
this->audioHost->UpdateServerConfiguration(
jackServerSettings,
[this](bool success, const std::string &errorMessage)
{
@@ -1341,7 +1430,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
audioHost->SetPedalBoard(lv2PedalBoard);
UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
#endif
@@ -1418,7 +1507,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
if (pedalBoardItem != nullptr)
{
std::vector<ControlValue> controlValues = storage.GetPluginPresetValues(pedalBoardItem->uri(), presetInstanceId);
jackHost->SetPluginPreset(pluginInstanceId, controlValues);
audioHost->SetPluginPreset(pluginInstanceId, controlValues);
for (size_t i = 0; i < controlValues.size(); ++i)
{
@@ -1451,7 +1540,7 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId)
--i;
}
}
jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
}
void PiPedalModel::DeleteMidiListeners(int64_t clientId)
@@ -1465,7 +1554,7 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
--i;
}
}
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson)
@@ -1489,7 +1578,7 @@ void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &at
}
}
}
jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
}
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
@@ -1513,7 +1602,7 @@ void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
}
}
}
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly)
@@ -1521,7 +1610,7 @@ void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bo
std::lock_guard<std::recursive_mutex> guard(mutex);
MidiListener listener{clientId, clientHandle, listenForControlsOnly};
midiEventListeners.push_back(listener);
jackHost->SetListenForMidiEvent(true);
audioHost->SetListenForMidiEvent(true);
}
void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId)
@@ -1529,7 +1618,7 @@ void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle,
std::lock_guard<std::recursive_mutex> guard(mutex);
AtomOutputListener listener{clientId, clientHandle, instanceId};
atomOutputListeners.push_back(listener);
jackHost->SetListenForAtomOutput(true);
audioHost->SetListenForAtomOutput(true);
}
void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle)
@@ -1546,7 +1635,7 @@ void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHand
}
if (midiEventListeners.size() == 0)
{
jackHost->SetListenForMidiEvent(false);
audioHost->SetListenForMidiEvent(false);
}
}
void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle)
@@ -1563,7 +1652,7 @@ void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHa
}
if (midiEventListeners.size() == 0)
{
jackHost->SetListenForMidiEvent(false);
audioHost->SetListenForMidiEvent(false);
}
}
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
@@ -1600,3 +1689,31 @@ void PiPedalModel::SetFavorites(const std::map<std::string, bool> &favorites)
t[i]->OnFavoritesChanged(favorites);
}
}
std::vector<MidiBinding> PiPedalModel::GetSystemMidiBidings() {
std::lock_guard<std::recursive_mutex> guard(mutex);
return this->systemMidiBindings;
}
void PiPedalModel::SetSystemMidiBindings(std::vector<MidiBinding> &bindings)
{
std::lock_guard<std::recursive_mutex> guard(mutex);
this->systemMidiBindings = bindings;
storage.SetSystemMidiBindings(bindings);
if (this->audioHost)
{
this->audioHost->SetSystemMidiBindings(bindings);
}
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->OnSystemMidiBindingsChanged(bindings);
}
delete[] t;
}
+239 -235
View File
@@ -25,7 +25,7 @@
#include "Storage.hpp"
#include "Banks.hpp"
#include "JackConfiguration.hpp"
#include "JackHost.hpp"
#include "AudioHost.hpp"
#include "VuUpdate.hpp"
#include <functional>
#include <filesystem>
@@ -38,245 +38,249 @@
#include "AvahiService.hpp"
#include <thread>
namespace pipedal
{
struct RealtimeMidiProgramRequest;
struct RealtimeNextMidiProgramRequest;
namespace pipedal {
class IPiPedalModelSubscriber {
public:
virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string&state) = 0;
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex&presets) = 0;
virtual void OnPluginPresetsChanged(const std::string&pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection&channelSelection) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate>& updates) = 0;
virtual void OnBankIndexChanged(const BankIndex& bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings& jackServerSettings) = 0;
virtual void OnJackConfigurationChanged(const JackConfiguration& jackServerConfiguration) = 0;
virtual void OnLoadPluginPreset(int64_t instanceId,const std::vector<ControlValue>&controlValues) = 0;
virtual void OnMidiValueChanged(int64_t instanceId, const std::string&symbol, float value) = 0;
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(int64_t clientModel,uint64_t instanceId,const std::string&atomType, const std::string&atomJson) = 0;
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) = 0;
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings&wifiDirectConfigSettings) = 0;
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
virtual void OnFavoritesChanged(const std::map<std::string,bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void Close() = 0;
};
class PiPedalModel: private IJackHostCallbacks {
private:
std::unique_ptr<std::jthread> pingThread;
AvahiService avahiService;
uint16_t webPort;
PiPedalAlsaDevices alsaDevices;
std::recursive_mutex mutex;
AdminClient adminClient;
class MidiListener {
class IPiPedalModelSubscriber
{
public:
int64_t clientId;
int64_t clientHandle;
bool listenForControlsOnly;
virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0;
virtual void OnJackConfigurationChanged(const JackConfiguration &jackServerConfiguration) = 0;
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) = 0;
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
virtual void Close() = 0;
};
class AtomOutputListener {
class PiPedalModel : private IAudioHostCallbacks
{
private:
std::unique_ptr<std::jthread> pingThread;
std::vector<MidiBinding> systemMidiBindings;
AvahiService avahiService;
uint16_t webPort;
PiPedalAlsaDevices alsaDevices;
std::recursive_mutex mutex;
AdminClient adminClient;
class MidiListener
{
public:
int64_t clientId;
int64_t clientHandle;
bool listenForControlsOnly;
};
class AtomOutputListener
{
public:
int64_t clientId;
int64_t clientHandle;
uint64_t instanceId;
};
void DeleteMidiListeners(int64_t clientId);
void DeleteAtomOutputListeners(int64_t clientId);
std::vector<MidiListener> midiEventListeners;
std::vector<AtomOutputListener> atomOutputListeners;
JackServerSettings jackServerSettings;
PiPedalHost lv2Host;
PedalBoard pedalBoard;
Storage storage;
bool hasPresetChanged = false;
std::unique_ptr<AudioHost> audioHost;
JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber *> subscribers;
void SetPresetChanged(int64_t clientId, bool value);
void FirePresetsChanged(int64_t clientId);
void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalBoardChanged(int64_t clientId);
void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void UpdateDefaults(PedalBoardItem *pedalBoardItem);
void UpdateDefaults(PedalBoard *pedalBoard);
class VuSubscription
{
public:
int64_t subscriptionHandle;
int64_t instanceid;
};
int64_t nextSubscriptionId = 1;
std::vector<VuSubscription> activeVuSubscriptions;
std::vector<MonitorPortSubscription> activeMonitorPortSubscriptions;
void UpdateRealtimeVuSubscriptions();
void UpdateRealtimeMonitorPortSubscriptions();
void OnVuUpdate(const std::vector<VuUpdate> &updates);
void RestartAudio();
std::vector<RealtimeParameterRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
private: // IAudioHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates);
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson);
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest);
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request);
void UpdateVst3Settings(PedalBoard &pedalBoard);
PiPedalConfiguration configuration;
public:
int64_t clientId;
int64_t clientHandle;
uint64_t instanceId;
PiPedalModel();
virtual ~PiPedalModel();
uint16_t GetWebPort() const { return webPort; }
void Close();
void UpdateDnsSd();
AdminClient &GetAdminClient() { return adminClient; }
void Init(const PiPedalConfiguration &configuration);
void LoadLv2PluginInfo();
void Load();
const PiPedalHost &GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy()
{
std::lock_guard<std::recursive_mutex> guard(mutex);
return pedalBoard; // can return a referece because we'd lose mutex protection
}
PluginUiPresets GetPluginUiPresets(const std::string &pluginUri);
PluginPresets GetPluginPresets(const std::string &pluginUri);
void LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetInstanceId);
void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber);
void RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber);
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void GetPresets(PresetIndex *pResult);
PedalBoard GetPreset(int64_t instanceId);
void GetBank(int64_t instanceId, BankFile *pBank);
int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1);
int64_t UploadPreset(const BankFile &bankFile, int64_t uploadAfter = -1);
void UploadPluginPresets(const PluginPresets &pluginPresets);
void SaveCurrentPreset(int64_t clientId);
int64_t SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId = -1);
int64_t SavePluginPresetAs(int64_t instanceId, const std::string &name);
void LoadPreset(int64_t clientId, int64_t instanceId);
bool UpdatePresets(int64_t clientId, const PresetIndex &presets);
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
int64_t DeletePreset(int64_t clientId, int64_t instanceId);
bool RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name);
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
uint64_t CopyPluginPreset(const std::string &pluginUri, uint64_t presetId);
void MoveBank(int64_t clientId, int from, int to);
int64_t DeleteBank(int64_t clientId, int64_t instanceId);
int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId, instanceId, -1); }
JackConfiguration GetJackConfiguration();
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
JackChannelSelection GetJackChannelSelection();
void SetShowStatusMonitor(bool show);
bool GetShowStatusMonitor();
void SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings);
WifiConfigSettings GetWifiConfigSettings();
void SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiConfigSettings);
WifiDirectConfigSettings GetWifiDirectConfigSettings();
void SetGovernorSettings(const std::string &governor);
GovernorSettings GetGovernorSettings();
int64_t AddVuSubscription(int64_t instanceId);
void RemoveVuSubscription(int64_t subscriptionHandle);
void SetSystemMidiBindings(std::vector<MidiBinding> &bindings);
std::vector<MidiBinding> GetSystemMidiBidings();
int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
void UnmonitorPort(int64_t subscriptionHandle);
void GetLv2Parameter(
int64_t clientId,
int64_t instanceId,
const std::string uri,
std::function<void(const std::string &jsonResjult)> onSuccess,
std::function<void(const std::string &error)> onError);
BankIndex GetBankIndex() const;
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
void OpenBank(int64_t clientId, int64_t bankId);
JackHostStatus GetJackStatus()
{
return this->audioHost->getJackStatus();
}
JackServerSettings GetJackServerSettings();
void SetJackServerSettings(const JackServerSettings &jackServerSettings);
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
std::vector<AlsaDeviceInfo> GetAlsaDevices();
const std::filesystem::path &GetWebRoot() const;
std::map<std::string, bool> GetFavorites() const;
void SetFavorites(const std::map<std::string, bool> &favorites);
};
void DeleteMidiListeners(int64_t clientId);
void DeleteAtomOutputListeners(int64_t clientId);
std::vector<MidiListener> midiEventListeners;
std::vector<AtomOutputListener> atomOutputListeners;
JackServerSettings jackServerSettings;
PiPedalHost lv2Host;
PedalBoard pedalBoard;
Storage storage;
bool hasPresetChanged = false;
std::unique_ptr<JackHost> jackHost;
JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber*> subscribers;
void SetPresetChanged(int64_t clientId,bool value);
void FirePresetsChanged(int64_t clientId);
void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalBoardChanged(int64_t clientId);
void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration&jackConfiguration);
void UpdateDefaults(PedalBoardItem*pedalBoardItem);
void UpdateDefaults(PedalBoard*pedalBoard);
class VuSubscription {
public:
int64_t subscriptionHandle;
int64_t instanceid;
};
int64_t nextSubscriptionId = 1;
std::vector<VuSubscription> activeVuSubscriptions;
std::vector<MonitorPortSubscription> activeMonitorPortSubscriptions;
void UpdateRealtimeVuSubscriptions();
void UpdateRealtimeMonitorPortSubscriptions();
void OnVuUpdate(const std::vector<VuUpdate>& updates);
void RestartAudio();
std::vector<RealtimeParameterRequest*> outstandingParameterRequests;
IPiPedalModelSubscriber*GetNotificationSubscriber(int64_t clientId);
private: // IJackHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates);
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
void UpdateVst3Settings(PedalBoard&pedalBoard);
PiPedalConfiguration configuration;
public:
PiPedalModel();
virtual ~PiPedalModel();
uint16_t GetWebPort() const { return webPort; }
void Close();
void UpdateDnsSd();
AdminClient&GetAdminClient() { return adminClient; }
void Init(const PiPedalConfiguration&configuration);
void LoadLv2PluginInfo();
void Load();
const PiPedalHost& GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy()
{
std::lock_guard<std::recursive_mutex> guard(mutex);
return pedalBoard; // can return a referece because we'd lose mutex protection
}
PluginUiPresets GetPluginUiPresets(const std::string& pluginUri);
PluginPresets GetPluginPresets(const std::string&pluginUri);
void LoadPluginPreset(int64_t pluginInstanceId,uint64_t presetInstanceId);
void AddNotificationSubscription(IPiPedalModelSubscriber* pSubscriber);
void RemoveNotificationSubsription(IPiPedalModelSubscriber* pSubscriber);
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
void SetControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
void PreviewControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
void SetPedalBoard(int64_t clientId,PedalBoard &pedalBoard);
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void GetPresets(PresetIndex*pResult);
PedalBoard GetPreset(int64_t instanceId);
void GetBank(int64_t instanceId, BankFile*pBank);
int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter = -1);
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter = -1);
void UploadPluginPresets(const PluginPresets&pluginPresets);
void SaveCurrentPreset(int64_t clientId);
int64_t SaveCurrentPresetAs(int64_t clientId, const std::string& name,int64_t saveAfterInstanceId = -1);
int64_t SavePluginPresetAs(int64_t instanceId, const std::string&name);
void LoadPreset(int64_t clientId, int64_t instanceId);
bool UpdatePresets(int64_t clientId,const PresetIndex&presets);
void UpdatePluginPresets(const PluginUiPresets&pluginPresets);
int64_t DeletePreset(int64_t clientId,int64_t instanceId);
bool RenamePreset(int64_t clientId,int64_t instanceId,const std::string&name);
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
uint64_t CopyPluginPreset(const std::string&pluginUri,uint64_t presetId);
void MoveBank(int64_t clientId,int from, int to);
int64_t DeleteBank(int64_t clientId, int64_t instanceId);
int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId,instanceId,-1); }
JackConfiguration GetJackConfiguration();
void SetJackChannelSelection(int64_t clientId,const JackChannelSelection &channelSelection);
JackChannelSelection GetJackChannelSelection();
void SetShowStatusMonitor(bool show);
bool GetShowStatusMonitor();
void SetWifiConfigSettings(const WifiConfigSettings&wifiConfigSettings);
WifiConfigSettings GetWifiConfigSettings();
void SetWifiDirectConfigSettings(const WifiDirectConfigSettings&wifiConfigSettings);
WifiDirectConfigSettings GetWifiDirectConfigSettings();
void SetGovernorSettings(const std::string& governor);
GovernorSettings GetGovernorSettings();
int64_t AddVuSubscription(int64_t instanceId);
void RemoveVuSubscription(int64_t subscriptionHandle);
int64_t MonitorPort(int64_t instanceId,const std::string &key,float updateInterval, PortMonitorCallback onUpdate);
void UnmonitorPort(int64_t subscriptionHandle);
void GetLv2Parameter(
int64_t clientId,
int64_t instanceId,
const std::string uri,
std::function<void (const std::string&jsonResjult)> onSuccess,
std::function<void (const std::string& error)> onError
);
BankIndex GetBankIndex() const;
void RenameBank(int64_t clientId,int64_t bankId, const std::string& newName);
int64_t SaveBankAs(int64_t clientId,int64_t bankId, const std::string& newName);
void OpenBank(int64_t clientId,int64_t bankId);
JackHostStatus GetJackStatus() {
return this->jackHost->getJackStatus();
}
JackServerSettings GetJackServerSettings();
void SetJackServerSettings(const JackServerSettings& jackServerSettings);
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
std::vector<AlsaDeviceInfo> GetAlsaDevices();
const std::filesystem::path& GetWebRoot() const;
std::map<std::string,bool> GetFavorites() const;
void SetFavorites(const std::map<std::string,bool> &favorites);
};
} // namespace pipedal.
+16 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2023 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
@@ -1235,6 +1235,17 @@ public:
pReader->read(&favorites);
this->model.SetFavorites(favorites);
}
else if (message == "setSystemMidiBindings")
{
std::vector<MidiBinding> bindings;
pReader->read(&bindings);
this->model.SetSystemMidiBindings(bindings);
}
else if (message == "getSystemMidiBindings")
{
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
this->Reply(replyTo,"getSystemMidiBindings",bindings);
}
else
{
Lv2Log::error("Unknown message received: %s", message.c_str());
@@ -1308,6 +1319,10 @@ protected:
}
public:
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
Send("onSystemMidiBindingsChanged",bindings);
}
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
{
Send("onFavoritesChanged", favorites);
+1 -1
View File
@@ -19,7 +19,7 @@
#pragma once
#include "BeastServer.hpp"
#include "WebServer.hpp"
#include "PiPedalModel.hpp"
+42 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2023 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
@@ -22,7 +22,7 @@
#include "PiPedalException.hpp"
#include "Lv2Log.hpp"
#include "VuUpdate.hpp"
#include "JackHost.hpp"
#include "AudioHost.hpp"
namespace pipedal
{
@@ -51,8 +51,25 @@ namespace pipedal
AtomOutput = 17,
MidiProgramChange = 18, // program change requested via midi.
AckMidiProgramChange = 19,
NextMidiProgram = 20,
};
struct RealtimeNextMidiProgramRequest {
int64_t requestId;
int32_t direction;
};
struct RealtimeMidiProgramRequest {
int64_t requestId;
int8_t bank;
uint8_t program;
};
class RealtimeMonitorPortSubscription
{
public:
@@ -304,6 +321,25 @@ namespace pipedal
write(RingBufferCommand::OnMidiListen, msg);
}
/**
* @brief Notify host of a midi program change request.
*
* @param bank MIDI bank number, or -1 for current bank.
* @param program MIDI program number.
*/
void OnMidiProgramChange(int64_t _requestId,int8_t _bank,uint8_t _program)
{
RealtimeMidiProgramRequest msg { requestId:_requestId, bank: _bank,program: _program};
write(RingBufferCommand::MidiProgramChange,msg);
}
void OnNextMidiProgram(int64_t requestId,int32_t direction)
{
RealtimeNextMidiProgramRequest msg { requestId: requestId, direction:direction};
write(RingBufferCommand::NextMidiProgram,msg);
}
void SetControlValue(int effectIndex, int controlIndex, float value)
{
SetControlValueBody body;
@@ -351,6 +387,10 @@ namespace pipedal
{
write(RingBufferCommand::SetVuSubscriptions, configuration);
}
void AckMidiProgramRequest(int64_t requestId)
{
write(RingBufferCommand::AckMidiProgramChange,requestId);
}
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
{
+52 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2023 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
@@ -511,6 +511,7 @@ void Storage::SetPresetIndex(const PresetIndex &presets)
this->currentBank = std::move(bankFile); // deleting any stray presets while we're at it.
this->SaveCurrentBank();
}
void Storage::GetPresetIndex(PresetIndex *pResult)
{
*pResult = PresetIndex();
@@ -523,6 +524,15 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
pResult->presets().push_back(entry);
}
}
int64_t Storage::GetPresetByProgramNumber(uint8_t program)const
{
if (program >= currentBank.presets().size())
{
if (currentBank.presets().size() == 0) return -1;
program = (uint8_t)currentBank.presets().size()-1;
}
return currentBank.presets()[program]->instanceId();
}
PedalBoard Storage::GetPreset(int64_t instanceId) const
{
@@ -736,6 +746,17 @@ void Storage::MoveBank(int from, int to)
this->bankIndex.move(from, to);
this->SaveBankIndex();
}
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) {
auto &entries = this->bankIndex.entries();
if (bankNumber >= entries.size())
{
if (entries.size() == 0) return -1;
bankNumber = (uint8_t)(entries.size()-1);
}
return entries[bankNumber].instanceId();
}
int64_t Storage::DeleteBank(int64_t bankId)
{
auto &entries = this->bankIndex.entries();
@@ -1333,6 +1354,36 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
#endif
}
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
{
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::ofstream f;
f.open(fileName);
if (f.is_open())
{
json_writer writer(f);
writer.write(bindings);
}
}
std::vector<MidiBinding> Storage::GetSystemMidiBindings()
{
std::vector<MidiBinding> result;
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::ifstream f;
f.open(fileName);
if (f.is_open())
{
json_reader reader(f);
reader.read(&result);
} else {
result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram"));
}
return result;
}
JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+5 -1
View File
@@ -109,6 +109,7 @@ public:
void LoadUserSettings();
void SaveUserSettings();
void LoadBank(int64_t instanceId);
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
const PedalBoard& GetCurrentPreset();
void SaveCurrentPreset(const PedalBoard&pedalBoard);
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
@@ -116,11 +117,12 @@ public:
void GetPresetIndex(PresetIndex*pResult);
void SetPresetIndex(const PresetIndex &presetIndex);
PedalBoard GetPreset(int64_t instanceId) const;
int64_t GetPresetByProgramNumber(uint8_t program) const;
void GetBankFile(int64_t instanceId,BankFile*pResult) const;
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter);
bool LoadPreset(int64_t presetId);
int64_t DeletePreset(int64_t presetId);
bool RenamePreset(int64_t presetId, const std::string&name);
@@ -170,6 +172,8 @@ public:
void SetShowStatusMonitor(bool show);
bool GetShowStatusMonitor() const;
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
std::vector<MidiBinding> GetSystemMidiBindings();
};
+12 -12
View File
@@ -18,7 +18,7 @@
#include <strings.h>
#include "Ipv6Helpers.hpp"
#include "BeastServer.hpp"
#include "WebServer.hpp"
#include "Uri.hpp"
@@ -133,7 +133,7 @@ std::string GetFromAddress(const tcp::socket &socket)
namespace pipedal
{
class BeastServerImpl : public BeastServer
class WebServerImpl : public WebServer
{
private:
int signalOnDone = -1;
@@ -200,7 +200,7 @@ namespace pipedal
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession>, public SocketHandler::IWriteCallback
{
BeastServerImpl *pServer;
WebServerImpl *pServer;
server::connection_ptr webSocket;
std::string fromAddress;
std::shared_ptr<SocketHandler> socketHandler;
@@ -224,7 +224,7 @@ namespace pipedal
Lv2Log::info("WebSocketSession closed.");
}
using ptr = std::shared_ptr<WebSocketSession>;
WebSocketSession(BeastServerImpl *pServer, server::connection_ptr &webSocket)
WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
: pServer(pServer),
webSocket(webSocket)
{
@@ -599,9 +599,9 @@ namespace pipedal
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
m_endpoint.set_open_handler(bind(&BeastServerImpl::on_open, this, _1));
m_endpoint.set_close_handler(bind(&BeastServerImpl::on_close, this, _1));
m_endpoint.set_http_handler(bind(&BeastServerImpl::on_http, this, _1));
m_endpoint.set_open_handler(bind(&WebServerImpl::on_open, this, _1));
m_endpoint.set_close_handler(bind(&WebServerImpl::on_close, this, _1));
m_endpoint.set_http_handler(bind(&WebServerImpl::on_http, this, _1));
std::string hostName = getHostName();
if (hostName.length() != 0)
@@ -660,7 +660,7 @@ namespace pipedal
}
}
static void ThreadProc(BeastServerImpl *server)
static void ThreadProc(WebServerImpl *server)
{
server->Run();
}
@@ -717,7 +717,7 @@ namespace pipedal
this->pBgThread = new std::thread(ThreadProc, this);
}
BeastServerImpl(const std::string &address, int port, const char *rootPath, int threads)
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads)
: address(address),
rootPath(rootPath),
port(port),
@@ -727,7 +727,7 @@ namespace pipedal
};
} // namespace pipedal
std::shared_ptr<ISocketFactory> BeastServerImpl::GetSocketFactory(const uri &requestUri)
std::shared_ptr<ISocketFactory> WebServerImpl::GetSocketFactory(const uri &requestUri)
{
for (auto factory : this->socket_factories)
@@ -740,7 +740,7 @@ std::shared_ptr<ISocketFactory> BeastServerImpl::GetSocketFactory(const uri &req
return nullptr;
}
std::shared_ptr<BeastServer> pipedal::createBeastServer(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
std::shared_ptr<WebServer> pipedal::WebServer::create(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
{
return std::shared_ptr<BeastServer>(new BeastServerImpl(address.to_string(), port, rootPath, threads));
return std::shared_ptr<WebServer>(new WebServerImpl(address.to_string(), port, rootPath, threads));
}
+12 -5
View File
@@ -68,10 +68,10 @@ public:
//xxx move this to HtmlHelpers.
std::string last_modified(const std::filesystem::path& path);
class BeastServerImpl;
class WebServerImpl;
class SocketHandler {
friend class BeastServerImpl;
friend class WebServerImpl;
public:
class IWriteCallback {
@@ -213,9 +213,9 @@ public:
};
class BeastServer {
class WebServer {
public:
virtual ~BeastServer() { }
virtual ~WebServer() { }
virtual void SetLogHttpRequests(bool enableLogging) = 0;
@@ -227,10 +227,17 @@ public:
// signalOnDone: fire the specified POSIX signal when the service thread terminates. -1 for no signal.
virtual void RunInBackground(int signalOnDone = -1) = 0;
static std::shared_ptr<WebServer> create(
const boost::asio::ip::address &address,
int port,
const char *rootPath,
int threads);
};
std::shared_ptr<BeastServer> createBeastServer(const boost::asio::ip::address &address, int port, const char *rootPath, int threads);
@@ -19,13 +19,13 @@
#include "pch.h"
#include "catch.hpp"
#include "BeastServer.hpp"
#include "WebServer.hpp"
#include "MemDebug.hpp"
#include <iostream>
using namespace pipedal;
TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
TEST_CASE("WebServer shutdown", "[webServerShutdown][Build][Dev]")
{
MemStats initialMemory = GetMemStats();
{
@@ -34,7 +34,7 @@ TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
std::string doc_root = ".";
auto const threads = 3;
auto server = createBeastServer(
auto server = WebServer::create(
address, port, doc_root.c_str(), threads);
server->RunInBackground();
sleep(5);
+3 -3
View File
@@ -20,7 +20,7 @@
#include "pch.h"
#include "AudioConfig.hpp"
#include "BeastServer.hpp"
#include "WebServer.hpp"
#include <iostream>
#include "Lv2Log.hpp"
#include "ServiceConfiguration.hpp"
@@ -601,7 +601,7 @@ int main(int argc, char *argv[])
}
uint16_t port;
std::shared_ptr<BeastServer> server;
std::shared_ptr<WebServer> server;
try
{
auto const address = boost::asio::ip::make_address(configuration.GetSocketServerAddress());
@@ -609,7 +609,7 @@ int main(int argc, char *argv[])
auto const threads = std::max<int>(1, configuration.GetThreads());
server = createBeastServer(
server = WebServer::create(
address, port, web_root.c_str(), threads);
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)threads);
+2 -2
View File
@@ -42,8 +42,8 @@ add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
add_executable(piddletest main.cpp pch.h UriTest.cpp JsonTest.cpp
../src/Uri.cpp ../src/Uri.hpp ../src/HtmlHelper.cpp ../src/HtmlHelper.hpp BeastServerTest.cpp
../src/BeastServer.cpp ../src/BeastServer.hpp ../src/json.hpp ../src/json.cpp
../src/Uri.cpp ../src/Uri.hpp ../src/HtmlHelper.cpp ../src/HtmlHelper.hpp WebServerTest.cpp
../src/WebServer.cpp ../src/WebServer.hpp ../src/json.hpp ../src/json.cpp
../src/Lv2Host.cpp ../src/Lv2Host.hpp
../src/Lv2Log.hpp ../src/Lv2Log.cpp
../src/PluginType.hpp ../src/PluginType.cpp