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.0.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.0.0.tar.gz | 371.4 kB | Details |
Built distributions (wheels)
Total release size: 24.9 MB
Release files / weaklink_modem-1.0.0.tar.gz
| Download URL | weaklink_modem-1.0.0.tar.gz |
|---|---|
| Size | 371.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
637fbc8bd7468e83d03ece9745e7eddd4b47b65be2a07f80f13bca4345112fb6
|
|
BLAKE2b-256 checksum How to use checksums |
3db5724a73d5644f84875be5feb591630b224a93c2e06085e1e00c695122e083
|
| 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.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
abb038007ea6b4557d9dd06598a1a54ff413f275782bb7bd28d931715196fe64
|
|
BLAKE2b-256 checksum How to use checksums |
b6ed4a3345384da5cafdab8fc44c389d195bacd0c62c58c359c143102ec6624b
|
| 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.0.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.0.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 |
baf1d6eab13faccc47b1f6cd5aa1b308b91ad9b7a5aa70a0bb3d7ed8afdc5009
|
|
BLAKE2b-256 checksum How to use checksums |
435543307c21c352383bca0849c15ee76bd412c728487b18b6337d1bf25b8239
|
| 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.0.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.0.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 |
de4038cdb91c882b32be22fdd17f35e20bec3fd6e26d76b31908d14e012f1176
|
|
BLAKE2b-256 checksum How to use checksums |
03a16c65eca43b5183ef7f7e7d1ca90ff1e436c4adebc6de2214f0569a846b3c
|
| 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.0.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
baba8ee7c7517afb47512fdd79585a634dcaaaf3fd673f233762c5413e32caa3
|
|
BLAKE2b-256 checksum How to use checksums |
227c6c34fb81b66a712fbeb3e442f70b5064a0acdfd372a137c91c4d66f40fa8
|
| 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.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
33a18f5cca4ec690ca342cdec5c0a591ec5dd0288a5255639e78cb0456715597
|
|
BLAKE2b-256 checksum How to use checksums |
c93c86faeb4df3574412a741ef4c8344f8205af35f9e3f75e34dde8c7e9a7045
|
| 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.0.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.0.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 |
fbeb4ec24f3ea5de244ac1ac7964cede4c1e196e9760853eee77422b662c80a8
|
|
BLAKE2b-256 checksum How to use checksums |
84d1e16d96beae8112cbe085629ad993732e08f2fb4a526bcff1d5649390f87d
|
| 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.0.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.0.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 |
f6bd56684f4707649010df0ecc3ec13b625ba556401e1f0f02b79682bfa180da
|
|
BLAKE2b-256 checksum How to use checksums |
bb7412e8b268b0ea4f0df6cb071e6b7db7ebf14edb382e2f57500e23c4bcae2c
|
| 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.0.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
c6a1d66f0723475b1190d76372688edf9575e7f6aadea79fe7c627c7e9580e95
|
|
BLAKE2b-256 checksum How to use checksums |
375b61b5e8472f493707af903151765aab7a1fa6e4ac563685aca23b65613ae0
|
| 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.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
079b4bab3bb83028d9b153acb2fc4f742287a1557dd3fbe2ea210a65d4341cbe
|
|
BLAKE2b-256 checksum How to use checksums |
8eda9fc2b7a51ff17d481c9ad0743c5cc7f13bc677e8399b3bbef7cbd075d4d0
|
| 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.0.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.0.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 |
95d31e68cfe11c56bfe0115376328cca7b579ea9a0c0ddaac589ea61e1cc2ddd
|
|
BLAKE2b-256 checksum How to use checksums |
82e391be329b9d4a7e896d7cd549c9c3dbb6507843277b73a13843a4ee653ead
|
| 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.0.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.0.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 |
e4161bf6d9d432837ca8b37e8f76dcf466143df40906cad74c6bba45bad1905a
|
|
BLAKE2b-256 checksum How to use checksums |
07aa793bbb88a78b822eda34cba0be436be749dbcfa20fb648683ca57f62956b
|
| 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.0.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
097d75a1830ad3ededccf9df076777cb903e6e806a5cf741d81d2df4c71ade30
|
|
BLAKE2b-256 checksum How to use checksums |
3a00a4a91dc1b65b3e54ca38e42ee7fcc91c6be320ad52e5e30217c67ed43d69
|
| 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.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
cdf89b41b8a7a752ce8a4d44ed2a3ba78bfd30ffc2a6f4a73c5c1a5dcedfbf72
|
|
BLAKE2b-256 checksum How to use checksums |
3c29b5cc3b06b8603d9c86006bfe4e26683b29611cf9c596a772780a06a82342
|
| 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.0.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.0.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 |
c6948462312648e778786d74a94dd118367c0bc810b3b81d286d1a399f8b8271
|
|
BLAKE2b-256 checksum How to use checksums |
74cdefffc4b1ae4361f2ff2f3cd8b5b2dd219e5ed980a787ab1fa63476b03658
|
| 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.0.0-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.0.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 |
436b874fbd14fd99d4dcbc81dce3a909af417273ad5d6e6f88080962a74a92b1
|
|
BLAKE2b-256 checksum How to use checksums |
bbd810951248127aa295558cd9f09ff8368e9e13efd9de4876e9304c52fcff58
|
| 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.0.0-cp310-cp310-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
7f951bb25977810c441cbc41c3c6e7429b9bfd8f0e53d1817e9e00d2fa4f8c78
|
|
BLAKE2b-256 checksum How to use checksums |
32579f5eabdac884705cb5dbdb32c48bf7700bbb0b537978c9bebbe0196a6a6a
|
| 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.0.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
9b45fb30bc01e087635f535c709504c947b9ecb788c68a5547bbd04fc4cc2d5e
|
|
BLAKE2b-256 checksum How to use checksums |
a56822fb4f0a38f0340200cab8a24c0b467f107235916772053086905de3ed7b
|
| 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.0.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | weaklink_modem-1.0.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 |
d9f5f95ac787fdb4d3f69b19f35dd52865c570a529882bdd99f76ee617a15da8
|
|
BLAKE2b-256 checksum How to use checksums |
b3d473050943fd43cfdffcc385857c8983ac2939242a02604926f681c8f5064c
|
| 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.0.0-cp39-cp39-macosx_11_0_arm64.whl
| Download URL | weaklink_modem-1.0.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 |
9561903743037fc5563ab68586fd163e696353e46ca5f981cab8fbcc58ccf40e
|
|
BLAKE2b-256 checksum How to use checksums |
44d6425d885d3b4c13389f7a01f817e2fa2542aa4625a78f10a1ae5d21769311
|
| 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.0.0-cp39-cp39-macosx_10_9_x86_64.whl
| Download URL | weaklink_modem-1.0.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 |
63e13e63e2fbfdf992f03f59b7a746b5bab5bca26fafaffd5f94b1dca227c463
|
|
BLAKE2b-256 checksum How to use checksums |
e6fbdf257e5bb4f2fa56933535d220117207076b919c47ae7fb8fa17ed5fb0f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|