Audio stability, snapshots

This commit is contained in:
Robin Davies
2024-10-06 00:00:20 -04:00
parent 9d4cc6e978
commit 472703627e
27 changed files with 956 additions and 844 deletions
+3 -123
View File
@@ -11,33 +11,6 @@
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Edge",
"port": 9222,
"request": "attach",
"type": "msedge",
"webRoot": "${workspaceFolder}"
},
{
"name": "(gdb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "enter program name, for example ${workspaceFolder}/a.out",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
@@ -67,57 +40,6 @@
}
]
},
{
"name": "(gdb) makeRelease",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/../..",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
}
],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) downloadCounts",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ "--downloads" ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/../..",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
}
],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) pipedal_nm_p2pd",
"type": "cppdbg",
@@ -174,13 +96,7 @@
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Add alsa source directories.",
"text": "directory $cd:$cwd:/usr/src/alsa-lib-1.2.8",
"ignoreFailures": true
}
]
},
@@ -196,7 +112,7 @@
//"[json_variants]" // subtest of your choice, or none to run all of the tests.
//"[inverting_mutex_test]"
// "[utf8_to_utf32]"
"[updater]"
"[wifi_channels_test]"
],
"stopAtEntry": false,
@@ -256,38 +172,6 @@
}
]
},
{
"name": "(gdb) profilePlugin Launch",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ "ToobNam_Profile", "--no-profile","-w" ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
},
{
"name": "OTHER_VALUE",
"value": "Something something"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
@@ -373,15 +257,11 @@
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [
"--prefix",
"/usr/sbin",
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
"--install"
//"--get-current-port"
//"--install"
// "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
//"--list-p2p-channels"
"--list-p2p-channels"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
+2 -1
View File
@@ -97,7 +97,8 @@
"strstream": "cpp",
"p2p_i.h": "c",
"p2p.h": "c",
"hash_set": "cpp"
"hash_set": "cpp",
"barrier": "cpp"
},
"cSpell.words": [
"Guitarix",
+1 -1
View File
@@ -14,7 +14,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG -D_GLIBCXX_DEBUG" )
endif()
# Can't get the pkg_check to work.
+1 -1
View File
@@ -680,7 +680,7 @@ void WifiConfigSettings::ParseArguments(
)
{
this->valid_ = false;
this->valid_ = false ;
if (argv.size() != 4)
{
throw invalid_argument("Invalid number of arguments.");
+7
View File
@@ -61,3 +61,10 @@ export class FakeAndroidHost implements AndroidHostInterface
return this.theme;
}
}
export function isAndroidHosted(): boolean {
return ((window as any).AndroidHost as AndroidHostInterface) !== undefined;
}
export function getAndroidHost(): AndroidHostInterface | undefined {
return ((window as any).AndroidHost as AndroidHostInterface);
}
+1 -1
View File
@@ -624,7 +624,7 @@ export class PiPedalModel //implements PiPedalModel
let atomJson = body.atomJson as any;
this.handleNotifyPathPatchPropertyChanged(instanceId, propertyUri, atomJson);
if (header.replyTo) {
this.webSocket?.reply(header.replyTo, "onNotifyPatchProperty", true);
this.webSocket?.reply(header.replyTo, "onNotifyPathPatchPropertyChanged", true);
}
} else if (message === "onNotifyPatchProperty") {
let clientHandle = body.clientHandle as number;
+84 -62
View File
@@ -51,6 +51,7 @@ import SelectThemeDialog from './SelectThemeDialog';
import Slide, { SlideProps } from '@mui/material/Slide';
import { createStyles, Theme } from '@mui/material/styles';
import { WithStyles, withStyles } from '@mui/styles';
import { canScaleWindow, getWindowScaleText } from './WindowScale';
@@ -466,8 +467,15 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
midiSummary(): string {
let ports = this.state.jackSettings.inputMidiDevices;
if (ports.length === 0) return "Disabled";
if (ports.length === 1) return ports[0].description;
return ports.length + " channels";
let result = "";
for (let port of ports) {
if (result.length !== 0) {
result += ", ";
}
result += port.description;
}
return result;
}
handleShowWifiConfigDialog() {
this.setState({
@@ -571,7 +579,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
(
<div>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}>
Select and configure an audio device. You may optionally configure MIDI inputs, and configure up a Wi-Fi Auto-Hotspot as well.
Select and configure an audio device. You may optionally configure MIDI inputs, and configure a Wi-Fi Auto-Hotspot as well.
The Auto-Hotspot feature allows you to connect to Pipedal even if you don't have access to a Wi-Fi router.
</Typography>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}>
@@ -678,6 +686,79 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
</div>
</div>
<Divider />
{(!this.props.onboarding) &&
(
<div>
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
DISPLAY
</Typography>
<ButtonBase
className={classes.setting}
onClick={() => { this.handleThemeSelection(); }} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Color theme</Typography>
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>
{this.model.getTheme() === ColorTheme.Dark ? "Dark" :
(this.model.getTheme() === ColorTheme.Light ? "Light" : "System")}
</Typography>
</div>
</ButtonBase>
{(canScaleWindow()) &&
(
<ButtonBase
className={classes.setting}
onClick={() => { this.handleThemeSelection(); }} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Scale</Typography>
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>
{getWindowScaleText()}
</Typography>
</div>
</ButtonBase>
)
}
<ButtonBase
className={classes.setting}
onClick={() => {
this.model.setShowStatusMonitor(!this.state.showStatusMonitor)
}} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<div style={{
width: "100%", display: "flex", flexDirection: "row", flexWrap: "nowrap",
alignItems: "center", maxWidth: 400
}}>
<div style={{ flex: "1 1 auto" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Show status monitor on main screen.</Typography>
</div>
<div style={{ flex: "0 0 auto" }}>
<Switch
checked={this.state.showStatusMonitor}
onChange={
(e) => { this.model.setShowStatusMonitor(e.target.checked); }
}
/>
</div>
</div>
</div>
</ButtonBase>
</div>
)
}
<Divider />
<div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
@@ -720,65 +801,6 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Divider />
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">SYSTEM</Typography>
<ButtonBase
style={{ display: "none" }}
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowGovernorSettingsDialogDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
CPU Governor</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.governorSettings.governor}
</Typography>
</div>
</ButtonBase>
<ButtonBase
className={classes.setting}
onClick={() => { this.handleThemeSelection(); }} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Color theme</Typography>
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>
{this.model.getTheme() === ColorTheme.Dark ? "Dark" :
(this.model.getTheme() === ColorTheme.Light ? "Light" : "System")}
</Typography>
</div>
</ButtonBase>
<ButtonBase
className={classes.setting}
onClick={() => {
this.model.setShowStatusMonitor(!this.state.showStatusMonitor)
}} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<div style={{
width: "100%", display: "flex", flexDirection: "row", flexWrap: "nowrap",
alignItems: "center", maxWidth: 400
}}>
<div style={{ flex: "1 1 auto" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Show status monitor on main screen.</Typography>
</div>
<div style={{ flex: "0 0 auto" }}>
<Switch
checked={this.state.showStatusMonitor}
onChange={
(e) => { this.model.setShowStatusMonitor(e.target.checked); }
}
/>
</div>
</div>
</div>
</ButtonBase>
{
this.model.enableAutoUpdate && (
<ButtonBase
-1
View File
@@ -133,7 +133,6 @@ export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPan
} else {
return window.innerWidth < 800;
}
return window.innerWidth < 500;
}
getPortraitOrientation() {
return window.innerWidth * 2 < window.innerHeight * 3;
+120 -95
View File
@@ -50,7 +50,7 @@ const styles = (theme: Theme) => createStyles({
flex: "1 1 auto",
},
pluginTable: {
border: "collapse",
borderCollapse: "collapse",
width: "100%",
},
@@ -66,6 +66,13 @@ const styles = (theme: Theme) => createStyles({
verticalAlign: "top",
paddingTop: 12
},
plainRow: {
borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: "transparent"
},
dividerRow: {
borderWidth: "1px 0px 0px 0px", borderStyle: "solid", borderColor: theme.palette.divider
}
});
@@ -118,40 +125,53 @@ export const SystemMidiBindingDialog =
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;
} else if (item.symbol === "startHotspot")
{
displayName = "Enable Hotspot";
instanceId = 3;
} else if (item.symbol === "stopHotspot")
{
displayName = "Disable Hotspot";
instanceId = 4;
} else if (item.symbol === "shutdown")
{
displayName = "Shutdown";
instanceId = 5;
} else if (item.symbol === "reboot")
{
displayName = "Reboot";
instanceId = 6;
let listenInstanceId = 0;
for (var item of this.model.systemMidiBindings.get()) {
let displayName = "";
let found = true;
if (item.symbol === "nextBank") {
displayName = "Next Bank";
}
if (instanceId !== -1)
{
result.push(new BindingEntry(displayName,instanceId,item));
else if (item.symbol === "prevBank") {
displayName = "Previous Bank";
}
else if (item.symbol === "nextProgram") {
displayName = "Next Preset";
}else if (item.symbol === "prevProgram") {
displayName = "Previous Preset";
}
else if (item.symbol === "snapshot1") {
displayName = "Snapshot 1";
}
else if (item.symbol === "snapshot2") {
displayName = "Snapshot 2";
}
else if (item.symbol === "snapshot3") {
displayName = "Snapshot 3";
}
else if (item.symbol === "snapshot4") {
displayName = "Snapshot 4";
}
else if (item.symbol === "snapshot5") {
displayName = "Snapshot 5";
}
else if (item.symbol === "snapshot6") {
displayName = "Snapshot 6";
} else if (item.symbol === "startHotspot") {
displayName = "Enable Hotspot";
} else if (item.symbol === "stopHotspot") {
displayName = "Disable Hotspot";
} else if (item.symbol === "shutdown") {
displayName = "Shutdown";
} else if (item.symbol === "reboot") {
displayName = "Reboot";
} else {
found = false;
}
if (found)
{
result.push(new BindingEntry(displayName, listenInstanceId, item));
++listenInstanceId;
}
}
return result;
@@ -174,8 +194,7 @@ export const SystemMidiBindingDialog =
clearTimeout(this.listenTimeoutHandle);
this.listenTimeoutHandle = undefined;
}
if (this.listenHandle)
{
if (this.listenHandle) {
this.model.cancelListenForMidiEvent(this.listenHandle)
this.listenHandle = undefined;
}
@@ -184,14 +203,11 @@ export const SystemMidiBindingDialog =
}
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
{
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number) {
this.cancelListenForControl();
for (var binding of this.state.systemMidiBindings)
{
if (binding.instanceId === instanceId)
{
for (var binding of this.state.systemMidiBindings) {
if (binding.instanceId === instanceId) {
let newBinding = binding.midiBinding.clone();
if (isNote) {
@@ -201,8 +217,8 @@ export const SystemMidiBindingDialog =
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL;
newBinding.control = noteOrControl;
}
this.model.setSystemMidiBinding(instanceId,newBinding);
this.model.setSystemMidiBinding(instanceId, newBinding);
return;
}
}
@@ -218,9 +234,9 @@ export const SystemMidiBindingDialog =
this.listenHandle = this.model.listenForMidiEvent(listenForControl,
(isNote: boolean, noteOrControl: number) => {
this.handleListenSucceeded(instanceId,symbol,isNote, noteOrControl);
this.handleListenSucceeded(instanceId, symbol, isNote, noteOrControl);
});
}
@@ -229,7 +245,7 @@ export const SystemMidiBindingDialog =
}
onMidiBindingsChanged() {
this.setState({systemMidiBindings: this.createBindings()});
this.setState({ systemMidiBindings: this.createBindings() });
}
componentDidMount() {
@@ -256,10 +272,20 @@ export const SystemMidiBindingDialog =
let result: React.ReactNode[] = [];
let items = this.state.systemMidiBindings;
for (var item of items) {
let symbol = item.midiBinding.symbol;
let hasDivider = symbol === "snapshot1" || symbol === "stopHotspot" || symbol === "shotdown";
if (hasDivider)
{
result.push(
<tr>
<td colSpan={2} className={classes.dividerRow}><div style={{height: 1}} /></td>
</tr>
);
}
result.push(
<tr key={item.instanceId}>
<tr key={item.instanceId} >
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
{item.displayName}
@@ -268,8 +294,7 @@ export const SystemMidiBindingDialog =
<td className={classes.bindingTd}>
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
if (instanceId === -2)
{
if (instanceId === -2) {
this.cancelListenForControl();
} else {
this.handleListenForControl(instanceId, symbol, listenForControl);
@@ -300,52 +325,52 @@ export const SystemMidiBindingDialog =
}
return (
<DialogEx tag="systemMidiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{userSelect: "none"}}
<DialogEx tag="systemMidiBindings" 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 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>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.listenSnackbarOpen}
autoHideDuration={1500}
onClose={() => this.setState({ listenSnackbarOpen: false })}
message="Listening for MIDI input"
/>
<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 >
);
}
+2 -1
View File
@@ -21,6 +21,7 @@
// converts android handling of the virtual keyboard to use pan instead of zoom.
import { isAndroidHosted } from './AndroidHost';
import Rectangle from './Rectangle';
@@ -45,7 +46,7 @@ export default class VirtualKeyboardHandler
}
}
if ('visualViewport' in window) {
if ('visualViewport' in window && isAndroidHosted()) {
window.visualViewport?.addEventListener('resize', this.handleVisualViewportResize.bind(this));
} else {
enabled = false;
+48
View File
@@ -0,0 +1,48 @@
import {isAndroidHosted} from './AndroidHost';
var validScales = [1.0,1.10,1.25,1.35,1.50,1.75,2.0,2.5];
export function canScaleWindow(): boolean {
return getValidWindowScales().length > 1;
}
export function getValidWindowScales(): number[]
{
if (isAndroidHosted())
{
return [1.0];
}
let result: number[] = [];
let minDimension = Math.min(window.innerHeight,window.innerWidth);
for (let validScale of validScales)
{
if (minDimension/validScale > 400)
{
result.push(validScale);
}
}
return result;
}
export function setWindowScale(scale: number): void {
localStorage.setItem("pipedalWindowScale", scale.toString());
}
export function getWindowScale(): number {
const strvalue = localStorage.getItem("pipedalWindowScale");
return strvalue ? parseFloat(strvalue) : 1.0;
}
export function getWindowScaleText(scale?: number)
{
let value: number;
if (scale)
{
value = scale;
} else {
value = getWindowScale();
}
let iValue = Math.round(value*100);
return iValue.toString() + "%";
}
+155 -155
View File
@@ -189,14 +189,23 @@ namespace pipedal
AudioDriverHost *driverHost = nullptr;
void validate_capture_handle() { // leftover debugging for a buffer overrun :-/
// if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW)
// {
// throw std::runtime_error("Capture handle has been overwritten");
// }
}
public:
AlsaDriverImpl(AudioDriverHost *driverHost)
: driverHost(driverHost)
{
#ifdef ALSADRIVER_CONFIG_DBG
snd_output_stdio_attach(&snd_output, stdout, 0);
snd_pcm_status_malloc(&snd_status);
#endif
midiEventMemory.resize(MAX_MIDI_EVENT * MAX_MIDI_EVENT_SIZE);
midiEvents.resize(MAX_MIDI_EVENT);
for (size_t i = 0; i < midiEvents.size(); ++i)
{
midiEvents[i].buffer = midiEventMemory.data() + i * MAX_MIDI_EVENT_SIZE;
}
}
virtual ~AlsaDriverImpl()
{
@@ -302,15 +311,14 @@ namespace pipedal
snd_pcm_sw_params_free(playbackSwParams);
playbackSwParams = nullptr;
}
for (auto *midiState : this->midiStates)
for (auto &midiState : this->midiDevices)
{
if (midiState != nullptr)
if (midiState)
{
midiState->Close();
delete midiState;
}
}
midiStates.resize(0);
midiDevices.resize(0);
}
std::string discover_alsa_using_apps()
@@ -1234,17 +1242,25 @@ namespace pipedal
void FillOutputBuffer()
{
validate_capture_handle();
memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize);
int retry = 0;
while (true)
{
auto avail = snd_pcm_avail(this->playbackHandle);
if (avail < 0)
{
if (++retry >= 5) // kinda sus code. let's make sure we don't spin forever.
{
throw std::runtime_error("Timed out trying to fill the audio output buffer.");
}
int err = snd_pcm_prepare(playbackHandle);
if (err < 0)
{
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err)));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
if (avail == 0)
@@ -1258,9 +1274,12 @@ namespace pipedal
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err)));
}
}
validate_capture_handle();
}
void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
{
validate_capture_handle();
if (err == -EPIPE)
{
err = snd_pcm_prepare(playback_handle);
@@ -1269,13 +1288,17 @@ namespace pipedal
throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
}
FillOutputBuffer();
} else {
}
else
{
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
}
validate_capture_handle();
}
void recover_from_input_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
{
validate_capture_handle();
if (err == -EPIPE)
{
@@ -1317,10 +1340,13 @@ namespace pipedal
{
throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
}
validate_capture_handle();
}
else if (err == ESTRPIPE)
{
audioRunning = false;
validate_capture_handle();
while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
{
sleep(1);
@@ -1334,9 +1360,11 @@ namespace pipedal
}
}
audioRunning = true;
validate_capture_handle();
} else {
}
else
{
throw std::runtime_error(SS("Can't restart audio: " << snd_strerror(err)));
}
}
@@ -1359,9 +1387,10 @@ namespace pipedal
// transcode to jack format.
// expand running status if neccessary.
// deal with regular and sysex messages split across
// buffer boundaries.
// buffer boundaries (but discard them)
snd_pcm_sframes_t framesRead;
auto state = snd_pcm_state(handle);
auto frame_bytes = this->captureFrameSize;
do
@@ -1384,6 +1413,18 @@ namespace pipedal
return framesRead;
}
void ReadMidiData(uint32_t audioFrame)
{
for (size_t i = 0; i < midiDevices.size(); ++i)
{
size_t nRead = midiDevices[i]->ReadMidiEvents(
this->midiEvents,
midiEventCount,
audioFrame);
midiEventCount += nRead;
}
}
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
{
long framesRead;
@@ -1448,34 +1489,42 @@ namespace pipedal
cpuUse.SetStartTime(cpuUse.Now());
while (true)
{
validate_capture_handle();
cpuUse.UpdateCpuUse();
if (terminateAudio())
{
break;
}
this->midiEventCount = 0;
// snd_pcm_wait(captureHandle, 1);
ssize_t framesToRead = bufferSize;
ssize_t framesRead = 0;
bool xrun = false;
validate_capture_handle();
while (framesToRead != 0)
{
ReadMidiData((uint32_t)framesRead);
ssize_t thisTime = framesToRead;
ssize_t nFrames;
if ((nFrames = ReadBuffer(
captureHandle,
this->rawCaptureBuffer.data() + this->captureFrameSize * framesRead,
bufferSize)) < 0)
framesToRead)) < 0)
{
this->driverHost->OnUnderrun();
recover_from_input_underrun(captureHandle, playbackHandle,nFrames);
recover_from_input_underrun(captureHandle, playbackHandle, nFrames);
xrun = true;
break;
}
framesRead += nFrames;
framesToRead -= nFrames;
}
validate_capture_handle();
if (xrun)
{
continue;
@@ -1504,7 +1553,7 @@ namespace pipedal
if (err < 0)
{
this->driverHost->OnUnderrun();
recover_from_output_underrun(captureHandle, playbackHandle,err);
recover_from_output_underrun(captureHandle, playbackHandle, err);
}
cpuUse.AddSample(ProfileCategory::Write);
}
@@ -1618,11 +1667,16 @@ namespace pipedal
Lv2Log::debug("Audio thread joined.");
}
static constexpr size_t MAX_MIDI_EVENT_SIZE = 3;
static constexpr size_t MIDI_BUFFER_SIZE = 16 * 1024;
static constexpr size_t MAX_MIDI_EVENT = 4 * 1024;
size_t midiEventCount = 0;
std::vector<MidiEvent> midiEvents;
std::vector<uint8_t> midiEventMemory;
public:
class MidiState
class AlsaMidiDeviceImpl
{
private:
snd_rawmidi_t *hIn = nullptr;
@@ -1637,15 +1691,12 @@ namespace pipedal
size_t data0 = 0;
size_t data1 = 0;
size_t eventCount = 0;
MidiEvent events[MAX_MIDI_EVENT];
size_t bufferCount = 0;
uint8_t buffer[MIDI_BUFFER_SIZE];
bool inputProcessingSysex = false;
size_t inputSysexBufferCount = 0;
std::vector<uint8_t> inputSysexBuffer;
uint8_t readBuffer[1024];
ssize_t sysexStartIndex = -1;
void checkError(int result, const char *message)
{
if (result < 0)
@@ -1655,12 +1706,16 @@ namespace pipedal
}
public:
AlsaMidiDeviceImpl()
{
inputSysexBuffer.resize(1024);
}
void Open(const AlsaMidiDeviceInfo &device)
{
bufferCount = 0;
eventCount = 0;
sysexStartIndex = -1;
runningStatus = 0;
inputProcessingSysex = false;
inputSysexBufferCount = 0;
dataIndex = 0;
dataLength = 0;
@@ -1701,73 +1756,29 @@ namespace pipedal
return sDataLength[cc >> 4];
}
size_t GetMidiInputEventCount()
{
return eventCount;
}
void NextEventBuffer()
{
// xxx preserve unflushed sysex data.
if (sysexStartIndex != -1)
{
int end = bufferCount;
bufferCount = 0;
eventCount = 0;
for (int i = sysexStartIndex; i < end; ++i)
{
buffer[bufferCount++] = buffer[i];
}
sysexStartIndex = 0;
}
else
{
bufferCount = 0;
eventCount = 0;
}
}
bool GetMidiInputEvent(MidiEvent *event, size_t nFrame)
{
if (nFrame >= eventCount)
return false;
*event = this->events[nFrame];
return true;
}
void MidiPut(uint8_t cc, uint8_t d0, uint8_t d1)
{
if (cc == 0)
return;
// check for overrun.
if (bufferCount + 1 + dataLength >= sizeof(buffer))
{
bufferCount = sizeof(buffer);
return;
}
if (eventCount >= MAX_MIDI_EVENT)
if (inputEventBufferIndex >= pInputEventBuffer->size())
{
return;
}
auto *event = &(this->events[eventCount++]);
auto &event = (*pInputEventBuffer)[inputEventBufferIndex];
event->time = 0;
event->buffer = &buffer[bufferCount];
event->size = dataLength + 1;
buffer[bufferCount++] = cc;
if (dataLength >= 1)
{
buffer[bufferCount++] = d0;
if (dataLength >= 2)
{
buffer[bufferCount++] = d1;
}
}
event.time = inputSampleFrame;
event.size = dataLength + 1;
assert(dataLength + 1 <= MAX_MIDI_EVENT_SIZE);
event.buffer[0] = cc;
event.buffer[1] = d0;
event.buffer[2] = d1;
++inputEventBufferIndex;
}
void FillBuffer()
void FillInputBuffer()
{
while (true)
{
@@ -1778,23 +1789,42 @@ namespace pipedal
{
checkError(nRead, SS(this->deviceName << "MIDI event read failed. (" << snd_strerror(nRead)).c_str());
}
WriteBuffer(readBuffer, nRead); // expose write to test code.
ProcessInputBuffer(readBuffer, nRead); // expose write to test code.
}
}
uint32_t inputSampleFrame = -1;
size_t inputEventBufferIndex;
std::vector<MidiEvent> *pInputEventBuffer = nullptr;
size_t ReadMidiEvents(
std::vector<MidiEvent> &outputBuffer,
size_t startIndex,
uint32_t sampleFrame)
{
inputSampleFrame = sampleFrame;
inputEventBufferIndex = startIndex;
pInputEventBuffer = &outputBuffer;
FillInputBuffer();
pInputEventBuffer = nullptr;
return inputEventBufferIndex - startIndex;
}
void FlushSysex()
{
if (sysexStartIndex != -1)
if (inputProcessingSysex)
{
if (this->eventCount != MAX_MIDI_EVENT)
{
auto *event = &(events[eventCount++]);
event->size = this->bufferCount - sysexStartIndex;
event->buffer = &(this->buffer[this->sysexStartIndex]);
event->time = 0;
}
sysexStartIndex = -1;
// just discard it. :-/
// if (this->eventCount != MAX_MIDI_EVENT)
// {
// auto *event = &(events[eventCount++]);
// event->size = this->bufferCount - sysexStartIndex;
// event->buffer = &(this->buffer[this->sysexStartIndex]);
// event->time = 0;
// }
// sysexStartIndex = -1;
}
inputProcessingSysex = false;
}
int GetSystemCommonLength(uint8_t cc)
@@ -1802,7 +1832,7 @@ namespace pipedal
static int sizes[] = {-1, 1, 2, 1, -1, -1, 0, 0};
return sizes[(cc >> 4) & 0x07];
}
void WriteBuffer(uint8_t *readBuffer, size_t nRead)
void ProcessInputBuffer(uint8_t *readBuffer, size_t nRead)
{
for (ssize_t i = 0; i < nRead; ++i)
{
@@ -1814,13 +1844,10 @@ namespace pipedal
{
if (v == 0xF0)
{
if (bufferCount == sizeof(buffer))
{
break;
}
sysexStartIndex = bufferCount;
inputProcessingSysex = true;
inputSysexBufferCount = 0;
buffer[bufferCount++] = 0xF0;
inputSysexBuffer[inputSysexBufferCount++] = 0xF0;
runningStatus = 0; // discard subsequent data.
dataLength = -2; // indefinitely.
@@ -1863,11 +1890,11 @@ namespace pipedal
}
else
{
if (sysexStartIndex != -1)
if (inputProcessingSysex)
{
if (bufferCount != sizeof(buffer))
if (inputSysexBufferCount != inputSysexBuffer.size())
{
buffer[bufferCount++] = v;
inputSysexBuffer[inputSysexBufferCount++] = v;
}
}
else
@@ -1888,7 +1915,7 @@ namespace pipedal
}
}
}
if (dataIndex == dataLength)
if (dataIndex == dataLength && dataLength >= 0 && runningStatus != 0)
{
MidiPut(runningStatus, data0, data1);
dataIndex = 0;
@@ -1897,70 +1924,40 @@ namespace pipedal
}
};
std::vector<MidiState *> midiStates;
std::vector<std::unique_ptr<AlsaMidiDeviceImpl>> midiDevices;
void OpenMidi(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{
const auto &devices = channelSelection.GetInputMidiDevices();
midiStates.reserve(devices.size());
midiDevices.reserve(devices.size());
for (size_t i = 0; i < devices.size(); ++i)
{
const auto &device = devices[i];
MidiState *midiState = nullptr;
try
{
midiState = new MidiState();
midiState->Open(device);
midiStates.push_back(midiState);
}
catch (const std::exception &e)
{
// logged already.
delete midiState;
}
}
}
virtual size_t MidiInputBufferCount() const
{
return this->midiStates.size();
}
virtual void *GetMidiInputBuffer(size_t channel, size_t nFrames)
{
return (void *)midiStates[channel];
}
virtual size_t GetMidiInputEventCount(void *portBuffer)
{
MidiState *state = (MidiState *)portBuffer;
return state->GetMidiInputEventCount();
}
virtual bool GetMidiInputEvent(MidiEvent *event, void *portBuf, size_t nFrame)
{
MidiState *state = (MidiState *)portBuf;
return state->GetMidiInputEvent(event, nFrame);
}
virtual void FillMidiBuffers()
{
for (size_t i = 0; i < this->midiStates.size(); ++i)
{
auto *state = midiStates[i];
state->NextEventBuffer();
state->FillBuffer();
auto midiDevice = std::make_unique<AlsaMidiDeviceImpl>();
midiDevice->Open(device);
midiDevices.push_back(std::move(midiDevice));
}
}
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
virtual float *GetInputBuffer(size_t channel, size_t nFrames)
virtual float *GetInputBuffer(size_t channel) override
{
return activeCaptureBuffers[channel];
}
virtual size_t GetMidiInputEventCount() override
{
return midiEventCount;
}
virtual MidiEvent *GetMidiEvents() override
{
return this->midiEvents.data();
}
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel, size_t nFrames)
virtual float *GetOutputBuffer(size_t channel) override
{
return activePlaybackBuffers[channel];
}
@@ -2158,7 +2155,8 @@ namespace pipedal
throw PiPedalStateException("Assert failed.");
}
static void ExpectEvent(AlsaDriverImpl::MidiState &m, int event, const std::vector<uint8_t> message)
#ifdef JUNK
static void ExpectEvent(AlsaDriverImpl::AlsaMidiDeviceImpl &m, int event, const std::vector<uint8_t> message)
{
MidiEvent e;
m.GetMidiInputEvent(&e, event);
@@ -2168,6 +2166,7 @@ namespace pipedal
AlsaAssert(message[i] == e.buffer[i]);
}
}
#endif
void AlsaDriverImpl::TestFormatEncodeDecode(snd_pcm_format_t captureFormat)
{
@@ -2243,8 +2242,8 @@ namespace pipedal
}
void MidiDecoderTest()
{
AlsaDriverImpl::MidiState midiState;
#ifdef JUNK
AlsaDriverImpl::AlsaMidiDeviceImpl midiState;
MidiEvent event;
@@ -2252,7 +2251,7 @@ namespace pipedal
{
static uint8_t m0[] = {0x80, 0x1, 0x2, 0x3, 0x4, 0x5};
midiState.NextEventBuffer();
midiState.WriteBuffer(m0, sizeof(m0));
midiState.ProcessInputBuffer(m0, sizeof(m0));
AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
@@ -2261,7 +2260,7 @@ namespace pipedal
static uint8_t m1[] = {0x06, 0xC0, 0x1, 0x2};
midiState.NextEventBuffer();
midiState.WriteBuffer(m1, sizeof(m1));
midiState.ProcessInputBuffer(m1, sizeof(m1));
AlsaAssert(midiState.GetMidiInputEventCount() == 3);
ExpectEvent(midiState, 0, {0x80, 0x05, 0x06});
ExpectEvent(midiState, 1, {0xC0, 0x1});
@@ -2272,7 +2271,7 @@ namespace pipedal
{
static uint8_t m0[] = {0xF0, 0x76, 0xF7, 0xA};
midiState.NextEventBuffer();
midiState.WriteBuffer(m0, 4);
midiState.ProcessInputBuffer(m0, 4);
AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
AlsaAssert(event.size == 2);
@@ -2284,11 +2283,11 @@ namespace pipedal
{
static uint8_t m0[] = {0xF0, 0x76, 0x3B};
midiState.NextEventBuffer();
midiState.WriteBuffer(m0, sizeof(m0));
midiState.ProcessInputBuffer(m0, sizeof(m0));
AlsaAssert(midiState.GetMidiInputEventCount() == 0);
static uint8_t m1[] = {0x77, 0xF7};
midiState.NextEventBuffer();
midiState.WriteBuffer(m1, sizeof(m1));
midiState.ProcessInputBuffer(m1, sizeof(m1));
AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
@@ -2298,5 +2297,6 @@ namespace pipedal
AlsaAssert(event.buffer[2] == 0x3B);
AlsaAssert(event.buffer[3] == 0x77);
}
#endif
}
} // namespace
+7 -13
View File
@@ -38,11 +38,9 @@ namespace pipedal {
struct MidiEvent
{
/* BINARY COMPATIBLE WITH jack_midi_event_t;! (in case we ever want to resurrect Jack support) */
uint32_t time; /**< Sample index at which event is valid */
size_t size; /**< Number of bytes of data in \a buffer */
uint8_t *buffer; /**< Raw MIDI data */
uint32_t time; /**< Sample frame at which event is valid */
uint32_t size; /**< Number of bytes of data in \a buffer */
uint8_t *buffer; /**< Raw MIDI data */
};
@@ -65,19 +63,15 @@ namespace pipedal {
virtual uint32_t GetSampleRate() = 0;
virtual size_t MidiInputBufferCount() const = 0;
virtual void*GetMidiInputBuffer(size_t channel, size_t nFrames) = 0;
virtual size_t GetMidiInputEventCount(void*buffer) = 0;
virtual bool GetMidiInputEvent(MidiEvent*event, void*portBuf, size_t nFrame) = 0;
virtual void FillMidiBuffers() = 0;
virtual size_t GetMidiInputEventCount() = 0;
virtual MidiEvent*GetMidiEvents() = 0;
virtual size_t InputBufferCount() const = 0;
virtual float*GetInputBuffer(size_t channel, size_t nFrames) = 0;
virtual float*GetInputBuffer(size_t channel) = 0;
virtual size_t OutputBufferCount() const = 0;
virtual float*GetOutputBuffer(size_t channel, size_t nFrames) = 0;
virtual float*GetOutputBuffer(size_t channe) = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0;
+264 -155
View File
@@ -291,6 +291,10 @@ public:
bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
{
if (!IsMatch(event))
{
return false;
}
switch (currentBinding.bindingType())
{
case BINDING_TYPE_NOTE:
@@ -374,6 +378,9 @@ private:
uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE];
size_t deferredMidiMessageCount = 0;
bool midiProgramChangePending = false;
bool midiSnapshotRequestPending = false;
int64_t snapshotRequestId = 0;
int selectedBank = -1;
int64_t midiProgramChangeId = 0;
@@ -462,8 +469,18 @@ private:
HostRingBufferReader hostReader;
HostRingBufferWriter hostWriter;
SystemMidiBinding nextMidiBinding;
SystemMidiBinding prevMidiBinding;
SystemMidiBinding prevBankMidiBinding;
SystemMidiBinding nextBankMidiBinding;
SystemMidiBinding nextPresetMidiBinding;
SystemMidiBinding prevPresetMidiBinding;
SystemMidiBinding snapshot1MidiBinding;
SystemMidiBinding snapshot2MidiBinding;
SystemMidiBinding snapshot3MidiBinding;
SystemMidiBinding snapshot4MidiBinding;
SystemMidiBinding snapshot5MidiBinding;
SystemMidiBinding snapshot6MidiBinding;
SystemMidiBinding startHotspotMidiBinding;
SystemMidiBinding stopHotspotMidiBinding;
SystemMidiBinding rebootMidiBinding;
@@ -478,8 +495,6 @@ private:
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
std::vector<uint8_t *> midiLv2Buffers;
uint32_t sampleRate = 0;
uint64_t currentSample = 0;
@@ -558,11 +573,6 @@ private:
this->inputRingBuffer.reset();
this->outputRingBuffer.reset();
for (size_t i = 0; i < midiLv2Buffers.size(); ++i)
{
delete[] midiLv2Buffers[i];
}
midiLv2Buffers.resize(0);
audioDriver = nullptr;
}
@@ -579,7 +589,7 @@ private:
{
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i)
{
float *out = (float *)audioDriver->GetOutputBuffer(i, nframes);
float *out = (float *)audioDriver->GetOutputBuffer(i);
if (out)
{
ZeroBuffer(out, nframes);
@@ -774,6 +784,16 @@ private:
}
break;
}
case RingBufferCommand::AckMidiSnapshotRequest:
{
int64_t requestId;
realtimeReader.readComplete(&requestId);
if (requestId == this->snapshotRequestId)
{
this->midiSnapshotRequestPending = false;
}
break;
}
case RingBufferCommand::LoadSnapshot:
{
IndexedSnapshot *snapshot;
@@ -851,6 +871,10 @@ private:
{
hostWriter.AckMidiProgramRequest(requestId);
}
virtual void AckSnapshotRequest(uint64_t snapshotRequestId)
{
hostWriter.AckMidiSnapshotRequest(snapshotRequestId);
}
void OnMidiValueChanged(uint64_t instanceId, int controlIndex, float value)
{
@@ -867,7 +891,7 @@ private:
bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
{
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
//eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
if (listenForMidiEvent)
@@ -886,126 +910,168 @@ private:
return true;
}
void OnSnapshotTriggered(int snapshotIndex)
{
// midiProgramChangePending = true;
this->midiSnapshotRequestPending = true;
this->realtimeWriter.OnRealtimeMidiSnapshotRequest(snapshotIndex, ++snapshotRequestId);
}
void ProcessMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
{
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->nextBankMidiBinding.IsTriggered(event))
{
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiBank(++(this->midiProgramChangeId), 1);
}
else if (this->prevBankMidiBinding.IsTriggered(event))
{
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiBank(++(this->midiProgramChangeId), -1);
}
else if (nextPresetMidiBinding.IsTriggered(event))
{
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true;
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), 1);
}
else if (prevPresetMidiBinding.IsTriggered(event))
{
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1);
}
else if (shutdownMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown);
}
if (rebootMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot);
}
if (startHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot);
}
if (stopHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot);
}
else if (midiProgramChangePending)
{
// defer the message for processing after the program change has completed discarding messages that don't fit in our buffer.
if (event.size > 0 && event.size < 128 && event.size + 2 + this->deferredMidiMessageCount < DEFERRED_MIDI_BUFFER_SIZE)
{
this->deferredMidiMessages[deferredMidiMessageCount++] = (uint8_t)event.size;
for (size_t i = 0; i < event.size; ++i)
{
this->deferredMidiMessages[deferredMidiMessageCount++] = event.buffer[i];
}
}
return;
}
else if (this->snapshot1MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(0);
}
else if (this->snapshot2MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(1);
}
else if (this->snapshot3MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(2);
}
else if (this->snapshot4MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(3);
}
else if (this->snapshot5MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(4);
}
else if (this->snapshot6MidiBinding.IsTriggered(event))
{
OnSnapshotTriggered(5);
}
else
{
onMidiEvent(eventBufferWriter, iterator, event);
}
}
void ProcessDeferredMidiMessages(
Lv2EventBufferWriter eventBufferWriter,
Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator)
{
MidiEvent event;
// write all deferred midi messages.
if (!midiProgramChangePending && !midiSnapshotRequestPending)
{
for (size_t i = 0; i < deferredMidiMessageCount; /**/)
{
int8_t deviceIndex = deferredMidiMessages[i++];
int8_t messageCount = deferredMidiMessages[i++];
event.size = messageCount;
event.buffer = deferredMidiMessages + i;
event.time = 0;
ProcessMidiEvent(eventBufferWriter, iterator, event);
if (midiProgramChangePending)
{
break;
}
i += messageCount;
if (midiSnapshotRequestPending)
{
// consume what has been written so far, leaving what follows for future processing.
size_t remaining = deferredMidiMessageCount - i;
for (size_t j = 0; j < remaining; ++i)
{
deferredMidiMessages[j] = deferredMidiMessages[i + j];
}
deferredMidiMessageCount = remaining;
break;
}
}
}
}
void ProcessMidiInput()
{
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids);
Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin();
size_t midiInputBufferCount = audioDriver->MidiInputBufferCount();
ProcessDeferredMidiMessages(eventBufferWriter, iterator);
audioDriver->FillMidiBuffers();
for (size_t midiDeviceIx = 0; midiDeviceIx < midiInputBufferCount; ++midiDeviceIx)
{
size_t n = audioDriver->GetMidiInputEventCount();
MidiEvent *events = audioDriver->GetMidiEvents();
void *portBuffer = audioDriver->GetMidiInputBuffer(midiDeviceIx, 0);
if (portBuffer)
for (size_t i = 0; i < n; ++i)
{
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 (this->shutdownMidiBinding.IsMatch(event))
{
if (shutdownMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown);
}
}
else if (this->rebootMidiBinding.IsMatch(event))
{
if (rebootMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot);
}
}
else if (this->startHotspotMidiBinding.IsMatch(event))
{
if (startHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot);
}
}
else if (this->stopHotspotMidiBinding.IsMatch(event))
{
if (stopHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot);
}
}
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);
}
}
}
ProcessMidiEvent(eventBufferWriter, iterator, events[i]);
}
}
}
@@ -1062,7 +1128,7 @@ private:
bool buffersValid = true;
for (int i = 0; i < audioDriver->InputBufferCount(); ++i)
{
float *input = (float *)audioDriver->GetInputBuffer(i, nframes);
float *input = (float *)audioDriver->GetInputBuffer(i);
if (input == nullptr)
{
buffersValid = false;
@@ -1074,7 +1140,7 @@ private:
for (int i = 0; i < audioDriver->OutputBufferCount(); ++i)
{
float *output = audioDriver->GetOutputBuffer(i, nframes);
float *output = audioDriver->GetOutputBuffer(i);
if (output == nullptr)
{
buffersValid = false;
@@ -1393,7 +1459,7 @@ public:
{
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
//this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
}
}
}
@@ -1449,12 +1515,27 @@ public:
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
}
else if (command == RingBufferCommand::NextMidiBank)
{
RealtimeNextMidiProgramRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiBank(request);
}
else if (command == RingBufferCommand::RealtimeMidiEvent)
{
RealtimeMidiEventRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyMidiRealtimeEvent(request.eventType);
}
else if (command == RingBufferCommand::RealtimeMidiSnapshotRequest)
{
RealtimeMidiSnapshotRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyMidiRealtimeSnapshotRequest(
request.snapshotIndex,
request.snapshotRequestId);
}
else if (command == RingBufferCommand::Lv2ErrorMessage)
{
size_t size;
@@ -1579,12 +1660,6 @@ public:
this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate) * OVERRUN_GRACE_PERIOD_S);
this->vuSamplesPerUpdate = (size_t)(sampleRate * VU_UPDATE_RATE_S);
midiLv2Buffers.resize(audioDriver->MidiInputBufferCount());
for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i)
{
midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE);
}
active = true;
audioStopped = false;
audioDriver->Activate();
@@ -1855,7 +1930,7 @@ public:
}
private:
virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
class RestartThread
@@ -2040,13 +2115,21 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
for (auto i = bindings.begin(); i != bindings.end(); ++i)
{
if (i->symbol() == "nextProgram")
if (i->symbol() == "nextBank")
{
this->nextMidiBinding.SetBinding(*i);
this->nextBankMidiBinding.SetBinding(*i);
}
else if (i->symbol() == "prevBank")
{
this->prevBankMidiBinding.SetBinding(*i);
}
else if (i->symbol() == "nextProgram")
{
this->nextPresetMidiBinding.SetBinding(*i);
}
else if (i->symbol() == "prevProgram")
{
this->prevMidiBinding.SetBinding(*i);
this->prevPresetMidiBinding.SetBinding(*i);
}
else if (i->symbol() == "startHotspot")
{
@@ -2064,6 +2147,30 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
{
this->shutdownMidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot1")
{
this->snapshot1MidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot2")
{
this->snapshot2MidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot3")
{
this->snapshot3MidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot4")
{
this->snapshot4MidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot5")
{
this->snapshot5MidiBinding.SetBinding(*i);
}
else if (i->symbol() == "snapshot6")
{
this->snapshot6MidiBinding.SetBinding(*i);
}
else
{
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
@@ -2076,22 +2183,24 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost)
return new AudioHostImpl(pHost);
}
bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard)
{
bool changed = false;
auto pedalboardItems = pedalboard.GetAllPlugins();
for (PedalboardItem *item : pedalboardItems)
{
if (!item->isSplit())
{
if (UpdatePluginState(*item))
{
changed = true;
}
}
}
return true;
}
// Removed because any updates to state have to be sent to clients as well,
// so this functionality was moved to PiPedalModel::SyncLv2States();
// bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard)
// {
// bool changed = false;
// auto pedalboardItems = pedalboard.GetAllPlugins();
// for (PedalboardItem *item : pedalboardItems)
// {
// if (!item->isSplit())
// {
// if (UpdatePluginState(*item))
// {
// changed = true;
// }
// }
// }
// return changed;
// }
void AudioHostImpl::OnNotifyPathPatchPropertyReceived(
int64_t instanceId,
@@ -2100,15 +2209,15 @@ void AudioHostImpl::OnNotifyPathPatchPropertyReceived(
{
if (this->currentPedalboard)
{
IEffect*effect = this->currentPedalboard->GetEffect(instanceId);
IEffect *effect = this->currentPedalboard->GetEffect(instanceId);
if (!effect)
{
return;
}
if (effect->IsLv2Effect())
{
Lv2Effect*lv2Effect = (Lv2Effect*)effect;
lv2Effect->SetPathPatchProperty(pathPatchPropertyUri,jsonAtom);
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
lv2Effect->SetPathPatchProperty(pathPatchPropertyUri, jsonAtom);
}
}
}
+5 -1
View File
@@ -166,8 +166,10 @@ namespace pipedal
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) = 0;
virtual void OnNotifyNextMidiBank(const RealtimeNextMidiProgramRequest &request) = 0;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
};
class JackHostStatus
@@ -206,7 +208,7 @@ namespace pipedal
virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(bool listen) = 0;
virtual bool UpdatePluginStates(Pedalboard &pedalboard) = 0;
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) = 0;
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) = 0;
virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
@@ -235,6 +237,8 @@ namespace pipedal
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) = 0;
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual void AckSnapshotRequest(uint64_t snapshotRequestId) = 0;
virtual JackHostStatus getJackStatus() = 0;
+2 -2
View File
@@ -113,9 +113,9 @@ add_compile_definitions(DEBIAN_ARCHITECTURE="${DEBIAN_ARCHITECTURE}")
if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG -D_GLIBCXX_DEBUG" )
if (USE_SANITIZE)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan " )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan " )
endif()
elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
message(STATUS "RelWithgDebInfo build")
+11 -115
View File
@@ -150,6 +150,14 @@ namespace pipedal
}
}
virtual size_t GetMidiInputEventCount() override {
return 0;
}
virtual MidiEvent*GetMidiEvents() {
return nullptr;
}
JackChannelSelection channelSelection;
bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
@@ -166,7 +174,6 @@ namespace pipedal
open = true;
try
{
OpenMidi(jackServerSettings, channelSelection);
OpenAudio(jackServerSettings, channelSelection);
}
catch (const std::exception &e)
@@ -348,128 +355,17 @@ namespace pipedal
static constexpr size_t MAX_MIDI_EVENT = 4 * 1024;
public:
class MidiState
{
private:
snd_rawmidi_t *hIn = nullptr;
snd_rawmidi_params_t *hInParams = nullptr;
std::string deviceName;
// running status state.
uint8_t runningStatus = 0;
int dataLength = 0;
int dataIndex = 0;
size_t statusBytesRemaining = 0;
size_t data0;
size_t data1;
size_t eventCount = 0;
MidiEvent events[MAX_MIDI_EVENT];
size_t bufferCount = 0;
uint8_t buffer[MIDI_BUFFER_SIZE];
uint8_t readBuffer[1024];
ssize_t sysexStartIndex = -1;
void checkError(int result, const char *message)
{
if (result < 0)
{
throw PiPedalStateException(SS("Unexpected error: " << message << " (" << this->deviceName));
}
}
public:
void Open(const AlsaMidiDeviceInfo &device)
{
bufferCount = 0;
eventCount = 0;
sysexStartIndex = -1;
runningStatus = 0;
dataIndex = 0;
dataLength = 0;
this->deviceName = device.description_;
}
void Close()
{
}
size_t GetMidiInputEventCount()
{
return 0;
}
bool GetMidiInputEvent(MidiEvent *event, size_t nFrame)
{
return false;
}
void MidiPut(uint8_t cc, uint8_t d0, uint8_t d1)
{
}
void FillBuffer()
{
}
void WriteBuffer(uint8_t *readBuffer, size_t nRead)
{
}
};
std::vector<MidiState *> midiStates;
void OpenMidi(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{
const auto &devices = channelSelection.GetInputMidiDevices();
midiStates.resize(devices.size());
for (size_t i = 0; i < devices.size(); ++i)
{
const auto &device = devices[i];
MidiState *state = new MidiState();
midiStates[i] = state;
state->Open(device);
}
}
virtual size_t MidiInputBufferCount() const
{
return this->midiStates.size();
}
virtual void *GetMidiInputBuffer(size_t channel, size_t nFrames)
{
return (void *)midiStates[channel];
}
virtual size_t GetMidiInputEventCount(void *portBuffer)
{
MidiState *state = (MidiState *)portBuffer;
return state->GetMidiInputEventCount();
}
virtual bool GetMidiInputEvent(MidiEvent *event, void *portBuf, size_t nFrame)
{
MidiState *state = (MidiState *)portBuf;
return state->GetMidiInputEvent(event, nFrame);
}
virtual void FillMidiBuffers()
{
}
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
virtual float *GetInputBuffer(size_t channel, size_t nFrames)
virtual float *GetInputBuffer(size_t channel)
{
return activeCaptureBuffers[channel];
}
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel, size_t nFrames)
virtual float *GetOutputBuffer(size_t channel)
{
return activePlaybackBuffers[channel];
}
+1 -1
View File
@@ -37,7 +37,7 @@ using namespace dbus::networkmanager;
namespace pipedal::impl
{
constexpr std::string PIPEDAL_HOTSPOT_NAME = "PiPedal Hotspot";
#define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
class HotspotManagerImpl : public HotspotManager
{
+2 -2
View File
@@ -358,11 +358,11 @@ public:
for (size_t i = 0; i < inputs; ++i)
{
inputBuffers[i] = audioDriver->GetInputBuffer(i, nFrames);
inputBuffers[i] = audioDriver->GetInputBuffer(i);
}
for (size_t i = 0; i < outputs; ++i)
{
outputBuffers[i] = audioDriver->GetOutputBuffer(i, nFrames);
outputBuffers[i] = audioDriver->GetOutputBuffer(i);
}
for (size_t i = 0; i < nFrames; ++i)
+91 -14
View File
@@ -349,6 +349,7 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s
}
}
// we were explicitly told that the state was changed by the plugin
void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
{
// a sent PATCH_Set, or an explicit state changed notification.
@@ -356,6 +357,7 @@ void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
this->SetPresetChanged(-1, true, false);
}
// The plugin notified us that a path path property changed. The state *purrobably changed.
void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
{
// one or more received PATCH_Sets, which MAY change the state.
@@ -363,7 +365,9 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
PedalboardItem *item = pedalboard.GetItem(instanceId);
if (item != nullptr)
{
if (!audioHost) {
return;
}
bool changed = this->audioHost->UpdatePluginState(*item);
if (changed)
{
@@ -371,11 +375,8 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
item->stateUpdateCount(item->stateUpdateCount() + 1);
Lv2PluginState newState = item->lv2State();
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t)
{
subscriber->OnLv2StateChanged(instanceId, newState);
}
FireLv2StateChanged(instanceId,newState);
}
}
}
@@ -509,6 +510,8 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
}
}
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
@@ -723,12 +726,43 @@ void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard)
#endif
}
void PiPedalModel::FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State) {
std::lock_guard<std::recursive_mutex> guard{mutex};
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t)
{
subscriber->OnLv2StateChanged(instanceId,lv2State);
}
}
// referesh the plugin state for all plugins.
bool PiPedalModel::SyncLv2State()
{
bool changed = false;
auto pedalboardItems = pedalboard.GetAllPlugins();
for (PedalboardItem *item : pedalboardItems)
{
if (!item->isSplit())
{
if (audioHost->UpdatePluginState(*item))
{
FireLv2StateChanged(item->instanceId(),item->lv2State());
changed = true;
}
}
}
return changed;
}
void PiPedalModel::SaveCurrentPreset(int64_t clientId)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
UpdateVst3Settings(this->pedalboard);
this->audioHost->UpdatePluginStates(this->pedalboard);
SyncLv2State();
storage.SaveCurrentPreset(this->pedalboard);
this->SetPresetChanged(clientId, false);
}
@@ -761,6 +795,7 @@ int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &n
{
std::lock_guard<std::recursive_mutex> guard{mutex};
SyncLv2State();
auto pedalboard = this->pedalboard;
UpdateVst3Settings(pedalboard);
pedalboard.name(name);
@@ -880,7 +915,7 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
try
{
if (request.direction < 0)
if (request.direction >= 0)
{
NextPreset();
}
@@ -900,6 +935,47 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
}
}
void PiPedalModel::OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId)
{
try {
SetSnapshot((int64_t)snapshotIndex);
} catch (const std::exception&e)
{
Lv2Log::error(SS("SetSnapshot failed. " << e.what()));
}
if (this->audioHost)
{
this->audioHost->AckSnapshotRequest(snapshotRequestId);
}
}
void PiPedalModel::OnNotifyNextMidiBank(const RealtimeNextMidiProgramRequest &request)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
try
{
if (request.direction >= 0)
{
NextBank();
}
else
{
PreviousBank();
}
}
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};
@@ -1953,14 +2029,14 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
atom_object atomObject{atomValue};
PedalboardItem::PropertyMap &properties = item->PatchProperties();
if (properties.contains(propertyUri))
if (properties.contains(propertyUri) && properties[propertyUri] == atomObject)
{
if (properties[propertyUri] == atomObject)
{
return;
}
// do noting.
} else {
properties[propertyUri] = std::move(atomObject);
}
properties[propertyUri] = std::move(atomObject);
OnNotifyMaybeLv2StateChanged(instanceId);
}
bool hasAtomJson = false;
@@ -2509,6 +2585,7 @@ void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnecte
FireNetworkChanged();
}
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
{
try
+5 -2
View File
@@ -50,6 +50,7 @@ namespace pipedal
class Lv2PluginChangeMonitor;
class Updater;
class AvahiService;
class Lv2PluginState;
class IPiPedalModelSubscriber
{
@@ -187,7 +188,7 @@ namespace pipedal
void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State);
void UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
void UpdateDefaults(Pedalboard *pedalboard);
@@ -211,7 +212,7 @@ namespace pipedal
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
std::atomic<bool> closed = false;
bool SyncLv2State();
private: // IAudioHostCallbacks
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) override;
@@ -221,6 +222,7 @@ namespace pipedal
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
void OnNotifyPathPatchPropertyReceived(
int64_t instanceId,
@@ -229,6 +231,7 @@ namespace pipedal
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) override;
virtual void OnNotifyNextMidiBank(const RealtimeNextMidiProgramRequest &request) override;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) override;
PostHandle networkChangingDelayHandle = 0;
+10 -1
View File
@@ -23,7 +23,16 @@ namespace pipedal {
Shutdown,
Reboot,
StartHotspot,
StopHotspot
StopHotspot,
NextBank,
PrevBank,
Snapshot1,
Snapshot2,
Snapshot3,
Snapshot4,
Snapshot5,
Snapshot6,
};
};
+95 -81
View File
@@ -27,8 +27,6 @@
#include "RealtimeMidiEventType.hpp"
#include <chrono>
namespace pipedal
{
class IndexedSnapshot;
@@ -65,8 +63,10 @@ namespace pipedal
MidiProgramChange, // program change requested via midi.
AckMidiProgramChange,
AckMidiSnapshotRequest,
NextMidiProgram,
NextMidiBank,
Lv2StateChanged,
MaybeLv2StateChanged,
@@ -75,28 +75,36 @@ namespace pipedal
Lv2ErrorMessage,
RealtimeMidiEvent,
RealtimeMidiSnapshotRequest,
SendPathPropertyBuffer,
};
struct RealtimeMidiEventRequest {
struct RealtimeMidiEventRequest
{
RealtimeMidiEventType eventType;
};
struct RealtimeNextMidiProgramRequest {
struct RealtimeMidiSnapshotRequest
{
int32_t snapshotIndex;
int64_t snapshotRequestId;
};
struct RealtimeNextMidiProgramRequest
{
int64_t requestId;
int32_t direction;
};
struct RealtimeMidiProgramRequest {
struct RealtimeMidiProgramRequest
{
int64_t requestId;
int8_t bank;
uint8_t program;
};
class RealtimeMonitorPortSubscription
{
public:
@@ -169,7 +177,6 @@ namespace pipedal
float value;
};
class SetControlValueBody
{
public:
@@ -201,36 +208,38 @@ namespace pipedal
{
private:
RingBuffer<MULTI_WRITE,SEMAPHORE_READ> *ringBuffer = nullptr;
RingBuffer<MULTI_WRITE, SEMAPHORE_READ> *ringBuffer = nullptr;
public:
RingBufferReader()
: ringBuffer(nullptr)
{
}
RingBufferReader(RingBuffer<MULTI_WRITE,SEMAPHORE_READ> *ringBuffer)
RingBufferReader(RingBuffer<MULTI_WRITE, SEMAPHORE_READ> *ringBuffer)
: ringBuffer(ringBuffer)
{
}
void Reset() { ringBuffer->reset(); }
// 0 -> ready. -1: timed out. -2: closing.
template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) {
template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep, Period> &timeout)
{
return ringBuffer->readWait_for(timeout);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(std::chrono::time_point<Clock,Duration>&time_point)
RingBufferStatus wait_until(std::chrono::time_point<Clock, Duration> &time_point)
{
return ringBuffer->readWait_until(time_point);
}
template <typename Clock, typename Duration>
RingBufferStatus wait_until(size_t size,std::chrono::time_point<Clock,Duration>&time_point)
RingBufferStatus wait_until(size_t size, std::chrono::time_point<Clock, Duration> &time_point)
{
return ringBuffer->readWait_until(size,time_point);
return ringBuffer->readWait_until(size, time_point);
}
bool wait() {
bool wait()
{
return ringBuffer->readWait();
}
size_t readSpace() const
@@ -240,33 +249,29 @@ namespace pipedal
template <typename T>
bool read(T *output)
{
if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
if (!ringBuffer->read(sizeof(T), (uint8_t *)output))
{
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
}
return true;
}
bool read(size_t size, uint8_t*data)
bool read(size_t size, uint8_t *data)
{
if (!ringBuffer->read(size,data))
if (!ringBuffer->read(size, data))
{
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
}
return true;
}
template <typename T>
void readComplete(T *output)
{
if (!ringBuffer->read(sizeof(T),(uint8_t*)output))
if (!ringBuffer->read(sizeof(T), (uint8_t *)output))
{
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
}
}
};
template <typename T>
@@ -290,19 +295,19 @@ namespace pipedal
T value;
};
template<bool MULTI_WRITER, bool SEMAPHORE_READER>
template <bool MULTI_WRITER, bool SEMAPHORE_READER>
class RingBufferWriter
{
private:
RingBuffer<MULTI_WRITER,SEMAPHORE_READER>* ringBuffer;
RingBuffer<MULTI_WRITER, SEMAPHORE_READER> *ringBuffer;
public:
RingBufferWriter()
: ringBuffer(nullptr)
{
}
RingBufferWriter(RingBuffer<MULTI_WRITER,SEMAPHORE_READER>*ringBuffer)
RingBufferWriter(RingBuffer<MULTI_WRITER, SEMAPHORE_READER> *ringBuffer)
: ringBuffer(ringBuffer)
{
}
@@ -315,88 +320,99 @@ namespace pipedal
// the goal: to atomically write the command and associated data.
CommandBuffer<T> buffer(command, value);
if (!ringBuffer->write(buffer.size(),(uint8_t *)&buffer))
if (!ringBuffer->write(buffer.size(), (uint8_t *)&buffer))
{
Lv2Log::error("No space in audio service ringbuffer.");
return;
}
}
template <typename T>
void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t*variableData)
void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t *variableData)
{
// the goal: to atomically write the command and associated data.
CommandBuffer<T> buffer(command, value);
if (!ringBuffer->write(buffer.size(),(uint8_t *)&buffer,dataLength,variableData))
if (!ringBuffer->write(buffer.size(), (uint8_t *)&buffer, dataLength, variableData))
{
Lv2Log::error("No space in audio service ringbuffer.");
return;
}
}
void Lv2StateChanged(uint64_t instanceId)
{
write(RingBufferCommand::Lv2StateChanged,instanceId);
write(RingBufferCommand::Lv2StateChanged, instanceId);
}
void MaybeLv2StateChanged(uint64_t instanceId)
{
write(RingBufferCommand::MaybeLv2StateChanged,instanceId);
write(RingBufferCommand::MaybeLv2StateChanged, instanceId);
}
void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data)
void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t *data)
{
write(RingBufferCommand::AtomOutput,instanceId,bytes,data);
write(RingBufferCommand::AtomOutput, instanceId, bytes, data);
}
void AtomOutput(uint64_t instanceId, const LV2_Atom*atom)
void AtomOutput(uint64_t instanceId, const LV2_Atom *atom)
{
write(RingBufferCommand::AtomOutput,instanceId,atom->size+sizeof(LV2_Atom),(uint8_t*)atom);
write(RingBufferCommand::AtomOutput, instanceId, atom->size + sizeof(LV2_Atom), (uint8_t *)atom);
}
void ParameterRequest(RealtimePatchPropertyRequest *pRequest)
{
write(RingBufferCommand::ParameterRequest,pRequest);
write(RingBufferCommand::ParameterRequest, pRequest);
}
void ParameterRequestComplete(RealtimePatchPropertyRequest *pRequest)
{
write(RingBufferCommand::ParameterRequestComplete,pRequest);
write(RingBufferCommand::ParameterRequestComplete, pRequest);
}
void MidiValueChanged(int64_t instanceId, int controlIndex,float value)
void MidiValueChanged(int64_t instanceId, int controlIndex, float value)
{
MidiValueChangedBody body;
body.instanceId = instanceId;
body.controlIndex = controlIndex;
body.value = value;
write(RingBufferCommand::MidiValueChanged,body);
write(RingBufferCommand::MidiValueChanged, body);
}
void OnMidiListen(bool isNote, uint8_t noteOrControl) {
void OnMidiListen(bool isNote, uint8_t noteOrControl)
{
uint16_t msg = noteOrControl;
if (isNote) msg |= 0x100;
if (isNote)
msg |= 0x100;
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)
void OnMidiProgramChange(int64_t _requestId, int8_t _bank, uint8_t _program)
{
RealtimeMidiProgramRequest msg { requestId:_requestId, bank: _bank,program: _program};
RealtimeMidiProgramRequest msg{requestId : _requestId, bank : _bank, program : _program};
write(RingBufferCommand::MidiProgramChange,msg);
write(RingBufferCommand::MidiProgramChange, msg);
}
void OnRealtimeMidiEvent(RealtimeMidiEventType eventType)
{
RealtimeMidiEventRequest msg { eventType};
write(RingBufferCommand::RealtimeMidiEvent,msg);
RealtimeMidiEventRequest msg{eventType};
write(RingBufferCommand::RealtimeMidiEvent, msg);
}
void OnNextMidiProgram(int64_t requestId,int32_t direction)
void OnRealtimeMidiSnapshotRequest(int32_t snapshotIndex, int64_t snapshotRequestId)
{
RealtimeNextMidiProgramRequest msg { requestId: requestId, direction:direction};
write(RingBufferCommand::NextMidiProgram,msg);
RealtimeMidiSnapshotRequest msg{snapshotIndex, snapshotRequestId};
write(RingBufferCommand::RealtimeMidiSnapshotRequest, msg);
}
void OnNextMidiProgram(int64_t requestId, int32_t direction)
{
RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction};
write(RingBufferCommand::NextMidiProgram, msg);
}
void OnNextMidiBank(int64_t requestId, int32_t direction)
{
RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction};
write(RingBufferCommand::NextMidiBank, msg);
}
void SetControlValue(int effectIndex, int controlIndex, float value)
@@ -405,22 +421,21 @@ namespace pipedal
body.effectIndex = effectIndex;
body.controlIndex = controlIndex;
body.value = value;
write(RingBufferCommand::SetValue,body);
write(RingBufferCommand::SetValue, body);
}
void SetInputVolume(float value)
{
SetVolumeBody body;
body.value = value;
write(RingBufferCommand::SetInputVolume,body);
write(RingBufferCommand::SetInputVolume, body);
}
void SetOutputVolume(float value)
{
SetVolumeBody body;
body.value = value;
write(RingBufferCommand::SetOutputVolume,body);
write(RingBufferCommand::SetOutputVolume, body);
}
void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
{
write(RingBufferCommand::FreeVuSubscriptions, configuration);
@@ -432,7 +447,7 @@ namespace pipedal
}
void SendMonitorPortUpdate(
PortMonitorCallback* callback,
PortMonitorCallback *callback,
int64_t subscriptionHandle,
float value)
{
@@ -442,12 +457,12 @@ namespace pipedal
void SendVuUpdate(const std::vector<VuUpdate> *pUpdates)
{
write(RingBufferCommand::SendVuUpdate,pUpdates);
write(RingBufferCommand::SendVuUpdate, pUpdates);
}
void AckVuUpdate()
{
bool value = true;
write(RingBufferCommand::AckVuUpdate,value);
write(RingBufferCommand::AckVuUpdate, value);
}
void AckMonitorPortUpdate(int64_t subscriptionHandle)
{
@@ -459,14 +474,17 @@ namespace pipedal
{
write(RingBufferCommand::SetVuSubscriptions, configuration);
}
void LoadSnapshot(IndexedSnapshot*snapshot)
void LoadSnapshot(IndexedSnapshot *snapshot)
{
write(RingBufferCommand::LoadSnapshot,snapshot);
write(RingBufferCommand::LoadSnapshot, snapshot);
}
void AckMidiProgramRequest(int64_t requestId)
{
write(RingBufferCommand::AckMidiProgramChange,requestId);
write(RingBufferCommand::AckMidiProgramChange, requestId);
}
void AckMidiSnapshotRequest(uint64_t snapshotRequestId) {
write(RingBufferCommand::AckMidiSnapshotRequest, snapshotRequestId);
}
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
@@ -481,7 +499,7 @@ namespace pipedal
SetBypassBody body;
body.effectIndex = effectIndex;
body.enabled = enabled;
write(RingBufferCommand::SetBypass,body);
write(RingBufferCommand::SetBypass, body);
}
void ReplaceEffect(Lv2Pedalboard *pedalboard)
@@ -500,19 +518,19 @@ namespace pipedal
write(RingBufferCommand::EffectReplaced, pedalboard);
}
void FreeSnapshot(IndexedSnapshot*snapshot)
void FreeSnapshot(IndexedSnapshot *snapshot)
{
write(RingBufferCommand::FreeSnapshot,snapshot);
write(RingBufferCommand::FreeSnapshot, snapshot);
}
void WriteLv2ErrorMessage(int64_t instanceId, const char*message)
void WriteLv2ErrorMessage(int64_t instanceId, const char *message)
{
size_t length = strlen(message);
write(RingBufferCommand::Lv2ErrorMessage,instanceId,length,(uint8_t*)message);
write(RingBufferCommand::Lv2ErrorMessage, instanceId, length, (uint8_t *)message);
}
void SendPathPropertyBuffer(PatchPropertyWriter::Buffer*buffer)
void SendPathPropertyBuffer(PatchPropertyWriter::Buffer *buffer)
{
write(RingBufferCommand::SendPathPropertyBuffer,buffer);
write(RingBufferCommand::SendPathPropertyBuffer, buffer);
}
};
@@ -521,20 +539,16 @@ namespace pipedal
typedef RingBufferWriter<true, false> HostRingBufferWriter;
// cures a forward-declaration problem.
class RealtimeRingBufferWriter: public RingBufferWriter<false, true>
class RealtimeRingBufferWriter : public RingBufferWriter<false, true>
{
public:
RealtimeRingBufferWriter()
{
}
RealtimeRingBufferWriter(RingBuffer<false,true>*ringBuffer)
: RingBufferWriter<false, true> (ringBuffer)
RealtimeRingBufferWriter(RingBuffer<false, true> *ringBuffer)
: RingBufferWriter<false, true>(ringBuffer)
{
}
};
} //namespace
} // namespace
+1 -1
View File
@@ -295,7 +295,7 @@ namespace pipedal
}
}
uint64_t instanceId;
virtual bool IsLv2Effect() const override { return true; }
virtual bool IsLv2Effect() const override { return false; }
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
+35
View File
@@ -1418,6 +1418,17 @@ void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
writer.write(bindings);
}
}
static bool hasBinding(std::vector<MidiBinding> &bindings, const std::string&name)
{
for (auto&binding: bindings)
{
if (binding.symbol() == name)
{
return true;
}
}
return false;
}
std::vector<MidiBinding> Storage::GetSystemMidiBindings()
{
std::vector<MidiBinding> result;
@@ -1441,6 +1452,20 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
result.push_back(MidiBinding::SystemBinding("shutdown"));
result.push_back(MidiBinding::SystemBinding("reboot"));
}
if (!hasBinding(result,"prevBank"))
{
result.insert(result.begin(),MidiBinding::SystemBinding("nextBank")); // reverse order.
result.insert(result.begin(),MidiBinding::SystemBinding("prevBank"));
}
if (!hasBinding(result,"snapshot1"))
{
auto position = 4;
for (int i = 0; i < 6; ++i)
{
result.insert(result.begin()+position,MidiBinding::SystemBinding(SS("snapshot" << (i+1))));
++position;
}
}
return result;
}
catch (const std::exception&e)
@@ -1449,8 +1474,18 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
}
}
result.clear();
result.push_back(MidiBinding::SystemBinding("prevBank"));
result.push_back(MidiBinding::SystemBinding("nextBank"));
result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram"));
result.push_back(MidiBinding::SystemBinding("snapshot1"));
result.push_back(MidiBinding::SystemBinding("snapshot2"));
result.push_back(MidiBinding::SystemBinding("snapshot3"));
result.push_back(MidiBinding::SystemBinding("snapshot4"));
result.push_back(MidiBinding::SystemBinding("snapshot5"));
result.push_back(MidiBinding::SystemBinding("snapshot6"));
result.push_back(MidiBinding::SystemBinding("stopHotspot"));
result.push_back(MidiBinding::SystemBinding("startHotspot"));
result.push_back(MidiBinding::SystemBinding("shutdown"));
+2 -2
View File
@@ -34,12 +34,12 @@ class Copyrights {
static std::string trim(const std::string&value)
{
size_t start = 0;
while (start < value.length() && value[start] == ' ' || value[start] == '\t')
while (start < value.length() && (value[start] == ' ' || value[start] == '\t'))
{
++start;
}
size_t end = value.length();
while (end > start && value[end-1] == ' ' || value[end-1] == '\t')
while (end > start && (value[end-1] == ' ' || value[end-1] == '\t'))
{
--end;
}
+1 -13
View File
@@ -1,7 +1,3 @@
ToobFreeverb has a big memory leak!
Review state changing code. Do we need this anymmore? I think not. It is a bit sus.
Review current handleNotifyPatchProperty. I tink server-side support needs to be
removed, and rely on handleNotifyPathPatchPropertyChanged instead.
@@ -10,26 +6,18 @@ removed, and rely on handleNotifyPathPatchPropertyChanged instead.
Midi bindings.
SEGV at void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
bool changed = this->audioHost->UpdatePluginState(*item);
PiPedalModel.cpp:375
because audioHost is gone.
revamp shutdown procedure for the audio thread.
write a marker on exit, and don't release
audioHost UNTIL the service thread has read the close marker.
- continuing corrupt memory.
- verify that scanning doesn't cause overruns.
- verify address change behaviour in client
-verify clean removal of dhcpcd and nm_p2p2d
X Review docs changes once we go live.
- Localisation: support non-UTF8 code pages.
- unicode commandline arguments.
- unicode commandline arguments. (probably the hotspot name)
- review unicode filenames (this is probably ok)
- BUG: gcs when we have an animated output control