Nitro Composer File (*.sdat) Specification

Current status

This spec is far from completion. And it may contains error. Use it at your own risk.

23 June 2007 SBNK update

20 June 2007 SSEQ Events

6 June 2007 SBNK + general update

23 May 2007 First published

For enquiries please contact me at "kiwi.ds AT gmail.com"

Acknowledgement

Many thanks to the following persons whose works I have studied:

Crystal - the author of CrystalTile2.exe

loveemu - the author of sseq2mid.exe, swave2wave.exe & strm2wave.exe

Nintendon - the author of ndssndext.exe

DJ Bouche - the author of sdattool.exe

VGMTrans - the author of VGMTrans.exe

Tables of Contents

0. Introduction

1. SDAT File Format

2. SSEQ File Format

3. SSAR File Format

4. SBNK File Format

5. SWAV File Format

6. SWAR File Format

7. STRM File Format

0. Introduction

"The DS SDK has all the tools in it to convert MIDI files to the DS format, and has text file templates to define the soundbanks." CptPiard from VGMix

The SDAT file is used by Nitro Composer to pack various types of sound files in a single file for use in NDS rom. Not all rom has a SDAT file. But it seems that SDAT file is very popular among the NDS game developers.

Inside the SDAT you will find: SSEQ (Sequence), SSAR (Sequence Archive), SBNK (Sound Bank), SWAR (Wave Archive), STRM (Stream).

SSAR is a collection of SSEQ, while SWAR is a collection of SWAV.

0.1 Terminology

File format is explained in C-style struct declaration. The following types of variable are used (as used for Win32 programming):
char	1 byte	// signed char
BYTE 	1 byte	// unsigned char
short	2 byte	// signed short
WORD	2 byte	// unsigned short
int	4 byte	// signed int
UINT	4 byte	// unsigned int
long	4 byte	// signed long
DWORD	4 byte	// unsigned long

0.2 NDS Standard File Header

Many files found in NDS rom share this header structure:
typedef struct tagNdsStdFile {
	char type[4];   // i.e. 'SDAT' or 'SBNK' ...
	UINT magic;	// 0x0100feff or 0x0100fffe
	UINT nFileSize; // Size of this file (include this structure)
	WORD nSize;     // Size of this structure = 16
	WORD nBlock;    // Number of Blocks
} NDSSTDF;

1. SDAT File Format

The file has the following structure:
	--------------------------------
	|            Header            |
	--------------------------------
	|         Symbol Block         |
	--------------------------------
	|          Info Block          |
	--------------------------------
	|  File Allocation Table (FAT) |
	--------------------------------
	|          File Block          |
	--------------------------------

1.1 Header

The Header appears at offset 0 in the SDAT file. All offsets in this structure are absolute offsets.
typedef struct tagSDATHeader
{
	struct tagNdsStdFile {
		char type[4];   // 'SDAT' = 0x54414453
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SDAT file (include this structure)
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks, usually 4, but some have 3 only
	} file;
	UINT nSymbOffset;  // offset of Symbol Block = 0x40
	UINT nSymbSize;    // size of Symbol Block
	UINT nInfoOffset;  // offset of Info Block
	UINT nInfoSize;    // size of Info Block
	UINT nFatOffset;   // offset of FAT
	UINT nFatSize;     // size of FAT
	UINT nFileOffset;  // offset of File Block
	UINT nFileSize;    // size of File Block
	BYTE reserved[16]; // unused, 0s
} SDATHEADER;

1.2 Symbol Block

It appears appears at offset 0x40, right after the Header. It contains the symbols (or "filenames") of each sound file in the SDAT file. All offsets are relative to this block's starting address (i.e. 0x40).
NB. Some files doesn't have Symbol block.
NB. The value of nSize of Symbol Block may not be 32-bit aligned. However, the value of nSymbsize in Header is.
typedef struct tagSDATSymbol
{
	char type[4];       // 'SYMB' = 0x424D5953
	UINT nSize;         // size of this Symbol Block
	UINT nRecOffset[8]; // offset of a Record (note below)
	UINT reserved[6];   // unused, zeros
} SDATSYMB;

1.2.1 Symbol Block - Record

There are a total of 8 records in the Symbol Block. They are:
Record No.Record NameDescription
0SEQSequence
1SEQARCSequence Archive
2BANKBank
3WAVEARCWave Archive
4PLAYER*Player
5GROUPGroup
6PLAYER2*Player2
7STRMStream
* Records 4 and 5 do not appear in SMAP file

