Skip to content

Make VirtualAudioBridge thread-safe for DAX RX DirectConnection (#2486)#2762

Merged
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-2486
May 16, 2026
Merged

Make VirtualAudioBridge thread-safe for DAX RX DirectConnection (#2486)#2762
ten9876 merged 1 commit into
mainfrom
aetherclaude/issue-2486

Conversation

@ten9876

@ten9876 ten9876 commented May 16, 2026

Copy link
Copy Markdown
Collaborator

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.

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 ten9876 requested a review from jensenpat as a code owner May 16, 2026 22:27

@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.

Thanks for the patch — this is a faithful mirror of the PipeWireAudioBridge thread-safety pattern from #2312 applied to the macOS-side bridge. The atomicization choices and the m_lastAudioMs + GUI-thread-self-stop split look correct to me, and the comments in the header capture the cross-thread reasoning clearly.

A few small observations:

  1. Consider guarding feedDaxAudio with m_open (matches the PipeWire reference). PipeWireAudioBridge::feedDaxAudio opens with:

    if (!m_open.load(std::memory_order_acquire)) return;

    This PR's feedDaxAudio relies only on if (!block) return;, but m_blocks[channel - 1] itself is a non-atomic pointer that close() (GUI thread) can null out concurrently with a network-thread feedDaxAudio. Loading m_open first piggybacks the established acquire/release pairing with open()/close() and gives the fast path the same early-exit the PipeWire side has. (The raw m_blocks[i] race is a separate concern and probably out of scope here.)

  2. Timing window change is worth flagging. Previously, feedDaxAudio stopped the silence timer the instant real RX audio arrived. Now the stop happens on the next 20 ms tick of feedSilenceToAllChannels, so the worst-case extra silence after radio resumes RX is ~20 ms (the timer interval) — the readPos snap that previously fired in-line is also deferred by the same window. PipeWireAudioBridge accepts this same delay so this is presumably fine for DAX RX, but it's a behavioral change worth noting in the PR description / a release-notes entry.

  3. m_silenceTimer->stop() inside feedSilenceToAllChannels no longer has the prior m_silenceTimer && ... guard. In practice it's safe — the method only runs as a slot of that timer, so the pointer is necessarily non-null — but a one-line comment to that effect would help future readers who don't have this PR's context.

  4. Re: the "Recovery note" in the PR description — for the code review, the diff stands on its own and looks clean. No action needed from a review standpoint, just flagging that maintainers may want to confirm provenance separately before merge.

Nothing blocking from my read. Nice tight diff.

@ten9876 ten9876 merged commit b1099bd into main May 16, 2026
5 checks passed
@ten9876 ten9876 deleted the aetherclaude/issue-2486 branch May 16, 2026 23:43
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.

Make VirtualAudioBridge thread-safe to enable Qt::DirectConnection for DAX RX (macOS)

1 participant