Wiki / The JTTY mode

JTTY, from the
tones up.

Everything JTTYAF's codec implements, in one page: the waveform, the frame, the coding, what a frame can say, how messages chain, how the receiver finds them, and where to operate. Written from the WSJT-X 3.2 design notes; kept in sync with the protocol reference in the app repo.

1What JTTY is

JTTY is the keyboard-to-keyboard digital mode introduced in WSJT-X 3.2 (3.2.0-rc1, announced September 23, 2026) as a modern replacement for RTTY. It keeps what people like about RTTY — you type, the other station reads, nobody waits for a 15-second slot — and fixes what they don't: every frame is error-protected by a CRC and a convolutional code, the signal is a tenth as wide, and it copies far deeper into the noise.

  • Asynchronous. No clock synchronisation and no T/R cycle. A transmission starts the moment you press Send and lasts as long as the message.
  • Framed. Text is cut into frames of 1.888 s. Each frame carries one structured contest exchange, one callsign action, or five characters of free text.
  • Coded. A 12-bit CRC and a rate-½ tail-biting convolutional code protect every frame. A frame either decodes exactly or not at all — no garbled characters.
  • Fast where it matters. Structured exchanges run at roughly 60 WPM; free text at roughly 30 WPM.

2The waveform

JTTY is a four-tone, continuous-phase frequency-shift signal at 31.25 baud. The tones are spaced by the baud rate (modulation index 1), so the signal is about 127 Hz wide — narrow enough that a 2.4 kHz passband holds a dozen QSOs side by side.

ParameterValue
Sample rate (reference)12 000 Hz
Symbol rate31.25 baud — 384 samples per symbol, 32 ms each
Modulation4-GFSK, continuous phase
Tone spacing31.25 Hz (h = 1)
Gaussian pulseBT = 2.0, frequency pulse spans 3 symbols — the same smoothing pulse FT8 uses
Occupied bandwidth≈ 127 Hz (99 % power), 125 Hz at half power
Amplitude rampRaised-cosine over 48 samples at the very start and end of a transmission, not of each frame
TimingAsynchronous — no UTC slots

Tone k (0–3) sits at f0 + k × 31.25 Hz, where f0 is the lowest tone. The frequency WSJT-X and JTTYAF display for a decode — and the RX/TX offset marker on the waterfall — is that lowest tone.

f0tone 0
f0 + 31.25tone 1
f0 + 62.5tone 2
f0 + 93.75tone 3

Multi-frame messages are sent back to back with continuous phase across frame boundaries; the first and last symbols of the transmission extend into the Gaussian guard region exactly as the FT8 synthesiser does. JTTYAF plays the entire buffer, head first — the sync at the start of a frame is never clipped.

3The frame

A frame is 59 symbols: 13 sync symbols followed by 46 data symbols, 1.888 s (22 656 samples) in all. The sync sequence is transmitted first in every frame and is what a receiver correlates against to find a signal in time and frequency:

0 2 2 3 0 0 3 2 1 3 1 2 0

Its peak autocorrelation sidelobe is 2/13, so a receiver can lock onto it even when most of the frame is buried. The 46 data symbols carry the coded payload described next.

4Channel coding

34-bit payload32 source + reserved + EOM→ CRC-12poly 0x80F→ 46 bitsinformation word→ TBCC r=½ K=10gens 1167 / 1545 (octal)→ 92 coded bitsone pair per info bit→ Gray map46 four-ary symbols→ 13 sync + 46 data59 symbols
ParameterValue
Payload34 bits, MSB first: bits 1–32 the source word (§5), bit 33 reserved (must be 0), bit 34 EOM — set only on the last frame of a message
CRC-12Polynomial 0x80F (x¹² + x¹¹ + x³ + x² + x + 1), initial register 0, no reflection, no final XOR, computed over exactly the 34 payload bits with no padding; 12 CRC bits appended → 46 bits
Convolutional codeRate ½, constraint length K = 10 (512 states), tail-biting: the register is pre-loaded with the last 9 information bits so the trellis starts and ends in the same state, no flush bits
Generators1167 and 1545 octal, delay-indexed (LSB taps the newest bit); 1167's output is the first bit of each pair
Symbol mappingEach information bit yields one coded pair; each pair is one 4-ary symbol, so data symbol i carries information bit i
Gray map00 → 0, 01 → 1, 11 → 2, 10 → 3 (tone = 2·b0 + (b0 xor b1))

