Complete specification of the CSVF2 radio modem, written so that anyone can build a client that sends and receives correctly and has every feature. Matches CSVF2 program version 3.6.3 (2026-10-17). ← CSVF2 download the reference implementation (zip)
radio_modem.py, radio_fec.py) is in the
download and is the reference when this text and the code disagree. The tables in this page were generated from that
code, not typed by hand. What goes on the air is plain text, files you chose to send, a station ID, and checksums.
Section 13 has test vectors so an implementation can check itself.
CSVF2 ("client server via frequency") moves text and files over an analog voice radio channel as audio tones. A transmission is: a lead-in of silence, a steady key tone (so a VOX radio is already transmitting when the data starts), optionally the station's callsign, one or more bursts of tones, optionally a spoken website announcement, the callsign again, optionally an end-of-transmission tone for a server.
The tones carry frames. A frame is a run of symbols: a synchronisation pattern, a type, an optional path, a body, a checksum and an end symbol. A symbol is one short burst of one tone (single-tone profiles) or two simultaneous tones (two-tone profiles). Several speed profiles exist; they differ only in symbol length, frequencies and how many bits a body symbol carries. A receiver listens for every profile at once and needs no speed setting.
Layers, bottom to top: (1) audio samples; (2) symbols (section 4); (3) frames (5, 6, 7, 8); (4) transmission timeline with bursts, key tone and IDs (9, 10). Section 11 describes a reference receiver.
SHIFT, 38 is END, 39 is SYNC_A. SYNC_B is the SHIFT symbol.| Symbols (number = character) |
|---|
0=q 1=w 2=e 3=r 4=t 5=y 6=u 7=i 8=o 9=p |
10=a 11=s 12=d 13=f 14=g 15=h 16=j 17=k 18=l 19=z |
20=x 21=c 22=v 23=b 24=n 25=m 26=1 27=2 28=3 29=4 |
30=5 31=6 32=7 33=8 34=9 35=0 36=space 37=SHIFT 38=END 39=SYNC_A |
SHIFT + digit: 1→! 2→@ 3→# 4→$ 5→% 6→^ 7→& 8→* 9→( 0→)
SHIFT SHIFT + digit: 1→, 2→. 3→/ 4→; 5→' 6→[ 7→] 8→\ 9→- 0→=
Text characters. A data symbol is its character. SHIFT followed by a letter gives the
capital letter; SHIFT followed by a digit symbol gives 1 2 3 4 5 6 7 8 9 0 → ! @ # $ % ^ & *
( ); SHIFT SHIFT followed by a digit symbol gives 1 2 3 4 5 6 7 8 9 0 → , . / ; ' [ ] \ -
=. SHIFT affects only the next symbol. A third SHIFT in a row, or SHIFT before a character with no shifted form,
is invalid and is shown as ?. Characters that can be sent: letters (both cases), digits, space and
! @ # $ % ^ & * ( ) , . / ; ' [ ] \ - =. Line breaks and tabs are sent as a space; anything else is
dropped by the sender.
File symbols. In a file body a symbol 0..31 is a 5-bit digit (base 32); see section 6.
Every profile has sym samples per symbol at 24000 Hz, a first tone f0 and a tone spacing
df. Tone k is f0 + df·k Hz. The spacing is chosen so each tone has a whole
number of cycles per symbol (df = 24000 / sym for every profile except the 50 Hz bank profile, which uses
half of that).
| profile | samples / symbol | symbol ms | f0 Hz | df Hz | physical tones | tones / symbol | band | symbols / s | file bits / symbol | file bytes / s | symbols / 120 s burst | SYNC symbols (+ / -) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
fast | 1600 | 66.67 | 555 | 15 | 40 | 1 | 555..1140 Hz | 15.0 | 5 | 9.38 | 1800 | SYNC_A / SHIFT (39 / 37) |
normal | 4800 | 200.00 | 550 | 5 | 40 | 1 | 550..745 Hz | 5.0 | 5 | 3.12 | 600 | SYNC_A / SHIFT (39 / 37) |
xfast | 600 | 25.00 | 560 | 40 | 15 | 2 (pair) | 560..1120 Hz | 40.0 | 5 | 25.00 | 4800 | SYNC_A / SHIFT (39 / 37) |
axfast | 600 | 25.00 | 560 | 40 | 15 | 2 (pair) | 560..1120 Hz | 40.0 | 7 | 35.00 | 4800 | q / y (0 / 5) |
xxfast | 240 | 10.00 | 600 | 100 | 15 | 2 (pair) | 600..2000 Hz | 100.0 | 5 | 62.50 | 12000 | SYNC_A / SHIFT (39 / 37) |
xxxfast | 160 | 6.67 | 600 | 150 | 15 | 2 (pair) | 600..2700 Hz | 150.0 | 5 | 93.75 | 18000 | SYNC_A / SHIFT (39 / 37) |
xxxfastn | 240 | 10.00 | 600 | 50 | 29 | 2 (pair) | 600..2000 Hz | 100.0 | 6 | 75.00 | 12000 | SYNC_A / SHIFT (39 / 37) |
slow | 9600 | 400.00 | 1200 | 2.5 | 40 | 1 | 1200..1298 Hz | 2.5 | 5 | 1.56 | 300 | SYNC_A / SHIFT (39 / 37) |
superslow | 19200 | 800.00 | 1400 | 1.25 | 40 | 1 | 1400..1449 Hz | 1.2 | 5 | 0.78 | 150 | SYNC_A / SHIFT (39 / 37) |
slowest | 38400 | 1600.00 | 1600 | 0.625 | 40 | 1 | 1600..1624 Hz | 0.6 | 5 | 0.39 | 75 | SYNC_A / SHIFT (39 / 37) |
Single-tone profiles (normal, fast, slow, superslow,
slowest): symbol
k is tone k, 40 tones.
Slow modes (v2.9). slow (400 ms symbols) and superslow (800 ms) are
normal with longer symbols, on tones at 1200 and 1400 Hz so no other profile's decoder confuses them. They
are for weak or noisy signals. The only difference in the receiver is the score of a tone (section 11): for these two
profiles it is the tone's share of the energy of the profile's own 40 tones, instead of its share of all the audio in
the window, so noise outside the 100 Hz or 50 Hz band no longer dilutes it. Everything else (frames, bursts, error
correction, IDs, thresholds in symbols) is as for normal; the receiver gives up on a weak stretch after
the same number of symbols (4 text, 25 file), not the same number of seconds.
xfast, axfast, xxfast, xxxfastAll four share one pair map: 15 physical tones (index 0..14, f0 + df·i); symbol k is
the pair (tone a, tone b) listed below, sounded together, each at half amplitude (so the peak equals
a single tone's). The map is: all pairs (i, i+g) in the order g = 2 (i = 0..12), g = 3 (i = 0..11),
g = 4 (i = 0..10), g = 5 (i = 0..9), taking the first 40; symbols 37, 38, 39 (SHIFT, END, SYNC_A) are (1,6), (2,7),
(3,8).
| sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | q | 0 | 2 | 560 | 640 | 14 | g | 1 | 4 | 600 | 720 | 28 | 3 | 3 | 7 | 680 | 840 |
| 1 | w | 1 | 3 | 600 | 680 | 15 | h | 2 | 5 | 640 | 760 | 29 | 4 | 4 | 8 | 720 | 880 |
| 2 | e | 2 | 4 | 640 | 720 | 16 | j | 3 | 6 | 680 | 800 | 30 | 5 | 5 | 9 | 760 | 920 |
| 3 | r | 3 | 5 | 680 | 760 | 17 | k | 4 | 7 | 720 | 840 | 31 | 6 | 6 | 10 | 800 | 960 |
| 4 | t | 4 | 6 | 720 | 800 | 18 | l | 5 | 8 | 760 | 880 | 32 | 7 | 7 | 11 | 840 | 1000 |
| 5 | y | 5 | 7 | 760 | 840 | 19 | z | 6 | 9 | 800 | 920 | 33 | 8 | 8 | 12 | 880 | 1040 |
| 6 | u | 6 | 8 | 800 | 880 | 20 | x | 7 | 10 | 840 | 960 | 34 | 9 | 9 | 13 | 920 | 1080 |
| 7 | i | 7 | 9 | 840 | 920 | 21 | c | 8 | 11 | 880 | 1000 | 35 | 0 | 10 | 14 | 960 | 1120 |
| 8 | o | 8 | 10 | 880 | 960 | 22 | v | 9 | 12 | 920 | 1040 | 36 | space | 0 | 5 | 560 | 760 |
| 9 | p | 9 | 11 | 920 | 1000 | 23 | b | 10 | 13 | 960 | 1080 | 37 | SHIFT | 1 | 6 | 600 | 800 |
| 10 | a | 10 | 12 | 960 | 1040 | 24 | n | 11 | 14 | 1000 | 1120 | 38 | END | 2 | 7 | 640 | 840 |
| 11 | s | 11 | 13 | 1000 | 1080 | 25 | m | 0 | 4 | 560 | 720 | 39 | SYNC_A | 3 | 8 | 680 | 880 |
| 12 | d | 12 | 14 | 1040 | 1120 | 26 | 1 | 1 | 5 | 600 | 760 | ||||||
| 13 | f | 0 | 3 | 560 | 680 | 27 | 2 | 2 | 6 | 640 | 800 |
| sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | q | 0 | 2 | 600 | 800 | 14 | g | 1 | 4 | 700 | 1000 | 28 | 3 | 3 | 7 | 900 | 1300 |
| 1 | w | 1 | 3 | 700 | 900 | 15 | h | 2 | 5 | 800 | 1100 | 29 | 4 | 4 | 8 | 1000 | 1400 |
| 2 | e | 2 | 4 | 800 | 1000 | 16 | j | 3 | 6 | 900 | 1200 | 30 | 5 | 5 | 9 | 1100 | 1500 |
| 3 | r | 3 | 5 | 900 | 1100 | 17 | k | 4 | 7 | 1000 | 1300 | 31 | 6 | 6 | 10 | 1200 | 1600 |
| 4 | t | 4 | 6 | 1000 | 1200 | 18 | l | 5 | 8 | 1100 | 1400 | 32 | 7 | 7 | 11 | 1300 | 1700 |
| 5 | y | 5 | 7 | 1100 | 1300 | 19 | z | 6 | 9 | 1200 | 1500 | 33 | 8 | 8 | 12 | 1400 | 1800 |
| 6 | u | 6 | 8 | 1200 | 1400 | 20 | x | 7 | 10 | 1300 | 1600 | 34 | 9 | 9 | 13 | 1500 | 1900 |
| 7 | i | 7 | 9 | 1300 | 1500 | 21 | c | 8 | 11 | 1400 | 1700 | 35 | 0 | 10 | 14 | 1600 | 2000 |
| 8 | o | 8 | 10 | 1400 | 1600 | 22 | v | 9 | 12 | 1500 | 1800 | 36 | space | 0 | 5 | 600 | 1100 |
| 9 | p | 9 | 11 | 1500 | 1700 | 23 | b | 10 | 13 | 1600 | 1900 | 37 | SHIFT | 1 | 6 | 700 | 1200 |
| 10 | a | 10 | 12 | 1600 | 1800 | 24 | n | 11 | 14 | 1700 | 2000 | 38 | END | 2 | 7 | 800 | 1300 |
| 11 | s | 11 | 13 | 1700 | 1900 | 25 | m | 0 | 4 | 600 | 1000 | 39 | SYNC_A | 3 | 8 | 900 | 1400 |
| 12 | d | 12 | 14 | 1800 | 2000 | 26 | 1 | 1 | 5 | 700 | 1100 | ||||||
| 13 | f | 0 | 3 | 600 | 900 | 27 | 2 | 2 | 6 | 800 | 1200 |
| sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b | sym | char | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | q | 0 | 2 | 600 | 900 | 14 | g | 1 | 4 | 750 | 1200 | 28 | 3 | 3 | 7 | 1050 | 1650 |
| 1 | w | 1 | 3 | 750 | 1050 | 15 | h | 2 | 5 | 900 | 1350 | 29 | 4 | 4 | 8 | 1200 | 1800 |
| 2 | e | 2 | 4 | 900 | 1200 | 16 | j | 3 | 6 | 1050 | 1500 | 30 | 5 | 5 | 9 | 1350 | 1950 |
| 3 | r | 3 | 5 | 1050 | 1350 | 17 | k | 4 | 7 | 1200 | 1650 | 31 | 6 | 6 | 10 | 1500 | 2100 |
| 4 | t | 4 | 6 | 1200 | 1500 | 18 | l | 5 | 8 | 1350 | 1800 | 32 | 7 | 7 | 11 | 1650 | 2250 |
| 5 | y | 5 | 7 | 1350 | 1650 | 19 | z | 6 | 9 | 1500 | 1950 | 33 | 8 | 8 | 12 | 1800 | 2400 |
| 6 | u | 6 | 8 | 1500 | 1800 | 20 | x | 7 | 10 | 1650 | 2100 | 34 | 9 | 9 | 13 | 1950 | 2550 |
| 7 | i | 7 | 9 | 1650 | 1950 | 21 | c | 8 | 11 | 1800 | 2250 | 35 | 0 | 10 | 14 | 2100 | 2700 |
| 8 | o | 8 | 10 | 1800 | 2100 | 22 | v | 9 | 12 | 1950 | 2400 | 36 | space | 0 | 5 | 600 | 1350 |
| 9 | p | 9 | 11 | 1950 | 2250 | 23 | b | 10 | 13 | 2100 | 2550 | 37 | SHIFT | 1 | 6 | 750 | 1500 |
| 10 | a | 10 | 12 | 2100 | 2400 | 24 | n | 11 | 14 | 2250 | 2700 | 38 | END | 2 | 7 | 900 | 1650 |
| 11 | s | 11 | 13 | 2250 | 2550 | 25 | m | 0 | 4 | 600 | 1200 | 39 | SYNC_A | 3 | 8 | 1050 | 1800 |
| 12 | d | 12 | 14 | 2400 | 2700 | 26 | 1 | 1 | 5 | 750 | 1350 | ||||||
| 13 | f | 0 | 3 | 600 | 1050 | 27 | 2 | 2 | 6 | 900 | 1500 |
xxxfastn ("Extra extra extra fast (new)")29 physical tones, 600..2000 Hz in 50 Hz steps (index 0..28), 240 samples per symbol. Control symbols and text use the 40 pairs of the control table (symbols 0..31 are bank 0's pairs, 32..36 five more characters, 37..39 the specials). A file body symbol is a pair chosen from 64: bank 0 or 1 and a pair 0..31 within it; 6 bits = 5 bits for the pair index (low bits) and 1 bit for the bank (the high bit). The decoder treats every pair of either bank, and END, as candidates inside a file body.
Tone i is 600 + 50·i Hz, i = 0..28. Pairs (tone a, tone b):
| sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 (q) | 4 | 8 | 800 | 1000 | 14 (g) | 6 | 16 | 900 | 1400 | 28 (3) | 6 | 18 | 900 | 1500 |
| 1 (w) | 12 | 16 | 1200 | 1400 | 15 (h) | 7 | 17 | 950 | 1450 | 29 (4) | 7 | 19 | 950 | 1550 |
| 2 (e) | 14 | 18 | 1300 | 1500 | 16 (j) | 8 | 18 | 1000 | 1500 | 30 (5) | 8 | 20 | 1000 | 1600 |
| 3 (r) | 16 | 20 | 1400 | 1600 | 17 (k) | 9 | 19 | 1050 | 1550 | 31 (6) | 9 | 21 | 1050 | 1650 |
| 4 (t) | 18 | 22 | 1500 | 1700 | 18 (l) | 10 | 20 | 1100 | 1600 | 32 (7) | 5 | 23 | 850 | 1750 |
| 5 (y) | 20 | 24 | 1600 | 1800 | 19 (z) | 11 | 21 | 1150 | 1650 | 33 (8) | 6 | 24 | 900 | 1800 |
| 6 (u) | 22 | 26 | 1700 | 1900 | 20 (x) | 12 | 22 | 1200 | 1700 | 34 (9) | 7 | 25 | 950 | 1850 |
| 7 (i) | 24 | 28 | 1800 | 2000 | 21 (c) | 14 | 24 | 1300 | 1800 | 35 (0) | 8 | 26 | 1000 | 1900 |
| 8 (o) | 18 | 24 | 1500 | 1800 | 22 (v) | 16 | 26 | 1400 | 1900 | 36 (space) | 10 | 28 | 1100 | 2000 |
| 9 (p) | 20 | 26 | 1600 | 1900 | 23 (b) | 18 | 28 | 1500 | 2000 | 37 (SHIFT) | 5 | 9 | 850 | 1050 |
| 10 (a) | 22 | 28 | 1700 | 2000 | 24 (n) | 2 | 14 | 700 | 1300 | 38 (END) | 13 | 17 | 1250 | 1450 |
| 11 (s) | 16 | 24 | 1400 | 1800 | 25 (m) | 3 | 15 | 750 | 1350 | 39 (SYNC_A) | 21 | 25 | 1650 | 1850 |
| 12 (d) | 18 | 26 | 1500 | 1900 | 26 (1) | 4 | 16 | 800 | 1400 | |||||
| 13 (f) | 20 | 28 | 1600 | 2000 | 27 (2) | 5 | 17 | 850 | 1450 |
| sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 4 | 8 | 800 | 1000 | 11 | 16 | 24 | 1400 | 1800 | 22 | 16 | 26 | 1400 | 1900 |
| 1 | 12 | 16 | 1200 | 1400 | 12 | 18 | 26 | 1500 | 1900 | 23 | 18 | 28 | 1500 | 2000 |
| 2 | 14 | 18 | 1300 | 1500 | 13 | 20 | 28 | 1600 | 2000 | 24 | 2 | 14 | 700 | 1300 |
| 3 | 16 | 20 | 1400 | 1600 | 14 | 6 | 16 | 900 | 1400 | 25 | 3 | 15 | 750 | 1350 |
| 4 | 18 | 22 | 1500 | 1700 | 15 | 7 | 17 | 950 | 1450 | 26 | 4 | 16 | 800 | 1400 |
| 5 | 20 | 24 | 1600 | 1800 | 16 | 8 | 18 | 1000 | 1500 | 27 | 5 | 17 | 850 | 1450 |
| 6 | 22 | 26 | 1700 | 1900 | 17 | 9 | 19 | 1050 | 1550 | 28 | 6 | 18 | 900 | 1500 |
| 7 | 24 | 28 | 1800 | 2000 | 18 | 10 | 20 | 1100 | 1600 | 29 | 7 | 19 | 950 | 1550 |
| 8 | 18 | 24 | 1500 | 1800 | 19 | 11 | 21 | 1150 | 1650 | 30 | 8 | 20 | 1000 | 1600 |
| 9 | 20 | 26 | 1600 | 1900 | 20 | 12 | 22 | 1200 | 1700 | 31 | 9 | 21 | 1050 | 1650 |
| 10 | 22 | 28 | 1700 | 2000 | 21 | 14 | 24 | 1300 | 1800 |
| sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b | sym | tone a | tone b | Hz a | Hz b |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10 | 22 | 1100 | 1700 | 11 | 7 | 21 | 950 | 1650 | 22 | 6 | 22 | 900 | 1700 |
| 1 | 11 | 23 | 1150 | 1750 | 12 | 8 | 22 | 1000 | 1700 | 23 | 7 | 23 | 950 | 1750 |
| 2 | 12 | 24 | 1200 | 1800 | 13 | 9 | 23 | 1050 | 1750 | 24 | 8 | 24 | 1000 | 1800 |
| 3 | 13 | 25 | 1250 | 1850 | 14 | 10 | 24 | 1100 | 1800 | 25 | 9 | 25 | 1050 | 1850 |
| 4 | 14 | 26 | 1300 | 1900 | 15 | 11 | 25 | 1150 | 1850 | 26 | 10 | 26 | 1100 | 1900 |
| 5 | 16 | 28 | 1400 | 2000 | 16 | 12 | 26 | 1200 | 1900 | 27 | 12 | 28 | 1200 | 2000 |
| 6 | 2 | 16 | 700 | 1400 | 17 | 14 | 28 | 1300 | 2000 | 28 | 1 | 19 | 650 | 1550 |
| 7 | 3 | 17 | 750 | 1450 | 18 | 2 | 18 | 700 | 1500 | 29 | 2 | 20 | 700 | 1600 |
| 8 | 4 | 18 | 800 | 1500 | 19 | 3 | 19 | 750 | 1550 | 30 | 3 | 21 | 750 | 1650 |
| 9 | 5 | 19 | 850 | 1550 | 20 | 4 | 20 | 800 | 1600 | 31 | 4 | 22 | 800 | 1700 |
| 10 | 6 | 20 | 900 | 1600 | 21 | 5 | 21 | 850 | 1650 |
The control table's first 32 pairs are the same as bank 0's. The codebook was chosen by a deterministic greedy removal of the most look-alike pair (the program's _bank_codebook()); the lists above are the result, and are what matters.
axfastSame tone pairs and 25 ms symbols as xfast, but with its own SYNC pattern (symbols 0 and 5 instead of
39 and 37) and, in a file body, two extra bits per symbol coded as the balance of the pair: the lower
tone is louder or quieter than the upper by the amounts below. A file symbol is 7 bits: low 5 = pair index, high 2 =
balance level. Text, TYPE, PATH and END use equal tones (balance -1: weight 1/2 each). Both tones together keep the
same peak.
| level | lower tone vs upper | weight of lower tone | weight of upper tone |
|---|---|---|---|
| 0 | +4.0 dB | 0.6131 | 0.3869 |
| 1 | +1.3 dB | 0.5373 | 0.4627 |
| 2 | -1.3 dB | 0.4627 | 0.5373 |
| 3 | -4.0 dB | 0.3869 | 0.6131 |
Every burst of an axfast transmission is: SYNC (11 symbols), then 40 calibration symbols
(each of symbols 0..39 once, equal tones), then 8 more (symbol 6, the letter u, at balance
level 0 twice, 1 twice, 2 twice, 3 twice), then the frame's remaining symbols. The receiver uses them to learn each pair's own
balance offset and the level thresholds. axfast files were published as experimental: over the air, text
worked and files failed on the one radio it was tried on.
symbol_audio[k][n] = mean over the symbol's tones t of sin(2*pi * f_t * n / 24000), n = 0 .. sym-1
(two-tone: the average of its two sines; axfast file body: w_low*sin(f_low) + w_high*sin(f_high),
r = 10^(LEVEL_DB[level]/20), w_low = r/(1+r), w_high = 1/(1+r))
0.5 - 0.5·cos(pi·n/N), N = 0.005·24000 =
120 samples) so there are no key clicks.plain frame: SYNC(11) TYPE [PATH(8)] body [CRC] END
extended frame: SYNC(11) x TYPE BURST FLAG [PATH(8)] body [CRC] END
+ + + - - - + - - + - sent as SYNC_A (for +)
and SYNC_B = SHIFT (for -). The amplitude profile uses symbol 0 (q) for + and symbol 5
(y) for -. Receivers search for it only between frames.t text, f file, s server text,
d server file, or x = extended frame (section 7), in which case the real TYPE follows./ of a URL.t, s)One symbol per character (SHIFT forms as in section 2), then a CRC-8 as two base-37 digits (high
digit first; each digit is a data symbol 0..36). CRC-8 polynomial 0x07, initial value 0, no reflection, no final
XOR, computed with one "byte" per symbol value (0..37) over: TYPE, PATH symbols (if any) and the body
symbols, in order. (In an extended frame the three header symbols after x are part of the checksummed
head as well; see section 7.)
crc = 0
for v in values: # symbol values, not characters
crc ^= v
repeat 8: crc = ((crc << 1) ^ 0x07) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
send crc // 37, crc % 37
f, d)Payload bytes: [name length: 1 byte][name: UTF-8, at most 255 bytes][size: 4 bytes, big endian][content]
[CRC-32: 4 bytes, big endian]. The file name is reduced to its base name with characters
< > : " / \ | ? * and control characters replaced by _, leading/trailing spaces and dots
removed, at most 150 characters. The CRC-32 is the standard zlib/PNG CRC-32 and covers
[extended-header symbols and PATH symbols, as bytes] + name length + name + size + content. (For a plain
non-server frame that prefix is empty.)
The payload is sent MSB first as 5-bit digits, one per symbol using symbols 0..31, the last digit zero-padded. The
receiver knows the number of symbols from the size field, so no padding marker is needed:
tones = ceil((9 + name_len + size) * 8 / bits). The 7-bit (axfast) and 6-bit
(xxxfastn) profiles cut the stream into 7 or 6 bit groups; the low 5 bits of a group are the symbol 0..31
and the high 1 or 2 bits are the balance level or the bank.
There is no size limit other than the 4-byte size field (4 GB). Each profile has a usual maximum
(10,000 bytes at normal and fast, 100,000 bytes for the others); larger files are still sent
and received, with a warning, and receivers older than v2.3 refuse them.
s with path idstart, idend or idalone)New in v2.7. An ordinary server text frame whose PATH is one of idstart (sent at the beginning of a
transmission), idend (at the end) or idalone (an ID sent by itself), and whose text is the
sender's callsign in lower case (letters, digits and /; at most 15 characters), optionally followed by one
space and the sender's location as a grid square, the Maidenhead locator (below). A receiver shows
"<CALLSIGN> is sending you a message" for idstart. Because it is an ordinary frame, every
receiver since v1.4 decodes it as a message with that path; a v2.7 receiver recognises the path and treats it as a
station ID instead of a message. See section 10.
Location. The text is callsign or callsign locator, e.g.
ke9gdp fn31pr21. The locator is the amateur-radio Maidenhead grid square: 4 characters
(FN31, about 100 x 150 km), 6 (FN31pr, about 5 x 7 km) or 8 (FN31pr21, about
0.5 x 0.7 km, "neighbourhood"; the default). Characters: field = two letters A..R (20° of longitude x 10° of
latitude), square = two digits (2° x 1°), subsquare = two letters a..x (5' x 2.5'), extended square = two
digits (30" x 15"). Longitude letter/digit first, then latitude. Sent in lower case; receivers read it
case-insensitively. A position is never sent unless its owner has switched sending on and entered a location, and
only the cell (never exact coordinates) is sent. The reference computation:
x = (lon + 180) / 360 * 18; y = (lat + 90) / 180 * 18
field = letter(A + int(x)), letter(A + int(y))
x = (x - int(x)) * 10; y = (y - int(y)) * 10 square = int(x), int(y)
x = (x - int(x)) * 24; y = (y - int(y)) * 24 subsquare = letter(a + int(x)), letter(a + int(y))
x = (x - int(x)) * 10; y = (y - int(y)) * 10 extended = int(x), int(y)
The centre of the cell is the position a receiver draws. A receiver ignores a second word that is not a valid 4-, 6- or 8-character locator.
s or d with path dm + address)New in v2.8. An ordinary text or file frame, sent as a server-type frame (s text, d file,
plain or extended, with or without error correction), whose PATH is the two characters dm followed by a
6-character address of the station it is meant for. The address is not a secret and the content is not
encrypted or obscured in any way: it is an addressing label, like the target ID of a private call on other digital
voice systems. Everyone can decode every frame; a receiver program simply does not display (or offer to save) a
direct message that is not addressed to its own operator.
cs = callsign, upper case, all white space removed (e.g. "KE9GDP", "W1AW/P")
n = CRC-32(UTF-8 bytes of cs) mod 36^6 (CRC-32 as in zlib / IEEE 802.3)
address = the 6 base-36 digits of n, most significant first, digit alphabet "0123456789abcdefghijklmnopqrstuvwxyz"
PATH = "dm" + address
The match is exact: W1AW and W1AW/P are different addresses. A receiver compares the PATH
with the address of its own callsign: equal = for this station (shown, saved), different = for somebody else (hidden;
it may say that a direct message for somebody else was heard), no dm path = a public message. Receivers
older than v2.8 treat the frame as a server message and show it with its path; nothing else about the frame
differs.
f/d frame whose name ends in .csvz)New in v3.0. Compression is an application-level convention inside an ordinary file frame: nothing in the
frame, its checksums, the bursts or the error correction changes. A compressed file is sent under its real name plus
the suffix .csvz (a compressed text message under the name m.csvz), and its content is
KIND 1 byte 0 = a file (the receiver removes ".csvz" from the name), 1 = a text message (ASCII, as it would
have been sent as text)
METHOD 1 byte 0 stored, 1 deflate, 2 deflate with the dictionary below, 3 bzip2, 4 LZMA2
data the compressed bytes
Deflate is the raw deflate stream (RFC 1951, no zlib header or checksum; the file frame's CRC-32 covers it). Method
2 is the same with the preset dictionary below (zlib zdict; 32 KiB window). Method 3 is a normal bzip2
stream (level 9). Method 4 is a raw LZMA2 stream (FORMAT_RAW, preset 9, dictionary size 1 MiB). A sender
uses the container only when the frame becomes shorter; otherwise it sends the original. A receiver that gets a file
whose CRC-32 is good and whose name ends in .csvz and whose content decodes as above gives the user the
original file or message (and may say how many bytes were sent); if it does not decode, it is an ordinary file with
that name. A receiver that does not know this convention (before v3.0) simply saves a file called
name.csvz. The unpacked size is limited to 256 MiB.
Lossy modes are not part of the format: the sender replaces the text or file by a smaller simplified one of the same kind (for example a smaller image, or text without vowels) and then sends that, with or without the container above. The receiver cannot tell and needs nothing.
Method 2 dictionary (the bytes, ASCII, one line):
the and that have for not with you this but his from they say her she will one all would there their what out if about who get which go me when make can like time no just him know take people into year your good some could them see other than then now look only come its over think also back after use two how our work first well way even new want because any these give day most us is are was were been being has had do does did am can may might must shall should where why here today tonight tomorrow yesterday morning afternoon evening hello thanks thank you please sorry okay yes message file received sent signal report weather radio station antenna power watts frequency band mhz khz repeater simplex copy roger over out cq de qth qsl qso qrz qrm qrn qsb qrp qrt qrv qsy 73 88 best regards good morning evening night how are you doing fine rst 599 59 name location county state grid locator callsign operator rig contact contest net check in checking schedule meeting tonight at pm am utc local time date home portable mobile base tower hill mountain river lake road street city town north south east west left right up down high low strong weak clear noisy static fade fading rough conditions test testing this is a test of the message from to on in of it be as by or an at so we he my up do go no ke9gdp w1aw n0call
Sent only when needed: the radio's time-out is not the 180 s default (the bursts are then sized to fit it), the
body has a run of more than 40 identical symbols (it is scrambled), or error correction is on.
SYNC x TYPE BURST FLAG [PATH] body END:
x: the data symbol x. TYPE: as above (t, f, s, d).5 · (c+1) seconds of tones, so
5..185 s: symbols per burst = round(5 · (c+1) · 24000 / sym). A plain frame's burst is 120 s
(round(120 · 24000 / sym)).scramble + 2 · fec_level; scramble 0 or 1, fec level
0..4 (section 8).x TYPE BURST FLAG PATH except that the leading TYPE_EXT is
excluded from the file CRC-32 prefix (the prefix is every head symbol after the first) and the text CRC-8 is computed
over the head symbols as sent.During a long run of one tone a receiver's clock tracker has nothing to lock on and drifts, so a body with more than
40 equal symbols in a row is whitened with a fixed sequence both ends know. Body symbol i (counting from the
first body symbol) is replaced by (s + K[i]) mod M, M = 32 for files and error-corrected bodies, 38 for
plain text (data symbols and SHIFT); symbols outside 0..M-1 (END, unreadable) are left alone. The receiver
subtracts the same K[i]. K is xorshift32 with seed 0x2545F491:
x = 0x2545F491
for i in 0, 1, 2, ...:
x ^= (x << 13) & 0xFFFFFFFF
x ^= x >> 17
x ^= (x << 5) & 0xFFFFFFFF
K[i] = x >> 8
First values: K[0..15] = 14754998 9149044 6611379 5958 15904187 16701265 9081936 1209626 9852735 4647537 1299178 10335439 9310030 10372034 2525782 15196658
Optional, per transmission; levels 0..4 (Off, Low, Medium, High, Maximum) = 0, 16, 32, 64, 128 parity
bytes per block. A frame with level > 0 is always an extended frame with the level in FLAG. Older receivers (before
v2.4) accept only 0 or 1 in FLAG and report such a frame as lost.
0x11D (x^8+x^4+x^3+x^2+1, the QR-code
field), primitive element alpha = 2.prod_{i=0}^{nsym-1} (x - alpha^i), roots
alpha^0 .. alpha^(nsym-1). A block holds up to 255 - nsym data bytes and gets
nsym parity bytes (bytes written highest power first, data then parity). The last block may be shorter
(shortened code, no padding). A block corrects nsym/2 unknown wrong bytes, or nsym bytes
whose positions are known (erasures: the receiver knows which symbols it could not read).[4-byte payload length, big endian] plus
p parity bytes of its own, p = min(16, max(8, nsym/2)), one RS codeword (same field and
generator rule with p roots). The receiver reads it first to learn the frame length.[head symbols after the first] + packed bytes. The text CRC-8 is not
used.length codeword + interleaved blocks) is sent as a file body (5/6/7-bit digits) and, if the
scramble flag is set, whitened (mod 32).Cost for a long message: Low +7%, Medium +14%, High +34%, Maximum +101%; a short text pays the length codeword and one block's parity.
A burst is the profile's SYNC (and, for axfast, the 48 calibration symbols) followed by up to
every frame symbols; the last burst ends with END. A transmission, in order:
website.wav
played 1.5x faster at the same pitch (WSOLA). Receivers ignore it, like all speech.Time-out off (BURST code 37). The BURST tone may also be 37 (the SHIFT symbol, outside the 36 codes above): the sender's radio has no time-out, and bursts are 480 s of tones long (the longest that still leaves a pause for a callsign inside the ten-minute window). A receiver that does not know code 37 reports the frame as lost. Because that tone is part of the checksummed head, checksum code treats a header symbol of 37 as valid.
Bursts and the radio time-out. A plain frame is cut into 120 s bursts. With another time-out
setting T (30..600 s) the sender uses the longest burst length (5..185 s in 5 s steps) for which the longest
keyed stretch (everything between two pauses, from the start of the lead-in) is at most T - 5 seconds, and
sends the length in the extended header. The receiver knows where pauses fall: after every every symbols
with no END coming it stops reading, waits up to 90 s for the next SYNC (matching the same profile), and continues the
same frame (symbol counting carries on).
Mid-transmission IDs. At a pause, an ID (spoken, or Morse if selected) is inserted when the time since the last ID plus the next burst (plus, for the last burst, the website clip and closing gaps) would exceed 540 s. A frame is never interrupted by a data ID frame: a second SYNC there would be taken for the next burst.
The program offers two mechanics; which one a station uses is its operator's setting. Receivers ignore the choice and decode both.
A data ID frame (section 6) carrying the station's callsign is sent in the speed of the
transmission at the beginning (idstart, after the KEY tone) and again at the end (idend, after
the website clip, before the end tone) of every transmission. An ID sent on its own (the Send callsign button) is an
idalone frame in the Fast profile. If more than 540 s would pass between IDs inside one long transmission,
the callsign is also spoken (or sent in Morse) at the pause between bursts, as in the legacy mechanics. A client shows
"<CALLSIGN> is sending you a message" when it hears an idstart and "has finished sending" at an
idend. The callsign is up to 15 characters of letters, digits and /.
Location and the map. The program's Location setting (on by default; nothing is sent until a location has been entered) adds the sender's neighbourhood locator after the callsign in these frames. It goes out over the radio, so anyone who can receive the signal can read it; the program does not send it to the website. A receiver remembers stations heard with a location and its Map tab draws a red dot for the operator and a blue dot, with a line, for each of them. Who may use or share what they receive is up to them; the location is exactly what is in the frame, no more.
The callsign is spoken (one pre-recorded clip per character: a.wav..z.wav are the phonetic
words alpha..zulu, 0.wav..9.wav digits, 0.15 s between characters, silence trimmed) or sent
as Morse code. In the desktop window the operator presses Send callsign (a 10-minute timer flashes from 9:30) and one
goes out where waiting any longer would pass 10 minutes; the unattended servers speak it at the start and end of every
reply. Morse: International Morse, PARIS timing, 20 words per minute (one unit = 1.2/20 s = 60 ms; dot 1 unit, dash 3,
1 between elements, 3 between characters), a sine at 2400 Hz, 5 ms fades. Table:
a .- b -... c -.-. d -.. e . f ..-. g --. h .... i .. j .--- k -.- l .-.. m -- n -. o --- p .--. q --.- r .-. s ... t - u ..- v ...- w .-- x -..- y -.-- z --.. 0 ----- 1 .---- 2 ..--- 3 ...-- 4 ....- 5 ..... 6 -.... 7 --... 8 ---.. 9 ----. / -..-.
This is a description of the reference receiver; any receiver that makes the same decisions interoperates. The reference runs one decoder per profile on the same audio and merges what they report.
For a profile with sym samples per symbol, every hop = sym / 40 samples compute, over the
last sym samples x and for each physical tone t at frequency f_t:
c = sum x[n] cos(2 pi f_t n / 24000) s = sum x[n] sin(2 pi f_t n / 24000) e = sum x[n]^2
frac_t = (c^2 + s^2) / ( (sym/2) * e + 0.0001 ) # fraction of the window's energy in tone t; ~1 for a clean tone
score(symbol) = frac_t # single-tone profiles
score(symbol) = 2 * sqrt(frac_a * frac_b) # two-tone profiles (a, b the symbol's pair)
Loudness does not matter: only the energy fraction, so speech and noise score low. This gives, per hop, a vector of 40 scores (the bank profile has 40 control columns plus 32 for each extra bank).
sync = mean over k=0..10 of score[i + 40k][SYNC_k] where
SYNC_k is the profile's Barker symbol k. A frame starts when it reaches the sync threshold: 0.55
at the default sensitivity (sensitivity is a user slider 0..1: threshold 0.80 at 0, 0.55 at 0.5, 0.01 at 1,
straight lines between), times the profile's sync_scale (1.0; 0.7 for the two XXXFAST profiles).sync within the next 12 symbols after the first hit (a Barker side lobe can hit
early). That hop is the symbol grid: symbol j of the frame is read at hop t0 + 40 (11 + j).axfast the 48 calibration symbols come next.0.20 at the default sensitivity (it is
sync threshold × 0.20/0.55), times weak_scale (0.5 for XXXFAST profiles).t f s d x, and for an extended frame the TYPE and FLAG
symbols come from short lists: when the overall best symbol is not valid but a valid one scores at least half as much
(and the sync matched at least 0.3), take the valid one.The sampling grid is adjusted by one hop when the evidence adds up: for a clean symbol (score at least 0.6) that
sits between two different symbols, accumulate score[next hop] - score[previous hop] of its tone; when the
accumulated value reaches ±0.2, move the grid one hop (later if positive, earlier if negative) and clear the
accumulator. That absorbs sound-card clock differences of at least ±1000 ppm; it is why long runs are scrambled
(a steady tone has nothing to track on).
When symbols_read is a multiple of the frame's every and the next symbol is not END
(score of END under 0.5), the sender has paused: stop reading and search for the profile's SYNC again; on finding it,
continue with the next symbol of the same frame. Give up (frame lost) after 90 s without it.
crc_ok when every symbol was readable and the CRC-8 matches. File: the CRC-32 matches and the
length equals the size field (unreadable symbols count as 0 so byte positions survive, and the file is flagged
damaged). A server frame's PATH is part of both checks.sync(score), message(text, crc_ok, path),
file(name, data, crc_ok, path), lost(what, kind), fec(corrected, unreadable, ok)
(just before its message/file), big_file(name, size, usual) and end_tone. A message whose path
is idstart, idend or idalone is a station ID (section 10).For xxxfast and xxxfastn file and error-corrected bodies, once the whole body has been read
the receiver re-decides it: for each body symbol keep the three hop rows around the live sampling point; repeat 5 passes
of "pick, per symbol, the (hop, pair) with the highest score divided by that pair's own current average; recompute each
pair's average over the symbols assigned to it" (a pair needs at least 3 symbols to have its own average, otherwise the
median is used); a symbol whose score is under 0.15 of its pair's average stays unreadable. It needs at least 300 body
symbols per 32 pairs of candidates.
For axfast the receiver also measures each tone's amplitude through a Hann window (w =
0.5 - 0.5 cos(2 pi n / sym), scaled so a steady tone reads its true amplitude). Calibration: l0[s] =
ln(amp_low / amp_high) for each of the 40 equal-tone symbols; the ladder gives the four level offsets
d[j] = mean ln(amp_low/amp_high) at level j - l0[6]; require d0 > d1 > 0.05,
d2 < -0.05 and d2 > d3, otherwise use the nominal LEVEL_DB·ln(10)/20.
Threshold T = (d0 - d3 + d1 - d2)/4. A body symbol's level: x = ln(amp_low/amp_high) -
l0[symbol]; x >= 0: level 0 if x > T else 1; x < 0: level 3 if
-x > T else 2. The clock tracker uses the symbol's own amplitude in this mode. The "weak symbol"
minimum is multiplied by 0.3.
Every profile's decoder sees every sample, and at a high sensitivity another speed's decoder can false-sync on a frame's data or on the speech around it. The reference therefore: treats as primary the receiving decoder with at least 16 non-weak symbols and the best average score (a real frame averages about 1); while one exists, passes on only a file whose CRC-32 passed, or a message whose CRC-8 passed, that is about as substantial and whose symbols averaged at least 0.25; drops any failed frame (lost, garbled, bad file) whose symbols averaged under 0.2 (speech or noise), or under 0.5 in the 30 s after an intact frame; and when a frame finishes intact, resets the other decoders' half-done frames. None of this is needed on a quiet channel at the default sensitivity.
Separately from the frames: every 0.1 s measure the energy fraction (same formula as 11.1) at 450 Hz over the last 0.5 s; after 12 consecutive windows at or over the sync threshold, report the tone once; re-arm when it stops.
A server is an unattended station that answers requests. A client sends a server text (s) or
server file (d) frame whose PATH names what it wants (ping, document,
chatgpt, ...), followed by the 450 Hz end tone. The server decodes the frame, waits for the end tone (it
means "I have finished, your turn"; if none comes within 30 s the server answers anyway), waits about a second, and
answers with an ordinary transmission (text or file, with its own IDs). A server ignores ID frames
(idstart, idend, idalone) and plain messages without a path are answered only if
they equal its configured trigger. A server must be half duplex: deaf while it transmits and for 1.5 s after. The
built-in paths are examples; the path namespace and reply contents are up to each server.
Server 2.0 (v3.2): connections. A TCP-style transport between two stations, carried in ordinary
server file frames (d) whose PATH is tc + the 6 base-36 characters of the destination
callsign (computed exactly as for a direct message: CRC-32 of the upper-case callsign mod 366), file name
t, and the file content a segment: [flags 1][conn 1][port 1][seq 4][ack 4][src len 1]
[src callsign][payload] (numbers big-endian). Flags: SYN 1, ACK 2, FIN 4, RST 8. Sequence numbers count bytes
(SYN and FIN each use one), are 32 bit, and the first one is random; ack is the next byte expected.
Open: SYN, SYN+ACK, ACK. Data and acknowledgements ride together. Close: FIN, ACK in each direction. One segment is
in flight at a time (stop and wait, because every transmission costs a key-up, an ID and a SYNC); an unacknowledged
segment is repeated when its timer runs out (the airtime of the segment, plus the airtime of an acknowledgement, plus
20 s for the peer's end tone and key-up; doubled on each repeat up to 8 times), up to a set number of tries. The frame's
CRC-32 is the checksum: a damaged frame is dropped and repaired by the repeat. A segment is sent with the end tone,
and the peer answers after hearing it. A segment for another callsign is ignored. A station that is not accepting
connections sends nothing. RST answers a SYN to a closed port or a segment of an unknown connection (only from a
station that accepts connections or has one open). A station has one connection at a time, so a SYN for a new
connection id (and port) from a station that still has an open connection with you replaces that old connection
(drop it silently; v3.6.2: over the air a SYN+ACK was lost and the retry was refused); a SYN from a different station
is refused with RST while a connection is open. The first transmission of a SYN+ACK is not a repeat. Test:
tests/test_tcp.py. Status: simulation only; not yet tried over the air.
Symbols are shown as numbers (the alphabet table of section 2). SYNC is
39 39 39 37 37 37 39 37 37 39 37 in every frame below and is omitted from the lists. If your implementation produces these symbol
sequences, and its audio decodes with another implementation, the framing is right.
t frame: TYPE, 'H' (SHIFT h), i, '!' (SHIFT 1), CRC-8 as two base-37 digits, END.
4 37 15 7 37 26 1 27 38CRC-8 over TYPE and body = 64.
s frame: TYPE, PATH ping (8 symbols), body, CRC-8, END.
11 9 7 24 14 36 36 36 36 9 7 24 14 0 13 38, is SHIFT SHIFT then the digit symbol for 1.
4 10 37 37 26 23 5 17 38f frame: base-32 digits of 05 'a.txt' 00000005 'hello' CRC-32.
13 0 21 16 18 28 29 3 24 14 16 0 0 0 0 0 5 13 1 18 22 24 27 3 15 16 3 23 7 17 0 8 38payload CRC-32 = 80EE7881
s frame with PATH idstart.
11 7 12 11 4 10 3 4 36 17 2 34 14 12 9 4 33 38s frame with PATH idstart; the text is ke9gdp fn31pr21 (the space is symbol 36).
11 7 12 11 4 10 3 4 36 17 2 34 14 12 9 36 13 24 28 26 9 3 27 26 1 12 3841.714775, -72.727260 is in FN31pr21 (8 characters); in 6 characters FN31pr, in 4 FN31.
s frame with PATH idend; / is SHIFT SHIFT then the digit symbol for 3.
11 7 12 2 24 12 36 36 36 1 26 10 1 37 37 28 9 0 26 38s frame with PATH dmiszlim (dm + the address of KE9GDP).
11 12 25 7 11 19 18 7 25 15 7 5 7 38CRC-32("KE9GDP") = 43C67BAE, mod 36^6 = 1137081262.
x TYPE BURST FLAG ... (flag 0).
20 4 23 0 37 15 7 37 26 0 14 38x t BURST FLAG with FLAG = 2, then the Reed-Solomon stream as base-32 digits.
20 4 23 2 0 0 0 0 0 2 5 11 11 2 25 10 26 10 24 1 1 27 0 9 9 28 15 5 13 13 25 7 26 18 18 3 9 21 20 4 15 11 17 11 7 21 25 28 9 15 13 9 4 5 7 22 21 11 21 12 18 8 38
encode_header(5, parity=8) = 00000005242708ee7423cf78encode(b"CSVF2", nsym=16) = 4353564632c870bce93835d828155af21fcc2b3750 (21 bytes: the 5 data bytes then 16 parity bytes)GF exp table alpha^0..alpha^15 = 1 2 4 8 16 32 64 128 29 58 116 232 205 135 19 38
The 0x11D field; alpha^8 = 29 because x^8 = x^4+x^3+x^2+1.
One fast symbol is 1600 samples (66.67 ms); a text frame of n symbols (SYNC and END
included) lasts n * 1600 / 24000 s at fast, n * 4800 / 24000 s at
normal, n * 600 / 24000 at xfast, n * 240 / 24000 at
xxfast.
| Program version | What it added to the on-air format |
|---|---|
| v1.0 | Text and file frames, NORMAL and FAST, SHIFT, CRC-8/CRC-32, SYNC, bursts |
| v1.1-1.2 | 120 s bursts with pauses, 400 Hz key tone |
| v1.4 | Server types s / d with an 8-symbol PATH, 450 Hz end tone |
| v1.9 | XFAST (two simultaneous tones per symbol) |
| v2.0 | Extended frames (burst code, scramble flag), radio time-out setting |
| v2.1 | Morse ID (at 1500 Hz; 2400 Hz since v2.3) |
| v2.3 | AXFAST (experimental for files), second shift layer, no hard file-size limit |
| v2.4 | Reed-Solomon error correction (flag 0..9), XXFAST |
| v2.6 | XXXFAST, XXXFAST (new, bank profile), receiver restart on a new SYNC |
| v2.7 | Station-ID frames (idstart/idend/idalone paths), "new" callsign
mechanics, an optional Maidenhead locator after the callsign; no change to anything else |
| v2.8 | Direct messages (dm + 6-character address PATH on s / d
frames); no change to the frame format itself |
| v2.9 | Profiles slow and superslow (band-scored tones); older receivers do not
hear them |
| v3.0 | Profile slowest (1600 ms symbols); compressed messages and files (the
.csvz container, section 6); the station ID of "Send callsign" follows the chosen speed |
| v3.1 | BURST code 37 = radio time-out off (480 s bursts); older receivers report such a frame as lost |
| v3.2 | Server 2.0: TCP-style connections (tc PATH, section 12); older receivers show a
segment as a received file |
| v3.3 | No change to frames, speeds or paths: ke9gdp.com accounts, Feedback chat and email inbox are website features (see the privacy policy) |
A receiver ignores audio it cannot decode (no output, never a false file: files carry a CRC-32). Older receivers report frames from newer versions as lost or not at all.
| name | value |
|---|---|
FS | 24000 |
HOPS_PER_SYM | 40 |
BURST_S | 120 |
ID_MAX_GAP_S | 540 |
BREAK_TIMEOUT_S | 90 |
KEY_TONE_HZ | 400.0 |
END_TONE_HZ | 450.0 |
END_TONE_S | 2.0 |
END_TONE_GAP_S | 0.25 |
DEFAULT_KEY_S | 5.0 |
DEFAULT_PAUSE_S | 6.0 |
MAX_PAUSE_S | 30.0 |
DEFAULT_TOT_S | 180 |
MIN_TOT_S | 30 |
MAX_TOT_S | 600 |
TOT_MARGIN_S | 5 |
BURST_STEP_S | 5 |
BURST_CODES | 37 |
EXT_LEN | 3 |
RUN_LIMIT | 40 |
PATH_LEN | 8 |
FILE_TONES | 32 |
FORMAT_MAX_BYTES | 4294967295 |
WEBSITE_SPEED | 1.5 |
WEBSITE_EVERY | 10 |
WORD_GAP_S | 0.15 |
MORSE_TONE_HZ | 2400.0 |
MORSE_WPM | 20 |
VOICE_TO_TONES_S | 0.25 |
TONES_TO_VOICE_S | 0.25 |
TAIL_S | 0.3 |
ID_MAX_CHARS | 15 |
DM_PREFIX | dm |
DM_ADDR_LEN | 6 |
FEC_PARITY | (0, 16, 32, 64, 128) |
BARKER11 | (1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1) |
Decoder.SYNC_THRESHOLD | 0.55 |
Decoder.SYMBOL_MIN | 0.2 |
Decoder.LOST_AFTER_S | {'text': 0.8, 'file': 5.0} |
Decoder.TRACK_MIN_PEAK / TRACK_THRESHOLD | 0.6 / 0.2 |
EndToneDetector WINDOW / HOP / NEED | 12000 / 2400 / 12 |
ID_PATHS | {'idstart': 'start', 'idend': 'end', 'idalone': 'alone'} |
This specification is generated from the program source by tools/build_protocol_doc.py
for each release. If you find a difference between it and the program, the program is the reference; please report it
using the Feedback button in the program.