Fixes to Changes.
Added helper functions to combine and intersect ALSA device capabilities, ensuring validation works across separate input and output devices Updated buffer handling logic to use both selected devices and compute min/max sizes appropriately Revised validation to check both ALSA input and output devices when enabling the OK button Simplified rendering logic to intersect sample rates and buffer options for the chosen devices AlsaDriver single device err calls fixed. Added guidance on using USB audio interfaces or Raspberry Pi audio HATs and choosing separate ALSA input and output devices during setup Clarified that the Audio Device Settings dialog allows independent configuration of input and output devices, with updated instructions for selecting audio channels
This commit is contained in:
@@ -8,6 +8,10 @@ icon_float: left
|
|||||||
|
|
||||||
Before using PiPedal, you will need to configure settings for the audio device that PiPedal will use.
|
Before using PiPedal, you will need to configure settings for the audio device that PiPedal will use.
|
||||||
|
|
||||||
|
PiPedal works with either external USB audio interfaces or Raspberry Pi audio HATs. You can now
|
||||||
|
select a distinct ALSA input device and output device, so recording and playback can use different
|
||||||
|
hardware if desired.
|
||||||
|
|
||||||
{% include pageIconL.html %}
|
{% include pageIconL.html %}
|
||||||
|
|
||||||
You will also want to configure PiPedal to provide a Wi-Fi auto-hotspot so that you can connect to using your using your Android phone or tablet. It's fine to use your home Wi-Fi network to connect to PiPedal when you're at home; but don't forget that when you take PiPedal out to a gig, you will need to ensure that PiPedal's Wi-Fi auto-hotspot is enabled before you do.
|
You will also want to configure PiPedal to provide a Wi-Fi auto-hotspot so that you can connect to using your using your Android phone or tablet. It's fine to use your home Wi-Fi network to connect to PiPedal when you're at home; but don't forget that when you take PiPedal out to a gig, you will need to ensure that PiPedal's Wi-Fi auto-hotspot is enabled before you do.
|
||||||
|
|||||||
+3
-3
@@ -1955,8 +1955,8 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
throw PiPedalStateException(SS(alsaDeviceName << " playback device not found. "
|
throw PiPedalStateException(SS(outDev << " playback device not found. "
|
||||||
<< "(" << snd_strerror(err) << ")"));
|
<< "(" << snd_strerror(err) << ")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int retry = 0; retry < 15; ++retry)
|
for (int retry = 0; retry < 15; ++retry)
|
||||||
@@ -1970,7 +1970,7 @@ namespace pipedal
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw PiPedalStateException(SS(alsaDeviceName << " capture device not found."));
|
throw PiPedalStateException(SS(inDev << " capture device not found."));
|
||||||
|
|
||||||
if (snd_pcm_hw_params_malloc(&playbackHwParams) < 0)
|
if (snd_pcm_hw_params_malloc(&playbackHwParams) < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,6 +114,22 @@ function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInf
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function intersectArrays(a: number[], b: number[]): number[] {
|
||||||
|
return a.filter((v) => b.indexOf(v) !== -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidBufferCountsMultiple(bufferSize: number, inDevice?: AlsaDeviceInfo, outDevice?: AlsaDeviceInfo): number[] {
|
||||||
|
let c1 = getValidBufferCounts(bufferSize, inDevice);
|
||||||
|
if (!outDevice) return c1;
|
||||||
|
return intersectArrays(c1, getValidBufferCounts(bufferSize, outDevice));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidBufferSizesMultiple(inDevice?: AlsaDeviceInfo, outDevice?: AlsaDeviceInfo): number[] {
|
||||||
|
let s1 = getValidBufferSizes(inDevice);
|
||||||
|
if (!outDevice) return s1;
|
||||||
|
return intersectArrays(s1, getValidBufferSizes(outDevice));
|
||||||
|
}
|
||||||
function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[]
|
function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[]
|
||||||
{
|
{
|
||||||
if (!alsaDeviceInfo )
|
if (!alsaDeviceInfo )
|
||||||
@@ -131,23 +147,31 @@ function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[]
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function getBestBuffers(
|
function getBestBuffers(
|
||||||
alsaDeviceInfo: AlsaDeviceInfo | undefined,
|
inDevice: AlsaDeviceInfo | undefined,
|
||||||
bufferSize: number,
|
outDevice: AlsaDeviceInfo | undefined,
|
||||||
|
bufferSize: number,
|
||||||
numberOfBuffers: number,
|
numberOfBuffers: number,
|
||||||
bufferSizeChanging: boolean = false
|
bufferSizeChanging: boolean = false
|
||||||
) : BufferSetting {
|
) : BufferSetting {
|
||||||
if (!alsaDeviceInfo)
|
if (!inDevice && !outDevice)
|
||||||
{
|
{
|
||||||
return { bufferSize:bufferSize, numberOfBuffers: numberOfBuffers};
|
return { bufferSize:bufferSize, numberOfBuffers: numberOfBuffers};
|
||||||
}
|
}
|
||||||
|
const device = inDevice || outDevice;
|
||||||
|
if (!device)
|
||||||
|
{
|
||||||
|
return { bufferSize:bufferSize, numberOfBuffers:numberOfBuffers};
|
||||||
|
}
|
||||||
|
|
||||||
// If the numberOfbuffers is fine as is, don't change the number of Buffers.
|
// If the numberOfbuffers is fine as is, don't change the number of Buffers.
|
||||||
// set default values. Otherwise, choose the best buffer count from available buffer counts.
|
// set default values. Otherwise, choose the best buffer count from available buffer counts.
|
||||||
let validBuffercounts = getValidBufferCounts(bufferSize,alsaDeviceInfo);
|
let validBuffercounts = getValidBufferCountsMultiple(bufferSize,inDevice,outDevice);
|
||||||
|
const minSize = Math.max(inDevice?.minBufferSize ?? MIN_BUFFER_SIZE, outDevice?.minBufferSize ?? MIN_BUFFER_SIZE);
|
||||||
|
const maxSize = Math.min(inDevice?.maxBufferSize ?? MAX_BUFFER_SIZE, outDevice?.maxBufferSize ?? MAX_BUFFER_SIZE);
|
||||||
let ix = validBuffercounts.indexOf(numberOfBuffers);
|
let ix = validBuffercounts.indexOf(numberOfBuffers);
|
||||||
if (ix !== -1) {
|
if (ix !== -1) {
|
||||||
if (bufferSize*numberOfBuffers <= alsaDeviceInfo.maxBufferSize
|
if (bufferSize*numberOfBuffers <= maxSize
|
||||||
&& bufferSize*numberOfBuffers >= alsaDeviceInfo.minBufferSize)
|
&& bufferSize*numberOfBuffers >= minSize)
|
||||||
{
|
{
|
||||||
return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
|
return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
|
||||||
}
|
}
|
||||||
@@ -174,15 +198,15 @@ function getBestBuffers(
|
|||||||
// otherwise select a sensible starting value.
|
// otherwise select a sensible starting value.
|
||||||
|
|
||||||
// favored default: 64x3.
|
// favored default: 64x3.
|
||||||
if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize)
|
if (64*3 >= minSize && 64*3 <= maxSize)
|
||||||
{
|
{
|
||||||
return { bufferSize: 64, numberOfBuffers: 3};
|
return { bufferSize: 64, numberOfBuffers: 3};
|
||||||
}
|
}
|
||||||
// if that isn't possible then minBufferSize/2 x 4.
|
// if that isn't possible then minBufferSize/2 x 4.
|
||||||
bufferSize = alsaDeviceInfo.minBufferSize/2;
|
bufferSize = minSize/2;
|
||||||
numberOfBuffers = 4;
|
numberOfBuffers = 4;
|
||||||
// otherwise, minBufferSize/2 x 2.
|
// otherwise, minBufferSize/2 x 2.
|
||||||
if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize)
|
if (bufferSize*numberOfBuffers > maxSize)
|
||||||
{
|
{
|
||||||
numberOfBuffers = 2;
|
numberOfBuffers = 2;
|
||||||
}
|
}
|
||||||
@@ -194,29 +218,21 @@ function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaD
|
|||||||
{
|
{
|
||||||
if (!jackServerSettings.valid) return false;
|
if (!jackServerSettings.valid) return false;
|
||||||
if (!alsaDevices) return false;
|
if (!alsaDevices) return false;
|
||||||
let alsaDevice: AlsaDeviceInfo | undefined = undefined;
|
const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice);
|
||||||
for (let i = 0; i < alsaDevices.length; ++i)
|
const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice);
|
||||||
{
|
if (!inDevice || !outDevice) return false;
|
||||||
if (alsaDevices[i].id === jackServerSettings.alsaDevice)
|
|
||||||
{
|
const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
|
||||||
alsaDevice = alsaDevices[i];
|
const minSize = Math.max(inDevice.minBufferSize, outDevice.minBufferSize);
|
||||||
}
|
const maxSize = Math.min(inDevice.maxBufferSize, outDevice.maxBufferSize);
|
||||||
}
|
if (deviceBufferSize < minSize || deviceBufferSize > maxSize) return false;
|
||||||
if (!alsaDevice)
|
|
||||||
{
|
const validBufferCounts = getValidBufferCountsMultiple(jackServerSettings.bufferSize, inDevice, outDevice);
|
||||||
return false;
|
if (validBufferCounts.indexOf(jackServerSettings.numberOfBuffers) === -1) return false;
|
||||||
}
|
|
||||||
let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
|
const sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
|
||||||
if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize)
|
if (sampleRates.indexOf(jackServerSettings.sampleRate) === -1) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice);
|
|
||||||
let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers);
|
|
||||||
if (ix === -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,33 +280,36 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
|
|
||||||
applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
|
applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
|
||||||
let result = jackServerSettings.clone();
|
let result = jackServerSettings.clone();
|
||||||
if (!alsaDevices) {
|
if (!alsaDevices || alsaDevices.length === 0) {
|
||||||
return result;
|
result.valid = false;
|
||||||
}
|
result.alsaInputDevice = INVALID_DEVICE_ID;
|
||||||
result.valid = false;
|
result.alsaOutputDevice = INVALID_DEVICE_ID;
|
||||||
if (alsaDevices.length === 0) {
|
|
||||||
result.alsaDevice = INVALID_DEVICE_ID;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
let inDevice = alsaDevices.find(d => d.id === result.alsaInputDevice);
|
||||||
for (let i = 0; i < alsaDevices.length; ++i) {
|
let outDevice = alsaDevices.find(d => d.id === result.alsaOutputDevice);
|
||||||
if (alsaDevices[i].id === result.alsaDevice) {
|
if (!inDevice) {
|
||||||
selectedDevice = alsaDevices[i]
|
inDevice = alsaDevices[0];
|
||||||
break;
|
result.alsaInputDevice = inDevice.id;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!selectedDevice) {
|
if (!outDevice) {
|
||||||
selectedDevice = alsaDevices[0];
|
outDevice = alsaDevices[0];
|
||||||
// if nothing selected yet, pick first for both:
|
result.alsaOutputDevice = outDevice.id;
|
||||||
if (!result.alsaInputDevice ) result.alsaInputDevice = selectedDevice.id;
|
|
||||||
if (!result.alsaOutputDevice) result.alsaOutputDevice = selectedDevice.id;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (result.sampleRate === 0) result.sampleRate = 48000;
|
|
||||||
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
if (result.sampleRate === 0) result.sampleRate = 48000;
|
||||||
|
let sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
|
||||||
|
if (sampleRates.length === 0) sampleRates = inDevice.sampleRates;
|
||||||
|
let bestSr = sampleRates[0];
|
||||||
|
let bestErr = 1e36;
|
||||||
|
for (let sr of sampleRates) {
|
||||||
|
let err = (sr - result.sampleRate) * (sr - result.sampleRate);
|
||||||
|
if (err < bestErr) { bestErr = err; bestSr = sr; }
|
||||||
|
}
|
||||||
|
result.sampleRate = bestSr;
|
||||||
|
|
||||||
let bestBuffers = getBestBuffers(selectedDevice,result.bufferSize,result.numberOfBuffers);
|
let bestBuffers = getBestBuffers(inDevice, outDevice, result.bufferSize, result.numberOfBuffers);
|
||||||
result.bufferSize = bestBuffers.bufferSize;
|
result.bufferSize = bestBuffers.bufferSize;
|
||||||
result.numberOfBuffers = bestBuffers.numberOfBuffers;
|
result.numberOfBuffers = bestBuffers.numberOfBuffers;
|
||||||
|
|
||||||
@@ -323,37 +342,22 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
getSelectedDevice(deviceId: string): AlsaDeviceInfo | null {
|
getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined {
|
||||||
if (!this.state.alsaDevices) return null;
|
if (!this.state.alsaDevices) return undefined;
|
||||||
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
|
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
|
||||||
if (this.state.alsaDevices[i].id === deviceId) {
|
if (this.state.alsaDevices[i].id === deviceId) {
|
||||||
return this.state.alsaDevices[i];
|
return this.state.alsaDevices[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = this.applyAlsaDevices(settings,this.state.alsaDevices);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
jackServerSettings: settings,
|
|
||||||
latencyText: getLatencyText(settings),
|
|
||||||
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleRateChanged(e: any) {
|
handleRateChanged(e: any) {
|
||||||
let rate = e.target.value as number;
|
let rate = e.target.value as number;
|
||||||
console.log("New rate: " + rate);
|
let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice);
|
||||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice);
|
||||||
if (!selectedDevice) return;
|
if (!inDev && !outDev) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.sampleRate = rate;
|
settings.sampleRate = rate;
|
||||||
|
|
||||||
@@ -366,11 +370,12 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
handleSizeChanged(e: any) {
|
handleSizeChanged(e: any) {
|
||||||
let size = e.target.value as number;
|
let size = e.target.value as number;
|
||||||
|
|
||||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice);
|
||||||
if (!selectedDevice) return;
|
let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice);
|
||||||
|
if (!inDev && !outDev) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.bufferSize = size;
|
settings.bufferSize = size;
|
||||||
let bestBufferSetting = getBestBuffers(selectedDevice,settings.bufferSize,settings.numberOfBuffers,true);
|
let bestBufferSetting = getBestBuffers(inDev,outDev,settings.bufferSize,settings.numberOfBuffers,true);
|
||||||
settings.bufferSize = bestBufferSetting.bufferSize;
|
settings.bufferSize = bestBufferSetting.bufferSize;
|
||||||
settings.numberOfBuffers = bestBufferSetting.numberOfBuffers;
|
settings.numberOfBuffers = bestBufferSetting.numberOfBuffers;
|
||||||
|
|
||||||
@@ -383,8 +388,9 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
handleNumberOfBuffersChanged(e: any) {
|
handleNumberOfBuffersChanged(e: any) {
|
||||||
let bufferCount = e.target.value as number;
|
let bufferCount = e.target.value as number;
|
||||||
|
|
||||||
let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice);
|
let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice);
|
||||||
if (!selectedDevice) return;
|
let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice);
|
||||||
|
if (!inDev && !outDev) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.numberOfBuffers = bufferCount;
|
settings.numberOfBuffers = bufferCount;
|
||||||
|
|
||||||
@@ -428,21 +434,17 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
let waitingForDevices = !this.state.alsaDevices
|
|
||||||
let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0;
|
let selectedInputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice);
|
||||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
let selectedOutputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice);
|
||||||
if (this.state.alsaDevices) {
|
|
||||||
for (let device of this.state.alsaDevices) {
|
let bufferSizes: number[] = getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice);
|
||||||
if (device.id === this.state.jackServerSettings.alsaDevice) {
|
let bufferCounts = getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice);
|
||||||
selectedDevice = device;
|
let bufferSizeDisabled = !(selectedInputDevice || selectedOutputDevice);
|
||||||
break;
|
let bufferCountDisabled = !(selectedInputDevice || selectedOutputDevice);
|
||||||
}
|
let sampleRates = selectedInputDevice && selectedOutputDevice ?
|
||||||
}
|
intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates)
|
||||||
}
|
: (selectedInputDevice ? selectedInputDevice.sampleRates : (selectedOutputDevice ? selectedOutputDevice.sampleRates : []));
|
||||||
let bufferSizes: number[] = getValidBufferSizes(selectedDevice);
|
|
||||||
let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice);
|
|
||||||
let bufferSizeDisabled = !selectedDevice;
|
|
||||||
let bufferCountDisabled = !selectedDevice;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
||||||
@@ -488,7 +490,7 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
<Select variant="standard"
|
<Select variant="standard"
|
||||||
onChange={(e) => this.handleRateChanged(e)}
|
onChange={(e) => this.handleRateChanged(e)}
|
||||||
value={this.state.jackServerSettings.sampleRate}
|
value={this.state.jackServerSettings.sampleRate}
|
||||||
disabled={!selectedDevice}
|
disabled={sampleRates.length === 0}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
id: 'jsd_sampleRate',
|
id: 'jsd_sampleRate',
|
||||||
style: {
|
style: {
|
||||||
@@ -496,11 +498,9 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedDevice &&
|
{sampleRates.map((sr) => {
|
||||||
selectedDevice.sampleRates.map((sr) => {
|
return (<MenuItem value={sr}>{sr}</MenuItem> );
|
||||||
return ( <MenuItem value={sr}>{sr}</MenuItem> );
|
})}
|
||||||
})
|
|
||||||
}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user