-
Notifications
You must be signed in to change notification settings - Fork 6
File Format Specification
- Version 1.2 (18 November 2014)
- More unknowns reverse-engineered. Refer to commit log for details.
- Version 1.1 (14 November 2014)
- Fix error where there was a Frame defined
DeviceType
, but in practiceDevice
itself is a Frame that contains theDeviceType
properties. - Moved
MidiNoteBindingList
fromDeviceData
intoMappingsContainer
- Version 1 (19 October 2014)
- First write-up
- File Structure complete
- 95% of field meanings known
- Unknown things are named explicitly named "Unknown"
There is no reference implementation, yet...
It's best to read those side by side with the actual data when using the guide from the Setting up guide.
Make sure to check out the Setting up guide to understand how to use those:
[View Structure Definitions File] (https://github.com/ivanz/TraktorMappingFileFormat/blob/master/Tools/TSI%20Mapping%20Template.bt)
The .tsi file is an XML file. However the actual mapping data is stored as Base64 encoded binary data in one of the attributes denoted by ### HUGE STRING HERE#### in the sample here:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<NIXML>
<TraktorSettings>
<Entry Name="DeviceIO.Config.Controller" Type="3" Value="### HUGE STRING HERE####"></Entry>
<Entry Name="Flavour" Type="1" Value="-1"></Entry>
<Entry Name="Version" Type="1" Value="0"></Entry>
</TraktorSettings>
</NIXML>
To work with the controller mapping data - the binary blob needs to be extracted out (refer to Setting up guide)
At a high level the binary part of the .tsi file is a list of ID3v2-like frames (blocks) with the following basic structure:
typedef struct Frame {
char[4] Identifier;
int Size;
char[] Data;
};
All data within binary file is stored in Big Endian format.
The data contained within those frames (which can be hierarchical) in a very simplified form is:
- The type of the device this mapping is for.
- Basic details about the mapping: comment, version information, etc
- Which in/out port does the mapping apply for.
- The input and output (midi) commands/messages/notes that the device supports.
- Bindings between those and Traktor commands together with mapping information and parameters (e.g. sensitivity, Button/Fader/Encoder, interaction types, etc)
The file format allows for it to contain more than one device mapping in it.
The format is Big Endian.
All strings within the file are stored as 2-bytes per character non-null terminated, but instead prefixed with the length of the string. For example:
0020h: 00 00 00 0C 00 47 00 65 00 6E 00 65 00 72 00 69 .....G.e.n.e.r.i
0030h: 00 63 00 20 00 4D 00 49 00 44 00 49 .c. .M.I.D.I
- int: 4 bytes (big endian)
- char: 1 byte
- DWORD:4 bytes
- float: 4 bytes (big endian)
- wchar_t: 2 bytes
As mentioned above data is encapsulated in Frames/Blocks of data each with the following header:
typedef struct FrameHeader {
char Id[4];
// The size is sizeof(FrameHeader) + size of the content of the frame
int Size;
};
The way elements are stored in an array is through child frames within a parent frame, where the parent frame specifies the number of children.
Example:
typedef struct DevicesList {
FrameHeader Header;
int NumberOfDevices;
Device Devices[NumberOfDevices] <optimize=false>;
};
Important: As such every structure member with a plural name (Devices
) will be defined by a type with a List suffix in the name (e.g. DevicesList
) will be an array.
At the top of the file we have a list of devices included in it
typedef struct DeviceMappingsContainer {
FrameHeader Header;
DIOI DIOI_Unknown;
DevicesList Devices <optimize=false>;
};
It's unclear what data the DIOI frame contains at this point, because it always seems to just be this:
typedef struct DIOI {
FrameHeader Header; // DIOI
// Seems to be a constant 1
int Unknown;
};
Each device definition in the mapping has a name and a bunch of data associated with it (mappings, supported midi notes, etc).
typedef struct Device {
FrameHeader Header; // DEVI
int NameLength;
wchar_t Name[NameLength];
DeviceData Data;
};
Example device is "Generic MIDI" or "Kontrol S4" and this directly corresponds to the list in the Traktor preferences:
images/file-spec/device-type.png
typedef struct DeviceData
{
FrameHeader Header; // DDAT
DeviceTargetInfo Target;
VersionInfo Version;
MappingFileComment Comment;
DevicePorts Ports;
MidiDefinitionsContainer MidiDefinitions;
MappingsContainer Mappings;
DVST Unknown;
};
A new frame that contains an enum value:
typedef struct DeviceTargetInfo {
FrameHeader Header; // DDIF
DeviceTarget Target;
};
enum <int> DeviceTarget {
Focus = 0,
DeckA = 1,
DeckB = 2,
DeckC = 3,
DeckD = 4
};
A string containing the Traktor version which was used to make this mapping and a integer with the revision of the mapping. Traktor seems to increment the revision of the mapping every now and then.
typedef struct VersionInfo {
FrameHeader Header; // DDIV
int VersionLength;
wchar_t Version[VersionLength];
int MappingFileRevision;
};
The comment is effectively when you click Edit -> Comment
in the mapping screen.
If you add a comment to a mapping in Traktor it uses that as the name of the device in place of e.g. "Generic MIDI"
typedef struct MappingFileComment {
FrameHeader Header; // DDIC
int CommentLength;
wchar_t Comment[CommentLength];
};
This is simply the name of the "In-Port" and "Out-Port" in the Traktor Controller Manager screen,.
typedef struct DevicePorts {
FrameHeader Header; // DDPT
int InPortNameLength;
wchar_t InPortName[InPortNameLength] <optimize=false>;
int OutPortNameLength;
wchar_t OutPortName[OutPortNameLength] <optimize=false>;
};
typedef struct DVST {
FrameHeader Header; // DVST
byte Content[Header.Size];
};
It is unknown at this point what the meaning of this Frame is, but it seems to always contain the following static data:
6:F890h: 44 56 53 54 00 00 00 14 00 00 00 01 00 00 00 00 DVST............
6:F8A0h: 00 00 00 00 00 00 00 00 00 00 00 00 ............
There are three elements involved in a mapping:
- Static definition of signals (midi notes/commands) can the device send and receive
- Traktor command and parameters What (e.g. Button vs Encoder or Direct vs Inc or Invert yes/no)
- Which MIDI note/signal should trigger the Traktor command
When you pick a device in the Controller Manager - Traktor uses a template. For example when you pick a "Generic Midi" device it will come with a list of In and Out midi notes/commands support such as Ch01.Note.C1
. In the Controller Manager you will then associate one of those (or use Learn) to a Traktor command.
The definition of the list for a device looks like this:
typedef struct MidiDefinitionsContainer {
FrameHeader Header; // DDDC
MidiInDefinitions In;
MidiOutDefinitions Out;
};
typedef struct MidiOutDefinitions {
FrameHeader Header; // DDCO
int NumberOfEntries;
MidiDefinition Definitions[NumberOfEntries] <optimize=false>;
};
typedef struct MidiInDefinitions {
FrameHeader Header; // DDCI
int NumberOfEntries;
MidiDefinition Definitions[NumberOfEntries] <optimize=false>;
};
And the actual Midi note definition looks like below. Note that although this definition is static - it is not read-only. For example if one changes the Encoder Type in the Controller Manager - it will update the definition.
It appears that the string defining the name of the note is used as the unique Identifier of the definition.
typedef struct MidiDefinition {
FrameHeader Header; // DCDT
int MidiNoteLength;
wchar_t MidiNote[MidiNoteLength];
DWORD Unknown1;
DWORD Unknown2;
float Velocity;
// Updated by Traktor Pro UI when changed
MidiEncoderMode EncoderMode;
// In the case of Native Instruments devices seems to identify the
// control Id. However this control Id will be the same for e.g. both
// left and right Shift keys (Kontrol S4). Otherwise 0xFFFFFFFF.
int ControlId;
};
enum <int> MidiEncoderMode {
_3Fh_41h = 0,
_7Fh_01h = 1,
};
Each device contains a list of used midi notes (which are later connected to the Traktor action mapping)
Each of those bindings contains the name of the midi note/command (same as in the MIDI capabilities definition) and a unique identifier (Id
)
This is the only place where the convention of having a different parent container for the array elements is broken and both the container and children share the same frame type id DCBM
. However the first occurance of DCBM
is the frame that contains the number of elements to follow:
typedef struct MidiNoteBindingList {
FrameHeader Header; // DCBM id
int NumberOfBindings;
MidiNoteBinding Bindings[NumberOfBindings] <optimize=false>;
};
typedef struct MidiNoteBinding {
FrameHeader Header; // DCBM id
int Id;
int MidiNoteLength;
wchar_t MidiNote[MidiNoteLength];
};
A device contains a list of mappings:
typedef struct MappingsContainer {
FrameHeader Header; // DDCB
MappingsList List;
MidiNoteBindingList MidiBindings;
};
typedef struct MappingsList {
FrameHeader Header; // CMAS
int NumberOfMappings;
Mapping Mappings[NumberOfMappings] <optimize=false>;
};
A mapping is composed of:
- The Traktor command it should trigger.
- Which midi binding should trigger the command
- Settings
Every command in Traktor (e.g. Cue) has a unique corresponding number (e.g. 50
) and this is what is stored in TraktorControlId
The MidiNoteBindingId
is the midi binding identifier from above.
typedef struct Mapping {
FrameHeader Header; // CMAI
int MidiNoteBindingId;
MappingType Type;
// Basically what UI Control this mapping is for
int TraktorControlId;
MappingSettings Settings;
};
The mapping settings pretty much correspond to what you see on the Controller Manager screen in Traktor. Some comments inline.
Checkboxes in the Controller Manager are int
here and 0
is false
/ 1
is true
.
typedef struct MappingSettings {
FrameHeader Header; // CMAD
// Seems to be always a constant 4
DWORD Unknown1;
MappingControllerType ControllerType;
MappingInteractionMode InteractionMode;
MappingTargetDeck Deck;
int AutoRepeat;
int Invert;
int SoftTakeover;
// 1% in the Traktor UI corresponds to 0.5f
// Traktor sets this to 300% / 15f when
// in Interaction mode is Direct
float RotarySensitivity;
float RotaryAcceleration;
DWORD Unknown10;
DWORD Unknown11;
float SetValueTo;
int CommentLength;
wchar_t Comment[CommentLength];
// Traktor Control Id
int ModifierOneId;
DWORD Unknown15;
int ModifierOneValue;
int ModifierTwoId;
DWORD Unknown18;
int ModifierTwoValue;
DWORD Unknown20;
float LedMinControllerRange;
DWORD Unknown22;
float LedMaxControllerRange;
int LedMinMidiRange;
int LedMaxMidiRange;
int LedInvert;
int LedBlend;
DWORD Unknown29;
// this field is actually a float under the hood
MappingResolution Resolution;
DWORD Unknown30;
};
Various enums used in the mapping settings:
enum <int> MappingTargetDeck {
DeviceTargetDeck = -1,
AorFX1orRemixDeck1Slot1OrGlobal = 0,
BorFX2orRemixDeck1Slot2 = 1,
CorFX3orRemixDeck1Slot3 = 2,
DorFX4orRemixDeck1Slot4 = 3,
RemixDeck2Slot1 = 4,
RemixDeck2Slot2 = 5,
RemixDeck2Slot3 = 6,
RemixDeck2Slot4 = 7,
RemixDeck3Slot1 = 8,
RemixDeck3Slot2 = 9,
RemixDeck3Slot3 = 10,
RemixDeck3Slot4 = 11,
RemixDeck4Slot1 = 12,
RemixDeck4Slot2 = 13,
RemixDeck4Slot3 = 14,
RemixDeck4Slot4 = 15,
};
enum <int> MappingInteractionMode {
Toggle = 1,
Hold = 2,
Direct = 3,
Relative = 4,
Increment = 5,
Decrement = 6,
Reset = 7,
Output = 8,
};
enum <int> MappingControllerType {
Button = 0,
FaderOrKnob = 1,
Encoder = 2,
LED = 65535,
};
// Big endian values !!
enum <DWORD> MappingResolution {
Fine = 0x3C800000,
Min = 0x3D800000,
Default = 0x3D800000,
Coarse = 0x3E000000,
Switch = 0x3F000000,
};
enum <int> MappingType {
In = 0,
Out = 1,
};