[bug] Route CW sidetone to selected audio output#2899
Conversation
Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
Thanks @jensenpat — well-scoped fix, and the docs update is a nice touch.
A few observations, none blocking:
CwSidetonePortAudioSink::findPortAudioOutputDevice — partial-match false positives
The fallback candidate.contains(target) || target.contains(candidate) widens the search beyond exact matches, with the ambiguity guard catching multi-hits. That's a good safety net, but a single accidental substring match still slips through silently. For example, a Qt description of "Speakers" would partial-match a PortAudio device named "Speakers (Conf Room)" if no other candidate exists — and the user would never see why sidetone went somewhere unexpected.
Two cheap mitigations worth considering:
- Log the partial-match path at info/warning level (something like "Qt 'X' partially matched PortAudio 'Y'"), so a misroute is at least discoverable in the support bundle.
- Or tighten the partial match to only
candidate.contains(target)(PortAudio name strictly contains Qt description, which is the common case for "Pulse vs ALSA hw:N" style names) and skip the reverse direction — the reverse direction is the one most likely to false-match.
AudioEngine.cpp line 1049 — dev = d vs dev = m_outputDevice
Good catch on using the freshly-enumerated d. Worth a one-line comment noting why — otherwise this looks like a no-op refactor and may get "fixed" back in a future cleanup pass.
MainWindow.cpp default-ID tracking — defaultInputChanged evaluation order
Logic looks right. One small thing: currentDefaultInputId/currentDefaultOutputId are read unconditionally, but defaultInputChanged/defaultOutputChanged are only consulted in the currentInput.isNull() / currentOutput.isNull() branches. Not a bug, just an observation — the two QMediaDevices::default* calls happen on every device-list-change tick now.
Scope / conventions
- All five touched files are in scope for the stated fix.
- No
QSettingsusage introduced; RAII boundaries unchanged. - Logging consistently uses
lcAudioviaqCWarning/qCInfo.
LGTM modulo the partial-match observability concern. The QAudioSink fallback when PortAudio mapping fails is the right call.
|
Windows validation (Win11, Flex 6700, paddle on radio's HWCW key jack, several PC audio endpoints: TOSHIBA-TV via NVIDIA HDMI, Realtek motherboard audio, USB Audio, Oculus Virtual, plus the usual FlexRadio DAX cables). Built clean with MSVC — fills the SKIPPED Distinctive bug-fix test:
That's the exact scenario this PR fixes — pre-PR, PortAudio's default-output would have routed sidetone to the TV regardless of AetherSDR's selection. Post-PR, the Qt → PortAudio mapping correctly wins. Audio-device hotplug: plugging Realtek headphones in mid-session fired the "Audio Device Detected" dialog. Accepting moved both RX and sidetone to the new device cleanly: Default change while explicit selection set: flipped Windows default back to TOSHIBA-TV while AetherSDR was explicit on Realtek. No restart fired (correct — explicit selection should win), sidetone stayed on Realtek. Not exercised: the partial-match / ambiguity branches of Re: @aethersdr-agent's observability concern — it's worse than the source reads. Two small (non-blocking) follow-up ideas:
LGTM on the user-visible fix. |
|
Confirmed @nigelfenton's diagnosis after reading the code on Why
Q_LOGGING_CATEGORY(lcAudio, "aether.audio", QtWarningMsg)That sets the category's compiled-in minimum level to That makes the second follow-up the clear win: one-line fix, surfaces every existing Suggested patch — void LogManager::applyFilterRules()
{
QStringList rules;
rules << "aether.*.debug=false";
rules << "aether.*.info=false";
for (const auto& c : m_categories) {
if (c.enabled) {
rules << QString("%1.debug=true").arg(c.id);
rules << QString("%1.info=true").arg(c.id);
}
}
QLoggingCategory::setFilterRules(rules.join('\n'));
}The explicit Separately — the partial-match forensic gap Even after the logging fix above, a silent partial-match misroute is the scarier failure mode because the user has no symptom until they key up. I'd still log the partial-match branch of qCWarning(lcAudio) << "CwSidetonePortAudioSink: Qt device" << qtDeviceName
<< "matched PortAudio device" << paDeviceName
<< "by substring — verify routing if sidetone is misrouted";That gives support bundles the forensic trail nigelfenton called out, independent of any per-user log-category preferences. Re: process — this issue isn't currently carrying the 73, Jeremy KK7GWY & Claude (AI dev partner) |
Co-authored-by: Codex <noreply@openai.com>
Summary
Fix CW sidetone routing so the local sidetone sink follows AetherSDR's selected PC audio output instead of silently drifting to whatever PortAudio considers the default output.
Root Cause
The Qt sidetone fallback already accepted a
QAudioDevice, but the PortAudio sidetone backend ignored the selected Qt device and always opened PortAudio's default output. That meant users who selected a non-default PC audio output in preferences, or who accepted a new output device from the audio device dialog, could still have CW sidetone routed to the wrong physical endpoint.There was a second related default-following gap: when AetherSDR was configured to follow the system default output, the audio monitor only restarted local audio paths when device IDs were added/removed. If the OS changed the default output without removing the previous device, an existing RX/sidetone sink could stay bound to the old endpoint.
Changes
AudioEnginefalls back to the QtQAudioSinksidetone path on the selected device.qCWarningso support bundles capture the sidetone route without refactoring global logging behavior.MainWindowand restart default-following local audio paths when the OS default changes, even when no device was removed.docs/audio-pipeline.md.User Impact
Switching PC audio output from app settings or from the new audio-device dialog now restarts CW sidetone on the same output device as RX audio. Users following the OS default output also get a restart when the OS changes that default, keeping sidetone, RX audio, and the title-bar PC audio labels aligned.
Validation
Local macOS validation:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfocmake --build build --parallel 22cmake --build build --target AetherSDR --parallel 22./build/cw_sidetone_testgit diff --checkBuilt app:
build/AetherSDR.appWindows validation by @nigelfenton:
TOSHIBA-TV (NVIDIA HDMI),Headphones (Realtek(R) Audio), USB Audio, Oculus Virtual, and FlexRadio DAX cables.check-windowsCI gap; no errors or new warnings on the four source files this PR touches.Headphones (Realtek(R) Audio).TOSHIBA-TV (NVIDIA HDMI).findPortAudioOutputDevice; his Realtek mapping was unambiguous.👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat