-
Notifications
You must be signed in to change notification settings - Fork 0
File Format: SND and SMP
.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.
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.
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 |
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 |
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 |
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.
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 |
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 |
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 |
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 |
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.
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.