Summary
We observed AetherSDR on macOS using hosted DAX with WSJT-X 3.1 decoding fewer FT8 transmissions than SmartSDR on Windows under the same radio conditions. In the test run, SmartSDR showed about 10% more activated callsigns in the WSJT-X roster: 113 vs 103. As the number of transmissions rose and fell, the split appeared to remain close to that 10% delta.
The strongest current hypothesis is that Aether's hosted DAX RX gain staging does not match SmartSDR's DAX gain semantics. Aether was treating a 50% DAX RX slider value as a local linear 0.5x multiplier, which is roughly -6 dB. SmartSDR/Flex DAX gain value 50 appears to be the radio-side unity/default gain point rather than a local half-scale multiplier.
Test setup
- AetherSDR on macOS, hosted DAX, not TCI
- SmartSDR on Windows, SmartSDR DAX
- WSJT-X 3.1 on both clients
- Same radio DAX channel: DAX RX 1
- Frequency/mode: 14.074 MHz DIGU
- Both clients had DAX gain set to 50
- AGC-T off, value 35
Findings so far
Aether hosted DAX RX gain was local-only and midpoint attenuated
Aether's DAX RX 1-4 slider values are persisted as 0.0..1.0. Before the experimental patch, the macOS hosted DAX bridge applied that value directly as a local sample multiplier. That means a slider value of 50% became 0.5x local gain.
That differs from SmartSDR/Flex DAX semantics. FlexLib's DAXRXAudioStream.RXGain sends a radio command shaped like:
audio stream 0x<stream> slice <slice> gain <0-100>
So SmartSDR's 50 is a radio DAX gain value, while Aether's previous 50% was a local 0.5x audio multiplier.
Experimental patch under test
A small local test patch now changes DAX RX 1-4 gain staging so the Aether slider midpoint is unity locally:
0% -> 0.0x local gain
50% -> 1.0x local gain
100% -> 2.0x local gain
The patch also sends the corresponding radio DAX RX gain command for the active stream/slice, so the slider value is not just logged. For example, at 50% it sends:
audio stream 0x400000a slice 1 gain 50
This is intentionally experimental so we can compare parity with SmartSDR before deciding on final UI/semantics.
Latest log confirms the radio command is being sent and accepted
From aethersdr-20260520-181036.log:
- SmartSDR/other client already had a DAX RX stream on channel 1:
stream 0x04000008 type=dax_rx dax_channel=1 ... ours=0
- Aether created and registered its own DAX RX stream on channel 1:
stream create type=dax_rx dax_channel=1
stream 0x0400000A type=dax_rx dax_channel=1 ... ours=1
MainWindow: registered DAX RX ch 1 "stream=0x400000a"
- Aether then sent the radio-side gain command and the radio accepted it:
RadioModel::sendCommand: "audio stream 0x400000a slice 1 gain 50"
C77|audio stream 0x400000a slice 1 gain 50
R77|0|
- Subsequent slider movements sent and received success responses for nearby values:
audio stream 0x400000a slice 1 gain 51 -> R163|0|
audio stream 0x400000a slice 1 gain 50 -> R164|0|
audio stream 0x400000a slice 1 gain 49 -> R165|0|
audio stream 0x400000a slice 1 gain 50 -> R167|0|
There is currently a duplicate gain command at stream registration because the radio reports duplicate status for our stream. The radio accepts both commands. This should probably be de-duplicated before finalizing the patch, but it does not appear to block the test.
Aetherial audio controls are probably not in the hosted DAX RX path
The macOS hosted DAX RX path appears to be:
PanadapterStream::daxAudioReady
-> VirtualAudioBridge::feedDaxAudio
-> HAL shared memory ring buffer
-> WSJT-X input device
This path does not appear to pass through the Aetherial RX strip, speaker mixer, client EQ, gate, compressor, de-esser, tube drive, or PUDU controls. Those controls feed the normal client audio path, not hosted DAX RX.
Squelch is probably not the root cause for this scenario
The project already has behavior to disable/force off squelch for digital/RTTY/CW style modes. Since the test mode was DIGU, squelch is unlikely to explain the decode delta.
DAX meter behavior is easy to misread
The small meter under the DAX RX slider is not a calibrated VU meter. It is effectively a low-level RMS indicator derived from the hosted DAX stream. The macOS bridge emits raw pre-local-gain RMS, while the applet display applies additional scaling for visualization. Before the midpoint-unity experiment, the visual relationship between the slider and actual post-gain samples was misleading because 50% also meant 0.5x local audio.
DAX shared-memory trimming needs separate attention
An earlier log, aethersdr-20260520-180307.log, showed heavy trimming immediately after audio started:
DAX RX ch 1 summary: interval_ms=101980 written_ms=101978.7 backlog_ms=136.0 peak_backlog_ms=205.3 trim_events=616 trimmed_ms=101842.7 overruns=0
That looked like the HAL/WSJT-X reader was not advancing the ring buffer read position, or that the reader was not active yet.
In the latest patched log, aethersdr-20260520-181036.log, the bridge initially trimmed during startup/catch-up, but then settled into a stable main test window with no trimming:
trim_events=0 trimmed_ms=0.0
Backlog during the stable period stayed roughly in the tens to low hundreds of milliseconds. Trimming reappeared near shutdown/end-of-run. This suggests initial reader state and live-edge catch-up may be a separate issue from the gain parity question, but it should stay on the list because lost DAX samples would affect decode parity.
Related history found during investigation
Open questions / next steps
- Re-run the Aether vs SmartSDR WSJT-X comparison with the midpoint-unity patch and radio gain command enabled.
- Decide the final intended semantics for Aether hosted DAX RX gain:
- radio-side DAX gain only,
- local gain only with SmartSDR-compatible midpoint behavior,
- or both radio-side and local gain as separate stages.
- De-duplicate the startup DAX gain command if duplicate stream status remains common.
- Continue investigating the initial DAX shared-memory trim behavior and confirm whether it happens while WSJT-X is actively reading.
- Decide how verbose DAX diagnostics should be exposed. The current test branch uses category logging; a temporary default-log experiment was intentionally backed out to avoid noisy production logs.
Current working branch
The local experimental branch is aether/dax-rx-gain-parity. No final PR semantics have been chosen yet; the branch is meant to validate whether matching SmartSDR's midpoint behavior improves FT8 decode parity.
Summary
We observed AetherSDR on macOS using hosted DAX with WSJT-X 3.1 decoding fewer FT8 transmissions than SmartSDR on Windows under the same radio conditions. In the test run, SmartSDR showed about 10% more activated callsigns in the WSJT-X roster: 113 vs 103. As the number of transmissions rose and fell, the split appeared to remain close to that 10% delta.
The strongest current hypothesis is that Aether's hosted DAX RX gain staging does not match SmartSDR's DAX gain semantics. Aether was treating a 50% DAX RX slider value as a local linear
0.5xmultiplier, which is roughly-6 dB. SmartSDR/Flex DAX gain value50appears to be the radio-side unity/default gain point rather than a local half-scale multiplier.Test setup
Findings so far
Aether hosted DAX RX gain was local-only and midpoint attenuated
Aether's DAX RX 1-4 slider values are persisted as
0.0..1.0. Before the experimental patch, the macOS hosted DAX bridge applied that value directly as a local sample multiplier. That means a slider value of 50% became0.5xlocal gain.That differs from SmartSDR/Flex DAX semantics. FlexLib's
DAXRXAudioStream.RXGainsends a radio command shaped like:So SmartSDR's
50is a radio DAX gain value, while Aether's previous50%was a local0.5xaudio multiplier.Experimental patch under test
A small local test patch now changes DAX RX 1-4 gain staging so the Aether slider midpoint is unity locally:
The patch also sends the corresponding radio DAX RX gain command for the active stream/slice, so the slider value is not just logged. For example, at 50% it sends:
This is intentionally experimental so we can compare parity with SmartSDR before deciding on final UI/semantics.
Latest log confirms the radio command is being sent and accepted
From
aethersdr-20260520-181036.log:There is currently a duplicate gain command at stream registration because the radio reports duplicate status for our stream. The radio accepts both commands. This should probably be de-duplicated before finalizing the patch, but it does not appear to block the test.
Aetherial audio controls are probably not in the hosted DAX RX path
The macOS hosted DAX RX path appears to be:
This path does not appear to pass through the Aetherial RX strip, speaker mixer, client EQ, gate, compressor, de-esser, tube drive, or PUDU controls. Those controls feed the normal client audio path, not hosted DAX RX.
Squelch is probably not the root cause for this scenario
The project already has behavior to disable/force off squelch for digital/RTTY/CW style modes. Since the test mode was DIGU, squelch is unlikely to explain the decode delta.
DAX meter behavior is easy to misread
The small meter under the DAX RX slider is not a calibrated VU meter. It is effectively a low-level RMS indicator derived from the hosted DAX stream. The macOS bridge emits raw pre-local-gain RMS, while the applet display applies additional scaling for visualization. Before the midpoint-unity experiment, the visual relationship between the slider and actual post-gain samples was misleading because 50% also meant
0.5xlocal audio.DAX shared-memory trimming needs separate attention
An earlier log,
aethersdr-20260520-180307.log, showed heavy trimming immediately after audio started:That looked like the HAL/WSJT-X reader was not advancing the ring buffer read position, or that the reader was not active yet.
In the latest patched log,
aethersdr-20260520-181036.log, the bridge initially trimmed during startup/catch-up, but then settled into a stable main test window with no trimming:Backlog during the stable period stayed roughly in the tens to low hundreds of milliseconds. Trimming reappeared near shutdown/end-of-run. This suggests initial reader state and live-edge catch-up may be a separate issue from the gain parity question, but it should stay on the list because lost DAX samples would affect decode parity.
Related history found during investigation
3f5afb3added per-channel gain and level meters to the macOS virtual audio bridge and introduced the local 0..1 gain behavior.DaxRxGain<N>as a floating point 0..1 value traces back through the DIGI applet split work.Open questions / next steps
Current working branch
The local experimental branch is
aether/dax-rx-gain-parity. No final PR semantics have been chosen yet; the branch is meant to validate whether matching SmartSDR's midpoint behavior improves FT8 decode parity.