Skip to content

File Format: SND and SMP

SalsaGal edited this page Oct 6, 2024 · 4 revisions

.snd/.smp is a proprietary format developed by Crystal Dynamics. Its purpose is to store sequences, custom samples, instrument attributes, etc... Very similar concept to Sony's format (.SEQ/.VH/.VB/.VAB) developed for the PlayStation, or in broader terms, it's a similar concept to MIDI files with their respective SoundFonts or DownLoadable Sounds (.mid/.sf2/.dls). The format is used in Crystal Dynamics games that were developed between 1994 to 2000.

SND

Header

There are three revisions, while no official name is given we have named them:

Name Description
soul-reaver The latest revision that can be found in later Crystal Dynamics games such as "Soul Reaver" and "Walt Disney's World Quest - Magical Racing Tour".
prototype A revision that can be found in early prototypes of the game "Soul Reaver", but it's possible it can be found in other places as well.
gex An old revision that can be found in early Crystal Dynamics games such as "Gex".

Read left to right, the magic number should always read "DNSa". After loading the header, the program uses the header size as an offset into the file's main body. However since the specification uses 4-byte alignment once a section has been loaded, the number in the header size must be adjusted. If the size is not a multiple of four, increase it to the next multiple of four.

Soul Reaver Header

Name Type Endianness
Magic number ASCII character x4 N/A
Header size Unsigned 32 bit integer Little
Bank version Unsigned 32 bit integer Little
Number of programs Unsigned 32 bit integer Little
Number of zones Unsigned 32 bit integer Little
Number of waves Unsigned 32 bit integer Little
Number of sequences Unsigned 32 bit integer Little
Number of labels Unsigned 32 bit integer Little
Reverb mode Unsigned 32 bit integer Little
Reverb depth Unsigned 32 bit integer Little

Prototype Header

Name Type Endianness
Magic number ASCII character x4 N/A
Header size Unsigned 32 bit integer Little
Bank version Unsigned 16 bit integer Little
Unknown Unsigned 8 bit integer N/A
Number of programs Unsigned 8 bit integer N/A
Number of zones Unsigned 16 bit integer Little
Number of waves Unsigned 16 bit integer Little
Number of sequences Unsigned 16 bit integer Little
Number of labels Unsigned 16 bit integer Little
Reverb mode Unsigned 16 bit integer Little
Reverb depth Unsigned 16 bit integer Little

Gex Header

Name Type Endianness
Magic number ASCII character x4 N/A
Header size Unsigned 16 bit integer Little
Unknown Unsigned 8 bit integer N/A
Number of programs Unsigned 8 bit integer N/A
Number of zones Unsigned 16 bit integer Little
Number of waves Unsigned 16 bit integer Little
Number of sequences Unsigned 16 bit integer Little
Number of labels Unsigned 16 bit integer Little
Reverb mode Unsigned 16 bit integer Little
Reverb depth Unsigned 16 bit integer Little

Program Structure

The header specifies how many programs are present, this number cannot exceed 16. Each program has at most 16 zones associated with it. The volume can range from 0 to 127, with a default of 127. The panning has the same range of 0 to 127, but has a default of 64.

Name Type Endianness Range Default
Number of program zones Unsigned 16 bit integer Little 0 - 16
First zone Unsigned 16 bit integer Little
Program volume Unsigned 8 bit integer N/A 0 - 127 127
Program panning position Unsigned 8 bit integer N/A 0 - 127 64
Unused Unsigned 16 bit integer Little

A program can sometimes request more zones when there are no more left. In this case the program should be treated as if it has zero zones.

Zone Structure

The header also specifies the maximum zone count.

Banks are used to collect multiple instrument patches together, with each bank number being associated with a set of patches. This is a problem because the "Parent program" entry does not know this and only functions as a patch index. When this index returns to 0, it marks the beginning of a new bank.

Name Type Endianness Range Default
Priority Unsigned 8 bit integer Little
Parent program Unsigned 8 bit integer Little
Zone volume Unsigned 8 bit integer Little 0 - 127 127
Zone panning position Unsigned 8 bit integer Little 0 - 127 64
Root key Unsigned 8 bit integer Little 0 - 127 60
Pitch finetuning Unsigned 8 bit integer Little
Lowest key Unsigned 8 bit integer Little 0 - 127 127
Highest key Unsigned 8 bit integer Little Lowest key - 127 127
Mode Unsigned 8 bit integer Little
Maximum pitch bending range Unsigned 8 bit integer Little
ADSR 1 Unsigned 16 bit integer Little
ADSR 2 Unsigned 16 bit integer Little
Wave index Unsigned 16 bit integer Little

Wave Offsets

These are the offsets to where the waves/samples are stored, the number is specified by the header. Sometimes these are relative, and sometimes absolute, to deal with this take the first offset from the list and subtract all other values from that to get an offset from the beginning of the list:

1200 -> 0000
1220 -> 0020
1235 -> 0035

The reason for the confusion is because the numbers sometimes refer to absolute memory addresses in the PlayStation's SPU buffer, rather than offsets into the file. The offsets are all relative to the beginning of the smp body, so make sure to load the smp header before using them.

Name Type Endianness
Wave offsets Unsigned 32 bit integer array Little

Sequence Offsets

The number of sequences is specified in the header. The offsets are relative to where the first sequence appears in the snd file; the first offset should always be 0.

Name Type Endianness
Sequence offsets Unsigned 32 bit integer array Little

Label Offsets

The number of labels is specified in the header, note the usage is currently a little unclear.

Name Type Endianness
Label offsets Unsigned 32 bit integer array Little

Sequences

After loading all of the label offsets you should arrive at the first sequence of the snd file, this is where the relative sequence offsets are used.

Each sequence can have two possible magic numbers:

  • QESa, a "Type 0" sequence. When we export it we give it the extension cds, standing for "Crystal Dynamics Sequence". seq isn't used since it is already a real format used for PlayStation sequences.
  • QSMa, a "Type 1" sequence. The extension msq is used which resembles the acronym found in the magic number.

After loading these, the snd file should be complete.

SMP

Sometimes the magic number is absent, you cannot tell ahead of time if it will be present, not even the SND revision will help with 100% certainty. If present the number should be "PMSa", if it is missing, use the first four bytes for the body size and continue with the header being only 4 bytes long.

If extracting from a PlayStation game, the body will contain Sony's proprietary ADPCM, also known as VAG or SONY_4BIT_ADPCM. If from another console you may find other codecs.

Name Type Endianness
Magic number ASCII character x4 N/A
Body size Unsigned 32 bit integer Little

The body continues for the rest of the file after the header.