White-box SID decompiler codec: register dump -> inline op-program tokens, residual-zero at < 1 token/frame.
Project description
preframr-tokens
The white-box SID decompiler codec. A C64 SID tune is decompiled into a compact op-program — not a dense per-frame register stream — and that program regenerates the original chip writes byte-exact (residual = 0). The whole tune fits a small context window: every gate tune encodes well under 4096 tokens. Extracted from the preframr research codebase.
Torch-free (numpy + pandas/pyarrow + py65 for
the 6502 emulation, pygoattracker for
the GoatTracker backend). The training-side concerns (model, loss, DataLoader,
predict) live in the main preframr repo; this package is the encoding layer.
The idea: trace = VM(program)
A SID register dump looks dense — per-frame vibrato, portamento, pulse-width modulation, arpeggio, envelope ramps — but that density is generated by a tiny fixed op-set running in the player. The frames are playback; the composer wrote steps. So the codec recovers the program, never the per-frame trace.
BACC — the bounded accumulator
The special-case effects (vibrato, slide, arp, PWM, ADSR, filter/pitch/pulse sweeps) are not distinct generators. They are one primitive, the bounded accumulator (BACC):
value += rate every dwell frames, withboundary ∈ {wrap-N, reflect, none},width ∈ {8, 12}bits, and an output map∈ {absolute, base+offset, note-table-scaled}(or a table-walk).
They differ only in their parameters. A voice's whole modulation is one straight-line PROGRAM of BACC ops. Per-instrument generators are pitch-invariant: vibrato is a depth shift, arp a set of semitone offsets, PWM a sweep rate — realized through the note table at render time, not stored per-note. (Free-running modulation falls out naturally: phase is generator state, not reset per note.)
Notes ride a canonical 12-TET A440 grid: the note token is the absolute
semitone index on a fixed A440 reference, computed from the onset frequency the
driver actually renders (pitch.fn_to_grid). So the same concert pitch is the
same token across drivers — a raw onset register write and GoatTracker's
note-table lookup both resolve to one grid index. This is what unifies the token
alphabet across engines.
The gate is residual = 0, byte-exact. A lossy codec trivially hits any token
budget, so the budgets only mean anything losslessly — verify_residual is the
invariant the codec is held to.
The virtual machine / recover → render loop
The codec is a set of per-driver backends, selected by player fingerprint
(backends/base.py: select_backend). Each backend implements three methods:
matches(psid)— does this backend handle the tune's playroutine?recover(psid, nframes, subtune)— run the playroutine white-box (py65, tapping driver RAM) and return aBaccProgram(score + pitch-invariant instrument generators + initial-state seed).render(program)— render the program back to a(nframes, 25)per-frame SID register array.
recover_program recovers the program; render_program renders it; and
verify_residual requires the rendered array to equal the ground-truth dump
byte-exact (modulo each backend's small declared don't-care mask, e.g. unused
PW-high bits). The shipped hand backend is GoatTracker (gt2reloc), kept as the
worked reference; every other tune is handled by the driver-agnostic generic
path (below). Both serialize into one shared token alphabet (the base-16 LEB digit
stream + REPEAT / TRANSPOSE markers), so a single learnable vocabulary spans
every driver.
Repeated phrases dedup via an inline backward orderlist — backward-reference
only, no forward declaration, no frozen table. Any prefix cut at an event
boundary is itself a valid, decodable, continuable song. A transposed phrase
repeat is a single TRANSPOSE op (a backward REPEAT whose copied notes are
re-coordinated by one constant grid-interval Δ), exactly what a tracker
orderlist's transpose does.
Two-file input — and the shipped .sid-only path
The hand-backend codec is a two-file codec: it takes a .sid (the player +
song data) and a per-frame register .dump.parquet of the ground-truth chip
writes. It recovers the program by emulating the .sid, then verifies the render
against the dump. That dump is produced offline by the anarkiwi/headlessvice
VICE container.
The generic recovery now closes the migration to a single input file:
from preframr_tokens.bacc.generic import recover_from_sid
# .sid ALONE -> BaccProgram(driver="generic"); NO .dump.parquet
program, resid, dump = recover_from_sid("Grid_Runner.sid")
assert sum(resid.values()) == 0 # whole-tune, all 25 registers, byte-exact
recover_from_sid runs the deterministic preframr-sidtrace tool ONCE over the
.sid, which emits BOTH the per-frame register dump (.sidwr.bin) and the bus
trace (.bus.bin) in-process. The generic fitter recovers the program from the
bus trace, and the render is verified residual-zero against the SAME-run dump —
the two are internally self-consistent (same emulator, same run). Validated
byte-exact on a range of drivers (e.g. Grid_Runner, GoatTracker) from the
.sid alone — one driver-agnostic path.
The binary is located via SIDTRACE_BIN (env), so the whole-tune residual-zero
test is skip-if-binary-absent: the default CI gate runs the committed-dump
oracle in a container with no preframr-sidtrace binary and stays self-contained
(no new hard external dependency).
Install
pip install preframr-tokens
Quick start
import preframr_tokens as P
sid, dump = "Grid_Runner.sid", "Grid_Runner.1.dump.parquet"
# Recover the BACC program, verify it's byte-exact, serialize to token ids.
assert P.verify_residual(sid, dump, P.CPF) # residual = 0 (lossless)
program = P.recover_program(sid, dump, P.CPF) # .sid + .dump -> BaccProgram
ids = P.program_to_ids(program) # model-facing token id stream
breakdown, frames = P.measure(program) # {block: tokens}, frame count
print(breakdown["total"], "tokens", "/", frames, "frames")
state = P.render_program(program) # BaccProgram -> (nframes, 25)
prog2 = P.ids_to_program(ids, driver=program.driver) # round-trips byte-exact
Public API
preframr_tokens.__all__ is the promised surface; everything under
preframr_tokens.bacc.* and preframr_tokens.codec.* is internal and may move
between releases — depend on the root re-exports.
recover_program(sid, dump, cpf=CPF, subtune=0)—(.sid, .dump) → BaccProgram.render_program(program)—BaccProgram → (nframes, 25)register state.verify_residual(sid, dump, cpf=CPF, subtune=0)—Trueiff render == dump, byte-exact (the gate).program_to_ids(program)/ids_to_program(ids, driver=...)— the model-facing token id stream (round-trips byte-exact to the program).measure(program)—({block: tokens}, nframes);breakdown["total"]is the pre-BPE token count.VOCAB/PAD_ID— token alphabet size and the reserved padding id.per_frame_state(dump, cpf, maxframes)/CPF/NTSC_CPF/cpf_from_meta(prefix)— the dump reader + PAL/NTSC frame clock (CPF= 19656 PAL cycles/frame,NTSC_CPF= 17095).
The input dump format
A raw tune's register dump is a .dump.parquet of register writes captured from a
SID player, with at least the columns clock (absolute φ2 cycle), reg (0..24),
val (byte written), and chipno (only chip 0 is read). per_frame_state
reconstructs the 25-register state per absolute frame at the tune's frame clock.
Register map (base = voice * 7): +0/+1 freq lo/hi, +2/+3 pulse-width lo/hi,
+4 control (bit0 GATE, bit3 TEST, bits4–7 waveform), +5 AD, +6 SR. Globals:
21/22 filter cutoff lo/hi, 23 resonance/routing, 24 mode/volume.
The gate / tests
The reference budget gate is
tests/test_goattracker.py::test_grid_runner_context_budget: it recovers the BACC
program from Grid_Runner (Jammer, GoatTracker), asserts verify_residual is
True, and requires both < 1 token/frame and the whole song < 8192 tokens. Its
fixtures are auto-acquired (the .sid is downloaded, the dump rendered). The
generic path is exercised end-to-end (whole-tune residual-zero) in
tests/test_generic_recovery.py and tests/test_sid_only_recovery.py.
Run the full gate (black, pytest, pylint, pyright, coverage) with:
./run_tests.sh
Where the design narrative lives
The canonical, end-to-end narrative — how the codec landed, the op-set grounding,
and the cross-driver design — lives in the sibling preframr-xpt repo: start at
its AGENTS.md, then design/encoding/ (sid_player_decompiler.md,
sid_opset_inventory.md, cross_driver_note_unification.md).
Stability
Pre-1.0 releases may break API as the codec evolves. Token-alphabet shape changes bump the major version since they invalidate downstream checkpoints.
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 preframr_tokens-0.54.0.tar.gz.
File metadata
- Download URL: preframr_tokens-0.54.0.tar.gz
- Upload date:
- Size: 7.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0476425f68ba9c3bf3969b5dffbd14c408f4a15b5fa964461395ed3912eddbb4
|
|
| MD5 |
7bd22ea6660d6ab4558acd42c9c79020
|
|
| BLAKE2b-256 |
796e65d515b7dde06616ec31e06b294359e05b9fdd6cbfe94fd758bff70f5054
|
Provenance
The following attestation bundles were made for preframr_tokens-0.54.0.tar.gz:
Publisher:
release.yml on anarkiwi/preframr-tokens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
preframr_tokens-0.54.0.tar.gz -
Subject digest:
0476425f68ba9c3bf3969b5dffbd14c408f4a15b5fa964461395ed3912eddbb4 - Sigstore transparency entry: 1932609907
- Sigstore integration time:
-
Permalink:
anarkiwi/preframr-tokens@2a5af228a1a59a658e0aad7f39dec601de1a4a79 -
Branch / Tag:
refs/tags/v0.54.0 - Owner: https://github.com/anarkiwi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a5af228a1a59a658e0aad7f39dec601de1a4a79 -
Trigger Event:
push
-
Statement type:
File details
Details for the file preframr_tokens-0.54.0-py3-none-any.whl.
File metadata
- Download URL: preframr_tokens-0.54.0-py3-none-any.whl
- Upload date:
- Size: 231.0 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 |
47adce46a9fb81cfabeba9dfa53091557f0d3bf61df53716c511a9a8577a881b
|
|
| MD5 |
8b2d171dd36145f0f75c936b7e3d9dba
|
|
| BLAKE2b-256 |
f49e2629bd6793ebba1c4c161535c730c5165667678b0694a33052fcd50a1bb1
|
Provenance
The following attestation bundles were made for preframr_tokens-0.54.0-py3-none-any.whl:
Publisher:
release.yml on anarkiwi/preframr-tokens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
preframr_tokens-0.54.0-py3-none-any.whl -
Subject digest:
47adce46a9fb81cfabeba9dfa53091557f0d3bf61df53716c511a9a8577a881b - Sigstore transparency entry: 1932610068
- Sigstore integration time:
-
Permalink:
anarkiwi/preframr-tokens@2a5af228a1a59a658e0aad7f39dec601de1a4a79 -
Branch / Tag:
refs/tags/v0.54.0 - Owner: https://github.com/anarkiwi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a5af228a1a59a658e0aad7f39dec601de1a4a79 -
Trigger Event:
push
-
Statement type: