/****************************************************************/
/*      Vestax VCI-400 MIDI controller script v2.00             */
/*   Copyright (C) 2011-2014, Owen Williams, Tobias Rafreider   */
/*      but feel free to tweak this to your heart's content!    */
/*      For Mixxx version 1.12                                  */
/****************************************************************/


/*
Owen todo:
* add no-handler versions of play / pause / etc so we can put out the lights.
*/

/*
 * The VCI-400 class definition
 * All attributes of this class represent
 * buttons and controls affecting the MASTER output
 */
VestaxVCI400 = new function() {
   this.group = "[Master]";
   this.Controls = []; //The list of control objects, i.e., knobs
   this.Buttons = [];  //The list of buttons objects
}

VestaxVCI400.shiftActive = false;
VestaxVCI400.enableMasterVu = false;

VestaxVCI400.ModeEnum = {
    NONE : -1,
    HOTCUE : 0,
    LOOP : 1,
    ROLL : 2,
    SAMPLER : 3,
}

/*
 * Called when the MIDI device is opened for set up
 */
VestaxVCI400.init = function (id) {
    engine.setValue("[App]", "num_decks", 4);
    if (engine.getValue("[App]", "num_samplers") < 8) {
        engine.setValue("[App]", "num_samplers", 8);
    }
    //Initialize controls and their default values here
    VestaxVCI400.Decks.A.init();
    VestaxVCI400.Decks.B.init();
    VestaxVCI400.Decks.C.init();
    VestaxVCI400.Decks.D.init();

    //Connect vu meters
    // No need if using the sound card
    engine.connectControl("[Main]", "vu_meter_left", "VestaxVCI400.onMasterVuMeterLChanged");
    engine.connectControl("[Main]", "vu_meter_right", "VestaxVCI400.onMasterVuMeterRChanged");

    //Reset VU meters
    if (VestaxVCI400.enableMasterVu) {
        midi.sendShortMsg("0xbe", 43, 0);
        midi.sendShortMsg("0xbe", 44, 0);
    }
};

/*
 * Called when the MIDI device is closed
 */
VestaxVCI400.shutdown = function () {
    //Initialize controls and their default values here
    VestaxVCI400.Decks.A.clearLights();
    VestaxVCI400.Decks.B.clearLights();
    VestaxVCI400.Decks.C.clearLights();
    VestaxVCI400.Decks.D.clearLights();

    VestaxVCI400.Decks.A.setButtonMode(VestaxVCI400.ModeEnum.NONE);
    VestaxVCI400.Decks.B.setButtonMode(VestaxVCI400.ModeEnum.NONE);
    VestaxVCI400.Decks.C.setButtonMode(VestaxVCI400.ModeEnum.NONE);
    VestaxVCI400.Decks.D.setButtonMode(VestaxVCI400.ModeEnum.NONE);

    //Reset VU meters
    if (VestaxVCI400.enableMasterVu) {
        midi.sendShortMsg("0xbe", 43, 0);
        midi.sendShortMsg("0xbe", 44, 0);
    }
};

VestaxVCI400.shiftActivate = function (channel, control, value, status, group) {
    VestaxVCI400.shiftActive = (value != 0);
}

VestaxVCI400.onMasterVuMeterLChanged = function(value){
    if (!VestaxVCI400.enableMasterVu) { return; }
    var normalizedVal = parseInt(value*127);
    midi.sendShortMsg("0xbe", 43, normalizedVal);
}
VestaxVCI400.onMasterVuMeterRChanged = function(value){
    if (!VestaxVCI400.enableMasterVu) { return; }
    var normailizedVal = parseInt(value*127);
    midi.sendShortMsg("0xbe", 44, normailizedVal);
}

/*
 * Adds a button to the VCI-400 class object
 * The button must affect the MASTER.
 *
 * Parameters
 *  - buttonName: 'unique name of the button'
 *  - button 'the button object to be added
 *  - eventHandlder 'the class method which will be executed if button state has changed.
 */
VestaxVCI400.addButton = function(buttonName, button, eventHandler) {
   if(eventHandler) {
      var executionEnvironment = this;
      button.group = this.group;
      function handler(value) {
         button.state = value;
         executionEnvironment[eventHandler](value);
      }
      button.handler = handler;
   }
   this.Buttons[buttonName] = button; //Append the button the class attribute
};

/* ============PADBUTTON CLASS DEFINITION====================
 *
 * A button refers to push button control
 *
 * =======================================================
 */
VestaxVCI400.ButtonState = {"released":0x00, "pressed":0x7F};
VestaxVCI400.ButtonLedState = {"on": 0x7F, "off": 0x00};

/*
 * The button object
 */
VestaxVCI400.Button = function (statusByte, midiNo) {
   this.statusByte = statusByte;
   this.midiNo = midiNo;
   this.state = VestaxVCI400.ButtonState.released;
   this.illuminated = false;
   this.group; //this will be set when by the addButton method
};

