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] 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