JTDX_CONTEST · CE3TSKFree · GPL v3

v3.0.0-rc05 · derivative work of JTDX by UA3DJY/ES1JA and WSJT-X by K1JT

Benchmark
WAV suites

The air the numbers were measured on.

Two recorded hours — 240 FT8 periods and 240 FT4 periods off a real band — plus the two crowded-band files with their truth sets, and a script that replays them through the decoder on any machine and prints the same columns the documents quote. Check the claims, or measure your own CPU before choosing a preset for it.

Support this work on Ko-fi

Download

FT8 — one hour
240 consecutive periods on 14.074 MHz, 16:57–17:57 UTC 2026‑08‑27, off the air in Santiago, Chile. Nothing selected or cleaned.
FT4 — one hour
240 FT4 periods on 14.080 MHz, 23:38–00:38 UTC 2026‑08‑29/30, during WW Digi weekend.
Crowded band
One deliberately crowded period per mode, 104 and 103 known signals, with the manifests — the only files here with a truth set, so false decodes are countable.
The benchmark script
One Python file, no dependencies, Linux · macOS · Windows. Also inside each suite zip.

Every file 16 bit PCM, mono, 12000 Hz — the format WSJT‑X and JTDX read natively. Each suite carries SHA256SUMS. Full description: README.md.

Run it

You need Python 3.6 or newer and the standalone decoder jtdxjt9, which ships in every JTDX Contest Edition download — C:\Program Files\JTDX_contest\bin\jtdxjt9.exe on Windows, or squashfs-root/usr/bin/jtdxjt9 after running the AppImage once with --appimage-extract. Unpack a suite so its directory sits beside bench/:

Linux, macOS                          Windows
./bench/run_ft8.sh --periods 40       bench\run_ft8.cmd --periods 40     a first look, ~5 min
./bench/run_ft8.sh                    bench\run_ft8.cmd                  the whole hour
./bench/run_ft4.sh --presets all      bench\run_ft4.cmd --presets all    every preset
./bench/run_ft8.sh --crowded          bench\run_ft8.cmd --crowded        scored true / false
./bench/run_ft4.sh --list             bench\run_ft4.cmd --list           what the presets are

It prints a table per run and, if you pass --json, the same as JSON. Nothing is written outside a scratch directory that is removed again; the WAV files are only read.

Close the program and anything else heavy first. The decode counts are reproducible on any machine; the seconds are not, and a busy machine reports the wrong ones.

What comes out

preset          decodes RX s mean RX s max bg s mean total s/period  wall s  in 2.7s
------------------------------------------------------------------------------------
maxeff             5417      0.40     0.73         -           0.40      97  240/240
light              6091      1.13     1.74      4.20           5.33    1275  240/240

decodes are distinct messages summed over the periods. RX s is the phase that has to decide your reply; bg s is the background phase that runs on while you transmit. The last column counts the periods whose RX phase finished inside the mode's reply deadline — 2.7 s for FT8, 1.36 s for FT4 — and it is the column that decides which preset a given machine can actually run. A second table puts the same run beside the reference machine and ends with a speed index.

The reference numbers

AMD Ryzen 7 5800H, 8 cores / 16 threads, powersave governor · Linux Mint 22.3, gfortran 13.3 ‑O3 · 12 decoder threads · engine jtdxjt9 8a3e17ee (the 3.0.0‑rc02 decoder), 2026‑09‑05 · mycall CE3TSK FF46, 100–3100 Hz, all 240 periods. total s is RX + background per period.

FT8, the one-hour suite

presetdecodesvs stockRX s mean (max)bg stotal s
stock JTDX v2.2.159, 12 threads4869baseline1.401.40
WSJT-X 3.0.2 MT, 12 threads5171+6.2 %1.191.19
classical5249+7.8 %0.76 (1.32)0.76
maxeff5417+11.3 %0.40 (0.73)0.40
maxdec5678+16.6 %1.14 (2.03)1.14
light6091+25.1 %1.13 (1.74)4.25.33
ensemble5992+23.1 %4.52 (8.01)4.52
pipeens6194+27.2 %1.29 (2.04)11.412.71
pipefull6208+27.5 %1.29 (2.07)12.714.03
piperun6234+28.0 %0.40 (0.71)12.913.29

The three pipeline presets need the two-period background window — the 15/15 TX cycle with the background enabled — which is why their total is past 15 s while their RX phase is not.

FT4, the one-hour suite

presetdecodesvs stockRX s mean (max)bg stotal s
stock JTDX v2.2.1591525baseline1.401.40
WSJT-X 3.0.21561+2.4 %0.140.14
WSJT-X improved 3.2.01688+10.7 %0.220.22
nohint1552+1.8 %0.09 (0.18)0.09
default1737+13.9 %0.10 (0.67)0.10
bestpower1822+19.5 %0.10 (0.19)0.30.38
recommended1881+23.3 %0.10 (0.19)2.12.22
mostreply1878+23.1 %0.48 (1.08)0.50.98
maxeffort1888+23.8 %0.59 (1.24)0.61.21

