Skip to content

[ux] Fix Flex compression meter derivation#1992

Merged
ten9876 merged 2 commits intoten9876:mainfrom
jensenpat:aether/flex-compression-meter-families
Apr 26, 2026
Merged

[ux] Fix Flex compression meter derivation#1992
ten9876 merged 2 commits intoten9876:mainfrom
jensenpat:aether/flex-compression-meter-families

Conversation

@jensenpat
Copy link
Copy Markdown
Collaborator

@jensenpat jensenpat commented Apr 26, 2026

Summary

This PR updates the Phone/CW compression meter data path to derive SmartSDR-style compression from radio TX meter pairs instead of displaying raw COMPPEAK level data.

  • FLEX-8000 series: display_db = -clamp(max(0, COMPPEAK - AFTEREQ), 0, 25)
  • FLEX-6600 / 6000-series when AFTEREQ is absent: display_db = -clamp(max(0, COMPPEAK - SC_MIC), 0, 25)
  • Meter selection now keys on TX meter source + name, not numeric IDs, because IDs are dynamic across sessions and radio families.
  • No local PC mic fallback and no raw COMPPEAK fallback are used; if the required radio-side pair is missing or stale, MeterModel marks the compression value unavailable and emits 0 dB to preserve the existing gauge presentation.
  • Phone/CW level meter behavior and UI layout are intentionally unchanged.

Investigation Notes

The 8000-series captures and SmartSDR comparison showed that COMPPEAK is a dBFS signal-level tap near the speech processor/clipper stage, not the value SmartSDR renders directly as compression. Comparing COMPPEAK against the post-EQ reference meter AFTEREQ produced the matching compression scale on the 8400M/8000-series path.

The FLEX-6600 meter manifest supplied by testing did not expose AFTEREQ. Its relevant TX meters were SC_MIC and COMPPEAK; using COMPPEAK - SC_MIC is the best-supported 6000-series model from the captures. SC_MIC advertises 10 fps while COMPPEAK advertises 20 fps, so the implementation includes a freshness guard to avoid comparing a fresh processor sample against a stale reference sample.

The two-tone testing also confirmed why the compression meter must be radio-meter derived: radio-generated two-tone RF can be active while local PC microphone meters still move, so local mic activity is not a reliable indicator of transmitted compression.

I'd like to recognize Randy AG4Q for his valuable assistance in testing, calibrating and debugging meters on the 6600. Thank you, Randy.

Documentation

  • Adds docs/flex-meter-learnings.md with the capture-backed meter rules, family-specific formulas, FPS table, two-tone notes.
  • Updates docs/tx-audio-signal-path.md to link the meter findings and document how AFTEREQ, SC_MIC, and COMPPEAK are used.

Validation

  • git diff --check origin/main
  • cmake --build build --target meter_model_test --parallel 10
  • ./build/meter_model_test
  • cmake --build build --target AetherSDR --parallel 10

Built app: /Users/patj/.codex/worktrees/be95/AetherSDR/build/AetherSDR.app

👨🏼‍💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat

@jensenpat jensenpat changed the title Fix Flex compression meter derivation [ux] Fix Flex compression meter derivation Apr 26, 2026
@jensenpat jensenpat marked this pull request as ready for review April 26, 2026 03:01
@jensenpat jensenpat requested a review from ten9876 as a code owner April 26, 2026 03:01
Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

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

Nice work on this, @jensenpat. The investigation is thorough, the documentation is excellent, and the dual-family meter derivation approach is well-reasoned. A few observations:

Looks good

  • Keying on source + name instead of numeric IDs is the right call. This has been a latent fragility.
  • Freshness guard (compressionSamplesFresh) for the mixed-cadence 6600 path (10 fps SC_MIC vs 20 fps COMPPEAK) is a nice detail — kCompressionReferenceMaxSkewMs = 250 gives ~2.5x the slower meter's period, which is reasonable.
  • packetUpdatedMs captured once per updateValues call avoids clock skew within a single VITA-49 packet. Good.
  • Test coverage for the 8000-series path, the 6600 fallback, meter removal, and freshness rejection is solid.
  • Documentation in flex-meter-learnings.md is capture-backed and clearly separates what's confirmed vs. what's still open. The FPS table will save future contributors a lot of time.

Items worth a look

  1. Stale consumer comments: SMeterWidget.cpp:99 and PhoneCwApplet.cpp:828 both say "compPeak is raw dBFS from COMPPEAK" with a silence gate at -30 dBFS. With this PR, compPeak() now returns values in [-25, 0] (derived compression reduction), so the gate is harmless but the comments are misleading. Since the PR intentionally doesn't touch those files, this is fine to defer — but worth a follow-up cleanup so the next person reading those consumers isn't confused about the semantic change.

  2. Redundant m_compPeak = 0.0f in removeMeter for m_afterEqIdx: Lines set m_compPeak = 0.0f and then immediately call updateCompressionReduction(), which will also set m_compPeak. Not a bug, just dead code — updateCompressionReduction() handles all paths.

  3. Removed EMA smoothing: The old code applied kAlpha = 0.3f exponential smoothing to both COMPPEAK and AFTEREQ before computing the delta. The new code uses raw converted values. Since both sides of the subtraction now use raw samples from the same packet timestamp, the subtraction itself should cancel correlated noise. But if the gauge visually jitters more on-air than the old path did, re-adding symmetric smoothing to both inputs (or smoothing the output) would be a straightforward follow-up. Just flagging in case testing shows this.

  4. hasCompressionMeterValue() is public but unused in this PR: The accessor is added to the header, which is fine for future use. If any consumer (e.g., PhoneCwApplet) should use it to show an "unavailable" state instead of 0 dB, that would be a separate UI change.

No issues found

  • No null pointer risks, resource leaks, or RAII concerns.
  • CMake test target follows existing patterns (cw_sidetone_test).
  • All changed files are within the stated scope — docs are directly relevant to the code changes.
  • No use of QSettings (AppSettings convention respected).
  • No Copilot comments to verify.

Thank you for the detailed investigation notes in the PR body and for the shout-out to Randy AG4Q. This is a solid community contribution. 73!

updateCompressionReduction() returned a bool that no caller used; drop
it in favor of void. The idempotent setCompressionMeterAvailable() body
collapses to a single assignment, so inline it at the call sites.

Co-Authored-By: jensenpat <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants