diff --git a/res/controllers/Hercules-DJControl-Inpulse-300-script.js b/res/controllers/Hercules-DJControl-Inpulse-300-script.js index e2759d619e2..380fcdf5de3 100644 --- a/res/controllers/Hercules-DJControl-Inpulse-300-script.js +++ b/res/controllers/Hercules-DJControl-Inpulse-300-script.js @@ -2,11 +2,20 @@ // // *************************************************************************** // * Mixxx mapping script file for the Hercules DJControl Inpulse 300. -// * Author: DJ Phatso, contributions by Kerrick Staley -// * Version 1.2 (March 2020) +// * Author: DJ Phatso, contributions by Kerrick Staley and BoredGuy1 +// * Version 1.3 (May 2024) // * Forum: https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 // * Wiki: https://mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 // +// Changes to v1.3 +// - Added ability to stop samplers (shift + button) +// - Added toneplay +// - Added shift + toneplay controls +// - Added slicer/slicer loop +// - Replaced the song end warning with an actual beatmatch guide +// - Changed the way scratching works (wheels have inertia, allowing backspins and other tricks) +// - Updated VU meter syntax (replaced vu_meter with VuMeter, connectControl with makeConnection, etc) +// // Changes to v1.2 // - Code cleanup. // @@ -20,20 +29,34 @@ // // TO DO: Functions that could be implemented to the script: // +// * HOTCUES: Make loop hotcues more intuitive. Currently, the pads are always lit when loop cues are set, +// regardless of whether or not the loop is enabled (maybe make the pad blink when set but inactive?) +// // * ROLL: Keep SLIP active (if already enabled) when exiting from rolls // -// * SLICER/SLICER LOOP +// * FX: See how to preselect effects for a rack // -// * TONEPLAY +// * SLICER: Currently resource intensive because it's connected to beat_distance (which is always updated). +// Is there a better way? +// +// * BEATMATCH: Also resource intensive because it's connected to beat_distance. We could optimize a bit by +// disconnecting functions when the beatmatch guide is disabled (currently the functions are +// always connected, but the LEDs are turned off by hardware controls) // -// * FX: -// - See how to preselect effects for a rack // **************************************************************************** var DJCi300 = {}; /////////////////////////////////////////////////////////////// // USER OPTIONS // /////////////////////////////////////////////////////////////// +// Beatmatch LED guide tolerances +DJCi300.beatmatchTempoTolerance = .1; // Measured in BPM (e.g. LEDS turn off if decks are <0.1 BPM apart) +DJCi300.beatmatchAlignTolerance = .02; // Measured in beats (e.g. LEDS turn off if decks are <0.02 beats apart) + +// Determines how fast the wheel must be moving to be considered "slipping" +// Higher numbers result in longer backspins +DJCi300.slipThreshold = .1; // Must be between 0 and 1, non-inclusive + // How fast scratching is. DJCi300.scratchScale = 1.0; @@ -44,80 +67,197 @@ DJCi300.scratchShiftMultiplier = 4; DJCi300.bendScale = 1.0; // Other scratch related options -DJCi300.kScratchActionNone = 0; +DJCi300.kScratchActionBend = 0; DJCi300.kScratchActionScratch = 1; DJCi300.kScratchActionSeek = 2; -DJCi300.kScratchActionBend = 3; -DJCi300.vuMeterUpdateMaster = function(value, _group, _control) { - value = (value * 122) + 5; +// Pad modes +DJCi300.padModeNone = 0; +// These correspond directly to the MIDI control values +DJCi300.padModeHotcue = 15; +DJCi300.padModeRoll = 16; +DJCi300.padModeSlicer = 17; +DJCi300.padModeSampler = 18; +DJCi300.padModeToneplay = 19; +DJCi300.padModeFX = 20; +DJCi300.padModeSlicerloop = 21; +DJCi300.padModeBeatjump = 22; + +DJCi300.vuMeterUpdateMain = function(value, _group, _control) { + value = (value * 125); midi.sendShortMsg(0xB0, 0x40, value); midi.sendShortMsg(0xB0, 0x41, value); }; DJCi300.vuMeterUpdateDeck = function(value, group, _control, _status) { - value = (value * 122) + 5; - var status = (group === "[Channel1]") ? 0xB1 : 0xB2; + value = (value * 125); + const status = (group === "[Channel1]") ? 0xB1 : 0xB2; midi.sendShortMsg(status, 0x40, value); }; DJCi300.init = function() { - if (engine.getValue("[App]", "num_samplers") < 16) { - engine.setValue("[App]", "num_samplers", 16); - } - // Scratch button state - DJCi300.scratchButtonState = true; + DJCi300.scratchButtonState = { + "[Channel1]": true, + "[Channel2]": true + }; // Scratch Action DJCi300.scratchAction = { - 1: DJCi300.kScratchActionNone, - 2: DJCi300.kScratchActionNone + "[Channel1]": DJCi300.kScratchActionBend, + "[Channel2]": DJCi300.kScratchActionBend + }; + // Platter state (whether the jog wheel is pressed or not) + DJCi300.wheelTouchState = { + "[Channel1]": false, + "[Channel2]": false + }; + + // Pad mode variables + // Initialize to padModeNone + DJCi300.padMode = { + "[Channel1]": DJCi300.padModeNone, + "[Channel2]": DJCi300.padModeNone }; // Turn On Vinyl buttons LED(one for each deck). midi.sendShortMsg(0x91, 0x03, 0x7F); midi.sendShortMsg(0x92, 0x03, 0x7F); - //Turn On Browser button LED + // Turn On Browser button LED midi.sendShortMsg(0x90, 0x04, 0x05); - //Softtakeover for Pitch fader - engine.softTakeover("[Channel1]", "rate", true); - engine.softTakeover("[Channel2]", "rate", true); - engine.softTakeoverIgnoreNextValue("[Channel1]", "rate"); - engine.softTakeoverIgnoreNextValue("[Channel2]", "rate"); - - // Connect the VUMeters - engine.connectControl("[Channel1]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.getValue("[Channel1]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.connectControl("[Channel2]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.getValue("[Channel2]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.connectControl("[Main]", "vu_meter_left", "DJCi300.vuMeterUpdateMaster"); - engine.connectControl("[Main]", "vu_meter_right", "DJCi300.vuMeterUpdateMaster"); - engine.getValue("[Main]", "vu_meter_left", "DJCi300.vuMeterUpdateMaster"); - engine.getValue("[Main]", "vu_meter_right", "DJCi300.vuMeterUpdateMaster"); + // Turn On Toneplay LED + midi.sendShortMsg(0x96, 0x40, 0x7F); + midi.sendShortMsg(0x96, 0x48, 0x7F); + midi.sendShortMsg(0x97, 0x40, 0x7F); + midi.sendShortMsg(0x97, 0x48, 0x7F); + + // Connect main VUMeters + engine.makeConnection("[Main]", "vu_meter_left", DJCi300.vuMeterUpdateMain); + engine.makeConnection("[Main]", "vu_meter_right", DJCi300.vuMeterUpdateMain); + + for (const group of ["[Channel1]", "[Channel2]"]) { + // Connect left and right VUMeters + engine.makeConnection(group, "vu_meter", DJCi300.vuMeterUpdateDeck); + + //Softtakeover for Pitch fader + engine.softTakeover(group, "rate", true); + engine.softTakeoverIgnoreNextValue(group, "rate"); + + // Connect jogwheel functions + engine.makeConnection(group, "scratch2", DJCi300.updateScratchAction); + + // Connect the toneplay LED updates + engine.makeConnection(group, "pitch", DJCi300.updateToneplayLED); + + // Connect beatmatch LED functions + engine.makeConnection(group, "bpm", DJCi300.updateBeatmatchTempoLED); + // We also want to update all beatmatch LEDs when a song is played/paused + engine.makeConnection(group, "play", DJCi300.updateBeatmatchAlignLED); + engine.makeConnection(group, "play", DJCi300.updateBeatmatchTempoLED); + } + // Only connect one channel to updateBeatmatchAlignLED because beatmatch LEDs are only enabled when both decks are playing + engine.makeConnection("[Channel1]", "beat_distance", DJCi300.updateBeatmatchAlignLED); // Ask the controller to send all current knob/slider values over MIDI, which will update // the corresponding GUI controls in MIXXX. midi.sendShortMsg(0xB0, 0x7F, 0x7F); + + DJCi300.deck = []; + for (let i = 0; i < 2; i++) { + DJCi300.deck[i] = new DJCi300.Deck(i + 1); + DJCi300.deck[i].setCurrentDeck(`[Channel${ i + 1 }]`); + // For some reason, the slicer callback functions start out connected + // This is a dirty hack to ensure they start disconnected + DJCi300.deck[i].slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + } +}; + +// Update beatmatch tempo LEDs +DJCi300.updateBeatmatchTempoLED = function(_value, _group, _control) { + const deck1tempo = engine.getValue("[Channel1]", "bpm"); + const deck2tempo = engine.getValue("[Channel2]", "bpm"); + + // If successfully synced, or if one of the songs are paused, turn all lights off + if ((Math.abs(deck1tempo - deck2tempo) < DJCi300.beatmatchTempoTolerance) || + (engine.getValue("[Channel1]", "play") === 0) || + (engine.getValue("[Channel2]", "play") === 0)) { + midi.sendShortMsg(0x91, 0x1E, 0x00); + midi.sendShortMsg(0x91, 0x1F, 0x00); + midi.sendShortMsg(0x92, 0x1E, 0x00); + midi.sendShortMsg(0x92, 0x1F, 0x00); + // If deck 1 is faster, lights tell user to slow down 1 and speed up 2 + } else if (deck1tempo > deck2tempo) { + midi.sendShortMsg(0x91, 0x1E, 0x7F); + midi.sendShortMsg(0x91, 0x1F, 0x00); + midi.sendShortMsg(0x92, 0x1E, 0x00); + midi.sendShortMsg(0x92, 0x1F, 0x7F); + // If deck 2 is faster, lights tell user to slow down 2 and speed up 1 + } else if (deck1tempo < deck2tempo) { + midi.sendShortMsg(0x91, 0x1E, 0x00); + midi.sendShortMsg(0x91, 0x1F, 0x7F); + midi.sendShortMsg(0x92, 0x1E, 0x7F); + midi.sendShortMsg(0x92, 0x1F, 0x00); + } +}; + +// Update beatmatch align LEDs +DJCi300.updateBeatmatchAlignLED = function(value, _group, _control) { + let deck1Align = value; + let deck2Align = engine.getValue("[Channel2]", "beat_distance"); + + // Because beat_distance resets to 0 every new beat, it's possible for the two decks to have + // very different beat values and still be almost aligned. So we must adjust for this + if (Math.abs(deck1Align - deck2Align) > .5) { + // Add 1 to the smaller number to compensate for roll over + if (deck1Align < deck2Align) { + deck1Align += 1; + } else { + deck2Align += 1; + } + } + + // If successfully synced, or if one of the songs are paused, turn all lights off + if ((Math.abs(deck1Align - deck2Align) < DJCi300.beatmatchAlignTolerance) || + (engine.getValue("[Channel1]", "play") === 0) || + (engine.getValue("[Channel2]", "play") === 0)) { + midi.sendShortMsg(0x91, 0x1C, 0x00); + midi.sendShortMsg(0x91, 0x1D, 0x00); + midi.sendShortMsg(0x92, 0x1C, 0x00); + midi.sendShortMsg(0x92, 0x1D, 0x00); + // If deck 1 is ahead, lights tell user to push 1 back and push 2 ahead + } else if (deck1Align > deck2Align) { + midi.sendShortMsg(0x91, 0x1C, 0x00); + midi.sendShortMsg(0x91, 0x1D, 0x7F); + midi.sendShortMsg(0x92, 0x1C, 0x7F); + midi.sendShortMsg(0x92, 0x1D, 0x00); + // If deck 2 is ahead, lights tell user to push 2 back and push 1 ahead + } else if (deck1Align < deck2Align) { + midi.sendShortMsg(0x91, 0x1C, 0x7F); + midi.sendShortMsg(0x91, 0x1D, 0x00); + midi.sendShortMsg(0x92, 0x1C, 0x00); + midi.sendShortMsg(0x92, 0x1D, 0x7F); + } }; // The Vinyl button, used to enable or disable scratching on the jog wheels (One per deck). -DJCi300.vinylButton = function(_channel, _control, value, status, _group) { +DJCi300.vinylButton = function(_channel, control, value, status, group) { if (value) { - if (DJCi300.scratchButtonState) { - DJCi300.scratchButtonState = false; - midi.sendShortMsg(status, 0x03, 0x00); + if (DJCi300.scratchButtonState[group]) { + DJCi300.scratchButtonState[group] = false; + midi.sendShortMsg(status, control, 0x00); } else { - DJCi300.scratchButtonState = true; - midi.sendShortMsg(status, 0x03, 0x7F); + DJCi300.scratchButtonState[group] = true; + midi.sendShortMsg(status, control, 0x7F); } } }; DJCi300._scratchEnable = function(deck) { - var alpha = 1.0/8; - var beta = alpha/32; + const alpha = 1.0/8; + const beta = alpha/32; engine.scratchEnable(deck, 248, 33 + 1/3, alpha, beta); }; @@ -128,55 +268,61 @@ DJCi300._convertWheelRotation = function(value) { return value < 0x40 ? 1 : -1; }; +// This is called immediately after the wheel is released and we want to switch between scratching and jogging gracefully. +// It is also connected to callbacks and called regularly to see if the wheel has slowed down enough. Once it does, then we switch from scratching to jogging +DJCi300.updateScratchAction = function(value, group, _control) { + const deck = script.deckFromGroup(group); + + // Stop scratching only if the jogwheel is slow enough and the wheels are not being touched + if (((Math.abs(value) < DJCi300.slipThreshold)) && (engine.isScratching(deck)) + && !DJCi300.wheelTouchState[group]) { + engine.scratchDisable(deck); + DJCi300.scratchAction[group] = DJCi300.kScratchActionBend; + } +}; + // The touch action on the jog wheel's top surface -DJCi300.wheelTouch = function(channel, control, value, _status, _group) { - var deck = channel; +DJCi300.wheelTouch = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); if (value > 0) { - // Touching the wheel. - if (engine.getValue("[Channel" + deck + "]", "play") !== 1 || DJCi300.scratchButtonState) { + // Enable scratching in vinyl mode OR if the deck is not playing + if ((engine.getValue(group, "play") !== 1) || (DJCi300.scratchButtonState[group])) { DJCi300._scratchEnable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionScratch; + DJCi300.wheelTouchState[group] = true; + DJCi300.scratchAction[group] = DJCi300.kScratchActionScratch; } else { - DJCi300.scratchAction[deck] = DJCi300.kScratchActionBend; + DJCi300.scratchAction[group] = DJCi300.kScratchActionBend; } } else { // Released the wheel. - engine.scratchDisable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionNone; + DJCi300.wheelTouchState[group] = false; + const scratchValue = engine.getValue(group, "scratch2"); + DJCi300.updateScratchAction(scratchValue, group); } }; // The touch action on the jog wheel's top surface while holding shift -DJCi300.wheelTouchShift = function(channel, control, value, _status, _group) { - var deck = channel - 3; +DJCi300.wheelTouchShift = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); // We always enable scratching regardless of button state. if (value > 0) { DJCi300._scratchEnable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionSeek; + DJCi300.wheelTouchState[group] = true; + DJCi300.scratchAction[group] = DJCi300.kScratchActionSeek; + // Released the wheel. } else { - // Released the wheel. - engine.scratchDisable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionNone; + DJCi300.wheelTouchState[group] = false; + const scratchValue = engine.getValue(group, "scratch2"); + DJCi300.updateScratchAction(scratchValue, group); } }; -// Scratching on the jog wheel (rotating it while pressing the top surface) -DJCi300.scratchWheel = function(channel, control, value, status, _group) { - var deck; - switch (status) { - case 0xB1: - case 0xB4: - deck = 1; - break; - case 0xB2: - case 0xB5: - deck = 2; - break; - default: - return; - } +// Using the jog wheel (spinning the jog wheel, regardless of whether surface or shift is held) +DJCi300.jogWheel = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); + var interval = DJCi300._convertWheelRotation(value); - var scratchAction = DJCi300.scratchAction[deck]; + const scratchAction = DJCi300.scratchAction[group]; if (scratchAction === DJCi300.kScratchActionScratch) { engine.scratchTick(deck, interval * DJCi300.scratchScale); } else if (scratchAction === DJCi300.kScratchActionSeek) { @@ -185,16 +331,322 @@ DJCi300.scratchWheel = function(channel, control, value, status, _group) { DJCi300.scratchShiftMultiplier); } else { engine.setValue( - "[Channel" + deck + "]", "jog", interval * DJCi300.bendScale); + group, "jog", interval * DJCi300.bendScale); } }; -// Bending on the jog wheel (rotating using the edge) -DJCi300.bendWheel = function(channel, control, value, _status, _group) { - var interval = DJCi300._convertWheelRotation(value); - engine.setValue( - "[Channel" + channel + "]", "jog", interval * DJCi300.bendScale); +// Helper function that calculates samples per beat +DJCi300._samplesPerBeat = function(group) { + const sampleRate = engine.getValue(group, "track_samplerate"); + const bpm = engine.getValue(group, "local_bpm"); + // The sample rate includes both channels (i.e. it is double the framerate) + // Hence, we multiply by 2*60 (120) instead of 60 to get the correct sample rate + const secondsPerBeat = 120/bpm; + const samplesPerBeat = secondsPerBeat * sampleRate; + return samplesPerBeat; +}; + +// Helper function that returns a deck object from a group +DJCi300._deckObjectFromGroup = function(group) { + return DJCi300.deck[script.deckFromGroup(group) - 1]; +}; + +// Mode buttons +DJCi300.changeMode = function(_channel, control, value, _status, group) { + const oldPadMode = DJCi300.padMode[group]; + DJCi300.padMode[group] = control; + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Connect slicer when entering slicer or slicerloop mode + if ((DJCi300.padMode[group] === DJCi300.padModeSlicer) || + (DJCi300.padMode[group] === DJCi300.padModeSlicerloop)) { + + // If slicer connections are not present, connect them. Otherwise, disconnect them + if (deckObject.slicerPad.beatConnection === undefined || + deckObject.slicerPad.beatConnection.isConnected === false) { + + deckObject.slicerPad.forEachComponent(function(component) { + component.connect(engine.getValue(group, "beat_closest")); + }); + } else { + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + }; + + // When switching from slicer/slicer loop mode into the other modes, disconnect slicer functions + } else if ((oldPadMode === DJCi300.padModeSlicer) || + (oldPadMode === DJCi300.padModeSlicerloop)) { + + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + } + } +}; + +// Toneplay +DJCi300.toneplay = function(_channel, control, value, _status, group) { + let button = control - 0x40; + + if (value) { + // Pad buttons (buttons 1-8) will jump to a hotcue and change pitch + // Shift + pad buttons (buttons 9-16) will only change pitch without jumping + if (button < 8) { + // Jump to the most recently used hotcue + const recentHotcue = engine.getValue(group, "hotcue_focus"); + if ((recentHotcue > 0) && (engine.getValue(group, + `hotcue_${ recentHotcue }_status`) > 0)) { + engine.setValue(group, `hotcue_${ recentHotcue }_goto`, 1); + } else { + // If that hotcue doesn't exist or was deleted, jump to cue + engine.setValue(group, + "cue_goto", 1); + } + } + + // Subtract 8 from buttons if they're shifted + button = (button < 8) ? button : button - 8; + // Adjust pitch + if (button < 4) { + // Buttons 1-4 are +0 to +3 semitones + engine.setValue(group, "pitch", button); + // Buttons 5-8 are -4 to -1 semitones + } else { + engine.setValue(group, "pitch", button - 8); + } + } +}; + +// Update toneplay LEDs (LEDS will change depending on pitch, even if not caused by toneplay) +DJCi300.updateToneplayLED = function(value, group, _control) { + const status = (group === "[Channel1]") ? 0x96 : 0x97; + let control = 0x40; + + // Cut off the value at -4 and 3 semitones, then round + value = Math.min(value, 3); + value = Math.max(value, -4); + value = Math.round(value); + + // Buttons 1-4 (ctrl 0x40-0x43) are +0 to +3 semitones + // Buttons 5-8 (ctrl 0x44-0x47) are -4 to -1 semitones + if (value >= 0) { + control = control + value; + } else { + control = control + 8 + value; + } + + // Do the following for normal LEDs and the shifted LEDs + // Turn off all LEDs + for (let i = 0; i < 8; i++) { + midi.sendShortMsg(status, 0x40 + i, 0x00); + midi.sendShortMsg(status, 0x40 + i + 8, 0x00); + } + // Turn on current LED + midi.sendShortMsg(status, control, 0x7F); + midi.sendShortMsg(status, control + 8, 0x7F); +}; + +// Loop in button +DJCi300.loopInButton = function(_channel, _control, value, _status, group) { + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Override the active slicer if it exists + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + + // Create a 4 beat loop + engine.setValue(group, "beatloop_4_activate", 1); + } +}; + +// Loop out button +DJCi300.loopOutButton = function(_channel, _control, value, _status, group) { + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Override the active slicer if it exists + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + + // Disable the current loop if it exists + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } +}; + +DJCi300.Deck = function(deckNumber) { + components.Deck.call(this, deckNumber); + + // Slicer/slicer loop pad buttons + this.slicerPad = new components.ComponentContainer(); + // It's easier to keep track of which buttons are pressed as an array instead of having a property for each button + this.slicerPad.pressed = [false, false, false, false, false, false, false, false]; + // For slicer/slicer loop pads + for (const midiOffset of [0x20, 0x60]) { + for (let i = 0; i < 8; i++) { + this.slicerPad[i] = new components.Button({ + midi: [0x95 + deckNumber, midiOffset + i], + connect: function(startPos) { + const group = this.currentDeck; + const samplesBetweenSlices = DJCi300._samplesPerBeat(group) * engine.getValue(group, "beatloop_size") / 8; + // Calculate the start and end points (in samples) for each slice + this.slicerPad[i].startSample = (i === 0) ? startPos : this.slicerPad[i-1].endSample; + this.slicerPad[i].endSample = this.slicerPad[i].startSample + samplesBetweenSlices; + // Everything in the if-statement only needs to be done once (and not 8 times) + // when connected, which is why it is only executed when i === 7 + if (i === 7) { + // Connect callback functions if they are not connected already + if (this.slicerPad.beatConnection === undefined || this.slicerPad.beatConnection.isConnected === false) { + this.slicerPad.beatConnection = engine.makeConnection(group, "beat_distance", this.slicerCountBeat); + // This connection will reinitialize Slicer when the beatloop size spinbox changes + this.slicerPad.sizeConnection = engine.makeConnection(group, "beatloop_size", function() { + const nextStartPos = this.slicerPad[0].startSample; + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + component.connect(nextStartPos); + }); + }.bind(this)); + // This connection will remove Slicer when a new track is loaded + this.slicerPad.loadConnection = engine.makeConnection(group, "LoadSelectedTrack", function() { + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + }.bind(this)); + } + // Set loop position indicators to the start and end of the Slicer section as visual feedback + engine.setValue(group, "loop_start_position", this.slicerPad[0].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[7].endSample); + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } else if (DJCi300.padMode[group] === DJCi300.padModeSlicerloop) { + if (engine.getValue(group, "loop_enabled") === 0) { engine.setValue(group, "reloop_toggle", 1); } + } + }; + }.bind(this), + disconnect: function() { + // Set start and end points of each slice to placeholder value + this.slicerPad[i].startSample = -1; + this.slicerPad[i].endSample = -1; + // Much like before, everything in the if-statement only needs to be done once (not 8 times) + if (i === 0) { + const group = this.currentDeck; + // Disconnect slicer if it is connected + if (this.slicerPad.beatConnection !== undefined && this.slicerPad.beatConnection.isConnected === true) { + this.slicerPad.beatConnection.disconnect(); + this.slicerPad.sizeConnection.disconnect(); + this.slicerPad.loadConnection.disconnect(); + this.slicerPad.beat = -1; + this.slicerUpdateLED(group); + } + // Make loop position indicators disappear as visual feedback + engine.setValue(group, "loop_start_position", -1); + engine.setValue(group, "loop_end_position", -1); + } + }.bind(this), + input: function(_channel, control, value, _status, group) { + const button = control % 0x20; + + // Update array. 1 for on, 0 for off + if (value) { + this.slicerPad.pressed[button] = true; + } else { + this.slicerPad.pressed[button] = false; + } + + const startPad = this.slicerPad.pressed.indexOf(true); + const endPad = this.slicerPad.pressed.lastIndexOf(true); + + // If the slicer points are uninitialized, then do nothing. Otherwise: + if (this.slicerPad[0].startSample !== -1) { + // If at least one button is pressed, create a loop between those points + if (startPad !== -1) { + engine.setValue(group, "loop_start_position", this.slicerPad[startPad].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[endPad].endSample); + engine.setValue(group, "loop_in_goto", 1); + // Enable a loop if it doesn't already exist + if (engine.getValue(group, "loop_enabled") === 0) { + engine.setValue(group, "reloop_toggle", 1); + } + // If no buttons are pressed, reset the loop + } else { + engine.setValue(group, "loop_start_position", this.slicerPad[0].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[7].endSample); + + // Disable the loop (if we're not in slicer loop mode) + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } else if (DJCi300.padMode[group] === DJCi300.padModeSlicerloop) { + if (engine.getValue(group, "loop_enabled") === 0) { engine.setValue(group, "reloop_toggle", 1); } + } + } + this.slicerUpdateLED(group); + } + }.bind(this), + }); + } + } + // This function will count beats and move the Slicer section forward when needed + // It also lights up LEDs corresponding to the beat + this.slicerCountBeat = function(_value, group, _control) { + // Calculate current position in samples + const currentPos = engine.getValue(group, "track_samples") * engine.getValue(group, "playposition"); + // Calculate beat + let beat = 0; + for (let i = 0; i < 8; i++) { + beat = (currentPos >= this.slicerPad[i].endSample) ? (beat + 1) : beat; + } + + // If the beat count has changed, update the object property's value + if (this.slicerPad.beat !== beat) { + this.slicerPad.beat = beat; + // Only send an LED update if no pads are currently held down (pressed pad LEDs are handled above) + if (!this.slicerPad.pressed.includes(true)) { this.slicerUpdateLED(group); } + }; + + // If in slicer mode (not slicer loop mode), check to see if the slicer section needs to be moved + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + + // If slicerBeat is 8, move the slicer section forward + if (beat > 7) { + const nextStartPos = this.slicerPad[7].endSample; + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + component.connect(nextStartPos); + }); + } + } + }.bind(this); + this.slicerUpdateLED = function(group) { + const offset = (DJCi300.padMode[group] === DJCi300.padModeSlicer) ? 0x20 : 0x60; + const status = (group === "[Channel1]") ? 0x96 : 0x97; + + const startPad = this.slicerPad.pressed.indexOf(true); + const endPad = this.slicerPad.pressed.lastIndexOf(true); + + // Turn off all LEDs + for (let i = 0; i < 8; i++) { + midi.sendShortMsg(status, offset + i, 0x00); + } + // If the slicer points are uninitialized, then do nothing. Otherwise: + if (this.slicerPad[0].startSample !== -1) { + // If at least 1 button is held down, light that up + // Or in the case of 2+ buttons, light up everything between the outer 2 buttons + if (startPad !== -1) { + for (let i = startPad; i <= endPad; i++) { + midi.sendShortMsg(status, offset + i, 0x7F); + } + // Otherwise, light up the LED corresponding to the beat + } else { + midi.sendShortMsg(status, offset + Math.min(this.slicerPad.beat, 7), 0x7F); + } + } + }; }; +DJCi300.Deck.prototype = new components.Deck(); DJCi300.shutdown = function() { midi.sendShortMsg(0xB0, 0x7F, 0x00); diff --git a/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml b/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml index 464a320c8e6..c1c0a58cdd6 100644 --- a/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml +++ b/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml @@ -1,11 +1,11 @@ - + Hercules DJControl Inpulse 300 - DJ Phatso for Hercules Technical Support - MIDI Preset for Hercules DJControl Inpulse 300 - https://www.mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 - https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 + DJ Phatso for Hercules Technical Support + MIDI Preset for Hercules DJControl Inpulse 300 + https://www.mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 + https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 @@ -156,22 +156,22 @@ [Channel1] - beatloop_4_activate + DJCi300.loopInButton Loop In button 0x91 0x09 - + [Channel1] - reloop_toggle + DJCi300.loopOutButton Loop Out button 0x91 0x0A - + @@ -187,6 +187,88 @@ + + + [Channel1] + DJCi300.changeMode + Hot cue mode + 0x91 + 0x0F + + + + + + [Channel1] + DJCi300.changeMode + Roll mode + 0x91 + 0x10 + + + + + + [Channel1] + DJCi300.changeMode + Slicer mode + 0x91 + 0x11 + + + + + + [Channel1] + DJCi300.changeMode + Sampler mode + 0x91 + 0x12 + + + + + + [Channel1] + DJCi300.changeMode + Toneplay mode + 0x91 + 0x13 + + + + + + [Channel1] + DJCi300.changeMode + FX mode + 0x91 + 0x14 + + + + + + [Channel1] + DJCi300.changeMode + Slicer loop mode + 0x91 + 0x15 + + + + + + [Channel1] + DJCi300.changeMode + Beatjump mode + 0x91 + 0x16 + + + + + @@ -276,6 +358,7 @@ + [Channel2] @@ -287,6 +370,7 @@ + [Channel2] @@ -298,29 +382,30 @@ + [Channel2] - beatloop_4_activate + DJCi300.loopInButton Loop In button 0x92 0x09 - + [Channel2] - reloop_toggle + DJCi300.loopOutButton Loop Out button 0x92 0x0A - + - + [EffectRack1_EffectUnit2_Effect3] enabled @@ -332,6 +417,88 @@ + + + [Channel2] + DJCi300.changeMode + Hot cue mode + 0x92 + 0x0F + + + + + + [Channel2] + DJCi300.changeMode + Roll mode + 0x92 + 0x10 + + + + + + [Channel2] + DJCi300.changeMode + Slicer mode + 0x92 + 0x11 + + + + + + [Channel2] + DJCi300.changeMode + Sampler mode + 0x92 + 0x12 + + + + + + [Channel2] + DJCi300.changeMode + Toneplay mode + 0x92 + 0x13 + + + + + + [Channel2] + DJCi300.changeMode + FX mode + 0x92 + 0x14 + + + + + + [Channel2] + DJCi300.changeMode + Slicer loop mode + 0x92 + 0x15 + + + + + + [Channel2] + DJCi300.changeMode + Beatjump mode + 0x92 + 0x16 + + + + + @@ -485,7 +652,7 @@ [Channel2] loop_double - SHIFT + Loop Ou: Loop Double + SHIFT + Loop Out: Loop Double 0x95 0x0A @@ -992,589 +1159,1387 @@ - - - - - - - + - [Channel1] - beatjump_1_backward - PAD 1 + [Sampler1] + cue_gotoandstop + SHIFT + PAD 1 0x96 - 0x70 + 0x38 - [Channel1] - beatjump_1_forward - PAD 2 + [Sampler2] + cue_gotoandstop + SHIFT + PAD 2 0x96 - 0x71 + 0x39 - [Channel1] - beatjump_2_backward - PAD 3 + [Sampler3] + cue_gotoandstop + SHIFT + PAD 3 0x96 - 0x72 + 0x3A - [Channel1] - beatjump_2_forward - PAD 4 + [Sampler4] + cue_gotoandstop + SHIFT + PAD 4 0x96 - 0x73 + 0x3B - [Channel1] - beatjump_4_backward - PAD 5 + [Sampler5] + cue_gotoandstop + SHIFT + PAD 5 0x96 - 0x74 + 0x3C - [Channel1] - beatjump_4_forward - PAD 6 + [Sampler6] + cue_gotoandstop + SHIFT + PAD 6 0x96 - 0x75 + 0x3D - [Channel1] - beatjump_8_backward - PAD 7 + [Sampler7] + cue_gotoandstop + SHIFT + PAD 7 0x96 - 0x76 + 0x3E - [Channel1] - beatjump_8_forward - PAD 8 + [Sampler8] + cue_gotoandstop + SHIFT + PAD 8 0x96 - 0x77 + 0x3F - - - - - + - [Channel2] - hotcue_1_activate + [Channel1] + DJCi300.deck[0].slicerPad[0].input PAD 1 - 0x97 - 0x00 + 0x96 + 0x20 - + - [Channel2] - hotcue_2_activate + [Channel1] + DJCi300.deck[0].slicerPad[1].input PAD 2 - 0x97 - 0x01 + 0x96 + 0x21 - + - [Channel2] - hotcue_3_activate + [Channel1] + DJCi300.deck[0].slicerPad[2].input PAD 3 - 0x97 - 0x02 + 0x96 + 0x22 - + - [Channel2] - hotcue_4_activate + [Channel1] + DJCi300.deck[0].slicerPad[3].input PAD 4 - 0x97 - 0x03 + 0x96 + 0x23 - + - [Channel2] - hotcue_5_activate + [Channel1] + DJCi300.deck[0].slicerPad[4].input PAD 5 - 0x97 - 0x04 + 0x96 + 0x24 - + - [Channel2] - hotcue_6_activate + [Channel1] + DJCi300.deck[0].slicerPad[5].input PAD 6 - 0x97 - 0x05 + 0x96 + 0x25 - + - [Channel2] - hotcue_7_activate + [Channel1] + DJCi300.deck[0].slicerPad[6].input PAD 7 - 0x97 - 0x06 + 0x96 + 0x26 - + - [Channel2] - hotcue_8_activate + [Channel1] + DJCi300.deck[0].slicerPad[7].input PAD 8 - 0x97 - 0x07 + 0x96 + 0x27 - + - + - [Channel2] - hotcue_1_clear + [Channel1] + DJCi300.toneplay PAD 1 - 0x97 - 0x08 + 0x96 + 0x40 + + + + + + [Channel1] + DJCi300.toneplay + PAD 2 + 0x96 + 0x41 + + + + + + [Channel1] + DJCi300.toneplay + PAD 3 + 0x96 + 0x42 + + + + + + [Channel1] + DJCi300.toneplay + PAD 4 + 0x96 + 0x43 + + + + + + [Channel1] + DJCi300.toneplay + PAD 5 + 0x96 + 0x44 + + + + + + [Channel1] + DJCi300.toneplay + PAD 6 + 0x96 + 0x45 + + + + + + [Channel1] + DJCi300.toneplay + PAD 7 + 0x96 + 0x46 + + + + + + [Channel1] + DJCi300.toneplay + PAD 8 + 0x96 + 0x47 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 1 + 0x96 + 0x48 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 2 + 0x96 + 0x49 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 3 + 0x96 + 0x4A + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 4 + 0x96 + 0x4B + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 5 + 0x96 + 0x4C + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 6 + 0x96 + 0x4D + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 7 + 0x96 + 0x4E + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 8 + 0x96 + 0x4F + + + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[0].input + PAD 1 + 0x96 + 0x60 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[1].input + PAD 2 + 0x96 + 0x61 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[2].input + PAD 3 + 0x96 + 0x62 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[3].input + PAD 4 + 0x96 + 0x63 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[4].input + PAD 5 + 0x96 + 0x64 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[5].input + PAD 6 + 0x96 + 0x65 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[6].input + PAD 7 + 0x96 + 0x66 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[7].input + PAD 8 + 0x96 + 0x67 + + + + + + + [Channel1] + beatjump_1_backward + PAD 1 + 0x96 + 0x70 + + + + + + [Channel1] + beatjump_1_forward + PAD 2 + 0x96 + 0x71 + + + + + + [Channel1] + beatjump_2_backward + PAD 3 + 0x96 + 0x72 + + + + + + [Channel1] + beatjump_2_forward + PAD 4 + 0x96 + 0x73 + + + + + + [Channel1] + beatjump_4_backward + PAD 5 + 0x96 + 0x74 + + + + + + [Channel1] + beatjump_4_forward + PAD 6 + 0x96 + 0x75 + + + + + + [Channel1] + beatjump_8_backward + PAD 7 + 0x96 + 0x76 + + + + + + [Channel1] + beatjump_8_forward + PAD 8 + 0x96 + 0x77 + + + + + + + + + + + [Channel2] + hotcue_1_activate + PAD 1 + 0x97 + 0x00 + + + + + + [Channel2] + hotcue_2_activate + PAD 2 + 0x97 + 0x01 + + + + + + [Channel2] + hotcue_3_activate + PAD 3 + 0x97 + 0x02 + + + + + + [Channel2] + hotcue_4_activate + PAD 4 + 0x97 + 0x03 + + + + + + [Channel2] + hotcue_5_activate + PAD 5 + 0x97 + 0x04 + + + + + + [Channel2] + hotcue_6_activate + PAD 6 + 0x97 + 0x05 + + + + + + [Channel2] + hotcue_7_activate + PAD 7 + 0x97 + 0x06 + + + + + + [Channel2] + hotcue_8_activate + PAD 8 + 0x97 + 0x07 + + + + + + + [Channel2] + hotcue_1_clear + PAD 1 + 0x97 + 0x08 + + + + + + [Channel2] + hotcue_2_clear + PAD 2 + 0x97 + 0x09 + + + + + + [Channel2] + hotcue_3_clear + PAD 3 + 0x97 + 0x0A + + + + + + [Channel2] + hotcue_4_clear + PAD 4 + 0x97 + 0x0B + + + + + + [Channel2] + hotcue_5_clear + PAD 5 + 0x97 + 0x0C + + + + + + [Channel2] + hotcue_6_clear + PAD 6 + 0x97 + 0x0D + + + + + + [Channel2] + hotcue_7_clear + PAD 7 + 0x97 + 0x0E + + + + + + [Channel2] + hotcue_8_clear + PAD 8 + 0x97 + 0x0F + + + + + + + [EffectRack1_EffectUnit2_Effect1] + enabled + FX Unit 2 - Slot 1 On/Off + 0x97 + 0x50 + + + + + + [EffectRack1_EffectUnit2_Effect2] + enabled + FX Unit 2 - Slot 2 On/Off + 0x97 + 0x51 + + + + + + [EffectRack1_EffectUnit2_Effect3] + enabled + FX Unit 2 - Slot 3 On/Off + 0x97 + 0x52 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel2]_enable + FX Unit 1 On/Off - Deck B + 0x97 + 0x53 + + + + + + [EffectRack1_EffectUnit2_Effect1] + next_effect + FX Unit 2 - Slot 1 next effect + 0x97 + 0x54 + + + + + + [EffectRack1_EffectUnit2_Effect2] + next_effect + FX Unit 2 - Slot 2 next effect + 0x97 + 0x55 + + + + + + [EffectRack1_EffectUnit2_Effect3] + next_effect + FX Unit 2 - Slot 3 next effect + 0x97 + 0x56 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + FX Unit 2 On/Off - Deck B + 0x97 + 0x57 + + + + + + + [EffectRack1_EffectUnit4_Effect1] + enabled + FX Unit 4 - Slot 1 On/Off + 0x97 + 0x58 + + + + + + [EffectRack1_EffectUnit4_Effect2] + enabled + FX Unit 4 - Slot 2 On/Off + 0x97 + 0x59 + + + + + + [EffectRack1_EffectUnit4_Effect3] + enabled + FX Unit 4 - Slot 3 On/Off + 0x97 + 0x5A + + + + + + [EffectRack1_EffectUnit3] + group_[Channel2]_enable + FX Unit 3 On/Off - Deck B + 0x97 + 0x5B + + + + + + [EffectRack1_EffectUnit4_Effect1] + next_effect + FX Unit 4 - Slot 1 next effect + 0x97 + 0x5C + + + + + + [EffectRack1_EffectUnit4_Effect2] + next_effect + FX Unit 4 - Slot 2 next effect + 0x97 + 0x5D + + + + + + [EffectRack1_EffectUnit4_Effect3] + next_effect + FX Unit 4 - Slot 3 next effect + 0x97 + 0x5E + + + + + + [EffectRack1_EffectUnit4] + group_[Channel2]_enable + FX Unit 4 On/Off - Deck B + 0x97 + 0x5F + + + + + + + [Channel2] + beatlooproll_0.125_activate + Loop 1/8 Beat (Pad 1) + 0x97 + 0x10 + + + + + + [Channel2] + beatlooproll_0.25_activate + Loop 1/4 Beat (Pad 2) + 0x97 + 0x11 + + + + + + [Channel2] + beatlooproll_0.5_activate + Loop 1/2 Beat (Pad 3) + 0x97 + 0x12 [Channel2] - hotcue_2_clear - PAD 2 + beatlooproll_1_activate + Loop 1 Beat (Pad 4) 0x97 - 0x09 + 0x13 [Channel2] - hotcue_3_clear - PAD 3 + beatlooproll_2_activate + Loop 2 Beat (Pad 5) 0x97 - 0x0A + 0x14 [Channel2] - hotcue_4_clear - PAD 4 + beatlooproll_4_activate + Loop 4 Beat (Pad 6) 0x97 - 0x0B + 0x15 [Channel2] - hotcue_5_clear - PAD 5 + beatlooproll_8_activate + Loop 8 Beat (Pad 7) 0x97 - 0x0C + 0x16 [Channel2] - hotcue_6_clear - PAD 6 + beatlooproll_16_activate + Loop 16 Beat (Pad 8) 0x97 - 0x0D + 0x17 + - [Channel2] - hotcue_7_clear - PAD 7 + [Sampler9] + cue_gotoandplay + PAD 1 0x97 - 0x0E + 0x30 - [Channel2] - hotcue_8_clear - PAD 8 + [Sampler10] + cue_gotoandplay + PAD 2 0x97 - 0x0F + 0x31 - - [EffectRack1_EffectUnit2_Effect1] - enabled - FX Unit 2 - Slot 1 On/Off + [Sampler11] + cue_gotoandplay + PAD 3 0x97 - 0x50 + 0x32 - [EffectRack1_EffectUnit2_Effect2] - enabled - FX Unit 2 - Slot 2 On/Off + [Sampler12] + cue_gotoandplay + PAD 1 0x97 - 0x51 + 0x33 - [EffectRack1_EffectUnit2_Effect3] - enabled - FX Unit 2 - Slot 3 On/Off + [Sampler13] + cue_gotoandplay + PAD 5 0x97 - 0x52 + 0x34 - [EffectRack1_EffectUnit1] - group_[Channel2]_enable - FX Unit 1 On/Off - Deck B + [Sampler14] + cue_gotoandplay + PAD 6 0x97 - 0x53 + 0x35 - [EffectRack1_EffectUnit2_Effect1] - next_effect - FX Unit 2 - Slot 1 next effect + [Sampler15] + cue_gotoandplay + PAD 7 0x97 - 0x54 + 0x36 - [EffectRack1_EffectUnit2_Effect2] - next_effect - FX Unit 2 - Slot 2 next effect + [Sampler16] + cue_gotoandplay + PAD 8 0x97 - 0x55 + 0x37 + - [EffectRack1_EffectUnit2_Effect3] - next_effect - FX Unit 2 - Slot 3 next effect + [Sampler9] + cue_gotoandstop + SHIFT + PAD 1 0x97 - 0x56 + 0x38 - [EffectRack1_EffectUnit2] - group_[Channel2]_enable - FX Unit 2 On/Off - Deck B + [Sampler10] + cue_gotoandstop + SHIFT + PAD 2 0x97 - 0x57 + 0x39 - - [EffectRack1_EffectUnit4_Effect1] - enabled - FX Unit 4 - Slot 1 On/Off + [Sampler11] + cue_gotoandstop + SHIFT + PAD 3 0x97 - 0x58 + 0x3A - [EffectRack1_EffectUnit4_Effect2] - enabled - FX Unit 4 - Slot 2 On/Off + [Sampler12] + cue_gotoandstop + SHIFT + PAD 4 0x97 - 0x59 + 0x3B - [EffectRack1_EffectUnit4_Effect3] - enabled - FX Unit 4 - Slot 3 On/Off + [Sampler13] + cue_gotoandstop + SHIFT + PAD 5 0x97 - 0x5A + 0x3C - [EffectRack1_EffectUnit3] - group_[Channel2]_enable - FX Unit 3 On/Off - Deck B + [Sampler14] + cue_gotoandstop + SHIFT + PAD 6 0x97 - 0x5B + 0x3D - [EffectRack1_EffectUnit4_Effect1] - next_effect - FX Unit 4 - Slot 1 next effect + [Sampler15] + cue_gotoandstop + SHIFT + PAD 7 0x97 - 0x5C + 0x3E - [EffectRack1_EffectUnit4_Effect2] - next_effect - FX Unit 4 - Slot 2 next effect + [Sampler16] + cue_gotoandstop + SHIFT + PAD 8 0x97 - 0x5D + 0x3F + - [EffectRack1_EffectUnit4_Effect3] - next_effect - FX Unit 4 - Slot 3 next effect + [Channel2] + DJCi300.deck[1].slicerPad[0].input + PAD 1 0x97 - 0x5E + 0x20 - + - [EffectRack1_EffectUnit4] - group_[Channel2]_enable - FX Unit 4 On/Off - Deck B + [Channel2] + DJCi300.deck[1].slicerPad[1].input + PAD 2 0x97 - 0x5F + 0x21 - + - [Channel2] - beatlooproll_0.125_activate - Loop 1/8 Beat (Pad 1) + DJCi300.deck[1].slicerPad[2].input + PAD 3 0x97 - 0x10 + 0x22 - + [Channel2] - beatlooproll_0.25_activate - Loop 1/4 Beat (Pad 2) + DJCi300.deck[1].slicerPad[3].input + PAD 4 + 0x97 + 0x23 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[4].input + PAD 5 + 0x97 + 0x24 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[5].input + PAD 6 + 0x97 + 0x25 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[6].input + PAD 7 + 0x97 + 0x26 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[7].input + PAD 8 + 0x97 + 0x27 + + + + + + + [Channel2] + DJCi300.toneplay + PAD 1 + 0x97 + 0x40 + + + + + + [Channel2] + DJCi300.toneplay + PAD 2 + 0x97 + 0x41 + + + + + + [Channel2] + DJCi300.toneplay + PAD 3 + 0x97 + 0x42 + + + + + + [Channel2] + DJCi300.toneplay + PAD 4 + 0x97 + 0x43 + + + + + + [Channel2] + DJCi300.toneplay + PAD 5 + 0x97 + 0x44 + + + + + + [Channel2] + DJCi300.toneplay + PAD 6 + 0x97 + 0x45 + + + + + + [Channel2] + DJCi300.toneplay + PAD 7 + 0x97 + 0x46 + + + + + + [Channel2] + DJCi300.toneplay + PAD 8 + 0x97 + 0x47 + + + + + + [Channel2] + DJCi300.toneplay + SHIFT + PAD 1 + 0x97 + 0x48 + + + + + + [Channel2] + DJCi300.toneplay + SHIFT + PAD 2 0x97 - 0x11 + 0x49 - + [Channel2] - beatlooproll_0.5_activate - Loop 1/2 Beat (Pad 3) + DJCi300.toneplay + SHIFT + PAD 3 0x97 - 0x12 + 0x4A - + [Channel2] - beatlooproll_1_activate - Loop 1 Beat (Pad 4) + DJCi300.toneplay + SHIFT + PAD 4 0x97 - 0x13 + 0x4B - + [Channel2] - beatlooproll_2_activate - Loop 2 Beat (Pad 5) + DJCi300.toneplay + SHIFT + PAD 5 0x97 - 0x14 + 0x4C - + [Channel2] - beatlooproll_4_activate - Loop 4 Beat (Pad 6) + DJCi300.toneplay + SHIFT + PAD 6 0x97 - 0x15 + 0x4D - + [Channel2] - beatlooproll_8_activate - Loop 8 Beat (Pad 7) + DJCi300.toneplay + SHIFT + PAD 7 0x97 - 0x16 + 0x4E - + [Channel2] - beatlooproll_16_activate - Loop 16 Beat (Pad 8) + DJCi300.toneplay + SHIFT + PAD 8 0x97 - 0x17 + 0x4F - + - + + - [Sampler9] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[0].input PAD 1 0x97 - 0x30 + 0x60 - + - [Sampler10] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[1].input PAD 2 0x97 - 0x31 + 0x61 - + - [Sampler11] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[2].input PAD 3 0x97 - 0x32 + 0x62 - + - [Sampler12] - cue_gotoandplay - PAD 1 + [Channel2] + DJCi300.deck[1].slicerPad[3].input + PAD 4 0x97 - 0x33 + 0x63 - + - [Sampler13] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[4].input PAD 5 0x97 - 0x34 + 0x64 - + - [Sampler14] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[5].input PAD 6 0x97 - 0x35 + 0x65 - + - [Sampler15] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[6].input PAD 7 0x97 - 0x36 + 0x66 - + - [Sampler16] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[7].input PAD 8 0x97 - 0x37 + 0x67 - + - - - - - - [Channel2] @@ -1784,7 +2749,7 @@ [Channel1] - DJCi300.scratchWheel + DJCi300.jogWheel Scratch Deck A (Jog-Wheel) 0xB1 0x0A @@ -1794,7 +2759,7 @@ [Channel1] - DJCi300.bendWheel + DJCi300.jogWheel Pitch Bend Deck A (Jog-Wheel) 0xB1 0x09 @@ -1802,16 +2767,6 @@ - - [Channel1] - DJCi300.scratchPad - Pitch Bend Deck A (FX PAD 7 / 8 ) - 0xB1 - 0x0C - - - - [EffectRack1_EffectUnit1_Effect3] @@ -1836,7 +2791,6 @@ - [Channel2] @@ -1923,8 +2877,8 @@ [Channel2] - DJCi300.scratchWheel - Pitch Bend Deck B (Jog-Wheel) + DJCi300.jogWheel + Scratch Deck B (Jog-Wheel) 0xB2 0x0A @@ -1933,7 +2887,7 @@ [Channel2] - DJCi300.bendWheel + DJCi300.jogWheel Pitch Bend Deck B (Jog-Wheel) 0xB2 0x09 @@ -1941,16 +2895,6 @@ - - [Channel2] - DJCi300.scratchPad - Pitch Bend Deck B (FX PAD 7 / 8 ) - 0xB2 - 0x0C - - - - @@ -1978,7 +2922,17 @@ [Channel1] - DJCi300.scratchWheel + DJCi300.jogWheel + SHIFT + Bend Deck A (Jog-Wheel) + 0xB4 + 0x09 + + + + + + [Channel1] + DJCi300.jogWheel SHIFT + Scratch Deck A (Jog-Wheel) 0xB4 0x0A @@ -1991,7 +2945,17 @@ [Channel2] - DJCi300.scratchWheel + DJCi300.jogWheel + SHIFT + Bend Deck B (Jog-Wheel) + 0xB5 + 0x09 + + + + + + [Channel2] + DJCi300.jogWheel SHIFT + Scratch Deck B (Jog-Wheel) 0xB5 0x0A @@ -2251,340 +3215,327 @@ 0x7f 0x0 - - - [Channel1] - end_of_track - Auto DJ On - 0.5 - 1 - 0x91 - 0x1C - 0x7f - 0x0 - - - [Channel1] - end_of_track - Auto DJ On - 0.5 - 1 - 0x91 - 0x1D - 0x7f - 0x0 - - - [Channel2] - end_of_track - Auto DJ On - 0.5 - 1 - 0x92 - 0x1C - 0x7f - 0x0 - - - [Channel2] - end_of_track - Auto DJ On - 0.5 - 1 - 0x92 - 0x1D - 0x7f - 0x0 - [Channel1] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x96 0x00 0x7E 0.5 + 2 [Channel1] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x96 0x01 0x7E 0.5 + 2 [Channel1] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x96 0x02 0x7E 0.5 + 2 [Channel1] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x96 0x03 0x7E 0.5 + 2 [Channel1] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x96 0x04 0x7E 0.5 + 2 [Channel1] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x96 0x05 0x7E 0.5 + 2 [Channel1] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x96 0x06 0x7E 0.5 + 2 [Channel1] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x96 0x07 0x7E 0.5 + 2 [Channel2] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x97 0x00 0x7E 0.5 + 2 [Channel2] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x97 0x01 0x7E 0.5 + 2 [Channel2] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x97 0x02 0x7E 0.5 + 2 [Channel2] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x97 0x03 0x7E 0.5 + 2 [Channel2] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x97 0x04 0x7E 0.5 + 2 [Channel2] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x97 0x05 0x7E 0.5 + 2 [Channel2] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x97 0x06 0x7E 0.5 + 2 [Channel2] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x97 0x07 0x7E 0.5 + 2 [Channel1] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x96 0x08 0x7E 0.5 + 2 [Channel1] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x96 0x09 0x7E 0.5 + 2 [Channel1] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x96 0x0A 0x7E 0.5 + 2 [Channel1] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x96 0x0B 0x7E 0.5 + 2 [Channel1] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x96 0x0C 0x7E 0.5 + 2 [Channel1] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x96 0x0D 0x7E 0.5 + 2 [Channel1] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x96 0x0E 0x7E 0.5 + 2 [Channel1] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x96 0x0F 0x7E 0.5 + 2 [Channel2] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x97 0x08 0x7E 0.5 + 2 [Channel2] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x97 0x09 0x7E 0.5 + 2 [Channel2] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x97 0x0A 0x7E 0.5 + 2 [Channel2] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x97 0x0B 0x7E 0.5 + 2 [Channel2] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x97 0x0C 0x7E 0.5 + 2 [Channel2] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x97 0x0D 0x7E 0.5 + 2 [Channel2] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x97 0x0E 0x7E 0.5 + 2 [Channel2] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x97 0x0F 0x7E 0.5 + 2 @@ -2803,7 +3754,7 @@ 0x7f - [EffectRack1_EffectUnit1] + [EffectRack1_EffectUnit2] group_[Channel1]_enable FX2 is active on Deck A 0.5 @@ -3197,6 +4148,150 @@ 0x37 0x7F 0.5 + + + [Sampler1] + play_indicator + (SHIFT + Pad 1 DECK A) + 0x96 + 0x38 + 0x7F + 0.5 + + + [Sampler9] + play_indicator + (SHIFT + Pad 1 DECK B) + 0x97 + 0x38 + 0x7F + 0.5 + + + [Sampler2] + play_indicator + (SHIFT + Pad 2 DECK A) + 0x96 + 0x39 + 0x7F + 0.5 + + + [Sampler10] + play_indicator + (SHIFT + Pad 2 DECK B) + 0x97 + 0x39 + 0x7F + 0.5 + + + [Sampler3] + play_indicator + (SHIFT + Pad 3 DECK A) + 0x96 + 0x3A + 0x7F + 0.5 + + + [Sampler11] + play_indicator + (SHIFT + Pad 3 DECK B) + 0x97 + 0x3A + 0x7F + 0.5 + + + [Sampler4] + play_indicator + (SHIFT + Pad 4 DECK A) + 0x96 + 0x3B + 0x7F + 0.5 + + + [Sampler12] + play_indicator + (SHIFT + Pad 4 DECK B) + 0x97 + 0x3B + 0x7F + 0.5 + + + [Sampler5] + play_indicator + (SHIFT + Pad 5 DECK A) + 0x96 + 0x3C + 0x7F + 0.5 + + + [Sampler13] + play_indicator + (SHIFT + Pad 5 DECK B) + 0x97 + 0x3C + 0x7F + 0.5 + + + [Sampler6] + play_indicator + (SHIFT + Pad 6 DECK A) + 0x96 + 0x3D + 0x7F + 0.5 + + + [Sampler14] + play_indicator + (SHIFT + Pad 6 DECK B) + 0x97 + 0x3D + 0x7F + 0.5 + + + [Sampler7] + play_indicator + (SHIFT + Pad 7 DECK A) + 0x96 + 0x3E + 0x7F + 0.5 + + + [Sampler15] + play_indicator + (SHIFT + Pad 7 DECK B) + 0x97 + 0x3E + 0x7F + 0.5 + + + [Sampler8] + play_indicator + (SHIFT + Pad 8 DECK A) + 0x96 + 0x3F + 0x7F + 0.5 + + + [Sampler16] + play_indicator + (SHIFT + Pad 4 DECK B) + 0x97 + 0x3F + 0x7F + 0.5 diff --git a/res/controllers/Numark NS6II.midi.xml b/res/controllers/Numark NS6II.midi.xml new file mode 100755 index 00000000000..ea329ce46d0 --- /dev/null +++ b/res/controllers/Numark NS6II.midi.xml @@ -0,0 +1,4990 @@ + + + + Numark NS6II + Swiftb0y + Mapping for the Numark NS6II controller. It is able to fully communicate with the integrated screens. You can manipulate the Beatgrid of the track via the slicer pad page (since mixxx doesn't have slicer capabilities) + Encoded URL to Mixxx wiki page documenting this controller mapping + + + + + + + + + + + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x80 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x81 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x82 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x83 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x84 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x85 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x86 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x87 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x88 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x89 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x90 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x91 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x92 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x93 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x94 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x95 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x96 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x97 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x98 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x99 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[1].input + 0xB8 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[1].input + 0xB9 + 0x00 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.turn.input + 0xBF + 0x00 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x80 + 0x01 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x81 + 0x01 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x82 + 0x01 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x83 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x88 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x89 + 0x01 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x90 + 0x01 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x91 + 0x01 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x92 + 0x01 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x93 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x98 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x99 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[2].input + 0xB8 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[2].input + 0xB9 + 0x01 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.turn.input + 0xBF + 0x01 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x80 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x81 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x82 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x83 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x84 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x85 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x86 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x87 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x88 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x89 + 0x02 + + + + + + [NS6II] + NS6II.mixer.channels[0].loadTrackIntoDeck.input + 0x8F + 0x02 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x90 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x91 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x92 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x93 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x94 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x95 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x96 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x97 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x98 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x99 + 0x02 + + + + + + [NS6II] + NS6II.mixer.channels[0].loadTrackIntoDeck.input + 0x9F + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[3].input + 0xB8 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[3].input + 0xB9 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x80 + 0x03 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x81 + 0x03 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x82 + 0x03 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x83 + 0x03 + + + + + + [NS6II] + NS6II.mixer.channels[1].loadTrackIntoDeck.input + 0x8F + 0x03 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x90 + 0x03 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x91 + 0x03 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x92 + 0x03 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x93 + 0x03 + + + + + + [NS6II] + NS6II.mixer.channels[1].loadTrackIntoDeck.input + 0x9F + 0x03 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].dryWetKnob.input + 0xB8 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].dryWetKnob.input + 0xB9 + 0x03 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x80 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x81 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x82 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x83 + 0x04 + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x84 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x85 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x86 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x87 + 0x04 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].effectFocusButton.input + 0x88 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].effectFocusButton.input + 0x89 + 0x04 + + + + + + [NS6II] + NS6II.mixer.channels[2].loadTrackIntoDeck.input + 0x8F + 0x04 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x90 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x91 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x92 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x93 + 0x04 + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x94 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x95 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x96 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x97 + 0x04 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].effectFocusButton.input + 0x98 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].effectFocusButton.input + 0x99 + 0x04 + + + + + + [NS6II] + NS6II.mixer.channels[2].loadTrackIntoDeck.input + 0x9F + 0x04 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x80 + 0x05 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x81 + 0x05 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x82 + 0x05 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x83 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel1.input + 0x88 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel1.input + 0x89 + 0x05 + + + + + + [NS6II] + NS6II.mixer.channels[3].loadTrackIntoDeck.input + 0x8F + 0x05 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x90 + 0x05 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x91 + 0x05 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x92 + 0x05 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x93 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel1.input + 0x98 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel1.input + 0x99 + 0x05 + + + + + + [NS6II] + NS6II.mixer.channels[3].loadTrackIntoDeck.input + 0x9F + 0x05 + + + + + + [Channel1] + NS6II.decks[0].jog.inputTouch + 0x80 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputTouch + 0x81 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputTouch + 0x82 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputTouch + 0x83 + 0x06 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel2.input + 0x88 + 0x06 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel2.input + 0x89 + 0x06 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.press.input + 0x8F + 0x06 + + + + + + [Channel1] + NS6II.decks[0].jog.inputTouch + 0x90 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputTouch + 0x91 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputTouch + 0x92 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputTouch + 0x93 + 0x06 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel2.input + 0x98 + 0x06 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel2.input + 0x99 + 0x06 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.press.input + 0x9F + 0x06 + + + + + + [Channel1] + NS6II.decks[0].jog.inputWheel + 0xB0 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputWheel + 0xB1 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputWheel + 0xB2 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputWheel + 0xB3 + 0x06 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x80 + 0x07 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x81 + 0x07 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x82 + 0x07 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x83 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel3.input + 0x88 + 0x07 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel3.input + 0x89 + 0x07 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x90 + 0x07 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x91 + 0x07 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x92 + 0x07 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x93 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel3.input + 0x98 + 0x07 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel3.input + 0x99 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel4.input + 0x88 + 0x08 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel4.input + 0x89 + 0x08 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel4.input + 0x98 + 0x08 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel4.input + 0x99 + 0x08 + + + + + + [Master] + NS6II.mixer.crossfader.input + 0xBF + 0x08 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.slider.input + 0x84 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.slider.input + 0x85 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.slider.input + 0x86 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.slider.input + 0x87 + 0x09 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.slider.input + 0x94 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.slider.input + 0x95 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.slider.input + 0x96 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.slider.input + 0x97 + 0x09 + + + + + + [Channel1] + NS6II.decks[0].pitch.inputMSB + 0xB0 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].pitch.inputMSB + 0xB1 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].pitch.inputMSB + 0xB2 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].pitch.inputMSB + 0xB3 + 0x09 + + + + + + [Mixer Profile] + NS6II.mixer.crossfaderContour.input + 0xBF + 0x09 + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x80 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x81 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x82 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x83 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x84 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x85 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x86 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x87 + 0x0B + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x88 + 0x0B + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x89 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x90 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x91 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x92 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x93 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x94 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x95 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x96 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x97 + 0x0B + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x98 + 0x0B + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x99 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x80 + 0x0C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x81 + 0x0C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x82 + 0x0C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x83 + 0x0C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x88 + 0x0C + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x89 + 0x0C + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x90 + 0x0C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x91 + 0x0C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x92 + 0x0C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x93 + 0x0C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x98 + 0x0C + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x99 + 0x0C + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x80 + 0x0D + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x81 + 0x0D + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x82 + 0x0D + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x83 + 0x0D + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x88 + 0x0D + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x89 + 0x0D + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x90 + 0x0D + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x91 + 0x0D + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x92 + 0x0D + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x93 + 0x0D + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x98 + 0x0D + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x99 + 0x0D + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.loop.input + 0x84 + 0x0E + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.loop.input + 0x85 + 0x0E + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.loop.input + 0x86 + 0x0E + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.loop.input + 0x87 + 0x0E + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x8F + 0x0E + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.loop.input + 0x94 + 0x0E + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.loop.input + 0x95 + 0x0E + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.loop.input + 0x96 + 0x0E + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.loop.input + 0x97 + 0x0E + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x9F + 0x0E + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x84 + 0x0F + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x85 + 0x0F + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x86 + 0x0F + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x87 + 0x0F + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x8F + 0x0F + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x94 + 0x0F + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x95 + 0x0F + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x96 + 0x0F + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x97 + 0x0F + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x9F + 0x0F + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x80 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x81 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x82 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x83 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.auto.input + 0x84 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.auto.input + 0x85 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.auto.input + 0x86 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.auto.input + 0x87 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x90 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x91 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x92 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x93 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.auto.input + 0x94 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.auto.input + 0x95 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.auto.input + 0x96 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.auto.input + 0x97 + 0x10 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x8F + 0x11 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x9F + 0x11 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x8F + 0x12 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x9F + 0x12 + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x8F + 0x13 + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x9F + 0x13 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x84 + 0x14 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x85 + 0x14 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x86 + 0x14 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x87 + 0x14 + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x8F + 0x14 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x94 + 0x14 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x95 + 0x14 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x96 + 0x14 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x97 + 0x14 + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x9F + 0x14 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x84 + 0x15 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x85 + 0x15 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x86 + 0x15 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x87 + 0x15 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x94 + 0x15 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x95 + 0x15 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x96 + 0x15 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x97 + 0x15 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x84 + 0x16 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x85 + 0x16 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x86 + 0x16 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x87 + 0x16 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x94 + 0x16 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x95 + 0x16 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x96 + 0x16 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x97 + 0x16 + + + + + + [Channel1] + NS6II.mixer.channels[0].preGain.input + 0xB0 + 0x16 + + + + + + [Channel2] + NS6II.mixer.channels[1].preGain.input + 0xB1 + 0x16 + + + + + + [Channel3] + NS6II.mixer.channels[2].preGain.input + 0xB2 + 0x16 + + + + + + [Channel4] + NS6II.mixer.channels[3].preGain.input + 0xB3 + 0x16 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[0].input + 0x80 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[0].input + 0x81 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[0].input + 0x82 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[0].input + 0x83 + 0x17 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x84 + 0x17 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x85 + 0x17 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x86 + 0x17 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x87 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[0].input + 0x90 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[0].input + 0x91 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[0].input + 0x92 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[0].input + 0x93 + 0x17 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x94 + 0x17 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x95 + 0x17 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x96 + 0x17 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x97 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[0].input + 0xB0 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[0].input + 0xB1 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[0].input + 0xB2 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[0].input + 0xB3 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[1].input + 0x80 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[1].input + 0x81 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[1].input + 0x82 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[1].input + 0x83 + 0x18 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x84 + 0x18 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x85 + 0x18 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x86 + 0x18 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x87 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[1].input + 0x90 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[1].input + 0x91 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[1].input + 0x92 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[1].input + 0x93 + 0x18 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x94 + 0x18 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x95 + 0x18 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x96 + 0x18 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x97 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[1].input + 0xB0 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[1].input + 0xB1 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[1].input + 0xB2 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[1].input + 0xB3 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[2].input + 0x80 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[2].input + 0x81 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[2].input + 0x82 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[2].input + 0x83 + 0x19 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x84 + 0x19 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x85 + 0x19 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x86 + 0x19 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x87 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[2].input + 0x90 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[2].input + 0x91 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[2].input + 0x92 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[2].input + 0x93 + 0x19 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x94 + 0x19 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x95 + 0x19 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x96 + 0x19 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x97 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[2].input + 0xB0 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[2].input + 0xB1 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[2].input + 0xB2 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[2].input + 0xB3 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].filterCap.input + 0x80 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filterCap.input + 0x81 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filterCap.input + 0x82 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filterCap.input + 0x83 + 0x1A + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x84 + 0x1A + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x85 + 0x1A + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x86 + 0x1A + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x87 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].filterCap.input + 0x90 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filterCap.input + 0x91 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filterCap.input + 0x92 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filterCap.input + 0x93 + 0x1A + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x94 + 0x1A + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x95 + 0x1A + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x96 + 0x1A + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x97 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].filter.input + 0xB0 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filter.input + 0xB1 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filter.input + 0xB2 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filter.input + 0xB3 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].pfl.input + 0x80 + 0x1B + + + + + + [Channel2] + NS6II.mixer.channels[1].pfl.input + 0x81 + 0x1B + + + + + + [Channel3] + NS6II.mixer.channels[2].pfl.input + 0x82 + 0x1B + + + + + + [Channel4] + NS6II.mixer.channels[3].pfl.input + 0x83 + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x84 + 0x1B + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x85 + 0x1B + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x86 + 0x1B + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x87 + 0x1B + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x8F + 0x1B + + + + + + [Channel1] + NS6II.mixer.channels[0].pfl.input + 0x90 + 0x1B + + + + + + [Channel2] + NS6II.mixer.channels[1].pfl.input + 0x91 + 0x1B + + + + + + [Channel3] + NS6II.mixer.channels[2].pfl.input + 0x92 + 0x1B + + + + + + [Channel4] + NS6II.mixer.channels[3].pfl.input + 0x93 + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x94 + 0x1B + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x95 + 0x1B + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x96 + 0x1B + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x97 + 0x1B + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x9F + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x84 + 0x1C + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x85 + 0x1C + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x86 + 0x1C + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x87 + 0x1C + + + + + + [Master] + NS6II.mixer.splitCue.input + 0x8F + 0x1C + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x94 + 0x1C + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x95 + 0x1C + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x96 + 0x1C + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x97 + 0x1C + + + + + + [Master] + NS6II.mixer.splitCue.input + 0x9F + 0x1C + + + + + + [Channel1] + NS6II.mixer.channels[0].volume.input + 0xB0 + 0x1C + + + + + + [Channel2] + NS6II.mixer.channels[1].volume.input + 0xB1 + 0x1C + + + + + + [Channel1] + NS6II.mixer.channels[2].volume.input + 0xB2 + 0x1C + + + + + + [Channel4] + NS6II.mixer.channels[3].volume.input + 0xB3 + 0x1C + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x84 + 0x1D + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x85 + 0x1D + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x86 + 0x1D + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x87 + 0x1D + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x94 + 0x1D + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x95 + 0x1D + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x96 + 0x1D + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x97 + 0x1D + + + + + + [Channel1] + NS6II.mixer.channels[0].crossfaderOrientation.input + 0x80 + 0x1E + + + + + + [Channel2] + NS6II.mixer.channels[1].crossfaderOrientation.input + 0x81 + 0x1E + + + + + + [Channel3] + NS6II.mixer.channels[2].crossfaderOrientation.input + 0x82 + 0x1E + + + + + + [Channel4] + NS6II.mixer.channels[3].crossfaderOrientation.input + 0x83 + 0x1E + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x84 + 0x1E + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x85 + 0x1E + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x86 + 0x1E + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x87 + 0x1E + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x8F + 0x1E + + + + + + [Channel1] + NS6II.mixer.channels[0].crossfaderOrientation.input + 0x90 + 0x1E + + + + + + [Channel2] + NS6II.mixer.channels[1].crossfaderOrientation.input + 0x91 + 0x1E + + + + + + [Channel3] + NS6II.mixer.channels[2].crossfaderOrientation.input + 0x92 + 0x1E + + + + + + [Channel4] + NS6II.mixer.channels[3].crossfaderOrientation.input + 0x93 + 0x1E + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x94 + 0x1E + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x95 + 0x1E + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x96 + 0x1E + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x97 + 0x1E + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x9F + 0x1E + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x80 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x81 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x82 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x83 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x84 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x85 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x86 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x87 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x90 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x91 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x92 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x93 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x94 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x95 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x96 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x97 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].shiftButton.input + 0x80 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].shiftButton.input + 0x81 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].shiftButton.input + 0x82 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].shiftButton.input + 0x83 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x84 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x85 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x86 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x87 + 0x20 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[1].input + 0x88 + 0x20 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[1].input + 0x89 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].shiftButton.input + 0x90 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].shiftButton.input + 0x91 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].shiftButton.input + 0x92 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].shiftButton.input + 0x93 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x94 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x95 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x96 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x97 + 0x20 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[1].input + 0x98 + 0x20 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[1].input + 0x99 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x84 + 0x21 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x85 + 0x21 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x86 + 0x21 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x87 + 0x21 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[2].input + 0x88 + 0x21 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[2].input + 0x89 + 0x21 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x94 + 0x21 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x95 + 0x21 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x96 + 0x21 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x97 + 0x21 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[2].input + 0x98 + 0x21 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[2].input + 0x99 + 0x21 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x84 + 0x22 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x85 + 0x22 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x86 + 0x22 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x87 + 0x22 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[3].input + 0x88 + 0x22 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[3].input + 0x89 + 0x22 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x94 + 0x22 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x95 + 0x22 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x96 + 0x22 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x97 + 0x22 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[3].input + 0x98 + 0x22 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[3].input + 0x99 + 0x22 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x84 + 0x23 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x85 + 0x23 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x86 + 0x23 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x87 + 0x23 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x94 + 0x23 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x95 + 0x23 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x96 + 0x23 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x97 + 0x23 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterLeft.input + 0x84 + 0x28 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterLeft.input + 0x85 + 0x28 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterLeft.input + 0x86 + 0x28 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterLeft.input + 0x87 + 0x28 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterLeft.input + 0x94 + 0x28 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterLeft.input + 0x95 + 0x28 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterLeft.input + 0x96 + 0x28 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterLeft.input + 0x97 + 0x28 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterRight.input + 0x84 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterRight.input + 0x85 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterRight.input + 0x86 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterRight.input + 0x87 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterRight.input + 0x94 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterRight.input + 0x95 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterRight.input + 0x96 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterRight.input + 0x97 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].pitch.inputLSB + 0xB0 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].pitch.inputLSB + 0xB1 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].pitch.inputLSB + 0xB2 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].pitch.inputLSB + 0xB3 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x80 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x81 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x82 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x83 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x90 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x91 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x92 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x93 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].stripSearch.inputMSB + 0xB0 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].stripSearch.inputMSB + 0xB1 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].stripSearch.inputMSB + 0xB2 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].stripSearch.inputMSB + 0xB3 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x80 + 0x2C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x81 + 0x2C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x82 + 0x2C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x83 + 0x2C + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x90 + 0x2C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x91 + 0x2C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x92 + 0x2C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x93 + 0x2C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].mixMode.input + 0x88 + 0x41 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].mixMode.input + 0x89 + 0x41 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].mixMode.input + 0x98 + 0x41 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].mixMode.input + 0x99 + 0x41 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x80 + 0x46 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x81 + 0x46 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x82 + 0x46 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x83 + 0x46 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x90 + 0x46 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x91 + 0x46 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x92 + 0x46 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x93 + 0x46 + + + + + + [Channel1] + NS6II.decks[0].stripSearch.inputLSB + 0xB0 + 0x4B + + + + + + [Channel2] + NS6II.decks[1].stripSearch.inputLSB + 0xB1 + 0x4B + + + + + + [Channel3] + NS6II.decks[2].stripSearch.inputLSB + 0xB2 + 0x4B + + + + + + [Channel4] + NS6II.decks[3].stripSearch.inputLSB + 0xB3 + 0x4B + + + + + + [Channel3] + NS6II.mixer.extInputChannel3.input + 0x9F + 0x57 + + + + + + [NS6II] + NS6II.knobCapBehavior.input + 0x9F + 0x59 + + + + + + [NS6II] + NS6II.filterKnobBehavior.input + 0x9F + 0x5A + + + + + + [Channel4] + NS6II.mixer.extInputChannel4.input + 0x9F + 0x60 + + + + + + [Channel1] + NS6II.deckWatcherInput + 0x90 + 0x08 + + + + + + [Channel2] + NS6II.deckWatcherInput + 0x91 + 0x08 + + + + + + [Channel3] + NS6II.deckWatcherInput + 0x92 + 0x08 + + + + + + [Channel4] + NS6II.deckWatcherInput + 0x93 + 0x08 + + + + + + + NS6II.PCSelectorInput + 0x8F + 0x3C + + + + + + + NS6II.PCSelectorInput + 0x9F + 0x3C + + + + + + + NS6II.PCSelectorInput + 0x8F + 0x3D + + + + + + + NS6II.PCSelectorInput + 0x9F + 0x3D + + + + + + + + diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js new file mode 100755 index 00000000000..35a4bc0f545 --- /dev/null +++ b/res/controllers/Numark-NS6II-scripts.js @@ -0,0 +1,1475 @@ +/* + +TODO: +Maybe indicate current loop-/jumpsize by coloring the pads in a gradient? + +Reverse Engineering notes (likely interesting for other Numark Mixtrack-like controllers): + Platter: 1000 steps/revolution + 14bit-precision elements: search strips, pitch + (CC: 0x06 setup display controls) + CC: 0x0E set fine pitch of display + CC: Ox3F set track duration leds + CC: 0x06 set platter pos led + CC: 0x75 (val: 0 turn off all leds, !0 turn all on) (LEDs surface) + CC: 0x7F (val: 0 turn all of, val: !0 turn all elements on) (Display) + on: 0x09 pitch up led (0: off, 1: dimm, 2: full); + on: 0x0A pitch down led (sam vals as pitch up); + on: 0x51 pitch led 0; + on: 0x0D KeyLock display; + on: 0x0E pitch range value; + CC: 0x1F channel VuMeter: 1: off, 1: 1, 21: 2, 41: 3, 61: 4, 81: 5 (clipping) + CC: 0x7E individual setting of some display elements (solo element)? + ON: channel=0xF control=0x3C left PC1/PC2 val: 0x00 (lost control), 0x7F, (gained control) + ON: channel=0xF control=0x3D right PC1/PC2 val: 0x00 (lost control), 0x7F, (gained control) + + Master VUMeters: is set by controller + PAD_COLORS: color channels (r,g,b) encoded in two bits each to form velocity value (0b0rrbbgg) + BPM Display: + Absolute BPM Syx: 0x00,0x20,0x7f,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + Least-significant-Nibble of Last 5 bytes are responsible for the display value + Pitch_percentage_change syx: 0x00,0x20,0x7f,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + Pitch_change_ratio: 0.1bpm=10 offset 5 => 100*bpm (so it hits right in the middle) + Pitch_percentage_change 0%: 0x00,0x20,0x7f,,0xf,0xf,0xf,0xf,0xd,0x5 + set pitch percentage by + get 2's complement of d: (~d + 1 >>> 0) + + pitch range: + midi: ["note_on", note=0x0E, velocity=|bpm|] (abs(bpm) = 100bpm=100velocity) + Time Display: + Set Current Time Syx: 0x00,0x20,0x7f,0x01,0x04,0x08,0x00,0x00,0x00,0x00,0x00,0x00 + Set Track duration syx: 0x00,0x20,0x7f,0x01,0x03,0x08,0x00,0x00,0x00,0x00,0x00,0x00 + Set Track duration syx: 0x00,0x20,0x7f,0x01,0x03,0x08,0x00,0x04,0x0a,0x07,0x03,0x07 + syx[3] = channel (1-based) + switch time display: ["note_on",control=0x46,velocity] only 0x00 || 0x7F (0x00 display elapsed) + Least-significant-Nibble of Last 5 bytes are responsible for the display value + 6bit value increase in sysex = 1ms timer increase on display +*/ + +// eslint-disable-next-line no-var +var NS6II = {}; + +// UserSettings +// available rateRanges to cycle through using the Pitch Bend +/- Buttons. +NS6II.RATE_RANGES = [0.04, 0.08, 0.10, 0.16, 0.24, 0.50, 0.90, 1.00,]; + +// Globals + +NS6II.SCRATCH_SETTINGS = Object.freeze({ + alpha: 1/8, + beta: 0.125/32, +}); + +NS6II.PAD_COLORS = Object.freeze({ + OFF: 0, + RED: {FULL: 48, DIMM: 32, DIMMER: 16}, + YELLOW: {FULL: 60, DIMM: 40}, + GREEN: {FULL: 12, DIMM: 8}, + CELESTE: {FULL: 15, DIMM: 10}, + BLUE: {FULL: 3, DIMM: 2}, + PURPLE: {FULL: 59, DIMM: 34}, + PINK: {FULL: 58, DIMM: 37}, + ORANGE: {FULL: 56, DIMM: 36}, + WHITE: {FULL: 63, DIMM: 42}, +}); + + +NS6II.SERATO_SYX_PREFIX = [0x00, 0x20, 0x7f]; + +components.Button.prototype.off = engine.getSetting("useButtonBacklight") ? 0x01 : 0x00; + +components.HotcueButton.prototype.off = NS6II.PAD_COLORS.OFF; +components.HotcueButton.prototype.sendShifted = true; +components.HotcueButton.prototype.shiftControl = true; +components.HotcueButton.prototype.shiftOffset = 8; +components.HotcueButton.prototype.outConnect = false; + +components.SamplerButton.prototype.sendShifted = true; +components.SamplerButton.prototype.shiftControl = true; +components.SamplerButton.prototype.shiftOffset = 8; +components.HotcueButton.prototype.outConnect = false; + +NS6II.physicalSliderPositions = { + left: 0.5, + right: 0.5, +}; + +NS6II.mixxxColorToDeviceColorCode = colorObj => { + const red = (colorObj.red & 0xC0) >> 2; + const green = (colorObj.green & 0xC0) >> 4; + const blue = (colorObj.blue & 0xC0) >> 6; + return (red | green | blue); +}; + +NS6II.hardwareColorToHex = colorcode => { + const red = (colorcode & 0x30) << 18; + const green = (colorcode & 0x0C) << 12; + const blue = (colorcode & 0x03) << 6; + return (red | green | blue); +}; + +NS6II.padColorMapper = new ColorMapper(_.keyBy(_.range(0, 64), NS6II.hardwareColorToHex)); + +NS6II.RingBufferView = class { + constructor(indexable, startIndex = 0) { + this.indexable = indexable; + this.index = startIndex; + } + advanceBy(n) { + this.index = script.posMod(this.index + n, this.indexable.length); + return this.current(); + } + next() { + return this.advanceBy(1); + } + previous() { + return this.advanceBy(-1); + } + current() { + return this.indexable[this.index]; + } +}; + +NS6II.createFilteredSend = function(filter, send) { + return function(value) { + if (filter.call(this, value)) { + send.call(this, value); + } + }; +}; + +NS6II.createIdempotentSend = function(send) { + return NS6II.createFilteredSend(function(value) { + const v = this._value !== value; + if (v) { + this._value = value; + } + return v; + }, send); +}; + +/** + * creates an this.isPress guarded input handler + * @param {(value: number) => void} func callback that is called on ButtonDown + * @returns {MidiInputHandler} a MIDI handler suitable to be called via a XML binding + */ +NS6II.makeButtonDownInputHandler = function(func) { + return function(channel, control, value, status, _group) { + const isPress = this.isPress(channel, control, value, status); + this.output(isPress); + if (!isPress) { + return; + } + func.call(this, value); + }; +}; + + +NS6II.Deck = function(channelOffset) { + const theDeck = this; + const deckNumber = channelOffset + 1; + this.group = `[Channel${deckNumber}]`; + + const lr = channelOffset % 2 === 0 ? "left" : "right"; + const sliderPosAccessors = { + set: function(pos) { + NS6II.physicalSliderPositions[lr] = pos; + }, + get: function() { + return NS6II.physicalSliderPositions[lr]; + } + }; + + this.slip = new components.Button({ + midi: [0x90+channelOffset, 0x1F], + // shift: [0x90+channelOffset,0x04], + type: components.Button.prototype.types.toggle, + unshift: function() { + this.inKey = "slip_enabled"; + this.outKey = this.inKey; + }, + shift: function() { + // use repeat instead of quantize since that + // is already handled by the SyncButton + this.inKey = "repeat"; + this.outKey = this.inKey; + }, + }); + + // also known as "censor" + this.bleep = new components.Button({ + midi: [0x90 + channelOffset, 0x10], + // shift: [0x90+channelOffset,0x0D] + unshift: function() { + this.inKey = "reverseroll"; + this.outKey = this.inKey; + this.type = components.Button.prototype.types.push; + }, + shift: function() { + this.inKey = "keylock"; + this.outKey = this.inKey; + this.type = components.Button.prototype.types.toggle; + }, + }); + + const takeoverLEDValues = Object.freeze({ + OFF: 0, + DIMM: 1, + FULL: 2, + }); + const takeoverLEDControls = Object.freeze({ + up: 0x09, + center: 0x51, + down: 0x0A, + }); + + const takeoverDistance2Brightness = distance => { + // src/controllers/softtakeover.cpp + // SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128; + const takeoverThreshold = 3 / 128; + if (distance > takeoverThreshold && distance < 0.10) { + return takeoverLEDValues.DIMM; + } else if (distance >= 0.10) { + return takeoverLEDValues.FULL; + } else { + return takeoverLEDValues.OFF; + } + }; + + const directionOutValueScale = function(softwareSliderPosition) { + const normalizedPhysicalSliderPosition = sliderPosAccessors.get()*2 - 1; + + if ((this.midi[1] !== takeoverLEDControls.up) !== (normalizedPhysicalSliderPosition > softwareSliderPosition)) { + return takeoverLEDValues.OFF; + } + + const distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); + return takeoverDistance2Brightness(distance); + }; + + + this.takeoverLeds = new components.ComponentContainer({ + trigger: function() { + this.up.trigger(); + this.down.trigger(); + }, + center: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.center], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: function(value) { + const distance = Math.abs(value); + if (distance === 0) { + return takeoverLEDValues.FULL; + } else if (distance < 0.10) { + return takeoverLEDValues.DIMM; + } else { + return takeoverLEDValues.OFF; + } + } + }), + up: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.up], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: directionOutValueScale, + }), + down: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.down], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: directionOutValueScale, + }), + }); + + // features 14-bit precision + this.pitch = new components.Pot({ + midi: [0xB0 + channelOffset, 0x9], + // LSB: [0x90+channelOffset,0x29] + group: theDeck.group, + inKey: "rate", + invert: true, + inSetParameter: function(value) { + sliderPosAccessors.set(value); + components.Pot.prototype.inSetParameter.call(this, value); + theDeck.takeoverLeds.trigger(); + }, + }); + const rates = new NS6II.RingBufferView(NS6II.RATE_RANGES); + this.pitchBendPlus = new components.Button({ + midi: [0x90 + channelOffset, 0x0B], + // shift: [0x90+channelOffset,0x2B] + unshift: function() { + this.inKey = "rate_temp_up"; + this.input = components.Button.prototype.input; + }, + shift: function() { + this.inKey = "rateRange"; + this.input = NS6II.makeButtonDownInputHandler(function() { + this.inSetValue(rates.next()); + }); + }, + outConnect: false, + }); + this.pitchBendMinus = new components.Button({ + midi: [0x90 + channelOffset, 0x0C], + // shift: [0x90+channelOffset,0x2C] + unshift: function() { + this.inKey = "rate_temp_down"; + this.input = components.Button.prototype.input; + }, + shift: function() { + this.inKey = "rateRange"; + this.input = NS6II.makeButtonDownInputHandler(function() { + this.inSetValue(rates.previous()); + }); + }, + outConnect: false, + }); + this.shiftButton = new components.Button({ + midi: [0x90 + channelOffset, 0x20], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + NS6II.mixer.shift(); + NS6II.EffectUnits[channelOffset % 2 + 1].shift(); + theDeck.shift(); + } else { + NS6II.mixer.unshift(); + NS6II.EffectUnits[channelOffset % 2 + 1].unshift(); + theDeck.unshift(); + } + }, + }); + + this.sync = new components.SyncButton({ + midi: [0x90 + channelOffset, 0x02], + // shift: [0x90+channelOffset,0x03] + }); + + this.play = new components.PlayButton({ + midi: [0x90 + channelOffset, 0x00], + // shift: [0x90+channelOffset,0x04] + }); + this.cue = new components.CueButton({ + midi: [0x90 + channelOffset, 0x01], + // shift: [0x90+channelOffset,0x05] + }); + + // midi: [0xB0 + channelOffset, 0x06], + this.jog = new components.JogWheelBasic({ + deck: deckNumber, + wheelResolution: 1000, // measurement (1000) wasn't producing accurate results (alt: 1073) + alpha: NS6II.SCRATCH_SETTINGS.alpha, + beta: NS6II.SCRATCH_SETTINGS.beta, + }); + + this.stripSearch = new components.Pot({ + midi: [0xB0 + channelOffset, 0x4D], // no feedback + // input MSB: [0xB0+deck,0x2F] LSB + group: theDeck.group, + inKey: "playposition", + shift: function() { + this.inSetParameter = components.Pot.prototype.inSetParameter; + }, + unshift: function() { + this.inSetParameter = function(value) { + // only allow searching when deck is not playing. + if (!engine.getParameter(this.group, "play")) { + engine.setParameter(this.group, this.inKey, value); + } + }; + }, + }); + this.scratch = new components.Button({ + midi: [0x90 + channelOffset, 0x07], + // shift: [0x90+channelOffset,0x46] + timerMode: false, + unshift: function() { + this.input = NS6II.makeButtonDownInputHandler; + this.input = function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + theDeck.jog.vinylMode = !theDeck.jog.vinylMode; + this.output(theDeck.jog.vinylMode); + } + }; + this.output(theDeck.jog.vinylMode); + }, + shift: function() { + this.input = function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + // toggle between time_elapsed/_remaining display mode + this.timerMode = !this.timerMode; + midi.sendShortMsg(0x90 + channelOffset, 0x46, this.timerMode ? 0x7F : 0x00); + } + }; + }, + }); + + this.display = new NS6II.Display(channelOffset, this); + + this.padUnit = new NS6II.PadModeContainers.ModeSelector(channelOffset+4, this.group); + + this.reconnectComponents(function(c) { + if (c.group === undefined) { + c.group = theDeck.group; + } + }); +}; + +NS6II.Deck.prototype = new components.Deck(); + +// JS implementation of engine/enginexfader.cpp:getPowerCalibration (8005e8cc81f7da91310bfc9088802bf5228a2d43) +NS6II.getPowerCalibration = function(transform) { + return Math.pow(0.5, 1.0/transform); +}; + +// JS implementation of util/rescaler.h:linearToOneByX (a939d976b12b4261f8ba14f7ba5e1f2ce9664342) +NS6II.linearToOneByX = function(input, inMin, inMax, outMax) { + const outRange = outMax - 1; + const inRange = inMax - inMin; + return outMax / (((inMax - input) / inRange * outRange) + 1); +}; + + +NS6II.MixerContainer = function() { + this.channels = []; + for (let i = 0; i < 4; i++) { + this.channels[i] = new NS6II.Channel(i); + } + this.crossfader = new components.Pot({ + midi: [0xBF, 0x08], + group: "[Master]", + inKey: "crossfader", + }); + this.splitCue = new components.Button({ + // There is a bug in Firmware v1.0.4 which causes the headsplit + // control to be sent inverted when the controller status is sent + // (either on PC1/PC2 switch or when requested via sysex). + // Numark is aware of the issue but they don't seem to be interested + // in fixing it, so this implements a workaround. + // `invertNext` should be called whenever the controller dumps the status + // of its physical controls to mixxx. + invertNext: function() { + this._invertNext = true; + this._timerHandle = engine.beginTimer(200, () => { + this._invertNext = false; + }, true); + }, + _invertNext: false, + midi: [0x9F, 0x1C], + group: "[Master]", + inKey: "headSplit", + isPress: function(channelmidi, control, value, status) { + const pressed = components.Button.prototype.isPress.call(this, channelmidi, control, value, status); + return this._invertNext ? !pressed : pressed; + } + }); + this.crossfaderContour = new components.Pot({ + midi: [0xBF, 0x09], + input: function(_channelMidi, _control, value, _status, _group) { + // mimic preferences/dialog/dlgprefcrossfader.cpp:slotUpdateXFader + const transform = NS6II.linearToOneByX(value, 0, 0x7F, 999.6); + engine.setValue("[Mixer Profile]", "xFaderCurve", transform); + const calibration = NS6II.getPowerCalibration(transform); + engine.setValue("[Mixer Profile]", "xFaderCalibration", calibration); + }, + }); + this.extInputChannel3 = new components.Button({ + midi: [0x9F, 0x57], + group: "[Channel3]", + max: 2, + inKey: "mute" + }); + this.extInputChannel4 = new components.Button({ + midi: [0x9F, 0x60], + group: "[Channel4]", + max: 2, + inKey: "mute" + }); + + this.browseSection = new NS6II.BrowseSection(); +}; + +NS6II.MixerContainer.prototype = new components.ComponentContainer(); + +/** + * Serialize a Number into the controller compatible format used in sysex messages + * @param {number} number input Integer to be converted + * @param {boolean} signed specify if the value can be negative. + * @param {number} precision how many nibbles the resulting buffer should have (depends on the message) + * @returns {Array} array of length that can be used to build sysex payloads + */ +NS6II.numberToSysex = function(number, signed, precision) { + const out = Array(precision).fill(0); + // build 2's complement in case number is negative + if (number < 0) { + number = ((~Math.abs(number|0) + 1) >>> 0); + } + // split nibbles of number into array + for (let i = out.length; i; i--) { + out[i-1] = number & 0xF; + number = number >> 4; + } + // set signed bit in sysex payload + if (signed) { + out[0] = (number < 0) ? 0b0111 : 0b1000; + } + return out; +}; +NS6II.sendSysexMessage = function(channel, location, payload) { + const msg = [0xF0].concat(NS6II.SERATO_SYX_PREFIX, channel, location, payload, 0xF7); + midi.sendSysexMsg(msg, msg.length); +}; + + +NS6II.DisplayElement = function(options) { + components.Component.call(this, options); +}; + +NS6II.DisplayElement.prototype = new components.Component({ + send: function(payload) { + if (this.loc !== undefined && this.loc.deck !== undefined && this.loc.control !== undefined) { + NS6II.sendSysexMessage(this.loc.deck, this.loc.control, payload); + } else { + components.Component.prototype.send.call(this, payload); + } + }, + shutdown: function() { + this.output(this.off); + }, +}); + + +NS6II.Display = function(channelOffset) { + const channel = (channelOffset + 1); + const deck = `[Channel${channel}]`; + + // optimization so frequently updated controls don't have to poll seldom + // updated controls each time. + const deckInfoCache = { + // seconds + duration: 0, + // stored as 1% = 100 + rate: 0, + rateDir: 1, // 1 or -1 (like the CO) + trackLoaded: false, + // stored as rotations per second instead of rpm. + vinylControlSpeedTypeRatio: 0, + }; + + const vinylControlSpeedTypeConnection = engine.makeConnection(deck, "vinylcontrol_speed_type", function(value) { + deckInfoCache.vinylControlSpeedTypeRatio = value/60; + }); + vinylControlSpeedTypeConnection.trigger(); + + const rateDirConnection = engine.makeConnection(deck, "rate_dir", function(value) { + deckInfoCache.rateDir = value; + }); + rateDirConnection.trigger(); + + this.keylockUI = new NS6II.DisplayElement({ + midi: [0x90 + channelOffset, 0x0D], + outKey: "keylock", + off: 0x00, + on: 0x7F, + }); + + this.rateRangeUI = new NS6II.DisplayElement({ + midi: [0x90 + channelOffset, 0x0E], + outKey: "rateRange", + off: 0, + outValueScale: function(value) { + deckInfoCache.rate = value * 10000; + return Math.round(value * 100); + } + }); + + this.bpmUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x01}, + outKey: "bpm", + off: 0, + outValueScale: function(value) { + return NS6II.numberToSysex(value * 100, false, 6); + }, + }); + + this.rateChangePercentageUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x02}, + outKey: "rate", + outValueScale: function(value) { + return NS6II.numberToSysex( + value * deckInfoCache.rate * deckInfoCache.rateDir, + true, + 6 + ); + }, + }); + + this.durationUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x3}, + outKey: "duration", + outValueScale: function(value) { + deckInfoCache.duration = value; + return NS6II.numberToSysex(value*62.5, true, 7); + }, + }); + + this.timeElapsedUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x04}, + outKey: "playposition", + outValueScale: function(playpos) { + const elapsedTime = deckInfoCache.duration * playpos; + return NS6II.numberToSysex( + elapsedTime*62.5, // arbitrary controller specific scaling factor + true, // signed int + 7 + ); + }, + }); + + this.playPositionRingUI = new NS6II.DisplayElement({ + midi: [0xB0 + channelOffset, 0x3F], + outKey: "playposition", + max: 0x7F, + off: 0x00, + outValueScale: function(playpos) { + // check if track is loaded because playpos value is 0.5 when there isn't a track loaded. + return deckInfoCache.trackLoaded ? + Math.round(playpos * this.max) : + this.off; + }, + send: NS6II.createIdempotentSend(NS6II.DisplayElement.prototype.send), + }); + + this.vinylStickerPositionUI = new NS6II.DisplayElement({ + midi: [0xB0 + channelOffset, 0x06], + outKey: "playposition", + max: 0x7F, + off: 0x00, + outValueScale: function(playpos) { + const elapsedTime = deckInfoCache.duration * playpos; + return script.posMod(elapsedTime * deckInfoCache.vinylControlSpeedTypeRatio, 1) * this.max; + }, + send: NS6II.createIdempotentSend(NS6II.DisplayElement.prototype.send), + }); + + this.deckLoadedConnection = engine.makeConnection(deck, "track_loaded", function(value) { + deckInfoCache.trackLoaded = value; + }); + this.deckLoadedConnection.trigger(); + +}; + +NS6II.Display.prototype = new components.ComponentContainer(); + +NS6II.PadMode = function(channelOffset) { + components.ComponentContainer.call(this, {}); + + this.constructPads = constructPad => { + this.pads = this.pads.map((_, padIndex) => constructPad(padIndex)); + }; + const makeParameterPressHandler = (control, onButtonDown) => + new components.Button({ + midi: [0x90 + channelOffset, control], + // never outconnect, as these buttons don't have LEDs + outConnect: false, + input: function(channelmidi, control, value, status, group) { + if (this.isPress(channelmidi, control, value, status)) { + onButtonDown(channelmidi, control, value, status, group); + } + }, + }); + this.assignParameterPressHandlerLeft = onButtonDown => { + this.parameterLeft = makeParameterPressHandler(0x28, onButtonDown); + }; + this.assignParameterPressHandlerRight = onButtonDown => { + this.parameterRight = makeParameterPressHandler(0x29, onButtonDown); + }; + // this is a workaround for components, forEachComponent only iterates + // over ownProperties, so these have to constructed by the constructor here + // instead of being merged by the ComponentContainer constructor + this.pads = Array(8).fill(undefined); + const doNothing = () => {}; + this.assignParameterPressHandlerLeft(doNothing); + this.assignParameterPressHandlerLeft(doNothing); +}; + +NS6II.PadMode.prototype = new components.ComponentContainer(); + +NS6II.Pad = function(options) { + components.Button.call(this, options); +}; +NS6II.Pad.prototype = new components.Button({ + // grey could be an alternative as well as a backlight color. + off: engine.getSetting("useButtonBacklight") ? NS6II.PAD_COLORS.RED.DIMMER : NS6II.PAD_COLORS.OFF, + outConnect: false, + sendShifted: true, + shiftControl: true, + shiftOffset: 8, +}); + +NS6II.PadModeContainers = {}; + +NS6II.PadModeContainers.HotcuesRegular = function(channelOffset, hotCueOffset) { + + NS6II.PadMode.call(this, channelOffset); + + this.constructPads(i => + new components.HotcueButton({ + midi: [0x90 + channelOffset, 0x14 + i], + // shift: [0x94+channelOffset,0x1b+i], + number: i + 1 + hotCueOffset, + colorMapper: NS6II.padColorMapper, + // sendRGB: function(colorObj) { + // this.send(NS6II.mixxxColorToDeviceColorCode(colorObj)); + // }, + off: NS6II.PAD_COLORS.OFF, + }) + ); + this.parameterLeft = new components.Button({ + midi: [0x90, 0x28], + unshift: function() { + // TODO change hotcue page + this.inKey = undefined; + this.input = () => {}; + }, + shift: function() { + this.inKey = "hotcue_focus_color_prev"; + this.input = components.Button.prototype.input; + } + }); + this.parameterRight = new components.Button({ + midi: [0x90, 0x29], + unshift: function() { + // TODO change hotcue page + this.inKey = undefined; + this.input = () => {}; + }, + shift: function() { + this.inKey = "hotcue_focus_color_next"; + this.input = components.Button.prototype.input; + } + }); +}; +NS6II.PadModeContainers.HotcuesRegular.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopAuto = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + const theContainer = this; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = loopSize => { + theContainer.currentBaseLoopSize = _.clamp(loopSize, -5, 7); + theContainer.pads.forEach((c, i) => { + if (c instanceof components.Component) { + c.disconnect(); + const loopSize = Math.pow(2, theContainer.currentBaseLoopSize + i); + c.inKey = `beatloop_${loopSize}_toggle`; + c.outKey = `beatloop_${loopSize}_enabled`; + c.connect(); + c.trigger(); + } + }); + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.LoopAuto.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.BeatJump = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + const theContainer = this; + this.currentBaseJumpExponent = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = function(loopSize) { + theContainer.currentBaseJumpExponent = _.clamp(loopSize, -5, 2); + + const applyToComponent = function(component, key) { + if (!(component instanceof components.Component)) { + return; + } + component.disconnect(); + component.inKey = key; + component.outKey = component.inKey; + component.connect(); + component.trigger(); + }; + for (let i = 0; i < 4; i++) { + const size = Math.pow(2, theContainer.currentBaseJumpExponent + i); + applyToComponent(theContainer.pads[i], `beatjump_${size}_forward`); + applyToComponent(theContainer.pads[i+4], `beatjump_${size}_backward`); + } + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseJumpExponent - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseJumpExponent + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopRoll = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + const theContainer = this; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = function(loopSize) { + // clamp loopSize to [-5;7] + theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); + let i = 0; + theContainer.pads.forEach(c => { + if (c instanceof components.Component) { + c.disconnect(); + c.inKey = `beatlooproll_${Math.pow(2, theContainer.currentBaseLoopSize + (i++))}_activate`; + c.outKey = c.inKey; + c.connect(); + c.trigger(); + } + }); + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + type: components.Button.prototype.types.toggle, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.LoopRoll.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopControl = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "loop_in", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "loop_out", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "beatloop_activate", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[3] = new components.LoopToggleButton({ + midi: [0x90 + channelOffset, 0x17], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[4] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x19], + key: "beatjump_backward", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[5] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x18], + key: "beatjump_forward", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[6] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1A], + key: "loop_halve", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + this.pads[7] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1B], + key: "loop_double", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); +}; +NS6II.PadModeContainers.LoopControl.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.KeyControl = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "sync_key", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "pitch_down", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "pitch_up", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[3] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x17], + inKey: "reset_key", + outKey: "pitch_adjust", + outValueScale: function(pitchAdjust) { + // reset_key sometimes sets the key to some small non-zero value sometimes (probably floating point rounding errors) + // so we check with tolerance here. + const epsilon = 0.001; + return Math.abs(pitchAdjust) > epsilon ? this.on : this.off; + }, + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + // TODO lower 4 pads; What should I map them to, maybe going by circle of fifths? + for (let i = 4; i < this.pads.length; i++) { + // Dummy pads for now. + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + trigger: function() { + this.send(this.off); + }, + }); + } +}; + +NS6II.PadModeContainers.KeyControl.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.SamplerNormal = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.constructPads(i => + new components.SamplerButton({ + midi: [0x90 + channelOffset, 0x14 + i], + number: i + 1, + empty: NS6II.PAD_COLORS.OFF, + playing: NS6II.PAD_COLORS.WHITE.FULL, + loaded: NS6II.PAD_COLORS.WHITE.DIMM, + }) + ); +}; +NS6II.PadModeContainers.SamplerNormal.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.SamplerVelocity = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + + this.constructPads(i => + new components.SamplerButton({ + midi: [0x90 + channelOffset, 0x14 + i], + number: i + 1, + empty: NS6II.PAD_COLORS.OFF, + playing: NS6II.PAD_COLORS.PINK.FULL, + loaded: NS6II.PAD_COLORS.PINK.DIMM, + volumeByVelocity: true, + }) + ); +}; + +NS6II.PadModeContainers.SamplerVelocity.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.BeatgridSettings = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + // Same layout as waveform customization in LateNight + // except pads[4] (bottom left button) + + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "beats_translate_curpos", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "beats_translate_earlier", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "beats_translate_later", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[3] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x17], + key: "shift_cues_later", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[4] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x18], + key: "bpm_tap", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[5] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x19], + key: "beats_adjust_faster", + on: NS6II.PAD_COLORS.YELLOW.FULL, + off: NS6II.PAD_COLORS.YELLOW.DIMM, + }); + this.pads[6] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1A], + key: "beats_adjust_slower", + on: NS6II.PAD_COLORS.YELLOW.FULL, + off: NS6II.PAD_COLORS.YELLOW.DIMM, + }); + this.pads[7] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1B], + key: "shift_cues_earlier", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); +}; + +NS6II.PadModeContainers.BeatgridSettings.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.IntroOutroMarkers = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + const keyPrefix = ["intro_start", "intro_end", "outro_start", "outro_end"]; + for (let i = 0; i < keyPrefix.length; ++i) { + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + outKey: `${keyPrefix[i]}_enabled`, + unshift: function() { + this.inKey = `${keyPrefix[i]}_activate`; + }, + shift: function() { + this.inKey = `${keyPrefix[i]}_clear`; + }, + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + } + // TODO lower 4 pads; What should I map them to? + for (let i = 4; i < this.pads.length; i++) { + // Dummy pads for now. + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + trigger: function() { + this.send(this.off); + }, + }); + } +}; + +NS6II.PadModeContainers.IntroOutroMarkers.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { + const theSelector = this; + + const updateSelectorLeds = () => { + theSelector.forEachComponent(c => c.trigger()); + }; + + const setPads = padInstance => { + if (padInstance === theSelector.padsContainer) { + return; + } + theSelector.padsContainer.forEachComponent(function(component) { + component.disconnect(); + }); + theSelector.padsContainer = padInstance; + updateSelectorLeds(); + theSelector.padsContainer.reconnectComponents(function(c) { + if (c.group === undefined) { + c.group = group; + } + }); + }; + + const makeModeSelectorInputHandler = (control, padInstances) => + new components.Button({ + midi: [0x90 + channelOffset, control], + padInstances: new NS6II.RingBufferView(padInstances), + input: function(channelmidi, control, value, status, _group) { + if (!this.isPress(channelmidi, control, value, status)) { + return; + } + if (this.padInstances.current() === theSelector.padsContainer) { + setPads(this.padInstances.next()); + } else { + this.padInstances.index = 0; + setPads(this.padInstances.current()); + } + }, + outValueScale: function(active) { + if (!active) { + return this.off; + } + switch (this.padInstances.index) { + case 0: return 0x04; // solid on + case 1: return 0x02; // blink on/off + case 2: return 0x03; // blink 3x + default: return this.off; + } + }, + trigger: function() { + this.output(this.padInstances.indexable.indexOf(theSelector.padsContainer) !== -1); + }, + }); + + const startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset, 0); + + this.modeSelectors = new components.ComponentContainer({ + cues: makeModeSelectorInputHandler(0x00 /*shift: 0x02*/, [startupModeInstance, new NS6II.PadModeContainers.HotcuesRegular(channelOffset, 8)]), + auto: makeModeSelectorInputHandler(0x10, [new NS6II.PadModeContainers.LoopAuto(channelOffset), new NS6II.PadModeContainers.LoopRoll(channelOffset)]), + loop: makeModeSelectorInputHandler(0x0E, [new NS6II.PadModeContainers.LoopControl(channelOffset), new NS6II.PadModeContainers.KeyControl(channelOffset)]), + sampler: makeModeSelectorInputHandler(0x0B /*shift: 0x0F*/, [new NS6II.PadModeContainers.SamplerNormal(channelOffset), new NS6II.PadModeContainers.SamplerVelocity(channelOffset)]), + slider: makeModeSelectorInputHandler(0x09, [new NS6II.PadModeContainers.BeatJump(channelOffset), new NS6II.PadModeContainers.IntroOutroMarkers(channelOffset), new NS6II.PadModeContainers.BeatgridSettings(channelOffset)]), + }); + + this.padsContainer = new NS6II.PadMode(channelOffset); + setPads(startupModeInstance); +}; + +NS6II.PadModeContainers.ModeSelector.prototype = new components.ComponentContainer(); + +NS6II.Channel = function(channelOffset) { + const deck = `[Channel${channelOffset+1}]`; + this.loadTrackIntoDeck = new components.Button({ + midi: [0x9F, 0x02 + channelOffset], + // midi: [0x90 + channelOffset, 0x17], + group: deck, + shift: function() { + this.inKey = "eject"; + this.outKey = this.inKey; + }, + unshift: function() { + this.inKey = "LoadSelectedTrack"; + this.outKey = this.inKey; + }, + }); + // used to determine whether vumeter on the controller would change + // so messages get only when that is the case. + let lastVuLevel = 0; + this.vuMeterLevelConnection = engine.makeConnection(deck, "vu_meter", value => { + // check if channel is peaking and increase value so that the peaking led gets lit as well + // (the vumeter and the peak led are driven by the same control) (values > 81 light up the peakLED as well) + + // convert high res value to 5 LED resolution + value = Math.floor(value*4) + engine.getValue(deck, "PeakIndicator"); + if (value === lastVuLevel) { + // return early if vumeter has not changed (on the controller) + return; + } else { + lastVuLevel = value; + } + midi.sendShortMsg(0xB0 + channelOffset, 0x1F, value * 20); + }); + this.preGain = new components.Pot({ + midi: [0xB0 + channelOffset, 0x16], + softTakeover: false, + group: deck, + inKey: "pregain" + }); + const eqIndicies = [0, 1, 2]; + this.eqKnobs = eqIndicies.map(i => + new components.Pot({ + midi: [0xB0 + channelOffset, 0x16 + i], + softTakeover: false, + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `parameter${3-i}`, + }) + ); + this.eqCaps = eqIndicies.map(i => + new components.Button({ + midi: [0x90 + channelOffset, 0x16 + i], + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `button_parameter${3-i}`, + isPress: function(_midiChannel, _control, value, _status) { + return NS6II.knobCapBehavior.state > 1 && value > 0; + } + }) + ); + this.filter = new components.Pot({ + midi: [0xB0 + channelOffset, 0x1A], + softTakeover: false, + group: `[QuickEffectRack1_${deck}]`, + inKey: "super1", + }); + + // Unused ATM + this.filterCap = new components.Button({ + // midi: [0x90 + channelOffset, 0x1A], + // group: "[QuickEffectRack1_" + deck + "_Effect1]", + // inKey: "enabled", + input: function() {}, + }); + + this.pfl = new components.Button({ + midi: [0x90 + channelOffset, 0x1B], + group: deck, + key: "pfl", + // override off as pfl buttons are always backlit (never completely off) + // and only turn dim with 0x00 + off: 0x00, + }); + this.crossfaderOrientation = new components.Component({ + midi: [0x90 + channelOffset, 0x1E], + group: deck, + inKey: "orientation", + inValueScale: function(value) { + // Controller values to represent the orientation and mixxx + // orientation representation don't match. + switch (value) { + case 1: return 0; + case 0: return 1; + case 2: return 2; + default: throw "unreachable!"; + } + }, + }); + + this.volume = new components.Pot({ + midi: [0xB0 + channelOffset, 0x1C], + softTakeover: false, + group: deck, + inKey: "volume", + }); +}; +NS6II.Channel.prototype = new components.ComponentContainer(); + +NS6II.BrowseSection = function() { + this.libraryNavigation = new components.ComponentContainer({ + turn: new components.Encoder({ + midi: [0xBF, 0x00], // shift: [0xBF,0x01] + group: "[Library]", + inKey: "MoveVertical", + shift: function() { + this.stepsize = engine.getSetting("navEncoderAcceleration"); + }, + unshift: function() { + this.stepsize = 1; + }, + input: function(_midiChannel, _control, value, _status, _group) { + this.inSetValue(value === 0x01 ? this.stepsize : -this.stepsize); + }, + }), + press: new components.Button({ + midi: [0x9F, 0x06], + group: "[Library]", + inKey: "GoToItem", + }), + }); + + /** + * @param {number} columnIdToSort Value from `[Library], sort_column` docs + * @returns {MidiInputHandler} a MIDI handler suitable to be called via a XML binding + */ + const makeSortColumnInputHandler = columnIdToSort => + NS6II.makeButtonDownInputHandler(function() { this.inSetValue(columnIdToSort); }); + + const makeSortColumnShiftHandler = inputFun => + function() { + this.group = "[Library]"; + this.inKey = "sort_column_toggle"; + this.outKey = this.inKey; + this.input = inputFun; + this.type = components.Button.prototype.types.push; + }; + + const sortBy = columnIdToSort => makeSortColumnShiftHandler(makeSortColumnInputHandler(columnIdToSort)); + + this.view = new components.Button({ + midi: [0x9F, 0x0E], // shift: [0x9F,0x13], + unshift: function() { + // TODO 2.5: switch to `[Skin], show_maximize_library`. + this.group = "[Master]"; + this.inKey = "maximize_library"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.toggle; + }, + shift: sortBy(script.LIBRARY_COLUMNS.BPM), + }); + this.back = new components.Button({ + midi: [0x9F, 0x11], // shift: [0x9F,0x12] + unshift: function() { + this.group = "[Library]"; + this.inKey = "MoveFocusBackward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.TITLE), + }); + this.area = new components.Button({ + midi: [0x9F, 0xF], // shift: [0x9F, 0x1E] + unshift: function() { + this.group = "[Library]"; + this.inKey = "MoveFocusForward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.KEY), + }); + this.lprep = new components.Button({ + midi: [0x9F, 0x1B], // shift: [0x9F, 0x14] + unshift: function() { + this.group = "[PreviewDeck1]"; + this.inKey = "LoadSelectedTrack"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.ARTIST), + }); +}; +NS6II.BrowseSection.prototype = new components.ComponentContainer(); + +// TouchFX / TouchAll +NS6II.knobCapBehavior = new components.Button({ + midi: [0x9F, 0x59], + state: 0, + input: function(_midiChannel, _control, value, _status, _group) { + // map 0, 64, 127 to 0, 1, 2 respectively + this.state = Math.round(value/64); + }, +}); + + +// FilterRoll / FilterFX +// Unused ATM +NS6II.filterKnobBehavior = new components.Button({ + midi: [0x9F, 0x5A], + state: 0, + input: function(_channel, _control, value, _status, _group) { + // map 0, 64, 127 to 0, 1, 2 respectively + this.state = Math.round(value/64); + }, +}); + +NS6II.deckWatcherInput = function(midichannel, _control, _value, _status, _group) { + const deck = midichannel; + const toDeck = NS6II.decks[deck]; + const fromDeck = NS6II.decks[(deck + 2) % 4]; + fromDeck.pitch.disconnect(); + toDeck.pitch.connect(); + toDeck.takeoverLeds.trigger(); +}; + +NS6II.PCSelectorInput = function(_midichannel, _control, value, _status, _group) { + if (value > 0) { + NS6II.mixer.splitCue.invertNext(); + } +}; + +NS6II.createEffectUnits = function() { + NS6II.EffectUnits = []; + for (let i = 1; i <= 2; i++) { + NS6II.EffectUnits[i] = new components.EffectUnit(i); + NS6II.EffectUnits[i].fxCaps = []; + for (let ii = 0; ii < 3; ii++) { + NS6II.EffectUnits[i].enableButtons[ii + 1].midi = [0x97 + i, ii]; // shift: [0x97+i,0x0B+ii] + NS6II.EffectUnits[i].fxCaps[ii + 1] = new components.Button({ + midi: [0x97 + i, 0x21 + ii], + group: `[EffectRack1_EffectUnit${NS6II.EffectUnits[i].currentUnitNumber}_Effect${ii+1}]`, + inKey: "enabled", + shifted: false, // used to disable fx input while selecting + input: function(midichannel, control, value, status, _group) { + if (NS6II.knobCapBehavior.state > 0) { + this.inSetParameter(this.isPress(midichannel, control, value, status) && !this.shifted); + } + }, + unshift: function() { + this.shifted = false; + }, + shift: function() { + this.shifted = true; + }, + }); + NS6II.EffectUnits[i].knobs[ii + 1].midi = [0xB7 + i, ii]; + } + NS6II.EffectUnits[i].effectFocusButton.midi = [0x97 + i, 0x04]; + NS6II.EffectUnits[i].dryWetKnob.midi = [0xB7 + i, 0x03]; + NS6II.EffectUnits[i].dryWetKnob.input = function(_midichannel, _control, value, _status, _group) { + if (value === 1) { + this.inSetParameter(this.inGetParameter() + 0.04); + } else if (value === 127) { + this.inSetParameter(this.inGetParameter() - 0.04); + } + }; + NS6II.EffectUnits[i].mixMode = new components.Button({ + midi: [0xB7 + i, 0x41], + type: components.Button.prototype.types.toggle, + inKey: "mix_mode", + group: NS6II.EffectUnits[i].group, + }); + for (let ii = 0; ii < 4; ii++) { + const channel = `Channel${ii + 1}`; + NS6II.EffectUnits[i].enableOnChannelButtons.addButton(channel); + NS6II.EffectUnits[i].enableOnChannelButtons[channel].midi = [0x97 + i, 0x05 + ii]; + } + NS6II.EffectUnits[i].init(); + } +}; + +NS6II.askControllerStatus = function() { + const controllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; + NS6II.mixer.splitCue.invertNext(); + midi.sendSysexMsg(controllerStatusSysex, controllerStatusSysex.length); +}; + +NS6II.init = function() { + + // force headMix to 0 because it is managed by the controller hardware mixer. + engine.setParameter("[Master]", "headMix", 0); + + NS6II.decks = new components.ComponentContainer(); + for (let i = 0; i < 4; i++) { + NS6II.decks[i] = new NS6II.Deck(i); + } + NS6II.mixer = new NS6II.MixerContainer(); + + NS6II.createEffectUnits(); + + NS6II.askControllerStatus(); +}; + +NS6II.shutdown = function() { + NS6II.mixer.shutdown(); + NS6II.decks.shutdown(); + NS6II.EffectUnits.forEach(unit => unit.shutdown()); +}; diff --git a/res/controllers/midi-controller-api.d.ts b/res/controllers/midi-controller-api.d.ts index 686489009db..cf5f08b32a5 100644 --- a/res/controllers/midi-controller-api.d.ts +++ b/res/controllers/midi-controller-api.d.ts @@ -1,7 +1,11 @@ +type MidiInputHandler = (channel: number, control: number, value:number, status:number, group:string) => void; + declare interface MidiInputHandlerController { disconnect(): boolean; } +/** MidiControllerJSProxy */ + declare namespace midi { /** diff --git a/res/skins/Deere (64 Samplers)/skin.xml b/res/skins/Deere (64 Samplers)/skin.xml index dc80dbaca77..6defc15c8b0 100644 --- a/res/skins/Deere (64 Samplers)/skin.xml +++ b/res/skins/Deere (64 Samplers)/skin.xml @@ -212,7 +212,7 @@ vertical - min,max + min,min