Modulate and demodulate files into audio, to record into and out of a cassette tape. They can last up to 100 years, you know!
https://marvil.co/blog/portfolio/2026-01-04-cassette/
| in | ||
| src | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
| requirements.txt | ||
Cassette Drive
Encode files into audio and decode them back — like a tape drive, but over any audio channel (speakers, aux cables, cassette tapes, radio, etc.).
Uses MFSK (Multiple Frequency-Shift Keying) modulation with Reed-Solomon error correction.
Install
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Optional: install ffmpeg to decode non-WAV audio formats.
Usage
Encode a file to audio
python -m src encode --input in/nala.webp --output modulated/myfile.wav
Decode audio back to a file
python -m src decode --input modulated/myfile.wav
Diagnostics (optional):
python -m src decode \
--input modulated/myfile.wav \
--diagnostics \
--report modulated/decode_report.json
Calibration
Generate a calibration WAV with segments at different symbol rates, play it through your audio chain, record it, then analyze to find the best symbol rate:
# Generate calibration audio
python -m src calibrate-encode
# Analyze recorded calibration audio
python -m src calibrate-decode --input modulated/calibration_recorded.wav
Optional reports:
# Save encode metadata for calibration
python -m src calibrate-encode --report modulated/calibration_encode.json
# Save a decode report and emit diagnostics to stdout
python -m src calibrate-decode \
--input modulated/calibration_recorded.wav \
--report modulated/calibration_report.json \
--diagnostics
Most forgiving settings
Slow, redundant, and resilient — suitable for noisy channels:
# Encode
python -m src encode \
--input in/myfile.bin \
--output modulated/myfile.wav \
--symbol-rate 6 \
--num-tones 4 \
--ecc-percent 50 \
--lead-silence 2.0 \
--tail-silence 2.0
# Decode (match the same tone/frequency settings)
python -m src decode \
--input modulated/myfile_recorded.wav \
--symbol-rate 6 \
--num-tones 4
Options
| Flag | Default | Description |
|---|---|---|
--sample-rate |
48000 | Audio sample rate in Hz |
--symbol-rate |
6 | Symbols per second |
--freq-base |
600 | Lowest tone frequency in Hz |
--freq-step |
120 | Hz between adjacent tones |
--num-tones |
4 | Number of tones |
--ecc-percent |
50.0 | Percentage of each block used for error correction |
--lead-silence |
2.0 | Seconds of silence before audio |
--tail-silence |
2.0 | Seconds of silence after audio |
--max-seconds |
0 (unlimited) | Maximum allowed audio duration |
--diagnostics |
false | Emit JSON diagnostics to stdout (decode only) |
--report |
(empty) | Write JSON report to path (decode only) |
Notes
It is recommended you record the audio to your cassette player at 20% volume. This was my experience, at least.