Pure-Python reader and player for JCH NewPlayer C64 SID songs
Project description
pyjch
A standalone, pure-Python reader and player for JCH NewPlayer C64 SID songs (the JCH / Jens-Christian Huus tracker player used across the demoscene). It parses a JCH tune into a typed song model and runs the playroutine to produce byte-exact per-frame SID register output, plus a register-log surface for downstream tooling.
Everything (read/play/register-log) is pure stdlib — no dependencies.
pip install pyjch
Quick start
import pyjch
song = pyjch.read("tune.sid") # PSID/RSID/.sid or bare .prg
# Per-frame SID register writes (changed registers only, after frame 0).
for writes in pyjch.iter_frames(song, max_frames=50 * 60):
... # writes: list[(register, value)]
# Forward-filled 25-register-per-frame snapshot grid (the oracle form).
grid = pyjch.render_grid(song, nframes=400)
# Register log (clock reg val triples).
pyjch.write_reglog(
pyjch.iter_register_writes(song, max_frames=2500), "tune.reglog"
)
CLI:
pyjch info tune.sid
pyjch reglog tune.sid tune.reglog --seconds 30
Public API
read(src) -> Song/parse(bytes) -> Song— read a.sid/.prg.Player(song)—.play_frame() -> list[(reg, val)],.regs.iter_frames(song, max_frames)— per-frame writes.render_grid(song, nframes) -> list[list[int]]— forward-filled grid.iter_register_writes/read_reglog/write_reglog/RegWrite.- Model:
Song(withorderlist_ptr/subpattern_ptrresolution). - Errors:
JCHError,SidParseError.
The JCH NewPlayer format
Three independent per-voice opcode streams. The player binary is identical across tunes; only its DATA and the addresses that data lives at differ, so the reader discovers each per-tune immediate and table base from the player code's instruction operands (relocation-safe — no fixed-address assumption):
- AD/SR defaults —
image[load+0xAA]/image[load+0xB5]. - Gate-on / gate-off (rest) CTRL —
image[load+0x1E1]/image[load+0x199]. - Subpattern pointer-table bases — the 16-bit operands at
load+0x12C(lo) /load+0x131(hi). - Orderlist pointers — three per-voice pairs at
$1010, indexed bysubtune * 8; the 7th byte seeds the tempo. - Frequency tables —
$121F(lo) /$1220(hi), 0x80 entries, indexed by note with a per-voice transpose.
Init (FUN_1060) copies the orderlist pointer pairs into per-voice
cur/base pointers, loads the tempo, and seeds the SID registers. Play
(FUN_10E8) walks each voice's opcode stream via the zero-page pointer
$fb/$fc: transpose-set opcodes ($80–$9F), subpattern references
(<$80 in the orderlist push a pointer and jump via the subpattern
tables), gate/tempo counters ($80–$8F inside a subpattern), and note
bytes that index the frequency table.
Byte-exact verdict
Validated against the preframr-sidtrace register oracle (PAL, 19656
cycles/frame, forward-filled, one leading silent play-call aligned over,
PW-hi registers nibble-masked):
| Tune | Author | Frames | Residual |
|---|---|---|---|
| Flexible | Scorpio | 368 | 0 (byte-exact) |
| Simple Tune | JCH | 368 | 0 (byte-exact) |
Tests
Test tunes are HVSC copyright works and are never committed. They are
resolved from a local HVSC tree ($JCH_LOCAL_HVSC) or fetched on demand
(python scripts/fetch_tunes.py) into a gitignored cache; the byte-exact
tests validate against the live preframr-sidtrace binary when
$SIDTRACE_BIN is set, else against the committed frozen oracle grids in
tests/fixtures/, so CI passes with neither the binary nor the tunes.
pip install -e ".[dev]"
./run_tests.sh
SIDTRACE_BIN=/path/to/sidtrace ./run_tests.sh # validate live, too
License
Apache 2.0 — see LICENSE.
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 pyjch-0.1.0.tar.gz.
File metadata
- Download URL: pyjch-0.1.0.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04156784e120c99ad3587c82c6fe6aa2ab5e84dc342108bebba0c89e872f8960
|
|
| MD5 |
5860932b456cda0eac319fcd37d30392
|
|
| BLAKE2b-256 |
1840a96371a6bf70fc2745188bfbe175997f8d69a30d9a1efe297eef284655ff
|
Provenance
The following attestation bundles were made for pyjch-0.1.0.tar.gz:
Publisher:
release.yml on anarkiwi/pyjch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyjch-0.1.0.tar.gz -
Subject digest:
04156784e120c99ad3587c82c6fe6aa2ab5e84dc342108bebba0c89e872f8960 - Sigstore transparency entry: 1964962351
- Sigstore integration time:
-
Permalink:
anarkiwi/pyjch@f2bb9058005add5e434413a4cdfa804c88f73729 -
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:
release.yml@f2bb9058005add5e434413a4cdfa804c88f73729 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyjch-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyjch-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.5 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 |
783cb4294cd520c4a2c291ef38f5b618481e660ae3961acf718d7e62564f2a7b
|
|
| MD5 |
804bf07575414074289453b7593c5213
|
|
| BLAKE2b-256 |
5c1639408e9272a528a79dd2d7d95fc03e94ccf0be5c4d5686ca80ed1e84a2ee
|
Provenance
The following attestation bundles were made for pyjch-0.1.0-py3-none-any.whl:
Publisher:
release.yml on anarkiwi/pyjch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyjch-0.1.0-py3-none-any.whl -
Subject digest:
783cb4294cd520c4a2c291ef38f5b618481e660ae3961acf718d7e62564f2a7b - Sigstore transparency entry: 1964962482
- Sigstore integration time:
-
Permalink:
anarkiwi/pyjch@f2bb9058005add5e434413a4cdfa804c88f73729 -
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:
release.yml@f2bb9058005add5e434413a4cdfa804c88f73729 -
Trigger Event:
release
-
Statement type: