DSP (File Format)

From Retro Modding Wiki
Revision as of 15:42, 25 January 2015 by MrSinistar (Talk | contribs) (Added completed file format notice)

Jump to: navigation, search

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.

Morphball render.png This file format has been completely documented
This file format is now completely understood and no further research is needed.


GravitySuitIcon.png 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;
    }
  }
}