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
|
install (FILES ${PROJECT_SOURCE_DIR}/src/template.service
|
||||||
${PROJECT_SOURCE_DIR}/src/templateShutdown.service
|
${PROJECT_SOURCE_DIR}/src/templateShutdown.service
|
||||||
|
${PROJECT_SOURCE_DIR}/src/templateJack.service
|
||||||
DESTINATION /etc/pipedal )
|
DESTINATION /etc/pipedal )
|
||||||
|
|
||||||
install(CODE
|
install(CODE
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
/* One or more directories containing LV2 plugins, seperated by ':' */
|
/* 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. */
|
/* 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 ':' */
|
/* 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",
|
||||||
//"lv2_path" : "/usr/modep/lv2/rkr.lv2",
|
|
||||||
|
|
||||||
|
|
||||||
/* A writable directory in which state files can be stored, preferrably only by the server process. */
|
/* 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/JackServerSettingsDialog.tsx
|
||||||
src/JackServerSettings.tsx
|
src/JackServerSettings.tsx
|
||||||
src/DialogEx.tsx
|
src/DialogEx.tsx
|
||||||
|
src/AlsaDeviceInfo.tsx
|
||||||
|
|
||||||
src/AboutDialog.tsx
|
src/AboutDialog.tsx
|
||||||
src/App.test.tsx
|
src/App.test.tsx
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"socket_server_port": 80,
|
"socket_server_port": 8080,
|
||||||
"socket_server_address": "*",
|
"socket_server_address": "*",
|
||||||
|
|
||||||
"max_upload_size": 1048576,
|
"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{
|
deserialize(input: any) : JackServerSettings{
|
||||||
this.valid = input.valid;
|
this.valid = input.valid;
|
||||||
this.rebootRequired = input.rebootRequired;
|
this.rebootRequired = input.rebootRequired;
|
||||||
|
this.alsaDevice = input.alsaDevice?? "";
|
||||||
this.sampleRate = input.sampleRate;
|
this.sampleRate = input.sampleRate;
|
||||||
this.bufferSize = input.bufferSize;
|
this.bufferSize = input.bufferSize;
|
||||||
this.numberOfBuffers = input.numberOfBuffers;
|
this.numberOfBuffers = input.numberOfBuffers;
|
||||||
return this;
|
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;
|
return new JackServerSettings().deserialize(this);
|
||||||
if (bufferSize) this.bufferSize = bufferSize;
|
|
||||||
if (numberOfBuffers) this.numberOfBuffers = numberOfBuffers;
|
|
||||||
if (numberOfBuffers) {
|
|
||||||
this.valid = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
valid: boolean = false;
|
valid: boolean = false;
|
||||||
rebootRequired = false;
|
rebootRequired = false;
|
||||||
|
alsaDevice: string = "";
|
||||||
sampleRate = 48000;
|
sampleRate = 48000;
|
||||||
bufferSize = 64;
|
bufferSize = 64;
|
||||||
numberOfBuffers = 3;
|
numberOfBuffers = 3;
|
||||||
|
|
||||||
getSummaryText() {
|
getSummaryText() {
|
||||||
if (this.valid) {
|
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 {
|
} else {
|
||||||
return "";
|
return "Not configured";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,15 @@ import DialogContent from '@material-ui/core/DialogContent';
|
|||||||
import MenuItem from '@material-ui/core/MenuItem';
|
import MenuItem from '@material-ui/core/MenuItem';
|
||||||
import { nullCast } from './Utility';
|
import { nullCast } from './Utility';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
|
||||||
|
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||||
|
|
||||||
|
const INVALID_DEVICE_ID = "_invalid_";
|
||||||
interface JackServerSettingsDialogState {
|
interface JackServerSettingsDialogState {
|
||||||
latencyText: string;
|
latencyText: string;
|
||||||
|
jackServerSettings: JackServerSettings;
|
||||||
|
alsaDevices?: AlsaDeviceInfo[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
@@ -54,106 +60,286 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
jackServerSettings: JackServerSettings;
|
jackServerSettings: JackServerSettings;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onApply: (sampleRate: number, bufferSize: number, numberOfBuffers: number) => void;
|
onApply: (jackServerSettings: JackServerSettings) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLatencyText(sampleRate: number, bufferSize: number, numberOfBuffers: number): string {
|
function getLatencyText(settings: JackServerSettings ): string {
|
||||||
let ms = bufferSize * numberOfBuffers / sampleRate * 1000;
|
if (!settings.valid) return "\u00A0";
|
||||||
|
let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000;
|
||||||
return ms.toFixed(1) + "ms";
|
return ms.toFixed(1) + "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
const JackServerSettingsDialog = withStyles(styles)(
|
const JackServerSettingsDialog = withStyles(styles)(
|
||||||
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
|
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
|
||||||
|
|
||||||
|
model: PiPedalModel;
|
||||||
|
|
||||||
constructor(props: JackServerSettingsDialogProps) {
|
constructor(props: JackServerSettingsDialogProps) {
|
||||||
super(props);
|
super(props);
|
||||||
let jackServerSettings = props.jackServerSettings;
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
latencyText:
|
latencyText: "\u00A0",
|
||||||
getLatencyText(
|
jackServerSettings: props.jackServerSettings.clone() // invalid, but not nullish
|
||||||
jackServerSettings.sampleRate,
|
|
||||||
jackServerSettings.bufferSize,
|
|
||||||
jackServerSettings.numberOfBuffers)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
updateLatency(e: any): void {
|
requestAlsaInfo() {
|
||||||
setTimeout(()=> {
|
this.model.getAlsaDevices()
|
||||||
let sampleRate: number = parseInt(nullCast<any>(document.getElementById('jsd_sampleRate')).value);
|
.then((devices) => {
|
||||||
let bufferSize: number = parseInt(nullCast<any>(document.getElementById('jsd_bufferSize')).value);
|
if (this.mounted) {
|
||||||
let bufferCount: number = parseInt(nullCast<any>(document.getElementById('jsd_bufferCount')).value);
|
if (this.props.open) {
|
||||||
|
let settings = this.applyAlsaDevices(this.props.jackServerSettings, devices);
|
||||||
this.setState({
|
this.setState({
|
||||||
latencyText: getLatencyText(sampleRate, bufferSize, bufferCount)
|
alsaDevices: devices,
|
||||||
|
jackServerSettings: settings,
|
||||||
|
latencyText: getLatencyText(settings)
|
||||||
});
|
});
|
||||||
},0);
|
} else {
|
||||||
|
this.setState({ alsaDevices: devices });
|
||||||
}
|
}
|
||||||
handleApply() {
|
}
|
||||||
let sampleRate = parseInt(nullCast<any>(document.getElementById('jsd_sampleRate')).value);
|
})
|
||||||
let bufferSize = parseInt(nullCast<any>(document.getElementById('jsd_bufferSize')).value);
|
.catch((error) => {
|
||||||
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() {
|
render() {
|
||||||
const classes = this.props.classes;
|
const classes = this.props.classes;
|
||||||
|
|
||||||
const { onClose, jackServerSettings, open } = this.props;
|
const { onClose, open } = this.props;
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
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 (
|
return (
|
||||||
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
|
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
<div>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl className={classes.formControl}>
|
||||||
<InputLabel htmlFor="sampleRate">Sample rate</InputLabel>
|
<InputLabel htmlFor="jsd_device">Device</InputLabel>
|
||||||
<Select
|
<Select onChange={(e) => this.handleDeviceChanged(e)}
|
||||||
onChange={(e) => this.updateLatency(e)}
|
value={this.state.jackServerSettings.alsaDevice}
|
||||||
defaultValue={jackServerSettings.sampleRate}
|
style={{width: 220}}
|
||||||
|
inputProps={{
|
||||||
|
name: "Device",
|
||||||
|
id: "jsd_device",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(noDevices && !waitingForDevices) &&
|
||||||
|
(
|
||||||
|
<MenuItem value={INVALID_DEVICE_ID}>No suitable devices.</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="jsd_sampleRate">Sample rate</InputLabel>
|
||||||
|
<Select
|
||||||
|
onChange={(e) => this.handleRateChanged(e)}
|
||||||
|
value={this.state.jackServerSettings.sampleRate}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: 'Sample Rate',
|
|
||||||
id: 'jsd_sampleRate',
|
id: 'jsd_sampleRate',
|
||||||
style: {
|
style: {
|
||||||
textAlign: "right"
|
textAlign: "right"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value={44100}>44100</MenuItem>
|
{selectedDevice &&
|
||||||
<MenuItem value={48000}>48000</MenuItem>
|
selectedDevice.sampleRates.map((sr) => {
|
||||||
<MenuItem value={88200}>88200</MenuItem>
|
return ( <MenuItem value={sr}>{sr}</MenuItem> );
|
||||||
<MenuItem value={96000}>96000</MenuItem>
|
})
|
||||||
<MenuItem value={176400}>176400</MenuItem>
|
}
|
||||||
<MenuItem value={192000}>192000</MenuItem>
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl className={classes.formControl}>
|
||||||
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
|
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
onChange={(e) => this.updateLatency(e)}
|
onChange={(e) => this.handleSizeChanged(e)}
|
||||||
defaultValue={jackServerSettings.bufferSize}
|
value={this.state.jackServerSettings.bufferSize}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: 'Buffer size',
|
name: 'Buffer size',
|
||||||
id: 'jsd_bufferSize',
|
id: 'jsd_bufferSize',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value={16}>16</MenuItem>
|
{bufferSizes.map((buffSize) =>
|
||||||
<MenuItem value={32}>32</MenuItem>
|
(
|
||||||
<MenuItem value={64}>64</MenuItem>
|
<MenuItem value={buffSize}>{buffSize}</MenuItem>
|
||||||
<MenuItem value={128}>128</MenuItem>
|
|
||||||
<MenuItem value={256}>256</MenuItem>
|
)
|
||||||
<MenuItem value={512}>512</MenuItem>
|
)}
|
||||||
<MenuItem value={1024}>1024</MenuItem>
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl className={classes.formControl}>
|
||||||
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
|
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
onChange={(e) => this.updateLatency(e)}
|
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
|
||||||
defaultValue={jackServerSettings.numberOfBuffers}
|
value={this.state.jackServerSettings.numberOfBuffers}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: 'Number of buffers',
|
name: 'Number of buffers',
|
||||||
id: 'jsd_bufferCount',
|
id: 'jsd_bufferCount',
|
||||||
@@ -165,6 +351,7 @@ const JackServerSettingsDialog = withStyles(styles)(
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 8, marginLeft: 24 }}
|
<Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 8, marginLeft: 24 }}
|
||||||
color="textSecondary">
|
color="textSecondary">
|
||||||
Latency: {this.state.latencyText}
|
Latency: {this.state.latencyText}
|
||||||
@@ -175,7 +362,8 @@ const JackServerSettingsDialog = withStyles(styles)(
|
|||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={()=> this.handleApply()} color="secondary">
|
<Button onClick={() => this.handleApply()} color="secondary" disabled={
|
||||||
|
(!this.state.alsaDevices) || !this.state.jackServerSettings.valid}>
|
||||||
OK
|
OK
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import MidiBinding from './MidiBinding';
|
|||||||
import PluginPreset from './PluginPreset';
|
import PluginPreset from './PluginPreset';
|
||||||
import WifiConfigSettings from './WifiConfigSettings';
|
import WifiConfigSettings from './WifiConfigSettings';
|
||||||
import WifiChannel from './WifiChannel';
|
import WifiChannel from './WifiChannel';
|
||||||
|
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||||
|
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
@@ -348,6 +349,7 @@ export interface PiPedalModel {
|
|||||||
|
|
||||||
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
||||||
|
|
||||||
|
getAlsaDevices() : Promise<AlsaDeviceInfo[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PiPedalModelImpl implements PiPedalModel {
|
class PiPedalModelImpl implements PiPedalModel {
|
||||||
@@ -1586,6 +1588,23 @@ class PiPedalModelImpl implements PiPedalModel {
|
|||||||
return result;
|
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
|
overflowX: "hidden", overflowY: "auto", marginTop: 22
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isConfigValid && (
|
<div >
|
||||||
<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 }}>
|
|
||||||
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
|
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
|
||||||
AUDIO
|
AUDIO
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
<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>
|
</div>
|
||||||
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
|
<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} />
|
<SelectHoverBackground selected={false} showHover={true} />
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
@@ -441,9 +424,11 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
open={this.state.showJackServerSettingsDialog}
|
open={this.state.showJackServerSettingsDialog}
|
||||||
jackServerSettings={this.state.jackServerSettings}
|
jackServerSettings={this.state.jackServerSettings}
|
||||||
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
||||||
onApply={(sampleRate, bufferSize, numberOfBuffers) => {
|
onApply={(jackServerSettings) => {
|
||||||
this.setState({ showJackServerSettingsDialog: false });
|
this.setState({ showJackServerSettingsDialog: false,
|
||||||
this.model.setJackServerSettings(new JackServerSettings(sampleRate, bufferSize, numberOfBuffers));
|
jackServerSettings: jackServerSettings
|
||||||
|
});
|
||||||
|
this.model.setJackServerSettings(jackServerSettings);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -118,6 +118,7 @@ set (PIPEDAL_SOURCES
|
|||||||
WifiChannels.hpp
|
WifiChannels.hpp
|
||||||
WifiChannels.cpp
|
WifiChannels.cpp
|
||||||
RegDb.cpp RegDb.hpp
|
RegDb.cpp RegDb.hpp
|
||||||
|
PiPedalAlsa.hpp PiPedalAlsa.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_file(config.hpp.in config.hpp)
|
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}
|
${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
|
${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
|
add_executable(pipedaltest ${PIPEDAL_SOURCES} testMain.cpp
|
||||||
jsonTest.cpp
|
jsonTest.cpp
|
||||||
WifiChannelsTest.cpp
|
WifiChannelsTest.cpp
|
||||||
|
PiPedalAlsaTest.cpp
|
||||||
|
|
||||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||||
SystemConfigFileTest.cpp
|
SystemConfigFileTest.cpp
|
||||||
@@ -160,7 +162,7 @@ target_include_directories(pipedaltest PRIVATE
|
|||||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS}
|
${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
|
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+122
-20
@@ -18,6 +18,8 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "CommandLineParser.hpp"
|
#include "CommandLineParser.hpp"
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -27,12 +29,16 @@
|
|||||||
#include "SetWifiConfig.hpp"
|
#include "SetWifiConfig.hpp"
|
||||||
#include "SysExec.hpp"
|
#include "SysExec.hpp"
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
#define SERVICE_ACCOUNT_NAME "pipedal_d"
|
#define SERVICE_ACCOUNT_NAME "pipedal_d"
|
||||||
#define SERVICE_GROUP_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 SYSTEMCTL_BIN "/usr/bin/systemctl"
|
||||||
#define GROUPADD_BIN "/usr/sbin/groupadd"
|
#define GROUPADD_BIN "/usr/sbin/groupadd"
|
||||||
@@ -42,12 +48,10 @@ using namespace pipedal;
|
|||||||
#define CHOWN_BIN "/usr/bin/chown"
|
#define CHOWN_BIN "/usr/bin/chown"
|
||||||
#define CHMOD_BIN "/usr/bin/chmod"
|
#define CHMOD_BIN "/usr/bin/chmod"
|
||||||
|
|
||||||
|
|
||||||
#define SERVICE_PATH "/usr/lib/systemd/system"
|
#define SERVICE_PATH "/usr/lib/systemd/system"
|
||||||
#define NATIVE_SERVICE "pipedald"
|
#define NATIVE_SERVICE "pipedald"
|
||||||
#define SHUTDOWN_SERVICE "pipedalshutdownd"
|
#define SHUTDOWN_SERVICE "pipedalshutdownd"
|
||||||
// #define REACT_SERVICE "pipedal_react"
|
#define JACK_SERVICE "jack"
|
||||||
|
|
||||||
|
|
||||||
std::filesystem::path GetServiceFileName(const std::string &serviceName)
|
std::filesystem::path GetServiceFileName(const std::string &serviceName)
|
||||||
{
|
{
|
||||||
@@ -113,6 +117,14 @@ void StopService()
|
|||||||
cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service.";
|
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()
|
void StartService()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -132,8 +144,98 @@ void RestartService()
|
|||||||
StartService();
|
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)
|
void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
InstallJackService();
|
||||||
auto endpos = endpointAddress.find_last_of(':');
|
auto endpos = endpointAddress.find_last_of(':');
|
||||||
if (endpos == string::npos)
|
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.");
|
throw PiPedalException("Failed to create service group.");
|
||||||
}
|
}
|
||||||
|
if (!userExists(SERVICE_ACCOUNT_NAME))
|
||||||
|
{
|
||||||
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
|
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
// throw PiPedalException("Failed to create service account.");
|
// throw PiPedalException("Failed to create service account.");
|
||||||
}
|
}
|
||||||
// lock account for login.
|
// lock account for login.
|
||||||
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
|
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
// Add to audio groups.
|
// Add to audio groups.
|
||||||
SysExec(USERMOD_BIN " -a -G jack " SERVICE_ACCOUNT_NAME);
|
SysExec(USERMOD_BIN " -a -G jack " SERVICE_ACCOUNT_NAME);
|
||||||
@@ -278,7 +382,8 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
|
|||||||
cout << "Complete" << endl;
|
cout << "Complete" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SudoExec(char**argv) {
|
int SudoExec(char **argv)
|
||||||
|
{
|
||||||
int pbPid;
|
int pbPid;
|
||||||
int returnValue = 0;
|
int returnValue = 0;
|
||||||
|
|
||||||
@@ -295,7 +400,6 @@ int SudoExec(char**argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
CommandLineParser parser;
|
CommandLineParser parser;
|
||||||
@@ -328,7 +432,7 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
parser.Parse(argc, (const 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)
|
if (actionCount > 1)
|
||||||
{
|
{
|
||||||
throw PiPedalException("Please provide only one action.");
|
throw PiPedalException("Please provide only one action.");
|
||||||
@@ -364,7 +468,8 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if (help || helpError)
|
if (help || helpError)
|
||||||
{
|
{
|
||||||
if (helpError) cout << endl;
|
if (helpError)
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
|
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
|
||||||
<< "Copyright (c) 2021 Robin Davies. All rights reserved." << endl
|
<< "Copyright (c) 2021 Robin Davies. All rights reserved." << endl
|
||||||
@@ -409,7 +514,7 @@ int main(int argc, char **argv)
|
|||||||
<< "Description:" << endl
|
<< "Description:" << endl
|
||||||
<< " pipedalconfig performs post-install configuration of pipedal." << endl
|
<< " pipedalconfig performs post-install configuration of pipedal." << endl
|
||||||
<< endl;
|
<< endl;
|
||||||
exit(helpError? EXIT_FAILURE: EXIT_SUCCESS);
|
exit(helpError ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
if (portOption.size() != 0 && !install)
|
if (portOption.size() != 0 && !install)
|
||||||
{
|
{
|
||||||
@@ -421,16 +526,16 @@ int main(int argc, char **argv)
|
|||||||
if (uid != 0 && !nopkexec)
|
if (uid != 0 && !nopkexec)
|
||||||
{
|
{
|
||||||
// re-execute with PKEXEC in order to prompt form SUDO credentials.
|
// 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"(!)
|
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::filesystem::path path = std::filesystem::absolute(argv[0]);
|
||||||
std::string sPath = path;
|
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)
|
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
|
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();
|
std::filesystem::path prefix = std::filesystem::path(argv[0]).parent_path().parent_path();
|
||||||
prefixOptionArg = "-prefix";
|
prefixOptionArg = "-prefix";
|
||||||
args.push_back((char*)(prefixOptionArg.c_str()));
|
args.push_back((char *)(prefixOptionArg.c_str()));
|
||||||
prefixArg = prefix;
|
prefixArg = prefix;
|
||||||
args.push_back((char*)prefixArg.c_str());
|
args.push_back((char *)prefixArg.c_str());
|
||||||
}
|
}
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
char**rawArgv = &args[0];
|
char **rawArgv = &args[0];
|
||||||
return SudoExec(rawArgv);
|
return SudoExec(rawArgv);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -488,6 +589,7 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
else if (uninstall)
|
else if (uninstall)
|
||||||
{
|
{
|
||||||
|
Uninstall();
|
||||||
}
|
}
|
||||||
else if (stop)
|
else if (stop)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace pipedal
|
|||||||
size_t GetBlockLength() const { return blockLength_; }
|
size_t GetBlockLength() const { return blockLength_; }
|
||||||
size_t GetMidiBufferSize() const { return midiBufferSize_;}
|
size_t GetMidiBufferSize() const { return midiBufferSize_;}
|
||||||
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
|
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> &GetInputAudioPorts() const { return inputAudioPorts_; }
|
||||||
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
|
||||||
|
|||||||
+10
-1
@@ -68,6 +68,7 @@ namespace pipedal
|
|||||||
class JackHostImpl : public JackHost
|
class JackHostImpl : public JackHost
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::recursive_mutex mutex;
|
std::recursive_mutex mutex;
|
||||||
int64_t overrunGracePeriodSamples = 0;
|
int64_t overrunGracePeriodSamples = 0;
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ private:
|
|||||||
auto port = outputPorts[i];
|
auto port = outputPorts[i];
|
||||||
if (port != nullptr)
|
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)
|
for (size_t i = 0; i < midiInputPorts.size(); ++i)
|
||||||
@@ -906,6 +907,7 @@ public:
|
|||||||
|
|
||||||
virtual void Open(const JackChannelSelection &channelSelection)
|
virtual void Open(const JackChannelSelection &channelSelection)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
|
|
||||||
this->currentSample = 0;
|
this->currentSample = 0;
|
||||||
@@ -1262,11 +1264,18 @@ private:
|
|||||||
{
|
{
|
||||||
this_->restarting = true;
|
this_->restarting = true;
|
||||||
this_->Close();
|
this_->Close();
|
||||||
|
try {
|
||||||
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
|
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
|
||||||
this_->Open(this_->channelSelection);
|
this_->Open(this_->channelSelection);
|
||||||
this_->restarting = false;
|
this_->restarting = false;
|
||||||
onComplete(true, "");
|
onComplete(true, "");
|
||||||
isComplete = true;
|
isComplete = true;
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
onComplete(false,e.what());
|
||||||
|
this_->restarting = false;
|
||||||
|
isComplete = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static void ThreadProc_(RestartThread *this_)
|
static void ThreadProc_(RestartThread *this_)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -27,6 +27,7 @@
|
|||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
#include "JackServerSettings.hpp"
|
#include "JackServerSettings.hpp"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include "PiPedalAlsa.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
@@ -118,7 +119,6 @@ public:
|
|||||||
class IHost;
|
class IHost;
|
||||||
|
|
||||||
class JackHost {
|
class JackHost {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
JackHost() { }
|
JackHost() { }
|
||||||
public:
|
public:
|
||||||
|
|||||||
+107
-76
@@ -23,6 +23,9 @@
|
|||||||
#include "JackServerSettings.hpp"
|
#include "JackServerSettings.hpp"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include "unistd.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#define DRC_FILENAME "/etc/jackdrc"
|
#define DRC_FILENAME "/etc/jackdrc"
|
||||||
|
|
||||||
@@ -55,24 +58,46 @@ static std::vector<std::string> SplitArgs(const char *szBuff)
|
|||||||
return result;
|
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)
|
for (size_t i = 1; i < args.size(); ++i)
|
||||||
{
|
{
|
||||||
if (args[i] == shortOption || args[i] == longOption)
|
auto pos = args[i].rfind(longOption,0);
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
if (args[i].length() == longOption.length())
|
||||||
{
|
{
|
||||||
pos = i;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pos == -1 || pos == args.size() - 1)
|
return strVal;
|
||||||
{
|
}
|
||||||
throw PiPedalException("Can't read Jack configuration.");
|
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
|
try
|
||||||
{
|
{
|
||||||
unsigned long value = std::stoul(args[pos + 1]);
|
unsigned long value = std::stoul(strVal);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
catch (const std::exception &)
|
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.");
|
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()
|
void JackServerSettings::ReadJackConfiguration()
|
||||||
{
|
{
|
||||||
this->valid_ = false;
|
this->valid_ = false;
|
||||||
|
|
||||||
char firstLine[1024];
|
std::string lastLine;
|
||||||
|
|
||||||
{
|
{
|
||||||
ifstream input(DRC_FILENAME);
|
ifstream input(DRC_FILENAME);
|
||||||
|
|
||||||
@@ -120,22 +123,32 @@ void JackServerSettings::ReadJackConfiguration()
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (input.eof())
|
std::string line;
|
||||||
{
|
std::getline(input,line);
|
||||||
Lv2Log::error("Premature end of file in " DRC_FILENAME);
|
if (line.length() != 0) {
|
||||||
return;
|
lastLine = line;
|
||||||
}
|
}
|
||||||
input.getline(firstLine, sizeof(firstLine));
|
if (input.eof()) {
|
||||||
if (firstLine[0] != '#')
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try {
|
}
|
||||||
std::vector < std::string> argv = SplitArgs(firstLine);
|
try
|
||||||
this->bufferSize_ = GetJackArg(argv, "-p", "-period");
|
{
|
||||||
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "-nperiods");
|
std::vector<std::string> argv = SplitArgs(lastLine.c_str());
|
||||||
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "-rate");
|
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;
|
valid_ = true;
|
||||||
} catch (std::exception &)
|
}
|
||||||
|
catch (std::exception &)
|
||||||
{
|
{
|
||||||
Lv2Log::error("Can't parse " DRC_FILENAME);
|
Lv2Log::error("Can't parse " DRC_FILENAME);
|
||||||
}
|
}
|
||||||
@@ -147,10 +160,10 @@ void JackServerSettings::Write()
|
|||||||
this->valid_ = false;
|
this->valid_ = false;
|
||||||
|
|
||||||
std::vector<std::string> precedingLines;
|
std::vector<std::string> precedingLines;
|
||||||
std::vector < std::string> argv;
|
std::vector<std::string> argv;
|
||||||
|
std::string lastLine;
|
||||||
|
|
||||||
{
|
if (std::filesystem::exists(DRC_FILENAME)) {
|
||||||
char firstLine[1024];
|
|
||||||
ifstream input(DRC_FILENAME);
|
ifstream input(DRC_FILENAME);
|
||||||
|
|
||||||
if (!input.is_open())
|
if (!input.is_open())
|
||||||
@@ -164,46 +177,64 @@ void JackServerSettings::Write()
|
|||||||
if (input.eof())
|
if (input.eof())
|
||||||
{
|
{
|
||||||
Lv2Log::error("Premature end of file in " DRC_FILENAME);
|
Lv2Log::error("Premature end of file in " DRC_FILENAME);
|
||||||
return;
|
|
||||||
}
|
|
||||||
input.getline(firstLine, sizeof(firstLine));
|
|
||||||
if (firstLine[0] != '#')
|
|
||||||
break;
|
break;
|
||||||
precedingLines.push_back(firstLine);
|
|
||||||
}
|
}
|
||||||
|
std::getline(input,lastLine);
|
||||||
// set new values for arguments.
|
precedingLines.push_back(lastLine);
|
||||||
argv = SplitArgs(firstLine);
|
if (input.eof())
|
||||||
try {
|
|
||||||
SetJackArg(argv, "-p", "-period", this->bufferSize_);
|
|
||||||
SetJackArg(argv, "-n", "-nperiods", this->numberOfBuffers_);
|
|
||||||
SetJackArg(argv, "-r", "-rate",this->sampleRate_);
|
|
||||||
} catch (std::exception &)
|
|
||||||
{
|
{
|
||||||
Lv2Log::error("Can't parse " DRC_FILENAME);
|
break;
|
||||||
return;
|
}
|
||||||
|
}
|
||||||
|
// erase blank lines at the end.
|
||||||
|
while (precedingLines.size() != 0 && precedingLines[precedingLines.size()-1] == "")
|
||||||
|
{
|
||||||
|
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.
|
// write to the output.
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
ofstream output(DRC_FILENAME);
|
ofstream output(DRC_FILENAME);
|
||||||
if (!output.is_open())
|
if (!output.is_open())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't write " DRC_FILENAME);
|
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;
|
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) {
|
throw PiPedalException("Failed to start jack audio service.");
|
||||||
output << " ";
|
|
||||||
}
|
}
|
||||||
output << argv[i];
|
|
||||||
}
|
}
|
||||||
output << endl;
|
catch (const std::exception &e)
|
||||||
} catch (const std::exception &e) {
|
{
|
||||||
stringstream s;
|
stringstream s;
|
||||||
s << "jack - " << e.what();
|
s << "jack - " << e.what();
|
||||||
Lv2Log::error(s.str());
|
Lv2Log::error(s.str());
|
||||||
@@ -211,10 +242,10 @@ void JackServerSettings::Write()
|
|||||||
}
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(JackServerSettings)
|
JSON_MAP_BEGIN(JackServerSettings)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings,valid)
|
JSON_MAP_REFERENCE(JackServerSettings, valid)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings,rebootRequired)
|
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings,sampleRate)
|
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings,bufferSize)
|
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings,numberOfBuffers)
|
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
|
||||||
|
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace pipedal {
|
|||||||
class JackServerSettings {
|
class JackServerSettings {
|
||||||
bool valid_ = false;
|
bool valid_ = false;
|
||||||
bool rebootRequired_ = false;
|
bool rebootRequired_ = false;
|
||||||
|
std::string alsaDevice_;
|
||||||
uint64_t sampleRate_ = 0;
|
uint64_t sampleRate_ = 0;
|
||||||
uint32_t bufferSize_ = 0;
|
uint32_t bufferSize_ = 0;
|
||||||
uint32_t numberOfBuffers_ = 0;
|
uint32_t numberOfBuffers_ = 0;
|
||||||
@@ -36,6 +37,7 @@ namespace pipedal {
|
|||||||
uint64_t GetSampleRate() const { return sampleRate_; }
|
uint64_t GetSampleRate() const { return sampleRate_; }
|
||||||
uint32_t GetBufferSize() const { return bufferSize_; }
|
uint32_t GetBufferSize() const { return bufferSize_; }
|
||||||
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
|
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
|
||||||
|
const std::string&GetAlsaDevice() const { return alsaDevice_; }
|
||||||
|
|
||||||
void ReadJackConfiguration();
|
void ReadJackConfiguration();
|
||||||
|
|
||||||
|
|||||||
+20
-2
@@ -22,6 +22,7 @@
|
|||||||
#include "Locale.hpp"
|
#include "Locale.hpp"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
|
||||||
|
|
||||||
// Must be UNICODE. Should reflect system locale. (.e.g en-US.UTF8, de-de.UTF8)
|
// 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");
|
result = getenv("LC_ALL");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
result = "en_US.UTF-8";
|
result = "en_US.UTF-8";
|
||||||
}
|
}
|
||||||
|
std::stringstream s;
|
||||||
|
s << "Locale: " << result;
|
||||||
|
Lv2Log::error(s.str());
|
||||||
return result;
|
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 {
|
class Locale {
|
||||||
public:
|
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> &left,
|
||||||
const std::shared_ptr<Lv2PluginInfo> &right)
|
const std::shared_ptr<Lv2PluginInfo> &right)
|
||||||
{
|
{
|
||||||
const char *pb1 = left->name().c_str();
|
const char *pb1 = left->name().c_str();
|
||||||
const char *pb2 = right->name().c_str();
|
const char *pb2 = right->name().c_str();
|
||||||
return Locale::collation.compare(
|
return collation.compare(
|
||||||
pb1, pb1 + left->name().size(),
|
pb1, pb1 + left->name().size(),
|
||||||
pb2, pb2 + right->name().size()) < 0;
|
pb2, pb2 + right->name().size()) < 0;
|
||||||
};
|
};
|
||||||
@@ -1005,9 +1006,11 @@ std::vector<Lv2PluginPreset> Lv2Host::GetPluginPresets(const std::string &plugin
|
|||||||
}
|
}
|
||||||
lilv_nodes_free(presets);
|
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_.c_str()+left.name_.size(),
|
left.name_.c_str()+left.name_.size(),
|
||||||
right.name_.c_str(),
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+26
-5
@@ -87,6 +87,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
|||||||
|
|
||||||
this->jackServerSettings.ReadJackConfiguration();
|
this->jackServerSettings.ReadJackConfiguration();
|
||||||
|
|
||||||
|
alsaDevices.PreLoadJackDevice(this->jackServerSettings.GetAlsaDevice());
|
||||||
|
|
||||||
storage.SetDataRoot(configuration.GetLocalStoragePath().c_str());
|
storage.SetDataRoot(configuration.GetLocalStoragePath().c_str());
|
||||||
storage.Initialize();
|
storage.Initialize();
|
||||||
|
|
||||||
@@ -332,7 +334,7 @@ PedalBoard PiPedalModel::getPreset(int64_t instanceId)
|
|||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
return this->storage.GetPreset(instanceId);
|
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);
|
std::lock_guard guard(mutex);
|
||||||
this->storage.GetBankFile(instanceId, pResult);
|
this->storage.GetBankFile(instanceId, pResult);
|
||||||
@@ -855,13 +857,19 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
{
|
{
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
|
|
||||||
this->jackServerSettings = jackServerSettings;
|
|
||||||
|
|
||||||
if (!ShutdownClient::CanUseShutdownClient())
|
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)
|
// 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()];
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
for (size_t i = 0; i < subscribers.size(); ++i)
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
@@ -879,7 +887,8 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
{
|
{
|
||||||
this->jackConfiguration.SetIsRestarting(true);
|
this->jackConfiguration.SetIsRestarting(true);
|
||||||
fireJackConfigurationChanged(this->jackConfiguration);
|
fireJackConfigurationChanged(this->jackConfiguration);
|
||||||
this->jackHost->UpdateServerConfiguration(jackServerSettings,
|
this->jackHost->UpdateServerConfiguration(
|
||||||
|
jackServerSettings,
|
||||||
[this](bool success, const std::string &errorMessage)
|
[this](bool success, const std::string &errorMessage)
|
||||||
{
|
{
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
@@ -891,6 +900,13 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
}
|
}
|
||||||
// Update jack server status.
|
// Update jack server status.
|
||||||
this->jackConfiguration.SetIsRestarting(false);
|
this->jackConfiguration.SetIsRestarting(false);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
this->jackConfiguration.SetErrorStatus(errorMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->jackConfiguration.SetErrorStatus("");
|
||||||
fireJackConfigurationChanged(this->jackConfiguration);
|
fireJackConfigurationChanged(this->jackConfiguration);
|
||||||
|
|
||||||
// restart the pedalboard on a new instance.
|
// restart the pedalboard on a new instance.
|
||||||
@@ -900,6 +916,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||||
updateRealtimeVuSubscriptions();
|
updateRealtimeVuSubscriptions();
|
||||||
updateRealtimeMonitorPortSubscriptions();
|
updateRealtimeMonitorPortSubscriptions();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1040,3 +1057,7 @@ void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHand
|
|||||||
jackHost->SetListenForMidiEvent(false);
|
jackHost->SetListenForMidiEvent(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
|
||||||
|
{
|
||||||
|
return this->alsaDevices.GetAlsaDevices();
|
||||||
|
}
|
||||||
@@ -60,6 +60,8 @@ public:
|
|||||||
|
|
||||||
class PiPedalModel: private IJackHostCallbacks {
|
class PiPedalModel: private IJackHostCallbacks {
|
||||||
private:
|
private:
|
||||||
|
PiPedalAlsaDevices alsaDevices;
|
||||||
|
|
||||||
|
|
||||||
class MidiListener {
|
class MidiListener {
|
||||||
public:
|
public:
|
||||||
@@ -202,6 +204,7 @@ public:
|
|||||||
|
|
||||||
void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||||
void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled);
|
void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled);
|
||||||
|
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||||
|
|
||||||
};
|
};
|
||||||
} // namespace pipedal.
|
} // namespace pipedal.
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "WifiConfigSettings.hpp"
|
#include "WifiConfigSettings.hpp"
|
||||||
#include "WifiChannels.hpp"
|
#include "WifiChannels.hpp"
|
||||||
#include "SysExec.hpp"
|
#include "SysExec.hpp"
|
||||||
|
#include "PiPedalAlsa.hpp"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
@@ -689,6 +690,12 @@ public:
|
|||||||
{
|
{
|
||||||
JackHostStatus status = model.getJackStatus();
|
JackHostStatus status = model.getJackStatus();
|
||||||
this->Reply(replyTo,"getJackStatus",status);
|
this->Reply(replyTo,"getJackStatus",status);
|
||||||
|
} else if (message == "getAlsaDevices")
|
||||||
|
{
|
||||||
|
std::vector<AlsaDeviceInfo> devices = model.GetAlsaDevices();
|
||||||
|
this->Reply(replyTo,"getAlsaDevices",devices);
|
||||||
|
|
||||||
|
|
||||||
} else if (message == "getWifiChannels")
|
} else if (message == "getWifiChannels")
|
||||||
{
|
{
|
||||||
std::string country;
|
std::string country;
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ bool ShutdownClient::WriteMessage(const char*message) {
|
|||||||
while (!eolFound)
|
while (!eolFound)
|
||||||
{
|
{
|
||||||
ssize_t nRead = read(sock,pWrite,available);
|
ssize_t nRead = read(sock,pWrite,available);
|
||||||
if (nRead == 0) {
|
if (nRead == -1)
|
||||||
|
{
|
||||||
*pWrite = 0;
|
*pWrite = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -156,12 +157,11 @@ bool ShutdownClient::WriteMessage(const char*message) {
|
|||||||
bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackServerSettings)
|
bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackServerSettings)
|
||||||
{
|
{
|
||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
s << "setJackConfiguration "
|
s << "setJackConfiguration ";
|
||||||
<< jackServerSettings.GetSampleRate()
|
|
||||||
<< " " << jackServerSettings.GetBufferSize()
|
|
||||||
<< " " << jackServerSettings.GetNumberOfBuffers()
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
|
json_writer writer(s,true);
|
||||||
|
writer.write(jackServerSettings);
|
||||||
|
s << std::endl;
|
||||||
return WriteMessage(s.str().c_str());
|
return WriteMessage(s.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-23
@@ -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;
|
bool success = true;
|
||||||
JackServerSettings serverSettings(sampleRate,bufferSize,numberOfBuffers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
serverSettings.Write();
|
serverSettings.Write();
|
||||||
} catch (const std::exception &e) {
|
return true;
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -246,23 +231,27 @@ private:
|
|||||||
{
|
{
|
||||||
auto remainder = s.substr(strlen("setJackConfiguration "));
|
auto remainder = s.substr(strlen("setJackConfiguration "));
|
||||||
|
|
||||||
|
// xxx delete me
|
||||||
|
Lv2Log::error("setJackConfiguration: " + remainder);
|
||||||
|
|
||||||
std::stringstream input(remainder);
|
std::stringstream input(remainder);
|
||||||
uint32_t sampleRate;
|
JackServerSettings serverSettings;
|
||||||
uint32_t bufferSize;
|
json_reader reader(input);
|
||||||
uint32_t numberOfBuffers;
|
|
||||||
input >> sampleRate >> bufferSize >> numberOfBuffers;
|
reader.read(&serverSettings);
|
||||||
|
|
||||||
if (input.fail())
|
if (input.fail())
|
||||||
{
|
{
|
||||||
result = -1;
|
result = -1;
|
||||||
} else {
|
} else {
|
||||||
result = setJackConfiguration(sampleRate,bufferSize,numberOfBuffers) ? 0: -1;
|
result = setJackConfiguration(serverSettings) ? 0: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e)
|
} catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
std::stringstream t;
|
std::stringstream t;
|
||||||
t << "-2 " << e.what();
|
t << "-2 " << e.what() << "\n";
|
||||||
this->response = t.str();
|
this->response = t.str();
|
||||||
this->writePosition = 0;
|
this->writePosition = 0;
|
||||||
WriteSome();
|
WriteSome();
|
||||||
|
|||||||
@@ -418,6 +418,8 @@ uint16_t g_ShutdownPort = 0;
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
sem_init(&signalSemaphore, 0, 0);
|
sem_init(&signalSemaphore, 0, 0);
|
||||||
|
|
||||||
signal(SIGINT, sig_handler);
|
signal(SIGINT, sig_handler);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ After=network.target
|
|||||||
BindsTo=jack.service
|
BindsTo=jack.service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
LimitMEMLOCK=infinity
|
LimitMEMLOCK=infinity
|
||||||
LimitRTPRIO=95
|
LimitRTPRIO=95
|
||||||
@@ -12,11 +13,11 @@ ExecStart=${COMMAND}
|
|||||||
User=pipedal_d
|
User=pipedal_d
|
||||||
Group=pipedal_d
|
Group=pipedal_d
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=15
|
RestartSec=25
|
||||||
TimeoutStopSec=10
|
TimeoutStopSec=10
|
||||||
WorkingDirectory=/var/pipedal
|
WorkingDirectory=/var/pipedal
|
||||||
Environment=JACK_PROMISCUOUS_SERVER=jack
|
Environment=JACK_PROMISCUOUS_SERVER=
|
||||||
Environment=JACK_START_SERVER=1
|
Environment=JACK_STJACK_NO_START_SERVER=
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
[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