CW decoder improvement roadmap¶
Based on a survey of the literature and serious open implementations (July 2026). Current decoder: Goertzel @ 5 ms hops → adaptive threshold + hysteresis → run lengths → two-cluster speed estimate → delayed decode — i.e. the same class as fldigi/ggmorse. Good on clean signals; degrades under QSB and low SNR. The field's answer, in effort order:
Phase 1 — matched filter + soft decisions (days; ~3-8 dB)¶
Blueprint: RSCW by PA3FWM (https://www.pa3fwm.nl/software/rscw/algorithm.html).
- Integrate tone energy over ~one dit (moving sum of the 5 ms Goertzel hops), sampled on an estimated dit clock, instead of deciding per 5 ms slice. Integrating over a full dit buys ~10·log10(dit/5ms) dB of processing gain (≈10 dB at 20 wpm).
- Log-domain AGC: track fast noise floor + slow signal peak, work with log-energy minus floor. QSB is multiplicative, so fades become slow additive offsets a two-state model can follow.
- Replace the boolean mark/space with p(mark) per hop from a two-state amplitude model. No hard decision before the sequence decoder (VE3NEA's stated core principle for CW Skimmer).
Phase 2 — duration-HMM Viterbi decode (1-2 weeks; several more dB¶
plus robustness to hand-keying jitter)¶
Lineage: Bell 1977 Naval Postgraduate School thesis (the ancestor of the CW Skimmer family; free at archive.org/details/optimalbayesiane00bell); AG1LE's fldigi Bayesian branch is a working open implementation (ag1le.blogspot.com, "New Morse Decoder" parts 1-5, github.com/ag1le/morse.py).
- Hidden states {dit, dah, element-space, char-space, word-space} with log-normal duration priors scaled by a tracked WPM posterior (not a point estimate).
- Viterbi over the soft p(mark) sequence with 1-2 s traceback — the existing delayed-decode structure maps directly onto this.
- A fade-shortened dah is then rescued by grammar + duration priors instead of being read as a dit; one bad edge no longer shatters the character.
- Optional cheap add-on: rescore top-N hypotheses with ham-exchange priors (callsign patterns, RST/599, CQ/DE/UR/73 lexicon) the way the Reverse Beacon Network validates Skimmer spots.
Wang et al. 2018, "Automatic Morse Code Recognition Under Low SNR" (Atlantis Press MECAE) documents the HMM/Viterbi gain over threshold decoders: roughly 3-6 dB.
Phase 3 — neural decoder, off the shelf (about a day; best absolute¶
weak-signal numbers)¶
github.com/e04/web-deep-cw-decoder + deepcw-engine: CNN/CTC ONNX model, published 0.00% CER from 0 to −4 dB SNR, <1.5% at −8 dB, <8% at −10 dB, runs real-time in browser WASM — trivial for the box's 2 Xeon cores via onnxruntime. Could run alongside the classical decoder (compare outputs in the UI). Trade-offs: opaque behaviour on odd hand keying, fixed tuning window, extra dependency. AG1LE's CNN-LSTM-CTC work (2019-2020) shows the same approach is trainable from our own synth generator (test_cw.py already synthesizes CW with noise/QSB) if we ever want a custom model.
Recommended order¶
1 → 2, keeping the current decoder as fallback behind a flag; 3 as an optional parallel decoder for weak-signal spotting. Phases 1-2 are pure NumPy-free Python (or a ~200-line helper) and keep the decoder fully inspectable.
sigid Phase-3 acceptance (full 589-signal corpus, 93 usable IQ samples)¶
Measured 2026-07-03: - Blind (no frequency context): 4/93 (4%) top-3. - Frequency-gated (operational, dial known): 18/93 (19%) top-3.
Below the plan's "majority in top-3" target. Root causes are structural, not bugs: the wiki's quoted bandwidth often doesn't match the recording's measurable occupied bandwidth (audio clips vs IQ, wide captures); coarse modulation-family classification; and hundreds of signals overlapping in bandwidth+family+band. The pipeline (ingest -> features -> match -> CLI) is complete and functional; meaningfully higher accuracy needs Phase 5 (a trained ML modulation classifier per the plan) or substantially better per-sample feature/bandwidth normalisation, not matcher tweaks.