All offsets are relative to Symbol block's starting address (i.e. 0x40). Each record (except Record 1 "SEQARC") has the following structure:
typedef struct tagSDATSymbolRec
{
	UINT nCount;		// No of entries in this record
	UINT nEntryOffset[1];	// array of offsets of each entry
} SDATSYMBREC;
For Record 1 (SEQARC), it is a group which contains sub-records. The sub-record is of the same structure as SDATSYMBREC (above). Record 1 has the following structure:
typedef struct tagSDATSymbolRec2
{
	UINT nCount;			// No of entries in this record
	struct {
		UINT nEntryOffset;	// offset of this Group's symbol
		UINT nSubRecOffset;	// offset of the sub-record
	} Group[1];			// array of offsets of each entry
} SDATSYMBREC2;
Below is an example to access these records:
SDATSYMB *symb;
SDATSYMBREC *symb_rec;
int i, j;
char *szSymbol;
...
// access record 0 'SSEQ'
symb_rec = (SDATSYMBREC *) ( (BYTE *)symb + symb->RecOffset[0] );

for (i = 0; i < symb_rec->nCount; i++)
{
	// print out the symbol
	szSymbol = (char *) ( (char *)symb + symb_rec->nEntryOffset[i] );
	printf("%s\n", szSymbol);
}
...

SDATSYMBREC2 symb_rec2;
// access record 1 'SSAR'
symb_rec2 = (SDATSYMBREC *)( (BYTE *)symb + symb->RecOffset[1] );

for (i = 0; i < symb_rec2->nCount; i++)
{
	szSymbol = (char *) ( (char *)symb + symb_rec2->Group[i].nEntryOffset );
	printf("%s\n", szSymbol);

	SDATSYMBREC *symb_subrec = (SDATSYMBREC *) ( (BYTE *)symb + symb_rec2->Group[i].nSubRecOffset );
	for (j = 0; j < symb_subrec->nCount; j++)
	{
		// print out sub record's symbols
		szSymbol = (char *) ( (char *)symb + symb_subrec->nEntryOffset[i] );
		printf("%s\n", szSymbol);
	}
}

1.2.2 Symbol Block - Entry

EXCEPT for Record 1 "SEQARC", an Entry in the record is a null terminated string. This corresponds to the "filename" of a sound file in the SDAT file.

For Record 1 "SEQARC", since a SEQARC file is an archive of one or more of SEQ files, therefore this record contains a sub-record. And this sub-record contains the symbols ("filenames") of each of the archived SEQ files.

1.3 Info Block

The Info Block appears just after the Symbol Block. It contains some information of each sound file in the SDAT file. All offsets are relative to this block's starting address.
typedef struct tagSDATInfo
{
	char type[4];           // 'INFO' = 0x4F464E49
	UINT nSize;             // size of this Info Block = Header.nInfoSize
	UINT nRecOffset[8];     // offset of a Record
	UINT reserved[6];       // unused, zeros
} SDATINFO;

1.3.1 Info Block - Record

There are a total of 8 records in the Info Block. The Record Names in 1.2.1 above applies here as well. All offsets are relative to Info block's starting address. With modifications, the code example above could be used to access the Info records and entries.
typedef struct tagSDATInfoRec
{
	UINT nCount;            // No of entries in this record
	UINT nEntryOffset[1];   // array of offsets of each entry
} SDATINFOREC;

1.3.2 Info Block - Entry

Each record has its own structure of entry. Please see below.

Record 0 "SEQ"
typedef struct tagSDATInfoSseq
{
	WORD fileID;
	WORD unknown;
	WORD bnk;
	BYTE vol;
	BYTE cpr;
	BYTE ppr;
	BYTE ply;
	BYTE unk1;
	BYTE unk2;
} SDATINFOSSEQ;
Remarks: Once I thought fileID should be 32bits in size. But I saw in the SDAT file of San DS which contains 0x00ff in the high words ...

Record 1 "SEQARC"
typedef struct tagSDATInfoSsar
{
	WORD fileID;
	WORD unknown;
} SDATINFOSSAR;
Remarks: no info is available for SEQARC files. The info of each archived SEQ is stored in that SEQARC file.

