From d8043d20411d6587fcf2b62c4e2640fb36a25983 Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:20:30 -0700 Subject: [PATCH 1/7] Separate IO Initial separation of input and output devices. Assisted by our super friend ChatGpt. --- src/AlsaDriver.cpp | 21 ++--- src/AlsaDriver.hpp | 2 +- src/JackConfiguration.hpp | 4 +- src/JackServerSettings.cpp | 17 +++- src/JackServerSettings.hpp | 29 ++++--- src/WebServer.cpp | 2 +- src/WebServer.hpp | 2 +- vite/src/pipedal/JackServerSettings.tsx | 15 ++-- vite/src/pipedal/JackServerSettingsDialog.tsx | 80 ++++++++++++------- 9 files changed, 109 insertions(+), 63 deletions(-) diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 195cec6..8870078 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -20,7 +20,7 @@ * 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 "util.hpp" @@ -1223,7 +1223,9 @@ namespace pipedal { int err; - alsa_device_name = jackServerSettings.GetAlsaInputDevice(); + std::string inputName = jackServerSettings.GetAlsaInputDevice(); + std::string outputName = jackServerSettings.GetAlsaOutputDevice(); + this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers(); this->bufferSize = jackServerSettings.GetBufferSize(); @@ -1232,7 +1234,7 @@ namespace pipedal try { - err = snd_pcm_open(&playbackHandle, alsa_device_name.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + err = snd_pcm_open(&playbackHandle, outputName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { switch (errno) @@ -1267,7 +1269,7 @@ namespace pipedal snd_pcm_nonblock(playbackHandle, 0); } - err = snd_pcm_open(&captureHandle, alsa_device_name.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (err < 0) { @@ -1934,7 +1936,8 @@ namespace pipedal } }}; - std::string alsaDeviceName = jackServerSettings.GetAlsaInputDevice(); + std::string inDev = jackServerSettings.GetAlsaInputDevice(); + std::string outDev = jackServerSettings.GetAlsaOutputDevice(); bool result = false; try @@ -1942,7 +1945,7 @@ namespace pipedal int err; for (int retry = 0; retry < 2; ++retry) { - err = snd_pcm_open(&playbackHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + err = snd_pcm_open(&playbackHandle, outDev.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) // field report of a device that is present, but won't immediately open. { sleep(1); @@ -1958,7 +1961,7 @@ namespace pipedal for (int retry = 0; retry < 15; ++retry) { - err = snd_pcm_open(&captureHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + err = snd_pcm_open(&captureHandle, inDev.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (err == -EBUSY) { sleep(1); @@ -1981,8 +1984,8 @@ namespace pipedal snd_pcm_hw_params_any(playbackHandle, playbackHwParams); snd_pcm_hw_params_any(captureHandle, captureHwParams); - SetPreferredAlsaFormat(alsaDeviceName, "capture", captureHandle, captureHwParams); - SetPreferredAlsaFormat(alsaDeviceName, "output", playbackHandle, playbackHwParams); + SetPreferredAlsaFormat(inDev, "capture", captureHandle, captureHwParams); + SetPreferredAlsaFormat(outDev, "output", playbackHandle, playbackHwParams); unsigned int sampleRate = jackServerSettings.GetSampleRate(); err = snd_pcm_hw_params_set_rate_near(playbackHandle, playbackHwParams, &sampleRate, 0); diff --git a/src/AlsaDriver.hpp b/src/AlsaDriver.hpp index c579813..daa8037 100644 --- a/src/AlsaDriver.hpp +++ b/src/AlsaDriver.hpp @@ -20,7 +20,7 @@ * 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 diff --git a/src/JackConfiguration.hpp b/src/JackConfiguration.hpp index b76abb0..4b7bd7b 100644 --- a/src/JackConfiguration.hpp +++ b/src/JackConfiguration.hpp @@ -107,7 +107,7 @@ namespace pipedal return outputAudioPorts_; } - // replaced with AlsaSequencerConfiguration + // replaced with AlsaSequencerConfiguration const std::vector& LegacyGetInputMidiDevices() const { return inputMidiDevices_; @@ -125,4 +125,4 @@ namespace pipedal }; -} // namespace. \ No newline at end of file +} // namespace. \ No newline at end of file diff --git a/src/JackServerSettings.cpp b/src/JackServerSettings.cpp index 93c2de1..bfbe8e0 100644 --- a/src/JackServerSettings.cpp +++ b/src/JackServerSettings.cpp @@ -148,7 +148,13 @@ void JackServerSettings::ReadJackDaemonConfiguration() 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"); + // read new dual device flags, fallback on old -d/--device + std::string capDev = GetJackStringArg(argv, "-C", "--capture"); + std::string playDev = GetJackStringArg(argv, "-P", "--playback"); + std::string dev = ""; + try { dev = GetJackStringArg(argv, "-d", "--device"); } catch(...) {} + this->alsaInputDevice_ = capDev.empty() ? dev : capDev; + this->alsaOutputDevice_ = playDev.empty() ? dev : playDev; this->valid_ = true; } catch (std::exception &) @@ -219,11 +225,13 @@ void JackServerSettings::WriteDaemonConfig() { output << line << endl; } - // the style used by qjackctl. :-/ + // the style used by qjackctl. :-/ // Lower to -P70 in order to allow the USB soft-irq to run at higher priority than JACK (it runs at 80). output << "/usr/bin/jackd " << "-R -P70 --silent" - << " -dalsa -d" << this->alsaDevice_ + << " -dalsa" + << " -C" << this->GetAlsaInputDevice() + << " -P" << this->GetAlsaOutputDevice() << " -r" << this->sampleRate_ << " -p" << this->bufferSize_ << " -n" << this->numberOfBuffers_ << " -Xseq" @@ -246,7 +254,8 @@ JSON_MAP_REFERENCE(JackServerSettings, valid) JSON_MAP_REFERENCE(JackServerSettings, isOnboarding) JSON_MAP_REFERENCE(JackServerSettings, rebootRequired) JSON_MAP_REFERENCE(JackServerSettings, isJackAudio) -JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) +JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice) +JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice) JSON_MAP_REFERENCE(JackServerSettings, sampleRate) JSON_MAP_REFERENCE(JackServerSettings, bufferSize) JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers) diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index da1f511..5fd9aad 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -31,18 +31,24 @@ namespace pipedal bool isOnboarding_ = true; bool isJackAudio_ = JACK_HOST ? true : false; bool rebootRequired_ = false; - std::string alsaDevice_; + std::string alsaInputDevice_; + std::string alsaOutputDevice_; uint64_t sampleRate_ = 0; uint32_t bufferSize_ = 64; uint32_t numberOfBuffers_ = 3; public: + void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; } + void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; } JackServerSettings(); - JackServerSettings( + JackServerSettings( const std::string alsaInputDevice, - uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers) + uint64_t sampleRate, + uint32_t bufferSize, + uint32_t numberOfBuffers) : valid_(true), - alsaDevice_(alsaInputDevice), + alsaInputDevice_(alsaInputDevice), + alsaOutputDevice_(alsaInputDevice), // default same sampleRate_(sampleRate), bufferSize_(bufferSize), numberOfBuffers_(numberOfBuffers), @@ -53,16 +59,17 @@ namespace pipedal uint64_t GetSampleRate() const { return sampleRate_; } uint32_t GetBufferSize() const { return bufferSize_; } uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; } - const std::string &GetAlsaInputDevice() const { return alsaDevice_; } + const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; } + const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; } void UseDummyAudioDevice() { this->valid_ = true; if (sampleRate_ == 0) sampleRate_ = 48000; - this->alsaDevice_ = "dummy:channels_2"; + this->alsaInputDevice_ = "__DUMMY_AUDIO__dummy:channels_2"; + this->alsaOutputDevice_ = "__DUMMY_AUDIO__dummy:channels_2"; } bool IsDummyAudioDevice() const { - return - this->alsaDevice_.starts_with("__DUMMY_AUDIO__") - || this->alsaDevice_.starts_with("dummy:"); + return this->alsaInputDevice_.starts_with("__DUMMY_AUDIO__") + || this->alsaOutputDevice_.starts_with("__DUMMY_AUDIO__"); } void ReadJackDaemonConfiguration(); @@ -75,7 +82,7 @@ namespace pipedal // this->rebootRequired_ = true; // this->sampleRate_ = sampleRate; // this->bufferSize_ = bufferSize; - // this->numberOfBuffers_ = numberOfBuffers; + // this->numberOfBuffers_ = numberOfBuffers; // } void WriteDaemonConfig(); // requires root perms. void SetRebootRequired(bool value) @@ -89,7 +96,7 @@ namespace pipedal bool Equals(const JackServerSettings &other) { - return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_; + return this->alsaInputDevice_ == other.alsaInputDevice_ && this->alsaOutputDevice_ == other.alsaOutputDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_; } DECLARE_JSON_MAP(JackServerSettings); diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 4a86c9a..5ea80a2 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -15,7 +15,7 @@ // 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. +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" diff --git a/src/WebServer.hpp b/src/WebServer.hpp index 1d9bd03..6555381 100644 --- a/src/WebServer.hpp +++ b/src/WebServer.hpp @@ -18,7 +18,7 @@ -// forward declaration. +// forward declaration. class websocket_session; diff --git a/vite/src/pipedal/JackServerSettings.tsx b/vite/src/pipedal/JackServerSettings.tsx index 9ece894..1263ad8 100644 --- a/vite/src/pipedal/JackServerSettings.tsx +++ b/vite/src/pipedal/JackServerSettings.tsx @@ -25,7 +25,8 @@ export default class JackServerSettings { this.isOnboarding = input.isOnboarding; this.isJackAudio = input.isJackAudio; this.rebootRequired = input.rebootRequired; - this.alsaDevice = input.alsaDevice?? ""; + this.alsaInputDevice = input.alsaInputDevice ?? ""; + this.alsaOutputDevice = input.alsaOutputDevice ?? ""; this.sampleRate = input.sampleRate; this.bufferSize = input.bufferSize; this.numberOfBuffers = input.numberOfBuffers; @@ -39,7 +40,7 @@ export default class JackServerSettings { // if (numberOfBuffers) { // this.valid = true; // } - // } + // } clone(): JackServerSettings { return new JackServerSettings().deserialize(this); @@ -48,16 +49,18 @@ export default class JackServerSettings { isOnboarding: boolean = true; rebootRequired = false; isJackAudio = false; - alsaDevice: string = ""; + alsaInputDevice: string = ""; + alsaOutputDevice: string = ""; sampleRate = 48000; bufferSize = 64; numberOfBuffers = 3; getSummaryText() { if (this.valid) { - let device = this.alsaDevice; - if (device.startsWith("hw:")) device = device.substring(3); - return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers; + let inDev = this.alsaInputDevice.startsWith("hw:") ? this.alsaInputDevice .substring(3) : this.alsaInputDevice; + let outDev = this.alsaOutputDevice.startsWith("hw:") ? this.alsaOutputDevice.substring(3) : this.alsaOutputDevice; + return "In: "+inDev+" Out: "+outDev+" — Rate "+this.sampleRate+", "+this.bufferSize+"×"+this.numberOfBuffers; + } else { return "Not configured"; } diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index ceff6e9..2edcaf2 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -15,7 +15,7 @@ // 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. +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import { Component } from 'react'; @@ -282,7 +282,9 @@ const JackServerSettingsDialog = withStyles( } if (!selectedDevice) { selectedDevice = alsaDevices[0]; - result.alsaDevice = selectedDevice.id; + // if nothing selected yet, pick first for both: + if (!result.alsaInputDevice ) result.alsaInputDevice = selectedDevice.id; + if (!result.alsaOutputDevice) result.alsaOutputDevice = selectedDevice.id; } if (result.sampleRate === 0) result.sampleRate = 48000; @@ -292,7 +294,7 @@ const JackServerSettingsDialog = withStyles( result.bufferSize = bestBuffers.bufferSize; result.numberOfBuffers = bestBuffers.numberOfBuffers; - result.valid = true; + result.valid = !!result.alsaInputDevice && !!result.alsaOutputDevice; return result; } @@ -399,7 +401,25 @@ const JackServerSettingsDialog = withStyles( this.props.onApply(this.state.jackServerSettings.clone()); } }; - + handleInputDeviceChanged(e: any) { + const d = e.target.value as string; + let s = this.state.jackServerSettings.clone(); + s.alsaInputDevice = d; + this.setState({ + jackServerSettings: s, + okEnabled: isOkEnabled(s, this.state.alsaDevices) + }); + } + + handleOutputDeviceChanged(e: any) { + const d = e.target.value as string; + let s = this.state.jackServerSettings.clone(); + s.alsaOutputDevice = d; + this.setState({ + jackServerSettings: s, + okEnabled: isOkEnabled(s, this.state.alsaDevices) + }); + } render() { const classes = withStyles.getClasses(this.props); @@ -433,31 +453,35 @@ const JackServerSettingsDialog = withStyles( >
+ {/* Audio Input Device */} - Device - - + Input Device + + + + {/* Audio Output Device */} + + Output Device + +
Sample rate From 54926871fc44724e318a47688eaabeabe425be8b Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:44:09 -0700 Subject: [PATCH 2/7] Uri Change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary Ensured that checking for a query key now returns true when the key is found, allowing accurate existence checks Added “&” separators when reconstructing query strings so that URIs are properly formatted with multiple parameters --- src/Uri.cpp | 1 + src/Uri.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Uri.cpp b/src/Uri.cpp index 1219e56..e235b6a 100644 --- a/src/Uri.cpp +++ b/src/Uri.cpp @@ -457,6 +457,7 @@ std::string uri_builder::str() const { s << '?'; for (size_t i = 0; i < queries_.size(); ++i) { + if (i != 0) s << '&'; s << queries_[i].key << "="; HtmlHelper::encode_url_segment(s,queries_[i].value,true); } diff --git a/src/Uri.hpp b/src/Uri.hpp index 52951f8..11ea763 100644 --- a/src/Uri.hpp +++ b/src/Uri.hpp @@ -204,7 +204,7 @@ public: bool has_query(const std::string &key) const { for (size_t i = 0; i < queries_.size(); ++i) { - if (queries_[i].key == key) true; + if (queries_[i].key == key) return true; } return false; 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 3/7] 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} ); + })}
From 7fc2ca7cdf2a17cefc77102e16352d9feb4e9b24 Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:01:57 -0700 Subject: [PATCH 4/7] Typos Corrected typos. --- docs/Configuring.md | 8 ++++---- src/AlsaDriver.cpp | 4 ++-- src/JackConfiguration.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Configuring.md b/docs/Configuring.md index 7e6656b..bf97660 100644 --- a/docs/Configuring.md +++ b/docs/Configuring.md @@ -14,7 +14,7 @@ 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. +You will also want to configure PiPedal to provide a Wi-Fi auto-hotspot so that you can connect to it 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. PiPedal uses LV2 audio plugins. There are literally thousands of freely available high-quality LV2 plugins that are suitable for use as guitar effects. @@ -25,9 +25,9 @@ with PiPedal. ### Installing the PiPedal Remote Android app -If you are using an Android phone or table to connect to PiPedal, you should download and install the [PiPedal Android Client](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) +If you are using an Android phone or tablet to connect to PiPedal, you should download and install the [PiPedal Android Client](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) -The Android PiPedal client provides three feature: +The Android PiPedal client provides three features: 1. It automatically locates running instances of PiPedal on the local Wi-Fi network using dns/Bonjour discovery. So you don't need to keep track of which IP address to connect with. @@ -76,7 +76,7 @@ an audio device. Many external USB audio devices that have two inputs provide th #### Selecting Audio Buffer Sizes -You may have to experiment to find the buffer size, and buffer count that works best for your. The actual round-trip latency and frequency of audio overruns and underruns depends the the operating system system version, the audio hardware being used, and upon CPU use of the audio patches you are using. Selecting larger buffer sizes or larger buffer counts increase the amount of computing power available for your audio effects, and will reduce the number of dropouts caused by audio overruns/underruns (xruns); smaller buffer sizes and buffer counts improve the overall round-trip latency but will increase the likelihood of xruns. +You may have to experiment to find the buffer size and buffer count that works best for you. The actual round-trip latency and frequency of audio overruns and underruns depends on the operating system version, the audio hardware being used, and upon CPU use of the audio patches you are using. Selecting larger buffer sizes or larger buffer counts increases the amount of computing power available for your audio effects and will reduce the number of dropouts caused by audio overruns/underruns (xruns); smaller buffer sizes and buffer counts improve the overall round-trip latency but will increase the likelihood of xruns. Please note that the Raspberry Pi OS is not completely robust with respect to realtime scheduling. Significant display activity, SD-CARD activity or heavy CPU use by programs other than PiPedal may cause audio xruns. For best results, run PiPedal without a display attached, and with no other programs running. Connecting remotely to the PiPedal web interface should not cause problems. diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 5cd9152..f285ac2 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -2005,13 +2005,13 @@ namespace pipedal err = snd_pcm_hw_params_get_channels_max(playbackHwParams, &playbackChannels); if (err < 0) { - throw PiPedalLogicException("No outut channels."); + throw PiPedalLogicException("No output channels."); } unsigned int channelsMin; err = snd_pcm_hw_params_get_channels_min(playbackHwParams, &channelsMin); if (err < 0) { - throw PiPedalLogicException("No outut channels."); + throw PiPedalLogicException("No output channels."); } if (ShouldForceStereoChannels(playbackHandle, playbackHwParams, channelsMin, playbackChannels)) { diff --git a/src/JackConfiguration.hpp b/src/JackConfiguration.hpp index 4b7bd7b..f120b1a 100644 --- a/src/JackConfiguration.hpp +++ b/src/JackConfiguration.hpp @@ -125,4 +125,4 @@ namespace pipedal }; -} // namespace. \ No newline at end of file +} // namespace. \ No newline at end of file From c6aec562b0a19a523ac26329eaa8967c13f85b2a Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Wed, 23 Jul 2025 07:58:23 -0700 Subject: [PATCH 5/7] Update JackServerSettingsDialog.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dialog now disables the device selectors and shows a “Loading…” placeholder until ALSA devices are available, preventing runtime errors during the initial render --- vite/src/pipedal/JackServerSettingsDialog.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index 4bd458d..4fd4246 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -458,31 +458,33 @@ const JackServerSettingsDialog = withStyles( {/* Audio Input Device */} Input Device - this.handleInputDeviceChanged(e)} + disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0} + style={{ width: 220 }} + > + {this.state.alsaDevices?.map(d => ( {d.name} - ))} - + )) || Loading...} + {/* Audio Output Device */} Output Device - +
From fbd925caca67431808a3211b1c17d425c107bdbe Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:35:22 -0700 Subject: [PATCH 6/7] Update JackServerSettingsDialog.tsx Summary Added imports for the new refresh button in the JACK settings dialog, enabling quick reloading of ALSA devices without closing the dialog Inserted an IconButton next to the audio device selectors so users can refresh the list on demand --- vite/src/pipedal/JackServerSettingsDialog.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index 4fd4246..c35a442 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -40,6 +40,8 @@ import DialogContent from '@mui/material/DialogContent'; import MenuItem from '@mui/material/MenuItem'; import Typography from '@mui/material/Typography'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; +import IconButtonEx from './IconButtonEx'; +import RefreshIcon from '@mui/icons-material/Refresh'; import AlsaDeviceInfo from './AlsaDeviceInfo'; @@ -454,7 +456,7 @@ const JackServerSettingsDialog = withStyles( > -
+
{/* Audio Input Device */} Input Device @@ -469,10 +471,10 @@ const JackServerSettingsDialog = withStyles( {d.name} )) || Loading...} - - - {/* Audio Output Device */} - + + + {/* Audio Output Device */} + Output Device - + + + this.requestAlsaInfo()} aria-label="refresh-audio-devices"> + +
Sample rate From 6ac89685830caac6beb8d4863a8c570a7a62d377 Mon Sep 17 00:00:00 2001 From: ExtremesecrecyOne <131911364+ExtremesecrecyOne@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:18:19 -0700 Subject: [PATCH 7/7] Devices with only I or O Fix Added new fields to track ALSA device capabilities, letting the application know whether a device supports capture or playback Updated serialization to include the new flags in the JSON data exchanged with the UI Modified the dummy driver so test devices always indicate capture and playback support Adjusted the TypeScript model and dialog to filter device choices based on the new flags, ensuring only valid options appear in selectors --- src/DummyAudioDriver.cpp | 2 + src/PiPedalAlsa.cpp | 40 +++++++++++-------- src/PiPedalAlsa.hpp | 2 + vite/src/pipedal/AlsaDeviceInfo.tsx | 6 ++- vite/src/pipedal/JackServerSettingsDialog.tsx | 35 ++++++++++------ 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index a03e034..de8a9dd 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -517,6 +517,8 @@ namespace pipedal result.sampleRates_.push_back(48000); result.minBufferSize_ = 16; result.maxBufferSize_ = 1024; + result.supportsCapture_ = true; + result.supportsPlayback_ = true; return result; } diff --git a/src/PiPedalAlsa.cpp b/src/PiPedalAlsa.cpp index af1be7e..b2f7f1a 100644 --- a/src/PiPedalAlsa.cpp +++ b/src/PiPedalAlsa.cpp @@ -97,20 +97,16 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() info.name_ = snd_ctl_card_info_get_name(alsaInfo); info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); - snd_pcm_t *hDevice = nullptr; + snd_pcm_t *captureDevice = nullptr; + snd_pcm_t *playbackDevice = nullptr; + bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0) == 0; + bool playbackOk = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0) == 0; + info.supportsCapture_ = captureOk; + info.supportsPlayback_ = playbackOk; - // 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) + if (captureOk || playbackOk) { + snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; snd_pcm_hw_params_t *params = nullptr; err = snd_pcm_hw_params_malloc(¶ms); if (err == 0) @@ -151,15 +147,25 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() info.minBufferSize_ = (uint32_t)minBufferSize; info.maxBufferSize_ = (uint32_t)maxBufferSize; - cacheDevice(info.name_, info); - result.push_back(info); } } } if (params != nullptr) snd_pcm_hw_params_free(params); - snd_pcm_close(hDevice); - } + if (captureOk) + snd_pcm_close(captureDevice); + if (playbackOk && playbackDevice != captureDevice) + snd_pcm_close(playbackDevice); + if (err == 0) + { + cacheDevice(info.name_, info); + result.push_back(info); + } + else if (getCachedDevice(info.name_, &info)) + { + result.push_back(info); + } + } else { if (getCachedDevice(info.name_, &info)) @@ -480,6 +486,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, longName) JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates) JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize) JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize) +JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture) +JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback) JSON_MAP_END() JSON_MAP_BEGIN(AlsaMidiDeviceInfo) diff --git a/src/PiPedalAlsa.hpp b/src/PiPedalAlsa.hpp index cf602b2..5259837 100644 --- a/src/PiPedalAlsa.hpp +++ b/src/PiPedalAlsa.hpp @@ -31,6 +31,8 @@ namespace pipedal { std::string longName_; std::vector sampleRates_; uint32_t minBufferSize_ = 0,maxBufferSize_ = 0; + bool supportsCapture_ = false; + bool supportsPlayback_ = false; bool isDummyDevice() const { return id_.starts_with("dummy:"); diff --git a/vite/src/pipedal/AlsaDeviceInfo.tsx b/vite/src/pipedal/AlsaDeviceInfo.tsx index 0cd10ff..d1f97e0 100644 --- a/vite/src/pipedal/AlsaDeviceInfo.tsx +++ b/vite/src/pipedal/AlsaDeviceInfo.tsx @@ -9,6 +9,8 @@ export default class AlsaDeviceInfo { this.sampleRates = input.sampleRates as number[]; this.minBufferSize = input.minBufferSize; this.maxBufferSize = input.maxBufferSize; + this.supportsCapture = input.supportsCapture ? true : false; + this.supportsPlayback = input.supportsPlayback ? true : false; return this; } static deserialize_array(input: any): AlsaDeviceInfo[] @@ -62,5 +64,7 @@ export default class AlsaDeviceInfo { longName: string = ""; sampleRates: number[] = []; minBufferSize: number = 0; - maxBufferSize: number = 0; + maxBufferSize: number = 0; + supportsCapture: boolean = false; + supportsPlayback: boolean = false; }; \ No newline at end of file diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index c35a442..a295bb5 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -220,8 +220,8 @@ function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaD { if (!jackServerSettings.valid) return false; if (!alsaDevices) return false; - const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice); - const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice); + const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice && d.supportsCapture); + const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice && d.supportsPlayback); if (!inDevice || !outDevice) return false; const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers; @@ -289,15 +289,24 @@ const JackServerSettingsDialog = withStyles( return result; } - let inDevice = alsaDevices.find(d => d.id === result.alsaInputDevice); - let outDevice = alsaDevices.find(d => d.id === result.alsaOutputDevice); + let inDevice = alsaDevices.find(d => d.id === result.alsaInputDevice && d.supportsCapture); + let outDevice = alsaDevices.find(d => d.id === result.alsaOutputDevice && d.supportsPlayback); if (!inDevice) { - inDevice = alsaDevices[0]; - result.alsaInputDevice = inDevice.id; + inDevice = alsaDevices.find(d => d.supportsCapture); + if (inDevice) { + result.alsaInputDevice = inDevice.id; + } } if (!outDevice) { - outDevice = alsaDevices[0]; - result.alsaOutputDevice = outDevice.id; + outDevice = alsaDevices.find(d => d.supportsPlayback); + if (outDevice) { + result.alsaOutputDevice = outDevice.id; + } + } + + if (!inDevice || !outDevice) { + result.valid = false; + return result; } if (result.sampleRate === 0) result.sampleRate = 48000; @@ -467,9 +476,9 @@ const JackServerSettingsDialog = withStyles( disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0} style={{ width: 220 }} > - {this.state.alsaDevices?.map(d => ( - {d.name} - )) || Loading...} + {this.state.alsaDevices?.filter(d => d.supportsCapture).map(d => ( + {d.name} + )) || Loading...} @@ -483,8 +492,8 @@ const JackServerSettingsDialog = withStyles( disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0} style={{ width: 220 }} > - {this.state.alsaDevices?.map(d => ( - {d.name} + {this.state.alsaDevices?.filter(d => d.supportsPlayback).map(d => ( + {d.name} )) || Loading...}