Actions

Nitro Composer File Specification

From Retro CDN

Revision as of 09:31, 7 July 2022 by Ccawley2011 (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Logo-txt.svg
This is a copy of an "unofficial" document containing original research, for use as a source on Retro CDN. This page likely exists for historical purposes - the contents should ideally be copy-edited and wikified to make better use of Retro CDN's software.
Original source: http://web.archive.org/web/20210614044355/https://sites.google.com/site/kiwids/

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

  • 0.1 Terminology
  • 0.2 NDS Standard File Header

1. SDAT File Format

  • 1.1 Header
  • 1.2 Symbol Block
  • 1.3 Info Block
  • 1.4 FAT
  • 1.5 File Block

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 Nitro Composer packs various types of sound files in a single file (*.sdat) for use in DS games. Not all games involve the Nitro Composer. But it seems that it is very popular for creation of DS game music.

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:

s8	1 byte	// signed char
u8 	1 byte	// unsigned char
s16	2 byte	// signed short
u16	2 byte	// unsigned short
s32	4 byte	// signed long
u32	4 byte	// unsigned long

0.2 NDS Standard File Header

Many files (besides sound-related) found in DS game rom share this header structure:

typedef struct tagNdsStdFile {
	s8  type[4];	// i.e. 'SDAT' or 'SBNK' etc...
	u32 magic;	// 0x0100feff or 0x0100fffe
	u32 nFileSize;	// Size of this file ( include this structure )
	u16 nSize;	// Size of this structure ( always 16 )
	u16 nBlock;	// Number of Blocks
} NDSSTDF;

The magic value can be 0x0002feff or 0x0001feff for non sound-related files.


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 {
		s8  type[4];   // 'SDAT'
		u32 magic;	// 0x0100feff
		u32 nFileSize;
		u16 nSize;
		u16 nBlock;    // usually 4, but some have 3 only ( Symbol Block omitted )
	} file;
	u32 nSymbOffset;  	// offset of Symbol Block = 0x40
	u32 nSymbSize;    	// size of Symbol Block
	u32 nInfoOffset; 	// offset of Info Block
	u32 nInfoSize;    	// size of Info Block
	u32 nFatOffset;   	// offset of FAT
	u32 nFatSize;     	// size of FAT
	u32 nFileOffset; 	// offset of File Block
	u32 nFileSize;   	// size of File Block
	u8  reserved[16]; 	// unused, 0s
} SDATHEADER;

1.2 Symbol Block