The stock and WSJT-X rows are there for scale. The script does not run them — they need those programs' own decoders — only the JTDX Contest Edition presets.

Reproducing them

The decode counts depend on more than the audio. To land on the numbers above, keep --mycall CE3TSK --mygrid FF46 (a different call changes what the AP search and the hint memory look for), --low 100 --high 3100, --threads 12, and the whole 240 periods in one run — the hint memory carries from period to period inside one process, as it does on air. A different build will not reproduce them to the message: expect a drift of a few messages in a few thousand between releases.

What the recordings are

Raw off-air audio of a real band: real callsigns, real QSOs, the noise, the splatter and the stations that never quite decode. Nothing was selected, trimmed or cleaned, and the periods are consecutive — a benchmark on clean synthetic signals answers a different question. There is no ground truth for them, so what is measured is how many distinct messages a preset recovers and how long each period took; the honest comparison is between presets and between machines on the same files.

The crowded band, scored

--crowded scores over the band the manifest's own signals occupy, not the on-air 100–3100 Hz: the FT4 twin reaches 4922 Hz, and 40 of its 103 signals sit above 3100 Hz, so the on-air range would mark a preset down for signals it was never allowed to look at. --low and --high override it. On the reference machine, with this release's engine:

presetFT8: found of 104best of 5not in setpresetFT4: found of 103best of 5false
classical77840nohint76760
maxeff77830default76760
maxdec88920bestpower76760
light95980recommended77790
ensemble981000mostreply77770
pipeens1011020maxeffort77770
pipefull1011020
piperun981010

found is the first, cold period. best of 5 is the best single period when the same file is handed to the decoder five times running — --crowded --repeat 5, which is what re-opening it in the program does. The audio is identical in all five, so the difference is purely what the hint memory carried forward: +1 to +7 on FT8, 0 to +2 on FT4. Note that it is the shallow presets that gain most — the deep ones had already found nearly everything on the first pass — and that nohint, which switches the memory off, gains exactly nothing, which is the control behaving.

Not one message outside the manifest, at any depth, in either mode, cold or repeated — which is the point of having a truth set at all.

Side note — you do not need the script for this. Every file here is an ordinary WAV, so any of these programs opens it with File → Open. The crowded-band results above can be checked by hand in a minute: put the program in FT8 mode and open ft8_full_band_16.wav, or in FT4 mode and open ft4_full_band_16.wav, from ce3tsk.com/download/wav/crowded_band/. Every JTDX and WSJT‑X version reports how many decodes it got, so two programs can be compared on the same period with nothing but the file.
Side note — opening the same file again is the next period. The decoder treats each open as a new period, so re-opening one file two or three times shows the hint memory working: what the first pass learned is carried into the next, and the count rises. On ft8_full_band_16.wav the third open decodes between two and six more than the first, depending on the preset — 95 then 95 then 98 on light, 71 / 71 / 73 at the plain default. Nothing about the audio changed; only what the decoder already knew.
Side note — widening the decode range finds more, on these files only. The FT4 twin's signals were stretched as high as 4922 Hz, so what you find in it depends on the range you hand the decoder: over the on-air 100–3100 Hz the default preset reaches 36 of the 103 signals, over the manifest's 100–4951 Hz it reaches 76 — for roughly a tenth more decode time. That is a property of the test file, not advice for the air. On a real band a wider range buys nothing: the radio's own passband already bounds where a signal can be, and every extra kilohertz is spectrum the decoder searches for the whole period. Which is why --crowded takes the range from the manifest, while the two hour suites stay at 100–3100 Hz.

The crowded-band files are the exception. ft8_full_band_16.wav carries 104 FT8 signals across the band; it is the recording 210615_071015.wav — 2021‑06‑15 at 07:10:15 UTC — a band packed with Japanese stations: 101 of its 104 senders are JA, answering Cyprus, Israel, European Russia, Crete, Ukraine and Mongolia. Every message in it is corroborated across several decoder families.

ft4_full_band_16.wav is a synthetic FT4 version of that same period: the same message set at the same SNRs and DTs, with the frequencies stretched by the FT4/FT8 signal-width ratio so every overlap is the same overlap in signal widths. One message would have landed above the band and was dropped, which is why it holds 103 and not 104. Because that file was built from its manifest, anything else printed from it is demonstrably a false decode — which is what --crowded counts.

Expect FT4 to find fewer of them, and not because of the decoder: FT4 is intrinsically less sensitive than FT8 — its transmission is about a third as long, so its decoding threshold sits several dB higher — and the weakest signals of the set fall below it. The deepest FT8 presets reach 101 of 104 here, the deepest FT4 presets 77 of 103. That gap is the difference between the two modes on identical content, not between two decoders.

Support this work on Ko-fi