wav2tapsp
Converts WAV files (recordings of Commodore 64 cassette tapes) into C64 .tap
files an emulator can read.
Hardly unique, but also somewhat easier to experiment with than having to hack ancient C code...
Install
$ pip install .
or, once released, pip install wav2tapsp.
Command line
$ wav2tapsp recording.wav # writes recording.tap
$ wav2tapsp --cpufreq 1022727 recording.wav # NTSC
(python -m wav2tapsp recording.wav works too.)
Library
import scipy.io.wavfile
import wav2tapsp
sr, samples = scipy.io.wavfile.read('recording.wav')
tap_bytes = wav2tapsp.wav_to_tap(samples, sr) # bytes of a .tap file
pulses = wav2tapsp.wav_to_pulses(samples, sr) # raw TAP pulse lengths
It works by finding rising zero crossings in the audio and emitting the time between them as TAP pulse lengths (CPU cycles / 8).
C64 tape formats
The package can synthesise and decode real C64 tape formats end to end (used by the tests, and handy for generating test tapes):
-
Standard ROM / Kernal loader — three pulse lengths, two pulses per bit, odd parity, per-block checksum, two block copies.
c64tape.py. -
Fast / turbo loaders — two pulse lengths, one pulse per bit (a short pulse for 0, a long pulse for 1).
formats.pymodels the pulse-timing scheme of several real loaders using their documented pulse widths:Loader short / long pulse (TAP, cycles) reference Firebird T1 $44/$7E(544 / 1008)FinalTAP ft[]Novaload $24/$56(288 / 688)FinalTAP docs Freeload $24/$42(288 / 528)FinalTAP docs Turbo Tape 250 $1A/$28(208 / 320)FinalTAP docs / c64-wiki Turrican $1B/$27(216 / 312)FinalTAP ft[]
References: the TAP format
(pulse byte = CPU cycles / 8), PAL/NTSC CPU clocks
(985248 / 1022727 Hz), the ROM loader timing
($30/$42/$56), and the FinalTAP loader database
(per-loader pulse widths, the de-facto reference).
Testing
Tests run in CI across Python 3.10–3.13. To run them yourself:
$ pip install -e .[test]
$ pytest -v
The headline test is a full, emulator-free round trip that proves wav2tapsp
preserves tape timing well enough to recover the original program:
PRG -> TAP -> WAV --(wav2tapsp)--> TAP -> PRG
A real C64 BASIC program is generated, encoded as a .tap, rendered to a .wav,
run back through wav2tapsp, and decoded; the recovered PRG must be
byte-identical to the original and still a structurally valid, runnable BASIC
program. The same round trip runs for every fast-loader format above.
Minimum sample rate and resolution, from first principles
wav2tapsp recovers a pulse by counting samples between rising zero crossings:
recovered_cycles = Δsamples × cpufreq / SR. Two bounds follow (derived in
analysis.py, checked per format in the tests):
-
Sample rate. A pulse is one wave cycle of frequency
cpufreq / pulse, so representing the shortest pulse needsSR ≥ 2·cpufreq/short(Nyquist). To keep a pulse on the correct side of the short/long threshold despite ±2 samples of crossing jitter needsSR > 4·cpufreq/gap, wheregapis the cycle distance between the two pulse widths. The reliable minimum is2 × max(...). Becausegapdominates, the requirement scales like 1/gap — faster loaders need proportionally higher sample rates (PAL, safety ×2):Loader gap (cycles) reliable min sample rate Firebird T1 464 ~17.0 kHz Novaload 400 ~19.7 kHz Freeload 240 ~32.8 kHz C64 ROM 144 ~54.7 kHz Turbo Tape 250 112 ~70.4 kHz Turrican 96 ~82.1 kHz The tests confirm each loader fails below Nyquist and decodes at its reliable rate, and that the measured minimum is bracketed between the two.
-
Resolution (bit depth). Zero-crossing detection depends only on the sign of the signal, so one bit (the sign) is enough for a clean recording; amplitude resolution barely matters. The tests verify the round trip survives 1-bit quantisation of a sine wave for every loader. Sample rate, not bit depth, is the real constraint.
Layout
src/wav2tapsp/convert.py— the WAV → TAP converter (the tool).src/wav2tapsp/c64tape.py— standard ROM tape codec, PRG/BASIC helpers, WAV synthesis.src/wav2tapsp/formats.py— fast/turbo loader registry and codec.src/wav2tapsp/analysis.py— first-principles sample-rate/resolution bounds.
Metadata
Release files for wav2tapsp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wav2tapsp-0.2.0.tar.gz | 22.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wav2tapsp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.7 kB
Release files / wav2tapsp-0.2.0.tar.gz
| Download URL | wav2tapsp-0.2.0.tar.gz |
|---|---|
| Size | 22.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
39d727305e38ba0dae384dcb8124cf9b5a84c2ae0f3635738dc0aaa2aecbd681
|
|
BLAKE2b-256 checksum How to use checksums |
f150033fdfe021c2dd58029fa68c8d018ca5d20f5d346c7024a963c9231308e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 13, 2026.
Transparency logRelease files / wav2tapsp-0.2.0-py3-none-any.whl
| Download URL | wav2tapsp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 18.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d2068ed847b1f48f478258ee0fb6be560ccf55a6fe974292fe021d05e2bdc2b9
|
|
BLAKE2b-256 checksum How to use checksums |
42da6b1db347725da3c131ba44e115881280866274fdf679ef887270601f98d9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 13, 2026.
Transparency log