OBD:SNDD/aif
Outside of Oni, the IMA4 codec is used for storing audio in .aif files, as one of the formats of the AIFC standard, which itself fits into a FORM container (a.k.a. "EA IFF 85").
Inside Oni, the AIFC storage format is completely stripped down: the raw content from the SSND section (i.e., the actual IMA4 blocks) is stored in the .raw part of Oni's SNDD, and the .dat part of the SNDD is filled in with basic storage parameters from SSND and COMM (channel count and either data size or number of frames).
No AIFC format other than "ima4" seems to be supported by Oni, therefore we are only documenting "ima4".
ima4 as stored in AIFC
Below is the header of one of Oni's sounds (mono) stored as an .aif file. Note the Big Endian order.
Offset | Type | Raw Hex | Value | Description |
---|---|---|---|---|
0x00 | char[4] | 46 4F 52 4D | FORM | identifier for the "EA IFF 85" standard |
0x04 | int32 | 00 00 2A 90 | 10896 | size of the file from 0x08 to the end (= size of the raw IMA4 data + 50 bytes) |
0x08 | char[4] | 41 49 46 43 | AIFC | identifier for the "AIFC" format (compressed aif file) |
0x0C | char[4] | 43 4F 4D 4D | COMM | identifier announcing the following aif format header (COMM stands for "common") |
0x10 | block[26] | COMM data (a.k.a. AIFC format header, similar to a WAVE file's "fmt"; see below) | ||
0x2A | char[4] | 53 53 4E 44 | SSND | identifier announcing the following aif data (actual stream + stream size + 2 more parameters) |
0x2E | int32 | 00 00 2A 66 | 10854 | size of the following SSND data (= size of the raw IMA4 data + 8 bytes)
|
0x32 | int32 | 00 00 00 00 | 0 | "offset"; determines where the first sample in the data starts; typically zero |
0x36 | int32 | 00 00 00 00 | 0 | "block size"; used in conjunction with offset for block-aligning data; typically zero |
Here is the detail of the format header at 0x10:
Offset | Type | Raw Hex | Value | Description |
---|---|---|---|---|
0x10 | int32 | 00 00 00 16 | 22 | size of the following COMM data (AIFC format settings) |
0x14 | int16 | 00 01 | 1 | number of channels (1 = mono) |
0x16 | int32 | 00 00 01 3F | 319 | number of frames: |
0x1A | int16 | 00 10 | 16 | bits per sample |
0x1C | float80 | 40 0D AC... | 22050 | samples per second (a.k.a. "frequency" or "sample rate")
(it's an 80 bit "IEEE Standard 754" floating point number) |
0x26 | char[4] | 69 6D 61 34 | ima4 | compression type |
Oni's support of IMA4 is limited to what you see above: 16 bits per sample, 22050 Hz sample rate, "ima4" compression type. The number of channels can be either 1 (mono) or 2 (stereo).
At 0x3A start the IMA4 frames, each of them 34 bytes long and coding for 64 samples. Here is a view of a stereo .aif file (based on Oni's SNDDalarm_loop.aif), showing how the IMA4 frames are organized.
Offset | Type | Raw Hex | Value | Description |
---|---|---|---|---|
0x00 | char[4] | 46 4F 52 4D | FORM | identifier for the "EA IFF 85" standard |
0x04 | int32 | 00 01 16 4E | 71246 | size of the file from 0x08 to the end (= size of the raw IMA4 data + 50 bytes) |
0x08 | char[4] | 41 49 46 43 | AIFC | identifier for the "AIFC" format (compressed aif file) |
0x0C | char[4] | 43 4F 4D 4D | COMM | identifier announcing the following aif format header (COMM stands for "common") |
0x10 | int32 | 00 00 00 16 | 22 | size of the following COMM data (AIFC format settings) |
0x14 | int16 | 00 02 | 2 | number of channels (2 = stereo) |
0x16 | int32 | 00 00 04 17 | 1047 | number of frames: |
0x1A | int16 | 00 10 | 16 | bits per sample |
0x1C | float80 | 40 0D AC... | 22050 | samples per second (a.k.a. "frequency" or "sample rate")
(it's an 80 bit "IEEE Standard 754" floating point number) |
0x26 | char[4] | 69 6D 61 34 | ima4 | compression type |
0x2A | char[4] | 53 53 4E 44 | SSND | identifier announcing the following aif data (actual stream + stream size + 2 more parameters) |
0x2E | int32 | 00 01 16 24 | 71204 | size of the following SSND data (= size of the raw IMA4 data + 8 bytes)
|
0x32 | int32 | 00 00 00 00 | 0 | "offset"; determines where the first sample in the data starts; typically zero |
0x36 | int32 | 00 00 00 00 | 0 | "block size"; used in conjunction with offset for block-aligning data; typically zero |
0x3A | block[34] | 00 00 07 ... | 1st L frame | (thin blue outline) the frame header is 0x0000, the first two nibbles are 0x0 and 0x7 |
0x5C | block[34] | 00 00 FF ... | 1st R frame | (thin red outline) the frame header is 0x0000, the first two nibbles are 0xF and 0xF |
0x7E | block[34] | 00 0D A0 ... | 2nd L frame | (thick blue outline) the frame header is 0x000D, the first two nibbles are 0xA and 0x0 |
0xA0 | block[34] | FF 96 9A ... | 2nd R frame | (thick red outline) the frame header is 0xFF96, the first two nibbles are 0x9 and 0xA |
Importing from AIFC into Oni
The frames should be copied to the .raw part of the SNDD. The size of the .raw data will be consistent with the size of the .aif file (at 0x04 above, right after FORM) or with the size of the SNDD section (at 0x2E above, right after SNDD), as well as with the number of frames (at 0x16) and channel count (at 0x14).
- For the above mono example, there are 319 frames of 34 bytes each, or 10846 bytes of .raw data (8 bytes less than the 0x2A66 size at 0x2E and 50 bytes less than the 0x2A90 size at 0x04).
- For the stereo example, there are 1047 frames of 68 bytes each (a stereo frame is the combination of a Left frame and a Right frame), or 71196 bytes (8 bytes less than 0x11624, 50 bytes less than 0x1164E).
The SNDD file in .dat receives the offset to the .raw data as well as several parameters that depend on the engine version.
Filling in Mac SNDD
Here is how the SNDD instance should look (in .dat) for the above mono example:
- The "compressed" flag is set (because IMA4 is a compressed format; however, if the flag isn't set, the Mac engine will process the data as IMA4 anyway).
- The "stereo" flag is not set (because the sound is mono).
- The .raw data size is set to 10846=0x2A5E (319 frames of 34 bytes each, as detailed above). Note the Little Endian order.
- The offset to the .raw data is 0x1B100 in this example (Little Endian as well).
- The duration is computed from the number of samples: 319 frames of 64 samples each is 20416 samples, which at 22.05 kHz corresponds to 0.92589 seconds, or 55.5537 game ticks, truncated to 55=0x37.
- The padding at the end is "DEAD" because this is a Vanilla .dat file (OniSplit pads with zeroes instead).
Here is how the SNDD instance looks (in an .oni file) for the above stereo example:
Same as above, except:
- The stereo flag is set; with the compressed flag also set, the bitset's value is 0x00000003.
- The level ID is blank and the padding is 0000 instead of DEAD, because this is an .oni file.
- The offset to the .raw data is 0x20 in this example (also because this is an .oni file).
- The .raw data size is set to 71196 (1047 frames of 68 bytes each, as detailed above).
- The duration is computed from the number of stereo samples: 1047 frames of 64 samples each is 67008 samples, which at 22.05 kHz corresponds to 3.0389 seconds, or 182.33 game ticks, truncated to 182=0xB6.
Filling in PC retail SNDD
Here is how the PC retail SNDD instance will look (in an .oni file) for the above stereo example:
All the fields can be zeroed out or filled with garbage, except:
- The "flags" bitset must be set to 0x00000004, or anything containing the "4" flag ("1" and "2" have no effect, and "8" is overridden by "4").
- The channel count (2 in this case).
- The "block size" field is used to store (somewhat counterintuitively) the number of frames (same as in the .aif file, but with Little Endian order).
- The "duration" field, with the same value as above (the sound lasts 3.0389 seconds, or 182.33 game ticks, truncated to 182=0xB6).
- The offset to the .raw data (the size of the .raw data is not required in this case; the number of frames is used instead).
Exporting from Oni to AIFC
From Mac SNDD
Start by collecting the data from the Mac SNDD. The size of the .raw data is explicitly stored at 0x10.
The channel count is either 2 or 1 depending on whether the "2" bit of the flags (at 0x08) is set: if the bitset's value is 0x00000001 then the sound is mono, if the bitset's value is 0x00000003 then the sound is stereo. Knowing the channel count, you deduce the frame count (number of 34- or 68- byte frames) by dividing the .raw size by either 34 (mono) or 68 (stereo); it should divide evenly.
Next you create an AIFC file based of the template described ABOVE and adjust it as follows (don't forget about Big Endian!).
- The SNDD's .raw data must be copied into the SSND section of the AIFC file, starting at 0x3A. Copy as-is, no byte swapping required.
- At 0x04, set the remaining file size to the size of the copied .raw data + 50 (Big Endian order!).
- At 0x2E, set the remaining SSND size to the size of the copied .raw data + 8 (Big Endian order!).
- At 0x14, fill in the channel count that you deduced from the SNDD's flags at 0x08 (Big Endian!).
- At 0x16, fill in the number of frames that you canculated from the .raw data size (Big Endian!).
That is all. Save the file and it should work.
From PC retail SNDD
In Vanilla Oni (PC retail) there are no SNDDs that use IMA4 compression, so the situation described here is not very likely. If you need to do it anyway, then you essentially need to look up two things in the .dat part of the SNDD: the channel count at 0x0E and the frame count at 0x18. The frame count multiplied by 34 and by the channel count gives you the size of the .raw part (unlike in all the other situations, the size will not necessarily be stored explicitly in the SNDD, because the engine uses the frame count instead), which allows you to retrieve the IMA4 stream from the .raw file.
Next you create an AIFC file based of the template described ABOVE and adjust it as follows (don't forget about Big Endian!).
- The SNDD's .raw data must be copied into the SSND section of the AIFC file, starting at 0x3A. Copy as-is, no byte swapping required.
- At 0x04, set the remaining file size to the size of the copied .raw data + 50 (Big Endian order!).
- At 0x2E, set the remaining SSND size to the size of the copied .raw data + 8 (Big Endian order!).
- At 0x14, fill in the channel count (same as 0x0E in the SNDD, but swapped to Big Endian).
- At 0x16, fill in the number of frames (same as 0x18 in the SNDD, but swapped to Big Endian).
That is all. Save the file and it should work.