Record 2 "BANK"
typdef struct tagSDATInfoBank
{
	WORD fileID;
	WORD unknown;
	WORD wa[4];      // 0xffff if not in use
}
Remarks: Each bank can links to up to 4 WAVEARC files. The wa[4] stores the WAVEARC entry number.

Record 3 "WAVEARC"
typedef struct tagSDATInfoSwar
{
	WORD fileID;
	WORD unknown;
} SDATINFOSwar;
Remarks: This is not a new structure. It is the same as SDATINFOSSAR above for Record 1.

Record 4 "PLAYER"
typedef struct tagSDATInfoPlayer
{
	BYTE unknown[8]; // nothing is known yet...
} SDATINFOPlayer;
Remarks: None

Record 5 "GROUP"
typedef struct tagSDATInfoPlayer
{
	UINT nCount;     // number of sub-records
        struct {         // array of Group
		UINT type;
		UINT nEntry;
	} Group[1];
} SDATINFOPlayer;
Remarks: SDATINFOPlayer::Group::type can be one of the following values. nEntry is the entry number in the relevant Record (0-3).
ValueType
0x0700SEQ
0x0803SEQARC
0x0601BANK
0x0402WAVEARC


Record 6 "PLAYER2"
typedef struct SDATInfoPlayer2
{
	BYTE nCount;
	BYTE v[16];         // 0xff is not in use
	BYTE reserved[7];   // padding, 0s
} SDATINFOPLAYER2;
Remarks: The use is unknown. The first byte states how many of the v[16] is used (non 0xff).

Record 7 "STRM"
typedef struct SDATInfoStrm
{
	WORD fileID;
	WORD unknown;
	BYTE vol;
	BYTE pri;
	BYTE ply;
	BYTE reserved[5];
} SDATINFOSTRM;
Remarks: 'vol' means volume, 'ply' means play?, 'pri' means priority (I guess)

1.4 FAT

The FAT appears just after the Info Block. It contains the records of offset and size of each sound file in the SDAT file.
typedef struct tagSDATFAT
{
	char type[4];       // 'FAT ' = 0x20544146
	UINT nSize;         // size of the FAT
	UINT nCount;        // Number of FAT records
	SDATFATREC Rec[1];  // Arrays of FAT records
} SDATFAT;

1.4.1 FAT - Record

It contains the offset and size of the sound file. All the offsets are relative to the SDAT Header structure's beginning address.
typedef struct tagSDATFATREC
{
	UINT nOffset;     // offset of the sound file
	UINT nSize;       // size of the Sound file
	BYTE reserved[8]; // always 0s
} SDATFATREC;

1.5 File Block

The File Block is the last block and appears just after the FAT. It has a small header (the structure below) which contains the total size and number of sound of files. All the sound files are stored after this structure.
typedef struct tagSDATFILE
{
	char type[4];  // 'FILE' = 0x454C4946
	UINT nSize;    // size of this block
	UINT nCount;   // number of sound files
	UINT reserved; // always 0
} SDATFILE;

2. SSEQ File Format

SSEQ stands for "(Sound) Sequence". It is a converted MIDI sequence. Linked to a BANK for instruments.
typedef struct tagSseq
{
	struct tagNdsStdFile {
		char type[4];   // 'SSEQ'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SSEQ file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA' = 0x41544144
		UINT nSize;		// Size of this structure = nFileSize - 16
		UINT nDataOffset;	// Offset of the sequence data = 0x1c
		BYTE data[1];		// Arrays of sequence data
	} data;
} SSEQ;

NB. For the details of the SSEQ file, please refer to loveemu's sseq2mid

2.1 Description

The design of SSEQ is more programming-oriented while MIDI is hardware-oriented. In MIDI, to produce a sound, a Note-On event is sent to the midi-instrument and then after a certain time, a Note-Off is sent to stop the sound (though it is also acceptable to send a Note-On message with 0 velocity). In SSEQ, a sound is produced by one event only which carries with data such as note, velocity and duration. So the SSEQ-sequencer knows exactly what and how to play and when to stop.

A SSEQ can have at maximum 16 tracks, notes in the range of 0..127. Each quartet note has a fixed tick length of 48.

2.2 Events

