-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Midi
To enable support for newer state based MIDI controllers (i.e. controllers which dynamically remap buttons by hitting certain mode buttons) the Mixxx MIDI mappings specs will need to be extended.
The extensions will enable controls to be mapped to QtScript (aka Javascript/EMCAScript) functions stored in a functions library file.
Scope of required changes:
Changes to the current and new MIDI mapping file formatsAdditional script option MIDI event type handlers (configobject.cpp midiobject.cpp)A script file loaded at start-up that contains a library of functions, add all those functions will need to be parsed and made available to midi learning.- Interface definition for arguments passed to mapped script methods (to include the raw MIDI event details, which channel, and any options associated with the mapping.) Partially complete. Currently passing channel, control/note, value, and device as parameters. The channel # and device name belong in MidiObject (instead of dlgprefmidibindings.)
For the purposes of this document we will use the Hercules Mk2 controller to illustrate how these changes would be configured. The Mk2 is a simple controller that features a FX/Cue/Loop Mode selector button (see area 3, selector is triangle shaped), and 3 trigger buttons numbered 1, 2, and 3 respectively (also part of area 3).
Currently, Mixxx does not support modes, so the function of the mode selector button is mapped to reverse. The new mapping changes that to call a QtScript function, designated by a "<script-binding/>" option tag. Similarly the mappings for the buttons change from their current mappings to call the second script method, though that is not shown in the snippets here.
Old mapping to reverse:
<control>
<group>[Channel1]</group>
<key>reverse</key>
<miditype>Ctrl</miditype>
<midino>7</midino>
<midichan>1</midichan>
<controltype>button</controltype>
<options>
<button/>
</options>
</control>
New mapping to mode selection:
<control>
<group>[Channel1]</group>
<key>HerculesMk2.fx_cue_loop_mode</key> <!-- changed -->
<miditype>Ctrl</miditype>
<midino>7</midino>
<midichan>1</midichan>
<controltype>button</controltype>
<options>
<Script-Binding/> <!-- changed -->
</options>
</control>
(Do we still even need this?)
Old mapping to reverse:
<control>
<group>[Channel1]</group>
<key>reverse</key>
<miditype>Ctrl</miditype>
<midino>0x07</midino>
<options>
<switch/>
</options>
</control>
New mapping to mode selection:
<control>
<group>[Channel1]</group>
<key>HerculesMk2.fx_cue_loop_mode</key> <!-- changed -->
<miditype>Ctrl</miditype>
<midino>0x07</midino>
<options>
<script-binding/> <!-- changed -->
</options>
</control>
When a midi event arrives in controlobject/midiobject the <script/> tag triggers an evaluation of the functions stored in the library. Here is an example implementation of the library which shows the basics of managing the controller's state. The implementation isn't complete as the callbacks to control objects to trigger the various button actions or set the LED values are only comments or stubs to alert().
(Note: you'll need to compile Mixxx with option script=1 to enable QtScript parsing.)
function HerculesMk2() {}
HerculesMk2.mode_store = { "[Channel1]":0, "[Channel2]":0 };
HerculesMk2.fx_button_map = { 13:3, 14:2, 15:1, 16:1, 17:2, 18:3 };
HerculesMk2.mode_def = { "[Channel1]": { "min":15, "inc":-1, "max":13 } , "[Channel2]": { "min":16, "inc":1, "max":18 } };
HerculesMk2.modes = { 13:"loop", 14:"cue", 15:"fx", 16:"fx", 17:"cue", 18:"loop" };
HerculesMk2.fx_cue_loop_mode = function (msg) {
if (msg.midino.value == 0) return; // ignore button up
var ch = msg.channel;
var B0 = 176; // Hex MIDI code for Hercules Mk2 LED output
var mode = HerculesMk2.mode_store[msg.channel];
if (mode != 0) {
midi.send(B0, mode, 0) // clear previous LED status
}
if (mode == HerculesMk2.mode_def[msg.channel]["min"] || mode == HerculesMk2.mode_def[msg.channel]["min"] + HerculesMk2.mode_def[msg.channel]["inc"]) { // In one of the first two modes
mode = mode + HerculesMk2.mode_def[msg.channel]["inc"];
} else { // either uninitialized (mode == 0) or in the final mode and need to roll back to first mode.
mode = HerculesMk2.mode_def[msg.channel]["min"];
}
HerculesMk2.mode_store[msg.channel] = mode;
midi.send(B0, mode, 127) // set new LED status
}
HerculesMk2.fx_cue_loop_button = function (msg) {
if (msg.midino.value == 0) return; // ignore button up
var mode = HerculesMk2.mode_store[msg.channel];
if (mode == 0) { HerculesMk2.fx_cue_loop_mode(msg); mode = HerculesMk2.mode_store[msg.channel]; }
var trigger_no = HerculesMk2.fx_button_map[msg.midino];
switch (HerculesMk2.modes[mode]) {
case "fx": /* trigger trigger_no on/off toggle event for fx on msg.channel */ ; break;
case "cue": /* seek trigger_no cue point on msg.channel */; break;
case "loop": /* trigger_no 1 to loop in/out, 2/3 to lengthen/shorten loop */ ; break;
}
alert(msg.channel + " " + HerculesMk2.modes[mode] + " button #" + trigger_no + " hit.");
}
Test code, which cycles through the buttons modes and triggers a few button events.
// --- Test stuff below
function midi(){
}
midi.send = function (status, midino, value) {
alert("midi.send - status: " + status + " midino: "+ midino + " value: " + value);
}
function msg(){
}
msg.channel = "[Channel1]";
msg.midino = 13;
msg.value = 127; // button down
function msg2(){
}
msg2.channel = "[Channel2]";
msg2.midino = 17;
msg2.value = 127; // button down
HerculesMk2.fx_cue_loop_mode(msg);
HerculesMk2.fx_cue_loop_button(msg);
HerculesMk2.fx_cue_loop_mode(msg2);
HerculesMk2.fx_cue_loop_button(msg2);
HerculesMk2.fx_cue_loop_mode(msg);
HerculesMk2.fx_cue_loop_button(msg);
HerculesMk2.fx_cue_loop_mode(msg);
HerculesMk2.fx_cue_loop_button(msg);
HerculesMk2.fx_cue_loop_mode(msg);
HerculesMk2.fx_cue_loop_button(msg);
HerculesMk2.fx_cue_loop_mode(msg2);
HerculesMk2.fx_cue_loop_button(msg2);
Library should be called something descriptive like 'midi-mappings-scripts.js' (.js so editors highlight properly).
Steps to loading:
read file 'midi-mappings-scripts.js' into a QString (refered to from here on as scriptFile)-
parse all lines matching mappable function signatures into a QStringList (refered to from here on as functionsMap).Pure regex equivalent of this:grep 'function' midi-mappings-scripts.js|grep -i '(msg)'|sed -e 's/function \(.*\)(msg).*/\1/i' -e 's/[= ]//g'
should just about do it. -
Load mapping file, verify that all <script-binding/> references are present in functionsMap, else pop-up an error message indicating unmapped option.(don't assert, otherwise app dies and it will be impossible to correct inside the learning prefs screen). - QtScriptEngine works by accepting a string argument.
Pass scriptFile, check canEvaluate -> false throw a pop-up indicating a scripting error...Using a qDebug() for now. -
Whenever a mapping with a <script-binding/> option is triggered, evaluate that method in the QtScript, passing in the msg (w/ with channel data and data from raw midi event that triggered it).Channel # and MIDI device name need to be in the MidiObject for script access in the future. (The init() and shutdown() functions depend on it.)
Phase 2: a <script> block will be added to the XML to hold controller specific QtScript functions. There are considerations such as function name collisions and remapping to consider, global functions generic to all controllers will still be loaded from the mid-mapping-script.js file as well.
Mixxx is a free and open-source DJ software.
Manual
Hardware Compatibility
Reporting Bugs
Getting Involved
Contribution Guidelines
Coding Guidelines
Using Git
Developer Guide
Creating Skins
Contributing Mappings
Mixxx Controls
MIDI Scripting
Components JS
HID Scripting