As discussed on IRC...

Attached is a cleaned up version of midi-mappings-scripts.js - since this 
script is expected to be read by mapping writers, it's readability and 
documentation is quite important I think.

Except for indentation changes, I did not change any code (of course please 
review to make sure I didn't introduce bugs by fatfingering the indentation).

Also changed the comments to jsdoc document strings, it should be possible to 
generate HTML or whatever documentation for this with jsdoc. Also, please 
review my idea of what type each documented parameter is, they might be wrong.

        *hile*

/**
 * @fileOverview This file contains commond MIDI script functions for Mixxx
 * @version 1.0.1
 */

/** @ignore 
  * Only here so you don't get a syntax error on load if the file 
  * was otherwise empty
  */
nop = function () {}

// ----------------- Prototype enhancements ---------------------

/** @ignore */
String.prototype.toInt = function() {
    var a = new Array();
    for (var i = 0; i < this.length; i++) {
        a[i] = this.charCodeAt(i);
    }
    return a;
}

// ----------------- Function overloads ---------------------

/** @ignore 
 * Causes script print() calls to appear in the log file as well
 */
print = function(string) {
    engine.log(string);
}

// ----------------- Generic functions ---------------------

/**
  * converts seconds to minutes:seconds string min:sec
  * @param {integer} secs seconds as integer
  */
function secondstominutes(secs) {
    var m = (secs / 60) | 0;
    return (m < 10 ? "0" + m : m) 
        + ":"
        + ( ( secs %= 60 ) < 10 ? "0" + secs : secs);
}

/**
  * converts milliseconds to min:sec.ms
  * @param {integer} msecs milliseconds as integer
  */
function msecondstominutes(msecs) {
    var m = (msecs / 60000) | 0;
    msecs %= 60000;
    var secs = (msecs / 1000) | 0;
    msecs %= 1000;
    msecs = Math.round(msecs * 100 / 1000);
    if (msecs==100) 
        msecs=99;
    return (m < 10 ? "0" + m : m) 
        + ":"
        + ( secs < 10 ? "0" + secs : secs )
        + "."
        + ( msecs < 10 ? "0" + msecs : msecs);
}

/**
  * Template function for script
  */
function script() {}

/**
 * Prints a debugging string with message details to log
 * @param {integer} channel midi channel (0-15)
 * @param {integer} control midi control code 
 * @param {integer} value midi value
 * @param {integer} status MIDI status code
 * @param {string} group mixxx group
 */
script.debug = function (channel, control, value, status, group) {
    print("Script.Debug --- channel: " + channel.toString(16) + 
          " control: " + control.toString(16) + " value: " + value.toString(16) + 
          " status: " + status.toString(16) + " group: " + group
    );
}

/**
  * Used to control a generic Mixxx control setting (low..high) 
  * from an absolute control (0..127)
  * @param {String} group mixxx control group
  * @param {String} key mixxx controller name (key)
  * @param {integer} value value
  * @param {integer} low minimum value
  * @param {integer} high maximum value
  */
script.absoluteSlider = function (group, key, value, low, high) {
    if (value==127) 
        engine.setValue(group, key, high);
    else 
        engine.setValue(group, key, (((high - low) / 127) * value) + low);
}

/**
  * Returns a value for a non-linear Mixxx control, (like EQs: 0..1..4) 
  * from an absolute control (0..127)
  * @param {integer} value input value
  * @param {integer} low low limit
  * @param {integer} mid middle value
  * @param {integer} high high limit
  */
script.absoluteNonLin = function (value, low, mid, high) {
    if (value<=64) 
        return value/(64/(mid-low));
    else 
        return 1+(value-63)/(64/(high-mid));
}

/** 
  * Takes the value from a little-endian 14-bit MIDI pitch wheel message 
  * and returns the value for a "rate" (pitch slider) Mixxx control
  * @param {integer} LSB Least significant byte ('control' in messages)
  * @param {integer} MSB Most significant byte ('value' in messages)
  * @param {integer} status MIDI status code, must be pitch bend message
  **/
script.pitch = function (LSB, MSB, status) {
    // Mask the upper nybble so we can check the opcode regardless of the channel
    if ((status & 0xF0) != 0xE0) {
        print("Script.Pitch: Error, not a MIDI pitch message: "+status);
        return false;
    }
    // Construct the 14-bit number
    // Range is 0x0000..0x3FFF center @ 0x2000, i.e. 0..16383 center @ 8192
    var value = (MSB << 7) | LSB;
    var rate = (value-8192)/8191;
    return rate;
}

/**
  * Used for tapping the desired BPM for a deck
  */
function bpm() {}

/** Default tap time value */
bpm.tapTime = 0.0;
/** List of tap sample values */
bpm.tap = [];

/**
  * Sets the bpm of the track on a deck by tapping the beats. This only 
  * works if the track's original BPM value is correct.
  * Call this each time the tap button is pressed.
  * @param {deck} Deck number 1 or 2 for two deck mixxx
  */
bpm.tapButton = function(deck) {
    // Current time in seconds
    var now = new Date()/1000;
    var tapDelta = now - bpm.tapTime;
    bpm.tapTime=now;
    // reset if longer than two seconds between taps
    if (tapDelta>2.0) {
        bpm.tap=[];
        return;
    }
    bpm.tap.push(60/tapDelta);
    // Keep the last 8 samples for averaging
    if (bpm.tap.length>8) 
        bpm.tap.shift();
    var sum = 0;
    for (i=0; i<bpm.tap.length; i++) {
        sum += bpm.tap[i];
    }
    var average = sum/bpm.tap.length;
    var fRateScale = average/engine.getValue("[Channel"+deck+"]","bpm");
    
    // Adjust the rate:
    fRateScale = (fRateScale-1.)/engine.getValue("[Channel"+deck+"]","rateRange");
    
    engine.setValue(
        "[Channel"+deck+"]",
        "rate",
        fRateScale * engine.getValue("[Channel"+deck+"]","rate_dir")
    );
    // print("Script: BPM="+average);
}

// ----------------- Object definitions --------------------------

/** Button state values: released=0, pressed=127 */
ButtonState = {"released":0x00, "pressed":0x7F};
/** Led state values: off=0, on=127 */
LedState =  {"off": 0x00, "on": 0x7F};

/**
  * Defines a controller object
  */
function Controller() {
    this.group = "[Master]";
    this.Controls = [];
    this.Buttons = [];
};

/**
  * Prototype for adding a button to controller
  * @param {String} buttonName Name of button to add
  * @param {object} button Button to add
  * @param {function} eventHandler Event handler to call for button
  */
Controller.prototype.addButton = function(buttonName, button, eventHandler) {
    if (eventHandler) {
        var executionEnvironment = this;
        function handler(value) {
            button.state = value;
            executionEnvironment[eventHandler](value);
        }
        button.handler = handler;
    }
    this.Buttons[buttonName] = button; 
}

/**
  * Prototype for setting control value
  * @param {object} control Control group target
  * @param {function} value Value to set
  */
Controller.prototype.setControlValue = function(control, value) {
    this.Controls[control].setValue(this.group, value);
}

/**
  * Declaration of buttons. Initialized to ButtonState.released.
  * @param {function} controlID Controller ID to map
  */
function Button(controlId) {
    this.controlId = controlId;
    this.state = ButtonState.released;
}
/**
  * Prototype to set button handler function
  * @param {function} value Handler function to use
  */
Button.prototype.handleEvent = function(value) {
    this.handler(value);
};

/**
  * Connects a control to a given function
  * @param {function} mappedFunction Function to map
  * @param {boolean} softMode Boolean to decide if soft takeover is used
  * @property {integer} minInput Minimum value for input range
  * @property {integer} midInput Middle value for input range
  * @propery {integer} maxInput Maximum value for input range
  * @property {float} minOutput Minimum value for output values
  * @property {float} midOutput Middle value for output values
  * @propery {float} maxOutput Maximum value for output values
  * @propery {function} mappedFunction Mapped contrller function, given as input
  * @propery {boolean} softMode Boolean if soft takeover is enabled, given as input
  * @propery {integer} maxJump Maximum jump in value
  */ 
function Control(mappedFunction, softMode) {
    this.minInput = 0;
    this.midInput = 0x3F;
    this.maxInput = 0x7F;
    this.minOutput = -1.0;
    this.midOutput = 0.0;
    this.maxOutput = 1.0;
    this.mappedFunction = mappedFunction;
    this.softMode = softMode;
    this.maxJump = 10;
}

/**
  * Prototype for setting control value based on mapped function. Limits
  * the value to configured allowed range, uses soft takeover into account
  * if enabled.
  * @param {group} Control group
  * @param {inputValue} Input value to set if valid
  */
Control.prototype.setValue = function(group, inputValue) {
    var outputValue = 0;
    if (inputValue <= this.midInput) {
        outputValue = this.minOutput + 
            ((inputValue - this.minInput) / 
            (this.midInput - this.minInput)) * 
            (this.midOutput - this.minOutput);
    } else {
        outputValue = this.midOutput + 
            ( (inputValue - this.midInput) / 
              (this.maxInput - this.midInput) ) * 
            (this.maxOutput - this.midOutput);
    }
    if (this.softMode) {
        var currentValue = engine.getValue(group, this.mappedFunction);
        var currentRelative = 0.0;
        if (currentValue <= this.midOutput) {
            currentRelative = this.minInput + 
                ( (currentValue - this.minOutput) / 
                  (this.midOutput - this.minOutput) ) * 
                (this.midInput - this.minInput);
        } else {
            currentRelative = this.midInput + 
                ( (currentValue - this.midOutput) / 
                  (this.maxOutput - this.midOutput) ) * 
                (this.maxInput - this.midInput);
        }
        if (inputValue > currentRelative - this.maxJump && 
            inputValue < currentRelative + this.maxJump) {
            engine.setValue(group, this.mappedFunction, outputValue);
        }
    } else {
        engine.setValue(group, this.mappedFunction, outputValue);
    }
}

/**
  * Prototype for a deck
  * @param {integer} deckNumber Deck number, currently 1 or 2
  * @param {String} group Configuration group for deck
  * @property {integer} deckNumber Deck number, set from parameter
  * @property {String} group Configuration group, set from parameter
  * @property {list} Buttons List of buttons for deck, initially empty 
  */
Deck = function(deckNumber, group) {
    this.deckNumber = deckNumber;
    this.group = group;
    this.Buttons = [];
}
/** @ignore */
Deck.prototype.setControlValue = Controller.prototype.setControlValue;
/** @ignore */
Deck.prototype.addButton = Controller.prototype.addButton;

// ----------------- END Object definitions ----------------------

------------------------------------------------------------------------------
Ridiculously easy VDI. With Citrix VDI-in-a-Box, you don't need a complex
infrastructure or vast IT resources to deliver seamless, secure access to
virtual desktops. With this all-in-one solution, easily deploy virtual 
desktops for less than the cost of PCs and save 60% on VDI infrastructure 
costs. Try it free! http://p.sf.net/sfu/Citrix-VDIinabox
_______________________________________________
Mixxx-devel mailing list
Mixxx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mixxx-devel

Reply via email to