0.9.11-rc-2

Switch from JACK to ALSA audio.
This commit is contained in:
Robin Davies
2022-07-01 21:15:57 -04:00
parent 0995e0fb16
commit 0a67b39103
51 changed files with 4301 additions and 632 deletions
+39 -1
View File
@@ -5,6 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) generic",
"type": "cppdbg",
@@ -77,7 +78,9 @@
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ "[avahi_service]" ],
"args": [ "[alsa_midi_test]" ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"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",
"type": "cppdbg",
+5 -3
View File
@@ -1,10 +1,11 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 0.9.10
VERSION 0.9.11
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
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/")
@@ -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_SHLIBDEPS ON)
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_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
+1 -1
View File
@@ -1,7 +1,7 @@
<img src='docs/GithubBanner.png' width="100%" />
Download: <a href='docs/download.md'>v0.9.10-rc-1</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Download: <a href='docs/download.md'>v0.9.11-rc-2</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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.
+2 -2
View File
@@ -4,14 +4,14 @@
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
```
sudo apt update
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).
+2 -2
View File
@@ -4,14 +4,14 @@
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
```
sudo apt update
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.
+1 -1
View File
@@ -1,7 +1,7 @@
<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.
+1
View File
@@ -21,6 +21,7 @@ add_custom_command(
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
DEPENDS
src/AlsaMidiDeviceInfo.tsx
src/SearchControl.tsx
src/SplitControlView.tsx
src/WifiConfigSettings.tsx
+45
View File
@@ -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
View File
@@ -18,13 +18,14 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import {PiPedalArgumentError} from './PiPedalError';
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
export class JackChannelSelection {
deserialize(input: any): JackChannelSelection
{
this.inputAudioPorts = input.inputAudioPorts.slice();
this.outputAudioPorts = input.outputAudioPorts.slice();
this.inputMidiPorts = input.inputMidiPorts.slice();
this.inputMidiDevices = AlsaMidiDeviceInfo.deserializeArray(input.inputMidiDevices);
this.sampleRate = input.sampleRate;
this.bufferSize = input.bufferSize;
this.numberOfBuffers = input.numberOfBuffers;
@@ -35,7 +36,7 @@ export class JackChannelSelection {
}
inputAudioPorts: string[] = [];
outputAudioPorts: string[] = [];
inputMidiPorts: string[] = [];
inputMidiDevices: AlsaMidiDeviceInfo[] = [];
sampleRate: number = 48000;
bufferSize: number = 64;
@@ -45,6 +46,7 @@ export class JackChannelSelection {
let result = new JackChannelSelection();
result.inputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
result.outputAudioPorts = jackConfiguration.inputAudioPorts.slice(0,2);
result.inputMidiDevices = [];
return result;
}
getChannelDisplayValue(selectedChannels: string[], availableChannels: string[],isConfigValid: boolean): string
@@ -97,8 +99,7 @@ export class JackConfiguration {
this.maxAllowedMidiDelta = input.maxAllowedMidiDelta;
this.inputAudioPorts = input.inputAudioPorts;
this.outputAudioPorts = input.outputAudioPorts;
this.inputMidiPorts = input.inputMidiPorts;
this.outputMidiPorts = input.outputMidiPorts;
this.inputMidiDevices = AlsaMidiDeviceInfo.deserializeArray(input.inputMidiDevices);
return this;
}
isValid: boolean = false;
@@ -111,8 +112,8 @@ export class JackConfiguration {
inputAudioPorts: string[] = [];
outputAudioPorts: string[] = [];
inputMidiPorts: string[] = [];
outputMidiPorts: string[] = [];
inputMidiDevices: AlsaMidiDeviceInfo[] = [];
};
+3 -1
View File
@@ -58,6 +58,7 @@ export default class JackHostStatus {
deserialize(input: any): JackHostStatus {
this.active = input.active;
this.restarting = input.restarting;
this.errorMessage = input.errorMessage;
this.underruns = input.underruns;
this.cpuUsage = input.cpuUsage;
this.msSinceLastUnderrun = input.msSinceLastUnderrun;
@@ -71,6 +72,7 @@ export default class JackHostStatus {
return this.temperaturemC >= -100000;
}
active: boolean = false;
errorMessage: string = "";
restarting: boolean = false;
underruns: number = 0;
cpuUsage: number = 0;
@@ -136,7 +138,7 @@ export default class JackHostStatus {
<Typography variant="caption" color="textSecondary">{label}</Typography>
<span style={{ color: RED_COLOR }}>
<Typography variant="caption" color="inherit">Stopped&nbsp;&nbsp;</Typography>
<Typography variant="caption" color="inherit">{status.errorMessage === "" ? "Stopped" : status.errorMessage}&nbsp;&nbsp;</Typography>
</span>
{
status.temperaturemC > -100000 &&
+1 -1
View File
@@ -135,7 +135,7 @@ const JackServerSettingsDialog = withStyles(styles)(
}
if (result.sampleRate === 0) result.sampleRate = 48000;
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.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
result.valid = true;
+1 -1
View File
@@ -96,6 +96,7 @@ export const MidiBindingDialog =
hasHooks: boolean = false;
handleClose() {
this.cancelListenForControl();
this.props.onClose();
}
@@ -120,7 +121,6 @@ export const MidiBindingDialog =
handleListenSucceeded(instanceId: number, symbol: string, isNote: boolean, noteOrControl: number)
{
this.listenHandle = undefined; // (one-shot event)
this.cancelListenForControl();
let pedalBoard = this.model.pedalBoard.get();
+6
View File
@@ -1826,12 +1826,18 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
cancelListenForMidiEvent(listenHandle: ListenHandle): void {
let found = false;
for (let i = 0; i < this.midiListeners.length; ++i) {
if (this.midiListeners[i].handle === listenHandle._handle) {
this.midiListeners.splice(i, 1);
found = true;
break;
}
}
if (!found)
{
console.log('cancelListenForMidiEvent: event not found.');
}
this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle);
}
cancelListenForAtomOutput(listenHandle: ListenHandle): void {
+20 -15
View File
@@ -27,38 +27,43 @@ import Dialog from '@mui/material/Dialog';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
export interface SelectMidiChannelsDialogProps {
open: boolean;
selectedChannels: string[];
availableChannels: string[];
onClose: (selectedChannels: string[] | null) => void;
selectedChannels: AlsaMidiDeviceInfo[];
availableChannels: AlsaMidiDeviceInfo[];
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) {
if (selectedChannels[i] === channel) return true;
if (selectedChannels[i].equals(channel)) return true;
}
return false;
}
function addPort(availableChannels: string[], selectedChannels: string[], newChannel: string) {
let result: string[] = [];
function addPort(availableChannels: AlsaMidiDeviceInfo[], selectedChannels: AlsaMidiDeviceInfo[], newChannel: AlsaMidiDeviceInfo)
:AlsaMidiDeviceInfo[]
{
let result: AlsaMidiDeviceInfo[] = [];
for (let i = 0; i < availableChannels.length; ++i) {
let channel = availableChannels[i];
if (isChecked(selectedChannels, channel) || channel === newChannel) {
if (isChecked(selectedChannels, channel) || channel.equals(newChannel)) {
result.push(channel);
}
}
return result;
}
function removePort(selectedChannels: string[], channel: string) {
let result: string[] = [];
function removePort(selectedChannels: AlsaMidiDeviceInfo[], channel: AlsaMidiDeviceInfo)
:AlsaMidiDeviceInfo[]
{
let result: AlsaMidiDeviceInfo[] = [];
for (let i = 0; i < selectedChannels.length; ++i) {
if (selectedChannels[i] !== channel) {
if (!selectedChannels[i].equals(channel)) {
result.push(selectedChannels[i]);
}
}
@@ -72,7 +77,7 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
let toggleSelect = (value: string) => {
let toggleSelect = (value: AlsaMidiDeviceInfo) => {
if (!isChecked(currentSelection, value)) {
setCurrentSelection(addPort(availableChannels, currentSelection, value));
} else {
@@ -90,15 +95,15 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
return (
<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>
{availableChannels.map((channel) => (
<ListItem button >
<FormControlLabel
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>
)
+10 -9
View File
@@ -42,6 +42,7 @@ import WifiConfigDialog from './WifiConfigDialog';
import WifiDirectConfigDialog from './WifiDirectConfigDialog';
import DialogEx from './DialogEx'
import GovernorSettings from './GovernorSettings';
import {AlsaMidiDeviceInfo} from './AlsaMidiDeviceInfo';
import Slide, { SlideProps } from '@mui/material/Slide';
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) {
let newSelection = this.state.jackSettings.clone();
newSelection.inputMidiPorts = channels;
let newSelection: JackChannelSelection = this.state.jackSettings.clone();
newSelection.inputMidiDevices = channels;
this.model.setJackSettings(newSelection);
}
@@ -430,9 +431,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
}
midiSummary(): string {
let ports = this.state.jackSettings.inputMidiPorts;
let ports = this.state.jackSettings.inputMidiDevices;
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";
}
handleShowWifiConfigDialog() {
@@ -592,7 +593,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<ButtonBase className={classes.setting} disabled={!isConfigValid} onClick={() => this.handleMidiSelection()} >
<SelectHoverBackground selected={false} showHover={true} />
<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>
</div>
@@ -767,9 +768,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
(this.state.showMidiSelectDialog) &&
(
<SelectMidiChannelsDialog open={this.state.showMidiSelectDialog}
onClose={(selectedChannels: string[] | null) => this.handleSelectMidiDialogResult(selectedChannels)}
selectedChannels={this.state.jackSettings.inputMidiPorts}
availableChannels={this.state.jackConfiguration.inputMidiPorts}
onClose={(selectedChannels: AlsaMidiDeviceInfo[] | null) => this.handleSelectMidiDialogResult(selectedChannels)}
selectedChannels={this.state.jackSettings.inputMidiDevices}
availableChannels={this.state.jackConfiguration.inputMidiDevices}
/>
)
+6
View File
@@ -204,10 +204,16 @@ bool setJackConfiguration(JackServerSettings serverSettings)
{
bool success = true;
#if JACK_HOST
serverSettings.Write();
silentSysExec("/usr/bin/systemctl unmask 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);
delayedRestartThread.detach();
+1686
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -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();
}
+278
View File
@@ -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();
}
+32
View File
@@ -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
+89
View File
@@ -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
View File
@@ -94,6 +94,7 @@ add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY -DBOOST_BIND_GLOBAL_PLACEHOLDERS
set (PIPEDAL_SOURCES
CpuUse.hpp CpuUse.cpp
P2pConfigFiles.hpp
AvahiService.cpp AvahiService.hpp
DeviceIdFile.cpp DeviceIdFile.hpp
@@ -153,6 +154,11 @@ set (PIPEDAL_SOURCES
InheritPriorityMutex.hpp InheritPriorityMutex.cpp
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
${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
jsonTest.cpp
AlsaDriverTest.cpp
AvahiServiceTest.cpp
WifiChannelsTest.cpp
PiPedalAlsaTest.cpp
@@ -259,6 +268,27 @@ add_executable(pipedalconfig
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
UnixSocket.cpp UnixSocket.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)
add_executable(processcopyrights copyrightMain.cpp
CommandLineParser.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)
+37
View File
@@ -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)
{
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 &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)
{
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>
void AddOption(const std::string &option, T *pResult)
{
+36 -12
View File
@@ -36,6 +36,17 @@
#include "DeviceIdFile.hpp"
#include <uuid/uuid.h>
#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())
@@ -110,6 +121,14 @@ void EnableService()
}
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)
{
cout << "Error: Failed to disable the " NATIVE_SERVICE " service.";
@@ -145,10 +164,14 @@ void StopService(bool excludeShutdownService = false)
}
#endif
}
#if INSTALL_JACK_SERVICE || UNINSTALL_JACK_SERVICE
if (sysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS)
{
#if INSTALL_JACK_SERVICE
throw PiPedalException("Failed to stop the " JACK_SERVICE " service.");
#endif
}
#endif
}
void StartService(bool excludeShutdownService = false)
@@ -166,6 +189,7 @@ void StartService(bool excludeShutdownService = false)
{
throw PiPedalException("Failed to start the " NATIVE_SERVICE " service.");
}
#if INSTALL_JACK_SERVICE
JackServerSettings serverSettings;
serverSettings.ReadJackConfiguration();
if (serverSettings.IsValid())
@@ -175,6 +199,7 @@ void StartService(bool excludeShutdownService = false)
throw PiPedalException("Failed to start the " JACK_SERVICE " service.");
}
}
#endif
}
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;
void UninstallPamEnv()
{
#if UNINSTALL_JACK_SERVICE
RemoveLine(
"/etc/security/pam_env.conf",
PAM_LINE);
#endif
}
void InstallPamEnv()
{
#if INSTALL_JACK_SERVICE
std::string newLine = PAM_LINE;
std::vector<std::string> lines;
std::filesystem::path path = "/etc/security/pam_env.conf";
@@ -313,6 +341,7 @@ void InstallPamEnv()
{
cout << "Failed to update " << path << ". " << e.what();
}
#endif
}
void InstallLimits()
@@ -322,12 +351,12 @@ void InstallLimits()
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))
{
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";
output << "@audio - rtprio 95"
<< "\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()
{
@@ -363,6 +383,8 @@ void InstallJackService()
{
InstallLimits();
InstallPamEnv();
#if INSTALL_JACK_SERVICE
if (sysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
{
throw PiPedalException("Failed to create jack service group.");
@@ -391,6 +413,8 @@ void InstallJackService()
WriteTemplateFile(map, GetServiceFileName(JACK_SERVICE));
MaybeStartJackService();
#endif
}
int SudoExec(char **argv)
@@ -463,7 +487,7 @@ void Uninstall()
{
}
UninstallPamEnv();
UninstallLimits();
// UninstallLimits();
sysExec(SYSTEMCTL_BIN " daemon-reload");
}
catch (const std::exception &e)
+38
View File
@@ -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
View File
@@ -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
View File
@@ -19,50 +19,24 @@
#include "pch.h"
#include "JackConfiguration.hpp"
#include "AudioConfig.hpp"
#include "AlsaDriver.hpp"
#include "Lv2Log.hpp"
#include "PiPedalException.hpp"
#if JACK_HOST
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/session.h>
#endif
#include <string.h>
#include "defer.hpp"
#include <algorithm>
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()
{
@@ -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,
const char *type_name_pattern,
unsigned long flags)
{
#if JACK_HOST
const char**ports = jack_get_ports(client, port_name_pattern, type_name_pattern, flags);
if (ports != nullptr)
{
@@ -88,9 +64,14 @@ static void AddPorts(jack_client_t *client, std::vector<std::string> *output, co
jack_free(ports);
}
#else
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
#endif
}
#endif
namespace pipedal {
#if JACK_HOST
std::string GetJackErrorMessage(jack_status_t status)
{
std::stringstream s;
@@ -122,9 +103,43 @@ namespace pipedal {
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_client_t *client = nullptr;
@@ -161,8 +176,6 @@ void JackConfiguration::Initialize()
AddPorts(client, &this->inputAudioPorts_, "system", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput);
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;
this->errorStatus_ = "";
jack_client_close(client);
@@ -172,6 +185,9 @@ void JackConfiguration::Initialize()
jack_client_close(client);
this->errorStatus_ = e.what();
}
#else
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
#endif
}
JackChannelSelection JackChannelSelection::MakeDefault(const JackConfiguration&config){
@@ -211,12 +227,59 @@ static std::vector<std::string> makeValid(const std::vector<std::string> & selec
}
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 result;
result.inputAudioPorts_ = makeValid(this->inputAudioPorts_,config.GetInputAudioPorts());
result.outputAudioPorts_ = makeValid(this->outputAudioPorts_,config.GetOutputAudioPorts());
result.inputMidiPorts_ = makeValid(this->inputMidiPorts_, config.GetInputMidiPorts());
result.inputMidiDevices_ = makeValid(this->inputMidiDevices_, config.GetInputMidiDevices());
if (!result.isValid())
{
return this->MakeDefault(config);
@@ -226,7 +289,7 @@ JackChannelSelection JackChannelSelection::RemoveInvalidChannels(const JackConfi
JSON_MAP_BEGIN(JackChannelSelection)
JSON_MAP_REFERENCE(JackChannelSelection,inputAudioPorts)
JSON_MAP_REFERENCE(JackChannelSelection,outputAudioPorts)
JSON_MAP_REFERENCE(JackChannelSelection,inputMidiPorts)
JSON_MAP_REFERENCE(JackChannelSelection,inputMidiDevices)
JSON_MAP_END()
@@ -240,7 +303,6 @@ JSON_MAP_BEGIN(JackConfiguration)
JSON_MAP_REFERENCE(JackConfiguration,maxAllowedMidiDelta)
JSON_MAP_REFERENCE(JackConfiguration,inputAudioPorts)
JSON_MAP_REFERENCE(JackConfiguration,outputAudioPorts)
JSON_MAP_REFERENCE(JackConfiguration,inputMidiPorts)
JSON_MAP_REFERENCE(JackConfiguration,outputMidiPorts)
JSON_MAP_REFERENCE(JackConfiguration,inputMidiDevices)
JSON_MAP_END()
+23 -9
View File
@@ -22,6 +22,9 @@
#include <vector>
#include <string>
#include "json.hpp"
#include "JackServerSettings.hpp"
#include "PiPedalAlsa.hpp"
namespace pipedal
{
@@ -42,11 +45,13 @@ namespace pipedal
std::vector<std::string> inputAudioPorts_;
std::vector<std::string> outputAudioPorts_;
std::vector<std::string> inputMidiPorts_;
std::vector<std::string> outputMidiPorts_;
std::vector<AlsaMidiDeviceInfo> inputMidiDevices_;
public:
JackConfiguration();
void Initialize();
void AlsaInitialize(const JackServerSettings &jackServerSettings); // from also config settings.
void JackInitialize(); // from jack server instance.
~JackConfiguration();
bool isValid() const { return isValid_;}
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> &GetOutputAudioPorts() const { return outputAudioPorts_; }
const std::vector<std::string> &GetInputMidiPorts() const { return inputMidiPorts_; }
const std::vector<std::string> &GetOutputMidiPorts() const { return outputMidiPorts_; }
const std::vector<AlsaMidiDeviceInfo> &GetInputMidiDevices() const { return inputMidiDevices_; }
DECLARE_JSON_MAP(JackConfiguration);
@@ -69,10 +73,20 @@ namespace pipedal
private:
std::vector<std::string> inputAudioPorts_;
std::vector<std::string> outputAudioPorts_;
std::vector<std::string> inputMidiPorts_;
std::vector<AlsaMidiDeviceInfo> inputMidiDevices_;
public:
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 {
@@ -83,14 +97,14 @@ namespace pipedal
{
return inputAudioPorts_;
}
const std::vector<std::string>& getOutputAudioPorts() const
const std::vector<std::string>& GetOutputAudioPorts() const
{
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;
+512
View File
@@ -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
+38
View File
@@ -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
View File
@@ -21,12 +21,15 @@
#include "Lv2Log.hpp"
#include "JackDriver.hpp"
#include "AlsaDriver.hpp"
using namespace pipedal;
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/session.h>
#include <jack/midiport.h>
#include "AudioConfig.hpp"
#include <string.h>
#include <stdio.h>
#include <mutex>
@@ -63,11 +66,6 @@ using namespace pipedal;
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)
{
@@ -105,9 +103,10 @@ static std::string GetGovernor()
class JackHostImpl : public JackHost
class JackHostImpl : public JackHost, private AudioDriverHost
{
private:
AudioDriver*audioDriver = nullptr;
inherit_priority_recursive_mutex mutex;
int64_t overrunGracePeriodSamples = 0;
@@ -133,36 +132,26 @@ private:
JackChannelSelection channelSelection;
bool active = false;
jack_client_t *client = nullptr;
std::shared_ptr<Lv2PedalBoard> currentPedalBoard;
std::vector<std::shared_ptr<Lv2PedalBoard>> activePedalBoards; // pedalboards that have been sent to the audio queue.
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;
uint32_t sampleRate;
uint32_t sampleRate = 0;
uint64_t currentSample = 0;
std::atomic<uint64_t> underruns = 0;
std::atomic<std::chrono::system_clock::time_point> lastUnderrunTime =
std::chrono::system_clock::from_time_t(0);
int XrunCallback()
virtual void OnUnderrun()
{
++this->underruns;
this->lastUnderrunTime = std::chrono::system_clock ::now();
return 0;
}
static int xrun_callback_fn(void *arg)
{
return ((JackHostImpl *)arg)->XrunCallback();
}
virtual void Close()
{
@@ -174,84 +163,13 @@ private:
StopReaderThread();
if (active)
{
// 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.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);
audioDriver->Deactivate();
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)
{
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);
audioDriver->Close();
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.
this->activePedalBoards.resize(0);
this->realtimeActivePedalBoard = nullptr;
@@ -268,20 +186,32 @@ private:
delete realtimeMonitorPortSubscriptions;
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;
}
}
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)
{
ZeroBuffer(out, nframes);
@@ -487,22 +417,26 @@ private:
{
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)
{
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);
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;
if (jack_midi_event_get(&event, portBuffer, frame) == 0)
MidiEvent event;
if (audioDriver->GetMidiInputEvent(&event,portBuffer,frame))
{
eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
@@ -528,11 +462,26 @@ private:
#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
{
jack_default_audio_sample_t *in, *out;
float *in, *out;
pParameterRequests = nullptr;
@@ -547,9 +496,9 @@ private:
float *inputBuffers[4];
float *outputBuffers[4];
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)
{
buffersValid = false;
@@ -557,11 +506,11 @@ private:
}
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)
{
buffersValid = false;
@@ -569,7 +518,7 @@ private:
}
outputBuffers[i] = output;
}
outputBuffers[outputPorts.size()] = nullptr;
outputBuffers[audioDriver->OutputBufferCount()] = nullptr;
if (buffersValid)
{
@@ -626,61 +575,9 @@ private:
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:
JackHostImpl(IHost *pHost)
: inputRingBuffer(RING_BUFFER_SIZE),
@@ -691,22 +588,29 @@ public:
hostWriter(&this->inputRingBuffer),
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()
{
Close();
CleanRestartThreads(true);
jack_set_error_function(nullptr);
jack_set_info_function(nullptr);
delete audioDriver;
}
virtual JackConfiguration GetServerConfiguration()
{
JackConfiguration result;
result.Initialize();
result.JackInitialize();
return result;
}
@@ -980,14 +884,13 @@ public:
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);
if (channelSelection.GetInputAudioPorts().size() == 0
|| channelSelection.getOutputAudioPorts().size() == 0)
|| channelSelection.GetOutputAudioPorts().size() == 0)
{
return;
}
@@ -1008,162 +911,32 @@ public:
StartReaderThread();
jack_status_t status;
try
{
// need a unique instance name every timme.
std::stringstream s;
s << "PiPedal";
if (jackInstanceId != 0) {
s << jackInstanceId;
}
++jackInstanceId;
try {
std::string instanceName = s.str();
audioDriver->Open(jackServerSettings,this->channelSelection);
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);
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->sampleRate = audioDriver->GetSampleRate();
this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate)*OVERRUN_GRACE_PERIOD_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());
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.GetInputMidiPorts();
midiLv2Buffers.resize(selectedMidiPorts.size());
for (size_t i = 0; i < selectedMidiPorts.size(); ++i)
midiLv2Buffers.resize(audioDriver->MidiInputBufferCount());
for (size_t i = 0; i < audioDriver->MidiInputBufferCount(); ++i)
{
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);
if (activateResult == 0)
{
active = true;
active = true;
audioDriver->Activate();
Lv2Log::info("Audio started.");
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)
{
Close();
active = false;
throw;
}
}
@@ -1366,6 +1139,8 @@ private:
AdminClient client;
client.SetJackServerConfiguration(jackServerSettings);
//this_->Open(this_->channelSelection);
this_->restarting = false;
onComplete(true, "");
@@ -1468,16 +1243,12 @@ public:
result.temperaturemC_ = GetRaspberryPiTemperature();
result.active_ = this->active;
result.active_ = IsAudioActive();
result.restarting_ = this->restarting;
if (client != nullptr)
if (this->audioDriver != nullptr)
{
result.cpuUsage_ = jack_cpu_load(this->client);
}
else
{
result.cpuUsage_ = 0;
result.cpuUsage_ = audioDriver->CpuUse();
}
GetCpuFrequency(&result.cpuFreqMax_,&result.cpuFreqMin_);
result.governor_ = GetGovernor();
@@ -1497,7 +1268,6 @@ public:
}
};
int JackHostImpl::jackInstanceId = 0;
JackHost *JackHost::CreateInstance(IHost *pHost)
{
@@ -1506,6 +1276,7 @@ JackHost *JackHost::CreateInstance(IHost *pHost)
JSON_MAP_BEGIN(JackHostStatus)
JSON_MAP_REFERENCE(JackHostStatus, active)
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
JSON_MAP_REFERENCE(JackHostStatus, restarting)
JSON_MAP_REFERENCE(JackHostStatus, underruns)
JSON_MAP_REFERENCE(JackHostStatus, cpuUsage)
+2 -1
View File
@@ -106,6 +106,7 @@ public:
class JackHostStatus {
public:
bool active_;
std::string errorMessage_;
bool restarting_;
uint64_t underruns_;
float cpuUsage_ = 0;
@@ -139,7 +140,7 @@ public:
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 uint32_t GetSampleRate() = 0;
+4
View File
@@ -157,6 +157,7 @@ void JackServerSettings::ReadJackConfiguration()
void JackServerSettings::Write()
{
#if JACK_HOST
this->valid_ = false;
std::vector<std::string> precedingLines;
@@ -232,6 +233,9 @@ void JackServerSettings::Write()
catch (const std::exception &e)
{
}
#else
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
#endif
}
JSON_MAP_BEGIN(JackServerSettings)
+9
View File
@@ -33,6 +33,15 @@ namespace pipedal {
public:
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_; }
uint32_t GetBufferSize() const { return bufferSize_; }
+1 -1
View File
@@ -266,7 +266,7 @@ void Lv2Host::OnConfigurationChanged(const JackConfiguration &configuration, con
if (configuration.isValid())
{
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
this->numberOfAudioOutputChannels = settings.getOutputAudioPorts().size();
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
this->maxBufferSize = configuration.GetBlockLength();
optionsFeature.Prepare(this->mapFeature, configuration.GetSampleRate(), configuration.GetBlockLength(), GetAtomBufferSize());
}
+8 -5
View File
@@ -31,9 +31,12 @@ using namespace pipedal;
static auto timeZero = std::chrono::system_clock::now();
bool Lv2Log::show_time_ = true;
std::string timeTag()
{
if (!Lv2Log::show_time()) return "";
using namespace std::chrono;
auto t = std::chrono::system_clock::now()- timeZero;
@@ -47,7 +50,7 @@ std::string timeTag()
std::stringstream s;
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();
}
@@ -61,23 +64,23 @@ class StdErrLogger : public Lv2Logger
virtual void onError(const char* message)
{
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)
{
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)
{
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)
{
std::lock_guard lock(m);
std::cerr << timeTag() << " INF: " << message << std::endl;
std::cerr << timeTag() << "INF: " << message << std::endl;
}
};
+9
View File
@@ -47,8 +47,10 @@ namespace pipedal
class Lv2Log
{
private:
static Lv2Logger *logger_;
static LogLevel log_level_;
static bool show_time_;
static void v_error(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_;
}
static void show_time(bool show)
{
show_time_ = show;
}
static bool show_time() {
return show_time_;
}
// static void error(const char* message) {
// if (Lv2Log::log_level_ >= LogLevel::Error)
+474
View File
@@ -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;
}
+95
View File
@@ -167,9 +167,99 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
}
}
snd_config_update_free_global();
Lv2Log::debug("GetAlsaDevices --");
for (auto& device: result)
{
Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_));
}
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_REFERENCE(AlsaDeviceInfo, cardId)
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
@@ -179,3 +269,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
JSON_MAP_END()
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, name)
JSON_MAP_REFERENCE(AlsaMidiDeviceInfo, description)
JSON_MAP_END()
+12
View File
@@ -33,6 +33,16 @@ namespace pipedal {
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 {
@@ -45,4 +55,6 @@ namespace pipedal {
std::vector<AlsaDeviceInfo> GetAlsaDevices();
};
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiInputDevices();
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiOutputDevices();
}
+8 -1
View File
@@ -32,7 +32,14 @@ TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) {
PiPedalAlsaDevices devices;
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;
}
+4
View File
@@ -57,6 +57,10 @@ namespace pipedal
: PiPedalException(std::forward<std::string>(what))
{
}
PiPedalStateException(const std::string &what)
: PiPedalException(what)
{
}
};
class PiPedalLogicException : public PiPedalException
{
+42 -10
View File
@@ -19,6 +19,7 @@
#include "pch.h"
#include "DeviceIdFile.hpp"
#include "AudioConfig.hpp"
#include <sched.h>
#include "PiPedalModel.hpp"
#include "JackHost.hpp"
@@ -49,7 +50,12 @@ std::string AtomToJson(int length, uint8_t *pData)
PiPedalModel::PiPedalModel()
{
this->pedalBoard = PedalBoard::MakeDefault();
#if JACK_HOST
this->jackServerSettings.ReadJackConfiguration();
#else
this->jackServerSettings = this->storage.GetJackServerSettings();
#endif
}
@@ -108,14 +114,19 @@ PiPedalModel::~PiPedalModel()
#include <fstream>
void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration)
void PiPedalModel::Init(const PiPedalConfiguration&configuration)
{
this->configuration = configuration;
storage.SetConfigRoot(configuration.GetDocRoot());
storage.SetDataRoot(configuration.GetLocalStoragePath());
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.
std::filesystem::path pluginClassesPath = configuration.GetDocRoot() / "plugin_classes.json";
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->webPort = (uint16_t)configuration.GetSocketServerPort();
this->jackServerSettings.ReadJackConfiguration();
adminClient.MonitorGovernor(storage.GetGovernorSettings());
@@ -192,8 +202,12 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
scheduler_params.sched_priority = 10;
memset(&scheduler_params,0,sizeof(sched_param));
sched_setscheduler(0,SCHED_RR,&scheduler_params);
this->jackConfiguration = jackHost->GetServerConfiguration();
#if JACK_HOST
this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else
this->jackServerSettings = storage.GetJackServerSettings();
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
#endif
if (this->jackConfiguration.isValid())
{
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
@@ -202,7 +216,7 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
try
{
jackHost->Open(selection);
jackHost->Open(this->jackServerSettings,selection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
@@ -212,6 +226,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
{
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);
if (this->jackHost->IsOpen())
{
// do a complete reload.
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->Open(channelSelection);
this->jackHost->Open(this->jackServerSettings,channelSelection);
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
this->UpdateRealtimeVuSubscriptions();
#endif
}
}
@@ -1127,6 +1155,10 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
if (adminClient.CanUseShutdownClient())
{
#if ALSA_HOST
storage.SetJackServerSettings(jackServerSettings);
#endif
// save the current (edited) preset now in case the service shutdown isn't clean.
CurrentPreset currentPreset;
currentPreset.modified_ = this->hasPresetChanged;
+5 -2
View File
@@ -153,6 +153,8 @@ private: // IJackHostCallbacks
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
PiPedalConfiguration configuration;
public:
PiPedalModel();
virtual ~PiPedalModel();
@@ -164,9 +166,10 @@ public:
AdminClient&GetAdminClient() { return adminClient; }
void Init(const PiPedalConfiguration&configuration);
void LoadLv2PluginInfo(const PiPedalConfiguration&configuration);
void Load(const PiPedalConfiguration&configuration);
void LoadLv2PluginInfo();
void Load();
const Lv2Host& GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy()
+27
View File
@@ -52,6 +52,10 @@ namespace pipedal
{
lineBuffer.reserve(120);
}
size_t width() const { return lineWidth; }
void width(size_t width) { lineWidth =width; }
std::ostream&Stream() const { return s;}
PrettyPrinter& Indent(size_t indent)
@@ -67,6 +71,21 @@ namespace pipedal
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()
{
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
View File
@@ -19,6 +19,7 @@
#include "pch.h"
#include "Storage.hpp"
#include "AudioConfig.hpp"
#include "PiPedalException.hpp"
#include <sstream>
#include "json.hpp"
@@ -35,8 +36,6 @@ const char *BANKS_FILENAME = "index.banks";
#define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json";
#define USER_SETTINGS_FILENAME "userSettings.json";
Storage::Storage()
{
SetConfigRoot("~/var/Config");
@@ -131,7 +130,7 @@ std::string Storage::SafeEncodeName(const std::string &name)
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())
return path;
@@ -161,28 +160,27 @@ std::filesystem::path ResolveHomePath(const std::filesystem::path& path)
return result;
}
void Storage::SetConfigRoot(const std::filesystem::path& path)
void Storage::SetConfigRoot(const std::filesystem::path &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);
}
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())
{
std::filesystem::path sourceFile = directoryEntry.path();
std::filesystem::path destFile = destination / sourceFile.filename();
std::filesystem::copy_file(sourceFile,destFile);
std::ignore = chmod(destFile.c_str(),S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
}
std::filesystem::copy_file(sourceFile, destFile);
std::ignore = chmod(destFile.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
}
}
}
void Storage::MaybeCopyDefaultPresets()
@@ -192,18 +190,15 @@ void Storage::MaybeCopyDefaultPresets()
if (!std::filesystem::exists(presetsDirectory / "index.banks"))
{
CopyDirectory(this->configRoot / "default_presets" / "presets",
presetsDirectory
);
presetsDirectory);
}
auto pluginDirectory = this->GetPluginPresetsDirectory();
if (!std::filesystem::exists(pluginDirectory / "index.json"))
{
CopyDirectory(this->configRoot / "default_presets" / "plugin_presets",
pluginDirectory
);
pluginDirectory);
}
}
void Storage::Initialize()
{
@@ -233,7 +228,6 @@ void Storage::Initialize()
LoadUserSettings();
}
void Storage::LoadBank(int64_t instanceId)
{
auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId);
@@ -265,7 +259,6 @@ std::filesystem::path Storage::GetCurrentPresetPath() const
return this->dataRoot / "currentPreset.json";
}
std::filesystem::path Storage::GetChannelSelectionFileName()
{
return this->dataRoot / "JackChannelSelection.json";
@@ -304,7 +297,7 @@ void Storage::LoadBankIndex()
std::string name = "Default Bank";
SaveBankFile(name, currentBank);
int64_t selectedBank = bankIndex.addBank(-1,name);
int64_t selectedBank = bankIndex.addBank(-1, name);
bankIndex.selectedBank(selectedBank);
}
SaveBankIndex();
@@ -365,7 +358,7 @@ void Storage::ReIndex()
if (path.extension() == BANK_EXTENSION)
{
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();
bankFile.selectedPreset(instanceId);
SaveBankFile(name, bankFile);
this->bankIndex.addBank(-1,name);
this->bankIndex.addBank(-1, name);
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 name = indexEntry.name();
std::filesystem::path fileName = GetBankFileName(name);
@@ -407,7 +401,6 @@ void Storage::LoadBankFile(const std::string &name, BankFile *pBank)
std::ifstream is(fileName);
json_reader reader(is);
reader.read(pBank);
}
void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
@@ -459,8 +452,9 @@ const PedalBoard &Storage::GetCurrentPreset()
bool Storage::LoadPreset(int64_t instanceId)
{
if (!currentBank.hasItem(instanceId))
return false;
if (instanceId != currentBank.selectedPreset()) {
return false;
if (instanceId != currentBank.selectedPreset())
{
currentBank.selectedPreset(instanceId);
SaveCurrentBank();
}
@@ -489,13 +483,13 @@ void Storage::SetPresetIndex(const PresetIndex &presets)
std::map<int64_t, std::unique_ptr<BankFileEntry> *> entries;
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]);
}
std::vector<std::unique_ptr<BankFileEntry>*> newPresets;
std::vector<std::unique_ptr<BankFileEntry> *> newPresets;
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)
{
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 newSelection = currentBank.deletePreset(presetId);
int64_t newSelection = currentBank.deletePreset(presetId);
SaveCurrentBank();
return newSelection;
}
@@ -724,7 +718,7 @@ int64_t Storage::SaveBankAs(int64_t bankId, const std::string &newName)
std::filesystem::path newPath = this->GetBankFileName(newName);
try
{
std::filesystem::copy(oldPath,newPath);
std::filesystem::copy(oldPath, newPath);
}
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() << ")";
throw PiPedalException(s.str());
}
int64_t newId = this->bankIndex.addBank(bankId,newName);
int64_t newId = this->bankIndex.addBank(bankId, newName);
SaveBankIndex();
return newId;
}
void Storage::MoveBank(int from, int to)
{
this->bankIndex.move(from,to);
this->bankIndex.move(from, to);
this->SaveBankIndex();
}
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)
{
auto & entry = entries[i];
auto &entry = entries[i];
if (entry.instanceId() == bankId) {
if (entry.instanceId() == bankId)
{
std::filesystem::path fileName = this->GetBankFileName(entry.name());
entries.erase(entries.begin()+i);
entries.erase(entries.begin() + i);
int64_t newSelection;
if (i < entries.size())
{
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?
// Create a default empty bank.
BankIndexEntry newEntry;
@@ -773,7 +771,7 @@ int64_t Storage::DeleteBank(int64_t bankId)
std::string name = "Default Bank";
SaveBankFile(name, defaultBank);
int64_t selectedBank = bankIndex.addBank(-1,name);
int64_t selectedBank = bankIndex.addBank(-1, name);
newSelection = selectedBank;
}
if (this->bankIndex.selectedBank() == bankId)
@@ -793,7 +791,7 @@ int64_t Storage::GetCurrentPresetId() const
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();
if (uploadAfter != -1)
@@ -801,7 +799,8 @@ int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter)
lastPreset = uploadAfter;
}
if (bankFile.presets().size() == 0) {
if (bankFile.presets().size() == 0)
{
throw PiPedalException("Invalid preset.");
}
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());
}
lastPreset = this->currentBank.addPreset(preset,lastPreset);
lastPreset = this->currentBank.addPreset(preset, lastPreset);
}
this->SaveCurrentBank();
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();
if (uploadAfter != -1)
@@ -830,7 +829,8 @@ int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter)
lastBank = uploadAfter;
}
if (bankFile.presets().size() == 0) {
if (bankFile.presets().size() == 0)
{
throw PiPedalException("Invalid bank.");
}
bankFile.updateNextIndex();
@@ -845,24 +845,25 @@ int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter)
}
std::filesystem::path path = this->GetBankFileName(bankFile.name());
std::ofstream f(path);
if (!f.is_open()) {
if (!f.is_open())
{
throw PiPedalException("Can't write to bank file.");
}
json_writer writer(f);
writer.write(bankFile);
lastBank = this->bankIndex.addBank(lastBank,bankFile.name());
lastBank = this->bankIndex.addBank(lastBank, bankFile.name());
this->SaveBankIndex();
return lastBank;
}
void Storage::SetGovernorSettings(const std::string & governor)
void Storage::SetGovernorSettings(const std::string &governor)
{
userSettings.governor_ = governor;
SaveUserSettings();
}
void Storage::SaveUserSettings() {
void Storage::SaveUserSettings()
{
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
{
std::ofstream f(path);
@@ -889,8 +890,7 @@ std::string Storage::GetGovernorSettings() const
return this->userSettings.governor_;
}
void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings)
void Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings)
{
WifiConfigSettings copyToSave = wifiConfigSettings;
copyToSave.rebootRequired_ = false;
@@ -915,19 +915,21 @@ void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSetting
if (copyToStore.enable_)
{
copyToStore.hasPassword_ = copyToStore.password_.length() != 0 || this->wifiConfigSettings.hasPassword_;
} else {
}
else
{
copyToStore.hasPassword_ = false;
}
copyToStore.password_ = "";
copyToStore.rebootRequired_ = false;
if (copyToStore.enable_ && !copyToStore.hasPassword_)
if (copyToStore.enable_ && !copyToStore.hasPassword_)
{
copyToStore.hasPassword_ = this->wifiConfigSettings.hasPassword_;
}
this->wifiConfigSettings = copyToStore;
}
void Storage::SetWifiDirectConfigSettings(const WifiDirectConfigSettings & wifiDirectConfigSettings)
void Storage::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiDirectConfigSettings)
{
WifiDirectConfigSettings copyToSave = wifiDirectConfigSettings;
copyToSave.rebootRequired_ = false;
@@ -945,7 +947,8 @@ void Storage::LoadUserSettings()
{
std::filesystem::path path = this->dataRoot / USER_SETTINGS_FILENAME;
try {
try
{
if (std::filesystem::is_regular_file(path))
{
std::ifstream f(path);
@@ -956,17 +959,18 @@ void Storage::LoadUserSettings()
json_reader reader(f);
reader.read(&userSettings);
}
} catch (const std::exception&)
}
catch (const std::exception &)
{
}
this->wifiConfigSettings.valid_ = true;
}
void Storage::LoadWifiConfigSettings()
{
std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME;
try {
try
{
if (std::filesystem::is_regular_file(path))
{
std::ifstream f(path);
@@ -979,9 +983,9 @@ void Storage::LoadWifiConfigSettings()
reader.read(&wifiConfigSettings);
this->wifiConfigSettings = wifiConfigSettings;
}
} catch (const std::exception&)
}
catch (const std::exception &)
{
}
this->wifiConfigSettings.valid_ = true;
}
@@ -996,8 +1000,6 @@ void Storage::LoadWifiDirectConfigSettings()
this->wifiDirectConfigSettings = settings;
}
WifiConfigSettings Storage::GetWifiConfigSettings()
{
return this->wifiConfigSettings;
@@ -1007,43 +1009,47 @@ WifiDirectConfigSettings Storage::GetWifiDirectConfigSettings()
return this->wifiDirectConfigSettings;
}
void Storage::SaveCurrentPreset(const CurrentPreset &currentPreset)
{
try {
try
{
std::filesystem::path path = GetCurrentPresetPath();
std::ofstream f(path);
json_writer writer(f);
writer.write(currentPreset);
} catch (std::exception&)
}
catch (std::exception &)
{
// called from destructor. Must be nothrow().
}
}
bool Storage::RestoreCurrentPreset(CurrentPreset*pResult)
bool Storage::RestoreCurrentPreset(CurrentPreset *pResult)
{
std::filesystem::path path = GetCurrentPresetPath();
if (std::filesystem::exists(path)) {
try {
if (std::filesystem::exists(path))
{
try
{
std::ifstream f(path);
json_reader reader(f);
reader.read(pResult);
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 true;
} else {
}
else
{
return false;
}
}
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)
{
@@ -1055,7 +1061,7 @@ bool Storage::HasPluginPresets(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)
{
@@ -1063,9 +1069,8 @@ std::filesystem::path Storage::GetPluginPresetPath(const std::string &pluginUri)
}
}
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::filesystem::path path;
@@ -1075,10 +1080,12 @@ void Storage::SavePluginPresets(const std::string&pluginUri, const PluginPresets
name = SS(pluginPresetIndex.nextInstanceId_ << ".json");
path = GetPluginPresetsDirectory() / name;
presetAdded = true;
} else {
}
else
{
path = GetPluginPresetPath(pluginUri);
}
auto tempPath = path.string()+".$$$";
auto tempPath = path.string() + ".$$$";
{
std::ofstream os;
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::rename(tempPath,path);
std::filesystem::rename(tempPath, path);
if (presetAdded)
{
pluginPresetIndex.entries_.push_back(
PluginPresetIndexEntry(
pluginUri,name
)
);
pluginUri, name));
pluginPresetIndex.nextInstanceId_++;
this->pluginPresetIndexChanged = true;
SavePluginPresetIndex();
}
}
PluginPresets Storage::GetPluginPresets(const std::string&pluginUri) const
PluginPresets Storage::GetPluginPresets(const std::string &pluginUri) const
{
PluginPresets result;
if (!HasPluginPresets(pluginUri))
@@ -1127,36 +1132,33 @@ PluginPresets Storage::GetPluginPresets(const std::string&pluginUri) const
reader.read(&result);
return result;
}
PluginUiPresets Storage::GetPluginUiPresets(const std::string&pluginUri) const
PluginUiPresets Storage::GetPluginUiPresets(const std::string &pluginUri) const
{
PluginPresets presets = GetPluginPresets(pluginUri);
PluginUiPresets result;
result.pluginUri_ = presets.pluginUri_;
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(
PluginUiPreset
{
PluginUiPreset{
preset.label_,
preset.instanceId_
}
);
preset.instanceId_});
}
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);
for (const auto & preset: presets.presets_)
for (const auto &preset : presets.presets_)
{
if (preset.instanceId_ == instanceId)
{
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;
}
@@ -1165,16 +1167,16 @@ std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string&plugi
}
uint64_t Storage::SavePluginPreset(
const std::string&pluginUri,
const std::string&name,
const std::map<std::string,float> & values)
const std::string &pluginUri,
const std::string &name,
const std::map<std::string, float> &values)
{
auto presets = GetPluginPresets(pluginUri);
uint64_t result = -1;
bool existing = false;
for (size_t i = 0; i < presets.presets_.size(); ++i)
{
auto & preset = presets.presets_[i];
auto &preset = presets.presets_[i];
if (preset.label_ == name)
{
preset.controlValues_ = values;
@@ -1190,10 +1192,9 @@ uint64_t Storage::SavePluginPreset(
PluginPreset(
result,
name,
values
));
values));
}
this->SavePluginPresets(pluginUri,presets);
this->SavePluginPresets(pluginUri, presets);
return result;
}
@@ -1201,26 +1202,24 @@ void Storage::UpdatePluginPresets(const PluginUiPresets &pluginPresets)
{
// handles deletions, renaming, and reordering only.
// If you need to add a preset, you neet to call SavePluginPreset or DulicatePluginPreset instead.
PluginPresets presets = this->GetPluginPresets(pluginPresets.pluginUri_);
PluginPresets newPresets;
newPresets.pluginUri_ = pluginPresets.pluginUri_;
newPresets.nextInstanceId_ = presets.nextInstanceId_;
for (const auto&preset: pluginPresets.presets_)
for (const auto &preset : pluginPresets.presets_)
{
PluginPreset newPreset = presets.GetPreset(preset.instanceId_);
newPreset.label_ = preset.label_;
newPresets.presets_.push_back(std::move(newPreset));
}
SavePluginPresets(newPresets.pluginUri_,newPresets);
SavePluginPresets(newPresets.pluginUri_, newPresets);
}
static std::string stripCopySuffix(const std::string &s)
{
int pos = s.length()-1;
int pos = s.length() - 1;
if (pos >= 0 && s[pos] == ')')
{
--pos;
@@ -1228,7 +1227,7 @@ static std::string stripCopySuffix(const std::string &s)
{
--pos;
}
if (pos >= 0 && s[pos] == '(')
if (pos >= 0 && s[pos] == '(')
{
--pos;
}
@@ -1236,11 +1235,11 @@ static std::string stripCopySuffix(const std::string &s)
{
--pos;
}
return s.substr(0,pos+1);
return s.substr(0, pos + 1);
}
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);
size_t pos = presets.Find(presetId);
@@ -1266,17 +1265,14 @@ uint64_t Storage::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId
}
}
t.label_ = name;
presets.presets_.insert(presets.presets_.begin()+pos+1,t);
SavePluginPresets(presets.pluginUri_,presets);
presets.presets_.insert(presets.presets_.begin() + pos + 1, t);
SavePluginPresets(presets.pluginUri_, presets);
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";
if (!std::filesystem::exists(fileName))
@@ -1291,10 +1287,9 @@ std::map<std::string,bool> Storage::GetFavorites() const
reader.read(&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::ofstream f;
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_REFERENCE(UserSettings,governor)
JSON_MAP_REFERENCE(UserSettings,showStatusMonitor)
JSON_MAP_REFERENCE(UserSettings, governor)
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
JSON_MAP_END()
JSON_MAP_BEGIN(CurrentPreset)
JSON_MAP_REFERENCE(CurrentPreset,modified)
JSON_MAP_REFERENCE(CurrentPreset,preset)
JSON_MAP_REFERENCE(CurrentPreset, modified)
JSON_MAP_REFERENCE(CurrentPreset, preset)
JSON_MAP_END()
+3
View File
@@ -25,6 +25,7 @@
#include "PluginPreset.hpp"
#include "Banks.hpp"
#include "JackConfiguration.hpp"
#include "JackServerSettings.hpp"
#include "WifiConfigSettings.hpp"
#include "WifiDirectConfigSettings.hpp"
#include <map>
@@ -100,6 +101,8 @@ public:
const BankIndex & GetBanks() const { return bankIndex; }
JackServerSettings GetJackServerSettings();
void SetJackServerSettings(const pipedal::JackServerSettings&jackServerSettings);
void LoadWifiConfigSettings();
void LoadWifiDirectConfigSettings();
+79 -56
View File
@@ -18,6 +18,8 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "AudioConfig.hpp"
#include "BeastServer.hpp"
#include <iostream>
#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.
*/
@@ -488,7 +490,6 @@ static bool isJackServiceRunning()
return std::filesystem::exists(path);
}
int main(int argc, char *argv[])
{
@@ -621,39 +622,10 @@ int main(int argc, char *argv[])
{
PiPedalModel model;
// Pre-populate ALSA device info,wait for jackd service (daemon only)
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)
model.Init(configuration);
auto serverSettings = model.GetJackServerSettings();
if (serverSettings.IsValid())
{
// 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);
// Get heavy IO out of the way before letting dependent (Jack/ALSA) services run.
model.LoadLv2PluginInfo();
if (systemd)
{
// Tell systemd we're done.
@@ -662,34 +634,85 @@ int main(int argc, char *argv[])
(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);
if (isJackServiceRunning())
auto serverSettings = model.GetJackServerSettings();
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())
{
Lv2Log::info("Found Jack service.");
sleep(3); // jack needs a little time to get up to speed.
}
else
{
Lv2Log::info("Jack service not started.");
// pre-cache device info before we let audio services run.
model.GetAlsaDevices();
}
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);
+1
View File
@@ -3,6 +3,7 @@ Description=Jack Audio Daemon
After=sound.target
After=local-fs.target
After=dbus.socket
After=pipedald
[Service]
-1
View File
@@ -1,6 +1,5 @@
[Unit]
Description=${DESCRIPTION}
Before=jack.service
After=network.target
After=sound.target
After=local-fs.target
+11
View File
@@ -1,3 +1,14 @@
JackConfiguration Input midi ports.
JackSettings Input midi ports.
Midi Port selection UI.
CPU Use should be % of AVAILABLE time, not % of total.
MIDI device selection.
raw Alsa midi.
Pri Description
-----------------