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.
| Parameter | Value |
|---|---|
| Sample rate (reference) | 12 000 Hz |
| Symbol rate | 31.25 baud — 384 samples per symbol, 32 ms each |
| Modulation | 4-GFSK, continuous phase |
| Tone spacing | 31.25 Hz (h = 1) |
| Gaussian pulse | BT = 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 ramp | Raised-cosine over 48 samples at the very start and end of a transmission, not of each frame |
| Timing | Asynchronous — 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.
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:
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
| Parameter | Value |
|---|---|
| Payload | 34 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-12 | Polynomial 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 code | Rate ½, 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 |
| Generators | 1167 and 1545 octal, delay-indexed (LSB taps the newest bit); 1167's output is the first bit of each pair |
| Symbol mapping | Each information bit yields one coded pair; each pair is one 4-ary symbol, so data symbol i carries information bit i |
| Gray map | 00 → 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:
| Type | What it carries | Examples |
|---|---|---|
i2 = 0, 1 · Callsign actions | A 28-bit standard callsign plus a two-bit action | CQ K1ABC CQ · K1ABC · TU K1ABC CQ · K1ABC TU · K1ABC AGN? · TU NOW K1ABC |
i2 = 2 · STRUCT30 | Structured contest exchanges, 27-bit body + 3-bit family | 599 001 · 599 CA · 05 NWT · 1D EMA · 156 1749 · AGN? · FN42 |
i2 = 3 · TEXT5 | Five 6-bit characters from a 64-symbol alphabet | HELLO · 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:
| Family | Content |
|---|---|
| EXCH_NUM | A 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_LOC | A two- or three-letter token with a kind: state/province, ARRL/RAC section, country prefix, generic QTH, local administrative code |
| EXCH_PAIR | Two 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_TIME | Serial plus UTC minute, rendered <serial> <HHMM> (156 1749) |
| MISC | 18 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.
| Text | Frames | Why |
|---|---|---|
CQ K1ABC CQ | 1 frame | callsign action |
599 001 | 1 frame under RTTY Roundup, 2 under General | EXCH_NUM serial vs. two TEXT5 atoms |
K1ABC 599 001 | 2 frames (RTTY Roundup) | callsign + serial |
1D EMA | 1 frame under Field Day | EXCH_PAIR class + section |
TNX FOR THE NEW ONE 73 | 5 frames | 22 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'sALL.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:
| Parameter | Value |
|---|---|
| Analysis window | 1.25 frames (2.36 s), stepped by a quarter frame (0.472 s) |
| Sync search | Correlate the 13-symbol sync waveform at 2 ms steps across candidate frequencies; smooth over frequency with a 1-2-3-2-1 kernel |
| Candidate channels | Your 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-up | Refine ±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 |
| Gates | nsync > 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) |
| Demodulation | Coherent 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 |
| Decoder | 512-state list Viterbi (list width 4) with two wrap-around passes; top hypotheses checked by CRC-12, then grammar |
| Subtraction | An 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:
| Band | Dial |
|---|---|
| 160 m | 1.838 MHz |
| 80 m | 3.575 MHz |
| 40 m | 7.090 MHz |
| 20 m | 14.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:
| Key | Macro | Use |
|---|---|---|
| F1 | CQ %M CQ | run: call CQ |
| F2 | %H %E | send the exchange |
| F3 | TU %M CQ | run: thank and call the next |
| F4 | %M | S&P: answer a CQ with your call |
| F5 | %H TU | confirm the other station |
| F6 | %H AGN? | ask for a repeat |
| F7 | NR? | ask for the number |
| F8 | TU NOW %M | moving 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.