From 1a350c914024ed1622cd2fba81b61413fed174dd Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:20:34 +0100 Subject: [PATCH 01/14] feat(controllers): add new controller mapping for Numark NS6II --- res/controllers/Numark NS6II.midi.xml | 4952 +++++++++++++++++++++++ res/controllers/Numark-NS6II-scripts.js | 1385 +++++++ 2 files changed, 6337 insertions(+) create mode 100755 res/controllers/Numark NS6II.midi.xml create mode 100755 res/controllers/Numark-NS6II-scripts.js diff --git a/res/controllers/Numark NS6II.midi.xml b/res/controllers/Numark NS6II.midi.xml new file mode 100755 index 00000000000..91b83e90613 --- /dev/null +++ b/res/controllers/Numark NS6II.midi.xml @@ -0,0 +1,4952 @@ + + + + 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..2f59d506294 --- /dev/null +++ b/res/controllers/Numark-NS6II-scripts.js @@ -0,0 +1,1385 @@ +/* + +Reverse Engineering notes: + Platter: 1000 steps/revolution + Dual-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 +*/ + +var NS6II = {}; + +// UserSettings +NS6II.RATE_RANGES = [0.04, 0.08, 0.10, 0.16, 0.24, 0.50, 0.90, 1.00,]; + +NS6II.NAVIGATION_ENCODER_ACCELERATION = 5; + +NS6II.DEFAULT_LOOP_ROOT_SIZE = 1; + +NS6II.USE_BUTTON_BACKLIGHT = true; + +// Globals + +NS6II.SCRATCH_SETTINGS = { + alpha: 1/8, + beta: 0.125/32, +}; + +NS6II.PAD_COLORS = { + 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 = NS6II.USE_BUTTON_BACKLIGHT ? 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.mixxxColorToDeviceColorCode = function(colorObj) { + var red = (colorObj.red & 0xC0) >> 2; + var green = (colorObj.green & 0xC0) >> 4; + var blue = (colorObj.blue & 0xC0) >> 6; + return (red | green | blue); +}; + +NS6II.hardwareColorToHex = function(colorcode) { + var red = (colorcode & 0x30) << 18; + var green = (colorcode & 0x0C) << 12; + var blue = (colorcode & 0x03) << 6; + return (red | green | blue); +}; + + +NS6II.padColorMapper = new ColorMapper(_.keyBy(_.range(0, 64), NS6II.hardwareColorToHex)); + +NS6II.physicalSliderPositions = { + left: 0.5, + right: 0.5, +}; + +NS6II.CyclingArrayView = function(indexable, startIndex) { + this.indexable = indexable; + this.index = startIndex || 0; + this.advanceBy = _.bind(function(n) { + this.index = NS6II.posMod(this.index + n, this.indexable.length); + return this.current(); + }, this); + this.next = _.bind(function() { + return this.advanceBy(1); + }, this); + this.previous = _.bind(function() { + return this.advanceBy(-1); + }, this); + this.current = _.bind(function() { + return this.indexable[this.index]; + }, this); +}; + +/** + * creates an this.isPress guarded input handler + * @param {(value: number) => void} func callback that is called on ButtonDown + */ +NS6II.makeButtonDownInputHandler = function(func) { + return function(channel, control, value, status, _group) { + var isPress = this.isPress(channel, control, value, status); + this.output(isPress); + if (!isPress) { + return; + } + func.call(this, value); + }; +}; + +// TODO: 2.4 replace with `script.posMod` +NS6II.posMod = function(n, m) { + return ((n % m) + m) % m; +}; + +NS6II.Deck = function(channelOffset) { + var theDeck = this; + var deckNumber = channelOffset + 1; + this.group = "[Channel" + deckNumber + "]"; + + var makeSliderPosAccessors = function() { + var lr = channelOffset % 2 === 0 ? "left" : "right"; + return { + setter: function(pos) { + NS6II.physicalSliderPositions[lr] = pos; + }, + getter: function() { + return NS6II.physicalSliderPositions[lr]; + } + }; + }; + + var sliderPosAccessors = makeSliderPosAccessors(); + + 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; + }, + }); + + var takeoverLEDValues = { + OFF: 0, + DIMM: 1, + FULL: 2, + }; + var takeoverLEDControls = { + up: 0x09, + center: 0x51, + down: 0x0A, + }; + + // TODO fix me: this suffers from the UP leds being on after startup + // seems to be a components.Pot or Mixxx issue. + this.takeoverLeds = new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.center], + outKey: "rate", + off: 0, + output: function(softwareSliderPosition) { + // rate slider centered? + this.send(softwareSliderPosition === 0 ? takeoverLEDValues.FULL : takeoverLEDValues.OFF); + + var distance2Brightness = function(distance) { + // src/controllers/softtakeover.cpp + // SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128; + var takeoverThreshold = 3 / 128; + if (distance > takeoverThreshold && distance < 0.10) { + return takeoverLEDValues.DIMM; + } else if (distance >= 0.10) { + return takeoverLEDValues.FULL; + } else { + return takeoverLEDValues.OFF; + } + }; + + var normalizedPhysicalSliderPosition = sliderPosAccessors.getter()*2 - 1; + var distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); + var directionLedBrightness = distance2Brightness(distance); + + if (normalizedPhysicalSliderPosition > softwareSliderPosition) { + midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, takeoverLEDValues.OFF); + midi.sendShortMsg(this.midi[0], takeoverLEDControls.down, directionLedBrightness); + } else { + midi.sendShortMsg(this.midi[0], takeoverLEDControls.down, takeoverLEDValues.OFF); + midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, directionLedBrightness); + } + }, + }); + + // 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.setter(value); + + engine.setParameter(this.group, this.inKey, value); + + theDeck.takeoverLeds.trigger(); + + }, + }); + var rates = new NS6II.CyclingArrayView(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) + // engine.getValue(this.group, "vinylcontrol_speed_type"), + 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) { + var outRange = outMax - 1; + var inRange = inMax - inMin; + return outMax / (((inMax - input) / inRange * outRange) + 1); +}; + + +NS6II.MixerContainer = function() { + this.channels = []; + for (var 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: function() { + this._invertNext = true; + this._timerHandle = engine.beginTimer(200, function() { + this._invertNext = false; + }, true); + }, + _invertNext: false, + midi: [0x9F, 0x1C], + group: "[Master]", + inKey: "headSplit", + isPress: function(channelmidi, control, value, status) { + var 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 + var transform = NS6II.linearToOneByX(value, 0, 0x7F, 999.6); + engine.setValue("[Mixer Profile]", "xFaderCurve", transform); + var 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 + * @param {boolean} signed specify if the value can be negative. + * @returns {Array} array of length that can be used to build sysex payloads + */ +NS6II.numberToSysex = function(number, signed, precision) { + var out = Array(precision); + // 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 (var 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) ? 0x07 : 0x08; + } + return out; +}; +NS6II.sendSysexMessage = function(channel, location, payload) { + var 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) { + var channel = (channelOffset + 1); + var deck = "[Channel" + channel + "]"; + + // optimization so frequently updated controls don't have to poll seldom + // updated controls each time. + var deckInfoCache = { + // seconds + duration: 0, + // stored as 1% = 100 + rate: 0, + trackLoaded: false, + // stored as rotations per second instead of rpm. + vinylControlSpeedTypeRatio: 0, + }; + + var vinylControlSpeedTypeConnection = engine.makeConnection(deck, "vinylcontrol_speed_type", function(value) { + deckInfoCache.vinylControlSpeedTypeRatio = value/60; + }); + vinylControlSpeedTypeConnection.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, + 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) { + var 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; + }, + }); + + this.vinylStickerPositionUI = new NS6II.DisplayElement({ + midi: [0xB0 + channelOffset, 0x06], + outKey: "playposition", + max: 0x7F, + off: 0x00, + outValueScale: function(playpos) { + var elapsedTime = deckInfoCache.duration * playpos; + return NS6II.posMod(elapsedTime * deckInfoCache.vinylControlSpeedTypeRatio, 1) * this.max; + }, + }); + + 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 = _.bind(function(constructPad) { + this.pads = _.map(this.pads, function(_, padIndex) { + return constructPad(padIndex); + }); + }, this); + // 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); + this.parameterLeft = new components.Button({ + midi: [0x90 + channelOffset, 0x28], + outConnect: false, + input: function(_channelmidi, _control, _value, _status, _group) { + // do nothing + }, + }); + this.parameterRight = new components.Button({ + midi: [0x90 + channelOffset, 0x29], + outConnect: false, + input: function(_channelmidi, _control, _value, _status, _group) { + // do nothing + }, + }); +}; +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: NS6II.USE_BUTTON_BACKLIGHT ? NS6II.PAD_COLORS.RED.DIMMER : NS6II.PAD_COLORS.OFF, + outConnect: false, + sendShifted: true, + shiftControl: true, + shiftOffset: 8, +}); + +NS6II.PadModeContainers = {}; + +NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + this.constructPads(function(i) { + return new components.HotcueButton({ + midi: [0x90 + channelOffset, 0x14 + i], + // shift: [0x94+channelOffset,0x1b+i], + number: i + 1, + colorMapper: NS6II.padColorMapper, + // sendRGB: function(colorObj) { + // this.send(NS6II.mixxxColorToDeviceColorCode(colorObj)); + // }, + off: NS6II.PAD_COLORS.OFF, + }); + }); +}; +NS6II.PadModeContainers.HotcuesRegular.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopAuto = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + var theContainer = this; + this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; + + var changeLoopSize = function(loopSize) { + // clamp loop_size to [-5;7] + theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); + _.forEach(theContainer.pads, function(c, i) { + if (c instanceof components.Component) { + c.disconnect(); + var loopSize = Math.pow(2, theContainer.currentBaseLoopSize + i); + c.inKey = "beatloop_" + loopSize + "_toggle"; + c.outKey = "beatloop_" + loopSize + "_enabled"; + c.connect(); + c.trigger(); + } + }); + }; + + this.constructPads(function(i) { + return 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.parameterLeft = new components.Button({ + midi: [0x90 + channelOffset, 0x28], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseLoopSize - 1); + } + }, + }); + this.parameterRight = new components.Button({ + midi: [0x90 + channelOffset, 0x29], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseLoopSize + 1); + } + }, + }); + changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); +}; + +NS6II.PadModeContainers.LoopAuto.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.BeatJump = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + var theContainer = this; + this.currentBaseJumpExponent = NS6II.DEFAULT_LOOP_ROOT_SIZE; + + var changeLoopSize = function(loopSize) { + theContainer.currentBaseJumpExponent = _.clamp(loopSize, -5, 2); + + var applyToComponent = function(component, key) { + if (!(component instanceof components.Component)) { + return; + } + component.disconnect(); + component.inKey = key; + component.outKey = component.inKey; + component.connect(); + component.trigger(); + }; + for (var i = 0; i < 4; i++) { + var size = Math.pow(2, theContainer.currentBaseJumpExponent + i); + applyToComponent(theContainer.pads[i], "beatjump_" + size + "_forward"); + applyToComponent(theContainer.pads[i+4], "beatjump_" + size + "_backward"); + } + }; + + this.constructPads(function(i) { + return 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.parameterLeft = new components.Button({ + midi: [0x90 + channelOffset, 0x28], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseJumpExponent - 1); + } + }, + }); + this.parameterRight = new components.Button({ + midi: [0x90 + channelOffset, 0x29], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseJumpExponent + 1); + } + }, + }); + + changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); +}; + +NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopRoll = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + var theContainer = this; + this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; + + var changeLoopSize = function(loopSize) { + // clamp loopSize to [-5;7] + theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); + var i = 0; + _.forEach(theContainer.pads, function(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(function(i) { + return 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.parameterLeft = new components.Button({ + midi: [0x90 + channelOffset, 0x28], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseLoopSize - 1); + } + }, + }); + this.parameterRight = new components.Button({ + midi: [0x90 + channelOffset, 0x29], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + changeLoopSize(theContainer.currentBaseLoopSize + 1); + } + }, + }); + + changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); +}; + +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.SamplerNormal = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.constructPads(function(i) { + return 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(function(i) { + return 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.ModeSelector = function(channelOffset, group) { + var theSelector = this; + + var updateSelectorLeds = function() { + _.forEach(theSelector.modeSelectors, function(selector) { + selector.trigger(); + }); + }; + + var setPads = function(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; + } + }); + }; + + + var makeModeSelectorInputHandler = function(control, padInstances) { + return new components.Button({ + midi: [0x90 + channelOffset, control], + padInstances: new NS6II.CyclingArrayView(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); + }, + }); + }; + + var startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset); + + this.modeSelectors = { + cues: makeModeSelectorInputHandler(0x00 /*shift: 0x02*/, [startupModeInstance]), + auto: makeModeSelectorInputHandler(0x10, [new NS6II.PadModeContainers.LoopAuto(channelOffset), new NS6II.PadModeContainers.LoopRoll(channelOffset)]), + loop: makeModeSelectorInputHandler(0x0E, [new NS6II.PadModeContainers.LoopControl(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.BeatgridSettings(channelOffset)]), + }; + + this.padsContainer = new NS6II.PadMode(channelOffset); + setPads(startupModeInstance); +}; + +NS6II.PadModeContainers.ModeSelector.prototype = new components.ComponentContainer(); + +NS6II.Channel = function(channelOffset) { + var deck = "[Channel" + (channelOffset+1) + "]"; + // var theChannel = this; + 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. + var lastVuLevel = 0; + this.vuMeterLevelConnection = engine.makeConnection(deck, "VuMeter", function(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" + }); + this.eqKnobs = _.map(Array(3), function(_, i) { + return new components.Pot({ + midi: [0xB0 + channelOffset, 0x16 + i], + softTakeover: false, + group: "[EqualizerRack1_" + deck + "_Effect1]", + inKey: "parameter" + (3-i), + }); + }); + this.eqCaps = _.map(Array(3), function(_, i) { + return 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 = NS6II.NAVIGATION_ENCODER_ACCELERATION; + }, + 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 + */ + var makeSortColumnInputHandler = function(columnIdToSort) { + return NS6II.makeButtonDownInputHandler(function() { + this.inSetValue(columnIdToSort); + }); + }; + var makeSortColumnShiftHandler = function(inputFun) { + return function() { + this.group = "[Library]"; + this.inKey = "sort_column_toggle"; + this.outKey = this.inKey; + this.input = inputFun; + this.type = components.Button.prototype.types.push; + }; + }; + + // subset of https://manual.mixxx.org/2.3/en/chapters/appendix/mixxx_controls.html#control-[Library]-sort_column + var sortColumnTypes = { + bpm: 15, + title: 2, + key: 20, + artist: 1, + }; + + this.view = new components.Button({ + midi: [0x9F, 0x0E], // shift: [0x9F,0x13], + unshift: function() { + 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: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.bpm)), + }); + this.back = new components.Button({ + midi: [0x9F, 0x11], // shift: [0x9F,0x12] + unshift: function() { + this.inKey = "MoveFocusBackward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.title)), + }); + this.area = new components.Button({ + midi: [0x9F, 0xF], // shift: [0x9F, 0x1E] + unshift: function() { + this.inKey = "MoveFocusForward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.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: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.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) { + 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) { + this.state = Math.round(value/64); + }, +}); + +NS6II.deckWatcherInput = function(midichannel, _control, _value, _status, _group) { + var deck = midichannel; + var toDeck = NS6II.decks[deck]; + var 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 (var i = 1; i <= 2; i++) { + NS6II.EffectUnits[i] = new components.EffectUnit(i); + NS6II.EffectUnits[i].fxCaps = []; + for (var 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 (ii = 0; ii < 4; ii++) { + var 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() { + var 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 (var 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(); +}; + +// ES3 Polyfills + +// var Number = {}; +Number.isInteger = function(value) { + return typeof value === "number" && + isFinite(value) && + Math.floor(value) === value; +}; From 7190c4096632292d1dd01364632ca75284c70279 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:44:42 +0100 Subject: [PATCH 02/14] add Typescript MidiInputHandler definition --- res/controllers/midi-controller-api.d.ts | 4 ++++ 1 file changed, 4 insertions(+) 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 { /** From c56add06603f89f45496f05270488036296f0b25 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:01:06 +0200 Subject: [PATCH 03/14] update mapping to ES6 featureset --- res/controllers/Numark-NS6II-scripts.js | 443 +++++++++++------------- 1 file changed, 196 insertions(+), 247 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 2f59d506294..32483b4597d 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -1,6 +1,10 @@ /* -Reverse Engineering notes: +TODO: +Make "CueLoops" page map to hotcues 9-16 (maps nicely to the serato cueloop import. +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 Dual-precision elements: search strips, pitch (CC: 0x06 setup display controls) @@ -42,6 +46,7 @@ Reverse Engineering notes: 6bit value increase in sysex = 1ms timer increase on display */ +// eslint-disable-next-line no-var var NS6II = {}; // UserSettings @@ -55,12 +60,12 @@ NS6II.USE_BUTTON_BACKLIGHT = true; // Globals -NS6II.SCRATCH_SETTINGS = { +NS6II.SCRATCH_SETTINGS = Object.freeze({ alpha: 1/8, beta: 0.125/32, -}; +}); -NS6II.PAD_COLORS = { +NS6II.PAD_COLORS = Object.freeze({ OFF: 0, RED: {FULL: 48, DIMM: 32, DIMMER: 16}, YELLOW: {FULL: 60, DIMM: 40}, @@ -71,7 +76,7 @@ NS6II.PAD_COLORS = { PINK: {FULL: 58, DIMM: 37}, ORANGE: {FULL: 56, DIMM: 36}, WHITE: {FULL: 63, DIMM: 42}, -}; +}); NS6II.SERATO_SYX_PREFIX = [0x00, 0x20, 0x7f]; @@ -89,17 +94,17 @@ components.SamplerButton.prototype.shiftControl = true; components.SamplerButton.prototype.shiftOffset = 8; components.HotcueButton.prototype.outConnect = false; -NS6II.mixxxColorToDeviceColorCode = function(colorObj) { - var red = (colorObj.red & 0xC0) >> 2; - var green = (colorObj.green & 0xC0) >> 4; - var blue = (colorObj.blue & 0xC0) >> 6; +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 = function(colorcode) { - var red = (colorcode & 0x30) << 18; - var green = (colorcode & 0x0C) << 12; - var blue = (colorcode & 0x03) << 6; +NS6II.hardwareColorToHex = colorcode => { + const red = (colorcode & 0x30) << 18; + const green = (colorcode & 0x0C) << 12; + const blue = (colorcode & 0x03) << 6; return (red | green | blue); }; @@ -111,31 +116,34 @@ NS6II.physicalSliderPositions = { right: 0.5, }; -NS6II.CyclingArrayView = function(indexable, startIndex) { - this.indexable = indexable; - this.index = startIndex || 0; - this.advanceBy = _.bind(function(n) { - this.index = NS6II.posMod(this.index + n, this.indexable.length); +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(); - }, this); - this.next = _.bind(function() { + } + next() { return this.advanceBy(1); - }, this); - this.previous = _.bind(function() { + } + previous() { return this.advanceBy(-1); - }, this); - this.current = _.bind(function() { + } + current() { return this.indexable[this.index]; - }, this); + } }; /** * 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) { - var isPress = this.isPress(channel, control, value, status); + const isPress = this.isPress(channel, control, value, status); this.output(isPress); if (!isPress) { return; @@ -144,30 +152,22 @@ NS6II.makeButtonDownInputHandler = function(func) { }; }; -// TODO: 2.4 replace with `script.posMod` -NS6II.posMod = function(n, m) { - return ((n % m) + m) % m; -}; NS6II.Deck = function(channelOffset) { - var theDeck = this; - var deckNumber = channelOffset + 1; - this.group = "[Channel" + deckNumber + "]"; - - var makeSliderPosAccessors = function() { - var lr = channelOffset % 2 === 0 ? "left" : "right"; - return { - setter: function(pos) { - NS6II.physicalSliderPositions[lr] = pos; - }, - getter: function() { - return NS6II.physicalSliderPositions[lr]; - } - }; + 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]; + } }; - var sliderPosAccessors = makeSliderPosAccessors(); - this.slip = new components.Button({ midi: [0x90+channelOffset, 0x1F], // shift: [0x90+channelOffset,0x04], @@ -200,31 +200,32 @@ NS6II.Deck = function(channelOffset) { }, }); - var takeoverLEDValues = { + const takeoverLEDValues = Object.freeze({ OFF: 0, DIMM: 1, FULL: 2, - }; - var takeoverLEDControls = { + }); + const takeoverLEDControls = Object.freeze({ up: 0x09, center: 0x51, down: 0x0A, - }; + }); // TODO fix me: this suffers from the UP leds being on after startup // seems to be a components.Pot or Mixxx issue. this.takeoverLeds = new components.Component({ midi: [0x90 + channelOffset, takeoverLEDControls.center], outKey: "rate", - off: 0, + off: 0x00, output: function(softwareSliderPosition) { + // slider position in [-1.0; 1.0] interval. center := 0.0 // rate slider centered? this.send(softwareSliderPosition === 0 ? takeoverLEDValues.FULL : takeoverLEDValues.OFF); - var distance2Brightness = function(distance) { + const distance2Brightness = distance => { // src/controllers/softtakeover.cpp // SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128; - var takeoverThreshold = 3 / 128; + const takeoverThreshold = 3 / 128; if (distance > takeoverThreshold && distance < 0.10) { return takeoverLEDValues.DIMM; } else if (distance >= 0.10) { @@ -234,9 +235,9 @@ NS6II.Deck = function(channelOffset) { } }; - var normalizedPhysicalSliderPosition = sliderPosAccessors.getter()*2 - 1; - var distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); - var directionLedBrightness = distance2Brightness(distance); + const normalizedPhysicalSliderPosition = sliderPosAccessors.get()*2 - 1; + const distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); + const directionLedBrightness = distance2Brightness(distance); if (normalizedPhysicalSliderPosition > softwareSliderPosition) { midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, takeoverLEDValues.OFF); @@ -256,16 +257,15 @@ NS6II.Deck = function(channelOffset) { inKey: "rate", invert: true, inSetParameter: function(value) { + sliderPosAccessors.set(value); - sliderPosAccessors.setter(value); - - engine.setParameter(this.group, this.inKey, value); + components.Pot.prototype.inSetParameter.call(this, value); theDeck.takeoverLeds.trigger(); }, }); - var rates = new NS6II.CyclingArrayView(NS6II.RATE_RANGES); + const rates = new NS6II.RingBufferView(NS6II.RATE_RANGES); this.pitchBendPlus = new components.Button({ midi: [0x90 + channelOffset, 0x0B], // shift: [0x90+channelOffset,0x2B] @@ -329,7 +329,6 @@ NS6II.Deck = function(channelOffset) { this.jog = new components.JogWheelBasic({ deck: deckNumber, wheelResolution: 1000, // measurement (1000) wasn't producing accurate results (alt: 1073) - // engine.getValue(this.group, "vinylcontrol_speed_type"), alpha: NS6II.SCRATCH_SETTINGS.alpha, beta: NS6II.SCRATCH_SETTINGS.beta, }); @@ -396,15 +395,15 @@ NS6II.getPowerCalibration = function(transform) { // JS implementation of util/rescaler.h:linearToOneByX (a939d976b12b4261f8ba14f7ba5e1f2ce9664342) NS6II.linearToOneByX = function(input, inMin, inMax, outMax) { - var outRange = outMax - 1; - var inRange = inMax - inMin; + const outRange = outMax - 1; + const inRange = inMax - inMin; return outMax / (((inMax - input) / inRange * outRange) + 1); }; NS6II.MixerContainer = function() { this.channels = []; - for (var i = 0; i < 4; i++) { + for (let i = 0; i < 4; i++) { this.channels[i] = new NS6II.Channel(i); } this.crossfader = new components.Pot({ @@ -418,6 +417,8 @@ NS6II.MixerContainer = function() { // (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, function() { @@ -429,7 +430,7 @@ NS6II.MixerContainer = function() { group: "[Master]", inKey: "headSplit", isPress: function(channelmidi, control, value, status) { - var pressed = components.Button.prototype.isPress.call(this, channelmidi, control, value, status); + const pressed = components.Button.prototype.isPress.call(this, channelmidi, control, value, status); return this._invertNext ? !pressed : pressed; } }); @@ -437,9 +438,9 @@ NS6II.MixerContainer = function() { midi: [0xBF, 0x09], input: function(_channelMidi, _control, value, _status, _group) { // mimic preferences/dialog/dlgprefcrossfader.cpp:slotUpdateXFader - var transform = NS6II.linearToOneByX(value, 0, 0x7F, 999.6); + const transform = NS6II.linearToOneByX(value, 0, 0x7F, 999.6); engine.setValue("[Mixer Profile]", "xFaderCurve", transform); - var calibration = NS6II.getPowerCalibration(transform); + const calibration = NS6II.getPowerCalibration(transform); engine.setValue("[Mixer Profile]", "xFaderCalibration", calibration); }, }); @@ -463,29 +464,30 @@ NS6II.MixerContainer.prototype = new components.ComponentContainer(); /** * Serialize a Number into the controller compatible format used in sysex messages - * @param {Number} number + * @param {number} number input Integer to be converted * @param {boolean} signed specify if the value can be negative. - * @returns {Array} array of length that can be used to build sysex payloads + * @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) { - var out = Array(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 (var i = out.length; i; i--) { + 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) ? 0x07 : 0x08; + out[0] = (number < 0) ? 0b0111 : 0b1000; } return out; }; NS6II.sendSysexMessage = function(channel, location, payload) { - var msg = [0xF0].concat(NS6II.SERATO_SYX_PREFIX, channel, location, payload, 0xF7); + const msg = [0xF0].concat(NS6II.SERATO_SYX_PREFIX, channel, location, payload, 0xF7); midi.sendSysexMsg(msg, msg.length); }; @@ -509,12 +511,12 @@ NS6II.DisplayElement.prototype = new components.Component({ NS6II.Display = function(channelOffset) { - var channel = (channelOffset + 1); - var deck = "[Channel" + channel + "]"; + const channel = (channelOffset + 1); + const deck = `[Channel${ channel }]`; // optimization so frequently updated controls don't have to poll seldom // updated controls each time. - var deckInfoCache = { + const deckInfoCache = { // seconds duration: 0, // stored as 1% = 100 @@ -524,7 +526,7 @@ NS6II.Display = function(channelOffset) { vinylControlSpeedTypeRatio: 0, }; - var vinylControlSpeedTypeConnection = engine.makeConnection(deck, "vinylcontrol_speed_type", function(value) { + const vinylControlSpeedTypeConnection = engine.makeConnection(deck, "vinylcontrol_speed_type", function(value) { deckInfoCache.vinylControlSpeedTypeRatio = value/60; }); vinylControlSpeedTypeConnection.trigger(); @@ -580,7 +582,7 @@ NS6II.Display = function(channelOffset) { loc: {deck: channel, control: 0x04}, outKey: "playposition", outValueScale: function(playpos) { - var elapsedTime = deckInfoCache.duration * playpos; + const elapsedTime = deckInfoCache.duration * playpos; return NS6II.numberToSysex( elapsedTime*62.5, // arbitrary controller specific scaling factor true, // signed int @@ -608,8 +610,8 @@ NS6II.Display = function(channelOffset) { max: 0x7F, off: 0x00, outValueScale: function(playpos) { - var elapsedTime = deckInfoCache.duration * playpos; - return NS6II.posMod(elapsedTime * deckInfoCache.vinylControlSpeedTypeRatio, 1) * this.max; + const elapsedTime = deckInfoCache.duration * playpos; + return script.posMod(elapsedTime * deckInfoCache.vinylControlSpeedTypeRatio, 1) * this.max; }, }); @@ -625,30 +627,35 @@ NS6II.Display.prototype = new components.ComponentContainer(); NS6II.PadMode = function(channelOffset) { components.ComponentContainer.call(this, {}); - this.constructPads = _.bind(function(constructPad) { - this.pads = _.map(this.pads, function(_, padIndex) { - return constructPad(padIndex); + 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); + 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); - this.parameterLeft = new components.Button({ - midi: [0x90 + channelOffset, 0x28], - outConnect: false, - input: function(_channelmidi, _control, _value, _status, _group) { - // do nothing - }, - }); - this.parameterRight = new components.Button({ - midi: [0x90 + channelOffset, 0x29], - outConnect: false, - input: function(_channelmidi, _control, _value, _status, _group) { - // do nothing - }, - }); + this.pads = Array(8).fill(undefined); + const doNothing = () => {}; + this.assignParameterPressHandlerLeft(doNothing); + this.assignParameterPressHandlerLeft(doNothing); }; + NS6II.PadMode.prototype = new components.ComponentContainer(); NS6II.Pad = function(options) { @@ -669,8 +676,8 @@ NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); - this.constructPads(function(i) { - return new components.HotcueButton({ + this.constructPads(i => + new components.HotcueButton({ midi: [0x90 + channelOffset, 0x14 + i], // shift: [0x94+channelOffset,0x1b+i], number: i + 1, @@ -679,8 +686,9 @@ NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { // this.send(NS6II.mixxxColorToDeviceColorCode(colorObj)); // }, off: NS6II.PAD_COLORS.OFF, - }); - }); + }) + ); + // TODO implement parameter buttons (change hotcue page / hotcue_focus_color_next/_prev) }; NS6II.PadModeContainers.HotcuesRegular.prototype = new NS6II.PadMode(); @@ -688,49 +696,34 @@ NS6II.PadModeContainers.LoopAuto = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); - var theContainer = this; + const theContainer = this; this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; - var changeLoopSize = function(loopSize) { - // clamp loop_size to [-5;7] - theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); - _.forEach(theContainer.pads, function(c, i) { + const changeLoopSize = loopSize => { + theContainer.currentBaseLoopSize = _.clamp(loopSize, -5, 7); + theContainer.pads.forEach((c, i) => { if (c instanceof components.Component) { c.disconnect(); - var loopSize = Math.pow(2, theContainer.currentBaseLoopSize + i); - c.inKey = "beatloop_" + loopSize + "_toggle"; - c.outKey = "beatloop_" + loopSize + "_enabled"; + const loopSize = Math.pow(2, theContainer.currentBaseLoopSize + i); + c.inKey = `beatloop_${ loopSize }_toggle`; + c.outKey = `beatloop_${ loopSize }_enabled`; c.connect(); c.trigger(); } }); }; - this.constructPads(function(i) { - return new NS6II.Pad({ + 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.parameterLeft = new components.Button({ - midi: [0x90 + channelOffset, 0x28], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseLoopSize - 1); - } - }, - }); - this.parameterRight = new components.Button({ - midi: [0x90 + channelOffset, 0x29], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseLoopSize + 1); - } - }, - }); + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); }; @@ -741,13 +734,13 @@ NS6II.PadModeContainers.BeatJump = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); - var theContainer = this; + const theContainer = this; this.currentBaseJumpExponent = NS6II.DEFAULT_LOOP_ROOT_SIZE; - var changeLoopSize = function(loopSize) { + const changeLoopSize = function(loopSize) { theContainer.currentBaseJumpExponent = _.clamp(loopSize, -5, 2); - var applyToComponent = function(component, key) { + const applyToComponent = function(component, key) { if (!(component instanceof components.Component)) { return; } @@ -757,38 +750,24 @@ NS6II.PadModeContainers.BeatJump = function(channelOffset) { component.connect(); component.trigger(); }; - for (var i = 0; i < 4; i++) { - var size = Math.pow(2, theContainer.currentBaseJumpExponent + i); - applyToComponent(theContainer.pads[i], "beatjump_" + size + "_forward"); - applyToComponent(theContainer.pads[i+4], "beatjump_" + size + "_backward"); + 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(function(i) { - return new NS6II.Pad({ + 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.parameterLeft = new components.Button({ - midi: [0x90 + channelOffset, 0x28], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseJumpExponent - 1); - } - }, - }); - this.parameterRight = new components.Button({ - midi: [0x90 + channelOffset, 0x29], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseJumpExponent + 1); - } - }, - }); + }) + ); + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseJumpExponent - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseJumpExponent + 1)); changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); }; @@ -796,17 +775,17 @@ NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); NS6II.PadModeContainers.LoopRoll = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); - var theContainer = this; + const theContainer = this; this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; - var changeLoopSize = function(loopSize) { + const changeLoopSize = function(loopSize) { // clamp loopSize to [-5;7] theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); - var i = 0; - _.forEach(theContainer.pads, function(c) { + 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.inKey = `beatlooproll_${ Math.pow(2, theContainer.currentBaseLoopSize + (i++)) }_activate`; c.outKey = c.inKey; c.connect(); c.trigger(); @@ -823,23 +802,9 @@ NS6II.PadModeContainers.LoopRoll = function(channelOffset) { // key is set by changeLoopSize() }); }); - this.parameterLeft = new components.Button({ - midi: [0x90 + channelOffset, 0x28], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseLoopSize - 1); - } - }, - }); - this.parameterRight = new components.Button({ - midi: [0x90 + channelOffset, 0x29], - input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { - changeLoopSize(theContainer.currentBaseLoopSize + 1); - } - }, - }); + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); }; @@ -990,15 +955,15 @@ NS6II.PadModeContainers.BeatgridSettings.prototype = new NS6II.PadMode(); NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { - var theSelector = this; + const theSelector = this; - var updateSelectorLeds = function() { - _.forEach(theSelector.modeSelectors, function(selector) { + const updateSelectorLeds = () => { + Object.values(theSelector.modeSelectors).forEach(selector => { selector.trigger(); }); }; - var setPads = function(padInstance) { + const setPads = padInstance => { if (padInstance === theSelector.padsContainer) { return; } @@ -1014,13 +979,12 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { }); }; - - var makeModeSelectorInputHandler = function(control, padInstances) { - return new components.Button({ + const makeModeSelectorInputHandler = (control, padInstances) => + new components.Button({ midi: [0x90 + channelOffset, control], - padInstances: new NS6II.CyclingArrayView(padInstances), + padInstances: new NS6II.RingBufferView(padInstances), input: function(channelmidi, control, value, status, _group) { - if (this.isPress(channelmidi, control, value, status)) { + if (!this.isPress(channelmidi, control, value, status)) { return; } if (this.padInstances.current() === theSelector.padsContainer) { @@ -1045,9 +1009,8 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { this.output(this.padInstances.indexable.indexOf(theSelector.padsContainer) !== -1); }, }); - }; - var startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset); + const startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset); this.modeSelectors = { cues: makeModeSelectorInputHandler(0x00 /*shift: 0x02*/, [startupModeInstance]), @@ -1064,8 +1027,7 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { NS6II.PadModeContainers.ModeSelector.prototype = new components.ComponentContainer(); NS6II.Channel = function(channelOffset) { - var deck = "[Channel" + (channelOffset+1) + "]"; - // var theChannel = this; + const deck = `[Channel${ channelOffset+1 }]`; this.loadTrackIntoDeck = new components.Button({ midi: [0x9F, 0x02 + channelOffset], // midi: [0x90 + channelOffset, 0x17], @@ -1081,8 +1043,8 @@ NS6II.Channel = function(channelOffset) { }); // used to determine whether vumeter on the controller would change // so messages get only when that is the case. - var lastVuLevel = 0; - this.vuMeterLevelConnection = engine.makeConnection(deck, "VuMeter", function(value) { + let lastVuLevel = 0; + this.vuMeterLevelConnection = engine.makeConnection(deck, "VuMeter", 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) @@ -1102,28 +1064,28 @@ NS6II.Channel = function(channelOffset) { group: deck, inKey: "pregain" }); - this.eqKnobs = _.map(Array(3), function(_, i) { - return new components.Pot({ + this.eqKnobs = _.map(Array(3), (_, i) => + new components.Pot({ midi: [0xB0 + channelOffset, 0x16 + i], softTakeover: false, - group: "[EqualizerRack1_" + deck + "_Effect1]", - inKey: "parameter" + (3-i), - }); - }); - this.eqCaps = _.map(Array(3), function(_, i) { - return new components.Button({ + group: `[EqualizerRack1_${ deck }_Effect1]`, + inKey: `parameter${ 3-i}`, + }) + ); + this.eqCaps = _.map(Array(3), (_, i) => + new components.Button({ midi: [0x90 + channelOffset, 0x16 + i], - group: "[EqualizerRack1_" + deck + "_Effect1]", - inKey: "button_parameter" + (3-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 + "]", + group: `[QuickEffectRack1_${ deck }]`, inKey: "super1", }); @@ -1192,30 +1154,21 @@ NS6II.BrowseSection = function() { }); /** - * @param {Number} columnIdToSort Value from `[Library], sort_column` docs + * @param {number} columnIdToSort Value from `[Library], sort_column` docs */ - var makeSortColumnInputHandler = function(columnIdToSort) { - return NS6II.makeButtonDownInputHandler(function() { - this.inSetValue(columnIdToSort); - }); - }; - var makeSortColumnShiftHandler = function(inputFun) { - return function() { + 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; }; - }; - // subset of https://manual.mixxx.org/2.3/en/chapters/appendix/mixxx_controls.html#control-[Library]-sort_column - var sortColumnTypes = { - bpm: 15, - title: 2, - key: 20, - artist: 1, - }; + const sortBy = columnIdToSort => makeSortColumnShiftHandler(makeSortColumnInputHandler(columnIdToSort)); this.view = new components.Button({ midi: [0x9F, 0x0E], // shift: [0x9F,0x13], @@ -1226,27 +1179,29 @@ NS6II.BrowseSection = function() { this.input = components.Button.prototype.input; this.type = components.Button.prototype.types.toggle; }, - shift: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.bpm)), + 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: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.title)), + 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: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.key)), + shift: sortBy(script.LIBRARY_COLUMNS.KEY), }); this.lprep = new components.Button({ midi: [0x9F, 0x1B], // shift: [0x9F, 0x14] @@ -1257,7 +1212,7 @@ NS6II.BrowseSection = function() { this.input = components.Button.prototype.input; this.type = components.Button.prototype.types.push; }, - shift: makeSortColumnShiftHandler(makeSortColumnInputHandler(sortColumnTypes.artist)), + shift: sortBy(script.LIBRARY_COLUMNS.ARTIST), }); }; NS6II.BrowseSection.prototype = new components.ComponentContainer(); @@ -1267,6 +1222,7 @@ 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); }, }); @@ -1278,14 +1234,15 @@ 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) { - var deck = midichannel; - var toDeck = NS6II.decks[deck]; - var fromDeck = NS6II.decks[(deck + 2) % 4]; + const deck = midichannel; + const toDeck = NS6II.decks[deck]; + const fromDeck = NS6II.decks[(deck + 2) % 4]; fromDeck.pitch.disconnect(); toDeck.pitch.connect(); toDeck.takeoverLeds.trigger(); @@ -1299,15 +1256,15 @@ NS6II.PCSelectorInput = function(_midichannel, _control, value, _status, _group) NS6II.createEffectUnits = function() { NS6II.EffectUnits = []; - for (var i = 1; i <= 2; i++) { + for (let i = 1; i <= 2; i++) { NS6II.EffectUnits[i] = new components.EffectUnit(i); NS6II.EffectUnits[i].fxCaps = []; - for (var ii = 0; ii < 3; ii++) { + 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) + "]", + 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) { @@ -1339,8 +1296,8 @@ NS6II.createEffectUnits = function() { inKey: "mix_mode", group: NS6II.EffectUnits[i].group, }); - for (ii = 0; ii < 4; ii++) { - var channel = "Channel"+(ii + 1); + 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]; } @@ -1349,7 +1306,7 @@ NS6II.createEffectUnits = function() { }; NS6II.askControllerStatus = function() { - var controllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; + const controllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; NS6II.mixer.splitCue.invertNext(); midi.sendSysexMsg(controllerStatusSysex, controllerStatusSysex.length); }; @@ -1360,7 +1317,7 @@ NS6II.init = function() { engine.setParameter("[Master]", "headMix", 0); NS6II.decks = new components.ComponentContainer(); - for (var i = 0; i < 4; i++) { + for (let i = 0; i < 4; i++) { NS6II.decks[i] = new NS6II.Deck(i); } NS6II.mixer = new NS6II.MixerContainer(); @@ -1373,13 +1330,5 @@ NS6II.init = function() { NS6II.shutdown = function() { NS6II.mixer.shutdown(); NS6II.decks.shutdown(); -}; - -// ES3 Polyfills - -// var Number = {}; -Number.isInteger = function(value) { - return typeof value === "number" && - isFinite(value) && - Math.floor(value) === value; + NS6II.EffectUnits.forEach(unit => unit.shutdown()); }; From 3b011ec68012ce66c9ec660e8fafc99ea84d47d6 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:06:39 +0200 Subject: [PATCH 04/14] fix broken splitcue workaround --- res/controllers/Numark-NS6II-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 32483b4597d..82516216442 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -421,7 +421,7 @@ NS6II.MixerContainer = function() { // of its physical controls to mixxx. invertNext: function() { this._invertNext = true; - this._timerHandle = engine.beginTimer(200, function() { + this._timerHandle = engine.beginTimer(200, () => { this._invertNext = false; }, true); }, From 25a5a4873887b955a0de16af6a0dd63d2ebdc794 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:20:37 +0200 Subject: [PATCH 05/14] properly shutdown takeoverleds --- res/controllers/Numark-NS6II-scripts.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 82516216442..81c9ac0e36b 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -247,6 +247,11 @@ NS6II.Deck = function(channelOffset) { midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, directionLedBrightness); } }, + shutdown: function() { + Object.values(takeoverLEDControls).forEach(control => { + midi.sendShortMsg(this.midi[0], control, this.off); + }); + } }); // features 14-bit precision From 81ba04c64a2b25de15340f67a644a49622e34579 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:25:53 +0200 Subject: [PATCH 06/14] misc cleanup --- res/controllers/Numark-NS6II-scripts.js | 79 ++++++++++++------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 81c9ac0e36b..0523f0db523 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -94,6 +94,11 @@ 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; @@ -108,14 +113,8 @@ NS6II.hardwareColorToHex = colorcode => { return (red | green | blue); }; - NS6II.padColorMapper = new ColorMapper(_.keyBy(_.range(0, 64), NS6II.hardwareColorToHex)); -NS6II.physicalSliderPositions = { - left: 0.5, - right: 0.5, -}; - NS6II.RingBufferView = class { constructor(indexable, startIndex = 0) { this.indexable = indexable; @@ -156,7 +155,7 @@ NS6II.makeButtonDownInputHandler = function(func) { NS6II.Deck = function(channelOffset) { const theDeck = this; const deckNumber = channelOffset + 1; - this.group = `[Channel${ deckNumber }]`; + this.group = `[Channel${deckNumber}]`; const lr = channelOffset % 2 === 0 ? "left" : "right"; const sliderPosAccessors = { @@ -263,11 +262,8 @@ NS6II.Deck = function(channelOffset) { 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); @@ -517,7 +513,7 @@ NS6II.DisplayElement.prototype = new components.Component({ NS6II.Display = function(channelOffset) { const channel = (channelOffset + 1); - const deck = `[Channel${ channel }]`; + const deck = `[Channel${channel}]`; // optimization so frequently updated controls don't have to poll seldom // updated controls each time. @@ -710,8 +706,8 @@ NS6II.PadModeContainers.LoopAuto = function(channelOffset) { 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.inKey = `beatloop_${loopSize}_toggle`; + c.outKey = `beatloop_${loopSize}_enabled`; c.connect(); c.trigger(); } @@ -757,8 +753,8 @@ NS6II.PadModeContainers.BeatJump = function(channelOffset) { }; 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`); + applyToComponent(theContainer.pads[i], `beatjump_${size}_forward`); + applyToComponent(theContainer.pads[i+4], `beatjump_${size}_backward`); } }; @@ -790,7 +786,7 @@ NS6II.PadModeContainers.LoopRoll = function(channelOffset) { theContainer.pads.forEach(c => { if (c instanceof components.Component) { c.disconnect(); - c.inKey = `beatlooproll_${ Math.pow(2, theContainer.currentBaseLoopSize + (i++)) }_activate`; + c.inKey = `beatlooproll_${Math.pow(2, theContainer.currentBaseLoopSize + (i++))}_activate`; c.outKey = c.inKey; c.connect(); c.trigger(); @@ -798,15 +794,15 @@ NS6II.PadModeContainers.LoopRoll = function(channelOffset) { }); }; - this.constructPads(function(i) { - return new NS6II.Pad({ + 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)); @@ -869,31 +865,31 @@ NS6II.PadModeContainers.LoopControl.prototype = new NS6II.PadMode(); NS6II.PadModeContainers.SamplerNormal = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); - this.constructPads(function(i) { - return new components.SamplerButton({ + 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(function(i) { - return new components.SamplerButton({ + 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(); @@ -963,9 +959,7 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { const theSelector = this; const updateSelectorLeds = () => { - Object.values(theSelector.modeSelectors).forEach(selector => { - selector.trigger(); - }); + theSelector.forEachComponent(c => c.trigger()); }; const setPads = padInstance => { @@ -1032,7 +1026,7 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { NS6II.PadModeContainers.ModeSelector.prototype = new components.ComponentContainer(); NS6II.Channel = function(channelOffset) { - const deck = `[Channel${ channelOffset+1 }]`; + const deck = `[Channel${channelOffset+1}]`; this.loadTrackIntoDeck = new components.Button({ midi: [0x9F, 0x02 + channelOffset], // midi: [0x90 + channelOffset, 0x17], @@ -1049,7 +1043,7 @@ NS6II.Channel = function(channelOffset) { // 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, "VuMeter", value => { + 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) @@ -1069,19 +1063,20 @@ NS6II.Channel = function(channelOffset) { group: deck, inKey: "pregain" }); - this.eqKnobs = _.map(Array(3), (_, i) => + 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}`, + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `parameter${3-i}`, }) ); - this.eqCaps = _.map(Array(3), (_, i) => + this.eqCaps = eqIndicies.map(i => new components.Button({ midi: [0x90 + channelOffset, 0x16 + i], - group: `[EqualizerRack1_${ deck }_Effect1]`, - inKey: `button_parameter${ 3-i}`, + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `button_parameter${3-i}`, isPress: function(_midiChannel, _control, value, _status) { return NS6II.knobCapBehavior.state > 1 && value > 0; } @@ -1090,7 +1085,7 @@ NS6II.Channel = function(channelOffset) { this.filter = new components.Pot({ midi: [0xB0 + channelOffset, 0x1A], softTakeover: false, - group: `[QuickEffectRack1_${ deck }]`, + group: `[QuickEffectRack1_${deck}]`, inKey: "super1", }); @@ -1178,6 +1173,7 @@ NS6II.BrowseSection = function() { 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; @@ -1268,8 +1264,7 @@ NS6II.createEffectUnits = function() { 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 }]`, + 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) { From 27bb79fea0512b316141c9215031f4707e754a4a Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:29:18 +0200 Subject: [PATCH 07/14] refactor takeoverLeds, reduce traffic --- res/controllers/Numark-NS6II-scripts.js | 91 ++++++++++++++++++------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 0523f0db523..f3bb67866f5 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -135,6 +135,24 @@ NS6II.RingBufferView = class { } }; +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 @@ -210,18 +228,7 @@ NS6II.Deck = function(channelOffset) { down: 0x0A, }); - // TODO fix me: this suffers from the UP leds being on after startup - // seems to be a components.Pot or Mixxx issue. - this.takeoverLeds = new components.Component({ - midi: [0x90 + channelOffset, takeoverLEDControls.center], - outKey: "rate", - off: 0x00, - output: function(softwareSliderPosition) { - // slider position in [-1.0; 1.0] interval. center := 0.0 - // rate slider centered? - this.send(softwareSliderPosition === 0 ? takeoverLEDValues.FULL : takeoverLEDValues.OFF); - - const distance2Brightness = distance => { + const takeoverDistance2Brightness = distance => { // src/controllers/softtakeover.cpp // SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128; const takeoverThreshold = 3 / 128; @@ -234,23 +241,53 @@ NS6II.Deck = function(channelOffset) { } }; + const directionOutValueScale = function(softwareSliderPosition) { const normalizedPhysicalSliderPosition = sliderPosAccessors.get()*2 - 1; - const distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); - const directionLedBrightness = distance2Brightness(distance); - if (normalizedPhysicalSliderPosition > softwareSliderPosition) { - midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, takeoverLEDValues.OFF); - midi.sendShortMsg(this.midi[0], takeoverLEDControls.down, directionLedBrightness); - } else { - midi.sendShortMsg(this.midi[0], takeoverLEDControls.down, takeoverLEDValues.OFF); - midi.sendShortMsg(this.midi[0], takeoverLEDControls.up, directionLedBrightness); - } - }, - shutdown: function() { - Object.values(takeoverLEDControls).forEach(control => { - midi.sendShortMsg(this.midi[0], control, this.off); - }); + 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 @@ -603,6 +640,7 @@ NS6II.Display = function(channelOffset) { Math.round(playpos * this.max) : this.off; }, + send: NS6II.createIdempotentSend(NS6II.DisplayElement.prototype.send), }); this.vinylStickerPositionUI = new NS6II.DisplayElement({ @@ -614,6 +652,7 @@ NS6II.Display = function(channelOffset) { 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) { From 0e46c62662ee54cea52cd4e0ae8aeb6aa4fb534d Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:34:31 +0200 Subject: [PATCH 08/14] add second hotcuepage; add hotcue_focus_color buttons --- res/controllers/Numark-NS6II-scripts.js | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index f3bb67866f5..88d26592b17 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -712,7 +712,7 @@ NS6II.Pad.prototype = new components.Button({ NS6II.PadModeContainers = {}; -NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { +NS6II.PadModeContainers.HotcuesRegular = function(channelOffset, hotCueOffset) { NS6II.PadMode.call(this, channelOffset); @@ -720,7 +720,7 @@ NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { new components.HotcueButton({ midi: [0x90 + channelOffset, 0x14 + i], // shift: [0x94+channelOffset,0x1b+i], - number: i + 1, + number: i + 1 + hotCueOffset, colorMapper: NS6II.padColorMapper, // sendRGB: function(colorObj) { // this.send(NS6II.mixxxColorToDeviceColorCode(colorObj)); @@ -728,7 +728,30 @@ NS6II.PadModeContainers.HotcuesRegular = function(channelOffset) { off: NS6II.PAD_COLORS.OFF, }) ); - // TODO implement parameter buttons (change hotcue page / hotcue_focus_color_next/_prev) + 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(); @@ -1048,15 +1071,15 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { }, }); - const startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset); + const startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset, 0); - this.modeSelectors = { - cues: makeModeSelectorInputHandler(0x00 /*shift: 0x02*/, [startupModeInstance]), + 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)]), 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.BeatgridSettings(channelOffset)]), - }; + }); this.padsContainer = new NS6II.PadMode(channelOffset); setPads(startupModeInstance); From f0bd7c8d818ec7050b5b6b2e133baecea2a0cc8d Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:50:02 +0200 Subject: [PATCH 09/14] implement pad mode for controlling a track's key --- res/controllers/Numark-NS6II-scripts.js | 49 ++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 88d26592b17..3ad10f48e9e 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -925,6 +925,53 @@ NS6II.PadModeContainers.LoopControl = function(channelOffset) { }; 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 => @@ -1076,7 +1123,7 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { 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)]), + 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.BeatgridSettings(channelOffset)]), }); From c3148e87c8a235d85a048bd40771c73d95313f40 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:54:15 +0200 Subject: [PATCH 10/14] add padmode for setting intro and outro markers --- res/controllers/Numark-NS6II-scripts.js | 34 +++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 3ad10f48e9e..86ba10a493a 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -1,7 +1,6 @@ /* TODO: -Make "CueLoops" page map to hotcues 9-16 (maps nicely to the serato cueloop import. Maybe indicate current loop-/jumpsize by coloring the pads in a gradient? Reverse Engineering notes (likely interesting for other Numark Mixtrack-like controllers): @@ -1064,6 +1063,37 @@ NS6II.PadModeContainers.BeatgridSettings = function(channelOffset) { 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; @@ -1125,7 +1155,7 @@ NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { 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.BeatgridSettings(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); From 84009703b1fa42e06dc6118c376b2a388b3b166e Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:01:27 +0200 Subject: [PATCH 11/14] fix precommit --- res/controllers/Numark-NS6II-scripts.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 86ba10a493a..dc95b318f79 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -228,20 +228,20 @@ NS6II.Deck = function(channelOffset) { }); 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; - } - }; + // 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; + const normalizedPhysicalSliderPosition = sliderPosAccessors.get()*2 - 1; if ((this.midi[1] !== takeoverLEDControls.up) !== (normalizedPhysicalSliderPosition > softwareSliderPosition)) { return takeoverLEDValues.OFF; @@ -1294,6 +1294,7 @@ NS6II.BrowseSection = function() { /** * @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); }); From 15c11d13cfe6b6d1074baf1b92abec227d478f63 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:20:16 +0200 Subject: [PATCH 12/14] document settings --- res/controllers/Numark-NS6II-scripts.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index dc95b318f79..32ec8993167 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -5,7 +5,7 @@ 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 - Dual-precision elements: search strips, pitch + 14bit-precision elements: search strips, pitch (CC: 0x06 setup display controls) CC: 0x0E set fine pitch of display CC: Ox3F set track duration leds @@ -49,12 +49,17 @@ Reverse Engineering notes (likely interesting for other Numark Mixtrack-like con 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,]; +// Amount of tracks moved in a single step when turning the BROWSE encoder while pressing SHIFT. NS6II.NAVIGATION_ENCODER_ACCELERATION = 5; +// the size of the first pad on the loop-related and beatjump padmodes. +// this defines the exponent of the loop size so the real loopsize is 2^DEFAULT_LOOP_ROOT_SIZE! NS6II.DEFAULT_LOOP_ROOT_SIZE = 1; +// true: deactivated button has a slight (usually red) backlight, false: LED of deactivated button is completely off NS6II.USE_BUTTON_BACKLIGHT = true; // Globals From 590b0f9e483a814463df1f3dbfe56aaf9ac00fb1 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:38:25 +0100 Subject: [PATCH 13/14] fix: account for a dewcks `rate_dir` for displaying `rate` --- res/controllers/Numark-NS6II-scripts.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 32ec8993167..3955ea8df09 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -563,6 +563,7 @@ NS6II.Display = function(channelOffset) { 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, @@ -573,6 +574,11 @@ NS6II.Display = function(channelOffset) { }); 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", @@ -604,7 +610,7 @@ NS6II.Display = function(channelOffset) { outKey: "rate", outValueScale: function(value) { return NS6II.numberToSysex( - value * deckInfoCache.rate, + value * deckInfoCache.rate * deckInfoCache.rateDir, true, 6 ); From 16119fe2863f42a927d058f071d726627eab999c Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:05:02 +0100 Subject: [PATCH 14/14] feat: use GUI controller settings instead of JS constants --- res/controllers/Numark NS6II.midi.xml | 40 ++++++++++++++++++++++++- res/controllers/Numark-NS6II-scripts.js | 28 ++++++----------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/res/controllers/Numark NS6II.midi.xml b/res/controllers/Numark NS6II.midi.xml index 91b83e90613..ea329ce46d0 100755 --- a/res/controllers/Numark NS6II.midi.xml +++ b/res/controllers/Numark NS6II.midi.xml @@ -1,11 +1,49 @@ - + 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 + + + + + diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js index 3955ea8df09..35a4bc0f545 100755 --- a/res/controllers/Numark-NS6II-scripts.js +++ b/res/controllers/Numark-NS6II-scripts.js @@ -52,16 +52,6 @@ var NS6II = {}; // 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,]; -// Amount of tracks moved in a single step when turning the BROWSE encoder while pressing SHIFT. -NS6II.NAVIGATION_ENCODER_ACCELERATION = 5; - -// the size of the first pad on the loop-related and beatjump padmodes. -// this defines the exponent of the loop size so the real loopsize is 2^DEFAULT_LOOP_ROOT_SIZE! -NS6II.DEFAULT_LOOP_ROOT_SIZE = 1; - -// true: deactivated button has a slight (usually red) backlight, false: LED of deactivated button is completely off -NS6II.USE_BUTTON_BACKLIGHT = true; - // Globals NS6II.SCRATCH_SETTINGS = Object.freeze({ @@ -85,7 +75,7 @@ NS6II.PAD_COLORS = Object.freeze({ NS6II.SERATO_SYX_PREFIX = [0x00, 0x20, 0x7f]; -components.Button.prototype.off = NS6II.USE_BUTTON_BACKLIGHT ? 0x01 : 0x00; +components.Button.prototype.off = engine.getSetting("useButtonBacklight") ? 0x01 : 0x00; components.HotcueButton.prototype.off = NS6II.PAD_COLORS.OFF; components.HotcueButton.prototype.sendShifted = true; @@ -713,7 +703,7 @@ NS6II.Pad = function(options) { }; NS6II.Pad.prototype = new components.Button({ // grey could be an alternative as well as a backlight color. - off: NS6II.USE_BUTTON_BACKLIGHT ? NS6II.PAD_COLORS.RED.DIMMER : NS6II.PAD_COLORS.OFF, + off: engine.getSetting("useButtonBacklight") ? NS6II.PAD_COLORS.RED.DIMMER : NS6II.PAD_COLORS.OFF, outConnect: false, sendShifted: true, shiftControl: true, @@ -770,7 +760,7 @@ NS6II.PadModeContainers.LoopAuto = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); const theContainer = this; - this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); const changeLoopSize = loopSize => { theContainer.currentBaseLoopSize = _.clamp(loopSize, -5, 7); @@ -797,7 +787,7 @@ NS6II.PadModeContainers.LoopAuto = function(channelOffset) { this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); - changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); }; NS6II.PadModeContainers.LoopAuto.prototype = new NS6II.PadMode(); @@ -808,7 +798,7 @@ NS6II.PadModeContainers.BeatJump = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); const theContainer = this; - this.currentBaseJumpExponent = NS6II.DEFAULT_LOOP_ROOT_SIZE; + this.currentBaseJumpExponent = engine.getSetting("defaultLoopRootSize"); const changeLoopSize = function(loopSize) { theContainer.currentBaseJumpExponent = _.clamp(loopSize, -5, 2); @@ -841,7 +831,7 @@ NS6II.PadModeContainers.BeatJump = function(channelOffset) { this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseJumpExponent - 1)); this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseJumpExponent + 1)); - changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); }; NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); @@ -849,7 +839,7 @@ NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); NS6II.PadModeContainers.LoopRoll = function(channelOffset) { NS6II.PadMode.call(this, channelOffset); const theContainer = this; - this.currentBaseLoopSize = NS6II.DEFAULT_LOOP_ROOT_SIZE; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); const changeLoopSize = function(loopSize) { // clamp loopSize to [-5;7] @@ -878,7 +868,7 @@ NS6II.PadModeContainers.LoopRoll = function(channelOffset) { this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); - changeLoopSize(NS6II.DEFAULT_LOOP_ROOT_SIZE); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); }; NS6II.PadModeContainers.LoopRoll.prototype = new NS6II.PadMode(); @@ -1287,7 +1277,7 @@ NS6II.BrowseSection = function() { group: "[Library]", inKey: "MoveVertical", shift: function() { - this.stepsize = NS6II.NAVIGATION_ENCODER_ACCELERATION; + this.stepsize = engine.getSetting("navEncoderAcceleration"); }, unshift: function() { this.stepsize = 1;