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/
Find a file
2026-03-01 14:06:58 -06:00
in Casette Drive 2026-02-12 11:29:55 -06:00
src fix: Enable header recovery during decode and improve sync seeking robustness 2026-02-14 14:34:50 -06:00
tests feat: Update framing and modem modules for improved robustness 2026-02-14 08:16:10 -06:00
.gitignore feat: Update framing and modem modules for improved robustness 2026-02-14 08:16:10 -06:00
LICENSE feat: add AGPLv3 license 2026-03-01 14:06:58 -06:00
README.md feat: Update framing and modem modules for improved robustness 2026-02-14 08:16:10 -06:00
requirements.txt feat: Update framing and modem modules for improved robustness 2026-02-14 08:16:10 -06:00

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.