Nomad Sound Format

From ModdingWiki
Jump to navigation Jump to search
Nomad Sound Format
Format typeSound
HardwarePCM
Number of sounds1
Sampling rate7042 Hz
Channel count1
Bits per sample4
Compressed?Yes
Tags?
Games

The Nomad Sound Format is used by all the audio files in the game Nomad. It is headerless, single-channel delta PCM audio with 4 bits per sample, and intended for playback at 7042 Hz. This unusual playback rate is the result of the time constant divisor math that is used by the Sound Blaster DSP programming interface.

The format was reverse engineered by cmb.

Delta table

Embedded in the game's main executable (NOMAD.EXE) is the table of delta PCM values required to decode the audio. This table is made up of fifteen sections, with each being sixteen bytes long. In Nomad v1.01, it is stored in the main executable at offset 29705h, which becomes seg060:1055 at runtime. The table is reproduced below.

/**
 * Table of delta PCM subtables. Each subtable is one row of 16 values, where the first value is unused.
 * These values are derived from an exponential transfer function.
 */
const int8_t Audio::s_deltaTable[240] =
{
  0, -7,    -6,    -5,    -4,    -3,    -2,    -1,    0, 1,    2,    3,    4,    5,    6,    7,
  0, -0x0A, -8,    -6,    -5,    -3,    -2,    -1,    0, 1,    2,    3,    5,    6,    8,    0x0A,
  0, -0x10, -0x0D, -0x0A, -8,    -6,    -4,    -2,    0, 2,    4,    6,    8,    0x0A, 0x0D, 0x10,
  0, -0x17, -0x13, -0x0F, -0x0B, -8,    -5,    -2,    0, 2,    5,    8,    0x0B, 0x0F, 0x13, 0x17,
  0, -0x1E, -0x18, -0x13, -0x0F, -0x0A, -7,    -3,    0, 3,    7,    0x0A, 0x0F, 0x13, 0x18, 0x1E,
  0, -0x26, -0x1F, -0x18, -0x12, -0x0D, -8,    -4,    0, 4,    8,    0x0D, 0x12, 0x18, 0x1F, 0x26,
  0, -0x2E, -0x25, -0x1D, -0x16, -0x10, -0x0A, -5,    0, 5,    0x0A, 0x10, 0x16, 0x1D, 0x25, 0x2E,
  0, -0x36, -0x2C, -0x23, -0x1A, -0x13, -0x0C, -6,    0, 6,    0x0C, 0x13, 0x1A, 0x23, 0x2C, 0x36,
  0, -0x3F, -0x33, -0x28, -0x1F, -0x16, -0x0E, -7,    0, 7,    0x0E, 0x16, 0x1F, 0x28, 0x33, 0x3F,
  0, -0x49, -0x3B, -0x2F, -0x23, -0x19, -0x10, -8,    0, 7,    0x10, 0x19, 0x23, 0x2E, 0x3A, 0x48,
  0, -0x53, -0x43, -0x35, -0x28, -0x1D, -0x12, -9,    0, 9,    0x12, 0x1C, 0x28, 0x35, 0x43, 0x52,
  0, -0x5D, -0x4B, -0x3C, -0x2D, -0x20, -0x14, -0x0A, 0, 0x0A, 0x14, 0x20, 0x2D, 0x3B, 0x4B, 0x5C,
  0, -0x68, -0x54, -0x43, -0x33, -0x24, -0x17, -0x0B, 0, 0x0B, 0x17, 0x24, 0x32, 0x42, 0x54, 0x67,
  0, -0x74, -0x5E, -0x4A, -0x38, -0x28, -0x19, -0x0C, 0, 0x0C, 0x19, 0x28, 0x38, 0x4A, 0x5D, 0x73,
  0, -0x80, -0x68, -0x52, -0x3E, -0x2C, -0x1C, -0x0D, 0, 0x0D, 0x1C, 0x2C, 0x3E, 0x51, 0x67, 0x7F
};

Decoding DPCM audio

The DPCM encoded data is read one nibble at a time, starting with the high nibble first. When a nibble of value 0 is encountered, the following nibble is used as an index to select one of the fifteen delta tables. Otherwise, when a nonzero nibble is encountered, it is used as an index into the currently selected delta table, and the resultant delta value is then added to the last byte written to this output. (This signed 8-bit value is allowed to wrap.) This becomes the next byte to write to the output. At the start of decoding, the "last value" is considered to be 80h.

When the nibble sequence 0 F is encountered, this cannot be used to set the delta table index because the value F is beyond the index range. Instead, this sequence is interpreted as a command to repeat the last byte that was written to the output. To get the repeat count, the next two nibbles are read and combined into a single 8-bit value (regardless of whether they shared the same byte in the input stream.) If this value is 0, it is interpreted as a repeat count of 256. After repeating the last output byte this number of times, decoding continues with reading the next nibble from the input.

After decoding, the stream may be played back as 8-bit unsigned single-channel PCM audio.

Example input sequence: 03 40 F0 41

  • Read nibble 0, which indicates the start of a command sequence.
  • Read nibble 3. This is less than F, so it is used to select the fourth section of the delta table.
  • Read nibble 4. Because this is not zero, it is used to select a single delta value from the currently selected delta table. In this case, the delta is -Bh. This delta is added to the last output value (which we assume to be 80h since this is the start of the stream) to create the next output value of 75h. This byte is written to the output stream.
  • Read nibble 0, which indicates the start of a command sequence.
  • Read nibble F. This is the byte-repeat command. The next two nibbles are read and combined into a single 8-bit value (04h) that is used as the repeat count. The last byte written to the output (75h) is repeated to the output this number of times.
  • Read nibble 1. This is another delta value selection. We are still using the same delta table section selected by the first two nibbles in this input stream, and the delta value at position 1 in that section happens to be -17h. This is added to 75h to compute the next output value of 5Eh

This example input stream generates the following output: 75 75 75 75 75 5E