Nitro Composer File Specification
From Retro CDN
Revision as of 09:24, 7 July 2022 by Ccawley2011 (talk | contribs) (Created page with "{{unofficialDoc|source=http://web.archive.org/web/20210614044355/https://sites.google.com/site/kiwids/}} <h1>Nitro Composer File (*.sdat) Specification</h1> <h2>Current stat...")
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/ |
Contents
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.1 Terminology
- 0.2 NDS Standard File Header
- 1.1 Header
- 1.2 Symbol Block
- 1.3 Info Block
- 1.4 FAT
- 1.5 File Block
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 Name | Description |
0 | SEQ | Sequence (for music) |
1 | SEQARC | Sequence Archive (for sound effect) |
2 | BANK | Sound Bank |
3 | WAVEARC | Wave Archive |
4 | PLAYER* | Player (Group-related) |
5 | GROUP | Group of SEQ/SEQARC/BANK/WAVEARC |
6 | PLAYER2* | Player2 (Stream-related) |
7 | STRM | Stream |
* 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).
Value | Type |
0x0700 | SEQ |
0x0803 | SEQARC |
0x0601 | BANK |
0x0402 | WAVEARC |
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.
cycle | variable | action |
1 | 0 | Add 160 |
2 | 160 | Add 160 |
3 | 320 | Subtract 240, process once, add 160 |
4 | 240 | Subtract 240, process once, add 160 |
5 | 160 | Add 160 |
6 | 320 | Subtract 240, process once, add 160 |
7 | 240 | Subtract 240, process once, add 160 |
8 | 160 | Add 160 |
2.2 Events
Status Byte | Parameter | Description |
0xFE | 2 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. |
0x93 | 4 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 .. 0x7f | Velocity: 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. |
0x80 | Duration: Variable Length | REST. It tells the SSEQ-sequencer to wait for a certain tick. Usually it is a multiple of 3. |
0x81 | Bank & Program Number: Variable Length | bits[0..7] is the program number, bits[8..14] is the bank number. Bank change is seldomly found, so usually bank 0 is used. |
0x94 | Destination 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. |
0x95 | Call 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. |
0xFD | NONE | RETURN. The SSEQ will return to the caller's address + 4 (a Call event is 4 bytes in size). |
0xA0 .. 0xBf | See loveemu's sseq2mid for more details. | Some arithmetic operations / comparions. Affect how SSEQ is to be played. |
0xC0 | Pan Value: 1 byte [0..127], middle is 64 | PAN |
0xC1 | Volume Value: 1 byte [0..127] | VOLUME |
0xC2 | Master Volume Value: 1 byte [0..127] | MASTER VOLUME |
0xC3 | Value: 1 byte [0..64] (Add 64 to make it a MIDI value) | TRANSPOSE (Channel Coarse Tuning) |
0xC4 | Value: 1 byte | PITCH BEND |
0xC5 | Value: 1 byte | PITCH BEND RANGE |
0xC6 | Value: 1 byte | TRACK PRIORITY |
0xC7 | Value: 1 byte [0: Poly, 1: Mono] | MONO/POLY |
0xC8 | Value: 1 byte [0: Off, 1: On] | TIE (unknown) |
0xC9 | Value: 1 byte | PORTAMENTO CONTROL |
0xCA | Value: 1 byte [0: Off, 1: On] | MODULATION DEPTH |
0xCB | Value: 1 byte | MODULATION SPEED |
0xCC | Value: 1 byte [0: Pitch, 1: Volume, 2: Pan] | MODULATION TYPE |
0xCD | Value: 1 byte | MODULATION RANGE |
0xCE | Value: 1 byte | PORTAMENTO ON/OFF |
0xCF | Time: 1 byte | PORTAMENTO TIME |
0xD0 | Value: 1 byte | ATTACK RATE |
0xD1 | Value: 1 byte | DECAY RATE |
0xD2 | Value: 1 byte | SUSTAIN RATE |
0xD3 | Value: 1 byte | RELEASE RATE |
0xD4 | Count: 1 byte (how many times to be looped) | LOOP START MARKER |
0xFC | NONE | LOOP END MARKER |
0xD5 | Value: 1 byte | EXPRESSION |
0xD6 | Value: 1 byte | PRINT VARIABLE (unknown) |
0xE0 | Value: 2 byte | MODULATION DELAY |
0xE1 | BPM: 2 byte | TEMPO |
0xE3 | Value: 2 byte | SWEEP PITCH |
0xFF | NONE | EOT: 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)