Validity. A decoded frame is accepted only if the CRC-12 matches, bit 33 is zero, and the source word passes every grammar rule below — no unassigned type, no out-of-range field, not the all-zero word. Anything else is dropped before it can be displayed, chained, or subtracted. With a 12-bit CRC plus the grammar, a wrong decoding convention produces essentially no valid frames, which is how JTTYAF's conventions were pinned down against the official sample recording.

5What a frame can say

The 32-bit source word has a two-bit type in its lowest bits and a 30-bit form above it. Four types exist:

TypeWhat it carriesExamples
i2 = 0, 1 · Callsign actionsA 28-bit standard callsign plus a two-bit actionCQ K1ABC CQ · K1ABC · TU K1ABC CQ · K1ABC TU · K1ABC AGN? · TU NOW K1ABC
i2 = 2 · STRUCT30Structured contest exchanges, 27-bit body + 3-bit family599 001 · 599 CA · 05 NWT · 1D EMA · 156 1749 · AGN? · FN42
i2 = 3 · TEXT5Five 6-bit characters from a 64-symbol alphabetHELLO · TNX 7 · 3 GL

Callsign actions

Only standard callsigns fit in 28 bits — the same pack28 coding FT8 uses: a one- or two-character prefix with at least one letter, a digit, and up to three letters, no /. Six actions are defined: CQ <call> CQ, bare <call>, TU <call> CQ, <call> TU, <call> AGN? and TU NOW <call>. Each is a single frame, which is why JTTYAF's default F-keys are built from them: a complete run QSO is three frames each way.

Structured exchanges (STRUCT30)

Five families, each with a role bit that optionally prefixes the rendered field with 599:

FamilyContent
EXCH_NUMA number with a kind: serial (rendered with at least three digits), CQ zone, ITU zone, age, power, check, first-licence year, or a generic number
EXCH_LOCA two- or three-letter token with a kind: state/province, ARRL/RAC section, country prefix, generic QTH, local administrative code
EXCH_PAIRTwo fields in one frame: zone + location (05 NWT) or Field Day class + section (1D EMA, section indexed into the 86-entry ARRL/RAC table)
EXCH_NUM_TIMESerial plus UTC minute, rendered <serial> <HHMM> (156 1749)
MISC18 control phrases — AGN? CALL? AGN CALL NR? AGN NR EXCH? STATE? SECTION? ZONE? GRID? RPRT? QSL TU TU QRZ? QSO B4 WAIT NIL? OK? — and a four-character Maidenhead grid

Free text (TEXT5)

Anything else goes out five characters per frame, from this 64-character alphabet (index 0 is 0, 36 is space):

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?!"#$%,&*()_'=[]{}<>|:;

Lower case is upper-cased before packing; unsupported characters become #. The final TEXT5 atom of a message is space-padded.

How text becomes frames

When you press Send, JTTYAF normalises the text (upper-case, trimmed, repeated spaces collapsed, 80-character limit enforced — longer text is rejected, never truncated), generates every candidate atom that starts and ends on a token boundary, and runs a dynamic program over character offsets to pick the minimum-frame sequence. Structured atoms win ties, then longer spans. Every atom gets bit 33 = 0 and only the last gets EOM = 1. Because TEXT5 is always available as a fallback, the result is never more than 16 frames.

TextFramesWhy
CQ K1ABC CQ1 framecallsign action
599 0011 frame under RTTY Roundup, 2 under GeneralEXCH_NUM serial vs. two TEXT5 atoms
K1ABC 599 0012 frames (RTTY Roundup)callsign + serial
1D EMA1 frame under Field DayEXCH_PAIR class + section
TNX FOR THE NEW ONE 735 frames22 characters of free text

That is what the contest profile setting controls: under RTTY Roundup the packer treats 599 5 as a serial atom spelled 599 005 and recognises state and province tokens; under Field Day it recognises class-and-section pairs; under General it packs the same text as plain characters.

6Messages and chaining