Status Byte Parameter Description
0xFE2 bytes
It indicates which tracks are used. Bit 0 for track 0, ... Bit 15 for track 15. If the bit is set, the corresponding track is used.
Indication begin of multitrack. A series of event 0x93 follows.
0x934 bytes
1st byte is track number [0..15]
The other 3 bytes are the relative adress of track data. Add nDataOffset (usually 0x1C) to find out the absolute address.
SSEQ is similar to MIDI in that track data are stored one after one track. Unlike mod music.
0x00 .. 0x7fVelocity: 1 byte [0..127]
Duration: Variable Length
NOTE-ON. Duration is expressed in tick. 48 for quartet note. Usually it is NOT a multiple of 3.
0x80Duration: Variable LengthREST. It tells the SSEQ-sequencer to wait for a certain tick. Usually it is a multiple of 3.
0x81Bank & Program Number: Variable Lengthbits[0..7] is the program number, bits[8..14] is the bank number. Bank change is seldomly found, so usually bank 0 is used.
0x94Destination Address: 3 bytes (Add nDataOffset (usually 0x1C) to find out the absolute address.)JUMP. A jump must be backward. So that the song will loop forever.
0x95Call Address: 3 bytes (Add nDataOffset (usually 0x1C) to find out the absolute address.)CALL. It's like a function call. The SSEQ-sequncer jumps to the address and starts playing at there, until it sees a RETURN event.
0xFDNONERETURN. The SSEQ will return to the caller's address + 4 (a Call event is 4 bytes in size).
0xA0 .. 0xBfSee loveemu's sseq2mid for more details.Some arithmetic / compare operations. Not playback related.
0xC0Pan Value: 1 byte [0..127], middle is 64PAN
0xC1Volume Value: 1 byte [0..127]VOLUME
0xC2Master Volume Value: 1 byte [0..127]MASTER VOLUME
0xC3Value: 1 byte [0..64] (Add 64 to make it a MIDI value)TRANSPOSE (Channel Coarse Tuning)
0xC4Value: 1 bytePITCH BEND
0xC5Value: 1 bytePITCH BEND RANGE
0xC6Value: 1 byteTRACK PRIORITY
0xC7Value: 1 byte [0: Poly, 1: Mono]MONO/POLY
0xC8Value: 1 byte [0: Off, 1: On]TIE (unknown)
0xC9Value: 1 bytePORTAMENTO CONTROL
0xCAValue: 1 byte [0: Off, 1: On]MODULATION DEPTH
0xCBValue: 1 byteMODULATION SPEED
0xCCValue: 1 byte [0: Pitch, 1: Volume, 2: Pan]MODULATION TYPE
0xCDValue: 1 byteMODULATION RANGE
0xCEValue: 1 bytePORTAMENTO ON/OFF
0xCFTime: 1 bytePORTAMENTO TIME
0xD0Value: 1 byteATTACK RATE
0xD1Value: 1 byteDECAY RATE
0xD2Value: 1 byteSUSTAIN RATE
0xD3Value: 1 byteRELEASE RATE
0xD4Count: 1 byte (how many times to be looped)LOOP START MARKER
0xFCNONELOOP END MARKER
0xD5Value: 1 byteEXPRESSION
0xD6Value: 1 bytePRINT VARIABLE (unknown)
0xE0Value: 2 byteMODULATION DELAY
0xE1BPM: 2 byteTEMPO
0xE3Value: 2 byteSWEEP PITCH
0xFFNONEEOT: End Of Track


3. SSAR File Format

SSAR stands for "(Sound) Sequence Archive". It is a collection of sequences (used mainly for sound effect). Therefore, each archived SSEQ is usually short, with one or two notes.
typedef struct tagSsarRec {
	int nOffset;		// relative offset of the archived SEQ file, absolute offset = nOffset + SSAR::nDataOffset
	WORD bnk;		// bank
	BYTE vol;		// volume
	BYTE cpr;		// channel pressure 
	BYTE ppr;		// polyphonic pressure
	BYTE ply;		// play
	BYTE reserved[2];
} SSARREC;

typedef struct tagSsar
{
	struct tagNdsStdFile {
		char type[4];   // 'SSAR'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SSAR file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA' = 0x41544144
		UINT nSize;		// Size of this structure
		UINT nDataOffset;	// Offset of data
		UINT nCount;		// nCount * 12 + 32 = nDataOffset
		SSARREC Rec[1];		// nCount of SSARREC
	} data;
} SSAR;

NB. Archived SSEQ files are not stored in sequence (order). So Rec[0].nOffset may point to 0x100 but Rec[1].nOffset points to 0x40.

