Westwood ADPCM Audio

From MultimediaWiki
Jump to: navigation, search

Credit

The information below was originally based on one of the many format documents written up by Valery V. Anisimovsky, available on http://wotsit.org/ and many other sites across the internet.

Note

AUD3.txt refers to Vladan Bato's file which is not yet up on the wiki.

AUD Files

Malcolm's AUD files have the same format as C&C's AUDs (which is described in AUD3.TXT) with only one exception: there's no OutSize field in their header. So it looks like the following:

struct AUDHeaderOld
{
   WORD  wSampleRate;
   DWORD dwSize;
   BYTE  bFlags;
   BYTE  bType;
};

bType is equal to 0x01 for WS ADPCM compressed AUDs. All WS ADPCM compressed sounds I've ever encountered are 8-bit.

The meanings of the other fields in AUD header are the same as for C&C AUDs. These AUDs are divided in chunks with the chunk header being the same as for C&C, but those chunks have variable size (may be NOT 512 bytes) unlike C&C AUDs!

Note that WS ADPCM compressed AUDs in C&C (death screams) have just the same format as other AUDs in this game, i.e. with OutSize field.

WS ADPCM Decompression Algorithm

Each AUD chunk may be decompressed independently of others. This lets you implement the seeking for WS ADPCM AUDs (unlike IMA ADPCM ones). But during the decompression of the given chunk a variable (CurSample) should be maintained for this whole chunk:

SHORT CurSample;
BYTE  InputBuffer[InputBufferSize]; // input buffer containing the whole chunk
WORD  wSize, wOutSize; // Size and OutSize values from this chunk's header
BYTE  code;
CHAR  count; // this is a signed char!
WORD  i; // index into InputBuffer
WORD  input; // shifted input

if (wSize==wOutSize) // such chunks are NOT compressed
{
 for (i=0;i<wOutSize;i++)
     Output(InputBuffer[i]); // send to output stream
 return; // chunk is done!
}
 
// otherwise we need to decompress chunk
 
CurSample=0x80; // unsigned 8-bit
i=0;

// note that wOutSize value is crucial for decompression!

while (wOutSize>0) // until wOutSize is exhausted!
{
 input=InputBuffer[i++];
 input<<=2;
 code=HIBYTE(input);
 count=LOBYTE(input)>>2;
 switch (code) // parse code
 {
   case 2: // no compression...
	 if (count & 0x20)
	 {
	   count<<=3;		// here it's significant that (count) is signed:
	   CurSample+=count>>3; // the sign bit will be copied by these shifts!

	   Output((BYTE)CurSample);

	   wOutSize--; // one byte added to output
	 }
	 else // copy (count+1) bytes from input to output
	 {
	   for (count++;count>0;count--,wOutSize--,i++)
	       Output(InputBuffer[i]);
	   CurSample=InputBuffer[i-1]; // set (CurSample) to the last byte sent to output
	 }
	 break;
   case 1: // ADPCM 8-bit -> 4-bit
	 for (count++;count>0;count--) // decode (count+1) bytes
	 {
	   code=InputBuffer[i++];

	   CurSample+=WSTable4bit[(code & 0x0F)]; // lower nibble

	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);

	   CurSample+=WSTable4bit[(code >> 4)]; // higher nibble
 
	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);
 
	   wOutSize-=2; // two bytes added to output
	 }
	 break;
   case 0: // ADPCM 8-bit -> 2-bit
	 for (count++;count>0;count--) // decode (count+1) bytes
	 {
	   code=InputBuffer[i++];
 
	   CurSample+=WSTable2bit[(code & 0x03)]; // lower 2 bits

	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);

	   CurSample+=WSTable2bit[((code>>2) & 0x03)]; // lower middle 2 bits

	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);

	   CurSample+=WSTable2bit[((code>>4) & 0x03)]; // higher middle 2 bits

	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);

	   CurSample+=WSTable2bit[((code>>6) & 0x03)]; // higher 2 bits

	   CurSample=Clip8BitSample(CurSample);
	   Output((BYTE)CurSample);

	   wOutSize-=4; // 4 bytes sent to output
	 }
	 break;
   default: // just copy (CurSample) (count+1) times to output
	 for (count++;count>0;count--,wOutSize--)
	     Output((BYTE)CurSample);
 }
}

HIBYTE and LOBYTE are just higher and lower bytes of WORD:

#define HIBYTE(word) ((word) >> 8)
#define LOBYTE(word) ((word) & 0xFF)

Note that depending on your compiler you may need to use additional byte separation in these defines, e.g. (((byte) >> 8) & 0xFF). The same holds for 4-bit and 2-bit nibble separation in the code above.

WSTable4bit and WSTable2bit are the delta tables given in the next section.

Output() is just a placeholder for any action you would like to perform for decompressed sample value.

Clip8BitSample is quite evident:

SHORT Clip8BitSample(SHORT sample)
{
 if (sample>255)
    return 255;
 else if (sample<0)
    return 0;
 else
    return sample;
}

