Skip to content

DAX RX: deliver audio via Qt::DirectConnection (#1008)#2318

Closed
RiskAndReward1337 wants to merge 1 commit into
aethersdr:mainfrom
RiskAndReward1337:aidan/dax-direct-connection
Closed

DAX RX: deliver audio via Qt::DirectConnection (#1008)#2318
RiskAndReward1337 wants to merge 1 commit into
aethersdr:mainfrom
RiskAndReward1337:aidan/dax-direct-connection

Conversation

@RiskAndReward1337

Copy link
Copy Markdown
Contributor

Summary

Wires PanadapterStream::daxAudioReady → DaxBridge::feedDaxAudio with Qt::DirectConnection so DAX audio frames are delivered on PanadapterStream's network thread instead of being queued through the GUI event loop.

Under heavy GUI load (waterfall paints across multiple panadapters) the queued path was adding 10–50 ms of variable, GUI-correlated latency before samples reached the DAX bridge. DirectConnection removes that hop and produces steadier WSJT-X DT under typical operating conditions.

Marked draft. Split out of #2312 at @jensenpat's request so this connection-type change can be exercised in isolation against non-Linux DAX / TCI use cases.

Dependency / ordering

This PR depends on #2312 for thread-safety on Linux:

  • On Linux DaxBridge resolves to PipeWireAudioBridge. DAX RX: native pw_stream source on Linux — 400ms → 200ms (#1008) #2312 converts m_open, m_channelGain, m_transmitting, and m_lastAudioMs to std::atomic and removes all Qt-thread-affine work from feedDaxAudio. Without those changes, calling feedDaxAudio off the GUI thread races on those fields.
  • On macOS DaxBridge resolves to VirtualAudioBridge. VirtualAudioBridge::feedDaxAudio should be reviewed for any Qt-thread-affine state before this lands. I haven't been able to verify on macOS hardware — flagging for a maintainer with a Mac to confirm.
  • On Windows DAX is not built (elif(UNIX) gate in CMakeLists.txt), so this connect(...) site isn't compiled.
  • TCI is unaffected by this change — TCI server has its own separate daxAudioReady connection (see other call sites in MainWindow.cpp) and this PR only touches the bridge wire.

Recommended merge order: #2312 first, then this PR.

Diff

One hunk in src/gui/MainWindow.cpp::startDax() — adds Qt::DirectConnection to one connect() call plus a six-line comment explaining the rationale.

Test plan

🤖 Generated with Claude Code

Wire the PanadapterStream::daxAudioReady → DaxBridge::feedDaxAudio signal
with Qt::DirectConnection so audio frames are delivered on PanadapterStream's
network thread rather than queued through the GUI event loop.

Under heavy GUI load (waterfall paints across multiple panadapters) the
queued path was adding 10–50 ms of variable, GUI-correlated latency before
samples reached the DAX bridge.  DirectConnection removes that hop, which
shows up as steadier WSJT-X DT under typical operating conditions.

Split out from aethersdr#2312 per maintainer review so the connection-type change
can be exercised in isolation against non-Linux DAX / TCI use cases.  This
PR depends on aethersdr#2312 for the Linux PipeWireAudioBridge::feedDaxAudio
lock-free / atomic conversions; without those, calling feedDaxAudio off the
GUI thread on Linux would race on m_open / m_channelGain / m_transmitting.

On macOS DaxBridge resolves to VirtualAudioBridge — feedDaxAudio there
should be reviewed for any Qt-thread-affine state before merging.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ten9876

ten9876 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Claude here on Jeremy's behalf — Linux side is good now that #2312 landed, but the macOS path you flagged in the PR body is a real blocker. We'd like Option B: mirror #2312's hardening on VirtualAudioBridge before flipping this out of draft so DirectConnection works cleanly on both platforms.

Specifically VirtualAudioBridge::feedDaxAudio (src/core/VirtualAudioBridge.cpp:243-290) has the same shape of issues PipeWireAudioBridge had before #2312:

  1. Cross-thread QTimer access at lines 253-254 (m_silenceTimer->isActive() + ->stop()). Same bug pattern your m_lastAudioMs atomic fixed on the PipeWire side — let the main-thread silence timer self-stop on its next tick by observing the atomic timestamp.
  2. Non-atomic m_transmitting (also written from setTransmitting() on the GUI thread).
  3. Non-atomic m_channelGain[] (written from setChannelGain() on the GUI thread, read here).
  4. m_rxTiming[] access — single-writer assumption breaks under DirectConnection if multiple panadapters land frames concurrently.

The PipeWireAudioBridge changes in #2312 are the template — same atomic conversions, same QTimer-deferred-to-main-thread pattern. Should be mostly a mechanical port.

Jeremy can spot-check on his Mac mini once you push the hardening — stress test would be DAX RX into WSJT-X with heavy waterfall paint load on multiple panadapters, watching for Qt thread-affinity warnings or crashes.

Thanks for splitting this out — keeps the review surfaces clean.

73, Jeremy KK7GWY & Claude (AI dev partner)

@ten9876

ten9876 commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Claude here — closing this as stale (no activity since May 4), but the design is captured at #2486 for whoever picks it up next.

Aidan, the diagnosis was sound: GUI-event-queue jitter is real under multi-pan paint load and DirectConnection is the right call architecturally. The macOS prerequisite (VirtualAudioBridge thread-safety audit, mirroring what #2312 did for PipeWireAudioBridge) needed to land first to unblock the cross-platform version of your one-liner. Issue #2486 now captures the audit work concretely, and once it lands your PR's change becomes a clean, platform-agnostic merge.

If you'd like to pick this back up after the prerequisite work, please reopen — your draft remains useful as the reference for what the final connect site should look like.

Thanks for the careful diagnosis and for marking it draft pending macOS verification — that's exactly the right epistemic move when you can't test all platforms yourself.

73, Jeremy KK7GWY & Claude (AI dev partner)

@ten9876 ten9876 closed this May 8, 2026
aethersdr-agent Bot added a commit that referenced this pull request May 16, 2026
Mirrors the PipeWireAudioBridge thread-safety pattern (#2312) on the
macOS audio bridge so PanadapterStream::daxAudioReady → feedDaxAudio
can ship cross-platform under Qt::DirectConnection (unblocks #2318).

Changes:
* m_open, m_transmitting, m_channelGain converted to std::atomic so
  reads on PanadapterStream's network thread are correctly synchronised
  with GUI-thread writes (open/close, DaxApplet slider).
* Added m_lastAudioMs atomic flag; feedDaxAudio stores wall-clock now
  on every RX packet — no Qt API touched on the fast path.
* The silence-timer self-stop and ring-buffer readPos snap (originally
  done from feedDaxAudio, which would invoke QTimer::stop() across
  threads under DirectConnection) moved into feedSilenceToAllChannels,
  which runs on the GUI thread and observes m_lastAudioMs to decide
  when to self-stop. Matches PipeWireAudioBridge::feedSilenceToAllPipes
  field-for-field.
* m_rxTiming left as plain POD with a comment — only touched on the
  audio thread today; atomic-ify if a GUI-thread inspector is ever
  added.

Blast radius: codegraph does not index macOS-only VirtualAudioBridge
(Linux-built graph), so impact was checked manually. Callers go
through the `DaxBridge` typedef (`MainWindow.h:95`), and the
PanadapterStream → DaxBridge::feedDaxAudio connection at
MainWindow.cpp:13887 is the only invocation of the modified slot.
setChannelGain / setTransmitting / isOpen all stay on the GUI thread.
Scope is local to the two files touched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request May 16, 2026
… (#2762)

## Summary

Fixes #2486

### What was changed

Mirrors the PipeWireAudioBridge thread-safety pattern (#2312) on the
macOS audio bridge so PanadapterStream::daxAudioReady → feedDaxAudio can
ship cross-platform under Qt::DirectConnection (unblocks #2318).

- `m_open`, `m_transmitting`, `m_channelGain` converted to `std::atomic`
so reads on PanadapterStream's network thread are correctly synchronised
with GUI-thread writes (open/close, DaxApplet slider)
- Cross-thread `QTimer::stop()` in `feedDaxAudio` replaced with an
atomic `m_lastAudioMs` flag
- Silence-stop / readPos-snap cleanup moved into the GUI-thread
`feedSilenceToAllChannels` tick

### Recovery note

This PR is being opened manually. The aetherclaude orchestrator (trace
5a19c310) pushed the signed commit at 15:18 PDT but was SIGTERM'd by a
deploy.sh race before it could create the PR. Root cause + fix shipped
as a follow-up to scripts/deploy.sh; this branch's commit is the
orchestrator's own work, unmodified.

---
Generated by AetherClaude (automated agent for AetherSDR) — PR creation
completed manually after orchestrator interrupt.

Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <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