Skip to content

V2 Tx: add SSB BPF (data + EOO), on by default - #20

Merged
drowe67 merged 3 commits into
mainfrom
dr-tx-bpf
Sep 9, 2026
Merged

drowe67 merged 3 commits into
mainfrom
dr-tx-bpf

Conversation

@drowe67

@drowe67 drowe67 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Add SSB BW (300-2700Hz) Tx filter, matching reference Python tx2.py Tx, and addressing #18 .

Companion PR: drowe67/radae#80

drowe67 and others added 2 commits September 5, 2026 08:10
rade_tx_v2_process()/rade_tx_v2_eoo() now filter through one continuous
rade_bpf state (300-2700Hz, matching radae_v2.py's RADEv2Transmitter and
the model's training conditions), reusing the existing generic rade_bpf
module already used by V1 Tx and V2 Rx. New RADE_NO_TX_BPF flag bit and
--no_bpf option on rade_tx_wav; filter on by default via rade_api.c.

Also fixes radae_tx.c's EOO tail-padding memset, which only zeroed
sizeof(pointer) (8 bytes) rather than the actual buffer.

CI: temporarily points the radae checkout at dr-tx-bpf (companion PR
drowe67/radae#80) instead of main, since that PR's tx2.py default-on SSB
BPF is needed for rade_c_v2_tx-style ctests to pass. Revert to main once
that PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzNQqmYXwUStAMrN4a5GFy
Each RX loop iteration consumes rade_nin() samples (one OFDM symbol),
not a full modem frame (Nmf = 2 symbols for V2) -- the old wording
mislabeled the granularity. Tx-side counters (radae_tx.c, rade_tx_wav.c)
are unaffected, they genuinely process one whole modem frame per call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzNQqmYXwUStAMrN4a5GFy
@drowe67

drowe67 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

OTC hardware verification, C vs Python Tx/Rx (260905, dr-tx-bpf)

Run Engine C/No (dB-Hz) PNR3k (dB) EOO V1 loss1/loss2/Δ V2 loss1/loss2/Δ
otc-09 C 73.21 38.44 0.110/0.111/0.001 0.082/0.084/0.002
otc-10 C 73.30 38.53 0.110/0.113/0.004 0.082/0.084/0.002
otc-11 C 74.26 39.49 0.110/0.111/0.001 0.082/0.085/0.003
otc-12 Python 74.49 39.72 0.110/0.111/0.001 0.082/0.084/0.002
otc-13 Python 74.97 40.19 0.110/0.111/0.001 0.082/0.084/0.002
otc-14 Python 74.36 39.58 0.110/0.111/0.001 0.082/0.084/0.002

loss1 = genie decode vs true input, loss2 = practical Rx vs true input, Δ = extra loss the real channel adds on top of genie (loss2 − loss1). Both engines land in the same tight band, and EOO fires reliably in all six runs with the new SSB BPF (data + EOO, both languages) in place.

As expected, the BPFs didn't change much. EOO was detected OK in all tests

…ring

rade_get_stats() (modeled on codec2-dev's freedv_get_modem_extended_stats())
exposes per-symbol V2 receiver diagnostics -- sync state, delta_hat/
delta_hat_g, freq_offset, AGC gain, SNR estimate -- previously only
available in the Python reference (rx2.py). Required persisting `gain`
(was a local var in rade_rx_v2_process()) into rade_rx_v2_state.

rade_rx_wav gains --write_state/--write_delta_hat/--write_delta_hat_g/
--write_freq_offset/--write_gain/--write_snr_est, streaming one value per
OFDM symbol in the same per-symbol resolution and file formats as rx2.py's
existing diagnostic log, so ota_test.sh's --v2_c path can drive the same
plot_v2_logs() Octave plot as the Python path.

Motivated by an OTA test (ota_01, PNR3k 0.57dB) whose whole-file loss
average looked bad but sounded fine after the first second -- this
diagnostic trace confirms it was a brief false-sync-then-reacquire
transient (~85 symbols) dragging the average, not genuine post-lock
quality. Validated against 4 real OTA recordings; also found the same
transient in a second file (ota_02) and a clean single acquisition in two
others (ota_03/04) at similar or lower SNR, suggesting it's a
probabilistic near-threshold event rather than SNR-band-specific.

Also fixes a stale/misleading model_file placeholder: rade_tx_wav.c,
rade_rx_wav.c, radae_tx.c and radae_rx.c all hardcoded a V1 checkpoint
filename passed to rade_open(), printed verbatim even in --v2 mode
(the string is never read -- weights are always compiled in). Changed to
"(unused, built-in weights)" to stop it looking like a real model load.

All 24 rade_c_v2/ota_test_v2_c ctests still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzNQqmYXwUStAMrN4a5GFy
@drowe67

drowe67 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

I've conducted some OTA tests between my home and various KiwiSDRs using this branch of the C code and the radae/ota_test.sh script with fine results. The loss metric doesn't work too well at low SNRs due to resyncs, but does give sensible results at high SNRs (ota_04). On the one sample tested (Brian), my personal preference is V2 to V1. This test shows the V2 code is working over a range of real world channel conditions. On one example, V1 wouldn't sync but V2 produced usable decoded speech.

Note that ota_test.sh gives calibrated/comparable results between V1/V2 and SSB - a much better test protocol than ad-hoc testing which the PLT has already agreed isn't a reliable basis for comparison. It also removes any possible application-bug issues, as it just uses cmd line tools for the sample I/O.

Test Path PNR3k (dB) EOO V1 loss (target/measured/Δ) V2 loss (target/measured/Δ) V1 SNR (dB) V2 SNR (dB) V1 decoded (s) V2 decoded (s)
ota_01 Tarlee 0.57 missed 0.115 / 0.359 / 0.244 0.088 / 1.046 / 0.958 -5.2 -0.9 9.55 10.31
ota_02 Tarlee 2.26 fired 0.115 / 0.259 / 0.144 0.088 / 0.422 / 0.334 -1.6* -2.5 0.31 9.47
ota_03 Tarlee 2.52 missed 0.115 / 0.241 / 0.126 0.088 / 0.325 / 0.236 -3.2 0.1 9.19 10.59
ota_04 Ironstone Range 11.72 fired 0.115 / 0.145 / 0.030 0.088 / 0.128 / 0.040 5.6 9.2 9.91 10.27

(V1/V2 SNR = mean over the receiver's own synced-state period, not the whole file. † ota_02: V1 essentially never locked — only 4/90 logged status lines show "sync" state, hence the 0.31s decode. * that V1 SNR figure is from those same 4 lines, not a reliable average. V2 stayed locked and decoded the full 9.47s on the same recording.)

@drowe67
drowe67 requested a review from tmiw September 9, 2026 00:53

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

Just some minor feedback, feel free to not change anything if that's desired.

with:
repository: drowe67/radae
ref: dr-radev2
ref: dr-tx-bpf

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.

Does this need to be reset prior to merge?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll set it back to radae/main eventually, but I have a little more work to go in dr-tx-bpf before we merge.

Comment thread src/rade_tx_v2.c
@drowe67
drowe67 merged commit 262a980 into main Sep 9, 2026
2 checks passed
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