Credits: Deckmatics
Deckmatics
Deckmatics vector SVG generated on this page.
Mixxx: XML and JavaScript mappings
Mixxx separates mapping into two layers: XML for message translation and JavaScript for complex logic. How to combine them and what each script handles.
Mixxx is the most educational example in this whole section, not because it is the easiest, but for the opposite reason: it is the only one that shows you the two layers separately. In other programs, mapping happens inside an interface; here it happens in two files that you can read, copy and modify.
That makes it the best way to understand what a mapping really is internally. What you learn here can be applied conceptually to any other software.
What Mixxx controller scripting is
Mixxx offers what it calls Controller Scripting: it lets you map hardware controls to JavaScript functions so that a mapping can handle complex behaviours, rather than only direct translations.
Since Mixxx 2.4, that JavaScript runs in a QJSEngine and can use the ES7 feature set, except ES6 modules. This matters when writing modern code: you can use let, const and template strings, but not import.
XML and JavaScript: two layers, not alternatives
The most common misconception is that you can choose to “do it in XML” or “do it in JavaScript”. It does not work that way: they are two layers of the same mapping and usually work together.
The file-format documentation says this directly: mappings are written in XML and JavaScript, XML-only mappings are possible, but most controllers will need some JavaScript for a complete mapping.
XML as the translation layer
The XML file is the translator. It tells Mixxx how to turn messages arriving from the controller into commands the software understands. Each message is declared as a <control> with its group, status, control number and options.
JavaScript as the behaviour layer
The script comes in when the response to a message is not always the same. If the action depends on whether another button is pressed, the player’s actual state or which deck you are on, that does not fit into a translation table: code is needed.
The contract between XML and the script
This is the part you must understand to avoid getting lost, because it is literally the connection between the two files.
scriptfiles and functionprefix
To make Mixxx load your script, declare it inside the XML:
<scriptfiles>
<file filename="Fabricante-modelo-scripts.js" functionprefix="MiControlador"/>
</scriptfiles>
The functionprefix attribute is the key: it specifies the name of the JavaScript object containing the init and shutdown methods, which Mixxx calls when opening and closing the controller. You can declare several files, but each must specify its functionprefix.
There is also a common function file, common-controller-scripts.js, which is always loaded and contains utilities shared by all controllers.
init and shutdown
In the script, declare the object and give it those two methods:
var MiControlador = {};
MiControlador.init = function (id, debugging) {
// se ejecuta al abrir el controlador
};
MiControlador.shutdown = function () {
// se ejecuta al cerrar
};
They can be empty, but are very useful for leaving the equipment in a known state: for example, switching LEDs off when closing. The debugging parameter is true if you started in controller debugging mode.
A style warning in the documentation prevents faults that are hard to find: instead of using global variables, define properties on your controller object, to avoid collisions with names in other loaded scripts.
Script-Binding and the function signature
To bind a message to a function, put its full name in the <key> tag and add <Script-Binding/> inside <options>:
<control>
<group>[Master]</group>
<key>MiControlador.faderTempo</key>
<status>0xB0</status>
<midino>0x04</midino>
<options>
<Script-Binding/>
</options>
</control>
When Mixxx receives a message whose first two bytes match <status> and <midino>, it calls that function. Here is the detail to memorise: the signature is always the same.
MiControlador.faderTempo = function (channel, control, value, status, group) {
// ...
};
The parameters, in order, are MIDI channel, control or note number, value, status byte and Mixxx group. If you do not use one, the convention is to prefix it with an underscore—_channel—to make the omission clear.
The reserved-name trap
The documentation contains a warning worth reading twice: do not call a normal MIDI input function incomingData. It is a reserved name Mixxx uses internally; using it makes your function receive incorrect parameters and fail to work. Use any other descriptive name.
What XML handles and what requires JavaScript
The table summarises the practical decision. The underlying rule: if the response to a message is always the same, it fits in XML; if it depends on something that changes, a script is needed.
| Task | Layer | Why |
|---|---|---|
| Assigning a button to a function | XML | Direct translation of a message into a command. |
| Lighting an LED the same way every time | XML | No logic: just a fixed value sent. |
| Making the LED reflect actual state | JavaScript | Requires reading software state and making a decision. |
| SHIFT layers and modifiers | JavaScript | The response depends on a changing state. |
| Soft-takeover | JavaScript | Only works if the control is manipulated from the script. |
| A 2-deck controller used as 4 | JavaScript | Controls must be redirected to other groups on the fly. |
The general rule: if the response to a message is always the same, it fits in XML. If it depends on something that changes, a script is needed.
The messages you will handle
Short messages of 3 bytes
Most MIDI messages contain three bytes. They reach your function in this order: channel—0x00 for channel 1 and 0x0F for 16—control or note number, value, status byte and group.
One concrete example in the documentation saves many mistakes: buttons usually send 0x7F when pressed and 0x00 when released, so your function is called in both cases. If you only want it to act on a press, check the value.
SysEx messages
System exclusive messages are different. In the XML, their status byte must be 0xF0, and no <midino> is needed. The function receives an array of bytes and its length. An important detail: if the controller can send several different SysEx messages, a single function is responsible for identifying which one arrived and acting accordingly.
How LEDs are controlled
To illuminate LEDs, send MIDI back to the controller. There are two functions, one for short messages and another for SysEx.
The general convention is reassuring: buttons usually control their LED by sending a short message with the same first two bytes as when the button is pressed. If the button sends 0x91, 0x11, 0x7F when pressed, sending the same message lights the LED, and sending it with 0x00 turns it off. For multicolour LEDs, the third byte normally determines the colour.
There is a design recommendation that separates a good mapping from a fragile one: do not send LED messages directly from the input function. Change the state of a Mixxx control and send MIDI from a callback function reacting to that change. This keeps the controller state consistent with what Mixxx is actually doing, even when the user uses the keyboard, mouse or another controller. It is the difference between a mapping that works only when you use its buttons and one that always works.
Debugging the mapping without restarting
Two advantages of working in Mixxx are worth using from the start:
- The script reloads when saved. There is no need to restart the application, making the test cycle very fast.
- There is a controller debugging mode, logging all incoming and outgoing MIDI messages, and since Mixxx 2.4 you can use
console.log.
That debugging mode also answers “what message does this button send?”: if the manufacturer does not document the codes, intercept them with tools such as MIDI-OX on Windows or MIDI Monitor on macOS, or directly through this option.
The soft-takeover trap in Mixxx
Mixxx has soft-takeover, with the most precise definition in this section: while it is active on a parameter, moving the physical control has no effect until its position approaches the software value, at which point it takes control and operates normally. You can enable and disable it whenever you want, and it works independently per control.
There is a limitation to know before losing an afternoon: it only works on controls manipulated from the script with setValue or setParameter. It does not affect controls mapped directly in XML.
One subtle detail for layers: if you change the function of an absolute control—one with end stops, rather than an endless encoder—with soft-takeover active, you must tell Mixxx which physical control you are moving whenever you change its function. Otherwise, on returning to the previous function, the value suddenly jumps. A specific “ignore the next value” function exists for this.
This is a perfect example of why soft-takeover and layers go hand in hand, and why they have their own guide.
Common mistakes
- Trying to do everything in XML. It works until something must depend on a state, and then you get stuck.
- Calling an input function
incomingData. Reserved name: it will not work. - Forgetting functionprefix or not defining
initandshutdown. - Not accepting all parameters in the function signature.
- Sending LEDs from the input function instead of a callback, making the light fall out of sync.
- Expecting soft-takeover on a control mapped in XML. It does not apply.
- Using global variables in the script and colliding with other loaded mappings.
Next step
You now have the two-layer model, the most transferable one in this section. Once you understand it, the logical next step is the problem many people suffer without knowing its name, already mentioned twice in this guide: soft-takeover, parameter jumps and how to prevent them.
Frequently asked questions
Can I create a Mixxx mapping without programming?
It depends on the equipment. A simple controller can be mapped with XML alone, but the documentation itself warns that most will need some JavaScript for a complete mapping.
What is functionprefix in XML?
The name of the JavaScript object Mixxx looks for when loading the script. That object must define init and shutdown methods, which Mixxx calls when opening and closing the controller.
How does Mixxx know which function to call for each MIDI message?
Through the Script-Binding tag inside the control's options block in XML. The function's full name goes in the key tag, and Mixxx calls it when a message matching status and midino arrives.
Why should I not call my input function incomingData?
Because it is a reserved name Mixxx uses internally. Using it for a normal input function gives it incorrect parameters and prevents it working. Use any other descriptive name.
Must I restart Mixxx every time I change the script?
No. Whenever you save the file, Mixxx reloads it immediately. This makes the test cycle very fast.
Does soft-takeover work on a control mapped from XML?
No. Soft-takeover only affects controls manipulated from the script with setValue or setParameter. It does not affect a control mapped directly in XML.
Continue learning
Start here if these concepts are new to you
Sources
- Mixxx Wiki — MIDI Scripting (Controller Scripting)
Main source. Explains Mixxx Controller Scripting, which maps hardware controls to JavaScript functions for complex behaviours, and that since Mixxx 2.4 mappings run in a QJSEngine with the ES7 feature set except ES6 modules. Documents the XML/script contract: scriptfiles with file and functionprefix, init and shutdown methods, Script-Binding, input function signature (channel, control, value, status, group), short 3-byte messages and the SysEx function (data, length), the warning against naming an input handler incomingData, engine.makeConnection for output callbacks, midi.sendShortMsg and midi.sendSysexMsg, immediate script reload on saving, SHIFT layers and the trick for turning a 2-deck controller into a 4-deck one. Includes soft-takeover's definition and limitation: it only works on controls manipulated with setValue or setParameter, not those mapped from XML.
- Mixxx Wiki — MIDI controller mapping file format
Defines the XML file format: it tells Mixxx how to translate controller MIDI messages into commands Mixxx understands, and documents the MIDI Learning Wizard as the easiest way to create a new mapping.
- Mixxx User Manual 2.4 — Advanced Topics
Documents the same model in the user manual: support for devices is added by creating a mapping file translating controller MIDI or HID messages into software commands.
- MIDI 1.0 Control Change Messages (Data Bytes)
Context for the bytes appearing in XML: control messages range from 0 to 127, and control numbers 120-127 are reserved for Channel Mode Messages.
Last technical review: 29/09/2026. Please report any errors so they can be corrected.