This algorithm is ONLY for mono 8-bit unsigned sound, as I've never seen any other sound format used with WS ADPCM compression.

Of course, the decompression routine described above may be greatly optimized.

WS ADPCM Tables

CHAR WSTable2bit[]=
{
   -2,
   -1,
    0,
    1
};

CHAR WSTable4bit[]=
{
   -9, -8, -6, -5, -4, -3, -2, -1,
    0,  1,  2,  3,  4,  5,  6,  8
};

AUDs in Legend Of Kyrandia III: Malcolm's Revenge

The WS ADPCM compression described above is used for all audio in this game:

Music is stand-alone .AUD files. Speech is AUDs in .TLK resource files. Sounds are AUDs in .PAK resource files.

These .TLKs and .PAKs do not use any compression or encryption for AUDs, so AUDs are stored "as is" in them. If you want to extract/play an AUD from PAK or TLK you just need to search the PAK or TLK for the AUD id, that is, DWORD value equal to 0x0000DEAF (or, in other words, string "\xAF\xDE\0\0"). Refer to Vladan Bato's AUD3.TXT for more details on AUD file structure.

VQA Movie Soundtracks

Soundtrack of VQA movie in Malcolm, C&C, Red Alert and C&C: Tiberian Sun is stored in SND0, SND1 or SND2 blocks. Refer to VQA_FRMT.TXT by Aaron Glover for details on the structure of VQA files. Here I only describe the contents of VQA sound blocks and VQHD (header) block.

VQHD block contains header for VQA. To the best of my knowledge, it has the following format:

struct VQAHeader
{
   WORD  wVersion;
   WORD  unknown1;
   WORD  wNumFrames;
   WORD  wWidth;
   WORD  wHeight;
   WORD  unknown2;
   WORD  unknown3;
   WORD  unknown4;
   WORD  unknown5;
   DWORD unknown6;
   WORD  unknown7;
   WORD  wSampleRate;
   BYTE  bChannels;
   BYTE  bResolution;
   char  unknown8[14];
};

wVersion -- version of VQA: 1 -- oldest Malcolm's VQAs, 2 -- C&C, Red Alert, 3 -- C&C: Tiberian Sun.

wNumFrames -- number of frames in VQA. Note that number of sound blocks is (wNumFrames+1) for VQAs of version 2 (C&C, Red Alert), and (wNumFrames) for versions 1 and 3. But Dune2000 VQAs have also (wNumFrames) sound blocks while they're version 2 VQAs.

wSampleRate -- sample rate for soundtrack. Note that version 1 (Malcolm's) VQAs may have this value set to 0x0000! Use 22050 Hz in such cases.

bChannels -- number of channels (1 -- mono, 2 -- stereo). Note that version 1 VQAs may have this set to 0x00, so use 1 (mono) for such files.

bResolution -- resolution of soundtrack (0x10 -- 16-bit, 0x8 -- 8-bit). Note that version 1 VQAs may have this set to 0x00, so use 0x8 for such files.

All VQAs in Malcolm have their sound in either SND0 or SND1 blocks. SND0 blocks contain non-compressed PCM data. SND1 blocks contain small header and WS ADPCM compressed sound data. The header is the following:

struct SND1Header
{
 WORD wOutSize;
 WORD wSize;
};

Following the header comes WS ADPCM compressed sound data. Each SND1 sound block may be decompressed, just like a chunk of AUD file, independently of the others and the routine described above may be used for its decompression without any changes, provided you use wOutSize from the SND1Header.

As to VQAs in C&C and Red Alert their sound is in the SND2 blocks and compressed with IMA ADPCM algorithm, described in Vladan Bato's AUD3.TXT. The contents of SND2 block is just compressed data, without any headers and those blocks should be decompressed in their turn just like chunks of IMA ADPCM compressed AUD file as it's described in AUD3.TXT. This holds only for mono soundtracks.

But there're also stereo soundtracks in C&C and C&C: Tiberian Sun. They have different left/right channel nibbles layout.

For C&C (version 2) VQAs the layout is the following: LL RR LL RR ... That is, first byte contains two nibbles for two left channel values, next byte contains nibbles for right channel, etc. Note that lower nibble should be processed first and then higher one (see AUD3.TXT).

For C&C: Tiberian Sun (version 3) VQAs the layout is different: in SND2 block first go all nibbles for left channel, then all nibbles for right channel: LL LL LL ... LL RR RR RR ... RR Note that nibbles should be processed in the same turn: lower nibble first. So, when decoding SND2 block, just decompress first half of the block data for left channel, then second half -- for right channel.

PC Games Using this Format

Legend of Kyrandia III: Malcom's Revenge

Command & Conquer

Command & Conquer Red Alert

Command & Conquer Tiberian Sun

Dune 2000

Possibly using the format:

Lands of Lore: Guardians of Destiny

Lands of Lore III

Blade Runner