diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index 645e0e4..4fb0932 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -63,7 +63,11 @@ namespace pipedal const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; } const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; } const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy + + void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; } + void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; } void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; } + void UseDummyAudioDevice() { this->valid_ = true; if (sampleRate_ == 0) sampleRate_ = 48000; diff --git a/src/PiPedalAlsa.cpp b/src/PiPedalAlsa.cpp index 2c02437..776cc14 100644 --- a/src/PiPedalAlsa.cpp +++ b/src/PiPedalAlsa.cpp @@ -23,6 +23,7 @@ #include "Lv2Log.hpp" #include #include +#include "Finally.hpp" using namespace pipedal; @@ -46,6 +47,14 @@ void PiPedalAlsaDevices::cacheDevice(const std::string &name, const AlsaDeviceIn cachedDevices[name] = deviceInfo; } +static bool isSupportedAudioDevice(const AlsaDeviceInfo &d) +{ + std::string name = d.name_ + " " + d.longName_; + std::transform(name.begin(), name.end(), name.begin(), [](char c) + { return std::tolower(c); }); + return name.find("hdmi") != std::string::npos || name.find("bcm2835") != std::string::npos; +}; + std::vector PiPedalAlsaDevices::GetAlsaDevices() { @@ -79,14 +88,19 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() continue; } - snd_ctl_card_info_t *alsaInfo; + Finally ffhDevice{[hDevice]() + { snd_ctl_close(hDevice); }}; + + snd_ctl_card_info_t *alsaInfo = nullptr; if (snd_ctl_card_info_malloc(&alsaInfo) != 0) { Lv2Log::error("Failed to allocate ALSA card info"); - snd_ctl_close(hDevice); continue; } + Finally ffCardInfo{[alsaInfo]() + { snd_ctl_card_info_free(alsaInfo); }}; + err = snd_ctl_card_info(hDevice, alsaInfo); if (err == 0) { @@ -98,32 +112,38 @@ 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, SND_PCM_NONBLOCK) == 0; + bool playbackOk = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) == 0; + Finally ffCaptureDevice{ + [captureDevice] + { if (captureDevice) snd_pcm_close(captureDevice); }}; + Finally ffPlaybackDevice{ + [playbackDevice] + { if (playbackDevice) snd_pcm_close(playbackDevice); }}; - // must support capture AND playback - err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0); - if (err == 0) + info.supportsCapture_ = captureOk; + info.supportsPlayback_ = playbackOk; + + if (captureOk || playbackOk) { - snd_pcm_close(hDevice); - } - if (err == 0) - { - err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0); - } - if (err == 0) - { - snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; // xxx: HECK NO! + snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; snd_pcm_hw_params_t *params = nullptr; err = snd_pcm_hw_params_malloc(¶ms); + if (err == 0) { + Finally ffParams{[params] + { snd_pcm_hw_params_free(params); }}; + err = snd_pcm_hw_params_any(hDevice, params); if (err == 0) { unsigned int minRate = 0, maxRate = 0; snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0; int dir; - + err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir); if (err == 0) { @@ -148,7 +168,7 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() { Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'.")); } - + if (err == 0) { err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize); @@ -166,17 +186,9 @@ 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); - if (captureOk) // HECK NO!! REVIEW THIS! - snd_pcm_close(captureDevice); - if (playbackOk && playbackDevice != captureDevice) - snd_pcm_close(playbackDevice); if (err == 0) { cacheDevice(info.name_, info); @@ -195,30 +207,25 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() } } } - snd_ctl_card_info_free(alsaInfo); - snd_ctl_close(hDevice); } } - snd_config_update_free_global(); - - auto isFiltered = [](const AlsaDeviceInfo &d) { - std::string name = d.name_ + " " + d.longName_; - std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c){return std::tolower(c);}); - return name.find("hdmi") != std::string::npos || name.find("bcm2835") != std::string::npos; - }; - - std::vector filtered; - for (auto &d : result) - { - if (!isFiltered(d)) filtered.push_back(d); - } Lv2Log::debug("GetAlsaDevices --"); - for (auto &device : filtered) + + std::vector filtered; + for (auto &device : result) { - Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_)); + if (!isSupportedAudioDevice(device)) + { + filtered.push_back(device); + Lv2Log::debug( + SS(" " + << device.name_ << " " << device.longName_ << " " << device.cardId_ + << (device.supportsCapture_ ? " in" : "") + << (device.supportsPlayback_ ? " out" : ""))); + } } - return filtered; + return filtered; } static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector *result) @@ -289,11 +296,12 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM name = snd_rawmidi_info_get_name(info); sub_name = snd_rawmidi_info_get_subdevice_name(info); // get card name. - + std::string cardName; snd_ctl_card_info_t *card_info = nullptr; snd_ctl_card_info_malloc(&card_info); - if (snd_ctl_card_info(ctl, card_info) == 0) { + if (snd_ctl_card_info(ctl, card_info) == 0) + { cardName = snd_ctl_card_info_get_name(card_info); } snd_ctl_card_info_free(card_info); @@ -301,7 +309,7 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM { return; } - + if (sub == 0 && sub_name[0] == '\0') { AlsaMidiDeviceInfo info; @@ -335,7 +343,8 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM { AlsaMidiDeviceInfo info; info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device); - if (sub != 0) { + if (sub != 0) + { info.name_ = SS(info.name_ << "," << sub); } info.description_ = sub_name; diff --git a/vite/src/pipedal/AlsaDeviceInfo.tsx b/vite/src/pipedal/AlsaDeviceInfo.tsx index 360b9a5..bc181fa 100644 --- a/vite/src/pipedal/AlsaDeviceInfo.tsx +++ b/vite/src/pipedal/AlsaDeviceInfo.tsx @@ -65,4 +65,6 @@ export default class AlsaDeviceInfo { sampleRates: number[] = []; minBufferSize: number = 0; maxBufferSize: number = 0; + supportsCapture: boolean = true; + supportsPlayback: boolean = true; }; \ No newline at end of file diff --git a/vite/src/pipedal/JackServerSettings.tsx b/vite/src/pipedal/JackServerSettings.tsx index 9924d4f..9c6b50f 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; diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index 3a314fa..2564c49 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -16,7 +16,6 @@ // 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 { Theme } from '@mui/material/styles'; @@ -151,6 +150,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 ) @@ -168,23 +183,26 @@ 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}; } // 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}; } @@ -394,7 +412,10 @@ const JackServerSettingsDialog = withStyles( applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) { let result = jackServerSettings.clone(); - if (!alsaDevices) { + if (!alsaDevices || alsaDevices.length === 0) { + result.valid = false; + result.alsaInputDevice = INVALID_DEVICE_ID; + result.alsaOutputDevice = INVALID_DEVICE_ID; return result; } @@ -494,29 +515,14 @@ const JackServerSettingsDialog = withStyles( } } - 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; settings.valid = false; @@ -530,8 +536,9 @@ 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; settings.valid = false; @@ -548,8 +555,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; settings.valid = false; @@ -736,7 +744,7 @@ const JackServerSettingsDialog = withStyles(