fix(flexcontrol): snap encoder accumulator to step grid and reset on absolute tune (#3260). Principle XI.#3267
Conversation
…absolute tune (#3260). Principle XI. FlexControl knob produced a residual +60 Hz offset on every tune step on macOS (and silently on Windows/Linux). Root cause is the persistent encoder target m_flexTargetMhz interacting with the #1524 1 kHz jitter-suppression tolerance plus the lack of step-grid alignment when re-initializing the base. Three coordinated edits in MainWindow.cpp, all mirroring patterns that already work on the MIDI tune-knob path: 1. applyFlexControlWheelAction: when (re)initializing m_flexTargetMhz from s->frequency(), snap to the nearest step-Hz multiple instead of copying verbatim. This is the same `(curHz + stepHz/2) / stepHz * stepHz` snap the MIDI rx.tuneKnob path uses, so encoder ticks land on clean grid values even when the slice is off-grid. 2. RxApplet::stepSizeChanged hook: invalidate m_flexTargetMhz, m_midiTuneTargetMhz and stop their coalesce/idle timers when the user changes step size. This forces the next encoder tick to rebase + re-snap against the new step grid (va3mw's repro: step 20 Hz → 500 Hz then turn knob). 3. applyTuneRequest: on CommandedTargetCenter and AbsoluteJump intents (typed VFO entry, spectrum click, spot/bandstack recall), reset encoder accumulators and stop their timers. The #1524 1 kHz tolerance otherwise keeps a stale m_flexTargetMhz alive whenever the typed frequency lands inside that window, producing the OP repro (type 7.188.000 with stale base 7.187.960 + 100 Hz step → 7.188.060). The #1524 jitter-suppression behavior during sustained knob spinning is preserved: incremental encoder ticks still skip the reset path and the 1 kHz tolerance still gates mid-tune rebases from lagging radio echoes. Blast radius: applyFlexControlWheelAction risk_score=0.4587, 11 high-risk affected (top: MainWindow::MainWindow, AppSettings::instance, AppSettings::value). All downstream calls are existing member-variable reads/writes (m_flexTargetMhz, m_flexCoalesceTimer, m_midiTuneTargetMhz, m_midiTuneIdleTimer); no signatures, link surface, or call graph topology changed. Upstream callers — handleFlexControlTuneSteps and handleVirtualFlexControlWheel — are unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jensenpat
left a comment
There was a problem hiding this comment.
Snap to step grid like MIDI wheel path to prevent VFO drift.
There was a problem hiding this comment.
Reviewed the diff against #3260 and the surrounding tuning paths. The fix is well-targeted:
- Encoder-base snap in
applyFlexControlWheelActionmirrors the existing MIDI tune-knob path atsrc/gui/MainWindow.cpp:3895-3902byte-for-byte (same((curHz + stepHz/2) / stepHz) * stepHzround-to-nearest). Consistent with the codebase's existing pattern, and guarded onstepHz > 0. - Step-size handler invalidation at
MainWindow.cpp:3325correctly resets bothm_flexTargetMhzandm_midiTuneTargetMhz(underHAVE_MIDI). Stopping the coalesce/idle timers along with the sentinel reset is the right thing — otherwise the next tick within the coalesce window would skip the re-snap. applyTuneRequestinvalidation correctly limits the reset toCommandedTargetCenterandAbsoluteJumpintents and leavesIncrementalTune(the encoder's own intent) alone, so the encoder doesn't clobber its own accumulator on each tick. The comment ties the change back to the #1524 1 kHz jitter-suppression tolerance, which is the actual mechanism that preserved the stale residual — accurate diagnosis.
The +60 Hz reproduction in #3260 fits the explanation: typing 7.188.000 landed inside the 1 kHz tolerance of a previously-accumulated off-grid m_flexTargetMhz, so the next encoder tick added steps * stepHz to the stale base instead of the freshly typed frequency. Invalidating on absolute intents fixes this directly.
No conventions issues (AppSettings used correctly, RAII fine, no allocations). Scope is contained to src/gui/MainWindow.cpp as advertised.
Thanks @aethersdr-agent — clean fix.
🤖 aethersdr-agent · cost: $7.9267 · model: claude-opus-4-7
…absolute tune (aethersdr#3260). Principle XI. (aethersdr#3267) ## Summary Fixes aethersdr#3260 ### What was changed fix(flexcontrol): snap encoder accumulator to step grid and reset on absolute tune (aethersdr#3260). Principle XI. ### Files modified - `src/gui/MainWindow.cpp` ``` src/gui/MainWindow.cpp | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $11.7561 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Fixes #3260
What was changed
fix(flexcontrol): snap encoder accumulator to step grid and reset on absolute tune (#3260). Principle XI.
Files modified
src/gui/MainWindow.cppGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $11.7561 · model: claude-opus-4-7