Background
AetherSDR currently uses libmodem's sinc_corr_afsk_demodulator for AX.25 AFSK demodulation on VHF (1200 baud Bell 202). libmodem (by Ion Todirel, MIT licensed) is self-described as "work in progress, not yet complete" at v0.1.0. It uses a sinc-correlator with a Gardner timing error detector and has no multi-slicer support.
To understand the performance gap, we ran an overnight comparison of three decoders on identical RF audio (144.390 MHz APRS, FlexRadio 6500 DAX, Raspberry Pi 5):
| Decoder |
Packets decoded |
| AetherSDR (libmodem) |
777 |
| Graywolf |
2,733 |
| Direwolf 1.7 |
2,891 |
AetherSDR was recovering approximately 27% of what Direwolf decoded on the same audio.
Approach
We adapted Direwolf's "profile A" demodulator (from demod_afsk.c and dsp.c) into a standalone C++ class, AetherAFSKDemod, with a type alias that matches the existing sinc_corr_afsk_demodulator interface exactly. The AX.25 framing layer (HDLC, FCS, address parsing) remains on libmodem unchanged — only the AFSK demodulation stage is replaced.
Algorithm (Direwolf profile A, 1200 baud VHF):
- FIR bandpass prefilter (~332 taps at 48 kHz)
- IQ mixing — independent mark/space free-running oscillators (32-bit phase accumulator)
- RRC lowpass filter (rolloff=0.20, width=2.80 symbols) per I/Q component
- Amplitude:
hypot(I, Q) per tone
- Peak/valley AGC (fast_attack=0.70, slow_decay=0.000090)
- Decision:
mNorm − sNorm
- DPLL — nudge on transitions, sample at symbol centres
Attribution: Both files carry full GPL-2+ attribution headers:
// Derived from Dire Wolf by John Langner WB2OSZ
// Copyright (C) 2011-2020 John Langner WB2OSZ
// GPL-2+: https://github.com/wb2osz/direwolf
// Reference: src/demod_afsk.c (profile A) and src/dsp.c
AetherSDR is also GPL-2+, so the licenses are compatible.
Build switch: The feature is opt-in and off by default — the default build is unchanged:
# Default: libmodem backend, no change
cmake -S . -B build
# Opt in to Direwolf-derived demodulator
cmake -S . -B build -DAETHERSDR_USE_AETHERDEMOD=ON
Results
A follow-up comment with screenshots showing the comparison results will be posted shortly.
Status
The code is on a local branch and builds cleanly on RPi 5 (aarch64) and macOS (Apple M2). We consider it still in development — testing is ongoing and edge cases (300 baud HF, non-standard paths) have not been characterised yet.
Questions for the maintainer
- Approach — Is embedding the Direwolf derivation the right direction, or would you prefer keeping Direwolf as an external process (existing TNC workflow model)?
- Framing layer — Should the AX.25 framing follow at some point, or is libmodem's framing layer adequate to keep?
- Multi-slicer — Direwolf profile D adds a multi-slicer for weak-signal work. Worth a follow-on?
- License — We believe GPL-2+ → GPL-2+ is clean, but a maintainer confirmation would be welcome.
- PR timing — Happy to open a PR whenever useful. Any preference on structure?
Tested on Raspberry Pi 5 (aarch64), audio from FlexRadio 6500 DAX — overnight comparison via ax25-compare.py. macOS (Apple M2) confirmed functional; exhaustive comparison testing was not performed on that platform.
Background
AetherSDR currently uses libmodem's
sinc_corr_afsk_demodulatorfor AX.25 AFSK demodulation on VHF (1200 baud Bell 202). libmodem (by Ion Todirel, MIT licensed) is self-described as "work in progress, not yet complete" at v0.1.0. It uses a sinc-correlator with a Gardner timing error detector and has no multi-slicer support.To understand the performance gap, we ran an overnight comparison of three decoders on identical RF audio (144.390 MHz APRS, FlexRadio 6500 DAX, Raspberry Pi 5):
AetherSDR was recovering approximately 27% of what Direwolf decoded on the same audio.
Approach
We adapted Direwolf's "profile A" demodulator (from
demod_afsk.canddsp.c) into a standalone C++ class,AetherAFSKDemod, with a type alias that matches the existingsinc_corr_afsk_demodulatorinterface exactly. The AX.25 framing layer (HDLC, FCS, address parsing) remains on libmodem unchanged — only the AFSK demodulation stage is replaced.Algorithm (Direwolf profile A, 1200 baud VHF):
hypot(I, Q)per tonemNorm − sNormAttribution: Both files carry full GPL-2+ attribution headers:
AetherSDR is also GPL-2+, so the licenses are compatible.
Build switch: The feature is opt-in and off by default — the default build is unchanged:
Results
A follow-up comment with screenshots showing the comparison results will be posted shortly.
Status
The code is on a local branch and builds cleanly on RPi 5 (aarch64) and macOS (Apple M2). We consider it still in development — testing is ongoing and edge cases (300 baud HF, non-standard paths) have not been characterised yet.
Questions for the maintainer
Tested on Raspberry Pi 5 (aarch64), audio from FlexRadio 6500 DAX — overnight comparison via
ax25-compare.py. macOS (Apple M2) confirmed functional; exhaustive comparison testing was not performed on that platform.