DSP (File Format)
The .dsp format is a common GameCube/Wii format for audio that comes with the SDK. It encodes sound using Nintendo's ADPCM codec. The same ADPCM codec is also embedded into several Retro Studios format, like AGSC; the CSMP format actually embeds the entire DSP format within it.
This file format has been completely documented This file format is now completely understood and no further research is needed. |
To do: An explanation of how ADPCM works would be nice to have somewhere on this page. Also, a better text explanation for the decoding process to go along with the example code. |
Header
Offset | Size | Description |
---|---|---|
0x0 | 4 | Sample count |
0x4 | 4 | ADPCM nibble count; includes frame headers |
0x8 | 4 | Sample rate |
0xC | 2 | Loop flag; 1 means looped, 0 means not looped |
0xE | 2 | Format; always 0 |
0x10 | 4 | Loop start offset |
0x14 | 4 | Loop end offset |
0x18 | 4 | Current address; always 0 |
0x1C | 2 × 16 | Decode coefficients; this is 8 pairs of signed 16-bit values |
0x3C | 2 | Gain; always 0 |
0x3E | 2 | Initial predictor/scale; always matches first frame header |
0x40 | 2 | Initial sample history 1 |
0x42 | 2 | Initial sample history 2 |
0x44 | 2 | Loop context predictor/scale |
0x46 | 2 | Loop context sample history 1 |
0x48 | 2 | Loop context sample history 2 |
0x4A | 2 × 11 | Reserved |
0x60 | End of DSP header |
ADPCM Data
The ADPCM audio data is split up into multiple frames. Each frame is 8 bytes; it starts with a one-byte header, then has 7 bytes (or 14 samples) of audio data. For each frame header, the bottom 4 bits are the scale value, and the top 4 bits are the coefficient index to use for the current frame.
Example C Decoding Function
vgmstream used as reference:
static const s8 nibble_to_s8[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1};
s8 get_low_nibble(u8 byte) {
return nibble_to_s8[byte & 0xF];
}
s8 get_high_nibble(u8 byte) {
return nibble_to_s8[(byte >> 4) & 0xF];
}
s16 clamp(s32 val) {
if (val < -32768) val = -32768;
if (val > 32767) val = 32767;
return s16(val);
}
void DecodeADPCM(u8 *src, s16 *dst, const DSPHeader& d)
{
s16 hist1 = d.initial_hist1;
s16 hist2 = d.initial_hist2;
s16 *dst_end = dst + d.num_samples;
while (dst < dst_end)
{
// Each frame, we need to read the header byte and use it to set the scale and coefficient values:
u8 header = *src++;
u16 scale = 1 << (header & 0xF);
u8 coef_index = (header >> 4);
s16 coef1 = d.coefs[coef_index][0];
s16 coef2 = d.coefs[coef_index][1];
// 7 bytes per frame
for (u32 b = 0; b < 7; b++)
{
u8 byte = *src++;
// 2 samples per byte
for (u32 s = 0; s < 2; s++)
{
s8 adpcm_nibble = (s == 0) ? get_high_nibble(byte) : get_low_nibble(byte);
s16 sample = clamp(((adpcm_nibble * scale) << 11) + 1024 + ((coef1 * hist1) + (coef2 * hist2)) >> 11);
hist2 = hist1;
hist1 = sample;
*dst++ = sample;
if (dst >= dst_end) break;
}
if (dst >= dst_end) break;
}
}
}