Difference between revisions of "DSP (File Format)"

From Retro Modding Wiki
Jump to: navigation, search
m (Header)
 
(12 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
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 (File Format)|AGSC]]; the [[CSMP (File Format)|CSMP]] format actually embeds the entire DSP format within it.
 
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 (File Format)|AGSC]]; the [[CSMP (File Format)|CSMP]] format actually embeds the entire DSP format within it.
 +
 +
{{todo|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.}}
  
 
__TOC__
 
__TOC__
Line 7: Line 9:
 
{| class="wikitable"
 
{| class="wikitable"
 
! Offset
 
! Offset
 +
! Type
 
! Size
 
! Size
 
! Description
 
! Description
 
|-
 
|-
 
| 0x0
 
| 0x0
 +
| u32
 
| 4
 
| 4
 
| '''Sample count'''
 
| '''Sample count'''
 
|-
 
|-
 
| 0x4
 
| 0x4
 +
| u32
 
| 4
 
| 4
| '''ADPCM nibble count'''
+
| '''ADPCM nibble count'''; includes frame headers
 
|-
 
|-
 
| 0x8
 
| 0x8
 +
| u32
 
| 4
 
| 4
 
| '''Sample rate'''
 
| '''Sample rate'''
 
|-
 
|-
 
| 0xC
 
| 0xC
 +
| u16
 
| 2
 
| 2
 
| '''Loop flag'''; 1 means looped, 0 means not looped
 
| '''Loop flag'''; 1 means looped, 0 means not looped
 
|-
 
|-
 
| 0xE
 
| 0xE
 +
| u16
 
| 2
 
| 2
 
| '''Format'''; always 0
 
| '''Format'''; always 0
 
|-
 
|-
 
| 0x10
 
| 0x10
 +
| u32
 
| 4
 
| 4
 
| '''Loop start offset'''
 
| '''Loop start offset'''
 
|-
 
|-
 
| 0x14
 
| 0x14
 +
| u32
 
| 4
 
| 4
 
| '''Loop end offset'''
 
| '''Loop end offset'''
 
|-
 
|-
 
| 0x18
 
| 0x18
 +
| u32
 
| 4
 
| 4
| Always 0
+
| '''Current address'''; always 0
 
|-
 
|-
 
| 0x1C
 
| 0x1C
 +
| s16[16]
 
| 2 × 16
 
| 2 × 16
 
| '''Decode coefficients'''; this is 8 pairs of signed 16-bit values
 
| '''Decode coefficients'''; this is 8 pairs of signed 16-bit values
 
|-
 
|-
 
| 0x3C
 
| 0x3C
 +
| u16
 
| 2
 
| 2
 
| '''Gain'''; always 0
 
| '''Gain'''; always 0
 
|-
 
|-
 
| 0x3E
 
| 0x3E
 +
| u16
 
| 2
 
| 2
 
| '''Initial predictor/scale'''; always matches first frame header
 
| '''Initial predictor/scale'''; always matches first frame header
 
|-
 
|-
 
| 0x40
 
| 0x40
 +
| s16
 
| 2
 
| 2
 
| '''Initial sample history 1'''
 
| '''Initial sample history 1'''
 
|-
 
|-
 
| 0x42
 
| 0x42
 +
| s16
 
| 2
 
| 2
 
| '''Initial sample history 2'''
 
| '''Initial sample history 2'''
 
|-
 
|-
 
| 0x44
 
| 0x44
 +
| u16
 
| 2
 
| 2
 
| '''Loop context predictor/scale'''
 
| '''Loop context predictor/scale'''
 
|-
 
|-
 
| 0x46
 
| 0x46
 +
| s16
 
| 2
 
| 2
 
| '''Loop context sample history 1'''
 
| '''Loop context sample history 1'''
 
|-
 
|-
 
| 0x48
 
| 0x48
 +
| s16
 
| 2
 
| 2
 
| '''Loop context sample history 2'''
 
| '''Loop context sample history 2'''
 
|-
 
|-
 
| 0x4A
 
| 0x4A
 +
| u16[11]
 
| 2 × 11
 
| 2 × 11
| '''Padding'''
+
| '''Reserved'''
 
|-
 
|-
 
| 0x60
 
| 0x60
| colspan=2 | End of DSP header
+
| colspan=3 {{unknown|End of DSP header}}
 
|}
 
|}
  
== Audio Data ==
+
== 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.
  
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, 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 ===
  
Sample decoding code ([https://github.com/kode54/vgmstream/blob/master/src/coding/ngc_dsp_decoder.c vgmstream] used as reference):
+
[https://github.com/kode54/vgmstream/blob/master/src/coding/ngc_dsp_decoder.c vgmstream] used as reference:
  
<pre>static const s8 nibble_to_s8[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1};
+
<syntaxhighlight lang="c">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) {
 
s8 get_low_nibble(u8 byte) {
Line 104: Line 126:
 
}
 
}
  
void DecodeADPCM(char *src, s16* dst, const DSPHeader& d)
+
void DecodeADPCM(u8 *src, s16 *dst, const DSPHeader& d)
 
{
 
{
 
   s16 hist1 = d.initial_hist1;
 
   s16 hist1 = d.initial_hist1;
Line 117: Line 139:
 
     u16 scale = 1 << (header & 0xF);
 
     u16 scale = 1 << (header & 0xF);
 
     u8 coef_index = (header >> 4);
 
     u8 coef_index = (header >> 4);
     s16 coef1 = d.coefs[coef_index * 2];
+
     s16 coef1 = d.coefs[coef_index][0];
     s16 coef2 = d.coefs[coef_index * 2 + 1];
+
     s16 coef2 = d.coefs[coef_index][1];
  
 
     // 7 bytes per frame
 
     // 7 bytes per frame
Line 140: Line 162:
 
     }
 
     }
 
   }
 
   }
}</pre>
+
}</syntaxhighlight>
  
 
[[Category:Audio]]
 
[[Category:Audio]]
 
[[Category:Metroid Prime]]
 
[[Category:Metroid Prime]]
 
[[Category:Metroid Prime 2: Echoes]]
 
[[Category:Metroid Prime 2: Echoes]]

Latest revision as of 23:42, 8 February 2015

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.


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 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;
    }
  }
}