/*
 * Button objects have a 'handleEvent' method
 * ,that is, the action to be performed if pressed.
 */
VestaxVCI400.Button.prototype.handleEvent = function(value) {
   this.handler(value);
};

//Calling this method will illuminate the button depending on if value is true or false
VestaxVCI400.Button.prototype.illuminate = function(value) {
   if(value ==true){
        midi.sendShortMsg(this.statusByte, this.midiNo, VestaxVCI400.ButtonLedState.on);
        this.illuminated = true;
    }
    else{
        midi.sendShortMsg(this.statusByte, this.midiNo, VestaxVCI400.ButtonLedState.off);
        this.illuminated = false;
    }
};

/*
 * =========DECK CLASS DEFINITION ===========================
 *
 * Although the VCI 400 has a 2 deck layout, you can
 * control up to 4 virtual decks.
 * Objects of this class do represent virtual decks.
 *
 *
 */
VestaxVCI400.Deck = function (deckNumber, group, active) {
   this.deckIdentifier = deckNumber;
   this.group = group;
   this.isActive = active; // If this deck is currently controlled by the VCI-400 (A/C B/D switches)
   this.Buttons = [];
   this.deckNumber = group.substring(8,9);// [Channel1]
   this.vinylActive = false;
   this.wheelTouchInertiaTimer = 0;
}
/*
 * Each deck has a disjunct set of buttons
 * This method adds a assigns a button to a deck
 */
VestaxVCI400.Deck.prototype.addButton = VestaxVCI400.addButton;

// ===========CREATING LEFT AND RIGHT DECKS OBJECTS
VestaxVCI400.Decks = {
    "A": new VestaxVCI400.Deck("A","[Channel1]", true), //Deck A and B will
    "B": new VestaxVCI400.Deck("B","[Channel2]", true),//be controlled by the VCI-400
    "C": new VestaxVCI400.Deck("C","[Channel3]", false),
    "D": new VestaxVCI400.Deck("D","[Channel4]", false)
};
VestaxVCI400.GroupToDeck = {
    "[Channel1]":"A",
    "[Channel2]":"B",
    "[Channel3]":"C",
    "[Channel4]":"D"
};
//returns the deck object given a group, e.g. "[Channel1]"
VestaxVCI400.GetDeck = function(group) {
   try {
      return VestaxVCI400.Decks[VestaxVCI400.GroupToDeck[group]];
   } catch(ex) {
      return null;
   }
}

//=========Pad Buttons=========================
VestaxVCI400.Deck.prototype.onPadButton1Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON1, 1, value);
};
VestaxVCI400.Deck.prototype.onPadButton2Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON2, 2, value);
};
VestaxVCI400.Deck.prototype.onPadButton3Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON3, 3, value);
};
VestaxVCI400.Deck.prototype.onPadButton4Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON4, 4, value);
};
VestaxVCI400.Deck.prototype.onPadButton5Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON5, 5, value);
};
VestaxVCI400.Deck.prototype.onPadButton6Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON6, 6, value);
};
VestaxVCI400.Deck.prototype.onPadButton7Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON7, 7, value);
};
VestaxVCI400.Deck.prototype.onPadButton8Activate = function(value){
    this.onDynamicButtonPressed(this.Buttons.PADBUTTON8, 8, value);
};

// Depending on what mode has been selected, the bottom row of buttons perform different functions.
VestaxVCI400.Deck.prototype.onDynamicButtonPressed = function(button, buttonNumber, value){
    switch(this.buttonMode) {
    case VestaxVCI400.ModeEnum.HOTCUE:
        var hotCueAction = "hotcue_".concat(buttonNumber.toString());
        if (VestaxVCI400.shiftActive) {
            hotCueAction = hotCueAction.concat("_clear");
        } else {
            hotCueAction = hotCueAction.concat("_activate");
        }
        if(value == VestaxVCI400.ButtonState.pressed){
            engine.setValue(this.group, hotCueAction, 1);
        }
        else{
            engine.setValue(this.group, hotCueAction, 0);
        }
        break;
    case VestaxVCI400.ModeEnum.LOOP:
        var loop_size = Math.pow(2, buttonNumber-4);
        if(value == VestaxVCI400.ButtonState.pressed) {
            engine.setValue(this.group, "beatloop_" + loop_size.toString() + "_activate", 1);
        }
        break;
    case VestaxVCI400.ModeEnum.ROLL:
        var loop_size = Math.pow(2, buttonNumber - 4);
        if(value == VestaxVCI400.ButtonState.pressed) {
            engine.setValue(this.group, "beatlooproll_" + loop_size.toString() + "_activate", 1);
        } else {
            engine.setValue(this.group, "beatlooproll_" + loop_size.toString() + "_activate", 0);
        }
        break;
    case VestaxVCI400.ModeEnum.SAMPLER:
        if (buttonNumber > 4) {
            break;
        }
        if (VestaxVCI400.shiftActive) {
            if (value == VestaxVCI400.ButtonState.pressed) {
                engine.setValue("[Sampler" + buttonNumber + "]", "eject", 1);
            } else {
                button.illuminate(false);
                engine.setValue("[Sampler" + buttonNumber + "]", "eject", 0);
            }
        } else {
            if(value == VestaxVCI400.ButtonState.pressed) {
                engine.setValue("[Sampler" + buttonNumber + "]", "cue_gotoandplay", 1);
            } else {
                engine.setValue("[Sampler" + buttonNumber + "]", "stop", 1);
            }
        }
        break;
    default:
    }
};

