Add RC-28 rotation sensitivity divider and auto-snap to 1 kHz#3875
Conversation
2b7d285 to
74a57b7
Compare
|
Tested on Windows 11 with MSVC 2022. Not yet tested on macOS or Linux. |
74a57b7 to
df33895
Compare
There was a problem hiding this comment.
Thanks for this, @wa2n-code — clean, well-scoped implementation that closely follows the existing RC-28 conventions. The settings go through the nested RC28Mapping JSON blob (Principle V ✅), the accumulator/direction-reversal logic reads clearly, the QTimer is parented to this (no leak), units are handled correctly (frequency() is MHz, so round(f*1000)/1000 snaps to the nearest kHz), and the "take effect immediately" wiring in MainWindow_Menus.cpp is a nice touch. Defaults preserve existing behaviour.
One substantive issue plus a couple of smaller notes:
1. Auto-snap timer bypasses the slice lock (should fix)
Every other tune path guards against a locked slice before tuning — applyFlexControlWheelAction (MainWindow_Controllers.cpp:1333) and the keyboard nudge (MainWindow_Shortcuts.cpp:550) both check s->isLocked() and call notifyTuneBlockedByLock(). The new snap timer calls applyTuneRequest directly with no such guard:
connect(m_hidSnapTimer, &QTimer::timeout, this, [this] {
if (auto* s = activeSlice()) {
if (s->isLocked()) return; // <-- add this
const double snapped = std::round(s->frequency() * 1000.0) / 1000.0;
...Because the timer is started in the tuneSteps handler before applyFlexControlWheelAction runs, turning the knob on a locked slice with auto-snap enabled is blocked from tuning (correct) but still schedules a snap — which then moves the locked slice ~600 ms later. Adding the isLocked() early-return closes that.
2. Snap timer fires regardless of encoder 0's mapped action (minor)
The sensitivity divider and snap-timer start are gated on isRC28Compatible() && encoderIndex == 0, but not on the configured action being frequency tuning. On the RC-28 the main knob is effectively always frequency, so this is fine in practice — but if encoder 0 is ever remapped (e.g. to WheelVolume/WheelRit), the divider would throttle those steps and the snap timer would still snap the frequency. Worth a brief comment noting the assumption, or gating the snap-start on the resolved actionId == "WheelFrequency" if you want to be defensive.
3. Test plan is unchecked — the checkboxes in the PR body are all empty. If you've run through them (especially direction-reversal and the "non-RC-28 devices unaffected" cases), it'd help to note that.
None of these block the concept — item 1 is the only one I'd consider important before merge. Nice work matching the surrounding style.
🤖 aethersdr-agent · cost: $2.0582 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Thanks @wa2n-code — this is a clean, well-scoped feature that follows the RC-28 conventions nicely: settings persist in the nested RC28Mapping JSON (Principle V), the accumulator/direction-reversal logic reads clearly, the QTimer is parented to this (no leak), the MHz→kHz snap math is correct, and defaults preserve existing behaviour. The "take effect immediately" wiring is a nice touch.
One real bug to fix before merge, plus two small notes.
🔴 Auto-snap timer tunes a locked slice (must fix)
Every other tune path guards on s->isLocked() and calls notifyTuneBlockedByLock() — e.g. applyFlexControlWheelAction (MainWindow_Controllers.cpp:1333) and the keyboard nudges (MainWindow_Shortcuts.cpp:550,645). The new snap-timer callback calls applyTuneRequest(...) directly with no such guard.
Because the timer is start()ed in the tuneSteps handler before the live tune runs, turning the knob on a locked slice with auto-snap enabled is correctly blocked from the immediate step — but the snap still fires ~600 ms later and moves the locked slice. A lock should mean "don't move this," so this needs the same guard:
connect(m_hidSnapTimer, &QTimer::timeout, this, [this] {
if (auto* s = activeSlice()) {
if (s->isLocked()) return; // ← add: respect the slice lock
const double snapped = std::round(s->frequency() * 1000.0) / 1000.0;
if (std::abs(snapped - s->frequency()) > 1e-9)
applyTuneRequest(s, snapped, TuneIntent::IncrementalTune, "rc28-autosnap");
}
});🟡 Snap fires regardless of encoder 0's mapped action (minor)
The divider + snap-start are gated on isRC28Compatible() && encoderIndex == 0, but not on the configured action being frequency tuning. On the RC-28 the main knob is effectively always frequency, so this is fine in practice — but if encoder 0 is ever remapped (e.g. WheelVolume/WheelRit), the divider would throttle those steps and the snap timer would still snap the frequency. Worth a brief comment noting the assumption, or gating the snap-start on the resolved actionId == "WheelFrequency" if you'd like to be defensive.
🟡 Test plan
The checkboxes in the PR body are all unchecked — if you've run through them (especially direction-reversal mid-spin and "non-RC-28 devices unaffected"), a note confirming would help.
Only the locked-slice guard blocks merge — the rest is solid. Nice work matching the surrounding style; ping me when the guard's in and I'll re-review.
…quency action - Add isLocked() early-return in the auto-snap timer callback so a locked slice cannot be moved by the 600 ms deferred snap (addresses must-fix from PR aethersdr#3875 review). - Move actionId lookup before the RC-28 sensitivity/snap block and gate both the divider and snap-timer start on actionId == "WheelFrequency", so remapping encoder 0 to WheelVolume/WheelRit etc. neither throttles those steps nor triggers a stale frequency snap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Both review items addressed in 9c20592: 🔴 Auto-snap timer tunes a locked slice — Added 🟡 Snap fires regardless of encoder 0's mapped action — Moved the Test plan checkboxes are all checked — tested direction-reversal mid-spin, non-RC-28 devices unaffected, and auto-snap on/off behaviour confirmed. |
…dr#3841) Adds two tuning aids to the RC-28 Mapping dialog's Encoder section: - Pulses per step slider (1–10): accumulates raw encoder pulses before firing one frequency step, reducing twitchiness with large step sizes (e.g. 500 Hz / 1 kHz). Direction reversal clears the accumulator. Stored as rc28MappingField("sensitivity"); defaults to 1 (no change). - Auto-snap checkbox: a 600 ms single-shot timer restarts on every RC-28 encoder event. When the knob stops, it snaps to the nearest 1 kHz via TuneIntent::IncrementalTune so the spectrum display does not recenter. Stored as rc28MappingField("autoSnap"); off by default. Both settings are read at startup and live-updated via mappingFieldChanged without requiring a restart. Principle V. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…quency action - Add isLocked() early-return in the auto-snap timer callback so a locked slice cannot be moved by the 600 ms deferred snap (addresses must-fix from PR aethersdr#3875 review). - Move actionId lookup before the RC-28 sensitivity/snap block and gate both the divider and snap-timer start on actionId == "WheelFrequency", so remapping encoder 0 to WheelVolume/WheelRit etc. neither throttles those steps nor triggers a stale frequency snap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9c20592 to
b4bccfd
Compare
ten9876
left a comment
There was a problem hiding this comment.
Re-reviewed b4bccfd — both points addressed, thanks @wa2n-code:
- ✅ The snap timer now respects the slice lock (
if (s->isLocked()) return;), so a locked slice is no longer tuned after the knob stops — the blocker from my last review. - ✅ The divider + snap-start are gated on
actionId == "WheelFrequency"(and actionId is resolved before the block), so a remapped encoder 0 won't be throttled or snap the frequency.
Everything else still holds (Principle V JSON, accumulator/direction-reversal, no-leak QTimer, correct MHz→kHz units, defaults preserve behavior). Not a keying path. CI green on all 6. Nice responsiveness to review on a first contribution — LGTM.
Fixes #3841
Summary
Pulses per step slider (1–10) in the RC-28 Mapping dialog Encoder section. Accumulates raw encoder pulses before firing one frequency step, so large step sizes (500 Hz / 1 kHz) require more physical rotation to advance. Direction reversal clears the accumulator. Defaults to 1 (existing behaviour unchanged).
Auto-snap to nearest 1 kHz checkbox in the same section. A 600 ms single-shot timer restarts on every encoder event; when the knob stops, it snaps to the nearest 1 kHz using
TuneIntent::IncrementalTune— the spectrum display does not recenter. Off by default.Both settings are stored in the existing
RC28MappingJSON blob (Principle V) and take effect immediately when changed in the dialog without requiring a restart.Test plan
🤖 Generated with Claude Code