Skip to content

RFC: Replace libmodem AFSK demodulator with Direwolf-derived algorithm #3387

Description

@K5PTB

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):

  1. FIR bandpass prefilter (~332 taps at 48 kHz)
  2. IQ mixing — independent mark/space free-running oscillators (32-bit phase accumulator)
  3. RRC lowpass filter (rolloff=0.20, width=2.80 symbols) per I/Q component
  4. Amplitude: hypot(I, Q) per tone
  5. Peak/valley AGC (fast_attack=0.70, slow_decay=0.000090)
  6. Decision: mNorm − sNorm
  7. 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

  1. Approach — Is embedding the Direwolf derivation the right direction, or would you prefer keeping Direwolf as an external process (existing TNC workflow model)?
  2. Framing layer — Should the AX.25 framing follow at some point, or is libmodem's framing layer adequate to keep?
  3. Multi-slicer — Direwolf profile D adds a multi-slicer for weak-signal work. Worth a follow-on?
  4. License — We believe GPL-2+ → GPL-2+ is clean, but a maintainer confirmation would be welcome.
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    audioAudio engine and streamingenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions