The sample format of the DOS era, written by the Sound Blaster's own tools and carried inside almost every Build-engine and early-90s game archive. A 26-byte header, then a chain of typed blocks: a type byte, a 24-bit little-endian length, and a payload. Three things about that chain send a reader quietly wrong rather than stopping it: the terminator has no length, the continuation block has no format, and the length is three bytes where the eye expects four.
The terminator has no length field. Every block is a type byte and a three-byte length -- except type 00, which is one byte and ends the file. A reader that reads four bytes for every block header consumes three bytes past the end of the data, and on a file that ends exactly at its terminator it reads past EOF. This is the single most common way to walk the format wrong.
Block 02 carries no format bytes. It continues whichever sound block came before it, so a reader that treats every sound block as self-describing loses the rate on every file longer than one block. A long sound is commonly one block 01 and a run of 8,192-byte continuations; the byte map below shows one, with the samples either side of its header so the join can be read rather than taken on trust.
The length is 24 bits, not 32. Three bytes, little-endian, so a block caps at 16 MB and the fourth byte of what looks like a u32 is the next field. Read it as a u32 in a block 01 and the time constant is swallowed into the length, taking the sample rate with it.
Twenty-six bytes, and the last four are worth reading rather than skipping: a
version, and a checksum derived from it as
(~version + 0x1234) & 0xFFFF. It validates nothing about the audio, but it is
the only integrity check the format has, and a mismatch means the header was not written by
something that knew the rule.
A time constant and a codec byte, then unsigned 8-bit samples. The constant is a hardware divisor rather than a rate, and the rate it produces is deliberately not round.
A sound longer than one block is split, and every block after the first is a type 02 that states nothing about itself. No time constant, no codec, no rate. It inherits all of it from the sound block before it, so a reader that asks each block what it is gets an answer from the first one and silence from the rest.
The same audio, described directly: an explicit u32 rate, a bit width, a channel count and a format code. Block 09 exists because the time constant could not express a rate exactly, and it is the version worth preferring when a file offers both.
Game data names its own sounds more often than you would expect, which makes the text block worth reading rather than skipping past as metadata.
| type | what it is | notes |
|---|---|---|
00 | terminator | one byte, no length field |
01 | sound data: time constant + codec, then samples | the original, and the common case |
02 | sound continuation | no format bytes of its own |
03 | silence: a duration and a time constant | occupies time, carries no samples |
04 | marker | a u16 id |
05 | ASCII text | NUL-terminated, inside the length |
06 | repeat start | a u16 count |
07 | repeat end | closes the nearest 06 |
08 | extended format | precedes a block 01 and overrides it |
09 | sound data with an explicit u32 rate | added in v1.20 |
Types 03, 04, 06, 07 and 08 are defined by the format and seldom written. A reader should frame them from their documented layouts so the chain does not derail on a file that has them, and should not claim to have verified what it has never seen.
A block 01 stores a divisor, not a rate, and the rate is
1000000 / (256 - tc). The constants in use produce values like 5988, 8000, 10870,
10989, 11111, 21739 and 22222 Hz -- clustering around the era's 11025 and 22050 in exactly
the way a coarse integer divisor would, and never landing on them.
A file is not required to state its rate twice, so nothing inside a block 01 confirms the formula. That is the argument for reporting the constant the file holds alongside the rate derived from it: the constant is a fact about the bytes, and the rate is an inference from a documented formula. Block 09 removes the question entirely by writing the rate as a plain u32, which is why a file that offers both is better read from the 09.