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", "version": "0.2.0",
"configurations": [ "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", "name": "(gdb) pipedal_nm_p2pd",
"type": "cppdbg", "type": "cppdbg",
@@ -174,13 +96,7 @@
"description": "Enable pretty-printing for gdb", "description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing", "text": "-enable-pretty-printing",
"ignoreFailures": true "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. //"[json_variants]" // subtest of your choice, or none to run all of the tests.
//"[inverting_mutex_test]" //"[inverting_mutex_test]"
// "[utf8_to_utf32]" // "[utf8_to_utf32]"
"[updater]" "[wifi_channels_test]"
], ],
"stopAtEntry": false, "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: // Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": [ "args": [
"--prefix",
"/usr/sbin",
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.) "--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
"--install"
//"--get-current-port" //"--get-current-port"
//"--install" // "--uninstall"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14" //"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
//"--list-p2p-channels" "--list-p2p-channels"
], ],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
+2 -1
View File
@@ -97,7 +97,8 @@
"strstream": "cpp", "strstream": "cpp",
"p2p_i.h": "c", "p2p_i.h": "c",
"p2p.h": "c", "p2p.h": "c",
"hash_set": "cpp" "hash_set": "cpp",
"barrier": "cpp"
}, },
"cSpell.words": [ "cSpell.words": [
"Guitarix", "Guitarix",
+1 -1
View File
@@ -14,7 +14,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
if (CMAKE_BUILD_TYPE MATCHES Debug) if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build") 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() endif()
# Can't get the pkg_check to work. # Can't get the pkg_check to work.
+7
View File
@@ -61,3 +61,10 @@ export class FakeAndroidHost implements AndroidHostInterface
return this.theme; 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; let atomJson = body.atomJson as any;
this.handleNotifyPathPatchPropertyChanged(instanceId, propertyUri, atomJson); this.handleNotifyPathPatchPropertyChanged(instanceId, propertyUri, atomJson);
if (header.replyTo) { if (header.replyTo) {
this.webSocket?.reply(header.replyTo, "onNotifyPatchProperty", true); this.webSocket?.reply(header.replyTo, "onNotifyPathPatchPropertyChanged", true);
} }
} else if (message === "onNotifyPatchProperty") { } else if (message === "onNotifyPatchProperty") {
let clientHandle = body.clientHandle as number; 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 Slide, { SlideProps } from '@mui/material/Slide';
import { createStyles, Theme } from '@mui/material/styles'; import { createStyles, Theme } from '@mui/material/styles';
import { WithStyles, withStyles } from '@mui/styles'; import { WithStyles, withStyles } from '@mui/styles';
import { canScaleWindow, getWindowScaleText } from './WindowScale';
@@ -466,8 +467,15 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
midiSummary(): string { midiSummary(): string {
let ports = this.state.jackSettings.inputMidiDevices; let ports = this.state.jackSettings.inputMidiDevices;
if (ports.length === 0) return "Disabled"; 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() { handleShowWifiConfigDialog() {
this.setState({ this.setState({
@@ -571,7 +579,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
( (
<div> <div>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}> <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. The Auto-Hotspot feature allows you to connect to Pipedal even if you don't have access to a Wi-Fi router.
</Typography> </Typography>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}> <Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}>
@@ -678,6 +686,79 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
</div> </div>
</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 /> <Divider />
<div > <div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography> <Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
@@ -720,65 +801,6 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Divider /> <Divider />
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">SYSTEM</Typography> <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 && ( this.model.enableAutoUpdate && (
<ButtonBase <ButtonBase
-1
View File
@@ -133,7 +133,6 @@ export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPan
} else { } else {
return window.innerWidth < 800; return window.innerWidth < 800;
} }
return window.innerWidth < 500;
} }
getPortraitOrientation() { getPortraitOrientation() {
return window.innerWidth * 2 < window.innerHeight * 3; return window.innerWidth * 2 < window.innerHeight * 3;
+67 -42
View File
@@ -50,7 +50,7 @@ const styles = (theme: Theme) => createStyles({
flex: "1 1 auto", flex: "1 1 auto",
}, },
pluginTable: { pluginTable: {
border: "collapse", borderCollapse: "collapse",
width: "100%", width: "100%",
}, },
@@ -66,6 +66,13 @@ const styles = (theme: Theme) => createStyles({
verticalAlign: "top", verticalAlign: "top",
paddingTop: 12 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[] { createBindings(): BindingEntry[] {
let result: BindingEntry[] = []; let result: BindingEntry[] = [];
for (var item of this.model.systemMidiBindings.get()) let listenInstanceId = 0;
{ for (var item of this.model.systemMidiBindings.get()) {
let displayName = ""; let displayName = "";
let instanceId = -1; let found = true;
if (item.symbol === "nextBank") {
if (item.symbol === "prevProgram") displayName = "Next Bank";
{
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;
} }
else if (item.symbol === "prevBank") {
if (instanceId !== -1) 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,instanceId,item)); result.push(new BindingEntry(displayName, listenInstanceId, item));
++listenInstanceId;
} }
} }
return result; return result;
@@ -174,8 +194,7 @@ export const SystemMidiBindingDialog =
clearTimeout(this.listenTimeoutHandle); clearTimeout(this.listenTimeoutHandle);
this.listenTimeoutHandle = undefined; this.listenTimeoutHandle = undefined;
} }
if (this.listenHandle) if (this.listenHandle) {
{
this.model.cancelListenForMidiEvent(this.listenHandle) this.model.cancelListenForMidiEvent(this.listenHandle)
this.listenHandle = undefined; 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(); this.cancelListenForControl();
for (var binding of this.state.systemMidiBindings) for (var binding of this.state.systemMidiBindings) {
{ if (binding.instanceId === instanceId) {
if (binding.instanceId === instanceId)
{
let newBinding = binding.midiBinding.clone(); let newBinding = binding.midiBinding.clone();
if (isNote) { if (isNote) {
@@ -258,6 +274,16 @@ export const SystemMidiBindingDialog =
let items = this.state.systemMidiBindings; let items = this.state.systemMidiBindings;
for (var item of items) { 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( result.push(
<tr key={item.instanceId} > <tr key={item.instanceId} >
<td className={classes.nameTd}> <td className={classes.nameTd}>
@@ -268,8 +294,7 @@ export const SystemMidiBindingDialog =
<td className={classes.bindingTd}> <td className={classes.bindingTd}>
<SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding} <SystemMidiBindingView instanceId={item.instanceId} midiBinding={item.midiBinding}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => { onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
if (instanceId === -2) if (instanceId === -2) {
{
this.cancelListenForControl(); this.cancelListenForControl();
} else { } else {
this.handleListenForControl(instanceId, symbol, listenForControl); this.handleListenForControl(instanceId, symbol, listenForControl);
+2 -1
View File
@@ -21,6 +21,7 @@
// converts android handling of the virtual keyboard to use pan instead of zoom. // converts android handling of the virtual keyboard to use pan instead of zoom.
import { isAndroidHosted } from './AndroidHost';
import Rectangle from './Rectangle'; 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)); window.visualViewport?.addEventListener('resize', this.handleVisualViewportResize.bind(this));
} else { } else {
enabled = false; 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() + "%";
}
+153 -153
View File
@@ -189,14 +189,23 @@ namespace pipedal
AudioDriverHost *driverHost = nullptr; 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: public:
AlsaDriverImpl(AudioDriverHost *driverHost) AlsaDriverImpl(AudioDriverHost *driverHost)
: driverHost(driverHost) : driverHost(driverHost)
{ {
#ifdef ALSADRIVER_CONFIG_DBG midiEventMemory.resize(MAX_MIDI_EVENT * MAX_MIDI_EVENT_SIZE);
snd_output_stdio_attach(&snd_output, stdout, 0); midiEvents.resize(MAX_MIDI_EVENT);
snd_pcm_status_malloc(&snd_status); for (size_t i = 0; i < midiEvents.size(); ++i)
#endif {
midiEvents[i].buffer = midiEventMemory.data() + i * MAX_MIDI_EVENT_SIZE;
}
} }
virtual ~AlsaDriverImpl() virtual ~AlsaDriverImpl()
{ {
@@ -302,15 +311,14 @@ namespace pipedal
snd_pcm_sw_params_free(playbackSwParams); snd_pcm_sw_params_free(playbackSwParams);
playbackSwParams = nullptr; playbackSwParams = nullptr;
} }
for (auto *midiState : this->midiStates) for (auto &midiState : this->midiDevices)
{ {
if (midiState != nullptr) if (midiState)
{ {
midiState->Close(); midiState->Close();
delete midiState;
} }
} }
midiStates.resize(0); midiDevices.resize(0);
} }
std::string discover_alsa_using_apps() std::string discover_alsa_using_apps()
@@ -1234,17 +1242,25 @@ namespace pipedal
void FillOutputBuffer() void FillOutputBuffer()
{ {
validate_capture_handle();
memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize); memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize);
int retry = 0;
while (true) while (true)
{ {
auto avail = snd_pcm_avail(this->playbackHandle); auto avail = snd_pcm_avail(this->playbackHandle);
if (avail < 0) 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); int err = snd_pcm_prepare(playbackHandle);
if (err < 0) if (err < 0)
{ {
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err))); throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err)));
} }
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue; continue;
} }
if (avail == 0) if (avail == 0)
@@ -1258,9 +1274,12 @@ namespace pipedal
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err))); 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) void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
{ {
validate_capture_handle();
if (err == -EPIPE) if (err == -EPIPE)
{ {
err = snd_pcm_prepare(playback_handle); 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) << ")")); throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
} }
FillOutputBuffer(); FillOutputBuffer();
} else { }
else
{
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")")); 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) void recover_from_input_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
{ {
validate_capture_handle();
if (err == -EPIPE) if (err == -EPIPE)
{ {
@@ -1317,10 +1340,13 @@ namespace pipedal
{ {
throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err))); throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
} }
validate_capture_handle();
} }
else if (err == ESTRPIPE) else if (err == ESTRPIPE)
{ {
audioRunning = false; audioRunning = false;
validate_capture_handle();
while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN) while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
{ {
sleep(1); sleep(1);
@@ -1334,9 +1360,11 @@ namespace pipedal
} }
} }
audioRunning = true; audioRunning = true;
validate_capture_handle();
}
} else { else
{
throw std::runtime_error(SS("Can't restart audio: " << snd_strerror(err))); throw std::runtime_error(SS("Can't restart audio: " << snd_strerror(err)));
} }
} }
@@ -1359,9 +1387,10 @@ namespace pipedal
// transcode to jack format. // transcode to jack format.
// expand running status if neccessary. // expand running status if neccessary.
// deal with regular and sysex messages split across // deal with regular and sysex messages split across
// buffer boundaries. // buffer boundaries (but discard them)
snd_pcm_sframes_t framesRead; snd_pcm_sframes_t framesRead;
auto state = snd_pcm_state(handle); auto state = snd_pcm_state(handle);
auto frame_bytes = this->captureFrameSize; auto frame_bytes = this->captureFrameSize;
do do
@@ -1384,6 +1413,18 @@ namespace pipedal
return framesRead; 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 WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
{ {
long framesRead; long framesRead;
@@ -1448,25 +1489,31 @@ namespace pipedal
cpuUse.SetStartTime(cpuUse.Now()); cpuUse.SetStartTime(cpuUse.Now());
while (true) while (true)
{ {
validate_capture_handle();
cpuUse.UpdateCpuUse(); cpuUse.UpdateCpuUse();
if (terminateAudio()) if (terminateAudio())
{ {
break; break;
} }
this->midiEventCount = 0;
// snd_pcm_wait(captureHandle, 1); // snd_pcm_wait(captureHandle, 1);
ssize_t framesToRead = bufferSize; ssize_t framesToRead = bufferSize;
ssize_t framesRead = 0; ssize_t framesRead = 0;
bool xrun = false; bool xrun = false;
validate_capture_handle();
while (framesToRead != 0) while (framesToRead != 0)
{ {
ReadMidiData((uint32_t)framesRead);
ssize_t thisTime = framesToRead; ssize_t thisTime = framesToRead;
ssize_t nFrames; ssize_t nFrames;
if ((nFrames = ReadBuffer( if ((nFrames = ReadBuffer(
captureHandle, captureHandle,
this->rawCaptureBuffer.data() + this->captureFrameSize * framesRead, this->rawCaptureBuffer.data() + this->captureFrameSize * framesRead,
bufferSize)) < 0) framesToRead)) < 0)
{ {
this->driverHost->OnUnderrun(); this->driverHost->OnUnderrun();
recover_from_input_underrun(captureHandle, playbackHandle, nFrames); recover_from_input_underrun(captureHandle, playbackHandle, nFrames);
@@ -1476,6 +1523,8 @@ namespace pipedal
framesRead += nFrames; framesRead += nFrames;
framesToRead -= nFrames; framesToRead -= nFrames;
} }
validate_capture_handle();
if (xrun) if (xrun)
{ {
continue; continue;
@@ -1618,11 +1667,16 @@ namespace pipedal
Lv2Log::debug("Audio thread joined."); 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 MIDI_BUFFER_SIZE = 16 * 1024;
static constexpr size_t MAX_MIDI_EVENT = 4 * 1024; static constexpr size_t MAX_MIDI_EVENT = 4 * 1024;
size_t midiEventCount = 0;
std::vector<MidiEvent> midiEvents;
std::vector<uint8_t> midiEventMemory;
public: public:
class MidiState class AlsaMidiDeviceImpl
{ {
private: private:
snd_rawmidi_t *hIn = nullptr; snd_rawmidi_t *hIn = nullptr;
@@ -1637,15 +1691,12 @@ namespace pipedal
size_t data0 = 0; size_t data0 = 0;
size_t data1 = 0; size_t data1 = 0;
size_t eventCount = 0; bool inputProcessingSysex = false;
MidiEvent events[MAX_MIDI_EVENT]; size_t inputSysexBufferCount = 0;
size_t bufferCount = 0; std::vector<uint8_t> inputSysexBuffer;
uint8_t buffer[MIDI_BUFFER_SIZE];
uint8_t readBuffer[1024]; uint8_t readBuffer[1024];
ssize_t sysexStartIndex = -1;
void checkError(int result, const char *message) void checkError(int result, const char *message)
{ {
if (result < 0) if (result < 0)
@@ -1655,12 +1706,16 @@ namespace pipedal
} }
public: public:
AlsaMidiDeviceImpl()
{
inputSysexBuffer.resize(1024);
}
void Open(const AlsaMidiDeviceInfo &device) void Open(const AlsaMidiDeviceInfo &device)
{ {
bufferCount = 0;
eventCount = 0;
sysexStartIndex = -1;
runningStatus = 0; runningStatus = 0;
inputProcessingSysex = false;
inputSysexBufferCount = 0;
dataIndex = 0; dataIndex = 0;
dataLength = 0; dataLength = 0;
@@ -1701,73 +1756,29 @@ namespace pipedal
return sDataLength[cc >> 4]; 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) void MidiPut(uint8_t cc, uint8_t d0, uint8_t d1)
{ {
if (cc == 0) if (cc == 0)
return; return;
// check for overrun. // check for overrun.
if (bufferCount + 1 + dataLength >= sizeof(buffer)) if (inputEventBufferIndex >= pInputEventBuffer->size())
{
bufferCount = sizeof(buffer);
return;
}
if (eventCount >= MAX_MIDI_EVENT)
{ {
return; return;
} }
auto *event = &(this->events[eventCount++]); auto &event = (*pInputEventBuffer)[inputEventBufferIndex];
event->time = 0; event.time = inputSampleFrame;
event->buffer = &buffer[bufferCount]; event.size = dataLength + 1;
event->size = dataLength + 1; assert(dataLength + 1 <= MAX_MIDI_EVENT_SIZE);
event.buffer[0] = cc;
buffer[bufferCount++] = cc; event.buffer[1] = d0;
if (dataLength >= 1) event.buffer[2] = d1;
{ ++inputEventBufferIndex;
buffer[bufferCount++] = d0;
if (dataLength >= 2)
{
buffer[bufferCount++] = d1;
}
}
} }
void FillBuffer() void FillInputBuffer()
{ {
while (true) while (true)
{ {
@@ -1778,23 +1789,42 @@ namespace pipedal
{ {
checkError(nRead, SS(this->deviceName << "MIDI event read failed. (" << snd_strerror(nRead)).c_str()); 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() void FlushSysex()
{ {
if (sysexStartIndex != -1) if (inputProcessingSysex)
{ {
if (this->eventCount != MAX_MIDI_EVENT) // just discard it. :-/
{ // if (this->eventCount != MAX_MIDI_EVENT)
auto *event = &(events[eventCount++]); // {
event->size = this->bufferCount - sysexStartIndex; // auto *event = &(events[eventCount++]);
event->buffer = &(this->buffer[this->sysexStartIndex]); // event->size = this->bufferCount - sysexStartIndex;
event->time = 0; // event->buffer = &(this->buffer[this->sysexStartIndex]);
} // event->time = 0;
sysexStartIndex = -1; // }
// sysexStartIndex = -1;
} }
inputProcessingSysex = false;
} }
int GetSystemCommonLength(uint8_t cc) int GetSystemCommonLength(uint8_t cc)
@@ -1802,7 +1832,7 @@ namespace pipedal
static int sizes[] = {-1, 1, 2, 1, -1, -1, 0, 0}; static int sizes[] = {-1, 1, 2, 1, -1, -1, 0, 0};
return sizes[(cc >> 4) & 0x07]; 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) for (ssize_t i = 0; i < nRead; ++i)
{ {
@@ -1814,13 +1844,10 @@ namespace pipedal
{ {
if (v == 0xF0) if (v == 0xF0)
{ {
if (bufferCount == sizeof(buffer)) inputProcessingSysex = true;
{ inputSysexBufferCount = 0;
break;
}
sysexStartIndex = bufferCount;
buffer[bufferCount++] = 0xF0; inputSysexBuffer[inputSysexBufferCount++] = 0xF0;
runningStatus = 0; // discard subsequent data. runningStatus = 0; // discard subsequent data.
dataLength = -2; // indefinitely. dataLength = -2; // indefinitely.
@@ -1863,11 +1890,11 @@ namespace pipedal
} }
else else
{ {
if (sysexStartIndex != -1) if (inputProcessingSysex)
{ {
if (bufferCount != sizeof(buffer)) if (inputSysexBufferCount != inputSysexBuffer.size())
{ {
buffer[bufferCount++] = v; inputSysexBuffer[inputSysexBufferCount++] = v;
} }
} }
else else
@@ -1888,7 +1915,7 @@ namespace pipedal
} }
} }
} }
if (dataIndex == dataLength) if (dataIndex == dataLength && dataLength >= 0 && runningStatus != 0)
{ {
MidiPut(runningStatus, data0, data1); MidiPut(runningStatus, data0, data1);
dataIndex = 0; 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) void OpenMidi(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{ {
const auto &devices = channelSelection.GetInputMidiDevices(); const auto &devices = channelSelection.GetInputMidiDevices();
midiStates.reserve(devices.size()); midiDevices.reserve(devices.size());
for (size_t i = 0; i < devices.size(); ++i) for (size_t i = 0; i < devices.size(); ++i)
{ {
const auto &device = devices[i]; const auto &device = devices[i];
MidiState *midiState = nullptr; auto midiDevice = std::make_unique<AlsaMidiDeviceImpl>();
try midiDevice->Open(device);
{ midiDevices.push_back(std::move(midiDevice));
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();
} }
} }
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); } 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]; 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 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]; return activePlaybackBuffers[channel];
} }
@@ -2158,7 +2155,8 @@ namespace pipedal
throw PiPedalStateException("Assert failed."); 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; MidiEvent e;
m.GetMidiInputEvent(&e, event); m.GetMidiInputEvent(&e, event);
@@ -2168,6 +2166,7 @@ namespace pipedal
AlsaAssert(message[i] == e.buffer[i]); AlsaAssert(message[i] == e.buffer[i]);
} }
} }
#endif
void AlsaDriverImpl::TestFormatEncodeDecode(snd_pcm_format_t captureFormat) void AlsaDriverImpl::TestFormatEncodeDecode(snd_pcm_format_t captureFormat)
{ {
@@ -2243,8 +2242,8 @@ namespace pipedal
} }
void MidiDecoderTest() void MidiDecoderTest()
{ {
#ifdef JUNK
AlsaDriverImpl::MidiState midiState; AlsaDriverImpl::AlsaMidiDeviceImpl midiState;
MidiEvent event; MidiEvent event;
@@ -2252,7 +2251,7 @@ namespace pipedal
{ {
static uint8_t m0[] = {0x80, 0x1, 0x2, 0x3, 0x4, 0x5}; static uint8_t m0[] = {0x80, 0x1, 0x2, 0x3, 0x4, 0x5};
midiState.NextEventBuffer(); midiState.NextEventBuffer();
midiState.WriteBuffer(m0, sizeof(m0)); midiState.ProcessInputBuffer(m0, sizeof(m0));
AlsaAssert(midiState.GetMidiInputEventCount() == 2); AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0)); AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
@@ -2261,7 +2260,7 @@ namespace pipedal
static uint8_t m1[] = {0x06, 0xC0, 0x1, 0x2}; static uint8_t m1[] = {0x06, 0xC0, 0x1, 0x2};
midiState.NextEventBuffer(); midiState.NextEventBuffer();
midiState.WriteBuffer(m1, sizeof(m1)); midiState.ProcessInputBuffer(m1, sizeof(m1));
AlsaAssert(midiState.GetMidiInputEventCount() == 3); AlsaAssert(midiState.GetMidiInputEventCount() == 3);
ExpectEvent(midiState, 0, {0x80, 0x05, 0x06}); ExpectEvent(midiState, 0, {0x80, 0x05, 0x06});
ExpectEvent(midiState, 1, {0xC0, 0x1}); ExpectEvent(midiState, 1, {0xC0, 0x1});
@@ -2272,7 +2271,7 @@ namespace pipedal
{ {
static uint8_t m0[] = {0xF0, 0x76, 0xF7, 0xA}; static uint8_t m0[] = {0xF0, 0x76, 0xF7, 0xA};
midiState.NextEventBuffer(); midiState.NextEventBuffer();
midiState.WriteBuffer(m0, 4); midiState.ProcessInputBuffer(m0, 4);
AlsaAssert(midiState.GetMidiInputEventCount() == 2); AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0)); AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
AlsaAssert(event.size == 2); AlsaAssert(event.size == 2);
@@ -2284,11 +2283,11 @@ namespace pipedal
{ {
static uint8_t m0[] = {0xF0, 0x76, 0x3B}; static uint8_t m0[] = {0xF0, 0x76, 0x3B};
midiState.NextEventBuffer(); midiState.NextEventBuffer();
midiState.WriteBuffer(m0, sizeof(m0)); midiState.ProcessInputBuffer(m0, sizeof(m0));
AlsaAssert(midiState.GetMidiInputEventCount() == 0); AlsaAssert(midiState.GetMidiInputEventCount() == 0);
static uint8_t m1[] = {0x77, 0xF7}; static uint8_t m1[] = {0x77, 0xF7};
midiState.NextEventBuffer(); midiState.NextEventBuffer();
midiState.WriteBuffer(m1, sizeof(m1)); midiState.ProcessInputBuffer(m1, sizeof(m1));
AlsaAssert(midiState.GetMidiInputEventCount() == 2); AlsaAssert(midiState.GetMidiInputEventCount() == 2);
AlsaAssert(midiState.GetMidiInputEvent(&event, 0)); AlsaAssert(midiState.GetMidiInputEvent(&event, 0));
@@ -2298,5 +2297,6 @@ namespace pipedal
AlsaAssert(event.buffer[2] == 0x3B); AlsaAssert(event.buffer[2] == 0x3B);
AlsaAssert(event.buffer[3] == 0x77); AlsaAssert(event.buffer[3] == 0x77);
} }
#endif
} }
} // namespace } // namespace
+6 -12
View File
@@ -38,11 +38,9 @@ namespace pipedal {
struct MidiEvent struct MidiEvent
{ {
/* BINARY COMPATIBLE WITH jack_midi_event_t;! (in case we ever want to resurrect Jack support) */ uint32_t time; /**< Sample frame at which event is valid */
uint32_t time; /**< Sample index at which event is valid */ uint32_t size; /**< Number of bytes of data in \a buffer */
size_t size; /**< Number of bytes of data in \a buffer */
uint8_t *buffer; /**< Raw MIDI data */ uint8_t *buffer; /**< Raw MIDI data */
}; };
@@ -65,19 +63,15 @@ namespace pipedal {
virtual uint32_t GetSampleRate() = 0; virtual uint32_t GetSampleRate() = 0;
virtual size_t MidiInputBufferCount() const = 0; virtual size_t GetMidiInputEventCount() = 0;
virtual void*GetMidiInputBuffer(size_t channel, size_t nFrames) = 0; virtual MidiEvent*GetMidiEvents() = 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 InputBufferCount() const = 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 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; virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0;
+212 -103
View File
@@ -291,6 +291,10 @@ public:
bool SystemMidiBinding::IsTriggered(const MidiEvent &event) bool SystemMidiBinding::IsTriggered(const MidiEvent &event)
{ {
if (!IsMatch(event))
{
return false;
}
switch (currentBinding.bindingType()) switch (currentBinding.bindingType())
{ {
case BINDING_TYPE_NOTE: case BINDING_TYPE_NOTE:
@@ -374,6 +378,9 @@ private:
uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE]; uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE];
size_t deferredMidiMessageCount = 0; size_t deferredMidiMessageCount = 0;
bool midiProgramChangePending = false; bool midiProgramChangePending = false;
bool midiSnapshotRequestPending = false;
int64_t snapshotRequestId = 0;
int selectedBank = -1; int selectedBank = -1;
int64_t midiProgramChangeId = 0; int64_t midiProgramChangeId = 0;
@@ -462,8 +469,18 @@ private:
HostRingBufferReader hostReader; HostRingBufferReader hostReader;
HostRingBufferWriter hostWriter; HostRingBufferWriter hostWriter;
SystemMidiBinding nextMidiBinding; SystemMidiBinding prevBankMidiBinding;
SystemMidiBinding prevMidiBinding; SystemMidiBinding nextBankMidiBinding;
SystemMidiBinding nextPresetMidiBinding;
SystemMidiBinding prevPresetMidiBinding;
SystemMidiBinding snapshot1MidiBinding;
SystemMidiBinding snapshot2MidiBinding;
SystemMidiBinding snapshot3MidiBinding;
SystemMidiBinding snapshot4MidiBinding;
SystemMidiBinding snapshot5MidiBinding;
SystemMidiBinding snapshot6MidiBinding;
SystemMidiBinding startHotspotMidiBinding; SystemMidiBinding startHotspotMidiBinding;
SystemMidiBinding stopHotspotMidiBinding; SystemMidiBinding stopHotspotMidiBinding;
SystemMidiBinding rebootMidiBinding; SystemMidiBinding rebootMidiBinding;
@@ -478,8 +495,6 @@ private:
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue. std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr; Lv2Pedalboard *realtimeActivePedalboard = nullptr;
std::vector<uint8_t *> midiLv2Buffers;
uint32_t sampleRate = 0; uint32_t sampleRate = 0;
uint64_t currentSample = 0; uint64_t currentSample = 0;
@@ -558,11 +573,6 @@ private:
this->inputRingBuffer.reset(); this->inputRingBuffer.reset();
this->outputRingBuffer.reset(); this->outputRingBuffer.reset();
for (size_t i = 0; i < midiLv2Buffers.size(); ++i)
{
delete[] midiLv2Buffers[i];
}
midiLv2Buffers.resize(0);
audioDriver = nullptr; audioDriver = nullptr;
} }
@@ -579,7 +589,7 @@ private:
{ {
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i) for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i)
{ {
float *out = (float *)audioDriver->GetOutputBuffer(i, nframes); float *out = (float *)audioDriver->GetOutputBuffer(i);
if (out) if (out)
{ {
ZeroBuffer(out, nframes); ZeroBuffer(out, nframes);
@@ -774,6 +784,16 @@ private:
} }
break; break;
} }
case RingBufferCommand::AckMidiSnapshotRequest:
{
int64_t requestId;
realtimeReader.readComplete(&requestId);
if (requestId == this->snapshotRequestId)
{
this->midiSnapshotRequestPending = false;
}
break;
}
case RingBufferCommand::LoadSnapshot: case RingBufferCommand::LoadSnapshot:
{ {
IndexedSnapshot *snapshot; IndexedSnapshot *snapshot;
@@ -851,6 +871,10 @@ private:
{ {
hostWriter.AckMidiProgramRequest(requestId); hostWriter.AckMidiProgramRequest(requestId);
} }
virtual void AckSnapshotRequest(uint64_t snapshotRequestId)
{
hostWriter.AckMidiSnapshotRequest(snapshotRequestId);
}
void OnMidiValueChanged(uint64_t instanceId, int controlIndex, float value) 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) 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); this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
if (listenForMidiEvent) if (listenForMidiEvent)
@@ -886,50 +910,14 @@ private:
return true; return true;
} }
void ProcessMidiInput() void OnSnapshotTriggered(int snapshotIndex)
{ {
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids); // midiProgramChangePending = true;
this->midiSnapshotRequestPending = true;
size_t midiInputBufferCount = audioDriver->MidiInputBufferCount(); this->realtimeWriter.OnRealtimeMidiSnapshotRequest(snapshotIndex, ++snapshotRequestId);
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; void ProcessMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
}
}
for (size_t frame = 0; frame < n; ++frame)
{
if (audioDriver->GetMidiInputEvent(&event, portBuffer, frame))
{ {
uint8_t midiCommand = (uint8_t)(event.buffer[0] & 0xF0); uint8_t midiCommand = (uint8_t)(event.buffer[0] & 0xF0);
if (midiCommand == 0xC0) // midi program change. if (midiCommand == 0xC0) // midi program change.
@@ -943,69 +931,147 @@ private:
{ {
this->selectedBank = event.buffer[2]; this->selectedBank = event.buffer[2];
} }
else if (this->nextMidiBinding.IsMatch(event)) else if (this->nextBankMidiBinding.IsTriggered(event))
{ {
if (nextMidiBinding.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; midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), 1); this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), 1);
} }
} else if (prevPresetMidiBinding.IsTriggered(event))
else if (this->prevMidiBinding.IsMatch(event))
{
if (prevMidiBinding.IsTriggered(event))
{ {
this->deferredMidiMessageCount = 0; // we can discard previous control changes.
midiProgramChangePending = true; midiProgramChangePending = true;
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1); this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1);
} }
}
else if (this->shutdownMidiBinding.IsMatch(event)) else if (shutdownMidiBinding.IsTriggered(event))
{
if (shutdownMidiBinding.IsTriggered(event))
{ {
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown); this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown);
} }
}
else if (this->rebootMidiBinding.IsMatch(event))
{
if (rebootMidiBinding.IsTriggered(event)) if (rebootMidiBinding.IsTriggered(event))
{ {
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot); this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot);
} }
}
else if (this->startHotspotMidiBinding.IsMatch(event))
{
if (startHotspotMidiBinding.IsTriggered(event)) if (startHotspotMidiBinding.IsTriggered(event))
{ {
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot); this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot);
} }
}
else if (this->stopHotspotMidiBinding.IsMatch(event))
{
if (stopHotspotMidiBinding.IsTriggered(event)) if (stopHotspotMidiBinding.IsTriggered(event))
{ {
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot); this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot);
} }
}
else if (midiProgramChangePending) else if (midiProgramChangePending)
{ {
// defer the message for processing after the program change has completed. // 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) 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; this->deferredMidiMessages[deferredMidiMessageCount++] = (uint8_t)event.size;
for (size_t i = 0; i < event.size; ++i) for (size_t i = 0; i < event.size; ++i)
{ {
this->deferredMidiMessages[deferredMidiMessageCount++] = event.buffer[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 else
{ {
onMidiEvent(eventBufferWriter, iterator, event); 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();
ProcessDeferredMidiMessages(eventBufferWriter, iterator);
{
size_t n = audioDriver->GetMidiInputEventCount();
MidiEvent *events = audioDriver->GetMidiEvents();
for (size_t i = 0; i < n; ++i)
{
ProcessMidiEvent(eventBufferWriter, iterator, events[i]);
} }
} }
} }
@@ -1062,7 +1128,7 @@ private:
bool buffersValid = true; bool buffersValid = true;
for (int i = 0; i < audioDriver->InputBufferCount(); ++i) for (int i = 0; i < audioDriver->InputBufferCount(); ++i)
{ {
float *input = (float *)audioDriver->GetInputBuffer(i, nframes); float *input = (float *)audioDriver->GetInputBuffer(i);
if (input == nullptr) if (input == nullptr)
{ {
buffersValid = false; buffersValid = false;
@@ -1074,7 +1140,7 @@ private:
for (int i = 0; i < audioDriver->OutputBufferCount(); ++i) for (int i = 0; i < audioDriver->OutputBufferCount(); ++i)
{ {
float *output = audioDriver->GetOutputBuffer(i, nframes); float *output = audioDriver->GetOutputBuffer(i);
if (output == nullptr) if (output == nullptr)
{ {
buffersValid = false; buffersValid = false;
@@ -1393,7 +1459,7 @@ public:
{ {
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body; LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value); this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId); //this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
} }
} }
} }
@@ -1449,12 +1515,27 @@ public:
hostReader.read(&request); hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request); pNotifyCallbacks->OnNotifyNextMidiProgram(request);
} }
else if (command == RingBufferCommand::NextMidiBank)
{
RealtimeNextMidiProgramRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiBank(request);
}
else if (command == RingBufferCommand::RealtimeMidiEvent) else if (command == RingBufferCommand::RealtimeMidiEvent)
{ {
RealtimeMidiEventRequest request; RealtimeMidiEventRequest request;
hostReader.read(&request); hostReader.read(&request);
pNotifyCallbacks->OnNotifyMidiRealtimeEvent(request.eventType); 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) else if (command == RingBufferCommand::Lv2ErrorMessage)
{ {
size_t size; size_t size;
@@ -1579,12 +1660,6 @@ 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());
for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i)
{
midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE);
}
active = true; active = true;
audioStopped = false; audioStopped = false;
audioDriver->Activate(); audioDriver->Activate();
@@ -1855,7 +1930,7 @@ public:
} }
private: private:
virtual bool UpdatePluginStates(Pedalboard &pedalboard) override; //virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override; virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
class RestartThread class RestartThread
@@ -2040,13 +2115,21 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
for (auto i = bindings.begin(); i != bindings.end(); ++i) 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") else if (i->symbol() == "prevProgram")
{ {
this->prevMidiBinding.SetBinding(*i); this->prevPresetMidiBinding.SetBinding(*i);
} }
else if (i->symbol() == "startHotspot") else if (i->symbol() == "startHotspot")
{ {
@@ -2064,6 +2147,30 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
{ {
this->shutdownMidiBinding.SetBinding(*i); 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 else
{ {
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol())); Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
@@ -2076,22 +2183,24 @@ AudioHost *AudioHost::CreateInstance(IHost *pHost)
return new AudioHostImpl(pHost); return new AudioHostImpl(pHost);
} }
bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard) // Removed because any updates to state have to be sent to clients as well,
{ // so this functionality was moved to PiPedalModel::SyncLv2States();
bool changed = false; // bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard)
auto pedalboardItems = pedalboard.GetAllPlugins(); // {
for (PedalboardItem *item : pedalboardItems) // bool changed = false;
{ // auto pedalboardItems = pedalboard.GetAllPlugins();
if (!item->isSplit()) // for (PedalboardItem *item : pedalboardItems)
{ // {
if (UpdatePluginState(*item)) // if (!item->isSplit())
{ // {
changed = true; // if (UpdatePluginState(*item))
} // {
} // changed = true;
} // }
return true; // }
} // }
// return changed;
// }
void AudioHostImpl::OnNotifyPathPatchPropertyReceived( void AudioHostImpl::OnNotifyPathPatchPropertyReceived(
int64_t instanceId, int64_t instanceId,
+5 -1
View File
@@ -166,8 +166,10 @@ namespace pipedal
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) = 0; virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) = 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 OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0; virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
}; };
class JackHostStatus class JackHostStatus
@@ -206,7 +208,7 @@ namespace pipedal
virtual void SetListenForMidiEvent(bool listen) = 0; virtual void SetListenForMidiEvent(bool listen) = 0;
virtual void SetListenForAtomOutput(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 bool UpdatePluginState(PedalboardItem &pedalboardItem) = 0;
virtual std::string AtomToJson(const LV2_Atom *atom) = 0; virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
@@ -235,6 +237,8 @@ namespace pipedal
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) = 0; virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) = 0;
virtual void AckMidiProgramRequest(uint64_t requestId) = 0; virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
virtual void AckSnapshotRequest(uint64_t snapshotRequestId) = 0;
virtual JackHostStatus getJackStatus() = 0; virtual JackHostStatus getJackStatus() = 0;
+1 -1
View File
@@ -113,7 +113,7 @@ add_compile_definitions(DEBIAN_ARCHITECTURE="${DEBIAN_ARCHITECTURE}")
if (CMAKE_BUILD_TYPE MATCHES Debug) if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build") 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) 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() endif()
+10 -114
View File
@@ -150,6 +150,14 @@ namespace pipedal
} }
} }
virtual size_t GetMidiInputEventCount() override {
return 0;
}
virtual MidiEvent*GetMidiEvents() {
return nullptr;
}
JackChannelSelection channelSelection; JackChannelSelection channelSelection;
bool open = false; bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
@@ -166,7 +174,6 @@ namespace pipedal
open = true; open = true;
try try
{ {
OpenMidi(jackServerSettings, channelSelection);
OpenAudio(jackServerSettings, channelSelection); OpenAudio(jackServerSettings, channelSelection);
} }
catch (const std::exception &e) catch (const std::exception &e)
@@ -348,128 +355,17 @@ namespace pipedal
static constexpr size_t MAX_MIDI_EVENT = 4 * 1024; static constexpr size_t MAX_MIDI_EVENT = 4 * 1024;
public: 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 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]; return activeCaptureBuffers[channel];
} }
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); } 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]; return activePlaybackBuffers[channel];
} }
+1 -1
View File
@@ -37,7 +37,7 @@ using namespace dbus::networkmanager;
namespace pipedal::impl namespace pipedal::impl
{ {
constexpr std::string PIPEDAL_HOTSPOT_NAME = "PiPedal Hotspot"; #define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
class HotspotManagerImpl : public HotspotManager class HotspotManagerImpl : public HotspotManager
{ {
+2 -2
View File
@@ -358,11 +358,11 @@ public:
for (size_t i = 0; i < inputs; ++i) 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) 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) 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) void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
{ {
// a sent PATCH_Set, or an explicit state changed notification. // 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); 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) void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
{ {
// one or more received PATCH_Sets, which MAY change the state. // 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); PedalboardItem *item = pedalboard.GetItem(instanceId);
if (item != nullptr) if (item != nullptr)
{ {
if (!audioHost) {
return;
}
bool changed = this->audioHost->UpdatePluginState(*item); bool changed = this->audioHost->UpdatePluginState(*item);
if (changed) if (changed)
{ {
@@ -371,11 +375,8 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
item->stateUpdateCount(item->stateUpdateCount() + 1); item->stateUpdateCount(item->stateUpdateCount() + 1);
Lv2PluginState newState = item->lv2State(); Lv2PluginState newState = item->lv2State();
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t) FireLv2StateChanged(instanceId,newState);
{
subscriber->OnLv2StateChanged(instanceId, newState);
}
} }
} }
} }
@@ -509,6 +510,8 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
} }
} }
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot) void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
@@ -723,12 +726,43 @@ void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard)
#endif #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) void PiPedalModel::SaveCurrentPreset(int64_t clientId)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
UpdateVst3Settings(this->pedalboard); UpdateVst3Settings(this->pedalboard);
this->audioHost->UpdatePluginStates(this->pedalboard); SyncLv2State();
storage.SaveCurrentPreset(this->pedalboard); storage.SaveCurrentPreset(this->pedalboard);
this->SetPresetChanged(clientId, false); 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}; std::lock_guard<std::recursive_mutex> guard{mutex};
SyncLv2State();
auto pedalboard = this->pedalboard; auto pedalboard = this->pedalboard;
UpdateVst3Settings(pedalboard); UpdateVst3Settings(pedalboard);
pedalboard.name(name); pedalboard.name(name);
@@ -880,7 +915,7 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
try try
{ {
if (request.direction < 0) if (request.direction >= 0)
{ {
NextPreset(); 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) void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -1953,16 +2029,16 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
atom_object atomObject{atomValue}; atom_object atomObject{atomValue};
PedalboardItem::PropertyMap &properties = item->PatchProperties(); PedalboardItem::PropertyMap &properties = item->PatchProperties();
if (properties.contains(propertyUri)) if (properties.contains(propertyUri) && properties[propertyUri] == atomObject)
{ {
if (properties[propertyUri] == atomObject) // do noting.
{ } else {
return;
}
}
properties[propertyUri] = std::move(atomObject); properties[propertyUri] = std::move(atomObject);
} }
OnNotifyMaybeLv2StateChanged(instanceId);
}
bool hasAtomJson = false; bool hasAtomJson = false;
std::string atomJson; std::string atomJson;
@@ -2509,6 +2585,7 @@ void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnecte
FireNetworkChanged(); FireNetworkChanged();
} }
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
{ {
try try
+5 -2
View File
@@ -50,6 +50,7 @@ namespace pipedal
class Lv2PluginChangeMonitor; class Lv2PluginChangeMonitor;
class Updater; class Updater;
class AvahiService; class AvahiService;
class Lv2PluginState;
class IPiPedalModelSubscriber class IPiPedalModelSubscriber
{ {
@@ -187,7 +188,7 @@ namespace pipedal
void FireChannelSelectionChanged(int64_t clientId); void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId); void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration); 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(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap); void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
void UpdateDefaults(Pedalboard *pedalboard); void UpdateDefaults(Pedalboard *pedalboard);
@@ -211,7 +212,7 @@ namespace pipedal
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId); IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
std::atomic<bool> closed = false; std::atomic<bool> closed = false;
bool SyncLv2State();
private: // IAudioHostCallbacks private: // IAudioHostCallbacks
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override; virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
virtual void OnNotifyMaybeLv2StateChanged(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 OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override; virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override; virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
void OnNotifyPathPatchPropertyReceived( void OnNotifyPathPatchPropertyReceived(
int64_t instanceId, int64_t instanceId,
@@ -229,6 +231,7 @@ namespace pipedal
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override; virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) 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; virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) override;
PostHandle networkChangingDelayHandle = 0; PostHandle networkChangingDelayHandle = 0;
+10 -1
View File
@@ -23,7 +23,16 @@ namespace pipedal {
Shutdown, Shutdown,
Reboot, Reboot,
StartHotspot, StartHotspot,
StopHotspot StopHotspot,
NextBank,
PrevBank,
Snapshot1,
Snapshot2,
Snapshot3,
Snapshot4,
Snapshot5,
Snapshot6,
}; };
}; };
+39 -25
View File
@@ -27,8 +27,6 @@
#include "RealtimeMidiEventType.hpp" #include "RealtimeMidiEventType.hpp"
#include <chrono> #include <chrono>
namespace pipedal namespace pipedal
{ {
class IndexedSnapshot; class IndexedSnapshot;
@@ -65,8 +63,10 @@ namespace pipedal
MidiProgramChange, // program change requested via midi. MidiProgramChange, // program change requested via midi.
AckMidiProgramChange, AckMidiProgramChange,
AckMidiSnapshotRequest,
NextMidiProgram, NextMidiProgram,
NextMidiBank,
Lv2StateChanged, Lv2StateChanged,
MaybeLv2StateChanged, MaybeLv2StateChanged,
@@ -75,28 +75,36 @@ namespace pipedal
Lv2ErrorMessage, Lv2ErrorMessage,
RealtimeMidiEvent, RealtimeMidiEvent,
RealtimeMidiSnapshotRequest,
SendPathPropertyBuffer, SendPathPropertyBuffer,
}; };
struct RealtimeMidiEventRequest
struct RealtimeMidiEventRequest { {
RealtimeMidiEventType eventType; RealtimeMidiEventType eventType;
}; };
struct RealtimeNextMidiProgramRequest {
struct RealtimeMidiSnapshotRequest
{
int32_t snapshotIndex;
int64_t snapshotRequestId;
};
struct RealtimeNextMidiProgramRequest
{
int64_t requestId; int64_t requestId;
int32_t direction; int32_t direction;
}; };
struct RealtimeMidiProgramRequest { struct RealtimeMidiProgramRequest
{
int64_t requestId; int64_t requestId;
int8_t bank; int8_t bank;
uint8_t program; uint8_t program;
}; };
class RealtimeMonitorPortSubscription class RealtimeMonitorPortSubscription
{ {
public: public:
@@ -169,7 +177,6 @@ namespace pipedal
float value; float value;
}; };
class SetControlValueBody class SetControlValueBody
{ {
public: public:
@@ -215,7 +222,8 @@ namespace pipedal
void Reset() { ringBuffer->reset(); } void Reset() { ringBuffer->reset(); }
// 0 -> ready. -1: timed out. -2: closing. // 0 -> ready. -1: timed out. -2: closing.
template <class Rep, class Period> template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) { RingBufferStatus wait_for(const std::chrono::duration<Rep, Period> &timeout)
{
return ringBuffer->readWait_for(timeout); return ringBuffer->readWait_for(timeout);
} }
@@ -230,7 +238,8 @@ namespace pipedal
return ringBuffer->readWait_until(size, time_point); return ringBuffer->readWait_until(size, time_point);
} }
bool wait() { bool wait()
{
return ringBuffer->readWait(); return ringBuffer->readWait();
} }
size_t readSpace() const size_t readSpace() const
@@ -254,7 +263,6 @@ namespace pipedal
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
} }
return true; return true;
} }
template <typename T> template <typename T>
void readComplete(T *output) void readComplete(T *output)
@@ -264,9 +272,6 @@ namespace pipedal
throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?");
} }
} }
}; };
template <typename T> template <typename T>
@@ -319,7 +324,6 @@ namespace pipedal
{ {
Lv2Log::error("No space in audio service ringbuffer."); Lv2Log::error("No space in audio service ringbuffer.");
return; return;
} }
} }
template <typename T> template <typename T>
@@ -333,7 +337,6 @@ namespace pipedal
{ {
Lv2Log::error("No space in audio service ringbuffer."); Lv2Log::error("No space in audio service ringbuffer.");
return; return;
} }
} }
void Lv2StateChanged(uint64_t instanceId) void Lv2StateChanged(uint64_t instanceId)
@@ -369,9 +372,11 @@ namespace pipedal
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; uint16_t msg = noteOrControl;
if (isNote) msg |= 0x100; if (isNote)
msg |= 0x100;
write(RingBufferCommand::OnMidiListen, msg); write(RingBufferCommand::OnMidiListen, msg);
} }
@@ -385,7 +390,6 @@ namespace pipedal
{ {
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) void OnRealtimeMidiEvent(RealtimeMidiEventType eventType)
@@ -393,11 +397,23 @@ namespace pipedal
RealtimeMidiEventRequest msg{eventType}; RealtimeMidiEventRequest msg{eventType};
write(RingBufferCommand::RealtimeMidiEvent, msg); write(RingBufferCommand::RealtimeMidiEvent, msg);
} }
void OnRealtimeMidiSnapshotRequest(int32_t snapshotIndex, int64_t snapshotRequestId)
{
RealtimeMidiSnapshotRequest msg{snapshotIndex, snapshotRequestId};
write(RingBufferCommand::RealtimeMidiSnapshotRequest, msg);
}
void OnNextMidiProgram(int64_t requestId, int32_t direction) void OnNextMidiProgram(int64_t requestId, int32_t direction)
{ {
RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction}; RealtimeNextMidiProgramRequest msg{requestId : requestId, direction : direction};
write(RingBufferCommand::NextMidiProgram, msg); 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) void SetControlValue(int effectIndex, int controlIndex, float value)
{ {
@@ -420,7 +436,6 @@ namespace pipedal
write(RingBufferCommand::SetOutputVolume, body); write(RingBufferCommand::SetOutputVolume, body);
} }
void FreeVuSubscriptions(RealtimeVuBuffers *configuration) void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
{ {
write(RingBufferCommand::FreeVuSubscriptions, configuration); write(RingBufferCommand::FreeVuSubscriptions, configuration);
@@ -468,6 +483,9 @@ namespace pipedal
{ {
write(RingBufferCommand::AckMidiProgramChange, requestId); write(RingBufferCommand::AckMidiProgramChange, requestId);
} }
void AckMidiSnapshotRequest(uint64_t snapshotRequestId) {
write(RingBufferCommand::AckMidiSnapshotRequest, snapshotRequestId);
}
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions) void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
{ {
@@ -531,10 +549,6 @@ namespace pipedal
: RingBufferWriter<false, true>(ringBuffer) : RingBufferWriter<false, true>(ringBuffer)
{ {
} }
}; };
} // namespace } // namespace
+1 -1
View File
@@ -295,7 +295,7 @@ namespace pipedal
} }
} }
uint64_t instanceId; 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 *GetAtomInputBuffer() { return nullptr; }
virtual uint8_t *GetAtomOutputBuffer() { 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); 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> Storage::GetSystemMidiBindings()
{ {
std::vector<MidiBinding> result; std::vector<MidiBinding> result;
@@ -1441,6 +1452,20 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
result.push_back(MidiBinding::SystemBinding("shutdown")); result.push_back(MidiBinding::SystemBinding("shutdown"));
result.push_back(MidiBinding::SystemBinding("reboot")); 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; return result;
} }
catch (const std::exception&e) catch (const std::exception&e)
@@ -1449,8 +1474,18 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
} }
} }
result.clear(); 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("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram")); 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("stopHotspot"));
result.push_back(MidiBinding::SystemBinding("startHotspot")); result.push_back(MidiBinding::SystemBinding("startHotspot"));
result.push_back(MidiBinding::SystemBinding("shutdown")); result.push_back(MidiBinding::SystemBinding("shutdown"));
+2 -2
View File
@@ -34,12 +34,12 @@ class Copyrights {
static std::string trim(const std::string&value) static std::string trim(const std::string&value)
{ {
size_t start = 0; size_t start = 0;
while (start < value.length() && value[start] == ' ' || value[start] == '\t') while (start < value.length() && (value[start] == ' ' || value[start] == '\t'))
{ {
++start; ++start;
} }
size_t end = value.length(); 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; --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 Review current handleNotifyPatchProperty. I tink server-side support needs to be
removed, and rely on handleNotifyPathPatchPropertyChanged instead. removed, and rely on handleNotifyPathPatchPropertyChanged instead.
@@ -10,26 +6,18 @@ removed, and rely on handleNotifyPathPatchPropertyChanged instead.
Midi bindings. 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. revamp shutdown procedure for the audio thread.
write a marker on exit, and don't release write a marker on exit, and don't release
audioHost UNTIL the service thread has read the close marker. 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 -verify clean removal of dhcpcd and nm_p2p2d
X Review docs changes once we go live. X Review docs changes once we go live.
- Localisation: support non-UTF8 code pages. - Localisation: support non-UTF8 code pages.
- unicode commandline arguments. - unicode commandline arguments. (probably the hotspot name)
- review unicode filenames (this is probably ok) - review unicode filenames (this is probably ok)
- BUG: gcs when we have an animated output control - BUG: gcs when we have an animated output control