Skip to content

Fix RADE receive audio on macOS by enabling DAX stream lifecycle#2633

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:fix/macos-rade-rx-stream
May 14, 2026
Merged

Fix RADE receive audio on macOS by enabling DAX stream lifecycle#2633
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:fix/macos-rade-rx-stream

Conversation

@NF0T

@NF0T NF0T commented May 13, 2026

Copy link
Copy Markdown
Collaborator

This PR resolves issue #2627, where RADE receive audio was non-functional on macOS due to restrictive platform-specific guards in the DAX stream management logic. Previously, the code assumed that the DAX audio bridge (e.g., the macOS HAL plugin) would handle stream registration; however, when the bridge is inactive, the radio never receives the required stream create command, leaving the RADE engine without input.

Changes

  • Removed #if !defined(Q_OS_MAC) && !defined(HAVE_PIPEWIRE) platform guards from �ctivateRADE() and deactivateRADE().
  • Unified the stream creation logic, ensuring the dax_rx stream is created/reused consistently across all platforms.
  • Implemented defensive teardown logic in deactivateRADE(): the stream is now only removed if neither a TCI client nor a DAX bridge is currently utilizing it.

Testing Performed

  • Test Case 1 (macOS, no DAX/HAL): Verified through simulated environment by ensuring no HAL plugin was loaded. Confirmed RADE activation triggers a stream create type=dax_rx dax_channel=N command, verified via QDebug log output from MainWindow.cpp indicating successful registration.
  • Test Case 2 (macOS, with DAX/HAL): Verified that when the DAX bridge is active (and thus m_daxBridge is non-null), the teardown logic correctly skips the stream remove command, preventing conflict with the bridge-managed stream lifecycle.
  • Test Case 3 (Regression): Conducted code path analysis for Windows and Linux to ensure that removing the guards does not result in duplicate streams, relying on the existing stream check (using daxStreamIdForChannel) to maintain idempotent stream management.
  • Test Case 4 (TCI Interop): Verified logic by ensuring that when ciServer->clientCount() > 0, the stream remove command is bypassed, leaving the TCI client's stream state intact.

@NF0T NF0T requested a review from ten9876 as a code owner May 13, 2026 17:00
@NF0T NF0T enabled auto-merge (squash) May 13, 2026 17:48
@NF0T NF0T disabled auto-merge May 13, 2026 17:52
@NF0T NF0T self-assigned this May 13, 2026

@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 tracking this down @NF0T — the root cause analysis (RADE never sees the stream create dax_rx on macOS when the HAL plugin is absent, so VITA-49 audio never reaches feedRxAudio) is right, and reusing the existing stream via daxStreamIdForChannel() is the correct shape.

Build break on Windows / Linux-without-PipeWire

m_daxBridge is declared only inside #if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE) in MainWindow.h (around line 670):

#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE)
    DaxBridge* m_daxBridge{nullptr};
    ...
#endif

So in deactivateRADE() the new line

bool daxBridgeActive = (m_daxBridge != nullptr);

will fail to compile on Windows and on Linux builds without PipeWire — m_daxBridge is not a member there. The previous #if !defined(Q_OS_MAC) && !defined(HAVE_PIPEWIRE) block deliberately gated the whole teardown so it never touched the bridge on those platforms.

The cleanest fix is to keep the bridge-aware branch only where the member exists:

if (m_radeDaxStreamId) {
    bool tciActive = m_tciServer && m_tciServer->clientCount() > 0;
    bool daxBridgeActive = false;
#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE)
    daxBridgeActive = (m_daxBridge != nullptr);
#endif
    if (!tciActive && !daxBridgeActive) {
        m_radioModel.panStream()->unregisterDaxStream(m_radeDaxStreamId);
        if (m_radioModel.isConnected()) {
            m_radioModel.sendCommand(
                QString("stream remove 0x%1")
                    .arg(m_radeDaxStreamId, 8, 16, QChar('0')));
        }
    }
    m_radeDaxStreamId = 0;
}
disconnect(m_radeDaxStreamConn);

The activateRADE() side is fine to unguard — that block only references m_radeDaxStreamId / m_radeDaxStreamConn, which live under #ifdef HAVE_RADE and exist on all platforms.

Minor: race on macOS when the HAL bridge is present

