Cassette Drive: A Tape-Deck Time Machine
A couple of months ago, I decided to upgrade my home network: To further experiment with networking, I replaced my tired Telmex modem/router's routing function with an ER605v2 running OpenWRT.
The plan was simple. The reality was not. Intermittent WAN drops, weird RF noise, and a long night staring at link lights.
Somewhere between factory resets and forum threads, I pulled up a YouTube clip of old dial-up sounds, and it sent me down a rabbit hole. Modulation, demodulation, error correction, sync tones; the whole analog-to-digital handshake story. A few hours later, I was sketching out a new idea: what if I could build a tiny tape drive in software, and store files as audio on a cassette?
Now we're here. Cassette Drive is a small Python tool that turns bytes into audio and back again. It uses MFSK (Multiple Frequency-Shift Keying) to map bits onto tones, adds a preamble + sync word to locate the payload, and wraps the data in Reed-Solomon error correction. The result is a modern, resilient data stream that can survive the chaos of a cheap tape deck. I bought a $20 cassette player/recorder off Amazon and a five-pack of 45-minute-per-side tapes, and suddenly I had a brand-new storage medium! You know what they say about the 3-2-1 backup rule... It's surely much better as the 3-3-1 rule, right? Right???
Summary
The encoder takes a file, builds a compact header with audio settings and metadata, splits the data into fixed-size blocks, and adds Reed-Solomon ECC. It then frames everything with a preamble (alternating 0/1 bits) and a sync word so the decoder can lock on. The modem layer uses MFSK: each symbol is a tone, and each tone encodes multiple bits depending on how many distinct frequencies you choose. On the way back, the decoder bandpass-filters the recording, finds the sync pattern, demodulates symbols with a Goertzel detector, and reconstructs the original file, verifying it with CRC-32.
My first experiment: Nala, on tape
The first thing I wanted to store was a photo of my cat, Nala. I resized an image with ImageMagick to be under 5 KB, and encoded it to a WAV file. Then I played that audio through the cassette recorder, rewound the tape, and recorded it back into my laptop to decode.
I expected errors, but I got a perfect CRC on the first try! To be fair, it took the entire 45 minutes of tape to store just that 5 KB image (for context, that could easily fit on a single sheet of letter paper at 8pt if I stored it as a base64 string), but it worked!
What's happening under the hood?
Cassette Drive has three layers:
- Framing and metadata
- A compact binary header stores sample rate, symbol rate, tone spacing, file size, and filename.
- The header includes a CRC-32 so the decoder can confirm the whole file is intact.
- Error correction
- Each block is protected with Reed-Solomon ECC, the same idea used in QR codes and CDs.
- You can tune ECC percent for more resilience at the cost of speed.
- Modem layer
- MFSK turns bit groups into tones (more tones -> more bits per symbol).
- A preamble of alternating bits gives the decoder a robust alignment target.
- A sync word marks the payload boundary so the stream can be byte-aligned.
All of it is designed to tolerate real-world noise: motor jitter, tape hiss, or messy cable routing across the desk.
Calibrating for real-world recorders and players
The calibration mode produces a WAV file with multiple symbol rates.
You run it through your audio chain and then analyze the recording to find the fastest rate that still decodes cleanly. This is where cassette decks show their capabilities. Some handle higher symbol rates just fine, others need a slower, more forgiving setting. As for my cheap recorder, it couldn't handle much.
How to try it
If you want to replicate the experiment, start small. Encode a file under 5 KB with forgiving settings (lower symbol rate and higher ECC). Once it works, you can try cranking up the speed.
Give it a shot by decoding this message!
# Clone the repo
git clone https://code.marvil.co/dumb-stuff/cassette-drive.git
cd cassette-drive
# Set up a virtual environment and install dependencies
python -m venv .venv
pip install -r requirements.txt
# Decode the example message (after you download the hello.wav file and place it in the modulated/ folder!)
python -m src decode --input modulated/hello.wav --output demodulated/hello.txtOr, encode your own:
# Encode
python -m src encode --input in/myfile.bin --output modulated/myfile.wav
# Decode after recording (match the same tone/frequency settings)
python -m src decode --input modulated/myfile_recorded.wav --output demodulated/myfile_decoded.binFinal thoughts
The Telmex-to-OpenWRT upgrade was a mess, but it nudged me into this small but cool project. Maybe this was a bad idea for storage, but it was a great idea for learning :)
If you try it, I want to see your tapes. Let me know in the guestbook!