CSVF stands for client server via frequency. Send text and files over a voice radio as audio tones, receive them again, and identify with your callsign spoken in NATO phonetics or sent in Morse code. Current version: 3.6.3 (released 2026-10-17 08:00 CDT).
Made by Reed Gabriel (KE9GDP). Licence: Creative Commons BY-NC-SA 4.0: you may share and change it, non-commercially and under the same licence, and you must give credit to the original author in a reasonable, easy-to-see way (not fine print): anyone using your copy should know it was originally made by Reed Gabriel (KE9GDP).
Windows desktop app: the window, the automatic server, the modem, the spoken-callsign clips and
website.wav. Two ways to run it: the Python version (recommended, the
reference) or the exe (no Python needed). Both need a free ke9gdp.com account, which the
program asks you to sign up for or log into the first time it starts.
How to run it: Python version
csvf2.zip to a folder you keep (for example in Documents).pip install -r requirements.txt
(that installs numpy, scipy and sounddevice).CSVF2.pyw -- that is the real program: the window opens
with no console window behind it, and the CSVF2 logo shows for 3 seconds. (Running
python radio_gui.py does the same but leaves a console open.)update_backup) and asks you to
restart. Your settings, document texts and audio clips are left alone.How to run it: exe version
csvf2-exe.zip to a folder you keep. Keep the whole CSVF2 folder
together: CSVF2.exe needs the files and folders next to it, so do not move the exe out
of it (a shortcut to it is fine).CSVF2.exe. No Python and no installing. The first start
can take a few seconds. Windows may warn about an unknown publisher because the exe is not signed:
choose "More info", then "Run anyway".csvf2-exe.zip, checks its
checksum, closes the program, replaces the files and starts it again by itself. Your settings,
document texts and audio clips are left alone, and the old program files are kept in
update_backup. Older exe versions cannot do this: download the new zip and unzip it over
the old folder once.CSVF2-Server.exe (ping and
pong, and a document), CSVF2-Server25.exe (the same plus ChatGPT, Google and social posts),
CSVF2-Echo.exe (Server 2.0 echo) and CSVF2-Web.exe (a Server 2.0 web site; its pages
are in the www folder). Double-click one: it is its own window, it listens by itself, and its
settings are kept next to it. They are not signed either.Versions the Python version is developed and tested with: Python 3.12.4, numpy 2.1.0,
scipy 1.16.2, sounddevice 0.5.2, tkinter 8.6, on Windows 10. Other recent versions should work (numpy 1.24+
or 2.x, scipy 1.10+, any sounddevice 0.4+) but I have not tried them. Python 3.10 and 3.11 are untested too.
The two server scripts are started with python radio_server_template.py or
python radio_server_25.py (they have no .pyw: you want their log window's
console output anyway).
The first time you run it, the program asks you to accept the privacy policy: it sends the callsign you enter, once, to the public CSVF2 users list on this site, and nothing else about you. If you do not accept, it closes.
Transmit tab.
Receive tab (example log: green = checksum OK, red = garbled reception).
Settings tab.
This picture, ABCDE.png (55 x 64 pixels, 8,053 bytes), was sent over the radio with
CSVF2 at Fast speed in 16 minutes. As tones that is 12,927 of them, sent in 8 bursts of about 2 minutes.
ABCDE.png, shown enlarged. Download the file
forest.png (650 x 544 pixels, 145,681 bytes, about 143 KB) -- sent over the radio with CSVF2 at
Extra extra extra fast (new) with Medium error correction, in about
40 minutes, and received with its checksum OK. It is the biggest file so far,
and the first over-the-air result for Extra extra extra fast (new). Everything arrived fine, so the error
correction turned out not to be needed this time; it stays on as insurance, at a cost of some speed.
forest.png, as received. Download the file
The record before it was 65.mp4 (about 65 KB) -- sent at Extra extra fast
with Low error correction, and received with its checksum OK. It was the
first over-the-air result for both Extra extra fast and Error correction.
65.mp4, as received. Download the file
The record before that was skull_and_roses.png (128 x 128 pixels, 26,157 bytes), sent at
Extra fast speed in about 20 minutes. As tones that is 41,909 of them, sent in 9 bursts of about
2 minutes, with the callsign spoken 4 times along the way.
skull_and_roses.png. Download the file
Every character or piece of data is one audio tone at a time (single-tone MFSK), except at Extra fast, which uses two at a time (see below). There are 37 data tones, then three control tones:
tone 0..25 q w e r t y u i o p a s d f g h j k l z x c v b n m tone 26..35 1 2 3 4 5 6 7 8 9 0 tone 36 space tone 37 SHIFT (makes the NEXT tone a capital, or a symbol) tone 38 END tone 39 SYNC
Shift works like the key on a keyboard, for one tone only. Shift + a letter is
the capital. Shift + a digit is its symbol: 1 2 3 4 5 6 7 8 9 0 become
! @ # $ % ^ & * ( ). So Hi! is SHIFT h i SHIFT 1. A capital or
symbol costs one extra tone. Two shifts in a row (new in v2.3) select a second layer:
SHIFT SHIFT then a digit gives , . / ; ' [ ] \ - = (digits 1 2 3 4 5 6 7 8 9 0
in that order), three tones for one character. No new tone is used; older versions show those ten
characters as ?.
The tone spacing is exactly 1 divided by the symbol time, so the tones are orthogonal: the receiver can tell them apart even when they are only a few Hz apart. That is why a shorter symbol needs wider spacing.
Extra fast makes each tone 25 ms long, so the tones have to be 40 Hz apart, and only 15 of them fit between 560 and 1120 Hz: too few for 40 characters. So each character is sent as a pair of tones played together, 80 to 200 Hz apart, and every character has its own pair. The tone numbers above, the shift rule, the checksums and the bursts are all exactly the same; only the sound of each tone changes.
| Fast (default) | Extra fast | Normal | |
|---|---|---|---|
| One tone lasts | 66.7 ms | 25 ms | 200 ms |
| Tones per second | 15 | 40 (pairs) | 5 |
| Tone spacing | 15 Hz | 40 Hz | 5 Hz |
| Data tones | 555 - 1095 Hz | 560 - 1120 Hz (two at once) | 550 - 730 Hz |
| SHIFT / END / SYNC | 1110 / 1125 / 1140 Hz | tone pairs | 735 / 740 / 745 Hz |
| Text | 15 characters/s | 40 characters/s | 5 characters/s |
| Files | about 9.4 bytes/s | 25 bytes/s | about 3.1 bytes/s |
| 1 KB file | 1 min 49 s | 40 s | 5 min 27 s |
| 10,000 byte file | about 18 min | about 7 min | about 54 min |
| Usual largest file (a warning, not a limit) | 10,000 bytes | 100,000 bytes (about 67 min) | 10,000 bytes |
| Frequency error it can take | about 3 Hz | about 3 Hz | about 1.5 Hz |
The faster modes, which need a cleaner audio path (a wider band, or tones that overlap a little):
| Extra extra fast | Extra extra extra fast (new) | Amplitude extra fast | |
|---|---|---|---|
| One tone lasts | 10 ms | 10 ms | 25 ms |
| Tones per second | 100 (pairs) | 100 (pairs) | 40 (pairs) |
| Tone spacing | 100 Hz | 50 Hz | 40 Hz |
| Data tones | 600 - 2000 Hz (two at once) | 600 - 2000 Hz (two at once, 29 tones, 64 pairs) | 560 - 1120 Hz (two at once, unequal) |
| SHIFT / END / SYNC | tone pairs | tone pairs | tone pairs (own SYNC) |
| Text | 100 characters/s | 100 characters/s | 40 characters/s |
| Files | 62.5 bytes/s | about 74 bytes/s (6 bits a tone pair) | 35 bytes/s (experimental) |
| 1 KB file | about 16 s | about 14 s | about 30 s |
| 10,000 byte file | about 2 min 40 s | about 2 min 15 s | about 5 min |
| Usual largest file (a warning, not a limit) | 100,000 bytes (about 27 min) | 100,000 bytes (about 22 min) | 100,000 bytes |
| Frequency error it can take | about 8 Hz | at least 8 Hz (simulated) | about 3 Hz |
The slower modes (new in v2.9), for weak, noisy or fading signals. Both are Normal with longer tones, so each tone collects more energy and the receiver scores it against its own band only. The receiver must be v2.9 or newer, and the sender should switch on error correction as well:
| Slow | Super slow | Slowest (v3.0) | |
|---|---|---|---|
| One tone lasts | 400 ms | 800 ms | 1.6 s |
| Tones per second | 2.5 | 1.25 | 0.625 |
| Tone spacing | 2.5 Hz | 1.25 Hz | 0.625 Hz |
| Data tones | 1200 - 1288 Hz | 1400 - 1444 Hz | 1600 - 1622 Hz |
| SHIFT / END / SYNC | 1292.5 / 1295 / 1297.5 Hz | 1446.25 / 1447.5 / 1448.75 Hz | 1623.1 / 1623.75 / 1624.4 Hz |
| Text | 2.5 characters/s | 1.25 characters/s | 0.6 characters/s |
| Files | about 1.6 bytes/s | about 0.8 bytes/s | about 0.4 bytes/s |
| 1 KB file | about 11 min | about 22 min | about 44 min |
| Usual largest file (a warning, not a limit) | 5,000 bytes (about 53 min) | 2,000 bytes (about 43 min) | 1,000 bytes (about 43 min) |
| In simulation, a short text still arrived at | -12 dB signal to noise | -15 dB signal to noise | -18 dB signal to noise |
The last row is a computer simulation (white noise in a 300 - 3000 Hz band; Normal stopped working between 0 and +3 dB in the same test). It is not a measured radio sensitivity: nothing has been tried over the air yet.
Fast is 3x quicker in total, not just for the data: the start marker, type tone and end tone shrink too. Normal keeps every tone between 550 and 745 Hz. The receiver listens for both at once, so the receiving side never needs a setting.
Extra fast is 8x Normal (2.7x Fast) and stays inside Fast's frequency range. It needs a slightly cleaner signal than Fast (in simulation, about 1 to 2 dB more). A 100,000 byte file is about 74 minutes on the air once the pauses and callsigns are counted. The times in the table count the tones only. Extra fast is new in v1.9. The first version of it did not receive on a real FM link; it was redesigned after that test, and the redesigned one has since been confirmed working over the air on analog FM.
Extra extra fast (new in v2.4) is Extra fast with 10 ms tones instead of 25 ms: the same pairs of tones, the same start marker and framing, but 100 tones a second, so files go 2.5x faster than Extra fast (62.5 bytes a second, about 27 minutes for 100,000 bytes) and text 100 characters a second. It uses plain frequency coding, no loudness, which is why it is built this way rather than on top of Amplitude extra fast. The price is bandwidth: a shorter tone needs wider spacing to stay clean, so its 15 tones are 100 Hz apart from 600 to 2000 Hz instead of 40 Hz apart from 560 to 1120 Hz. In my simulated channel it decoded as far down in noise as Extra fast (about 2 dB signal-to-noise) and survived the same abuse: a hard limiter, pre-emphasis, clock errors of 1000 ppm, a frequency shift of 8 Hz, and speaker and microphone roll-offs of up to 18 dB per octave. Its weak spot is a steeply tilted level across the band, where a weak tone next to a much louder one gets swamped (a smooth tilt of 9 dB per octave is its limit). First over-the-air result: a 65 KB file (65.mp4, see Biggest file sent so far) arrived with its checksum OK at this speed, with Low error correction. That is one test, so it may still need a radio with a clean audio path up to 2 kHz; tell me what yours does. Older versions do not hear it at all.
Amplitude extra fast (new in v2.3) is Extra fast with a second piece of information in every file symbol. The two tones of a pair are sent slightly unequal, and which one is louder, and by how much (four balances: +4, +1.3, -1.3, -4 dB), carries 2 more bits. Files go about 1.4x faster than Extra fast (35 bytes/s); text, the file header and the end tone are plain Extra fast, so text gains nothing. Every burst starts with its own start marker and about 1.2 s of calibration tones (all 40 pairs equal, then the four balances) so the receiver learns what the link does to them. It needs roughly 10 dB more signal-to-noise than Extra fast in simulation, and older versions do not hear it at all.
Experimental for files. In the first over-the-air tests, text at Amplitude extra fast was received reliably, but files failed often (a radio's limiter and level control change loudness, and the balance only partly survives them). A damaged file is reported as lost or as failing its checksum and is never saved as good. For files use Extra fast; use Amplitude extra fast for files only to experiment, and tell me what your radios do.
Extra extra extra fast (new) (new in v2.6) is Extra extra fast with more data in every tone pair. The symbols are the same 10 ms, but the tones are 50 Hz apart instead of 100 Hz, so there are 29 of them between 600 and 2000 Hz, and a file symbol picks one of 64 pairs (two banks of 32) instead of 32: 6 bits a symbol instead of 5. Files go about 74 bytes a second (1.2x Extra extra fast); text is the same speed as Extra extra fast. Tones that close overlap a little (a tone leaks 40% into the next 50 Hz slot), so the 64 pairs are picked to keep look-alikes out, and after a file is in the receiver decides it again with each pair's own average level. In my simulated channel it holds about 3 dB less noise than Extra extra fast (each extra bit costs roughly that) and passes the same abuse (hard limiter, clock errors, roll-offs, 6 dB per octave of tilt). First over-the-air result: a 143 KB picture (forest.png, see Biggest file sent so far) arrived with its checksum OK in about 40 minutes, with Medium error correction (which was not needed). That is one test, so tell me what your radio does. Older versions do not hear it at all.
Old modes (new in v2.6). Amplitude extra fast and the first Extra extra extra fast (6.67 ms tones, 150 Hz apart, below) are no longer in the Speed list. To use them, tick Show old modes on the Settings tab and accept the warning. Receiving needs no setting: every mode is always listened for.
Extra extra extra fast (old) (new in v2.6) was Extra extra fast shortened to 6.67 ms tones 150 Hz apart (600 to 2700 Hz): files about 94 bytes a second. Over the air it was not good. On the one radio I tried, the first test did not even start a frame; the second received the whole file but about 1 symbol in 38 was wrong even after the receiver's repairs (4% of the bytes: more than Low error correction can fix, so it needs Medium or High and gives back most of the speed). The recording showed why: that radio put loud low-frequency junk under the tones and sent the pairs at very unequal levels, so the weak ones lost. That is what led to the receiver re-deciding a file once it is in, and to the new design above.
Until v2.4 the checksums could only detect damage: a transfer with a bad tone had to be sent again. The Error correction box (Settings tab, and both server scripts) adds a Reed-Solomon code, the same family of code QR codes use, so the receiver can repair damage up to a chosen amount. It works for text and for files at every speed, and the receiver needs no setting: it reads the level from the transmission.
| Level | Extra time on air (long messages) | Wrong tones it repairs | Gap it bridges (tones not heard at all) |
|---|---|---|---|
| Off | none | none | none |
| Low | +7% | about 3% of the message | about 6% |
| Medium | +14% | about 6% | about 13% |
| High | +34% | about 13% | about 25% |
| Maximum | +101% | about 25% | about 50% |
First over-the-air result. The 65 KB file 65.mp4 (see Biggest file sent so far) was sent at Extra extra fast with Low error correction and arrived with its checksum OK; I do not know how much, if anything, the correction had to repair. Apart from that this has only been tested in simulation: the code itself at its exact limits, and whole transmissions at every speed and level through clean audio, band-passed noise, dropouts of 1 to 8 seconds and bursts of noise. At 1 dB signal-to-noise on plain band-passed noise, Off and Maximum both delivered 6 of 6 files in my test, so do not expect it to add range by itself; it is for dropouts, fades and bursts of interference, which is what a checksum could not help with. Tell me what your radios do.
[KEY tone] [SYNC x11] [TYPE] [PATH x8] [ ...data... ] [END] [website.wav] [end tone]
PATH and the end tone are only in messages sent with the Server tab. Your callsign is sent with its own button (see Station ID), and goes into a transmission only when the 10 minute rule needs it. The automatic server scripts still put it at the start and the end of every reply.
audio/: replace it with your own announcement. The
receiver ignores it, like any other speech.t for text, f for a file.
Two more types, s (server text) and d (server file), are followed by an
8-tone PATH before the data. The checksum covers TYPE, PATH and data.name length | name | size | content | CRC-32, in base 32: 32 of the
data tones each carry 5 bits, so a byte takes 1.6 tones. Sending hex would take 2 tones a byte.
The number of tones fixes the exact byte count, so no padding marker is needed.The Server tab is the client side of client and server on one frequency. It works like the Transmit and Receive tabs together and uses the same settings (they are the same settings: change one and the other tabs change too), plus one extra box. Looking for a server to talk to, or want to list your own? See the CSVF2 Server Map.
Server tab.
radio_server_template.py is a station that runs by itself. It listens all the time, and
when it hears the text ping it transmits pong; sent to the path
document, it sends document.txt's text back instead, as a plain message, not
a file. That is the first step toward client and server on one frequency.
The server window.
radio_server_template.py is
meant to be copied and edited: add your own paths in its route_for() function and teach
transmit_reply() how to build each one's audio (another canned reply, another file,
anything else you code up) to build your own CSVF2 server.radio_server.json.Ping works
and hello ping does not.document.txt (next to the script -- replace it with your own), sent back as
an ordinary message, not a file, whatever the message itself says. Only letters, digits, space and
!@#$%^&*() survive the trip; anything else in the file is dropped and logged, not
fatal. A missing document.txt just fails that one reply; the server keeps running.radio_server_25.py started as a copy of the template above and has since grown four
more paths, to show what building your own server on top of it looks like. It has the same window,
the same settings, and the same "document" idea -- just its own document_25.txt, a short
reference for these paths (not the path/end-tone mechanics already covered above).
gpt-4o-mini
model) as a prompt, and its reply is sent back the same way, as text. This needs your own OpenAI
account: set the OPENAI_API_KEY environment variable before starting the script (it is
never written to a file here) and have a working internet connection. The request runs in the
background, so the window and the receiver keep working while it is in flight, and nothing else is
sent out while it waits. Capped at 100 calls a day (resets at midnight, local
server time) -- past that, a fixed "limit reached" message is sent instead and OpenAI is not
called, so it can't run away with your account. A missing key, no internet, or a bad response just
fails that one reply; the server keeps running.200.no posts yet if nothing has been posted.1 <title> 2 <title> ....
Send back one of those numbers (as its own "google" message) and the server fetches that page and
replies with up to 800 characters of its plain text. Numbers are only remembered from your most
recent search. Like "chatgpt", this needs a working internet connection, runs in the background so
nothing else is held up while it fetches, and is capped at 100 calls a day (a
search and a fetch each count as one, tracked separately from the chatgpt cap) -- past that, a
fixed "limit reached" message goes out instead. The search and the page-to-text conversion are both
done here directly (no third-party API, no extra Python package), scraping DuckDuckGo's own results
page, so it can break if that page's layout changes; a failed search or fetch just fails that one
reply and is logged, same as a missing document.txt.Most radios cut the transmitter after a set time (the time-out timer), and a VOX radio takes about a second to key and clips the start of the audio. CSVF2 works around both. Set Radio time-out (on the Transmit and Server tabs, and in the server programs) to your radio's time-out timer, 30 to 600 seconds. It starts at 180. For a radio with no time-out at all, set it to 0 (off) (new in v3.1): the transmission is then sent in bursts of 8 minutes, so a long one still pauses for your callsign. Receivers before v3.1 cannot read such a transmission.
The pauses and keying tones add about 10% to the time on air.
Built around the amateur rule that you identify at the end of a communication and at least every 10 minutes during one.
idstart / idend (idalone
for the Send callsign button) and whose text is the callsign, so any receiver since v1.4 decodes it. In a
transmission so long that 10 minutes would pass between callsigns it is also spoken (or sent in Morse)
at the pause between bursts. Location (required to transmit): once you enter a location on the Settings
tab, the same frame also carries your neighbourhood (an 8-character grid square, also called a
Maidenhead locator, about 0.5 x 0.7 km; coarser if you choose) next to the callsign, so other programs can draw you on their
Map tab: a red dot for you, a blue dot and a line for each station heard with a location, click a
blue dot for its callsign. It goes out over the radio, so anyone who receives your signal can read it, it
is not sent to this website, and it cannot be switched off (you choose how coarse it is). The exact format is on the
protocol specification page. Choose
Legacy on the Settings tab (Station ID) for the behaviour below, which is what v2.6 and
earlier did.audio/./ are sent; anything else is
skipped and listed. The Send callsign button and the automatic ones use it too.You are responsible for operating within your licence and local rules, including for anything radio_server_25.py's "chatgpt" or "google" paths relay from a third-party service or web page.
!@#$%^&*() and ,./;'[]\-=) are skipped, and the app lists them.update_backup folder, and installs. Your settings, your document
texts and your audio clips are never overwritten. You restart the program afterwards. The checksum comes
from this site, so it guards against a damaged download, not against someone who has taken the site over:
there is no code signing.radio_server_*.py) do not ask for a login. See the
privacy policy (version 6).127.0.0.1 (default 7373, this computer
only; nothing else can connect). Everything the radio connection receives is sent to every program
connected to it, and everything those programs write goes out over the radio on the open connection
(it needs a location like any transmission; a program that writes while there is no open connection
just waits). Try telnet 127.0.0.1 7373. Off by default, and while it is on, any program on
your computer can transmit through it. A second box serves a browser client
(csvf2_client.html) at http://127.0.0.1:7374/: type a station's callsign and a
path and it asks that station's web server for the page over the radio and draws it as it arrives
(no scripts, nothing from the internet).radio_server2.py is a base to copy for
your own station; radio_server2_echo.py sends back whatever it hears (port 7);
radio_server2_http.py is a small web server on port 80 (no encryption, no scripts, no
music: pages like /, /about, /time, /hello, /echo, /api/status, /guestbook and the files in its
www folder; audio and video are refused). Run them with
python radio_server2_echo.py / python radio_server2_http.py.Pillow package, pip install pillow);
WAV audio becomes mono, a lower sample rate and 8-bit. Other kinds of file are left as they are. The
Transmit tab shows what will be sent and how much shorter it is. A lossy message arrives simplified; there
is no way to get the original back. It is a convention inside an ordinary file frame (the file name gets
.csvz added), documented on the
protocol page; programs older than v3.0 save it as a
.csvz file. Short messages are usually sent as they are, because the packed form carries about 17
bytes of header.Site - 2026-10-18 - New biggest file: forest.png, 143 KB
(650 x 544 pixels), sent over the radio at Extra extra extra fast (new) with Medium error correction in about
40 minutes, checksum OK, and no corrections needed. The earlier records (65.mp4, skull_and_roses.png) are kept
on the page. This is the first over-the-air result for Extra extra extra fast (new).
v3.6.3 - 2026-10-17 08:00 CDT - Servers: the server windows
(radio_server_template.py, radio_server_25.py and the Server 2.0 servers) now use the
callsign of the logged-in account, read-only, as the main window does (it was editable). The Server 2.0
servers also stop repeating the connecting station's callsign on every log line: it appears once per
exchange on the TCP segment line (with where it said it was); the plain template keeps its
"is sending a message" lines.
v3.6.2 - 2026-10-17 06:30 CDT - Server 2.0 fix, from the first real radio log: a server whose SYN+ACK never reached the client kept that half-open connection and then refused the client's second attempt, spending 21 s of airtime on a reset. A station has one connection at a time, so a new connection from the same station now replaces its old one, silently. Also, a server's first SYN+ACK was labelled a repeat and used up one of its tries; it no longer is. Both ends need no change to interoperate (the client side was already right).
v3.6.1 - 2026-10-17 04:30 CDT - Servers as exes: the exe download
now has CSVF2-Server.exe, CSVF2-Server25.exe, CSVF2-Echo.exe and
CSVF2-Web.exe beside CSVF2.exe, so a server can run without Python (the Python
version is unchanged). The exe's Update button installs them.
v3.6.0 - 2026-10-17 01:00 CDT - Other programs can use Server 2.0:
a new box on the Server 2.0 tab opens a TCP port on 127.0.0.1 (this computer only, off by default). What the
radio connection receives is sent to every connected program, and what they write is transmitted on the open
connection, so any program that can open a socket (or telnet) can use CSVF2.
Browser client: a second box serves csvf2_client.html on 127.0.0.1 that
connects to a station, requests its pages over the radio and draws them as they arrive.
New servers: radio_server2_echo.py (sends back what it hears) and
radio_server2_http.py (a small HTTP/1.0 web server with endpoints, a guestbook and a
www folder; no encryption, no music), both built on radio_server2.py.
Fix: several places did nothing without a location since v3.5.0 (Server 2.0 Connect, Accept
connections, Send, Auto callsign, closing the window); they now say what is missing and take you to the
Settings tab.
v3.5.0 - 2026-10-16 23:00 CDT - Location required to transmit: the program will not transmit (Transmit, Send callsign, the Server tabs) until you have entered a location (Settings tab), and it always goes out next to your callsign, so the window uses only the New callsign mechanics. You still choose how coarse it is. Receiving needs no location. This changes the privacy policy (now version 6), so the program asks you to accept it again once. Small screens: every tab scrolls (mouse wheel, Shift+wheel sideways, scroll bars that appear only when needed) and the window can be made much smaller. Lag while receiving: the receiver did about 40 times more arithmetic than needed and did it in the window's own thread, so a slow computer froze while listening. It now computes the same numbers with a sliding sum and decodes on a separate thread (the window only shows results). The exe update installs this.
v3.4.5 - 2026-10-16 21:15 CDT - Fix (exe): the exe build was missing part of the sound library, so it said to install sounddevice and could not listen or pick a sound device. It is now included. If the sound library still cannot load, the message shows the reason. Exe v3.4.4 can install this with its update button.
v3.4.4 - 2026-10-16 20:30 CDT - The exe updates itself: the Update button in the exe build downloads csvf2-exe.zip, checks its SHA-256, closes the program, copies the new files over the old ones (your document texts, settings and existing audio clips are kept; the old program files go to update_backup\previous) and starts it again. To get this, install v3.4.4 by hand once; v3.4.3 and older exes cannot do it. In the Python version the update box now says so when the files on disk were already current.
v3.4.3 - 2026-10-16 19:10 CDT - exe fixes: the icon is now built into CSVF2.exe (shortcuts and the pinned taskbar button no longer show a blank icon), and the exe finds its own files and libraries when started from a shortcut or another folder. Small code clean-ups.
v3.4.2 - 2026-10-15 19:36 CDT - Credit corrected: the author credit in the program, LICENSE.txt and on this site now reads Reed Gabriel (KE9GDP). The C source is no longer offered as a download (the exe is built from it; the Python is the reference).
v3.4.1 - 2026-10-15 17:30 CDT - Licence: CSVF2 is now under Creative Commons BY-NC-SA 4.0 (LICENSE.txt in every download). The credit to the original author, Reed Gabriel (KE9GDP), is shown at the bottom of every tab and in the window title, and must be kept. Time stamp: the window title and the Settings tab show the version with its build time. Fix: v3.4 still reported itself as 3.3.2, so it offered to update to itself; it now reports its real version.
v3.4 - 2026-10-15 - Three downloads: the same program as Python (recommended; updates itself), as C source and as a ready-built exe that needs no Python. The C is generated from the Python with Cython and the exe is that C compiled with MSVC; the Python is always the reference. The C and exe builds do not update themselves: download the new version here. The exe has been started and its modem round-trips a message in a check, but it has not been used over the air. Callsign: the Callsign box is now locked to the one on your account and cannot be edited.
v3.3.2 - 2026-10-12 - Fixes: tutorial boxes no longer run off the window or the screen (each box is fitted to the window, and its position was not being applied); the program's window is now fitted inside the screen when it opens, which at a high display scale used to put its bottom edge off the screen. Wording: the Maidenhead locator is now called a grid square in the program and on this site (the official name is still mentioned once).
v3.3.1 - 2026-10-12 - Fixes: the tutorial's grey layer now follows the window when you move it, and the window's Maximize and full-screen buttons work during the tutorial (it no longer holds the mouse; a nearly invisible layer blocks clicks on the highlighted controls instead). The licence upload no longer refuses files by type: it accepts any file up to 6 MB and the site owner reviews it by hand.
v3.3 - 2026-10-12 - Accounts. CSVF2 now needs a free ke9gdp.com account: sign up with an email, a password and your callsign, confirm the email, and send your licence exactly as issued; it is reviewed by hand, deleted once decided, and the account is then approved. The users list, server map and feedback all use the approved account's callsign, so a callsign can only act through the account approved for it. An approved account keeps working without the internet for 14 days. Feedback tab: a private chat with the author about feedback you sent, shown only to your account and fetched only when you open the tab or press Check for replies. Privacy policy version 5 (you are asked to accept it again). The website also rate-limits requests. Older programs can still use the radio, but not register a callsign or send feedback.
v3.2 - 2026-10-12 - Server 2.0: a new tab next to Server for TCP-style connections between two stations (handshake, numbered bytes, acknowledgements, repeats, closing). Warning: it has only been tested in simulation, not over the air. The server map now shows Server 1.0 and Server 2.0 stations in different colours.
v3.1 - 2026-10-12 - Radio time-out off: set Radio time-out to 0 to turn it off (instead of typing a huge number); long transmissions are then sent in 8 minute bursts so the callsign still goes out at least every 10 minutes. Both ends need v3.1 for such a transmission. Speed list is now ordered fastest to slowest (Fast is still the default).
v3.0.1 - 2026-10-11 - Fix: the taskbar icon. Started by
double-clicking CSVF2.pyw (above all with the Microsoft Store Python), Windows showed Python's icon
on the taskbar instead of the CSVF2 icon; the program now tells Windows its own identity and icon for the window.
Nothing else changed.
v3.0 - 2026-10-11 - Compression for text and files: a list on the
Settings tab with None, Lossless and three Lossy levels (simplified text, scaled images, lower-rate WAV audio),
used only when it makes the transmission shorter; the receiver unpacks it by itself. New speed
Slowest (1.6 s tones, 1/8 of Normal; in simulation still reads a short text at -18 dB signal to
noise). A guided tour (offered on first start, replayed with the Tutorial button) and the
site favicon as the window icon. Fix: Send callsign (and the callsign sent when the window closes) now goes out at the
speed chosen on the Settings tab instead of always at Fast. Slow modes use less processor time. Older programs do
not hear Slowest and save compressed files as .csvz.
v2.9 - 2026-10-11 - New speeds Slow (400 ms tones, half of Normal) and Super slow (800 ms tones, a quarter of Normal) for rough conditions. In simulation they decode a short text at -12 dB and -15 dB signal to noise, where Normal stops between 0 and +3 dB; files go at about 1.6 and 0.8 bytes per second. They use tones at 1200 and 1400 Hz, so older programs do not hear them at all. Not yet tried over the air.
v2.8 - 2026-10-11 - Direct messages: text or a file can be addressed
to a callsign (Transmit tab, Direct message to). Programs with a different callsign hide it and say
"<callsign> sent a dm to somebody else"; ordinary messages are announced as "is sending a public
message". Not encrypted, and documented on the
protocol page (address = dm plus six
characters made from the callsign, in the PATH). Map: a station heard from a new location keeps
its earlier positions as pale dots, and tiles that failed to load are retried.
v2.7 - 2026-10-11 - New callsign mechanics (default): the callsign is sent as a data frame at the start and the end of every transmission and receivers show "<callsign> is sending you a message" (Legacy mechanics stay available on the Settings tab). Feedback button in every tab (sent only when you press Send), a Notifications tab, dark mode (default) and a Sleek (default) or Legacy look on the Settings tab. The protocol specification page documents the whole on-air format. Location and Map: your neighbourhood (an 8-character grid square) is sent next to your callsign in those frames, switchable and on by default once you enter one; the new Map tab shows you as a red dot and the stations you hear as blue dots joined to you by lines (click one for its callsign). The privacy policy is version 3 (feedback, notifications, location and the map tiles), so the program asks you to accept it again once. The server scripts take the same callsign and location options.
v2.6 - 2026-10-11 - New speed Extra extra extra fast (new): Extra
extra fast's 10 ms symbols with the tones 50 Hz apart and 64 file pairs, 6 bits a symbol, about 74 bytes a
second (so far tested in simulation only). Old modes: Amplitude extra fast and the first
Extra extra extra fast (6.67 ms tones; poor over the air, see Speeds) are only listed when Show old
modes is ticked on the Settings tab, after a warning. Start-up: double-click
CSVF2.pyw to run the window with no console; the CSVF2 logo shows for 3 seconds; the window
hides its own console if it has one. Receiver: a file or corrected transmission is decided
again once it is all in, with each pair's score compared to that pair's own average on the link; a
transmission that stops and starts again at once is picked up from its new start instead of being read as
the old one's continuation; the first tones of a frame are chosen from the few valid ones; the receiver
ranks competing speeds by how well their tones scored. The Receive tab can show how close each speed came
to starting a frame and can save everything it hears to heard.wav for debugging. The updater
keeps a replaced csvf2.png logo.
v2.5 - 2026-10-10 - Privacy policy and users list: the program
asks you to accept a privacy policy once, then sends your callsign once to a public
CSVF2 users page (10 most recent, and the whole
database to download). Updater: the window looks for a newer version when it starts, asks
before installing, verifies the download, keeps a backup and never overwrites your settings, document texts or
clips. New radio_online.py (the only code that talks to this site). Older versions cannot update
themselves; download this one by hand.
v2.4.1 - 2026-10-10 - Receiver fixes found over the air at Extra extra fast. With the Sensitivity turned up, the receiver for another speed could start on the data of a file that was arriving and show random text, save-prompts for random files, or "heard what sounded like the start of a transmission" in the middle of it. The speed that is really receiving now takes priority, and anything whose tones hardly scored (speech, noise) is left unsaid. The status line said "Receiving file None" for a file sent with error correction, because its name is only known once everything is decoded; it now says so. A damaged tone that read as the end marker could stop a file early; a file now ends at its known length. The Biggest file record is now 65.mp4, about 65 KB, at Extra extra fast with Low error correction, checksum OK.
v2.4 - 2026-10-10 - New speed Extra extra fast: Extra fast's tone
pairs at 10 ms, 100 Hz apart (600 to 2000 Hz), 2.5x faster for files (62.5 bytes a second), plain frequency
coding with no loudness. Simulated only, not yet tried over the air. New Settings tab holds
the radio and sound and the speed and reliability settings. website.wav now plays with the first
transmission and then every 10th, not with every one. The Morse callsign tone moved from 1500 Hz to
2400 Hz so it stays above every data tone. New Error correction (Reed-Solomon, the code
QR codes use): Low, Medium, High and Maximum levels repair damaged or unheard tones in text and files at
every speed, instead of the transfer having to be sent again. Simulated only so far, not yet tried over the
air. Receivers older than v2.4 report corrected transmissions as lost; with the box on Off nothing changes.
New Auto callsign checkbox: your callsign goes out by itself when it is due or after a
communication, once nobody (you included) is sending. The window was reorganised into grouped boxes and
restyled, both server scripts have the Error correction setting and the same look, and radio_fec.py
joins the download.
v2.3 - 2026-10-10 - New speed Amplitude extra fast: Extra fast
with the balance of each pair's two tones carrying 2 more bits, about 1.4x faster for files (text is
unchanged). Experimental for files: in over-the-air tests text worked but files often
failed, so use Extra fast for files that matter. A second shift layer (SHIFT SHIFT then a digit) sends
, . / ; ' [ ] \ - =. File size limits are gone, replaced by warnings. Older versions cannot
receive Amplitude extra fast, and show the new punctuation as ?.
v2.2 - 2026-10-04 - Your callsign is no longer sent with every transmission: a Send callsign button with a timer since your last callsign, flashing red from 9:30. A transmission still gets the callsign by itself whenever it would otherwise go past 9 minutes without one, and closing the program sends it once the channel has been quiet for 2 seconds. website.wav plays 1.5x faster at the same pitch. A short message is now about 15 seconds on the air instead of 27. The server scripts still identify every reply. Extra fast is now confirmed over the air on analog FM. The tones themselves are unchanged, so older versions still read it.
v2.1 - 2026-10-03 - The callsign can be sent in Morse code instead of spoken: a Send in Morse code checkbox on the Transmit and Server tabs and in both server scripts (20 words per minute on 1500 Hz, outside the data tones, so receivers of any version ignore it). The Sensitivity slider now goes all the way to 1% (it stopped at 30%); the middle is still the usual 55%. Nothing about the data tones changed, so v2.0 and v2.1 read each other.
v2.0 - 2026-09-26 - New Radio time-out setting (30 to 600 s, default 180): the burst length now follows it, so radios with a short time-out can send long files. Files and messages with long runs of one byte are scrambled so they no longer slip. A file over the receiver's size limit is reported with its name and size instead of "signal lost". At the default time-out, ordinary transmissions are unchanged and older versions still read them.
v1.9 - 2026-09-26 - New Extra fast speed: 40 characters a second (25 bytes a second for files), 8x Normal, in the same 555 - 1140 Hz range as Fast, by sending two tones at once. Files up to 100,000 bytes at Extra fast (still 10,000 at Fast and Normal). Receivers listen for all three speeds automatically. Older versions cannot receive Extra fast. (Confirmed over the air on analog FM since; see v2.2.)
v1.8 - 2026-09-22 - radio_server_25.py gains a fourth path,
google: a DuckDuckGo search that lists up to 5 numbered results, then a second message
with one of those numbers fetches that page and sends back up to 800 characters of its plain text.
Capped at 100 calls a day, like "chatgpt" but counted separately.
v1.7 - 2026-09-21 - radio_server_25.py now has three
more paths: chatgpt (relays to OpenAI, capped at 100 calls a day, needs your own
OPENAI_API_KEY), smpost and smget (a small 5-post log).
It is public from this version on -- previously it was mentioned nowhere but the code itself.
v1.6 - 2026-09-21 - The "document" path now sends
document.txt's text back as an ordinary message instead of a file; characters it
cannot carry (anything but letters, digits, space and !@#$%^&*()) are dropped and
logged, not fatal.
v1.5 - 2026-09-21 - radio_server.py renamed to
radio_server_template.py and set up as a template you copy and edit to build your own
server. First built-in route: sending to path document gets document.txt
sent back as a file, whatever the message says.
v1.4 - 2026-09-21 - New Server tab in the window: an 8-character path is sent
right before the message, the tab starts listening the moment the send is finished, and it sends an end
tone (450 Hz, 2 seconds) after the last callsign. radio_server.py now waits for that end tone
before it replies (1 second later) instead of always waiting 30 seconds; the 30 seconds stays as the
fallback for messages that have no end tone. Ordinary Transmit-tab messages are unchanged and older
versions still read them, but an older version cannot read messages that carry a path and reports them as
lost.
v1.3 - 2026-09-21 - New radio_server.py: an automatic station
that answers ping with pong after 30 seconds, with the same settings as the
window. New Sensitivity slider on the Receive tab. Fixed the receiver locking onto the start-of-message
pattern too early when the sensitivity is set high.
v1.2 - 2026-09-20 - The VOX keying tone can be switched off with a checkbox on the Transmit tab, for radios that are not VOX keyed.
v1.1 - 2026-09-20 - For radios with a time-out timer and VOX. Long transmissions are sent in bursts of 2 minutes with a silent gap between them, each burst starts with a 400 Hz VOX keying tone, and the Transmit tab shows the longest keyed stretch. The callsign is now spoken often enough to stay within 10 minutes at any settings, and the recorded clips are trimmed so a VOX radio does not un-key between words. Older versions cannot receive a transmission longer than 2 minutes from this version.
v1.0 - 2026-09-20 - First release. Text and file sending, shift tone, digits and symbols, Fast and Normal speeds, automatic receiving with a save prompt for files, station ID at the start, at the end and every 9 minutes, and website.wav played after the tones and before the last callsign.
Everything that goes on the air, written out so anyone can build their own client: tones, speeds, frames, checksums, error correction, bursts, station ID, receiving, and test vectors.