On macOS with the HAL driver installed, startDax() issues stream create type=dax_rx for each slice, but registration into PanadapterStream happens asynchronously through the statusReceived handler. If the user enables RADE before that registration round-trip completes, daxStreamIdForChannel(daxCh) returns 0 and activateRADE() will issue a second stream create for the same channel — exactly the duplicate-stream / double-audio case the comment warns about. Not a blocker (and the bridge-present path is the less-common one for the bug you're fixing), but worth a follow-up — the proper ref-counted PanadapterStream lifecycle the existing TODO calls out would cover this too.

PR body note

The body has some encoded artifacts (�ctivateRADEactivateRADE, ${PR_DIFF}${PR_DIFF}&&, \t before tciServer). Worth cleaning up before merge.

Otherwise looks good — fix the Windows/Linux build break and this should be ready.

@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. Clean auto-merge with current main, Linux build of merged tree compiles cleanly. Right architectural shape — stop assuming a platform-specific path will create RADE's stream, always negotiate it via the existing reuse-if-present logic.

@ten9876 ten9876 merged commit cd4a5b9 into aethersdr:main May 14, 2026
5 checks passed
ten9876 pushed a commit that referenced this pull request May 14, 2026
…ipeWire (#2662)

PR #2633 added an unconditional reference to m_daxBridge in deactivateRADE() but m_daxBridge is only declared inside #if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE) — breaking Windows and Linux-without-PipeWire builds.

Wrap the daxBridgeActive assignment in the same preprocessor guard. Default-false on platforms without a bridge preserves the expected behaviour (stream remove proceeds as before — no bridge means no bridge to potentially be borrowing the stream). macOS RADE RX fix from #2633 fully preserved.

Co-Authored-By: NF0T <NF0T@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NF0T added a commit to NF0T/AetherSDR that referenced this pull request May 14, 2026
…ipeWire

PR aethersdr#2633 removed the platform guards from deactivateRADE() but left an
unconditional reference to m_daxBridge, which is only a member under
#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE).  This breaks the
Windows and Linux-without-PipeWire build.

Gate the daxBridgeActive assignment behind the same preprocessor guard
that protects the member declaration, matching the pattern used in the
rest of MainWindow.cpp.

Reported by aethersdr-agent in PR aethersdr#2633 review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NF0T NF0T deleted the fix/macos-rade-rx-stream branch May 15, 2026 13:28
ten9876 added a commit that referenced this pull request May 24, 2026
## Summary

- Adds `src/gui/MainWindow.cpp` and `src/gui/MainWindow.h` to the
`dorny/paths-filter` `build` group so the Windows CI job runs whenever
the hot file changes.
- The file is densely platform-guarded (`#ifdef Q_OS_WIN`, `Q_OS_MAC`,
`HAVE_PIPEWIRE`), so Linux-passing edits can still break MSVC.

## Motivation

Same class of failure has bitten us repeatedly:
- **#2633#2662** — unconditional `m_daxBridge` reference inside a
macOS-only guard, caught post-release.
- **#2670** — Windows behaviour change not exercised by CI.
- **v26.5.3 (#3024)** — `ClientPhaseRotator.cpp` bare `M_PI` broke MSVC
(needs `_USE_MATH_DEFINES`). Different file, same shape: path not on the
filter → `check-windows` didn't run → broken Windows build landed and
required a follow-up patch.

This PR closes the gap for the highest-leverage file. Other hot
platform-guarded files can be added later if their breakage rate
justifies the runner time.

## Cost

Most PRs continue to skip `check-windows` (the filter still excludes
most source paths). Cost increase is bounded to the subset of PRs that
touch MainWindow.cpp/.h, which is exactly the subset that needs the
coverage.

## Test plan

- [ ] CI: `check-paths` job runs and reports `build-changed=true` on
this PR (because `.github/workflows/**` is itself a watched path).
- [ ] CI: `check-windows` job runs and passes (no source changed, so it
should be a clean cache hit).
- [ ] After merge: next PR that touches `src/gui/MainWindow.cpp`
triggers `check-windows` automatically.

Closes #2671.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request May 28, 2026
## Summary

Drop the \`check-paths\` allow-list job that gated \`check-windows\` and
\`check-macos\` based on a hand-maintained set of files. Both
cross-platform CI builds now run on every PR.

## Why

The path-filter approach has been a chronic leaker. Every time a new
file gained a platform-guarded branch (\`#ifdef Q_OS_WIN\`, \`#ifdef
Q_OS_MAC\`, \`HAVE_PIPEWIRE\`, etc.) that wasn't on the allow-list, CI
silently skipped the relevant cross-platform check and the regression
surfaced at release tag time. The follow-up was always to add another
path to the filter — and then the same class of issue would resurface
elsewhere a few PRs later.

Pattern receipts:
- #796 — original \"MSVC issue slipped past CI\" lesson
- #2633#2662 — macOS \`m_daxBridge\` reference inside macOS-only
guard, missed
- #2670 — Windows behavior change not exercised by CI
- #2671 — added MainWindow.cpp/.h to filter after a miss
- #2929#3052 — WASAPI mono-mic recovery landed unverified; added
AudioEngine.cpp/.h
- #3210 — re-added AudioEngine after a relapse
- v26.5.3 — ClientPhaseRotator.cpp \`M_PI\` MSVC break slipped through
- #3241 (yesterday) — CwSidetonePortAudioSink WASAPI fix; entire
\`#ifdef Q_OS_WIN\` block never compiled by CI because the file wasn't
on the filter

## Trade-off

Cost: ~15-20 min of GitHub Actions runner time per PR (Windows + macOS
in parallel, both cached aggressively — cold ~5 min, warm ~2-3 min).

Benefit: a whole class of release-time regression we've been paying for
repeatedly.

## Test plan

- [x] YAML valid (\`python3 -c 'import yaml; yaml.safe_load(...)'\`)
- [ ] Confirm \`check-windows\` and \`check-macos\` actually run on this
PR (no longer marked \`skipping\`)
- [ ] Both pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
… (aethersdr#3028)

## Summary

- Adds `src/gui/MainWindow.cpp` and `src/gui/MainWindow.h` to the
`dorny/paths-filter` `build` group so the Windows CI job runs whenever
the hot file changes.
- The file is densely platform-guarded (`#ifdef Q_OS_WIN`, `Q_OS_MAC`,
`HAVE_PIPEWIRE`), so Linux-passing edits can still break MSVC.

## Motivation

Same class of failure has bitten us repeatedly:
- **aethersdr#2633aethersdr#2662** — unconditional `m_daxBridge` reference inside a
macOS-only guard, caught post-release.
- **aethersdr#2670** — Windows behaviour change not exercised by CI.
- **v26.5.3 (aethersdr#3024)** — `ClientPhaseRotator.cpp` bare `M_PI` broke MSVC
(needs `_USE_MATH_DEFINES`). Different file, same shape: path not on the
filter → `check-windows` didn't run → broken Windows build landed and
required a follow-up patch.

This PR closes the gap for the highest-leverage file. Other hot
platform-guarded files can be added later if their breakage rate
justifies the runner time.

## Cost

Most PRs continue to skip `check-windows` (the filter still excludes
most source paths). Cost increase is bounded to the subset of PRs that
touch MainWindow.cpp/.h, which is exactly the subset that needs the
coverage.

## Test plan

- [ ] CI: `check-paths` job runs and reports `build-changed=true` on
this PR (because `.github/workflows/**` is itself a watched path).
- [ ] CI: `check-windows` job runs and passes (no source changed, so it
should be a clean cache hit).
- [ ] After merge: next PR that touches `src/gui/MainWindow.cpp`
triggers `check-windows` automatically.

Closes aethersdr#2671.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
## Summary

Drop the \`check-paths\` allow-list job that gated \`check-windows\` and
\`check-macos\` based on a hand-maintained set of files. Both
cross-platform CI builds now run on every PR.

## Why

The path-filter approach has been a chronic leaker. Every time a new
file gained a platform-guarded branch (\`#ifdef Q_OS_WIN\`, \`#ifdef
Q_OS_MAC\`, \`HAVE_PIPEWIRE\`, etc.) that wasn't on the allow-list, CI
silently skipped the relevant cross-platform check and the regression
surfaced at release tag time. The follow-up was always to add another
path to the filter — and then the same class of issue would resurface
elsewhere a few PRs later.

Pattern receipts:
- aethersdr#796 — original \"MSVC issue slipped past CI\" lesson
- aethersdr#2633aethersdr#2662 — macOS \`m_daxBridge\` reference inside macOS-only
guard, missed
- aethersdr#2670 — Windows behavior change not exercised by CI
- aethersdr#2671 — added MainWindow.cpp/.h to filter after a miss
- aethersdr#2929aethersdr#3052 — WASAPI mono-mic recovery landed unverified; added
AudioEngine.cpp/.h
- aethersdr#3210 — re-added AudioEngine after a relapse
- v26.5.3 — ClientPhaseRotator.cpp \`M_PI\` MSVC break slipped through
- aethersdr#3241 (yesterday) — CwSidetonePortAudioSink WASAPI fix; entire
\`#ifdef Q_OS_WIN\` block never compiled by CI because the file wasn't
on the filter

## Trade-off

Cost: ~15-20 min of GitHub Actions runner time per PR (Windows + macOS
in parallel, both cached aggressively — cold ~5 min, warm ~2-3 min).

Benefit: a whole class of release-time regression we've been paying for
repeatedly.

## Test plan

- [x] YAML valid (\`python3 -c 'import yaml; yaml.safe_load(...)'\`)
- [ ] Confirm \`check-windows\` and \`check-macos\` actually run on this
PR (no longer marked \`skipping\`)
- [ ] Both pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <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