It appears at offset 0x40, right after the Header. It may be omitted. 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'
	u32 nSize;		// size of this Symbol Block
	u32 nRecOffset[8];	// offset of Records (note below)
	u8  reserved[24];	// unused, 0s
} 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 (for music)
1SEQARCSequence Archive (for sound effect)
2BANKSound Bank
3WAVEARCWave Archive
4PLAYER*Player (Group-related)
5GROUPGroup of SEQ/SEQARC/BANK/WAVEARC
6PLAYER2*Player2 (Stream-related)
7STRMStream
* Records 4 and 5 do not appear in SMAP file. A SMAP File is generated by the Nitro Composer listing all sound files in the SDAT file. An example can be found from <<Zoids Saga DS - Legend of Arcadia>>


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
{
	u32 nCount;		// No of entries in this record
	u32 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
{
	u32 nCount;			// No of entries in this record
	struct {
		u32 nEntryOffset;	// Offset of this Group's symbol
		u32 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;
int         i, j;
char        *szSymbol;
...
<font color="green">// access record 0 'SSEQ'</font>
SDATSYMBREC *symb_rec = (SDATSYMBREC *) ( (u8 *)symb + symb->RecOffset[0] );

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

<font color="green">// access record 1 'SSAR'</font>
SDATSYMBREC2 symb_rec2 = (SDATSYMBREC *)( (u8  *)symb + symb->RecOffset[1] );

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

	SDATSYMBREC *symb_subrec = (SDATSYMBREC *) ( (u8 *)symb + symb_rec2->Group[i].nSubRecOffset );
	for ( j = 0; j < symb_subrec->nCount; j++ )
	{
		// print out sub record's symbols
		szSymbol = (char *) ( (u8 *)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 a collection of Sequence 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'
	u32 nSize;             // size of this Info Block
	u32 nRecOffset[8];     // offset of a Record
	u8  reserved[24];       // unused, 0s
} 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
{
	u32 nCount;            // No of entries in this record
	u32 nEntryOffset[1];   // array of offsets of each entry
} SDATINFOREC;


1.3.2 Info Block - Entry

Record 0 "SEQ" - The Info Entry for SEQ contains playback information.

typedef struct tagSDATInfoSseq
{
	u16 fileID;	// for accessing this file
	u16 unknown;
	u16 bnk;	// Associated BANK
	u8  vol;	// Volume
	u8  cpr;
	u8  ppr;
	u8  ply;
	u8  unknown[2];
} SDATINFOSSEQ;



Record 1 "SEQARC"

typedef struct tagSDATInfoSsar
{
	u16 fileID;
	u16 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
{
	u16 fileID;
	u16 unknown;
	u16 wa[4];      // Associated WAVEARC. 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
{
	u16 fileID;
	u16 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
{
	u8  unknown;
	u8  padding[3];
	u32 unknown2;
} SDATINFOPlayer;

Remarks: None



Record 5 "GROUP"

typedef struct tagSDATInfoPlayer
{
	u32 nCount;		// number of sub-records
        struct {		// array of Group
		u32 type;
		u32 nEntry;
	} Group[1];
} SDATINFOPlayer;

Remarks: SDATINFOPlayer::Group::type can be one of the following values. nEntry is the entry number in the relevant Record (SEQ/SEQARC/BANK/WAVEARC).

ValueType
0x0700SEQ
0x0803SEQARC
0x0601BANK
0x0402WAVEARC




Record 6 "PLAYER2"

typedef struct SDATInfoPlayer2
{
	u8  nCount;
	u8  v[16];		// 0xff if not in use
	u8  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
{
	u16 fileID;		// for accessing the file
	u16 unknown;
	u8  vol;		// volume
	u8  pri;
	u8  ply;
	u8  reserved[5];
} SDATINFOSTRM;

Remarks: 'ply' means play?, 'pri' means priority?

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 '
	u32 nSize;		// size of the FAT
	u32 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
{
	u32 nOffset;		// offset of the sound file
	u32 nSize;		// size of the Sound file
	u32 reserved[2];	// always 0s, for storing data in runtime.
} 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'
	u32 nSize;    // size of this block
	u32 nCount;   // Mumber of sound files
	u32 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'
		u32 magic;	// 0x0100feff
		u32 nFileSize;	// Size of this SSEQ file
		u16 nSize;	// Size of this structure = 16
		u16 nBlock;	// Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA'
		u32 nSize;		// Size of this structure = nFileSize - 16
		u32 nDataOffset;	// Offset of the sequence data = 0x1c
		u8  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 (middle C is 60). Each quartet note has a fixed tick length of 48. Tempo in the range of 1..240 BPM (Default is 120). The SSEQ will not be played correctly if tempo higher than 240.

The SEQ player uses Arm7's Timer1 for timing. The Arm7's 4 Timers runs at 33MHz (approximately 2^25). The SEQ player sets Timer1 reload value to 2728, prescaler to F/64. So on about every 0.0052 sec (64 * 2728 / 33MHz) the SEQ Player will be notified ( 1 cycle ). As a quartet note has fixed tick value of 48, the highest tempo that SEQ Player can handle is 240 BPM ( 60 / (0.0052 * 48) ).

During each cycle, the SEQ player adds the tempo value to a variable. Then it checks if the value exceeds 240. If it does, the SEQ player subtracts 240 from the variable, and process the SSEQ file. Using this method, the playback is not very precise but the difference is too small to be noticed.

Take an example with tempo = 160 BPM, the SSEQ file is processed twice in 3 notifications.

cyclevariableaction
10Add 160
2160Add 160
3320Subtract 240, process once, add 160
4240Subtract 240, process once, add 160
5160Add 160
6320Subtract 240, process once, add 160
7240Subtract 240, process once, add 160
8160Add 160

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. Must be in the beginning of the first track to work. 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 operations / comparions. Affect how SSEQ is to be played.
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 {
	u32 nOffset;		// relative offset of the archived SEQ file, absolute offset = nOffset + SSAR::nDataOffset
	u16 bnk;		// bank
	u8  vol;		// volume
	u8  cpr;		// channel pressure 
	u8  ppr;		// polyphonic pressure
	u8  ply;		// play
	u8  reserved[2];
} SSARREC;

typedef struct tagSsar
{
	struct tagNdsStdFile {
		char type[4];   // 'SSAR'
		u32 magic;	// 0x0100feff
		u32 nFileSize; // Size of this SSAR file
		u16 nSize;     // Size of this structure = 16
		u16 nBlock;    // Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA'
		u32 nSize;		// Size of this structure
		u32 nDataOffset;	// Offset of data
		u32 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
{
	u8  fRecord;	// can be either 0, 1..4, 16 or 17
	u16 nOffset;	// absolute offset of the data in file
	u8  reserved;	// must be zero
} SBNKINS;

typedef struct tagSbnk
{
	struct tagNdsStdFile {
		char type[4];   // 'SBNK'
		u32 magic;	// 0x0100feff
		u32 nFileSize;	// Size of this SBNK file
		u16 nSize;	// Size of this structure = 16
		u16 nBlock;	// Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA'
		u32 nSize;		// Size of this structure
		u32 reserved[8];	// reserved 0s, for use in runtime
		u32 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 Rate	1 byte	// 0..127
	Decay Rate	1 byte	// 0..127
	Sustain Level	1 byte	// 0..127
	Release Rate	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 Rate	1 byte
	Decay Rate	1 byte
	Sustain Level	1 byte
	Release Rate	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 Rate	1 byte
	Decay Rate	1 byte
	Sustain Level	1 byte
	Release Rate	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 Rate	1 byte
	Decay Rate	1 byte
	Sustain Level	1 byte
	Release Rate	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 Rate', 'Decay Rate', 'Sustain Level' and 'Release Rate' (all have a value in range [0..127])

amplitude (%)

100% |    /\
     |   /  \__________
     |  /              \
     | /                \
0%   |/__________________\___ time (sec)

Imagine how the amplitude of a note varies from begin to the end.

The graph above shows the amplitude envelope when a note is sound. The y-axis is Amplitude, x-asix is time.

Attack rate determines how fast the note reaches 100% amplitude. (See the first upward curve). Thus the highest value 127 means the sound reaches 100% amplitude in the shortest time; 0 means the longest time.

Decay rate determines how fast the amplitude decays to 0% amplitude. Of course the sound will not drop to 0% but stops at sustain level. (See the first downward curve). Thus the highest value 127 means the sound reachs the sustain level in the shortest time; 0 means the longest time.

Sustain level determines the amplitude at which the sound sustains. (See the horizonal part). Thus the highest value 127 means the sound sustains at 100% amplitude (no decay), while 0 means 0% (full decay).

Release rate determines how fast the amplitude drops from 100% to 0%. Not from sustain level to 0%. (See the second downward curve). The value has the same meaning as Decay rate.

See this file for more details on how to interpret the articulation data. The raw data column is the transformed value used for calculation.

The SEQ Player treats 0 as the 100% amplitude value and -92544 (723*128) as the 0% amplitude value. The starting ampltitude is 0% (-92544).

During the attack phase, in each cycle, the SSEQ Player calculates the new amplitude value: amplitude value = attack rate * amplitude value / 255. The attack phase stops when amplitude reaches 0.

The times column shows how many cycles are needed to reach 100% amplitude value.

The sec column shows the corresponding time needed to reach 100% amplitude value.

The scale column is the corresponding value to feed in DLS Bank.

During the decay phase, in each cycle, the SSEQ Player calculates the new amplitude value: amplitude value = amplitude value - decay rate. Note the starting amplitude value is 0. The decay phase stops when amplitude reaches sustain level.

The other columns are self-explanatory.

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
{
	u8  nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
	u8  bLoop;		// Loop flag = TRUE|FALSE
	u16 nSampleRate;	// Sampling Rate
	u16 nTime;		// (ARM7_CLOCK / nSampleRate) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991 E +7]
	u16 nLoopOffset;	// Loop Offset (expressed in words (32-bits))
	u32 nNonLoopLen;	// Non Loop Length (expressed in words (32-bits))
} SWAVINFO;

// Swav file format
typedef struct tagSwav
{
	struct tagNdsStdFile {
		char type[4];   // 'SWAV'
		u32 magic;	// 0x0100feff
		u32 nFileSize;	// Size of this SWAV file
		u16 nSize;	// Size of this structure = 16
		u16 nBlock;	// Number of Blocks = 1
	} file;
	struct {
		char type[4];	// 'DATA'
		u32 nSize;	// Size of this structure
		SWAVINFO info;	// info about the sample
		u8  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'
		u32 magic;	// 0x0100feff
		u32 nFileSize;	// Size of this SWAR file
		u16 nSize;	// Size of this structure = 16
		u16 nBlock;	// Number of Blocks = 1
	} file;
	struct {
		char type[4];		// 'DATA'
		u32 nSize;		// Size of this structure
		u32 reserved[8];	// reserved 0s, for use in runtime
		u32 nSample;		// Number of Samples 
	} data;
	u32 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 mono/stereo wave file (PCM8, PCM16 or ADPCM).

typedef struct tagSTRM
{
	struct tagNdsStdFile {
		char type[4];   // 'STRM'
		u32 magic;	// 0x0100feff
		u32 nFileSize;	// Size of this STRM file
		u16 nSize;	// Size of this structure = 16
		u16 nBlock;	// Number of Blocks = 2
	} file;
	struct {
		char type[4];		// 'HEAD'
		u32 nSize;		// Size of this structure
		u8  nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
		u8  bLoop;		// Loop flag = TRUE|FALSE
		u8  nChannel;		// Channels
		u8  unknown;		// always 0
		u16 nSampleRate;	// Sampling Rate (perhaps resampled from the original) 
		u16 nTime;		// (1.0 / rate * ARM7_CLOCK / 32) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991e7]
		u32 nLoopOffset;	// Loop Offset (samples) 
		u32 nSample;		// Number of Samples 
		u32 nDataOffset;	// Data Offset (always 68h)
		u32 nBlock;		// Number of Blocks 
		u32 nBlockLen;		// Block Length (Per Channel) 
		u32 nBlockSample;	// Samples Per Block (Per Channel)
		u32 nLastBlockLen;	// Last Block Length (Per Channel)
		u32 nLastBlockSample;	// Samples Per Last Block (Per Channel)
		u8  reserved[32];	// always 0
	} head;
	struct {
		char type[4];		// 'DATA'
		u32 nSize;		// Size of this structure
		u8  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)