NB. Archived SSEQ files cannot be readily extracted from SSAR file because data in one SSEQ may 'call' data in other SSEQ.

4. SBNK File Format

SBNK stands for "Sound Bank". A bank is linked to up to 4 SWAR files which contain the samples. It define the instruments by which a SSEQ sequence can use. You may imagine SSEQ + SBNK + SWAR are similar to module music created by trackers.

typedef struct tagSbnkInstrument
{
	BYTE fRecord;	// can be either 0, 1..3, 16 or 17
	WORD nOffset;	// absolute offset of the data in file
	BYTE reserved;
} SBNKINS;

typedef struct tagSbnk
{
	struct tagNdsStdFile {
		char type[4];   // 'SBNK'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SBNK file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA' = 0x41544144
		UINT nSize;		// Size of this structure
		BYTE reserved[32];	// reserved, 0s
		UINT nCount;		// number of instrument
		SBNKINS Ins[1];	
	} data;
} SBNK;

So, after SBNK::data, there come SBNK::data::nCount of SBNKINS. After the last SBNKINS, there will be SBNK::data::nCount of instrument records. In each instrument records, we can find one or more wave/note definitions.

4.1 Instrument Record

If SBNKINS::fRecord = 0, it is empty. SBNKINS::nOffset will also = 0.

If SBNKINS::fRecord < 16, the record is a note/wave definition. I have seen values 1, 2 and 3. But it seems the value does not affect the wave/note definition that follows. Instrument record size is 16 bytes.

	swav number 	2 bytes	// the swav used
	swar number	2 bytes	// the swar used. NB. cross-reference to "1.3.2 Info Block - Entry, Record 2 BANK" 
	note number	1 byte 	// 0..127
	Attack Time	1 byte	// 0..127
	Decay Time	1 byte	// 0..127
	Sustain Level	1 byte	// 0..127
	Release Time	1 byte	// 0..127
	Pan		1 byte	// 0..127, 64 = middle

If SBNKINS::fRecord = 16, the record is a range of note/wave definitions. The number of definitions = 'upper note' - 'lower note' + 1. The Instrument Record size is 2 + no. of definitions * 12 bytes.

	lower note	1 byte 	// 0..127
	upper note	1 byte 	// 0..127

	unknown		2 bytes	// usually == 01 00
	swav number 	2 bytes	// the swav used
	swar number	2 bytes	// the swar used. 
	note number	1 byte
	Attack Time	1 byte
	Decay Time	1 byte
	Sustain Level	1 byte
	Release Time	1 byte
	Pan		1 byte

	...
	...
	...

	unknown		2 bytes	// usually == 01 00
	swav number 	2 bytes	// the swav used
	swar number	2 bytes	// the swar used. 
	note number	1 byte
	Attack Time	1 byte
	Decay Time	1 byte
	Sustain Level	1 byte
	Release Time	1 byte
	Pan		1 byte

For example, lower note = 30, upper note = 40, there will be 40 - 30 + 1 = 11 wave/note definitions.
The first wave/note definition applies to note 30.
The second wave/note definition applies to note 31.
The third wave/note definition applies to note 32.
...
The eleventh wave/note definition applies to note 40.

If SBNKINS::fRecord = 17, the record is a regional wave/note definition.

	The first 8 bytes defines the regions. They divide the full note range [0..127] into several regions (max. is 8)
	An example is:
	25  35  45  55  65  127 0   0 (So there are 6 regions: 0..25, 26..35, 36..45, 46..55, 56..65, 66..127)
	Another example:
	50  59  66  83  127 0   0   0 (5 regions: 0..50, 51..59, 60..66, 67..84, 85..127)

	Depending on the number of regions defined, the corresponding number of wave/note definitions follow:

	unknown		2 bytes	// usually == 01 00
	swav number 	2 bytes	// the swav used
	swar number	2 bytes	// the swar used. 
	note number	1 byte	
	Attack Time	1 byte
	Decay Time	1 byte
	Sustain Level	1 byte
	Release Time	1 byte
	Pan		1 byte
	...
	...

	In the first example, for region 0..25, the first wave/note definition applies.
	For region 26..35, the 2nc wave/note definition applies.
	For region 36..45, the 3rd wave/note definition applies.
	... 
	For region 66..127, the 6th wave/note definition applies.

