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.
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.
Offsets 0x00 through 0x15. Identical in every header version; v2 and later only append.
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.
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.
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.
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.
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.
Version 1 of the header ends at 0x76, and the C64 data begins there.
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.
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.
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.
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.
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.
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 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.