Jack configuration (alpha)
This commit is contained in:
@@ -34,6 +34,7 @@ install (FILES ${PROJECT_SOURCE_DIR}/build/src/notices.txt
|
||||
|
||||
install (FILES ${PROJECT_SOURCE_DIR}/src/template.service
|
||||
${PROJECT_SOURCE_DIR}/src/templateShutdown.service
|
||||
${PROJECT_SOURCE_DIR}/src/templateJack.service
|
||||
DESTINATION /etc/pipedal )
|
||||
|
||||
install(CODE
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
/* One or more directories containing LV2 plugins, seperated by ':' */
|
||||
"lv2_path": "/usr/modep/lv2:/usr/local/lib/lv2",
|
||||
"lv2_path": "/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2",
|
||||
|
||||
|
||||
/* A writable directory in which state files can be stored, preferrably only by the server process. */
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
/* One or more directories containing LV2 plugins, seperated by ':' */
|
||||
"lv2_path": "/usr/modep/lv2:/usr/local/lib/lv2",
|
||||
//"lv2_path" : "/usr/modep/lv2/rkr.lv2",
|
||||
"lv2_path": "/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2",
|
||||
|
||||
|
||||
/* A writable directory in which state files can be stored, preferrably only by the server process. */
|
||||
|
||||
@@ -1 +1 @@
|
||||
cmake --install /home/patch/src/pipedal/build --prefix /usr/local --config Release
|
||||
cmake --install build --prefix /usr/local --config Release -v
|
||||
|
||||
@@ -25,6 +25,7 @@ add_custom_command(
|
||||
src/JackServerSettingsDialog.tsx
|
||||
src/JackServerSettings.tsx
|
||||
src/DialogEx.tsx
|
||||
src/AlsaDeviceInfo.tsx
|
||||
|
||||
src/AboutDialog.tsx
|
||||
src/App.test.tsx
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"socket_server_port": 80,
|
||||
"socket_server_port": 8080,
|
||||
"socket_server_address": "*",
|
||||
|
||||
"max_upload_size": 1048576,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
|
||||
export default class AlsaDeviceInfo {
|
||||
deserialize(input: any): AlsaDeviceInfo {
|
||||
this.cardId = input.cardId;
|
||||
this.id = input.id;
|
||||
this.name = input.name;
|
||||
this.longName = input.longName;
|
||||
this.sampleRates = input.sampleRates as number[];
|
||||
this.minBufferSize = input.minBufferSize;
|
||||
this.maxBufferSize = input.maxBufferSize;
|
||||
return this;
|
||||
}
|
||||
static deserialize_array(input: any): AlsaDeviceInfo[]
|
||||
{
|
||||
let result: AlsaDeviceInfo[] = [];
|
||||
for (let i = 0; i < input.length; ++i)
|
||||
{
|
||||
result.push(new AlsaDeviceInfo().deserialize(input[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
closestSampleRate(sr: number): number
|
||||
{
|
||||
let result = 48000;
|
||||
let bestError = 1E36;
|
||||
for (let rate of this.sampleRates)
|
||||
{
|
||||
let error = (sr-rate)*(sr-rate);
|
||||
if (error < bestError)
|
||||
{
|
||||
bestError = error;
|
||||
result = rate;
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
closestBufferSize(buffSize: number): number
|
||||
{
|
||||
let result = 64;
|
||||
let bestError = 1E36;
|
||||
|
||||
for (let sz = 2; sz <= this.maxBufferSize; sz *= 2)
|
||||
{
|
||||
if (sz >= this.minBufferSize)
|
||||
{
|
||||
let error = (sz-buffSize)*(sz-buffSize);
|
||||
if (error < bestError)
|
||||
{
|
||||
bestError = error;
|
||||
result = sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
cardId: number = -1;
|
||||
id: string = "";
|
||||
name: string = "";
|
||||
longName: string = "";
|
||||
sampleRates: number[] = [];
|
||||
minBufferSize: number = 0;
|
||||
maxBufferSize: number = 0;
|
||||
};
|
||||
@@ -23,31 +23,37 @@ export default class JackServerSettings {
|
||||
deserialize(input: any) : JackServerSettings{
|
||||
this.valid = input.valid;
|
||||
this.rebootRequired = input.rebootRequired;
|
||||
this.alsaDevice = input.alsaDevice?? "";
|
||||
this.sampleRate = input.sampleRate;
|
||||
this.bufferSize = input.bufferSize;
|
||||
this.numberOfBuffers = input.numberOfBuffers;
|
||||
return this;
|
||||
}
|
||||
constructor(sampleRate?: number, bufferSize?: number, numberOfBuffers?: number)
|
||||
// constructor(alsaDevice: string, sampleRate?: number, bufferSize?: number, numberOfBuffers?: number)
|
||||
// {
|
||||
// if (sampleRate) this.sampleRate = sampleRate;
|
||||
// if (bufferSize) this.bufferSize = bufferSize;
|
||||
// if (numberOfBuffers) this.numberOfBuffers = numberOfBuffers;
|
||||
// if (numberOfBuffers) {
|
||||
// this.valid = true;
|
||||
// }
|
||||
// }
|
||||
clone(): JackServerSettings
|
||||
{
|
||||
if (sampleRate) this.sampleRate = sampleRate;
|
||||
if (bufferSize) this.bufferSize = bufferSize;
|
||||
if (numberOfBuffers) this.numberOfBuffers = numberOfBuffers;
|
||||
if (numberOfBuffers) {
|
||||
this.valid = true;
|
||||
}
|
||||
return new JackServerSettings().deserialize(this);
|
||||
}
|
||||
valid: boolean = false;
|
||||
rebootRequired = false;
|
||||
alsaDevice: string = "";
|
||||
sampleRate = 48000;
|
||||
bufferSize = 64;
|
||||
numberOfBuffers = 3;
|
||||
|
||||
getSummaryText() {
|
||||
if (this.valid) {
|
||||
return "Sample Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Number of Buffers: " + this.numberOfBuffers;
|
||||
return this.alsaDevice + " Sample Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Number of Buffers: " + this.numberOfBuffers;
|
||||
} else {
|
||||
return "";
|
||||
return "Not configured";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,15 @@ import DialogContent from '@material-ui/core/DialogContent';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import { nullCast } from './Utility';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
|
||||
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||
|
||||
const INVALID_DEVICE_ID = "_invalid_";
|
||||
interface JackServerSettingsDialogState {
|
||||
latencyText: string;
|
||||
jackServerSettings: JackServerSettings;
|
||||
alsaDevices?: AlsaDeviceInfo[];
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -54,116 +60,297 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
|
||||
open: boolean;
|
||||
jackServerSettings: JackServerSettings;
|
||||
onClose: () => void;
|
||||
onApply: (sampleRate: number, bufferSize: number, numberOfBuffers: number) => void;
|
||||
onApply: (jackServerSettings: JackServerSettings) => void;
|
||||
}
|
||||
|
||||
function getLatencyText(sampleRate: number, bufferSize: number, numberOfBuffers: number): string {
|
||||
let ms = bufferSize * numberOfBuffers / sampleRate * 1000;
|
||||
|
||||
function getLatencyText(settings: JackServerSettings ): string {
|
||||
if (!settings.valid) return "\u00A0";
|
||||
let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000;
|
||||
return ms.toFixed(1) + "ms";
|
||||
}
|
||||
|
||||
const JackServerSettingsDialog = withStyles(styles)(
|
||||
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: JackServerSettingsDialogProps) {
|
||||
super(props);
|
||||
let jackServerSettings = props.jackServerSettings;
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
|
||||
this.state = {
|
||||
latencyText:
|
||||
getLatencyText(
|
||||
jackServerSettings.sampleRate,
|
||||
jackServerSettings.bufferSize,
|
||||
jackServerSettings.numberOfBuffers)
|
||||
latencyText: "\u00A0",
|
||||
jackServerSettings: props.jackServerSettings.clone() // invalid, but not nullish
|
||||
};
|
||||
}
|
||||
mounted: boolean = false;
|
||||
|
||||
requestAlsaInfo() {
|
||||
this.model.getAlsaDevices()
|
||||
.then((devices) => {
|
||||
if (this.mounted) {
|
||||
if (this.props.open) {
|
||||
let settings = this.applyAlsaDevices(this.props.jackServerSettings, devices);
|
||||
this.setState({
|
||||
alsaDevices: devices,
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
} else {
|
||||
this.setState({ alsaDevices: devices });
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
updateLatency(e: any): void {
|
||||
setTimeout(()=> {
|
||||
let sampleRate: number = parseInt(nullCast<any>(document.getElementById('jsd_sampleRate')).value);
|
||||
let bufferSize: number = parseInt(nullCast<any>(document.getElementById('jsd_bufferSize')).value);
|
||||
let bufferCount: number = parseInt(nullCast<any>(document.getElementById('jsd_bufferCount')).value);
|
||||
this.setState({
|
||||
latencyText: getLatencyText(sampleRate, bufferSize, bufferCount)
|
||||
});
|
||||
},0);
|
||||
}
|
||||
handleApply() {
|
||||
let sampleRate = parseInt(nullCast<any>(document.getElementById('jsd_sampleRate')).value);
|
||||
let bufferSize = parseInt(nullCast<any>(document.getElementById('jsd_bufferSize')).value);
|
||||
let bufferCount = parseInt(nullCast<any>(document.getElementById('jsd_bufferCount')).value);
|
||||
|
||||
this.props.onApply(sampleRate,bufferSize,bufferCount);
|
||||
applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
|
||||
let result = jackServerSettings.clone();
|
||||
if (!alsaDevices) {
|
||||
return result;
|
||||
}
|
||||
if (alsaDevices.length === 0) {
|
||||
result.valid = false;
|
||||
result.alsaDevice = INVALID_DEVICE_ID;
|
||||
return result;
|
||||
}
|
||||
|
||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
||||
for (let i = 0; i < alsaDevices.length; ++i) {
|
||||
if (alsaDevices[i].id === result.alsaDevice) {
|
||||
selectedDevice = alsaDevices[i]
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!selectedDevice) {
|
||||
selectedDevice = alsaDevices[0];
|
||||
result.alsaDevice = selectedDevice.id;
|
||||
|
||||
}
|
||||
if (result.sampleRate === 0) result.sampleRate = 48000;
|
||||
if (result.bufferSize === 0) result.bufferSize = 64;
|
||||
if (result.numberOfBuffers === 0) result.numberOfBuffers = 3;
|
||||
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
||||
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.requestAlsaInfo();
|
||||
|
||||
}
|
||||
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
|
||||
if (this.props.open && !oldProps.open) {
|
||||
let settings = this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices);
|
||||
this.setState({
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
this.requestAlsaInfo();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
|
||||
}
|
||||
getSelectedDevice(deviceId: string): AlsaDeviceInfo | null {
|
||||
if (!this.state.alsaDevices) return null;
|
||||
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
|
||||
if (this.state.alsaDevices[i].id === deviceId) {
|
||||
return this.state.alsaDevices[i];
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
handleDeviceChanged(e: any) {
|
||||
let device = e.target.value as string;
|
||||
|
||||
let selectedDevice = this.getSelectedDevice(device);
|
||||
if (!selectedDevice) return;
|
||||
let settings = this.state.jackServerSettings.clone();
|
||||
settings.alsaDevice = device;
|
||||
settings.sampleRate = selectedDevice.closestSampleRate(settings.sampleRate);
|
||||
settings.bufferSize = selectedDevice.closestSampleRate(settings.bufferSize);
|
||||
|
||||
this.setState({
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
}
|
||||
handleRateChanged(e: any) {
|
||||
let rate = e.target.value as number;
|
||||
console.log("New rate: " + rate);
|
||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
||||
if (!selectedDevice) return;
|
||||
let settings = this.state.jackServerSettings.clone();
|
||||
settings.sampleRate = rate;
|
||||
|
||||
this.setState({
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
}
|
||||
handleSizeChanged(e: any) {
|
||||
let size = e.target.value as number;
|
||||
|
||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
||||
if (!selectedDevice) return;
|
||||
let settings = this.state.jackServerSettings.clone();
|
||||
settings.bufferSize = size;
|
||||
|
||||
this.setState({
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
}
|
||||
handleNumberOfBuffersChanged(e: any) {
|
||||
let bufferCount = e.target.value as number;
|
||||
|
||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
||||
if (!selectedDevice) return;
|
||||
let settings = this.state.jackServerSettings.clone();
|
||||
settings.numberOfBuffers = bufferCount;
|
||||
|
||||
this.setState({
|
||||
jackServerSettings: settings,
|
||||
latencyText: getLatencyText(settings)
|
||||
});
|
||||
}
|
||||
|
||||
handleApply() {
|
||||
|
||||
this.props.onApply(this.state.jackServerSettings.clone());
|
||||
};
|
||||
|
||||
getBufferSizes(alsaDevice: AlsaDeviceInfo): number[] {
|
||||
let result: number[] = [];
|
||||
let max = alsaDevice.maxBufferSize;
|
||||
if (max > 2048) max = 2048;
|
||||
|
||||
for (let i = 16; i <= max; i *= 2) {
|
||||
if (i >= alsaDevice.minBufferSize) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const classes = this.props.classes;
|
||||
|
||||
const { onClose, jackServerSettings, open } = this.props;
|
||||
const { onClose, open } = this.props;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
let waitingForDevices = !this.state.alsaDevices
|
||||
let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0;
|
||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
||||
if (this.state.alsaDevices) {
|
||||
for (let device of this.state.alsaDevices) {
|
||||
if (device.id === this.state.jackServerSettings.alsaDevice) {
|
||||
selectedDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let bufferSizes: number[] = [];
|
||||
if (selectedDevice) {
|
||||
bufferSizes = this.getBufferSizes(selectedDevice);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
|
||||
<DialogContent>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="sampleRate">Sample rate</InputLabel>
|
||||
<Select
|
||||
onChange={(e) => this.updateLatency(e)}
|
||||
defaultValue={jackServerSettings.sampleRate}
|
||||
inputProps={{
|
||||
name: 'Sample Rate',
|
||||
id: 'jsd_sampleRate',
|
||||
style: {
|
||||
textAlign: "right"
|
||||
<div>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="jsd_device">Device</InputLabel>
|
||||
<Select onChange={(e) => this.handleDeviceChanged(e)}
|
||||
value={this.state.jackServerSettings.alsaDevice}
|
||||
style={{width: 220}}
|
||||
inputProps={{
|
||||
name: "Device",
|
||||
id: "jsd_device",
|
||||
}}
|
||||
>
|
||||
{(noDevices && !waitingForDevices) &&
|
||||
(
|
||||
<MenuItem value={INVALID_DEVICE_ID}>No suitable devices.</MenuItem>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value={44100}>44100</MenuItem>
|
||||
<MenuItem value={48000}>48000</MenuItem>
|
||||
<MenuItem value={88200}>88200</MenuItem>
|
||||
<MenuItem value={96000}>96000</MenuItem>
|
||||
<MenuItem value={176400}>176400</MenuItem>
|
||||
<MenuItem value={192000}>192000</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
|
||||
<Select
|
||||
onChange={(e) => this.updateLatency(e)}
|
||||
defaultValue={jackServerSettings.bufferSize}
|
||||
inputProps={{
|
||||
name: 'Buffer size',
|
||||
id: 'jsd_bufferSize',
|
||||
}}
|
||||
>
|
||||
<MenuItem value={16}>16</MenuItem>
|
||||
<MenuItem value={32}>32</MenuItem>
|
||||
<MenuItem value={64}>64</MenuItem>
|
||||
<MenuItem value={128}>128</MenuItem>
|
||||
<MenuItem value={256}>256</MenuItem>
|
||||
<MenuItem value={512}>512</MenuItem>
|
||||
<MenuItem value={1024}>1024</MenuItem>
|
||||
{((!noDevices) && !waitingForDevices) && (
|
||||
this.state.alsaDevices!.map((device) =>
|
||||
(
|
||||
<MenuItem value={device.id}>{device.name}</MenuItem>
|
||||
)
|
||||
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div><div>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
|
||||
<InputLabel htmlFor="jsd_sampleRate">Sample rate</InputLabel>
|
||||
<Select
|
||||
onChange={(e) => this.updateLatency(e)}
|
||||
defaultValue={jackServerSettings.numberOfBuffers}
|
||||
onChange={(e) => this.handleRateChanged(e)}
|
||||
value={this.state.jackServerSettings.sampleRate}
|
||||
inputProps={{
|
||||
name: 'Number of buffers',
|
||||
id: 'jsd_bufferCount',
|
||||
id: 'jsd_sampleRate',
|
||||
style: {
|
||||
textAlign: "right"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
<MenuItem value={3}>3</MenuItem>
|
||||
<MenuItem value={4}>4</MenuItem>
|
||||
{selectedDevice &&
|
||||
selectedDevice.sampleRates.map((sr) => {
|
||||
return ( <MenuItem value={sr}>{sr}</MenuItem> );
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
|
||||
<Select
|
||||
onChange={(e) => this.handleSizeChanged(e)}
|
||||
value={this.state.jackServerSettings.bufferSize}
|
||||
inputProps={{
|
||||
name: 'Buffer size',
|
||||
id: 'jsd_bufferSize',
|
||||
}}
|
||||
>
|
||||
{bufferSizes.map((buffSize) =>
|
||||
(
|
||||
<MenuItem value={buffSize}>{buffSize}</MenuItem>
|
||||
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
|
||||
<Select
|
||||
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
|
||||
value={this.state.jackServerSettings.numberOfBuffers}
|
||||
inputProps={{
|
||||
name: 'Number of buffers',
|
||||
id: 'jsd_bufferCount',
|
||||
}}
|
||||
>
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
<MenuItem value={3}>3</MenuItem>
|
||||
<MenuItem value={4}>4</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 8, marginLeft: 24 }}
|
||||
color="textSecondary">
|
||||
@@ -175,7 +362,8 @@ const JackServerSettingsDialog = withStyles(styles)(
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={()=> this.handleApply()} color="secondary">
|
||||
<Button onClick={() => this.handleApply()} color="secondary" disabled={
|
||||
(!this.state.alsaDevices) || !this.state.jackServerSettings.valid}>
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -34,6 +34,7 @@ import MidiBinding from './MidiBinding';
|
||||
import PluginPreset from './PluginPreset';
|
||||
import WifiConfigSettings from './WifiConfigSettings';
|
||||
import WifiChannel from './WifiChannel';
|
||||
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||
|
||||
|
||||
export enum State {
|
||||
@@ -348,6 +349,7 @@ export interface PiPedalModel {
|
||||
|
||||
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
||||
|
||||
getAlsaDevices() : Promise<AlsaDeviceInfo[]>;
|
||||
};
|
||||
|
||||
class PiPedalModelImpl implements PiPedalModel {
|
||||
@@ -1586,6 +1588,23 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
getAlsaDevices() : Promise<AlsaDeviceInfo[]>
|
||||
{
|
||||
let result = new Promise<AlsaDeviceInfo[]>((resolve, reject) => {
|
||||
if (!this.webSocket) {
|
||||
reject("Connection closed.");
|
||||
return;
|
||||
}
|
||||
this.webSocket.request<any>("getAlsaDevices")
|
||||
.then((data) => {
|
||||
resolve(AlsaDeviceInfo.deserialize_array(data));
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -400,36 +400,19 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
overflowX: "hidden", overflowY: "auto", marginTop: 22
|
||||
}}
|
||||
>
|
||||
{!isConfigValid && (
|
||||
<div>
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="error">
|
||||
Error
|
||||
</Typography>
|
||||
<Typography className={classes.textBlock} display="block" variant="body2" >
|
||||
The server encounter the following problem:
|
||||
</Typography>
|
||||
<Typography className={classes.textBlockIndented} display="block" variant="body2" >
|
||||
{this.state.jackConfiguration.errorState}
|
||||
</Typography>
|
||||
<Typography className={classes.textBlock} display="block" variant="body2">
|
||||
Please get the Jack Audio Server running properly on the server before attempting to configure audio.
|
||||
</Typography>
|
||||
<Divider style={{ marginTop: 16, marginBottom: 16 }} />
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
|
||||
<div style={{ opacity: isConfigValid ? 1.0 : 0.6 }}>
|
||||
<div >
|
||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
|
||||
AUDIO
|
||||
</Typography>
|
||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
{JackHostStatus.getDisplayView("Status:\u00A0", this.state.jackStatus)}
|
||||
{ (!isConfigValid) ?
|
||||
(
|
||||
<Typography variant="caption" color="textSecondary">Status: <span style={{color: "#F00"}}>Not configured.</span></Typography>
|
||||
):
|
||||
JackHostStatus.getDisplayView("Status:\u00A0", this.state.jackStatus)
|
||||
}
|
||||
</div>
|
||||
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
|
||||
disabled={!(isConfigValid && this.state.jackServerSettings.valid)}
|
||||
style={{ opacity: !(isConfigValid && this.state.jackServerSettings.valid) ? 0.6 : 1.0 }}
|
||||
>
|
||||
<SelectHoverBackground selected={false} showHover={true} />
|
||||
<div style={{ width: "100%" }}>
|
||||
@@ -441,9 +424,11 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
||||
open={this.state.showJackServerSettingsDialog}
|
||||
jackServerSettings={this.state.jackServerSettings}
|
||||
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
||||
onApply={(sampleRate, bufferSize, numberOfBuffers) => {
|
||||
this.setState({ showJackServerSettingsDialog: false });
|
||||
this.model.setJackServerSettings(new JackServerSettings(sampleRate, bufferSize, numberOfBuffers));
|
||||
onApply={(jackServerSettings) => {
|
||||
this.setState({ showJackServerSettingsDialog: false,
|
||||
jackServerSettings: jackServerSettings
|
||||
});
|
||||
this.model.setJackServerSettings(jackServerSettings);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
+4
-2
@@ -118,6 +118,7 @@ set (PIPEDAL_SOURCES
|
||||
WifiChannels.hpp
|
||||
WifiChannels.cpp
|
||||
RegDb.cpp RegDb.hpp
|
||||
PiPedalAlsa.hpp PiPedalAlsa.cpp
|
||||
)
|
||||
|
||||
configure_file(config.hpp.in config.hpp)
|
||||
@@ -136,7 +137,7 @@ target_include_directories(pipedald PRIVATE
|
||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs
|
||||
target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs asound
|
||||
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd
|
||||
)
|
||||
|
||||
@@ -144,6 +145,7 @@ target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs
|
||||
add_executable(pipedaltest ${PIPEDAL_SOURCES} testMain.cpp
|
||||
jsonTest.cpp
|
||||
WifiChannelsTest.cpp
|
||||
PiPedalAlsaTest.cpp
|
||||
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
SystemConfigFileTest.cpp
|
||||
@@ -160,7 +162,7 @@ target_include_directories(pipedaltest PRIVATE
|
||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs
|
||||
target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs asound
|
||||
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd
|
||||
)
|
||||
|
||||
|
||||
+128
-26
@@ -18,6 +18,8 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "CommandLineParser.hpp"
|
||||
#include "PiPedalException.hpp"
|
||||
#include <filesystem>
|
||||
@@ -27,27 +29,29 @@
|
||||
#include "SetWifiConfig.hpp"
|
||||
#include "SysExec.hpp"
|
||||
#include <sys/wait.h>
|
||||
#include <pwd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
|
||||
#define SERVICE_ACCOUNT_NAME "pipedal_d"
|
||||
#define SERVICE_GROUP_NAME "pipedal_d"
|
||||
#define JACK_SERVICE_ACCOUNT_NAME "jack"
|
||||
#define JACK_SERVICE_GROUP_NAME "jack"
|
||||
#define AUDIO_SERVICE_GROUP_NAME "audio"
|
||||
|
||||
#define SYSTEMCTL_BIN "/usr/bin/systemctl"
|
||||
#define GROUPADD_BIN "/usr/sbin/groupadd"
|
||||
#define USERADD_BIN "/usr/sbin/useradd"
|
||||
#define USERADD_BIN "/usr/sbin/useradd"
|
||||
#define USERMOD_BIN "/usr/sbin/usermod"
|
||||
#define CHGRP_BIN "/usr/bin/chgrp"
|
||||
#define CHOWN_BIN "/usr/bin/chown"
|
||||
#define CHMOD_BIN "/usr/bin/chmod"
|
||||
|
||||
|
||||
#define SERVICE_PATH "/usr/lib/systemd/system"
|
||||
#define NATIVE_SERVICE "pipedald"
|
||||
#define SHUTDOWN_SERVICE "pipedalshutdownd"
|
||||
// #define REACT_SERVICE "pipedal_react"
|
||||
|
||||
#define JACK_SERVICE "jack"
|
||||
|
||||
std::filesystem::path GetServiceFileName(const std::string &serviceName)
|
||||
{
|
||||
@@ -113,6 +117,14 @@ void StopService()
|
||||
cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service.";
|
||||
}
|
||||
}
|
||||
|
||||
void Uninstall() {
|
||||
StopService();
|
||||
DisableService();
|
||||
system(SYSTEMCTL_BIN " stop jack");
|
||||
system(SYSTEMCTL_BIN " disable jack");
|
||||
}
|
||||
|
||||
void StartService()
|
||||
{
|
||||
|
||||
@@ -132,8 +144,98 @@ void RestartService()
|
||||
StartService();
|
||||
}
|
||||
|
||||
static bool userExists(const char *userName)
|
||||
{
|
||||
struct passwd pwd;
|
||||
struct passwd *result;
|
||||
char *buf;
|
||||
size_t bufsize;
|
||||
int s;
|
||||
|
||||
bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
|
||||
if (bufsize == -1) /* Value was indeterminate */
|
||||
bufsize = 16384; /* Should be more than enough */
|
||||
|
||||
buf = (char *)malloc(bufsize);
|
||||
if (buf == NULL)
|
||||
{
|
||||
perror("malloc");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
s = getpwnam_r(userName, &pwd, buf, bufsize, &result);
|
||||
if (result == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallLimits()
|
||||
{
|
||||
if (SysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
||||
{
|
||||
throw PiPedalException("Failed to create audio service group.");
|
||||
}
|
||||
|
||||
std::filesystem::path limitsConfig = "/usr/security/audio.conf";
|
||||
|
||||
if (!std::filesystem::exists(limitsConfig))
|
||||
{
|
||||
ofstream output(limitsConfig);
|
||||
output << "# Realtime priority group used by pipedal/jack daemon" << endl;
|
||||
output << "@audio - rtprio 95" << endl;
|
||||
output << "@audio - memlock unlimited" << endl;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MaybeStartJackService() {
|
||||
std::filesystem::path drcFile = "/etc/jackdrc";
|
||||
|
||||
if (std::filesystem::exists(drcFile) && std::filesystem::file_size(drcFile) != 0) {
|
||||
system(SYSTEMCTL_BIN " start jack");
|
||||
} else {
|
||||
system(SYSTEMCTL_BIN " mask jack");
|
||||
}
|
||||
}
|
||||
void InstallJackService()
|
||||
{
|
||||
InstallLimits();
|
||||
if (SysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
|
||||
{
|
||||
throw PiPedalException("Failed to create jack service group.");
|
||||
}
|
||||
if (!userExists(JACK_SERVICE_ACCOUNT_NAME))
|
||||
{
|
||||
if (SysExec(USERADD_BIN " " JACK_SERVICE_ACCOUNT_NAME " -g " JACK_SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
|
||||
{
|
||||
// throw PiPedalException("Failed to create service account.");
|
||||
}
|
||||
// lock account for login.
|
||||
SysExec("passwd -l " JACK_SERVICE_ACCOUNT_NAME);
|
||||
}
|
||||
|
||||
// Add to audio groups.
|
||||
SysExec(USERMOD_BIN " -a -G " JACK_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
|
||||
SysExec(USERMOD_BIN " -a -G" AUDIO_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
|
||||
|
||||
// deploy the systemd service file
|
||||
std::map<std::string,std::string> map; // nothing to customize.
|
||||
|
||||
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateJack.service"), GetServiceFileName(JACK_SERVICE));
|
||||
|
||||
|
||||
MaybeStartJackService();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress)
|
||||
{
|
||||
|
||||
InstallJackService();
|
||||
auto endpos = endpointAddress.find_last_of(':');
|
||||
if (endpos == string::npos)
|
||||
{
|
||||
@@ -168,13 +270,15 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
|
||||
{
|
||||
throw PiPedalException("Failed to create service group.");
|
||||
}
|
||||
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
|
||||
if (!userExists(SERVICE_ACCOUNT_NAME))
|
||||
{
|
||||
// throw PiPedalException("Failed to create service account.");
|
||||
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
|
||||
{
|
||||
// throw PiPedalException("Failed to create service account.");
|
||||
}
|
||||
// lock account for login.
|
||||
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
|
||||
}
|
||||
// lock account for login.
|
||||
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
|
||||
|
||||
|
||||
// Add to audio groups.
|
||||
SysExec(USERMOD_BIN " -a -G jack " SERVICE_ACCOUNT_NAME);
|
||||
@@ -278,8 +382,9 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
|
||||
cout << "Complete" << endl;
|
||||
}
|
||||
|
||||
int SudoExec(char**argv) {
|
||||
int pbPid;
|
||||
int SudoExec(char **argv)
|
||||
{
|
||||
int pbPid;
|
||||
int returnValue = 0;
|
||||
|
||||
if ((pbPid = fork()) == 0)
|
||||
@@ -295,7 +400,6 @@ int SudoExec(char**argv) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CommandLineParser parser;
|
||||
@@ -328,7 +432,7 @@ int main(int argc, char **argv)
|
||||
{
|
||||
parser.Parse(argc, (const char **)argv);
|
||||
|
||||
int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap;
|
||||
int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart;
|
||||
if (actionCount > 1)
|
||||
{
|
||||
throw PiPedalException("Please provide only one action.");
|
||||
@@ -364,7 +468,8 @@ int main(int argc, char **argv)
|
||||
|
||||
if (help || helpError)
|
||||
{
|
||||
if (helpError) cout << endl;
|
||||
if (helpError)
|
||||
cout << endl;
|
||||
|
||||
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
|
||||
<< "Copyright (c) 2021 Robin Davies. All rights reserved." << endl
|
||||
@@ -409,7 +514,7 @@ int main(int argc, char **argv)
|
||||
<< "Description:" << endl
|
||||
<< " pipedalconfig performs post-install configuration of pipedal." << endl
|
||||
<< endl;
|
||||
exit(helpError? EXIT_FAILURE: EXIT_SUCCESS);
|
||||
exit(helpError ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
if (portOption.size() != 0 && !install)
|
||||
{
|
||||
@@ -421,16 +526,16 @@ int main(int argc, char **argv)
|
||||
if (uid != 0 && !nopkexec)
|
||||
{
|
||||
// re-execute with PKEXEC in order to prompt form SUDO credentials.
|
||||
std::vector<char*> args;
|
||||
std::vector<char *> args;
|
||||
std::string pkexec = "/usr/bin/pkexec"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
||||
args.push_back((char*)(pkexec.c_str()));
|
||||
args.push_back((char *)(pkexec.c_str()));
|
||||
|
||||
std::filesystem::path path = std::filesystem::absolute(argv[0]);
|
||||
std::string sPath = path;
|
||||
args.push_back((char*)path.c_str());
|
||||
args.push_back((char *)path.c_str());
|
||||
for (int arg = 1; arg < argc; ++arg)
|
||||
{
|
||||
args.push_back((char*)argv[arg]);
|
||||
args.push_back((char *)argv[arg]);
|
||||
}
|
||||
|
||||
std::string prefixArg; // lifetime management for the prefix arguments
|
||||
@@ -440,18 +545,14 @@ int main(int argc, char **argv)
|
||||
{
|
||||
std::filesystem::path prefix = std::filesystem::path(argv[0]).parent_path().parent_path();
|
||||
prefixOptionArg = "-prefix";
|
||||
args.push_back((char*)(prefixOptionArg.c_str()));
|
||||
args.push_back((char *)(prefixOptionArg.c_str()));
|
||||
prefixArg = prefix;
|
||||
args.push_back((char*)prefixArg.c_str());
|
||||
args.push_back((char *)prefixArg.c_str());
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
|
||||
char**rawArgv = &args[0];
|
||||
char **rawArgv = &args[0];
|
||||
return SudoExec(rawArgv);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
@@ -488,6 +589,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else if (uninstall)
|
||||
{
|
||||
Uninstall();
|
||||
}
|
||||
else if (stop)
|
||||
{
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace pipedal
|
||||
size_t GetBlockLength() const { return blockLength_; }
|
||||
size_t GetMidiBufferSize() const { return midiBufferSize_;}
|
||||
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
|
||||
void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; }
|
||||
|
||||
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
|
||||
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
||||
|
||||
+15
-6
@@ -68,6 +68,7 @@ namespace pipedal
|
||||
class JackHostImpl : public JackHost
|
||||
{
|
||||
private:
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
int64_t overrunGracePeriodSamples = 0;
|
||||
|
||||
@@ -145,7 +146,7 @@ private:
|
||||
auto port = outputPorts[i];
|
||||
if (port != nullptr)
|
||||
{
|
||||
jack_disconnect(client, channelSelection.getOutputAudioPorts()[i].c_str(), jack_port_name(port));
|
||||
jack_disconnect(client, jack_port_name(port),channelSelection.getOutputAudioPorts()[i].c_str());
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < midiInputPorts.size(); ++i)
|
||||
@@ -906,6 +907,7 @@ public:
|
||||
|
||||
virtual void Open(const JackChannelSelection &channelSelection)
|
||||
{
|
||||
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
this->currentSample = 0;
|
||||
@@ -1262,11 +1264,18 @@ private:
|
||||
{
|
||||
this_->restarting = true;
|
||||
this_->Close();
|
||||
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
|
||||
this_->Open(this_->channelSelection);
|
||||
this_->restarting = false;
|
||||
onComplete(true, "");
|
||||
isComplete = true;
|
||||
try {
|
||||
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
|
||||
this_->Open(this_->channelSelection);
|
||||
this_->restarting = false;
|
||||
onComplete(true, "");
|
||||
isComplete = true;
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
onComplete(false,e.what());
|
||||
this_->restarting = false;
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
static void ThreadProc_(RestartThread *this_)
|
||||
{
|
||||
|
||||
+1
-1
@@ -27,6 +27,7 @@
|
||||
#include "json.hpp"
|
||||
#include "JackServerSettings.hpp"
|
||||
#include <functional>
|
||||
#include "PiPedalAlsa.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
@@ -118,7 +119,6 @@ public:
|
||||
class IHost;
|
||||
|
||||
class JackHost {
|
||||
|
||||
protected:
|
||||
JackHost() { }
|
||||
public:
|
||||
|
||||
+110
-79
@@ -23,6 +23,9 @@
|
||||
#include "JackServerSettings.hpp"
|
||||
#include <fstream>
|
||||
#include "PiPedalException.hpp"
|
||||
#include <string>
|
||||
#include "unistd.h"
|
||||
#include <filesystem>
|
||||
|
||||
#define DRC_FILENAME "/etc/jackdrc"
|
||||
|
||||
@@ -55,24 +58,46 @@ static std::vector<std::string> SplitArgs(const char *szBuff)
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint64_t GetJackArg(const std::vector<std::string> &args, const char *shortOption, const char *longOption)
|
||||
static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
|
||||
{
|
||||
int pos = -1;
|
||||
std::string strVal;
|
||||
for (size_t i = 1; i < args.size(); ++i)
|
||||
{
|
||||
if (args[i] == shortOption || args[i] == longOption)
|
||||
{
|
||||
pos = i;
|
||||
auto pos = args[i].rfind(longOption,0);
|
||||
if (pos != std::string::npos) {
|
||||
if (args[i].length() == longOption.length())
|
||||
{
|
||||
if (i == args.size()-1) {
|
||||
throw PiPedalException("Can't read Jack configuration.");
|
||||
}
|
||||
strVal = args[i+1];
|
||||
} else {
|
||||
strVal = args[i].substr(longOption.length());
|
||||
}
|
||||
break;
|
||||
}
|
||||
pos = args[i].rfind(shortOption,0);
|
||||
if (pos != std::string::npos) {
|
||||
if (args[i].length() == shortOption.length())
|
||||
{
|
||||
if (i == args.size()-1) {
|
||||
throw PiPedalException("Can't read Jack configuration.");
|
||||
}
|
||||
strVal = args[i+1];
|
||||
} else {
|
||||
strVal = args[i].substr(shortOption.length());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos == -1 || pos == args.size() - 1)
|
||||
{
|
||||
throw PiPedalException("Can't read Jack configuration.");
|
||||
}
|
||||
return strVal;
|
||||
}
|
||||
static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
|
||||
{
|
||||
std::string strVal = GetJackStringArg(args,shortOption,longOption);
|
||||
try
|
||||
{
|
||||
unsigned long value = std::stoul(args[pos + 1]);
|
||||
unsigned long value = std::stoul(strVal);
|
||||
return value;
|
||||
}
|
||||
catch (const std::exception &)
|
||||
@@ -80,35 +105,13 @@ static uint64_t GetJackArg(const std::vector<std::string> &args, const char *sho
|
||||
throw PiPedalException("Can't read Jack configuration.");
|
||||
}
|
||||
}
|
||||
static void SetJackArg(std::vector<std::string> &args, const char *shortOption, const char *longOption,uint64_t value)
|
||||
{
|
||||
int pos = -1;
|
||||
for (size_t i = 1; i < args.size(); ++i)
|
||||
{
|
||||
if (args[i] == shortOption || args[i] == longOption)
|
||||
{
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos == -1 || pos == args.size() - 1)
|
||||
{
|
||||
throw PiPedalException("Can't read Jack configuration.");
|
||||
}
|
||||
stringstream s;
|
||||
s << value;
|
||||
args[pos+1] = s.str();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void JackServerSettings::ReadJackConfiguration()
|
||||
{
|
||||
this->valid_ = false;
|
||||
|
||||
char firstLine[1024];
|
||||
|
||||
std::string lastLine;
|
||||
{
|
||||
ifstream input(DRC_FILENAME);
|
||||
|
||||
@@ -120,22 +123,32 @@ void JackServerSettings::ReadJackConfiguration()
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (input.eof())
|
||||
{
|
||||
Lv2Log::error("Premature end of file in " DRC_FILENAME);
|
||||
return;
|
||||
std::string line;
|
||||
std::getline(input,line);
|
||||
if (line.length() != 0) {
|
||||
lastLine = line;
|
||||
}
|
||||
input.getline(firstLine, sizeof(firstLine));
|
||||
if (firstLine[0] != '#')
|
||||
if (input.eof()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
std::vector < std::string> argv = SplitArgs(firstLine);
|
||||
this->bufferSize_ = GetJackArg(argv, "-p", "-period");
|
||||
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "-nperiods");
|
||||
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "-rate");
|
||||
try
|
||||
{
|
||||
std::vector<std::string> argv = SplitArgs(lastLine.c_str());
|
||||
for (auto i = argv.begin(); i != argv.end(); ++i)
|
||||
{
|
||||
if ((*i) == "-dalsa") {
|
||||
argv.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
|
||||
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
|
||||
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
|
||||
this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device");
|
||||
valid_ = true;
|
||||
} catch (std::exception &)
|
||||
}
|
||||
catch (std::exception &)
|
||||
{
|
||||
Lv2Log::error("Can't parse " DRC_FILENAME);
|
||||
}
|
||||
@@ -147,10 +160,10 @@ void JackServerSettings::Write()
|
||||
this->valid_ = false;
|
||||
|
||||
std::vector<std::string> precedingLines;
|
||||
std::vector < std::string> argv;
|
||||
std::vector<std::string> argv;
|
||||
std::string lastLine;
|
||||
|
||||
{
|
||||
char firstLine[1024];
|
||||
if (std::filesystem::exists(DRC_FILENAME)) {
|
||||
ifstream input(DRC_FILENAME);
|
||||
|
||||
if (!input.is_open())
|
||||
@@ -164,46 +177,64 @@ void JackServerSettings::Write()
|
||||
if (input.eof())
|
||||
{
|
||||
Lv2Log::error("Premature end of file in " DRC_FILENAME);
|
||||
return;
|
||||
}
|
||||
input.getline(firstLine, sizeof(firstLine));
|
||||
if (firstLine[0] != '#')
|
||||
break;
|
||||
precedingLines.push_back(firstLine);
|
||||
}
|
||||
std::getline(input,lastLine);
|
||||
precedingLines.push_back(lastLine);
|
||||
if (input.eof())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// set new values for arguments.
|
||||
argv = SplitArgs(firstLine);
|
||||
try {
|
||||
SetJackArg(argv, "-p", "-period", this->bufferSize_);
|
||||
SetJackArg(argv, "-n", "-nperiods", this->numberOfBuffers_);
|
||||
SetJackArg(argv, "-r", "-rate",this->sampleRate_);
|
||||
} catch (std::exception &)
|
||||
// erase blank lines at the end.
|
||||
while (precedingLines.size() != 0 && precedingLines[precedingLines.size()-1] == "")
|
||||
{
|
||||
Lv2Log::error("Can't parse " DRC_FILENAME);
|
||||
return;
|
||||
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
|
||||
}
|
||||
}
|
||||
// erase the last line, which should contain the command invocation.
|
||||
if (precedingLines.size() != 0)
|
||||
{
|
||||
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
|
||||
}
|
||||
}
|
||||
// write to the output.
|
||||
try {
|
||||
try
|
||||
{
|
||||
ofstream output(DRC_FILENAME);
|
||||
if (!output.is_open())
|
||||
{
|
||||
throw PiPedalException("Can't write " DRC_FILENAME);
|
||||
}
|
||||
for (auto line: precedingLines)
|
||||
if (precedingLines.size() == 0)
|
||||
{
|
||||
// jack1 incantation for promiscuous servers.
|
||||
output << "#!/bin/sh" <<endl;
|
||||
output << "export JACK_PROMISCUOUS_SERVER=" << endl;
|
||||
output << "export JACK_NO_AUDIO_RESERVATION=" << endl;
|
||||
output << "umask 0" << endl;
|
||||
}
|
||||
for (auto line : precedingLines)
|
||||
{
|
||||
output << line << endl;
|
||||
}
|
||||
for (size_t i = 0; i < argv.size(); ++i)
|
||||
// the style used by qjackctl. :-/
|
||||
output << "/usr/bin/jackd "
|
||||
<< "-R -P90"
|
||||
<< " -driver alsa -d" << this->alsaDevice_
|
||||
<< " -r" << this->sampleRate_
|
||||
<< " -p" << this->bufferSize_
|
||||
<< " -n" << this->numberOfBuffers_ << " -Xseq"
|
||||
<< endl;
|
||||
system("/usr/bin/chmod 755 " DRC_FILENAME);
|
||||
system("pulseaudio --kill");
|
||||
system("/usr/bin/systemctl enable jack");
|
||||
if (system("/usr/bin/systemctl restart jack") != 0)
|
||||
{
|
||||
if (i != 0) {
|
||||
output << " ";
|
||||
}
|
||||
output << argv[i];
|
||||
throw PiPedalException("Failed to start jack audio service.");
|
||||
}
|
||||
output << endl;
|
||||
} catch (const std::exception &e) {
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
stringstream s;
|
||||
s << "jack - " << e.what();
|
||||
Lv2Log::error(s.str());
|
||||
@@ -211,10 +242,10 @@ void JackServerSettings::Write()
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(JackServerSettings)
|
||||
JSON_MAP_REFERENCE(JackServerSettings,valid)
|
||||
JSON_MAP_REFERENCE(JackServerSettings,rebootRequired)
|
||||
JSON_MAP_REFERENCE(JackServerSettings,sampleRate)
|
||||
JSON_MAP_REFERENCE(JackServerSettings,bufferSize)
|
||||
JSON_MAP_REFERENCE(JackServerSettings,numberOfBuffers)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, valid)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace pipedal {
|
||||
class JackServerSettings {
|
||||
bool valid_ = false;
|
||||
bool rebootRequired_ = false;
|
||||
std::string alsaDevice_;
|
||||
uint64_t sampleRate_ = 0;
|
||||
uint32_t bufferSize_ = 0;
|
||||
uint32_t numberOfBuffers_ = 0;
|
||||
@@ -36,6 +37,7 @@ namespace pipedal {
|
||||
uint64_t GetSampleRate() const { return sampleRate_; }
|
||||
uint32_t GetBufferSize() const { return bufferSize_; }
|
||||
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
|
||||
const std::string&GetAlsaDevice() const { return alsaDevice_; }
|
||||
|
||||
void ReadJackConfiguration();
|
||||
|
||||
|
||||
+20
-2
@@ -22,6 +22,7 @@
|
||||
#include "Locale.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
|
||||
// Must be UNICODE. Should reflect system locale. (.e.g en-US.UTF8, de-de.UTF8)
|
||||
@@ -36,12 +37,29 @@ static const char*getLocale(const char*localeEnvironmentVariable)
|
||||
{
|
||||
result = getenv("LC_ALL");
|
||||
}
|
||||
|
||||
if (result == nullptr) {
|
||||
result = "en_US.UTF-8";
|
||||
}
|
||||
std::stringstream s;
|
||||
s << "Locale: " << result;
|
||||
Lv2Log::error(s.str());
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::locale collationLocale(getLocale("LC_COLLATION"));
|
||||
const std::collate<char>& Locale::collation = std::use_facet<std::collate<char> >(collationLocale);
|
||||
|
||||
void Locale::setDefaultLocale() {
|
||||
const char* locale = getLocale("LC_ALL");
|
||||
try {
|
||||
setlocale(LC_ALL,locale);
|
||||
} catch (const std::exception&)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "Failed to set default locale (" << locale << "). Defaulting to en_US.";
|
||||
Lv2Log::error(s.str());
|
||||
|
||||
std::setlocale(LC_ALL,"en_US.UTF-8");
|
||||
}
|
||||
}
|
||||
const std::collate<char>& Locale::collation() { return std::use_facet<std::collate<char> >(std::locale()); }
|
||||
|
||||
|
||||
+3
-1
@@ -25,6 +25,8 @@ namespace pipedal {
|
||||
|
||||
class Locale {
|
||||
public:
|
||||
static const std::collate<char>& collation;
|
||||
static void setDefaultLocale();
|
||||
|
||||
static const std::collate<char>& collation();
|
||||
};
|
||||
}
|
||||
|
||||
+7
-4
@@ -394,13 +394,14 @@ void Lv2Host::Load(const char *lv2Path)
|
||||
}
|
||||
}
|
||||
|
||||
auto compare = [](
|
||||
const auto &collation = std::use_facet<std::collate<char> >(std::locale());
|
||||
auto compare = [&collation](
|
||||
const std::shared_ptr<Lv2PluginInfo> &left,
|
||||
const std::shared_ptr<Lv2PluginInfo> &right)
|
||||
{
|
||||
const char *pb1 = left->name().c_str();
|
||||
const char *pb2 = right->name().c_str();
|
||||
return Locale::collation.compare(
|
||||
return collation.compare(
|
||||
pb1, pb1 + left->name().size(),
|
||||
pb2, pb2 + right->name().size()) < 0;
|
||||
};
|
||||
@@ -1005,9 +1006,11 @@ std::vector<Lv2PluginPreset> Lv2Host::GetPluginPresets(const std::string &plugin
|
||||
}
|
||||
lilv_nodes_free(presets);
|
||||
|
||||
auto compare = [] (const Lv2PluginPreset&left, const Lv2PluginPreset&right)
|
||||
auto& collation = std::use_facet<std::collate<char> >(std::locale());
|
||||
|
||||
auto compare = [&collation] (const Lv2PluginPreset&left, const Lv2PluginPreset&right)
|
||||
{
|
||||
return Locale::collation.compare(
|
||||
return collation.compare(
|
||||
left.name_.c_str(),
|
||||
left.name_.c_str()+left.name_.size(),
|
||||
right.name_.c_str(),
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2021 Robin 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 "PiPedalAlsa.hpp"
|
||||
#include "alsa/asoundlib.h"
|
||||
#include "Lv2Log.hpp"
|
||||
#include <mutex>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static uint32_t RATES[] = {
|
||||
44100, 48000, 44100 * 2, 48000 * 2, 44100 * 4, 48000 * 4};
|
||||
|
||||
|
||||
std::mutex alsaMutex;
|
||||
|
||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAvailableAlsaDevices()
|
||||
{
|
||||
std::lock_guard guard{alsaMutex};
|
||||
|
||||
std::vector<AlsaDeviceInfo> result;
|
||||
|
||||
int cardNum = -1; // Start with first card
|
||||
int err;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if ((err = snd_card_next(&cardNum)) < 0)
|
||||
{
|
||||
Lv2Log::error("Unexpected error enumerating ALSA devices.");
|
||||
break;
|
||||
}
|
||||
if (cardNum < 0)
|
||||
// No more cards
|
||||
break;
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "hw:" << cardNum;
|
||||
std::string cardId = ss.str();
|
||||
|
||||
snd_ctl_t *hDevice = nullptr;
|
||||
|
||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
snd_ctl_card_info_t *alsaInfo;
|
||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||
{
|
||||
Lv2Log::error("Failed to allocate ALSA card info");
|
||||
snd_ctl_close(hDevice);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||
if (err == 0)
|
||||
{
|
||||
AlsaDeviceInfo info;
|
||||
info.cardId_ = cardNum;
|
||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||
const char* driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||
|
||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||
|
||||
|
||||
snd_pcm_t *hDevice = nullptr;
|
||||
|
||||
// must support capture AND playback
|
||||
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0);
|
||||
if (err == 0) {
|
||||
snd_pcm_close(hDevice);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
snd_pcm_hw_params_t *params = nullptr;
|
||||
err = snd_pcm_hw_params_malloc(¶ms);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_any(hDevice, params);
|
||||
if (err == 0)
|
||||
{
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize,maxBufferSize;
|
||||
int dir;
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_min(params,&minBufferSize);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_max(params,&maxBufferSize);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||
{
|
||||
uint32_t rate = RATES[i];
|
||||
if (rate >= minRate && rate <= maxRate)
|
||||
{
|
||||
info.sampleRates_.push_back(rate);
|
||||
}
|
||||
}
|
||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||
result.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params != nullptr)
|
||||
snd_pcm_hw_params_free(params);
|
||||
snd_pcm_close(hDevice);
|
||||
}
|
||||
}
|
||||
snd_ctl_card_info_free(alsaInfo);
|
||||
snd_ctl_close(hDevice);
|
||||
}
|
||||
}
|
||||
snd_config_update_free_global();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void PiPedalAlsaDevices::PreLoadJackDevice(const std::string&deviceName)
|
||||
{
|
||||
// save the device info before we start the jack server, because
|
||||
// we won't be able to do it later.
|
||||
std::vector<AlsaDeviceInfo> devices = GetAvailableAlsaDevices();
|
||||
this->hasJackDevice = false;
|
||||
for (auto &device : devices)
|
||||
{
|
||||
if (device.id_ == deviceName) {
|
||||
this->currentJackDevice = device;
|
||||
this->hasJackDevice = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
{
|
||||
std::vector<AlsaDeviceInfo> devices = GetAvailableAlsaDevices();
|
||||
if (this->hasJackDevice)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto &device: devices)
|
||||
{
|
||||
if (device.id_ == this->currentJackDevice.id_)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
devices.push_back(this->currentJackDevice);
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(AlsaDeviceInfo)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, name)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
||||
JSON_MAP_END()
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2021 Robin 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 "json.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
class AlsaDeviceInfo {
|
||||
public:
|
||||
int cardId_ = -1;
|
||||
std::string id_;
|
||||
std::string name_;
|
||||
std::string longName_;
|
||||
std::vector<uint32_t> sampleRates_;
|
||||
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
||||
|
||||
DECLARE_JSON_MAP(AlsaDeviceInfo);
|
||||
|
||||
};
|
||||
|
||||
class PiPedalAlsaDevices {
|
||||
|
||||
bool hasJackDevice = false;
|
||||
AlsaDeviceInfo currentJackDevice;
|
||||
std::vector<AlsaDeviceInfo> GetAvailableAlsaDevices();
|
||||
public:
|
||||
void PreLoadJackDevice(const std::string&deviceName);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2021 Robin 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 <sstream>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "PiPedalAlsa.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
TEST_CASE( "ALSA Test", "[pipedal_alsa_test]" ) {
|
||||
|
||||
PiPedalAlsaDevices devices;
|
||||
auto result = devices.GetAlsaDevices();
|
||||
REQUIRE(result.size() >= 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+46
-25
@@ -87,6 +87,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
||||
|
||||
this->jackServerSettings.ReadJackConfiguration();
|
||||
|
||||
alsaDevices.PreLoadJackDevice(this->jackServerSettings.GetAlsaDevice());
|
||||
|
||||
storage.SetDataRoot(configuration.GetLocalStoragePath().c_str());
|
||||
storage.Initialize();
|
||||
|
||||
@@ -332,7 +334,7 @@ PedalBoard PiPedalModel::getPreset(int64_t instanceId)
|
||||
std::lock_guard guard(mutex);
|
||||
return this->storage.GetPreset(instanceId);
|
||||
}
|
||||
void PiPedalModel::getBank(int64_t instanceId, BankFile*pResult)
|
||||
void PiPedalModel::getBank(int64_t instanceId, BankFile *pResult)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
this->storage.GetBankFile(instanceId, pResult);
|
||||
@@ -855,13 +857,19 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
this->jackServerSettings = jackServerSettings;
|
||||
|
||||
if (!ShutdownClient::CanUseShutdownClient())
|
||||
{
|
||||
throw PiPedalException("Can't change server settings when running interactively.");
|
||||
throw PiPedalException("Can't change server settings when running a debug server.");
|
||||
}
|
||||
|
||||
if (this->jackServerSettings.GetAlsaDevice() != jackServerSettings.GetAlsaDevice())
|
||||
{
|
||||
this->alsaDevices.PreLoadJackDevice(jackServerSettings.GetAlsaDevice());
|
||||
}
|
||||
this->jackServerSettings = jackServerSettings;
|
||||
|
||||
|
||||
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||
@@ -879,29 +887,38 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
||||
{
|
||||
this->jackConfiguration.SetIsRestarting(true);
|
||||
fireJackConfigurationChanged(this->jackConfiguration);
|
||||
this->jackHost->UpdateServerConfiguration(jackServerSettings,
|
||||
[this](bool success, const std::string &errorMessage)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (!success)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "UpdateServerconfiguration failed: " << errorMessage;
|
||||
Lv2Log::error(s.str().c_str());
|
||||
}
|
||||
// Update jack server status.
|
||||
this->jackConfiguration.SetIsRestarting(false);
|
||||
fireJackConfigurationChanged(this->jackConfiguration);
|
||||
this->jackHost->UpdateServerConfiguration(
|
||||
jackServerSettings,
|
||||
[this](bool success, const std::string &errorMessage)
|
||||
{
|
||||
std::lock_guard guard(mutex);
|
||||
if (!success)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "UpdateServerconfiguration failed: " << errorMessage;
|
||||
Lv2Log::error(s.str().c_str());
|
||||
}
|
||||
// Update jack server status.
|
||||
this->jackConfiguration.SetIsRestarting(false);
|
||||
if (!success)
|
||||
{
|
||||
this->jackConfiguration.SetErrorStatus(errorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->jackConfiguration.SetErrorStatus("");
|
||||
fireJackConfigurationChanged(this->jackConfiguration);
|
||||
|
||||
// restart the pedalboard on a new instance.
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
// restart the pedalboard on a new instance.
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
updateRealtimeVuSubscriptions();
|
||||
updateRealtimeMonitorPortSubscriptions();
|
||||
});
|
||||
}
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
updateRealtimeVuSubscriptions();
|
||||
updateRealtimeMonitorPortSubscriptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::updateDefaults(PedalBoardItem *pedalBoardItem)
|
||||
@@ -1040,3 +1057,7 @@ void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHand
|
||||
jackHost->SetListenForMidiEvent(false);
|
||||
}
|
||||
}
|
||||
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
|
||||
{
|
||||
return this->alsaDevices.GetAlsaDevices();
|
||||
}
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
|
||||
class PiPedalModel: private IJackHostCallbacks {
|
||||
private:
|
||||
PiPedalAlsaDevices alsaDevices;
|
||||
|
||||
|
||||
class MidiListener {
|
||||
public:
|
||||
@@ -202,6 +204,7 @@ public:
|
||||
|
||||
void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||
void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled);
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
|
||||
};
|
||||
} // namespace pipedal.
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "WifiConfigSettings.hpp"
|
||||
#include "WifiChannels.hpp"
|
||||
#include "SysExec.hpp"
|
||||
#include "PiPedalAlsa.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
@@ -689,6 +690,12 @@ public:
|
||||
{
|
||||
JackHostStatus status = model.getJackStatus();
|
||||
this->Reply(replyTo,"getJackStatus",status);
|
||||
} else if (message == "getAlsaDevices")
|
||||
{
|
||||
std::vector<AlsaDeviceInfo> devices = model.GetAlsaDevices();
|
||||
this->Reply(replyTo,"getAlsaDevices",devices);
|
||||
|
||||
|
||||
} else if (message == "getWifiChannels")
|
||||
{
|
||||
std::string country;
|
||||
|
||||
@@ -109,7 +109,8 @@ bool ShutdownClient::WriteMessage(const char*message) {
|
||||
while (!eolFound)
|
||||
{
|
||||
ssize_t nRead = read(sock,pWrite,available);
|
||||
if (nRead == 0) {
|
||||
if (nRead == -1)
|
||||
{
|
||||
*pWrite = 0;
|
||||
break;
|
||||
}
|
||||
@@ -156,12 +157,11 @@ bool ShutdownClient::WriteMessage(const char*message) {
|
||||
bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackServerSettings)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "setJackConfiguration "
|
||||
<< jackServerSettings.GetSampleRate()
|
||||
<< " " << jackServerSettings.GetBufferSize()
|
||||
<< " " << jackServerSettings.GetNumberOfBuffers()
|
||||
<< "\n";
|
||||
|
||||
s << "setJackConfiguration ";
|
||||
|
||||
json_writer writer(s,true);
|
||||
writer.write(jackServerSettings);
|
||||
s << std::endl;
|
||||
return WriteMessage(s.str().c_str());
|
||||
}
|
||||
|
||||
|
||||
+13
-24
@@ -80,27 +80,12 @@ static void ReleaseAccessPoint(const std::string gatewayAddress)
|
||||
}
|
||||
|
||||
|
||||
bool setJackConfiguration(uint32_t sampleRate,uint32_t bufferSize,uint32_t numberOfBuffers)
|
||||
bool setJackConfiguration(JackServerSettings serverSettings)
|
||||
{
|
||||
bool success = true;
|
||||
JackServerSettings serverSettings(sampleRate,bufferSize,numberOfBuffers);
|
||||
|
||||
try {
|
||||
serverSettings.Write();
|
||||
} catch (const std::exception &e) {
|
||||
std::stringstream s;
|
||||
s << "Failed to write jackdrc settings. " << e.what();
|
||||
Lv2Log::error(s.str());
|
||||
success = false;
|
||||
}
|
||||
::system("pulseaudio --kill");
|
||||
|
||||
if (::system("systemctl restart jack") != 0)
|
||||
{
|
||||
Lv2Log::error("Failed to restart jack server.");
|
||||
success = false;
|
||||
}
|
||||
return false;
|
||||
serverSettings.Write();
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
@@ -245,24 +230,28 @@ private:
|
||||
} else if (startsWith(s,"setJackConfiguration "))
|
||||
{
|
||||
auto remainder = s.substr(strlen("setJackConfiguration "));
|
||||
|
||||
// xxx delete me
|
||||
Lv2Log::error("setJackConfiguration: " + remainder);
|
||||
|
||||
std::stringstream input(remainder);
|
||||
uint32_t sampleRate;
|
||||
uint32_t bufferSize;
|
||||
uint32_t numberOfBuffers;
|
||||
input >> sampleRate >> bufferSize >> numberOfBuffers;
|
||||
JackServerSettings serverSettings;
|
||||
json_reader reader(input);
|
||||
|
||||
reader.read(&serverSettings);
|
||||
|
||||
if (input.fail())
|
||||
{
|
||||
result = -1;
|
||||
} else {
|
||||
result = setJackConfiguration(sampleRate,bufferSize,numberOfBuffers) ? 0: -1;
|
||||
result = setJackConfiguration(serverSettings) ? 0: -1;
|
||||
}
|
||||
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
std::stringstream t;
|
||||
t << "-2 " << e.what();
|
||||
t << "-2 " << e.what() << "\n";
|
||||
this->response = t.str();
|
||||
this->writePosition = 0;
|
||||
WriteSome();
|
||||
|
||||
@@ -418,6 +418,8 @@ uint16_t g_ShutdownPort = 0;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
|
||||
sem_init(&signalSemaphore, 0, 0);
|
||||
|
||||
signal(SIGINT, sig_handler);
|
||||
|
||||
@@ -5,6 +5,7 @@ After=network.target
|
||||
BindsTo=jack.service
|
||||
|
||||
|
||||
|
||||
[Service]
|
||||
LimitMEMLOCK=infinity
|
||||
LimitRTPRIO=95
|
||||
@@ -12,11 +13,11 @@ ExecStart=${COMMAND}
|
||||
User=pipedal_d
|
||||
Group=pipedal_d
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
RestartSec=25
|
||||
TimeoutStopSec=10
|
||||
WorkingDirectory=/var/pipedal
|
||||
Environment=JACK_PROMISCUOUS_SERVER=jack
|
||||
Environment=JACK_START_SERVER=1
|
||||
Environment=JACK_PROMISCUOUS_SERVER=
|
||||
Environment=JACK_STJACK_NO_START_SERVER=
|
||||
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=Jack Audio Daemon
|
||||
After=sound.target
|
||||
After=local-fs.target
|
||||
After=dbus.socket
|
||||
|
||||
|
||||
[Service]
|
||||
LimitMEMLOCK=infinity
|
||||
LimitRTTIME=infinity
|
||||
LimitRTPRIO=95
|
||||
ExecStartPre=/bin/sleep 3
|
||||
ExecStart=/etc/jackdrc
|
||||
User=jack
|
||||
Group=jack
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
|
||||
Environment=JACK_PROMISCUOUS_SERVER=jack
|
||||
Environment=JACK_NO_AUDIO_RESERVATION=1
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user