REMARKS: Unknown bytes before wave/defnition definition = 5, not 1 in stage_04_bank.sbnk, stage_04.sdat, Rom No.1156

4.2 Articulation Data

The articulation data affects the playback of the SSEQ file. They are 'Attack Time', 'Decay Time', 'Sustain Level', 'Release Time' and 'Pan'.

I have studied the output values in DLS bank file produced by VGMTrans. See this file for more details.

5. SWAV File Format

SWAV doesn't appear in SDAT. They may be found in the ROM elsewhere. They can also be readily extracted from a SWAR file (see below).

// info about the sample
typedef struct tagSwavInfo
{
	BYTE nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
	BYTE bLoop;		// Loop flag = TRUE|FALSE
	WORD nSampleRate;	// Sampling Rate
	WORD nTime;		// (ARM7_CLOCK / nSampleRate) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991e7]
	WORD nLoopOffset;	// Loop Offset (expressed in words (32-bits))
	UINT nNonLoopLen;	// Non Loop Length (expressed in words (32-bits))
} SWAVINFO;

// Swav file format
typedef struct tagSwav
{
	struct tagNdsStdFile {
		char type[4];   // 'SWAV'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SWAV file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA'
		UINT nSize;		// Size of this structure
		SWAVINFO info;		// info about the sample
		BYTE data[1];		// array of binary data
	} data;
} SWAV;

6. SWAR File Format

SWAR stands for "(Sound) Wave Archive". It is a collection of mono wave (SWAV) samples only (which can be in either PCM8, PCM16 or ADPCM compression).
typedef struct tagSwar
{
	struct tagNdsStdFile {
		char type[4];   // 'SWAR'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this SWAR file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];	// 'DATA'
		UINT nSize;	// Size of this structure
		BYTE reserved[0x20];
		UINT nSample;	// Number of Samples 
	} data;
	UINT nOffset[1];	// array of offsets of samples
} SWAR;

NB. After the array of offsets, the binary samples follow. Each sample has a SWAVINFO structure before the sample data. Therefore, it is easy to make a SWAV from the samples in SWAR.

7. STRM File Format

STRM stands for "Stream". It is an individual wave file (PCM8, PCM16 or ADPCM).
typedef struct tagSTRMHeader
{
	char type[4];   // 'STRM'
	BYTE magic[4];  // 0xFF 0xFE 0x00 0x01
	UINT nFileSize; // Size of this file
	WORD nSize;     // Size of this structure = 0x10
	WORD nChunk;    // Number of Chunks, always 2
} STRMHEADER;
typedef struct tagSTRMHead
{
	struct tagNdsStdFile {
		char type[4];   // 'STRM'
		UINT magic;	// 0x0100feff
		UINT nFileSize; // Size of this STRM file
		WORD nSize;     // Size of this structure = 16
		WORD nBlock;    // Number of Blocks = 2
	} file;
	struct {
		char type[4];		// 'HEAD'
		UINT nSize;		// Size of this structure
		BYTE nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
		BYTE bLoop;		// Loop flag = TRUE|FALSE
		BYTE nChannel;		// Channels
		BYTE unknown;		// always 0
		WORD nSampleRate;	// Sampling Rate (perhaps resampled from the original) 
		WORD nTime;		// (1.0 / rate * ARM7_CLOCK / 32) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991e7]
		UINT nLoopOffset;	// Loop Offset (samples) 
		UINT nSample;		// Number of Samples 
		UINT nDataOffset;	// Data Offset (always 68h)
		UINT nBlock;		// Number of Blocks 
		UINT nBlockLen;		// Block Length (Per Channel) 
		UINT nBlockSample;	// Samples Per Block (Per Channel)
		UINT nLastBlockLen;	// Last Block Length (Per Channel)
		UINT nLastBlockSample;	// Samples Per Last Block (Per Channel)
		BYTE reserved[32];	// always 0
	} head;
	struct {
		char type[4];		// 'DATA'
		UINT nSize;		// Size of this structure
		BYTE data[1];		// Arrays of wave data
	} data;
} SDATSTRM;

7.1 Wave Data

A Block is the same as SWAV Wave Data.
Mono (SWAV)
Block 1
Block 2
...
Block N (Last Block)

Stereo (STRM)
Block 1 L
Block 1 R
Block 2 L
Block 2 R
...
Block N L (Last Block)
Block N R (Last Block)