DSP (File Format): Difference between revisions
Jump to navigation
Jump to search
>Aruki |
m (Fix syntaxhighlight) Tag: Manual revert |
||
(12 intermediate revisions by 3 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 | ||
| | | '''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 | ||
| ''' | | '''Reserved''' | ||
|- | |- | ||
| 0x60 | | 0x60 | ||
| colspan= | | colspan=3 {{unknown|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 === | |||
[https://github.com/kode54/vgmstream/blob/master/src/coding/ngc_dsp_decoder.c vgmstream] used as reference: | |||
<syntaxhighlight lang="c | <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 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 | s16 coef1 = d.coefs[coef_index][0]; | ||
s16 coef2 = d.coefs[coef_index | s16 coef2 = d.coefs[coef_index][1]; | ||
// 7 bytes per frame | // 7 bytes per frame |
Latest revision as of 16:28, 11 October 2023
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;
}
}
}