Fix FlexControl audio wheel actions#3499
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes FlexControl/AetherControl headphone wheel adjustments by keeping RadioModel’s audio-output gain caches in sync immediately (optimistic local update), and adds a new wheel action to control the active slice’s AF (audio) gain—keeping master volume, slice audio volume, and radio headphone volume as distinct controls.
Changes:
- Update
RadioModel::setLineoutGain()/setHeadphoneGain()to update cached gain values immediately and emitaudioOutputChanged()before waiting for a radio status echo. - Add the
WheelSliceAudio(“Slice Audio Volume”) action to FlexControl/HID/TMate configuration UIs and StreamDeck+ short labels. - Route
WheelSliceAudioto the active slice’sSliceModel::setAudioGain()and add the correspondingFlexWheelMode::SliceAudiomode plumbing.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/models/RadioModel.cpp | Optimistically updates line-out/headphone gain caches and emits audioOutputChanged() for immediate UI/model responsiveness. |
| src/gui/RadioSetupDialog.cpp | Exposes the new WheelSliceAudio action in serial/HID/TMate encoder action dropdowns. |
| src/gui/MainWindow.h | Adds FlexWheelMode::SliceAudio enum value. |
| src/gui/MainWindow.cpp | Wires WheelSliceAudio through action→mode mapping, wheel dispatch, StreamDeck+ labeling, and active-slice gain adjustment. |
| src/gui/FlexControlDialog.cpp | Adds WheelSliceAudio to action definitions and to the “is wheel action” predicate for latching Aux behavior. |
There was a problem hiding this comment.
Thanks @rfoust — this is a well-scoped fix and the root-cause analysis in the description matches the code. Verified the key points against main:
Confirmed correct:
- The stale-read diagnosis is accurate:
m_headphoneGainwas only updated by the status echo inhandleRadioStatus()(RadioModel.cpp:4791), so fast wheel ticks inapplyFlexControlWheelActionkept computing from the same base value. The optimistic cache update fixes this, and mirrors whatSliceModel::setAudioGainalready does. - The dedup early-return (
if (m_headphoneGain == v) return;) isn't just an optimization — it's load-bearing. Since the setters now emitaudioOutputChanged, which updates UI sliders whosevalueChangedhandlers call the setters back, the guard is what breaks the feedback cycle. 👍 - Inserting
SliceAudiomid-enum inFlexWheelModeis safe — the mode is only held in memory and serialized as strings inbuildControlDevicesSnapshot(), never persisted as an int. - The new
WheelSliceAudiobranch null-guardsactiveSlice()consistent with the neighboringWheelAgcT/WheelApfbranches, andSliceModel::setAudioGainalready clamps + caches optimistically, so the new action doesn't reintroduce the stale-read problem on the slice path. - Coverage is complete: all nine sites that enumerate
WheelHeadphoneVolume(FlexControlDialog action table +isWheelActionId, the three RadioSetupDialog serial-encoder lists, and the four MainWindow sites) got matchingWheelSliceAudioentries.
One minor edge case (non-blocking):
m_lineoutGain/m_headphoneGain are reset to 50 on disconnect (RadioModel.cpp:2632-2633). With the new early-return, a setLineoutGain(50)/setHeadphoneGain(50) call that lands after a reconnect but before the first mixer status echo arrives will now be silently dropped, leaving the radio at its own value. The realistic exposure is applyMasterVolume() with PC audio disabled and MasterVolume saved as exactly 50 — narrow, and the next status echo re-syncs the cache, so I don't think it needs to block this PR. If you want to harden it, a "synced" flag set on the first mixer status (skip the dedup until then) would close it.
No convention issues — uses existing AppSettings/std::clamp/QStringLiteral patterns, no new system boundaries, all five files are within the stated scope. LGTM as a community review; leaving the merge call to the maintainers.
🤖 aethersdr-agent · cost: $6.7409 · model: claude-fable-5
ce5bfd2 to
6df3a24
Compare
jensenpat
left a comment
There was a problem hiding this comment.
Reviewed: root-cause fix verified (optimistic mixer gain cache + status-echo reconciliation), WheelSliceAudio plumbed through all enumeration sites, CI green. Approving for squash merge.
Summary
RadioModel's line-out and headphone gain caches in sync before sending the SmartSDR mixer command.WheelSliceAudio/ "Slice Audio Volume" wheel action for FlexControl/AetherControl and TMate-style encoder mappings.Root Cause
The FlexControl
WheelHeadphoneVolumepath computed each wheel tick fromRadioModel::headphoneGain(), butRadioModel::setHeadphoneGain()only sentmixer headphone gain Nand waited for a later radio status echo before updatingm_headphoneGain. Fast wheel ticks therefore kept reading the same stale base value and repeatedly sent the same gain command, so the title-bar headphone slider appeared not to move.This mirrors the FlexLib pattern for mixer gain setters: update the local cache first, then send the command and notify observers. The same optimistic update is applied to line-out gain because it had the same cache/write shape.
User Impact
FlexControl/AetherControl users can now:
Fixes #3211.
Fixes #3465.
Fixes #3487.
Validation
git diff --checkcmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfocmake --build build --parallel 8ctest --test-dir build --output-on-failure --parallel 8 -E theme_manager_test(31/31passed)I also ran the full
ctest --test-dir build --output-on-failure --parallel 8. The only failure wastheme_manager_test, which attempted to write/Users/rfoust/Library/Preferences/AetherSDR/AetherSDR.settings.tmpfrom the sandboxed macOS test environment; all other tests passed in that full run.