============================================================================= Sierra On-Line Audio File Format Description 5-01-2002 ============================================================================= By Valery V. Anisimovsky (samael@avn.mccme.ru) In this document I'll try to describe audio file format used in many Sierra On-Line games. In most games these files are contained within .SFX and .AUD resource files (usually named RESOURCE.SFX and RESOURCE.AUD). When encountered as stand-alone files, they usually have extension .AUD (but it has nothing to do with Westwood's AUD audio file format!). The games using this format include: King's Quest 6, King's Quest 7, King's Quest 8, Leisure Suit Larry 6, Leisure Suit Larry 7, Pepper's Adventures in Time, Quest For Glory 3, Space Quest 5, Torin's Passage, Phantasmagoria, Phantasmagoria II: A Puzzle Of Flesh, Gabriel Knight, Gabriel Knight II, Shivers, Shivers 2: Harvest of Souls, Under a Killing Moon. Maybe many more, e.g.: other games of SQx, GQx, LSLx, KQx series. The files this document deals with have extensions: .SFX, .AUD. Note that the extension of resource files containing AUD audio files may be different from these. Throughout this document I use C-like notation. All numbers in all structures described in this document are stored in files using little-endian (Intel) byte order. =================== 1. AUD File Header =================== The AUD file has the following header: struct AUDHeader { BYTE bID; BYTE bShift; char szID[4]; WORD wSampleRate; BYTE bFlags; DWORD dwDataSize; }; bID -- is equal to 0x8D in all Sierra On-Line games I've seen, except for King's Quest 8, where it equals to 0x0D. bShift -- defines where audio data starts: (bShift+2) is the starting position of the audio data relative to the file start (NOT to the start of RESOURCE.SFX/RESOURCE.AUD containing this file). szID -- always "SOL\0". Note that there're four bytes including terminating zero! wSampleRate -- sample rate for the file. bFlags -- bit-mapped flags: bit 0 -- if set, audio data is compressed (otherwise it's PCM), bit 1 -- ??? (I've never seen it set), bit 2 -- if set, audio data is 16-bit (8-bit otherwise), bit 3 -- if set, audio data is in signed format (unsigned otherwise): 16-bit sound is signed and 8-bit is unsigned, bit 4 -- if set, sound is stereo (mono otherwise). dwDataSize -- size of the audio data (in bytes). ================= 2. AUD File Data ================= Starting at (bShift+2) from the file start, comes AUD audio data. If bit 0 of bFlags is not set, it's just PCM: 8-bit or 16-bit, signed or unsigned. Otherwise it's compressed with the algorithm, which I refer to as SOL ADPCM. SOL ADPCM has two types: 8-bit (for 8-bit sound) and 16-bit (for 16-bit sound). =========================================== 3. 8-bit SOL ADPCM Decompression Algorithm =========================================== Let's (CurSample) be current sample value and (InputBuffer) contain SOL ADPCM compressed data: SHORT CurSample; BYTE InputBuffer[InputBufferSize]; BYTE code; DWORD i; // index into InputBuffer CurSample=0x80; // unsigned 8-bit for (i=0;i> 4) #define LONIBBLE(byte) ((byte) & 0x0F) Note that depending on your compiler you may need to use additional nibble separation in these defines, e.g. (((byte) >> 4) & 0x0F). Output() is just a placeholder for any action you would like to perform for decompressed sample value. SOLTable3bit is the delta table given near the end of this document. INDEX4(code) is really a tricky thing. In some games (mostly older ones) it should be the following: #define INDEX4(code) (0xF-(code)) While in some other games it's the following: #define INDEX4(code) ((code) & 7) "Old" INDEX4 is used, for example, in King's Quest 6, Quest For Glory 3, Gabriel Knight. "New" INDEX4 is used in Torin's Passage, maybe in other games. I do not know the reliable way to figure out which of those you should use for particular file, but currently I use the simplest technique: I just decode first, say, 1Kb of data using both approaches and look if one of them results in the output stream which is far from reasonable 8-bit unsigned sound (that is, its mean sample value is far from 0x80). Clip8BitSample is quite evident: SHORT Clip8BitSample(SHORT sample) { if (sample>255) return 255; else if (sample<0) return 0; else return sample; } Note that the HIGHER nibble is processed first. ============================================ 4. 16-bit SOL ADPCM Decompression Algorithm ============================================ It's just analoguous to the 8-bit decompression scheme: LONG CurSample; BYTE InputBuffer[InputBufferSize]; BYTE code; DWORD i; CurSample=0x0000; // signed 16-bit for (i=0;i32767) return 32767; else if (sample<-32768) return (-32768); else return sample; } Note that the decompression schemes are given ONLY for unsigned 8-bit sound and signed 16-bit sound. I've never seen signed 8-bit or unsigned 16-bit sound in AUD format, but to support these you should only support the correspondent clipping (-128..127 for signed 8-bit and 0..65535 for unsigned 16-bit) and make additional conversion before outputting the sample value: signed->unsigned for 8-bit sound or unsigned->signed for 16-bit sound, provided that you've initialized (CurSample) to the correspondent value: 0x00 for signed 8-bit and 0x8000 for unsigned 16-bit. Also, those algorithms are ONLY for mono sound, but their improvement for stereo is simple: for 8-bit sound left channel is in HIGHER nibble and right is in LOWER one, while for 16-bit sound left channel is first byte and right channel is second one. Note that you should maintain two different (CurSample) variables for left and right channels: (CurSampleLeft) and (CurSampleRight). Of course, both decompression routines described above may be greatly optimized. ==================== 5. SOL ADPCM Tables ==================== BYTE SOLTable3bit[]= { 0, 1, 2, 3, 6, 0xA, 0xF, 0x15 }; WORD SOLTable7bit[]= { 0x0, 0x8, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1A0, 0x1B0, 0x1C0, 0x1D0, 0x1E0, 0x1F0, 0x200, 0x208, 0x210, 0x218, 0x220, 0x228, 0x230, 0x238, 0x240, 0x248, 0x250, 0x258, 0x260, 0x268, 0x270, 0x278, 0x280, 0x288, 0x290, 0x298, 0x2A0, 0x2A8, 0x2B0, 0x2B8, 0x2C0, 0x2C8, 0x2D0, 0x2D8, 0x2E0, 0x2E8, 0x2F0, 0x2F8, 0x300, 0x308, 0x310, 0x318, 0x320, 0x328, 0x330, 0x338, 0x340, 0x348, 0x350, 0x358, 0x360, 0x368, 0x370, 0x378, 0x380, 0x388, 0x390, 0x398, 0x3A0, 0x3A8, 0x3B0, 0x3B8, 0x3C0, 0x3C8, 0x3D0, 0x3D8, 0x3E0, 0x3E8, 0x3F0, 0x3F8, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, 0x5C0, 0x600, 0x640, 0x680, 0x6C0, 0x700, 0x740, 0x780, 0x7C0, 0x800, 0x900, 0xA00, 0xB00, 0xC00, 0xD00, 0xE00, 0xF00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 }; ======================= 6. SOL Low-Pass Filter ======================= If decompressed as described above, SOL ADPCM compressed 8-bit files from Torin's Passage seem to have some minor noise. It appears that some Sierra On-Line games use a kind of strange low-pass filter after the decompression of SOL APDCM compressed 8-bit files. SOL ADPCM compressed 16-bit files does not seem to need such enhancement (as well as SOL ADPCM compressed 8-bit files in some other Sierra games). The SOL LPF scheme looks like to be the following. For unsigned 8-bit sound: BYTE Sound[BufferSize]; // decompressed sound buffer BYTE FilteredSound[BufferSize]; // filtered sound buffer DWORD i; // index for (i=0;i<(BufferSize-2);i++) FilteredSound[i]=(BYTE)((WORD)Sound[i]+(WORD)Sound[i+2])/2; For 16-bit sound the implementation is just analoguous. ================================================ 7. AUD Resources: RESOURCE.AUD and RESOURCE.SFX ================================================ When stored in .SFX/.AUD resources, the audio files are stored "as is", without compression (unlike other Sierra On-Line resource files) or encryption. That means if you want to play/extract AUD file from the RESOURCE.SFX/.AUD resource you just need to search for szID id-string ("SOL\0") and read AUDHeader starting at the position two bytes before found id-string. This will give you starting point of the file and the size of the file will be (dwDataSize+bShift+2). =========== 8. Credits =========== Anthony Larme (larme@bit.net.au) http://www.bit.net.au/~larme/ [Phantasmagoria Memorial Websites] It was just him who inspired me to explore this format deeper and helped me much with the AUDs from Sierra's games I had no access to. It was also him who tested my Game Audio Player on many Sierra's games and reported me results. ------------------------------------------- Valery V. Anisimovsky (samael@avn.mccme.ru) http://bim.km.ru/gap/ http://www.anxsoft.newmail.ru http://anx.da.ru On these sites you can find my GAP program which can search for SOL audio files in .SFX/.AUD resources, extract them, convert them to WAV and play them back. There's also complete source code of GAP and all its plug-ins there, including SOL plug-in, which could be used for further details on how you can deal with this format.