From 1ca09738337fd3254a97125875713f441a7b7b72 Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:51:33 -0700 Subject: [PATCH] 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 --- docs/Configuring.md | 4 + src/AlsaDriver.cpp | 6 +- vite/src/pipedal/JackServerSettingsDialog.tsx | 200 +++++++++--------- 3 files changed, 107 insertions(+), 103 deletions(-) diff --git a/docs/Configuring.md b/docs/Configuring.md index 33bbfed..7e6656b 100644 --- a/docs/Configuring.md +++ b/docs/Configuring.md @@ -8,6 +8,10 @@ icon_float: left 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 %} 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. diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 8870078..5cd9152 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -1955,8 +1955,8 @@ namespace pipedal } if (err < 0) { - throw PiPedalStateException(SS(alsaDeviceName << " playback device not found. " - << "(" << snd_strerror(err) << ")")); + throw PiPedalStateException(SS(outDev << " playback device not found. " + << "(" << snd_strerror(err) << ")")); } for (int retry = 0; retry < 15; ++retry) @@ -1970,7 +1970,7 @@ namespace pipedal break; } 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) { diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index 2edcaf2..4bd458d 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -114,6 +114,22 @@ function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInf 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[] { if (!alsaDeviceInfo ) @@ -131,23 +147,31 @@ function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[] return result; } function getBestBuffers( - alsaDeviceInfo: AlsaDeviceInfo | undefined, - bufferSize: number, + inDevice: AlsaDeviceInfo | undefined, + outDevice: AlsaDeviceInfo | undefined, + bufferSize: number, numberOfBuffers: number, bufferSizeChanging: boolean = false ) : BufferSetting { - if (!alsaDeviceInfo) + if (!inDevice && !outDevice) { 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. // 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); if (ix !== -1) { - if (bufferSize*numberOfBuffers <= alsaDeviceInfo.maxBufferSize - && bufferSize*numberOfBuffers >= alsaDeviceInfo.minBufferSize) + if (bufferSize*numberOfBuffers <= maxSize + && bufferSize*numberOfBuffers >= minSize) { return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers}; } @@ -174,15 +198,15 @@ function getBestBuffers( // otherwise select a sensible starting value. // favored default: 64x3. - if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize) + if (64*3 >= minSize && 64*3 <= maxSize) { return { bufferSize: 64, numberOfBuffers: 3}; } // if that isn't possible then minBufferSize/2 x 4. - bufferSize = alsaDeviceInfo.minBufferSize/2; + bufferSize = minSize/2; numberOfBuffers = 4; // otherwise, minBufferSize/2 x 2. - if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize) + if (bufferSize*numberOfBuffers > maxSize) { numberOfBuffers = 2; } @@ -194,29 +218,21 @@ function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaD { if (!jackServerSettings.valid) return false; if (!alsaDevices) return false; - let alsaDevice: AlsaDeviceInfo | undefined = undefined; - for (let i = 0; i < alsaDevices.length; ++i) - { - if (alsaDevices[i].id === jackServerSettings.alsaDevice) - { - alsaDevice = alsaDevices[i]; - } - } - if (!alsaDevice) - { - return false; - } - let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers; - if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize) - { - return false; - } - let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice); - let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers); - if (ix === -1) - { - return false; - } + const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice); + const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice); + if (!inDevice || !outDevice) return false; + + const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers; + const minSize = Math.max(inDevice.minBufferSize, outDevice.minBufferSize); + const maxSize = Math.min(inDevice.maxBufferSize, outDevice.maxBufferSize); + if (deviceBufferSize < minSize || deviceBufferSize > maxSize) return false; + + const validBufferCounts = getValidBufferCountsMultiple(jackServerSettings.bufferSize, inDevice, outDevice); + if (validBufferCounts.indexOf(jackServerSettings.numberOfBuffers) === -1) return false; + + const sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates); + if (sampleRates.indexOf(jackServerSettings.sampleRate) === -1) return false; + return true; } @@ -264,33 +280,36 @@ const JackServerSettingsDialog = withStyles( applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) { let result = jackServerSettings.clone(); - if (!alsaDevices) { - return result; - } - result.valid = false; - if (alsaDevices.length === 0) { - result.alsaDevice = INVALID_DEVICE_ID; + if (!alsaDevices || alsaDevices.length === 0) { + result.valid = false; + result.alsaInputDevice = INVALID_DEVICE_ID; + result.alsaOutputDevice = 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; - } + let inDevice = alsaDevices.find(d => d.id === result.alsaInputDevice); + let outDevice = alsaDevices.find(d => d.id === result.alsaOutputDevice); + if (!inDevice) { + inDevice = alsaDevices[0]; + result.alsaInputDevice = inDevice.id; } - if (!selectedDevice) { - selectedDevice = alsaDevices[0]; - // if nothing selected yet, pick first for both: - if (!result.alsaInputDevice ) result.alsaInputDevice = selectedDevice.id; - if (!result.alsaOutputDevice) result.alsaOutputDevice = selectedDevice.id; - + if (!outDevice) { + outDevice = alsaDevices[0]; + result.alsaOutputDevice = outDevice.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.numberOfBuffers = bestBuffers.numberOfBuffers; @@ -323,37 +342,22 @@ const JackServerSettingsDialog = withStyles( this.mounted = false; } - getSelectedDevice(deviceId: string): AlsaDeviceInfo | null { - if (!this.state.alsaDevices) return null; + getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined { + if (!this.state.alsaDevices) return undefined; for (let i = 0; i < this.state.alsaDevices.length; ++i) { if (this.state.alsaDevices[i].id === deviceId) { 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) { let rate = e.target.value as number; - console.log("New rate: " + rate); - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); settings.sampleRate = rate; @@ -366,11 +370,12 @@ const JackServerSettingsDialog = withStyles( handleSizeChanged(e: any) { let size = e.target.value as number; - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); 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.numberOfBuffers = bestBufferSetting.numberOfBuffers; @@ -383,8 +388,9 @@ const JackServerSettingsDialog = withStyles( handleNumberOfBuffersChanged(e: any) { let bufferCount = e.target.value as number; - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); settings.numberOfBuffers = bufferCount; @@ -428,21 +434,17 @@ const JackServerSettingsDialog = withStyles( const handleClose = () => { onClose(); }; - let waitingForDevices = !this.state.alsaDevices - let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0; - let selectedDevice: AlsaDeviceInfo | undefined = undefined; - if (this.state.alsaDevices) { - for (let device of this.state.alsaDevices) { - if (device.id === this.state.jackServerSettings.alsaDevice) { - selectedDevice = device; - break; - } - } - } - let bufferSizes: number[] = getValidBufferSizes(selectedDevice); - let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice); - let bufferSizeDisabled = !selectedDevice; - let bufferCountDisabled = !selectedDevice; + + let selectedInputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let selectedOutputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + + let bufferSizes: number[] = getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice); + let bufferCounts = getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice); + let bufferSizeDisabled = !(selectedInputDevice || selectedOutputDevice); + let bufferCountDisabled = !(selectedInputDevice || selectedOutputDevice); + let sampleRates = selectedInputDevice && selectedOutputDevice ? + intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates) + : (selectedInputDevice ? selectedInputDevice.sampleRates : (selectedOutputDevice ? selectedOutputDevice.sampleRates : [])); return ( this.handleRateChanged(e)} value={this.state.jackServerSettings.sampleRate} - disabled={!selectedDevice} + disabled={sampleRates.length === 0} inputProps={{ id: 'jsd_sampleRate', style: { @@ -496,11 +498,9 @@ const JackServerSettingsDialog = withStyles( } }} > - {selectedDevice && - selectedDevice.sampleRates.map((sr) => { - return ( {sr} ); - }) - } + {sampleRates.map((sr) => { + return ({sr} ); + })}