Convert WAV recordings of Commodore 64 cassette tapes into emulator-readable TAP files.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wav2tapsp-0.2.0.tar.gz.
File metadata
- Download URL: wav2tapsp-0.2.0.tar.gz
- Upload date:
- Size: 22.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39d727305e38ba0dae384dcb8124cf9b5a84c2ae0f3635738dc0aaa2aecbd681
|
|
| MD5 |
d136d0041f4abf50dda932d43260d3eb
|
|
| BLAKE2b-256 |
f150033fdfe021c2dd58029fa68c8d018ca5d20f5d346c7024a963c9231308e0
|
Provenance
The following attestation bundles were made for wav2tapsp-0.2.0.tar.gz:
Publisher:
publish.yml on anarkiwi/wav2tapsp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wav2tapsp-0.2.0.tar.gz -
Subject digest:
39d727305e38ba0dae384dcb8124cf9b5a84c2ae0f3635738dc0aaa2aecbd681 - Sigstore transparency entry: 1807706201
- Sigstore integration time:
-
Permalink:
anarkiwi/wav2tapsp@c39834168c76229a00ee37e19e344c5de0c993d1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/anarkiwi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c39834168c76229a00ee37e19e344c5de0c993d1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file wav2tapsp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: wav2tapsp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2068ed847b1f48f478258ee0fb6be560ccf55a6fe974292fe021d05e2bdc2b9
|
|
| MD5 |
e1cfa5d5e2d02c96024c9d53009b934f
|
|
| BLAKE2b-256 |
42da6b1db347725da3c131ba44e115881280866274fdf679ef887270601f98d9
|
Provenance
The following attestation bundles were made for wav2tapsp-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on anarkiwi/wav2tapsp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wav2tapsp-0.2.0-py3-none-any.whl -
Subject digest:
d2068ed847b1f48f478258ee0fb6be560ccf55a6fe974292fe021d05e2bdc2b9 - Sigstore transparency entry: 1807706236
- Sigstore integration time:
-
Permalink:
anarkiwi/wav2tapsp@c39834168c76229a00ee37e19e344c5de0c993d1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/anarkiwi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c39834168c76229a00ee37e19e344c5de0c993d1 -
Trigger Event:
release
-
Statement type: