DSE SWDL Format
General Information
SWDL containers are used to contain sample and programs/presets information for any accompanying SEDL or SMDL files.
They can be used in a few ways.
- To accompany a SMDL, and contain both the samples it uses and the data for the programs it uses.
- To accompany a SMDL, and contain only the program/preset data while referring to a main sample bank for the samples it uses.
- As a sample bank.
When used in the second manner, it allows the game to only load the samples it actually uses from the main bank, and it keeps redundancy to a minimum. SWDLs also seems to override some of the data they contains. So, if a sample has its rootkey set to 60 in the main bank, and it was set to 80 in another SWDL referring to it, the rootkey actually used after loading that last SWDL would be 80. This works with a lot more parameters however.
File Structure
The file format is based around chunks, a bit like the RIFF file format. There doesn't seem to be a particular order to the chunks other than the header and the eod chunk.
Overview
Offset | Length | Name | Description |
---|---|---|---|
0x0 | 80 | SWDLHeader | The container's header. |
- | Varies | wavi Chunk | Contains details on all the samples contained, or referred to by the SWDL container. |
- | Varies | prgi Chunk | The prgi chunk contains the programs/presets used by the SMDL files. (It may be omitted in SWDL purely for storing sample data.) |
- | Varies | kgrp Chunk | The kgrp chunk contains information on every keygroups used in the SWDL by the programs/presets. (It may be omitted in SWDL purely for storing sample data.) |
- | Varies | pcmd Chunk | The pcmd chunk contains the raw sample data for every samples contained in the file. (It may be omitted if the SWDL refers to a main bank for its sample data.) |
- | 16 | End of Data Chunk | This empty chunk marks the end of the SWDL container. |
SWDL Header
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | magicn | The 4 characters "swdl" {0x73, 0x77, 0x64, 0x6C} |
0x04 | 4 | - | unk18 | 4 bytes of zeroes. |
0x08 | 4 | uint32 | flen | File length in bytes. |
0x0C | 2 | uint16 | version? | Version number? ( 0x1504 ) |
0x0E | 1 | uint8 | unk1 | Unknown. |
0x0F | 1 | uint8 | unk2 | Unknown. |
0x10 | 4 | - | unk3 | 4 bytes of zeroes. |
0x14 | 4 | - | unk4 | 4 bytes of zeroes. |
0x18 | 2 | uint16 | year | Year the file was last modified. |
0x1A | 1 | uint8 | month | Month the file was last modified. |
0x1B | 1 | uint8 | day | Day the file was last modified. |
0x1C | 1 | uint8 | hour | Hour the file was last modified. |
0x1D | 1 | uint8 | minute | Minute the file was last modified. |
0x1E | 1 | uint8 | second | Second the file was last modified. |
0x1F | 1 | uint8 | centisecond? | Could possibly be the centisecond that the file was last modified. |
0x20 | 16 | char[16] | fname | Filename, ASCII null terminated string. Any extra space after the 0 on the total 16 bytes, is padded with 0xAA. |
0x30 | 4 | uint32 | unk10 | Always 0x00AA AAAA |
0x34 | 4 | uint32 | unk11 | 4 bytes of zeroes. |
0x38 | 4 | uint32 | unk12 | 4 bytes of zeroes. |
0x3C | 4 | uint32 | unk13 | So far always 0x10 |
0x40 | 4 | uint32 | pcmdlen | Length of "pcmd" chunk if there is one. If not, is null! If set to 0xAAAA0000 (The 0000 may contains something else), the file refers to samples inside an external "pcmd" chunk, inside another SWDL ! |
0x44 | 4 | uint32 | unk14 | 4 bytes of zeroes. |
0x46 | 2 | uint16 | nbwavislots | Numbers of sample pointer slots, empty or not, in the "wavi" chunk's "WavTable". |
0x48 | 2 | uint16 | nbprgislots | Numbers of presets pointer slots , empty or not, in the "prgi" chunk's "TablA". |
0x4A | 2 | uint16 | unk17 | Unknown |
0x4C | 4 | uint32 | wavilen | Length of "wavi" chunk. |
wavi Chunk
The wavi chunk contains information on all the samples. Its what links the prgi chunk to the sample data within the local or external pcmd chunk. Its made up of two main parts:
- A pointer table, with a slot for every samples used globally.
- A table of sample information.
Each non-null pointers in the first table points to a single sample information entry in the second table. Null pointers indicate unused, or missing samples, depending on the context.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | label | "wavi" {0x77, 0x61, 0x76, 0x69} |
0x04 | 2 | uint16 | unk1 | Always 0. |
0x06 | 2 | uint16 | unk2 | Always 0x1504. |
0x08 | 4 | uint32 | chunkbeg | Seems to always be 0x10, possibly the start of the chunk data. |
0x0C | 4 | uint32 | chunklen | Length of the chunk data. Begins counting right after this field |
0x10 | Varies | - | WabTable | Array containing 2 bytes offsets from the beginning offset of WavTable to an entry in the SampleInfoTbl table! It may be null. |
After WavTable | 0-15 | - | Padding Bytes | 0xAA padding bytes to align the next part on 16 bytes. |
After Padding | varies | - | SampleInfoTbl | This table contains details on each sample entries in the "WavTable". |
WavTable
The table of pointers to the sample info. Each pointers is 16 bits, and may be null. The nb of entries is set in the SWDL header.
SampleInfoTbl
The table made up of sample info entries. Each entries are 64 bytes, thus no padding is ever needed between entries.
Here's the format of a sample info entry:
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 2 | uint16 | unk1 | Entry marker? Always 0x01AA. |
0x02 | 2 | uint16 | ID | Index number from WavTable. Empty/null entries in WavTable are counted! |
0x04 | 2 | uint16 | unk2 | Unknown. Affect sample's tuning. |
0x06 | 2 | uint16 | rootkey | The MIDI note associated to the sample. (The note that the instrument sampled is playing) |
0x08 | 2 | uint16 | unk4 | Always 0x7F40. |
0x0A | 2 | uint16 | unk5 | Always 0x0002. |
0x0C | 2 | uint16 | unk6 | Always 0x0000. |
0x0E | 2 | - | unk7 | 0xAA padding. |
0x10 | 2 | uint16 | version? | Always 0x1504. |
0x12 | 2 | uint16 | smplfmt | Sample format.
|
0x14 | 1 | uint8 | unk9 | Often 0x09 |
0x15 | 1 | uint8 | smplloop | Flag indicating whether the sample should be looped or not ! (1 = looped, 0 = not looped) |
0x16 | 2 | uint16 | unk10 | Often 0x0108 |
0x18 | 2 | uint16 | unk11 | Often 0004. |
0x1A | 2 | uint16 | unk12 | Often 0x0101. |
0x1C | 4 | - | unk13 | Often 0x0000 0000. |
0x20 | 4 | uint32 | smplrate | Sample rate in hertz. |
0x24 | 4 | uint32 | smplpos | The offset of the sound sample in the "pcmd" chunk when there is one. Otherwise, possibly offset of the exact sample among all the sample data loaded in memory? (The value usually doesn't match the main bank's) |
0x28 | 4 | uint32 | loopbeg | The position in bytes divided by 4, the loop begins at, from smplpos. ( multiply by 4 to get size in bytes ) Adding loopbeg + looplen gives the sample's length ! (For ADPCM samples, the 4 bytes preamble is counted in the loopbeg!) |
0x2C | 4 | uint32 | looplen | The length of the loop in bytes, divided by 4. ( multiply by 4 to get size in bytes ) Adding loopbeg + looplen gives the sample's length ! |
0x30 | 1 | uint8 | unk17 | Unknown. Usually 0x1 |
0x31 | 1 | uint8 | unk18 | Unknown. Usually 0x1 |
0x32 | 1 | uint8 | unk19 | Unknown. Usually 0x1 |
0x33 | 1 | uint8 | unk20 | Unknown. Usually 0x3 |
0x34 | 2 | uint16 | unk21 | Unknown. Usually 0x03FF ( Little endian -253) |
0x36 | 2 | uint16 | unk22 | Unknown. Usually 0xFFFF |
0x38 | 2 | uint16 | unk23 | Unknown. Usually 0x0000 |
0x3A | 2 | uint16 | unk24 | Unknown. Usually 0x007F (little endian) |
0x3C | 2 | uint16 | unk25 | Unknown. Usually 0x007F (little endian) |
0x3E | 2 | uint16 | unk26 | Unknown. Usually 0x28FF (little endian) |
prgi Chunk
The prgi chunk contains programs/presets that the SMDL music sequences use as instrument presets in their tracks. Its made up of :
- A table of pointers to all the programs info entries. Some may be null.
- A table containing program info entries.
The pointer table works in the exact same way as it does in the wavi chunk. 16 bits offsets from the beginning of the table to a program info entry.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | label | "prgi" {0x70, 0x72, 0x67, 0x69} |
0x04 | 2 | uint16 | unk1 | Always 0. |
0x06 | 2 | uint16 | unk2 | Always 0x1504. |
0x08 | 4 | uint32 | chunkbeg | Seems to always be 0x10, possibly the start of the chunk data. |
0x0C | 4 | uint32 | chunklen | Length of the chunk data. Begins counting right after this field |
0x10 | (nbprgislots * 2) + padding | - | ProgramPtrTbl | A table of 16 bits pointers to entries in the ProgramInfoTbl. Some may be null. It usually has 128 slots. Like General Midi. If the nb of presets were to change, its possible there would be a need for padding bytes, seeing how the wavi chunk works. |
After ProgramPtrTbl | Varies | ProgramInfo[nbprgislots] | ProgramInfoTbl | A table containing information on all the presets available in the current SWDL. |
ProgramInfoTbl
This table contains entries for every single presets available in the SWDL. Each entry is pointed to by a pointer in the ProgramPtrTbl.
It contains ProgramInfo entries:
ProgramInfo
A ProgramInfo entry is minimum 144 bytes long. Its made of 3 parts:
- The program info header.
- The LFO table.
- The split table.
The program info header contains details for identifying the preset, and the size of the LFO and split table.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 2 | uint16 | ID | Index of the pointer in "TableA" that points to this entry. Also correspond to instrument ID used in the corresponding SMDL file! |
0x02 | 2 | uint16 | nbsplits | Nb of samples mapped to this presets, in the split table. |
0x04 | 1 | int8 | prgvol | Volume of the entire program. |
0x05 | 1 | int8 | prgpan | Pan of the entire program. (0-127, 64 is middle, 127 is full right, 0 is full left ) |
0x06 | 1 | uint8 | unk3 | Most of the time 0x00. |
0x07 | 1 | uint8 | thatFbyte | Most of the time 0x0F. |
0x08 | 2 | uint16 | unk4 | Most of the time is 0x200. |
0x0A | 1 | uint8 | unk5 | Most of the time is 0x00. |
0x0B | 1 | uint8 | nblfos | Nb of entries in the LFO Table. |
0x0C | 1 | uint8 | PadByte | Most of the time is 0xAA, or 0x0. The value here is used as the delimiter and padding ! |
0x0D | 1 | uint8 | unk7 | Most of the time is 0x0. |
0x0E | 1 | uint8 | unk8 | Most of the time is 0x0. |
0x0F | 1 | uint8 | unk9 | Most of the time is 0x0. |
0x10 | (nblfos * 16) | LFOEntry[nblfos] | LFOTbl | Table that contains details on how to use the 4 LFOs with this preset. |
After LFOTbl | 16 | - | Delimiter | 16 bytes of "PadByte" padding bytes, possibly to delimit the start of the section below. Uses the value of PadByte as padding value! |
After Delimiter | (nbsplits * 48) | SplitEntry[nbsplits] | SplitsTbl | Table of samples splits mapped to this program. |
LFOEntry
These determine how to configure the 4 Low Frquency Oscillators (LFO) linked to this program. It allows to set the shape of the waveform/the function that generate the value. The output of the LFO. The frequency, the depth, the delay, the fade-out. And what might be a resonance parameter(Q) with what might possibly be a low-pass filter.
Here's the structure of an entry:
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 1 | uint8 | unk34 | Unknown, usually 0x00. It does seems to have an effect with a certain combination of other values in the other parameters. |
0x01 | 1 | uint8 | unk52 | Unknown, usually 0x00. (Posibly boolean to confirm its enabled) If is set to 0, the LFO corresponding to the entry is disabled. |
0x02 | 1 | uint8 | dest | The destination of the LFO's output.
|
0x03 | 1 | uint8 | wshape | The shape/function of the waveform. (When the LFO is disabled, its always 1)
|
0x04 | 2 | uint16 | rate | Rate at which the LFO "oscillate". May or may not be in Hertz. |
0x06 | 2? | uint16? | unk29 | Changing the value seems to induce feedback or resonance. (Or perhaps its because it ended up corrupting the sound engine state when messing with the parameter?) |
0x08 | 2 | uint16 | depth | The depth parameter of the LFO. |
0x0A | 2 | uint16 | delay | Delay in milliseconds before the LFO effect is applied after the sample begins playing. |
0x0C | 2 | uint16 | unk32 | Unknown, usually 0x0000. Possibly fade-out in milliseconds. |
0x0E | 2 | uint16 | unk33 | Unknown, usually 0x0000. Possibly an extra parameter? Or a cutoff/lowpass filter's frequency cutoff? |
SplitEntry
This represents a sample mapped to the preset. Those are played depending on certain conditions when a playnote event is received by the sequencer for that particular preset/program. Some samples may be played only for a certain range of keys or velocities for example.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 1 | - | unk10 | A leading 0. |
0x01 | 1 | uint8 | id | The Index of the sample in the SplitsTbl! |
0x02 | 1 | uint8 | unk11 | Unknown. Is always the same value as offset 0x1A below ! (Possibly labelled "B.Rn" on the DSE screenshots) |
0x03 | 1 | uint8 | unk25 | Unknown. Possibly a boolean. Possibly labelled "K.Tps" in the DSE screenshots |
0x04 | 1 | int8 | lowkey | Usually 0x00. Lowest MIDI Key this sample can play on. |
0x05 | 1 | int8 | hikey | Usually 0x7F. Highest MIDI Key this sample can play on. |
0x06 | 1 | int8 | lovel | Lowest note velocity the sample is played on.(0 - 127) |
0x07 | 1 | int8 | hivel | Highest note velocity the sample is played on.(0 - 127) |
0x08 | 1 | int8 | unk14 | Usually 0x00. |
0x09 | 1 | int8 | unk47 | Usually 7F. If smaller than 0x7F the sample won't play... |
0x0A | 2 | int16 | unk15 | Usually 0x00. |
0x0B | 1 | int8 | unk48 | Usually 0x7F. |
0x0C | 4 | - | unk16 | Usually the same value as "PadByte", or 0. Possibly padding ? |
0x10 | 2 | - | unk17 | Usually the same value as "PadByte", or 0. Possibly padding ? |
0x12 | 2 | uint16 | SmplID | The ID/index of sample in the "wavi" chunk's lookup table. |
0x14 | 1 | int8 | ftune | Possibly used to tune the sample's pitch. Fine tuning ? |
0x15 | 1 | int8 | ctunetrk | Sets coarse tuning based on a strange unit where 249 is no tuning.. Might have to do with the track's pitch bending? |
0x16 | 1 | int8 | rootkey | Note at which the sample is sampled at ! |
0x17 | 1 | int8 | ctune | Another parameter affecting pitch in a quantity similar to ctunetrk. |
0x18 | 1 | int8 | smplvol | Volume of the sample. |
0x19 | 1 | int8 | smplpan | Pan of the sample. |
0x1A | 1 | uint8 | kgrpid | Keygroup ID of the keygroup this split belongs to! |
0x1B | 1 | uint8 | unk22 | Unknown, possibly a flag. |
0x1C | 2 | uint16 | unk23 | Unknown, usually 0000. |
0x1E | 2 | - | unk24 | Usually the same value as "PadByte", or 0. Possibly padding ? |
Those last 16 bytes are a perfect copy of last 16 bytes of the sample's entry in the "wavi" chunk. | ||||
0x20 | 1 | uint8 | envon | If not == 0, the volume envelope is processed. Otherwise its ignored. |
0x21 | 1 | uint8 | envmult | If not == 0, is used as multiplier for envelope paramters, and the 16bits lookup table is used for parameter durations. If 0, the 32bits duration lookup table is used instead. This value has no effects on volume parameters, like sustain, and atkvol. |
0x22 | 1 | uint8 | unk37 | Unknown. |
0x23 | 1 | uint8 | unk38 | Unknown. |
0x24 | 2 | uint16 | unk39 | Unknown. |
0x26 | 2 | uint16 | unk40 | Unknown. |
0x28 | 1 | int8 | atkvol | Sample volume envelope Attack Level.(0 to 127) Higher values towards 0x7F means the volume at which the attack phase begins at is louder. Doesn't shorten the attack time. |
0x29 | 1 | int8 | attack | Sample volume envelope Attack.(0 to 127) Higher values towards 0x7F means the attack phase takes longer to reach full volume! 126 is ~10 seconds. |
0x2A | 1 | int8 | decay | Sample volume envelope Decay. (0 to 127) The duration the note has to be held until the volume is smoothly decreased to the value of "Sustain Volume". Higher values towards 0x7F means it takes longer before the held note's volume changes to "Sustain Volume". |
0x2B | 1 | int8 | sustain | Sample volume envelope Sustain.(0 to 127) The volume at which the held note will stay at. (Default 0x7F) |
0x2C | 1 | int8 | hold | Sample volume envelope Hold (0 to 127). Higher values towards 0x7F means the note is held at full volume longer after the attack phase. 126 is ~10 seconds. 0x7F, does the same as 0. |
0x2D | 1 | int8 | decay2 | Sample volume envelope Decay2 (0 to 127). Higher values towards 0x7F means longer fade-out. 0x7F means never fade-out. (Default 0x7F) At 0x7E, it takes ~8 seconds for the volume to reach 0. |
0x2E | 1 | int8 | release | Sample volume envelope Release parameter(0 to 127). Higher values towards 0x7F means longer release. Negative values mirror positive range. (Default is 0x28(40)) |
0x2F | 1 | int8 | unk53 | Usually 0xFF. |
kgrp Chunk
The kgrp chunk contains a list of all the keygroups in use in this SWDL. Its not known yet how keygroups work, but it appears its a set of properties that can be assigned to splits of every programs that overrides some of those splits' parameters, determine their max polyphony, their volume, their "priority" and a few other things.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | label | "kgrp" {0x6B, 0x67, 0x72, 0x70} |
0x04 | 2 | uint16 | unk1 | Always 0. |
0x06 | 2 | uint16 | unk2 | Always 0x1504. |
0x08 | 4 | uint32 | chunkbeg | Seems to always be 0x10, possibly the start of the chunk data. |
0x0C | 4 | uint32 | chunklen | Length of the chunk data. Begins counting right after this field |
0x10 | Varies | Keygroup[] | Keygroups | A table containing all the keygroups used in the SWDL. The first entry is usually the global Keygroup, of which most splits are part of. |
After Keygroups | 0 or 8 | - | Padding? | When there is an odd number of Keygroup entry, it appears there is some garbage(?) inserted here to make the next chunk start on an offset divisible by 16. |
Keygroup
The name and purpose of each was extrapolated from the screenshots of the DSE software from the official website. Its unclear what each parameters really do, and in what context, at this time.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 2 | uint16 | ID | Index/ID of the keygroup. |
0x02 | 1 | uint8 | poly | Polyphony. AKA max number of simultaneous voices. 0 to 255. |
0x03 | 1 | uint8 | priority | Priority. A value from 0 to possibly 99. Default is 8. |
0x04 | 1 | uint8 | vclow | Lowest Vc possible ? Usually between 0 to 15. |
0x05 | 1 | uint8 | vchigh | Highest Vc possible ? Usually between 0 to 15. |
0x06 | 1 | uint8 | unk50 | Unknown. |
0x07 | 1 | uint8 | unk51 | Unknown. |
pcmd Chunk
The pcmd chunk contains the sample data for every samples. Each samples is stored one after the other, regardless of the sample type or sample rate, without headers or delimiters of any sort! Each samples is located by using the sample entries in the wavi chunk.
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | label | "pcmd" {0x70, 0x63, 0x6D, 0x64} |
0x04 | 2 | uint16 | unk1 | Always 0. |
0x06 | 2 | uint16 | unk2 | Always 0x1504. |
0x08 | 4 | uint32 | chunkbeg | Seems to always be 0x10, possibly the start of the chunk data. |
0x0C | 4 | uint32 | chunklen | Length of the chunk data. Begins counting right after this field |
0x10 | Varies | - | SampleDataBlob | Contains the sample data for all the samples used in the SWDL. |
The data can be stored in any of the compatible formats:
- The NDS's 4 bits IMA ADPCM encoding (Same as official IMA ADPCM. Even uses the same tables. only the way sample values are clamped when converting back to PCM16 differ a tiny bit, which might not even be noticeable.)
- raw PCM16 samples.
- raw PCM8 samples ?
- Possibly PSG or something else ?
Note About ADPCM:
- Each ADPCM sample begins with the initial value of the "predictor" on 16 bits. Then the "step index", also on 16 bits. Afterwards, comes the actual sample data.
- The ADPCM preamble is included in the value of the "loopbeg" parameter of the sample!
Eod Chunk
This chunk marks the the end of the SWDL container/file. There is nothing past the chunk header!
Offset | Length | Type | Name | Description |
---|---|---|---|---|
0x00 | 4 | char[4] | label | "eod\20" {0x65, 0x6F, 0x64, 0x20} |
0x04 | 2 | uint16 | unk1 | Always 0. |
0x06 | 2 | uint16 | unk2 | Always 0x1504. |
0x08 | 4 | uint32 | chunkbeg | Seems to always be 0x10, possibly the start of the chunk data. |
0x0C | 4 | uint32 | chunklen | Length of the chunk data. Begins counting right after this field. Always 0 for eod chunk! |