Skip to content

Codec backends: native vs third-party

Every digital mode in Rafe now has a native encoder/decoder written in this repo (pure Python + numpy/scipy). The app defaults to native everywhere, so a stock install needs none of the external tools it used to shell out to:

Was shelled out to Replaced by (native)
jt9 (WSJT-X) app/radio/jtx/ — JT9, JT4, JT65, FST4, FST4W, Q65
wsprd (WSJT-X) app/radio/wsprx/ — WSPR
decode_ft8 / gen_ft8 (ft8_lib) app/radio/ftx/ — FT8, FT4
fldigi (+ pacat/parec) app/radio/mfsk|domino|thor|olivia|throb|feld.py

You can still fall back to the original third-party code per family — e.g. if you want wsprd's deeper weak-signal sensitivity, or a fldigi feature the native modem doesn't cover yet.

The switch

Selection lives in app/radio/codec_backend.py and is driven entirely by environment variables. All are optional; the default is native.

Variable Applies to Values Default
RAFE_CODEC_BACKEND global default for every family native | external native
RAFE_FTX_BACKEND FT8 / FT4 native | ft8_lib | both native
RAFE_WSJTX_BACKEND JT9/JT4/JT65/FST4/FST4W/Q65/WSPR native | external native
RAFE_FLDIGI_BACKEND MFSK16/32, DominoEX, Thor, Olivia, Contestia, THROB, Feld Hell native | external native

Precedence: family variable → legacy variable → RAFE_CODEC_BACKEND → native. Setting the global to external maps each family to its third-party option (ft8_lib for FT8/FT4, external for the rest).

DIGIMODE_BACKEND (the original FT8-only variable: ft8_lib | native | both) is still honoured for backward compatibility and takes precedence over RAFE_FTX_BACKEND.

Examples

# Default: everything native, no external binaries needed.

# Use the WSJT-X binaries for the whole weak-signal family (needs jt9 + wsprd):
RAFE_WSJTX_BACKEND=external

# Just WSPR on wsprd but keep the JT/FST4/Q65 modes native? Not per-mode yet —
# RAFE_WSJTX_BACKEND is whole-family. (Per-mode override is a possible follow-up.)

# Fall the fldigi keyboard modes back to the fldigi XML-RPC bridge:
RAFE_FLDIGI_BACKEND=external      # needs the headless fldigi sidecar running

# Run FT8 through both decoders and emit tagged, compared results (evaluation):
RAFE_FTX_BACKEND=both             # (or legacy DIGIMODE_BACKEND=both)

# Force absolutely everything back to third-party:
RAFE_CODEC_BACKEND=external

Set these in the systemd unit / .env for the icom-remote service and restart.

What external needs installed

  • RAFE_WSJTX_BACKEND=externaljt9 and wsprd on PATH (or set JT9_BIN / WSPRD_BIN). From the wsjtx package.
  • RAFE_FTX_BACKEND=ft8_libdecode_ft8 / gen_ft8 (set DECODE_FT8_BIN / GEN_FT8_BIN). From kgoba/ft8_lib.
  • RAFE_FLDIGI_BACKEND=external → the headless fldigi sidecar + PulseAudio (pacat/parec). See the deploy docs for the sidecar service.

Sensitivity notes (native vs third-party)

The native codecs are validated bit-/tone-exact on the encode side and decode their own and the reference tools' output. A few have a sensitivity gap worth knowing before you switch a weak-signal station to native:

  • WSPR — native is ~2–3 dB shy of wsprd (no OSD / calibrated LLR yet). For a dedicated WSPR spotter, RAFE_WSJTX_BACKEND=external is still the stronger call.
  • JT65 — native uses hard-decision Reed-Solomon; jt9's soft/deep-search reaches a few dB deeper.
  • Q65 — native is a solid single-shot decoder; WSJT-X's −26 dB figure needs multi-frame averaging + a-priori/deep search (unported).
  • FT8/FT4, JT9, JT4, FST4/FST4W — native decode matches the reference on strong-to-moderate signals; the very deepest decodes may still favour the external tools' OSD.
  • fldigi modes — native Thor/MFSK/Olivia include soft-decision + timing recovery and decode well below 0 dB; the exact overlap-add pulse shaping and fldigi's CWI/multipath refinements are the remaining differences.

See docs/native-digimodes.md for the full mode-by-mode status and validation.