0.9.11-rc-2
Switch from JACK to ALSA audio.
This commit is contained in:
Vendored
+39
-1
@@ -5,6 +5,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "(gdb) generic",
|
"name": "(gdb) generic",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
@@ -77,7 +78,9 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
// Resolved by CMake Tools:
|
// Resolved by CMake Tools:
|
||||||
"program": "${command:cmake.launchTargetPath}",
|
"program": "${command:cmake.launchTargetPath}",
|
||||||
"args": [ "[avahi_service]" ],
|
|
||||||
|
"args": [ "[alsa_midi_test]" ],
|
||||||
|
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"environment": [
|
"environment": [
|
||||||
@@ -102,6 +105,41 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "(gdb) pipedal_latency_test Launch",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
// Resolved by CMake Tools:
|
||||||
|
"program": "${command:cmake.launchTargetPath}",
|
||||||
|
|
||||||
|
"args": [ "--rate", "48000", "hw:M2" ],
|
||||||
|
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [
|
||||||
|
{
|
||||||
|
// add the directory where our target was built to the PATHs
|
||||||
|
// it gets resolved by CMake Tools:
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "(gdb) pipedal Launch",
|
"name": "(gdb) pipedal Launch",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
|
|||||||
+5
-3
@@ -1,10 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.16.0)
|
cmake_minimum_required(VERSION 3.16.0)
|
||||||
project(pipedal
|
project(pipedal
|
||||||
VERSION 0.9.10
|
VERSION 0.9.11
|
||||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||||
)
|
)
|
||||||
set (DISPLAY_VERSION "v0.9.10-rc-1")
|
|
||||||
|
set (DISPLAY_VERSION "v0.9.11-rc-2")
|
||||||
|
|
||||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||||
|
|
||||||
@@ -85,7 +86,8 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect for Raspberry Pi")
|
|||||||
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
|
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
|
||||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
|
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "jackd2, hostapd, dnsmasq" )
|
#set(CPACK_DEBIAN_PACKAGE_DEPENDS "jackd2, hostapd, dnsmasq" )
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "hostapd, dnsmasq" )
|
||||||
#set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
|
#set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
|
||||||
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
|
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
|
||||||
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
<img src='docs/GithubBanner.png' width="100%" />
|
<img src='docs/GithubBanner.png' width="100%" />
|
||||||
|
|
||||||
Download: <a href='docs/download.md'>v0.9.10-rc-1</a> Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
Download: <a href='docs/download.md'>v0.9.11-rc-2</a> Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||||
|
|
||||||
|
|
||||||
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
|
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
|
||||||
|
|||||||
+2
-2
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
Download the most recent Debian (.deb) package for your platform:
|
Download the most recent Debian (.deb) package for your platform:
|
||||||
|
|
||||||
- [Raspberry Pi OS (64-bit) v0.9.10-rc-1](https://github.com/rerdavies/pipedal/releases/download/v0.9.10-rc-1/pipedal_0.9.10_arm64.deb)
|
- [Raspberry Pi OS (64-bit) v0.9.11-rc-2](https://github.com/rerdavies/pipedal/releases/download/v0.9.11-rc-2/pipedal_0.9.11_arm64.deb)
|
||||||
|
|
||||||
Install the package by running
|
Install the package by running
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt update
|
sudo apt update
|
||||||
cd ~/Downloads
|
cd ~/Downloads
|
||||||
sudo apt-get install ./pipedal_0.9.10_arm64.deb
|
sudo apt-get install ./pipedal_0.9.11_arm64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md).
|
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md).
|
||||||
|
|||||||
+2
-2
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
Download the most recent Debian (.deb) package for your platform:
|
Download the most recent Debian (.deb) package for your platform:
|
||||||
|
|
||||||
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.9.10-rc-1/pipedal_0.9.10_arm64.deb) v0.9.10-rc-1
|
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.9.11-rc-2/pipedal_0.9.11_arm64.deb) v0.9.11-rc-2
|
||||||
|
|
||||||
Install the package by running
|
Install the package by running
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt update
|
sudo apt update
|
||||||
cd ~/Downloads
|
cd ~/Downloads
|
||||||
sudo apt-get install ./pipedal_0.9.10_arm64.deb
|
sudo apt-get install ./pipedal_0.9.11_arm64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
|
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<img src="GithubBanner.png" width="100%"/>
|
<img src="GithubBanner.png" width="100%"/>
|
||||||
<a href="Installing.html"><i>v0.9.10-rc-1</i></a>
|
<a href="Installing.html"><i>v0.9.11-rc-2</i></a>
|
||||||
|
|
||||||
|
|
||||||
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
|
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ add_custom_command(
|
|||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
|
||||||
|
|
||||||
DEPENDS
|
DEPENDS
|
||||||
|
src/AlsaMidiDeviceInfo.tsx
|
||||||
src/SearchControl.tsx
|
src/SearchControl.tsx
|
||||||
src/SplitControlView.tsx
|
src/SplitControlView.tsx
|
||||||
src/WifiConfigSettings.tsx
|
src/WifiConfigSettings.tsx
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AlsaMidiDeviceInfo {
|
||||||
|
deserialize(input: any) : AlsaMidiDeviceInfo{
|
||||||
|
this.name = input.name;
|
||||||
|
this.description = input.description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
static deserializeArray(input: any[]): AlsaMidiDeviceInfo[] {
|
||||||
|
let result: AlsaMidiDeviceInfo[] = [];
|
||||||
|
for (let i = 0; i < input.length; ++i) {
|
||||||
|
result[i] = new AlsaMidiDeviceInfo().deserialize(input[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
name: string = "";
|
||||||
|
description: string = "";
|
||||||
|
|
||||||
|
equals(other: AlsaMidiDeviceInfo) : boolean {
|
||||||
|
return this.name === other.name && this.description === other.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-6
@@ -18,13 +18,14 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import {PiPedalArgumentError} from './PiPedalError';
|
import {PiPedalArgumentError} from './PiPedalError';
|
||||||
|
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
|
||||||
|
|
||||||
export class JackChannelSelection {
|
export class JackChannelSelection {
|
||||||
deserialize(input: any): JackChannelSelection
|
deserialize(input: any): JackChannelSelection
|
||||||
{
|
{
|
||||||
this.inputAudioPorts = input.inputAudioPorts.slice();
|
this.inputAudioPorts = input.inputAudioPorts.slice();
|
||||||
this.outputAudioPorts = input.outputAudioPorts.slice();
|
this.outputAudioPorts = input.outputAudioPorts.slice();
|
||||||
this.inputMidiPorts = input.inputMidiPorts.slice();
|
this.inputMidiDevices = AlsaMidiDeviceInfo.deserializeArray(input.inputMidiDevices);
|
||||||
this.sampleRate = input.sampleRate;
|
this.sampleRate = input.sampleRate;
|
||||||
this.bufferSize = input.bufferSize;
|
this.bufferSize = input.bufferSize;
|
||||||
this.numberOfBuffers = input.numberOfBuffers;
|
this.numberOfBuffers = input.numberOfBuffers;
|
||||||
@@ -35,7 +36,7 @@ export class JackChannelSelection {
|
|||||||
}
|
}
|
||||||
inputAudioPorts: string[] = [];
|
inputAudioPorts: string[] = [];
|
||||||
outputAudioPorts: string[] = [];
|
outputAudioPorts: string[] = [];
|
||||||
inputMidiPorts: string[] = [];
|
inputMidiDevices: AlsaMidiDeviceInfo[] = [];
|
||||||
|
|
||||||
sampleRate: number = 48000;
|
sampleRate: number = 48000;
|
||||||
bufferSize: number = 64;
|
bufferSize: number = 64;
|
||||||
@@ -45,6 +46,7 @@ export class JackChannelSelection {
|
|||||||
let result = new JackChannelSelection();
|
let result = new JackChannelSelection();
|
||||||
result.inputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
|
result.inputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
|
||||||
result.outputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
|
result.outputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
|
||||||
|
result.inputMidiDevices = [];
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
getChannelDisplayValue(selectedChannels: string[], availableChannels: string[],isConfigValid: boolean): string
|
getChannelDisplayValue(selectedChannels: string[], availableChannels: string[],isConfigValid: boolean): string
|
||||||
@@ -97,8 +99,7 @@ export class JackConfiguration {
|
|||||||
this.maxAllowedMidiDelta = input.maxAllowedMidiDelta;
|
this.maxAllowedMidiDelta = input.maxAllowedMidiDelta;
|
||||||
this.inputAudioPorts = input.inputAudioPorts;
|
this.inputAudioPorts = input.inputAudioPorts;
|
||||||
this.outputAudioPorts = input.outputAudioPorts;
|
this.outputAudioPorts = input.outputAudioPorts;
|
||||||
this.inputMidiPorts = input.inputMidiPorts;
|
this.inputMidiDevices = AlsaMidiDeviceInfo.deserializeArray(input.inputMidiDevices);
|
||||||
this.outputMidiPorts = input.outputMidiPorts;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
isValid: boolean = false;
|
isValid: boolean = false;
|
||||||
@@ -111,8 +112,8 @@ export class JackConfiguration {
|
|||||||
|
|
||||||
inputAudioPorts: string[] = [];
|
inputAudioPorts: string[] = [];
|
||||||
outputAudioPorts: string[] = [];
|
outputAudioPorts: string[] = [];
|
||||||
inputMidiPorts: string[] = [];
|
inputMidiDevices: AlsaMidiDeviceInfo[] = [];
|
||||||
outputMidiPorts: string[] = [];
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export default class JackHostStatus {
|
|||||||
deserialize(input: any): JackHostStatus {
|
deserialize(input: any): JackHostStatus {
|
||||||
this.active = input.active;
|
this.active = input.active;
|
||||||
this.restarting = input.restarting;
|
this.restarting = input.restarting;
|
||||||
|
this.errorMessage = input.errorMessage;
|
||||||
this.underruns = input.underruns;
|
this.underruns = input.underruns;
|
||||||
this.cpuUsage = input.cpuUsage;
|
this.cpuUsage = input.cpuUsage;
|
||||||
this.msSinceLastUnderrun = input.msSinceLastUnderrun;
|
this.msSinceLastUnderrun = input.msSinceLastUnderrun;
|
||||||
@@ -71,6 +72,7 @@ export default class JackHostStatus {
|
|||||||
return this.temperaturemC >= -100000;
|
return this.temperaturemC >= -100000;
|
||||||
}
|
}
|
||||||
active: boolean = false;
|
active: boolean = false;
|
||||||
|
errorMessage: string = "";
|
||||||
restarting: boolean = false;
|
restarting: boolean = false;
|
||||||
underruns: number = 0;
|
underruns: number = 0;
|
||||||
cpuUsage: number = 0;
|
cpuUsage: number = 0;
|
||||||
@@ -136,7 +138,7 @@ export default class JackHostStatus {
|
|||||||
<Typography variant="caption" color="textSecondary">{label}</Typography>
|
<Typography variant="caption" color="textSecondary">{label}</Typography>
|
||||||
|
|
||||||
<span style={{ color: RED_COLOR }}>
|
<span style={{ color: RED_COLOR }}>
|
||||||
<Typography variant="caption" color="inherit">Stopped </Typography>
|
<Typography variant="caption" color="inherit">{status.errorMessage === "" ? "Stopped" : status.errorMessage} </Typography>
|
||||||
</span>
|
</span>
|
||||||
{
|
{
|
||||||
status.temperaturemC > -100000 &&
|
status.temperaturemC > -100000 &&
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const JackServerSettingsDialog = withStyles(styles)(
|
|||||||
}
|
}
|
||||||
if (result.sampleRate === 0) result.sampleRate = 48000;
|
if (result.sampleRate === 0) result.sampleRate = 48000;
|
||||||
if (result.bufferSize === 0) result.bufferSize = 64;
|
if (result.bufferSize === 0) result.bufferSize = 64;
|
||||||
if (result.numberOfBuffers === 0) result.numberOfBuffers = 3;
|
if (result.numberOfBuffers === 0) result.numberOfBuffers = 2;
|
||||||
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
||||||
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
|
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
|
||||||
result.valid = true;
|
result.valid = true;
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const MidiBindingDialog =
|
|||||||
hasHooks: boolean = false;
|
hasHooks: boolean = false;
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
|
this.cancelListenForControl();
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +121,6 @@ export const MidiBindingDialog =
|
|||||||
|
|
||||||
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
|
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
|
||||||
{
|
{
|
||||||
this.listenHandle = undefined; // (one-shot event)
|
|
||||||
this.cancelListenForControl();
|
this.cancelListenForControl();
|
||||||
|
|
||||||
let pedalBoard = this.model.pedalBoard.get();
|
let pedalBoard = this.model.pedalBoard.get();
|
||||||
|
|||||||
@@ -1826,12 +1826,18 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancelListenForMidiEvent(listenHandle: ListenHandle): void {
|
cancelListenForMidiEvent(listenHandle: ListenHandle): void {
|
||||||
|
let found = false;
|
||||||
for (let i = 0; i < this.midiListeners.length; ++i) {
|
for (let i = 0; i < this.midiListeners.length; ++i) {
|
||||||
if (this.midiListeners[i].handle === listenHandle._handle) {
|
if (this.midiListeners[i].handle === listenHandle._handle) {
|
||||||
this.midiListeners.splice(i, 1);
|
this.midiListeners.splice(i, 1);
|
||||||
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
console.log('cancelListenForMidiEvent: event not found.');
|
||||||
|
}
|
||||||
this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle);
|
this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle);
|
||||||
}
|
}
|
||||||
cancelListenForAtomOutput(listenHandle: ListenHandle): void {
|
cancelListenForAtomOutput(listenHandle: ListenHandle): void {
|
||||||
|
|||||||
@@ -27,38 +27,43 @@ import Dialog from '@mui/material/Dialog';
|
|||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface SelectMidiChannelsDialogProps {
|
export interface SelectMidiChannelsDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
selectedChannels: string[];
|
selectedChannels: AlsaMidiDeviceInfo[];
|
||||||
availableChannels: string[];
|
availableChannels: AlsaMidiDeviceInfo[];
|
||||||
onClose: (selectedChannels: string[] | null) => void;
|
onClose: (selectedChannels: AlsaMidiDeviceInfo[] | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChecked(selectedChannels: string[], channel: string): boolean {
|
function isChecked(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo): boolean {
|
||||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||||
if (selectedChannels[i] === channel) return true;
|
if (selectedChannels[i].equals(channel)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function addPort(availableChannels: string[], selectedChannels: string[], newChannel: string) {
|
function addPort(availableChannels: AlsaMidiDeviceInfo[], selectedChannels: AlsaMidiDeviceInfo[], newChannel: AlsaMidiDeviceInfo)
|
||||||
let result: string[] = [];
|
:AlsaMidiDeviceInfo[]
|
||||||
|
{
|
||||||
|
let result: AlsaMidiDeviceInfo[] = [];
|
||||||
for (let i = 0; i < availableChannels.length; ++i) {
|
for (let i = 0; i < availableChannels.length; ++i) {
|
||||||
let channel = availableChannels[i];
|
let channel = availableChannels[i];
|
||||||
if (isChecked(selectedChannels, channel) || channel === newChannel) {
|
if (isChecked(selectedChannels, channel) || channel.equals(newChannel)) {
|
||||||
result.push(channel);
|
result.push(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePort(selectedChannels: string[], channel: string) {
|
function removePort(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo)
|
||||||
let result: string[] = [];
|
:AlsaMidiDeviceInfo[]
|
||||||
|
{
|
||||||
|
let result: AlsaMidiDeviceInfo[] = [];
|
||||||
for (let i = 0; i < selectedChannels.length; ++i) {
|
for (let i = 0; i < selectedChannels.length; ++i) {
|
||||||
if (selectedChannels[i] !== channel) {
|
if (!selectedChannels[i].equals(channel)) {
|
||||||
result.push(selectedChannels[i]);
|
result.push(selectedChannels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +77,7 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
let toggleSelect = (value: string) => {
|
let toggleSelect = (value: AlsaMidiDeviceInfo) => {
|
||||||
if (!isChecked(currentSelection, value)) {
|
if (!isChecked(currentSelection, value)) {
|
||||||
setCurrentSelection(addPort(availableChannels, currentSelection, value));
|
setCurrentSelection(addPort(availableChannels, currentSelection, value));
|
||||||
} else {
|
} else {
|
||||||
@@ -90,15 +95,15 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
|
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
|
||||||
<DialogTitle id="simple-dialog-title">Select Channels</DialogTitle>
|
<DialogTitle id="simple-dialog-title">Select MIDI Device</DialogTitle>
|
||||||
<List>
|
<List>
|
||||||
{availableChannels.map((channel) => (
|
{availableChannels.map((channel) => (
|
||||||
<ListItem button >
|
<ListItem button >
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox checked={isChecked(currentSelection, channel)} onClick={() => toggleSelect(channel)} key={channel} />
|
<Checkbox checked={isChecked(currentSelection, channel)} onClick={() => toggleSelect(channel)} key={channel.name} />
|
||||||
}
|
}
|
||||||
label={channel}
|
label={channel.description}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import WifiConfigDialog from './WifiConfigDialog';
|
|||||||
import WifiDirectConfigDialog from './WifiDirectConfigDialog';
|
import WifiDirectConfigDialog from './WifiDirectConfigDialog';
|
||||||
import DialogEx from './DialogEx'
|
import DialogEx from './DialogEx'
|
||||||
import GovernorSettings from './GovernorSettings';
|
import GovernorSettings from './GovernorSettings';
|
||||||
|
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
|
||||||
|
|
||||||
import 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';
|
||||||
@@ -390,10 +391,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
handleSelectMidiDialogResult(channels: string[] | null): void {
|
handleSelectMidiDialogResult(channels: AlsaMidiDeviceInfo[] | null): void {
|
||||||
if (channels) {
|
if (channels) {
|
||||||
let newSelection = this.state.jackSettings.clone();
|
let newSelection: JackChannelSelection = this.state.jackSettings.clone();
|
||||||
newSelection.inputMidiPorts = channels;
|
newSelection.inputMidiDevices = channels;
|
||||||
this.model.setJackSettings(newSelection);
|
this.model.setJackSettings(newSelection);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -430,9 +431,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
|
|
||||||
midiSummary(): string {
|
midiSummary(): string {
|
||||||
let ports = this.state.jackSettings.inputMidiPorts;
|
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];
|
if (ports.length === 1) return ports[0].description;
|
||||||
return ports.length + " channels";
|
return ports.length + " channels";
|
||||||
}
|
}
|
||||||
handleShowWifiConfigDialog() {
|
handleShowWifiConfigDialog() {
|
||||||
@@ -592,7 +593,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiSelection()} >
|
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiSelection()} >
|
||||||
<SelectHoverBackground selected={false} showHover={true} />
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>Select MIDI input channels</Typography>
|
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>Select MIDI input</Typography>
|
||||||
|
|
||||||
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{this.midiSummary()}</Typography>
|
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>{this.midiSummary()}</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -767,9 +768,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
(this.state.showMidiSelectDialog) &&
|
(this.state.showMidiSelectDialog) &&
|
||||||
(
|
(
|
||||||
<SelectMidiChannelsDialog open={this.state.showMidiSelectDialog}
|
<SelectMidiChannelsDialog open={this.state.showMidiSelectDialog}
|
||||||
onClose={(selectedChannels: string[] | null) => this.handleSelectMidiDialogResult(selectedChannels)}
|
onClose={(selectedChannels: AlsaMidiDeviceInfo[] | null) => this.handleSelectMidiDialogResult(selectedChannels)}
|
||||||
selectedChannels={this.state.jackSettings.inputMidiPorts}
|
selectedChannels={this.state.jackSettings.inputMidiDevices}
|
||||||
availableChannels={this.state.jackConfiguration.inputMidiPorts}
|
availableChannels={this.state.jackConfiguration.inputMidiDevices}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -204,10 +204,16 @@ bool setJackConfiguration(JackServerSettings serverSettings)
|
|||||||
{
|
{
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
|
|
||||||
serverSettings.Write();
|
serverSettings.Write();
|
||||||
|
|
||||||
silentSysExec("/usr/bin/systemctl unmask jack");
|
silentSysExec("/usr/bin/systemctl unmask jack");
|
||||||
silentSysExec("/usr/bin/systemctl enable jack");
|
silentSysExec("/usr/bin/systemctl enable jack");
|
||||||
|
#else
|
||||||
|
// otherwise,
|
||||||
|
// the config was written before invoking admin main. but we still need to restart the service.
|
||||||
|
#endif
|
||||||
|
|
||||||
std::thread delayedRestartThread(delayedRestartProc);
|
std::thread delayedRestartThread(delayedRestartProc);
|
||||||
delayedRestartThread.detach();
|
delayedRestartThread.detach();
|
||||||
|
|||||||
+1686
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AudioDriver.hpp"
|
||||||
|
#include "JackServerSettings.hpp"
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
bool GetAlsaChannels(const JackServerSettings &jackServerSettings,
|
||||||
|
std::vector<std::string>&inputAudioPorts,
|
||||||
|
std::vector<std::string>&outputAudioPorts
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
AudioDriver* CreateAlsaDriver(AudioDriverHost*driverHost);
|
||||||
|
|
||||||
|
|
||||||
|
void MidiDecoderTest();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pch.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <mutex>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "AlsaDriver.hpp"
|
||||||
|
#include "JackDriver.hpp"
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
class AlsaTester: private AudioDriverHost {
|
||||||
|
public:
|
||||||
|
enum class TestType { Oscillator, LatencyMonitor, NullTest};
|
||||||
|
private:
|
||||||
|
AudioDriver *audioDriver = nullptr;
|
||||||
|
TestType testType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AlsaTester(TestType testType)
|
||||||
|
: testType(testType)
|
||||||
|
{
|
||||||
|
// audioDriver = CreateAlsaDriver(this);
|
||||||
|
// audioDriver = CreateJackDriver(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
~AlsaTester()
|
||||||
|
{
|
||||||
|
delete audioDriver;
|
||||||
|
delete[] inputBuffers;
|
||||||
|
delete[] outputBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool useJack = false;
|
||||||
|
|
||||||
|
void Test()
|
||||||
|
{
|
||||||
|
JackServerSettings serverSettings("hw:M2",48000,32,3);
|
||||||
|
|
||||||
|
JackConfiguration jackConfiguration;
|
||||||
|
if (useJack)
|
||||||
|
{
|
||||||
|
jackConfiguration.JackInitialize();
|
||||||
|
} else {
|
||||||
|
jackConfiguration.AlsaInitialize(serverSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
JackChannelSelection channelSelection(
|
||||||
|
jackConfiguration.GetInputAudioPorts(),
|
||||||
|
jackConfiguration.GetOutputAudioPorts(),
|
||||||
|
jackConfiguration.GetInputMidiDevices());
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
|
if (useJack)
|
||||||
|
{
|
||||||
|
audioDriver = CreateJackDriver(this);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
audioDriver = CreateAlsaDriver(this);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
audioDriver = CreateAlsaDriver(this);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
oscillator.Init(440,jackConfiguration.GetSampleRate());
|
||||||
|
latencyMonitor.Init(jackConfiguration.GetSampleRate());
|
||||||
|
audioDriver->Open(serverSettings,channelSelection);
|
||||||
|
|
||||||
|
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
|
||||||
|
outputBuffers = new float*[channelSelection.GetOutputAudioPorts().size()];
|
||||||
|
|
||||||
|
audioDriver->Activate();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
sleep(1);
|
||||||
|
if (testType == TestType::LatencyMonitor)
|
||||||
|
{
|
||||||
|
auto latency = this->latencyMonitor.GetLatency();
|
||||||
|
double ms = 1000.0*latency/jackConfiguration.GetSampleRate();
|
||||||
|
|
||||||
|
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audioDriver->Deactivate();
|
||||||
|
audioDriver->Close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
float**inputBuffers = nullptr;
|
||||||
|
float**outputBuffers = nullptr;
|
||||||
|
|
||||||
|
class Oscillator {
|
||||||
|
private:
|
||||||
|
double dx = 0;
|
||||||
|
double x = 0;
|
||||||
|
double dx2 = 0;
|
||||||
|
double x2 = 0;
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Init(float frequency, size_t sampleRate)
|
||||||
|
{
|
||||||
|
dx = frequency*3.141592736*2/sampleRate;
|
||||||
|
dx2 = 0.5*3.141592736*2/sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Next() {
|
||||||
|
float result = (float)std::cos(x);
|
||||||
|
float env = (float)std::cos(x2);
|
||||||
|
x += dx;
|
||||||
|
x2 += dx2;
|
||||||
|
return result*env;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class LatencyMonitor {
|
||||||
|
enum class State {
|
||||||
|
Idle,
|
||||||
|
Waiting,
|
||||||
|
};
|
||||||
|
State state = State::Idle;
|
||||||
|
uint64_t t;
|
||||||
|
uint64_t idle_samples;
|
||||||
|
uint64_t waiting_samples;
|
||||||
|
size_t current_latency = 0;
|
||||||
|
size_t latency = 0;
|
||||||
|
std::mutex sync;
|
||||||
|
public:
|
||||||
|
void Init(uint64_t sampleRate)
|
||||||
|
{
|
||||||
|
idle_samples = (uint64_t)sampleRate*2;
|
||||||
|
waiting_samples = (uint64_t)sampleRate*2;
|
||||||
|
|
||||||
|
state = State::Idle;
|
||||||
|
t = idle_samples;
|
||||||
|
latency = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetLatency() {
|
||||||
|
std::lock_guard lock { sync};
|
||||||
|
return latency;
|
||||||
|
}
|
||||||
|
float Next(float input)
|
||||||
|
{
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case State::Idle:
|
||||||
|
{
|
||||||
|
if (t-- == 0) {
|
||||||
|
state = State::Waiting;
|
||||||
|
current_latency = 0;
|
||||||
|
}
|
||||||
|
return 0.01;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::Waiting:
|
||||||
|
{
|
||||||
|
if (std::abs(input) > 0.1 || current_latency > 500) {
|
||||||
|
{
|
||||||
|
std::lock_guard lock { sync};
|
||||||
|
latency = current_latency;
|
||||||
|
}
|
||||||
|
state = State::Idle;
|
||||||
|
t = idle_samples;
|
||||||
|
} else {
|
||||||
|
++current_latency;
|
||||||
|
}
|
||||||
|
return current_latency < 100 ? 0.25 : 0.0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Oscillator oscillator;
|
||||||
|
LatencyMonitor latencyMonitor;
|
||||||
|
|
||||||
|
|
||||||
|
virtual void OnAudioStopped() {
|
||||||
|
}
|
||||||
|
virtual void OnProcess(size_t nFrames) {
|
||||||
|
if (testType == TestType::NullTest) return;
|
||||||
|
|
||||||
|
|
||||||
|
size_t inputs = audioDriver->InputBufferCount();
|
||||||
|
size_t outputs = audioDriver->OutputBufferCount();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < inputs; ++i)
|
||||||
|
{
|
||||||
|
inputBuffers[i] = audioDriver->GetInputBuffer(i,nFrames);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < outputs; ++i)
|
||||||
|
{
|
||||||
|
outputBuffers[i] = audioDriver->GetOutputBuffer(i,nFrames);
|
||||||
|
}
|
||||||
|
if (this->testType == TestType::Oscillator)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < nFrames; ++i)
|
||||||
|
{
|
||||||
|
float v = oscillator.Next()*0.25f;
|
||||||
|
for (size_t c = 0; c < outputs; ++c)
|
||||||
|
{
|
||||||
|
outputBuffers[c][i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (size_t i = 0; i < nFrames; ++i)
|
||||||
|
{
|
||||||
|
float v = latencyMonitor.Next(inputBuffers[0][i]);
|
||||||
|
for (size_t c = 0; c < outputs; ++c)
|
||||||
|
{
|
||||||
|
outputBuffers[c][i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
std::mutex sync;
|
||||||
|
uint64_t xruns;
|
||||||
|
|
||||||
|
uint64_t GetXruns() {
|
||||||
|
lock_guard lock { sync};
|
||||||
|
return xruns;
|
||||||
|
}
|
||||||
|
virtual void OnUnderrun() {
|
||||||
|
lock_guard lock { sync };
|
||||||
|
++xruns;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE( "alsa_test", "[alsa_test]" ) {
|
||||||
|
|
||||||
|
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
|
||||||
|
|
||||||
|
alsaDriver.Test();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE( "alsa_midi_test", "[alsa_midi_test]" ) {
|
||||||
|
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
|
||||||
|
|
||||||
|
MidiDecoderTest();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define JACK_HOST 0
|
||||||
|
#define ALSA_HOST 1
|
||||||
|
|
||||||
|
#if JACK_HOST && ALSA_HOST
|
||||||
|
#error Choose either JACK or ALSA
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JackConfiguration.hpp"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
|
||||||
|
using ProcessCallback = std::function<void (size_t)>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AudioDriverHost {
|
||||||
|
public:
|
||||||
|
virtual void OnProcess(size_t nFrames) = 0;
|
||||||
|
virtual void OnUnderrun() = 0;
|
||||||
|
virtual void OnAudioStopped() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
class AudioDriver {
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~AudioDriver() { }
|
||||||
|
|
||||||
|
virtual float CpuUse() = 0;
|
||||||
|
virtual float CpuOverhead() = 0;
|
||||||
|
|
||||||
|
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 InputBufferCount() const = 0;
|
||||||
|
virtual float*GetInputBuffer(size_t channel, size_t nFrames) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
virtual size_t OutputBufferCount() const = 0;
|
||||||
|
virtual float*GetOutputBuffer(size_t channel, size_t nFrames) = 0;
|
||||||
|
|
||||||
|
virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0;
|
||||||
|
|
||||||
|
virtual void Activate() = 0;
|
||||||
|
virtual void Deactivate() = 0;
|
||||||
|
virtual void Close() = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
+32
-3
@@ -94,6 +94,7 @@ add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY -DBOOST_BIND_GLOBAL_PLACEHOLDERS
|
|||||||
|
|
||||||
|
|
||||||
set (PIPEDAL_SOURCES
|
set (PIPEDAL_SOURCES
|
||||||
|
CpuUse.hpp CpuUse.cpp
|
||||||
P2pConfigFiles.hpp
|
P2pConfigFiles.hpp
|
||||||
AvahiService.cpp AvahiService.hpp
|
AvahiService.cpp AvahiService.hpp
|
||||||
DeviceIdFile.cpp DeviceIdFile.hpp
|
DeviceIdFile.cpp DeviceIdFile.hpp
|
||||||
@@ -153,6 +154,11 @@ set (PIPEDAL_SOURCES
|
|||||||
InheritPriorityMutex.hpp InheritPriorityMutex.cpp
|
InheritPriorityMutex.hpp InheritPriorityMutex.cpp
|
||||||
UnixSocket.cpp UnixSocket.hpp
|
UnixSocket.cpp UnixSocket.hpp
|
||||||
|
|
||||||
|
JackDriver.cpp JackDriver.hpp
|
||||||
|
AlsaDriver.cpp AlsaDriver.hpp
|
||||||
|
AudioDriver.hpp
|
||||||
|
AudioConfig.hpp
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -165,7 +171,9 @@ set (PIPEDAL_INCLUDES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(PIPEDAL_LIBS libpipedald pthread atomic stdc++fs asound avahi-common avahi-client systemd
|
set(PIPEDAL_LIBS libpipedald pthread atomic stdc++fs asound avahi-common avahi-client systemd
|
||||||
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} )
|
${LILV_0_LIBRARIES}
|
||||||
|
# ${JACK_LIBRARIES}
|
||||||
|
${LIBNL3_LIBRARIES} )
|
||||||
|
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
@@ -195,6 +203,7 @@ target_link_libraries(pipedald PRIVATE
|
|||||||
#################################
|
#################################
|
||||||
add_executable(pipedaltest testMain.cpp
|
add_executable(pipedaltest testMain.cpp
|
||||||
jsonTest.cpp
|
jsonTest.cpp
|
||||||
|
AlsaDriverTest.cpp
|
||||||
AvahiServiceTest.cpp
|
AvahiServiceTest.cpp
|
||||||
WifiChannelsTest.cpp
|
WifiChannelsTest.cpp
|
||||||
PiPedalAlsaTest.cpp
|
PiPedalAlsaTest.cpp
|
||||||
@@ -259,6 +268,27 @@ add_executable(pipedalconfig
|
|||||||
target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs
|
target_link_libraries(pipedalconfig PRIVATE pthread atomic uuid stdc++fs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(pipedal_latency_test
|
||||||
|
PrettyPrinter.hpp
|
||||||
|
CommandLineParser.hpp
|
||||||
|
PiLatencyMain.cpp
|
||||||
|
PiPedalAlsa.hpp PiPedalAlsa.cpp
|
||||||
|
json.hpp json.cpp
|
||||||
|
HtmlHelper.cpp HtmlHelper.hpp
|
||||||
|
asan_options.cpp
|
||||||
|
Lv2Log.cpp Lv2Log.hpp
|
||||||
|
AlsaDriver.cpp AlsaDriver.hpp
|
||||||
|
JackConfiguration.hpp JackConfiguration.cpp
|
||||||
|
JackServerSettings.hpp JackServerSettings.cpp
|
||||||
|
CpuUse.hpp
|
||||||
|
CpuUse.cpp
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(pipedal_latency_test PRIVATE pthread asound
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
||||||
UnixSocket.cpp UnixSocket.hpp
|
UnixSocket.cpp UnixSocket.hpp
|
||||||
DeviceIdFile.cpp DeviceIdFile.hpp
|
DeviceIdFile.cpp DeviceIdFile.hpp
|
||||||
@@ -281,7 +311,6 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
|||||||
)
|
)
|
||||||
target_link_libraries(pipedaladmind PRIVATE pthread atomic stdc++fs systemd)
|
target_link_libraries(pipedaladmind PRIVATE pthread atomic stdc++fs systemd)
|
||||||
|
|
||||||
|
|
||||||
add_executable(processcopyrights copyrightMain.cpp
|
add_executable(processcopyrights copyrightMain.cpp
|
||||||
CommandLineParser.hpp
|
CommandLineParser.hpp
|
||||||
PiPedalException.hpp
|
PiPedalException.hpp
|
||||||
@@ -323,7 +352,7 @@ add_custom_target (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
install (TARGETS pipedalconfig pipedald pipedaladmind DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
install (TARGETS pipedalconfig pipedald pipedaladmind pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
||||||
EXPORT pipedalTargets)
|
EXPORT pipedalTargets)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -157,16 +157,53 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AddOption(const std::string &shortOption, const std::string &longOption, bool *pResult)
|
||||||
|
{
|
||||||
|
if (shortOption.length() != 0)
|
||||||
|
{
|
||||||
|
auto option = "-" + longOption;
|
||||||
|
options.push_back(new BooleanOption(option, pResult, true));
|
||||||
|
options.push_back(new BooleanOption(option + "+", pResult, true));
|
||||||
|
options.push_back(new BooleanOption(option + "-", pResult, false));
|
||||||
|
}
|
||||||
|
if (longOption.length() != 0)
|
||||||
|
{
|
||||||
|
auto option = "--" + longOption;
|
||||||
|
options.push_back(new BooleanOption(option, pResult, true));
|
||||||
|
options.push_back(new BooleanOption(option + "+", pResult, true));
|
||||||
|
options.push_back(new BooleanOption(option + "-", pResult, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AddOption(const std::string &option, bool *pResult)
|
void AddOption(const std::string &option, bool *pResult)
|
||||||
{
|
{
|
||||||
options.push_back(new BooleanOption(option, pResult, true));
|
options.push_back(new BooleanOption(option, pResult, true));
|
||||||
options.push_back(new BooleanOption(option + "+", pResult, true));
|
options.push_back(new BooleanOption(option + "+", pResult, true));
|
||||||
options.push_back(new BooleanOption(option + "-", pResult, false));
|
options.push_back(new BooleanOption(option + "-", pResult, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddOption(const std::string &shortOption, const std::string &longOption, std::string *pResult)
|
||||||
|
{
|
||||||
|
options.push_back(new StringOption("-" + shortOption, pResult));
|
||||||
|
options.push_back(new StringOption("--" + longOption, pResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AddOption(const std::string &option, std::string *pResult)
|
void AddOption(const std::string &option, std::string *pResult)
|
||||||
{
|
{
|
||||||
options.push_back(new StringOption(option, pResult));
|
options.push_back(new StringOption(option, pResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void AddOption(const std::string &shortOption, const std::string &longOption, T *pResult)
|
||||||
|
{
|
||||||
|
if (shortOption.length() != 0)
|
||||||
|
options.push_back(new Option<T>("-" + shortOption, pResult));
|
||||||
|
if (longOption.length() != 0)
|
||||||
|
options.push_back(new Option<T>("--" + longOption, pResult));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void AddOption(const std::string &option, T *pResult)
|
void AddOption(const std::string &option, T *pResult)
|
||||||
{
|
{
|
||||||
|
|||||||
+36
-12
@@ -36,6 +36,17 @@
|
|||||||
#include "DeviceIdFile.hpp"
|
#include "DeviceIdFile.hpp"
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
|
#define INSTALL_JACK_SERVICE 1
|
||||||
|
#else
|
||||||
|
#define INSTALL_JACK_SERVICE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define UNINSTALL_JACK_SERVICE 1
|
||||||
|
|
||||||
|
|
||||||
#define SS(x) (((std::stringstream &)(std::stringstream() << x)).str())
|
#define SS(x) (((std::stringstream &)(std::stringstream() << x)).str())
|
||||||
|
|
||||||
@@ -110,6 +121,14 @@ void EnableService()
|
|||||||
}
|
}
|
||||||
void DisableService()
|
void DisableService()
|
||||||
{
|
{
|
||||||
|
#if INSTALL_JACK_SERVICE || UNINSTALL_JACK_SERVICE
|
||||||
|
if (sysExec(SYSTEMCTL_BIN " disable " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
|
{
|
||||||
|
#if INSTALL_JACK_SERVICE
|
||||||
|
cout << "Error: Failed to disable the " JACK_SERVICE " service.";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (sysExec(SYSTEMCTL_BIN " disable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
if (sysExec(SYSTEMCTL_BIN " disable " NATIVE_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
cout << "Error: Failed to disable the " NATIVE_SERVICE " service.";
|
cout << "Error: Failed to disable the " NATIVE_SERVICE " service.";
|
||||||
@@ -145,10 +164,14 @@ void StopService(bool excludeShutdownService = false)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#if INSTALL_JACK_SERVICE || UNINSTALL_JACK_SERVICE
|
||||||
if (sysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
if (sysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
|
#if INSTALL_JACK_SERVICE
|
||||||
throw PiPedalException("Failed to stop the " JACK_SERVICE " service.");
|
throw PiPedalException("Failed to stop the " JACK_SERVICE " service.");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartService(bool excludeShutdownService = false)
|
void StartService(bool excludeShutdownService = false)
|
||||||
@@ -166,6 +189,7 @@ void StartService(bool excludeShutdownService = false)
|
|||||||
{
|
{
|
||||||
throw PiPedalException("Failed to start the " NATIVE_SERVICE " service.");
|
throw PiPedalException("Failed to start the " NATIVE_SERVICE " service.");
|
||||||
}
|
}
|
||||||
|
#if INSTALL_JACK_SERVICE
|
||||||
JackServerSettings serverSettings;
|
JackServerSettings serverSettings;
|
||||||
serverSettings.ReadJackConfiguration();
|
serverSettings.ReadJackConfiguration();
|
||||||
if (serverSettings.IsValid())
|
if (serverSettings.IsValid())
|
||||||
@@ -175,6 +199,7 @@ void StartService(bool excludeShutdownService = false)
|
|||||||
throw PiPedalException("Failed to start the " JACK_SERVICE " service.");
|
throw PiPedalException("Failed to start the " JACK_SERVICE " service.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void RestartService(bool excludeShutdownService)
|
void RestartService(bool excludeShutdownService)
|
||||||
{
|
{
|
||||||
@@ -258,12 +283,15 @@ static void RemoveLine(const std::string &path, const std::string lineToRemove)
|
|||||||
const std::string PAM_LINE = "JACK_PROMISCUOUS_SERVER DEFAULT=" JACK_SERVICE_GROUP_NAME;
|
const std::string PAM_LINE = "JACK_PROMISCUOUS_SERVER DEFAULT=" JACK_SERVICE_GROUP_NAME;
|
||||||
void UninstallPamEnv()
|
void UninstallPamEnv()
|
||||||
{
|
{
|
||||||
|
#if UNINSTALL_JACK_SERVICE
|
||||||
RemoveLine(
|
RemoveLine(
|
||||||
"/etc/security/pam_env.conf",
|
"/etc/security/pam_env.conf",
|
||||||
PAM_LINE);
|
PAM_LINE);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void InstallPamEnv()
|
void InstallPamEnv()
|
||||||
{
|
{
|
||||||
|
#if INSTALL_JACK_SERVICE
|
||||||
std::string newLine = PAM_LINE;
|
std::string newLine = PAM_LINE;
|
||||||
std::vector<std::string> lines;
|
std::vector<std::string> lines;
|
||||||
std::filesystem::path path = "/etc/security/pam_env.conf";
|
std::filesystem::path path = "/etc/security/pam_env.conf";
|
||||||
@@ -313,6 +341,7 @@ void InstallPamEnv()
|
|||||||
{
|
{
|
||||||
cout << "Failed to update " << path << ". " << e.what();
|
cout << "Failed to update " << path << ". " << e.what();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallLimits()
|
void InstallLimits()
|
||||||
@@ -322,12 +351,12 @@ void InstallLimits()
|
|||||||
throw PiPedalException("Failed to create audio service group.");
|
throw PiPedalException("Failed to create audio service group.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path limitsConfig = "/usr/security/pipedal.conf";
|
std::filesystem::path limitsConfig = "/etc/security/limits.d/audio.conf";
|
||||||
|
|
||||||
if (!std::filesystem::exists(limitsConfig))
|
if (!std::filesystem::exists(limitsConfig))
|
||||||
{
|
{
|
||||||
ofstream output(limitsConfig);
|
ofstream output(limitsConfig);
|
||||||
output << "# Realtime priority group used by pipedal/jack daemon"
|
output << "# Realtime priority group used by pipedal audio (and also by jack)"
|
||||||
<< "\n";
|
<< "\n";
|
||||||
output << "@audio - rtprio 95"
|
output << "@audio - rtprio 95"
|
||||||
<< "\n";
|
<< "\n";
|
||||||
@@ -336,15 +365,6 @@ void InstallLimits()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UninstallLimits()
|
|
||||||
{
|
|
||||||
std::filesystem::path limitsConfig = "/usr/security/pipedal.conf";
|
|
||||||
|
|
||||||
if (std::filesystem::exists(limitsConfig))
|
|
||||||
{
|
|
||||||
std::filesystem::remove(limitsConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaybeStartJackService()
|
void MaybeStartJackService()
|
||||||
{
|
{
|
||||||
@@ -363,6 +383,8 @@ void InstallJackService()
|
|||||||
{
|
{
|
||||||
InstallLimits();
|
InstallLimits();
|
||||||
InstallPamEnv();
|
InstallPamEnv();
|
||||||
|
|
||||||
|
#if INSTALL_JACK_SERVICE
|
||||||
if (sysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
if (sysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
throw PiPedalException("Failed to create jack service group.");
|
throw PiPedalException("Failed to create jack service group.");
|
||||||
@@ -391,6 +413,8 @@ void InstallJackService()
|
|||||||
WriteTemplateFile(map, GetServiceFileName(JACK_SERVICE));
|
WriteTemplateFile(map, GetServiceFileName(JACK_SERVICE));
|
||||||
|
|
||||||
MaybeStartJackService();
|
MaybeStartJackService();
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int SudoExec(char **argv)
|
int SudoExec(char **argv)
|
||||||
@@ -463,7 +487,7 @@ void Uninstall()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
UninstallPamEnv();
|
UninstallPamEnv();
|
||||||
UninstallLimits();
|
// UninstallLimits();
|
||||||
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CpuUse.hpp"
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
CpuUse::CpuUseAverager::CpuUseAverager()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < NUM_SAMPLES; ++i)
|
||||||
|
{
|
||||||
|
samples[i] = 0;
|
||||||
|
}
|
||||||
|
sampleTotal = 0;
|
||||||
|
}
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace pipedal
|
||||||
|
{
|
||||||
|
|
||||||
|
enum ProfileCategory
|
||||||
|
{
|
||||||
|
Init,
|
||||||
|
Read,
|
||||||
|
Driver,
|
||||||
|
Execute,
|
||||||
|
Write,
|
||||||
|
|
||||||
|
MaxCategory
|
||||||
|
};
|
||||||
|
constexpr size_t NUM_PROFILE_CATEGORIES = (size_t)ProfileCategory::MaxCategory;
|
||||||
|
|
||||||
|
class CpuUse
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ClockT = std::chrono::steady_clock;
|
||||||
|
using TimeT = ClockT::time_point;
|
||||||
|
using DurationT = ClockT::duration;
|
||||||
|
using SampleT = DurationT::rep;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class CpuUseAverager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static constexpr size_t NUM_SAMPLES = 20; // average over N samples.
|
||||||
|
SampleT sampleTotal = 0;
|
||||||
|
SampleT samples[NUM_SAMPLES];
|
||||||
|
size_t sampleIndex = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
CpuUseAverager();
|
||||||
|
|
||||||
|
SampleT GetAverage() const
|
||||||
|
{
|
||||||
|
return sampleTotal / NUM_SAMPLES;
|
||||||
|
}
|
||||||
|
SampleT GetTotal() const {
|
||||||
|
return sampleTotal;
|
||||||
|
}
|
||||||
|
void AddSample(DurationT duration)
|
||||||
|
{
|
||||||
|
SampleT sample = duration.count();
|
||||||
|
sampleTotal = sampleTotal + sample-samples[sampleIndex];
|
||||||
|
samples[sampleIndex] = sample;
|
||||||
|
++sampleIndex;
|
||||||
|
if (sampleIndex >= NUM_SAMPLES)
|
||||||
|
{
|
||||||
|
sampleIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex sync;
|
||||||
|
|
||||||
|
TimeT lastSample;
|
||||||
|
|
||||||
|
CpuUseAverager profileTimes[NUM_PROFILE_CATEGORIES];
|
||||||
|
float currentCpuUse = 0;
|
||||||
|
float currentOverhead = 0;
|
||||||
|
|
||||||
|
CpuUseAverager &GetCategory(ProfileCategory category) {
|
||||||
|
return profileTimes[(size_t)category];
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
TimeT Now() {
|
||||||
|
return ClockT::now();
|
||||||
|
}
|
||||||
|
void SetStartTime(TimeT time)
|
||||||
|
{
|
||||||
|
lastSample = time;
|
||||||
|
}
|
||||||
|
void AddSample(ProfileCategory category, TimeT time)
|
||||||
|
{
|
||||||
|
profileTimes[(size_t)category].AddSample((time-lastSample));
|
||||||
|
lastSample = time;
|
||||||
|
}
|
||||||
|
void AddSample(ProfileCategory category)
|
||||||
|
{
|
||||||
|
AddSample(category,Now());
|
||||||
|
}
|
||||||
|
void AddSample(ProfileCategory category, TimeT startTime, TimeT endTime)
|
||||||
|
{
|
||||||
|
profileTimes[(size_t)category].AddSample((endTime-startTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCpuUse() {
|
||||||
|
|
||||||
|
SampleT readTime = GetCategory(ProfileCategory::Read).GetTotal();
|
||||||
|
SampleT writeTime = GetCategory(ProfileCategory::Driver).GetTotal();
|
||||||
|
|
||||||
|
SampleT processingTime = GetCategory(ProfileCategory::Execute).GetTotal() + GetCategory(ProfileCategory::Driver).GetTotal();
|
||||||
|
|
||||||
|
// most of the waiting occurs in the write. Assume that the difference between readTime and write Time is how long we spent waiting (i.e. free time)
|
||||||
|
|
||||||
|
SampleT waitTime;
|
||||||
|
SampleT overheadTime;
|
||||||
|
if (readTime > writeTime)
|
||||||
|
{
|
||||||
|
waitTime = readTime-writeTime;
|
||||||
|
overheadTime = writeTime;
|
||||||
|
} else {
|
||||||
|
waitTime = writeTime-readTime;
|
||||||
|
overheadTime = readTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleT totalTime = writeTime+ readTime + processingTime;
|
||||||
|
SampleT maxTime = waitTime+processingTime;
|
||||||
|
|
||||||
|
|
||||||
|
float result = 100.0f*(processingTime)/(maxTime);
|
||||||
|
float overhead = 100.0F*(overheadTime*2)/totalTime;
|
||||||
|
{
|
||||||
|
std::lock_guard lock { sync};
|
||||||
|
currentCpuUse = result;
|
||||||
|
currentOverhead = overhead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float GetCpuUse()
|
||||||
|
{
|
||||||
|
std::lock_guard lock { sync};
|
||||||
|
return currentCpuUse;
|
||||||
|
}
|
||||||
|
float GetCpuOverhead()
|
||||||
|
{
|
||||||
|
std::lock_guard lock { sync};
|
||||||
|
return currentOverhead;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
+101
-39
@@ -19,50 +19,24 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "JackConfiguration.hpp"
|
#include "JackConfiguration.hpp"
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
|
#include "AlsaDriver.hpp"
|
||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
#include <jack/jack.h>
|
#include <jack/jack.h>
|
||||||
#include <jack/types.h>
|
#include <jack/types.h>
|
||||||
#include <jack/session.h>
|
#include <jack/session.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "defer.hpp"
|
#include "defer.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
std::string JackStatusToString(jack_status_t status)
|
|
||||||
{
|
|
||||||
switch (status)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return "Unknown Jack Audio error.";
|
|
||||||
case JackStatus::JackFailure:
|
|
||||||
return "Jack Audio operation failed.";
|
|
||||||
case JackStatus::JackInvalidOption:
|
|
||||||
return "Invalid Jack Audio operation.";
|
|
||||||
case JackStatus::JackNameNotUnique:
|
|
||||||
return "Jack Audio name in use.";
|
|
||||||
case JackStatus::JackServerStarted:
|
|
||||||
return "Jack Audio Server started.";
|
|
||||||
case JackStatus::JackServerFailed:
|
|
||||||
return "Jack Audio Server failed.";
|
|
||||||
case JackStatus::JackServerError:
|
|
||||||
return "Can't connect to the Jack Audio Server.";
|
|
||||||
case JackStatus::JackNoSuchClient:
|
|
||||||
return "Jack Audio client not found.";
|
|
||||||
case JackLoadFailure:
|
|
||||||
return "Jack Audio client failed to load.";
|
|
||||||
case JackStatus::JackInitFailure:
|
|
||||||
return "Failed to initialize the Jack Audio client.";
|
|
||||||
case JackStatus::JackShmFailure:
|
|
||||||
return "Jack Audio failed to access shared memory.";
|
|
||||||
case JackStatus::JackVersionError:
|
|
||||||
return "Jack Audio Server version error.";
|
|
||||||
case JackStatus::JackClientZombie:
|
|
||||||
return "JackClientZombie error.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JackConfiguration::JackConfiguration()
|
JackConfiguration::JackConfiguration()
|
||||||
{
|
{
|
||||||
@@ -73,10 +47,12 @@ JackConfiguration::~JackConfiguration()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
static void AddPorts(jack_client_t *client, std::vector<std::string> *output, const char *port_name_pattern,
|
static void AddPorts(jack_client_t *client, std::vector<std::string> *output, const char *port_name_pattern,
|
||||||
const char *type_name_pattern,
|
const char *type_name_pattern,
|
||||||
unsigned long flags)
|
unsigned long flags)
|
||||||
{
|
{
|
||||||
|
#if JACK_HOST
|
||||||
const char**ports = jack_get_ports(client, port_name_pattern, type_name_pattern, flags);
|
const char**ports = jack_get_ports(client, port_name_pattern, type_name_pattern, flags);
|
||||||
if (ports != nullptr)
|
if (ports != nullptr)
|
||||||
{
|
{
|
||||||
@@ -88,9 +64,14 @@ static void AddPorts(jack_client_t *client, std::vector<std::string> *output, co
|
|||||||
jack_free(ports);
|
jack_free(ports);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
#if JACK_HOST
|
||||||
std::string GetJackErrorMessage(jack_status_t status)
|
std::string GetJackErrorMessage(jack_status_t status)
|
||||||
{
|
{
|
||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
@@ -122,9 +103,43 @@ namespace pipedal {
|
|||||||
return s.str();
|
return s.str();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void JackConfiguration::Initialize()
|
|
||||||
|
void JackConfiguration::AlsaInitialize(
|
||||||
|
const JackServerSettings &jackServerSettings)
|
||||||
{
|
{
|
||||||
|
this->isValid_ = false;
|
||||||
|
this->errorStatus_ = "";
|
||||||
|
|
||||||
|
this->inputMidiDevices_ = GetAlsaMidiInputDevices();
|
||||||
|
if (jackServerSettings.IsValid())
|
||||||
|
{
|
||||||
|
this->blockLength_ = jackServerSettings.GetBufferSize();
|
||||||
|
this->sampleRate_ = jackServerSettings.GetSampleRate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (GetAlsaChannels(jackServerSettings,
|
||||||
|
inputAudioPorts_,outputAudioPorts_))
|
||||||
|
{
|
||||||
|
this->isValid_ = true;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& /*ignore*/)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!this->isValid_)
|
||||||
|
{
|
||||||
|
this->isValid_ = false;
|
||||||
|
this->errorStatus_ = "Not configured.";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void JackConfiguration::JackInitialize()
|
||||||
|
{
|
||||||
|
#if JACK_HOST
|
||||||
jack_status_t status;
|
jack_status_t status;
|
||||||
|
|
||||||
jack_client_t *client = nullptr;
|
jack_client_t *client = nullptr;
|
||||||
@@ -161,8 +176,6 @@ void JackConfiguration::Initialize()
|
|||||||
|
|
||||||
AddPorts(client, &this->inputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput);
|
AddPorts(client, &this->inputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput);
|
||||||
AddPorts(client, &this->outputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput);
|
AddPorts(client, &this->outputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput);
|
||||||
AddPorts(client, &this->inputMidiPorts_, "system", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput);
|
|
||||||
AddPorts(client, &this->outputMidiPorts_, "system", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput);
|
|
||||||
isValid_ = true;
|
isValid_ = true;
|
||||||
this->errorStatus_ = "";
|
this->errorStatus_ = "";
|
||||||
jack_client_close(client);
|
jack_client_close(client);
|
||||||
@@ -172,6 +185,9 @@ void JackConfiguration::Initialize()
|
|||||||
jack_client_close(client);
|
jack_client_close(client);
|
||||||
this->errorStatus_ = e.what();
|
this->errorStatus_ = e.what();
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JackChannelSelection JackChannelSelection::MakeDefault(const JackConfiguration&config){
|
JackChannelSelection JackChannelSelection::MakeDefault(const JackConfiguration&config){
|
||||||
@@ -211,12 +227,59 @@ static std::vector<std::string> makeValid(const std::vector<std::string> & selec
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
static std::vector<AlsaMidiDeviceInfo> makeValid(const std::vector<AlsaMidiDeviceInfo> & selected, const std::vector<AlsaMidiDeviceInfo> &available)
|
||||||
|
{
|
||||||
|
// Matching rule:
|
||||||
|
// same name AND same description (unchanged)
|
||||||
|
// different name and same description: use the new device name. (i.e. same device plugged into different USB port)
|
||||||
|
// otherwise discard.
|
||||||
|
|
||||||
|
std::vector<AlsaMidiDeviceInfo> sourceDevices = available;
|
||||||
|
std::vector<AlsaMidiDeviceInfo> result;
|
||||||
|
std::vector<AlsaMidiDeviceInfo> partialMatch;
|
||||||
|
|
||||||
|
for (auto & sel : selected)
|
||||||
|
{
|
||||||
|
size_t matchIndex = -1;
|
||||||
|
for (size_t i = 0; i < sourceDevices.size(); ++i)
|
||||||
|
{
|
||||||
|
const AlsaMidiDeviceInfo &candidate = sourceDevices[i];
|
||||||
|
if (candidate.description_ == sel.description_)
|
||||||
|
{
|
||||||
|
if (candidate.name_ == sel.name_)
|
||||||
|
{
|
||||||
|
result.push_back(candidate);
|
||||||
|
} else {
|
||||||
|
partialMatch.push_back(candidate);
|
||||||
|
}
|
||||||
|
sourceDevices.erase(sourceDevices.begin()+i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &sel : partialMatch)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sourceDevices.size(); ++i)
|
||||||
|
{
|
||||||
|
const AlsaMidiDeviceInfo &candidate = sourceDevices[i];
|
||||||
|
if (candidate.description_ == sel.description_)
|
||||||
|
{
|
||||||
|
result.push_back(candidate); // same description, usb address has changed. includeit.
|
||||||
|
sourceDevices.erase(sourceDevices.begin()+i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfiguration&config) const
|
JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfiguration&config) const
|
||||||
{
|
{
|
||||||
JackChannelSelection result;
|
JackChannelSelection result;
|
||||||
result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,config.GetInputAudioPorts());
|
result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,config.GetInputAudioPorts());
|
||||||
result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.GetOutputAudioPorts());
|
result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.GetOutputAudioPorts());
|
||||||
result.inputMidiPorts_ = makeValid(this->inputMidiPorts_, config.GetInputMidiPorts());
|
result.inputMidiDevices_ = makeValid(this->inputMidiDevices_, config.GetInputMidiDevices());
|
||||||
if (!result.isValid())
|
if (!result.isValid())
|
||||||
{
|
{
|
||||||
return this->MakeDefault(config);
|
return this->MakeDefault(config);
|
||||||
@@ -226,7 +289,7 @@ JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfi
|
|||||||
JSON_MAP_BEGIN(JackChannelSelection)
|
JSON_MAP_BEGIN(JackChannelSelection)
|
||||||
JSON_MAP_REFERENCE(JackChannelSelection,inputAudioPorts)
|
JSON_MAP_REFERENCE(JackChannelSelection,inputAudioPorts)
|
||||||
JSON_MAP_REFERENCE(JackChannelSelection,outputAudioPorts)
|
JSON_MAP_REFERENCE(JackChannelSelection,outputAudioPorts)
|
||||||
JSON_MAP_REFERENCE(JackChannelSelection,inputMidiPorts)
|
JSON_MAP_REFERENCE(JackChannelSelection,inputMidiDevices)
|
||||||
|
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
@@ -240,7 +303,6 @@ JSON_MAP_BEGIN(JackConfiguration)
|
|||||||
JSON_MAP_REFERENCE(JackConfiguration,maxAllowedMidiDelta)
|
JSON_MAP_REFERENCE(JackConfiguration,maxAllowedMidiDelta)
|
||||||
JSON_MAP_REFERENCE(JackConfiguration,inputAudioPorts)
|
JSON_MAP_REFERENCE(JackConfiguration,inputAudioPorts)
|
||||||
JSON_MAP_REFERENCE(JackConfiguration,outputAudioPorts)
|
JSON_MAP_REFERENCE(JackConfiguration,outputAudioPorts)
|
||||||
JSON_MAP_REFERENCE(JackConfiguration,inputMidiPorts)
|
JSON_MAP_REFERENCE(JackConfiguration,inputMidiDevices)
|
||||||
JSON_MAP_REFERENCE(JackConfiguration,outputMidiPorts)
|
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
#include "JackServerSettings.hpp"
|
||||||
|
#include "PiPedalAlsa.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
@@ -42,11 +45,13 @@ namespace pipedal
|
|||||||
|
|
||||||
std::vector<std::string> inputAudioPorts_;
|
std::vector<std::string> inputAudioPorts_;
|
||||||
std::vector<std::string> outputAudioPorts_;
|
std::vector<std::string> outputAudioPorts_;
|
||||||
std::vector<std::string> inputMidiPorts_;
|
std::vector<AlsaMidiDeviceInfo> inputMidiDevices_;
|
||||||
std::vector<std::string> outputMidiPorts_;
|
|
||||||
public:
|
public:
|
||||||
JackConfiguration();
|
JackConfiguration();
|
||||||
void Initialize();
|
|
||||||
|
void AlsaInitialize(const JackServerSettings &jackServerSettings); // from also config settings.
|
||||||
|
|
||||||
|
void JackInitialize(); // from jack server instance.
|
||||||
~JackConfiguration();
|
~JackConfiguration();
|
||||||
bool isValid() const { return isValid_;}
|
bool isValid() const { return isValid_;}
|
||||||
bool isRestarting() const { return isRestarting_; }
|
bool isRestarting() const { return isRestarting_; }
|
||||||
@@ -59,8 +64,7 @@ namespace pipedal
|
|||||||
|
|
||||||
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
|
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
|
||||||
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
||||||
const std::vector<std::string> &GetInputMidiPorts() const { return inputMidiPorts_; }
|
const std::vector<AlsaMidiDeviceInfo> &GetInputMidiDevices() const { return inputMidiDevices_; }
|
||||||
const std::vector<std::string> &GetOutputMidiPorts() const { return outputMidiPorts_; }
|
|
||||||
|
|
||||||
DECLARE_JSON_MAP(JackConfiguration);
|
DECLARE_JSON_MAP(JackConfiguration);
|
||||||
|
|
||||||
@@ -69,10 +73,20 @@ namespace pipedal
|
|||||||
private:
|
private:
|
||||||
std::vector<std::string> inputAudioPorts_;
|
std::vector<std::string> inputAudioPorts_;
|
||||||
std::vector<std::string> outputAudioPorts_;
|
std::vector<std::string> outputAudioPorts_;
|
||||||
std::vector<std::string> inputMidiPorts_;
|
std::vector<AlsaMidiDeviceInfo> inputMidiDevices_;
|
||||||
public:
|
public:
|
||||||
JackChannelSelection()
|
JackChannelSelection()
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
JackChannelSelection(
|
||||||
|
std::vector<std::string> inputAudioPorts,
|
||||||
|
std::vector<std::string> outputAudioPorts,
|
||||||
|
std::vector<AlsaMidiDeviceInfo> inputMidiDevices
|
||||||
|
) : inputAudioPorts_(inputAudioPorts),
|
||||||
|
outputAudioPorts_(outputAudioPorts),
|
||||||
|
inputMidiDevices_(inputMidiDevices)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValid() const {
|
bool isValid() const {
|
||||||
@@ -83,14 +97,14 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
return inputAudioPorts_;
|
return inputAudioPorts_;
|
||||||
}
|
}
|
||||||
const std::vector<std::string>& getOutputAudioPorts() const
|
const std::vector<std::string>& GetOutputAudioPorts() const
|
||||||
{
|
{
|
||||||
return outputAudioPorts_;
|
return outputAudioPorts_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string>& GetInputMidiPorts() const
|
const std::vector<AlsaMidiDeviceInfo>& GetInputMidiDevices() const
|
||||||
{
|
{
|
||||||
return inputMidiPorts_;
|
return inputMidiDevices_;
|
||||||
}
|
}
|
||||||
JackChannelSelection RemoveInvalidChannels(const JackConfiguration&config) const;
|
JackChannelSelection RemoveInvalidChannels(const JackConfiguration&config) const;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,512 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Status of Jack support.
|
||||||
|
|
||||||
|
PiPedal was originally written for Jack, but subsequently ported to Alsa because running
|
||||||
|
Jack as a systemd daemon proved to be unsupportable,.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pch.h"
|
||||||
|
#include "JackDriver.hpp"
|
||||||
|
|
||||||
|
#include <jack/jack.h>
|
||||||
|
#include <jack/types.h>
|
||||||
|
#include <jack/session.h>
|
||||||
|
#include <jack/midiport.h>
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
|
||||||
|
std::string JackStatusToString(jack_status_t status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return "Unknown Jack Audio error.";
|
||||||
|
case JackStatus::JackFailure:
|
||||||
|
return "Jack Audio operation failed.";
|
||||||
|
case JackStatus::JackInvalidOption:
|
||||||
|
return "Invalid Jack Audio operation.";
|
||||||
|
case JackStatus::JackNameNotUnique:
|
||||||
|
return "Jack Audio name in use.";
|
||||||
|
case JackStatus::JackServerStarted:
|
||||||
|
return "Jack Audio Server started.";
|
||||||
|
case JackStatus::JackServerFailed:
|
||||||
|
return "Jack Audio Server failed.";
|
||||||
|
case JackStatus::JackServerError:
|
||||||
|
return "Can't connect to the Jack Audio Server.";
|
||||||
|
case JackStatus::JackNoSuchClient:
|
||||||
|
return "Jack Audio client not found.";
|
||||||
|
case JackLoadFailure:
|
||||||
|
return "Jack Audio client failed to load.";
|
||||||
|
case JackStatus::JackInitFailure:
|
||||||
|
return "Failed to initialize the Jack Audio client.";
|
||||||
|
case JackStatus::JackShmFailure:
|
||||||
|
return "Jack Audio failed to access shared memory.";
|
||||||
|
case JackStatus::JackVersionError:
|
||||||
|
return "Jack Audio Server version error.";
|
||||||
|
case JackStatus::JackClientZombie:
|
||||||
|
return "JackClientZombie error.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This code relies on the fact that jack_midi_event_t and pipedal::MidiEvent are binary-compatible.
|
||||||
|
(the default build does not require Jack Audio headers to be present).
|
||||||
|
|
||||||
|
|
||||||
|
Test to make sure this is still true.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static_assert(sizeof(jack_midi_event_t) == sizeof(pipedal::MidiEvent));
|
||||||
|
|
||||||
|
#define CHECK_FIELD(fieldName) \
|
||||||
|
static_assert(offsetof(jack_midi_event_t,fieldName) == offsetof(pipedal::MidiEvent,fieldName)); \
|
||||||
|
static_assert(sizeof(jack_midi_event_t::fieldName) == sizeof(pipedal::MidiEvent::fieldName));
|
||||||
|
|
||||||
|
CHECK_FIELD(size)
|
||||||
|
CHECK_FIELD(buffer)
|
||||||
|
CHECK_FIELD(time)
|
||||||
|
|
||||||
|
|
||||||
|
// private import from JackConfiguration.cpp
|
||||||
|
std::string GetJackErrorMessage(jack_status_t status);
|
||||||
|
|
||||||
|
|
||||||
|
// forward decl
|
||||||
|
static int process_fn(jack_nframes_t nframes, void *arg);
|
||||||
|
|
||||||
|
static void jack_error_fn(const char *msg)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Jack - %s", msg);
|
||||||
|
}
|
||||||
|
static void jack_info_fn(const char *msg)
|
||||||
|
{
|
||||||
|
Lv2Log::info("Jack - %s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class JackDriverImpl : public AudioDriver {
|
||||||
|
private:
|
||||||
|
jack_client_t *client = nullptr;
|
||||||
|
std::vector<jack_port_t *> inputPorts;
|
||||||
|
std::vector<jack_port_t *> outputPorts;
|
||||||
|
std::vector<jack_port_t *> midiInputPorts;
|
||||||
|
uint32_t sampleRate = 0;
|
||||||
|
|
||||||
|
AudioDriverHost *driverHost = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
JackDriverImpl(AudioDriverHost*driverHost)
|
||||||
|
: driverHost(driverHost)
|
||||||
|
{
|
||||||
|
jack_set_error_function(jack_error_fn);
|
||||||
|
jack_set_info_function(jack_info_fn);
|
||||||
|
|
||||||
|
}
|
||||||
|
virtual ~JackDriverImpl()
|
||||||
|
{
|
||||||
|
jack_set_error_function(nullptr);
|
||||||
|
jack_set_info_function(nullptr);
|
||||||
|
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
|
||||||
|
void OnShutdown()
|
||||||
|
{
|
||||||
|
Lv2Log::info("Jack Audio Server has shut down.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
jack_shutdown_fn(void *arg)
|
||||||
|
{
|
||||||
|
((JackDriverImpl *)arg)->OnShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int jackInstanceId;
|
||||||
|
|
||||||
|
static int xrun_callback_fn(void *arg)
|
||||||
|
{
|
||||||
|
((AudioDriverHost *)arg)->OnUnderrun();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual uint32_t GetSampleRate() {
|
||||||
|
return this->sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open = false;
|
||||||
|
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection &channelSelection) {
|
||||||
|
if (open) throw PiPedalStateException("Already open");
|
||||||
|
open = true;
|
||||||
|
|
||||||
|
this->channelSelection = channelSelection;
|
||||||
|
jack_status_t status;
|
||||||
|
// need a unique instance name every timme.
|
||||||
|
std::stringstream s;
|
||||||
|
s << "PiPedal";
|
||||||
|
if (jackInstanceId != 0) {
|
||||||
|
s << jackInstanceId;
|
||||||
|
}
|
||||||
|
++jackInstanceId;
|
||||||
|
|
||||||
|
std::string instanceName = s.str();
|
||||||
|
|
||||||
|
client = jack_client_open(instanceName.c_str(), JackNullOption, &status);
|
||||||
|
|
||||||
|
if (client == nullptr || status & JackFailure)
|
||||||
|
{
|
||||||
|
if (client)
|
||||||
|
{
|
||||||
|
jack_client_close(client);
|
||||||
|
client = nullptr;
|
||||||
|
}
|
||||||
|
std::string error = GetJackErrorMessage(status);
|
||||||
|
Lv2Log::error(error);
|
||||||
|
throw PiPedalStateException(error.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & JackServerStarted)
|
||||||
|
{
|
||||||
|
Lv2Log::info("Jack server started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_set_process_callback(client, process_fn, this->driverHost);
|
||||||
|
|
||||||
|
jack_on_shutdown(client, jack_shutdown_fn, this);
|
||||||
|
|
||||||
|
jack_set_xrun_callback(client, xrun_callback_fn, this->driverHost);
|
||||||
|
|
||||||
|
this->sampleRate = jack_get_sample_rate(client);
|
||||||
|
|
||||||
|
|
||||||
|
auto &selectedInputPorts = channelSelection.GetInputAudioPorts();
|
||||||
|
this->inputPorts.clear();
|
||||||
|
this->outputPorts.clear();
|
||||||
|
this->midiInputPorts.clear();
|
||||||
|
|
||||||
|
this->inputPorts.resize(selectedInputPorts.size());
|
||||||
|
for (int i = 0; i < selectedInputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
std::stringstream name;
|
||||||
|
name << "input" << (i + 1);
|
||||||
|
this->inputPorts[i] =
|
||||||
|
jack_port_register(
|
||||||
|
client, name.str().c_str(),
|
||||||
|
JACK_DEFAULT_AUDIO_TYPE,
|
||||||
|
JackPortIsInput, 0);
|
||||||
|
if (this->inputPorts[i] == nullptr)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Can't allocate Jack Audio input ports.");
|
||||||
|
throw PiPedalStateException("Failed to allocate Jack Audio port.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto &selectedOutputPorts = channelSelection.GetOutputAudioPorts();
|
||||||
|
this->outputPorts.resize(selectedOutputPorts.size());
|
||||||
|
for (int i = 0; i < selectedOutputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
std::stringstream name;
|
||||||
|
name << "output" << (i + 1);
|
||||||
|
this->outputPorts[i] =
|
||||||
|
jack_port_register(client, name.str().c_str(),
|
||||||
|
JACK_DEFAULT_AUDIO_TYPE,
|
||||||
|
JackPortIsOutput, 0);
|
||||||
|
if (this->outputPorts[i] == nullptr)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Can't allocate Jack Audio output port.");
|
||||||
|
throw PiPedalStateException("Failed to allocate Jack Audio port.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto &selectedMidiPorts = channelSelection.GetInputMidiDevices();
|
||||||
|
|
||||||
|
this->midiInputPorts.resize(selectedMidiPorts.size());
|
||||||
|
for (int i = 0; i < selectedMidiPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
std::stringstream name;
|
||||||
|
name << "midiIn" << (i + 1);
|
||||||
|
this->midiInputPorts[i] =
|
||||||
|
jack_port_register(client, name.str().c_str(),
|
||||||
|
JACK_DEFAULT_MIDI_TYPE,
|
||||||
|
JackPortIsInput, 0);
|
||||||
|
if (this->midiInputPorts[i] == nullptr)
|
||||||
|
{
|
||||||
|
std::stringstream s;
|
||||||
|
s << "can't register Jack port " << name.str().c_str();
|
||||||
|
Lv2Log::error(s.str());
|
||||||
|
throw PiPedalStateException(s.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool jackActive = false;
|
||||||
|
|
||||||
|
JackChannelSelection channelSelection;
|
||||||
|
virtual void Activate()
|
||||||
|
{
|
||||||
|
int activateResult = jack_activate(client);
|
||||||
|
if (activateResult == 0)
|
||||||
|
{
|
||||||
|
jackActive = true;
|
||||||
|
|
||||||
|
auto &selectedInputPorts = channelSelection.GetInputAudioPorts();
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedInputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto result = jack_connect(client, selectedInputPorts[i].c_str(), jack_port_name(this->inputPorts[i]));
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Can't connect input port %s", selectedInputPorts[i].c_str());
|
||||||
|
throw PiPedalStateException("Jack Audio port connection failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &selectedOutputPorts = channelSelection.GetOutputAudioPorts();
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedOutputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto result = jack_connect(client, jack_port_name(this->outputPorts[i]), selectedOutputPorts[i].c_str());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Can't connect output port %s", selectedOutputPorts[i].c_str());
|
||||||
|
throw PiPedalStateException("Jack Audio port connection failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &selectedMidiPorts = channelSelection.GetInputMidiDevices();
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedMidiPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto result = jack_connect(client, selectedMidiPorts[i].name_.c_str(), jack_port_name(this->midiInputPorts[i]));
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Can't connect midi input port %s", selectedMidiPorts[i].name_.c_str());
|
||||||
|
throw PiPedalStateException("Jack Audio midi port connection failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lv2Log::info("Jack configuration complete.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::error("Failed to activate Jack Audio client. (%d)", (int)activateResult);
|
||||||
|
throw PiPedalStateException("Failed to activate Jack Audio client.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillMidiBuffers()
|
||||||
|
{
|
||||||
|
// do nothing. Jack does this for us.
|
||||||
|
}
|
||||||
|
virtual void Deactivate()
|
||||||
|
{
|
||||||
|
if (!jackActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jackActive = false;
|
||||||
|
// disconnect ports.
|
||||||
|
for (size_t i = 0; i < this->inputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = inputPorts[i];
|
||||||
|
if (port != nullptr)
|
||||||
|
{
|
||||||
|
jack_disconnect(client, channelSelection.GetInputAudioPorts()[i].c_str(), jack_port_name(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < outputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = outputPorts[i];
|
||||||
|
if (port != nullptr)
|
||||||
|
{
|
||||||
|
jack_disconnect(client, jack_port_name(port),channelSelection.GetOutputAudioPorts()[i].c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < midiInputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = midiInputPorts[i];
|
||||||
|
if (port != nullptr)
|
||||||
|
{
|
||||||
|
jack_disconnect(client, channelSelection.GetInputMidiDevices()[i].name_.c_str(), jack_port_name(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jackActive)
|
||||||
|
{
|
||||||
|
jack_deactivate(client);
|
||||||
|
jackActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_set_process_callback(client, nullptr, nullptr);
|
||||||
|
jack_on_shutdown(client, nullptr, nullptr);
|
||||||
|
#if JACK_SESSION_CALLBACK // (deprecated, and not actually useful)
|
||||||
|
jack_set_session_callback(client, nullptr, nullptr);
|
||||||
|
#endif
|
||||||
|
jack_set_xrun_callback(client, nullptr, nullptr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t MidiInputBufferCount() const { return this->midiInputPorts.size(); }
|
||||||
|
virtual void*GetMidiInputBuffer(size_t channel, size_t nFrames) {
|
||||||
|
return (void*)jack_port_get_buffer(this->midiInputPorts[channel], nFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t GetMidiInputEventCount(void *portBuffer)
|
||||||
|
{
|
||||||
|
return jack_midi_get_event_count(portBuffer);
|
||||||
|
}
|
||||||
|
virtual bool GetMidiInputEvent(MidiEvent*event, void*portBuf, size_t nFrame)
|
||||||
|
{
|
||||||
|
return jack_midi_event_get((jack_midi_event_t*)event,portBuf,(uint32_t)nFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
virtual size_t InputBufferCount() const { return inputPorts.size(); }
|
||||||
|
virtual float*GetInputBuffer(size_t channel, size_t nFrames) {
|
||||||
|
return (float*)jack_port_get_buffer(this->inputPorts[channel], nFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t OutputBufferCount() const { return outputPorts.size(); }
|
||||||
|
virtual float*GetOutputBuffer(size_t channel, size_t nFrames)
|
||||||
|
{
|
||||||
|
return (float*)jack_port_get_buffer(this->outputPorts[channel], nFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Close()
|
||||||
|
{
|
||||||
|
if (!open) return;
|
||||||
|
open = false;
|
||||||
|
|
||||||
|
// unregister ports.
|
||||||
|
for (size_t i = 0; i < this->inputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = inputPorts[i];
|
||||||
|
if (port)
|
||||||
|
{
|
||||||
|
jack_port_unregister(client, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputPorts.clear();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < this->outputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = outputPorts[i];
|
||||||
|
if (port)
|
||||||
|
{
|
||||||
|
jack_port_unregister(client, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputPorts.clear();
|
||||||
|
for (size_t i = 0; i < this->midiInputPorts.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = midiInputPorts[i];
|
||||||
|
if (port)
|
||||||
|
{
|
||||||
|
jack_port_unregister(client, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
midiInputPorts.resize(0);
|
||||||
|
if (client)
|
||||||
|
{
|
||||||
|
jack_client_close(client);
|
||||||
|
client = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual float CpuOverhead() { return 0; }
|
||||||
|
virtual float CpuUse()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (client)
|
||||||
|
{
|
||||||
|
return jack_cpu_load(this->client);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_fn(jack_nframes_t nframes, void *arg)
|
||||||
|
{
|
||||||
|
((AudioDriverHost *)arg)->OnProcess(nframes);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSessionCallback(jack_session_event_t *event)
|
||||||
|
{
|
||||||
|
#if JACK_SESSION_CALLBACK // deprecated and not actually useful.
|
||||||
|
char retval[100];
|
||||||
|
Lv2Log::info("path %s, uuid %s, type: %s\n", event->session_dir, event->client_uuid, event->type == JackSessionSave ? "save" : "quit");
|
||||||
|
|
||||||
|
snprintf(retval, 100, "jack_simple_session_client %s", event->client_uuid);
|
||||||
|
event->command_line = strdup(retval);
|
||||||
|
|
||||||
|
jack_session_reply(client, event);
|
||||||
|
|
||||||
|
if (event->type == JackSessionSaveAndQuit)
|
||||||
|
{
|
||||||
|
// simple_quit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
jack_session_event_free(event);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
session_callback_fn(jack_session_event_t *event, void *arg)
|
||||||
|
{
|
||||||
|
((JackDriverImpl *)arg)->OnSessionCallback(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioDriver* CreateJackDriver(AudioDriverHost*driverHost)
|
||||||
|
{
|
||||||
|
return new JackDriverImpl(driverHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int JackDriverImpl::jackInstanceId = 0;
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AudioDriver.hpp"
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#if JACK_HOST
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
AudioDriver* CreateJackDriver(AudioDriverHost*driverHost);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
+94
-323
@@ -21,12 +21,15 @@
|
|||||||
|
|
||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
|
|
||||||
|
#include "JackDriver.hpp"
|
||||||
|
#include "AlsaDriver.hpp"
|
||||||
|
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
#include <jack/jack.h>
|
#include "AudioConfig.hpp"
|
||||||
#include <jack/types.h>
|
|
||||||
#include <jack/session.h>
|
|
||||||
#include <jack/midiport.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -63,11 +66,6 @@ using namespace pipedal;
|
|||||||
|
|
||||||
const int MIDI_LV2_BUFFER_SIZE = 16 * 1024;
|
const int MIDI_LV2_BUFFER_SIZE = 16 * 1024;
|
||||||
|
|
||||||
namespace pipedal
|
|
||||||
{
|
|
||||||
// private import from JackConfiguration.cpp
|
|
||||||
std::string GetJackErrorMessage(jack_status_t status);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GetCpuFrequency(uint64_t*freqMin,uint64_t*freqMax)
|
static void GetCpuFrequency(uint64_t*freqMin,uint64_t*freqMax)
|
||||||
{
|
{
|
||||||
@@ -105,9 +103,10 @@ static std::string GetGovernor()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class JackHostImpl : public JackHost
|
class JackHostImpl : public JackHost, private AudioDriverHost
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
AudioDriver*audioDriver = nullptr;
|
||||||
|
|
||||||
inherit_priority_recursive_mutex mutex;
|
inherit_priority_recursive_mutex mutex;
|
||||||
int64_t overrunGracePeriodSamples = 0;
|
int64_t overrunGracePeriodSamples = 0;
|
||||||
@@ -133,36 +132,26 @@ private:
|
|||||||
|
|
||||||
JackChannelSelection channelSelection;
|
JackChannelSelection channelSelection;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
jack_client_t *client = nullptr;
|
|
||||||
|
|
||||||
std::shared_ptr<Lv2PedalBoard> currentPedalBoard;
|
std::shared_ptr<Lv2PedalBoard> currentPedalBoard;
|
||||||
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<jack_port_t *> inputPorts;
|
|
||||||
std::vector<jack_port_t *> outputPorts;
|
|
||||||
std::vector<jack_port_t *> midiInputPorts;
|
|
||||||
|
|
||||||
std::vector<uint8_t *> midiLv2Buffers;
|
std::vector<uint8_t *> midiLv2Buffers;
|
||||||
|
|
||||||
uint32_t sampleRate;
|
uint32_t sampleRate = 0;
|
||||||
uint64_t currentSample = 0;
|
uint64_t currentSample = 0;
|
||||||
|
|
||||||
std::atomic<uint64_t> underruns = 0;
|
std::atomic<uint64_t> underruns = 0;
|
||||||
std::atomic<std::chrono::system_clock::time_point> lastUnderrunTime =
|
std::atomic<std::chrono::system_clock::time_point> lastUnderrunTime =
|
||||||
std::chrono::system_clock::from_time_t(0);
|
std::chrono::system_clock::from_time_t(0);
|
||||||
|
|
||||||
int XrunCallback()
|
virtual void OnUnderrun()
|
||||||
{
|
{
|
||||||
++this->underruns;
|
++this->underruns;
|
||||||
this->lastUnderrunTime = std::chrono::system_clock ::now();
|
this->lastUnderrunTime = std::chrono::system_clock ::now();
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xrun_callback_fn(void *arg)
|
|
||||||
{
|
|
||||||
return ((JackHostImpl *)arg)->XrunCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Close()
|
virtual void Close()
|
||||||
{
|
{
|
||||||
@@ -174,84 +163,13 @@ private:
|
|||||||
StopReaderThread();
|
StopReaderThread();
|
||||||
if (active)
|
if (active)
|
||||||
{
|
{
|
||||||
// disconnect ports.
|
audioDriver->Deactivate();
|
||||||
for (size_t i = 0; i < this->inputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto port = inputPorts[i];
|
|
||||||
if (port != nullptr)
|
|
||||||
{
|
|
||||||
jack_disconnect(client, channelSelection.GetInputAudioPorts()[i].c_str(), jack_port_name(port));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < outputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto port = outputPorts[i];
|
|
||||||
if (port != nullptr)
|
|
||||||
{
|
|
||||||
jack_disconnect(client, jack_port_name(port),channelSelection.getOutputAudioPorts()[i].c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < midiInputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto port = midiInputPorts[i];
|
|
||||||
if (port != nullptr)
|
|
||||||
{
|
|
||||||
jack_disconnect(client, channelSelection.GetInputMidiPorts()[i].c_str(), jack_port_name(port));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_deactivate(client);
|
|
||||||
|
|
||||||
jack_set_process_callback(client, nullptr, nullptr);
|
|
||||||
jack_on_shutdown(client, nullptr, nullptr);
|
|
||||||
#if JACK_SESSION_CALLBACK // (deprecated, and not actually useful)
|
|
||||||
jack_set_session_callback(client, nullptr, nullptr);
|
|
||||||
#endif
|
|
||||||
jack_set_xrun_callback(client, nullptr, nullptr);
|
|
||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
// unregister ports.
|
|
||||||
for (size_t i = 0; i < this->inputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto port = inputPorts[i];
|
|
||||||
if (port)
|
|
||||||
{
|
|
||||||
jack_port_unregister(client, port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputPorts.clear();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < this->outputPorts.size(); ++i)
|
audioDriver->Close();
|
||||||
{
|
|
||||||
auto port = outputPorts[i];
|
|
||||||
if (port)
|
|
||||||
{
|
|
||||||
jack_port_unregister(client, port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outputPorts.clear();
|
|
||||||
for (size_t i = 0; i < this->midiInputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto port = midiInputPorts[i];
|
|
||||||
if (port)
|
|
||||||
{
|
|
||||||
jack_port_unregister(client, port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
midiInputPorts.resize(0);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < midiLv2Buffers.size(); ++i)
|
|
||||||
{
|
|
||||||
delete[] midiLv2Buffers[i];
|
|
||||||
}
|
|
||||||
midiLv2Buffers.resize(0);
|
|
||||||
|
|
||||||
if (client)
|
|
||||||
{
|
|
||||||
jack_client_close(client);
|
|
||||||
client = nullptr;
|
|
||||||
}
|
|
||||||
// release any pdealboards owned by the process thread.
|
// release any pdealboards owned by the process thread.
|
||||||
this->activePedalBoards.resize(0);
|
this->activePedalBoards.resize(0);
|
||||||
this->realtimeActivePedalBoard = nullptr;
|
this->realtimeActivePedalBoard = nullptr;
|
||||||
@@ -268,20 +186,32 @@ private:
|
|||||||
delete realtimeMonitorPortSubscriptions;
|
delete realtimeMonitorPortSubscriptions;
|
||||||
realtimeVuBuffers = nullptr;
|
realtimeVuBuffers = nullptr;
|
||||||
}
|
}
|
||||||
|
this->inputRingBuffer.reset();
|
||||||
|
this->outputRingBuffer.reset();
|
||||||
|
|
||||||
|
|
||||||
|
for (size_t i = 0; i < midiLv2Buffers.size(); ++i)
|
||||||
|
{
|
||||||
|
delete[] midiLv2Buffers[i];
|
||||||
|
}
|
||||||
|
midiLv2Buffers.resize(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZeroBuffer(float *buffer, jack_nframes_t nframes)
|
void ZeroBuffer(float *buffer, size_t nframes)
|
||||||
{
|
{
|
||||||
for (jack_nframes_t i = 0; i < nframes; ++i)
|
for (size_t i = 0; i < nframes; ++i)
|
||||||
{
|
{
|
||||||
buffer[i] = 0;
|
buffer[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ZeroOutputBuffers(jack_nframes_t nframes)
|
Lv2EventBufferUrids eventBufferUrids;
|
||||||
|
|
||||||
|
void ZeroOutputBuffers(size_t nframes)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < this->outputPorts.size(); ++i)
|
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i)
|
||||||
{
|
{
|
||||||
float *out = (float *)jack_port_get_buffer(this->outputPorts[i], nframes);
|
float *out = (float *)audioDriver->GetOutputBuffer(i, nframes);
|
||||||
if (out)
|
if (out)
|
||||||
{
|
{
|
||||||
ZeroBuffer(out, nframes);
|
ZeroBuffer(out, nframes);
|
||||||
@@ -487,22 +417,26 @@ private:
|
|||||||
{
|
{
|
||||||
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids);
|
Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids);
|
||||||
|
|
||||||
for (int i = 0; i < this->midiInputPorts.size(); ++i)
|
size_t midiInputBufferCount = audioDriver->MidiInputBufferCount();
|
||||||
|
|
||||||
|
audioDriver->FillMidiBuffers();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < midiInputBufferCount; ++i)
|
||||||
{
|
{
|
||||||
jack_port_t *port = midiInputPorts[i];
|
|
||||||
void *portBuffer = jack_port_get_buffer(midiInputPorts[i], 0);
|
void *portBuffer = audioDriver->GetMidiInputBuffer(i,0);
|
||||||
if (portBuffer)
|
if (portBuffer)
|
||||||
{
|
{
|
||||||
uint8_t *lv2Buffer = this->midiLv2Buffers[i];
|
uint8_t *lv2Buffer = this->midiLv2Buffers[i];
|
||||||
jack_nframes_t n = jack_midi_get_event_count(portBuffer);
|
size_t n = audioDriver->GetMidiInputEventCount(portBuffer);
|
||||||
|
|
||||||
eventBufferWriter.Reset(lv2Buffer, MIDI_LV2_BUFFER_SIZE);
|
eventBufferWriter.Reset(lv2Buffer, MIDI_LV2_BUFFER_SIZE);
|
||||||
auto iterator = eventBufferWriter.begin();
|
auto iterator = eventBufferWriter.begin();
|
||||||
|
|
||||||
for (jack_nframes_t frame = 0; frame < n; ++frame)
|
for (size_t frame = 0; frame < n; ++frame)
|
||||||
{
|
{
|
||||||
jack_midi_event_t event;
|
MidiEvent event;
|
||||||
if (jack_midi_event_get(&event, portBuffer, frame) == 0)
|
if (audioDriver->GetMidiInputEvent(&event,portBuffer,frame))
|
||||||
{
|
{
|
||||||
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
||||||
|
|
||||||
@@ -528,11 +462,26 @@ private:
|
|||||||
|
|
||||||
#define RESET_XRUN_SAMPLES 22050ul // 1/2 a second-ish.
|
#define RESET_XRUN_SAMPLES 22050ul // 1/2 a second-ish.
|
||||||
|
|
||||||
int OnProcess(jack_nframes_t nframes)
|
std::mutex audioStoppedMutex;
|
||||||
|
|
||||||
|
bool IsAudioActive()
|
||||||
|
{
|
||||||
|
std::lock_guard lock { audioStoppedMutex};
|
||||||
|
return this->active;
|
||||||
|
}
|
||||||
|
virtual void OnAudioStopped()
|
||||||
|
{
|
||||||
|
std::lock_guard lock { audioStoppedMutex};
|
||||||
|
this->active = false;
|
||||||
|
Lv2Log::info("Audio stopped.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnProcess(size_t nframes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
jack_default_audio_sample_t *in, *out;
|
float *in, *out;
|
||||||
|
|
||||||
pParameterRequests = nullptr;
|
pParameterRequests = nullptr;
|
||||||
|
|
||||||
@@ -547,9 +496,9 @@ private:
|
|||||||
float *inputBuffers[4];
|
float *inputBuffers[4];
|
||||||
float *outputBuffers[4];
|
float *outputBuffers[4];
|
||||||
bool buffersValid = true;
|
bool buffersValid = true;
|
||||||
for (int i = 0; i < inputPorts.size(); ++i)
|
for (int i = 0; i < audioDriver->InputBufferCount(); ++i)
|
||||||
{
|
{
|
||||||
float *input = (float *)jack_port_get_buffer(inputPorts[i], nframes);
|
float *input = (float *)audioDriver->GetInputBuffer(i, nframes);
|
||||||
if (input == nullptr)
|
if (input == nullptr)
|
||||||
{
|
{
|
||||||
buffersValid = false;
|
buffersValid = false;
|
||||||
@@ -557,11 +506,11 @@ private:
|
|||||||
}
|
}
|
||||||
inputBuffers[i] = input;
|
inputBuffers[i] = input;
|
||||||
}
|
}
|
||||||
inputBuffers[inputPorts.size()] = nullptr;
|
inputBuffers[audioDriver->InputBufferCount()] = nullptr;
|
||||||
|
|
||||||
for (int i = 0; i < outputPorts.size(); ++i)
|
for (int i = 0; i < audioDriver->OutputBufferCount(); ++i)
|
||||||
{
|
{
|
||||||
float *output = (float *)jack_port_get_buffer(outputPorts[i], nframes);
|
float *output = audioDriver->GetOutputBuffer(i,nframes);
|
||||||
if (output == nullptr)
|
if (output == nullptr)
|
||||||
{
|
{
|
||||||
buffersValid = false;
|
buffersValid = false;
|
||||||
@@ -569,7 +518,7 @@ private:
|
|||||||
}
|
}
|
||||||
outputBuffers[i] = output;
|
outputBuffers[i] = output;
|
||||||
}
|
}
|
||||||
outputBuffers[outputPorts.size()] = nullptr;
|
outputBuffers[audioDriver->OutputBufferCount()] = nullptr;
|
||||||
|
|
||||||
if (buffersValid)
|
if (buffersValid)
|
||||||
{
|
{
|
||||||
@@ -626,61 +575,9 @@ private:
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_fn(jack_nframes_t nframes, void *arg)
|
|
||||||
{
|
|
||||||
return ((JackHostImpl *)arg)->OnProcess(nframes);
|
|
||||||
}
|
|
||||||
void OnShutdown()
|
|
||||||
{
|
|
||||||
Lv2Log::info("Jack Audio Server has shut down.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
jack_shutdown_fn(void *arg)
|
|
||||||
{
|
|
||||||
((JackHostImpl *)arg)->OnShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnSessionCallback(jack_session_event_t *event)
|
|
||||||
{
|
|
||||||
#if JACK_SESSION_CALLBACK // deprecated and not actually useful.
|
|
||||||
char retval[100];
|
|
||||||
Lv2Log::info("path %s, uuid %s, type: %s\n", event->session_dir, event->client_uuid, event->type == JackSessionSave ? "save" : "quit");
|
|
||||||
|
|
||||||
snprintf(retval, 100, "jack_simple_session_client %s", event->client_uuid);
|
|
||||||
event->command_line = strdup(retval);
|
|
||||||
|
|
||||||
jack_session_reply(client, event);
|
|
||||||
|
|
||||||
if (event->type == JackSessionSaveAndQuit)
|
|
||||||
{
|
|
||||||
// simple_quit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_session_event_free(event);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void jack_error_fn(const char *msg)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Jack - %s", msg);
|
|
||||||
}
|
|
||||||
static void jack_info_fn(const char *msg)
|
|
||||||
{
|
|
||||||
Lv2Log::info("Jack - %s", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
session_callback_fn(jack_session_event_t *event, void *arg)
|
|
||||||
{
|
|
||||||
((JackHostImpl *)arg)->OnSessionCallback(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
Lv2EventBufferUrids eventBufferUrids;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JackHostImpl(IHost *pHost)
|
JackHostImpl(IHost *pHost)
|
||||||
: inputRingBuffer(RING_BUFFER_SIZE),
|
: inputRingBuffer(RING_BUFFER_SIZE),
|
||||||
@@ -691,22 +588,29 @@ public:
|
|||||||
hostWriter(&this->inputRingBuffer),
|
hostWriter(&this->inputRingBuffer),
|
||||||
eventBufferUrids(pHost)
|
eventBufferUrids(pHost)
|
||||||
{
|
{
|
||||||
jack_set_error_function(jack_error_fn);
|
|
||||||
jack_set_info_function(jack_info_fn);
|
#if JACK_HOST
|
||||||
|
audioDriver = CreateJackDriver(this);
|
||||||
|
#endif
|
||||||
|
#if ALSA_HOST
|
||||||
|
audioDriver = CreateAlsaDriver(this);
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
virtual ~JackHostImpl()
|
virtual ~JackHostImpl()
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
CleanRestartThreads(true);
|
CleanRestartThreads(true);
|
||||||
jack_set_error_function(nullptr);
|
|
||||||
jack_set_info_function(nullptr);
|
delete audioDriver;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual JackConfiguration GetServerConfiguration()
|
virtual JackConfiguration GetServerConfiguration()
|
||||||
{
|
{
|
||||||
JackConfiguration result;
|
JackConfiguration result;
|
||||||
|
|
||||||
result.Initialize();
|
result.JackInitialize();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,14 +884,13 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int jackInstanceId;
|
|
||||||
|
|
||||||
virtual void Open(const JackChannelSelection &channelSelection)
|
virtual void Open(const JackServerSettings &jackServerSettings,const JackChannelSelection &channelSelection)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
if (channelSelection.GetInputAudioPorts().size() == 0
|
if (channelSelection.GetInputAudioPorts().size() == 0
|
||||||
|| channelSelection.getOutputAudioPorts().size() == 0)
|
|| channelSelection.GetOutputAudioPorts().size() == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1008,162 +911,32 @@ public:
|
|||||||
|
|
||||||
StartReaderThread();
|
StartReaderThread();
|
||||||
|
|
||||||
jack_status_t status;
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
// need a unique instance name every timme.
|
|
||||||
std::stringstream s;
|
|
||||||
s << "PiPedal";
|
|
||||||
if (jackInstanceId != 0) {
|
|
||||||
s << jackInstanceId;
|
|
||||||
}
|
|
||||||
++jackInstanceId;
|
|
||||||
|
|
||||||
std::string instanceName = s.str();
|
audioDriver->Open(jackServerSettings,this->channelSelection);
|
||||||
|
|
||||||
client = jack_client_open(instanceName.c_str(), JackNullOption, &status);
|
this->sampleRate = audioDriver->GetSampleRate();
|
||||||
|
|
||||||
if (client == nullptr || status & JackFailure)
|
|
||||||
{
|
|
||||||
if (client)
|
|
||||||
{
|
|
||||||
jack_client_close(client);
|
|
||||||
client = nullptr;
|
|
||||||
}
|
|
||||||
std::string error = GetJackErrorMessage(status);
|
|
||||||
Lv2Log::error(error);
|
|
||||||
throw PiPedalStateException(error.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status & JackServerStarted)
|
|
||||||
{
|
|
||||||
Lv2Log::info("Jack server started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
jack_set_process_callback(client, process_fn, this);
|
|
||||||
|
|
||||||
jack_on_shutdown(client, jack_shutdown_fn, this);
|
|
||||||
|
|
||||||
#if JACK_SESSION_CALLBACK
|
|
||||||
jack_set_session_callback(client, session_callback_fn, NULL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
jack_set_xrun_callback(client, xrun_callback_fn, this);
|
|
||||||
|
|
||||||
this->sampleRate = jack_get_sample_rate(client);
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
auto &selectedInputPorts = channelSelection.GetInputAudioPorts();
|
|
||||||
this->inputPorts.clear();
|
|
||||||
this->outputPorts.clear();
|
|
||||||
this->midiInputPorts.clear();
|
|
||||||
|
|
||||||
this->inputPorts.resize(selectedInputPorts.size());
|
midiLv2Buffers.resize(audioDriver->MidiInputBufferCount());
|
||||||
for (int i = 0; i < selectedInputPorts.size(); ++i)
|
for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i)
|
||||||
{
|
|
||||||
std::stringstream name;
|
|
||||||
name << "input" << (i + 1);
|
|
||||||
this->inputPorts[i] =
|
|
||||||
jack_port_register(
|
|
||||||
client, name.str().c_str(),
|
|
||||||
JACK_DEFAULT_AUDIO_TYPE,
|
|
||||||
JackPortIsInput, 0);
|
|
||||||
if (this->inputPorts[i] == nullptr)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Can't allocate Jack Audio input ports.");
|
|
||||||
throw PiPedalStateException("Failed to allocate Jack Audio port.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto &selectedOutputPorts = channelSelection.getOutputAudioPorts();
|
|
||||||
this->outputPorts.resize(selectedOutputPorts.size());
|
|
||||||
for (int i = 0; i < selectedOutputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
std::stringstream name;
|
|
||||||
name << "output" << (i + 1);
|
|
||||||
this->outputPorts[i] =
|
|
||||||
jack_port_register(client, name.str().c_str(),
|
|
||||||
JACK_DEFAULT_AUDIO_TYPE,
|
|
||||||
JackPortIsOutput, 0);
|
|
||||||
if (this->outputPorts[i] == nullptr)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Can't allocate Jack Audio output port.");
|
|
||||||
throw PiPedalStateException("Failed to allocate Jack Audio port.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto &selectedMidiPorts = channelSelection.GetInputMidiPorts();
|
|
||||||
|
|
||||||
midiLv2Buffers.resize(selectedMidiPorts.size());
|
|
||||||
for (size_t i = 0; i < selectedMidiPorts.size(); ++i)
|
|
||||||
{
|
{
|
||||||
midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE);
|
midiLv2Buffers[i] = AllocateRealtimeBuffer(MIDI_LV2_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->midiInputPorts.resize(selectedMidiPorts.size());
|
|
||||||
for (int i = 0; i < selectedMidiPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
std::stringstream name;
|
|
||||||
name << "midiIn" << (i + 1);
|
|
||||||
this->midiInputPorts[i] =
|
|
||||||
jack_port_register(client, name.str().c_str(),
|
|
||||||
JACK_DEFAULT_MIDI_TYPE,
|
|
||||||
JackPortIsInput, 0);
|
|
||||||
if (this->midiInputPorts[i] == nullptr)
|
|
||||||
{
|
|
||||||
std::stringstream s;
|
|
||||||
s << "can't register Jack port " << name.str().c_str();
|
|
||||||
Lv2Log::error(s.str());
|
|
||||||
throw PiPedalStateException(s.str().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int activateResult = jack_activate(client);
|
active = true;
|
||||||
if (activateResult == 0)
|
audioDriver->Activate();
|
||||||
{
|
Lv2Log::info("Audio started.");
|
||||||
active = true;
|
|
||||||
|
|
||||||
for (int i = 0; i < selectedInputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto result = jack_connect(client, selectedInputPorts[i].c_str(), jack_port_name(this->inputPorts[i]));
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Can't connect input port %s", selectedInputPorts[i].c_str());
|
|
||||||
throw PiPedalStateException("Jack Audio port connection failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < selectedOutputPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto result = jack_connect(client, jack_port_name(this->outputPorts[i]), selectedOutputPorts[i].c_str());
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Can't connect output port %s", selectedOutputPorts[i].c_str());
|
|
||||||
throw PiPedalStateException("Jack Audio port connection failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < selectedMidiPorts.size(); ++i)
|
|
||||||
{
|
|
||||||
auto result = jack_connect(client, selectedMidiPorts[i].c_str(), jack_port_name(this->midiInputPorts[i]));
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
Lv2Log::error("Can't connect midi input port %s", selectedMidiPorts[i].c_str());
|
|
||||||
throw PiPedalStateException("Jack Audio midi port connection failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lv2Log::info("Jack configuration complete.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Lv2Log::error("Failed to activate Jack Audio client. (%d)", (int)activateResult);
|
|
||||||
throw PiPedalStateException("Failed to activate Jack Audio client.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (PiPedalException &e)
|
catch (PiPedalException &e)
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
|
active = false;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1366,6 +1139,8 @@ private:
|
|||||||
AdminClient client;
|
AdminClient client;
|
||||||
|
|
||||||
client.SetJackServerConfiguration(jackServerSettings);
|
client.SetJackServerConfiguration(jackServerSettings);
|
||||||
|
|
||||||
|
|
||||||
//this_->Open(this_->channelSelection);
|
//this_->Open(this_->channelSelection);
|
||||||
this_->restarting = false;
|
this_->restarting = false;
|
||||||
onComplete(true, "");
|
onComplete(true, "");
|
||||||
@@ -1468,16 +1243,12 @@ public:
|
|||||||
|
|
||||||
result.temperaturemC_ = GetRaspberryPiTemperature();
|
result.temperaturemC_ = GetRaspberryPiTemperature();
|
||||||
|
|
||||||
result.active_ = this->active;
|
result.active_ = IsAudioActive();
|
||||||
result.restarting_ = this->restarting;
|
result.restarting_ = this->restarting;
|
||||||
|
|
||||||
if (client != nullptr)
|
if (this->audioDriver != nullptr)
|
||||||
{
|
{
|
||||||
result.cpuUsage_ = jack_cpu_load(this->client);
|
result.cpuUsage_ = audioDriver->CpuUse();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.cpuUsage_ = 0;
|
|
||||||
}
|
}
|
||||||
GetCpuFrequency(&result.cpuFreqMax_,&result.cpuFreqMin_);
|
GetCpuFrequency(&result.cpuFreqMax_,&result.cpuFreqMin_);
|
||||||
result.governor_ = GetGovernor();
|
result.governor_ = GetGovernor();
|
||||||
@@ -1497,7 +1268,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int JackHostImpl::jackInstanceId = 0;
|
|
||||||
|
|
||||||
JackHost *JackHost::CreateInstance(IHost *pHost)
|
JackHost *JackHost::CreateInstance(IHost *pHost)
|
||||||
{
|
{
|
||||||
@@ -1506,6 +1276,7 @@ JackHost *JackHost::CreateInstance(IHost *pHost)
|
|||||||
|
|
||||||
JSON_MAP_BEGIN(JackHostStatus)
|
JSON_MAP_BEGIN(JackHostStatus)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, active)
|
JSON_MAP_REFERENCE(JackHostStatus, active)
|
||||||
|
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, restarting)
|
JSON_MAP_REFERENCE(JackHostStatus, restarting)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, underruns)
|
JSON_MAP_REFERENCE(JackHostStatus, underruns)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, cpuUsage)
|
JSON_MAP_REFERENCE(JackHostStatus, cpuUsage)
|
||||||
|
|||||||
+2
-1
@@ -106,6 +106,7 @@ public:
|
|||||||
class JackHostStatus {
|
class JackHostStatus {
|
||||||
public:
|
public:
|
||||||
bool active_;
|
bool active_;
|
||||||
|
std::string errorMessage_;
|
||||||
bool restarting_;
|
bool restarting_;
|
||||||
uint64_t underruns_;
|
uint64_t underruns_;
|
||||||
float cpuUsage_ = 0;
|
float cpuUsage_ = 0;
|
||||||
@@ -139,7 +140,7 @@ public:
|
|||||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||||
|
|
||||||
|
|
||||||
virtual void Open(const JackChannelSelection & channelSelection) = 0;
|
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
||||||
virtual uint32_t GetSampleRate() = 0;
|
virtual uint32_t GetSampleRate() = 0;
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ void JackServerSettings::ReadJackConfiguration()
|
|||||||
|
|
||||||
void JackServerSettings::Write()
|
void JackServerSettings::Write()
|
||||||
{
|
{
|
||||||
|
#if JACK_HOST
|
||||||
this->valid_ = false;
|
this->valid_ = false;
|
||||||
|
|
||||||
std::vector<std::string> precedingLines;
|
std::vector<std::string> precedingLines;
|
||||||
@@ -232,6 +233,9 @@ void JackServerSettings::Write()
|
|||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(JackServerSettings)
|
JSON_MAP_BEGIN(JackServerSettings)
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ namespace pipedal {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
JackServerSettings();
|
JackServerSettings();
|
||||||
|
JackServerSettings(const std::string alsaDevice, uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
||||||
|
: valid_(true),
|
||||||
|
alsaDevice_(alsaDevice),
|
||||||
|
sampleRate_(sampleRate),
|
||||||
|
bufferSize_(bufferSize),
|
||||||
|
numberOfBuffers_(numberOfBuffers)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t GetSampleRate() const { return sampleRate_; }
|
uint64_t GetSampleRate() const { return sampleRate_; }
|
||||||
uint32_t GetBufferSize() const { return bufferSize_; }
|
uint32_t GetBufferSize() const { return bufferSize_; }
|
||||||
|
|||||||
+1
-1
@@ -266,7 +266,7 @@ void Lv2Host::OnConfigurationChanged(const JackConfiguration &configuration, con
|
|||||||
if (configuration.isValid())
|
if (configuration.isValid())
|
||||||
{
|
{
|
||||||
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
||||||
this->numberOfAudioOutputChannels = settings.getOutputAudioPorts().size();
|
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
|
||||||
this->maxBufferSize = configuration.GetBlockLength();
|
this->maxBufferSize = configuration.GetBlockLength();
|
||||||
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize());
|
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize());
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -31,9 +31,12 @@ using namespace pipedal;
|
|||||||
|
|
||||||
|
|
||||||
static auto timeZero = std::chrono::system_clock::now();
|
static auto timeZero = std::chrono::system_clock::now();
|
||||||
|
bool Lv2Log::show_time_ = true;
|
||||||
|
|
||||||
std::string timeTag()
|
std::string timeTag()
|
||||||
{
|
{
|
||||||
|
if (!Lv2Log::show_time()) return "";
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
auto t = std::chrono::system_clock::now()- timeZero;
|
auto t = std::chrono::system_clock::now()- timeZero;
|
||||||
@@ -47,7 +50,7 @@ std::string timeTag()
|
|||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
s << setfill('0') << setw(2) << hours_ << ':' << setw(2) << minutes_ << ':' << setw(2) << seconds_ << "." << setw(3) << milliseconds_;
|
s << setfill('0') << setw(2) << hours_ << ':' << setw(2) << minutes_ << ':' << setw(2) << seconds_ << "." << setw(3) << milliseconds_ << " ";
|
||||||
return s.str();
|
return s.str();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -61,23 +64,23 @@ class StdErrLogger : public Lv2Logger
|
|||||||
virtual void onError(const char* message)
|
virtual void onError(const char* message)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m);
|
std::lock_guard lock(m);
|
||||||
std::cerr << timeTag() << " ERR: " << message << std::endl;
|
std::cerr << timeTag() << "ERR: " << message << std::endl;
|
||||||
}
|
}
|
||||||
virtual void onWarning(const char* message)
|
virtual void onWarning(const char* message)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m);
|
std::lock_guard lock(m);
|
||||||
std::cerr << timeTag() << " WRN: " << message << std::endl;
|
std::cerr << timeTag() << "WRN: " << message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void onDebug(const char* message)
|
virtual void onDebug(const char* message)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m);
|
std::lock_guard lock(m);
|
||||||
std::cerr << timeTag() << " DBG: " << message << std::endl;
|
std::cerr << timeTag() << "DBG: " << message << std::endl;
|
||||||
}
|
}
|
||||||
virtual void onInfo(const char* message)
|
virtual void onInfo(const char* message)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m);
|
std::lock_guard lock(m);
|
||||||
std::cerr << timeTag() << " INF: " << message << std::endl;
|
std::cerr << timeTag() << "INF: " << message << std::endl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ namespace pipedal
|
|||||||
class Lv2Log
|
class Lv2Log
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static Lv2Logger *logger_;
|
static Lv2Logger *logger_;
|
||||||
static LogLevel log_level_;
|
static LogLevel log_level_;
|
||||||
|
static bool show_time_;
|
||||||
|
|
||||||
static void v_error(const char *format, va_list arglist);
|
static void v_error(const char *format, va_list arglist);
|
||||||
static void v_warning(const char *format, va_list arglist);
|
static void v_warning(const char *format, va_list arglist);
|
||||||
@@ -68,6 +70,13 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
return Lv2Log::log_level_;
|
return Lv2Log::log_level_;
|
||||||
}
|
}
|
||||||
|
static void show_time(bool show)
|
||||||
|
{
|
||||||
|
show_time_ = show;
|
||||||
|
}
|
||||||
|
static bool show_time() {
|
||||||
|
return show_time_;
|
||||||
|
}
|
||||||
|
|
||||||
// static void error(const char* message) {
|
// static void error(const char* message) {
|
||||||
// if (Lv2Log::log_level_ >= LogLevel::Error)
|
// if (Lv2Log::log_level_ >= LogLevel::Error)
|
||||||
|
|||||||
@@ -0,0 +1,474 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CommandLineParser.hpp"
|
||||||
|
#include "PrettyPrinter.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include "PiPedalAlsa.hpp"
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
#include <mutex>
|
||||||
|
#include "AlsaDriver.hpp"
|
||||||
|
#include <iomanip>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
constexpr int E_NOSIGNAL = 1;
|
||||||
|
constexpr int E_OPEN_FAILURE = 2;
|
||||||
|
constexpr int E_XRUN = 3;
|
||||||
|
|
||||||
|
constexpr uint64_t NO_SIGNAL_VALUE = 0x7000000;
|
||||||
|
|
||||||
|
struct TestResult {
|
||||||
|
int error = 0;
|
||||||
|
uint64_t latency = 0;
|
||||||
|
float cpuOverhead = 0;
|
||||||
|
};
|
||||||
|
void PrintHelp()
|
||||||
|
{
|
||||||
|
PrettyPrinter pp;
|
||||||
|
pp.width(160);
|
||||||
|
|
||||||
|
pp << "PiPedal Latency Tester\n";
|
||||||
|
pp << "Copyright (c) 2022 Robin Davies\n";
|
||||||
|
pp << "\n";
|
||||||
|
pp << Indent(0) << "Syntax\n\n";
|
||||||
|
pp << Indent(4) << "pipedal_latency_test [<options>] <device-name>\n\n";
|
||||||
|
pp << Indent(0) << "Options\n\n";
|
||||||
|
pp << Indent(15);
|
||||||
|
|
||||||
|
pp << HangingIndent() << " -l --list\t"
|
||||||
|
<< "List available devices.\n\n";
|
||||||
|
pp << HangingIndent() << " -r --rate\t"
|
||||||
|
<< "Sample rate (default 48000).\n\n";
|
||||||
|
pp << HangingIndent() << " -h --help\t"
|
||||||
|
<< "Display this message.\n\n";
|
||||||
|
|
||||||
|
pp << Indent(0) << "Remarks\n\n";
|
||||||
|
pp << Indent(4);
|
||||||
|
pp << "PiPedal Latency Tester measure actual audio latency from output to input of an ALSA device. "
|
||||||
|
<< "To run a latency test, you must connect an audio cable from left (first) output of the device "
|
||||||
|
"under test to the left (first) input of the device under test.\n\n"
|
||||||
|
|
||||||
|
<< "PiPedal Latency Tester will measure the time it takes for a signal to propagate from output to input.\n\n"
|
||||||
|
|
||||||
|
<< "The tests run over a variety of buffer sizes. A nominal compute load is provided in order to put some "
|
||||||
|
"stress on the audio system.\n\n"
|
||||||
|
|
||||||
|
<< "You may need to stop the pipedald audio service in order to access the ALSA device:\n\n"
|
||||||
|
<< Indent(8) << "sudo systemctl stop pipedald\n\n";
|
||||||
|
pp << Indent(0) << "Examples\n\n";
|
||||||
|
pp << Indent(4) << "pipedal_latency_test --list\n\n";
|
||||||
|
pp << Indent(4) << "pipedal_latency_test M2\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDevices()
|
||||||
|
{
|
||||||
|
PiPedalAlsaDevices alsaDevices;
|
||||||
|
auto devices = alsaDevices.GetAlsaDevices();
|
||||||
|
|
||||||
|
PrettyPrinter pp;
|
||||||
|
if (devices.size() == 0)
|
||||||
|
{
|
||||||
|
pp << "No devices found.\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pp << Indent(0);
|
||||||
|
pp << "Alsa Devices\n";
|
||||||
|
pp << Indent(15);
|
||||||
|
|
||||||
|
for (auto &device : devices)
|
||||||
|
{
|
||||||
|
pp << HangingIndent() << (device.id_) << "\t"
|
||||||
|
<< device.longName_ << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlsaTester : private AudioDriverHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class TestType
|
||||||
|
{
|
||||||
|
Oscillator,
|
||||||
|
LatencyMonitor,
|
||||||
|
NullTest
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioDriver *audioDriver = nullptr;
|
||||||
|
|
||||||
|
const std::string &deviceId;
|
||||||
|
uint32_t sampleRate;
|
||||||
|
int bufferSize;
|
||||||
|
int buffers;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AlsaTester(const std::string &deviceId, uint32_t sampleRate, int bufferSize, int buffers)
|
||||||
|
: deviceId(deviceId),
|
||||||
|
sampleRate(sampleRate),
|
||||||
|
bufferSize(bufferSize),
|
||||||
|
buffers(buffers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~AlsaTester()
|
||||||
|
{
|
||||||
|
delete audioDriver;
|
||||||
|
delete[] inputBuffers;
|
||||||
|
delete[] outputBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult Test()
|
||||||
|
{
|
||||||
|
TestResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JackServerSettings serverSettings(deviceId, sampleRate, bufferSize, buffers);
|
||||||
|
|
||||||
|
JackConfiguration jackConfiguration;
|
||||||
|
jackConfiguration.AlsaInitialize(serverSettings);
|
||||||
|
|
||||||
|
JackChannelSelection channelSelection(
|
||||||
|
jackConfiguration.GetInputAudioPorts(),
|
||||||
|
jackConfiguration.GetOutputAudioPorts(),
|
||||||
|
std::vector<AlsaMidiDeviceInfo>());
|
||||||
|
|
||||||
|
audioDriver = CreateAlsaDriver(this);
|
||||||
|
|
||||||
|
latencyMonitor.Init(jackConfiguration.GetSampleRate());
|
||||||
|
audioDriver->Open(serverSettings, channelSelection);
|
||||||
|
|
||||||
|
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
|
||||||
|
outputBuffers = new float *[channelSelection.GetOutputAudioPorts().size()];
|
||||||
|
|
||||||
|
audioDriver->Activate();
|
||||||
|
|
||||||
|
sleep(3); // let audio stabilize.
|
||||||
|
|
||||||
|
this->SetXruns(0);
|
||||||
|
|
||||||
|
sleep(7); // run for a bit.
|
||||||
|
|
||||||
|
audioDriver->Deactivate();
|
||||||
|
audioDriver->Close();
|
||||||
|
|
||||||
|
if (this->GetXruns() != 0)
|
||||||
|
{
|
||||||
|
result.error = E_XRUN;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.latency = latencyMonitor.GetLatency();
|
||||||
|
if (result.latency == NO_SIGNAL_VALUE)
|
||||||
|
{
|
||||||
|
result.error = E_NOSIGNAL;
|
||||||
|
}
|
||||||
|
result.cpuOverhead = audioDriver->CpuOverhead();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
result.error = E_OPEN_FAILURE;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float **inputBuffers = nullptr;
|
||||||
|
float **outputBuffers = nullptr;
|
||||||
|
|
||||||
|
class Oscillator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
double dx = 0;
|
||||||
|
double x = 0;
|
||||||
|
double dx2 = 0;
|
||||||
|
double x2 = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Init(float frequency, size_t sampleRate)
|
||||||
|
{
|
||||||
|
dx = frequency * 3.141592736 * 2 / sampleRate;
|
||||||
|
dx2 = 0.5 * 3.141592736 * 2 / sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Next()
|
||||||
|
{
|
||||||
|
float result = (float)std::cos(x);
|
||||||
|
float env = (float)std::cos(x2);
|
||||||
|
x += dx;
|
||||||
|
x2 += dx2;
|
||||||
|
return result * env;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class LatencyMonitor
|
||||||
|
{
|
||||||
|
enum class State
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Waiting,
|
||||||
|
};
|
||||||
|
State state = State::Idle;
|
||||||
|
uint64_t t;
|
||||||
|
uint64_t idle_samples;
|
||||||
|
uint64_t waiting_samples;
|
||||||
|
size_t current_latency = 0;
|
||||||
|
size_t latency = 0;
|
||||||
|
std::mutex sync;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Init(uint64_t sampleRate)
|
||||||
|
{
|
||||||
|
idle_samples = (uint64_t)(sampleRate * 0.5);
|
||||||
|
waiting_samples = (uint64_t)(sampleRate * 0.5);
|
||||||
|
|
||||||
|
state = State::Idle;
|
||||||
|
t = idle_samples;
|
||||||
|
latency = 0;
|
||||||
|
}
|
||||||
|
void StartTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetLatency()
|
||||||
|
{
|
||||||
|
std::lock_guard lock{sync};
|
||||||
|
return latency;
|
||||||
|
}
|
||||||
|
float Next(float input)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case State::Idle:
|
||||||
|
{
|
||||||
|
if (t-- == 0)
|
||||||
|
{
|
||||||
|
state = State::Waiting;
|
||||||
|
current_latency = 0;
|
||||||
|
}
|
||||||
|
return 0.01;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::Waiting:
|
||||||
|
{
|
||||||
|
if (std::abs(input) > 0.1 || current_latency >= 2000)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lock{sync};
|
||||||
|
if (latency >= 2000)
|
||||||
|
{
|
||||||
|
latency = NO_SIGNAL_VALUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
latency = current_latency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = State::Idle;
|
||||||
|
t = idle_samples;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++current_latency;
|
||||||
|
}
|
||||||
|
return current_latency < 100 ? 0.25 : 0.0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Oscillator oscillator;
|
||||||
|
LatencyMonitor latencyMonitor;
|
||||||
|
|
||||||
|
virtual void OnAudioStopped()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void OnProcess(size_t nFrames)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t inputs = audioDriver->InputBufferCount();
|
||||||
|
size_t outputs = audioDriver->OutputBufferCount();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < inputs; ++i)
|
||||||
|
{
|
||||||
|
inputBuffers[i] = audioDriver->GetInputBuffer(i, nFrames);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < outputs; ++i)
|
||||||
|
{
|
||||||
|
outputBuffers[i] = audioDriver->GetOutputBuffer(i, nFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nFrames; ++i)
|
||||||
|
{
|
||||||
|
float v = latencyMonitor.Next(inputBuffers[0][i]);
|
||||||
|
for (size_t c = 0; c < outputs; ++c)
|
||||||
|
{
|
||||||
|
outputBuffers[c][i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::mutex sync;
|
||||||
|
uint64_t xruns;
|
||||||
|
|
||||||
|
uint64_t GetXruns()
|
||||||
|
{
|
||||||
|
lock_guard lock{sync};
|
||||||
|
return xruns;
|
||||||
|
}
|
||||||
|
void SetXruns(uint64_t value)
|
||||||
|
{
|
||||||
|
lock_guard lock{sync};
|
||||||
|
xruns = value;
|
||||||
|
}
|
||||||
|
virtual void OnUnderrun()
|
||||||
|
{
|
||||||
|
lock_guard lock{sync};
|
||||||
|
++xruns;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TestResult RunLatencyTest(const std::string deviceId, uint32_t sampleRate, int bufferSize, int buffers)
|
||||||
|
{
|
||||||
|
AlsaTester tester(deviceId, sampleRate, bufferSize, buffers);
|
||||||
|
return tester.Test();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string msDisplay(float value)
|
||||||
|
{
|
||||||
|
std::stringstream s;
|
||||||
|
s << fixed;
|
||||||
|
s.precision(1);
|
||||||
|
s << value << "ms";
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
static std::string overheadDisplay(float value)
|
||||||
|
{
|
||||||
|
std::stringstream s;
|
||||||
|
s << setw(3) << value << "%";
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunLatencyTest(const std::string &deviceId, uint32_t sampleRate)
|
||||||
|
{
|
||||||
|
PrettyPrinter pp;
|
||||||
|
pp << "Device: " << deviceId << " Rate: " << sampleRate << "\n\n";
|
||||||
|
|
||||||
|
const int SIZE_COLUMN_WIDTH = 8;
|
||||||
|
const int BUFFERS_COLUMN_WIDTH = 20;
|
||||||
|
|
||||||
|
static int bufferCounts[] = {2, 3, 4};
|
||||||
|
static int bufferSizes[] = {16, 24, 32, 48, 64, 128};
|
||||||
|
|
||||||
|
pp << Column(SIZE_COLUMN_WIDTH) << "Buffers\n";
|
||||||
|
pp << "Size";
|
||||||
|
|
||||||
|
int column = SIZE_COLUMN_WIDTH;
|
||||||
|
for (auto bufferCount : bufferCounts)
|
||||||
|
{
|
||||||
|
pp << Column(column) << bufferCount;
|
||||||
|
column += BUFFERS_COLUMN_WIDTH;
|
||||||
|
}
|
||||||
|
pp << "\n";
|
||||||
|
|
||||||
|
for (auto bufferSize : bufferSizes)
|
||||||
|
{
|
||||||
|
pp << bufferSize;
|
||||||
|
|
||||||
|
int column = SIZE_COLUMN_WIDTH;
|
||||||
|
|
||||||
|
for (auto bufferCount : bufferCounts)
|
||||||
|
{
|
||||||
|
auto result = RunLatencyTest(deviceId, sampleRate, bufferSize, bufferCount);
|
||||||
|
|
||||||
|
pp.Column(column);
|
||||||
|
column += BUFFERS_COLUMN_WIDTH;
|
||||||
|
switch (result.error)
|
||||||
|
{
|
||||||
|
case E_NOSIGNAL:
|
||||||
|
pp << "No signal";
|
||||||
|
break;
|
||||||
|
case E_OPEN_FAILURE:
|
||||||
|
pp << "Failed";
|
||||||
|
break;
|
||||||
|
case E_XRUN:
|
||||||
|
pp << "Xrun";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
float ms = 1000.0f * result.latency / sampleRate;
|
||||||
|
pp << result.latency << "/" << msDisplay(ms) ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pp << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
Lv2Log::log_level(LogLevel::Warning);
|
||||||
|
|
||||||
|
CommandLineParser parser;
|
||||||
|
|
||||||
|
std::string deviceName;
|
||||||
|
bool listDevices = false;
|
||||||
|
bool help = false;
|
||||||
|
uint32_t sampleRate = 48000;
|
||||||
|
|
||||||
|
parser.AddOption("l", "list", &listDevices);
|
||||||
|
parser.AddOption("h", "help", &help);
|
||||||
|
parser.AddOption("r", "rate", &sampleRate);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parser.Parse(argc, argv);
|
||||||
|
|
||||||
|
if (help)
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
}
|
||||||
|
else if (listDevices)
|
||||||
|
{
|
||||||
|
ListDevices();
|
||||||
|
}
|
||||||
|
else if (parser.Arguments().size() == 1)
|
||||||
|
{
|
||||||
|
RunLatencyTest(parser.Arguments()[0], sampleRate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception &e)
|
||||||
|
{
|
||||||
|
cout << "Error: " << e.what() << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -167,9 +167,99 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
snd_config_update_free_global();
|
snd_config_update_free_global();
|
||||||
|
|
||||||
|
Lv2Log::debug("GetAlsaDevices --");
|
||||||
|
for (auto& device: result)
|
||||||
|
{
|
||||||
|
Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::vector<AlsaMidiDeviceInfo> GetAlsaDevices(const char*devname, const char*direction)
|
||||||
|
{
|
||||||
|
std::vector<AlsaMidiDeviceInfo> result;
|
||||||
|
{
|
||||||
|
|
||||||
|
char **hints;
|
||||||
|
int err;
|
||||||
|
char **n;
|
||||||
|
char *name;
|
||||||
|
char *desc;
|
||||||
|
char *ioid;
|
||||||
|
|
||||||
|
/* Enumerate sound devices */
|
||||||
|
err = snd_device_name_hint(-1, devname, (void ***)&hints);
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = hints;
|
||||||
|
while (*n != NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
name = snd_device_name_get_hint(*n, "NAME");
|
||||||
|
desc = snd_device_name_get_hint(*n, "DESC");
|
||||||
|
ioid = snd_device_name_get_hint(*n, "IOID");
|
||||||
|
|
||||||
|
if (desc != nullptr) // skip virtual device
|
||||||
|
{
|
||||||
|
if (ioid == nullptr || strcmp(ioid,direction) == 0)
|
||||||
|
{
|
||||||
|
result.push_back(AlsaMidiDeviceInfo(name,desc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name && strcmp("null", name) != 0)
|
||||||
|
free(name);
|
||||||
|
if (desc && strcmp("null", desc) != 0)
|
||||||
|
free(desc);
|
||||||
|
if (ioid && strcmp("null", ioid) != 0)
|
||||||
|
free(ioid);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free hint buffer too
|
||||||
|
snd_device_name_free_hint((void **)hints);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<AlsaMidiDeviceInfo> pipedal::GetAlsaMidiInputDevices()
|
||||||
|
{
|
||||||
|
return GetAlsaDevices("rawmidi","Input");
|
||||||
|
}
|
||||||
|
std::vector<AlsaMidiDeviceInfo> pipedal::GetAlsaMidiOutputDevices()
|
||||||
|
{
|
||||||
|
return GetAlsaDevices("rawmidi","Output");
|
||||||
|
}
|
||||||
|
|
||||||
|
AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char*name, const char*description)
|
||||||
|
:name_(name)
|
||||||
|
{
|
||||||
|
// extract just the display name from description.
|
||||||
|
// undocumented but e.g.: M2, M2\nM2 Raw Midi
|
||||||
|
const char *p = description;
|
||||||
|
const char *pEnd = p;
|
||||||
|
// undocumented but e.g.: M2, M2\nM2 Raw Midi
|
||||||
|
while (pEnd != nullptr && *pEnd != 0
|
||||||
|
&& *pEnd != ','
|
||||||
|
&& *pEnd != '\n'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
++pEnd;
|
||||||
|
}
|
||||||
|
if (p != pEnd)
|
||||||
|
{
|
||||||
|
description_ = std::string(p,pEnd);
|
||||||
|
} else {
|
||||||
|
description = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
JSON_MAP_BEGIN(AlsaDeviceInfo)
|
JSON_MAP_BEGIN(AlsaDeviceInfo)
|
||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
|
||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
|
||||||
@@ -179,3 +269,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
|
|||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
||||||
|
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, name)
|
||||||
|
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, description)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ namespace pipedal {
|
|||||||
|
|
||||||
DECLARE_JSON_MAP(AlsaDeviceInfo);
|
DECLARE_JSON_MAP(AlsaDeviceInfo);
|
||||||
|
|
||||||
|
};
|
||||||
|
class AlsaMidiDeviceInfo {
|
||||||
|
public:
|
||||||
|
AlsaMidiDeviceInfo() { }
|
||||||
|
AlsaMidiDeviceInfo(const char*name, const char*description);
|
||||||
|
std::string name_;
|
||||||
|
std::string description_;
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(AlsaMidiDeviceInfo);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PiPedalAlsaDevices {
|
class PiPedalAlsaDevices {
|
||||||
@@ -45,4 +55,6 @@ namespace pipedal {
|
|||||||
|
|
||||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||||
};
|
};
|
||||||
|
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiInputDevices();
|
||||||
|
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiOutputDevices();
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,14 @@ TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) {
|
|||||||
|
|
||||||
PiPedalAlsaDevices devices;
|
PiPedalAlsaDevices devices;
|
||||||
auto result = devices.GetAlsaDevices();
|
auto result = devices.GetAlsaDevices();
|
||||||
std::cout << result.size() << " ALSA devices found." << 0;
|
std::cout << result.size() << " ALSA devices found." << std::endl;
|
||||||
|
|
||||||
|
auto midiInputDevices = GetAlsaMidiInputDevices();
|
||||||
|
std::cout << midiInputDevices.size() << " ALSA MIDI input devices found." << std::endl;
|
||||||
|
auto midiOutputDevices = GetAlsaMidiOutputDevices();
|
||||||
|
std::cout << midiOutputDevices.size() << " ALSA MIDI output devices found." << std::endl;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ namespace pipedal
|
|||||||
: PiPedalException(std::forward<std::string>(what))
|
: PiPedalException(std::forward<std::string>(what))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
PiPedalStateException(const std::string &what)
|
||||||
|
: PiPedalException(what)
|
||||||
|
{
|
||||||
|
}
|
||||||
};
|
};
|
||||||
class PiPedalLogicException : public PiPedalException
|
class PiPedalLogicException : public PiPedalException
|
||||||
{
|
{
|
||||||
|
|||||||
+42
-10
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "DeviceIdFile.hpp"
|
#include "DeviceIdFile.hpp"
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include "PiPedalModel.hpp"
|
#include "PiPedalModel.hpp"
|
||||||
#include "JackHost.hpp"
|
#include "JackHost.hpp"
|
||||||
@@ -49,7 +50,12 @@ std::string AtomToJson(int length, uint8_t *pData)
|
|||||||
PiPedalModel::PiPedalModel()
|
PiPedalModel::PiPedalModel()
|
||||||
{
|
{
|
||||||
this->pedalBoard = PedalBoard::MakeDefault();
|
this->pedalBoard = PedalBoard::MakeDefault();
|
||||||
|
#if JACK_HOST
|
||||||
this->jackServerSettings.ReadJackConfiguration();
|
this->jackServerSettings.ReadJackConfiguration();
|
||||||
|
#else
|
||||||
|
this->jackServerSettings = this->storage.GetJackServerSettings();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +114,19 @@ PiPedalModel::~PiPedalModel()
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
void PiPedalModel::Init(const PiPedalConfiguration&configuration)
|
||||||
void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration)
|
|
||||||
{
|
{
|
||||||
|
this->configuration = configuration;
|
||||||
storage.SetConfigRoot(configuration.GetDocRoot());
|
storage.SetConfigRoot(configuration.GetDocRoot());
|
||||||
storage.SetDataRoot(configuration.GetLocalStoragePath());
|
storage.SetDataRoot(configuration.GetLocalStoragePath());
|
||||||
storage.Initialize();
|
storage.Initialize();
|
||||||
|
|
||||||
|
this->jackServerSettings.ReadJackConfiguration();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::LoadLv2PluginInfo()
|
||||||
|
{
|
||||||
// Not all Lv2 directories have the lv2 base declarations. Load a full set of plugin classes generated on a default /usr/local/lib/lv2 directory.
|
// Not all Lv2 directories have the lv2 base declarations. Load a full set of plugin classes generated on a default /usr/local/lib/lv2 directory.
|
||||||
std::filesystem::path pluginClassesPath = configuration.GetDocRoot() / "plugin_classes.json";
|
std::filesystem::path pluginClassesPath = configuration.GetDocRoot() / "plugin_classes.json";
|
||||||
try
|
try
|
||||||
@@ -149,11 +160,10 @@ void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
void PiPedalModel::Load()
|
||||||
{
|
{
|
||||||
this->webRoot = configuration.GetWebRoot();
|
this->webRoot = configuration.GetWebRoot();
|
||||||
this->webPort = (uint16_t)configuration.GetSocketServerPort();
|
this->webPort = (uint16_t)configuration.GetSocketServerPort();
|
||||||
this->jackServerSettings.ReadJackConfiguration();
|
|
||||||
|
|
||||||
|
|
||||||
adminClient.MonitorGovernor(storage.GetGovernorSettings());
|
adminClient.MonitorGovernor(storage.GetGovernorSettings());
|
||||||
@@ -192,8 +202,12 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
|||||||
scheduler_params.sched_priority = 10;
|
scheduler_params.sched_priority = 10;
|
||||||
memset(&scheduler_params,0,sizeof(sched_param));
|
memset(&scheduler_params,0,sizeof(sched_param));
|
||||||
sched_setscheduler(0,SCHED_RR,&scheduler_params);
|
sched_setscheduler(0,SCHED_RR,&scheduler_params);
|
||||||
|
#if JACK_HOST
|
||||||
this->jackConfiguration = jackHost->GetServerConfiguration();
|
this->jackConfiguration = this->jackConfiguration.JackInitialize();
|
||||||
|
#else
|
||||||
|
this->jackServerSettings = storage.GetJackServerSettings();
|
||||||
|
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
||||||
|
#endif
|
||||||
if (this->jackConfiguration.isValid())
|
if (this->jackConfiguration.isValid())
|
||||||
{
|
{
|
||||||
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
|
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
|
||||||
@@ -202,7 +216,7 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
|||||||
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
|
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
jackHost->Open(selection);
|
jackHost->Open(this->jackServerSettings,selection);
|
||||||
|
|
||||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||||
this->lv2PedalBoard = lv2PedalBoard;
|
this->lv2PedalBoard = lv2PedalBoard;
|
||||||
@@ -212,6 +226,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
|||||||
{
|
{
|
||||||
Lv2Log::error("Failed to load initial plugin. (%s)", e.what());
|
Lv2Log::error("Failed to load initial plugin. (%s)", e.what());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Lv2Log::error("Audio device not configured.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,15 +796,27 @@ void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSe
|
|||||||
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
|
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
|
||||||
if (this->jackHost->IsOpen())
|
if (this->jackHost->IsOpen())
|
||||||
{
|
{
|
||||||
// do a complete reload.
|
|
||||||
|
|
||||||
this->jackHost->Close();
|
this->jackHost->Close();
|
||||||
|
#ifdef JUNK
|
||||||
|
// restarting is a bit dodgy. It was impossible with Jack, but
|
||||||
|
// now very plausible with the ALSA audio stack.
|
||||||
|
|
||||||
|
// Still bugs wrt/ restarting the circular buffers for the audio thread.
|
||||||
|
|
||||||
|
// for the meantime, just rely on the fact that the admin service will restart
|
||||||
|
// the process.
|
||||||
|
//...
|
||||||
|
|
||||||
|
// do a complete reload.
|
||||||
|
|
||||||
this->jackHost->SetPedalBoard(nullptr);
|
this->jackHost->SetPedalBoard(nullptr);
|
||||||
this->jackHost->Open(channelSelection);
|
this->jackHost->Open(this->jackServerSettings,channelSelection);
|
||||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||||
this->lv2PedalBoard = lv2PedalBoard;
|
this->lv2PedalBoard = lv2PedalBoard;
|
||||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||||
this->UpdateRealtimeVuSubscriptions();
|
this->UpdateRealtimeVuSubscriptions();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1127,6 +1155,10 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
if (adminClient.CanUseShutdownClient())
|
if (adminClient.CanUseShutdownClient())
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#if ALSA_HOST
|
||||||
|
storage.SetJackServerSettings(jackServerSettings);
|
||||||
|
#endif
|
||||||
|
|
||||||
// save the current (edited) preset now in case the service shutdown isn't clean.
|
// save the current (edited) preset now in case the service shutdown isn't clean.
|
||||||
CurrentPreset currentPreset;
|
CurrentPreset currentPreset;
|
||||||
currentPreset.modified_ = this->hasPresetChanged;
|
currentPreset.modified_ = this->hasPresetChanged;
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ private: // IJackHostCallbacks
|
|||||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
|
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
|
||||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
|
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
|
||||||
|
|
||||||
|
|
||||||
|
PiPedalConfiguration configuration;
|
||||||
public:
|
public:
|
||||||
PiPedalModel();
|
PiPedalModel();
|
||||||
virtual ~PiPedalModel();
|
virtual ~PiPedalModel();
|
||||||
@@ -164,9 +166,10 @@ public:
|
|||||||
|
|
||||||
AdminClient&GetAdminClient() { return adminClient; }
|
AdminClient&GetAdminClient() { return adminClient; }
|
||||||
|
|
||||||
|
void Init(const PiPedalConfiguration&configuration);
|
||||||
|
|
||||||
void LoadLv2PluginInfo(const PiPedalConfiguration&configuration);
|
void LoadLv2PluginInfo();
|
||||||
void Load(const PiPedalConfiguration&configuration);
|
void Load();
|
||||||
|
|
||||||
const Lv2Host& GetLv2Host() const { return lv2Host; }
|
const Lv2Host& GetLv2Host() const { return lv2Host; }
|
||||||
PedalBoard GetCurrentPedalBoardCopy()
|
PedalBoard GetCurrentPedalBoardCopy()
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
lineBuffer.reserve(120);
|
lineBuffer.reserve(120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t width() const { return lineWidth; }
|
||||||
|
void width(size_t width) { lineWidth =width; }
|
||||||
|
|
||||||
std::ostream&Stream() const { return s;}
|
std::ostream&Stream() const { return s;}
|
||||||
|
|
||||||
PrettyPrinter& Indent(size_t indent)
|
PrettyPrinter& Indent(size_t indent)
|
||||||
@@ -67,6 +71,21 @@ namespace pipedal
|
|||||||
column = 0;
|
column = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrettyPrinter& Column(int n)
|
||||||
|
{
|
||||||
|
if (column >= n) {
|
||||||
|
lineBuffer.push_back(' ');
|
||||||
|
++column;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (column != n)
|
||||||
|
{
|
||||||
|
lineBuffer.push_back(' ');
|
||||||
|
++column;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
PrettyPrinter& HangingIndent()
|
PrettyPrinter& HangingIndent()
|
||||||
{
|
{
|
||||||
hangingIndent = true;
|
hangingIndent = true;
|
||||||
@@ -235,5 +254,13 @@ namespace pipedal
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp_manip Column(int n)
|
||||||
|
{
|
||||||
|
return [n] (PrettyPrinter &pp) -> PrettyPrinter& {
|
||||||
|
pp.Column(n);
|
||||||
|
return pp;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+151
-126
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "Storage.hpp"
|
#include "Storage.hpp"
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
@@ -35,8 +36,6 @@ const char *BANKS_FILENAME = "index.banks";
|
|||||||
#define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json";
|
#define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json";
|
||||||
#define USER_SETTINGS_FILENAME "userSettings.json";
|
#define USER_SETTINGS_FILENAME "userSettings.json";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Storage::Storage()
|
Storage::Storage()
|
||||||
{
|
{
|
||||||
SetConfigRoot("~/var/Config");
|
SetConfigRoot("~/var/Config");
|
||||||
@@ -131,7 +130,7 @@ std::string Storage::SafeEncodeName(const std::string &name)
|
|||||||
return s.str();
|
return s.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path ResolveHomePath(const std::filesystem::path& path)
|
std::filesystem::path ResolveHomePath(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
if (path.begin() == path.end())
|
if (path.begin() == path.end())
|
||||||
return path;
|
return path;
|
||||||
@@ -161,28 +160,27 @@ std::filesystem::path ResolveHomePath(const std::filesystem::path& path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::SetConfigRoot(const std::filesystem::path& path)
|
void Storage::SetConfigRoot(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
this->configRoot = ResolveHomePath(path);
|
this->configRoot = ResolveHomePath(path);
|
||||||
}
|
}
|
||||||
void Storage::SetDataRoot(const std::filesystem::path& path)
|
void Storage::SetDataRoot(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
this->dataRoot = ResolveHomePath(path);
|
this->dataRoot = ResolveHomePath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CopyDirectory(const std::filesystem::path &source, const std::filesystem::path &destination)
|
static void CopyDirectory(const std::filesystem::path &source, const std::filesystem::path &destination)
|
||||||
{
|
{
|
||||||
for (auto &directoryEntry: std::filesystem::directory_iterator(source))
|
for (auto &directoryEntry : std::filesystem::directory_iterator(source))
|
||||||
{
|
{
|
||||||
if (directoryEntry.is_regular_file())
|
if (directoryEntry.is_regular_file())
|
||||||
{
|
{
|
||||||
std::filesystem::path sourceFile = directoryEntry.path();
|
std::filesystem::path sourceFile = directoryEntry.path();
|
||||||
std::filesystem::path destFile = destination / sourceFile.filename();
|
std::filesystem::path destFile = destination / sourceFile.filename();
|
||||||
|
|
||||||
std::filesystem::copy_file(sourceFile,destFile);
|
std::filesystem::copy_file(sourceFile, destFile);
|
||||||
std::ignore = chmod(destFile.c_str(),S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
|
std::ignore = chmod(destFile.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
|
||||||
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Storage::MaybeCopyDefaultPresets()
|
void Storage::MaybeCopyDefaultPresets()
|
||||||
@@ -192,18 +190,15 @@ void Storage::MaybeCopyDefaultPresets()
|
|||||||
if (!std::filesystem::exists(presetsDirectory / "index.banks"))
|
if (!std::filesystem::exists(presetsDirectory / "index.banks"))
|
||||||
{
|
{
|
||||||
CopyDirectory(this->configRoot / "default_presets" / "presets",
|
CopyDirectory(this->configRoot / "default_presets" / "presets",
|
||||||
presetsDirectory
|
presetsDirectory);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pluginDirectory = this->GetPluginPresetsDirectory();
|
auto pluginDirectory = this->GetPluginPresetsDirectory();
|
||||||
if (!std::filesystem::exists(pluginDirectory / "index.json"))
|
if (!std::filesystem::exists(pluginDirectory / "index.json"))
|
||||||
{
|
{
|
||||||
CopyDirectory(this->configRoot / "default_presets" / "plugin_presets",
|
CopyDirectory(this->configRoot / "default_presets" / "plugin_presets",
|
||||||
pluginDirectory
|
pluginDirectory);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void Storage::Initialize()
|
void Storage::Initialize()
|
||||||
{
|
{
|
||||||
@@ -233,7 +228,6 @@ void Storage::Initialize()
|
|||||||
LoadUserSettings();
|
LoadUserSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Storage::LoadBank(int64_t instanceId)
|
void Storage::LoadBank(int64_t instanceId)
|
||||||
{
|
{
|
||||||
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
|
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
|
||||||
@@ -265,7 +259,6 @@ std::filesystem::path Storage::GetCurrentPresetPath() const
|
|||||||
return this->dataRoot / "currentPreset.json";
|
return this->dataRoot / "currentPreset.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::filesystem::path Storage::GetChannelSelectionFileName()
|
std::filesystem::path Storage::GetChannelSelectionFileName()
|
||||||
{
|
{
|
||||||
return this->dataRoot / "JackChannelSelection.json";
|
return this->dataRoot / "JackChannelSelection.json";
|
||||||
@@ -304,7 +297,7 @@ void Storage::LoadBankIndex()
|
|||||||
|
|
||||||
std::string name = "Default Bank";
|
std::string name = "Default Bank";
|
||||||
SaveBankFile(name, currentBank);
|
SaveBankFile(name, currentBank);
|
||||||
int64_t selectedBank = bankIndex.addBank(-1,name);
|
int64_t selectedBank = bankIndex.addBank(-1, name);
|
||||||
bankIndex.selectedBank(selectedBank);
|
bankIndex.selectedBank(selectedBank);
|
||||||
}
|
}
|
||||||
SaveBankIndex();
|
SaveBankIndex();
|
||||||
@@ -365,7 +358,7 @@ void Storage::ReIndex()
|
|||||||
if (path.extension() == BANK_EXTENSION)
|
if (path.extension() == BANK_EXTENSION)
|
||||||
{
|
{
|
||||||
std::string name = SafeDecodeName(path.stem());
|
std::string name = SafeDecodeName(path.stem());
|
||||||
bankIndex.addBank(-1,name);
|
bankIndex.addBank(-1, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,11 +380,12 @@ void Storage::CreateBank(const std::string &name)
|
|||||||
int64_t instanceId = bankFile.presets()[0]->instanceId();
|
int64_t instanceId = bankFile.presets()[0]->instanceId();
|
||||||
bankFile.selectedPreset(instanceId);
|
bankFile.selectedPreset(instanceId);
|
||||||
SaveBankFile(name, bankFile);
|
SaveBankFile(name, bankFile);
|
||||||
this->bankIndex.addBank(-1,name);
|
this->bankIndex.addBank(-1, name);
|
||||||
this->SaveBankIndex();
|
this->SaveBankIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::GetBankFile(int64_t instanceId,BankFile *pBank) const {
|
void Storage::GetBankFile(int64_t instanceId, BankFile *pBank) const
|
||||||
|
{
|
||||||
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
|
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
|
||||||
auto name = indexEntry.name();
|
auto name = indexEntry.name();
|
||||||
std::filesystem::path fileName = GetBankFileName(name);
|
std::filesystem::path fileName = GetBankFileName(name);
|
||||||
@@ -407,7 +401,6 @@ void Storage::LoadBankFile(const std::string &name, BankFile *pBank)
|
|||||||
std::ifstream is(fileName);
|
std::ifstream is(fileName);
|
||||||
json_reader reader(is);
|
json_reader reader(is);
|
||||||
reader.read(pBank);
|
reader.read(pBank);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
|
void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
|
||||||
@@ -459,8 +452,9 @@ const PedalBoard &Storage::GetCurrentPreset()
|
|||||||
bool Storage::LoadPreset(int64_t instanceId)
|
bool Storage::LoadPreset(int64_t instanceId)
|
||||||
{
|
{
|
||||||
if (!currentBank.hasItem(instanceId))
|
if (!currentBank.hasItem(instanceId))
|
||||||
return false;
|
return false;
|
||||||
if (instanceId != currentBank.selectedPreset()) {
|
if (instanceId != currentBank.selectedPreset())
|
||||||
|
{
|
||||||
currentBank.selectedPreset(instanceId);
|
currentBank.selectedPreset(instanceId);
|
||||||
SaveCurrentBank();
|
SaveCurrentBank();
|
||||||
}
|
}
|
||||||
@@ -489,13 +483,13 @@ void Storage::SetPresetIndex(const PresetIndex &presets)
|
|||||||
std::map<int64_t, std::unique_ptr<BankFileEntry> *> entries;
|
std::map<int64_t, std::unique_ptr<BankFileEntry> *> entries;
|
||||||
for (int i = 0; i < this->currentBank.presets().size(); ++i)
|
for (int i = 0; i < this->currentBank.presets().size(); ++i)
|
||||||
{
|
{
|
||||||
auto & preset = this->currentBank.presets()[i];
|
auto &preset = this->currentBank.presets()[i];
|
||||||
entries[preset->instanceId()] = &(this->currentBank.presets()[i]);
|
entries[preset->instanceId()] = &(this->currentBank.presets()[i]);
|
||||||
}
|
}
|
||||||
std::vector<std::unique_ptr<BankFileEntry>*> newPresets;
|
std::vector<std::unique_ptr<BankFileEntry> *> newPresets;
|
||||||
for (size_t i = 0; i < presets.presets().size(); ++i)
|
for (size_t i = 0; i < presets.presets().size(); ++i)
|
||||||
{
|
{
|
||||||
std::unique_ptr<BankFileEntry>* p = entries[presets.presets()[i].instanceId()];
|
std::unique_ptr<BankFileEntry> *p = entries[presets.presets()[i].instanceId()];
|
||||||
if (p == nullptr)
|
if (p == nullptr)
|
||||||
{
|
{
|
||||||
throw PiPedalStateException("Presets do not match the currently loaded bank.");
|
throw PiPedalStateException("Presets do not match the currently loaded bank.");
|
||||||
@@ -544,7 +538,7 @@ PedalBoard Storage::GetPreset(int64_t instanceId) const
|
|||||||
|
|
||||||
int64_t Storage::DeletePreset(int64_t presetId)
|
int64_t Storage::DeletePreset(int64_t presetId)
|
||||||
{
|
{
|
||||||
int64_t newSelection = currentBank.deletePreset(presetId);
|
int64_t newSelection = currentBank.deletePreset(presetId);
|
||||||
SaveCurrentBank();
|
SaveCurrentBank();
|
||||||
return newSelection;
|
return newSelection;
|
||||||
}
|
}
|
||||||
@@ -724,7 +718,7 @@ int64_t Storage::SaveBankAs(int64_t bankId, const std::string &newName)
|
|||||||
std::filesystem::path newPath = this->GetBankFileName(newName);
|
std::filesystem::path newPath = this->GetBankFileName(newName);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::filesystem::copy(oldPath,newPath);
|
std::filesystem::copy(oldPath, newPath);
|
||||||
}
|
}
|
||||||
catch (std::exception &e)
|
catch (std::exception &e)
|
||||||
{
|
{
|
||||||
@@ -732,36 +726,40 @@ int64_t Storage::SaveBankAs(int64_t bankId, const std::string &newName)
|
|||||||
s << "Unable to save the bank. (" << e.what() << ")";
|
s << "Unable to save the bank. (" << e.what() << ")";
|
||||||
throw PiPedalException(s.str());
|
throw PiPedalException(s.str());
|
||||||
}
|
}
|
||||||
int64_t newId = this->bankIndex.addBank(bankId,newName);
|
int64_t newId = this->bankIndex.addBank(bankId, newName);
|
||||||
SaveBankIndex();
|
SaveBankIndex();
|
||||||
return newId;
|
return newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::MoveBank(int from, int to)
|
void Storage::MoveBank(int from, int to)
|
||||||
{
|
{
|
||||||
this->bankIndex.move(from,to);
|
this->bankIndex.move(from, to);
|
||||||
this->SaveBankIndex();
|
this->SaveBankIndex();
|
||||||
}
|
}
|
||||||
int64_t Storage::DeleteBank(int64_t bankId)
|
int64_t Storage::DeleteBank(int64_t bankId)
|
||||||
{
|
{
|
||||||
auto & entries = this->bankIndex.entries();
|
auto &entries = this->bankIndex.entries();
|
||||||
|
|
||||||
for (size_t i = 0; i < entries.size(); ++i)
|
for (size_t i = 0; i < entries.size(); ++i)
|
||||||
{
|
{
|
||||||
auto & entry = entries[i];
|
auto &entry = entries[i];
|
||||||
|
|
||||||
if (entry.instanceId() == bankId) {
|
if (entry.instanceId() == bankId)
|
||||||
|
{
|
||||||
std::filesystem::path fileName = this->GetBankFileName(entry.name());
|
std::filesystem::path fileName = this->GetBankFileName(entry.name());
|
||||||
entries.erase(entries.begin()+i);
|
entries.erase(entries.begin() + i);
|
||||||
|
|
||||||
int64_t newSelection;
|
int64_t newSelection;
|
||||||
if (i < entries.size())
|
if (i < entries.size())
|
||||||
{
|
{
|
||||||
newSelection = entries[i].instanceId();
|
newSelection = entries[i].instanceId();
|
||||||
} else if (entries.size() > 0)
|
}
|
||||||
|
else if (entries.size() > 0)
|
||||||
|
{
|
||||||
|
newSelection = entries[entries.size() - 1].instanceId();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
newSelection = entries[entries.size()-1].instanceId();
|
|
||||||
} else {
|
|
||||||
// zero entries?
|
// zero entries?
|
||||||
// Create a default empty bank.
|
// Create a default empty bank.
|
||||||
BankIndexEntry newEntry;
|
BankIndexEntry newEntry;
|
||||||
@@ -773,7 +771,7 @@ int64_t Storage::DeleteBank(int64_t bankId)
|
|||||||
|
|
||||||
std::string name = "Default Bank";
|
std::string name = "Default Bank";
|
||||||
SaveBankFile(name, defaultBank);
|
SaveBankFile(name, defaultBank);
|
||||||
int64_t selectedBank = bankIndex.addBank(-1,name);
|
int64_t selectedBank = bankIndex.addBank(-1, name);
|
||||||
newSelection = selectedBank;
|
newSelection = selectedBank;
|
||||||
}
|
}
|
||||||
if (this->bankIndex.selectedBank() == bankId)
|
if (this->bankIndex.selectedBank() == bankId)
|
||||||
@@ -793,7 +791,7 @@ int64_t Storage::GetCurrentPresetId() const
|
|||||||
return this->currentBank.selectedPreset();
|
return this->currentBank.selectedPreset();
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter)
|
int64_t Storage::UploadPreset(const BankFile &bankFile, int64_t uploadAfter)
|
||||||
{
|
{
|
||||||
int64_t lastPreset = this->currentBank.selectedPreset();
|
int64_t lastPreset = this->currentBank.selectedPreset();
|
||||||
if (uploadAfter != -1)
|
if (uploadAfter != -1)
|
||||||
@@ -801,7 +799,8 @@ int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter)
|
|||||||
lastPreset = uploadAfter;
|
lastPreset = uploadAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bankFile.presets().size() == 0) {
|
if (bankFile.presets().size() == 0)
|
||||||
|
{
|
||||||
throw PiPedalException("Invalid preset.");
|
throw PiPedalException("Invalid preset.");
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < bankFile.presets().size(); ++i)
|
for (size_t i = 0; i < bankFile.presets().size(); ++i)
|
||||||
@@ -817,12 +816,12 @@ int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter)
|
|||||||
preset.name(s.str());
|
preset.name(s.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPreset = this->currentBank.addPreset(preset,lastPreset);
|
lastPreset = this->currentBank.addPreset(preset, lastPreset);
|
||||||
}
|
}
|
||||||
this->SaveCurrentBank();
|
this->SaveCurrentBank();
|
||||||
return lastPreset;
|
return lastPreset;
|
||||||
}
|
}
|
||||||
int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter)
|
int64_t Storage::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
||||||
{
|
{
|
||||||
int64_t lastBank = this->bankIndex.selectedBank();
|
int64_t lastBank = this->bankIndex.selectedBank();
|
||||||
if (uploadAfter != -1)
|
if (uploadAfter != -1)
|
||||||
@@ -830,7 +829,8 @@ int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter)
|
|||||||
lastBank = uploadAfter;
|
lastBank = uploadAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bankFile.presets().size() == 0) {
|
if (bankFile.presets().size() == 0)
|
||||||
|
{
|
||||||
throw PiPedalException("Invalid bank.");
|
throw PiPedalException("Invalid bank.");
|
||||||
}
|
}
|
||||||
bankFile.updateNextIndex();
|
bankFile.updateNextIndex();
|
||||||
@@ -845,24 +845,25 @@ int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter)
|
|||||||
}
|
}
|
||||||
std::filesystem::path path = this->GetBankFileName(bankFile.name());
|
std::filesystem::path path = this->GetBankFileName(bankFile.name());
|
||||||
std::ofstream f(path);
|
std::ofstream f(path);
|
||||||
if (!f.is_open()) {
|
if (!f.is_open())
|
||||||
|
{
|
||||||
throw PiPedalException("Can't write to bank file.");
|
throw PiPedalException("Can't write to bank file.");
|
||||||
}
|
}
|
||||||
json_writer writer(f);
|
json_writer writer(f);
|
||||||
writer.write(bankFile);
|
writer.write(bankFile);
|
||||||
|
|
||||||
lastBank = this->bankIndex.addBank(lastBank,bankFile.name());
|
lastBank = this->bankIndex.addBank(lastBank, bankFile.name());
|
||||||
this->SaveBankIndex();
|
this->SaveBankIndex();
|
||||||
return lastBank;
|
return lastBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Storage::SetGovernorSettings(const std::string &governor)
|
||||||
void Storage::SetGovernorSettings(const std::string & governor)
|
|
||||||
{
|
{
|
||||||
userSettings.governor_ = governor;
|
userSettings.governor_ = governor;
|
||||||
SaveUserSettings();
|
SaveUserSettings();
|
||||||
}
|
}
|
||||||
void Storage::SaveUserSettings() {
|
void Storage::SaveUserSettings()
|
||||||
|
{
|
||||||
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
|
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
|
||||||
{
|
{
|
||||||
std::ofstream f(path);
|
std::ofstream f(path);
|
||||||
@@ -889,8 +890,7 @@ std::string Storage::GetGovernorSettings() const
|
|||||||
return this->userSettings.governor_;
|
return this->userSettings.governor_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings)
|
||||||
void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings)
|
|
||||||
{
|
{
|
||||||
WifiConfigSettings copyToSave = wifiConfigSettings;
|
WifiConfigSettings copyToSave = wifiConfigSettings;
|
||||||
copyToSave.rebootRequired_ = false;
|
copyToSave.rebootRequired_ = false;
|
||||||
@@ -915,19 +915,21 @@ void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSetting
|
|||||||
if (copyToStore.enable_)
|
if (copyToStore.enable_)
|
||||||
{
|
{
|
||||||
copyToStore.hasPassword_ = copyToStore.password_.length() != 0 || this->wifiConfigSettings.hasPassword_;
|
copyToStore.hasPassword_ = copyToStore.password_.length() != 0 || this->wifiConfigSettings.hasPassword_;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
copyToStore.hasPassword_ = false;
|
copyToStore.hasPassword_ = false;
|
||||||
}
|
}
|
||||||
copyToStore.password_ = "";
|
copyToStore.password_ = "";
|
||||||
copyToStore.rebootRequired_ = false;
|
copyToStore.rebootRequired_ = false;
|
||||||
if (copyToStore.enable_ && !copyToStore.hasPassword_)
|
if (copyToStore.enable_ && !copyToStore.hasPassword_)
|
||||||
{
|
{
|
||||||
copyToStore.hasPassword_ = this->wifiConfigSettings.hasPassword_;
|
copyToStore.hasPassword_ = this->wifiConfigSettings.hasPassword_;
|
||||||
}
|
}
|
||||||
this->wifiConfigSettings = copyToStore;
|
this->wifiConfigSettings = copyToStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Storage::SetWifiDirectConfigSettings(const WifiDirectConfigSettings & wifiDirectConfigSettings)
|
void Storage::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiDirectConfigSettings)
|
||||||
{
|
{
|
||||||
WifiDirectConfigSettings copyToSave = wifiDirectConfigSettings;
|
WifiDirectConfigSettings copyToSave = wifiDirectConfigSettings;
|
||||||
copyToSave.rebootRequired_ = false;
|
copyToSave.rebootRequired_ = false;
|
||||||
@@ -945,7 +947,8 @@ void Storage::LoadUserSettings()
|
|||||||
{
|
{
|
||||||
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
|
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
if (std::filesystem::is_regular_file(path))
|
if (std::filesystem::is_regular_file(path))
|
||||||
{
|
{
|
||||||
std::ifstream f(path);
|
std::ifstream f(path);
|
||||||
@@ -956,17 +959,18 @@ void Storage::LoadUserSettings()
|
|||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
reader.read(&userSettings);
|
reader.read(&userSettings);
|
||||||
}
|
}
|
||||||
} catch (const std::exception&)
|
}
|
||||||
|
catch (const std::exception &)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
this->wifiConfigSettings.valid_ = true;
|
this->wifiConfigSettings.valid_ = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
void Storage::LoadWifiConfigSettings()
|
void Storage::LoadWifiConfigSettings()
|
||||||
{
|
{
|
||||||
std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME;
|
std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME;
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
if (std::filesystem::is_regular_file(path))
|
if (std::filesystem::is_regular_file(path))
|
||||||
{
|
{
|
||||||
std::ifstream f(path);
|
std::ifstream f(path);
|
||||||
@@ -979,9 +983,9 @@ void Storage::LoadWifiConfigSettings()
|
|||||||
reader.read(&wifiConfigSettings);
|
reader.read(&wifiConfigSettings);
|
||||||
this->wifiConfigSettings = wifiConfigSettings;
|
this->wifiConfigSettings = wifiConfigSettings;
|
||||||
}
|
}
|
||||||
} catch (const std::exception&)
|
}
|
||||||
|
catch (const std::exception &)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
this->wifiConfigSettings.valid_ = true;
|
this->wifiConfigSettings.valid_ = true;
|
||||||
}
|
}
|
||||||
@@ -996,8 +1000,6 @@ void Storage::LoadWifiDirectConfigSettings()
|
|||||||
this->wifiDirectConfigSettings = settings;
|
this->wifiDirectConfigSettings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WifiConfigSettings Storage::GetWifiConfigSettings()
|
WifiConfigSettings Storage::GetWifiConfigSettings()
|
||||||
{
|
{
|
||||||
return this->wifiConfigSettings;
|
return this->wifiConfigSettings;
|
||||||
@@ -1007,43 +1009,47 @@ WifiDirectConfigSettings Storage::GetWifiDirectConfigSettings()
|
|||||||
return this->wifiDirectConfigSettings;
|
return this->wifiDirectConfigSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset)
|
void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
std::filesystem::path path = GetCurrentPresetPath();
|
std::filesystem::path path = GetCurrentPresetPath();
|
||||||
|
|
||||||
std::ofstream f(path);
|
std::ofstream f(path);
|
||||||
json_writer writer(f);
|
json_writer writer(f);
|
||||||
writer.write(currentPreset);
|
writer.write(currentPreset);
|
||||||
} catch (std::exception&)
|
}
|
||||||
|
catch (std::exception &)
|
||||||
{
|
{
|
||||||
// called from destructor. Must be nothrow().
|
// called from destructor. Must be nothrow().
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
bool Storage::RestoreCurrentPreset(CurrentPreset*pResult)
|
bool Storage::RestoreCurrentPreset(CurrentPreset *pResult)
|
||||||
{
|
{
|
||||||
std::filesystem::path path = GetCurrentPresetPath();
|
std::filesystem::path path = GetCurrentPresetPath();
|
||||||
if (std::filesystem::exists(path)) {
|
if (std::filesystem::exists(path))
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
std::ifstream f(path);
|
std::ifstream f(path);
|
||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
reader.read(pResult);
|
reader.read(pResult);
|
||||||
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
|
std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown.
|
||||||
} catch (const std::exception&)
|
}
|
||||||
|
catch (const std::exception &)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
bool Storage::HasPluginPresets(const std::string &pluginUri) const
|
bool Storage::HasPluginPresets(const std::string &pluginUri) const
|
||||||
{
|
{
|
||||||
for (const auto &entry: this->pluginPresetIndex.entries_)
|
for (const auto &entry : this->pluginPresetIndex.entries_)
|
||||||
{
|
{
|
||||||
if (entry.pluginUri_ == pluginUri)
|
if (entry.pluginUri_ == pluginUri)
|
||||||
{
|
{
|
||||||
@@ -1055,7 +1061,7 @@ bool Storage::HasPluginPresets(const std::string &pluginUri) const
|
|||||||
|
|
||||||
std::filesystem::path Storage::GetPluginPresetPath(const std::string &pluginUri) const
|
std::filesystem::path Storage::GetPluginPresetPath(const std::string &pluginUri) const
|
||||||
{
|
{
|
||||||
for (const auto &entry: this->pluginPresetIndex.entries_)
|
for (const auto &entry : this->pluginPresetIndex.entries_)
|
||||||
{
|
{
|
||||||
if (entry.pluginUri_ == pluginUri)
|
if (entry.pluginUri_ == pluginUri)
|
||||||
{
|
{
|
||||||
@@ -1063,9 +1069,8 @@ std::filesystem::path Storage::GetPluginPresetPath(const std::string &pluginUri)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw PiPedalArgumentException("Plugin preset file not found.");
|
throw PiPedalArgumentException("Plugin preset file not found.");
|
||||||
|
|
||||||
}
|
}
|
||||||
void Storage::SavePluginPresets(const std::string&pluginUri, const PluginPresets&presets)
|
void Storage::SavePluginPresets(const std::string &pluginUri, const PluginPresets &presets)
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
@@ -1075,10 +1080,12 @@ void Storage::SavePluginPresets(const std::string&pluginUri, const PluginPresets
|
|||||||
name = SS(pluginPresetIndex.nextInstanceId_ << ".json");
|
name = SS(pluginPresetIndex.nextInstanceId_ << ".json");
|
||||||
path = GetPluginPresetsDirectory() / name;
|
path = GetPluginPresetsDirectory() / name;
|
||||||
presetAdded = true;
|
presetAdded = true;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
path = GetPluginPresetPath(pluginUri);
|
path = GetPluginPresetPath(pluginUri);
|
||||||
}
|
}
|
||||||
auto tempPath = path.string()+".$$$";
|
auto tempPath = path.string() + ".$$$";
|
||||||
{
|
{
|
||||||
std::ofstream os;
|
std::ofstream os;
|
||||||
os.open(tempPath, std::ios_base::trunc);
|
os.open(tempPath, std::ios_base::trunc);
|
||||||
@@ -1093,22 +1100,20 @@ void Storage::SavePluginPresets(const std::string&pluginUri, const PluginPresets
|
|||||||
{
|
{
|
||||||
std::filesystem::remove(path);
|
std::filesystem::remove(path);
|
||||||
}
|
}
|
||||||
std::filesystem::rename(tempPath,path);
|
std::filesystem::rename(tempPath, path);
|
||||||
|
|
||||||
if (presetAdded)
|
if (presetAdded)
|
||||||
{
|
{
|
||||||
pluginPresetIndex.entries_.push_back(
|
pluginPresetIndex.entries_.push_back(
|
||||||
PluginPresetIndexEntry(
|
PluginPresetIndexEntry(
|
||||||
pluginUri,name
|
pluginUri, name));
|
||||||
)
|
|
||||||
);
|
|
||||||
pluginPresetIndex.nextInstanceId_++;
|
pluginPresetIndex.nextInstanceId_++;
|
||||||
this->pluginPresetIndexChanged = true;
|
this->pluginPresetIndexChanged = true;
|
||||||
SavePluginPresetIndex();
|
SavePluginPresetIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginPresets Storage::GetPluginPresets(const std::string&pluginUri) const
|
PluginPresets Storage::GetPluginPresets(const std::string &pluginUri) const
|
||||||
{
|
{
|
||||||
PluginPresets result;
|
PluginPresets result;
|
||||||
if (!HasPluginPresets(pluginUri))
|
if (!HasPluginPresets(pluginUri))
|
||||||
@@ -1127,36 +1132,33 @@ PluginPresets Storage::GetPluginPresets(const std::string&pluginUri) const
|
|||||||
reader.read(&result);
|
reader.read(&result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
PluginUiPresets Storage::GetPluginUiPresets(const std::string&pluginUri) const
|
PluginUiPresets Storage::GetPluginUiPresets(const std::string &pluginUri) const
|
||||||
{
|
{
|
||||||
PluginPresets presets = GetPluginPresets(pluginUri);
|
PluginPresets presets = GetPluginPresets(pluginUri);
|
||||||
PluginUiPresets result;
|
PluginUiPresets result;
|
||||||
result.pluginUri_ = presets.pluginUri_;
|
result.pluginUri_ = presets.pluginUri_;
|
||||||
for (size_t i = 0; i < presets.presets_.size(); ++i)
|
for (size_t i = 0; i < presets.presets_.size(); ++i)
|
||||||
{
|
{
|
||||||
const auto& preset = presets.presets_[i];
|
const auto &preset = presets.presets_[i];
|
||||||
result.presets_.push_back(
|
result.presets_.push_back(
|
||||||
PluginUiPreset
|
PluginUiPreset{
|
||||||
{
|
|
||||||
preset.label_,
|
preset.label_,
|
||||||
preset.instanceId_
|
preset.instanceId_});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId)
|
std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string &pluginUri, uint64_t instanceId)
|
||||||
{
|
{
|
||||||
auto presets = GetPluginPresets(pluginUri);
|
auto presets = GetPluginPresets(pluginUri);
|
||||||
for (const auto & preset: presets.presets_)
|
for (const auto &preset : presets.presets_)
|
||||||
{
|
{
|
||||||
if (preset.instanceId_ == instanceId)
|
if (preset.instanceId_ == instanceId)
|
||||||
{
|
{
|
||||||
std::vector<ControlValue> result;
|
std::vector<ControlValue> result;
|
||||||
for (const auto &valuePair: preset.controlValues_)
|
for (const auto &valuePair : preset.controlValues_)
|
||||||
{
|
{
|
||||||
result.push_back(ControlValue(valuePair.first.c_str(),valuePair.second));
|
result.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1165,16 +1167,16 @@ std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string&plugi
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Storage::SavePluginPreset(
|
uint64_t Storage::SavePluginPreset(
|
||||||
const std::string&pluginUri,
|
const std::string &pluginUri,
|
||||||
const std::string&name,
|
const std::string &name,
|
||||||
const std::map<std::string,float> & values)
|
const std::map<std::string, float> &values)
|
||||||
{
|
{
|
||||||
auto presets = GetPluginPresets(pluginUri);
|
auto presets = GetPluginPresets(pluginUri);
|
||||||
uint64_t result = -1;
|
uint64_t result = -1;
|
||||||
bool existing = false;
|
bool existing = false;
|
||||||
for (size_t i = 0; i < presets.presets_.size(); ++i)
|
for (size_t i = 0; i < presets.presets_.size(); ++i)
|
||||||
{
|
{
|
||||||
auto & preset = presets.presets_[i];
|
auto &preset = presets.presets_[i];
|
||||||
if (preset.label_ == name)
|
if (preset.label_ == name)
|
||||||
{
|
{
|
||||||
preset.controlValues_ = values;
|
preset.controlValues_ = values;
|
||||||
@@ -1190,10 +1192,9 @@ uint64_t Storage::SavePluginPreset(
|
|||||||
PluginPreset(
|
PluginPreset(
|
||||||
result,
|
result,
|
||||||
name,
|
name,
|
||||||
values
|
values));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
this->SavePluginPresets(pluginUri,presets);
|
this->SavePluginPresets(pluginUri, presets);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1201,26 +1202,24 @@ void Storage::UpdatePluginPresets(const PluginUiPresets &pluginPresets)
|
|||||||
{
|
{
|
||||||
// handles deletions, renaming, and reordering only.
|
// handles deletions, renaming, and reordering only.
|
||||||
// If you need to add a preset, you neet to call SavePluginPreset or DulicatePluginPreset instead.
|
// If you need to add a preset, you neet to call SavePluginPreset or DulicatePluginPreset instead.
|
||||||
|
|
||||||
PluginPresets presets = this->GetPluginPresets(pluginPresets.pluginUri_);
|
PluginPresets presets = this->GetPluginPresets(pluginPresets.pluginUri_);
|
||||||
PluginPresets newPresets;
|
PluginPresets newPresets;
|
||||||
newPresets.pluginUri_ = pluginPresets.pluginUri_;
|
newPresets.pluginUri_ = pluginPresets.pluginUri_;
|
||||||
newPresets.nextInstanceId_ = presets.nextInstanceId_;
|
newPresets.nextInstanceId_ = presets.nextInstanceId_;
|
||||||
|
|
||||||
for (const auto&preset: pluginPresets.presets_)
|
for (const auto &preset : pluginPresets.presets_)
|
||||||
{
|
{
|
||||||
PluginPreset newPreset = presets.GetPreset(preset.instanceId_);
|
PluginPreset newPreset = presets.GetPreset(preset.instanceId_);
|
||||||
newPreset.label_ = preset.label_;
|
newPreset.label_ = preset.label_;
|
||||||
newPresets.presets_.push_back(std::move(newPreset));
|
newPresets.presets_.push_back(std::move(newPreset));
|
||||||
}
|
}
|
||||||
SavePluginPresets(newPresets.pluginUri_,newPresets);
|
SavePluginPresets(newPresets.pluginUri_, newPresets);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string stripCopySuffix(const std::string &s)
|
static std::string stripCopySuffix(const std::string &s)
|
||||||
{
|
{
|
||||||
int pos = s.length()-1;
|
int pos = s.length() - 1;
|
||||||
if (pos >= 0 && s[pos] == ')')
|
if (pos >= 0 && s[pos] == ')')
|
||||||
{
|
{
|
||||||
--pos;
|
--pos;
|
||||||
@@ -1228,7 +1227,7 @@ static std::string stripCopySuffix(const std::string &s)
|
|||||||
{
|
{
|
||||||
--pos;
|
--pos;
|
||||||
}
|
}
|
||||||
if (pos >= 0 && s[pos] == '(')
|
if (pos >= 0 && s[pos] == '(')
|
||||||
{
|
{
|
||||||
--pos;
|
--pos;
|
||||||
}
|
}
|
||||||
@@ -1236,11 +1235,11 @@ static std::string stripCopySuffix(const std::string &s)
|
|||||||
{
|
{
|
||||||
--pos;
|
--pos;
|
||||||
}
|
}
|
||||||
return s.substr(0,pos+1);
|
return s.substr(0, pos + 1);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
uint64_t Storage::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId)
|
uint64_t Storage::CopyPluginPreset(const std::string &pluginUri, uint64_t presetId)
|
||||||
{
|
{
|
||||||
PluginPresets presets = this->GetPluginPresets(pluginUri);
|
PluginPresets presets = this->GetPluginPresets(pluginUri);
|
||||||
size_t pos = presets.Find(presetId);
|
size_t pos = presets.Find(presetId);
|
||||||
@@ -1266,17 +1265,14 @@ uint64_t Storage::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.label_ = name;
|
t.label_ = name;
|
||||||
presets.presets_.insert(presets.presets_.begin()+pos+1,t);
|
presets.presets_.insert(presets.presets_.begin() + pos + 1, t);
|
||||||
SavePluginPresets(presets.pluginUri_,presets);
|
SavePluginPresets(presets.pluginUri_, presets);
|
||||||
return t.instanceId_;
|
return t.instanceId_;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string,bool> Storage::GetFavorites() const
|
std::map<std::string, bool> Storage::GetFavorites() const
|
||||||
{
|
{
|
||||||
std::map<std::string,bool> result;
|
std::map<std::string, bool> result;
|
||||||
|
|
||||||
std::filesystem::path fileName = this->dataRoot / "favorites.json";
|
std::filesystem::path fileName = this->dataRoot / "favorites.json";
|
||||||
if (!std::filesystem::exists(fileName))
|
if (!std::filesystem::exists(fileName))
|
||||||
@@ -1291,10 +1287,9 @@ std::map<std::string,bool> Storage::GetFavorites() const
|
|||||||
reader.read(&result);
|
reader.read(&result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
void Storage::SetFavorites(const std::map<std::string,bool>&favorites) {
|
void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
|
||||||
|
{
|
||||||
std::filesystem::path fileName = this->dataRoot / "favorites.json";
|
std::filesystem::path fileName = this->dataRoot / "favorites.json";
|
||||||
std::ofstream f;
|
std::ofstream f;
|
||||||
f.open(fileName);
|
f.open(fileName);
|
||||||
@@ -1305,15 +1300,45 @@ void Storage::SetFavorites(const std::map<std::string,bool>&favorites) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pipedal::JackServerSettings Storage::GetJackServerSettings()
|
||||||
|
{
|
||||||
|
JackServerSettings result;
|
||||||
|
#if JACK_HOST
|
||||||
|
result.Initialize();
|
||||||
|
#else
|
||||||
|
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
|
||||||
|
std::ifstream f;
|
||||||
|
f.open(fileName);
|
||||||
|
if (f.is_open())
|
||||||
|
{
|
||||||
|
json_reader reader(f);
|
||||||
|
reader.read(&result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfiguration)
|
||||||
|
{
|
||||||
|
#if JACK_HOST
|
||||||
|
#error IMPLEMENT ME
|
||||||
|
#else
|
||||||
|
std::filesystem::path fileName = this->dataRoot / "AudioConfig.json";
|
||||||
|
std::ofstream f;
|
||||||
|
f.open(fileName);
|
||||||
|
if (f.is_open())
|
||||||
|
{
|
||||||
|
json_writer writer(f);
|
||||||
|
writer.write(jackConfiguration);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UserSettings)
|
JSON_MAP_BEGIN(UserSettings)
|
||||||
JSON_MAP_REFERENCE(UserSettings,governor)
|
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||||
JSON_MAP_REFERENCE(UserSettings,showStatusMonitor)
|
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
JSON_MAP_BEGIN(CurrentPreset)
|
JSON_MAP_BEGIN(CurrentPreset)
|
||||||
JSON_MAP_REFERENCE(CurrentPreset,modified)
|
JSON_MAP_REFERENCE(CurrentPreset, modified)
|
||||||
JSON_MAP_REFERENCE(CurrentPreset,preset)
|
JSON_MAP_REFERENCE(CurrentPreset, preset)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "PluginPreset.hpp"
|
#include "PluginPreset.hpp"
|
||||||
#include "Banks.hpp"
|
#include "Banks.hpp"
|
||||||
#include "JackConfiguration.hpp"
|
#include "JackConfiguration.hpp"
|
||||||
|
#include "JackServerSettings.hpp"
|
||||||
#include "WifiConfigSettings.hpp"
|
#include "WifiConfigSettings.hpp"
|
||||||
#include "WifiDirectConfigSettings.hpp"
|
#include "WifiDirectConfigSettings.hpp"
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -100,6 +101,8 @@ public:
|
|||||||
|
|
||||||
const BankIndex & GetBanks() const { return bankIndex; }
|
const BankIndex & GetBanks() const { return bankIndex; }
|
||||||
|
|
||||||
|
JackServerSettings GetJackServerSettings();
|
||||||
|
void SetJackServerSettings(const pipedal::JackServerSettings&jackServerSettings);
|
||||||
|
|
||||||
void LoadWifiConfigSettings();
|
void LoadWifiConfigSettings();
|
||||||
void LoadWifiDirectConfigSettings();
|
void LoadWifiDirectConfigSettings();
|
||||||
|
|||||||
+79
-56
@@ -18,6 +18,8 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "AudioConfig.hpp"
|
||||||
#include "BeastServer.hpp"
|
#include "BeastServer.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
@@ -404,7 +406,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* When hosting a react app, replace /var/config.json with
|
/* When hosting a react app, replace /var/config.json with
|
||||||
data that will connect the react app with our socket server.
|
data that will connect the react app with our socket server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -488,7 +490,6 @@ static bool isJackServiceRunning()
|
|||||||
return std::filesystem::exists(path);
|
return std::filesystem::exists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -621,39 +622,10 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
PiPedalModel model;
|
PiPedalModel model;
|
||||||
|
|
||||||
// Pre-populate ALSA device info,wait for jackd service (daemon only)
|
model.Init(configuration);
|
||||||
model.GetAlsaDevices(); // pre-cache ALSA devices.
|
|
||||||
if (systemd)
|
|
||||||
{
|
|
||||||
auto devices = model.GetAlsaDevices(); // pre-cache ALSA devices (most especially, the one which Jack will open, that we can no longer read)
|
|
||||||
|
|
||||||
auto serverSettings = model.GetJackServerSettings();
|
// Get heavy IO out of the way before letting dependent (Jack/ALSA) services run.
|
||||||
if (serverSettings.IsValid())
|
model.LoadLv2PluginInfo();
|
||||||
{
|
|
||||||
// wait up to 15 seconds for the midi device to come online.
|
|
||||||
if (!HasAlsaDevice(devices, serverSettings.GetAlsaDevice()))
|
|
||||||
{
|
|
||||||
Lv2Log::info("Waiting for ALSA device " + serverSettings.GetAlsaDevice());
|
|
||||||
for (int i = 0; i < 10; ++i)
|
|
||||||
{
|
|
||||||
sleep(1);
|
|
||||||
devices = model.GetAlsaDevices();
|
|
||||||
if (HasAlsaDevice(devices, serverSettings.GetAlsaDevice()))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (g_SigBreak)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-cache device info before we let audio services run.
|
|
||||||
model.GetAlsaDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get heavy IO out of the way before letting dependent (Jack) services run.
|
|
||||||
model.LoadLv2PluginInfo(configuration);
|
|
||||||
if (systemd)
|
if (systemd)
|
||||||
{
|
{
|
||||||
// Tell systemd we're done.
|
// Tell systemd we're done.
|
||||||
@@ -662,34 +634,85 @@ int main(int argc, char *argv[])
|
|||||||
(unsigned long)getpid());
|
(unsigned long)getpid());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isJackServiceRunning())
|
|
||||||
{
|
|
||||||
Lv2Log::info("Waiting for Jack service.");
|
|
||||||
// wait up to 15 seconds for the jack service to come online.
|
|
||||||
for (int i = 0; i < 15; ++i)
|
|
||||||
{
|
|
||||||
// use the time to prepopulate ALSA device cache before jack
|
|
||||||
// opens the device and we can't read properties.
|
|
||||||
model.GetAlsaDevices();
|
|
||||||
|
|
||||||
sleep(1);
|
auto serverSettings = model.GetJackServerSettings();
|
||||||
if (isJackServiceRunning())
|
|
||||||
|
|
||||||
|
if (true || systemd)
|
||||||
|
{
|
||||||
|
// IF running as a service, wait for selected audio device to be initialized.
|
||||||
|
// It may take some time for ALSA to publish all available devices.
|
||||||
|
|
||||||
|
if (serverSettings.IsValid())
|
||||||
|
{
|
||||||
|
// wait up to 15 seconds for the midi device to come online.
|
||||||
|
auto devices = model.GetAlsaDevices();
|
||||||
|
bool firstMessage = true;
|
||||||
|
bool found = false;
|
||||||
|
if (!HasAlsaDevice(devices, serverSettings.GetAlsaDevice()))
|
||||||
{
|
{
|
||||||
break;
|
if (firstMessage) Lv2Log::info(SS("Waiting for ALSA device " << serverSettings.GetAlsaDevice() << " to come online..."));
|
||||||
|
firstMessage = false;
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
sleep(3);
|
||||||
|
devices = model.GetAlsaDevices();
|
||||||
|
if (HasAlsaDevice(devices, serverSettings.GetAlsaDevice()))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (g_SigBreak)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaDevice() << "."));
|
||||||
|
} else {
|
||||||
|
Lv2Log::info(SS("ALSA device " << serverSettings.GetAlsaDevice() << " not found."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isJackServiceRunning())
|
// pre-cache device info before we let audio services run.
|
||||||
{
|
model.GetAlsaDevices();
|
||||||
Lv2Log::info("Found Jack service.");
|
|
||||||
sleep(3); // jack needs a little time to get up to speed.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Lv2Log::info("Jack service not started.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.Load(configuration);
|
#if JACK_HOST
|
||||||
|
if (systemd)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!isJackServiceRunning())
|
||||||
|
{
|
||||||
|
Lv2Log::info("Waiting for Jack service.");
|
||||||
|
// wait up to 15 seconds for the jack service to come online.
|
||||||
|
for (int i = 0; i < 15; ++i)
|
||||||
|
{
|
||||||
|
// use the time to prepopulate ALSA device cache before jack
|
||||||
|
// opens the device and we can't read properties.
|
||||||
|
model.GetAlsaDevices();
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
if (isJackServiceRunning())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isJackServiceRunning())
|
||||||
|
{
|
||||||
|
Lv2Log::info("Found Jack service.");
|
||||||
|
sleep(3); // jack needs a little time to get up to speed.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::info("Jack service not started.");
|
||||||
|
}
|
||||||
|
model.GetAlsaDevices();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
model.Load();
|
||||||
|
|
||||||
auto pipedalSocketFactory = MakePiPedalSocketFactory(model);
|
auto pipedalSocketFactory = MakePiPedalSocketFactory(model);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Description=Jack Audio Daemon
|
|||||||
After=sound.target
|
After=sound.target
|
||||||
After=local-fs.target
|
After=local-fs.target
|
||||||
After=dbus.socket
|
After=dbus.socket
|
||||||
|
After=pipedald
|
||||||
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=${DESCRIPTION}
|
Description=${DESCRIPTION}
|
||||||
Before=jack.service
|
|
||||||
After=network.target
|
After=network.target
|
||||||
After=sound.target
|
After=sound.target
|
||||||
After=local-fs.target
|
After=local-fs.target
|
||||||
|
|||||||
Reference in New Issue
Block a user