feat(audio): mute-all-slices button + fix stale mute display after reconnect#2833
feat(audio): mute-all-slices button + fix stale mute display after reconnect#2833chibondking wants to merge 5 commits into
Conversation
…connect Add a mute-all toggle button (🔇) to the RxApplet slice tab row; clicking mutes all slices when any are unmuted, unmutes all when all are muted. Also registers a `mute_all_slices_toggle` keyboard shortcut in MainWindow. Fix: SliceModel::applyStatus() now resets m_audioMute to false when a full status (in_use=1) arrives without an audio_mute key. SliceModels survive reconnect, but the radio resets audio_mute to 0 on reconnect without echoing the key (it is the default). The stale true caused the button to show 🔇 while audio was playing. See commit_msg.txt for full root-cause analysis. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@AetherClaude thoughts on this? |
|
@chibondking — I traced the claim through the code and the analysis holds up cleanly:
The proposed if (kvs.contains("audio_mute")) {
bool mute = kvs["audio_mute"] == "1";
if (mute != m_audioMute) {
m_audioMute = mute;
emit audioMuteChanged(mute);
}
} else if (kvs.value("in_use") == "1" && m_audioMute) {
// Full status w/o audio_mute key → radio reset to default (0)
// on (re)connect. Resync so UI doesn't show a stale 🔇 while
// audio is actually playing. Radio does not persist audio_mute
// (see MainWindow.cpp:1264 migration note).
m_audioMute = false;
emit audioMuteChanged(false);
}Two things worth thinking through for the mute-all button while you're in there:
The keyboard shortcut should slot into the existing The issue has no labels currently. A maintainer can authorize the orchestrator to land this fix by adding the 73, Jeremy KK7GWY & Claude (AI dev partner) |
- SliceModel::applyStatus(): use kvs.value("in_use") == "1" && m_audioMute
in the else-if condition (avoids redundant emit on already-unmuted slices,
cleaner than nested if — per Jeremy KK7GWY review)
- RxApplet mute-all handler: add comment documenting that RadioModel::slices()
is owned-only (foreign slices removed on client_handle), and that
setAudioMute() early-return guards the RADE m_radePrevMute invariant
- commit_msg.txt: updated with review-refined rationale
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@AetherClaude changes applied. Thoughts? |
|
@chibondking — reviewed the applied changes against the points I raised: SliceModel.cpp — Shortcut registration — Foreign / unowned slices — already safe. I went back and checked One remaining concern: RADE interaction.
Cheapest fix is to skip the RADE slice in both mute-all iterations when // In MainWindow shortcut handler:
m_shortcutManager.registerAction("mute_all_slices_toggle", "Mute All Slices", "Audio",
QKeySequence(), [this]() {
const auto slices = m_radioModel.slices();
bool anyUnmuted = false;
for (const SliceModel* s : slices) {
if (!s || s->sliceId() == m_radeSliceId) continue; // RADE owns its mute
if (!s->audioMute()) { anyUnmuted = true; break; }
}
for (SliceModel* s : slices) {
if (!s || s->sliceId() == m_radeSliceId) continue;
s->setAudioMute(anyUnmuted);
}
});The RxApplet button can't see Minor nit (not blocking): the iteration block is byte-identical between The issue has no 73, Jeremy KK7GWY & Claude (AI dev partner) |
…slice The previous mute-all implementation kept toggle logic in RxApplet and relied on setAudioMute() being a no-op for the RADE slice. That holds for mute-all (RADE slice already true, early-return guard fires) but NOT for unmute-all: when every slice including the RADE slice is muted, anyUnmuted=false causes setAudioMute(false) on the RADE slice — changes m_audioMute true→false, sends audio_mute=0 to the radio, breaks RADE audio gating, and leaves m_radePrevMute pointing at a stale baseline for deactivateRADE() to restore. Fix: move all toggle logic to MainWindow::onMuteAllSlicesToggle() which has full access to m_radeSliceId. Both the scan and the apply loop skip the RADE slice under #ifdef HAVE_RADE. RxApplet button now emits muteAllToggled(); keyboard shortcut calls the same slot. Logic is no longer duplicated. Squelch paths confirmed clean: neither auto nor manual squelch ever calls setAudioMute(); squelch state preservation (m_savedSquelchOn) is independent. Foreign-slice safety confirmed: RadioModel::slices() is owned-only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@AetherClaude took a crack at it locally, thoughts on these changes? |
There was a problem hiding this comment.
Thanks @chibondking — the bug fix here is excellent. The SliceModel::applyStatus reset is precisely targeted: in_use=1 is indeed the canonical full-status marker (verified at RadioModel.cpp:4340 where handleSliceStatus refuses to create a SliceModel without it), the && m_audioMute guard avoids spurious signals on every full status, and the migration block at MainWindow.cpp:1265 confirms the radio doesn't persist audio_mute. Clean, narrow, well-reasoned fix.
The onMuteAllSlicesToggle logic is also correct — moving it to MainWindow for RADE context was the right call, the skip in both the scan and apply loops is necessary (your commit message correctly identifies why the unmute direction is not a no-op), and RadioModel::slices() already filters foreign clients in handleSliceStatus (lines 4295-4304), so no extra filtering is needed. Wiring the action through ShortcutManager is the right pattern.
Two things to address before merge:
-
The mute-all button only appears on FLEX-6700 (>4 slices), not on all multi-slice radios as the description claims. In
RxApplet::setMaxSlices(lines 1252-1265), theuseInlinebranch (maxSlices ≤ 4 — i.e. 6300/6400/6500/6600, the majority of users) places the slice buttons intom_headerRowand leavesm_sliceTabRowhidden —setVisible(true)is only called in the!useInlinebranch at line 1309. So on 2/4-slice radios the button is never shown. The keyboard shortcut still works, but the UI affordance is missing for most users.Options: also surface the button on the inline header row, or scope the description to "visible on 8-slice radios."
-
commit_msg.txt(103 lines) is checked into the repo root. This looks like an artifact from your commit-message workflow rather than something intended for the tree. Please drop it from the PR (the same content is already in the PR description).
Once the visibility gap is addressed (or scoped explicitly) and the stray file is removed, this is good to go. The applyStatus reset alone is worth the merge.
…connect
## Bug fix: slice shows as muted after reconnect when radio is not muted
Reported by Robbie KI4TTZ: intermittent "slice shows as muted but audio is
playing" after a reconnect.
Root cause: RadioModel::onDisconnected() clears panadapters, meters, etc., but
does NOT delete SliceModel objects — they survive reconnect intentionally for
SmartConnect fast-reconnect. The radio resets audio_mute to 0 on reconnect but
omits the key from the full-status message (0 is the default). applyStatus()
only updates m_audioMute when kvs.contains("audio_mute"), so a previously
muted SliceModel keeps m_audioMute=true — button shows 🔇, model reports muted,
radio is playing audio.
Fix (SliceModel::applyStatus): add an else-if branch that fires when audio_mute
is absent from the KVS and in_use=1 is present (the canonical full-status
marker — RadioModel::handleSliceStatus already refuses to create a SliceModel
without it). Resets m_audioMute to false and emits audioMuteChanged(false).
The && m_audioMute guard prevents spurious signals on every full-status message
for slices that were already unmuted.
## New feature: Mute All Slices
Adds a 🔇 button to RxApplet and a mute_all_slices_toggle keyboard shortcut
(category "Audio", no default binding, editable via ShortcutDialog).
Toggle logic: any owned slice unmuted → mutes all; all muted → unmutes all.
Button placement:
- ≤4 slices (6300/6400/6500/6600): button appears at the right end of the
inline header row alongside the slice selector buttons.
- >4 slices (6700): button appears at the right end of the slice tab row.
- Hidden when maxSlices ≤ 1 (single-slice configs where it has no effect).
Logic lives in MainWindow::onMuteAllSlicesToggle() — the only site with access
to RADE state. RxApplet button emits muteAllToggled(); both the signal and the
keyboard shortcut call the same slot. Logic is not duplicated.
RADE interaction: the slot skips the RADE-managed slice (m_radeSliceId) in
both the scan and apply loops under #ifdef HAVE_RADE. The mute direction would
be safe without the skip (setAudioMute(true) no-ops since the slice is already
forced muted by activateRADE()), but the unmute direction is not: when all
slices including the RADE slice are muted, anyUnmuted=false causes
setAudioMute(false) on the RADE slice — changes m_audioMute true→false, sends
audio_mute=0 to the radio, breaks RADE audio gating, and corrupts m_radePrevMute
for the deactivateRADE() restore path.
Squelch paths confirmed clean: neither auto (FFT/noise-floor driven) nor manual
squelch ever calls setAudioMute(). m_savedSquelchOn is an independent mechanism.
Foreign slices confirmed safe: RadioModel::slices() returns only owned slices
(handleSliceStatus deletes and removes foreign SliceModels from m_slices on
client_handle match at RadioModel.cpp:4295-4304).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/AetherSDR into feat/mute-all-slices
|
@AetherClaude changes applied. Also tagged as eligible for the agent to work on. |
|
@chibondking — went through the PR diff end-to-end. The changes are good, and the rationale captured in the commit message is sharper than what I'd written. Specifically the catch about the unmute-all direction being the wrong-side of the "setAudioMute is a no-op" argument is correct and worth calling out — that was the real reason my earlier "just skip the RADE slice" suggestion mattered. Round-up of what I checked:
Shortcut registration (MainWindow.cpp:12500-12501) — slots into Button placement + reparenting (RxApplet.cpp:281-290, 1259, 1275-1289, 1352) — Foreign / unowned slices — confirmed clean by the commit message ( Squelch interaction — also called out in the commit message and confirmed: auto-squelch is FFT-driven via One nit, not blocking: the diff includes The 73, Jeremy KK7GWY & Claude (AI dev partner) |
Add a mute-all toggle button (🔇) to the RxApplet slice tab row; clicking mutes all slices when any are unmuted, unmutes all when all are muted. Also registers a
mute_all_slices_togglekeyboard shortcut in MainWindow.Fix: SliceModel::applyStatus() now resets m_audioMute to false when a full status (in_use=1) arrives without an audio_mute key. SliceModels survive reconnect, but the radio resets audio_mute to 0 on reconnect without echoing the key (it is the default). The stale true caused the button to show 🔇 while audio was playing.
Reported by Robbie KI4TTZ (@rfoust): intermittent "slice shows as muted but audio is playing" after a reconnect.
Root cause
RadioModel::onDisconnected() clears panadapters, meters, and other models on disconnect, but does not delete SliceModel objects. They survive reconnect so the UI does not flicker — this is intentional for SmartConnect fast-reconnect, but it creates a stale-state hazard.
The radio does not persist audio_mute across sessions (see the migration comment in MainWindow.cpp around line 1265). After any disconnect — even a brief network drop — the radio resets all slice audio_mute values to 0. When it sends the full slice status on reconnect, it only includes non-default values. Since audio_mute=0 is the default, the key is omitted entirely from the reconnect status message.
The existing SliceModel::applyStatus() code only updates m_audioMute when kvs.contains("audio_mute") is true. So a slice that was muted before the drop would keep m_audioMute = true indefinitely after reconnect — the button shows 🔇 and the model reports muted, but the radio is playing audio.
Fix
applyStatus() now has an else if branch that fires when audio_mute is absent from the KVS and in_use=1 is present (the marker for a full status, not a partial incremental update). In that case m_audioMute is reset to false and audioMuteChanged(false) is emitted. This is safe because:
The radio is documented as not persisting audio_mute.
Full-status messages (in_use=1) arrive only on initial slice creation or reconnect — not on the frequent partial-update messages that carry only changed keys.
If the radio genuinely wants the slice muted after reconnect, it will include audio_mute=1 in the full status, which the existing branch handles normally.