DSP (File Format)

From Retro Modding Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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.


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 Type Size Description
0x0 u32 4 Sample count
0x4 u32 4 ADPCM nibble count; includes frame headers
0x8 u32 4 Sample rate
0xC u16 2 Loop flag; 1 means looped, 0 means not looped
0xE u16 2 Format; always 0
0x10 u32 4 Loop start offset
0x14 u32 4 Loop end offset
0x18 u32 4 Current address; always 0
0x1C s16[16] 2 × 16 Decode coefficients; this is 8 pairs of signed 16-bit values
0x3C u16 2 Gain; always 0
0x3E u16 2 Initial predictor/scale; always matches first frame header
0x40 s16 2 Initial sample history 1
0x42 s16 2 Initial sample history 2
0x44 u16 2 Loop context predictor/scale
0x46 s16 2 Loop context sample history 1
0x48 s16 2 Loop context sample history 2
0x4A u16[11] 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;
    }
  }
}