From 1b900a1a472870f8eb031badac8313152399c952 Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:36:49 +0200 Subject: [PATCH 1/2] Standalone: persist the sample rate the user asked for, not the one the device granted captureAudioSettings() wrote back the resolved rate, so one launch on a device without it replaced the user's choice permanently. --- src/detail/standalone/standalone_host.cpp | 10 +++++----- src/detail/standalone/standalone_host.h | 4 ++-- src/detail/standalone/standalone_settings.h | 4 ++-- src/detail/standalone/windows/windows_standalone.cpp | 5 +++++ 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/detail/standalone/standalone_host.cpp b/src/detail/standalone/standalone_host.cpp index 59154160..fbbadcd7 100644 --- a/src/detail/standalone/standalone_host.cpp +++ b/src/detail/standalone/standalone_host.cpp @@ -474,13 +474,13 @@ bool StandaloneHost::saveStandaloneSettings() void StandaloneHost::captureAudioSettings() { settings.audioApiName = audioApiName; - settings.sampleRate = currentSampleRate; settings.bufferSize = currentBufferSize; - // Device names and used flags are deliberately not captured: they describe what - // could actually be opened (a fallback device, a side that failed to open), and - // saveSettings() runs often enough that persisting them would silently replace - // the user's choice. The frontend writes them at the point of choice instead. + // Device names, used flags and the sample rate are deliberately not captured: + // they describe what could actually be opened (a fallback device, a side that + // failed to open, a rate the device does not offer), and saveSettings() runs + // often enough that persisting them would silently replace the user's choice. + // The frontend writes them at the point of choice instead. } void StandaloneHost::applyAudioSettings() diff --git a/src/detail/standalone/standalone_host.h b/src/detail/standalone/standalone_host.h index 16398cbc..55e95f81 100644 --- a/src/detail/standalone/standalone_host.h +++ b/src/detail/standalone/standalone_host.h @@ -149,8 +149,8 @@ struct StandaloneHost : Clap::IHost // Current audio configuration -> settings, and back. applyAudioSettings() // selects the API and resolves the persisted device *names* against the // devices this machine actually has right now. - // captureAudioSettings() writes back only the API, sample rate and buffer size; - // the device names and used flags are the user's request, written by the frontend. + // captureAudioSettings() writes back only the API and buffer size; the device + // names, used flags and sample rate are the user's request, written by the frontend. void captureAudioSettings(); void applyAudioSettings(); diff --git a/src/detail/standalone/standalone_settings.h b/src/detail/standalone/standalone_settings.h index 1d25abe9..de866e82 100644 --- a/src/detail/standalone/standalone_settings.h +++ b/src/detail/standalone/standalone_settings.h @@ -41,13 +41,13 @@ struct StandaloneSettings std::string audioApiName; // RtAudio::getApiName(); empty means unspecified // What the user asked for, written only at the point of choice - never from the - // device actually opened, which may be a fallback. + // device actually opened or the rate actually granted, which may be a fallback. std::string inputDeviceName; // empty means the system default device std::string outputDeviceName; // empty means the system default device bool audioInputUsed{true}; bool audioOutputUsed{true}; // no UI turns this off yet; the .conf can + int32_t sampleRate{0}; // 0 means the device's preferred rate - int32_t sampleRate{0}; // 0 means the device's preferred rate // Clamped on apply; see StandaloneHost::applyAudioSettings(). uint32_t bufferSize{defaultBufferSize}; diff --git a/src/detail/standalone/windows/windows_standalone.cpp b/src/detail/standalone/windows/windows_standalone.cpp index 2b054f27..afdb1d04 100644 --- a/src/detail/standalone/windows/windows_standalone.cpp +++ b/src/detail/standalone/windows/windows_standalone.cpp @@ -1041,6 +1041,7 @@ Plugin::Plugin(std::shared_ptr clapPlugin, int nCmdShow) if (auto index{settings.sampleRate.selection(sampleRates.size())}; index) { sah->currentSampleRate = sampleRates[*index]; + sah->settings.sampleRate = sampleRates[*index]; saveSettings(); startAudio(); @@ -1370,6 +1371,10 @@ Plugin::Plugin(std::shared_ptr clapPlugin, int nCmdShow) startAudio(); + // The rate asked for may have been 0 ("device preferred") or unsupported, so the + // combo can only show the truth once the stream is open. + refreshSampleRates(); + // Honor the show state requested by the launcher (shortcut "Run:" / STARTUPINFO), // falling back to a normal window. SW_HIDE would otherwise leave us invisible-but-running. ::ShowWindow(hwnd.get(), nCmdShow == SW_HIDE ? SW_SHOWNORMAL : nCmdShow); From 5ed93179389f339f25feb6c7ce9045e8306bf3f1 Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:37:28 +0200 Subject: [PATCH 2/2] Standalone: validate the sample rate on an input-only stream too Only the output path checked the requested rate against the device, so a hand-edited .conf reached openStream() and raised a startup dialog. --- src/detail/standalone/standalone_host.cpp | 3 +- .../standalone/standalone_host_audio.cpp | 40 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/detail/standalone/standalone_host.cpp b/src/detail/standalone/standalone_host.cpp index fbbadcd7..681746d2 100644 --- a/src/detail/standalone/standalone_host.cpp +++ b/src/detail/standalone/standalone_host.cpp @@ -526,7 +526,8 @@ void StandaloneHost::applyAudioSettings() } } - // The sample rate needs no clamp: startAudioThreadOn() validates it. + // The sample rate needs no clamp: startAudioThreadOn() resolves it against the + // device it is about to open. } bool StandaloneHost::isKnownDevice(unsigned int deviceID) diff --git a/src/detail/standalone/standalone_host_audio.cpp b/src/detail/standalone/standalone_host_audio.cpp index 9ca273e0..746e662a 100644 --- a/src/detail/standalone/standalone_host_audio.cpp +++ b/src/detail/standalone/standalone_host_audio.cpp @@ -21,6 +21,21 @@ namespace freeaudio::clap_wrapper::standalone { +namespace +{ +// The requested rate if the device offers it, its preferred rate otherwise. +int32_t rateOfferedBy(const RtAudio::DeviceInfo &info, int32_t requested) +{ + if (requested > 0 && std::find(info.sampleRates.begin(), info.sampleRates.end(), + static_cast(requested)) != info.sampleRates.end()) + { + return requested; + } + + return static_cast(info.preferredSampleRate); +} +} // namespace + int rtaCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double /* streamTime */, RtAudioStreamStatus status, void *data) { @@ -380,23 +395,7 @@ void StandaloneHost::startAudioThreadOnImpl(unsigned int inputDeviceID, uint32_t outInfo = deviceInfoFor(outputDeviceID); oParams.nChannels = std::min(outputChannels, outInfo.outputChannels); oParams.firstChannel = 0; - if (sampleRate < 0) - { - sampleRate = outInfo.preferredSampleRate; - } - else - { - // Mkae sure this sample rate is available - bool isPossible{false}; - for (auto sr : outInfo.sampleRates) - { - isPossible = isPossible || ((int)sr == (int)sampleRate); - } - if (!isPossible) - { - sampleRate = outInfo.preferredSampleRate; - } - } + sampleRate = rateOfferedBy(outInfo, sampleRate); } RtAudio::StreamParameters iParams; @@ -406,10 +405,13 @@ void StandaloneHost::startAudioThreadOnImpl(unsigned int inputDeviceID, uint32_t inInfo = deviceInfoFor(inputDeviceID); iParams.nChannels = std::min(inputChannels, inInfo.inputChannels); iParams.firstChannel = 0; - if (sampleRate < 0) sampleRate = inInfo.preferredSampleRate; + + // With no output side nothing else has vetted the rate, and the .conf is + // hand-editable; openStream() would simply fail and raise a dialog. + if (!useOutput) sampleRate = rateOfferedBy(inInfo, sampleRate); } - if (sampleRate < 0) + if (sampleRate <= 0) { LOGINFO("[WARNING] No preferred sample rate detected; using 48k"); sampleRate = 48000;