| project | openpulsehf |
|---|---|
| doc | README.md |
| status | living |
| last_updated | 2026-06-29 |
A plugin-based HF/VHF/UHF software modem and protocol stack written in Rust.
OpenPulseHF is a multi-crate Rust workspace providing a full digital communications stack — from DSP primitives and adaptive rate profiles through ARDOP/KISS TNC interfaces, B2F/Winlink protocol support, QSY frequency agility, mesh networking, and post-quantum key exchange. The plugin architecture lets modulation modes be added without touching the core modem engine. All tests run against a deterministic loopback backend; no audio hardware is required to build or test.
Author: Simon Keimer · DC0SK
Big thanks to:
DD2ZM for providing access to a remote controllable station for testing.
DB1IUA for helping stuffing the budget sink-hole.
Notfunk Deutschland e.V. for providing space on their booth at HAM RADIO 2026.
🎉 v0.3.0 released (29th of June '26) — release notes. Headline: the daemon now runs a signed handshake over the air on connect — it exchanges and verifies an Ed25519 ConReq/ConAck, stores the peer's verified callsign + Maidenhead grid, and writes the verified grid to an automatic ADIF logbook. Also new: opt-in ARDOP adaptive ARQ (making the host ARQBW/ARQTIMEOUT hints real), a generic serial CAT backend for rigs Hamlib doesn't support, a reworked operator panel (right-hand controls, full-width waterfall, AGC toggle) with full daemon/CLI/panel control parity, receiver auto-notch + auto-QSY on in-band interference, and linksim I/Q constellation views with a --serve mode so the panel can attach with no radio.
v0.2.1 (24th of June '26) — headline feature: CE-SSB transmit envelope conditioning (per-mode, default-on for the high-PAPR OFDM/SC-FDMA modes). The average-power gain at fixed PEP is confirmed on-air (+1.18 dB on 2 m via an FT-991A), matching the channel-sim prediction; software ACPR and an on-air SDR spectral-mask check show no added splatter on QPSK OFDM (dense OFDM-HOM stays clean at normal data-mode drive). The operator panel now carries a CE-SSB toggle and a tabbed Messages/Event-Log pane.
9th of June '26, the soundsystem bugs have been fixed and validated, now returning to on-air tests.
As of 6th of June '26, I'm busy with on-air-testing and fixing a lot of bugs and some misconceptions. Mostly related to Linux' soundsystems.
Several capabilities here are firsts or near-firsts in open-source amateur digital modes:
| Capability | What makes it different |
|---|---|
| Post-quantum link security | ML-DSA-44 signing + ML-KEM-768 key encapsulation negotiated in-band. Hybrid mode signs with both Ed25519 and ML-DSA-44 simultaneously. No other open HF modem does this. |
| SC-FDMA waveform on HF | Single-Carrier FDMA (the LTE uplink waveform) brought to HF with DFT-CE pilot-aided channel estimation and MMSE equalization. (DFT-spread is the low-PAPR-capable structure; the current frequency-interleaved pilot scheme limits the realized PAPR — see mode/FEC guide §7.) |
| 64QAM and SCFDMA-64QAM with soft demodulation | Gray-coded 64QAM with max-log-MAP soft demodulator. Aggressive constellation for VHF/UHF links with proper soft FEC backing. |
| Pilot-framed carrier recovery | A single-carrier waveform family (PILOT-QPSK500…PILOT-32APSK500, up to DVB-S2 32APSK) whose known in-band pilots drive carrier tracking instead of a decision-directed Costas loop — cycle-slip-immune on dense constellations and robust to soundcard sample-rate offset. |
| LDPC belief propagation | Real rate-1/2 min-sum belief propagation — not a stub. First open-source HF software modem with working LDPC. |
| LLR-accumulating ARQ | Soft LLR values accumulate across retransmissions (PACTOR-style Memory-ARQ), turning each retry into a soft combining gain. |
| GPU-accelerated DSP | 6 wgpu compute kernels (BPSK modulate/demodulate, timing search, RRC FIR matched filter, 256-pt FFT, soft LLR demod) accelerating BPSK, QPSK, 8PSK, 64QAM, and SC-FDMA — all with automatic CPU fallback. See GPU-accelerated features. |
| QSY frequency agility | Ed25519-signed QSY_REQ/LIST/VOTE/ACK wire protocol. Initiator and responder roles wired into the daemon; rig CAT control via rigctld. |
| FreeDV authenticated voice | Ed25519-signed authentication beacons transmitted via the FreeDV Qt-GUI UDP data port (openpulse-freedv-auth); no FreeDV fork required. |
| CE-SSB conditioning for data modes | Controlled-Envelope SSB (Hershberger W9GR, QEX 2014) — a voice-SSB technique — applied as an adaptive, per-mode TX conditioner that raises average power at fixed PEP on high-PAPR multicarrier modes (OFDM/SC-FDMA). Confirmed +1.18 dB on-air, with software and on-air SDR spectral checks showing no added splatter on QPSK OFDM (dense HOM clean at normal data-mode drive). Believed to be the first open-source HF data modem to do this. |
| JS8-based discovery + rendezvous | When idle, a station QSYs to the band's JS8 calling frequency, participates as a real JS8 station (native 8-GFSK / LDPC(174,87) waveform — not a JS8Call bridge), marks itself with an in-band @OPULSE capability hint, and caches other OpenPulse stations. On request it negotiates a working frequency over JS8, QSYs there, and hands off to a native signed HPX session. Off by default; RX-only until the operator opts into TX (§97.221-documented). |
| Direct P2P file transfer with cryptographic verification | Send a file to a connected peer over an RF session with an offer/accept handshake, progress, and size-gated auto-accept — plus an inline signed TransferManifest + SHA-256 verified against the peer's handshake key, so a tampered or wrong-key file is quarantined with an UNVERIFIED badge. That end-to-end verification is something VarAC's file transfer does not do. |
On-air regulatory validation (spectral mask) has not been completed. Except for the CE-SSB average-power confirmation above, all tests use loopback and simulated-channel paths only.
Capabilities that are firsts or near-firsts among open-source amateur digital-mode software:
| # | Capability | Evidence / where to look |
|---|---|---|
| 1 | Post-quantum in-band handshake | ML-DSA-44 + ML-KEM-768 negotiated inside the ConReq/ConAck wire frames; Hybrid mode dual-signs with Ed25519 + ML-DSA-44 simultaneously (crates/openpulse-core/src/pq_handshake.rs) |
| 2 | SC-FDMA (LTE uplink waveform) on HF | DFT-spread OFDM with DFT-CE pilot-aided channel estimation and MMSE equalization (plugins/scfdma). DFT-spread is the low-PAPR-capable structure; realizing the full PAPR advantage over OFDM would need a non-interleaved pilot scheme. That redesign (old roadmap FF-14) was dropped in favour of the OFDM higher-order ladder as the HF high-throughput path — see mode/FEC guide §7 |
| 3 | 64QAM and SCFDMA-64QAM soft demodulation | Gray-coded 64QAM max-log-MAP LLR demodulator; SCFDMA52-64QAM reaching 8 667 bps gross over a 2 kHz slice (plugins/64qam, plugins/scfdma) |
| 4 | Working LDPC belief propagation | Rate-1/2 min-sum BP — not a passthrough stub; wired into transmit_with_ldpc / receive_with_ldpc in the modem engine (crates/openpulse-core/src/ldpc.rs) |
| 5 | LLR-accumulating Memory-ARQ | Soft LLR values accumulated across retransmissions (PACTOR-style, SoftCombiner in crates/openpulse-core/src/fec.rs); HARQ retry/mode policy on sustained NACK (crates/openpulse-modem/src/harq.rs, rate_policy.rs) |
| 6 | GPU DSP across 5 modulation families | 6 wgpu WGSL kernels (BPSK modulate/demodulate, timing search, RRC FIR, 256-pt FFT, soft demod) accelerating BPSK, QPSK, 8PSK, 64QAM, and SC-FDMA, each with CPU fallback — see GPU-accelerated features |
| 7 | Ed25519-signed QSY frequency agility | Full initiator + responder state machines wired into the daemon; SNR-ranked channel-list negotiation; rig CAT via rigctld (crates/openpulse-qsy) |
| 8 | Zstd pre-trained compression dictionary | Dictionary trained on amateur/Winlink traffic patterns; negotiated at session setup and covered by handshake signature (crates/openpulse-core/src/compression.rs) |
| 9 | Trust-weighted multi-hop relay with query propagation | RelayForwarder enforces hop limits and suppresses duplicates; score_route weights paths by trust level (Verified=4 … Reduced=1); QueryForwarder propagates route-discovery requests across nodes (crates/openpulse-core/src/relay.rs, query_propagation.rs) |
| 10 | Cross-band full-duplex repeater | CrossBandRepeater runs in a daemon-managed thread; EnableRepeater/DisableRepeater control commands; trust-policy filtering on forwarded frames (crates/openpulse-repeater) |
| 11 | Mesh broadcast daemon with authenticated beacons | TTL-limited re-broadcast; (session_id, nonce) duplicate suppression; beacon payloads carry signed peer descriptors where the peer ID is the Ed25519 verifying key (crates/openpulse-mesh) |
| 12 | FreeDV frame signing via codec2 data channel | External shim adding Ed25519 per-frame signatures to FreeDV voice transmissions using the codec2 embedded data channel; no FreeDV fork required |
| 13 | CE-SSB envelope conditioning for digital waveforms | Hershberger's Controlled-Envelope SSB (QEX 2014) — long used in voice SSB (WDSP/Thetis) — applied here as a per-mode, default-on adaptive TX conditioner for high-PAPR multicarrier data modes (OFDM/SC-FDMA). Raises average TX power at fixed PEP: +1.6/+2.7/+3.8 dB in channel-sim (OFDM52 at 2.5/2.0/1.5×rms, zero BER cost) and +1.18 dB confirmed on-air (FT-991A, 2 m, 20 W via attenuator); software ACPR and an on-air SDR spectral-mask check (SDRplay RSP2pro) both confirm CE-SSB raises average power without added splatter on QPSK OFDM; on dense OFDM-HOM it stays clean at normal data-mode drive (the bigger average-power boost there only splatters if the PA's ALC is over-driven). Believed to be the first open-source HF data modem to apply CE-SSB (crates/openpulse-dsp/src/cessb.rs, ModemEngine::cessb_benefits) |
| 14 | JS8-based station discovery + rendezvous | A native JS8 waveform (8-GFSK, 79 symbols, Costas 3×7 sync, LDPC(174,87) — ported from GPL-3.0 JS8Call and validated bit-exact against compiled Boost/Qt5, not a bridge to JS8Call) that lets a station discover peers on the shared JS8 calling frequency via an in-band @OPULSE capability hint, then negotiate a working frequency over a 2-message rendezvous and QSY to a native signed HPX session. RX-only by default; all TX gated behind an explicit mode + callsign + ±2 s clock-skew/DCD gates and the §97.221 automatic-control documentation (plugins/js8, crates/openpulse-discovery) |
| 15 | Signed-manifest direct P2P file transfer | Offer/accept file transfer over an RF session (OPFX wire protocol, ≤48 KiB blocks over SAR, hybrid OTA-rate + BlockAck-bitmap selective retransmit, block-level resume) with an inline signed TransferManifest + SHA-256 verified against the peer's CONREQ/CONACK key — verify-fail quarantines the file with an UNVERIFIED badge. Cryptographic end-to-end verification that VarAC's file transfer lacks (crates/openpulse-filexfer) |
Sorted by occupied bandwidth (the mode-name number is the baud rate for
single-carrier modes; SC-FDMA/OFDM are named by data-subcarrier count and span
total_SCs × 31.25 Hz). The single-carrier modes also have -RRC (α = 0.35,
~+35 % bandwidth) and -HF tuning variants not all listed here. The -RRC
variants are the operational, carrier-offset-robust ones at 2000 baud: the plain
rectangular QPSK2000/8PSK2000 are registered but RRC-superseded (their
crossfade pulse is ISI-limited at 4 samples/symbol — use -RRC).
The PILOT-* modes are a pilot-framed single-carrier family: known in-band pilot
symbols drive carrier recovery (cycle-slip-immune, sample-rate-offset-robust)
instead of a decision-directed Costas loop — see the
pilot-framed waveform note.
| Mode | Plugin | Baud | Bits/sym | Gross bps | Occ. BW (Hz) | Waveform | Notes |
|---|---|---|---|---|---|---|---|
| BPSK31 | bpsk |
31.25 | 1 | 31 | ~50 | Single-carrier | Weak-signal narrowband HF |
| BPSK63 | bpsk |
62.5 | 1 | 63 | ~70 | Single-carrier | |
| BPSK100 | bpsk |
100 | 1 | 100 | ~110 | Single-carrier | |
| QPSK125 | qpsk |
125 | 2 | 250 | ~140 | Single-carrier | |
| BPSK250 | bpsk |
250 | 1 | 250 | ~275 | Single-carrier (+RRC) | |
| QPSK250 | qpsk |
250 | 2 | 500 | ~275 | Single-carrier | |
| FSK4-ACK | fsk4 |
100 | 2 | 200 | ~400 | 4-FSK | ACK control channel only |
| QPSK500 | qpsk |
500 | 2 | 1 000 | ~550 | Single-carrier (+RRC) | |
| 8PSK500 | psk8 |
500 | 3 | 1 500 | ~550 | Single-carrier (+RRC) | Gray-coded |
| 64QAM500 | 64qam |
500 | 6 | 3 000 | ~550 | Single-carrier | |
| PILOT-QPSK500 | pilot |
500 | 2 | 1 000 | ~550 | Single-carrier (pilot-framed) | In-band pilots → cycle-slip-immune carrier tracking; SRO-robust |
| PILOT-8PSK500 | pilot |
500 | 3 | 1 500 | ~550 | Single-carrier (pilot-framed) | Gray-coded; pilot-aided |
| PILOT-16QAM500 | pilot |
500 | 4 | 2 000 | ~550 | Single-carrier (pilot-framed) | Pilot-amplitude-referenced demap |
| PILOT-32APSK500 | pilot |
500 | 5 | 2 500 | ~550 | Single-carrier (pilot-framed) | DVB-S2 32APSK geometry |
| OFDM16 | ofdm |
— | 2 | ~889 | ~625 | OFDM (16 SCs, QPSK) | LS + ZF; ≡ SCFDMA16 throughput |
| SCFDMA16 | scfdma |
— | 2 | ~889 | ~625 | SC-FDMA (16 SCs, QPSK) | DFT-CE + MMSE |
| SCFDMA26-8PSK | scfdma |
— | 3 | ~2 167 | ~1 000 | SC-FDMA (26 SCs, 8PSK) | Narrowband HOM (+3 dB/SC) |
| SCFDMA26-16QAM | scfdma |
— | 4 | ~2 889 | ~1 000 | SC-FDMA (26 SCs, 16QAM) | Narrowband HOM (+3 dB/SC) |
| SCFDMA26-32QAM | scfdma |
— | 5 | ~3 611 | ~1 000 | SC-FDMA (26 SCs, cross-32QAM) | Narrowband HOM (+3 dB/SC) |
| QPSK1000 | qpsk |
1 000 | 2 | 2 000 | ~1 100 | Single-carrier (+RRC/HF) | |
| 8PSK1000 | psk8 |
1 000 | 3 | 3 000 | ~1 100 | Single-carrier (+RRC/HF) | |
| 64QAM1000 | 64qam |
1 000 | 6 | 6 000 | ~1 100 | Single-carrier | |
| OFDM52 | ofdm |
— | 2 | ~2 889 | ~2 031 | OFDM (52 SCs, QPSK) | ≡ SCFDMA52 throughput; OFDM trades PAPR for per-SC EQ |
| OFDM52-8PSK | ofdm |
— | 3 | ~4 333 | ~2 031 | OFDM (52 SCs, 8PSK) | OFDM higher-order ladder — the HF high-throughput path |
| OFDM52-16QAM | ofdm |
— | 4 | ~5 778 | ~2 031 | OFDM (52 SCs, 16QAM) | OFDM higher-order ladder |
| OFDM52-32QAM | ofdm |
— | 5 | ~7 222 | ~2 031 | OFDM (52 SCs, cross-32QAM) | OFDM higher-order ladder |
| OFDM52-64QAM | ofdm |
— | 6 | ~8 667 | ~2 031 | OFDM (52 SCs, 64QAM) | OFDM higher-order ladder |
| SCFDMA52 | scfdma |
— | 2 | ~2 889 | ~2 031 | SC-FDMA (52 SCs, QPSK) | Adaptive pilot density |
| SCFDMA52-P2 | scfdma |
— | 2 | ~2 889 | ~2 031 | SC-FDMA (52 SCs, PN pilots) | Low-PAPR SCFDMA52: Zadoff-Chu-phase pilots cut envelope-CCDF PAPR ~2.15 dB (8.85→6.70) at the same geometry/rate and keep full DFT-CE (beats the flat-CE SCFDMA52-LP); demonstrator, not in any profile |
| SCFDMA52-LP | scfdma |
— | 2 | ~3 389 | ~2 031 | SC-FDMA (61 SCs, localized) | Low-PAPR demonstrator (~2 dB lower mean PAPR: 11.9→9.7 — mostly from 4 pilots vs 13, only ~0.5 dB from localization); single-tap flat-channel CE ⇒ AWGN/flat/well-timed only (silently mis-decodes on selectivity/tilt/timing error), not in any profile |
| SCFDMA52-8PSK | scfdma |
— | 3 | ~4 333 | ~2 031 | SC-FDMA (52 SCs, 8PSK) | |
| SCFDMA52-16QAM | scfdma |
— | 4 | ~5 778 | ~2 031 | SC-FDMA (52 SCs, 16QAM) | |
| SCFDMA52-32QAM | scfdma |
— | 5 | ~7 222 | ~2 031 | SC-FDMA (52 SCs, cross-32QAM) | |
| SCFDMA52-64QAM | scfdma |
— | 6 | ~8 667 | ~2 031 | SC-FDMA (52 SCs, 64QAM) | |
| SCFDMA52-64QAM-P4 | scfdma |
— | 6 | ~8 167 | ~2 031 | SC-FDMA (49 SCs, dense pilots) | |
| QPSK2000-RRC | qpsk |
2 000 | 2 | 4 000 | ~2 700 | Single-carrier + RRC | |
| 8PSK2000-RRC | psk8 |
2 000 | 3 | 6 000 | ~2 700 | Single-carrier + RRC | |
| 64QAM2000-RRC | 64qam |
2 000 | 6 | 12 000 | ~2 700 | Single-carrier + RRC | Requires SNR ≥ 25 dB |
| QPSK9600-RRC | qpsk |
9 600 | 2 | 19 200 | ~13 000 | Single-carrier + RRC | Deferred (post-1.0) — VHF/UHF, needs ≥38.4 kHz Fs |
| 8PSK9600-RRC | psk8 |
9 600 | 3 | 28 800 | ~13 000 | Single-carrier + RRC | Deferred (post-1.0) — VHF/UHF, needs ≥38.4 kHz Fs |
Each PILOT-* mode above also has a -RRC variant (~half the occupied bandwidth) and
1000 / 2000-RRC baud rungs — e.g. PILOT-16QAM1000-RRC (16QAM, 1000 baud, RRC) — all
selectable by name and surfaced by the hpx_pilot{,_rrc,_fast,_fast_rrc} profiles.
The mode/FEC selection ladder and which combinations are usable on HF is documented in docs/mode-fec-ladder.md.
| Mechanism | Where used | Description |
|---|---|---|
| 0.3-persistence CSMA | openpulse-modem |
DCD energy check; transmit deferred when channel busy; configurable per ModemEngine |
| DCD energy threshold | openpulse-core (dcd.rs) |
RMS energy gate with configurable hold window (default 100 ms); forced-busy override for testing |
| HPX adaptive session | openpulse-core (hpx.rs) |
ACK/NACK-driven speed-ladder state machine; RateAdapter with per-level SNR gates and NACK-decrement hysteresis |
| ARQ retry loop | openpulse-modem (arq_session.rs) |
LLR-accumulating retransmission loop; mode switching on sustained NACK; configurable retry limit |
| QSY frequency agility | openpulse-qsy |
SNR-ranked channel-list negotiation; initiator transmits QSY_REQ → LIST → VOTE/ACK; responder role wired into daemon receive path |
| Cross-band repeater | openpulse-repeater |
Full-duplex digipeater; configurable trust-policy filter; EnableRepeater/DisableRepeater daemon commands |
| Mesh re-broadcast | openpulse-mesh |
TTL-limited beacon re-broadcast with (session_id, nonce) duplicate suppression |
| Multi-hop relay | openpulse-core (relay.rs) |
RelayForwarder with trust-weighted path scoring; hop-limit enforcement; score_route/select_best_scored_route |
| Algorithm | Layer | Direction | Notes |
|---|---|---|---|
| LZ4 | Session (in-band) | Both | lz4_flex; transparent negotiation in ConReq/ConAck; fast, good for structured text |
| Zstd + HPX dictionary | Session (in-band) | Both | Pre-trained dictionary on amateur/Winlink traffic; best compression ratio |
| None | Session (in-band) | Both | Binary payloads that are already compressed |
| Gzip | B2F wire (Type D) | Both | flate2; Winlink Type D proposal |
| LZHUF / LH5 | B2F wire (Type C) | Both | oxiarc-lzhuf; 4-byte LE prefix; Winlink Type C wire-compatible |
Compression algorithm negotiated at session setup via supported_compression / selected_compression fields in ConReq/ConAck, covered by Ed25519 signature — post-signing injection is detectable.
| Type | Crate / module | Description |
|---|---|---|
| Stop-and-wait ARQ | openpulse-modem (engine.rs) |
Basic per-frame ACK; NACK triggers retransmit of last frame |
| LLR-accumulating ARQ (Memory-ARQ) | openpulse-modem (arq_session.rs) |
Soft LLR values accumulated across retransmissions (PACTOR-style); each retry adds soft-combining gain; mode switch on sustained NACK |
| SAR (Segmentation and Reassembly) | openpulse-core (sar.rs) |
4-byte header (segment_id, fragment_index, fragment_total); max 64 005 bytes per segment; configurable reassembly timeout; duplicate-idempotent |
| QSY ACK | openpulse-qsy |
Ed25519-signed ACK/REJECT frames completing the QSY_REQ → LIST → VOTE → ACK negotiation loop |
| Algorithm | Crate / module | Code rate | Strength | Notes |
|---|---|---|---|---|
| Reed-Solomon RS(255,223) | openpulse-core (fec.rs) |
223/255 ≈ 0.87 | 16-byte burst correction per block | Default for HF burst-error profiles; always paired with block interleaver |
| Reed-Solomon RS(255,191) | openpulse-core (fec.rs) |
191/255 ≈ 0.75 | 32-byte burst correction per block | Higher erasure tolerance |
| Block interleaver | openpulse-core (fec.rs) |
1.0 | Disperses burst errors | Configurable depth; used with RS by default |
| Convolutional K=3 | openpulse-core (conv.rs) |
1/2 | AWGN-dominant paths | G={7,5}; hard-decision Viterbi; better than RS at random-error BER 1% |
| LDPC rate-1/2 | openpulse-core (ldpc.rs) |
1/2 | Highest coding gain | Min-sum belief propagation; configurable iterations; first open-source HF modem with working LDPC |
| LDPC high-rate (PEG) | openpulse-core (ldpc.rs) |
8/9 (k=1024, n=1152) | Throughput on strong channels | Progressive Edge-Growth graph; soft-decision; auto-selected on dense high-SNR rungs (FecMode::LdpcHighRate) |
| Turbo (rate-1/3 PCCC) | openpulse-core (turbo.rs) |
1/3 | Near-capacity on AWGN | RSC K=3, 3GPP QPP interleaver K=40–6144, Max-Log-MAP BCJR, 8 iterations, CRC-16 early exit |
| Concatenated RS + Conv | openpulse-core |
~0.44 | Strong burst + AWGN | RS outer, Conv inner |
| Short-block FEC | openpulse-core |
varies | ACK/control frames | For FSK4-ACK and small control payloads |
All GPU functions return Option<T> — None triggers automatic CPU fallback. Gated by #[cfg(feature = "gpu")]; --no-default-features builds use CPU paths throughout.
| Feature | Kernel | Crate / file | Description |
|---|---|---|---|
| BPSK modulation | bpsk_modulate.wgsl |
openpulse-gpu / bpsk-plugin |
GPU symbol mapping and carrier generation (bpsk_modulate_gpu) |
| BPSK IQ demodulation | bpsk_demodulate.wgsl |
openpulse-gpu / bpsk-plugin |
Parallel IQ correlation across all sample offsets (bpsk_iq_demod_gpu) |
| Timing-offset search | timing_search.wgsl |
openpulse-gpu / bpsk-plugin |
Symbol-timing offset search via parallel energy integration |
| RRC FIR matched filter | rrc_fir.wgsl |
openpulse-gpu |
Causal RRC pulse-shaping/matched filter (gpu_rrc_fir); wired into the RRC paths of BPSK, QPSK, 8PSK, and 64QAM |
| 256-pt FFT / IFFT | fft256.wgsl |
openpulse-gpu |
Cooley-Tukey radix-2 DIT; one workgroup per symbol; shared-memory in-place butterfly |
| Batch FFT (SC-FDMA hard demod) | fft256.wgsl (batched) |
scfdma-plugin |
All per-symbol FFTs in one gpu_fft256_batch call; CPU DFT-CE + MMSE + demap; covers the QPSK/8PSK/16QAM/32QAM/64QAM SC-FDMA variants |
| Batch FFT (SC-FDMA soft demod) | fft256.wgsl (batched) |
scfdma-plugin |
Same batch dispatch; CPU DFT-CE + MMSE + LLR; all SC-FDMA constellations |
| 64QAM soft demodulation | rrc_fir.wgsl + soft_demod.wgsl |
64qam-plugin |
GPU RRC matched filter + batched max-log-MAP LLR (gpu_soft_demod) |
| 8PSK soft demodulation | rrc_fir.wgsl + soft_demod.wgsl |
psk8-plugin |
GPU RRC matched filter + batched Gray-coded LLR (gpu_soft_demod) |
Twelve SessionProfile mappings from speed levels to modes, driven by ACK/NACK feedback
and per-level SNR floor/ceiling gates:
| Profile | SL range | Initial | Top mode | Target link |
|---|---|---|---|---|
hpx500 |
SL2–SL6 | SL2 | QPSK500 | Robust narrowband (≤600 Hz) |
hpx_modcod |
SL2–SL7 | SL2 | QPSK500 | MODCOD demo: modulation × FEC (BPSK250/QPSK250/QPSK500 × LDPC/RS/none) |
hpx_hf |
SL2–SL17 | SL2 | OFDM52-64QAM | Primary HF (full ≤2700 Hz span) |
hpx_ofdm_hf |
SL5–SL10 | SL5 | OFDM52-64QAM | HF OFDM higher-order ladder |
hpx_pilot |
SL2–SL5 | SL2 | PILOT-32APSK500 | HF pilot-aided (cycle-slip-immune, SRO-robust) |
hpx_pilot_rrc |
SL2–SL5 | SL2 | PILOT-32APSK500-RRC | Pilot, narrowband (RRC, ~half band) |
hpx_pilot_fast |
SL2–SL5 | SL2 | PILOT-32APSK1000 | Pilot, high-throughput (1000 baud) |
hpx_pilot_fast_rrc |
SL2–SL5 | SL2 | PILOT-32APSK1000-RRC | Pilot, fast + narrowband |
hpx_wideband |
SL8–SL11 | SL8 | 8PSK1000 | Wideband HF |
hpx_narrowband |
SL8–SL11 | SL8 | 8PSK2000-RRC | Narrowband HF / VHF |
hpx_narrowband_hd |
SL8–SL9 | SL8 | 8PSK9600-RRC | VHF/UHF narrowband |
hpx_wideband_hd |
SL9–SL15 | SL12 | 64QAM2000-RRC | VHF/UHF FM / satellite |
hpx_wideband_hd requires SNR ≥ 16 dB and is not suitable for HF ionospheric paths. The
four hpx_pilot* profiles share one carrier architecture and per-symbol SNR floors,
trading bandwidth (rect vs -RRC) against throughput (500 vs 1000 baud).
- ARDOP-compatible TCP TNC (
openpulse-tnc) — Pat-compatible command set; GRIDSQUARE, ARQBW, ARQTIMEOUT, CWID, SENDID, PING - KISS/AX.25 TNC (
openpulse-kisstnc) — full byte stuffing, AX.25 UI frames - B2F/Winlink — ISS and IRS roles, FC/FS/Ff/Fq frames, Gzip and LZHUF compression
- Direct TCP Winlink CMS gateway (
openpulse-gateway) — no TNC bridge needed - QSY frequency agility — Ed25519-signed wire codec; initiator and responder session state machines; SNR-ranked channel selection; rig CAT via rigctld
- Mesh broadcast daemon — TTL-limited re-broadcast with duplicate suppression
- Cross-band repeater — configurable digipeater with trust-policy filtering
- Multi-hop relay — trust-weighted path scoring; hop-limit enforcement; duplicate
suppression;
RelayForwarderandQueryForwarderfor query propagation
- Ed25519 handshake signing + transfer manifest signing/verification
- ML-DSA-44 + ML-KEM-768 post-quantum handshake — Hybrid (Ed25519 + ML-DSA-44) and PQ-only modes
- Three trust profiles: Permissive, Balanced, Strict — configurable per deployment
- PKI service — Ed25519 trust-bundle signing with PostgreSQL persistence
- Signed peer descriptors — self-authenticating identity; peer ID is the verifying key bytes
- FreeDV frame signing (
crates/openpulse-freedv-auth) — Ed25519 signatures over the codec2 embedded data channel; authenticates voice transmissions without modifying FreeDV itself
- Watterson HF fading — Good F1/F2, Moderate, Poor profiles; Doppler-shaping filter
- Gilbert-Elliott burst error — configurable state machine; AWGN and burst modes
- QRN/QRM/QSB/Chirp — broadband noise, interference, slow fading, frequency drift
- DCD energy threshold and 0.3-persistence CSMA channel access
Options that reduce out-of-band emissions, suppress spectral sidelobes, improve receiver sensitivity, or raise transmit power efficiency. Each can be selected independently per mode.
| Technique | Where | Sidelobe / benefit | Notes |
|---|---|---|---|
CE-SSB envelope conditioning (openpulse_dsp::cessb) |
TX; high-PAPR multicarrier (OFDM/SC-FDMA), default-on | +1.6/+2.7/+3.8 dB avg power at fixed PEP (2.5/2.0/1.5×rms); negligible OOB regrowth | Look-ahead peak-stretcher; gated by cessb_benefits (no-op on single-carrier/BPSK); +1.18 dB confirmed on-air; panel "CE-SSB" toggle + SetCessb control |
Half-Hann overlapping crossfade (PulseShape::Hann) |
All single-carrier modes (default) | ~−32 dB first sidelobe | 50 % symbol overlap; no ISI at SNR > 3 dB; CPU path only |
Cosine overlap (PulseShape::CosineOverlap) |
Single-carrier alternative | ~−32 dB; null-to-null BW ≈ 2×Rs | Lower spectral leakage than rectangular; GPU-compatible |
Root Raised Cosine (SRRC) FIR (PulseShape::Rrc) |
-RRC mode suffix (QPSK, 8PSK, 64QAM) |
~−35 dB OOB; excess BW = 35 % | α = 0.35 rolloff; taps configurable; ISI-free by matched-filter design |
| Barker-11 preamble | Preamble / timing | PSL = −13 dB | 11-chip Barker; used for frame timing acquisition |
| Barker-13 preamble | Preamble / timing | PSL = −17 dB | 13-chip Barker; better sidelobe suppression than Barker-11 |
| DFT-CE pilot-aided channel estimation | SC-FDMA (all SCFDMA modes) | Removes multipath phase rotation | DFT-domain CE on pilot subcarriers; combines with MMSE |
| MMSE equalization | SC-FDMA | Suppresses inter-subcarrier interference | Per-subcarrier minimum mean-square error; requires DFT-CE |
| LS channel estimation | OFDM (OFDM16 / OFDM52) | Least-squares pilot tap estimation | Per-symbol LS CE → ZF equalization |
| ZF equalization | OFDM | Removes pilot-estimated channel distortion | Per-subcarrier zero-forcing; follows LS-CE |
| LMS/DFE adaptive equalizer | BPSK-RRC demod path | Residual ISI suppression | Supervised preamble training → decision-directed; crates/openpulse-dsp |
| Gardner timing error detector | All single-carrier modes | Symbol clock recovery | Symbol-rate TED; feeds symbol timing interpolator |
| PLL carrier phase tracking | BPSK / QPSK / 8PSK | Phase noise rejection | Phase-locked loop updated per symbol; crates/openpulse-dsp |
| AFC IQ-squaring estimator | BPSK (all rates) | Frequency offset correction ±baud/4 | Tracking range: ±7.8 Hz (BPSK31) … ±62.5 Hz (BPSK250) |
| Soft-input FEC (LDPC / Turbo) | Any mode with supports_soft_demod() |
Coding gain vs. hard-decision FEC | Requires a plugin that returns genuine LLRs; engine warns if paired with hard-only plugin |
Rectangular pulse spectrum for reference: first sidelobes −13 dB; classical full-Hann windowing
improves this to −32 dB at the cost of ISI. See docs/features.md for the
detailed crossfade and ISI analysis.
| Interface | Description |
|---|---|
Operator panel (openpulse-panel) |
Full iced GUI connecting to the daemon control port (TCP/WS): a controls band (connection, PTT, mode/frequency, RF peer, feature toggles, TX-atten/squelch, OTA, QSY), live spectrum + waterfall + rate ladder, and a tabbed lower panel (Additional info / Daemon config / Messages / Event log). Dark/Light/Contrast/System themes; hover tooltips on every control |
Twin-station view (openpulse-twinview) |
egui both-directions viewer — one window over two daemons; per-station spectrum/waterfall + rate/OTA/HPX readouts, so both link directions show at once |
TUI (openpulse-tui) |
ratatui terminal UI — HPX state (colour-coded), AFC/rate meters, DCD energy bar, scrollable transitions log |
CLI (openpulse-cli) |
Full-featured command-line interface: transmit, receive, benchmark, monitor, config init, calibrate (audio/PTT/AFC) |
Signal testbench (openpulse-testbench) |
egui 4-column live view: TX / channel / mixed / RX; waterfall, spectrum, scatter; 7 channel models; SNR slider |
# Toolchain preflight (required: rustc >= 1.94.0)
./scripts/check-toolchain.sh
# Build (requires libasound2-dev on Linux for the CPAL audio feature)
cargo build --workspace
# Test suite — no audio hardware required
cargo test --workspace --no-default-features
# Lint
cargo clippy --workspace --no-default-features -- -D warnings
cargo fmt --all -- --check
# Benchmark regression gate
cargo run -p openpulse-cli --no-default-features -- --backend loopback --log error benchmark run
# Automated mode × channel test matrix (outputs to docs/test-reports/)
cargo run -p openpulse-testmatrix --no-default-featuresIf you are temporarily pinned to an older Rust toolchain, run the fallback core gates to keep CI-relevant coverage for all non-PKI crates:
cargo clippy --workspace --exclude pki-tooling --no-default-features -- -D warnings
cargo test --workspace --exclude pki-tooling --no-default-featuresThe --no-default-features flag disables the CPAL audio backend. All tests must
pass with this flag. Never add tests that require real audio hardware.
No radio, sound card, or station is required for any of this — it is purely a rig-less demo to get a first impression of the GUIs and the adaptive modem. None of it transmits on the air.
# Two-station ARQ link simulator GUI — fully self-contained: it simulates BOTH
# stations over a virtual HF channel. No radio, daemon, or config needed. Tweak
# the SNR / channel model / mode live and watch the rate ladder adapt.
cargo run -p openpulse-linksim --features gui --bin openpulse-linksim-gui
# Want the operator panel too? The simulator stands in for the daemon — add
# --serve so it speaks the panel control protocol, then connect the panel to it
# (no daemon, radio, or config). Press Run in the simulator so it streams frames.
cargo run -p openpulse-linksim --features "gui,serve" --bin openpulse-linksim-gui -- --serve 127.0.0.1:9000
cargo run -p openpulse-panel # in another terminal → Connect (default 127.0.0.1:9000)For real on-air operation (sound card / radio, CAT/PTT), build the relevant binary
with --features cpal and configure ~/.config/openpulse/config.toml — see the
on-air test plan in docs/on-air_testplan.md.
| Crate | Role |
|---|---|
crates/openpulse-core |
Frame format, CRC-16, FEC (RS+Conv+LDPC+interleaver), HPX session state machine, plugin registry, trust/signing, SAR, ACK, rate adaptation, relay, query propagation, peer cache, LZ4/Zstd compression, PQ handshake |
crates/openpulse-audio |
LoopbackBackend (testing) and CpalBackend (hardware, feature-gated) |
crates/openpulse-modem |
ModemEngine, PipelineScheduler, ArqSession (LLR-accumulating retry), benchmark harness, CSMA/DCD, channel sim harness |
crates/openpulse-channel |
Channel simulation: Watterson, Gilbert-Elliott, QRN/QRM/QSB/Chirp |
crates/openpulse-radio |
PttController trait: NoOp, SerialRtsDtr, Vox, Rigctld; RigctldController for CAT |
crates/openpulse-dsp |
RRC filter, PLL, Gardner timing recovery, LMS/DFE adaptive equalizer, CE-SSB envelope conditioner |
crates/openpulse-config |
Typed TOML configuration with CLI-override precedence |
crates/openpulse-gpu |
wgpu-backed BPSK DSP kernels; CPU fallback; gpu feature flag |
| Crate | Role |
|---|---|
crates/openpulse-ardop |
ARDOP-compatible TCP TNC; openpulse-tnc binary |
crates/openpulse-kiss |
KISS/AX.25 TNC; openpulse-kisstnc binary |
crates/openpulse-b2f |
B2F/Winlink state machine: FC/FS/Ff/Fq frames, Gzip (Type D), LZHUF (Type C) |
crates/openpulse-b2f-driver |
High-level ISS/IRS session driver over ARDOP TCP; e2e loopback tests |
crates/openpulse-gateway |
Direct TCP Winlink CMS gateway; openpulse-gateway binary |
crates/openpulse-qsy |
QSY frequency agility: Ed25519-signed wire codec, QsySession (initiator + responder), QsyScanner |
crates/openpulse-mesh |
Mesh broadcast daemon with TTL-limited re-broadcast |
crates/openpulse-repeater |
Cross-band repeater / digipeater with trust-policy filtering + §97.119 auto-ID |
crates/openpulse-discovery |
JS8-based station discovery + rendezvous (FF-15): @OPULSE hint, Js8Clock, DiscoveryRuntime, rendezvous SM |
crates/openpulse-filexfer |
Direct P2P file-transfer protocol (FF-16): OPFX wire codec + sender/receiver state machines + blocks/resume |
crates/openpulse-keystore |
FileKeystore — named secrets encrypted at rest (Argon2id KDF → ChaCha20-Poly1305) |
crates/openpulse-linksec |
Control-channel link security: PSK-authenticated encrypted daemon↔client link (Noise Noise_NNpsk0) |
crates/openpulse-freedv-auth |
External shim adding Ed25519 frame signing to FreeDV via the codec2 data channel (FF-11) |
crates/openpulse-daemon |
Unified background daemon: modem engine, PTT, QSY, repeater, NDJSON+WebSocket control port |
| Crate | Role |
|---|---|
crates/openpulse-cli |
CLI binary: transmit, receive, benchmark, monitor NDJSON events, config init |
crates/openpulse-tui |
ratatui TUI: HPX state, AFC/rate meters, DCD energy bar, transitions log |
apps/openpulse-panel |
iced operator panel GUI connecting to the daemon control port (Dark/Light/Contrast/System themes) |
apps/openpulse-twinview |
egui both-directions viewer over two daemons (twin-station rig) |
apps/openpulse-testbench |
egui signal-path testbench: 4-column waterfall/spectrum/scatter, 7 channel models |
apps/openpulse-testmatrix |
Automated mode × channel test matrix runner |
apps/openpulse-linksim |
Two-station bidirectional ARQ link simulator (lib + CLI + GUI): effective two-way transfer rate under simulated SNR |
pki-tooling |
Ed25519 trust-bundle signing service with PostgreSQL persistence |
| Crate | Registered modes |
|---|---|
plugins/bpsk |
BPSK31, BPSK63, BPSK100, BPSK250; GPU path; LMS/DFE equalizer on RRC path |
plugins/qpsk |
QPSK125, QPSK250, QPSK500, QPSK1000, QPSK2000-RRC, QPSK9600-RRC |
plugins/psk8 |
8PSK500, 8PSK1000, 8PSK2000-RRC, 8PSK9600-RRC; max-log-MAP soft demodulator |
plugins/64qam |
64QAM500, 64QAM1000, 64QAM2000-RRC; Gray-coded 8×8 PAM-8; soft demodulator |
plugins/scfdma |
SCFDMA52-8PSK, SCFDMA52-16QAM, SCFDMA52-32QAM, SCFDMA52-64QAM; DFT-CE + MMSE |
plugins/ofdm |
OFDM16, OFDM52; LS channel estimation + ZF equalization |
plugins/fsk4 |
FSK4-ACK (100 baud ACK channel; Goertzel demodulator) |
plugins/pilot |
Pilot-framed PILOT-QPSK/8PSK/16QAM/32APSK (up to DVB-S2 32APSK); pilot-aided carrier recovery |
plugins/js8 |
JS8-compatible 8-GFSK weak-signal waveform (FF-15); LDPC(174,87), native TX+RX decoder, message layer |
OpenPulseHF is GPL v3, but several interfaces let non-GPL software interoperate without GPL obligations. All of the following sit behind a process boundary or a network protocol boundary, which the FSF and courts have consistently held does not trigger copyleft:
| Interface | Transport | Description |
|---|---|---|
| ARDOP TNC | TCP 8515 (cmd) / 8516 (data) | ARDOP ASCII command protocol; Pat, Winlink Express, JS8Call–compatible |
| KISS/AX.25 TNC | TCP 8100 | Standard KISS framing; compatible with Xastir, YAAC, APRX, Linux AX.25 tools |
| Daemon control port | NDJSON over TCP 9000 | Full event stream + control commands; operator panel and scripting |
| Daemon WebSocket | JSON over WS 9001 | Same protocol as TCP port; browser and Electron clients |
| PKI REST API | HTTP/JSON 8080 | Trust-bundle and key-management endpoints; read-only routes unauthenticated |
| CLI subprocess | stdin / stdout | NDJSON output; invocation crosses the process boundary |
| Winlink CMS gateway | B2F over TCP 8772 | Outbound-only client to cms.winlink.org; CMS is proprietary |
Note: plugins that statically link against openpulse-core are derivative works
and must be GPL-compatible. The only supported path for proprietary DSP backends is to
run them as a separate process and bridge data through one of the TCP interfaces above.
See docs/non-gpl-interfacing.md for the full interface
specifications including wire formats, authentication, and schema references.
GNU General Public License v3.0 or later — see LICENSE.
