weaklink-modem
Streaming MFSK modem: bytes in → audio → bytes back. Reed-Solomon + convolutional K=7 r=1/2 + soft Viterbi, per-block interleaver, soft-LLR combining across block repeats. Modes: OOK and 2/4/8/16-FSK at 45 / 300 / 1200 baud.
This is the C++ implementation, behind a Python API and a native CLI. It is
wire compatible with the pure-Python weaklink-modem it replaces: audio from
one decodes on the other, bit for bit.
Install
pip install weaklink-modem
Wheels: Linux x86_64 / aarch64, macOS arm64 / x86_64, CPython 3.9–3.13. Other platforms build from the sdist and need CMake 3.18+ and a C++17 compiler.
The wheel carries the weaklink-modem CLI as a native executable, not a
Python launcher, so there is no interpreter startup in front of it.
Quickstart
from weaklink.modem import ModemOptions, rx, tx
options = ModemOptions(baud=300, num_tones=4)
samples = tx(b"hello weaklink", options) # float32 numpy array
print(rx(samples, options).rstrip(b"\x00")) # b'hello weaklink'
Through a WAV:
tx(b"hello", wav="hello.wav")
data = rx(wav="hello.wav")
From the shell, same binary the releases ship:
echo -n "hello weaklink" | weaklink-modem tx --modem-wav /tmp/hello.wav
weaklink-modem rx --modem-wav /tmp/hello.wav
Both sides must agree on the mode. There is no handshake: a receiver
running different baud or num_tones hears nothing and tells you nothing.
It is the first thing to check when a decode comes back empty.
Decoded bytes are NUL-padded to the block size, which is why the examples strip them.
Why this one
Both implementations encode the same 210-byte payload and decode the same
WAV, at 4 tones. Apple M3 Pro, release build against CPython 3.14 + NumPy,
CPU time as charged by wait4, minimum of five runs:
| Baud | Audio | Decode CPU (C++ / Python) | Decode load at real time | Speedup |
|---|---|---|---|---|
| 45 | 164.1 s | 0.44 s / 2.86 s | 0.27% / 1.7% of one core | 7x |
| 300 | 12.4 s | 0.05 s / 1.33 s | 0.38% / 10.7% of one core | 28x |
| 1200 | 3.2 s | 0.02 s / 1.18 s | 0.66% / 36.6% of one core | 56x |
Decoding is the figure that matters, because a receiver has to keep up with audio as it arrives. At 1200 baud the pure-Python implementation spends about 37% of a core to stay real time; this one spends under 1% — the difference between needing a laptop and running on a Pi alongside everything else.
Presets
The three baud rates are presets, not fixed configurations: every
parameter is overridable through ModemOptions, and both sides have to carry
the same overrides. Defaults are 4-FSK with RS(16,8) + CRC-32.
| Baud | Default tones | Bandwidth | Repeats | 13 B | 100 B | 1 kB |
|---|---|---|---|---|---|---|
| 45 | 1200 / 1400 / 1600 / 1800 Hz | 600 Hz | 4x | 26.3 s | 205.5 s | 32.9 min |
| 300 | 1050 / 1350 / 1650 / 1950 Hz | 900 Hz | 2x | 2.0 s | 15.5 s | 2.5 min |
| 1200 | 500 / 1700 / 2900 / 4100 Hz | 3600 Hz | 2x | 0.5 s | 3.9 s | 37.0 s |
Time on air is measured, at 18 kHz mono. Slower bauds are not merely slower:
they repeat each block more and spread it over a narrower band, which is what
buys their noise tolerance. Raising block_repeats buys a few more dB of
margin at proportional air time.
num_tones=M means M frequencies to choose from per symbol, not M playing
at once. One sinusoid is on air at any instant, hopping at the symbol clock,
so higher M carries log₂(M) bits per symbol at no cost in peak power.
num_tones=1 is OOK: one carrier, symbol 0 silent.
Streaming
Audio never crosses into Python. Either hand over samples as they arrive:
from weaklink.modem import ModemOptions, Receiver
receiver = Receiver(ModemOptions(baud=300), on_bytes=lambda b: print(b, flush=True))
print(receiver.sample_rate) # 18000 -- what your capture must produce
for chunk in my_audio_source(): # float32 mono, any chunk size
receiver.push(chunk)
receiver.drain() # flush the tail; nothing more is coming
or name a device and let the modem open it, with no audio library on the Python side at all:
receiver.listen("default") # blocks until Ctrl-C
Device hints take an index, a name substring, or a pulse identifier; an empty hint means the OS default.
Threads
A Receiver belongs to one thread. push, drain and listen refuse to run
concurrently on the same instance and raise ConfigError rather than let two
callers corrupt a decode. Use one Receiver per thread if you need several.
on_bytes runs on the decoder's thread, never the audio callback thread, and
always with the GIL held — ordinary Python work is fine inside it.
Options
ModemOptions(
baud=300.0, # 45, 300 or 1200
num_tones=4, # 1 (OOK), 2, 4, 8 or 16
rs_data_bytes=None, # None means the baud preset's value
rs_parity_bytes=None,
rs_crc_enabled=True,
block_repeats=None,
sync_every_blocks=None,
tone_spacing_hz=None,
)
Not every combination fits: 16 tones at 1200 baud needs more bandwidth than
18 kHz allows, and raises NyquistError.
Diagnostics
Nothing is written anywhere until you ask for it — a library has no business writing to a caller's stdout:
from weaklink.modem import set_log_file
set_log_file("modem.log", debug=True)
Errors
WeaklinkError is the base. ConfigError (and its subclass NyquistError),
EncodeError and PTTError sit under it.
from weaklink.modem import ConfigError, rx
try:
rx()
except ConfigError as error:
print(error) # rx() requires one of samples=, wav=, audio_input=
Source
https://github.com/ivica3730k/weaklink-modem-cpp
The C++ library is consumable on its own through CMake, via find_package,
FetchContent or add_subdirectory.
MIT licensed.
Metadata
Release files for weaklink-modem 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| weaklink_modem-1.1.0.tar.gz | 148.5 kB | Details |
Built distributions (wheels)
Total release size: 24.7 MB
Release files / weaklink_modem-1.1.0.tar.gz
| Download URL | weaklink_modem-1.1.0.tar.gz |
|---|---|
| Size | 148.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b5afc92e2c0052276b52c6e7a4c466f63da1dbb0541f7be98872cc37ca174e9
|
|
BLAKE2b-256 checksum How to use checksums |
5e01c3d8656f7eeaedb85880a41a0e87fd08bf9fca9333ec3f50b93baca55fe6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
36fcf1c270c34d1bb5cb5f2c9f03d84352f3a59bc2546396c2c338eb2f3e47b5
|
|
BLAKE2b-256 checksum How to use checksums |
f3e1bd8855911883f062b3ba1d36571ab3608de8a9d4a66690a167cfaffe120a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.13 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
e9a449c2a640594c4e4446ab4fa4b3eda3060004f1cbd2c9dbc90f72ee759648
|
|
BLAKE2b-256 checksum How to use checksums |
b3c96ab9ecbe0903b90cfe20a285a121f450fd488f74c55db4df965fdfd536b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.1.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
81a9937fe85d9d52a0dd4f586e7ec86a8f304fee969f76ac7051125143052b54
|
|
BLAKE2b-256 checksum How to use checksums |
0acae5bc9b2181d8b68f0a8d9b601ab2f2d43ab4c6b5c7ab026369fd927e42b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.13 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
b6364a3326e85fbe1e4ad0264c4bd719d244abf1bfd10782d3cb6ee32b59bb13
|
|
BLAKE2b-256 checksum How to use checksums |
1f4d2321a63fedb0695472d3537ae9024d9d42a08f26612f19b16291b68e2bcb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
b594646ac9ced4dabed2d34de279646faa11432b03f8a7a404572e73d229b56c
|
|
BLAKE2b-256 checksum How to use checksums |
0e0cc9c565359ddaf97e214f4e51dd6a368df58feb71e92c434845f018cb0ea4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.12 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
a373ce6afb9ae37e9c2c73925ed78a301e7ce7f51eb271131152405eb8e04a65
|
|
BLAKE2b-256 checksum How to use checksums |
62fdaff6765842278d7f12cbfde49e1edfd62e1c7658ba1828826e9fffba6aae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.1.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3e7a4a4465161ce0c708e321ef3fbdce94277e23a169bcb7693aba86d2447d1b
|
|
BLAKE2b-256 checksum How to use checksums |
7739d15cfdb697a8caf074b2a63f15a6e509f4807d0679305ac685c2d41295c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.12 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
725c0258a5fc8a6521b3c9162aee4519864da7a94d15ba68b0fab738b3ef1bda
|
|
BLAKE2b-256 checksum How to use checksums |
cfb730581e97dca72c5af9ab8efaeb4cce08542177b0c570ca137a16e1d3867e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
f221bdac6eaf4eae4cae08b04055248dcdf93d490a4a4d2af6ff1878f84e0564
|
|
BLAKE2b-256 checksum How to use checksums |
0403532925a654e47dedb1dc2d170617fafa9ca203ff15b3c1e3bd9dad20e415
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.11 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
e7ea2e574f74ea2dafe446ea96a8f9d2c108ed2bbacb8079ea0abc4b71a7705d
|
|
BLAKE2b-256 checksum How to use checksums |
4540118b2a497cef9906eb3870c8b7aa3f6f1f29102b971d20adca919998c483
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.1.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
094cade9da44a39f3dbec4ed2e522798a5eb6259843d2b18f238812b5825d5d2
|
|
BLAKE2b-256 checksum How to use checksums |
b899dbb58e072b21f13c9967613c45db46190ccbd5617930a1f466c4bd924cb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.11 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
21035b0c6553b88a9904cd36ff6ef53dd96466507d2ebd5b5ee3994068d1cdda
|
|
BLAKE2b-256 checksum How to use checksums |
d3d846efb5578229ce6e7d3ff5a9f9fd3db4b025bea3d1945c1b683c9a910083
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.10 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
5be9979b2a0aeba9387b922bf3a1a3d11ba7de123327eb22fd385e844be26205
|
|
BLAKE2b-256 checksum How to use checksums |
9f93744384c40366161bd8019d2591dca751e76859923a4f925d9ed110d88886
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.10 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
91b7d9a31e31ab854bd905d3db139863302dd736949c348b969901c1eefd8e24
|
|
BLAKE2b-256 checksum How to use checksums |
174eea39ff13050b6f9815f13311ea8a540e5a257c694deea37822cdb1269928
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.1.0-cp310-cp310-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
4541816b47d9fca1a3adcdcad8cfb17a2b09056a774fd5d91f9e2154e97294c4
|
|
BLAKE2b-256 checksum How to use checksums |
b4909ac842d3092e8fed6074cdd7c9da8ae0e6b0667afc6f50746a859bc96a52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.10 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
282cdbb680fa489114df4504032bb1c8369d2da6e5b858b238aac5893dfbdecc
|
|
BLAKE2b-256 checksum How to use checksums |
9202bd9d714dec047d1499d20be7d09c3fd6e54f06f227c6594ff8dfbe3c7f74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.9 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
f5fbadc368e7f5f556f8d7cc64408b299e7a2249e0267ddbd51d055cae641484
|
|
BLAKE2b-256 checksum How to use checksums |
8978cb783a9cad74f64125d76d6e1e54456617980927cf05760f8b572b01b6c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.9 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
75ef0f911c6708c0c3e7817732a1a2290582818f5782cbf1356eada33fb5c8d3
|
|
BLAKE2b-256 checksum How to use checksums |
9bf52fcb85ed81c91663415c462e400b970f7174f4430dff7f7b36f2a5b4710f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp39-cp39-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.1.0-cp39-cp39-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.9 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b5f0bbd4abbd1ff5a5233587f89ad1ffda47f9ab5ebefbca150ca42b6026a5dd
|
|
BLAKE2b-256 checksum How to use checksums |
3afb070fde7c3354bfd552dc6eb6673967ea94b3db9b8f92471836988692c386
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / weaklink_modem-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.9 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
91e35f660f339fcb675a5a5fc3d4afc09fe03746da27c8932fa56f5c347fe86e
|
|
BLAKE2b-256 checksum How to use checksums |
1014b011db3af7ac708d4017141fe219a6f55ed5b57423e8477de9d44ae0edce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|