// **************************************************************************** // * Mixxx mapping script file for the Behringer CMD Studio 4a. // * Author: Craig Easton // * Version 1.4 (Jan 2016) // * Forum: http://www.mixxx.org/forums/viewtopic.php?f=7&t=7868 // * Wiki: http://www.mixxx.org/wiki/doku.php/behringer_cmd_studio_4a // **************************************************************************** //////////////////////////////////////////////////////////////////////// // JSHint configuration // //////////////////////////////////////////////////////////////////////// /* global engine */ /* global script */ /* global print */ /* global midi */ //////////////////////////////////////////////////////////////////////// // Master function definition. function BehringerCMDStudio4a() {} // ***************************** Global Vars ********************************** // Shift/mode state variables. BehringerCMDStudio4a.delButtonState = [false,false,false,false]; BehringerCMDStudio4a.scratchButtonState = [false,false,false,false]; // Button push/release state variables. BehringerCMDStudio4a.pitchPushed = [[false,false,false,false], [false,false,false,false]]; BehringerCMDStudio4a.delPushed = false; BehringerCMDStudio4a.delShiftUsed = false; BehringerCMDStudio4a.fxAssignPushed = false; BehringerCMDStudio4a.fxAssignShiftUsed = false; BehringerCMDStudio4a.fxAssignLastGroup = ""; // ************************ Initialisation stuff. ***************************** BehringerCMDStudio4a.vuMeterUpdate = function (value, group, control){ value = (value*15)+48; switch(control) { case "VuMeterL": midi.sendShortMsg(0xB0, 0x7E, value); break; case "VuMeterR": midi.sendShortMsg(0xB0, 0x7F, value); break; } } BehringerCMDStudio4a.initLEDs = function () { // (re)Initialise any LEDs that are direcctly controlled by this script. // DEL buttons (one for each virtual deck). midi.sendShortMsg(0x90, 0x2A, 0x00); midi.sendShortMsg(0x91, 0x4A, 0x00); midi.sendShortMsg(0x92, 0x2A, 0x00); midi.sendShortMsg(0x93, 0x4A, 0x00); // Scratch buttons (one for each virtual deck). midi.sendShortMsg(0x90, 0x16, 0x00); midi.sendShortMsg(0x91, 0x36, 0x00); midi.sendShortMsg(0x92, 0x16, 0x00); midi.sendShortMsg(0x93, 0x36, 0x00); } BehringerCMDStudio4a.init = function () { // Initialise anything that might not be in the correct state. BehringerCMDStudio4a.initLEDs(); // Connect the VUMeters engine.connectControl("[Master]","VuMeterL","BehringerCMDStudio4a.vuMeterUpdate"); engine.connectControl("[Master]","VuMeterR","BehringerCMDStudio4a.vuMeterUpdate"); } BehringerCMDStudio4a.shutdown = function() { // Leave the deck in a properly initialised state. BehringerCMDStudio4a.initLEDs(); // Disconnect the VUMeters. // Maybe not! It seems you don't have to do this even though the connection // in done in init(), in fact if you try it throws an error. // engine.connectControl("[Master]","VuMeterL","BehringerCMDStudio4a.vuMeterUpdate",true); // engine.connectControl("[Master]","VuMeterR","BehringerCMDStudio4a.vuMeterUpdate",true); } // *************************** Control Stuff. ********************************* // The code below is primarily "shift/mode" key functionality as there is no // native support for this in Mixxx at the moment. // I suspect that the vast majority of controller mappings could be completed // with little or no scripting if Mixxx supported shift/mode buttons in the // XML (together with standard wheel/scratching functionality). // Function to deal with the DEL "shift/mode" buttons. BehringerCMDStudio4a.del = function (channel, control, value, status, group) { if (value == 127) { // Button pushed. BehringerCMDStudio4a.delPushed = true; BehringerCMDStudio4a.delShiftUsed = false; } else { // Button released. BehringerCMDStudio4a.delPushed = false; // Only toggle the DEL-mode if the "shift" function wasn't used. if (!BehringerCMDStudio4a.delShiftUsed) { BehringerCMDStudio4a.delButtonState[channel] = !BehringerCMDStudio4a.delButtonState[channel]; midi.sendShortMsg(0x90 + channel, control, BehringerCMDStudio4a.delButtonState[channel] ? 0x01 : 0x00); } } } // Function to deal with the play buttons, (because they have a DEL-mode behaviour). BehringerCMDStudio4a.play = function (channel, control, value, status, group) { if (BehringerCMDStudio4a.delButtonState[channel]) { // DEL-mode is active, do reverse-roll (slip). engine.setValue(group, "reverseroll", (value == 127) ? 1 : 0); } else { // DEL-mode is not active, just toggle play (on push only). if (value == 127) { script.toggleControl(group,"play"); } } } // Function to deal with the cue buttons, (because they have a DEL-mode behaviour). BehringerCMDStudio4a.cue = function (channel, control, value, status, group) { if (BehringerCMDStudio4a.delButtonState[channel]) { // DEL-mode is active, do reverse play. engine.setValue(group, "reverse", (value == 127) ? 1 : 0); } else { // DEL-mode is not active so just cue. engine.setValue(group, "cue_default", (value == 127) ? 1 : 0); } } // Function to deal with the scratch mode buttons. BehringerCMDStudio4a.scratch = function (channel, control, value, status, group) { BehringerCMDStudio4a.scratchButtonState[channel] = !BehringerCMDStudio4a.scratchButtonState[channel]; midi.sendShortMsg(status, control, BehringerCMDStudio4a.scratchButtonState[channel] ? 0x01 : 0x00); } // Function to deal with the FX Assign buttons, (because they also act as "shift" buttons). BehringerCMDStudio4a.fxAssign = function (channel, control, value, status, group) { // FX Assign buttons start at 0x52. var fxAssignButton = (control - 0x52) & 1; // Either 0 or 1 depending on button (1 or 2). if (value == 127) { // Button pushed. BehringerCMDStudio4a.fxAssignPushed = true; BehringerCMDStudio4a.fxAssignShiftUsed = false; BehringerCMDStudio4a.fxAssignLastGroup = group; } else { // Button released. BehringerCMDStudio4a.fxAssignPushed = false; // Only toggle the effect on release if the "shift" function wasn't used. if (!BehringerCMDStudio4a.fxAssignShiftUsed) { script.toggleControl(group,"group_[Channel"+(channel+1)+"]_enable"); } } } // Function to deal with the browse left/right buttons, (because they have an "FX Assign mode" behaviour). BehringerCMDStudio4a.browseLR = function (channel, control, value, status, group) { if (BehringerCMDStudio4a.fxAssignPushed) { BehringerCMDStudio4a.fxAssignShiftUsed = true; if (control == 0x2) { // Left. engine.setValue(BehringerCMDStudio4a.fxAssignLastGroup,"prev_chain", 1); } else { // Right. engine.setValue(BehringerCMDStudio4a.fxAssignLastGroup,"next_chain", 1); } } else { if (control == 0x2) { // Left. engine.setValue(group, "SelectPrevPlaylist",1) } else { // Right. engine.setValue(group, "SelectNextPlaylist",1) } } } // Functions to deal with the hot-cue buttons, (because they have a DEL-mode behaviour). BehringerCMDStudio4a.hotcue = function (channel, control, value, status, group) { // Translate the button to the actual hotcue. var hotcue = control-0x21; // Hotcue buttons on left deck go from 0x22 to 0x29 if (hotcue>8) { // Right deck, buttons are 0x20 higher so we need to compensate. hotcue = hotcue-0x20; } if (BehringerCMDStudio4a.delPushed) { // DEL button is being held so delete the hotcue. engine.setValue(group, "hotcue_"+hotcue+"_clear", 1); BehringerCMDStudio4a.delShiftUsed = true; } else { // DEL button is not being held down. if (BehringerCMDStudio4a.delButtonState[channel]) { // DEL-mode is active, lets do auto-loops. engine.setValue(group, "beatloop_"+(1/8)*Math.pow(2,hotcue-1)+"_toggle", 1); if (value == 0) { // Button is being released. Disable then re-enable slip if it // is active. This "re-syncs" the playback after every // auto-loop in slip-mode which is a nice effect and probably // what you want most of the time if slip is on. if (engine.getValue(group, "slip_enabled") == 1) { engine.setValue(group, "slip_enabled", 0); // It seems we can't just flip a param off and on in the // same call! Since we've just turned slip off, I can't now // turn it on directly here, the only work-around I could // think of was to create a (very short) timed call-back // to turn it off! // Raised bug about this: // https://bugs.launchpad.net/mixxx/+bug/1538200 // Changed timer from 50 to 100 after the pathology of this // bug was explined in the bug report. engine.beginTimer(100, function() { engine.setValue(group, "slip_enabled", 1); }, 1); } } } else { // DEL-mode is not active so do the set/jump-to hotcue function as normal. engine.setValue(group, "hotcue_"+hotcue+"_activate", (value == 127) ? 1 : 0); } } } // Functions to deal with the pitch inc/dec buttons, (because they have a DEL-mode behaviour). BehringerCMDStudio4a.pitch = function (channel, control, value, status, group) { // Work out the direction. var direction = ((control & 0x01) == 0) ? "down" : "up"; // Work out the type (and join) by looking at the DEL button state. var type = BehringerCMDStudio4a.delButtonState[channel] ? "pitch" : "rate"; var join = BehringerCMDStudio4a.delButtonState[channel] ? "" : "_perm"; // Pushed or released? if (value == 127) { // Button pushed. BehringerCMDStudio4a.pitchPushed[control & 0x01][channel] = true; // Is the other button pushed too? if (BehringerCMDStudio4a.pitchPushed[(~control) & 0x01][channel]) { engine.setValue(group, type, 0); // Yep! reset the control. } else { engine.setValue(group, type+join+"_"+direction, 1); } } else { // Button released. BehringerCMDStudio4a.pitchPushed[control & 0x01][channel] = false; engine.setValue(group, "rate_perm_"+direction, 0); // Keeps the UI in sync with the button state. } } // Functions to deal with the wheel (i.e. scratcing and jog). // Why is there no (XML) support in Mixxx for this most basic of functions? // I suspect the vast majority of controller mappings use the same code // (provided in the Wiki). BehringerCMDStudio4a.wheelTouch = function (channel, control, value, status, group) { channel = channel+1; if (value > 0) { // We're touching the wheel. var alpha = 1.0/8; var beta = alpha/32; engine.scratchEnable(channel, 600, 33+1/3, alpha, beta); } else { // We've released the wheel. engine.scratchDisable(channel); } }; BehringerCMDStudio4a.wheelTurn = function (channel, control, value, status, group) { var deck = channel+1; var newValue = value-64; if (BehringerCMDStudio4a.scratchButtonState[channel]){ if (engine.isScratching(deck)){ engine.scratchTick(deck,newValue); // Scratch! } } else { engine.setValue(group, "jog", newValue); // Jog. } };