Pure Python implementation of digital modem algorithms from fldigi
PyDigi is a Python library that reimplements digital mode modem algorithms from the popular fldigi software. It provides a simple API for generating digital mode signals that can be used with GNU Radio, saved to WAV files, or used in other DSP applications.
Status: 100% Complete - 22/22 stable mode families implemented (~151 mode variants)
- Pure Python Implementation: No C++ dependencies, uses NumPy and SciPy for performance
- Extensive Modem Support: 22 mode families, ~151 mode variants implemented
- Simple API: Generate signals with a single function call
- fldigi Validated: All signals decode correctly in fldigi
- Flexible Output: Returns numpy arrays for integration with any audio pipeline
- Comprehensive Documentation: Full API docs and examples
pip install pydigiClone the repository:
git clone https://github.com/yourusername/pydigi.git
cd pydigiInstall the package:
pip install -e .For development with testing tools:
pip install -e ".[dev]"- Python 3.8 or later
- NumPy >= 1.20.0
- SciPy >= 1.7.0
- soundfile >= 0.10.0 (for WAV file support)
from pydigi import CW, save_wav
modem = CW(wpm=20)
audio = modem.modulate("CQ CQ CQ DE W1ABC", frequency=800)
save_wav("cw_output.wav", audio, sample_rate=8000)from pydigi import RTTY, save_wav
modem = RTTY(baud=45.45, shift=170)
audio = modem.modulate("RYRYRY THE QUICK BROWN FOX", frequency=1500)
save_wav("rtty_output.wav", audio, sample_rate=8000)from pydigi import PSK31, save_wav
modem = PSK31()
audio = modem.modulate("HELLO WORLD", frequency=1000)
save_wav("psk31_output.wav", audio, sample_rate=8000)
# Add silence padding (useful for PTT/VOX)
audio = modem.modulate("HELLO WORLD", leading_silence=0.5, trailing_silence=0.5)
save_wav("psk31_with_silence.wav", audio, sample_rate=8000)from pydigi import WEFAX576, save_wav
modem = WEFAX576()
# Transmit text (automatically rendered as image)
text = """MARINE WEATHER FORECAST
Winds: NE 10-15 kt
Waves: 2-3 ft
Weather: Partly cloudy"""
audio = modem.modulate(text)
save_wav("wefax_text.wav", audio, sample_rate=11025)
# Or transmit images
import numpy as np
img = np.random.randint(0, 256, (200, 1809), dtype=np.uint8)
audio = modem.transmit_image(img)
save_wav("wefax_image.wav", audio, sample_rate=11025)All generated WAV files can be decoded in fldigi!
See the examples/ directory for more complete examples of each modem type.
from pydigi import CW, RTTY, PSK31, PSK63, PSK125, save_wav
# CW at 20 WPM
cw = CW(wpm=20)
audio_cw = cw.modulate("CQ DE W1ABC", frequency=800)
save_wav("output_cw.wav", audio_cw, 8000)
# RTTY at 45.45 baud
rtty = RTTY(baud=45.45, shift=170)
audio_rtty = rtty.modulate("HELLO WORLD", frequency=1500)
save_wav("output_rtty.wav", audio_rtty, 8000)
# PSK31
psk31 = PSK31()
audio_psk = psk31.modulate("TEST PSK31", frequency=1000)
save_wav("output_psk31.wav", audio_psk, 8000)- BPSK: PSK31, PSK63, PSK125, PSK250, PSK500, PSK1000
- QPSK: QPSK31, QPSK63, QPSK125, QPSK250, QPSK500
- 8PSK: 8PSK125, 8PSK250, 8PSK500, 8PSK1000
- 8PSK FEC: 8PSK125F/FL, 8PSK250F/FL, 8PSK500F, 8PSK1000F, 8PSK1200F
- Multi-carrier PSK: 6 standard variants
- Multi-carrier PSK-R: 27 variants with soft-symbol FEC
- MFSK: MFSK4, MFSK8, MFSK11, MFSK16, MFSK22, MFSK31, MFSK32, MFSK64, MFSK128
- Olivia: 7 configurations (4/125, 8/250, 8/500, 16/500, 16/1000, 32/1000)
- Contestia: 7 configurations (4/125, 4/250, 8/125, 8/250, 8/500, 16/500, 32/1000)
- DominoEX: 10 variants (Micro, 4, 5, 8, 11, 16, 22, 44, 88)
- Thor: 16 variants (Micro, 4, 5, 8, 11, 16, 22, 25, 32, 44, 56, 25x4, 50x1, 50x2, 100)
- MT63: 6 variants (MT63-500/1000/2000, Short/Long interleaver)
- CW (Morse Code) - Variable speed (5-200 WPM), prosign support, edge shaping
- RTTY (Radioteletype) - Multiple baud rates and shifts, ITA-2 and US-TTY
- Hellschreiber: 8 variants (Feld Hell, Slow Hell, HellX5/X9, FSK Hell 105/245, Hell80)
- Throb: 6 variants (Throb1/2/4, ThrobX1/X2/X4)
- FSQ: 3 variants (FSQ-2, FSQ-3, FSQ-6)
- IFKP: 3 variants (IFKP-0.5, IFKP-1.0, IFKP-2.0)
- SCAMP: 6 variants (SCAMPFSK, SCAMPOOK, SCFSKFST, SCFSKSLW, SCOOKSLW, SCFSKVSL)
- NAVTEX/SITOR-B: 2 variants (NAVTEX with headers, SITOR-B raw mode)
- WEFAX (Weather Facsimile): 2 variants (WEFAX-576, WEFAX-288) - Image & text transmission mode
Total: 22 mode families, ~151 mode variants - All validated and decode correctly in fldigi!
TX (Transmit): ✅ 100% Complete - All 22 stable mode families implemented!
- ✅ Core DSP infrastructure - Filters, oscillators, FEC encoders
- ✅ 22 mode families - CW, RTTY, PSK (all variants), QPSK, 8PSK, 8PSK FEC, Multi-carrier PSK/PSK-R, MFSK, Olivia, Contestia, DominoEX, Thor, Throb, Hell, FSQ, MT63, IFKP, SCAMP, NAVTEX/SITOR-B, WEFAX
- ✅ ~151 mode variants - All decode correctly in fldigi
- 🎉 All stable fldigi TX modes complete!
RX (Receive/Decode): 🔄 32% Complete - 6 decoder families working
- ✅ PSK decoder - All rates (31/63/125/250/500/1000)
- ✅ QPSK decoder - All rates with Viterbi FEC (31/63/125/250/500)
- ✅ 8PSK decoder - Non-FEC modes (125/250/500/1000)
- ✅ 8PSK FEC decoder - All 7 modes (125F/FL, 250F/FL, 500F, 1000F, 1200F)
- ✅ CW decoder - Morse code with adaptive timing
- ✅ RTTY decoder - Baudot/ITA-2, all baud rates
- ✅ Throb decoder - All 6 modes (Throb1/2/4, ThrobX1/2/4)
- 📋 16 decoder families remaining (FSQ, MFSK, Olivia, Contestia, etc.)
See PROJECT_TRACKER.md for detailed implementation status and development roadmap.
PyDigi is structured as follows:
pydigi/
├── core/ # DSP building blocks (filters, oscillators, FFT)
├── varicode/ # Character encoding tables (Baudot, PSK varicode, etc.)
├── modems/ # Modem implementations (CW, RTTY, PSK, MFSK, etc.)
└── utils/ # Helper utilities (audio I/O, constants, etc.)
Each modem inherits from a base Modem class and implements:
tx_init(): Initialize transmittertx_process(): Process symbols and generate audiomodulate(text, frequency, sample_rate): High-level API
Full documentation is available in the docs/ directory and can be built with MkDocs:
pip install mkdocs mkdocs-material
mkdocs serveQuick references:
- Quick Start Guide - Get started quickly
- API Standard - API conventions and usage
- Project Tracker - Detailed implementation status
pytestblack pydigi/ tests/mypy pydigi/mkdocs buildPyDigi reimplements algorithms from the fldigi source code. The fldigi source is included in the fldigi/ directory for reference purposes only and is not modified.
Key reference files:
fldigi/src/include/modem.h- Base modem interfacefldigi/src/cw/cw.cxx- CW implementationfldigi/src/rtty/rtty.cxx- RTTY implementationfldigi/src/psk/psk.cxx- PSK implementation
PyDigi is licensed under the GNU General Public License v3.0 or later (GPL-3.0+), the same license as fldigi.
Contributions are welcome! We're actively working on:
- Adding receive/decode functionality
- Improving documentation and examples
- Bug fixes and optimizations
- Performance enhancements
Please see the PROJECT_TRACKER.md for detailed development priorities and status.
- Fork the repository
- Create a feature branch (
git checkout -b feature/new-modem) - Make your changes and add tests
- Ensure tests pass (
pytest) - Format your code (
black pydigi/) - Submit a pull request
For questions or discussions, please open an issue on GitHub.
PyDigi is based on the excellent work of the fldigi project, particularly:
- Dave Freese, W1HKJ
- And all fldigi contributors