Linux only: the runtime ALSA library (not the -dev headers the
September 1, 2026 · View on GitHub
Pancetta
A full FT8 station in one Rust binary — decode, score, work, log.
Built and operated by Tony Hagale, W5AU.
Pancetta is an FT8 station that lives in your terminal.

pancetta --replay feeding archived WAV captures through the real pipeline:
startup, band monitoring, the TX-placement scorer ranking candidate offsets,
a burst of real FT8 decodes filling Band Activity when the capture reaches
its decodable slot, and a clean self-shutdown when the recording ends. One
recording of one small corpus proves nothing about recall — for measured
decode performance see
docs/decoder-comparison.md.
The pitch
Operating FT8 seriously today means WSJT-X for the modem, a logger for the log, GridTracker for what's needed, and a cluster client for spots — four alt-tabbed windows, each with its own idea of what your station is doing. Pancetta is one binary and one terminal: decode, priority scoring, QSO state machine, DX cluster, PSKReporter, and logbook upload in a single process that runs as happily over SSH on a headless box behind the radio as on a desktop.
Why Pancetta
- A decoder measured against the real reference. Against WSJT-X's
jt9— the decoder hams actually run — Pancetta needs ~2.1 dB more SNR to reach 50% recall on FT8, and ~3.95 dB more on FT4, where it caps at 78% recall, at a false-positive cost of 1 decode per 1,000 pure-noise recordings (0.1%). In short: competitive, not yet class-leading. (An older 1,201-file comparison againstft8_libshowed +11.6% more decodes, butft8_libis not the bar operators care about.) Full numbers and caveats:docs/decoder-comparison.md. - A priority engine, not a list. Every decoded CQ is scored against needed DXCC, needed grid, POTA/SOTA, rarity, signal, and recent activity — so the station you should work is the one at the top.
- Multi-stream TX. A smart frequency allocator places N simultaneous FT8 signals inside one 15-second slot, each on its own audio offset, all on the same parity.
- Headless by design.
--headlesslogs instead of drawing; the decoder is an anytime algorithm with a per-window budget that self-tunes to the host, so slow hardware degrades recall instead of falling over. pancetta doctor. One command checks config, clock vs. NTP, audio device and level, decoder, andrigctld— and prints the fix for each failure.
On-air TX has been validated end-to-end against a Yaesu FTdx10 (clean ALC, PSKReporter spots across North America and Europe). ~295 FT8 tests cover encode, decode, LDPC, CRC, and OSD.
What it looks like
Every capture below — and the GIF further down, and the hero GIF above — was
recorded on a development host with pancetta --replay, feeding archived
off-air WAV captures through the real pipeline (see .tapes/).
Real audio, real decodes, real panels — no mockups. The corpus is one short
recording, so treat these as a tour of the interface, not a performance
claim: the QSO panel in particular is shown idle (STANDBY, "No QSOs yet"),
because a completed QSO needs a multi-slot two-way handshake that a receive-
only replay of someone else's traffic can't produce.
![]() | ![]() |
| Operate overview: TX placement, band activity, QSO status, callers. | DX Hunter: the priority-scoring table (entity, grid, SNR, rarity, score). |
![]() | ![]() |
| QSO status (idle in this replay): state machine, exchanges, TX/RX reports. | Monitor: the waterfall and the TX-frequency marker. |
Decode effort is a live control — press e to cycle presets and the status
chip follows:

| Preset | Behavior |
|---|---|
Auto (default) | Budget derived from the auto-probed hardware tier. |
Eco | Floor pass only — fastest, lowest recall. |
Standard | A moderate per-window budget. |
Deep | A generous budget — more passes/candidates, better recall. |
Max | Runs every decode stage, still bounded by the coordinator's per-slot ceiling (2 s on FT8). |
Pin it at startup via [decoder] in pancetta.toml
(docs/CONFIG.md); e is the only live control. Full
keybinding reference: docs/KEYBINDINGS.md, or press
? in the TUI.
Autonomy with a control operator present
Hands-off operation is opt-in ([autonomous] enabled = true) and assumes a
licensed control operator is at the keyboard and can stop it instantly:
Shift+Qis an emergency stop — halts TX and switches autonomous off. It drops the runtime autonomy gate, and every TX item the engine produced for that cycle is discarded before it can be keyed.- Autonomous initiation requires a present operator. Calling CQ or
pouncing needs a console keypress within the last two minutes (FCC §97.221:
a station with nobody at the control point may respond, not originate).
Headless or idle, Pancetta drops to respond-only; QSOs already in progress
still finish. Every autonomous TX item additionally has to clear the active
TxPolicy(e.g. dry-run/listen-only modes). - The remote-TX arm gate fails closed. Separate from the above, and
specific to transmissions originated over the remote-operation protocol:
a poisoned lock means no transmit, never a permissive fallback, and it ANDs
under the active TX policy. It governs
TxOrigin::Remoteonly — local and autonomous TX are gated by the mechanisms above. - Drop-stale-TX. The TX worker re-checks QSO liveness at the last instant before PTT, so a frame for a QSO that just ended never goes out.
- One parity, always. Concurrent QSOs share a slot parity; Pancetta never transmits in sequential windows.
Regulatory notes, including FCC §97.221 automatic-control considerations:
docs/fcc-part97-compliance.md.
cqdx.io integration
Pancetta pairs with cqdx.io, the DX/logbook layer around the station:
- Hunting. The needed-DXCC feed from cqdx.io flows straight into the
priority scorer, so DX Hunter weighs decoded CQs by what you actually
still need — not just what's loudest. Matching is prefix-based, so a
needed entity represented by a short/overlapping prefix can occasionally
mismatch (e.g. a needed
Kentry can false-matchKH6/KL7/KP4); treat rankings as a strong hint, not a guarantee. Needed-grid is wired on the Pancetta side but has no live cqdx.io endpoint yet, so it doesn't affect rankings today. - Scoring. Rarity and live-spot data from cqdx.io feed the same scorer alongside signal and recent activity, so the top of the list is the contact worth working right now.
- Logbook. Opt in via
[network.cqdx](with a token) and every completed QSO and spot report uploads to cqdx.io directly — no ADIF export/import round-trip. (This tracks completed QSOs, not LoTW/ClubLog-confirmed ones — confirmation status arrives separately.) - Remote rig access. cqdx.io also brokers authorization for remote control: an operator can grant a delegate device — panino, the companion remote-control client, or a browser — scoped, revocable access to the pancetta-controlled rig. Delegated guests get status/QSY visibility and can request a QSO/TX action, but never authorization to actually key the radio — only the station's own (non-delegated) client can arm TX, subject to the remote-TX arm described above.
Not every path is equally mature yet — see Why not (yet) for current status.
Quick start
Prebuilt binaries. No Rust toolchain needed. Starting with the first
tagged release after this note was added, every
release
ships CI-verified binaries for macOS (Apple Silicon), Linux x86_64, Linux
aarch64 (Raspberry Pi 4/5 and other 64-bit ARM boards — needs glibc >= 2.35,
i.e. Raspberry Pi OS Bookworm (2023-10) or newer; Bullseye-era images
can't load it), and Windows x86_64 — if /releases/latest doesn't list an
archive for your platform yet, either build from source below or wait for
the next tag.
# Linux/macOS: the archive extracts into a version-named directory — the
# binary isn't at the archive root.
tar xzf pancetta-*.tar.gz && cd pancetta-*/
# Linux only: the runtime ALSA library (not the `-dev` headers the
# from-source table below lists) — install if `./pancetta` fails to start
# with a missing libasound.so.2 error.
sudo apt install -y libasound2 # libasound2t64 on Debian 13/Ubuntu 24.04+
# macOS only: this binary isn't notarized yet, so a browser download is
# quarantined and Gatekeeper blocks it until you clear that bit once.
xattr -d com.apple.quarantine ./pancetta 2>/dev/null || true
./pancetta # first-run wizard — same as step 3 below
(Windows: extract the .zip, open the extracted folder, and run
pancetta.exe.)
Run it again the same way to start the station — step 4's cargo run --release -p pancetta is the source-build equivalent of that second run; a
prebuilt binary doesn't need it or a source checkout. Hamlib is still needed
at runtime if you want to key a radio (see the table below).
To build from source instead:
1. Dependencies.
# Linux (Debian/Ubuntu):
sudo apt update
sudo apt install -y libasound2-dev libudev-dev libssl-dev pkg-config libhamlib-utils
# macOS:
brew install hamlib
curl https://sh.rustup.rs -sSf | sh
| Requirement | Linux | macOS | Windows |
|---|---|---|---|
| Rust toolchain | rustup → stable | rustup → stable | rustup → stable |
| Audio dev headers | libasound2-dev, libudev-dev | (built in) | (built in) |
| TLS | libssl-dev, pkg-config | (built in) | (built in) |
| Hamlib (CAT — optional, runtime-only) | apt install libhamlib-utils | brew install hamlib | hamlib Windows build |
Hamlib is only needed to key a radio, not to build or to decode.
2. Build. --recursive matters: without the submodule the build warns and
falls back to the Rust-only decode path. First build is 5–10 minutes.
git clone --recursive https://github.com/HagaleTechnologies/pancetta.git
cd pancetta
cargo build --release
3. Configure. The first run walks you through writing
~/.pancetta/pancetta.toml — callsign, grid, audio devices, rig. Every key
is documented in docs/CONFIG.md.
./target/release/pancetta # first-run wizard
cargo install --path pancetta # optional: put `pancetta` on PATH
4. Run. With [rig.interface] disabled (the default) this is decode-only
and cannot key the radio. Enable the rig interface for TX; see
docs/RUNBOOK.md for the autonomous-QSO procedure.
cargo run --release -p pancetta
Put your real callsign in
station.callsignbefore transmitting. Pancetta refuses to call CQ asNOCALL/N0CALL, but it will transmit whatever else you give it — Part 97 is your responsibility, not the software's.
Command-line tools
One binary, several subcommands (pancetta <cmd> --help for details):
| Command | What it does |
|---|---|
pancetta | Run the station (TUI). First run launches the setup wizard. |
pancetta doctor | Station health check with a printed fix per failure. |
pancetta setup | Interactive station/audio/rig/PTT wizard. Safe to re-run. |
pancetta test-audio --list | List audio devices exactly as pancetta sees them. |
pancetta test-rig | Test the rig link; --ptt keys TX for 1 s (careful!). |
pancetta config --validate | Validate config, non-zero on error (--show, --generate). |
pancetta export --output log.adi | Export logged QSOs to ADIF. |
pancetta benchmark-decode <wav-or-dir> | Native decoder vs. ft8_lib on WAV captures. |
pancetta --wav <file> | Decode one 15-s WAV and exit (no audio hardware). |
pancetta --replay <wav-dir> | Feed a directory of WAVs through the full TUI pipeline. |
pancetta --headless | No TUI; logs to ~/.pancetta/logs/. |
Why not (yet)
Honest gaps, so you can judge whether it fits your station:
- Pre-1.0. Config keys, CLI surface, and on-disk formats can still change between releases.
- Some integrations are scaffolded, not live.
pancetta-hamlib's FFI bindings are done, but the project's own status table still calls the crate an integration stub;pancetta-cqdxawaits live API validation; LoTW/eQSL upload is scaffolded where ClubLog/QRZ/cqdx paths are live. - Platform coverage is narrow. Developed on macOS (Apple Silicon), CI on Linux, deployed on a Windows 11 MiniPC. Prebuilt Linux aarch64 binaries are published for Pi-class ARM, but real-hardware validation (the effort-budget work's actual target) hasn't happened yet — CI only proves the binary builds and decodes correctly on an ARM64 cloud runner, not on a Raspberry Pi.
- One radio, well tested. CAT/PTT is exercised against a Yaesu FTdx10. Other hamlib-supported rigs should work; nobody has proven it.
pancetta-researchis a local-only decoder harness, excluded from CI and unsupported.
Building, testing, lint
cargo build --workspace
cargo test --workspace --features transmit
cargo test -p pancetta-hamlib --lib -- --test-threads=1 # deterministic mock rig
cargo test -p pancetta --test loopback_qso # end-to-end encode→decode
cargo clippy --workspace --features transmit
cargo fmt --all -- --check
CI runs all of this per PR plus a macOS cargo check lane; cargo deny check
guards advisories and license drift. The 14-crate strictly layered workspace
is mapped in docs/ARCHITECTURE.md.
Documentation
docs/GUIDE.md— start here: your first 5 minutes, your first QSO, how-do-I recipes.docs/CONFIG.md— every config key, with examples and defaults.docs/TROUBLESHOOTING.md— audio init, missing decodes, duplicate QSOs,rigctld.docs/ARCHITECTURE.md— crate graph, data flow, key abstractions.docs/KEYBINDINGS.md— every TUI binding.docs/RUNBOOK.md— operating procedures, including the autonomous QSO loop.docs/decoder-comparison.md— native decoder vs. ft8_lib, measured.docs/PROVENANCE.md— licensing provenance and the clean-room firewall.FEATURES.md— capabilities and feature status.SECURITY.md— vulnerability reporting and known trade-offs.CONTRIBUTING.md— coding standards and contribution flow.CHANGELOG.md— release notes.
API docs: cargo doc --workspace --no-deps --open.
Acknowledgments
- Joe Taylor (K1JT) and Steve Franke (K9AN) designed FT8 — the LDPC code, Costas sync arrays, modulation, and message schema this project implements, documented in The FT4 and FT8 Communication Protocols.
- Kārlis Goba (YL3JG) authored
ft8_lib, the MIT-licensed C implementation Pancetta vendors atpancetta-ft8/vendor/ft8_lib/and calls via FFI. Several algorithms in the native Rust decoder are ported from it (CRC-14, LDPC tables, Gray code mapping, sliding spectrogram, LLR normalization) and attributed in the source where they appear. - The WSJT-X project (GPL) is the de-facto reference FT8 application. Pancetta does not link or vendor any WSJT-X source; it interoperates through the published protocol only.
Full third-party license text: THIRD-PARTY-NOTICES.md.
Licensing provenance and the clean-room process: docs/PROVENANCE.md.
License
Dual-licensed under your choice of:
- MIT — see
LICENSE-MIT - Apache 2.0 — see
LICENSE-APACHE
Contributions are accepted under the same dual-license terms unless explicitly stated otherwise in the PR.



