Skip to content

fix(audio): suppress mic capture while TCI client feeds TX audio#2982

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
nigelfenton:fix/tci-audio-mic-collision
May 23, 2026
Merged

fix(audio): suppress mic capture while TCI client feeds TX audio#2982
ten9876 merged 1 commit into
aethersdr:mainfrom
nigelfenton:fix/tci-audio-mic-collision

Conversation

@nigelfenton

Copy link
Copy Markdown
Contributor

Problem

When a slice's mic_selection=PC AND a TCI client (TCI Monitor, WSJT-X, JTDX) is sending binary TX audio, two packet producers compete for the same UDP path to the radio:

  • AudioEngine::onTxAudioReady — local mic capture via QAudioSource → emits txPacketReady
  • AudioEngine::feedDaxTxAudio — TCI binary audio → emits txPacketReady

If the host's default audio input is a real microphone producing samples (the common case on macOS where CoreAudio defaults to a webcam mic), the mic-derived packets (~5 ms cadence) drown out the TCI tone (~50 ms cadence).

End-to-end symptom

A TCI client's calibration sweep keys the radio, tx_sensors flows back — but ALC stays pinned at the host mic's noise floor regardless of tx_gain, and forward power stays at ~0 W.

Caught while building a TX-drive calibration panel against AetherSDR on macOS:

  • Mic permission denied (terminal-launched AE, no TCC grant): no audio at all reached the modulator → clean -150 dBFS across the whole sweep.
  • Mic permission granted (normal launch): ALC pinned at ~-38 dBFS — exactly the level of room ambient picked up by the webcam mic. Coughing produced fwd-power spikes that confirmed mic-derived packets were reaching the modulator while TCI packets were being lost in the stream.

Linux escapes this only by accident — on hosts with no usable default audio input, QAudioSource produces silence so there's no competition.

Fix

feedDaxTxAudio stamps a QElapsedTimer on every frame. In onTxAudioReady, if that timer is fresh (< 200 ms, comfortably wider than the 50 ms TCI cadence), early-return so the mic samples are dropped while TCI audio is flowing. No platform branch — correct on every host.

When the TCI client stops sending audio (timer goes stale), the mic-capture path resumes normally.

Verified

  • macOS (Mac mini Apple Silicon, Qt 6.11): mic suppression eliminates the false TCI baseline; calibration sweep then produces a clean monotonic ALC-vs-tx_gain curve into a Flex 6300.
  • Linux (Ubuntu 24.04, Qt 6.4.2): no behavior change (no real mic samples to suppress on the test rig).
  • Windows (Qt 6.10.3): builds clean; equivalent behavior expected (same code path, no platform branches).

Notes

Draft for now — opened so it doesn't disappear into a personal fork branch. Happy to ready-for-review once reviewers want a look. The TX-drive calibration tool that surfaced this is a separate standalone (TCI Monitor — github.com/nigelfenton/tci-monitor) and is not being proposed for inclusion in AetherSDR.

When a slice's mic_selection=PC and a TCI client is sending binary
TX audio, two packet producers compete for the same UDP path to
the radio:
  - onTxAudioReady (local mic capture via QAudioSource -> txPacketReady)
  - feedDaxTxAudio (TCI binary audio -> txPacketReady)

If the host's default audio input is a real mic producing samples,
the mic-derived packets (~5 ms cadence) drown out the TCI tone
(~50 ms). End-to-end symptom: a TCI client's calibration sweep keys
the radio, tx_sensors flows back, but ALC stays pinned at the mic's
noise floor regardless of tx_gain and fwd ~ 0 W.

Most visible on macOS where the CoreAudio default input is a real
mic (webcam etc.). Linux only escapes by accident on hosts with no
usable default input.

Fix: feedDaxTxAudio stamps a QElapsedTimer on every frame; in
onTxAudioReady, if that timer is fresh (<200 ms, comfortably wider
than the 50 ms TCI cadence), early-return so the mic samples are
dropped while TCI audio is flowing. No platform branch -- correct
on every host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 marked this pull request as ready for review May 23, 2026 06:25
@ten9876 ten9876 requested review from jensenpat and ten9876 as code owners May 23, 2026 06:25
@ten9876 ten9876 merged commit 7f87cb6 into aethersdr:main May 23, 2026
5 checks passed
@ten9876

ten9876 commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Merged — thanks @nigelfenton! The bug-report writeup itself is the kind of competing-producer diagnosis that's worth keeping as project lore: the side-by-side TCC-permission-denied (clean -150 dBFS across the sweep) vs TCC-granted (-38 dBFS pinned to room ambient + cough-spikes on forward power) is exactly how you nail a packet collision, and the "Linux escapes only by accident" insight about QAudioSource producing silence on hosts with no usable default input is a great catch.

The QElapsedTimer gate at the producer boundary (rather than a platform branch or a mic_selection-aware gate) is the right factoring — invisible to the radio's protocol, recovers automatically when TCI stops, and the 200 ms window over the 50 ms TCI cadence has comfortable margin without locking the mic out long after TCI actually drops.

