[bug] Fix Slice audio loss after band changes#2128
Conversation
There was a problem hiding this comment.
Nice catch and clean fix, @jensenpat. The root cause analysis is thorough -- the radio silently dropping the slice from the audio mix without reflecting it in status is a tricky one, and the deferred reassertion mirrors exactly what the manual mute/unmute toggle does. A few observations:
Looks good:
- Scoping to
slices.size() > 1avoids unnecessary commands on single-slice setups. - Skipping
audioMute() == trueslices correctly preserves user-muted state. - Bypassing
SliceModel::setAudioMute()and sending the command directly is the right call -- the setter would no-op since the model already believes it's unmuted. - The null guard on
slicein the loop is defensive but harmless givenm_slicesshouldn't contain nulls. - Files changed are strictly within scope of the stated fix.
One thing to consider:
The QTimer::singleShot(300, this, ...) lambda captures panId by value (good), but captures this implicitly. If MainWindow is destroyed during the 300 ms window (e.g., rapid disconnect), the timer fires on a dangling this. In practice this is extremely unlikely since MainWindow is the top-level widget and outlives most operations, and Qt's parent-based timer cancellation covers it when the timer's receiver (this) is passed as the context object -- which it is here. So this is safe as written.
The 300 ms delay is a reasonable empirical choice. If testers find radios that take longer to settle (e.g., older firmware), this could be bumped or made configurable, but I wouldn't over-engineer that now.
Declaration placement nit (non-blocking):
reassertUnmutedSliceAudioForPan is declared in the private slots: section of MainWindow.h, but it's a regular private method, not a slot. It works fine either way, but strictly it belongs in the private: methods section. Not worth a respin.
Overall this is a clean, minimal fix for a real user-facing bug. Thanks for the contribution and the detailed write-up.
Community-driven release. WAVE Phase 2 visualization (#2124), DAX-aware TCI multi-stream routing for FlexRadio firmware 4.2.18 (#2140), TCXO frequency-offset calibration (#2119), VFO marker tri-state UX (#2141), v4.2.18 discovery beacon parsing (#2138). Bug fixes from the community: r8b heap corruption (#2114, NF0T), serial PTT triple-fix (#2125, chibondking), slice-audio mute on band change (#2128, jensenpat), CWX Live toggle (#2122, jensenpat), connect-radio dialog polish (#2121, jensenpat). Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Fixes #2064
Summary
This PR fixes the reported Slice B audio loss after changing bands in a two-slice setup. After Aether sends the radio-authoritative
display pan set <pan> band=<key>command, it now waits briefly for the Flex band stack restore to settle, then reassertsaudio_mute=0for any unmuted slice on that pan.Problem
The issue report describes a Windows 10 FLEX-6400 setup with two panadapters/slices. Slice B remains visually unmuted after a band change, but its audio goes silent until the user manually clicks the speaker icon muted and then unmuted again.
The Slice Troubleshooter snapshot captured while the problem was active showed:
audio mutereported asNoThat means the app model and UI still believed Slice B was unmuted. The failure was below the UI state: the radio-side audio contribution for that slice was no longer being mixed/routed even though no
audio_mute=1status was reflected in Aether.Root Cause
Band buttons intentionally use
display pan set <pan> band=<key>so the Flex firmware remains authoritative for band-stack state: frequency, mode, filters, pan geometry, antenna selection, and related slice properties.That command can also make the radio tear down and rebuild slice audio routing during the band transition. In the failing case, the radio appears to leave the slice out of the audio mix without sending an
audio_mutestatus change. Since Aether's model already saysaudioMute() == false, callingSliceModel::setAudioMute(false)would no-op and would not send anything to the radio. The manual mute/unmute workaround works because it forces a freshslice set <id> audio_mute=0command after the transition.Fix
After a successful band-stack command, Aether now schedules a 300 ms deferred reassertion:
slice set <id> audio_mute=0directly, bypassing the no-op setter pathThis preserves user-muted slices while giving the radio the same explicit nudge that the manual mute/off toggle provides.
Validation
git diff --checkcmake --build build -j10Local app bundle from the validated build:
/Users/patj/.codex/worktrees/3b89/AetherSDR/build/AetherSDR.app👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat