{"action":"created","comment":{"url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/62410016","id":62410016,"diff_hunk":"@@ -0,0 +1,544 @@\n+/**\n+ * Denon DJ MC4000 DJ controller script for Mixxx 2.0\n+ * Makes heavy use of the JS library in Hercules P32 DJ controller \n+ * script for Mixxx 2.0 by Be, and draws inspiration from many \n+ * of the other controller mappings.\n+ * \n+ * Copyright (C) 2016 Tim Rae <perceptualchaos2@gmail.com>\n+ *\n+ * This program is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU General Public License\n+ * as published by the Free Software Foundation; either version 2\n+ * of the License, or (at your option) any later version.\n+ * \n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ * \n+ * You should have received a copy of the GNU General Public License\n+ * along with this program; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n+**/\n+\n+var Control = function (signals, group, inOptions, outOptions) {\n+    /**\n+    A Control represents a physical component on a controller, such as a button, knob, encoder, or fader.\n+    This constructor function can help set up both the input and output handling functions easily.\n+    Derivative objects are provided below with even more convenient constructor functions for common use cases.\n+    Any of these parameters can be null or ommitted, which can be helpful when setting this Control's properties after initialization.\n+    It is also helpful to intentionally avoid overwriting certain properties of Controls using LayerContainer.applyLayer().\n+    signals, array with 2 members: first two bytes of the MIDI message (status and note numbers)\n+    group, string: the group this belongs to, for example '[Channel1]'\n+    inOptions, array with up to 3 members:\n+        1st member of array, string: Mixxx CO that this affects when receiving MIDI input, for example, 'play'\n+        2nd member of array, function: function to execute upon receiving MIDI input. The function should return\n+                                       the value to set the 1st member of the array to. The function's 'this' object\n+                                       is set to the Control. If null, you need to set the inFunc or input property yourself,\n+                                       which is helpful if you want to define a long, custom input function.\n+        3rd member of array, boolean, optional: whether to react only when a button is pressed, or on both press & release.\n+                                                Default to true if ommitted.\n+    outOptions, array with up to 4 members, optional: If null or ommitted, you need to set up the output yourself.\n+        1st member of array, string: send signals back to the controller when this Mixxx CO changes, for example, 'play_indicator'\n+        2nd member of array, function: function to execute when Mixxx CO specified by 1st member of array changes.\n+                                       The function should return a value to send back as the 3rd byte of a MIDI message\n+                                       (with the first 2 bytes of the MIDI message being those specified in the first argument).\n+                                       The function's 'this' object is set to the Control. If null, you need to set the input property yourself,\n+                                       which is helpful if you want to define a long, custom output function.\n+        3rd member of array, boolean, optional: whether to connect the Mixxx CO (1st member) to the function (2nd member) immediately.\n+                                                Default to true if ommitted.\n+        4th member of array, boolean, optional: whether to execute the function (2nd member) immediately. Default to true if ommitted.\n+    **/\n+    if (arguments.length === 1 && arguments[0] === null) {\n+        this = null;\n+        return;\n+    }\n+\n+    var that = this;\n+\n+    if (Array.isArray(signals)) {\n+        this.midi = {status: signals[0], note: signals[1]};\n+    }\n+    if (typeof group === 'string') {\n+        this.group = group;\n+    }\n+\n+    this.setValue = function (value) {\n+        engine.setValue(this.group, this.inCo, value);\n+    };\n+\n+    this.getValue = function () {\n+        return engine.getValue(this.group, this.inCo);\n+    };\n+\n+    this.toggle = function () {\n+        this.setValue( ! this.getValue());\n+    };\n+\n+    this.inSetup = function (inOptions) {\n+        if (inOptions === null || inOptions === undefined) {\n+            return;\n+        }\n+        this.inCo = inOptions[0];\n+        this.inFunc = inOptions[1];\n+        if (this.inFunc === undefined) {this.inFunc = function (value) {return value;};}\n+        this.onlyOnPress = inOptions[2];\n+        if (this.onlyOnPress === undefined) {this.onlyOnPress = true;}\n+        this.input = function (channel, control, value, status, group) {\n+            // FIXME for 2.1: hacks around https://bugs.launchpad.net/mixxx/+bug/1567203\n+            if (that.onlyOnPress) {\n+                if (value > 0) {\n+                    that.setValue(that.inFunc.call(that, value));\n+                }\n+            } else {\n+                that.setValue(that.inFunc.call(that, value));\n+            }\n+        };\n+    };\n+    this.inSetup(inOptions);\n+    this.previousInput = null;\n+\n+    this.connections = [];\n+    this.connect = function () {\n+        /**\n+        Override this method with a custom one to connect multiple Mixxx COs\n+        for a single Control. Add the connection objects to the this.connections array so\n+        they all get disconnected just by calling this.disconnect().\n+        This can be helpful for multicolor LEDs that show a different color depending\n+        on the state of different Mixxx COs. See SamplerButton.connect()\n+        and SamplerButton.output() for an example.\n+        **/\n+        this.connections[0] = engine.connectControl(this.group, this.outCo, this.output);\n+    };\n+    this.disconnect = function () {\n+        this.connections.forEach(function (connection) {\n+            connection.disconnect();\n+        });\n+    };\n+    this.trigger = function() { engine.trigger(this.group, this.outCo); };\n+    this.send = function (value) { midi.sendShortMsg(this.midi.status, this.midi.note, value); };\n+\n+    this.outSetup = function (outOptions) {\n+        if (outOptions === null || outOptions === undefined) {\n+            return;\n+        }\n+        this.outCo = outOptions[0];\n+        this.outFunc = outOptions[1];\n+        var connect = outOptions[2];\n+        var trigger = outOptions[3];\n+        if (this.outFunc === undefined) {this.outFunc = function (value) {return value;};}\n+        if (connect === undefined) {connect = true;}\n+        if (trigger === undefined) {trigger = true;}\n+        if (this.outFunc === null) {\n+            this.output = null;\n+        } else {\n+            this.output = function (value, group, control) {\n+                this.send(this.outFunc.call(this, value));\n+            };\n+        }\n+        if (connect) { this.connect(); }\n+        if (trigger) { this.trigger(); }\n+    };\n+    this.outSetup(outOptions);\n+    this.previousOutput = null;\n+};\n+\n+var ToggleButton = function (signals, group, co, onlyOnPress, on, off) {\n+    /**\n+    A Control that toggles a binary Mixxx CO\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    co, string: the Mixxx CO to toggle\n+    onlyOnPress, boolean, optional: whether to toggle the CO only when the button is pressed,\n+                                    or on both press and release. Defaults to true if ommited.\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when the Mixxx CO is on\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when the Mixxx CO is off\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    Control.call(this, signals, group,\n+                [co, function () { return ! this.getValue(); }, onlyOnPress],\n+                [co, function () { return (this.getValue()) ? on : off; } ]);\n+};\n+ToggleButton.prototype = Object.create(Control.prototype);\n+ToggleButton.prototype.constructor = ToggleButton;\n+\n+var ToggleButtonAsymmetric = function (signals, group, inCo, outCo, onlyOnPress, on, off) {\n+    /**\n+    A Control that toggles a binary Mixxx CO, but its LEDs respond to a different CO\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    inCo, string: the Mixxx CO to toggle when receiving MIDI input\n+    outCo, string: send signals back to the controller when this Mixxx CO changes\n+    onlyOnPress, boolean, optional: whether to toggle inCO only when the button is pressed,\n+                                    or on both press and release. Defaults to true if ommited.\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when outCo on\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when outCo is off\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    Control.call(this, signals, group,\n+                [inCo, function () { return ! this.getValue(); }, onlyOnPress],\n+                [outCo, function (value) { return (value) ? on : off; } ]);\n+};\n+ToggleButtonAsymmetric.prototype = Object.create(Control.prototype);\n+ToggleButtonAsymmetric.prototype.constructor = ToggleButtonAsymmetric;\n+\n+var CueButton = function (signals, group, on, off) {\n+    /**\n+    A Control for cue buttons\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when cue_indicator is 1\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when cue_indicator is 0\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    ToggleButtonAsymmetric.call(this, signals, group, 'cue_default', 'cue_indicator', false, on, off);\n+};\n+CueButton.prototype = Object.create(ToggleButtonAsymmetric.prototype);\n+CueButton.prototype.constructor = CueButton;\n+\n+var PlayButton = function (signals, group, on, off) {\n+    /**\n+    A Control for play buttons\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when play_indicator is 1\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when play_indicator is 0\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    ToggleButtonAsymmetric.call(this, signals, group,\n+                               'play', 'play_indicator', true, on, off);\n+};\n+PlayButton.prototype = Object.create(ToggleButtonAsymmetric.prototype);\n+PlayButton.prototype.constructor = PlayButton;\n+\n+var HotcueButton = function (signals, group, hotcueNumber, on, off) {\n+    /**\n+    A Control for hotcue buttons\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    hotcueNumber, number: the number of the hotcue\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when cue_indicator is 1\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when cue_indicator is 0\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    ToggleButtonAsymmetric.call(this, signals, group,\n+                               'hotcue_'+hotcueNumber+'_activate', 'hotcue_'+hotcueNumber+'_enabled', false, on, off);\n+};\n+HotcueButton.prototype = Object.create(ToggleButtonAsymmetric.prototype);\n+HotcueButton.prototype.constructor = HotcueButton;\n+\n+var HotcueClearButton = function (signals, group, hotcueNumber, on, off) {\n+    /**\n+    A Control for buttons to clear a hotcue. Typically, these are the same buttons as HotcueButtons,\n+    but active with a shift button held.\n+    signals: two member array, first two bytes of the MIDI message (status and note numbers)\n+    group: string, the group this belongs to, for example '[Channel1]'\n+    hotcueNumber, number: the number of the hotcue\n+    on, number, optional: value for the 3rd byte of the MIDI message to send back to light the LED when cue_indicator is 1\n+                          Defaults to 127 if ommited. Use this if the button has a multicolor LED.\n+    off, number, optional: value for the 3rd byte of the MIDI message to send back to turn off the LED when cue_indicator is 0\n+                           Defaults to 0 if ommitted.\n+    **/\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    HotcueButton.call(this, signals, group, hotcueNumber, on, off);\n+    this.inSetup(['hotcue_'+hotcueNumber+'_clear', function () {return 1;} ]);\n+};\n+HotcueClearButton.prototype = Object.create(HotcueButton.prototype);\n+HotcueClearButton.prototype.constructor = HotcueClearButton;\n+\n+/**\n+A Control for sampler buttons. Press the button to load the track selected in\n+the library into an empty sampler. Press a loaded sampler to play it from\n+its cue point. Press again while playing to jump back to the cue point.\n+@param {Array} signals: first two bytes of the MIDI message (status and note numbers)\n+@param {Integer} samplerNumber: number of the sampler\n+@param {Number} on (optional): MIDI value to send back to button LED when sampler\n+                               is loaded. Defaults to 127. Use this to for multicolor LEDs.\n+@param {Number} off (optional): MIDI value to send back to button LED when sampler\n+                                is empty. Defaults to 0.\n+@param {Number} playing (optional): MIDI value to send back to button LED when sampler\n+                                    is playing. Useful for buttons with multicolor LEDs.\n+                                    If ommitted, only the MIDI message specified by\n+                                    the \"on\" parameter is sent when the sampler\n+                                    is loaded, regardless of whether it is playing.\n+**/\n+var SamplerButton = function (signals, samplerNumber, on, off, playing) {\n+    if (on === undefined) {on = 127;}\n+    if (off === undefined) {off = 0;}\n+    // track_samples is 0 when the sampler is empty and > 0 when a sample is loaded\n+    Control.call(this, signals, '[Sampler' + samplerNumber + ']', null,\n+                 null);\n+    var that = this; // FIXME for 2.1: hacks around https://bugs.launchpad.net/mixxx/+bug/1567203\n+\n+    this.input = function (channel, control, value, status, group) {\n+        if (value > 0) {\n+            if (engine.getValue(that.group, 'track_samples') === 0) {\n+                engine.setValue(that.group, 'LoadSelectedTrack', 1);\n+            } else {\n+                engine.setValue(that.group, 'cue_gotoandplay', 1);\n+            }\n+        }\n+    };\n+\n+    this.output = function (value, group, control) {\n+        if (engine.getValue(that.group, 'track_samples') > 0) {\n+            if (playing === undefined) {\n+                that.send(on);\n+            } else {\n+                if (engine.getValue(that.group, 'play') === 1) {\n+                    that.send(on);\n+                } else {\n+                    that.send(playing);\n+                }\n+            }\n+        } else {\n+            that.send(off);\n+        }\n+    };\n+\n+    this.connect = function() {\n+        this.connections[0] = engine.connectControl(this.group, 'track_samples', this.output);\n+        if (playing !== undefined) {\n+            this.connections[1] = engine.connectControl(this.group, 'play', this.output);\n+        }\n+    };\n+\n+    this.connect();\n+    this.trigger();\n+}\n+SamplerButton.prototype = Object.create(Control.prototype);\n+SamplerButton.prototype.constructor = SamplerButton;\n+\n+/**\n+A Control for buttons to stop samplers. Typically, these are the same buttons\n+as SamplerButtons but active with shift held down. If the sampler is stopped,\n+eject the loaded sample.\n+@param {Array} signals: first two bytes of the MIDI message (status and note numbers)\n+@param {Integer} samplerNumber: number of the sampler\n+@param {Number} on (optional): MIDI value to send back to button LED when sampler\n+                               is loaded. Defaults to 127. Use this to for multicolor LEDs.\n+@param {Number} off (optional): MIDI value to send back to button LED when sampler\n+                                is empty. Defaults to 0.\n+@param {Number} playing (optional): MIDI value to send back to button LED when sampler\n+                                    is playing. Useful for buttons with multicolor LEDs.\n+                                    If ommitted, only the MIDI message specified by\n+                                    the \"on\" parameter is sent when the sampler\n+                                    is loaded, regardless of whether it is playing.\n+**/\n+var SamplerStopButton = function (signals, samplerNumber, on, off, playing) {\n+    SamplerButton.call(this, signals, samplerNumber, on, off, playing);\n+\n+    var that = this; // FIXME for 2.1: hacks around https://bugs.launchpad.net/mixxx/+bug/1567203\n+\n+    this.input = function (channel, control, value, status, group) {\n+        if (value > 0) {\n+            if (engine.getValue(that.group, 'play') === 1) {\n+                engine.setValue(that.group, 'play', 0);\n+            } else {\n+                engine.setValue(that.group, 'eject', 1);\n+            }\n+        }\n+    };\n+}\n+SamplerStopButton.prototype = Object.create(SamplerButton);\n+SamplerStopButton.prototype.constructor = SamplerStopButton;\n+\n+\n+\n+var MC4000 = {};\n+\n+MC4000.init = function () {\n+    // Decks\n+    MC4000.leftDeck = new MC4000.Deck(0);\n+    MC4000.rightDeck = new MC4000.Deck(1);\n+    // VU meters\n+    // TODO: implement VU meters for the PFL when cue button is on\n+    engine.connectControl(\"[Master]\", \"VuMeterL\", \"MC4000.OnVuMeterChangeL\");\n+    engine.connectControl(\"[Master]\", \"VuMeterR\", \"MC4000.OnVuMeterChangeR\");\n+    // Samplers\n+    for (var i = 1; i <= 4; i++) {\n+        this['sampler' + i] = new SamplerButton([0x9F, 0x20 + i], i);\n+        this['samplerClear' + i] = new SamplerStopButton([0x9F, 0x27 + i], i);\n+    }\n+    MC4000.samplerLevel = function (channel, control, value, status, group) {\n+        engine.setValue(\"[Sampler1]\", \"pregain\", script.absoluteNonLin(value, 0, 1.0, 4.0));\n+        engine.setValue(\"[Sampler2]\", \"pregain\", script.absoluteNonLin(value, 0, 1.0, 4.0));\n+        engine.setValue(\"[Sampler3]\", \"pregain\", script.absoluteNonLin(value, 0, 1.0, 4.0));\n+        engine.setValue(\"[Sampler4]\", \"pregain\", script.absoluteNonLin(value, 0, 1.0, 4.0));\n+    }\n+    // Initalization of controller states\n+    // TODO: find equivalent message for MC4000\n+    // tell controller to send MIDI messages with positions of faders and knobs\n+    // midi.sendShortMsg(0xB0, 0x7F, 0x7F);\n+};\n+\n+MC4000.shutdown = function () {};\n+\n+MC4000.padColors = {\n+    off: 0,\n+    red: 2,\n+    blue: 1,\n+    purple: 3\n+};\n+\n+// Found by averaging total 'ticks' received over 100 rotations\n+MC4000.jogWheelTicksPerRevolution = 605;\n+\n+MC4000.scratchParams = {\n+    recordSpeed: 33.3333333333,\n+    alpha: (1.0/8),\n+    beta: (1.0/8)/32.0\n+};\n+\n+MC4000.jogParams = {\n+    sensitivity: 25,\n+    maxJogValue: 3\n+};\n+\n+MC4000.rateRanges = [0.08, 0.16, 0.5];\n+\n+MC4000.OnVuMeterChangeL = function(value, group, control) {\n+    midi.sendShortMsg(0xBF, 0x44, value*0x7F);\n+}\n+\n+MC4000.OnVuMeterChangeR = function(value, group, control) {\n+    midi.sendShortMsg(0xBF, 0x45, value*0x7F);\n+}\n+\n+MC4000.Deck = function (channel) {\n+    // Some state variables\n+    this.isVinylMode = false;\n+    this.rateMsbValue = 0;\n+    this.midiChannel = channel;\n+    this.engineChannel = channel + 1;\n+    this.group = '[Channel' + this.engineChannel + ']';\n+    this.currentRateIndex = 0;\n+    \n+    // Set default rate range (to +/- 8% like Serato and Technics)\n+    engine.setValue(this.group, \"rateRange\", MC4000.rateRanges[this.currentRateIndex]);","path":"res/controllers/Denon-MC4000-scripts.js","position":316,"original_position":434,"commit_id":"b44c919f9dac292b285510564d8d52d1c3c8f779","original_commit_id":"4dcee7f172408d704ffea276057901605bb8d7b5","user":{"login":"timrae","id":2818274,"avatar_url":"https://avatars.githubusercontent.com/u/2818274?v=3","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false},"body":"Alright, I guess I could hardcode the options listed in preferences into\nthe JS and toggle through that.\nOn 7 May 2016 10:59, \"RJ Ryan\" <notifications@github.com> wrote:\n\n> In res/controllers/Denon-MC4000-scripts.js\n> <https://github.com/mixxxdj/mixxx/pull/938#discussion_r62409476>:\n>\n> > +\n> > +MC4000.OnVuMeterChangeR = function(value, group, control) {\n> > +    midi.sendShortMsg(0xBF, 0x45, value*0x7F);\n> > +}\n> > +\n> > +MC4000.Deck = function (channel) {\n> > +    // Some state variables\n> > +    this.isVinylMode = false;\n> > +    this.rateMsbValue = 0;\n> > +    this.midiChannel = channel;\n> > +    this.engineChannel = channel + 1;\n> > +    this.group = '[Channel' + this.engineChannel + ']';\n> > +    this.currentRateIndex = 0;\n> > +\n> > +    // Set default rate range (to +/- 8% like Serato and Technics)\n> > +    engine.setValue(this.group, \"rateRange\", MC4000.rateRanges[this.currentRateIndex]);\n>\n> @Be-ing <https://github.com/Be-ing> is right here, you can access the\n> user's preferred rate range via the \"rateRange\" control.\n>\n> —\n> You are receiving this because you authored the thread.\n> Reply to this email directly or view it on GitHub\n> <https://github.com/mixxxdj/mixxx/pull/938/files/4dcee7f172408d704ffea276057901605bb8d7b5#r62409476>\n>\n","created_at":"2016-05-07T02:30:28Z","updated_at":"2016-05-07T02:30:28Z","html_url":"https://github.com/mixxxdj/mixxx/pull/938#discussion_r62410016","pull_request_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938","_links":{"self":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/62410016"},"html":{"href":"https://github.com/mixxxdj/mixxx/pull/938#discussion_r62410016"},"pull_request":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938"}}},"pull_request":{"url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938","id":68530255,"html_url":"https://github.com/mixxxdj/mixxx/pull/938","diff_url":"https://github.com/mixxxdj/mixxx/pull/938.diff","patch_url":"https://github.com/mixxxdj/mixxx/pull/938.patch","issue_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/938","number":938,"state":"open","locked":false,"title":"Denon MC4000 mapping","user":{"login":"timrae","id":2818274,"avatar_url":"https://avatars.githubusercontent.com/u/2818274?v=3","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false},"body":"Here's my mapping for the [Denon DJ MC4000](http://denondj.com/products/view/mc4000) controller. Please refer to the [documentation on the wiki](http://mixxx.org/wiki/doku.php/denon_mc4000).\r\n\r\nIt's basically complete now, with a few TODOs:\r\n\r\n0. FX3 is not mapped to anything since there are only 4 FX units in total. Should fix this after support for multiple racks is added.\r\n0. The \"pad mode\" button doesn't map to anything. I'm not even sure what this does in Serato.\r\n0. The library encoder push button should switch between selecting songs / vs. crates / vs/ playlists, etc. I'll send a PR for that soonish.\r\n0. The panel / back buttons should also be updated similarly to above point after PR\r\n0. The FX beats encoder doesn't map to anything\r\n0. The autloop button doesn't work that well. I'll try send a PR to fix that up and adjust here accordingly.\r\n0. The BPM tap button appears to be mapped correctly but not do anything (should check source code)","created_at":"2016-05-02T07:29:34Z","updated_at":"2016-05-07T02:30:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2455eadb21904c0ef81036dbb3a6585d54efb6f5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938/commits","review_comments_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938/comments","review_comment_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/938/comments","statuses_url":"https://api.github.com/repos/mixxxdj/mixxx/statuses/b44c919f9dac292b285510564d8d52d1c3c8f779","head":{"label":"timrae:mc4000","ref":"mc4000","sha":"b44c919f9dac292b285510564d8d52d1c3c8f779","user":{"login":"timrae","id":2818274,"avatar_url":"https://avatars.githubusercontent.com/u/2818274?v=3","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false},"repo":{"id":57447151,"name":"mixxx","full_name":"timrae/mixxx","owner":{"login":"timrae","id":2818274,"avatar_url":"https://avatars.githubusercontent.com/u/2818274?v=3","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/timrae/mixxx","description":"Mixxx is Free DJ software that gives you everything you need to perform live mixes.","fork":true,"url":"https://api.github.com/repos/timrae/mixxx","forks_url":"https://api.github.com/repos/timrae/mixxx/forks","keys_url":"https://api.github.com/repos/timrae/mixxx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/timrae/mixxx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/timrae/mixxx/teams","hooks_url":"https://api.github.com/repos/timrae/mixxx/hooks","issue_events_url":"https://api.github.com/repos/timrae/mixxx/issues/events{/number}","events_url":"https://api.github.com/repos/timrae/mixxx/events","assignees_url":"https://api.github.com/repos/timrae/mixxx/assignees{/user}","branches_url":"https://api.github.com/repos/timrae/mixxx/branches{/branch}","tags_url":"https://api.github.com/repos/timrae/mixxx/tags","blobs_url":"https://api.github.com/repos/timrae/mixxx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/timrae/mixxx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/timrae/mixxx/git/refs{/sha}","trees_url":"https://api.github.com/repos/timrae/mixxx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/timrae/mixxx/statuses/{sha}","languages_url":"https://api.github.com/repos/timrae/mixxx/languages","stargazers_url":"https://api.github.com/repos/timrae/mixxx/stargazers","contributors_url":"https://api.github.com/repos/timrae/mixxx/contributors","subscribers_url":"https://api.github.com/repos/timrae/mixxx/subscribers","subscription_url":"https://api.github.com/repos/timrae/mixxx/subscription","commits_url":"https://api.github.com/repos/timrae/mixxx/commits{/sha}","git_commits_url":"https://api.github.com/repos/timrae/mixxx/git/commits{/sha}","comments_url":"https://api.github.com/repos/timrae/mixxx/comments{/number}","issue_comment_url":"https://api.github.com/repos/timrae/mixxx/issues/comments{/number}","contents_url":"https://api.github.com/repos/timrae/mixxx/contents/{+path}","compare_url":"https://api.github.com/repos/timrae/mixxx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/timrae/mixxx/merges","archive_url":"https://api.github.com/repos/timrae/mixxx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/timrae/mixxx/downloads","issues_url":"https://api.github.com/repos/timrae/mixxx/issues{/number}","pulls_url":"https://api.github.com/repos/timrae/mixxx/pulls{/number}","milestones_url":"https://api.github.com/repos/timrae/mixxx/milestones{/number}","notifications_url":"https://api.github.com/repos/timrae/mixxx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/timrae/mixxx/labels{/name}","releases_url":"https://api.github.com/repos/timrae/mixxx/releases{/id}","deployments_url":"https://api.github.com/repos/timrae/mixxx/deployments","created_at":"2016-04-30T14:49:00Z","updated_at":"2016-04-30T14:49:20Z","pushed_at":"2016-05-07T00:34:50Z","git_url":"git://github.com/timrae/mixxx.git","ssh_url":"git@github.com:timrae/mixxx.git","clone_url":"https://github.com/timrae/mixxx.git","svn_url":"https://github.com/timrae/mixxx","homepage":"http://mixxx.org","size":256448,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"mixxxdj:master","ref":"master","sha":"f1ef539558ab92cec33acf9218ef325a3b7efbaf","user":{"login":"mixxxdj","id":1743769,"avatar_url":"https://avatars.githubusercontent.com/u/1743769?v=3","gravatar_id":"","url":"https://api.github.com/users/mixxxdj","html_url":"https://github.com/mixxxdj","followers_url":"https://api.github.com/users/mixxxdj/followers","following_url":"https://api.github.com/users/mixxxdj/following{/other_user}","gists_url":"https://api.github.com/users/mixxxdj/gists{/gist_id}","starred_url":"https://api.github.com/users/mixxxdj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixxxdj/subscriptions","organizations_url":"https://api.github.com/users/mixxxdj/orgs","repos_url":"https://api.github.com/users/mixxxdj/repos","events_url":"https://api.github.com/users/mixxxdj/events{/privacy}","received_events_url":"https://api.github.com/users/mixxxdj/received_events","type":"Organization","site_admin":false},"repo":{"id":10126031,"name":"mixxx","full_name":"mixxxdj/mixxx","owner":{"login":"mixxxdj","id":1743769,"avatar_url":"https://avatars.githubusercontent.com/u/1743769?v=3","gravatar_id":"","url":"https://api.github.com/users/mixxxdj","html_url":"https://github.com/mixxxdj","followers_url":"https://api.github.com/users/mixxxdj/followers","following_url":"https://api.github.com/users/mixxxdj/following{/other_user}","gists_url":"https://api.github.com/users/mixxxdj/gists{/gist_id}","starred_url":"https://api.github.com/users/mixxxdj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixxxdj/subscriptions","organizations_url":"https://api.github.com/users/mixxxdj/orgs","repos_url":"https://api.github.com/users/mixxxdj/repos","events_url":"https://api.github.com/users/mixxxdj/events{/privacy}","received_events_url":"https://api.github.com/users/mixxxdj/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mixxxdj/mixxx","description":"Mixxx is Free DJ software that gives you everything you need to perform live mixes.","fork":false,"url":"https://api.github.com/repos/mixxxdj/mixxx","forks_url":"https://api.github.com/repos/mixxxdj/mixxx/forks","keys_url":"https://api.github.com/repos/mixxxdj/mixxx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mixxxdj/mixxx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mixxxdj/mixxx/teams","hooks_url":"https://api.github.com/repos/mixxxdj/mixxx/hooks","issue_events_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/events{/number}","events_url":"https://api.github.com/repos/mixxxdj/mixxx/events","assignees_url":"https://api.github.com/repos/mixxxdj/mixxx/assignees{/user}","branches_url":"https://api.github.com/repos/mixxxdj/mixxx/branches{/branch}","tags_url":"https://api.github.com/repos/mixxxdj/mixxx/tags","blobs_url":"https://api.github.com/repos/mixxxdj/mixxx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mixxxdj/mixxx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mixxxdj/mixxx/git/refs{/sha}","trees_url":"https://api.github.com/repos/mixxxdj/mixxx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mixxxdj/mixxx/statuses/{sha}","languages_url":"https://api.github.com/repos/mixxxdj/mixxx/languages","stargazers_url":"https://api.github.com/repos/mixxxdj/mixxx/stargazers","contributors_url":"https://api.github.com/repos/mixxxdj/mixxx/contributors","subscribers_url":"https://api.github.com/repos/mixxxdj/mixxx/subscribers","subscription_url":"https://api.github.com/repos/mixxxdj/mixxx/subscription","commits_url":"https://api.github.com/repos/mixxxdj/mixxx/commits{/sha}","git_commits_url":"https://api.github.com/repos/mixxxdj/mixxx/git/commits{/sha}","comments_url":"https://api.github.com/repos/mixxxdj/mixxx/comments{/number}","issue_comment_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/comments{/number}","contents_url":"https://api.github.com/repos/mixxxdj/mixxx/contents/{+path}","compare_url":"https://api.github.com/repos/mixxxdj/mixxx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mixxxdj/mixxx/merges","archive_url":"https://api.github.com/repos/mixxxdj/mixxx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mixxxdj/mixxx/downloads","issues_url":"https://api.github.com/repos/mixxxdj/mixxx/issues{/number}","pulls_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls{/number}","milestones_url":"https://api.github.com/repos/mixxxdj/mixxx/milestones{/number}","notifications_url":"https://api.github.com/repos/mixxxdj/mixxx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mixxxdj/mixxx/labels{/name}","releases_url":"https://api.github.com/repos/mixxxdj/mixxx/releases{/id}","deployments_url":"https://api.github.com/repos/mixxxdj/mixxx/deployments","created_at":"2013-05-17T15:14:38Z","updated_at":"2016-05-04T22:35:37Z","pushed_at":"2016-05-07T00:48:43Z","git_url":"git://github.com/mixxxdj/mixxx.git","ssh_url":"git@github.com:mixxxdj/mixxx.git","clone_url":"https://github.com/mixxxdj/mixxx.git","svn_url":"https://github.com/mixxxdj/mixxx","homepage":"http://mixxx.org","size":272111,"stargazers_count":513,"watchers_count":513,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":322,"mirror_url":null,"open_issues_count":63,"forks":322,"open_issues":63,"watchers":513,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938"},"html":{"href":"https://github.com/mixxxdj/mixxx/pull/938"},"issue":{"href":"https://api.github.com/repos/mixxxdj/mixxx/issues/938"},"comments":{"href":"https://api.github.com/repos/mixxxdj/mixxx/issues/938/comments"},"review_comments":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938/comments"},"review_comment":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/938/commits"},"statuses":{"href":"https://api.github.com/repos/mixxxdj/mixxx/statuses/b44c919f9dac292b285510564d8d52d1c3c8f779"}}}}