/*
 * The deck VU meters
 */
VestaxVCI400.Deck.prototype.onVuMeterChanged = function(value, group, key) {
    var normalizedVal = parseInt(value*127);
    var deckNumber = parseInt(group.substring(8,9));
    var midiNo = deckNumber + 1;
    var statusByte = "0xb".concat(midiNo.toString());
    midi.sendShortMsg(statusByte, 17, normalizedVal);
};

/*
 * This is called when Mixxx notifies us that a button state has changed
 * via engine.connect.
 */
VestaxVCI400.Deck.prototype.onHotCue1Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON1, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue2Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON2, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue3Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON3, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue4Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON4, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue5Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON5, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue6Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON6, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue7Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON7, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onHotCue8Changed = function(value, group, key) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.onHotCueChanged(deck.Buttons.PADBUTTON8, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};

VestaxVCI400.Deck.prototype.onSampler1DurationChanged = function(value, group, key) {
    try {
        this.onSamplerDurationChanged(this.Buttons.PADBUTTON1, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onSampler2DurationChanged = function(value, group, key) {
    try {
        this.onSamplerDurationChanged(this.Buttons.PADBUTTON2, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onSampler3DurationChanged = function(value, group, key) {
    try {
        this.onSamplerDurationChanged(this.Buttons.PADBUTTON3, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};
VestaxVCI400.Deck.prototype.onSampler4DurationChanged = function(value, group, key) {
    try {
        this.onSamplerDurationChanged(this.Buttons.PADBUTTON4, value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};

VestaxVCI400.Deck.prototype.onToggleLightsChanged = function(value, group, key) {
    try {
        VestaxVCI400.setToggleLights(group);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};

/*
 * This is called when Mixxx notifies us that a button state has changed
 * via engine.connect.
 */
VestaxVCI400.Deck.prototype.onHotCueChanged = function(button, value) {
    try {
        if (this.buttonMode == VestaxVCI400.ModeEnum.HOTCUE) {
            if(value == 0) {
                button.illuminate(false); //turn off LED
            }
            else {
                button.illuminate(true); //turn LED on
            }
        }
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
    }
};

VestaxVCI400.Deck.prototype.onSamplerDurationChanged = function(button, value) {
    try {
        if (this.buttonMode == VestaxVCI400.ModeEnum.SAMPLER) {
            button.illuminate(value > 0);
        }
    } catch(ex) {
        VestaxVCI400.printError(ex);
    }
};

VestaxVCI400.Deck.prototype.clearLights = function() {
    // Make sure all buttons are not illuminated
    for(b in this.Buttons){
        this.Buttons[b].illuminate(false);
    }

    this.setButtonMode(VestaxVCI400.ModeEnum.ROLL);
    this.onVuMeterChanged(0, this.group, 0);
}

/*
 * Method to set the initial state of a deck
 */
VestaxVCI400.Deck.prototype.init = function() {
    this.clearLights();
    //Connect controls
    engine.connectControl(this.group,"vu_meter", "VestaxVCI400.Decks."+this.deckIdentifier+".onVuMeterChanged");

    engine.connectControl(this.group,"hotcue_1_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue1Changed");
    engine.connectControl(this.group,"hotcue_2_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue2Changed");
    engine.connectControl(this.group,"hotcue_3_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue3Changed");
    engine.connectControl(this.group,"hotcue_4_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue4Changed");
    engine.connectControl(this.group,"hotcue_5_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue5Changed");
    engine.connectControl(this.group,"hotcue_6_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue6Changed");
    engine.connectControl(this.group,"hotcue_7_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue7Changed");
    engine.connectControl(this.group,"hotcue_8_enabled", "VestaxVCI400.Decks."+this.deckIdentifier+".onHotCue8Changed");

    engine.connectControl("[Sampler1]","track_samples", "VestaxVCI400.Decks."+this.deckIdentifier+".onSampler1DurationChanged");
    engine.connectControl("[Sampler2]","track_samples", "VestaxVCI400.Decks."+this.deckIdentifier+".onSampler2DurationChanged");
    engine.connectControl("[Sampler3]","track_samples", "VestaxVCI400.Decks."+this.deckIdentifier+".onSampler3DurationChanged");
    engine.connectControl("[Sampler4]","track_samples", "VestaxVCI400.Decks."+this.deckIdentifier+".onSampler4DurationChanged");

    engine.connectControl(this.group, "quantize", "VestaxVCI400.Decks."+this.deckIdentifier+".onToggleLightsChanged");
    engine.connectControl(this.group, "keylock", "VestaxVCI400.Decks."+this.deckIdentifier+".onToggleLightsChanged");
};

// Add buttons to individual decks.  The handlers here are not connected to the Mixxx engine.
// Another function that takes a standard channel/control/value/status/group argument needs to be
// created and then hand off to these handlers.

VestaxVCI400.Decks.A.addButton("VINYL", new VestaxVCI400.Button(0x92,0x06), "onVinyl");
VestaxVCI400.Decks.B.addButton("VINYL", new VestaxVCI400.Button(0x93,0x06), "onVinyl");
VestaxVCI400.Decks.C.addButton("VINYL", new VestaxVCI400.Button(0x94,0x06), "onVinyl");
VestaxVCI400.Decks.D.addButton("VINYL", new VestaxVCI400.Button(0x95,0x06), "onVinyl");

VestaxVCI400.Decks.A.addButton("WHEEL", new VestaxVCI400.Button(0xB2,0x27), "onWheelTouch");
VestaxVCI400.Decks.B.addButton("WHEEL", new VestaxVCI400.Button(0xB3,0x27), "onWheelTouch");
VestaxVCI400.Decks.C.addButton("WHEEL", new VestaxVCI400.Button(0xB4,0x27), "onWheelTouch");
VestaxVCI400.Decks.D.addButton("WHEEL", new VestaxVCI400.Button(0xB5,0x27), "onWheelTouch");

VestaxVCI400.Decks.A.addButton("MODE_HOTCUE", new VestaxVCI400.Button(0x92,0x15), "onModeHotcue");
VestaxVCI400.Decks.B.addButton("MODE_HOTCUE", new VestaxVCI400.Button(0x93,0x15), "onModeHotcue");
VestaxVCI400.Decks.C.addButton("MODE_HOTCUE", new VestaxVCI400.Button(0x94,0x15), "onModeHotcue");
VestaxVCI400.Decks.D.addButton("MODE_HOTCUE", new VestaxVCI400.Button(0x95,0x15), "onModeHotcue");

VestaxVCI400.Decks.A.addButton("MODE_LOOP", new VestaxVCI400.Button(0x92,0x16), "onModeLoop");
VestaxVCI400.Decks.B.addButton("MODE_LOOP", new VestaxVCI400.Button(0x93,0x16), "onModeLoop");
VestaxVCI400.Decks.C.addButton("MODE_LOOP", new VestaxVCI400.Button(0x94,0x16), "onModeLoop");
VestaxVCI400.Decks.D.addButton("MODE_LOOP", new VestaxVCI400.Button(0x95,0x16), "onModeLoop");

VestaxVCI400.Decks.A.addButton("MODE_ROLL", new VestaxVCI400.Button(0x92,0x17), "onModeRoll");
VestaxVCI400.Decks.B.addButton("MODE_ROLL", new VestaxVCI400.Button(0x93,0x17), "onModeRoll");
VestaxVCI400.Decks.C.addButton("MODE_ROLL", new VestaxVCI400.Button(0x94,0x17), "onModeRoll");
VestaxVCI400.Decks.D.addButton("MODE_ROLL", new VestaxVCI400.Button(0x95,0x17), "onModeRoll");

VestaxVCI400.Decks.A.addButton("MODE_SAMPLER", new VestaxVCI400.Button(0x92,0x18), "onModeSampler");
VestaxVCI400.Decks.B.addButton("MODE_SAMPLER", new VestaxVCI400.Button(0x93,0x18), "onModeSampler");
VestaxVCI400.Decks.C.addButton("MODE_SAMPLER", new VestaxVCI400.Button(0x94,0x18), "onModeSampler");
VestaxVCI400.Decks.D.addButton("MODE_SAMPLER", new VestaxVCI400.Button(0x95,0x18), "onModeSampler");

VestaxVCI400.Decks.A.addButton("PADBUTTON1", new VestaxVCI400.Button(0x92,0x07), "onPadButton1Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON2", new VestaxVCI400.Button(0x92,0x08), "onPadButton2Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON3", new VestaxVCI400.Button(0x92,0x09), "onPadButton3Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON4", new VestaxVCI400.Button(0x92,0x0A), "onPadButton4Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON5", new VestaxVCI400.Button(0x92,0x0B), "onPadButton5Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON6", new VestaxVCI400.Button(0x92,0x0C), "onPadButton6Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON7", new VestaxVCI400.Button(0x92,0x0D), "onPadButton7Activate");
VestaxVCI400.Decks.A.addButton("PADBUTTON8", new VestaxVCI400.Button(0x92,0x0E), "onPadButton8Activate");

VestaxVCI400.Decks.B.addButton("PADBUTTON1", new VestaxVCI400.Button(0x93,0x07), "onPadButton1Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON2", new VestaxVCI400.Button(0x93,0x08), "onPadButton2Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON3", new VestaxVCI400.Button(0x93,0x09), "onPadButton3Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON4", new VestaxVCI400.Button(0x93,0x0A), "onPadButton4Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON5", new VestaxVCI400.Button(0x93,0x0B), "onPadButton5Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON6", new VestaxVCI400.Button(0x93,0x0C), "onPadButton6Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON7", new VestaxVCI400.Button(0x93,0x0D), "onPadButton7Activate");
VestaxVCI400.Decks.B.addButton("PADBUTTON8", new VestaxVCI400.Button(0x93,0x0E), "onPadButton8Activate");

VestaxVCI400.Decks.C.addButton("PADBUTTON1", new VestaxVCI400.Button(0x94,0x07), "onPadButton1Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON2", new VestaxVCI400.Button(0x94,0x08), "onPadButton2Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON3", new VestaxVCI400.Button(0x94,0x09), "onPadButton3Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON4", new VestaxVCI400.Button(0x94,0x0A), "onPadButton4Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON5", new VestaxVCI400.Button(0x94,0x0B), "onPadButton5Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON6", new VestaxVCI400.Button(0x94,0x0C), "onPadButton6Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON7", new VestaxVCI400.Button(0x94,0x0D), "onPadButton7Activate");
VestaxVCI400.Decks.C.addButton("PADBUTTON8", new VestaxVCI400.Button(0x94,0x0E), "onPadButton8Activate");

VestaxVCI400.Decks.D.addButton("PADBUTTON1", new VestaxVCI400.Button(0x95,0x07), "onPadButton1Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON2", new VestaxVCI400.Button(0x95,0x08), "onPadButton2Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON3", new VestaxVCI400.Button(0x95,0x09), "onPadButton3Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON4", new VestaxVCI400.Button(0x95,0x0A), "onPadButton4Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON5", new VestaxVCI400.Button(0x95,0x0B), "onPadButton5Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON6", new VestaxVCI400.Button(0x95,0x0C), "onPadButton6Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON7", new VestaxVCI400.Button(0x95,0x0D), "onPadButton7Activate");
VestaxVCI400.Decks.D.addButton("PADBUTTON8", new VestaxVCI400.Button(0x95,0x0E), "onPadButton8Activate");


/* ================ MAPPING FUNCTIONS ==========================
 *
 * Having defined the some objects and class definitions
 * we can now easily build the mapping
 */
VestaxVCI400.otherSoundcard = function (channel, control, value, status, group) {
    try{
        if (value == 0) {
            return;
        }
        // I think there is a bug in the VCI400 that causes the master vu meters to always be
        // linked to the 400's sound card, so if we try to drive the meters ourselves we get strobing.
        // Therefore we can't drive the VU Meters with Mixxx in a way that looks good.
        // See: http://help.vestax.co.jp/en/detail.php?id=459&id_page_url=396
        //VestaxVCI400.enableMasterVu = !VestaxVCI400.enableMasterVu;
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.deckSwitch = function (channel, control, value, status, group) {
    var deck = VestaxVCI400.GetDeck(group);
    deck.isActive = (value == 127)? true: false;
    print("Deck "+deck.deckIdentifier+ " is controlled by VCI-400: "+deck.isActive);

    VestaxVCI400.setToggleLights(deck.group)
};

VestaxVCI400.setToggleLights = function (group) {
    var chan;

    if (!VestaxVCI400.GetDeck(group).isActive) {
        return;
    }

    if (group == "[Channel1]" || group == "[Channel3]") {
        chan = "0x9C";
    } else {
        chan = "0x9D";
    }

    midi.sendShortMsg(chan, "0x03", engine.getValue(group, "quantize") > 0.5 ? 0x7F : 0);
    midi.sendShortMsg(chan, "0x04", engine.getValue(group, "keylock") > 0.5 ? 0x7F : 0);
}

VestaxVCI400.wheelTouch = function (channel, control, value, status, group) {
    try{
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.WHEEL.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};
VestaxVCI400.Deck.prototype.onWheelTouch = function(value) {
    if (this.wheelTouchInertiaTimer != 0) {
        // The wheel was touched again, reset the timer.
        engine.stopTimer(this.wheelTouchInertiaTimer);
        this.wheelTouchInertiaTimer = 0;
    }
    if(value == VestaxVCI400.ButtonState.pressed) {
        engine.scratchEnable(this.deckNumber, 4096, 33.3333, 0.125, 0.125/32, true);
    } else {
        // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away.
        // Depending on how fast the platter was moving, lengthen the time we'll wait.
        var scratchRate = Math.abs(engine.getValue(this.group, "scratch2"));
        var inertiaTime = Math.pow(1.8, scratchRate) * 50;
        if (inertiaTime < 100) {
            // Just do it now.
            this.finishWheelTouch();
        } else {
            this.wheelTouchInertiaTimer = engine.beginTimer(
                    inertiaTime, () => { VestaxVCI400.Decks[this.deckIdentifier].finishWheelTouch(); }, true);
        }
    }
};

VestaxVCI400.Deck.prototype.finishWheelTouch = function() {
    this.wheelTouchInertiaTimer = 0;
    if (this.vinylActive) {
        // Vinyl button still being pressed, don't disable scratch mode yet.
        this.wheelTouchInertiaTimer = engine.beginTimer(
                100, () => { VestaxVCI400.Decks[this.deckIdentifier].finishWheelTouch(); }, true);
        return;
    }
    var play = engine.getValue(this.group, "play");
    if (play != 0) {
        // If we are playing, just hand off to the engine.
        engine.scratchDisable(this.deckNumber, true);
    } else {
        // If things are paused, there will be a non-smooth handoff between scratching and jogging.
        // Instead, keep scratch on until the platter is not moving.
        var scratchRate = Math.abs(engine.getValue(this.group, "scratch2"));
        if (scratchRate < 0.01) {
            // The platter is basically stopped, now we can disable scratch and hand off to jogging.
            engine.scratchDisable(this.deckNumber, false);
        } else {
            // Check again soon.
            this.wheelTouchInertiaTimer = engine.beginTimer(
                    100, () => { VestaxVCI400.Decks[this.deckIdentifier].finishWheelTouch(); }, true);
        }
    }
};

// Jog wheels
VestaxVCI400.wheelMove = function (channel, control, value, status, group) {
    try{
        var deck = VestaxVCI400.GetDeck(group);
        deck.onWheelMove(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.Deck.prototype.onWheelMove = function(value) {
    var jogValue = value - 0x40; // -64 to +63, - = CCW, + = CW
    // Note that we always set the jog value even if scratching is active.  This seems
    // to create a better handoff between scratching and not-scratching.
    if (engine.getValue(this.group, "play")) {
        engine.setValue(this.group, "jog", jogValue / 40);
    } else {
        engine.setValue(this.group, "jog", jogValue / 10);
    }
    if(engine.getValue(this.group, "scratch2_enable")){
        engine.scratchTick(this.deckNumber, jogValue);
    }
};

// The play button usually does play/pause as normal, but if shift is held
// we do a braking stop.
VestaxVCI400.playButton = function (channel, control, value, status, group) {
    if (value === 0) {
        return;
    }
    var playing = engine.getValue(group, "play");
    if (playing && VestaxVCI400.shiftActive) {
        script.brake(channel, control, value, status, group, 100.0);
        return;
    }

    script.toggleControl(group, "play");
};

// The censor button usually does a reverse roll, but if shift is held
// we do a backspin stop.
VestaxVCI400.censorButton = function (channel, control, value, status, group) {
    var playing = engine.getValue(group, "play");
    if (playing && VestaxVCI400.shiftActive && value !== 0) {
        script.spinback(channel, control, value, status, group, 30.0, -10.0);
        return;
    }

    engine.setValue(group, "reverseroll", value);
};

VestaxVCI400.vinylButton = function (channel, control, value, status, group) {
    try{
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.VINYL.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

// The Vinyl button acts the same as a wheel touch, allowing the DJ to fling the platter and
// let go of the platter without worrying that the track will start playing again.
VestaxVCI400.Deck.prototype.onVinyl = function(value) {
    this.vinylActive = (value != 0);
    if (value > 0) {
        this.onWheelTouch(value);
    } else {
        this.finishWheelTouch();
    }
}

VestaxVCI400.fx1ToggleButton1 = function (channel, control, value, status, group) {
}

VestaxVCI400.fx1ToggleButton2 = function (channel, control, value, status, group) {
}

VestaxVCI400.fx1ToggleButton3 = function (channel, control, value, status, group) {
    if (value == 0 ) { return; }
    var group = VestaxVCI400.Decks.A.isActive ? "[Channel1]" : "[Channel3]";
    var curval = engine.getValue(group, "quantize");
    engine.setValue(group, "quantize", !curval);
}

VestaxVCI400.fx1ToggleButton4 = function (channel, control, value, status, group) {
    if (value == 0 ) { return; }
    var group = VestaxVCI400.Decks.A.isActive ? "[Channel1]" : "[Channel3]";
    var curval = engine.getValue(group, "keylock");
    engine.setValue(group, "keylock", !curval);
}

VestaxVCI400.fx2ToggleButton1 = function (channel, control, value, status, group) {
}

VestaxVCI400.fx2ToggleButton2 = function (channel, control, value, status, group) {
}

VestaxVCI400.fx2ToggleButton3 = function (channel, control, value, status, group) {
    if (value == 0 ) { return; }
    var group = VestaxVCI400.Decks.B.isActive ? "[Channel2]" : "[Channel4]";
    var curval = engine.getValue(group, "quantize");
    engine.setValue(group, "quantize", !curval);
}

VestaxVCI400.fx2ToggleButton4 = function (channel, control, value, status, group) {
    if (value == 0 ) { return; }
    var group = VestaxVCI400.Decks.B.isActive ? "[Channel2]" : "[Channel4]";
    var curval = engine.getValue(group, "keylock");
    engine.setValue(group, "keylock", !curval);
}

VestaxVCI400.loadTrack = function (channel, control, value, status, group) {
    if (VestaxVCI400.shiftActive) {
        engine.setValue(group, "eject", value);
    } else {
        engine.setValue(group, "LoadSelectedTrack", value);
    }
}

VestaxVCI400.fx1Knob = function (channel, control, value, status, group) {
    var mixVal = engine.getValue("[EffectRack1_EffectUnit1]", "mix");
    if (value == 0x01) {
        mixVal += 0.05;
    } else {
        mixVal -= 0.05;
    }
    mixVal = Math.max(0.0, Math.min(1.0, mixVal));
    engine.setValue("[EffectRack1_EffectUnit1]", "mix", mixVal);
}

VestaxVCI400.fx2Knob = function (channel, control, value, status, group) {
    var mixVal = engine.getValue("[EffectRack1_EffectUnit2]", "mix");
    if (value == 0x01) {
        mixVal += 0.05;
    } else {
        mixVal -= 0.05;
    }
    mixVal = Math.max(0.0, Math.min(1.0, mixVal));
    engine.setValue("[EffectRack1_EffectUnit2]", "mix", mixVal);
}

/*
 * Pad Buttons
 */
VestaxVCI400.padbutton1Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON1.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton2Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON2.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton3Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON3.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton4Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON4.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton5Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON5.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton6Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON6.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton7Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON7.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};
VestaxVCI400.padbutton8Activate = function (channel, control, value, status, group) {
    try{
       var deck = VestaxVCI400.GetDeck(group);
       deck.Buttons.PADBUTTON8.handleEvent(value);
   }
   catch(ex) {
       VestaxVCI400.printError(ex);
  }
};

VestaxVCI400.loopKnob = function (channel, control, value, status, group) {
    try{
        var deck = VestaxVCI400.GetDeck(group);
        var isLoopActive = engine.getValue(deck.group, "loop_enabled");
        var jogValue = value - 0x40; // -64 to +63, - = CCW, + = CW

        if(isLoopActive){
            if (VestaxVCI400.shiftActive) {
                if(jogValue > 0)
                    engine.setValue(deck.group, "loop_move", -1)
                else
                    engine.setValue(deck.group, "loop_move", 1)
            } else {
                if(jogValue > 0) {
                    // Because loop_halve is supposed to be a pushbutton, we have to
                    // fake the button-off event to clear out the "pressed" status.
                    engine.setValue(deck.group, "loop_halve", 1)
                    engine.setValue(deck.group, "loop_halve", 0)
                } else {
                    engine.setValue(deck.group, "loop_double", 1)
                    engine.setValue(deck.group, "loop_double", 0)
                }
            }
        }
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
}

VestaxVCI400.pitchKnob = function (channel, control, value, status, group) {
    try{
        var deck = VestaxVCI400.GetDeck(group);
        var jogValue = value - 0x40; // -64 to +63, - = CCW, + = CW

        if (VestaxVCI400.shiftActive) {
            var playPosition = engine.getValue(deck.group, "playposition")
            if (jogValue > 0 ) {
                playPosition -= 0.0125;
            } else {
                playPosition += 0.0125;
            }
            engine.setValue(deck.group, "playposition", playPosition);
        } else {
            if(jogValue > 0) {
                  engine.setValue(deck.group, "pitch_down_small", 1);
                  engine.setValue(deck.group, "pitch_down_small", 0);
            } else {
                  engine.setValue(deck.group, "pitch_up_small", 1);
                  engine.setValue(deck.group, "pitch_up_small", 0);
            }
        }
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
}

VestaxVCI400.modeHotcue = function (channel, control, value, status, group) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.MODE_HOTCUE.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.modeLoop = function (channel, control, value, status, group) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.MODE_LOOP.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.modeRoll = function (channel, control, value, status, group) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.MODE_ROLL.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.modeSampler = function (channel, control, value, status, group) {
    try {
        var deck = VestaxVCI400.GetDeck(group);
        deck.Buttons.MODE_SAMPLER.handleEvent(value);
    }
    catch(ex) {
        VestaxVCI400.printError(ex);
   }
};

VestaxVCI400.Deck.prototype.lightButton = function(index, on, dim) {

}

VestaxVCI400.Deck.prototype.setButtonMode = function(value) {
    this.buttonMode = value;
    this.Buttons.MODE_HOTCUE.illuminate(this.buttonMode == VestaxVCI400.ModeEnum.HOTCUE);
    this.Buttons.MODE_LOOP.illuminate(this.buttonMode == VestaxVCI400.ModeEnum.LOOP);
    this.Buttons.MODE_ROLL.illuminate(this.buttonMode == VestaxVCI400.ModeEnum.ROLL);
    this.Buttons.MODE_SAMPLER.illuminate(this.buttonMode == VestaxVCI400.ModeEnum.SAMPLER);

    switch(this.buttonMode) {
    case VestaxVCI400.ModeEnum.HOTCUE:
        this.onHotCueChanged(this.Buttons.PADBUTTON1, engine.getValue(this.group, "hotcue_1_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON2, engine.getValue(this.group, "hotcue_2_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON3, engine.getValue(this.group, "hotcue_3_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON4, engine.getValue(this.group, "hotcue_4_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON5, engine.getValue(this.group, "hotcue_5_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON6, engine.getValue(this.group, "hotcue_6_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON7, engine.getValue(this.group, "hotcue_7_enabled"));
        this.onHotCueChanged(this.Buttons.PADBUTTON8, engine.getValue(this.group, "hotcue_8_enabled"));
        break;
    case VestaxVCI400.ModeEnum.LOOP:
        this.Buttons.PADBUTTON1.illuminate(true);
        this.Buttons.PADBUTTON2.illuminate(true);
        this.Buttons.PADBUTTON3.illuminate(true);
        this.Buttons.PADBUTTON4.illuminate(true);
        this.Buttons.PADBUTTON5.illuminate(true);
        this.Buttons.PADBUTTON6.illuminate(true);
        this.Buttons.PADBUTTON7.illuminate(true);
        this.Buttons.PADBUTTON8.illuminate(true);
        break;
    case VestaxVCI400.ModeEnum.ROLL:
        this.Buttons.PADBUTTON1.illuminate(true);
        this.Buttons.PADBUTTON2.illuminate(true);
        this.Buttons.PADBUTTON3.illuminate(true);
        this.Buttons.PADBUTTON4.illuminate(true);
        this.Buttons.PADBUTTON5.illuminate(true);
        this.Buttons.PADBUTTON6.illuminate(true);
        this.Buttons.PADBUTTON7.illuminate(true);
        this.Buttons.PADBUTTON8.illuminate(true);
        break;
    case VestaxVCI400.ModeEnum.SAMPLER:
        this.onSampler1DurationChanged(engine.getValue("[Sampler1]", "track_samples"), this.group);
        this.onSampler2DurationChanged(engine.getValue("[Sampler2]", "track_samples"), this.group);
        this.onSampler3DurationChanged(engine.getValue("[Sampler3]", "track_samples"), this.group);
        this.onSampler4DurationChanged(engine.getValue("[Sampler4]", "track_samples"), this.group);
        this.Buttons.PADBUTTON5.illuminate(false);
        this.Buttons.PADBUTTON6.illuminate(false);
        this.Buttons.PADBUTTON7.illuminate(false);
        this.Buttons.PADBUTTON8.illuminate(false);
        break;
    }
}

VestaxVCI400.Deck.prototype.onModeHotcue = function(value) {
    if(value == VestaxVCI400.ButtonState.pressed) {
        this.setButtonMode(VestaxVCI400.ModeEnum.HOTCUE);
    }
}

VestaxVCI400.Deck.prototype.onModeLoop = function(value) {
    if(value == VestaxVCI400.ButtonState.pressed) {
        this.setButtonMode(VestaxVCI400.ModeEnum.LOOP);
    }
}

VestaxVCI400.Deck.prototype.onModeRoll = function(value) {
    if(value == VestaxVCI400.ButtonState.pressed) {
        this.setButtonMode(VestaxVCI400.ModeEnum.ROLL);
    }
}

VestaxVCI400.Deck.prototype.onModeSampler = function(value) {
    if(value == VestaxVCI400.ButtonState.pressed) {
        this.setButtonMode(VestaxVCI400.ModeEnum.SAMPLER);
   }
}

VestaxVCI400.printError = function(exception){
    print("Error Msg: "+exception.toString());
};