A message is 1 to 16 frames, terminated by the EOM bit. On receive, JTTYAF chains frames into messages the way WSJT-X does:

  • A new frame continues an open message when it arrives n frame periods (n = 1–3) after the previous frame, within ±0.1 s, and within 10 + 3·(n − 1) Hz of it. A skipped period inserts ... in the text.
  • Up to 30 messages may be open at once; a message with no continuation for 3 frame periods is flushed and printed as (incomplete).
  • Frames within 12 Hz and 50 ms of an already-reported frame are duplicates and are dropped.
  • Received and transmitted text is appended to jtty_all.txt, in the style of WSJT-X's ALL.TXT.

7How JTTYAF receives

The streaming receiver taps the app's 12 kHz audio fan-out and follows the approach the WSJT-X design notes describe, extended so a phone can watch the whole band:

ParameterValue
Analysis window1.25 frames (2.36 s), stepped by a quarter frame (0.472 s)
Sync searchCorrelate the 13-symbol sync waveform at 2 ms steps across candidate frequencies; smooth over frequency with a 1-2-3-2-1 kernel
Candidate channelsYour RX offset ± tolerance (priority), the two WSJT-X "listening" channels at 1350 and 1650 Hz (±150 Hz), and — JTTYAF's addition — a configurable wide search over 300–2700 Hz so every station prints, budgeted so a phone keeps up
Peak-upRefine ±4 ms and ±2.5 Hz in 0.5 Hz steps; fit a line to the unwrapped phase of the 13 sync phasors and accept if the residual is under 1 rad RMS
Gatesnsync > 6 and S/N ≥ 4.6 dB on the priority channel; nsync > 8 and S/N ≥ 5.0 dB elsewhere (S/N referenced to 2500 Hz, like FT8)
DemodulationCoherent block lengths L = 1, 2, 4 on full-symbol correlations, then L = 1 on half-symbol energies; the first rung that yields an accepted hypothesis wins
Decoder512-state list Viterbi (list width 4) with two wrap-around passes; top hypotheses checked by CRC-12, then grammar
SubtractionAn accepted frame is re-encoded, fitted with a complex gain, subtracted from the buffer, and the search re-runs so overlapping and weaker signals decode

Turn the wide search off in Settings and the receiver decodes only around your RX offset, which saves battery when you're portable.

8Operating conventions

JTTY is new and its band plan is still preliminary. The suggested dial frequencies, with 20 m as the main meeting place:

BandDial
160 m1.838 MHz
80 m3.575 MHz
40 m7.090 MHz
20 m14.090 MHz — main meeting place

JTTYAF's band picker has these built in and lets you add your own. Macros follow the WSJT-X convention: %M is your call, %H the other station's, %E the exchange (599 + serial). The eight default F-keys cover running and search-and-pounce:

KeyMacroUse
F1CQ %M CQrun: call CQ
F2%H %Esend the exchange
F3TU %M CQrun: thank and call the next
F4%MS&P: answer a CQ with your call
F5%H TUconfirm the other station
F6%H AGN?ask for a repeat
F7NR?ask for the number
F8TU NOW %Mmoving on

Long-press any key to edit it. Queued messages need no spacing frame between them. N1MM Logger+ talks to WSJT-X's JTTY through an MMTTY-compatible interface; that integration is on JTTYAF's roadmap but not in the first release.

9Clean room and sources

WSJT-X is GPL-3 and JTTYAF is MIT, so the codec in jtty_lib/ was written for this project from the protocol description, not ported from the Fortran. The sources were the WSJT-X 3.2 design documents (jtty_design, jtty_source_encoding, with their golden vectors), the WSJT-X 3.2 User Guide, independent published write-ups of the waveform parameters, and the official sample recording 260807_134110.wav from the WSJT-X samples archive. No GPL JTTY source — WSJT-X, libjtty, mfsk-core — was consulted or copied.

Where the documents left a convention open (CRC padding, generator bit order, which generator feeds the Gray-map MSB, which information bit enters the encoder first), the implementation was checked against the sample: with a 12-bit CRC and the grammar rules, only the right combination yields a coherent, readable QSO. Pure-C host tests cover the source-coding golden vectors, channel-coding round trips and modem sensitivity, and run in CI on every pull request; the interoperability suite additionally decodes the sample recording.

Credit where it's due.

JTTY was designed and documented by the WSJT-X team — K1JT, K9AN, G4KLA, N9ADG, DL3WDG, W3SZ, KJ5HST and KD0BTO. JTTYAF exists because they published how it works.