- 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) cmake_minimum_required(VERSION 3.16.0)
project(pipedal project(pipedal
VERSION 1.0.17 VERSION 1.0.18
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
set (DISPLAY_VERSION "v1.0.17") set (DISPLAY_VERSION "v1.0.18")
set (CMAKE_INSTALL_PREFIX "/usr/") 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 Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"), 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/> <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). 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 Pre-Depends: hostapd;authbind
Priority: optional Priority: optional
Section: sound Section: sound
Version: 1.0.17 Version: 1.0.18
Installed-Size: 15147 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: 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 Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads 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. 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: 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 Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads 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. 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%"/> <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; &nbsp;
+5
View File
@@ -31,6 +31,11 @@ export default class MidiBinding {
this.switchControlType = input.switchControlType; this.switchControlType = input.switchControlType;
return this; return this;
} }
static systemBinding(symbol: string): MidiBinding {
let result = new MidiBinding();
result.symbol = symbol;
return result;
}
static deserialize_array(input: any): MidiBinding[] { static deserialize_array(input: any): MidiBinding[] {
let result: MidiBinding[] = []; let result: MidiBinding[] = [];
for (let i = 0; i < input.length; ++i) 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)} onChange={(e, extra) => this.handleLatchControlTypeChange(e, extra)}
value={midiBinding.switchControlType} 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> <MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>Momentary</MenuItem>
</Select> </Select>
</div> </div>
+54 -24
View File
@@ -339,6 +339,7 @@ export interface PiPedalModel {
favorites: ObservableProperty<FavoritesList>; favorites: ObservableProperty<FavoritesList>;
presets: ObservableProperty<PresetIndex>; presets: ObservableProperty<PresetIndex>;
systemMidiBindings: ObservableProperty<MidiBinding[]>;
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined>; zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined>;
@@ -409,6 +410,8 @@ export interface PiPedalModel {
updatePluginPresets(uri: string, presets: PluginUiPresets): Promise<void>; updatePluginPresets(uri: string, presets: PluginUiPresets): Promise<void>;
duplicatePluginPreset(uri: string, instanceId: number): Promise<number>; duplicatePluginPreset(uri: string, instanceId: number): Promise<number>;
setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
shutdown(): Promise<void>; shutdown(): Promise<void>;
restart(): Promise<void>; restart(): Promise<void>;
@@ -495,7 +498,13 @@ class PiPedalModelImpl implements PiPedalModel {
( (
new PresetIndex() new PresetIndex()
); );
systemMidiBindings: ObservableProperty<MidiBinding[]> = new ObservableProperty<MidiBinding[]>
(
[
MidiBinding.systemBinding("prevProgram"),
MidiBinding.systemBinding("nextProgram")
]
);
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined); zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
svgImgUrl(svgImage: string): string { svgImgUrl(svgImage: string): string {
@@ -682,7 +691,10 @@ class PiPedalModelImpl implements PiPedalModel {
if (header.replyTo) { if (header.replyTo) {
this.webSocket?.reply(header.replyTo, "onVuUpdate", true); this.webSocket?.reply(header.replyTo, "onVuUpdate", true);
} }
} else if (message === "onSystemMidiBindingsChanged")
{
let bindings = MidiBinding.deserialize_array(body);
this.systemMidiBindings.set(bindings);
} else { } else {
throw new PiPedalStateError("Unrecognized message received from server: " + message); throw new PiPedalStateError("Unrecognized message received from server: " + message);
} }
@@ -815,6 +827,12 @@ class PiPedalModelImpl implements PiPedalModel {
.then((data) => { .then((data) => {
this.favorites.set(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); this.setState(State.Ready);
}) })
.catch((what) => { .catch((what) => {
@@ -977,6 +995,12 @@ class PiPedalModelImpl implements PiPedalModel {
.then((data) => { .then((data) => {
this.favorites.set(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) { if (this.webSocket) {
// MUST not allow reconnect until at least one complete load has finished. // MUST not allow reconnect until at least one complete load has finished.
this.webSocket.canReconnect = true; this.webSocket.canReconnect = true;
@@ -1837,8 +1861,7 @@ class PiPedalModelImpl implements PiPedalModel {
}); });
} }
updateVst3State(pedalBoard: PedalBoard) updateVst3State(pedalBoard: PedalBoard) {
{
// let it = pedalBoard.itemsGenerator(); // let it = pedalBoard.itemsGenerator();
// while (true) { // while (true) {
// let v = it.next(); // 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[] = []; midiListeners: MidiEventListener[] = [];
atomOutputListeners: AtomOutputListener[] = []; atomOutputListeners: AtomOutputListener[] = [];
@@ -1918,8 +1958,7 @@ class PiPedalModelImpl implements PiPedalModel {
break; break;
} }
} }
if (!found) if (!found) {
{
console.log('cancelListenForMidiEvent: event not found.'); console.log('cancelListenForMidiEvent: event not found.');
} }
this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle); this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle);
@@ -2183,19 +2222,14 @@ class PiPedalModelImpl implements PiPedalModel {
} }
zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void { zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void {
let name = uiControl.name; let name = uiControl.name;
if (uiControl.port_group !== "") if (uiControl.port_group !== "") {
{
let pedalboard = this.pedalBoard.get(); let pedalboard = this.pedalBoard.get();
if (pedalboard) if (pedalboard) {
{
let plugin = pedalboard.getItem(instanceId); let plugin = pedalboard.getItem(instanceId);
let uiPlugin = this.getUiPlugin(plugin.uri); let uiPlugin = this.getUiPlugin(plugin.uri);
if (uiPlugin) if (uiPlugin) {
{ for (let i = 0; i < uiPlugin.port_groups.length; ++i) {
for (let i = 0; i < uiPlugin.port_groups.length; ++i) if (uiPlugin.port_groups[i].symbol === uiControl.port_group) {
{
if (uiPlugin.port_groups[i].symbol === uiControl.port_group)
{
name = uiPlugin.port_groups[i].name + " / " + name; name = uiPlugin.port_groups[i].name + " / " + name;
break; break;
} }
@@ -2205,8 +2239,7 @@ class PiPedalModelImpl implements PiPedalModel {
} }
this.zoomedUiControl.set({ source: sourceElement, name: name, instanceId: instanceId, uiControl: uiControl }); this.zoomedUiControl.set({ source: sourceElement, name: name, instanceId: instanceId, uiControl: uiControl });
} }
onPreviousZoomedControl() : void onPreviousZoomedControl(): void {
{
let currentUiControl = this.zoomedUiControl.get(); let currentUiControl = this.zoomedUiControl.get();
if (!currentUiControl) return; if (!currentUiControl) return;
@@ -2222,8 +2255,7 @@ class PiPedalModelImpl implements PiPedalModel {
let i = 0; let i = 0;
let ix = -1; 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) { if (uiPlugin.controls[i].symbol === currentSymbol) {
ix = i; ix = i;
break; break;
@@ -2236,8 +2268,7 @@ class PiPedalModelImpl implements PiPedalModel {
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(); let currentUiControl = this.zoomedUiControl.get();
if (!currentUiControl) return; if (!currentUiControl) return;
@@ -2253,8 +2284,7 @@ class PiPedalModelImpl implements PiPedalModel {
let i = 0; let i = 0;
let ix = -1; 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) { if (uiPlugin.controls[i].symbol === currentSymbol) {
ix = i; ix = i;
break; break;
+21
View File
@@ -44,6 +44,7 @@ import WifiDirectConfigDialog from './WifiDirectConfigDialog';
import DialogEx from './DialogEx' import DialogEx from './DialogEx'
import GovernorSettings from './GovernorSettings'; import GovernorSettings from './GovernorSettings';
import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo'; import { AlsaMidiDeviceInfo } from './AlsaMidiDeviceInfo';
import SystemMidiBindingsDialog from './SystemMidiBindingsDialog';
import Slide, { SlideProps } from '@mui/material/Slide'; import Slide, { SlideProps } from '@mui/material/Slide';
import { createStyles, Theme } from '@mui/material/styles'; import { createStyles, Theme } from '@mui/material/styles';
@@ -84,6 +85,7 @@ interface SettingsDialogState {
isAndroidHosted: boolean; isAndroidHosted: boolean;
showRestartOkDialog: boolean; showRestartOkDialog: boolean;
showShutdownOkDialog: boolean; showShutdownOkDialog: boolean;
showSystemMidiBindingsDialog: boolean;
}; };
@@ -182,6 +184,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
restarting: false, restarting: false,
showShutdownOkDialog: false, showShutdownOkDialog: false,
showRestartOkDialog: false, showRestartOkDialog: false,
showSystemMidiBindingsDialog: false,
isAndroidHosted: this.model.isAndroidHosted() isAndroidHosted: this.model.isAndroidHosted()
@@ -376,6 +379,12 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
showMidiSelectDialog: true showMidiSelectDialog: true
}) })
} }
handleMidiMessageSettings() {
this.setState({
showSystemMidiBindingsDialog: true
});
}
handleJackServerSettings() { handleJackServerSettings() {
this.setState({ this.setState({
showJackServerSettingsDialog: true, 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> <Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{this.midiSummary()}</Typography>
</div> </div>
</ButtonBase> </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>
</div> </div>
<Divider /> <Divider />
@@ -840,6 +857,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
onOk={() => { this.setState({ showShutdownOkDialog: false }); this.handleShutdownOk(); }} onOk={() => { this.setState({ showShutdownOkDialog: false }); this.handleShutdownOk(); }}
onClose={() => { this.setState({ showShutdownOkDialog: false }); }} onClose={() => { this.setState({ showShutdownOkDialog: false }); }}
/> />
<SystemMidiBindingsDialog
open={this.state.showSystemMidiBindingsDialog}
onClose={()=> { this.setState({showSystemMidiBindingsDialog: false});}}
/>
</DialogEx > </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;
+311 -107
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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -17,19 +17,17 @@
// 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 "JackHost.hpp" #include "AudioHost.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include "JackDriver.hpp" #include "JackDriver.hpp"
#include "AlsaDriver.hpp" #include "AlsaDriver.hpp"
using namespace pipedal; using namespace pipedal;
#include "AudioConfig.hpp" #include "AudioConfig.hpp"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <mutex> #include <mutex>
@@ -66,13 +64,13 @@ using namespace pipedal;
const int MIDI_LV2_BUFFER_SIZE = 16 * 1024; const int MIDI_LV2_BUFFER_SIZE = 16 * 1024;
static void GetCpuFrequency(uint64_t *freqMin, uint64_t *freqMax) static void GetCpuFrequency(uint64_t *freqMin, uint64_t *freqMax)
{ {
uint64_t fMax = 0; uint64_t fMax = 0;
uint64_t fMin = UINT64_MAX; uint64_t fMin = UINT64_MAX;
char deviceName[128]; char deviceName[128];
try { try
{
for (int i = 0; true; ++i) for (int i = 0; true; ++i)
{ {
@@ -84,15 +82,19 @@ static void GetCpuFrequency(uint64_t*freqMin,uint64_t*freqMax)
} }
uint64_t freq; uint64_t freq;
f >> freq; f >> freq;
if (!f) break; if (!f)
if (freq < fMin) fMin = freq; break;
if (freq > fMax) fMax = freq; if (freq < fMin)
fMin = freq;
if (freq > fMax)
fMax = freq;
} }
} catch (const std::exception &) }
catch (const std::exception &)
{ {
} }
if (fMin == 0) fMax = 0; if (fMin == 0)
fMax = 0;
*freqMin = fMin; *freqMin = fMin;
*freqMax = fMax; *freqMax = fMax;
} }
@@ -102,12 +104,88 @@ static std::string GetGovernor()
} }
class SystemMidiBinding {
private:
MidiBinding currentBinding;
bool controlState = false;
public:
void SetBinding(const MidiBinding&binding) { currentBinding = binding; controlState = false; }
class JackHostImpl : public JackHost, private AudioDriverHost bool IsMatch(const MidiEvent&event);
bool IsTriggered(const MidiEvent&event);
};
bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
{
switch (currentBinding.bindingType())
{
case BINDING_TYPE_NOTE:
{
if (event.size != 3) return false;
auto command = event.buffer[0] & 0xF0;
if (command != 0x90) return false; // MIDI note on
if (event.buffer[1] != currentBinding.note()) return false;
return event.buffer[2] != 0; // note-on with velicity of zero is a note-off.
}
break;
case BINDING_TYPE_CONTROL:
{
if (event.size != 3) return false;
auto command = event.buffer[0] & 0xF0;
if (command != 0xB0) return false;
if (event.buffer[1] != currentBinding.control()) return false;
bool state = event.buffer[2] >= 0x64;
if (state != this->controlState)
{
this->controlState = state;
return state;
}
return false;
}
break;
default:
return false;
}
}
bool SystemMidiBinding::IsMatch(const MidiEvent&event)
{
switch (currentBinding.bindingType())
{
case BINDING_TYPE_NOTE:
{
if (event.size != 3) return false;
auto command = event.buffer[0] & 0xF0;
if (command != 0x80 && command != 0x90) return false; // note on/note off.
return event.buffer[1] == currentBinding.note();
}
break;
case BINDING_TYPE_CONTROL:
{
if (event.size != 3) return false;
auto command = event.buffer[0] & 0xF0;
if (command != 0xB0) return false;
return event.buffer[1] == currentBinding.control();
}
break;
default:
return false;
}
}
class AudioHostImpl : public AudioHost, private AudioDriverHost
{ {
private: private:
IHost *pHost = nullptr; IHost *pHost = nullptr;
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE];
size_t deferredMidiMessageCount;
bool midiProgramChangePending = false;
int selectedBank = -1;
int64_t midiProgramChangeId = 0;
class Uris class Uris
{ {
public: public:
@@ -124,7 +202,6 @@ private:
atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector); atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object); atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object);
atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence); atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk); atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID); atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID);
@@ -138,7 +215,6 @@ private:
// patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept); // patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept);
patch_value = pHost->GetLv2Urid(LV2_PATCH__value); patch_value = pHost->GetLv2Urid(LV2_PATCH__value);
unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame); unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame);
} }
// LV2_URID patch_accept; // LV2_URID patch_accept;
@@ -171,16 +247,14 @@ private:
Uris uris; Uris uris;
AudioDriver *audioDriver = nullptr; AudioDriver *audioDriver = nullptr;
inherit_priority_recursive_mutex mutex; inherit_priority_recursive_mutex mutex;
int64_t overrunGracePeriodSamples = 0; int64_t overrunGracePeriodSamples = 0;
IJackHostCallbacks *pNotifyCallbacks = nullptr; IAudioHostCallbacks *pNotifyCallbacks = nullptr;
virtual void SetNotificationCallbacks(IJackHostCallbacks *pNotifyCallbacks) virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks)
{ {
this->pNotifyCallbacks = pNotifyCallbacks; this->pNotifyCallbacks = pNotifyCallbacks;
} }
@@ -197,6 +271,9 @@ private:
HostRingBufferReader hostReader; HostRingBufferReader hostReader;
HostRingBufferWriter hostWriter; HostRingBufferWriter hostWriter;
SystemMidiBinding nextMidiBinding;
SystemMidiBinding prevMidiBinding;
JackChannelSelection channelSelection; JackChannelSelection channelSelection;
bool active = false; bool active = false;
@@ -213,7 +290,6 @@ private:
std::atomic<std::chrono::system_clock::time_point> lastUnderrunTime = std::atomic<std::chrono::system_clock::time_point> lastUnderrunTime =
std::chrono::system_clock::from_time_t(0); std::chrono::system_clock::from_time_t(0);
std::string GetAtomObjectType(uint8_t *pData) std::string GetAtomObjectType(uint8_t *pData)
{ {
LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData; LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData;
@@ -222,7 +298,6 @@ private:
throw std::invalid_argument("Not an Lv2 Object"); throw std::invalid_argument("Not an Lv2 Object");
} }
return pHost->Lv2UriudToString(pAtom->body.otype); return pHost->Lv2UriudToString(pAtom->body.otype);
} }
void WriteAtom(json_writer &writer, LV2_Atom *pAtom); void WriteAtom(json_writer &writer, LV2_Atom *pAtom);
@@ -243,7 +318,6 @@ private:
this->lastUnderrunTime = std::chrono::system_clock ::now(); this->lastUnderrunTime = std::chrono::system_clock ::now();
} }
virtual void Close() virtual void Close()
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
@@ -286,13 +360,11 @@ private:
this->inputRingBuffer.reset(); this->inputRingBuffer.reset();
this->outputRingBuffer.reset(); this->outputRingBuffer.reset();
for (size_t i = 0; i < midiLv2Buffers.size(); ++i) for (size_t i = 0; i < midiLv2Buffers.size(); ++i)
{ {
delete[] midiLv2Buffers[i]; delete[] midiLv2Buffers[i];
} }
midiLv2Buffers.resize(0); midiLv2Buffers.resize(0);
} }
void ZeroBuffer(float *buffer, size_t nframes) void ZeroBuffer(float *buffer, size_t nframes)
@@ -341,6 +413,9 @@ private:
} }
} }
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
void writeVu() void writeVu()
{ {
// throttling: we send one; but won't send another until the host thread // throttling: we send one; but won't send another until the host thread
@@ -459,6 +534,16 @@ private:
this->realtimeMonitorPortSubscriptions = pSubscriptions; this->realtimeMonitorPortSubscriptions = pSubscriptions;
break; break;
} }
case RingBufferCommand::AckMidiProgramChange:
{
int64_t requestId;
realtimeReader.readComplete(&requestId);
if (requestId == this->midiProgramChangeId)
{
this->midiProgramChangePending = false;
}
break;
}
case RingBufferCommand::SetVuSubscriptions: case RingBufferCommand::SetVuSubscriptions:
{ {
RealtimeVuBuffers *configuration; RealtimeVuBuffers *configuration;
@@ -485,6 +570,8 @@ private:
ReplaceEffectBody body; ReplaceEffectBody body;
realtimeReader.readComplete(&body); realtimeReader.readComplete(&body);
if (body.effect != nullptr)
{
auto oldValue = this->realtimeActivePedalBoard; auto oldValue = this->realtimeActivePedalBoard;
this->realtimeActivePedalBoard = body.effect; this->realtimeActivePedalBoard = body.effect;
@@ -493,6 +580,7 @@ private:
// invalidate the possibly no-good subscriptions. Model will update them shortly. // invalidate the possibly no-good subscriptions. Model will update them shortly.
freeRealtimeVuConfiguration(); freeRealtimeVuConfiguration();
freeRealtimeMonitorPortSubscriptions(); freeRealtimeMonitorPortSubscriptions();
}
break; break;
} }
default: default:
@@ -502,38 +590,25 @@ private:
reEntered = false; reEntered = false;
} }
virtual void AckMidiProgramRequest(uint64_t requestId)
{
hostWriter.AckMidiProgramRequest(requestId);
}
void OnMidiValueChanged(uint64_t instanceId, int controlIndex, float value) void OnMidiValueChanged(uint64_t instanceId, int controlIndex, float value)
{ {
realtimeWriter.MidiValueChanged(instanceId, controlIndex, value); realtimeWriter.MidiValueChanged(instanceId, controlIndex, value);
} }
static void fnMidiValueChanged(void *data, uint64_t instanceId, int controlIndex, float value) static void fnMidiValueChanged(void *data, uint64_t instanceId, int controlIndex, float value)
{ {
((JackHostImpl *)data)->OnMidiValueChanged(instanceId, controlIndex, value); ((AudioHostImpl *)data)->OnMidiValueChanged(instanceId, controlIndex, value);
} }
void ProcessJackMidi() static bool isBankChange(MidiEvent &event)
{ {
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids); return (event.size == 3 && event.buffer[0] == 0xB0 && event.buffer[1] == 0x00);
}
size_t midiInputBufferCount = audioDriver->MidiInputBufferCount(); bool onMidiEvent(Lv2EventBufferWriter&eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent&event)
audioDriver->FillMidiBuffers();
for (size_t i = 0; i < midiInputBufferCount; ++i)
{
void *portBuffer = audioDriver->GetMidiInputBuffer(i,0);
if (portBuffer)
{
uint8_t *lv2Buffer = this->midiLv2Buffers[i];
size_t n = audioDriver->GetMidiInputEventCount(portBuffer);
eventBufferWriter.Reset(lv2Buffer, MIDI_LV2_BUFFER_SIZE);
auto iterator = eventBufferWriter.begin();
for (size_t frame = 0; frame < n; ++frame)
{
MidiEvent event;
if (audioDriver->GetMidiInputEvent(&event,portBuffer,frame))
{ {
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
@@ -551,6 +626,102 @@ private:
} }
} }
} }
return true;
}
void ProcessMidiInput()
{
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids);
size_t midiInputBufferCount = audioDriver->MidiInputBufferCount();
audioDriver->FillMidiBuffers();
for (size_t midiDeviceIx = 0; midiDeviceIx < midiInputBufferCount; ++midiDeviceIx)
{
void *portBuffer = audioDriver->GetMidiInputBuffer(midiDeviceIx, 0);
if (portBuffer)
{
uint8_t *lv2Buffer = this->midiLv2Buffers[midiDeviceIx];
size_t n = audioDriver->GetMidiInputEventCount(portBuffer);
eventBufferWriter.Reset(lv2Buffer, MIDI_LV2_BUFFER_SIZE);
Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin();
MidiEvent event;
// write all deferred midi messages.
if (deferredMidiMessageCount != 0 && !midiProgramChangePending)
{
for (size_t i = 0; i < deferredMidiMessageCount; /**/)
{
int8_t deviceIndex = deferredMidiMessages[i++];
int8_t messageCount = deferredMidiMessages[i++];
if (deviceIndex == midiDeviceIx)
{
event.size = messageCount;
event.buffer = deferredMidiMessages +i;
event.time = 0;
onMidiEvent(eventBufferWriter,iterator,event);
}
i += messageCount;
}
}
for (size_t frame = 0; frame < n; ++frame)
{
if (audioDriver->GetMidiInputEvent(&event, portBuffer, frame))
{
uint8_t midiCommand = (uint8_t)(event.buffer[0] & 0xF0);
if (midiCommand == 0xC0) // midi program change.
{
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true;
this->realtimeWriter.OnMidiProgramChange(++(this->midiProgramChangeId), selectedBank,event.buffer[1]);
}
else if (isBankChange(event))
{
this->selectedBank = event.buffer[2];
} else if (this->nextMidiBinding.IsMatch(event))
{
if (nextMidiBinding.IsTriggered(event))
{
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId),1);
}
} else if (this->prevMidiBinding.IsMatch(event))
{
if (prevMidiBinding.IsTriggered(event))
{
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId),-1);
}
}
else if (midiProgramChangePending)
{
// defer the message for processing after the program change has completed.
if (event.size > 0
&& event.size < 128
&& event.size + 2 + this->deferredMidiMessageCount < DEFERRED_MIDI_BUFFER_SIZE)
{
this->deferredMidiMessages[deferredMidiMessageCount++] = midiDeviceIx;
this->deferredMidiMessages[deferredMidiMessageCount++] = (uint8_t)event.size;
for (size_t i = 0; i < event.size; ++i)
{
this->deferredMidiMessages[deferredMidiMessageCount++] = event.buffer[i];
}
}
}
else
{
onMidiEvent(eventBufferWriter,iterator,event);
}
} }
} }
} }
@@ -571,7 +742,6 @@ private:
std::lock_guard lock{audioStoppedMutex}; std::lock_guard lock{audioStoppedMutex};
this->active = false; this->active = false;
Lv2Log::info("Audio stopped."); Lv2Log::info("Audio stopped.");
} }
virtual void OnProcess(size_t nframes) virtual void OnProcess(size_t nframes)
@@ -589,7 +759,7 @@ private:
Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard; Lv2PedalBoard *pedalBoard = this->realtimeActivePedalBoard;
if (pedalBoard != nullptr) if (pedalBoard != nullptr)
{ {
ProcessJackMidi(); ProcessMidiInput();
float *inputBuffers[4]; float *inputBuffers[4];
float *outputBuffers[4]; float *outputBuffers[4];
bool buffersValid = true; bool buffersValid = true;
@@ -664,18 +834,16 @@ private:
this->underruns = 0; this->underruns = 0;
} }
this->currentSample += nframes; this->currentSample += nframes;
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
Lv2Log::error("Fatal error while processing jack audio. (%s)", e.what()); Lv2Log::error("Fatal error while processing jack audio. (%s)", e.what());
throw; throw;
} }
} }
public: public:
JackHostImpl(IHost *pHost) AudioHostImpl(IHost *pHost)
: inputRingBuffer(RING_BUFFER_SIZE), : inputRingBuffer(RING_BUFFER_SIZE),
outputRingBuffer(RING_BUFFER_SIZE), outputRingBuffer(RING_BUFFER_SIZE),
realtimeReader(&this->inputRingBuffer), realtimeReader(&this->inputRingBuffer),
@@ -693,15 +861,13 @@ public:
#if ALSA_HOST #if ALSA_HOST
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
#endif #endif
} }
virtual ~JackHostImpl() virtual ~AudioHostImpl()
{ {
Close(); Close();
CleanRestartThreads(true); CleanRestartThreads(true);
delete audioDriver; delete audioDriver;
} }
virtual JackConfiguration GetServerConfiguration() virtual JackConfiguration GetServerConfiguration()
@@ -717,6 +883,8 @@ public:
return this->sampleRate; return this->sampleRate;
} }
void OnAudioComplete() void OnAudioComplete()
{ {
// there is actually no compelling circumstance in which this should ever happen. // there is actually no compelling circumstance in which this should ever happen.
@@ -878,7 +1046,8 @@ public:
this->pNotifyCallbacks->OnNotifyVusSubscription(*updates); this->pNotifyCallbacks->OnNotifyVusSubscription(*updates);
} }
this->hostWriter.AckVuUpdate(); // please sir, can I have some more? this->hostWriter.AckVuUpdate(); // please sir, can I have some more?
} else if (command == RingBufferCommand::AtomOutput) }
else if (command == RingBufferCommand::AtomOutput)
{ {
uint64_t instanceId; uint64_t instanceId;
hostReader.read(&instanceId); hostReader.read(&instanceId);
@@ -890,7 +1059,6 @@ public:
} }
hostReader.read(extraBytes, &(atomBuffer[0])); hostReader.read(extraBytes, &(atomBuffer[0]));
IEffect *pEffect = currentPedalBoard->GetEffect(instanceId); IEffect *pEffect = currentPedalBoard->GetEffect(instanceId);
if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput) if (pEffect != nullptr && this->pNotifyCallbacks && listenForAtomOutput)
{ {
@@ -923,6 +1091,16 @@ public:
hostReader.read(&body); hostReader.read(&body);
OnAudioComplete(); OnAudioComplete();
return; return;
} else if (command == RingBufferCommand::MidiProgramChange)
{
RealtimeMidiProgramRequest programRequest;
hostReader.read(&programRequest);
OnMidiProgramRequest(programRequest);
} else if (command == RingBufferCommand::NextMidiProgram)
{
RealtimeNextMidiProgramRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
} }
else else
{ {
@@ -982,13 +1160,11 @@ public:
return result; return result;
} }
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
if (channelSelection.GetInputAudioPorts().size() == 0 if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0)
|| channelSelection.GetOutputAudioPorts().size() == 0)
{ {
return; return;
} }
@@ -1009,7 +1185,8 @@ public:
StartReaderThread(); StartReaderThread();
try { try
{
audioDriver->Open(jackServerSettings, this->channelSelection); audioDriver->Open(jackServerSettings, this->channelSelection);
@@ -1018,18 +1195,15 @@ public:
this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate) * OVERRUN_GRACE_PERIOD_S); this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate) * OVERRUN_GRACE_PERIOD_S);
this->vuSamplesPerUpdate = (size_t)(sampleRate * VU_UPDATE_RATE_S); this->vuSamplesPerUpdate = (size_t)(sampleRate * VU_UPDATE_RATE_S);
midiLv2Buffers.resize(audioDriver->MidiInputBufferCount()); midiLv2Buffers.resize(audioDriver->MidiInputBufferCount());
for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i) for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i)
{ {
midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE); midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE);
} }
active = true; active = true;
audioDriver->Activate(); audioDriver->Activate();
Lv2Log::info("Audio started."); Lv2Log::info("Audio started.");
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@@ -1040,6 +1214,10 @@ public:
} }
} }
void OnMidiProgramRequest(RealtimeMidiProgramRequest &programRequest)
{
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
}
void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard) void OnActivePedalBoardReleased(Lv2PedalBoard *pPedalBoard)
{ {
if (pPedalBoard) if (pPedalBoard)
@@ -1207,7 +1385,7 @@ public:
private: private:
class RestartThread class RestartThread
{ {
JackHostImpl *this_; AudioHostImpl *this_;
JackServerSettings jackServerSettings; JackServerSettings jackServerSettings;
std::function<void(bool success, const std::string &errorMessage)> onComplete; std::function<void(bool success, const std::string &errorMessage)> onComplete;
std::atomic<bool> isComplete = false; std::atomic<bool> isComplete = false;
@@ -1215,7 +1393,7 @@ private:
public: public:
RestartThread( RestartThread(
JackHostImpl *host, AudioHostImpl *host,
const JackServerSettings &jackServerSettings_, const JackServerSettings &jackServerSettings_,
std::function<void(bool success, const std::string &errorMessage)> onComplete_) std::function<void(bool success, const std::string &errorMessage)> onComplete_)
: this_(host), : this_(host),
@@ -1234,17 +1412,18 @@ private:
{ {
this_->restarting = true; this_->restarting = true;
// this_->Close(); (JackServerConfiguration now does a service restart.) // this_->Close(); (JackServerConfiguration now does a service restart.)
try { try
{
AdminClient client; AdminClient client;
client.SetJackServerConfiguration(jackServerSettings); client.SetJackServerConfiguration(jackServerSettings);
// this_->Open(this_->channelSelection); // this_->Open(this_->channelSelection);
this_->restarting = false; this_->restarting = false;
onComplete(true, ""); onComplete(true, "");
isComplete = true; isComplete = true;
} catch (const std::exception &e) }
catch (const std::exception &e)
{ {
onComplete(false, e.what()); onComplete(false, e.what());
this_->restarting = false; this_->restarting = false;
@@ -1367,7 +1546,8 @@ public:
} }
}; };
static std::string UriToFieldName(const std::string&uri){ static std::string UriToFieldName(const std::string &uri)
{
int pos; int pos;
for (pos = uri.length(); pos >= 0; --pos) for (pos = uri.length(); pos >= 0; --pos)
{ {
@@ -1380,7 +1560,7 @@ static std::string UriToFieldName(const std::string&uri){
return uri.substr(pos + 1); return uri.substr(pos + 1);
} }
void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom) void AudioHostImpl::WriteAtom(json_writer &writer, LV2_Atom *pAtom)
{ {
if (pAtom->type == uris.atom_Blank) if (pAtom->type == uris.atom_Blank)
{ {
@@ -1389,36 +1569,35 @@ void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
else if (pAtom->type == uris.atom_float) else if (pAtom->type == uris.atom_float)
{ {
writer.write( writer.write(
((LV2_Atom_Float*)pAtom)->body ((LV2_Atom_Float *)pAtom)->body);
); }
else if (pAtom->type == uris.atom_Int)
} else if (pAtom->type == uris.atom_Int)
{ {
writer.write( writer.write(
((LV2_Atom_Int*)pAtom)->body ((LV2_Atom_Int *)pAtom)->body);
); }
} else if (pAtom->type == uris.atom_Long) else if (pAtom->type == uris.atom_Long)
{ {
writer.write( writer.write(
((LV2_Atom_Long*)pAtom)->body ((LV2_Atom_Long *)pAtom)->body);
); }
} else if (pAtom->type == uris.atom_Double) else if (pAtom->type == uris.atom_Double)
{ {
writer.write( writer.write(
((LV2_Atom_Double*)pAtom)->body ((LV2_Atom_Double *)pAtom)->body);
); }
} else if (pAtom->type == uris.atom_Bool) else if (pAtom->type == uris.atom_Bool)
{ {
writer.write( writer.write(
((LV2_Atom_Bool*)pAtom)->body ((LV2_Atom_Bool *)pAtom)->body);
); }
} else if (pAtom->type == uris.atom_String) else if (pAtom->type == uris.atom_String)
{ {
const char *p = (((const char *)pAtom) + sizeof(LV2_Atom_String)); const char *p = (((const char *)pAtom) + sizeof(LV2_Atom_String));
writer.write( writer.write(
p p);
); }
} else if (pAtom->type == uris.atom_Vector) else if (pAtom->type == uris.atom_Vector)
{ {
LV2_Atom_Vector *pVector = (LV2_Atom_Vector *)pAtom; LV2_Atom_Vector *pVector = (LV2_Atom_Vector *)pAtom;
writer.start_array(); writer.start_array();
@@ -1430,45 +1609,55 @@ void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
float *p = (float *)pItems; float *p = (float *)pItems;
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
if (i != 0) writer.write_raw(","); if (i != 0)
writer.write_raw(",");
writer.write(*p++); writer.write(*p++);
} }
} else if (pVector->body.child_type == uris.atom_Int) }
else if (pVector->body.child_type == uris.atom_Int)
{ {
int32_t *p = (int32_t *)pItems; int32_t *p = (int32_t *)pItems;
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
if (i != 0) writer.write_raw(","); if (i != 0)
writer.write_raw(",");
writer.write(*p++); writer.write(*p++);
} }
} else if (pVector->body.child_type == uris.atom_Long) }
else if (pVector->body.child_type == uris.atom_Long)
{ {
int64_t *p = (int64_t *)pItems; int64_t *p = (int64_t *)pItems;
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
if (i != 0) writer.write_raw(","); if (i != 0)
writer.write_raw(",");
writer.write(*p++); writer.write(*p++);
} }
} else if (pVector->body.child_type == uris.atom_Double) }
else if (pVector->body.child_type == uris.atom_Double)
{ {
double *p = (double *)pItems; double *p = (double *)pItems;
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
if (i != 0) writer.write_raw(","); if (i != 0)
writer.write_raw(",");
writer.write(*p++); writer.write(*p++);
} }
} else if (pVector->body.child_type == uris.atom_Bool) }
else if (pVector->body.child_type == uris.atom_Bool)
{ {
bool *p = (bool *)pItems; bool *p = (bool *)pItems;
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
if (i != 0) writer.write_raw(","); if (i != 0)
writer.write_raw(",");
writer.write(*p++); writer.write(*p++);
} }
} }
} }
writer.end_array(); writer.end_array();
} else if (pAtom->type == uris.atom_Object) }
else if (pAtom->type == uris.atom_Object)
{ {
writer.start_object(); writer.start_object();
@@ -1492,8 +1681,10 @@ void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
firstMember = false; firstMember = false;
} }
LV2_ATOM_OBJECT_FOREACH (obj, prop) { LV2_ATOM_OBJECT_FOREACH(obj, prop)
if (!firstMember) { {
if (!firstMember)
{
writer.write_raw(","); writer.write_raw(",");
} }
firstMember = false; firstMember = false;
@@ -1505,14 +1696,27 @@ void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
WriteAtom(writer, value); WriteAtom(writer, value);
} }
writer.end_object(); writer.end_object();
}
} }
} void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
JackHost *JackHost::CreateInstance(IHost *pHost)
{ {
return new JackHostImpl(pHost); for (auto i = bindings.begin(); i != bindings.end(); ++i)
{
if (i->symbol() == "nextProgram")
{
this->nextMidiBinding.SetBinding(*i);
} else if (i->symbol() == "prevProgram")
{
this->prevMidiBinding.SetBinding(*i);
}
}
}
AudioHost *AudioHost::CreateInstance(IHost *pHost)
{
return new AudioHostImpl(pHost);
} }
JSON_MAP_BEGIN(JackHostStatus) JSON_MAP_BEGIN(JackHostStatus)
+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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -24,7 +24,7 @@
#include "Lv2PedalBoard.hpp" #include "Lv2PedalBoard.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "json.hpp" #include "json.hpp"
#include "JackHost.hpp" #include "AudioHost.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
#include <functional> #include <functional>
#include "PiPedalAlsa.hpp" #include "PiPedalAlsa.hpp"
@@ -32,6 +32,8 @@
namespace pipedal { namespace pipedal {
struct RealtimeMidiProgramRequest;
struct RealtimeNextMidiProgramRequest;
using PortMonitorCallback = std::function<void (int64_t handle, float value)>; using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
@@ -91,7 +93,7 @@ public:
}; };
class IJackHostCallbacks { class IAudioHostCallbacks {
public: public:
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0; virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
@@ -99,6 +101,8 @@ public:
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0; 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 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 IHost;
class JackHost { class AudioHost {
protected: protected:
JackHost() { } AudioHost() { }
public: public:
static JackHost*CreateInstance(IHost *pHost); static AudioHost*CreateInstance(IHost *pHost);
virtual ~JackHost() { }; virtual ~AudioHost() { };
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings, virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0; 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 SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(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 SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 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 getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual JackHostStatus getJackStatus() = 0; virtual JackHostStatus getJackStatus() = 0;
+3 -3
View File
@@ -149,7 +149,7 @@ set (PIPEDAL_SOURCES
CpuGovernor.cpp CpuGovernor.hpp CpuGovernor.cpp CpuGovernor.hpp
GovernorSettings.cpp GovernorSettings.hpp GovernorSettings.cpp GovernorSettings.hpp
SysExec.cpp SysExec.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 WifiConfigSettings.hpp WifiConfigSettings.cpp
WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp
ConfigUtil.hpp ConfigUtil.cpp ConfigUtil.hpp ConfigUtil.cpp
@@ -164,7 +164,7 @@ set (PIPEDAL_SOURCES
Presets.hpp Presets.cpp Presets.hpp Presets.cpp
Storage.hpp Storage.cpp Storage.hpp Storage.cpp
Banks.hpp Banks.cpp Banks.hpp Banks.cpp
JackHost.hpp JackHost.cpp AudioHost.hpp AudioHost.cpp
JackConfiguration.hpp JackConfiguration.cpp JackConfiguration.hpp JackConfiguration.cpp
defer.hpp defer.hpp
Lv2Effect.cpp Lv2Effect.hpp Lv2Effect.cpp Lv2Effect.hpp
@@ -264,7 +264,7 @@ add_executable(pipedaltest testMain.cpp
SystemConfigFile.hpp SystemConfigFile.cpp SystemConfigFile.hpp SystemConfigFile.cpp
SystemConfigFileTest.cpp SystemConfigFileTest.cpp
BeastServerTest.cpp WebServerTest.cpp
MemDebug.cpp MemDebug.cpp
MemDebug.hpp MemDebug.hpp
) )
+1 -1
View File
@@ -37,7 +37,7 @@
#include "lv2/parameters.lv2/parameters.h" #include "lv2/parameters.lv2/parameters.h"
#include "lv2/units.lv2/units.h" #include "lv2/units.lv2/units.h"
#include "lv2/atom.lv2/util.h" #include "lv2/atom.lv2/util.h"
#include "JackHost.hpp" #include "AudioHost.hpp"
#include <exception> #include <exception>
#include "RingBufferReader.hpp" #include "RingBufferReader.hpp"
+1 -1
View File
@@ -25,7 +25,7 @@
#include "SplitEffect.hpp" #include "SplitEffect.hpp"
#include "RingBufferReader.hpp" #include "RingBufferReader.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "JackHost.hpp" #include "AudioHost.hpp"
#include "Lv2EventBufferWriter.hpp" #include "Lv2EventBufferWriter.hpp"
using namespace pipedal; using namespace pipedal;
+9 -3
View File
@@ -58,15 +58,21 @@ public:
private: private:
std::string symbol_; std::string symbol_;
int channel_ = -1; int channel_ = -1;
int bindingType_; int bindingType_ = BINDING_TYPE_NONE;
int note_ = 12*4+24; int note_ = 12*4+24;
int control_ = 1; int control_ = 1;
float minValue_ = 0; float minValue_ = 0;
float maxValue_ = 1; float maxValue_ = 1;
float rotaryScale_ = 1; float rotaryScale_ = 1;
int linearControlType_ = 0; int linearControlType_ = LINEAR_CONTROL_TYPE;
int switchControlType_ = 0; int switchControlType_ = LATCH_CONTROL_TYPE;
public: public:
static MidiBinding SystemBinding(const std::string&symbol)
{
MidiBinding result;
result.symbol_ = symbol;
return result;
}
GETTER_SETTER(channel); GETTER_SETTER(channel);
GETTER_SETTER_REF(symbol); GETTER_SETTER_REF(symbol);
GETTER_SETTER(bindingType); 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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -23,13 +23,14 @@
#include "ConfigUtil.hpp" #include "ConfigUtil.hpp"
#include <sched.h> #include <sched.h>
#include "PiPedalModel.hpp" #include "PiPedalModel.hpp"
#include "JackHost.hpp" #include "AudioHost.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <set> #include <set>
#include "PiPedalConfiguration.hpp" #include "PiPedalConfiguration.hpp"
#include "AdminClient.hpp" #include "AdminClient.hpp"
#include "SplitEffect.hpp" #include "SplitEffect.hpp"
#include "CpuGovernor.hpp" #include "CpuGovernor.hpp"
#include "RingBufferReader.hpp"
#ifndef NO_MLOCK #ifndef NO_MLOCK
#include <sys/mman.h> #include <sys/mman.h>
@@ -84,9 +85,9 @@ void PiPedalModel::Close()
} }
delete[] t; delete[] t;
if (jackHost) if (audioHost)
{ {
jackHost->Close(); audioHost->Close();
} }
} }
@@ -113,9 +114,9 @@ PiPedalModel::~PiPedalModel()
try try
{ {
if (jackHost) if (audioHost)
{ {
jackHost->Close(); audioHost->Close();
} }
} }
catch (...) catch (...)
@@ -133,6 +134,8 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
storage.SetDataRoot(configuration.GetLocalStoragePath()); storage.SetDataRoot(configuration.GetLocalStoragePath());
storage.Initialize(); storage.Initialize();
this->systemMidiBindings = storage.GetSystemMidiBindings();
this->jackServerSettings.ReadJackConfiguration(); this->jackServerSettings.ReadJackConfiguration();
} }
@@ -191,10 +194,14 @@ void PiPedalModel::Load()
} }
UpdateDefaults(&this->pedalBoard); UpdateDefaults(&this->pedalBoard);
std::unique_ptr<JackHost> p{JackHost::CreateInstance(lv2Host.asIHost())}; std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(lv2Host.asIHost())};
this->jackHost = std::move(p); 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()) if (configuration.GetMLock())
{ {
@@ -226,11 +233,11 @@ void PiPedalModel::Load()
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection); this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
try try
{ {
jackHost->Open(this->jackServerSettings, selection); audioHost->Open(this->jackServerSettings, selection);
try { try {
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard; this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard); audioHost->SetPedalBoard(lv2PedalBoard);
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@@ -305,7 +312,7 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s
effect->SetControl(index,value);; effect->SetControl(index,value);;
} }
} else { } else {
jackHost->SetControlValue(pedalItemId, symbol, value); audioHost->SetControlValue(pedalItemId, symbol, value);
} }
} }
@@ -387,11 +394,11 @@ void PiPedalModel::FirePedalBoardChanged(int64_t clientId)
delete[] t; delete[] t;
// notify the audio thread. // notify the audio thread.
if (jackHost->IsOpen()) if (audioHost->IsOpen())
{ {
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard; this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard); audioHost->SetPedalBoard(lv2PedalBoard);
UpdateRealtimeVuSubscriptions(); UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions(); UpdateRealtimeMonitorPortSubscriptions();
} }
@@ -446,7 +453,7 @@ void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId
this->SetPresetChanged(clientId, true); this->SetPresetChanged(clientId, true);
// Notify audo thread. // 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; 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) void PiPedalModel::LoadPreset(int64_t clientId, int64_t instanceId)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -883,10 +971,10 @@ JackConfiguration PiPedalModel::GetJackConfiguration()
void PiPedalModel::RestartAudio() 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 // restarting is a bit dodgy. It was impossible with Jack, but
// now very plausible with the ALSA audio stack. // now very plausible with the ALSA audio stack.
@@ -899,7 +987,7 @@ void PiPedalModel::RestartAudio()
// do a complete reload. // do a complete reload.
this->jackHost->SetPedalBoard(nullptr); this->audioHost->SetPedalBoard(nullptr);
this->jackConfiguration.AlsaInitialize(this->jackServerSettings); this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
@@ -928,13 +1016,13 @@ void PiPedalModel::RestartAudio()
Lv2Log::error("Audio configuration not valid."); Lv2Log::error("Audio configuration not valid.");
return; return;
} }
this->jackHost->Open(this->jackServerSettings, channelSelection); this->audioHost->Open(this->jackServerSettings, channelSelection);
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard; this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard); audioHost->SetPedalBoard(lv2PedalBoard);
this->UpdateRealtimeVuSubscriptions(); this->UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions(); UpdateRealtimeMonitorPortSubscriptions();
} }
@@ -1121,12 +1209,12 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
} }
} }
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end()); std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
jackHost->SetVuSubscriptions(instanceids); audioHost->SetVuSubscriptions(instanceids);
} }
void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions() 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) 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); std::lock_guard<std::recursive_mutex> guard(mutex);
outstandingParameterRequests.push_back(request); outstandingParameterRequests.push_back(request);
this->jackHost->getRealtimeParameter(request); this->audioHost->getRealtimeParameter(request);
} }
BankIndex PiPedalModel::GetBankIndex() const BankIndex PiPedalModel::GetBankIndex() const
@@ -1254,6 +1342,7 @@ void PiPedalModel::OpenBank(int64_t clientId, int64_t bankId)
FirePresetsChanged(clientId); FirePresetsChanged(clientId);
this->pedalBoard = storage.GetCurrentPreset(); this->pedalBoard = storage.GetCurrentPreset();
UpdateDefaults(&this->pedalBoard); UpdateDefaults(&this->pedalBoard);
this->hasPresetChanged = false; this->hasPresetChanged = false;
this->FirePedalBoardChanged(clientId); this->FirePedalBoardChanged(clientId);
@@ -1311,7 +1400,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
this->jackConfiguration.SetIsRestarting(true); this->jackConfiguration.SetIsRestarting(true);
FireJackConfigurationChanged(this->jackConfiguration); FireJackConfigurationChanged(this->jackConfiguration);
this->jackHost->UpdateServerConfiguration( this->audioHost->UpdateServerConfiguration(
jackServerSettings, jackServerSettings,
[this](bool success, const std::string &errorMessage) [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)}; std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard; this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard); audioHost->SetPedalBoard(lv2PedalBoard);
UpdateRealtimeVuSubscriptions(); UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions(); UpdateRealtimeMonitorPortSubscriptions();
#endif #endif
@@ -1418,7 +1507,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
if (pedalBoardItem != nullptr) if (pedalBoardItem != nullptr)
{ {
std::vector<ControlValue> controlValues = storage.GetPluginPresetValues(pedalBoardItem->uri(), presetInstanceId); 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) for (size_t i = 0; i < controlValues.size(); ++i)
{ {
@@ -1451,7 +1540,7 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId)
--i; --i;
} }
} }
jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0); audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
} }
void PiPedalModel::DeleteMidiListeners(int64_t clientId) void PiPedalModel::DeleteMidiListeners(int64_t clientId)
@@ -1465,7 +1554,7 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
--i; --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) 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) 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) 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); std::lock_guard<std::recursive_mutex> guard(mutex);
MidiListener listener{clientId, clientHandle, listenForControlsOnly}; MidiListener listener{clientId, clientHandle, listenForControlsOnly};
midiEventListeners.push_back(listener); midiEventListeners.push_back(listener);
jackHost->SetListenForMidiEvent(true); audioHost->SetListenForMidiEvent(true);
} }
void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId) 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); std::lock_guard<std::recursive_mutex> guard(mutex);
AtomOutputListener listener{clientId, clientHandle, instanceId}; AtomOutputListener listener{clientId, clientHandle, instanceId};
atomOutputListeners.push_back(listener); atomOutputListeners.push_back(listener);
jackHost->SetListenForAtomOutput(true); audioHost->SetListenForAtomOutput(true);
} }
void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle) 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) if (midiEventListeners.size() == 0)
{ {
jackHost->SetListenForMidiEvent(false); audioHost->SetListenForMidiEvent(false);
} }
} }
void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle) 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) if (midiEventListeners.size() == 0)
{ {
jackHost->SetListenForMidiEvent(false); audioHost->SetListenForMidiEvent(false);
} }
} }
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices() std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
@@ -1600,3 +1689,31 @@ void PiPedalModel::SetFavorites(const std::map<std::string, bool> &favorites)
t[i]->OnFavoritesChanged(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;
}
+30 -26
View File
@@ -25,7 +25,7 @@
#include "Storage.hpp" #include "Storage.hpp"
#include "Banks.hpp" #include "Banks.hpp"
#include "JackConfiguration.hpp" #include "JackConfiguration.hpp"
#include "JackHost.hpp" #include "AudioHost.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include <functional> #include <functional>
#include <filesystem> #include <filesystem>
@@ -38,16 +38,14 @@
#include "AvahiService.hpp" #include "AvahiService.hpp"
#include <thread> #include <thread>
namespace pipedal
{
struct RealtimeMidiProgramRequest;
struct RealtimeNextMidiProgramRequest;
class IPiPedalModelSubscriber
{
namespace pipedal {
class IPiPedalModelSubscriber {
public: public:
virtual int64_t GetClientId() = 0; virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0; virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
@@ -70,13 +68,17 @@ public:
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0; virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0; virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0; virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
virtual void Close() = 0; virtual void Close() = 0;
}; };
class PiPedalModel: private IJackHostCallbacks { class PiPedalModel : private IAudioHostCallbacks
{
private: private:
std::unique_ptr<std::jthread> pingThread; std::unique_ptr<std::jthread> pingThread;
std::vector<MidiBinding> systemMidiBindings;
AvahiService avahiService; AvahiService avahiService;
uint16_t webPort; uint16_t webPort;
@@ -85,14 +87,15 @@ private:
AdminClient adminClient; AdminClient adminClient;
class MidiListener
class MidiListener { {
public: public:
int64_t clientId; int64_t clientId;
int64_t clientHandle; int64_t clientHandle;
bool listenForControlsOnly; bool listenForControlsOnly;
}; };
class AtomOutputListener { class AtomOutputListener
{
public: public:
int64_t clientId; int64_t clientId;
int64_t clientHandle; int64_t clientHandle;
@@ -110,12 +113,11 @@ private:
Storage storage; Storage storage;
bool hasPresetChanged = false; bool hasPresetChanged = false;
std::unique_ptr<JackHost> jackHost; std::unique_ptr<AudioHost> audioHost;
JackConfiguration jackConfiguration; JackConfiguration jackConfiguration;
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard; std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
std::filesystem::path webRoot; std::filesystem::path webRoot;
std::vector<IPiPedalModelSubscriber *> subscribers; std::vector<IPiPedalModelSubscriber *> subscribers;
void SetPresetChanged(int64_t clientId, bool value); void SetPresetChanged(int64_t clientId, bool value);
void FirePresetsChanged(int64_t clientId); void FirePresetsChanged(int64_t clientId);
@@ -128,8 +130,8 @@ private:
void UpdateDefaults(PedalBoardItem *pedalBoardItem); void UpdateDefaults(PedalBoardItem *pedalBoardItem);
void UpdateDefaults(PedalBoard *pedalBoard); void UpdateDefaults(PedalBoard *pedalBoard);
class VuSubscription
class VuSubscription { {
public: public:
int64_t subscriptionHandle; int64_t subscriptionHandle;
int64_t instanceid; int64_t instanceid;
@@ -149,16 +151,19 @@ private:
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId); IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
private: // IJackHostCallbacks private: // IAudioHostCallbacks
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates); virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates);
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update); virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value); virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl); virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson); 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); void UpdateVst3Settings(PedalBoard &pedalBoard);
PiPedalConfiguration configuration; PiPedalConfiguration configuration;
public: public:
PiPedalModel(); PiPedalModel();
virtual ~PiPedalModel(); virtual ~PiPedalModel();
@@ -195,8 +200,8 @@ public:
void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard); void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard); void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
void GetPresets(PresetIndex *pResult); void GetPresets(PresetIndex *pResult);
PedalBoard GetPreset(int64_t instanceId); PedalBoard GetPreset(int64_t instanceId);
void GetBank(int64_t instanceId, BankFile *pBank); void GetBank(int64_t instanceId, BankFile *pBank);
@@ -218,7 +223,6 @@ public:
void MoveBank(int64_t clientId, int from, int to); void MoveBank(int64_t clientId, int from, int to);
int64_t DeleteBank(int64_t clientId, int64_t instanceId); int64_t DeleteBank(int64_t clientId, int64_t instanceId);
int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId, instanceId, -1); } int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId, instanceId, -1); }
JackConfiguration GetJackConfiguration(); JackConfiguration GetJackConfiguration();
@@ -238,11 +242,11 @@ public:
void SetGovernorSettings(const std::string &governor); void SetGovernorSettings(const std::string &governor);
GovernorSettings GetGovernorSettings(); GovernorSettings GetGovernorSettings();
int64_t AddVuSubscription(int64_t instanceId); int64_t AddVuSubscription(int64_t instanceId);
void RemoveVuSubscription(int64_t subscriptionHandle); 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); int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
void UnmonitorPort(int64_t subscriptionHandle); void UnmonitorPort(int64_t subscriptionHandle);
@@ -252,16 +256,16 @@ public:
int64_t instanceId, int64_t instanceId,
const std::string uri, const std::string uri,
std::function<void(const std::string &jsonResjult)> onSuccess, std::function<void(const std::string &jsonResjult)> onSuccess,
std::function<void (const std::string& error)> onError std::function<void(const std::string &error)> onError);
);
BankIndex GetBankIndex() const; BankIndex GetBankIndex() const;
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName); 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); int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
void OpenBank(int64_t clientId, int64_t bankId); void OpenBank(int64_t clientId, int64_t bankId);
JackHostStatus GetJackStatus() { JackHostStatus GetJackStatus()
return this->jackHost->getJackStatus(); {
return this->audioHost->getJackStatus();
} }
JackServerSettings GetJackServerSettings(); JackServerSettings GetJackServerSettings();
void SetJackServerSettings(const JackServerSettings &jackServerSettings); void SetJackServerSettings(const JackServerSettings &jackServerSettings);
+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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -1235,6 +1235,17 @@ public:
pReader->read(&favorites); pReader->read(&favorites);
this->model.SetFavorites(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 else
{ {
Lv2Log::error("Unknown message received: %s", message.c_str()); Lv2Log::error("Unknown message received: %s", message.c_str());
@@ -1308,6 +1319,10 @@ protected:
} }
public: public:
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
Send("onSystemMidiBindingsChanged",bindings);
}
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
{ {
Send("onFavoritesChanged", favorites); Send("onFavoritesChanged", favorites);
+1 -1
View File
@@ -19,7 +19,7 @@
#pragma once #pragma once
#include "BeastServer.hpp" #include "WebServer.hpp"
#include "PiPedalModel.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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -22,7 +22,7 @@
#include "PiPedalException.hpp" #include "PiPedalException.hpp"
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include "VuUpdate.hpp" #include "VuUpdate.hpp"
#include "JackHost.hpp" #include "AudioHost.hpp"
namespace pipedal namespace pipedal
{ {
@@ -51,8 +51,25 @@ namespace pipedal
AtomOutput = 17, 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 class RealtimeMonitorPortSubscription
{ {
public: public:
@@ -304,6 +321,25 @@ namespace pipedal
write(RingBufferCommand::OnMidiListen, msg); 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) void SetControlValue(int effectIndex, int controlIndex, float value)
{ {
SetControlValueBody body; SetControlValueBody body;
@@ -351,6 +387,10 @@ namespace pipedal
{ {
write(RingBufferCommand::SetVuSubscriptions, configuration); write(RingBufferCommand::SetVuSubscriptions, configuration);
} }
void AckMidiProgramRequest(int64_t requestId)
{
write(RingBufferCommand::AckMidiProgramChange,requestId);
}
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions) 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 // 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 // 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->currentBank = std::move(bankFile); // deleting any stray presets while we're at it.
this->SaveCurrentBank(); this->SaveCurrentBank();
} }
void Storage::GetPresetIndex(PresetIndex *pResult) void Storage::GetPresetIndex(PresetIndex *pResult)
{ {
*pResult = PresetIndex(); *pResult = PresetIndex();
@@ -523,6 +524,15 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
pResult->presets().push_back(entry); 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 PedalBoard Storage::GetPreset(int64_t instanceId) const
{ {
@@ -736,6 +746,17 @@ void Storage::MoveBank(int from, int to)
this->bankIndex.move(from, to); this->bankIndex.move(from, to);
this->SaveBankIndex(); 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) int64_t Storage::DeleteBank(int64_t bankId)
{ {
auto &entries = this->bankIndex.entries(); auto &entries = this->bankIndex.entries();
@@ -1333,6 +1354,36 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
#endif #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_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor) JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
+4
View File
@@ -109,6 +109,7 @@ public:
void LoadUserSettings(); void LoadUserSettings();
void SaveUserSettings(); void SaveUserSettings();
void LoadBank(int64_t instanceId); void LoadBank(int64_t instanceId);
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
const PedalBoard& GetCurrentPreset(); const PedalBoard& GetCurrentPreset();
void SaveCurrentPreset(const PedalBoard&pedalBoard); void SaveCurrentPreset(const PedalBoard&pedalBoard);
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1); int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
@@ -116,6 +117,7 @@ public:
void GetPresetIndex(PresetIndex*pResult); void GetPresetIndex(PresetIndex*pResult);
void SetPresetIndex(const PresetIndex &presetIndex); void SetPresetIndex(const PresetIndex &presetIndex);
PedalBoard GetPreset(int64_t instanceId) const; PedalBoard GetPreset(int64_t instanceId) const;
int64_t GetPresetByProgramNumber(uint8_t program) const;
void GetBankFile(int64_t instanceId,BankFile*pResult) const; void GetBankFile(int64_t instanceId,BankFile*pResult) const;
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter); int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter); int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter);
@@ -170,6 +172,8 @@ public:
void SetShowStatusMonitor(bool show); void SetShowStatusMonitor(bool show);
bool GetShowStatusMonitor() const; 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 <strings.h>
#include "Ipv6Helpers.hpp" #include "Ipv6Helpers.hpp"
#include "BeastServer.hpp" #include "WebServer.hpp"
#include "Uri.hpp" #include "Uri.hpp"
@@ -133,7 +133,7 @@ std::string GetFromAddress(const tcp::socket &socket)
namespace pipedal namespace pipedal
{ {
class BeastServerImpl : public BeastServer class WebServerImpl : public WebServer
{ {
private: private:
int signalOnDone = -1; int signalOnDone = -1;
@@ -200,7 +200,7 @@ namespace pipedal
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession>, public SocketHandler::IWriteCallback class WebSocketSession : public std::enable_shared_from_this<WebSocketSession>, public SocketHandler::IWriteCallback
{ {
BeastServerImpl *pServer; WebServerImpl *pServer;
server::connection_ptr webSocket; server::connection_ptr webSocket;
std::string fromAddress; std::string fromAddress;
std::shared_ptr<SocketHandler> socketHandler; std::shared_ptr<SocketHandler> socketHandler;
@@ -224,7 +224,7 @@ namespace pipedal
Lv2Log::info("WebSocketSession closed."); Lv2Log::info("WebSocketSession closed.");
} }
using ptr = std::shared_ptr<WebSocketSession>; using ptr = std::shared_ptr<WebSocketSession>;
WebSocketSession(BeastServerImpl *pServer, server::connection_ptr &webSocket) WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
: pServer(pServer), : pServer(pServer),
webSocket(webSocket) webSocket(webSocket)
{ {
@@ -599,9 +599,9 @@ namespace pipedal
using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2; using websocketpp::lib::placeholders::_2;
m_endpoint.set_open_handler(bind(&BeastServerImpl::on_open, this, _1)); m_endpoint.set_open_handler(bind(&WebServerImpl::on_open, this, _1));
m_endpoint.set_close_handler(bind(&BeastServerImpl::on_close, this, _1)); m_endpoint.set_close_handler(bind(&WebServerImpl::on_close, this, _1));
m_endpoint.set_http_handler(bind(&BeastServerImpl::on_http, this, _1)); m_endpoint.set_http_handler(bind(&WebServerImpl::on_http, this, _1));
std::string hostName = getHostName(); std::string hostName = getHostName();
if (hostName.length() != 0) if (hostName.length() != 0)
@@ -660,7 +660,7 @@ namespace pipedal
} }
} }
static void ThreadProc(BeastServerImpl *server) static void ThreadProc(WebServerImpl *server)
{ {
server->Run(); server->Run();
} }
@@ -717,7 +717,7 @@ namespace pipedal
this->pBgThread = new std::thread(ThreadProc, this); 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), : address(address),
rootPath(rootPath), rootPath(rootPath),
port(port), port(port),
@@ -727,7 +727,7 @@ namespace pipedal
}; };
} // 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) for (auto factory : this->socket_factories)
@@ -740,7 +740,7 @@ std::shared_ptr<ISocketFactory> BeastServerImpl::GetSocketFactory(const uri &req
return nullptr; 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. //xxx move this to HtmlHelpers.
std::string last_modified(const std::filesystem::path& path); std::string last_modified(const std::filesystem::path& path);
class BeastServerImpl; class WebServerImpl;
class SocketHandler { class SocketHandler {
friend class BeastServerImpl; friend class WebServerImpl;
public: public:
class IWriteCallback { class IWriteCallback {
@@ -213,9 +213,9 @@ public:
}; };
class BeastServer { class WebServer {
public: public:
virtual ~BeastServer() { } virtual ~WebServer() { }
virtual void SetLogHttpRequests(bool enableLogging) = 0; 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. // signalOnDone: fire the specified POSIX signal when the service thread terminates. -1 for no signal.
virtual void RunInBackground(int signalOnDone = -1) = 0; 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 "pch.h"
#include "catch.hpp" #include "catch.hpp"
#include "BeastServer.hpp" #include "WebServer.hpp"
#include "MemDebug.hpp" #include "MemDebug.hpp"
#include <iostream> #include <iostream>
using namespace pipedal; using namespace pipedal;
TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]") TEST_CASE("WebServer shutdown", "[webServerShutdown][Build][Dev]")
{ {
MemStats initialMemory = GetMemStats(); MemStats initialMemory = GetMemStats();
{ {
@@ -34,7 +34,7 @@ TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
std::string doc_root = "."; std::string doc_root = ".";
auto const threads = 3; auto const threads = 3;
auto server = createBeastServer( auto server = WebServer::create(
address, port, doc_root.c_str(), threads); address, port, doc_root.c_str(), threads);
server->RunInBackground(); server->RunInBackground();
sleep(5); sleep(5);
+3 -3
View File
@@ -20,7 +20,7 @@
#include "pch.h" #include "pch.h"
#include "AudioConfig.hpp" #include "AudioConfig.hpp"
#include "BeastServer.hpp" #include "WebServer.hpp"
#include <iostream> #include <iostream>
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include "ServiceConfiguration.hpp" #include "ServiceConfiguration.hpp"
@@ -601,7 +601,7 @@ int main(int argc, char *argv[])
} }
uint16_t port; uint16_t port;
std::shared_ptr<BeastServer> server; std::shared_ptr<WebServer> server;
try try
{ {
auto const address = boost::asio::ip::make_address(configuration.GetSocketServerAddress()); 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()); auto const threads = std::max<int>(1, configuration.GetThreads());
server = createBeastServer( server = WebServer::create(
address, port, web_root.c_str(), threads); address, port, web_root.c_str(), threads);
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)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 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/Uri.cpp ../src/Uri.hpp ../src/HtmlHelper.cpp ../src/HtmlHelper.hpp WebServerTest.cpp
../src/BeastServer.cpp ../src/BeastServer.hpp ../src/json.hpp ../src/json.cpp ../src/WebServer.cpp ../src/WebServer.hpp ../src/json.hpp ../src/json.cpp
../src/Lv2Host.cpp ../src/Lv2Host.hpp ../src/Lv2Host.cpp ../src/Lv2Host.hpp
../src/Lv2Log.hpp ../src/Lv2Log.cpp ../src/Lv2Log.hpp ../src/Lv2Log.cpp
../src/PluginType.hpp ../src/PluginType.cpp ../src/PluginType.hpp ../src/PluginType.cpp