In order to support the advanced features of many MIDI controllers, Mixxx offers what we call MIDI Scripting (introduced in Mixxx v1.7.0.) It enables MIDI controls to be mapped to QtScript (aka Javascript/EMCAScript) functions stored in function library files, freeing Mixxx from a one-to-one MIDI mapping ideology. These user-created functions can then do anything desired with the MIDI event info such as have a single controller button simultaneously affect two or more Mixxx properties (“controls”,) adjust incoming control values to work better with Mixxx (scratching,) display a complex LED sequence, or even send messages to text displays on the controller.
Script files use the naming convention <manufacturer>-<device>-scripts.js (e.g. Stanton-SCS3d-scripts.js) and are found in the midi/ subdirectory wherever your Mixxx shared data is stored. (Usually /usr/share/mixxx on Linux/Mac, and C:\Program Files\Mixxx on Windows.) Functions use the naming convention <manufacturer><device>.<function name> (e.g. StantonSCS3d.pitchSlider). Global variables use <manufacturer><device>.<variable name> (e.g. StantonSCS3d.deck). These are very important to avoid name collisions with other scripts that may be loaded.
To link a script function to a particular control, in the device's XML MIDI mapping file, put the full function name in the <key> tag, and a <Script-Binding/> tag in the <options> block, like so:
<control> <!-- Pitch slider --> <group>[Master]</group> <key>StantonSCS3d.pitchSlider</key> <status>0xB0</status> <midino>0x04</midino> <options> <Script-Binding/> </options> </control>
The value for <group> doesn't matter when using a script function, but it still needs to be valid (”[Master]” or ”[Channel#]”) or the XML parser will report an error. No tags or options are considered other than those shown above, so you can leave them out.
Coming in 1.8: The <group> value is passed as an additional parameter to the script function. (Useful for dual-deck controllers since you only need one function that checks the passed Channel # and reacts appropriately.)
When this device control is operated, the named script function is called. It is then up to the function to effect all desired changes (Mixxx properties, device LEDs, etc.)
There is a default script function file called midi-mappings-scripts.js which contains functions common to all controllers and is always loaded. See below for information on these functions.
To specify additional script files to load, add the following section to the device's XML MIDI mapping file right underneath the <controller> tag:
<scriptfiles> <file filename="Stanton-SCS3d-scripts.js" functionprefix="StantonSCS3d"/> </scriptfiles>
You can add as many <file> tags as you like, but be sure to specify the appropriate function prefix in every one. These will all be loaded when the controller is activated.
All device script files are expected to contain initialize and shutdown functions (called <manufacturer><device>.init(ID) and <manufacturer><device>.shutdown() ) which will be called when Mixxx opens and closes the device, respectively. They can be empty, but are useful for putting controllers into known states and/or lighting certain LEDs before operation begins or the program exits. The ID parameter is the controller id attribute from the XML file and is useful for identifying the particular controller instance in print statements.
This API will change for Mixxx 1.8:
ControllerName.functionName = function (channel, control, value, status, group) (This makes it easier to work with larger controllers that allow manipulating both decks at once.)Data passed to functions are, in order: MIDI channel (0x00 = Channel 1..0x0F = Channel 16,) control/note, value, and MIDI status (Note (0x9#), Control Change (0xB#), etc.) Therefore, function definitions should look like:
ControllerName.functionName = function (channel, control, value, status) { ... }
You can leave off any parameters at the end that you don't need; the function is identified only by name (so make sure it's unique!) For example, if you don't need the MIDI status or value bytes, just do:
ControllerName.functionName = function (channel, control) { ... }
(If more than one function have the same name, only the last one listed in the script file(s) will be called, regardless of the number of parameters.)
Script functions can check and set Mixxx control values using the following functions:
engine.getValue(string group, string key); engine.setValue(string group, string key, double newValue);
To check a Mixxx control value, call engine.getValue() with the ”group” and ”key” values for a particular Mixxx control, a list of which can be found here. So for example:
var currentValue = engine.getValue("[Channel1]","rate");
Values can be set just as easily by calling engine.setValue() with the group and key as above, and the new value to set, like so:
engine.setValue("[Channel1]","rate",0.5);
Note that since this is a script, you can do calculations and use state variables so a single function can work for multiple cases, such as a single controller working with Mixxx's multiple virtual decks (assuming you've defined currentDeck and currentValue here):
engine.setValue("[Channel"+currentDeck+"]","rate",(currentValue+10)/2);
Coming in v1.8
We have an easy way to scratch with any MIDI control that sends relative (+1/-1) signals. (Others can be scaled to work as well.) The applicable functions are:
engine.scratchEnable(int deck, int intervalsPerRev, float rpm, float alpha, float beta); engine.scratchTick(int deck, int interval); engine.scratchDisable(int deck);
Here is how to use them:
engine.scratchEnable() with:engine.scratchTick() with:engine.scratchDisable() with the number of the virtual deck to stop scratching.Here is an example for the two most common types of wheels:
// The button that enables/disables scratching MyController.wheelTouch = function (channel, control, value, status) { if ((status & 0xF0) == 0x90) { // If button down engine.scratchEnable(MyController.currentDeck, 128, 33+1/3, 1.0/8, (1.0/8)/32); // Keep track of whether and what deck we're scratching MyController.scratching = MyController.currentDeck; } else { // If button up engine.scratchDisable(MyController.currentDeck); MyController.scratching = -1; // Not scratching any more } } // The wheel that actually controls the scratching MyController.wheelTurn = function (channel, control, value, status) { // See if we're scratching. If not, skip this. if (MyController.scratching == -1) return; // For a control that centers on 0: var newValue; if (value-64 > 0) newValue = value-128; else newValue = value; // For a control that centers on 0x40 (64): var newValue=(value-64); // In either case, register the movement engine.scratchTick(MyController.currentDeck,newValue); }
And that's it! Just make sure to map the button/touch sensor and wheel to these script functions as described above and you'll be ready to tear up some tracks.
You can send three-byte “short” messages and arbitrary-length system-exclusive “long” ones to the controller using the following functions:
midi.sendShortMsg(status, byte2, byte3); midi.sendSysexMsg(data, length);
Together, these cover virtually all types of MIDI messages you would need to send. (This is how you light LEDs, change displays, etc.)
For short messages, call midi.sendShortMsg() with:
It's completely up to you (and your controller's MIDI spec) what those bytes can be. (Status will usually be 0x90, 0x80 or 0xB0.) For example:
midi.sendShortMsg(0x90,0x11,0x01); // This might light an LED
For system-exclusive messages, call midi.sendSysexMsg() with:
0xF0 and ending with 0xF7var byteArray = [ 0xF0, byte2, byte3, ..., byteN, 0xF7 ]; midi.sendSysexMsg(byteArray,byteArray.length);
Here again, it's completely up to you (and your controller's MIDI spec) what those bytes should be for the change you wish to effect.
Here are some simple examples to get you started.
To control the play button for Deck 1 and light its LED:
MyController.playButton1 = function (channel, control, value, status) { // Play button for deck 1 var currentlyPlaying = engine.getValue("[Channel1]","play"); if (currentlyPlaying == 1) { // If currently playing engine.setValue("[Channel1]","play",0); // Stop midi.sendShortMsg(0x80,0x11,0x00); // Turn off the Play LED } else { // If not currently playing, engine.setValue("[Channel1]","play",1); // Start midi.sendShortMsg(0x90,0x11,0x7F); // Turn on the Play LED } }
To reduce the sensitivity of a relative-mode (touch strip) pitch slider:
MyController.pitchSlider1 = function (channel, control, value, status) { // Lower the sensitivity of the pitch slider for channel 1 var currentValue = engine.getValue("[Channel1]","rate"); engine.setValue("[Channel1]","rate",currentValue+(value-64)/128); }
To find the current elapsed time in seconds of a track on the specified deck (intended to be called from another function):
MyController.elapsedTime = function (deck) { return engine.getValue("[Channel"+deck+"]","duration") * engine.getValue("[Channel"+deck+"]","playposition"); }
IMPORTANT NOTE: You must always declare variables with “var” when you first use them since it establishes scope. If you omit this, the variable becomes global and will clobber anything else with the same name even if it's in another script file.
Up to this point, script functions are only called in response to the controller being manipulated. They can also be called automatically in response to some value changing within Mixxx, such as when you use the mouse to move the channel volume slider, you want the LEDs on the controller to react. Here are the related functions:
,true on to the list of parameters disconnects the specified Mixxx control signal from the specified script function. It returns true if the disconnection was successful.To connect the volume of the current virtual deck to a function called MyController.volumeLEDs, do:
engine.connectControl("[Channel"+MyController.currentDeck+"]","volume","MyController.volumeLEDs");
To force the above-mentioned volume LEDs to sync up, just do:
engine.trigger("[Channel"+MyController.currentDeck+"]","volume");
If you change what the volume LEDs represent (like when switching modes,) you would disconnect the Mixxx “volume” control from them like this:
engine.connectControl("[Channel"+MyController.currentDeck+"]","volume","MyController.volumeLEDs",true);
Coming in v1.8
Sometimes you need to be able to do things at certain time intervals regardless of whether the controller is manipulated or something changes in Mixxx. Timed reactions let you do just that with 20ms resolution. Here are the functions:
”function”, one-shot) - Starts a timer that will call the specified script function (with parameters if desired) repeatedly every time (if one-shot is false or not present) or just once (if one-shot is true) the given number of milliseconds (1/1000 second) pass. It returns an ID number for the timer (0 on failure) that you'll want to store in a variable so you can stop it later if it's a repeating timer. Note that the function must be enclosed in quotes.You can create and stop timers as much as you like but be aware that the operating system has limits on the number of timers it will allow, so remember to stop them as soon as you're done with them. (Not to mention that overall performance decreases as the number and/or frequency of timers increase.)
NEVER use busy-wait loops! (Loops that do nothing but delay. They can cause Mixxx to stutter.) Always use a timer instead!
To start a timer to flash LEDs on a controller 4 times per second (250ms) and store the ID in an array for later you would do:
MyController.timer[0] = engine.beginTimer(250,"MyController.flash()");
When the LEDs need to stop flashing, just do:
engine.stopTimer(MyController.timer[0]);
This one-shot timer example causes an LED (note number 0x3A in this case) to light up red one second after the beginTimer call: (Note the escaped quotes in the target function call.)
...
if (engine.beginTimer(1000,"MyController.lightUp(0x3A,\"red\")",true) == 0) {
print("LightUp timer setup failed");
}
...
MyController.lightUp = function (led,color) {
switch (color) {
case "red":
midi.sendShortMsg(0x90,led,0x01);
break;
case "green":
midi.sendShortMsg(0x90,led,0x02);
break;
default:
print("Warning: no color specified, using blue");
midi.sendShortMsg(0x90,led,0x03);
break;
}
}
...
String.prototype.toInt - returns an ASCII byte array for all the characters in any string. Use like so: “Test string”.toInt()
Here is a list of functions available to you from the always-loaded midi-mappings-scripts.js file:
MM:SS format.MM:SS.ss format.engine.setValue("[Channel"+deck+"]","rate",script.pitch(control, value, status));
The below functions are for scratching with absolute value controls. These functions do not work as well as the new ones for Mixxx v1.8 and you are advised to use those instead.