Skip to content

Fix FlexControl audio wheel actions#3499

Merged
jensenpat merged 1 commit into
aethersdr:mainfrom
rfoust:codex/flexcontrol-headphone-slice-audio
Jun 11, 2026
Merged

Fix FlexControl audio wheel actions#3499
jensenpat merged 1 commit into
aethersdr:mainfrom
rfoust:codex/flexcontrol-headphone-slice-audio

Conversation

@rfoust

@rfoust rfoust commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Updates radio mixer gain setters to keep RadioModel's line-out and headphone gain caches in sync before sending the SmartSDR mixer command.
  • Adds a WheelSliceAudio / "Slice Audio Volume" wheel action for FlexControl/AetherControl and TMate-style encoder mappings.
  • Routes the new slice-audio wheel action to the active slice AF gain, preserving the existing master-volume and radio-headphone-volume actions as distinct controls.

Root Cause

The FlexControl WheelHeadphoneVolume path computed each wheel tick from RadioModel::headphoneGain(), but RadioModel::setHeadphoneGain() only sent mixer headphone gain N and waited for a later radio status echo before updating m_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:

  • map a wheel action to radio headphone jack gain and get immediate local slider/model movement on each tick;
  • map a separate wheel action to active-slice AF gain for SO2R-style workflows and PC-audio listening;
  • keep master volume, slice audio volume, and radio headphone volume as separate controls.

Fixes #3211.
Fixes #3465.
Fixes #3487.

Validation

  • git diff --check
  • cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
  • cmake --build build --parallel 8
  • ctest --test-dir build --output-on-failure --parallel 8 -E theme_manager_test (31/31 passed)

I also ran the full ctest --test-dir build --output-on-failure --parallel 8. The only failure was theme_manager_test, which attempted to write /Users/rfoust/Library/Preferences/AetherSDR/AetherSDR.settings.tmp from the sandboxed macOS test environment; all other tests passed in that full run.

@rfoust rfoust requested a review from a team as a code owner June 10, 2026 02:10
Copilot AI review requested due to automatic review settings June 10, 2026 02:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 emit audioOutputChanged() 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 WheelSliceAudio to the active slice’s SliceModel::setAudioGain() and add the corresponding FlexWheelMode::SliceAudio mode 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.

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_headphoneGain was only updated by the status echo in handleRadioStatus() (RadioModel.cpp:4791), so fast wheel ticks in applyFlexControlWheelAction kept computing from the same base value. The optimistic cache update fixes this, and mirrors what SliceModel::setAudioGain already does.
  • The dedup early-return (if (m_headphoneGain == v) return;) isn't just an optimization — it's load-bearing. Since the setters now emit audioOutputChanged, which updates UI sliders whose valueChanged handlers call the setters back, the guard is what breaks the feedback cycle. 👍
  • Inserting SliceAudio mid-enum in FlexWheelMode is safe — the mode is only held in memory and serialized as strings in buildControlDevicesSnapshot(), never persisted as an int.
  • The new WheelSliceAudio branch null-guards activeSlice() consistent with the neighboring WheelAgcT/WheelApf branches, and SliceModel::setAudioGain already 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 matching WheelSliceAudio entries.

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

@rfoust rfoust self-assigned this Jun 11, 2026
@rfoust rfoust force-pushed the codex/flexcontrol-headphone-slice-audio branch from ce5bfd2 to 6df3a24 Compare June 11, 2026 03:31

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed: root-cause fix verified (optimistic mixer gain cache + status-echo reconciliation), WheelSliceAudio plumbed through all enumeration sites, CI green. Approving for squash merge.

@jensenpat jensenpat merged commit 02fe8e2 into aethersdr:main Jun 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants