Skip to content

C Port of RADE V2#14

Open
drowe67 wants to merge 7 commits into
peterbmarks:mainfrom
drowe67:dr-radev2
Open

C Port of RADE V2#14
drowe67 wants to merge 7 commits into
peterbmarks:mainfrom
drowe67:dr-radev2

Conversation

@drowe67
Copy link
Copy Markdown
Collaborator

@drowe67 drowe67 commented May 2, 2026

Claude doing his magic, backed by ctests from drowe67/radae#70.

See https://github.com/drowe67/radae_nopy/tree/dr-radev2 for V2 C Port test results.

WIP - do not merge.

The on-air waveform is not frozen and is likely to change at anytime

drowe67 and others added 6 commits May 2, 2026 15:45
- rade_v2_core.h: shared typedefs and function declarations
- rade_v2_constants.h: generated constants (latent_dim=56, fps=4, features=21)
- rade_enc_v2.{h,c} + data: CoreEncoderStatefull inference (DenseNet, 5x GRU+dilated conv)
- rade_dec_v2.{h,c} + data: CoreDecoderStatefull inference (DenseNet, 5x GRU+GLU+conv)
- rade_sync.{h,c} + data: FrameSyncNet inference (3-layer feedforward)
- rade_enc_v2_test.c, rade_dec_v2_test.c: stdin/stdout test tools
- opus-nnet.c.diff: raise MAX_CONV_INPUTS_ALL to 2048 for V2 decoder conv5 (896 ch * k=2)
- BuildOpus.cmake: apply opus-nnet.c.diff patch during build
- CMakeLists.txt: add RADE_V2_ML_SOURCES to librade, add test executables

Encoder and decoder validated against Python reference (n() disabled):
max diff ~0.014 (float32 arithmetic + int8 quantisation noise).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Model 250725 trained with bottleneck=0 (no tanh on z_dense output).
ACTIVATION_TANH was producing z≈±0.76 instead of natural range,
causing decoder loss ~7.5. Linear activation gives loss 0.096.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C port of tx2.py/RADEv2Transmitter. V2 frame structure is simpler than
V1: Ns=2 pure data symbols per frame (no pilots), Nc=14, M=128, Ncp=32.
EOO is 6 x pend_cp symbols scaled by pilot_gain_eoo_v2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C port of rx2.py / RADEv2Receiver:
- rade_rx_v2.h/.c: full receiver state machine — CP autocorrelation,
  signal/sine detection, IIR timing+freq tracking, symbol extraction,
  OFDM demod via rade_v2_ofdm_demod_frame, FrameSyncNet even/odd
  frame sync, EOO detection via channel sparsity metric, stateful
  decoder via rade_core_decoder_v2
- radae_v2_rx_nopy.c: CLI binary (nin-based read loop, features to stdout)
- CMakeLists.txt: adds rade_rx_v2.c to RADE_V2_DSP_SOURCES, adds
  radae_v2_rx executable target

Verified: C tx -> C rx pipeline, loss 0.083 (threshold 0.2), n_acq=1,
EOO detected correctly at noiseless SNR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add rade_v2_ofdm_demod_frame() and rade_v2_ofdm_eoo_metric() needed
by the V2 receiver. Also adds DFT matrix (Wfwd), per-carrier phase
correction, and pend_td fields to the rade_v2_ofdm struct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@drowe67
Copy link
Copy Markdown
Collaborator Author

drowe67 commented May 5, 2026

@peterbmarks @tmiw - thinking about naming conventions for V1 & V2 code in this repo.

So Claude has come up with rade_v2_tx and radae_v2_rx for the cmd line V2 Tx and Rx. Should the current rade_tx and rade_rx be renamed rade_v1_tx and rade_v1_rx for consistency? There are probably other example in the code where we could apply similar conventions.

In drowe67/radae#70 I've renamed the ctests radae_nopy_v1_xxx and radae_nopy_v2_xxx.

Adds --write_snr_est <filename> flag to write per-symbol snr_est_dB
values to a binary float32 file, matching rx2.py behaviour. Used by
ctests radae_nopy_v2_rx_snr_high and radae_nopy_v2_rx_snr_low.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@peterbmarks
Copy link
Copy Markdown
Owner

I think rade_vX_tx etc is good. (Leaves the door open to v3)
Should we keep old public methods so as not to break any uses in existing code, we could mark the old ones with deprecated to generate a warning.
I think we should also get rid of any mention of "nopy" as it's clearly already in the filename extension.

@drowe67
Copy link
Copy Markdown
Collaborator Author

drowe67 commented May 5, 2026

Should we keep old public methods so as not to break any uses in existing code, we could mark the old ones with deprecated to generate a warning.

You mean the API? Lets take a look at that next.

@tmiw
Copy link
Copy Markdown
Collaborator

tmiw commented May 5, 2026

Using a version in the function names would be good.

Also, what about adding a function to the existing API to indicate which version to use (and then eventually default to using the RADEV2 implementations or something)? For example:

struct rade* rade = rade_open(...); // uses RADEV1, will eventually be the same as rade_open_with_version(RADE_VERSION_2, ...)
struct rade* rade2 = rade_open_with_version(RADE_VERSION_2, ...);

Then internally (for example):

struct rade* rade_open_with_version(int version, ...) {
    struct rade* result = malloc(sizeof(struct rade));

    if (version == RADE_VERSION_1) result->impl = rade_v1_get_impl();
    else if (version == RADE_VERSION_2) result->impl = rade_v2_get_impl();
    ...

    // additional initialization here
    return result;
}

int rade_tx(struct rade* rade, ...) {
    return rade->impl.tx(rade, ...);
}

Anyway, just thinking from the perspective of RADEV2 being a thing for potentially quite a while before there's a V3. If we want something quick we can just have e.g. rade_v2_tx and rade_v2_open and expect users to use those if they want V2.

EDIT: whoops, I think @drowe67 and I commented at the same time. We can look at rade_api after for sure.

@drowe67
Copy link
Copy Markdown
Collaborator Author

drowe67 commented May 5, 2026

EDIT: whoops, I think @drowe67 and I commented at the same time. We can look at rade_api after for sure.

That's fine - what you are brainstorming is the topic I was suggesting we discuss.

TBH I'm not as worried about breaking the API at this stage - it's pretty early days and the fix (like adding a version argument to rade_open) will be pretty straight fwd. End users will only be upgrading to get V2, so will already be making changes to the way they open the library.

But I'm not mandating anything atm - lets ponder it and form a consensus.

@peterbmarks
Copy link
Copy Markdown
Owner

I'm not sure that sharing rade_open returning a pointer to the rade structure can work as that struct might change with future versions. It contains rade_tx_state and rade_rx_state and they could also be different.

Maybe we should just go with rade and rade2?

Also, it's good to build a few clients apps that use the API and collect ideas of what the API should expose.

@tmiw
Copy link
Copy Markdown
Collaborator

tmiw commented May 6, 2026

I'm not sure that sharing rade_open returning a pointer to the rade structure can work as that struct might change with future versions. It contains rade_tx_state and rade_rx_state and they could also be different.

Maybe we should just go with rade and rade2?

Also, it's good to build a few clients apps that use the API and collect ideas of what the API should expose.

There could also be a private state inside the struct that could be initialized/used in a version dependent manner. But as mentioned, if we're already going to be breaking compatibility anyway...

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.

3 participants