Skip to main content

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 (with orderlist_ptr / subpattern_ptr resolution).
  • 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 defaultsimage[load+0xAA] / image[load+0xB5].
  • Gate-on / gate-off (rest) CTRLimage[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 by subtune * 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyjch-0.1.0.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyjch-0.1.0-py3-none-any.whl (23.5 kB view details)

Uploaded Python 3

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

Hashes for pyjch-0.1.0.tar.gz
Algorithm Hash digest
SHA256 04156784e120c99ad3587c82c6fe6aa2ab5e84dc342108bebba0c89e872f8960
MD5 5860932b456cda0eac319fcd37d30392
BLAKE2b-256 1840a96371a6bf70fc2745188bfbe175997f8d69a30d9a1efe297eef284655ff

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyjch-0.1.0.tar.gz:

Publisher: release.yml on anarkiwi/pyjch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pyjch-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 783cb4294cd520c4a2c291ef38f5b618481e660ae3961acf718d7e62564f2a7b
MD5 804bf07575414074289453b7593c5213
BLAKE2b-256 5c1639408e9272a528a79dd2d7d95fc03e94ccf0be5c4d5686ca80ed1e84a2ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyjch-0.1.0-py3-none-any.whl:

Publisher: release.yml on anarkiwi/pyjch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page