Skip to content

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

Description

@ten9876

Background

PR #2318 proposed wiring 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 (multi-pan waterfall paints) the queued path adds 10-50 ms of variable, GUI-correlated latency before samples reach the DAX bridge — visible as WSJT-X DT jitter.

That PR is now stale (4 days no activity). The Linux dependency #2312 has already landed (v0.9.7), making PipeWireAudioBridge lock-free / atomic. But macOS VirtualAudioBridge was never audited for the same thread-safety, so DirectConnection would crash or warn on macOS.

Concrete blockers in VirtualAudioBridge

src/core/VirtualAudioBridge.cpp:243-300 has at least these unsafe-from-network-thread call sites in feedDaxAudio:

  1. m_silenceTimer->stop() at line 254 — QTimer::stop() is not thread-safe; the timer is parented to VirtualAudioBridge which lives on the GUI thread.
  2. m_transmitting = false at line 255 — plain bool member, would race with GUI-thread reads/writes.
  3. m_channelGain[channel - 1] at line 277 — plain float array; gain changes from the GUI thread would race with the audio-thread read.
  4. m_rxTiming[channel - 1] statsstats.windowElapsed, stats.writtenSamples are non-atomic; any GUI-thread inspection (diagnostics dialog) would race.

Proposed fix

Mirror what #2312 did for PipeWireAudioBridge:

  1. Convert m_open, m_transmitting, m_channelGain to std::atomic (atomic_bool, atomic).
  2. Convert relevant stats fields to atomics or document the read-races as best-effort diagnostics.
  3. Replace m_silenceTimer->stop() from feedDaxAudio with an atomic flag (e.g. m_silenceFillEnabled) that the GUI-thread timer reads on each tick — when false, the timer self-stops next iteration. Set the flag from the audio thread; let the GUI thread observe it. No cross-thread QTimer manipulation.
  4. Audit any other QObject member calls in feedDaxAudio for cross-thread safety.

Probably ~30-50 lines, similar in shape to #2312.

Once this lands

PR #2318's one-line change (Qt::DirectConnection on the daxAudioReady → feedDaxAudio connect) becomes platform-agnostic and can ship cross-platform, removing the GUI-event-queue jitter on macOS too.

Workaround for the impatient

If we want the Linux latency improvement now, an #ifdef Q_OS_LINUX guard on the DirectConnection argument is a pragmatic stopgap. Linux uses PipeWireAudioBridge (already thread-safe per #2312), macOS retains queued semantics until this issue lands. But the per-platform divergence in the connect site is ugly; better to do the macOS audit first if there's bandwidth.

Test plan

References

Priority

Low-medium — DAX RX works on macOS today via the queued path. Latency improvement would benefit WSJT-X / FT8 operators on macOS during multi-pan use, but isn't a regression.

73, Jeremy KK7GWY & Claude (AI dev partner)

Metadata

Metadata

Assignees

No one assigned

    Labels

    audioAudio engine and streamingenhancementImprovement to existing featuremacOSmacOS-specific issuemaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions