OBD:SNDD: Difference between revisions

16,674 bytes removed ,  6 January 2024
m
replaced formula GIFs with Math markup; replaced nowiki tags around equals signs with new {{=}} magic word
m (→‎Known data issues: sorta typos)
m (replaced formula GIFs with Math markup; replaced nowiki tags around equals signs with new {{=}} magic word)
 
(14 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{OBD_File_Header | type=SNDD | prev=QTNA | next=StNA | name=Sound Data | family=Generic | align=center}}
{{OBD_File_Header | type=SNDD | prev=QTNA | next=StNA | name=Sound Data | family=General | align=center}}


:''For metadata instances used to group sounds together, randomize them, adjust their volume or frequency, etc, see [[OSBD]] and its subtypes: [[OSAm]], [[OSIm]] and [[OSGr]].''
:''For metadata instances used to group sounds together, randomize them, adjust their volume or frequency, etc, see [[OSBD]] and its subtypes: [[OSAm]], [[OSIm]] and [[OSGr]].''
SNDD instances is where Oni stores sound data. In Vanilla Oni game data, sounds are either mono or stereo waveforms (with sampling frequencies of either 22.05 kHz or 44.1 kHz), compressed to save on storage space. Both the PC and Mac versions use a form of [[wp:ADPCM|ADPCM]] compression (Adaptive Differential Pulse-Code Modulation), where 16-bit sound samples are encoded as 4-bit "nibbles" (resulting roughly in a 4:1 compression ratio as compared to uncompressed 16-bit [[wp:PCM|PCM]]).
SNDD instances is where Oni stores sound data. In Vanilla Oni game data, sounds are either mono or stereo waveforms (with sampling frequencies of either 22.05 kHz or 44.1 kHz), compressed to save on storage space. Both the PC and Mac versions use a form of [[wp:Adaptive differential pulse-code modulation|ADPCM]] compression (Adaptive Differential Pulse-Code Modulation), where 16-bit sound samples are encoded as 4-bit "nibbles" (resulting roughly in a 4:1 compression ratio as compared to uncompressed 16-bit [[wp:Pulse-code modulation|PCM]]).
*On PC (both retail and demo), sounds are encoded using Microsoft's ADPCM codec (implemented in [[wp:FFmpeg|FFmpeg]] as '''adpcm_ms'''). See [https://wiki.multimedia.cx/index.php/Microsoft_ADPCM HERE] for a quick description.
*On PC (both retail and demo), sounds are encoded using Microsoft's ADPCM codec (implemented in [[wp:FFmpeg|FFmpeg]] as '''adpcm_ms'''). See [https://wiki.multimedia.cx/index.php/Microsoft_ADPCM HERE] for a quick description.
*On Mac, sounds are encoded using QuickTime's IMA4 codec (implemented in [[wp:FFmpeg|FFmpeg]] as '''adpcm_ima_qt'''). See [https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM HERE] for a quick description.
*On Mac, sounds are encoded using QuickTime's IMA4 codec (implemented in FFmpeg as '''adpcm_ima_qt'''). See [https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM HERE] for a quick description.
*On PS2, sounds are encoded using Sony's VAG codec (a.k.a. Sony PSX ADPCM, or '''adpcm_psx''' in FFmpeg). See [https://www.psdevwiki.com/ps3/Multimedia_Formats_and_Tools#VAG HERE] for a quick description.
 
As a unique feature of Oni game data, SNDD files have a significantly different structure depending on the engine version. For PC retail (.dat/.raw storage, no .sep files), the SNDD files are larger and include a 50-byte chunk of data that is equivalent to the "fmt " chunk of a WAVE file. For the other two versions (PC demo and Mac, .dat/.raw/.sep storage), this 50-byte block is missing. It turns out that the extra format data allows the PC retail to support both MS ADPCM and IMA4, as well as uncompressed PCM, whereas PC demo and Mac engines only support MS ADPCM and IMA4, respectively. (It has not been confirmed whether the PC retail engine supports other WAVE formats beyond PCM and MS ADPCM, such as Mu-Law or A-Law PCM, IEEE float PCM, etc.) The PS2 engine uses the same short data header as for PC demo and Mac, but the waveform is stored as VAG (a.k.a. PSX ADPCM) and resides in a completely separate SOUNDS folder, accessed through an additional layer of indexation beyond the usual .dat/.raw./.sep logic (not unlike PS2 TXMPs which rely on color palettes stored in additional level#_palette.pal files).


As a unique feature of Oni game data, SNDD files have a significantly different structure depending on the engine version. For PC retail (.dat/.raw storage, no .sep files), the SNDD files are larger and include a 50-byte chunk of data that is equivalent to the "fmt " chunk of a WAVE file. For the other two versions (PC demo and Mac, .dat/.raw/.sep storage), this 50-byte block is missing. It turns out that the extra format data allows the PC retail to support both MS ADPCM and IMA4, as well as uncompressed PCM, whereas PC demo and Mac engines only support MS ADPCM and IMA4, respectively. (It has not been confirmed whether the PC retail engine supports other WAVE formats beyond PCM and MS ADPCM, such as Mu-Law or A-Law PCM, IEEE float PCM, etc.)
----
----
For clarity, the simpler and more straightforward SNDDs of PC demo and Mac are documented first, followed by the more complex and versatile PC retail SNDDs.
For clarity, the simpler and more straightforward SNDDs of PC demo and Mac are documented first, followed by the more complex and versatile PC retail SNDDs.
:(Historically, though, the PC retail implementation is older, and the PC demo and Mac versions were trimmed-down iterations of PC retail.)
:(Historically, though, the PC retail implementation is older, and the PC demo and Mac versions were trimmed-down iterations of PC retail.)
The exotic PS2 storage is documented last, after which we list some legacy tips (for manual sound conversion) and known issues/limitations, as well as the current sound capabilities of OniX and OniSplit.


==Mac and PC demo==
==Mac and PC demo==
The below example was taken from Mac Oni. In PC demo the file would look the same, except for possibly different res_id (at 0x00) and smaller raw data size (at 0x10).
The below example was taken from Mac Oni. In PC demo the file would look the same, except for possibly different res_id (at 0x00) and smaller raw data size (at 0x10).


[[image:sndd_alm.gif]]
[[Image:sndd_alm.gif]]


{{Table}}
{{Table}}
Line 42: Line 46:


===IMA4 ADPCM .raw data (Mac)===
===IMA4 ADPCM .raw data (Mac)===
''For an overview of the IMA ADPCM algorithm and IMA4 header (if interested), see [https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM HERE]. For an actual implementation example, see, e.g., [[wp:FFmpeg|FFmpeg]].''
''For an overview of the IMA ADPCM algorithm and IMA4 header (if interested), see [https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM HERE]. For an actual implementation example, see FFmpeg.''
:The IMA4 ADPCM stream data (stored in the .raw file) consists of 34-byte blocks (in the case of stereo, there is an even number of such blocks, with Left and Right blocks interleaved).
:The IMA4 ADPCM stream data (stored in the .raw file) consists of 34-byte blocks (in the case of stereo, there is an even number of such blocks, with Left and Right blocks interleaved).
:The first two bytes of each block form a header that sets the initial predictor (upper 9 bits) and step (lower 7 bits) for decoding the block's samples. Typically they are used only for the first block, or for sudden changes of the waveform's value range.
:The first two bytes of each block form a header that sets the initial predictor (upper 9 bits) and step (lower 7 bits) for decoding the block's samples. Typically they are used only for the first block, or for sudden changes of the waveform's value range.
Line 95: Line 99:


===MS ADPCM .raw data (PC demo)===
===MS ADPCM .raw data (PC demo)===
''For a detailed overview of the ADPCM algorithm (if interested), see [https://wiki.multimedia.cx/index.php/Microsoft_ADPCM HERE]. For an actual implementation example, see, e.g., [[wp:FFmpeg|FFmpeg]].''
''For a detailed overview of the ADPCM algorithm (if interested), see [https://wiki.multimedia.cx/index.php/Microsoft_ADPCM HERE]. For an actual implementation example, see FFmpeg.''
:The MS ADPCM stream data (stored in .raw) consists of 512- or 1024-byte blocks (512 bytes for 22.05 kHz mono, 1024 bytes for 22.05 kHz stereo)
:The MS ADPCM stream data (stored in .raw) consists of 512- or 1024-byte blocks (512 bytes for 22.05 kHz mono, 1024 bytes for 22.05 kHz stereo)
:Each block starts with a 7- or 14-byte header (7 bytes for mono, 14 bytes for stereo), which includes the 16-bit values of the block's first two samples.
:Each block starts with a 7- or 14-byte header (7 bytes for mono, 14 bytes for stereo), which includes the 16-bit values of the block's first two samples.
Line 104: Line 108:
==PC retail==
==PC retail==
Below is the .dat file part used in the PC retail version.
Below is the .dat file part used in the PC retail version.
<!--
{|cellpadding=3 cellspacing=0 style="line-height:13px"
{{HexRow|0x00|
|52|49|46|46|4E|0C|01|00|57|41|56|45|66|6D|74|20|
|FF|FF|FF|FF|FF|FF|FF|FF|00|00|00|00|00|00|00|00|
|00|00|00|00|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|00|00|00|00|FF|FF|FF|FF|
|RIFF P°°WAVEfmt
}}
{{HexRow|0x10|
|32|00|00|00|'''''02'''''|'''''00'''''|'''''02'''''|'''''00'''''
|'''''22'''''|'''''56'''''|'''''00'''''|'''''00'''''|'''''27'''''|'''''57'''''|'''''00'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|2°°°°°°°"V°°'W°°
}}
{{HexRow|0x20|
|'''''00'''''|'''''04'''''|'''''04'''''|'''''00'''''|'''''20'''''|'''''00'''''|'''''F4'''''|'''''03'''''
|'''''07'''''|'''''00'''''|'''''00'''''|'''''01'''''|'''''00'''''|'''''00'''''|'''''00'''''|'''''02'''''|
|FF|FF|00|00|C8|C8|B0|B0|E7|E7|FF|FF|FF|FF|FF|FF|
|C8|C8|FF|FF|7C|7C|C3|C3|CE|CE|DD|DD|DD|DD|DD|DD|
|00|00|C8|C8|64|64|D4|D4|A5|A5|DD|DD|DD|DD|DD|DD|
|°°°° °ô°°°°°°°°
}}
{{HexRow|0x30|
|'''''00'''''|'''''FF'''''|'''''00'''''|'''''00'''''|'''''00'''''|'''''00'''''|'''''C0'''''|'''''00'''''
|'''''40'''''|'''''00'''''|'''''F0'''''|'''''00'''''|'''''00'''''|'''''00'''''|'''''CC'''''|'''''01'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|
|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|DD|
|°ÿ°°°°À°@°ð°°°Ì°
}}
{{HexRow|0x40|
|'''''30'''''|'''''FF'''''|'''''88'''''|'''''01'''''|'''''18'''''|'''''FF'''''|66|61|63|74|04|00|00|00|8A|05|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|
|DD|DD|DD|DD|DD|DD|00|00|00|00|FF|FF|FF|FF|00|00|
|DD|DD|DD|DD|DD|DD|C8|C8|C8|C8|00|00|00|00|FF|FF|
|0ÿˆ°°ÿfact°°°°Š°
}}
{{HexRow|0x50|
|01|00|64|61|74|61|00|0C|01|00|05|05|10|00|10|00|
|C8|C8|C8|C8|C8|C8|00|00|00|00|FF|FF|FF|FF|FF|FF|
|00|00|C8|C8|C8|C8|FF|FF|FF|FF|00|00|00|00|00|00|
|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|
|°°data°°°°°°°°°°
}}
{{HexRow|0x60|
|0C|00|AF|FF|2E|00|B4|FF|F0|F1|00|00|0F|30|20|10|
|FF|FF|FF|FF|FF|FF|FF|FF|00|00|00|00|00|00|00|00|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°¯ÿ.°´ÿðñ°°°0°°
}}
|}
:(The contents of the "fmt " header, relevant to SNDD storage, has been highlighted in '''''bold italic'''''.)
{{Table}}
{{OBDth}}
{{OBDtr| 0x00 | char[4]  |FF0000| 52 49 46 46 | RIFF      | identifier for the "IBM/Microsoft RIFF" standard }}
{{OBDtr| 0x04 | uint32  |FFFF00| 4E 0C 01 00 | 68686    | size of the RIFF container from 0x08 to end of file }}
{{OBDtr| 0x08 | char[4]  |00FF00| 57 41 56 45 | WAVE      | identifier for the "WAVE" format }}
{{OBDtr| 0x0C | char[4]  |00FFFF| 66 6D 74 20 | "fmt "    | identifier announcing the following "fmt " (format) section }}
{{OBDtr| 0x10 | uint32  |FFC8C8| 32 00 00 00 | 50        | content size for the "fmt " section, in bytes (typically 50 for MS ADPCM) }}
{{OBDtr| 0x14 | uint16  |FFFFC8| '''''02 00'''''      | 2        | format ID (2 <nowiki>=</nowiki> MS ADPCM format) }}
{{OBDtr| 0x16 | uint16  |C8FFC8| '''''02 00'''''      | 2        | number of channels (2 <nowiki>=</nowiki> stereo) }}
{{OBDtr| 0x18 | uint32  |C8FFFF| '''''22 56 00 00''''' | 22050    | sample rate in Hz (samples per second), a.k.a. "sampling frequency" }}
{{OBDtr| 0x1C | uint32  |FFC8FF| '''''27 57 00 00''''' | 22311    | [[image:sndd_hd1.gif]]
:<small>'''N.B.''' The data rate (in bytes per second) is truncated to the lower integer value.</small> }}
{{OBDtr| 0x20 | uint16  |FFC800| '''''00 04'''''      | 1024      | block alignment a.k.a "block size", in bytes
:<small>'''N.B.''' The block size for MS ADPCM is typically a power of two.</small> }}
{{OBDtr| 0x22 | uint16  |00FFC8| '''''04 00'''''      | 4        | bits per sample (per channel); typically 4 bits for MS ADPCM }}
{{OBDtr| 0x24 | uint16  |C87C64| '''''20 00'''''      | 32        | size of the following extended format specification, in bytes }}
{{OBDtr| 0x26 | uint16  |B0C3D4| '''''F4 03'''''      | 1012      | [[image:sndd_hd2.gif]] }}
{{OBDtr| 0x28 | uint16  |E7CEA5| '''''07 00'''''      | 7        | number of the following coefficient pairs; always 7 in practice }}
|-align=center valign=top
| 0x2A || int16-16 || bgcolor="#FFDDDD" | '''''00 01 00 00''''' || 256, 0 || rowspan=7 align=left | The coefficient pairs themselves (always the same in practice).<br>[[image:sndd_hd3.gif]]
|-align=center valign=top
| 0x2E || int16-16 || bgcolor="#FFDDDD" | '''''00 02 00 FF''''' || 512, -256
|-align=center valign=top
| 0x32 || int16-16 || bgcolor="#FFDDDD" | '''''00 00 00 00''''' || 0, 0
|-align=center valign=top
| 0x36 || int16-16 || bgcolor="#FFDDDD" | '''''C0 00 40 00''''' || 192, 64
|-align=center valign=top
| 0x3A || int16-16 || bgcolor="#FFDDDD" | '''''F0 00 00 00''''' || 240, 0
|-align=center valign=top
| 0x3E || int16-16 || bgcolor="#FFDDDD" | '''''CC 01 30 FF''''' || 460, -208
|-align=center valign=top
| 0x42 || int16-16 || bgcolor="#FFDDDD" | '''''88 01 18 FF''''' || 392, -232
{{OBDtr| 0x46 | char[4]  |FF00C8| 66 61 63 74 | fact      | identifier announcing the following "fact" section }}
{{OBDtr| 0x4A | int32    |C8FF00| 04 00 00 00 | 4        | size of the following "fact" section in bytes }}
{{OBDtr| 0x4E | int32    |C800FF| 8A 05 01 00 | 66954    | actual number of samples (see below for calculation) }}
{{OBDtr| 0x52 | char[4]  |C8C8FF| 64 61 74 61 | data      | identifier announcing the following "data" section }}
{{OBDtr| 0x56 | int32    |00FFC8| 00 0C 01 00 | 68608    | size of the following "data" section in bytes (67 blocks of 1024 bytes) }}
{{OBDtr| 0x5A | block[14]|FF00FF| 05 05 10 00 10 00 0C<br>00 AF FF 2E 00 B4 FF | (5,5) (16,16)<br>(12,-81) (46,-76)| the header of the first 1024-byte block (14 bytes for a stereo block) }}
{{OBDtr| 0x68 | byte[8]...|00C8FF| F0 F1 00 00<br>00 30 20 10 | (-1,0) (-1,1) (0,0) (0,0)<br>(0,0) (3,0) (2,0) (1,0)| the first 8 pairs of nibbles (stereo samples); 1002 more bytes follow }}
|}
-->


[[image:sndd_all.gif]]
[[Image:sndd_all.gif]]


{{Table}}
{{Table}}
Line 231: Line 138:
If the "8" flag of the SNDD (at 0x08) is ON and the "4" flag is OFF (as is always the case in Vanilla Oni), the 50-byte block is interpreted as a standard "fmt " chunk that you find in WAVE files (see [[/wav|HERE]] for details).
If the "8" flag of the SNDD (at 0x08) is ON and the "4" flag is OFF (as is always the case in Vanilla Oni), the 50-byte block is interpreted as a standard "fmt " chunk that you find in WAVE files (see [[/wav|HERE]] for details).


[[image:sndd_hd.gif]]
[[Image:sndd_hd.gif]]


{{Table}}
{{Table}}
{{OBDth}}
{{OBDth}}
{{OBDtr| 0x0C | int16    |FFFFC8| 02 00      | 2    | format ID (2 <nowiki>=</nowiki> MS ADPCM format)
{{OBDtr| 0x0C | int16    |FFFFC8| 02 00      | 2    | format ID (2 {{=}} MS ADPCM format)
:<small>'''N.B.''' At the time of writing, only "1" (linear PCM) and "2" (MS ADPCM) are known to work in Oni; in Vanilla Oni, only MS ADPCM is ever used.</small>}}
:<small>'''N.B.''' At the time of writing, only "1" (linear PCM) and "2" (MS ADPCM) are known to work in Oni; in Vanilla Oni, only MS ADPCM is ever used.</small>}}
{{OBDtr| 0x0E | int16    |C8FFC8| 01 00      | 1    | number of channels (1 <nowiki>=</nowiki> mono)
{{OBDtr| 0x0E | int16    |C8FFC8| 01 00      | 1    | number of channels (1 {{=}} mono)
:<small>'''N.B.''' Both PCM and ADPCM support only mono and stereo sounds, i.e., 1 or 2 channels.</small>}}
:<small>'''N.B.''' Both PCM and ADPCM support only mono and stereo sounds, i.e., 1 or 2 channels.</small>}}
{{OBDtr| 0x10 | int32    |C8FFFF| 22 56 00 00 | 22050 | sample rate in Hz (samples per second), a.k.a. "sampling frequency" }}
{{OBDtr| 0x10 | int32    |C8FFFF| 22 56 00 00 | 22050 | sample rate in Hz (samples per second), a.k.a. "sampling frequency" }}
{{OBDtr| 0x14 | int32    |FFC8FF| 93 2B 00 00 | 11155 | [[image:sndd_hd1.gif]]
{{OBDtr| 0x14 | int32    |FFC8FF| 93 2B 00 00 | 11155 | ADPCM average data rate: <math>\frac{\text{samples per second}*\text{block alignment}}{\text{samples per block}}</math> }}
:<small>'''N.B.''' For PCM, the data rate is simply "samples per second"*"block alignment", seeing as each sample gets its own block.</small>
:<small>'''N.B.''' For PCM, the data rate is simply ''samples per second*block alignment'', seeing as each sample gets its own block.</small>
:<small>'''N.B.''' For ADPCM, the average data rate is based on whole ADPCM blocks (not accounting for how Oni truncates the .raw data).</small>}}
:<small>'''N.B.''' For ADPCM, the average data rate is based on whole ADPCM blocks (not accounting for how Oni truncates the .raw data).</small>}}
{{OBDtr| 0x18 | int16    |FFC800| 02 00      | 512  | block alignment a.k.a "block size", in bytes
{{OBDtr| 0x18 | int16    |FFC800| 02 00      | 512  | block alignment a.k.a. "block size", in bytes
:<small>'''N.B.''' The block size is trivially 2 bytes for PCM mono (one 16-bit sample) and 4 bytes for PCM stereo (Left and Right 16-bit samples).</small>
:<small>'''N.B.''' The block size is trivially 2 bytes for PCM mono (one 16-bit sample) and 4 bytes for PCM stereo (Left and Right 16-bit samples).</small>
:<small>'''N.B.''' For ADPCM, Oni's Vanilla data always uses 512 bytes per channel for 22050 Hz waveforms, and 1024 bytes for 44.1 kHz mono (see below).}}
:<small>'''N.B.''' For ADPCM, Oni's Vanilla data always uses 512 bytes per channel for 22050 Hz waveforms, and 1024 bytes for 44.1 kHz mono (see below).}}
Line 249: Line 156:
{{OBDtrBK|1=Special extended ADPCM wav format header (black outline); fully ignored if the format ID is 1 }}
{{OBDtrBK|1=Special extended ADPCM wav format header (black outline); fully ignored if the format ID is 1 }}
{{OBDtr| 0x1C | int16    |C87C64| 20 00      | 32    | size of the extra ADPCM parameters, in bytes; typically always 32 }}
{{OBDtr| 0x1C | int16    |C87C64| 20 00      | 32    | size of the extra ADPCM parameters, in bytes; typically always 32 }}
{{OBDtr| 0x1E | int16    |B0C3D4| F4 03      | 1012  | [[image:sndd_hd2.gif]] }}
{{OBDtr| 0x1E | int16    |B0C3D4| F4 03      | 1012  | samples per block: <math>\dfrac{(\text{block alignment}-7*\text{number of channels})*8}{\text{bits per sample}*\text{number of channels}}+2</math> }}
{{OBDtr| 0x20 | int16    |E7CEA5| 07 00      | 7    | number of the following coefficient pairs; always 7 in practice }}
{{OBDtr| 0x20 | int16    |E7CEA5| 07 00      | 7    | number of the following coefficient pairs; always 7 in practice }}
|-align=center valign=top
|-align=center valign=top
| 0x22 || int16-16 || bgcolor="#FFDDDD" | 00 01 00 00 || 256, 0 || rowspan=7 align=left | The coefficient pairs themselves (always the same in practice).<br>[[image:sndd_hd3.gif|left]]
| 0x22 || int16-16 || bgcolor="#FFDDDD" | 00 01 00 00 || 256, 0 || rowspan=7 align=left | The coefficient pairs themselves (always the same in practice).<br><math>\begin{array}{|c|c||c|} \text{coefficient set} & \text{coefficient 1} & \text{coefficient 2} \\
\hline
0 & 256 &    0\\
1 & 512 & -256\\
2 &  0 &    0\\
3 & 192 &  64\\
4 & 240 &    0\\
5 & 460 & -208\\
6 & 392 & -232
\end{array} </math>
|-align=center valign=top
|-align=center valign=top
| 0x26 || int16-16 || bgcolor="#FFDDDD" | 00 02 00 FF || 512, -256  
| 0x26 || int16-16 || bgcolor="#FFDDDD" | 00 02 00 FF || 512, -256  
Line 458: Line 374:
:It is possible that the "1" and "2" flags used to affect playback in raw PCM mode (something about swapping the .raw data to allow both for Little Endian and Big Endian PCM samples), but currently they do not seem to have any effect.
:It is possible that the "1" and "2" flags used to affect playback in raw PCM mode (something about swapping the .raw data to allow both for Little Endian and Big Endian PCM samples), but currently they do not seem to have any effect.


<!--
The '''duration''' (number of game ticks) is rounded to the ''lower'' value (a.k.a. "floor") in Vanilla Oni data. For example, on PC, '''SNDDmus_ot7.aif''' consists of 152360 samples, which at 22.05 kHz corresponds to 6.90975 seconds, or 414.585 ticks; the duration, however, is indicated as 414 ticks and not 415.
;Flags 0x00000001 and 0x00000002
:Setting these two flags in a PC retail SNDD file has no noticeable effect, both on their own and in combination with the other two. Possibly they are for runtime use.
;Behavior without 0x00000004 or 0x00000008
:If neither the 0x00000004 nor the 0x00000008 flags are set, the stream data from .raw is apparently copied as-is to the output buffer. The expected sample rate is 22.05 kHz, the expected sample type is regular PCM (Little-Endian signed 16-bit linear), and the channel count of the buffer is determined at OSGr level. Note that 22.05 kHz PCM data can also be supplied more explicitly, with the 0x00000008 flag enabled, and PCM parameters listed in the WAVEfmt header: sample rate (ignored), channel count and bit depth.
;Behavior with 0x00000004
:If the 0x00000004 flag is set, it overrides 0x00000008 and forces the interpretation of the stream data as IMA4 ADPCM. In this configuration, only two fields of the 50-byte "format" chunk are used: the channel count (at 0xE), and the
;Behavior with 0x00000008 and without 0x00000004


===raw PCM data (no 0x4 or 0x8 flag)===
==PS2 implementation==
===IMA4 ADPCM data (0x4 flag)===
The PS2 implementation has an unorthodox approach to raw data when it comes to SNDDs. Here are the .dat parts of the SNDDs in a PS2 level0_Final.dat file.
===WAVEfmt header (0x8 flag and no 0x04)===
{|cellpadding=3 cellspacing=0 style="line-height:13px"
-->
{{HexRow|0xD5EA0|
|01|5B|0E|00|01|00|00|00|00|00|00|00|9E|01|'''''00'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5EB0|
|20|F7|00|00|'''''40'''''|'''''30'''''|'''''76'''''|'''''13'''''|AD|DE|AD|DE|AD|DE|AD|DE|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|C8|C8|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5EC0|
|01|5C|0E|00|01|00|00|00|00|00|00|00|9E|01|'''''01'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5ED0|
|A0|F6|00|00|'''''C0'''''|'''''37'''''|'''''77'''''|'''''13'''''|AD|DE|AD|DE|AD|DE|AD|DE|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|C8|C8|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5EE0|
|01|5D|0E|00|01|00|00|00|00|00|00|00|8F|02|'''''02'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5EF0|
|40|86|01|00|'''''C0'''''|'''''2E'''''|'''''78'''''|'''''13'''''|AD|DE|AD|DE|AD|DE|AD|DE|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|C8|C8|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5F00|
|01|5E|0E|00|01|00|00|00|00|00|00|00|91|02|'''''03'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5F10|
|40|87|01|00|'''''40'''''|'''''B5'''''|'''''79'''''|'''''13'''''|AD|DE|AD|DE|AD|DE|AD|DE|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|C8|C8|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5F20|
|01|5F|0E|00|01|00|00|00|00|00|00|00|3C|01|'''''04'''''|'''''00'''''|
|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|00|00|00|00|FF|FF|FF|FF|C8|C8|C8|C8|FF|FF|FF|FF|
|00|00|00|00|00|00|00|00|C8|C8|C8|C8|C8|C8|C8|C8|
|°°°°°°°°°°°°°°°°
}}
{{HexRow|0xD5F30|
|60|BC|00|00|'''''00'''''|'''''0A'''''|'''''75'''''|'''''13'''''|AD|DE|AD|DE|AD|DE|AD|DE|
|C8|C8|C8|C8|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|
|FF|FF|FF|FF|FF|FF|FF|FF|C8|C8|C8|C8|C8|C8|C8|C8|
|C8|C8|C8|C8|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|FF|
|°°°°°°°°°°°°°°°°
}}
|}
(Yes, the PS2 retail level0_Final.dat only has those five sounds. All the other sounds (weapons, particles, impacts, footsteps, etc) are stored per-chapter, which causes a lot of duplicates but supposedly lightens the memory usage for a given level.)
 
The layout is similar to the PC demo and Mac SNDDs described above - and indeed the SNDD template checksum is the same for PC demo, Mac and PS2, implying that the data structure in the .dat is the same. However, there are two major novelties/anomalies (apart from all the music being mono), emphasized above with '''''bold italic'''''. First, the five .raw offsets at the end of each SNDD are obviously not pointers into level0_Final.raw or level0_Final.sep (the .raw's size is 4 MB, the .sep's 6MB, and the pointers are in the 311 MB range, possibly pointing into a memory region where the sounds will be stored at runtime). Second, the 2-byte padding field between the duration and the .raw storage size is obviously not blank here; rather, it is an index into a file called SOUNDS\LEVEL0\SOUND.DAT, which looks like this:
0x00: '''''00 00 20 F7 00 00 00 00 00 00''''' 01 00 A0 F6 00 00
0x10: 20 F7 00 00 '''''02 00 40 86 01 00 C0 ED 01 00''''' 03 00
0x20: 40 87 01 00 00 74 03 00 '''''04 00 60 BC 00 00 40 FB'''''
0x30: '''''04 00''''' FE FF
These are 5 blocks of 10 bytes each, followed by the two bytes FE FF which signal the end of the file. For each sound there is a 2-byte index, then a 4-byte data size (including padding), then the offset at which the data is stored in the SOUNDS\LEVEL0\SOUND.SEP file. The .SEP data for each sound consists of 32 blank bytes followed by a large number of VAG packets (16 bytes each), the final VAG packet having a terminating bit set. At the very end of the .SEP file is a terminating pair of bytes, FE FF, same as in the .DAT file. The file SOUNDS\LEVEL0\SOUND.RAW exists in the same folder, but has no data except for the two bytes FE FF.
 
For all the levels other than LEVEL0 (i.e., game chapters), some of the sounds are stored in SOUNDS\LEVEL#\SOUND.RAW (the size is about 1 MB for all chapters). RAW storage is indicated by a zero .SEP offset in the corresponding block of the SOUNDS\LEVEL#\SOUND.DAT file (note, however, that the first sound in SOUND.SEP also has a zero offset). As an example, here is a fragment of SOUNDS\LEVEL1\SOUND.DAT featuring the first .RAW-resident sounds.
0x690: '''''A8 00 70 3B 02 00 22 B2 83 00''''' A9 00 50 98 00 00
0x6A0: 92 ED 85 00 '''''AA 00 20 80 00 00 E2 85 86 00''''' AB 00
0x6B0: F0 F2 00 00 02 06 87 00 '''''AC 00 10 2D 00 00 00 00'''''
0x6C0: '''''00 00''''' AD 00 C0 1B 00 00 00 00 00 00 '''''AE 00 40 0B'''''
0x6D0: '''''00 00 00 00 00 00''''' AF 00 F0 11 00 00 00 00 00 00
0x6E0: '''''B0 00 20 24 00 00 F2 F8 87 00''''' B1 00 50 25 00 00
0x6F0: 12 1D 88 00 '''''B2 00 10 58 00 00 62 42 88 00''''' B3 00
0x700: 20 1F 00 00 72 9A 88 00 '''''B4 00 A0 26 00 00 00 00'''''
0x710: '''''00 00''''' B5 00 F0 0E 00 00 00 00 00 00 '''''B6 00 B0 09'''''
0x720: '''''00 00 00 00 00 00''''' B7 00 B0 0B 00 00 00 00 00 00
Here the .SEP offset field is zero for entries 0xAC, 0xAD, 0xAE and 0xAF, then non-zero for the next four entries, and zero again for the following four. The start of the SOUNDS\LEVEL1\SOUND.RAW file looks as follows
0x00: '''''AC 00''''' 10 2D 00 00 '''''00 00 00 00 00 00 00 00 00 00'''''
0x10: '''''00 00 00 00 00 00''''' 00 00 00 00 00 00 00 00 00 00
0x20: 00 00 00 00 00 00 '''''1A 00 24 00 00 01 10 00 02 10'''''
0x30: '''''0F 00 11 1F 1F 1E''''' 1A 00 01 1F 10 0F 11 21 3E 10
0x40: 20 0F 20 41 D2 E3
Here AC 00 is the 2-byte index of the sound (the same as in the SOUNDS\LEVEL1\SOUND.DAT and in the corresponding SNDD in level1_Final.dat), then there is the 4-byte data size (also the same as announced in the .dat and .DAT), followed by the same data as in the .SEP (32 zero bytes, then some 16-byte VAG packets, the last packet having a terminating bit set). At the very end of the .RAW file is a terminating pair of bytes, FE FF.
:'''N.B.''' It appears that SOUND.RAW is loaded in its entirely when a level starts, whereas sound data from a level's SOUND.SEP is (re)loaded on-demand. Accordingly, SOUND.RAW typically contains short recurrent sounds (gunshots, impacts, footsteps, hurt sounds, etc). Permanent storage is not decided based on size alone, though: for example, the rather long SNDDheliflyby2 (6 seconds) is stored in SOUND.RAW, whereas the much shorter SNCCconsole-locked (1.5 seconds long) is stored in SOUND.SEP.
:'''N.B.''' A level's SOUND.DAT and SOUND.SEP always start with the same 5 entries (music segments) as in LEVEL0/SOUND.DAT and LEVEL0/SOUND.SEP, including the terminating code FE FF at 0x5B7A0. Those segments, indexed as 0 through 4 (same as in LEVEL0/SOUND.DAT), do not have a corresponding SNDD in the chapter's level#_Final (i.e., the 16-bit indices of the SNDD instances in level#_Final always start at 5 except for level0_Final, which only has those five SNDDs). It would seem that the sound indices listed in SOUND.DAT need to be unique across all the loaded level files. It would also seem that the duplicated storage of level0 music in all 14 chapters is highly suboptimal, using up 5 MB of disk space, and that the LEVEL0/SOUND.* files are redundant.
:'''N.B.''' Because of how the contents of LEVEL0/SOUND.SEP is included at the start of each level's LEVEL#/SOUND.SEP, complete with the terminating FE FF code, the data for the following, level-specific sounds is shifted by two bytes, i.e., from then on the starting offsets of each sound and VAG packet look like 0x.......2 rather than 0x.......0 (this is reflected by the offsets in LEVEL#/SOUND.DAT). This terminating code in the middle of the .SEP file probably serves no purpose whatsoever (the reading routines stop whenever they encounter a terminating bit in a VAG packet).


==Exporting and importing tips==
==Exporting and importing tips==
Line 519: Line 526:
Alternatively, both the flags and the uint at 0x0E can be used to specify a custom sample rate and/or block size (either as fully custom values or as power-of-two multiplicative factors), without the need for a detailed WAVEfmt header. Still, it is probably easiest to either adhere to the standard parameters (without too many extra flags) or go fully custom and read all the parameters from a standard-compliant WAVEfmt header.
Alternatively, both the flags and the uint at 0x0E can be used to specify a custom sample rate and/or block size (either as fully custom values or as power-of-two multiplicative factors), without the need for a detailed WAVEfmt header. Still, it is probably easiest to either adhere to the standard parameters (without too many extra flags) or go fully custom and read all the parameters from a standard-compliant WAVEfmt header.


<!--
correspond to SScSoundDataFlag_PCM_Big and SScSoundDataFlag_PCM_Little. (traces of flag values found in)
;WAVEfmt/Ext header
:If the 0x00000008 flag is OFF (never used in Oni), the WAVEfmt block is completely ignored, and the engine to "format ID" field in the [[OBD:SNDD/wav|wav header]], and the precedence is as follows:
:*the format ID of the wav header is is always 2 (ADPCM), but many mods use 1 (PCM) instead.
;Padding
:The 24 bytes at the end are ''not'' part of the SNDD template. They are not loaded by the Oni engine.
The .raw data contains the actual audio sample blocks without any other headers (other than ADPCM block headers).==
and 2034 more samples in the case of 44.1 kHz mono)
and 44.1 kHz mono
and 2036 for 44.1 kHz mono.
:If the "compressed" flag (0x00000001) is enabled, the .raw audio data is assumed to be compressed with MS ADPCM, at 22.05 kHz and with a block size of 512 bytes per channel (on the Mac, IMA4 compression is used, with 34-byte blocks).
:If the "compressed" flag (0x00000001) is disabled, it would be sensible for the engine to assume standard uncompressed PCM (linear signed 16-bit), sampled at 22.05 kHz and just copy that data to the output buffer. Instead the engine merely skips the initialization of the decompression routine, but still attempts to decompress later on, finding no bytes to decode and immediately interrupting playback as if it had completed. For impulse sounds, the system merely plays no sound, and no further problems occur. However, if the sound participates in a looping permutation (randomized or not), then the engine will be stuck in endless recursion within a single tick, chaining up immediately-interrupted playbacks of the same permutation, which ends in a "Blam". The fix is either that Oni should compress any uncompressed SNDDs at runtime (and then run them through the generic decompression routine), or that the playback of uncompressed sounds should use a separate set of rules.
;IMA4 or MS ADPCM?
:In theory the engine can easily determine between MS ADPCM and IMA4, from several criteria:
:*Coarse criterion: if the raw data size is a multiple of 34 bytes, then the data is very probably in IMA4 format (although coincidence can not be ruled out completely);
:*Fine criterion: if ''every'' 34-byte block starts with a valid predictor-step pair, with the 7-bit step value falling in the 0-88 range, this makes IMA4 practically certain.
:*For sufficiently long sounds (starting at ~21 ticks or 0.35 seconds) the "duration" field can be used to validate IMA4 as well: for N samples, the "duration" being '''floor(N/367.5)''' ticks, the .raw size of a IMA4 sound will be '''34*ceil(N/64)''' bytes per channel, whereas for a standard MS ADPCM sound it will not exceed '''512*ceil(N/1012)''' per channel, i.e., about 5% smaller than for IMA4.
;Key shortcomings of the PC demo and Mac SNDDs as compared to PC retail SNDDs:
*PC demo and Mac SNDDs have a "short" .dat part that specifies only the frame count (animation length in game ticks) and number of channels (mono or stereo). The waveform data is always assumed to be sampled at 22.05 kHz, and compressed into 4-bit ADPCM (either IMA4, on Mac, or MS ADPCM, on PC). Uncompressed PCM playback is broken for PC demo (and probably for Mac as well).
*PC retail SNDDs contain a WAVE "fmt " (format) chunk that can be used to specify arbitrary compression parameters as well as a custom sample rate. In practice, though, Oni always interprets the waveforms as having a 22.05 kHz sample rate. Thus, although the 46 electric spark sounds '''ap_hit_shld''' and '''zap##''' have 44.1 kHz waveforms in PC retail versions of Oni, the actual rendering of these sounds is 2x slower/lower than intended.
;Key shortcomings of Mac SNDDs as compared to PC SNDDs (both retail and demo):
*At 22.05 kHz, the storage size of Mac SNDDs (IMA4) is about 5% larger than for PC equivalents (MS ADPCM), because of a much smaller block size in the .raw part (the smaller .dat part of Mac SNDDs doesn't help).
*Mac SNDDs have encoding/editing artifacts at the ends of looping segments (music and ambient tracks). PC SNDDs (retail or demo) have no such artifacts and allow nearly seamless playback. See [[OBD:SNDD#Looping_issues|BELOW]] for details.
;Key shortcomings of PC SNDDs (both retail and demo) as compared to Mac SNDDs:
*The large block size of PC SNDDs (1012 samples for 22.05 KHz mono or stereo, and 2036 samples for 44.1 kHz mono) is space efficient, but the tradeoff is a coarser sample resolution whenever a block spans a wide range of amplitudes (this is because of how a MS ADPCM block relies on a single "predictor" that accommodates all the waveform samples within that block).
*Custom sample rates are formally supported through the use of the WAVE "fmt " header, but do not actually work, which is frustrating.
----
===PC demo and Mac===
The Mac version and the PC demo version use a simpler format, with no support for different sample rates (all sounds are sampled at 22050 Hz).
The .raw data contains the actual audio sample blocks without any other headers (other than ADPCM block headers).
====.raw part (MS ADPCM, PC demo)====
For PC demo the .raw SNDD data is actually the same as for PC retail, but with the same short .dat header as on Mac. The ADPCM block size is 512 bytes for mono, and 1024 for stereo. The sample rate is 22.05 kHz.
====.raw part (IMA4 ADPCM, Mac)====
For an overview of the IMA ADPCM algorithm and IMA4 header (if interested), see [https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM HERE]
:The IMA4 ADPCM data has 34-byte blocks (in the case of stereo, there is an even number of such blocks, because Left and Right blocks are interleaved).
:The first two bytes of each block is a header that  set the initial predictor (upper 9 bits) and step (lower 7 bits) for decoding the block's samples. Typically they are only used only for the first block
:The other 32 bytes consist of 64 samples stored as nibbles (half-bytes). In the case of stereo, all the nibbles in a block belong to the same channel (either all Left or all Right).
:Unlike for MS ADPCM, incomplete trailing blocks (if any) are not indicated in any way: the final blocks are stored in their entirety, with no way to tell how much of it is actual data.
:For this reason, identical sounds do not have the same sample count on PC (both retail and demo) and on Mac. As an example, here are the stats for the main menu music:
{{divhide|Main menu music a.k.a. "Oni Trailer"}}
{|
|
{{Table}}
!SNDD name (and frame count)
!PC (retail and demo)
!Mac
!difference
|-
|
:'''SNDDmus_ot6'''
:415 frames = 6.9166667 seconds
:~= '''152512.5''' samples (@ 22.05 kHz)
|
:0x25B0C = 154380 = 150x1024 + 780 bytes
:= 150x1012 + 768 = '''152568''' stereo samples
:= 6.919183673469388 s (@ 22.05 kHz)
|
:0x27940 = 162112 = 4768x34 bytes
:= 2384x64 = '''152576''' stereo samples
:= 6.919546485260771 s (@ 22.05 kHz)
|
:As compared to the PC version of the SNDD,
:the Mac version has 8 extra samples at the end
:(i.e., the last 4 bytes of the last two blocks).
|-
|
:'''SNDDmus_ot7'''
:414 frames = 6.9 seconds
:~= '''152145''' samples (@ 22.05 kHz)
|
:0x25A3C = 154172 = 150x1024 + 572 bytes
:= 150x1012 + 560 = '''152360''' stereo samples
:= 6.909750566893424 s (@ 22.05 kHz)
|
:0x27874 = 161908 = 4762x34 bytes
:= 2381x64 = '''152384''' stereo samples
:= 6.910839002267574 s (@ 22.05 kHz)
|
:As compared to the PC version of the SNDD,
:the Mac version has 24 extra samples at the end
:(i.e., the last 12 bytes of the last two blocks)
|}
|}
{{divhide|end}}
By looking at the end of the Mac SNDDs (or the exported AIFF files), it can be confirmed that the extraneous samples are actually there, at the end of the last two 34-byte blocks (last Left block and last Right block), with no way to interrupt playback upon reaching these trailing samples - because they are no different from regular samples.
Also, from a careful examination of the sound stream that is actually played back by Oni in the main menu, it is clear that all the Oni engines (both Mac and PC) play back all the available data (including the "padding" of the fixed-size IMA4 blocks) before switching to the next segment. The frame count (number of game ticks) does not affect a sound's playback and is used only as an indication for subsequent sounds (e.g., for approximate cueing in [[BSL]]).
This uninterrupted playback of fixed-size IMA4 blocks is one of the aspects that impact seamless playback of sound sequences in Mac Oni (music or ambient tracks). See [[OBD:SNDD#Looping_issues|"Looping issues"]] below.
;NOTE
:Musically, the two segments of the main menu music correspond to the same duration (four bars of a 4:4 beat). However, somewhat suprisingly, the two segments don't have the same sample count - or even the same frame count (in game ticks) -, not even when comparing the two sounds on the same platform. That means that, even on PC where the playback is nearly seamless, we are actually hearing musical loops of unequal length, ending 10 milliseconds early or late, and it still sounds OK.
----
==Exporting and importing tips==
To create a wav/aif file one needs to write a file header like below and then write the contents of the raw data part.
===WAV files (from PC retail/demo SNDDs)===
*Write "RIFF"
*add the size of the part in the raw file + 70 bytes
*write "WAVE"
*write "fmt "
*write 50
*write the wav header '''(for PC demo, the wav header is ''not'' present in the .dat part, and has to be deduced)'''
*'''OPTIONAL/RECOMMENDED: compute the number of samples and add a "fact" section announcing it'''
*write "data"
*add the size of the part in the raw file '''OPTIONAL/RECOMMENDED: increase the size if the last sample block is incomplete'''
*add the raw file data '''OPTIONAL/RECOMMENDED: add padding to the last sample block if it is incomplete'''
*save it as a wav file.
[[image:sndd_wav.gif]]
{{Table}}
{{OBDth}}
{{OBDtrBK|1=Complete ADPCM wav format header (black outline)}}
{{OBDtr| 0x00 | char[4]  |FF0000| 52 49 46 46 | RIFF      | identifier for the "IBM/Microsoft RIFF" standard }}
{{OBDtr| 0x04 | int32    |FFFF00| 9C 28 00 00 | 10396    | size of the file from 0x08 to the end (<nowiki>=</nowiki> size of the .raw part + 70 bytes) }}
{{OBDtr| 0x08 | char[4]  |00FF00| 57 41 56 45 | WAVE      | identifier for the "WAVE" format }}
{{OBDtr| 0x0C | char[4]  |00FFFF| 66 6D 74 20 | "fmt "    | identifier announcing the following wav format header }}
{{OBDtr| 0x10 | int32    |FFC8C8| 32 00 00 00 | 50        | wave format header size }}
{{OBDtr| 0x14 | block[50]|FFC8C8| &nbsp;      | &nbsp;    | [[OBD:SNDD/wav|wav header]] }}
{{OBDtr| 0x46 | char[4]  |FFFFC8| 64 61 74 61 | data      | identifier announcing the following wav data }}
{{OBDtr| 0x4A | int32    |C8FFC8| 56 28 00 00 | 10326    | size of the following wav data in bytes (<nowiki>=</nowiki> size of the .raw part) }}
|}
The above is not 100% consistent with the WAVE storage rules, because it allows for a completely arbitrary "data" size. Microsoft ADPCM data is supposed to be stored as a number of fixed-size blocks (in Oni, each block is either 512 bytes for 22.05 kHz mono, or 1024 bytes for 22.05 kHz stereo and 44.1 kHz mono). Thus, according to the standard, the last block - even if incomplete - must be stored in its entirety, and the "data" size must be a multiple of the block size. In the above example, since the format is 22.05 kHz mono, the "data" size should be increased from 10326 to 10752=21x512, and 426 empty bytes should be added as padding, so that there are 21 complete data blocks.
The standard way to deal with incomplete blocks is to specify not just the data size, but the ''actual number of samples'', by adding a "fact" section to the WAVE header, like this:
{{Table}}
{{OBDth}}
{{OBDtrBK|1=Complete ADPCM wav format header}}
{{OBDtr| 0x00 | char[4]  |FF0000| 52 49 46 46 | RIFF      | identifier for the "IBM/Microsoft RIFF" standard }}
{{OBDtr| 0x04 | int32    |FFFF00| 9C 28 00 00 | 10396    | size of the file from 0x08 to the end (<nowiki>=</nowiki> size of the .raw part + 70 bytes) }}
{{OBDtr| 0x08 | char[4]  |00FF00| 57 41 56 45 | WAVE      | identifier for the "WAVE" format }}
{{OBDtr| 0x0C | char[4]  |00FFFF| 66 6D 74 20 | "fmt "    | identifier announcing the following wav format header section }}
{{OBDtr| 0x10 | int32    |FFC8C8| 32 00 00 00 | 50        | wave format header size }}
{{OBDtr| 0x14 | block[50]|FFC8C8| &nbsp;      | &nbsp;    | [[OBD:SNDD/wav|wav header]] }}
{{OBDtr| 0x46 | char[4]  |FFFFC8| 66 61 63 74 | fact      | identifier announcing the following "fact" section }}
{{OBDtr| 0x4A | int32    |FFFFC8| 04 00 00 00 | 4        | size of the following "fact" section in bytes }}
{{OBDtr| 0x4E | int32    |C8FFC8| B0 4F 00 00 | 20400    | actual number of samples (see below for calculation) }}
{{OBDtr| 0x52 | char[4]  |FFFFC8| 64 61 74 61 | data      | identifier announcing the following wav data }}
{{OBDtr| 0x56 | int32    |C8FFC8| 00 2A 00 00 | 10752    | size of the following wav data in bytes (<nowiki>=</nowiki> size of the .raw part + 426 empty bytes) }}
|}
The actual number of samples is implied from the actual data size (size of the .raw part) and [[OBD:SNDD/wav|wav header]] properties as follows:
* n_whole_blocks = floor(raw_size/block_size);    '''// EXAMPLE: floor(10326/512) = 20'''
* last_block_size = raw_size - whole_blocks*block_size;    '''// EXAMPLE: 10326 - 20x512 = 86'''
* last_block_samples = (last_block_size - 7*n_channels)*(8/bits_per_sample/n_channels) + 2;      '''// EXAMPLE: (86 - 7)*(8/4) + 2 = 160'''
* n_samples = n_whole_blocks*samples_per_block + last_block_samples;        '''// EXAMPLE: 20*1012 + 160 = 20400'''
==Looping issues==
As detailed above, ADPCM data is stored in blocks, but the actual sound data does not necessarily end exactly at the end of a block. This is true both for MS ADPCM (PC retail or demo) and IMA4 ADPCM (Mac), but is especially noticeable for the comparatively large blocks of MS ADPCM, where the padding can be as large as ~1010 samples, i.e., a ~46-millisecond silence in the case of 22.05 kHz (for IMA4, the biggest possible gap is 63 samples, or ~3 milliseconds).
===MS ADPCM===
Although the final block of a MS ADPCM SNDD file (PC retail) is stored in incomplete form (with only the actual samples and no padding), the standard decoding behavior when loading an ADPCM-compressed WAV (e.g., in a non-destructive audio program) is to assume full-sized blocks, with padding up to the end of the last block. Depending on the audio program, this can create a silence or some "bad data" at the end of the imported audio, which can be a problem if one wants to join SNDDs that are supposed to play seamlessly one after another (e.g., a musical or ambient sequence).
As a workaround, one can preprocess .wav files with some tools that can handle incomplete MS ADPCM blocks and convert to a less ambiguous format:
*For [http://sox.sourceforge.net Sox], padding is disabled by default when joining several files.
*For [https://www.ffmpeg.org/download.html FFmpeg], padding can be disabled as an optional setting.
So, you'd either join the .wav files in Sox or FFmpeg, or convert them, e.g., to uncompressed PCM, and then import them into a fancy audio tool.
As an actual solution, the .wav file should be made compliant with RIFF WAVE standards, i.e., the last block should be padded to its full size, and a "fact" section should be used to specify the actual number of samples. This is implemented in OniSplit v 0.9.###
Slight distorsions are sometimes observed near the ends of looping SNDDs (music and ambient tracks). These artifacts were likely caused by Bungie's audio tools, and can not be undone automatically. Barely noticeable, they can be healed by manually editing audio samples near the seams.
===IMA ADPCM===
====Padding====
In the case of IMA ADPCM, the padding is actually present in the stored audio, so it is impossible (both for OniSplit and for a third-party converter) to automatically trim it down to just the relevant audio data. In fact, just by looking at the Mac SNDD itself, there is no way to tell how many of the trailing samples need to be cut for a truly seamless transition (for one thing, the trailing samples are not flat zero).
As a workaround/solution, the correct sample count of a Mac SNDD can be looked up in a PC counterpart (always available, since we're only talking of music/ambients/sirens, which are neither localized nor sampled at 44.1 kHz), and then used to trim the .aif file in FFmpeg, while converting to .wav (either PCM or ADPCM). However, it's easier (and more reliable) to just grab a PC retail copy of Oni and extract the MS ADPCM sounds.
====Initial transient====
The biggest problem with seamless playback of Mac SNDDs (for music and ambient tracks) is that - even if you figure out the correct length of each segment - the waveform of each next segment does not pick up where the previous segment left (or should have left) - instead it builds up from zero over ~7 samples. This introduces about 0.3 milliseconds of silence, and an audible discontinuity in the waveform, even if the two segments are lined up properly.
The values of those initial samples is not recoverable (unlike the padding at the end of SNDDs, which can be trimmed down). Therefore, if working with sound samples extracted from Oni, it is recommended to turn to a PC version's SNDDs.
==PCM export and PC demo detection==
OniSplit v0.9.### implements export to uncompressed PCM (signed 16-bit linear) from both the PC retail and the Mac SNDD format: use '''-extract:pcm''' instead of either '''-extract:wav''' or '''-extract:aif'''. As compared to ADPCM, linear PCM is a much more straightforward format (almost human readable), and makes it easier to analyze artifacts.
Since the only difference between PC demo and Mac is the actual storage format of SNDD files (in the .raw part), and the template checksum is the same, OniSplit has no way of determining which ADPCM algorithm to use, other than by actually scanning and validating the data as IMA4 (or not). Starting with OniSplit v0.9.###, this automatic check is implemented, allowing both '''-extract:wav''' and '''-extract:pcm''' on PC-demo SNDDs. It is very unlikely that any Mac SNDDs will be falsely identified as MS ADPCM (or, rather, invalidated as IMA4). If it ever happens, do as instructed by the following warning: ''"PC-demo MS ADPCM detected; use '''-nodemo''' flag to treat as IMA4."''
Note that transcoding (between IMA4 and MS ADPCM) and encoding is not implemented at this point. So '''-extract:aif''' will not work on PC SNDDs, '''-extract:wav''' will not work on Mac SNDDs, and '''-create''' will only work on sound files that use the correct codec and sample rate supported by the PC retail/demo or Mac Oni engine.
Importing SNDDs for PC demo (with a short .dat part and MS ADPCM in the .raw part) is also not implemented yet. Last but not least, PC retail apparently supports uncompressed PCM sounds, but they need to be tested in the engine first. Possibly ADPCM encoding/transcoding will be implemented at some point, too.
-->


==Notes==
==Notes==
<references/>
<references/>
----
----
{{OBD_File_Footer | type=SNDD | prev=QTNA | next=StNA | name=Sound Data | family=Generic}}
{{OBD_File_Footer | type=SNDD | prev=QTNA | next=StNA | name=Sound Data | family=General}}


{{OBD}}
{{OBD}}