ACIDCAT . FILE FORMAT REFERENCE

SID Anatomy

PSID / RSIDC64 sidtune
rev 2026.08
magic PSID / RSID
endian big (header)
container own
sample 50 53 49 44

A .sid file contains no audio. It is a fixed big-endian header describing a 6502 machine-code music player, followed by that player and its data as a raw Commodore 64 memory image. Playback means loading the image into a C64, calling an init routine with the subtune number in the accumulator, then calling a play routine at interrupt rate while the code writes the SID chip's registers itself.

byte order

Every multi-byte field in the header is Motorola big-endian, on a file format that describes a little-endian 6502. The format was designed on the Amiga and the byte order followed the host rather than the target. The C64 image after dataOffset is native little-endian.

header v1 -- the fixed numeric block

Offsets 0x00 through 0x15. Identical in every header version; v2 and later only append.

▸the two magics0x00 . 4 bytes

RSID is PSID with four fields pinned and a stricter runtime contract, because the tune needs a real C64 rather than the shortcuts early emulators took. A reader that meets an RSID breaking any of these must reject it.

PSID0x50534944 -- permissive; runs on PlaySID and libsidplay1 era emulators
RSID0x52534944 -- requires a true C64 environment
RSID: version2, 3 or 4 only -- never 1
RSID: loadAddressmust be 0, so the address comes from the data
RSID: playAddressmust be 0, so init installs its own interrupt handler
RSID: speedmust be 0, so the tune configures its own timing
RSID: load floorthe effective load address must not be below $07E8, which is $0400 plus the 1,000 bytes of default screen RAM: the first byte a tune can occupy without landing on the screen
RSID: initmust not point into $A000-$BFFF or $D000-$FFFF; both ROMs are banked in
▸where the load address lives0x08 . 2 bytes

A loadAddress of 0 does not mean address zero. It means the C64 data is an ordinary C64 binary whose first two bytes are the load address, little-endian -- the layout SAVE writes and LOAD"FILE",8,1 expects. Those two bytes are then not code, and the image begins after them.

A non-zero loadAddress means the opposite: the data is all code and carries no address of its own. Nothing in the bytes distinguishes the two cases, which is why the field exists and why writing it wrongly displaces an entire tune by two bytes.

▸init, play and the speed bits0x0A . 0x12

initAddress is called once per subtune with the subtune number in the accumulator. 0 means it equals the effective load address.

playAddress is called repeatedly to produce sound. 0 means init installs its own interrupt handler and the player must not call anything -- always the case for RSID.

speed is one bit per subtune, bit 0 being subtune 1: a 0 bit selects the vertical blank interrupt (50 Hz PAL, 60 Hz NTSC), a 1 bit the CIA 1 timer. Past 32 subtunes the two header generations disagree, and only the header says which rule applies.

This is not the field's original design. The specification carries its own warning that speed never worked as intended on PlaySID for Amiga, and that what is documented today is a redefinition of the v2NG field. The clock bits exist because of that failure: the speed field alone cannot express NTSC.

The bits stay meaningful even when nothing reads them. With playAddress at 0 they should still be set, for players old enough to act on them; a player running in a real C64 environment ignores them in that case.

v1, or PlaySID-specific setbits wrap -- subtune 33 reuses bit 0
v2NG with that flag clearbit 31 repeats for every subtune above 32

The wrapping rule above is the self-consistent reading, not the literal one. The format description states that "the speed specified for tune 32 is the same as tune 1", but its own preceding sentence gives subtune 1 to bit 0, which leaves subtune 32 holding bit 31. Taking the later sentence literally would strand bit 31 and shift every subtune above it by one. The wrap therefore begins at subtune 33.

▸the three text fields0x16 . 96 bytes

Three 32-byte strings in Windows-1252, not ASCII, not Latin-1 and not UTF-8. Latin-1 agrees everywhere except 0x80-0x9F, where cp1252 carries typographic characters and Latin-1 has C1 control codes.

A field holding exactly 32 characters has no terminator. A C-string read that does not stop at 32 runs straight into the next field and produces a plausible, wrong string.

0x16name -- 32 bytes
0x36author -- 32 bytes
0x56released -- 32 bytes, once called copyright

Version 1 of the header ends at 0x76, and the C64 data begins there.

▸the v2 / v3 / v4 tail0x76 . 6 bytes

There are only two header sizes. v1 is 0x76 bytes; v2, v3 and v4 are all 0x7C and differ only in which of these trailing bytes carry meaning.

startPage 0x00the tune is clean: it writes nothing outside its own data range
startPage 0xFFnot one free page; a driver cannot be relocated
otherwisestart of the largest free page range, e.g. 0x1E means $1E00
pageLengthfree pages after startPage; must be 0 at both sentinel values
▸flags, bit by bit0x76 . 16 bits

Drawn most-significant bit first, the way a 16-bit word is written. The specification numbers these from the least significant end, so bit 0 of the spec is the rightmost cell here.

Bit 1 carries two different meanings depending on the magic. In a PSID it marks a tune as PlaySID specific -- typically one using PlaySID volume samples, which no longer play on a real C64. In an RSID the same bit is the C64 BASIC flag, and when it is set initAddress must be 0 and the subtune number is written to $030C instead.

▸second and third SID0x7A . 0x7B

Each byte encodes the middle of $Dxx0: 0x42 means $D420, 0xFE means $DFE0. Only even values in 0x42-0x7F or 0xE0-0xFE are legal, and any other value -- including 0x00 -- means no chip at that position.

0x00-0x41 would collide with the first SID and the VIC-II; 0x80-0xDF lands in colour RAM and the CIAs. The third SID may not share the second SID's address.

secondSIDAddressa v3 field; 0 in v2NG
thirdSIDAddressa v4 field; 0 in v2NG and v3
model bits unknownan extra chip inherits the first SID's model
▸the environment the file assumesnot in the file

Loading the image is not enough. The machine around it has to be in a defined state or the tune's timing is wrong from the first frame.

$02A61 for PAL, 0 for NTSC, from the clock bits
CIA 1 timer A$4025 PAL, $4295 NTSC
PSID bank registerset per call from the target address: 0x37, 0x36, 0x35 or 0x34
RSID bank registeralways 0x37, which is why init may not sit under ROM
PSID VICraster IRQ below 0x100, enabled only when the speed bit is 0
RSID VICraster IRQ at 0x137, not enabled; the CIA runs instead

A tune written for one video standard and played on the other is detuned, because the frame rates differ. Playing an NTSC tune on a PAL machine wants a CIA latch of $3FFB; a PAL tune on NTSC wants $5021.

▸what the payload can be insteadflags bit 0

When bit 0 of flags is set the data is not a player at all. It is Compute!'s Sidplayer MUS music data with no player attached, and an external player must be merged with it before anything can be replayed.

▸the two chip revisionsflags bits 4-9

The MOS6581 and MOS8580 are not interchangeable, which is why the header names one. Combined waveforms are far louder on the 8580, to the point that combinations clearly audible on one are silent on the other. The 8580's internal DC levels are small enough that volume-register samples need a hardware or software trick. The two analog filters have different characteristics, so a filter sweep written for one is a different sound on the other.