Skip to content

feat(audio): v26.5.3 — PAPR processor, split-band de-esser, peak hold#3024

Merged
ten9876 merged 3 commits into
mainfrom
feat/2887-phrot
May 24, 2026
Merged

feat(audio): v26.5.3 — PAPR processor, split-band de-esser, peak hold#3024
ten9876 merged 3 commits into
mainfrom
feat/2887-phrot

Conversation

@ten9876

@ten9876 ten9876 commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three threads landed together. The de-esser fix is the release-critical part (multiple operators reporting RMS / forward-power loss vs SmartSDR — root-caused to this stage). The PAPR processor (#2887) and the meter-UI polish came along for the ride during the same bench session.

Bumps version to 26.5.3.

The de-esser bug (release-critical)

ClientDeEss::process() was applying its sidechain-derived gain reduction to the full signal every time HF energy crossed threshold:

// OLD — broadband attenuation when sibilance triggers:
l *= gainLin;
r *= gainLin;

So every S / T / F phoneme pulled lows and mids down alongside the highs, crashing RMS. SmartSDR doesn't have this stage at all, which is why operators saw lower average power on AetherSDR than SmartSDR at the same drive setting (Antonio D'Arpino's report on the AetherSDR Users Group, plus matching symptoms from others).

Fix: split-band topology — output = full + bandpass × (gain − 1). The bandpass acts as both the sidechain detector and the slice to attenuate; lows and mids pass through unchanged.

Also adds:

  • Cascaded biquads (1–4 stages) so the user can dial slope from 12 → 24 → 36 → 48 dB/oct via a new toggle in the de-esser panel. Default 24 dB/oct (2 stages). Narrower notch = less mid-band collateral on Ess-heavy phrases.
  • AppSettings persistence: ClientDeEssTxSlopeStages / ClientDeEssRxSlopeStages.

PAPR processor (#2887)

New ClientPhaseRotator module — cascade of all-pass biquads that symmetrizes asymmetric voice peaks before downstream compression/limiting, so the clipping work produces primarily odd-order (musical) harmonics instead of even-order (buzzy) ones.

Embedded inside ClientComp as a private member, exposed via two new controls in the comp panel:

  • Drive (0..18 dB) — pre-gain into the comp threshold. Pushes more material across the curve so the comp engages harder, lifting RMS.
  • Phase (0..6 stages) — pre-comp rotator stage count.

The per-sample gain auto-tracks Drive (gainLin = curve × makeup × driveLin), so Drive doesn't fight the comp's natural GR response — same model as broadcast Optimod. Drive and Phase apply regardless of the comp's chain-bypass state.

Bench result: with Drive +9 dB / Phase 4 stg, CRST drops from ~12 dB to ~8 dB cleanly, peaks pegged at the limiter ceiling, no audible squash.

UI polish

  • Comp GR + Out meters — now render THRESH-style tick columns (Left/Right) and a right-anchored dB-value footer, so all three vertical meters in the comp editor read with one visual vocabulary.
  • Final Output Stage peak-hold toggle — latches PK/RMS/GR worst-case-since-engaged for capturing burst maxima. CRST always tracks live so adjustments visibly land in real time.
  • 10 Hz numeric-readout throttle — new shared kMeterReadoutUpdateMs constant in MeterSmoother.h. Applied to Final Output Stage, comp GR/Out meters, Tube OUT meter, and RX chain output stage. Bars still animate at 125 Hz, only text setText is gated to 10 Hz so digits are readable.

Stats

  • 22 files changed, +898 / −50
  • 2 new source files (ClientPhaseRotator.{h,cpp})
  • DSP changes covered by manual hardware test (Jeremy KK7GWY's bench)
  • No new tests added for the new DSPs — should follow up in a Phase 2 PR

Test plan

  • Clean build, 0 errors
  • Manual: Drive 0..+18 dB lifts RMS as expected, GR meter responds
  • Manual: Phase toggle audibly clean under heavy Drive
  • Manual: De-esser no longer crashes RMS on sibilant phrases
  • Manual: Slope toggle cycles 12 → 24 → 36 → 48 dB/oct
  • Manual: Peak hold latches worst-case; CRST tracks live
  • Manual: Meter readouts readable at 10 Hz, bars smooth at 125 Hz
  • CI: build, check-paths, check-windows green before merge

Operator workaround for pre-v26.5.3 builds

For anyone on v26.5.2 hitting the de-esser bug: disable the DESS card in the channel strip CHAIN row (single click to bypass). Forward power should recover immediately. v26.5.3 makes the fix permanent.

Closes #2887.

🤖 Generated with Claude Code

Adds Optimod-style PAPR-reduction processing to the TX audio path,
fixes a bug in the de-esser that was silently crashing average power,
and polishes the meter UI to make tuning the new controls feasible.

## DSP changes

**ClientPhaseRotator** (new) — cascade of N second-order all-pass
biquads at staggered audio centres (300/700/1500/2500 Hz, +1k/2k
spares). Symmetrizes asymmetric voice peaks so downstream compression
and limiting produce primarily odd-order (musical) harmonics instead
of even-order (buzzy) ones. Same atomics + version-counter pattern as
the other client DSPs. No locks, no allocations in process().

**ClientComp** — embeds ClientPhaseRotator as a private member and
adds two new pre-comp controls (#2887):
  - Drive (0..18 dB) — pre-gain into the comp threshold so more
    material crosses the curve, raising RMS.
  - Phase (0..6 stages) — pre-comp phase rotator stage count.
Drive and Phase apply regardless of the comp's enabled flag (an
expression of intent independent of the curve itself). The per-sample
gain auto-tracks Drive (gainLin = curve × makeup × driveLin), so
dialling Drive up doesn't get eaten by the comp's natural GR
response — the broadcast-Optimod model.

**ClientDeEss** — fixes the broadband-attenuation bug that was
crashing RMS during sibilance. The old code applied the sidechain
gain reduction to the FULL signal, pulling down lows + mids
alongside the highs every time S/T/F sounds triggered. Now uses a
split-band topology: output = full + bp × (gain − 1), so only the
bandpass slice gets attenuated and lows/mids pass through clean.

Also adds a user-selectable slope-stages toggle (1..4 cascaded
bandpass biquads = 12/24/36/48 dB/oct) so the operator can dial in
how tight the sibilant notch is. Defaults to 24 dB/oct (2 stages).

## UI changes

- Drive + Phase knobs in the comp panel's right column (after Makeup)
- Slope-cycle button in the de-esser panel's left column, bottom-
  aligned, dimensions match the LIMIT chip
- GR and Out meters in the comp panel now render THRESH-style tick
  columns (Left/Right) and a right-anchored dB-value footer for
  visual parity with the input meter
- Peak-hold toggle in the Final Output Stage latches PK/RMS/GR worst-
  case-since-engage; CRST always tracks live so adjustments visibly
  land in real time
- Numeric meter readouts (PK/RMS/GR/CRST etc.) throttled to 10 Hz
  via shared kMeterReadoutUpdateMs in MeterSmoother.h; bars continue
  to animate at the underlying 125 Hz smoother rate. Applied to
  Final Output Stage, comp GR/Out meters, Tube OUT meter, and RX
  chain output stage so the cadence is consistent across the strip.

## Why this matters

Multiple users (Antonio D'Arpino on the AetherSDR Users Group; same
symptoms reported by other operators) saw "less forward power vs
SmartSDR" — average TX power 60-80% of expected, voice sounding
muted. Root cause was the broadband de-esser pulling RMS down on
every sibilant phoneme. The fix recovers the lost RMS without
changing user settings; existing operators just need to relaunch.

The Drive + Phase + auto-makeup combo is the path forward for ops
who want to push closer to broadcast loudness (CRST 8-10 dB) without
nasty clipping artifacts.

Closes #2887.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 requested a review from jensenpat as a code owner May 24, 2026 00:21
@ten9876 ten9876 enabled auto-merge (squash) May 24, 2026 00:21
ten9876 and others added 2 commits May 23, 2026 17:37
Two failures in PR #3024's CI run:

1. client_comp_test failed to link — the new ClientPhaseRotator.cpp
   was added to the main AetherSDR target in CMakeLists.txt but not
   to the comp test target, so unresolved symbols for the embedded
   rotator constructor / setEnabled / prepare / process / setStages.
   Add the new source to client_comp_test as well.

2. check-windows failed to compile ClientPhaseRotator.cpp — MSVC
   does not define M_PI in <cmath> without _USE_MATH_DEFINES.
   Replace with a constexpr literal so the math is portable across
   gcc/clang/MSVC without depending on platform-specific macros.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 merged commit c19a802 into main May 24, 2026
5 checks passed
@ten9876 ten9876 deleted the feat/2887-phrot branch May 24, 2026 01:33
ten9876 added a commit that referenced this pull request May 24, 2026
## Summary

- Adds `src/gui/MainWindow.cpp` and `src/gui/MainWindow.h` to the
`dorny/paths-filter` `build` group so the Windows CI job runs whenever
the hot file changes.
- The file is densely platform-guarded (`#ifdef Q_OS_WIN`, `Q_OS_MAC`,
`HAVE_PIPEWIRE`), so Linux-passing edits can still break MSVC.

## Motivation

Same class of failure has bitten us repeatedly:
- **#2633#2662** — unconditional `m_daxBridge` reference inside a
macOS-only guard, caught post-release.
- **#2670** — Windows behaviour change not exercised by CI.
- **v26.5.3 (#3024)** — `ClientPhaseRotator.cpp` bare `M_PI` broke MSVC
(needs `_USE_MATH_DEFINES`). Different file, same shape: path not on the
filter → `check-windows` didn't run → broken Windows build landed and
required a follow-up patch.

This PR closes the gap for the highest-leverage file. Other hot
platform-guarded files can be added later if their breakage rate
justifies the runner time.

## Cost

Most PRs continue to skip `check-windows` (the filter still excludes
most source paths). Cost increase is bounded to the subset of PRs that
touch MainWindow.cpp/.h, which is exactly the subset that needs the
coverage.

## Test plan

- [ ] CI: `check-paths` job runs and reports `build-changed=true` on
this PR (because `.github/workflows/**` is itself a watched path).
- [ ] CI: `check-windows` job runs and passes (no source changed, so it
should be a clean cache hit).
- [ ] After merge: next PR that touches `src/gui/MainWindow.cpp`
triggers `check-windows` automatically.

Closes #2671.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request May 24, 2026
## Summary

Release prep for v26.5.3 — Aetherial Audio TX completion + security
hardening + 100-commit reliability sweep.

- **CHANGELOG.md** — new v26.5.3 section: 138 commits across 14
contributors since v26.5.2.1
- **README.md** — version line bump 26.5.2.1 → 26.5.3
- **CMakeLists.txt** — already at 26.5.3 (set in #3024)

## Highlights

- **Aetherial Audio TX completion** — PAPR all-pass cascade + split-band
DESS, full per-stage CHAIN UI
- **Security advisories** — GHSA-wfx7-w6p8-4jr2 (WAN cert-pin Phase 2,
#3026) + GHSA-qxhr-cwrc-pvrm (CAT PTY symlink, #3027)
- **MQTT settings dialog refactor** (#3051, @s53zo) — JSON-array topics
+ button-event lists, nested-JSON consolidation
- **Audio reliability sweep** — WASAPI silent-open watchdog, mono-only
USB PnP mic recovery, MQTT subscribe diff
- **Native Hamlib NET rigctl** — eliminates external rigctld dependency
- **Stream Deck integration** — Elgato SDK plugin
(Win/Mac/Linux-via-OpenDeck)

## Contributors thanked

@jensenpat (16), @aethersdr-agent (22 bot), @NF0T (12), @rfoust (9),
@Ozy311 (5), @M7HNF-Ian (5), @chibondking (5), @M8WLO (4), @s53zo (2),
@pepefrog1234 (2), @K5PTB (2), @chrisb1964 (1)

## Test plan

- [ ] CI green (build + check-paths + check-windows)
- [ ] CHANGELOG renders correctly on GitHub
- [ ] README version banner shows 26.5.3
- [ ] After merge: tag v26.5.3 once CI green on main

73, Jeremy KK7GWY & Claude (AI dev partner)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…aethersdr#3024)

## Summary

Three threads landed together. The de-esser fix is the
**release-critical** part (multiple operators reporting RMS /
forward-power loss vs SmartSDR — root-caused to this stage). The PAPR
processor (aethersdr#2887) and the meter-UI polish came along for the ride during
the same bench session.

Bumps version to **26.5.3**.

## The de-esser bug (release-critical)

`ClientDeEss::process()` was applying its sidechain-derived gain
reduction to the **full signal** every time HF energy crossed threshold:

```cpp
// OLD — broadband attenuation when sibilance triggers:
l *= gainLin;
r *= gainLin;
```

So every S / T / F phoneme pulled lows and mids down alongside the
highs, crashing RMS. SmartSDR doesn't have this stage at all, which is
why operators saw lower average power on AetherSDR than SmartSDR at the
same drive setting (Antonio D'Arpino's report on the AetherSDR Users
Group, plus matching symptoms from others).

**Fix**: split-band topology — `output = full + bandpass × (gain − 1)`.
The bandpass acts as both the sidechain detector and the slice to
attenuate; lows and mids pass through unchanged.

Also adds:
- **Cascaded biquads** (1–4 stages) so the user can dial slope from 12 →
24 → 36 → 48 dB/oct via a new toggle in the de-esser panel. Default 24
dB/oct (2 stages). Narrower notch = less mid-band collateral on
Ess-heavy phrases.
- AppSettings persistence: `ClientDeEssTxSlopeStages` /
`ClientDeEssRxSlopeStages`.

## PAPR processor (aethersdr#2887)

New **ClientPhaseRotator** module — cascade of all-pass biquads that
symmetrizes asymmetric voice peaks before downstream
compression/limiting, so the clipping work produces primarily odd-order
(musical) harmonics instead of even-order (buzzy) ones.

Embedded inside **ClientComp** as a private member, exposed via two new
controls in the comp panel:

- **Drive** (0..18 dB) — pre-gain into the comp threshold. Pushes more
material across the curve so the comp engages harder, lifting RMS.
- **Phase** (0..6 stages) — pre-comp rotator stage count.

The per-sample gain auto-tracks Drive (`gainLin = curve × makeup ×
driveLin`), so Drive doesn't fight the comp's natural GR response — same
model as broadcast Optimod. Drive and Phase apply regardless of the
comp's chain-bypass state.

Bench result: with Drive +9 dB / Phase 4 stg, CRST drops from ~12 dB to
~8 dB cleanly, peaks pegged at the limiter ceiling, no audible squash.

## UI polish

- **Comp GR + Out meters** — now render THRESH-style tick columns
(Left/Right) and a right-anchored dB-value footer, so all three vertical
meters in the comp editor read with one visual vocabulary.
- **Final Output Stage peak-hold toggle** — latches PK/RMS/GR
worst-case-since-engaged for capturing burst maxima. CRST always tracks
live so adjustments visibly land in real time.
- **10 Hz numeric-readout throttle** — new shared
`kMeterReadoutUpdateMs` constant in `MeterSmoother.h`. Applied to Final
Output Stage, comp GR/Out meters, Tube OUT meter, and RX chain output
stage. Bars still animate at 125 Hz, only text setText is gated to 10 Hz
so digits are readable.

## Stats

- 22 files changed, +898 / −50
- 2 new source files (`ClientPhaseRotator.{h,cpp}`)
- DSP changes covered by manual hardware test (Jeremy KK7GWY's bench)
- No new tests added for the new DSPs — should follow up in a Phase 2 PR

## Test plan

- [x] Clean build, 0 errors
- [x] Manual: Drive 0..+18 dB lifts RMS as expected, GR meter responds
- [x] Manual: Phase toggle audibly clean under heavy Drive
- [x] Manual: De-esser no longer crashes RMS on sibilant phrases
- [x] Manual: Slope toggle cycles 12 → 24 → 36 → 48 dB/oct
- [x] Manual: Peak hold latches worst-case; CRST tracks live
- [x] Manual: Meter readouts readable at 10 Hz, bars smooth at 125 Hz
- [ ] CI: build, check-paths, check-windows green before merge

## Operator workaround for pre-v26.5.3 builds

For anyone on v26.5.2 hitting the de-esser bug: **disable the DESS
card** in the channel strip CHAIN row (single click to bypass). Forward
power should recover immediately. v26.5.3 makes the fix permanent.

Closes aethersdr#2887.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
… (aethersdr#3028)

## Summary

- Adds `src/gui/MainWindow.cpp` and `src/gui/MainWindow.h` to the
`dorny/paths-filter` `build` group so the Windows CI job runs whenever
the hot file changes.
- The file is densely platform-guarded (`#ifdef Q_OS_WIN`, `Q_OS_MAC`,
`HAVE_PIPEWIRE`), so Linux-passing edits can still break MSVC.

## Motivation

Same class of failure has bitten us repeatedly:
- **aethersdr#2633aethersdr#2662** — unconditional `m_daxBridge` reference inside a
macOS-only guard, caught post-release.
- **aethersdr#2670** — Windows behaviour change not exercised by CI.
- **v26.5.3 (aethersdr#3024)** — `ClientPhaseRotator.cpp` bare `M_PI` broke MSVC
(needs `_USE_MATH_DEFINES`). Different file, same shape: path not on the
filter → `check-windows` didn't run → broken Windows build landed and
required a follow-up patch.

This PR closes the gap for the highest-leverage file. Other hot
platform-guarded files can be added later if their breakage rate
justifies the runner time.

## Cost

Most PRs continue to skip `check-windows` (the filter still excludes
most source paths). Cost increase is bounded to the subset of PRs that
touch MainWindow.cpp/.h, which is exactly the subset that needs the
coverage.

## Test plan

- [ ] CI: `check-paths` job runs and reports `build-changed=true` on
this PR (because `.github/workflows/**` is itself a watched path).
- [ ] CI: `check-windows` job runs and passes (no source changed, so it
should be a clean cache hit).
- [ ] After merge: next PR that touches `src/gui/MainWindow.cpp`
triggers `check-windows` automatically.

Closes aethersdr#2671.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
## Summary

Release prep for v26.5.3 — Aetherial Audio TX completion + security
hardening + 100-commit reliability sweep.

- **CHANGELOG.md** — new v26.5.3 section: 138 commits across 14
contributors since v26.5.2.1
- **README.md** — version line bump 26.5.2.1 → 26.5.3
- **CMakeLists.txt** — already at 26.5.3 (set in aethersdr#3024)

## Highlights

- **Aetherial Audio TX completion** — PAPR all-pass cascade + split-band
DESS, full per-stage CHAIN UI
- **Security advisories** — GHSA-wfx7-w6p8-4jr2 (WAN cert-pin Phase 2,
aethersdr#3026) + GHSA-qxhr-cwrc-pvrm (CAT PTY symlink, aethersdr#3027)
- **MQTT settings dialog refactor** (aethersdr#3051, @s53zo) — JSON-array topics
+ button-event lists, nested-JSON consolidation
- **Audio reliability sweep** — WASAPI silent-open watchdog, mono-only
USB PnP mic recovery, MQTT subscribe diff
- **Native Hamlib NET rigctl** — eliminates external rigctld dependency
- **Stream Deck integration** — Elgato SDK plugin
(Win/Mac/Linux-via-OpenDeck)

## Contributors thanked

@jensenpat (16), @aethersdr-agent (22 bot), @NF0T (12), @rfoust (9),
@Ozy311 (5), @M7HNF-Ian (5), @chibondking (5), @M8WLO (4), @s53zo (2),
@pepefrog1234 (2), @K5PTB (2), @chrisb1964 (1)

## Test plan

- [ ] CI green (build + check-paths + check-windows)
- [ ] CHANGELOG renders correctly on GitHub
- [ ] README version banner shows 26.5.3
- [ ] After merge: tag v26.5.3 once CI green on main

73, Jeremy KK7GWY & Claude (AI dev partner)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Add PAPR-reduction phase rotator as a pre-compressor TX chain stage

1 participant