[codex] Mute KiwiSDR audio during non-FDX transmit#3865
Conversation
There was a problem hiding this comment.
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
MainWindowand synchronize it toAudioEngine(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 surfacekiwiSdrTransmitMutedplus 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. |
|
Addressed the Copilot audio-thread review in
Rechecked locally:
|
There was a problem hiding this comment.
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 coverage —
RadioModel::setFullDuplex()emitsinfoChanged(), which is wired tosyncKiwiSdrTransmitMute(), so the gate releases if FDX is enabled mid-TX. Good. - Liveness watchdog interaction —
feedKiwiSdrAudioData()updatesm_lastAudioFeedTimebefore the transmit-mute early return, so the#1411RX-restart watchdog won't spuriously fire during a long transmit while Kiwi packets are being dropped. Correct ordering. - Gate state —
m_kiwiSdrAudioTransmitMutedatomic +m_dspMutex-guarded buffer flush insetKiwiSdrAudioTransmitMuted(), invoked viaQueuedConnectiononto 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
left a comment
There was a problem hiding this comment.
Approved. Reviewed the mute gate end-to-end:
- Radio-authoritative predicate (Principle II) —
kiwiSdrTransmitMuteRequired()= not-full-duplex AND (isTransmitting ∨ isTuning ∨ isRadioTransmitting ∨ RADE-EOO).isRadioTransmittingfires 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 newaudiobridge 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.
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
RadioModelfull-duplex and transmit/interlock state rather than persisted client assumptions.Test plan
cmake --build build --target AetherSDR)ctest --test-dir build -R "kiwi|rade|transmit|audio|automation" --output-on-failuregit diff --checkpython3 tools/check_a11y.pyran; only existing warning-only findings in unrelated GUI filesfullDuplex: falsetxAntenna: ANT1kiwiSdrTransmitMuted: falsetransmitting: true,kiwiSdrTransmitMuted: truetransmitting: false,kiwiSdrTransmitMuted: falseChecklist
docs/COMMIT-SIGNING.md)AppSettingscalls - use nested-JSON-under-one-key (Principle V)MeterSmoother(AGENTS.md convention; N/A, no meter UI touched)