Cool to hear about tci-monitor too — TX-drive calibration is a recurring gap. If you ever want to drop a line in our docs about it as a companion tool, happy to merge a docs PR pointing users at it.

Shipping in the next release.

73,
Jeremy KK7GWY & Claude (AI dev partner)

ten9876 added a commit that referenced this pull request May 24, 2026
… + @G6PWY-Chris (#3067)

Two attribution errors in the v26.5.3 release CHANGELOG:

1. @M7HNF-Ian (Ian, separate GitHub account) → @nigelfenton (Nigel
   Fenton, G0JKN) — Nigel authored all 5 of the commits I bucketed
   under that line: TCI Network dialog tab (#2879), tx_gain + ALC
   (#2950), use-after-free fix (#2946), vfo emit on band-change
   (#2828), mic-capture suppress during TCI feed (#2982).

2. @chrisb1964 → @G6PWY-Chris (Chris G6PWY) — single commit author
   in the cycle was the latter; chrisb1964 is unrelated.

Reported by Nigel directly.  All other handles verified against
gh api repos/.../commits/<sha>.

GitHub release notes already updated; this lands the same fix in
the in-repo CHANGELOG.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nigelfenton nigelfenton deleted the fix/tci-audio-mic-collision branch May 25, 2026 18:36
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…hersdr#2982)

## Problem

When a slice's `mic_selection=PC` AND a TCI client (TCI Monitor, WSJT-X,
JTDX) is sending binary TX audio, two packet producers compete for the
same UDP path to the radio:

- `AudioEngine::onTxAudioReady` — local mic capture via `QAudioSource` →
emits `txPacketReady`
- `AudioEngine::feedDaxTxAudio` — TCI binary audio → emits
`txPacketReady`

If the host's default audio input is a real microphone producing samples
(the common case on macOS where CoreAudio defaults to a webcam mic), the
mic-derived packets (~5 ms cadence) drown out the TCI tone (~50 ms
cadence).

## End-to-end symptom

A TCI client's calibration sweep keys the radio, `tx_sensors` flows back
— **but ALC stays pinned at the host mic's noise floor regardless of
`tx_gain`, and forward power stays at ~0 W.**

Caught while building a TX-drive calibration panel against AetherSDR on
macOS:

- **Mic permission denied** (terminal-launched AE, no TCC grant): no
audio at all reached the modulator → clean `-150 dBFS` across the whole
sweep.
- **Mic permission granted** (normal launch): ALC pinned at `~-38 dBFS`
— exactly the level of room ambient picked up by the webcam mic.
Coughing produced fwd-power spikes that confirmed mic-derived packets
were reaching the modulator while TCI packets were being lost in the
stream.

Linux escapes this only by accident — on hosts with no usable default
audio input, `QAudioSource` produces silence so there's no competition.

## Fix

`feedDaxTxAudio` stamps a `QElapsedTimer` on every frame. In
`onTxAudioReady`, if that timer is fresh (< 200 ms, comfortably wider
than the 50 ms TCI cadence), early-return so the mic samples are dropped
while TCI audio is flowing. No platform branch — correct on every host.

When the TCI client stops sending audio (timer goes stale), the
mic-capture path resumes normally.

## Verified

- **macOS** (Mac mini Apple Silicon, Qt 6.11): mic suppression
eliminates the false TCI baseline; calibration sweep then produces a
clean monotonic ALC-vs-`tx_gain` curve into a Flex 6300.
- **Linux** (Ubuntu 24.04, Qt 6.4.2): no behavior change (no real mic
samples to suppress on the test rig).
- **Windows** (Qt 6.10.3): builds clean; equivalent behavior expected
(same code path, no platform branches).

## Notes

Draft for now — opened so it doesn't disappear into a personal fork
branch. Happy to ready-for-review once reviewers want a look. The
TX-drive calibration tool that surfaced this is a separate standalone
(TCI Monitor — github.com/nigelfenton/tci-monitor) and is **not** being
proposed for inclusion in AetherSDR.

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
… + @G6PWY-Chris (aethersdr#3067)

Two attribution errors in the v26.5.3 release CHANGELOG:

1. @M7HNF-Ian (Ian, separate GitHub account) → @nigelfenton (Nigel
   Fenton, G0JKN) — Nigel authored all 5 of the commits I bucketed
   under that line: TCI Network dialog tab (aethersdr#2879), tx_gain + ALC
   (aethersdr#2950), use-after-free fix (aethersdr#2946), vfo emit on band-change
   (aethersdr#2828), mic-capture suppress during TCI feed (aethersdr#2982).

2. @chrisb1964 → @G6PWY-Chris (Chris G6PWY) — single commit author
   in the cycle was the latter; chrisb1964 is unrelated.

Reported by Nigel directly.  All other handles verified against
gh api repos/.../commits/<sha>.

GitHub release notes already updated; this lands the same fix in
the in-repo CHANGELOG.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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.

2 participants