Skip to content

[codex] Mute KiwiSDR audio during non-FDX transmit#3865

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/kiwi-tx-mute
Jun 27, 2026
Merged

[codex] Mute KiwiSDR audio during non-FDX transmit#3865
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/kiwi-tx-mute

Conversation

@rfoust

@rfoust rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

When Flex full duplex is disabled, mute KiwiSDR audio output while the radio is transmitting, then automatically unmute when TX ends. This mirrors Flex RX audio behavior without pausing or disconnecting Kiwi streams. The mute predicate follows radio-authoritative full-duplex and TX/interlock state, so MOX/PTT, tune, hardware/interlock TX, DAX/TCI-driven TX paths, and RADE EOO holdoff are covered.

Constitution principle honored

Principle II - radio-authoritative live state. The Kiwi mute gate is driven from RadioModel full-duplex and transmit/interlock state rather than persisted client assumptions.

Test plan

  • Local build passes (cmake --build build --target AetherSDR)
  • Existing focused tests pass: ctest --test-dir build -R "kiwi|rade|transmit|audio|automation" --output-on-failure
  • git diff --check
  • python3 tools/check_a11y.py ran; only existing warning-only findings in unrelated GUI files
  • Behavior verified on a real radio via automation bridge:
    • confirmed fullDuplex: false
    • confirmed TX slice txAntenna: ANT1
    • confirmed RF power was 10 W
    • before PTT: kiwiSdrTransmitMuted: false
    • during short ANT1-only PTT: radio transmitting: true, kiwiSdrTransmitMuted: true
    • after PTT off: radio transmitting: false, kiwiSdrTransmitMuted: false

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls - use nested-JSON-under-one-key (Principle V)
  • Code is clean-room - not decompiled, disassembled, or reverse-engineered from a proprietary binary (Principle IV)
  • All meter UI uses MeterSmoother (AGENTS.md convention; N/A, no meter UI touched)
  • Documentation updated if user-visible behavior changed
  • Security-sensitive changes reference a GHSA if applicable (N/A)

@rfoust rfoust marked this pull request as ready for review June 27, 2026 12:35
@rfoust rfoust requested review from a team as code owners June 27, 2026 12:35
Copilot AI review requested due to automatic review settings June 27, 2026 12:35
@rfoust rfoust requested a review from a team as a code owner June 27, 2026 12:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR mutes KiwiSDR audio output whenever the Flex radio is transmitting and full duplex is disabled, mirroring SmartSDR’s RX-audio behavior while keeping Kiwi streams connected. It also extends the automation bridge to expose relevant audio-engine state needed to assert on the new mute gate.

Changes:

  • Add a transmit-driven KiwiSDR mute predicate in MainWindow and synchronize it to AudioEngine (including RADE EOO holdoff integration).
  • Implement an AudioEngine-level “KiwiSDR transmit mute” gate that suppresses mixing/output and clears/prebuffers appropriately.
  • Extend the automation bridge (get audio) and documentation to surface kiwiSdrTransmitMuted plus audio buffer/stream state.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/main.cpp Wires AudioEngine into the automation server.
src/gui/MainWindow.h Exposes audioEngine() and adds KiwiSDR transmit-mute tracking/methods.
src/gui/MainWindow_KiwiSdr.cpp Computes transmit mute requirement and syncs it to AudioEngine; hooks transmit/full-duplex state changes.
src/gui/MainWindow_DigitalModes.cpp Ensures RADE EOO pending/active transitions also drive KiwiSDR transmit mute sync.
src/core/AutomationServer.h Documents new get audio model and adds AudioEngine pointer plumbing.
src/core/AutomationServer.cpp Adds audio snapshot/get handling and includes fullDuplex in radio snapshot.
src/core/AudioEngine.h Adds KiwiSDR transmit mute API and state helpers.
src/core/AudioEngine.cpp Implements transmit mute gating across Kiwi internal + external sources, including buffer/prebuffer/reset behavior.
docs/automation-bridge.md Documents get audio and the added fullDuplex radio field.

Comment thread src/core/AutomationServer.cpp
@rfoust rfoust self-assigned this Jun 27, 2026
@rfoust

rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the Copilot audio-thread review in 619be5de.

AutomationServer::doGet("audio") now builds the AudioEngine snapshot on the audio object thread with Qt::BlockingQueuedConnection, while keeping a direct path when the caller is already on that thread. The original inline anchor is now outdated after the push, so I am leaving this PR-level summary and resolving the thread.

Rechecked locally:

  • cmake --build build --target AetherSDR
  • ctest --test-dir build -R "kiwi|rade|transmit|audio|automation" --output-on-failure
  • git diff --check
  • Visible automation smoke test: get audio and get audio _ kiwiSdrTransmitMuted

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the diff. This is clean, well-scoped work — thanks @rfoust. The mute gate is correctly driven from radio-authoritative full-duplex + TX/interlock state, the signal wiring covers the relevant paths, and the new automation audio snapshot is a nice testability win.

Copilot's data-race concern is valid in premise but already addressed. AudioEngine is moved to a dedicated worker thread (MainWindow.cpp:985), so building the snapshot on the AutomationServer (GUI) thread would indeed read m_audioSink/m_audioSource cross-thread. But the second commit (619be5de) routes through audioSnapshotOnObjectThread(), which formats the JSON on the AudioEngine's own thread via Qt::BlockingQueuedConnection and only calls inline when already on that thread (correctly avoiding self-deadlock). So the current head resolves it — Copilot appears to have flagged the first commit. ✅

A few things I verified that hold up well:

  • Full-duplex toggle coverageRadioModel::setFullDuplex() emits infoChanged(), which is wired to syncKiwiSdrTransmitMute(), so the gate releases if FDX is enabled mid-TX. Good.
  • Liveness watchdog interactionfeedKiwiSdrAudioData() updates m_lastAudioFeedTime before the transmit-mute early return, so the #1411 RX-restart watchdog won't spuriously fire during a long transmit while Kiwi packets are being dropped. Correct ordering.
  • Gate statem_kiwiSdrAudioTransmitMuted atomic + m_dspMutex-guarded buffer flush in setKiwiSdrAudioTransmitMuted(), invoked via QueuedConnection onto the audio thread. Thread-safe.

One minor, non-blocking observation: in the multi-source feedKiwiSdrAudioData(sourceId, ...), the source->muted check moved to after m_lastAudioFeedTime.start(), so an enabled-but-muted source now bumps the liveness timer where it previously didn't. Given the watchdog's purpose this is harmless (arguably better), just calling it out as an intentional-looking behavior change in case it wasn't.

Conventions all look good — atomics with explicit memory order, no flat-key AppSettings, RAII, Q_INVOKABLE/queued-invoke pattern consistent with the existing Kiwi code, and the docs table updated for the new audio model + fullDuplex field. Nice work.


🤖 aethersdr-agent · cost: $3.3854 · model: claude-opus-4-8

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. Reviewed the mute gate end-to-end:

  • Radio-authoritative predicate (Principle II)kiwiSdrTransmitMuteRequired() = not-full-duplex AND (isTransmitting ∨ isTuning ∨ isRadioTransmitting ∨ RADE-EOO). isRadioTransmitting fires from the interlock handler, so hardware PTT / DAX / TCI / VOX are all covered alongside client MOX/Tune. Not a keying path — Principle VI N/A.
  • Reliable unmute — every TX-on path has a matching off-signal (moxChanged/tuneChanged/radioTransmittingChanged + RADE transitions), and the state-change guard prevents redundant calls. FDX toggled mid-TX releases the gate via infoChanged.
  • Mute, not pause/disconnect — atomic flag gates externalKiwiSourceAudible(); the Kiwi stream stays connected, no churn.
  • Thread-safe — atomic + m_dspMutex-guarded flush via QueuedConnection onto the audio thread; the new audio bridge snapshot reads cross-thread state via BlockingQueuedConnection on the audio thread (Copilot's race resolved in 619be5d).
  • #1411 watchdog ordering preserved — liveness timer bumped before the mute early-return.

Live-verified on a real radio; CI green on all 6. Thanks @rfoust.

@ten9876 ten9876 merged commit ed92e96 into aethersdr:main Jun 27, 2026
6 checks passed
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.

3 participants