diff --git a/.vscode/launch.json b/.vscode/launch.json index 1bc3a2b..d459cd3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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}", diff --git a/.vscode/settings.json b/.vscode/settings.json index efc34fd..b305b9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -97,7 +97,8 @@ "strstream": "cpp", "p2p_i.h": "c", "p2p.h": "c", - "hash_set": "cpp" + "hash_set": "cpp", + "barrier": "cpp" }, "cSpell.words": [ "Guitarix", diff --git a/PiPedalCommon/src/CMakeLists.txt b/PiPedalCommon/src/CMakeLists.txt index f95dc77..80e2108 100644 --- a/PiPedalCommon/src/CMakeLists.txt +++ b/PiPedalCommon/src/CMakeLists.txt @@ -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. diff --git a/PiPedalCommon/src/WifiConfigSettings.cpp b/PiPedalCommon/src/WifiConfigSettings.cpp index 399a6a6..1cc1da6 100644 --- a/PiPedalCommon/src/WifiConfigSettings.cpp +++ b/PiPedalCommon/src/WifiConfigSettings.cpp @@ -680,7 +680,7 @@ void WifiConfigSettings::ParseArguments( ) { - this->valid_ = false; + this->valid_ = false ; if (argv.size() != 4) { throw invalid_argument("Invalid number of arguments."); diff --git a/react/src/AndroidHost.tsx b/react/src/AndroidHost.tsx index f62c931..10b8065 100644 --- a/react/src/AndroidHost.tsx +++ b/react/src/AndroidHost.tsx @@ -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); +} \ No newline at end of file diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index a192c9e..6a7ad4a 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -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; diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index f983fdf..d5ab736 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -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 })( (
- 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. @@ -678,6 +686,79 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
+ + {(!this.props.onboarding) && + ( +
+ + DISPLAY + + { this.handleThemeSelection(); }} > + +
+ + Color theme + + {this.model.getTheme() === ColorTheme.Dark ? "Dark" : + (this.model.getTheme() === ColorTheme.Light ? "Light" : "System")} + +
+
+ + {(canScaleWindow()) && + ( + { this.handleThemeSelection(); }} > + +
+ + Scale + + {getWindowScaleText()} + +
+
+ + ) + } + + + { + this.model.setShowStatusMonitor(!this.state.showStatusMonitor) + }} > + +
+
+
+ + Show status monitor on main screen. +
+ +
+ { this.model.setShowStatusMonitor(e.target.checked); } + } + /> +
+ +
+
+
+
+ + ) + } +
CONNECTION @@ -720,65 +801,6 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( SYSTEM - this.handleShowGovernorSettingsDialogDialog()} > - -
- - CPU Governor - - {this.state.governorSettings.governor} - - -
-
- - { this.handleThemeSelection(); }} > - -
- - Color theme - - {this.model.getTheme() === ColorTheme.Dark ? "Dark" : - (this.model.getTheme() === ColorTheme.Light ? "Light" : "System")} - -
-
- - - { - this.model.setShowStatusMonitor(!this.state.showStatusMonitor) - }} > - -
-
-
- - Show status monitor on main screen. -
- -
- { this.model.setShowStatusMonitor(e.target.checked); } - } - /> -
- -
-
-
- { this.model.enableAutoUpdate && ( 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( + +
+ + ); + } result.push( - + {item.displayName} @@ -268,8 +294,7 @@ export const SystemMidiBindingDialog = { - if (instanceId === -2) - { + if (instanceId === -2) { this.cancelListenForControl(); } else { this.handleListenForControl(instanceId, symbol, listenForControl); @@ -300,52 +325,52 @@ export const SystemMidiBindingDialog = } return ( - -
-
- - - - - - - System MIDI Bindings - - - -
-
- - - - - - - - {this.generateTable()} - - -
-
+
+
+ + + + + + + System MIDI Bindings + + +
- this.setState({ listenSnackbarOpen: false })} - message="Listening for MIDI input" - /> +
+ + + + + + + + {this.generateTable()} + + +
+
+
+ this.setState({ listenSnackbarOpen: false })} + message="Listening for MIDI input" + /> ); } diff --git a/react/src/VirtualKeyboardHandler.tsx b/react/src/VirtualKeyboardHandler.tsx index afe839e..93b75de 100644 --- a/react/src/VirtualKeyboardHandler.tsx +++ b/react/src/VirtualKeyboardHandler.tsx @@ -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; diff --git a/react/src/WindowScale.tsx b/react/src/WindowScale.tsx new file mode 100644 index 0000000..fc68daf --- /dev/null +++ b/react/src/WindowScale.tsx @@ -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() + "%"; +} \ No newline at end of file diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index e395a5c..0eacab6 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -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 midiEvents; + std::vector 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 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 *pInputEventBuffer = nullptr; + + size_t ReadMidiEvents( + std::vector &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 midiStates; + std::vector> 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(); + 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 message) +#ifdef JUNK + static void ExpectEvent(AlsaDriverImpl::AlsaMidiDeviceImpl &m, int event, const std::vector 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 diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp index 7e051b2..0d45658 100644 --- a/src/AudioDriver.hpp +++ b/src/AudioDriver.hpp @@ -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; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 488ae58..7f69178 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -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> activePedalboards; // pedalboards that have been sent to the audio queue. Lv2Pedalboard *realtimeActivePedalboard = nullptr; - std::vector 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 &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 &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); } } } diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index ca68961..8245dfe 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -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; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 25a20d4..d35728a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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") diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index 377aa8d..6194394 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -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 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]; } diff --git a/src/HotspotManager.cpp b/src/HotspotManager.cpp index bb7b6a9..b4db376 100644 --- a/src/HotspotManager.cpp +++ b/src/HotspotManager.cpp @@ -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 { diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index 58094ed..a678c23 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -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) diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 9ccd306..aa3e02d 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -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 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 lock(mutex); @@ -723,12 +726,43 @@ void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard) #endif } +void PiPedalModel::FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State) { + std::lock_guard guard{mutex}; + std::vector 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 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 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 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 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 diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 95f271e..6cdc8cf 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -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 &itemMap); void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map &itemMap); void UpdateDefaults(Pedalboard *pedalboard); @@ -211,7 +212,7 @@ namespace pipedal IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId); std::atomic 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; diff --git a/src/RealtimeMidiEventType.hpp b/src/RealtimeMidiEventType.hpp index 8867ec9..87a9a54 100644 --- a/src/RealtimeMidiEventType.hpp +++ b/src/RealtimeMidiEventType.hpp @@ -23,7 +23,16 @@ namespace pipedal { Shutdown, Reboot, StartHotspot, - StopHotspot + StopHotspot, + NextBank, + PrevBank, + Snapshot1, + Snapshot2, + Snapshot3, + Snapshot4, + Snapshot5, + Snapshot6, + }; }; \ No newline at end of file diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index f9504d0..30f0181 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -27,8 +27,6 @@ #include "RealtimeMidiEventType.hpp" #include - - 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 *ringBuffer = nullptr; + RingBuffer *ringBuffer = nullptr; public: RingBufferReader() : ringBuffer(nullptr) { } - RingBufferReader(RingBuffer *ringBuffer) + RingBufferReader(RingBuffer *ringBuffer) : ringBuffer(ringBuffer) { } void Reset() { ringBuffer->reset(); } // 0 -> ready. -1: timed out. -2: closing. - template - RingBufferStatus wait_for(const std::chrono::duration& timeout) { + template + RingBufferStatus wait_for(const std::chrono::duration &timeout) + { return ringBuffer->readWait_for(timeout); } template - RingBufferStatus wait_until(std::chrono::time_point&time_point) + RingBufferStatus wait_until(std::chrono::time_point &time_point) { return ringBuffer->readWait_until(time_point); } template - RingBufferStatus wait_until(size_t size,std::chrono::time_point&time_point) + RingBufferStatus wait_until(size_t size, std::chrono::time_point &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 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 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 @@ -290,19 +295,19 @@ namespace pipedal T value; }; - template + template class RingBufferWriter { private: - RingBuffer* ringBuffer; + RingBuffer *ringBuffer; public: RingBufferWriter() : ringBuffer(nullptr) { } - RingBufferWriter(RingBuffer*ringBuffer) + RingBufferWriter(RingBuffer *ringBuffer) : ringBuffer(ringBuffer) { } @@ -315,88 +320,99 @@ namespace pipedal // the goal: to atomically write the command and associated data. CommandBuffer 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 - 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 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 *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 HostRingBufferWriter; // cures a forward-declaration problem. - class RealtimeRingBufferWriter: public RingBufferWriter + class RealtimeRingBufferWriter : public RingBufferWriter { public: RealtimeRingBufferWriter() { } - RealtimeRingBufferWriter(RingBuffer*ringBuffer) - : RingBufferWriter (ringBuffer) + RealtimeRingBufferWriter(RingBuffer *ringBuffer) + : RingBufferWriter(ringBuffer) { } - - - }; - -} //namespace \ No newline at end of file +} // namespace \ No newline at end of file diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp index 3ae90e0..e408a0e 100644 --- a/src/SplitEffect.hpp +++ b/src/SplitEffect.hpp @@ -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; } diff --git a/src/Storage.cpp b/src/Storage.cpp index f6fc319..c0539fb 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -1418,6 +1418,17 @@ void Storage::SetSystemMidiBindings(const std::vector &bindings) writer.write(bindings); } } +static bool hasBinding(std::vector &bindings, const std::string&name) +{ + for (auto&binding: bindings) + { + if (binding.symbol() == name) + { + return true; + } + } + return false; +} std::vector Storage::GetSystemMidiBindings() { std::vector result; @@ -1441,6 +1452,20 @@ std::vector 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 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")); diff --git a/src/copyrightMain.cpp b/src/copyrightMain.cpp index 1a4d221..a9be7be 100644 --- a/src/copyrightMain.cpp +++ b/src/copyrightMain.cpp @@ -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; } diff --git a/todo.txt b/todo.txt index 6e9ecb0..e242bfd 100644 --- a/todo.txt +++ b/todo.txt @@ -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