Skip to content

File Format Specification

Ivan Zlatev edited this page Nov 18, 2014 · 14 revisions

Version History

  • 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 practice Device itself is a Frame that contains the DeviceType properties.
  • Moved MidiNoteBindingList from DeviceData into MappingsContainer
  • Version 1 (19 October 2014)
  • First write-up
  • File Structure complete
  • 95% of field meanings known
  • Unknown things are named explicitly named "Unknown"

Example Source Code (Reference Implementation)

There is no reference implementation, yet...

File Structure Overview

Warning

It's best to read those side by side with the actual data when using the guide from the Setting up guide.

Structure definitions

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)

.tsi Format

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)

High Level overview

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.

File Structure Details

Conventions

Endianness

The format is Big Endian.

Strings

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

Data Types Used Below

  • int: 4 bytes (big endian)
  • char: 1 byte
  • DWORD:4 bytes
  • float: 4 bytes (big endian)
  • wchar_t: 2 bytes

Frames

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;
};

Arrays, Array Frames and Naming Convention

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.

Root

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;
};

Device

Definition

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

Data

typedef struct DeviceData
{
    FrameHeader Header; // DDAT
    DeviceTargetInfo Target;
    VersionInfo Version;
    MappingFileComment Comment;
    DevicePorts Ports;
    MidiDefinitionsContainer MidiDefinitions;
    MappingsContainer Mappings;
    DVST Unknown;
};

Target Deck

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
};

Version Information

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;
};

Comment

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];
};

Device Port

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>;
};

DVST - Unkown

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              ............

Mapping

Overview

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

Device Capabilities/MIDI Definitions

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,
};

Binding between Mapping and MIDI note/command

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];
};

Mapping

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,
};