From ba6304a721451e53f6fbf5f11c9a1469824f2602 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 23 Jul 2020 22:13:25 -0400 Subject: [PATCH 01/84] Traktor S3: Initial commit Branched from Traktor S2 config Most simple buttons and lights mapped. --- res/controllers/Traktor Kontrol S3.hid.xml | 19 + .../Traktor-Kontrol-S3-hid-scripts.js | 1093 +++++++++++++++++ 2 files changed, 1112 insertions(+) create mode 100644 res/controllers/Traktor Kontrol S3.hid.xml create mode 100644 res/controllers/Traktor-Kontrol-S3-hid-scripts.js diff --git a/res/controllers/Traktor Kontrol S3.hid.xml b/res/controllers/Traktor Kontrol S3.hid.xml new file mode 100644 index 00000000000..cb0b0863485 --- /dev/null +++ b/res/controllers/Traktor Kontrol S3.hid.xml @@ -0,0 +1,19 @@ + + + + Traktor Kontrol S3 + Owen Williams + HID Mapping for Traktor Kontrol S3 + https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_s3 + + + + + + + + + + + + diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js new file mode 100644 index 00000000000..edbd35ec35c --- /dev/null +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -0,0 +1,1093 @@ +/////////////////////////////////////////////////////////////////////////////////// +// JSHint configuration // +/////////////////////////////////////////////////////////////////////////////////// +/* global engine */ +/* global script */ +/* global HIDDebug */ +/* global HIDPacket */ +/* global HIDController */ +/* jshint -W016 */ +/////////////////////////////////////////////////////////////////////////////////// +/* */ +/* Traktor Kontrol S3 HID controller script v1.00 */ +/* Last modification: July 2020 */ +/* Author: Owen Williams */ +/* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ +/* */ +/////////////////////////////////////////////////////////////////////////////////// + +var TraktorS3 = new function() { + this.controller = new HIDController(); + this.shiftPressed = {"[Channel1]": false, "[Channel2]": false}; + this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; + this.padModeState = {"[Channel1]": 0, "[Channel2]": 0}; // 0 = Hotcues Mode, 1 = Samples Mode + + // Knob encoder states (hold values between 0x0 and 0xF) + // Rotate to the right is +1 and to the left is means -1 + this.browseKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; + this.loopKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; + this.moveKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; + + // Microphone button + this.microphonePressedTimer = 0; // Timer to distinguish between short and long press + + // Sync buttons + this.syncPressedTimer = {"[Channel1]": 0, "[Channel2]": 0}; // Timer to distinguish between short and long press + + // Jog wheels + this.pitchBendMultiplier = 1.1; + this.lastTickVal = [0, 0]; + this.lastTickTime = [0.0, 0.0]; + this.wheelTouchInertiaTimer = { + "[Channel1]": 0, + "[Channel2]": 0, + "[Channel3]": 0, + "[Channel4]": 0 + }; + + // VuMeter + this.vuLeftConnection = {}; + this.vuRightConnection = {}; + this.clipLeftConnection = {}; + this.clipRightConnection = {}; + + // Sampler callbacks + this.samplerCallbacks = []; + this.samplerHotcuesRelation = { + "[Channel1]": { + 1: 1, 2: 2, 3: 3, 4: 4, 5: 9, 6: 10, 7: 11, 8: 12 + }, "[Channel2]": { + 1: 5, 2: 6, 3: 7, 4: 8, 5: 13, 6: 14, 7: 15, 8: 16 + } + }; +}; + +TraktorS3.init = function(id) { + TraktorS3.registerInputPackets(); + TraktorS3.registerOutputPackets(); + HIDDebug("TraktorS3: Init done!"); + + TraktorS3.debugLights(); +}; + +TraktorS3.registerInputPackets = function() { + var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); + var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); + + this.registerInputButton(messageShort, "[Channel1]", "!play", 0x03, 0x01, this.playHandler); + this.registerInputButton(messageShort, "[Channel2]", "!play", 0x06, 0x02, this.playHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!cue_default", 0x02, 0x80, this.cueHandler); + this.registerInputButton(messageShort, "[Channel2]", "!cue_default", 0x06, 0x01, this.cueHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!shift", 0x01, 0x01, this.shiftHandler); + this.registerInputButton(messageShort, "[Channel2]", "!shift", 0x04, 0x02, this.shiftHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!sync", 0x02, 0x08, this.syncHandler); + this.registerInputButton(messageShort, "[Channel2]", "!sync", 0x05, 0x10, this.syncHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!keylock", 0x02, 0x10, this.keylockHandler); + this.registerInputButton(messageShort, "[Channel2]", "!keylock", 0x05, 0x20, this.keylockHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!hotcues", 0x02, 0x20, this.padModeHandler); + this.registerInputButton(messageShort, "[Channel2]", "!hotcues", 0x05, 0x40, this.padModeHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!samples", 0x02, 0x40, this.padModeHandler); + this.registerInputButton(messageShort, "[Channel2]", "!samples", 0x05, 0x80, this.padModeHandler); + + // // Number pad buttons (Hotcues or Samplers depending on current mode) + this.registerInputButton(messageShort, "[Channel1]", "!pad_1", 0x03, 0x02, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_2", 0x03, 0x04, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_3", 0x03, 0x08, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_4", 0x03, 0x10, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_5", 0x03, 0x20, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_6", 0x03, 0x40, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_7", 0x03, 0x80, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel1]", "!pad_8", 0x04, 0x01, this.numberButtonHandler); + + this.registerInputButton(messageShort, "[Channel2]", "!pad_1", 0x06, 0x04, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_2", 0x06, 0x08, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_3", 0x06, 0x10, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_4", 0x06, 0x20, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_5", 0x06, 0x40, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_6", 0x06, 0x80, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_7", 0x07, 0x01, this.numberButtonHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pad_8", 0x07, 0x02, this.numberButtonHandler); + + // // Headphone buttons + this.registerInputButton(messageShort, "[Channel1]", "!pfl", 0x08, 0x01, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pfl", 0x08, 0x02, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel3]", "!pfl", 0x07, 0x80, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel4]", "!pfl", 0x08, 0x04, this.headphoneHandler); + + // // Track browsing + // TODO: bind touch: 0x09/0x40, 0x0A/0x02 + this.registerInputButton(messageShort, "[Channel1]", "!SelectTrack", 0x0B, 0x0F, this.selectTrackHandler); + this.registerInputButton(messageShort, "[Channel2]", "!SelectTrack", 0x0C, 0xF0, this.selectTrackHandler); + this.registerInputButton(messageShort, "[Channel1]", "!LoadSelectedTrack", 0x09, 0x01, this.loadTrackHandler); + this.registerInputButton(messageShort, "[Channel2]", "!LoadSelectedTrack", 0x09, 0x08, this.loadTrackHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!MaximizeLibrary", 0x01, 0x40, this.maximizeLibraryHandler); + this.registerInputButton(messageShort, "[Channel2]", "!MaximizeLibrary", 0x04, 0x80, this.maximizeLibraryHandler); + // this.registerInputButton(messageShort, "[Channel1]", "!AddTrack", 0x01, 0x04, this.addTrackHandler); + // this.registerInputButton(messageShort, "[Channel2]", "!AddTrack", 0x04, 0x10, this.addTrackHandler); + + // // Loop control + // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 + this.registerInputButton(messageShort, "[Channel1]", "!SelectLoop", 0x0C, 0x0F, this.selectLoopHandler); + this.registerInputButton(messageShort, "[Channel2]", "!SelectLoop", 0x0D, 0xF0, this.selectLoopHandler); + this.registerInputButton(messageShort, "[Channel1]", "!ActivateLoop", 0x09, 0x04, this.activateLoopHandler); + this.registerInputButton(messageShort, "[Channel2]", "!ActivateLoop", 0x09, 0x20, this.activateLoopHandler); + + // // Beatjump + // TODO: bind touch detections: 0x09/0x80, 0x0A/0x04 + this.registerInputButton(messageShort, "[Channel1]", "!SelectBeatjump", 0x0B, 0xF0, this.selectBeatjumpHandler); + this.registerInputButton(messageShort, "[Channel2]", "!SelectBeatjump", 0x0D, 0x0F, this.selectBeatjumpHandler); + this.registerInputButton(messageShort, "[Channel1]", "!ActivateBeatjump", 0x09, 0x02, this.activateBeatjumpHandler); + this.registerInputButton(messageShort, "[Channel2]", "!ActivateBeatjump", 0x09, 0x10, this.activateBeatjumpHandler); + + // // There is only one button on the controller, we use to toggle quantization for all channels + // this.registerInputButton(messageShort, "[ChannelX]", "!quantize", 0x06, 0x40, this.quantizeHandler); + + // // Microphone + // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); + + // // Jog wheels + this.registerInputButton(messageShort, "[Channel1]", "!jog_touch", 0x0A, 0x10, this.jogTouchHandler); + this.registerInputButton(messageShort, "[Channel2]", "!jog_touch", 0x0A, 0x20, this.jogTouchHandler); + this.registerInputJog(messageShort, "[Channel1]", "!jog", 0x0E, 0xFFFFFF, this.jogHandler); + this.registerInputJog(messageShort, "[Channel2]", "!jog", 0x12, 0xFFFFFF, this.jogHandler); + + // // FX Buttons + this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); + + // // Rev / FLUX / GRID + this.registerInputButton(messageShort, "[Channel1]", "!reverse", 0x01, 0x04, this.reverseHandler); + this.registerInputButton(messageShort, "[Channel2]", "!reverse", 0x04, 0x08, this.reverseHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!slip_enabled", 0x01, 0x02, this.fluxHandler); + this.registerInputButton(messageShort, "[Channel2]", "!slip_enabled", 0x04, 0x04, this.fluxHandler); + + this.registerInputButton(messageShort, "[Channel1]", "!grid", 0x01, 0x08, this.beatgridHandler); + this.registerInputButton(messageShort, "[Channel2]", "!grid", 0x05, 0x01, this.beatgridHandler); + + // // TODO: implement jog + // this.registerInputButton(messageShort, "[Channel1]", "!grid", 0x02, 0x01, this.jogHandler); + // this.registerInputButton(messageShort, "[Channel2]", "!grid", 0x05, 0x02, this.jpgHandler); + + // Unmapped: preview, star, list, encoder touches, jog + // DECK ASSIGNMENT + + this.controller.registerInputPacket(messageShort); + + this.registerInputScaler(messageLong, "[Channel1]", "rate", 0x01, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel2]", "rate", 0x0D, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel3]", "volume", 0x03, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel4]", "volume", 0x09, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[Channel1]", "pregain", 0x11, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel2]", "pregain", 0x13, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel3]", "pregain", 0x0F, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel4]", "pregain", 0x15, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter3", 0x25, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter2", 0x27, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter1", 0x29, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter3", 0x2B, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter2", 0x2D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter1", 0x2F, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter3", 0x1F, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter2", 0x21, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter1", 0x23, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter3", 0x31, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter2", 0x33, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel1]]", "super1", 0x39, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel2]]", "super1", 0x3B, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel3]]", "super1", 0x37, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel4]]", "super1", 0x3D, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); + // this.registerInputScaler(messageLong, "[Sampler]", "pregain", 0x17, 0xFFFF, this.samplerPregainHandler); + this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); + + this.controller.registerInputPacket(messageLong); + + // Soft takeover for all knobs + engine.softTakeover("[Channel1]", "rate", true); + engine.softTakeover("[Channel2]", "rate", true); + + engine.softTakeover("[Channel1]", "volume", true); + engine.softTakeover("[Channel2]", "volume", true); + + engine.softTakeover("[Channel1]", "pregain", true); + engine.softTakeover("[Channel2]", "pregain", true); + + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); + + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter1", true); + + engine.softTakeover("[QuickEffectRack1_[Channel1]]", "super1", true); + engine.softTakeover("[QuickEffectRack1_[Channel2]]", "super1", true); + + engine.softTakeover("[Master]", "crossfader", true); + engine.softTakeover("[Master]", "gain", true); + engine.softTakeover("[Master]", "headMix", true); + engine.softTakeover("[Master]", "headGain", true); + + for (var i = 1; i <= 16; ++i) { + engine.softTakeover("[Sampler" + i + "]", "pregain", true); + } + + // Dirty hack to set initial values in the packet parser + var data = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + TraktorS3.incomingData(data); +}; + +TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { + // Jog wheels have 4 byte input + message.addControl(group, name, offset, "I", bitmask); + message.setCallback(group, name, callback); +}; + +TraktorS3.registerInputScaler = function(message, group, name, offset, bitmask, callback) { + message.addControl(group, name, offset, "H", bitmask); + message.setCallback(group, name, callback); +}; + +TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, callback) { + message.addControl(group, name, offset, "B", bitmask); + message.setCallback(group, name, callback); +}; + +TraktorS3.playHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "start_stop", field.value); + } else if (field.value === 1) { + script.toggleControl(field.group, "play"); + } +}; + +TraktorS3.cueHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "cue_gotoandstop", field.value); + } else { + engine.setValue(field.group, "cue_default", field.value); + } +}; + +TraktorS3.shiftHandler = function(field) { + HIDDebug("SHIFT!" + field.group + " " + field.value); + TraktorS3.shiftPressed[field.group] = field.value; + engine.setValue("[Controls]", "touch_shift", field.value); + TraktorS3.outputHandler(field.value, field.group, "shift"); +}; + +TraktorS3.keylockHandler = function(field) { + if (field.value === 0) { + return; + } + + script.toggleControl(field.group, "keylock"); +}; + +TraktorS3.syncHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + // engine.setValue(field.group, "start_stop", field.value); + } else { + //TODO: latching not working? + engine.setValue(field.group, "sync_enabled", field.value); + } +}; + +TraktorS3.padModeHandler = function(field) { + if (field.value === 0) { + return; + } + + if (TraktorS3.padModeState[field.group] === 0 && field.name === "!samples") { + // If we are in hotcues mode and samples mode is activated + engine.setValue("[Samplers]", "show_samplers", 1); + TraktorS3.padModeState[field.group] = 1; + TraktorS3.outputHandler(0, field.group, "hotcues"); + TraktorS3.outputHandler(1, field.group, "samples"); + + // Light LEDs for all slots with loaded samplers + for (var key in TraktorS3.samplerHotcuesRelation[field.group]) { + if (TraktorS3.samplerHotcuesRelation[field.group].hasOwnProperty(key)) { + var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[field.group][key] + "]", "track_loaded"); + TraktorS3.outputHandler(loaded, field.group, "pad_" + key); + } + } + } else if (field.name === "!hotcues") { + // If we are in samples mode and hotcues mode is activated + TraktorS3.padModeState[field.group] = 0; + TraktorS3.outputHandler(1, field.group, "hotcues"); + TraktorS3.outputHandler(0, field.group, "samples"); + + // Light LEDs for all enabled hotcues + for (var i = 1; i <= 8; ++i) { + var active = engine.getValue(field.group, "hotcue_" + i + "_enabled"); + TraktorS3.outputHandler(active, field.group, "pad_" + i); + } + } +}; + +TraktorS3.numberButtonHandler = function(field) { + var padNumber = parseInt(field.id[field.id.length - 1]); + if (TraktorS3.padModeState[field.group] === 0) { + // Hotcues mode + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "hotcue_" + padNumber + "_clear", field.value); + } else { + engine.setValue(field.group, "hotcue_" + padNumber + "_activate", field.value); + } + } else { + // Samples mode + var sampler = TraktorS3.samplerHotcuesRelation[field.group][padNumber]; + if (TraktorS3.shiftPressed[field.group]) { + var playing = engine.getValue("[Sampler" + sampler + "]", "play"); + if (playing) { + engine.setValue("[Sampler" + sampler + "]", "cue_default", field.value); + } else { + engine.setValue("[Sampler" + sampler + "]", "eject", field.value); + } + } else { + var loaded = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); + if (loaded) { + engine.setValue("[Sampler" + sampler + "]", "cue_gotoandplay", field.value); + } else { + engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); + } + } + } +}; + +TraktorS3.headphoneHandler = function(field) { + if (field.value === 0) { + return; + } + + script.toggleControl(field.group, "pfl"); +}; + +TraktorS3.selectTrackHandler = function(field) { + var delta = 1; + if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[field.group]) { + delta = -1; + } + + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue("[Library]", "MoveHorizontal", delta); + } else { + engine.setValue("[Library]", "MoveVertical", delta); + } + + TraktorS3.browseKnobEncoderState[field.group] = field.value; +}; + +TraktorS3.loadTrackHandler = function(field) { + HIDDebug("LOAD TRACK!!!"); + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "eject", field.value); + } else { + engine.setValue(field.group, "LoadSelectedTrack", field.value); + } +}; + +TraktorS3.addTrackHandler = function(field) { + TraktorS3.outputHandler(field.value, field.group, "addTrack"); + + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue("[Library]", "AutoDjAddTop", field.value); + } else { + engine.setValue("[Library]", "AutoDjAddBottom", field.value); + } +}; + +TraktorS3.maximizeLibraryHandler = function(field) { + if (field.value === 0) { + return; + } + + script.toggleControl("[Master]", "maximize_library"); +}; + +TraktorS3.selectLoopHandler = function(field) { + if ((field.value + 1) % 16 === TraktorS3.loopKnobEncoderState[field.group]) { + script.triggerControl(field.group, "loop_halve"); + } else { + script.triggerControl(field.group, "loop_double"); + } + + TraktorS3.loopKnobEncoderState[field.group] = field.value; +}; + +TraktorS3.activateLoopHandler = function(field) { + if (field.value === 0) { + return; + } + + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "reloop_toggle", field.value); + } else { + engine.setValue(field.group, "beatloop_activate", field.value); + } +}; + +TraktorS3.selectBeatjumpHandler = function(field) { + var delta = 1; + if ((field.value + 1) % 16 === TraktorS3.moveKnobEncoderState[field.group]) { + delta = -1; + } + + if (TraktorS3.shiftPressed[field.group]) { + var beatjump_size = engine.getValue(field.group, "beatjump_size"); + if (delta > 0) { + engine.setValue(field.group, "beatjump_size", beatjump_size * 2); + } else { + engine.setValue(field.group, "beatjump_size", beatjump_size / 2); + } + } else { + if (delta < 0) { + script.triggerControl(field.group, "beatjump_backward"); + } else { + script.triggerControl(field.group, "beatjump_forward"); + } + } + + TraktorS3.moveKnobEncoderState[field.group] = field.value; +}; + +TraktorS3.activateBeatjumpHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "reloop_andstop", field.value); + } else { + engine.setValue(field.group, "beatlooproll_activate", field.value); + } +}; + +TraktorS3.quantizeHandler = function(field) { + if (field.value === 0) { + return; + } + + var res = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); + engine.setValue("[Channel1]", "quantize", res); + engine.setValue("[Channel2]", "quantize", res); + TraktorS3.outputHandler(res, field.group, "quantize"); +}; + +TraktorS3.microphoneHandler = function(field) { + if (field.value) { + if (TraktorS3.microphonePressedTimer === 0) { + // Start timer to measure how long button is pressed + TraktorS3.microphonePressedTimer = engine.beginTimer(300, function() { + // Reset microphone button timer status if active + if (TraktorS3.microphonePressedTimer !== 0) { + TraktorS3.microphonePressedTimer = 0; + } + }, true); + } + + script.toggleControl("[Microphone]", "talkover"); + } else { + // Button is released, check if timer is still running + if (TraktorS3.microphonePressedTimer !== 0) { + // short klick -> permanent activation + TraktorS3.microphonePressedTimer = 0; + } else { + engine.setValue("[Microphone]", "talkover", 0); + } + } +}; + +TraktorS3.parameterHandler = function(field) { + engine.setParameter(field.group, field.name, field.value / 4095); +}; + +TraktorS3.samplerPregainHandler = function(field) { + // Map sampler gain knob of all sampler together. + // Dirty hack, but the best we can do for now. + for (var i = 1; i <= 16; ++i) { + engine.setParameter("[Sampler" + i + "]", field.name, field.value / 4095); + } +}; + +TraktorS3.jogTouchHandler = function(field) { + if (TraktorS3.wheelTouchInertiaTimer[field.group] != 0) { + // The wheel was touched again, reset the timer. + engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[field.group]); + TraktorS3.wheelTouchInertiaTimer[field.group] = 0; + } + if (field.value !== 0) { + var deckNumber = TraktorS3.controller.resolveDeck(group); + engine.scratchEnable(deckNumber, 1024, 33.3333, 0.125, 0.125/8, true); + } else { + // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. + // Depending on how fast the platter was moving, lengthen the time we'll wait. + var scratchRate = Math.abs(engine.getValue(field.group, "scratch2")); + // Note: inertiaTime multiplier is controller-specific and should be factored out. + var inertiaTime = Math.pow(1.8, scratchRate) * 2; + if (inertiaTime < 100) { + // Just do it now. + TraktorS3.finishJogTouch(field.group); + } else { + TraktorS3.controller.wheelTouchInertiaTimer[field.group] = engine.beginTimer( + inertiaTime, "TraktorS3.finishJogTouch(\"" + field.group + "\")", true); + } + } +}; + +TraktorS3.jogHandler = function(field) { + var deltas = TraktorS3.wheelDeltas(field.group, field.value); + var tick_delta = deltas[0]; + var time_delta = deltas[1]; + + var velocity = TraktorS3.scalerJog(tick_delta, time_delta); + HIDDebug("VELO: " + velocity); + engine.setValue(field.group, "jog", velocity); + if (engine.getValue(field.group, "scratch2_enable")) { + var deckNumber = TraktorS3.controller.resolveDeck(group); + engine.scratchTick(deckNumber, tick_delta); + } +}; + +TraktorS3.scalerJog = function(tick_delta, time_delta) { + // If it's playing nudge + var multiplier = 1.0; + if (TraktorS3.shiftPressed["[Channel1]"] || TraktorS3.shiftPressed["[Channel2]"]) { + multiplier = 100.0; + } + if (engine.getValue(group, "play")) { + return multiplier * (tick_delta / time_delta) / 3; + } + + return (tick_delta / time_delta) * multiplier * 2.0; +}; + +TraktorS3.wheelDeltas = function(deckNumber, value) { + // When the wheel is touched, four bytes change, but only the first behaves predictably. + // It looks like the wheel is 1024 ticks per revolution. + var tickval = value & 0xFF; + var timeval = value >>> 16; + var prevTick = 0; + var prevTime = 0; + + // Group 1 and 2 -> Array index 0 and 1 + prevTick = this.lastTickVal[deckNumber - 1]; + prevTime = this.lastTickTime[deckNumber - 1]; + this.lastTickVal[deckNumber - 1] = tickval; + this.lastTickTime[deckNumber - 1] = timeval; + + if (prevTime > timeval) { + // We looped around. Adjust current time so that subtraction works. + timeval += 0x10000; + } + var timeDelta = timeval - prevTime; + if (timeDelta === 0) { + // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. + timeDelta = 1; + } + + var tickDelta = 0; + if (prevTick >= 200 && tickval <= 100) { + tickDelta = tickval + 256 - prevTick; + } else if (prevTick <= 100 && tickval >= 200) { + tickDelta = tickval - prevTick - 256; + } else { + tickDelta = tickval - prevTick; + } + + return [tickDelta, timeDelta]; +}; + +TraktorS3.finishJogTouch = function(group) { + TraktorS3.wheelTouchInertiaTimer[group] = 0; + var deckNumber = TraktorS3.controller.resolveDeck(group); + // No vinyl button (yet) + /*if (this.vinylActive) { + // Vinyl button still being pressed, don't disable scratch mode yet. + this.wheelTouchInertiaTimer[group] = engine.beginTimer( + 100, "VestaxVCI400.Decks." + this.deckIdentifier + ".finishJogTouch()", true); + return; + }*/ + var play = engine.getValue(group, "play"); + if (play != 0) { + // If we are playing, just hand off to the engine. + engine.scratchDisable(deckNumber, true); + } else { + // If things are paused, there will be a non-smooth handoff between scratching and jogging. + // Instead, keep scratch on until the platter is not moving. + var scratchRate = Math.abs(engine.getValue(group, "scratch2")); + if (scratchRate < 0.01) { + // The platter is basically stopped, now we can disable scratch and hand off to jogging. + engine.scratchDisable(deckNumber, false); + } else { + // Check again soon. + TraktorS3.wheelTouchInertiaTimer[group] = engine.beginTimer( + 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); + } + } +}; + +TraktorS3.fxHandler = function(field) { + if (field.value === 0) { + return; + } + + var fxNumber = parseInt(field.id[field.id.length - 1]); + var group = "[EffectRack1_EffectUnit" + fxNumber + "]"; + + // Toggle effect unit + TraktorS3.fxButtonState[fxNumber] = !TraktorS3.fxButtonState[fxNumber]; + + engine.setValue(group, "group_[Channel1]_enable", TraktorS3.fxButtonState[fxNumber]); + engine.setValue(group, "group_[Channel2]_enable", TraktorS3.fxButtonState[fxNumber]); + TraktorS3.outputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "fxButton" + fxNumber); +}; + +TraktorS3.reverseHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "reverseroll", field.value); + } else { + engine.setValue(field.group, "reverse", field.value); + } + + TraktorS3.outputHandler(field.value, field.group, "reverse"); +}; + +TraktorS3.fluxHandler = function(field) { + if (field.value === 0) { + return; + } + + script.toggleControl(field.group, "slip_enabled"); +}; + +TraktorS3.beatgridHandler = function(field) { + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue(field.group, "beats_translate_match_alignment", field.value); + } else { + engine.setValue(field.group, "beats_translate_curpos", field.value); + } + + TraktorS3.outputHandler(field.value, field.group, "grid"); +}; + +TraktorS3.debugLights = function() { + // Call this if you want to just send raw packets to the controller (good for figuring out what + // bytes do what). + //var data_strings = ["80 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 0A 0A 0A 0A 0A 0A 0A 0A 00 7F 00 00 00 00 0A 0A 0A 0A 0A 0A", + // "81 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 7F 0A 0A 0A 0A 0A 7F 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A", + // "82 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 00 00 7F 7F 7F 7F 7F 7F 00 00 7F 7F 7F 7F 7F 00 00 00 7F 7F 7F 7F 7F 7F 00 00 7F 7F 7F 7F 7F 00 00 00"]; + // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + var data_strings = [ + " FF 00 00 FF FF FF 00 00 00 00 FF FF FF 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 FF 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "FF ", + " FF 00 00 00 00 00 00 00 00 00 00 00 00 FF " + + "FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF " + + "FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 00 " + + "FF 00 00 00 FF 00 00 FF 00 00 00 00 FF 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + ]; + var data = [Object(), Object()]; + + for (i = 0; i < data.length; i++) { + var ok = true; + var splitted = data_strings[i].split(/\s+/); + HIDDebug("i " + i + " " + splitted); + data[i].length = splitted.length; + for (j = 0; j < splitted.length; j++) { + var byte_str = splitted[j]; + if (byte_str.length === 0) { + continue; + } + if (byte_str.length !== 2) { + ok = false; + HIDDebug("not two characters?? " + byte_str); + } + var b = parseInt(byte_str, 16); + if (b < 0 || b > 255) { + ok = false; + HIDDebug("number out of range: " + byte_str + " " + b); + } + data[i][j] = b; + } + if (ok) { + controller.send(data[i], data[i].length, 0x80 + i); + } + } +}; + +TraktorS3.registerOutputPackets = function() { + var outputA = new HIDPacket("outputA", 0x80); + + outputA.addOutput("[Channel1]", "shift", 0x01, "B"); + outputA.addOutput("[Channel2]", "shift", 0x1A, "B"); + + outputA.addOutput("[Channel1]", "slip_enabled", 0x02, "B"); + outputA.addOutput("[Channel2]", "slip_enabled", 0x1B, "B"); + + outputA.addOutput("[Channel1]", "reverse", 0x03, "B"); + outputA.addOutput("[Channel2]", "reverse", 0x1C, "B"); + + outputA.addOutput("[Channel1]", "keylock", 0x0D, "B"); + outputA.addOutput("[Channel2]", "keylock", 0x26, "B"); + + outputA.addOutput("[Channel1]", "hotcues", 0x0E, "B"); + outputA.addOutput("[Channel2]", "hotcues", 0x27, "B"); + + outputA.addOutput("[Channel1]", "samples", 0x0F, "B"); + outputA.addOutput("[Channel2]", "samples", 0x28, "B"); + + outputA.addOutput("[Channel1]", "cue_indicator", 0x10, "B"); + outputA.addOutput("[Channel2]", "cue_indicator", 0x29, "B"); + + outputA.addOutput("[Channel1]", "play_indicator", 0x11, "B"); + outputA.addOutput("[Channel2]", "play_indicator", 0x2A, "B"); + + outputA.addOutput("[Channel1]", "sync_enabled", 0x0C, "B"); + outputA.addOutput("[Channel2]", "sync_enabled", 0x25, "B"); + + outputA.addOutput("[Channel1]", "pad_1", 0x12, "B"); + outputA.addOutput("[Channel1]", "pad_2", 0x13, "B"); + outputA.addOutput("[Channel1]", "pad_3", 0x14, "B"); + outputA.addOutput("[Channel1]", "pad_4", 0x15, "B"); + outputA.addOutput("[Channel1]", "pad_5", 0x16, "B"); + outputA.addOutput("[Channel1]", "pad_6", 0x17, "B"); + outputA.addOutput("[Channel1]", "pad_7", 0x18, "B"); + outputA.addOutput("[Channel1]", "pad_8", 0x19, "B"); + + outputA.addOutput("[Channel2]", "pad_1", 0x2B, "B"); + outputA.addOutput("[Channel2]", "pad_2", 0x2C, "B"); + outputA.addOutput("[Channel2]", "pad_3", 0x2D, "B"); + outputA.addOutput("[Channel2]", "pad_4", 0x2E, "B"); + outputA.addOutput("[Channel2]", "pad_5", 0x2F, "B"); + outputA.addOutput("[Channel2]", "pad_6", 0x30, "B"); + outputA.addOutput("[Channel2]", "pad_7", 0x31, "B"); + outputA.addOutput("[Channel2]", "pad_8", 0x32, "B"); + + outputA.addOutput("[Channel1]", "pfl", 0x39, "B"); + outputA.addOutput("[Channel2]", "pfl", 0x3A, "B"); + outputA.addOutput("[Channel3]", "pfl", 0x38, "B"); + outputA.addOutput("[Channel4]", "pfl", 0x3B, "B"); + + // outputA.addOutput("[Channel1]", "addTrack", 0x03, "B"); + // outputA.addOutput("[Channel2]", "addTrack", 0x2A, "B"); + + outputA.addOutput("[Channel1]", "grid", 0x08, "B"); + outputA.addOutput("[Channel2]", "grid", 0x20, "B"); + + outputA.addOutput("[Channel1]", "MaximizeLibrary", 0x07, "B"); + outputA.addOutput("[Channel2]", "MaximizeLibrary", 0x21, "B"); + + // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); + // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); + + this.controller.registerOutputPacket(outputA); + + var outputB = new HIDPacket("outputB", 0x81); + + var VuOffsets = { + "[Channel3]": 0x01, + "[Channel1]": 0x10, + "[Channel2]": 0x1F, + "[Channel4]": 0x2E + }; + for (ch in VuOffsets) { + for (i = 0; i < 14; i++) { + outputB.addOutput(ch, "!" + "VuMeter" + i, VuOffsets[ch] + i, "B"); + } + } + + outputB.addOutput("[Channel3]", "PeakIndicator", 0x09, "B"); + outputB.addOutput("[Channel1]", "PeakIndicator", 0x1E, "B"); + outputB.addOutput("[Channel2]", "PeakIndicator", 0x2D, "B"); + outputB.addOutput("[Channel4]", "PeakIndicator", 0x3C, "B"); + + outputB.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); + outputB.addOutput("[ChannelX]", "fxButton2", 0x3D, "B"); + outputB.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); + outputB.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); + + this.controller.registerOutputPacket(outputB); + + this.linkOutput("[Channel1]", "play_indicator", this.outputHandler); + this.linkOutput("[Channel2]", "play_indicator", this.outputHandler); + + this.linkOutput("[Channel1]", "cue_indicator", this.outputHandler); + this.linkOutput("[Channel2]", "cue_indicator", this.outputHandler); + + this.linkOutput("[Channel1]", "sync_enabled", this.outputHandler); + this.linkOutput("[Channel2]", "sync_enabled", this.outputHandler); + + this.linkOutput("[Channel1]", "keylock", this.outputHandler); + this.linkOutput("[Channel2]", "keylock", this.outputHandler); + + for (var i = 1; i <= 8; ++i) { + TraktorS3.controller.linkOutput("[Channel1]", "pad_" + i, "[Channel1]", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + TraktorS3.controller.linkOutput("[Channel2]", "pad_" + i, "[Channel2]", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + } + + this.linkOutput("[Channel1]", "pfl", this.outputHandler); + this.linkOutput("[Channel2]", "pfl", this.outputHandler); + + this.linkOutput("[Channel1]", "slip_enabled", this.outputHandler); + this.linkOutput("[Channel2]", "slip_enabled", this.outputHandler); + + this.linkOutput("[Microphone]", "talkover", this.outputHandler); + + // VuMeter + this.vuLeftConnection = engine.makeConnection("[Channel1]", "VuMeter", this.vuMeterHandler); + this.vuRightConnection = engine.makeConnection("[Channel2]", "VuMeter", this.vuMeterHandler); + this.clipLeftConnection = engine.makeConnection("[Channel1]", "PeakIndicator", this.peakOutputHandler); + this.clipRightConnection = engine.makeConnection("[Channel2]", "PeakIndicator", this.peakOutputHandler); + + // Sampler callbacks + for (i = 1; i <= 16; ++i) { + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", this.samplesOutputHandler)); + } + + TraktorS3.lightDeck(false); +}; + +/* Helper function to link output in a short form */ +TraktorS3.linkOutput = function(group, name, callback) { + TraktorS3.controller.linkOutput(group, name, group, name, callback); +}; + +// TraktorS3.vuMeterHandler = function (value, group, key) { +// var vuKeys = Object.keys(TraktorS3.vuMeterThresholds); +// for (var i = 0; i < vuKeys.length; ++i) { +// // Avoid spamming HID by only sending last LED update +// var last = (i === (vuKeys.length - 1)); +// if (TraktorS3.vuMeterThresholds[vuKeys[i]] > value) { +// TraktorS3.controller.setOutput(group, vuKeys[i], 0x00, last); +// } else { +// TraktorS3.controller.setOutput(group, vuKeys[i], 0x7E, last); +// } +// } +// }; + +TraktorS3.vuMeterHandler = function(value, group, key) { + // This handler is called a lot so it should be as fast as possible. + + // VU is drawn on 14 segments, the 15th indicates clip. + // Figure out number of fully-illuminated segments. + var scaledValue = value * 14.0; + var fullIllumCount = Math.floor(scaledValue); + + // Figure out how much the partially-illuminated segment is illuminated. + var partialIllum = (scaledValue - fullIllumCount) * 0x7F; + + for (i = 0; i < 14; i++) { + var key = "!" + "VuMeter" + i; + if (i < fullIllumCount) { + // Don't update lights until they're all done, so the last term is false. + TraktorS3.controller.setOutput(group, key, 0x7F, false); + } else if (i == fullIllumCount) { + TraktorS3.controller.setOutput(group, key, partialIllum, false); + } else { + TraktorS3.controller.setOutput(group, key, 0x00, false); + } + } + TraktorS3.controller.OutputPackets["outputB"].send(); +}; + +TraktorS3.peakOutputHandler = function(value, group, key) { + var ledValue = 0x00; + if (value) { + ledValue = 0x7E; + } + + TraktorS3.controller.setOutput(group, key, ledValue, true); +}; + +TraktorS3.outputHandler = function(value, group, key) { + // Custom value for multi-colored LEDs + var ledValue = value; + if (value === 0 || value === false) { + // Off value + ledValue = 0x7C; + } else if (value === 1 || value === true) { + // On value + ledValue = 0x7E; + } + + TraktorS3.controller.setOutput(group, key, ledValue, true); +}; + +TraktorS3.hotcueOutputHandler = function(value, group, key) { + // Light button LED only when we are in hotcue mode + if (TraktorS3.padModeState[group] === 0) { + TraktorS3.outputHandler(value, group, key); + } +}; + +TraktorS3.samplesOutputHandler = function(value, group, key) { + // Sampler 1-4, 9-12 -> Channel1 + // Samples 5-8, 13-16 -> Channel2 + var sampler = TraktorS3.resolveSampler(group); + var deck = "[Channel1]"; + var num = sampler; + if (sampler === undefined) { + return; + } else if (sampler > 4 && sampler < 9) { + deck = "[Channel2]"; + num = sampler - 4; + } else if (sampler > 8 && sampler < 13) { + num = sampler - 4; + } else if (sampler > 12 && sampler < 17) { + deck = "[Channel2]"; + num = sampler - 8; + } + + // If we are in samples modes light corresponding LED + if (TraktorS3.padModeState[deck] === 1) { + if (key === "play" && engine.getValue(group, "track_loaded")) { + if (value) { + // Green light on play + TraktorS3.outputHandler(0x9E, deck, "pad_" + num); + } else { + // Reset LED to full white light + TraktorS3.outputHandler(1, deck, "pad_" + num); + } + } else if (key === "track_loaded") { + TraktorS3.outputHandler(value, deck, "pad_" + num); + } + } +}; + +TraktorS3.resolveSampler = function(group) { + if (group === undefined) { + return undefined; + } + + var result = group.match(script.samplerRegEx); + + if (result === null) { + return undefined; + } + + // Return sample number + return result[1]; +}; + +TraktorS3.lightDeck = function(switchOff) { + var softLight = 0x7C; + var fullLight = 0x7E; + if (switchOff) { + softLight = 0x00; + fullLight = 0x00; + } + + var current = (engine.getValue("[Channel1]", "play_indicator") ? fullLight : softLight); + TraktorS3.controller.setOutput("[Channel1]", "play_indicator", current, false); + current = (engine.getValue("[Channel2]", "play_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "play_indicator", current, false); + + current = (engine.getValue("[Channel1]", "cue_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "cue_indicator", current, false); + current = (engine.getValue("[Channel2]", "cue_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "cue_indicator", current, false); + + TraktorS3.controller.setOutput("[Channel1]", "shift", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "shift", softLight, false); + + current = (engine.getValue("[Channel1]", "sync_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "sync_enabled", current, false); + current = (engine.getValue("[Channel1]", "sync_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "sync_enabled", current, false); + + // Hotcues mode is default start value + TraktorS3.controller.setOutput("[Channel1]", "hotcues", fullLight, false); + TraktorS3.controller.setOutput("[Channel2]", "hotcues", fullLight, false); + + TraktorS3.controller.setOutput("[Channel1]", "samples", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "samples", softLight, false); + + current = (engine.getValue("[Channel1]", "keylock")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "keylock", current, false); + current = (engine.getValue("[Channel2]", "keylock")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "keylock", current, false); + + for (var i = 1; i <= 8; ++i) { + current = (engine.getValue("[Channel1]", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "pad_" + i, current, false); + current = (engine.getValue("[Channel2]", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "pad_" + i, current, false); + } + + current = (engine.getValue("[Channel1]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "pfl", current, false); + current = (engine.getValue("[Channel2]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "pfl", current, false); + + TraktorS3.controller.setOutput("[ChannelX]", "fxButton1", softLight, false); + TraktorS3.controller.setOutput("[ChannelX]", "fxButton2", softLight, false); + TraktorS3.controller.setOutput("[ChannelX]", "fxButton3", softLight, false); + TraktorS3.controller.setOutput("[ChannelX]", "fxButton4", softLight, false); + + TraktorS3.controller.setOutput("[Channel1]", "reverse", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "reverse", softLight, false); + + current = (engine.getValue("[Channel1]", "slip_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "slip_enabled", current, false); + current = (engine.getValue("[Channel2]", "slip_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "slip_enabled", current, false); + + TraktorS3.controller.setOutput("[Channel1]", "addTrack", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "addTrack", softLight, false); + + TraktorS3.controller.setOutput("[Channel1]", "grid", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "grid", softLight, false); + + TraktorS3.controller.setOutput("[Channel1]", "MaximizeLibrary", softLight, false); + TraktorS3.controller.setOutput("[Channel2]", "MaximizeLibrary", softLight, false); + + TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); + + // For the last output we should send the packet finally + current = (engine.getValue("[Microphone]", "talkover")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Microphone]", "talkover", current, true); +}; + +TraktorS3.messageCallback = function(packet, data) { + for (var name in data) { + if (data.hasOwnProperty(name)) { + TraktorS3.controller.processButton(data[name]); + } + } +}; + +TraktorS3.shutdown = function() { + // Deactivate all LEDs + TraktorS3.lightDeck(true); + + HIDDebug("TraktorS3: Shutdown done!"); +}; + +TraktorS3.incomingData = function(data, length) { + TraktorS3.controller.parsePacket(data, length); +}; From df87b97a6f7520dc59ef0152e8c10757f1f30ddb Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 26 Jul 2020 15:37:36 -0400 Subject: [PATCH 02/84] Traktor S3: move from channel-specific to deck aliases (does not work) --- .../Traktor-Kontrol-S3-hid-scripts.js | 431 ++--- res/controllers/common-hid-packet-parser.js | 1410 +++++++++-------- 2 files changed, 974 insertions(+), 867 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index edbd35ec35c..c1dedd4a63e 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -18,21 +18,21 @@ var TraktorS3 = new function() { this.controller = new HIDController(); - this.shiftPressed = {"[Channel1]": false, "[Channel2]": false}; + this.shiftPressed = {"deck1": false, "deck2": false}; this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; - this.padModeState = {"[Channel1]": 0, "[Channel2]": 0}; // 0 = Hotcues Mode, 1 = Samples Mode + this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode // Knob encoder states (hold values between 0x0 and 0xF) // Rotate to the right is +1 and to the left is means -1 - this.browseKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; - this.loopKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; - this.moveKnobEncoderState = {"[Channel1]": 0, "[Channel2]": 0}; + this.browseKnobEncoderState = {"deck1": 0, "deck2": 0}; + this.loopKnobEncoderState = {"deck1": 0, "deck2": 0}; + this.moveKnobEncoderState = {"deck1": 0, "deck2": 0}; // Microphone button this.microphonePressedTimer = 0; // Timer to distinguish between short and long press // Sync buttons - this.syncPressedTimer = {"[Channel1]": 0, "[Channel2]": 0}; // Timer to distinguish between short and long press + this.syncPressedTimer = {"deck1": 0, "deck2": 0}; // Timer to distinguish between short and long press // Jog wheels this.pitchBendMultiplier = 1.1; @@ -46,73 +46,95 @@ var TraktorS3 = new function() { }; // VuMeter - this.vuLeftConnection = {}; - this.vuRightConnection = {}; - this.clipLeftConnection = {}; - this.clipRightConnection = {}; + this.vuConnections = { + "[Channel1]": {}, + "[Channel2]": {}, + "[Channel3]": {}, + "[Channel4]": {} + }; + this.clipConnections = { + "[Channel1]": {}, + "[Channel2]": {}, + "[Channel3]": {}, + "[Channel4]": {} + }; // Sampler callbacks this.samplerCallbacks = []; this.samplerHotcuesRelation = { - "[Channel1]": { + "deck1": { 1: 1, 2: 2, 3: 3, 4: 4, 5: 9, 6: 10, 7: 11, 8: 12 - }, "[Channel2]": { + }, "deck2": { 1: 5, 2: 6, 3: 7, 4: 8, 5: 13, 6: 14, 7: 15, 8: 16 } }; + + this.controller.switchDeck(1); + this.controller.switchDeck(2); }; -TraktorS3.init = function(id) { +TraktorS3.init = function(_id) { TraktorS3.registerInputPackets(); TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - TraktorS3.debugLights(); + //TraktorS3.debugLights(); }; TraktorS3.registerInputPackets = function() { - var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); - var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); + var messageShort = new HIDPacket(TraktorS3.controller, "shortmessage", 0x01, this.messageCallback); + var messageLong = new HIDPacket(TraktorS3.controller, "longmessage", 0x02, this.messageCallback); + + // Establish all of the groups + messageShort.registerGroup("[Channel1]"); + messageShort.registerGroup("[Channel2]"); + messageShort.registerGroup("[Channel3]"); + messageShort.registerGroup("[Channel4]"); + + this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel1]", "!play", 0x03, 0x01, this.playHandler); - this.registerInputButton(messageShort, "[Channel2]", "!play", 0x06, 0x02, this.playHandler); + this.registerInputButton(messageShort, "deck1", "!play", 0x03, 0x01, this.playHandler); + this.registerInputButton(messageShort, "deck2", "!play", 0x06, 0x02, this.playHandler); - this.registerInputButton(messageShort, "[Channel1]", "!cue_default", 0x02, 0x80, this.cueHandler); - this.registerInputButton(messageShort, "[Channel2]", "!cue_default", 0x06, 0x01, this.cueHandler); + this.registerInputButton(messageShort, "deck1", "!cue_default", 0x02, 0x80, this.cueHandler); + this.registerInputButton(messageShort, "deck2", "!cue_default", 0x06, 0x01, this.cueHandler); - this.registerInputButton(messageShort, "[Channel1]", "!shift", 0x01, 0x01, this.shiftHandler); - this.registerInputButton(messageShort, "[Channel2]", "!shift", 0x04, 0x02, this.shiftHandler); + this.registerInputButton(messageShort, "deck1", "!shift", 0x01, 0x01, this.shiftHandler); + this.registerInputButton(messageShort, "deck2", "!shift", 0x04, 0x02, this.shiftHandler); - this.registerInputButton(messageShort, "[Channel1]", "!sync", 0x02, 0x08, this.syncHandler); - this.registerInputButton(messageShort, "[Channel2]", "!sync", 0x05, 0x10, this.syncHandler); + this.registerInputButton(messageShort, "deck1", "!sync", 0x02, 0x08, this.syncHandler); + this.registerInputButton(messageShort, "deck2", "!sync", 0x05, 0x10, this.syncHandler); - this.registerInputButton(messageShort, "[Channel1]", "!keylock", 0x02, 0x10, this.keylockHandler); - this.registerInputButton(messageShort, "[Channel2]", "!keylock", 0x05, 0x20, this.keylockHandler); + this.registerInputButton(messageShort, "deck1", "!keylock", 0x02, 0x10, this.keylockHandler); + this.registerInputButton(messageShort, "deck2", "!keylock", 0x05, 0x20, this.keylockHandler); - this.registerInputButton(messageShort, "[Channel1]", "!hotcues", 0x02, 0x20, this.padModeHandler); - this.registerInputButton(messageShort, "[Channel2]", "!hotcues", 0x05, 0x40, this.padModeHandler); + this.registerInputButton(messageShort, "deck1", "!hotcues", 0x02, 0x20, this.padModeHandler); + this.registerInputButton(messageShort, "deck2", "!hotcues", 0x05, 0x40, this.padModeHandler); - this.registerInputButton(messageShort, "[Channel1]", "!samples", 0x02, 0x40, this.padModeHandler); - this.registerInputButton(messageShort, "[Channel2]", "!samples", 0x05, 0x80, this.padModeHandler); + this.registerInputButton(messageShort, "deck1", "!samples", 0x02, 0x40, this.padModeHandler); + this.registerInputButton(messageShort, "deck2", "!samples", 0x05, 0x80, this.padModeHandler); // // Number pad buttons (Hotcues or Samplers depending on current mode) - this.registerInputButton(messageShort, "[Channel1]", "!pad_1", 0x03, 0x02, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_2", 0x03, 0x04, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_3", 0x03, 0x08, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_4", 0x03, 0x10, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_5", 0x03, 0x20, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_6", 0x03, 0x40, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_7", 0x03, 0x80, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel1]", "!pad_8", 0x04, 0x01, this.numberButtonHandler); - - this.registerInputButton(messageShort, "[Channel2]", "!pad_1", 0x06, 0x04, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_2", 0x06, 0x08, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_3", 0x06, 0x10, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_4", 0x06, 0x20, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_5", 0x06, 0x40, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_6", 0x06, 0x80, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_7", 0x07, 0x01, this.numberButtonHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pad_8", 0x07, 0x02, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_1", 0x03, 0x02, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_2", 0x03, 0x04, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_3", 0x03, 0x08, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_4", 0x03, 0x10, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_5", 0x03, 0x20, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_6", 0x03, 0x40, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_7", 0x03, 0x80, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck1", "!pad_8", 0x04, 0x01, this.numberButtonHandler); + + this.registerInputButton(messageShort, "deck2", "!pad_1", 0x06, 0x04, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_2", 0x06, 0x08, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_3", 0x06, 0x10, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_4", 0x06, 0x20, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_5", 0x06, 0x40, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_6", 0x06, 0x80, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_7", 0x07, 0x01, this.numberButtonHandler); + this.registerInputButton(messageShort, "deck2", "!pad_8", 0x07, 0x02, this.numberButtonHandler); // // Headphone buttons this.registerInputButton(messageShort, "[Channel1]", "!pfl", 0x08, 0x01, this.headphoneHandler); @@ -122,29 +144,29 @@ TraktorS3.registerInputPackets = function() { // // Track browsing // TODO: bind touch: 0x09/0x40, 0x0A/0x02 - this.registerInputButton(messageShort, "[Channel1]", "!SelectTrack", 0x0B, 0x0F, this.selectTrackHandler); - this.registerInputButton(messageShort, "[Channel2]", "!SelectTrack", 0x0C, 0xF0, this.selectTrackHandler); - this.registerInputButton(messageShort, "[Channel1]", "!LoadSelectedTrack", 0x09, 0x01, this.loadTrackHandler); - this.registerInputButton(messageShort, "[Channel2]", "!LoadSelectedTrack", 0x09, 0x08, this.loadTrackHandler); + this.registerInputButton(messageShort, "deck1", "!SelectTrack", 0x0B, 0x0F, this.selectTrackHandler); + this.registerInputButton(messageShort, "deck2", "!SelectTrack", 0x0C, 0xF0, this.selectTrackHandler); + this.registerInputButton(messageShort, "deck1", "!LoadSelectedTrack", 0x09, 0x01, this.loadTrackHandler); + this.registerInputButton(messageShort, "deck2", "!LoadSelectedTrack", 0x09, 0x08, this.loadTrackHandler); - this.registerInputButton(messageShort, "[Channel1]", "!MaximizeLibrary", 0x01, 0x40, this.maximizeLibraryHandler); - this.registerInputButton(messageShort, "[Channel2]", "!MaximizeLibrary", 0x04, 0x80, this.maximizeLibraryHandler); - // this.registerInputButton(messageShort, "[Channel1]", "!AddTrack", 0x01, 0x04, this.addTrackHandler); - // this.registerInputButton(messageShort, "[Channel2]", "!AddTrack", 0x04, 0x10, this.addTrackHandler); + this.registerInputButton(messageShort, "deck1", "!MaximizeLibrary", 0x01, 0x40, this.maximizeLibraryHandler); + this.registerInputButton(messageShort, "deck2", "!MaximizeLibrary", 0x04, 0x80, this.maximizeLibraryHandler); + // this.registerInputButton(messageShort, "deck1", "!AddTrack", 0x01, 0x04, this.addTrackHandler); + // this.registerInputButton(messageShort, "deck2", "!AddTrack", 0x04, 0x10, this.addTrackHandler); // // Loop control // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 - this.registerInputButton(messageShort, "[Channel1]", "!SelectLoop", 0x0C, 0x0F, this.selectLoopHandler); - this.registerInputButton(messageShort, "[Channel2]", "!SelectLoop", 0x0D, 0xF0, this.selectLoopHandler); - this.registerInputButton(messageShort, "[Channel1]", "!ActivateLoop", 0x09, 0x04, this.activateLoopHandler); - this.registerInputButton(messageShort, "[Channel2]", "!ActivateLoop", 0x09, 0x20, this.activateLoopHandler); + this.registerInputButton(messageShort, "deck1", "!SelectLoop", 0x0C, 0x0F, this.selectLoopHandler); + this.registerInputButton(messageShort, "deck2", "!SelectLoop", 0x0D, 0xF0, this.selectLoopHandler); + this.registerInputButton(messageShort, "deck1", "!ActivateLoop", 0x09, 0x04, this.activateLoopHandler); + this.registerInputButton(messageShort, "deck2", "!ActivateLoop", 0x09, 0x20, this.activateLoopHandler); // // Beatjump // TODO: bind touch detections: 0x09/0x80, 0x0A/0x04 - this.registerInputButton(messageShort, "[Channel1]", "!SelectBeatjump", 0x0B, 0xF0, this.selectBeatjumpHandler); - this.registerInputButton(messageShort, "[Channel2]", "!SelectBeatjump", 0x0D, 0x0F, this.selectBeatjumpHandler); - this.registerInputButton(messageShort, "[Channel1]", "!ActivateBeatjump", 0x09, 0x02, this.activateBeatjumpHandler); - this.registerInputButton(messageShort, "[Channel2]", "!ActivateBeatjump", 0x09, 0x10, this.activateBeatjumpHandler); + this.registerInputButton(messageShort, "deck1", "!SelectBeatjump", 0x0B, 0xF0, this.selectBeatjumpHandler); + this.registerInputButton(messageShort, "deck2", "!SelectBeatjump", 0x0D, 0x0F, this.selectBeatjumpHandler); + this.registerInputButton(messageShort, "deck1", "!ActivateBeatjump", 0x09, 0x02, this.activateBeatjumpHandler); + this.registerInputButton(messageShort, "deck2", "!ActivateBeatjump", 0x09, 0x10, this.activateBeatjumpHandler); // // There is only one button on the controller, we use to toggle quantization for all channels // this.registerInputButton(messageShort, "[ChannelX]", "!quantize", 0x06, 0x40, this.quantizeHandler); @@ -153,10 +175,10 @@ TraktorS3.registerInputPackets = function() { // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); // // Jog wheels - this.registerInputButton(messageShort, "[Channel1]", "!jog_touch", 0x0A, 0x10, this.jogTouchHandler); - this.registerInputButton(messageShort, "[Channel2]", "!jog_touch", 0x0A, 0x20, this.jogTouchHandler); - this.registerInputJog(messageShort, "[Channel1]", "!jog", 0x0E, 0xFFFFFF, this.jogHandler); - this.registerInputJog(messageShort, "[Channel2]", "!jog", 0x12, 0xFFFFFF, this.jogHandler); + this.registerInputButton(messageShort, "deck1", "!jog_touch", 0x0A, 0x10, this.jogTouchHandler); + this.registerInputButton(messageShort, "deck2", "!jog_touch", 0x0A, 0x20, this.jogTouchHandler); + this.registerInputJog(messageShort, "deck1", "!jog", 0x0E, 0xFFFFFF, this.jogHandler); + this.registerInputJog(messageShort, "deck2", "!jog", 0x12, 0xFFFFFF, this.jogHandler); // // FX Buttons this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); @@ -165,26 +187,26 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); // // Rev / FLUX / GRID - this.registerInputButton(messageShort, "[Channel1]", "!reverse", 0x01, 0x04, this.reverseHandler); - this.registerInputButton(messageShort, "[Channel2]", "!reverse", 0x04, 0x08, this.reverseHandler); + this.registerInputButton(messageShort, "deck1", "!reverse", 0x01, 0x04, this.reverseHandler); + this.registerInputButton(messageShort, "deck2", "!reverse", 0x04, 0x08, this.reverseHandler); - this.registerInputButton(messageShort, "[Channel1]", "!slip_enabled", 0x01, 0x02, this.fluxHandler); - this.registerInputButton(messageShort, "[Channel2]", "!slip_enabled", 0x04, 0x04, this.fluxHandler); + this.registerInputButton(messageShort, "deck1", "!slip_enabled", 0x01, 0x02, this.fluxHandler); + this.registerInputButton(messageShort, "deck2", "!slip_enabled", 0x04, 0x04, this.fluxHandler); - this.registerInputButton(messageShort, "[Channel1]", "!grid", 0x01, 0x08, this.beatgridHandler); - this.registerInputButton(messageShort, "[Channel2]", "!grid", 0x05, 0x01, this.beatgridHandler); + this.registerInputButton(messageShort, "deck1", "!grid", 0x01, 0x08, this.beatgridHandler); + this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x01, this.beatgridHandler); // // TODO: implement jog - // this.registerInputButton(messageShort, "[Channel1]", "!grid", 0x02, 0x01, this.jogHandler); - // this.registerInputButton(messageShort, "[Channel2]", "!grid", 0x05, 0x02, this.jpgHandler); + // this.registerInputButton(messageShort, "deck1", "!grid", 0x02, 0x01, this.jogHandler); + // this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x02, this.jpgHandler); // Unmapped: preview, star, list, encoder touches, jog // DECK ASSIGNMENT this.controller.registerInputPacket(messageShort); - this.registerInputScaler(messageLong, "[Channel1]", "rate", 0x01, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel2]", "rate", 0x0D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "deck1", "rate", 0x01, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "deck2", "rate", 0x0D, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); @@ -226,25 +248,32 @@ TraktorS3.registerInputPackets = function() { this.controller.registerInputPacket(messageLong); // Soft takeover for all knobs - engine.softTakeover("[Channel1]", "rate", true); - engine.softTakeover("[Channel2]", "rate", true); + engine.softTakeover("deck1", "rate", true); + engine.softTakeover("deck2", "rate", true); - engine.softTakeover("[Channel1]", "volume", true); - engine.softTakeover("[Channel2]", "volume", true); + engine.softTakeover("deck1", "volume", true); + engine.softTakeover("deck2", "volume", true); - engine.softTakeover("[Channel1]", "pregain", true); - engine.softTakeover("[Channel2]", "pregain", true); + engine.softTakeover("deck1", "pregain", true); + engine.softTakeover("deck2", "pregain", true); - engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); - engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); - + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter1", true); engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter3", true); engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter3", true); engine.softTakeover("[QuickEffectRack1_[Channel1]]", "super1", true); engine.softTakeover("[QuickEffectRack1_[Channel2]]", "super1", true); + engine.softTakeover("[QuickEffectRack1_[Channel3]]", "super1", true); + engine.softTakeover("[QuickEffectRack1_[Channel4]]", "super1", true); engine.softTakeover("[Master]", "crossfader", true); engine.softTakeover("[Master]", "gain", true); @@ -276,11 +305,21 @@ TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, message.setCallback(group, name, callback); }; +TraktorS3.deckSwitchHandler = function(field) { + for (var key in field) { + var value = field[key]; + HIDDebug("what is a field: " + key + ": " + value); + } + TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); +}; + TraktorS3.playHandler = function(field) { + HIDDebug("group?? " + field.group); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(field.group, "start_stop", field.value); } else if (field.value === 1) { - script.toggleControl(field.group, "play"); + HIDDebug("sooooo play?"); + script.toggleControl(group, "play"); } }; @@ -292,6 +331,7 @@ TraktorS3.cueHandler = function(field) { } }; + TraktorS3.shiftHandler = function(field) { HIDDebug("SHIFT!" + field.group + " " + field.value); TraktorS3.shiftPressed[field.group] = field.value; @@ -309,8 +349,8 @@ TraktorS3.keylockHandler = function(field) { TraktorS3.syncHandler = function(field) { if (TraktorS3.shiftPressed[field.group]) { - // engine.setValue(field.group, "start_stop", field.value); - } else { + // engine.setValue(field.group, "sync_enabled", field.value); + } else if (field.value === 1) { //TODO: latching not working? engine.setValue(field.group, "sync_enabled", field.value); } @@ -488,6 +528,7 @@ TraktorS3.quantizeHandler = function(field) { return; } + // TODO: fix quantize var res = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); engine.setValue("[Channel1]", "quantize", res); engine.setValue("[Channel2]", "quantize", res); @@ -572,7 +613,7 @@ TraktorS3.jogHandler = function(field) { TraktorS3.scalerJog = function(tick_delta, time_delta) { // If it's playing nudge var multiplier = 1.0; - if (TraktorS3.shiftPressed["[Channel1]"] || TraktorS3.shiftPressed["[Channel2]"]) { + if (TraktorS3.shiftPressed["deck1"] || TraktorS3.shiftPressed["deck2"]) { multiplier = 100.0; } if (engine.getValue(group, "play")) { @@ -660,6 +701,8 @@ TraktorS3.fxHandler = function(field) { engine.setValue(group, "group_[Channel1]_enable", TraktorS3.fxButtonState[fxNumber]); engine.setValue(group, "group_[Channel2]_enable", TraktorS3.fxButtonState[fxNumber]); + engine.setValue(group, "group_[Channel3]_enable", TraktorS3.fxButtonState[fxNumber]); + engine.setValue(group, "group_[Channel4]_enable", TraktorS3.fxButtonState[fxNumber]); TraktorS3.outputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "fxButton" + fxNumber); }; @@ -740,73 +783,73 @@ TraktorS3.debugLights = function() { }; TraktorS3.registerOutputPackets = function() { - var outputA = new HIDPacket("outputA", 0x80); + var outputA = new HIDPacket(TraktorS3.controller, "outputA", 0x80); - outputA.addOutput("[Channel1]", "shift", 0x01, "B"); - outputA.addOutput("[Channel2]", "shift", 0x1A, "B"); + outputA.addOutput("deck1", "shift", 0x01, "B"); + outputA.addOutput("deck2", "shift", 0x1A, "B"); - outputA.addOutput("[Channel1]", "slip_enabled", 0x02, "B"); - outputA.addOutput("[Channel2]", "slip_enabled", 0x1B, "B"); + outputA.addOutput("deck1", "slip_enabled", 0x02, "B"); + outputA.addOutput("deck2", "slip_enabled", 0x1B, "B"); - outputA.addOutput("[Channel1]", "reverse", 0x03, "B"); - outputA.addOutput("[Channel2]", "reverse", 0x1C, "B"); + outputA.addOutput("deck1", "reverse", 0x03, "B"); + outputA.addOutput("deck2", "reverse", 0x1C, "B"); - outputA.addOutput("[Channel1]", "keylock", 0x0D, "B"); - outputA.addOutput("[Channel2]", "keylock", 0x26, "B"); + outputA.addOutput("deck1", "keylock", 0x0D, "B"); + outputA.addOutput("deck2", "keylock", 0x26, "B"); - outputA.addOutput("[Channel1]", "hotcues", 0x0E, "B"); - outputA.addOutput("[Channel2]", "hotcues", 0x27, "B"); + outputA.addOutput("deck1", "hotcues", 0x0E, "B"); + outputA.addOutput("deck2", "hotcues", 0x27, "B"); - outputA.addOutput("[Channel1]", "samples", 0x0F, "B"); - outputA.addOutput("[Channel2]", "samples", 0x28, "B"); + outputA.addOutput("deck1", "samples", 0x0F, "B"); + outputA.addOutput("deck2", "samples", 0x28, "B"); - outputA.addOutput("[Channel1]", "cue_indicator", 0x10, "B"); - outputA.addOutput("[Channel2]", "cue_indicator", 0x29, "B"); + outputA.addOutput("deck1", "cue_indicator", 0x10, "B"); + outputA.addOutput("deck2", "cue_indicator", 0x29, "B"); - outputA.addOutput("[Channel1]", "play_indicator", 0x11, "B"); - outputA.addOutput("[Channel2]", "play_indicator", 0x2A, "B"); + outputA.addOutput("deck1", "play_indicator", 0x11, "B"); + outputA.addOutput("deck2", "play_indicator", 0x2A, "B"); - outputA.addOutput("[Channel1]", "sync_enabled", 0x0C, "B"); - outputA.addOutput("[Channel2]", "sync_enabled", 0x25, "B"); + outputA.addOutput("deck1", "sync_enabled", 0x0C, "B"); + outputA.addOutput("deck2", "sync_enabled", 0x25, "B"); - outputA.addOutput("[Channel1]", "pad_1", 0x12, "B"); - outputA.addOutput("[Channel1]", "pad_2", 0x13, "B"); - outputA.addOutput("[Channel1]", "pad_3", 0x14, "B"); - outputA.addOutput("[Channel1]", "pad_4", 0x15, "B"); - outputA.addOutput("[Channel1]", "pad_5", 0x16, "B"); - outputA.addOutput("[Channel1]", "pad_6", 0x17, "B"); - outputA.addOutput("[Channel1]", "pad_7", 0x18, "B"); - outputA.addOutput("[Channel1]", "pad_8", 0x19, "B"); + outputA.addOutput("deck1", "pad_1", 0x12, "B"); + outputA.addOutput("deck1", "pad_2", 0x13, "B"); + outputA.addOutput("deck1", "pad_3", 0x14, "B"); + outputA.addOutput("deck1", "pad_4", 0x15, "B"); + outputA.addOutput("deck1", "pad_5", 0x16, "B"); + outputA.addOutput("deck1", "pad_6", 0x17, "B"); + outputA.addOutput("deck1", "pad_7", 0x18, "B"); + outputA.addOutput("deck1", "pad_8", 0x19, "B"); - outputA.addOutput("[Channel2]", "pad_1", 0x2B, "B"); - outputA.addOutput("[Channel2]", "pad_2", 0x2C, "B"); - outputA.addOutput("[Channel2]", "pad_3", 0x2D, "B"); - outputA.addOutput("[Channel2]", "pad_4", 0x2E, "B"); - outputA.addOutput("[Channel2]", "pad_5", 0x2F, "B"); - outputA.addOutput("[Channel2]", "pad_6", 0x30, "B"); - outputA.addOutput("[Channel2]", "pad_7", 0x31, "B"); - outputA.addOutput("[Channel2]", "pad_8", 0x32, "B"); + outputA.addOutput("deck2", "pad_1", 0x2B, "B"); + outputA.addOutput("deck2", "pad_2", 0x2C, "B"); + outputA.addOutput("deck2", "pad_3", 0x2D, "B"); + outputA.addOutput("deck2", "pad_4", 0x2E, "B"); + outputA.addOutput("deck2", "pad_5", 0x2F, "B"); + outputA.addOutput("deck2", "pad_6", 0x30, "B"); + outputA.addOutput("deck2", "pad_7", 0x31, "B"); + outputA.addOutput("deck2", "pad_8", 0x32, "B"); outputA.addOutput("[Channel1]", "pfl", 0x39, "B"); outputA.addOutput("[Channel2]", "pfl", 0x3A, "B"); outputA.addOutput("[Channel3]", "pfl", 0x38, "B"); outputA.addOutput("[Channel4]", "pfl", 0x3B, "B"); - // outputA.addOutput("[Channel1]", "addTrack", 0x03, "B"); - // outputA.addOutput("[Channel2]", "addTrack", 0x2A, "B"); + // outputA.addOutput("deck1", "addTrack", 0x03, "B"); + // outputA.addOutput("deck2", "addTrack", 0x2A, "B"); - outputA.addOutput("[Channel1]", "grid", 0x08, "B"); - outputA.addOutput("[Channel2]", "grid", 0x20, "B"); + outputA.addOutput("deck1", "grid", 0x08, "B"); + outputA.addOutput("deck2", "grid", 0x20, "B"); - outputA.addOutput("[Channel1]", "MaximizeLibrary", 0x07, "B"); - outputA.addOutput("[Channel2]", "MaximizeLibrary", 0x21, "B"); + outputA.addOutput("deck1", "MaximizeLibrary", 0x07, "B"); + outputA.addOutput("deck2", "MaximizeLibrary", 0x21, "B"); // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); this.controller.registerOutputPacket(outputA); - var outputB = new HIDPacket("outputB", 0x81); + var outputB = new HIDPacket(TraktorS3.controller, "outputB", 0x81); var VuOffsets = { "[Channel3]": 0x01, @@ -820,9 +863,9 @@ TraktorS3.registerOutputPackets = function() { } } - outputB.addOutput("[Channel3]", "PeakIndicator", 0x09, "B"); outputB.addOutput("[Channel1]", "PeakIndicator", 0x1E, "B"); outputB.addOutput("[Channel2]", "PeakIndicator", 0x2D, "B"); + outputB.addOutput("[Channel3]", "PeakIndicator", 0x09, "B"); outputB.addOutput("[Channel4]", "PeakIndicator", 0x3C, "B"); outputB.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); @@ -832,36 +875,38 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputB); - this.linkOutput("[Channel1]", "play_indicator", this.outputHandler); - this.linkOutput("[Channel2]", "play_indicator", this.outputHandler); + this.linkOutput("deck1", "play_indicator", this.outputHandler); + this.linkOutput("deck2", "play_indicator", this.outputHandler); - this.linkOutput("[Channel1]", "cue_indicator", this.outputHandler); - this.linkOutput("[Channel2]", "cue_indicator", this.outputHandler); + this.linkOutput("deck1", "cue_indicator", this.outputHandler); + this.linkOutput("deck2", "cue_indicator", this.outputHandler); - this.linkOutput("[Channel1]", "sync_enabled", this.outputHandler); - this.linkOutput("[Channel2]", "sync_enabled", this.outputHandler); + this.linkOutput("deck1", "sync_enabled", this.outputHandler); + this.linkOutput("deck2", "sync_enabled", this.outputHandler); - this.linkOutput("[Channel1]", "keylock", this.outputHandler); - this.linkOutput("[Channel2]", "keylock", this.outputHandler); + this.linkOutput("deck1", "keylock", this.outputHandler); + this.linkOutput("deck2", "keylock", this.outputHandler); for (var i = 1; i <= 8; ++i) { - TraktorS3.controller.linkOutput("[Channel1]", "pad_" + i, "[Channel1]", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - TraktorS3.controller.linkOutput("[Channel2]", "pad_" + i, "[Channel2]", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + TraktorS3.controller.linkOutput("deck1", "pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + TraktorS3.controller.linkOutput("deck2", "pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); } this.linkOutput("[Channel1]", "pfl", this.outputHandler); this.linkOutput("[Channel2]", "pfl", this.outputHandler); + this.linkOutput("[Channel3]", "pfl", this.outputHandler); + this.linkOutput("[Channel4]", "pfl", this.outputHandler); - this.linkOutput("[Channel1]", "slip_enabled", this.outputHandler); - this.linkOutput("[Channel2]", "slip_enabled", this.outputHandler); + this.linkOutput("deck1", "slip_enabled", this.outputHandler); + this.linkOutput("deck2", "slip_enabled", this.outputHandler); this.linkOutput("[Microphone]", "talkover", this.outputHandler); // VuMeter - this.vuLeftConnection = engine.makeConnection("[Channel1]", "VuMeter", this.vuMeterHandler); - this.vuRightConnection = engine.makeConnection("[Channel2]", "VuMeter", this.vuMeterHandler); - this.clipLeftConnection = engine.makeConnection("[Channel1]", "PeakIndicator", this.peakOutputHandler); - this.clipRightConnection = engine.makeConnection("[Channel2]", "PeakIndicator", this.peakOutputHandler); + for (var i = 1; i <= 4; i++) { + this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.vuMeterHandler); + this.clipConnections[i] = engine.makeConnection("[Channel" + i + "]", "PeakIndicator", this.peakOutputHandler); + } // Sampler callbacks for (i = 1; i <= 16; ++i) { @@ -949,17 +994,17 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { // Sampler 1-4, 9-12 -> Channel1 // Samples 5-8, 13-16 -> Channel2 var sampler = TraktorS3.resolveSampler(group); - var deck = "[Channel1]"; + var deck = "deck1"; var num = sampler; if (sampler === undefined) { return; } else if (sampler > 4 && sampler < 9) { - deck = "[Channel2]"; + deck = "deck2"; num = sampler - 4; } else if (sampler > 8 && sampler < 13) { num = sampler - 4; } else if (sampler > 12 && sampler < 17) { - deck = "[Channel2]"; + deck = "deck2"; num = sampler - 8; } @@ -1002,69 +1047,73 @@ TraktorS3.lightDeck = function(switchOff) { fullLight = 0x00; } - var current = (engine.getValue("[Channel1]", "play_indicator") ? fullLight : softLight); - TraktorS3.controller.setOutput("[Channel1]", "play_indicator", current, false); - current = (engine.getValue("[Channel2]", "play_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "play_indicator", current, false); + var current = (engine.getValue("deck1", "play_indicator") ? fullLight : softLight); + TraktorS3.controller.setOutput("deck1", "play_indicator", current, false); + current = (engine.getValue("deck2", "play_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "play_indicator", current, false); - current = (engine.getValue("[Channel1]", "cue_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "cue_indicator", current, false); - current = (engine.getValue("[Channel2]", "cue_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "cue_indicator", current, false); + current = (engine.getValue("deck1", "cue_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck1", "cue_indicator", current, false); + current = (engine.getValue("deck2", "cue_indicator")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "cue_indicator", current, false); - TraktorS3.controller.setOutput("[Channel1]", "shift", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "shift", softLight, false); + TraktorS3.controller.setOutput("deck1", "shift", softLight, false); + TraktorS3.controller.setOutput("deck2", "shift", softLight, false); - current = (engine.getValue("[Channel1]", "sync_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "sync_enabled", current, false); - current = (engine.getValue("[Channel1]", "sync_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "sync_enabled", current, false); + current = (engine.getValue("deck1", "sync_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck1", "sync_enabled", current, false); + current = (engine.getValue("deck2", "sync_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "sync_enabled", current, false); // Hotcues mode is default start value - TraktorS3.controller.setOutput("[Channel1]", "hotcues", fullLight, false); - TraktorS3.controller.setOutput("[Channel2]", "hotcues", fullLight, false); + TraktorS3.controller.setOutput("deck1", "hotcues", fullLight, false); + TraktorS3.controller.setOutput("deck2", "hotcues", fullLight, false); - TraktorS3.controller.setOutput("[Channel1]", "samples", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "samples", softLight, false); + TraktorS3.controller.setOutput("deck1", "samples", softLight, false); + TraktorS3.controller.setOutput("deck2", "samples", softLight, false); - current = (engine.getValue("[Channel1]", "keylock")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "keylock", current, false); - current = (engine.getValue("[Channel2]", "keylock")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "keylock", current, false); + current = (engine.getValue("deck1", "keylock")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck1", "keylock", current, false); + current = (engine.getValue("deck2", "keylock")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "keylock", current, false); for (var i = 1; i <= 8; ++i) { - current = (engine.getValue("[Channel1]", "hotcue_" + i + "_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "pad_" + i, current, false); - current = (engine.getValue("[Channel2]", "hotcue_" + i + "_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "pad_" + i, current, false); + current = (engine.getValue("deck1", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck1", "pad_" + i, current, false); + current = (engine.getValue("deck2", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "pad_" + i, current, false); } current = (engine.getValue("[Channel1]", "pfl")) ? fullLight : softLight; TraktorS3.controller.setOutput("[Channel1]", "pfl", current, false); current = (engine.getValue("[Channel2]", "pfl")) ? fullLight : softLight; TraktorS3.controller.setOutput("[Channel2]", "pfl", current, false); + current = (engine.getValue("[Channel3]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel3]", "pfl", current, false); + current = (engine.getValue("[Channel4]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel4]", "pfl", current, false); TraktorS3.controller.setOutput("[ChannelX]", "fxButton1", softLight, false); TraktorS3.controller.setOutput("[ChannelX]", "fxButton2", softLight, false); TraktorS3.controller.setOutput("[ChannelX]", "fxButton3", softLight, false); TraktorS3.controller.setOutput("[ChannelX]", "fxButton4", softLight, false); - TraktorS3.controller.setOutput("[Channel1]", "reverse", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "reverse", softLight, false); + TraktorS3.controller.setOutput("deck1", "reverse", softLight, false); + TraktorS3.controller.setOutput("deck", "reverse", softLight, false); - current = (engine.getValue("[Channel1]", "slip_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "slip_enabled", current, false); - current = (engine.getValue("[Channel2]", "slip_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "slip_enabled", current, false); + current = (engine.getValue("deck1", "slip_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck1", "slip_enabled", current, false); + current = (engine.getValue("deck", "slip_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck", "slip_enabled", current, false); - TraktorS3.controller.setOutput("[Channel1]", "addTrack", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "addTrack", softLight, false); + TraktorS3.controller.setOutput("deck1", "addTrack", softLight, false); + TraktorS3.controller.setOutput("deck", "addTrack", softLight, false); - TraktorS3.controller.setOutput("[Channel1]", "grid", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "grid", softLight, false); + TraktorS3.controller.setOutput("deck1", "grid", softLight, false); + TraktorS3.controller.setOutput("deck", "grid", softLight, false); - TraktorS3.controller.setOutput("[Channel1]", "MaximizeLibrary", softLight, false); - TraktorS3.controller.setOutput("[Channel2]", "MaximizeLibrary", softLight, false); + TraktorS3.controller.setOutput("deck1", "MaximizeLibrary", softLight, false); + TraktorS3.controller.setOutput("deck", "MaximizeLibrary", softLight, false); TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); @@ -1073,7 +1122,7 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("[Microphone]", "talkover", current, true); }; -TraktorS3.messageCallback = function(packet, data) { +TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { if (data.hasOwnProperty(name)) { TraktorS3.controller.processButton(data[name]); diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index e887575473b..953f3023fe4 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1,7 +1,7 @@ /** Common HID script debugging function. Just to get logging with 'HID' prefix. */ HIDDebug = function(message) { - print("HID " + message) -} + print("HID " + message); +}; /** HID Bit Vector Class * @@ -9,104 +9,104 @@ HIDDebug = function(message) { * created by HIDPacket addControl and addOutput and should not be * created manually. */ HIDBitVector = function() { - this.size = 0 - this.bits = {} -} + this.size = 0; + this.bits = {}; +}; /** Return bit offset based on bitmask */ HIDBitVector.prototype.getOffset = function(bitmask) { for (var i = 0; i < 32; i++) if ((1 & bitmask >> i) != 0) - return i - return 0 -} + return i; + return 0; +}; /** Add a control bitmask to the HIDBitVector */ HIDBitVector.prototype.addBitMask = function(group, name, bitmask) { - var bit = {} - bit.type = "button" - bit.packet = undefined - bit.id = group + "." + name - bit.group = group - bit.name = name - bit.mapped_group = undefined - bit.mapped_name = undefined - bit.bitmask = bitmask - bit.bitmask = bitmask - bit.bit_offset = this.getOffset(bitmask) - bit.callback = undefined - bit.value = undefined - bit.auto_repeat = undefined - bit.auto_repeat_interval = undefined - this.bits[bit.id] = bit -} + var bit = {}; + bit.type = "button"; + bit.packet = undefined; + bit.id = group + "." + name; + bit.group = group; + bit.name = name; + bit.mapped_group = undefined; + bit.mapped_name = undefined; + bit.bitmask = bitmask; + bit.bitmask = bitmask; + bit.bit_offset = this.getOffset(bitmask); + bit.callback = undefined; + bit.value = undefined; + bit.auto_repeat = undefined; + bit.auto_repeat_interval = undefined; + this.bits[bit.id] = bit; +}; /** Add a Output control bitmask to the HIDBitVector */ HIDBitVector.prototype.addOutputMask = function(group, name, bitmask) { - var bit = {} - bit.type = "output" - bit.packet = undefined - bit.id = group + "." + name - bit.group = group - bit.name = name - bit.mapped_group = undefined - bit.mapped_name = undefined - bit.bitmask = bitmask - bit.bit_offset = this.getOffset(bitmask) - bit.callback = undefined - bit.value = undefined - bit.toggle = undefined - this.bits[bit.id] = bit -} + var bit = {}; + bit.type = "output"; + bit.packet = undefined; + bit.id = group + "." + name; + bit.group = group; + bit.name = name; + bit.mapped_group = undefined; + bit.mapped_name = undefined; + bit.bitmask = bitmask; + bit.bit_offset = this.getOffset(bitmask); + bit.callback = undefined; + bit.value = undefined; + bit.toggle = undefined; + this.bits[bit.id] = bit; +}; /** HID Modifiers object * * Wraps all defined modifiers to one object with uniform API. * Don't call directly, this is available as HIDController.modifiers */ HIDModifierList = function() { - this.modifiers = Object() - this.callbacks = Object() -} + this.modifiers = Object(); + this.callbacks = Object(); +}; /** Add a new modifier to controller. */ HIDModifierList.prototype.add = function(name) { if (name in this.modifiers) { - HIDDebug("Modifier already defined: " + name) - return + HIDDebug("Modifier already defined: " + name); + return; } - this.modifiers[name] = undefined -} + this.modifiers[name] = undefined; +}; /** Set modifier value */ HIDModifierList.prototype.set = function(name, value) { if ((!name in this.modifiers)) { - HIDDebug("Unknown modifier: " + name) - return + HIDDebug("Unknown modifier: " + name); + return; } - this.modifiers[name] = value + this.modifiers[name] = value; if (name in this.callbacks) { - var callback = this.callbacks[name] - callback(value) + var callback = this.callbacks[name]; + callback(value); } -} +}; /** Get modifier value */ HIDModifierList.prototype.get = function(name) { if (!(name in this.modifiers)) { - HIDDebug("Unknown modifier: " + name) - return false + HIDDebug("Unknown modifier: " + name); + return false; } - return this.modifiers[name] -} + return this.modifiers[name]; +}; /** Set modifier callback (update function after modifier state changes) */ HIDModifierList.prototype.setCallback = function(name, callback) { if ((!name in this.modifiers)) { HIDDebug("Unknown modifier: " + name); - return + return; } - this.callbacks[name] = callback -} + this.callbacks[name] = callback; +}; /** * HID Packet object @@ -122,70 +122,71 @@ HIDModifierList.prototype.setCallback = function(name, callback) { * @param header (optional) list of bytes to match from beginning * of packet. Do NOT put the report ID in this; use * the reportId parameter instead. */ -HIDPacket = function(name, reportId, callback, header) { - this.name = name - this.header = header - this.callback = callback +HIDPacket = function(controller, name, reportId, callback, header) { + this.controller = controller; + this.name = name; + this.header = header; + this.callback = callback; - this.reportId = 0 + this.reportId = 0; if (reportId !== undefined) { - this.reportId = reportId + this.reportId = reportId; } - this.groups = {} + this.groups = {}; // Size of various 'pack' values in bytes - this.packSizes = {b: 1, B: 1, h: 2, H: 2, i: 4, I: 4} - this.signedPackFormats = ["b", "h", "i"] -} + this.packSizes = {b: 1, B: 1, h: 2, H: 2, i: 4, I: 4}; + this.signedPackFormats = ["b", "h", "i"]; +}; /** Pack a field value to the packet. * Can only pack bits and byte values, patches welcome. */ HIDPacket.prototype.pack = function(data, field) { - var value + var value; if (!(field.pack in this.packSizes)) { - HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack) - return + HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack); + return; } - var bytes = this.packSizes[field.pack] - var signed = false + var bytes = this.packSizes[field.pack]; + var signed = false; if (this.signedPackFormats.indexOf(field.pack) != -1) - signed = true + signed = true; if (field.type == "bitvector") { // TODO - fix multi byte bit vector outputs if (bytes > 1) { - HIDDebug("ERROR: packing multibyte bit vectors not yet supported") - return + HIDDebug("ERROR: packing multibyte bit vectors not yet supported"); + return; } for (bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] - data[field.offset] = data[field.offset] | bit.value + var bit = field.value.bits[bit_id]; + data[field.offset] = data[field.offset] | bit.value; } - return + return; } - value = (field.value != undefined) ? field.value : 0 + value = (field.value != undefined) ? field.value : 0; if (value < field.min || value > field.max) { - HIDDebug("ERROR " + field.id + " packed value out of range: " + value) - return + HIDDebug("ERROR " + field.id + " packed value out of range: " + value); + return; } for (var byte_index = 0; byte_index < bytes; byte_index++) { - var index = field.offset + byte_index + var index = field.offset + byte_index; if (signed) { if (value >= 0) { - data[index] = (value >> (byte_index * 8)) & 255 + data[index] = (value >> (byte_index * 8)) & 255; } else { - data[index] = 255 - ((-(value + 1) >> (byte_index * 8)) & 255) + data[index] = 255 - ((-(value + 1) >> (byte_index * 8)) & 255); } } else { - data[index] = (value >> (byte_index * 8)) & 255 + data[index] = (value >> (byte_index * 8)) & 255; } } -} +}; /** Parse and return field value matching the 'pack' field from field attributes. * Valid values are: @@ -196,135 +197,152 @@ HIDPacket.prototype.pack = function(data, field) { * i signed integer * I unsigned integer */ HIDPacket.prototype.unpack = function(data, field) { - var value = 0 + var value = 0; if (!(field.pack in this.packSizes)) { - HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack) - return + HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack); + return; } - var bytes = this.packSizes[field.pack] - var signed = false + var bytes = this.packSizes[field.pack]; + var signed = false; if (this.signedPackFormats.indexOf(field.pack) != -1) - signed = true + signed = true; for (field_byte = 0; field_byte < bytes; field_byte++) { if (data[field.offset + field_byte] == 255 && field_byte == 4) - value += 0 + value += 0; else - value += data[field.offset + field_byte] * Math.pow(2, (field_byte * 8)) + value += data[field.offset + field_byte] * Math.pow(2, (field_byte * 8)); } if (signed) { - var max_value = Math.pow(2, bytes * 8) - var split = max_value / 2 - 1 + var max_value = Math.pow(2, bytes * 8); + var split = max_value / 2 - 1; if (value > split) - value = value - max_value + value = value - max_value; + } + return value; +}; + +HIDPacket.prototype.registerGroup = function(group) { + if (group in this.groups) { + HIDDebug("Group already registered: " + group); + return; } - return value -} + this.groups[group] = {}; +}; /** Find HID packet group matching name. * Create group if create is true */ HIDPacket.prototype.getGroup = function(name, create) { if (this.groups == undefined) - this.groups = {} + this.groups = {}; if (name in this.groups) - return this.groups[name] + return this.groups[name]; if (!create) - return undefined - this.groups[name] = {} - return this.groups[name] -} + return undefined; + this.groups[name] = {}; + return this.groups[name]; +}; /** Lookup HID packet field matching given offset and pack type * Returns undefined if no patching field can be found. */ HIDPacket.prototype.getFieldByOffset = function(offset, pack) { if (!(pack in this.packSizes)) { - HIDDebug("Unknown pack string " + pack) - return undefined + HIDDebug("Unknown pack string " + pack); + return undefined; } - var end_offset = offset + this.packSizes[pack] - var group - var field + var end_offset = offset + this.packSizes[pack]; + var group; + var field; for (var group_name in this.groups) { - group = this.groups[group_name] + group = this.groups[group_name]; for (var field_id in group) { - field = group[field_id] + field = group[field_id]; // Same field offset if (field.offset == offset) - return field + return field; // 7-8 8-9 // Offset for smaller packet inside multibyte field if (field.offset < offset && field.end_offset >= end_offset) - return field + return field; // Packet offset starts inside field, may overflow if (field.offset < offset && field.end_offset > offset) - return field + return field; // Packet start before field, ends or overflows field if (field.offset > offset && field.offset < end_offset) - return field + return field; } } - return undefined -} + return undefined; +}; /** Return a field by group and name from the packet, * Returns undefined if field could not be found */ HIDPacket.prototype.getField = function(group, name) { - var field_id = group + "." + name + var field_id = group + "." + name; if (!(group in this.groups)) { - HIDDebug("PACKET " + this.name + " group not found " + group) - return undefined + HIDDebug("PACKET " + this.name + " group not found " + group); + return undefined; } - var control_group = this.groups[group] + // Resolve virtual decks automatically. + HIDDebug("GROUPP????? " + group); + if (this.controller.virtualDecks.indexOf(group) !== -1) { + HIDDebug("resolving!"); + group = this.controller.resolveGroup(group); + HIDDebug("and.... " + group); + } + + var control_group = this.groups[group]; + HIDDebug("groups? " + this.groups + " " + group + " " + control_group); if (field_id in control_group) - return control_group[field_id] + return control_group[field_id]; // Lookup for bit fields in bitvector matching field name for (var group_name in this.groups) { - var control_group = this.groups[group_name] + var control_group = this.groups[group_name]; for (field_name in control_group) { - var field = control_group[field_name] + var field = control_group[field_name]; if (field == undefined || field.type != "bitvector") - continue + continue; for (bit_name in field.value.bits) { - var bit = field.value.bits[bit_name] + var bit = field.value.bits[bit_name]; if (bit.id == field_id) { - return field + return field; } } } } // Field not found - return undefined -} + return undefined; +}; /** Return reference to a bit in a bitvector field */ HIDPacket.prototype.lookupBit = function(group, name) { - var field = this.getField(group, name) + var field = this.getField(group, name); if (field == undefined) { - HIDDebug("Bitvector match not found: " + group + "." + name) - return undefined + HIDDebug("Bitvector match not found: " + group + "." + name); + return undefined; } - var bit_id = group + "." + name + var bit_id = group + "." + name; for (bit_name in field.value.bits) { - var bit = field.value.bits[bit_name] + var bit = field.value.bits[bit_name]; if (bit.id == bit_id) - return bit + return bit; } - HIDDebug("BUG: bit not found after successful field lookup") - return undefined -} + HIDDebug("BUG: bit not found after successful field lookup"); + return undefined; +}; /** Remove a control registered. Normally not needed */ HIDPacket.prototype.removeControl = function(group, name) { - var control_group = this.getGroup(group) + var control_group = this.getGroup(group); if (!(name in control_group)) { - HIDDebug("Field not in control group " + group + ": " + name) - return + HIDDebug("Field not in control group " + group + ": " + name); + return; } - delete control_group[name] -} + delete control_group[name]; +}; /** Register a numeric value to parse from input packet * @@ -336,86 +354,86 @@ HIDPacket.prototype.removeControl = function(group, name) { * NOTE: Parsing bitmask with multiple bits is not supported yet. * @param isEncoder indicates if this is an encoder which should be wrapped and delta reported */ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, isEncoder) { - var control_group = this.getGroup(group, true) - var bitvector = undefined + var control_group = this.getGroup(group, true); + var bitvector = undefined; if (control_group == undefined) { - HIDDebug("ERROR creating HID packet group " + group) - return + HIDDebug("ERROR creating HID packet group " + group); + return; } if (!(pack in this.packSizes)) { - HIDDebug("Unknown pack value " + pack) - return + HIDDebug("Unknown pack value " + pack); + return; } - var field = this.getFieldByOffset(offset, pack) + var field = this.getFieldByOffset(offset, pack); if (field != undefined) { if (bitmask == undefined) { - HIDDebug("ERROR registering offset " + offset + " pack " + pack) + HIDDebug("ERROR registering offset " + offset + " pack " + pack); HIDDebug( "ERROR trying to overwrite non-bitmask control " + group + " " + name - ) - return + ); + return; } - bitvector = field.value - bitvector.addBitMask(group, name, bitmask) - return - } - - field = {} - field.packet = undefined - field.id = group + "." + name - field.group = group - field.name = name - field.mapped_group = undefined - field.mapped_name = undefined - field.pack = pack - field.offset = offset - field.end_offset = offset + this.packSizes[field.pack] - field.bitmask = bitmask - field.isEncoder = isEncoder - field.callback = undefined - field.soft_takeover = false - field.ignored = false - field.auto_repeat = undefined - field.auto_repeat_interval = undefined - - var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8) + bitvector = field.value; + bitvector.addBitMask(group, name, bitmask); + return; + } + + field = {}; + field.packet = undefined; + field.id = group + "." + name; + field.group = group; + field.name = name; + field.mapped_group = undefined; + field.mapped_name = undefined; + field.pack = pack; + field.offset = offset; + field.end_offset = offset + this.packSizes[field.pack]; + field.bitmask = bitmask; + field.isEncoder = isEncoder; + field.callback = undefined; + field.soft_takeover = false; + field.ignored = false; + field.auto_repeat = undefined; + field.auto_repeat_interval = undefined; + + var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8); if (this.signedPackFormats.indexOf(pack) != -1) { - field.min = 0 - (packet_max_value / 2) + 1 - field.max = (packet_max_value / 2) - 1 + field.min = 0 - (packet_max_value / 2) + 1; + field.max = (packet_max_value / 2) - 1; } else { - field.min = 0 - field.max = packet_max_value - 1 + field.min = 0; + field.max = packet_max_value - 1; } if (bitmask == undefined || bitmask == packet_max_value) { - field.type = "control" - field.value = undefined - field.delta = 0 - field.mindelta = 0 + field.type = "control"; + field.value = undefined; + field.delta = 0; + field.mindelta = 0; } else { if (this.signedPackFormats.indexOf(pack) != -1) { - HIDDebug("ERROR registering bitvector: signed fields not supported") - return + HIDDebug("ERROR registering bitvector: signed fields not supported"); + return; } // Create a new bitvector field and add the bit to that // TODO - accept controls with bitmask < packet_max_value - field_name = "bitvector_" + offset - field.type = "bitvector" - field.name = field_name - field.id = group + "." + field_name - bitvector = new HIDBitVector(field.max) - bitvector.size = field.max - bitvector.addBitMask(group, name, bitmask) - field.value = bitvector - field.delta = undefined - field.soft_takeover = undefined - field.mindelta = undefined + field_name = "bitvector_" + offset; + field.type = "bitvector"; + field.name = field_name; + field.id = group + "." + field_name; + bitvector = new HIDBitVector(field.max); + bitvector.size = field.max; + bitvector.addBitMask(group, name, bitmask); + field.value = bitvector; + field.delta = undefined; + field.soft_takeover = undefined; + field.mindelta = undefined; } // Add the new field to the packet - control_group[field.id] = field -} + control_group[field.id] = field; +}; /** Register a Output control field or Output control bit to output packet * Output control field: @@ -430,250 +448,284 @@ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, is * (0 is automatically populated with the reportId) * @param pack control packing format for pack(), one of b/B, h/H, i/I */ HIDPacket.prototype.addOutput = function(group, name, offset, pack, bitmask, callback) { - var control_group = this.getGroup(group, true) - var field - var bitvector = undefined - var field_id = group + "." + name + var control_group = this.getGroup(group, true); + var field; + var bitvector = undefined; + var field_id = group + "." + name; if (control_group == undefined) { - return + return; } if (!(pack in this.packSizes)) { - HIDDebug("ERROR: unknown Output control pack value " + pack) - return + HIDDebug("ERROR: unknown Output control pack value " + pack); + return; } // Adjust offset by 1 because the reportId was previously considered part of the payload // but isn't anymore and we can't be bothered to adjust every single script manually - offset -= 1 + offset -= 1; // Check if we are adding a Output bit to existing bitvector - field = this.getFieldByOffset(offset, pack) + field = this.getFieldByOffset(offset, pack); if (field != undefined) { if (bitmask == undefined) { HIDDebug( "ERROR: overwrite non-bitmask control " + group + "." + name - ) - return + ); + return; } - bitvector = field.value - bitvector.addOutputMask(group, name, bitmask) - return - } - - field = {} - field.id = field_id - field.group = group - field.name = name - field.mapped_group = undefined - field.mapped_name = undefined - field.pack = pack - field.offset = offset - field.end_offset = offset + this.packSizes[field.pack] - field.bitmask = bitmask - field.callback = callback - field.toggle = undefined - - var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8) + bitvector = field.value; + bitvector.addOutputMask(group, name, bitmask); + return; + } + + field = {}; + field.id = field_id; + field.group = group; + field.name = name; + field.mapped_group = undefined; + field.mapped_name = undefined; + field.pack = pack; + field.offset = offset; + field.end_offset = offset + this.packSizes[field.pack]; + field.bitmask = bitmask; + field.callback = callback; + field.toggle = undefined; + + var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8); if (this.signedPackFormats.indexOf(pack) != -1) { - field.min = 0 - (packet_max_value / 2) + 1 - field.max = (packet_max_value / 2) - 1 + field.min = 0 - (packet_max_value / 2) + 1; + field.max = (packet_max_value / 2) - 1; } else { - field.min = 0 - field.max = packet_max_value - 1 + field.min = 0; + field.max = packet_max_value - 1; } if (bitmask == undefined || bitmask == packet_max_value) { - field.type = "output" - field.value = undefined - field.delta = undefined - field.mindelta = undefined + field.type = "output"; + field.value = undefined; + field.delta = undefined; + field.mindelta = undefined; } else { // Create new Output bitvector control field, add bit to it // rewrite name to use bitvector instead - field_name = "bitvector_" + offset - field.type = "bitvector" - field.id = group + "." + field_name - field.name = field_name - bitvector = new HIDBitVector() - bitvector.size = field.max - bitvector.addOutputMask(group, name, bitmask) - field.value = bitvector - field.delta = undefined - field.mindelta = undefined + field_name = "bitvector_" + offset; + field.type = "bitvector"; + field.id = group + "." + field_name; + field.name = field_name; + bitvector = new HIDBitVector(); + bitvector.size = field.max; + bitvector.addOutputMask(group, name, bitmask); + field.value = bitvector; + field.delta = undefined; + field.mindelta = undefined; } // Add Output to HID packet - control_group[field.id] = field -} + control_group[field.id] = field; +}; /** Register a callback to field or a bit vector bit. * Does not make sense for Output fields but you can do that. */ HIDPacket.prototype.setCallback = function(group, name, callback) { - var field = this.getField(group, name) - var field_id = group + "." + name + var field = this.getField(group, name); + var field_id = group + "." + name; if (callback == undefined) { - HIDDebug("Callback to add was undefined for " + field_id) - return + HIDDebug("Callback to add was undefined for " + field_id); + return; } if (field == undefined) { HIDDebug("setCallback: field for " + field_id + " not found" - ) - return + ); + return; } if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] + var bit = field.value.bits[bit_id]; if (bit_id != field_id) - continue - bit.callback = callback - return + continue; + bit.callback = callback; + return; } - HIDDebug("ERROR: BIT NOT FOUND " + field_id) + HIDDebug("ERROR: BIT NOT FOUND " + field_id); } else { - field.callback = callback + field.callback = callback; } -} +}; /** Set 'ignored' flag for field to given value (true or false) * If field is ignored, it is not reported in 'delta' objects. */ HIDPacket.prototype.setIgnored = function(group, name, ignored) { - var field = this.getField(group, name) + var field = this.getField(group, name); if (field == undefined) { - HIDDebug("ERROR setting ignored flag for " + group + " " + name) - return + HIDDebug("ERROR setting ignored flag for " + group + " " + name); + return; } - field.ignored = ignored -} + field.ignored = ignored; +}; /** Adjust field's minimum delta value. * Input value changes smaller than this are not reported in delta */ HIDPacket.prototype.setMinDelta = function(group, name, mindelta) { - field = this.getField(group, name) + field = this.getField(group, name); if (field == undefined) { - HIDDebug("ERROR adjusting mindelta for " + group + " " + name) - return + HIDDebug("ERROR adjusting mindelta for " + group + " " + name); + return; } if (field.type == "bitvector") { - HIDDebug("ERROR setting mindelta for bitvector packet does not make sense") - return + HIDDebug("ERROR setting mindelta for bitvector packet does not make sense"); + return; } - field.mindelta = mindelta -} + field.mindelta = mindelta; +}; /** Parse bitvector field values, returning object with the named bits set. * Value must be a valid unsigned byte to parse, with enough bits. * Returns list of modified bits (delta) */ HIDPacket.prototype.parseBitVector = function(field, value) { - var bits = {} - var bit - var new_value + var bits = {}; + var bit; + var new_value; for (var bit_id in field.value.bits) { - bit = field.value.bits[bit_id] - new_value = (bit.bitmask & value) >> bit.bit_offset + bit = field.value.bits[bit_id]; + new_value = (bit.bitmask & value) >> bit.bit_offset; if (bit.value != undefined && bit.value != new_value) - bits[bit_id] = bit - bit.value = new_value + bits[bit_id] = bit; + bit.value = new_value; } - return bits -} + return bits; +}; /** Parse input packet fields from data. * Data is expected to be a Packet() received from HID device. * Returns list of changed fields with new value. * BitVectors are returned as bits you can iterate separately. */ HIDPacket.prototype.parse = function(data) { - var field_changes = {} - var group - var group_name - var field - var field_id + var field_changes = {}; + var group; + var group_name; + var field; + var field_id; for (group_name in this.groups) { - group = this.groups[group_name] + group = this.groups[group_name]; for (field_id in group) { - field = group[field_id] + field = group[field_id]; if (field == undefined) - continue + continue; - var value = this.unpack(data, field) + var value = this.unpack(data, field); if (value == undefined) { - HIDDebug("Error parsing packet field value for " + field_id) - return + HIDDebug("Error parsing packet field value for " + field_id); + return; } if (field.type == "bitvector") { // Bitvector deltas are checked in parseBitVector - var changed_bits = this.parseBitVector(field, value) + var changed_bits = this.parseBitVector(field, value); for (var bit_name in changed_bits) - field_changes[bit_name] = changed_bits[bit_name] + field_changes[bit_name] = changed_bits[bit_name]; } else if (field.type == "control") { if (field.value == value && field.mindelta != undefined) - continue + continue; if (field.ignored || field.value == undefined) { - field.value = value - continue + field.value = value; + continue; } - var change + var change; if (field.isEncoder) { if (field.value == field.max && value == field.min) { - change = 1 - field.delta = 1 + change = 1; + field.delta = 1; } else if (value == field.max && field.value == field.min) { - change = 1 - field.delta = -1 + change = 1; + field.delta = -1; } else { - change = 1 - field.delta = value - field.value + change = 1; + field.delta = value - field.value; } - field.value = value + field.value = value; } else { - change = Math.abs(value - field.value) - field.delta = value - field.value + change = Math.abs(value - field.value); + field.delta = value - field.value; } if (field.mindelta == undefined || change > field.mindelta) { - field_changes[field.name] = field - field.value = value + field_changes[field.name] = field; + field.value = value; } } } } - return field_changes -} + return field_changes; +}; /** Send this HID packet to device. * First the header bytes are copied to beginning of packet, then * field object values are packed to the HID packet according to the * field type. */ HIDPacket.prototype.send = function(debug) { - var data = [] + var data = []; if (this.header !== undefined) { for (header_byte = 0; header_byte < this.header.length; header_byte++) { - data[header_byte] = this.header[header_byte] + data[header_byte] = this.header[header_byte]; } } for (var group_name in this.groups) { - var group = this.groups[group_name] + var group = this.groups[group_name]; for (var field_name in group) { - this.pack(data, group[field_name]) + this.pack(data, group[field_name]); } } if (debug) { - var packet_string = "" + var packet_string = ""; for (var d in data) { if (data[d] < 0x10) { // Add padding for bytes smaller than 10 - packet_string += "0" + packet_string += "0"; } - packet_string += data[d].toString(16) + " " + packet_string += data[d].toString(16) + " "; } - HIDDebug("Sending packet with Report ID " + this.reportId + ": " + packet_string) - } - controller.send(data, data.length, this.reportId) -} + HIDDebug("Sending packet with Report ID " + this.reportId + ": " + packet_string); + } + controller.send(data, data.length, this.reportId); +}; + +// ActiveDecks = function () { +// this.activeDeckBitfield = undefined; +// this.numDecks = 0; +// } + +// // Hypothetically ActiveDecks could work with any multiple of two. In practice it'll either +// // be 2 or 4. +// ActiveDecks.setDeckCount = function (count) { +// this.numDecks = count; +// } + +// // Returns the deck number of the only active deck. +// ActiveDecks.getSingleActiveDeck { +// if (this.activeDeck === undefined) { +// return undefined; +// } + +// var activeCount = 0; +// var val = this.activeDeckBitfield; +// // Kernighan bitmask counting algorithm. It only +// // needs as many loop iterations as there are set values in the bitfield. +// while (val != 0) { +// val = val & (val - 1); +// ++activeCount; +// } +// if (activeCount === 0) { +// return undefined; +// } +// if (activeCount > 1) { +// HIDDebug("ERROR: expected only 1 active deck, got " + activeCount); +// return undefined; +// } +// return /** * HID Controller Class @@ -683,8 +735,9 @@ HIDPacket.prototype.send = function(debug) { * * initialized by default false, you should set this to true when * controller is found and everything is OK - * activeDeck by default undefined, used to map the virtual deck - * names 'deck','deck1' and 'deck2' to actual [ChannelX] + * activeDeck by default undefined, is list of ints that map + * names 'deck','deck1' and 'deck2' to actual [ChannelX]. + * Supports multiple switches, and each switch can select 2 or 4 decks. * isScratchEnabled set to true, when button 'jog_touch' is active * buttonStates valid state values for buttons, should contain fields * released (default 0) and pressed (default 1) @@ -708,43 +761,43 @@ HIDPacket.prototype.send = function(debug) { * scratchRampOnDisable If 'ramp' is used when disabling scratch */ HIDController = function() { - this.initialized = false - this.activeDeck = undefined + this.initialized = false; + this.activeDeck = undefined; - this.InputPackets = {} - this.OutputPackets = {} + this.InputPackets = {}; + this.OutputPackets = {}; // Default input control packet name: can be modified for controllers // which can swap modes (wiimote for example) - this.defaultPacket = "control" + this.defaultPacket = "control"; // Callback functions called by deck switching. Undefined by default - this.disconnectDeck = undefined - this.connectDeck = undefined + this.disconnectDeck = undefined; + this.connectDeck = undefined; // Scratch parameter defaults for this.scratchEnable function // override for custom control - this.isScratchEnabled = false - this.scratchintervalsPerRev = 128 - this.scratchRPM = 33 + 1 / 3 - this.scratchAlpha = 1.0 / 8 - this.scratchBeta = this.scratchAlpha / 32 - this.scratchRampOnEnable = false - this.scratchRampOnDisable = false + this.isScratchEnabled = false; + this.scratchintervalsPerRev = 128; + this.scratchRPM = 33 + 1 / 3; + this.scratchAlpha = 1.0 / 8; + this.scratchBeta = this.scratchAlpha / 32; + this.scratchRampOnEnable = false; + this.scratchRampOnDisable = false; // Button states available - this.buttonStates = {released: 0, pressed: 1} + this.buttonStates = {released: 0, pressed: 1}; // Output color values to send - this.LEDColors = {off: 0x0, on: 0x7f} + this.LEDColors = {off: 0x0, on: 0x7f}; // Toggle buttons this.toggleButtons = ["play", "pfl", "keylock", "quantize", "reverse", "slip_enabled", "group_[Channel1]_enable", "group_[Channel2]_enable", - "group_[Channel3]_enable", "group_[Channel4]_enable"] + "group_[Channel3]_enable", "group_[Channel4]_enable"]; // Override to set specific colors for multicolor button Output per deck - this.deckOutputColors = {1: "on", 2: "on", 3: "on", 4: "on"} + this.deckOutputColors = {1: "on", 2: "on", 3: "on", 4: "on"}; // Mapping of automatic deck switching with deckSwitch function - this.virtualDecks = ["deck", "deck1", "deck2", "deck3", "deck4"] - this.deckSwitchMap = {1: 2, 2: 1, 3: 4, 4: 3, undefined: 1} + this.virtualDecks = ["deck", "deck1", "deck2", "deck3", "deck4"]; + this.deckSwitchMap = {1: 2, 2: 1, 3: 4, 4: 3, undefined: 1}; // Standard target groups available in mixxx. This is used by // HID packet parser to recognize group parameters we should @@ -756,212 +809,217 @@ HIDController = function() { "[Master]", "[PreviewDeck1]", "[Effects]", "[Playlist]", "[Flanger]", "[Microphone]", "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit3]", "[EffectRack1_EffectUnit4]", - "[InternalClock]"] + "[InternalClock]"]; // Set to value in ms to update Outputs periodically - this.OutputUpdateInterval = undefined + this.OutputUpdateInterval = undefined; - this.modifiers = new HIDModifierList() - this.scalers = {} - this.timers = {} + this.modifiers = new HIDModifierList(); + this.scalers = {}; + this.timers = {}; - this.auto_repeat_interval = 100 -} + this.auto_repeat_interval = 100; +}; /** Function to close the controller object cleanly */ HIDController.prototype.close = function() { for (var name in this.timers) { - var timer = this.timers[name] + var timer = this.timers[name]; if (timer == undefined) - continue - engine.stopTimer(timer) - this.timers[name] = undefined + continue; + engine.stopTimer(timer); + this.timers[name] = undefined; } -} +}; /** Initialize our packet data and callbacks. This does not seem to * work when executed from here, but we keep a stub just in case. */ HIDController.prototype.initializePacketData = function() { -} +}; /** Return deck number from deck name. Deck name can't be virtual deck name * in this function call. */ HIDController.prototype.resolveDeck = function(group) { if (group == undefined) - return undefined - var result = group.match(/\[Channel[0-9]+\]/) + return undefined; + var result = group.match(/\[Channel[0-9]+\]/); if (!result) - return undefined - var str = group.replace(/\[Channel/, "") - return str.substring(0, str.length - 1) -} + return undefined; + var str = group.replace(/\[Channel/, ""); + return str.substring(0, str.length - 1); +}; /** Return the group name from given deck number. */ HIDController.prototype.resolveDeckGroup = function(deck) { if (deck == undefined) - return undefined - return "[Channel" + deck + "]" -} + return undefined; + return "[Channel" + deck + "]"; +}; /** Map virtual deck names to real deck group. If group is already * a real mixxx group value, just return it as it without mapping. */ HIDController.prototype.resolveGroup = function(group) { - var channel_name = /\[Channel[0-9]+\]/ - if (group != undefined && group.match(channel_name)) - return group + var channel_name = /\[Channel[0-9]+\]/; + if (group != undefined && group.match(channel_name)) { + return group; + } if (this.valid_groups.indexOf(group) != -1) { - return group + return group; } + // activeDeck could refer to more than one active deck per controller, + // so we return the lowest deck if (group == "deck" || group == undefined) { if (this.activeDeck == undefined) - return undefined - return "[Channel" + this.activeDeck + "]" + return undefined; + return "[Channel" + this.activeDeck + "]"; } + HIDDebug("here?? " + this.activeDeck); if (this.activeDeck == 1 || this.activeDeck == 2) { - if (group == "deck1") return "[Channel1]" - if (group == "deck2") return "[Channel2]" + if (group === "deck1") return "[Channel1]"; + if (group === "deck2") return "[Channel2]"; } if (this.activeDeck == 3 || this.activeDeck == 4) { - if (group == "deck1") return "[Channel3]" - if (group == "deck2") return "[Channel4]" + if (group === "deck1") return "[Channel3]"; + if (group === "deck2") return "[Channel4]"; } - return undefined -} + HIDDebug("whoop whoop undef"); + return undefined; +}; /** Find Output control matching give group and name * Returns undefined if output field can't be found. */ HIDController.prototype.getOutputField = function(m_group, m_name) { for (var packet_name in this.OutputPackets) { - var packet = this.OutputPackets[packet_name] + var packet = this.OutputPackets[packet_name]; for (var group_name in packet.groups) { - var group = packet.groups[group_name] + var group = packet.groups[group_name]; for (var field_name in group) { - var field = group[field_name] + var field = group[field_name]; if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] + var bit = field.value.bits[bit_id]; if (bit.mapped_group == m_group && bit.mapped_name == m_name) - return bit + return bit; if (bit.group == m_group && bit.name == m_name) - return bit + return bit; } - continue + continue; } if (field.mapped_group == m_group && field.mapped_name == m_name) - return field + return field; if (field.group == m_group && field.name == m_name) - return field + return field; } } } - return undefined -} + return undefined; +}; /** Find input packet matching given name. * Returns undefined if input packet name is not registered. */ HIDController.prototype.getInputPacket = function(name) { if (!(name in this.InputPackets)) - return undefined - return this.InputPackets[name] -} + return undefined; + return this.InputPackets[name]; +}; /** Find output packet matching given name * Returns undefined if output packet name is not registered. */ HIDController.prototype.getOutputPacket = function(name) { if (!(name in this.OutputPackets)) - return undefined - return this.OutputPackets[name] -} + return undefined; + return this.OutputPackets[name]; +}; /** Set input packet callback afterwards */ HIDController.prototype.setPacketCallback = function(packet, callback) { - var input_packet = this.getInputPacket(packet) - input_packet.callback = callback -} + var input_packet = this.getInputPacket(packet); + input_packet.callback = callback; +}; /** Register packet field's callback. * If packet has callback, it is still parsed but no field processing is done, * callback is called directly after unpacking fields from packet. */ HIDController.prototype.setCallback = function(packet, group, name, callback) { - var input_packet = this.getInputPacket(packet) + var input_packet = this.getInputPacket(packet); if (input_packet == undefined) { - HIDDebug("Input packet not found " + packet) - return + HIDDebug("Input packet not found " + packet); + return; } - input_packet.setCallback(group, name, callback) -} + input_packet.setCallback(group, name, callback); +}; /** Register scaling function for a control name * This does not check if given control name is valid */ HIDController.prototype.setScaler = function(name, callback) { if (name in this.scalers) - return - this.scalers[name] = callback -} + return; + this.scalers[name] = callback; +}; /** Lookup scaling function for control * Returns undefined if function is not registered. */ HIDController.prototype.getScaler = function(name, callback) { if (!(name in this.scalers)) - return undefined - return this.scalers[name] -} + return undefined; + return this.scalers[name]; +}; /** Change type of a previously defined field to modifier and register it */ HIDController.prototype.linkModifier = function(group, name, modifier) { - var packet = this.getInputPacket(this.defaultPacket) + var packet = this.getInputPacket(this.defaultPacket); if (packet == undefined) { HIDDebug( "ERROR creating modifier: input packet " + this.defaultPacket + " not found" - ) - return + ); + return; } - var bit_id = group + "." + name - var field = packet.lookupBit(group, name) + var bit_id = group + "." + name; + var field = packet.lookupBit(group, name); if (field == undefined) { - HIDDebug("BIT field not found: " + bit_id) - return + HIDDebug("BIT field not found: " + bit_id); + return; } - field.group = "modifiers" - field.name = modifier - this.modifiers.set(modifier) -} + field.group = "modifiers"; + field.name = modifier; + this.modifiers.set(modifier); +}; /** TODO - implement unlinking of modifiers */ HIDController.prototype.unlinkModifier = function(group, name, modifier) { - HIDDebug("Unlinking of modifiers not yet implemented") -} + HIDDebug("Unlinking of modifiers not yet implemented"); +}; /** Link a previously declared HID control to actual mixxx control */ HIDController.prototype.linkControl = function(group, name, m_group, m_name, callback) { - var field - var packet = this.getInputPacket(this.defaultPacket) + var field; + var packet = this.getInputPacket(this.defaultPacket); if (packet == undefined) { - HIDDebug("ERROR creating modifier: input packet " + this.defaultPacket + " not found") - return + HIDDebug("ERROR creating modifier: input packet " + this.defaultPacket + " not found"); + return; } - field = packet.getField(group, name) + field = packet.getField(group, name); if (field == undefined) { - HIDDebug("Field not found: " + group + "." + name) - return + HIDDebug("Field not found: " + group + "." + name); + return; } if (field.type == "bitvector") { - field = packet.lookupBit(group, name) + field = packet.lookupBit(group, name); if (field == undefined) { - HIDDebug("bit not found: " + group + "." + name) - return + HIDDebug("bit not found: " + group + "." + name); + return; } } - field.mapped_group = m_group - field.mapped_name = m_name + field.mapped_group = m_group; + field.mapped_name = m_name; if (callback != undefined) - field.callback = callback -} + field.callback = callback; +}; /** TODO - implement unlinking of controls */ HIDController.prototype.unlinkControl = function(group, name) { -} +}; /** Register HID input packet type to controller. * Input packets can be responses from device to queries, or control @@ -970,25 +1028,25 @@ HIDController.prototype.unlinkControl = function(group, name) { HIDController.prototype.registerInputPacket = function(packet) { // Find modifiers and other special cases from packet fields for (var group_name in packet.groups) { - var group = packet.groups[group_name] + var group = packet.groups[group_name]; for (var field_name in group) { - var field = group[field_name] - field.packet = packet + var field = group[field_name]; + field.packet = packet; if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] - bit.packet = packet + var bit = field.value.bits[bit_id]; + bit.packet = packet; if (bit.group == "modifiers") - this.modifiers.add(bit.name) + this.modifiers.add(bit.name); } } else { if (field.group == "modifiers") - this.modifiers.add(field.name) + this.modifiers.add(field.name); } } } - this.InputPackets[packet.name] = packet -} + this.InputPackets[packet.name] = packet; +}; /** Register HID output packet type to controller * There are no special Output control output packets, just register Outputs to any @@ -997,35 +1055,35 @@ HIDController.prototype.registerInputPacket = function(packet) { * If you need other data structures, patches are welcome, or you can just do it * manually in your script without registering the packet. */ HIDController.prototype.registerOutputPacket = function(packet) { - this.OutputPackets[packet.name] = packet + this.OutputPackets[packet.name] = packet; // Link packet to all fields for (var group_name in packet.groups) { - var group = packet.groups[group_name] + var group = packet.groups[group_name]; for (var field_name in group) { - var field = group[field_name] - field.packet = packet + var field = group[field_name]; + field.packet = packet; if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] - bit.packet = packet + var bit = field.value.bits[bit_id]; + bit.packet = packet; } } } } -} +}; /** Parse a received input packet fields with "unpack" calls to fields * Calls packet callback and returns, if packet callback was defined * Calls processIncomingPacket and processes automated events there. * If defined, calls processDelta for results after processing automated fields */ HIDController.prototype.parsePacket = function(data, length) { - var packet - var changed_data + var packet; + var changed_data; if (this.InputPackets == undefined) { - return + return; } for (var name in this.InputPackets) { - packet = this.InputPackets[name] + packet = this.InputPackets[name]; // When the device uses multiple report types with report IDs, hidapi // prepends the report ID to the data sent to Mixxx. If the device @@ -1033,37 +1091,37 @@ HIDController.prototype.parsePacket = function(data, length) { // reportId as 0. In this case, hidapi only sends the data of the // report to Mixxx without a report ID. if (packet.reportId !== 0 && packet.reportId !== data[0]) { - continue + continue; } if (packet.header !== undefined) { for (var header_byte = 0; header_byte < packet.header.length; header_byte++) { if (packet.header[header_byte] != data[header_byte]) { - packet = undefined - break + packet = undefined; + break; } } if (packet === undefined) - continue + continue; } - changed_data = packet.parse(data) + changed_data = packet.parse(data); if (packet.callback != undefined) { - packet.callback(packet, changed_data) - return + packet.callback(packet, changed_data); + return; } // Process named group controls if (packet.name == this.defaultPacket) - this.processIncomingPacket(packet, changed_data) + this.processIncomingPacket(packet, changed_data); // Process generic changed_data packet, if callback is defined if (this.processDelta != undefined) - this.processDelta(packet, changed_data) + this.processDelta(packet, changed_data); if (this.postProcessDelta != undefined) - this.postProcessDelta(packet, changed_data) - return + this.postProcessDelta(packet, changed_data); + return; } - HIDDebug("Received unknown packet of " + length + " bytes") - for (var i in data) HIDDebug("BYTE " + data[i]) -} + HIDDebug("Received unknown packet of " + length + " bytes"); + for (var i in data) HIDDebug("BYTE " + data[i]); +}; /** Process the modified field values (delta) from input packet fields for * input control packet, if packet name is in this.defaultPacket. @@ -1083,177 +1141,177 @@ HIDController.prototype.parsePacket = function(data, length) { * - Finally tries run matching engine.setValue() function for control * fields in default mixxx groups. Not done if a callback was defined. */ HIDController.prototype.processIncomingPacket = function(packet, delta) { - var field + var field; for (var name in delta) { if (this.ignoredControlChanges != undefined && this.ignoredControlChanges.indexOf(name) != -1) - continue - field = delta[name] + continue; + field = delta[name]; if (field.type == "button") - this.processButton(field) + this.processButton(field); else if (field.type == "control") - this.processControl(field) + this.processControl(field); else - HIDDebug("Unknown field " + field.name + " type " + field.type) + HIDDebug("Unknown field " + field.name + " type " + field.type); } -} +}; /** Get active group for this field */ HIDController.prototype.getActiveFieldGroup = function(field) { if (field.mapped_group != undefined) { - return this.resolveGroup(field.mapped_group) + return this.resolveGroup(field.mapped_group); } - group = field.group + group = field.group; if (group == undefined) { if (this.activeDeck != undefined) - return "[Channel" + this.activeDeck + "]" + return "[Channel" + this.activeDeck + "]"; } if (this.valid_groups.indexOf(group) != -1) { //HIDDebug("Resolving group " + group); - return this.resolveGroup(group) + return this.resolveGroup(group); } - return group -} + return group; +}; /** Get active control name from field */ HIDController.prototype.getActiveFieldControl = function(field) { if (field.mapped_name != undefined) - return field.mapped_name - return field.name -} + return field.mapped_name; + return field.name; +}; /** Process given button field, triggering events */ HIDController.prototype.processButton = function(field) { - var group = this.getActiveFieldGroup(field) - var control = this.getActiveFieldControl(field) + var group = this.getActiveFieldGroup(field); + var control = this.getActiveFieldControl(field); if (group == undefined) { HIDDebug("processButton: Could not resolve group from " + field.group + " " + field.mapped_group + " " + field.name + " " + field.mapped_name - ) - return + ); + return; } if (group == "modifiers") { if (field.value != 0) - this.modifiers.set(control, true) + this.modifiers.set(control, true); else - this.modifiers.set(control, false) - return + this.modifiers.set(control, false); + return; } if (field.auto_repeat) { - timer_id = "auto_repeat_" + field.id + timer_id = "auto_repeat_" + field.id; if (field.value) { - this.startAutoRepeatTimer(timer_id, field.auto_repeat_interval) + this.startAutoRepeatTimer(timer_id, field.auto_repeat_interval); } else { - this.stopAutoRepeatTimer(timer_id) + this.stopAutoRepeatTimer(timer_id); } } if (field.callback != undefined) { - field.callback(field) - return + field.callback(field); + return; } if (control == "jog_touch") { if (group != undefined) { if (field.value == this.buttonStates.pressed) - this.enableScratch(group, true) + this.enableScratch(group, true); else - this.enableScratch(group, false) + this.enableScratch(group, false); } - return + return; } if (this.toggleButtons.indexOf(control) != -1) { if (field.value == this.buttonStates.released) - return + return; if (engine.getValue(group, control)) { if (control == "play") - engine.setValue(group, "stop", true) + engine.setValue(group, "stop", true); else - engine.setValue(group, control, false) + engine.setValue(group, control, false); } else { - engine.setValue(group, control, true) + engine.setValue(group, control, true); } - return + return; } if (field.auto_repeat && field.value == this.buttonStates.pressed) { - HIDDebug("Callback for " + field.group) - engine.setValue(group, control, field.auto_repeat(field)) + HIDDebug("Callback for " + field.group); + engine.setValue(group, control, field.auto_repeat(field)); } else if (engine.getValue(group, control) == false) { - engine.setValue(group, control, true) + engine.setValue(group, control, true); } else { - engine.setValue(group, control, false) + engine.setValue(group, control, false); } -} +}; /** Process given control field, triggering events */ HIDController.prototype.processControl = function(field) { - var value - var group = this.getActiveFieldGroup(field) - var control = this.getActiveFieldControl(field) + var value; + var group = this.getActiveFieldGroup(field); + var control = this.getActiveFieldControl(field); if (group == undefined) { HIDDebug("processControl: Could not resolve group from " + field.group + " " + field.mapped_group + " " + field.name + " " + field.mapped_name - ) - return + ); + return; } if (field.callback != undefined) { - value = field.callback(field) - return + value = field.callback(field); + return; } if (group == "modifiers") { - this.modifiers.set(control, field.value) - return + this.modifiers.set(control, field.value); + return; } if (control == "jog_wheel") { // Handle jog wheel scratching transparently - this.jog_wheel(field) - return + this.jog_wheel(field); + return; } // Call value scaler if defined and send mixxx signal - value = field.value - scaler = this.getScaler(control) + value = field.value; + scaler = this.getScaler(control); if (field.isEncoder) { - var field_delta = field.delta + var field_delta = field.delta; if (scaler != undefined) - field_delta = scaler(group, control, field_delta) - engine.setValue(group, control, field_delta) + field_delta = scaler(group, control, field_delta); + engine.setValue(group, control, field_delta); } else { if (scaler != undefined) { - value = scaler(group, control, value) + value = scaler(group, control, value); // See the Traktor S4 script for how to use this. If the scaler function has this // parameter set to true, we use the effects-engine setParameter call instead of // setValue. if (scaler.useSetParameter) { - engine.setParameter(group, control, value) - return + engine.setParameter(group, control, value); + return; } } - engine.setValue(group, control, value) + engine.setValue(group, control, value); } -} +}; /** Toggle control state from toggle button */ HIDController.prototype.toggle = function(group, control, value) { if (value == this.buttonStates.released) - return - var status = (engine.getValue(group, control) == true) ? false : true - engine.setValue(group, control, status) -} + return; + var status = engine.getValue(group, control) != true; + engine.setValue(group, control, status); +}; /** Toggle play/pause state */ HIDController.prototype.togglePlay = function(group, field) { if (field.value == this.buttonStates.released) - return - var status = (engine.getValue(group, "play")) ? false : true + return; + var status = !(engine.getValue(group, "play")); if (!status) - engine.setValue(group, "stop", true) + engine.setValue(group, "stop", true); else - engine.setValue(group, "play", true) -} + engine.setValue(group, "play", true); +}; /** Processing of the 'jog_touch' special button name, which is used to detect * when scratching should be enabled. @@ -1267,23 +1325,23 @@ HIDController.prototype.togglePlay = function(group, field) { * Sets the internal 'isScratchEnabled attribute to false, and calls scratchDisable * to end scratching mode */ HIDController.prototype.enableScratch = function(group, status) { - var deck = this.resolveDeck(group) + var deck = this.resolveDeck(group); if (status) { - this.isScratchEnabled = true + this.isScratchEnabled = true; engine.scratchEnable(deck, this.scratchintervalsPerRev, this.scratchRPM, this.scratchAlpha, this.scratchBeta, this.rampedScratchEnable - ) - if (this.enableScratchCallback != undefined) this.enableScratchCallback(true) + ); + if (this.enableScratchCallback != undefined) this.enableScratchCallback(true); } else { - this.isScratchEnabled = false - engine.scratchDisable(deck, this.rampedScratchDisable) - if (this.enableScratchCallback != undefined) this.enableScratchCallback(false) + this.isScratchEnabled = false; + engine.scratchDisable(deck, this.rampedScratchDisable); + if (this.enableScratchCallback != undefined) this.enableScratchCallback(false); } -} +}; /** Default jog scratching function. Used to handle jog move events from special * input control field called 'jog_wheel'. Handles both 'scratch' and 'jog' mixxx @@ -1302,232 +1360,232 @@ HIDController.prototype.enableScratch = function(group, status) { * both negative and positive values. */ HIDController.prototype.jog_wheel = function(field) { - var scaler = undefined - var active_group = this.getActiveFieldGroup(field) - var value = undefined + var scaler = undefined; + var active_group = this.getActiveFieldGroup(field); + var value = undefined; if (field.isEncoder) - value = field.delta + value = field.delta; else - value = field.value + value = field.value; if (this.isScratchEnabled) { - var deck = this.resolveDeck(active_group) + var deck = this.resolveDeck(active_group); if (deck == undefined) - return - scaler = this.getScaler("jog_scratch") + return; + scaler = this.getScaler("jog_scratch"); if (scaler != undefined) - value = scaler(active_group, "jog_scratch", value) + value = scaler(active_group, "jog_scratch", value); else - HIDDebug("WARNING non jog_scratch scaler, you likely want one") - engine.scratchTick(deck, value) + HIDDebug("WARNING non jog_scratch scaler, you likely want one"); + engine.scratchTick(deck, value); } else { if (active_group == undefined) - return - scaler = this.getScaler("jog") + return; + scaler = this.getScaler("jog"); if (scaler != undefined) - value = scaler(active_group, "jog", value) + value = scaler(active_group, "jog", value); else - HIDDebug("WARNING non jog scaler, you likely want one") - engine.setValue(active_group, "jog", value) + HIDDebug("WARNING non jog scaler, you likely want one"); + engine.setValue(active_group, "jog", value); } -} +}; HIDController.prototype.stopAutoRepeatTimer = function(timer_id) { if (this.timers[timer_id]) { - engine.stopTimer(this.timers[timer_id]) - delete this.timers[timer_id] + engine.stopTimer(this.timers[timer_id]); + delete this.timers[timer_id]; } else { //HIDDebug("No such autorepeat timer: " + timer_id); } -} +}; /** Toggle field autorepeat on or off */ HIDController.prototype.setAutoRepeat = function(group, name, callback, interval) { - var packet = this.getInputPacket(this.defaultPacket) - var field = packet.getField(group, name) + var packet = this.getInputPacket(this.defaultPacket); + var field = packet.getField(group, name); if (field == undefined) { - HIDDebug("setAutoRepeat: field not found " + group + "." + name) - return + HIDDebug("setAutoRepeat: field not found " + group + "." + name); + return; } - field.auto_repeat = callback + field.auto_repeat = callback; if (interval) - field.auto_repeat_interval = interval + field.auto_repeat_interval = interval; else - field.auto_repeat_interval = controller.auto_repeat_interval + field.auto_repeat_interval = controller.auto_repeat_interval; if (callback) - callback(field) -} + callback(field); +}; /** Callback for auto repeat timer to send again the values for * buttons and controls marked as 'auto_repeat' * Timer must be defined from actual controller side, because of * callback call namespaces and 'this' reference */ HIDController.prototype.autorepeatTimer = function() { - var group_name - var group - var field - var field_name - var bit_name - var bit - var packet = this.InputPackets[this.defaultPacket] + var group_name; + var group; + var field; + var field_name; + var bit_name; + var bit; + var packet = this.InputPackets[this.defaultPacket]; for (group_name in packet.groups) { - group = packet.groups[group_name] + group = packet.groups[group_name]; for (field_name in group) { - field = group[field_name] + field = group[field_name]; if (field.type != "bitvector") { if (field.auto_repeat) - this.processControl(field) - continue + this.processControl(field); + continue; } for (bit_name in field.value.bits) { - bit = field.value.bits[bit_name] + bit = field.value.bits[bit_name]; if (bit.auto_repeat) - this.processButton(bit) + this.processButton(bit); } } } -} +}; /** Toggle active deck and update virtual output field control mappings */ HIDController.prototype.switchDeck = function(deck) { - var packet - var field - var controlgroup + var packet; + var field; + var controlgroup; if (deck == undefined) { if (this.activeDeck == undefined) { - deck = 1 + deck = 1; } else { // This is unusable: num_decks has always minimum 4 decks // var totalDecks = engine.getValue("[Master]","num_decks"); // deck = (this.activeDeck+1) % totalDecks; - deck = this.deckSwitchMap[this.activeDeck] + deck = this.deckSwitchMap[this.activeDeck]; if (deck == undefined) - deck = 1 + deck = 1; } } - new_group = this.resolveDeckGroup(deck) - HIDDebug("Switching to deck " + deck + " group " + new_group) + new_group = this.resolveDeckGroup(deck); + HIDDebug("Switching to deck " + deck + " group " + new_group); if (this.disconnectDeck != undefined) - this.disconnectDeck() + this.disconnectDeck(); for (var packet_name in this.OutputPackets) { - packet = this.OutputPackets[packet_name] - var send_packet = false + packet = this.OutputPackets[packet_name]; + var send_packet = false; for (var group_name in packet.groups) { - var group = packet.groups[group_name] + var group = packet.groups[group_name]; for (var field_name in group) { - field = group[field_name] + field = group[field_name]; if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id] + var bit = field.value.bits[bit_id]; if (this.virtualDecks.indexOf(bit.mapped_group) == -1) - continue - send_packet = true - controlgroup = this.resolveGroup(bit.mapped_group) - engine.connectControl(controlgroup, bit.mapped_name, bit.mapped_callback, true) - engine.connectControl(new_group, bit.mapped_name, bit.mapped_callback) - var value = engine.getValue(new_group, bit.mapped_name) - HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value) + continue; + send_packet = true; + controlgroup = this.resolveGroup(bit.mapped_group); + engine.connectControl(controlgroup, bit.mapped_name, bit.mapped_callback, true); + engine.connectControl(new_group, bit.mapped_name, bit.mapped_callback); + var value = engine.getValue(new_group, bit.mapped_name); + HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value); if (value) this.setOutput( bit.group, bit.name, this.LEDColors[this.deckOutputColors[deck]] - ) + ); else this.setOutput( bit.group, bit.name, this.LEDColors.off - ) + ); } - continue + continue; } // Only move outputs of virtual decks if (this.virtualDecks.indexOf(field.mapped_group) == -1) - continue - send_packet = true - controlgroup = this.resolveGroup(field.mapped_group) - engine.connectControl(controlgroup, field.mapped_name, field.mapped_callback, true) - engine.connectControl(new_group, field.mapped_name, field.mapped_callback) - var value = engine.getValue(new_group, field.mapped_name) + continue; + send_packet = true; + controlgroup = this.resolveGroup(field.mapped_group); + engine.connectControl(controlgroup, field.mapped_name, field.mapped_callback, true); + engine.connectControl(new_group, field.mapped_name, field.mapped_callback); + var value = engine.getValue(new_group, field.mapped_name); if (value) this.setOutput( field.group, field.name, this.LEDColors[this.deckOutputColors[deck]] - ) + ); else this.setOutput( field.group, field.name, this.LEDColors.off - ) + ); } } } - this.activeDeck = deck + this.activeDeck = deck; if (this.connectDeck != undefined) - this.connectDeck() -} + this.connectDeck(); +}; /** Link a virtual HID Output to mixxx control */ HIDController.prototype.linkOutput = function(group, name, m_group, m_name, callback) { - var controlgroup - var field = this.getOutputField(group, name) + var controlgroup; + var field = this.getOutputField(group, name); if (field == undefined) { - HIDDebug("Linked output not found: " + group + "." + name) - return + HIDDebug("Linked output not found: " + group + "." + name); + return; } if (field.mapped_group != undefined) { - HIDDebug("Output already linked: " + field.mapped_group) - return - } - controlgroup = this.resolveGroup(m_group) - field.mapped_group = m_group - field.mapped_name = m_name - field.mapped_callback = callback - engine.connectControl(controlgroup, m_name, callback) + HIDDebug("Output already linked: " + field.mapped_group); + return; + } + controlgroup = this.resolveGroup(m_group); + field.mapped_group = m_group; + field.mapped_name = m_name; + field.mapped_callback = callback; + engine.connectControl(controlgroup, m_name, callback); if (engine.getValue(controlgroup, m_name)) - this.setOutput(m_group, m_name, "on") + this.setOutput(m_group, m_name, "on"); else - this.setOutput(m_group, m_name, "off") -} + this.setOutput(m_group, m_name, "off"); +}; /** Unlink a virtual HID Output from mixxx control */ HIDController.prototype.unlinkOutput = function(group, name, callback) { - var field = this.getOutputField(group, name) - var controlgroup + var field = this.getOutputField(group, name); + var controlgroup; if (field == undefined) { - HIDDebug("Unlinked output not found: " + group + "." + name) - return + HIDDebug("Unlinked output not found: " + group + "." + name); + return; } if (field.mapped_group == undefined || field.mapped_name == undefined) { - HIDDebug("Unlinked output not mapped: " + group + "." + name) - return + HIDDebug("Unlinked output not mapped: " + group + "." + name); + return; } - controlgroup = this.resolveGroup(field.mapped_group) - engine.connectControl(controlgroup, field.mapped_name, callback, true) - field.mapped_group = undefined - field.mapped_name = undefined - field.mapped_callback = undefined -} + controlgroup = this.resolveGroup(field.mapped_group); + engine.connectControl(controlgroup, field.mapped_name, callback, true); + field.mapped_group = undefined; + field.mapped_name = undefined; + field.mapped_callback = undefined; +}; /** Set output state to given value */ HIDController.prototype.setOutput = function(group, name, value, send_packet) { - var field = this.getOutputField(group, name) + var field = this.getOutputField(group, name); if (field == undefined) { - HIDDebug("setOutput: unknown field: " + group + "." + name) - return + HIDDebug("setOutput: unknown field: " + group + "." + name); + return; } - field.value = value << field.bit_offset - field.toggle = value << field.bit_offset + field.value = value << field.bit_offset; + field.toggle = value << field.bit_offset; if (send_packet) - field.packet.send() -} + field.packet.send(); +}; /** Set Output to toggle between two values. Reset with setOutput(name,'off') */ HIDController.prototype.setOutputToggle = function(group, name, toggle_value) { - var field = this.getOutputField(group, name) + var field = this.getOutputField(group, name); if (field == undefined) { - HIDDebug("setOutputToggle: unknown field " + group + "." + name) - return + HIDDebug("setOutputToggle: unknown field " + group + "." + name); + return; } - field.value = toggle_value << field.bit_offset - field.toggle = toggle_value << field.bit_offset - field.packet.send() -} + field.value = toggle_value << field.bit_offset; + field.toggle = toggle_value << field.bit_offset; + field.packet.send(); +}; From 297537a3e559889a6745ec4e78851a205d841e46 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 27 Jul 2020 08:15:12 -0500 Subject: [PATCH 03/84] HidController: loop until no more messages are available on poll Otherwise, it seems messages build up in a queue which produces a dramatic lag on Linux when moving faders quickly. This regression was introduced between Mixxx 2.2 and 2.3 beta, likely by switching HidController to nonblocking polling in PR #2179. --- src/controllers/hid/hidcontroller.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index 08bff7ef126..3e9c720d4a6 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -243,13 +243,16 @@ int HidController::close() { bool HidController::poll() { Trace hidRead("HidController poll"); - int result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0])); - if (result == -1) { - return false; - } else if (result > 0) { - Trace process("HidController process packet"); - QByteArray outData(reinterpret_cast(m_pPollData), result); - receive(outData, mixxx::Time::elapsed()); + int result = 1; + while (result > 0) { + result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0])); + if (result == -1) { + return false; + } else if (result > 0) { + Trace process("HidController process packet"); + QByteArray outData(reinterpret_cast(m_pPollData), result); + receive(outData, mixxx::Time::elapsed()); + } } return true; From 1ac6500478e33d326e2e8d8a0e6c53ed74407853 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 27 Jul 2020 08:41:45 -0500 Subject: [PATCH 04/84] HidController: do not copy incoming data before processing it --- src/controllers/hid/hidcontroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index 3e9c720d4a6..5fc6e621974 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -250,8 +250,8 @@ bool HidController::poll() { return false; } else if (result > 0) { Trace process("HidController process packet"); - QByteArray outData(reinterpret_cast(m_pPollData), result); - receive(outData, mixxx::Time::elapsed()); + auto data = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); + receive(data, mixxx::Time::elapsed()); } } From 6fcecb5594c02e908b9e66641d85cc6b3b5e4a53 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Fri, 31 Jul 2020 17:48:20 -0400 Subject: [PATCH 05/84] Traktor S3: Fix most connections for deck switching --- .../Traktor-Kontrol-S3-hid-scripts.js | 200 ++++++++++-------- res/controllers/common-hid-packet-parser.js | 74 +------ 2 files changed, 114 insertions(+), 160 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c1dedd4a63e..12c3905baa7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -15,6 +15,15 @@ /* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ /* */ /////////////////////////////////////////////////////////////////////////////////// +/* */ +/* TODO: */ +/* * touch for track browse, loop control, beatjump */ +/* * jog button */ +/* * sync latch */ +/* * quantize */ +/* */ +/////////////////////////////////////////////////////////////////////////////////// + var TraktorS3 = new function() { this.controller = new HIDController(); @@ -78,18 +87,12 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - //TraktorS3.debugLights(); + // TraktorS3.debugLights(); }; TraktorS3.registerInputPackets = function() { - var messageShort = new HIDPacket(TraktorS3.controller, "shortmessage", 0x01, this.messageCallback); - var messageLong = new HIDPacket(TraktorS3.controller, "longmessage", 0x02, this.messageCallback); - - // Establish all of the groups - messageShort.registerGroup("[Channel1]"); - messageShort.registerGroup("[Channel2]"); - messageShort.registerGroup("[Channel3]"); - messageShort.registerGroup("[Channel4]"); + var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); + var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, this.deckSwitchHandler); @@ -314,45 +317,50 @@ TraktorS3.deckSwitchHandler = function(field) { }; TraktorS3.playHandler = function(field) { - HIDDebug("group?? " + field.group); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "start_stop", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + HIDDebug("group?? " + activeGroup); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "start_stop", field.value); } else if (field.value === 1) { HIDDebug("sooooo play?"); - script.toggleControl(group, "play"); + script.toggleControl(activeGroup, "play"); } }; TraktorS3.cueHandler = function(field) { - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "cue_gotoandstop", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "cue_gotoandstop", field.value); } else { - engine.setValue(field.group, "cue_default", field.value); + engine.setValue(activeGroup, "cue_default", field.value); } }; TraktorS3.shiftHandler = function(field) { - HIDDebug("SHIFT!" + field.group + " " + field.value); - TraktorS3.shiftPressed[field.group] = field.value; + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + HIDDebug("SHIFT!" + activeGroup + " " + field.value); + TraktorS3.shiftPressed[activeGroup] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); TraktorS3.outputHandler(field.value, field.group, "shift"); }; TraktorS3.keylockHandler = function(field) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); if (field.value === 0) { return; } - script.toggleControl(field.group, "keylock"); + script.toggleControl(activeGroup, "keylock"); }; TraktorS3.syncHandler = function(field) { - if (TraktorS3.shiftPressed[field.group]) { - // engine.setValue(field.group, "sync_enabled", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + // engine.setValue(activeGroup, "sync_enabled", field.value); } else if (field.value === 1) { //TODO: latching not working? - engine.setValue(field.group, "sync_enabled", field.value); + engine.setValue(activeGroup, "sync_enabled", field.value); } }; @@ -360,30 +368,31 @@ TraktorS3.padModeHandler = function(field) { if (field.value === 0) { return; } + var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.padModeState[field.group] === 0 && field.name === "!samples") { + if (TraktorS3.padModeState[activeGroup] === 0 && field.name === "!samples") { // If we are in hotcues mode and samples mode is activated engine.setValue("[Samplers]", "show_samplers", 1); - TraktorS3.padModeState[field.group] = 1; + TraktorS3.padModeState[activeGroup] = 1; TraktorS3.outputHandler(0, field.group, "hotcues"); TraktorS3.outputHandler(1, field.group, "samples"); // Light LEDs for all slots with loaded samplers - for (var key in TraktorS3.samplerHotcuesRelation[field.group]) { - if (TraktorS3.samplerHotcuesRelation[field.group].hasOwnProperty(key)) { - var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[field.group][key] + "]", "track_loaded"); + for (var key in TraktorS3.samplerHotcuesRelation[activeGroup]) { + if (TraktorS3.samplerHotcuesRelation[activeGroup].hasOwnProperty(key)) { + var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[activeGroup][key] + "]", "track_loaded"); TraktorS3.outputHandler(loaded, field.group, "pad_" + key); } } } else if (field.name === "!hotcues") { // If we are in samples mode and hotcues mode is activated - TraktorS3.padModeState[field.group] = 0; + TraktorS3.padModeState[activeGroup] = 0; TraktorS3.outputHandler(1, field.group, "hotcues"); TraktorS3.outputHandler(0, field.group, "samples"); // Light LEDs for all enabled hotcues for (var i = 1; i <= 8; ++i) { - var active = engine.getValue(field.group, "hotcue_" + i + "_enabled"); + var active = engine.getValue(activeGroup, "hotcue_" + i + "_enabled"); TraktorS3.outputHandler(active, field.group, "pad_" + i); } } @@ -391,17 +400,18 @@ TraktorS3.padModeHandler = function(field) { TraktorS3.numberButtonHandler = function(field) { var padNumber = parseInt(field.id[field.id.length - 1]); - if (TraktorS3.padModeState[field.group] === 0) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.padModeState[activeGroup] === 0) { // Hotcues mode - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "hotcue_" + padNumber + "_clear", field.value); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "hotcue_" + padNumber + "_clear", field.value); } else { - engine.setValue(field.group, "hotcue_" + padNumber + "_activate", field.value); + engine.setValue(activeGroup, "hotcue_" + padNumber + "_activate", field.value); } } else { // Samples mode - var sampler = TraktorS3.samplerHotcuesRelation[field.group][padNumber]; - if (TraktorS3.shiftPressed[field.group]) { + var sampler = TraktorS3.samplerHotcuesRelation[activeGroup][padNumber]; + if (TraktorS3.shiftPressed[activeGroup]) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { engine.setValue("[Sampler" + sampler + "]", "cue_default", field.value); @@ -423,38 +433,42 @@ TraktorS3.headphoneHandler = function(field) { if (field.value === 0) { return; } + var activeGroup = TraktorS3.controller.resolveGroup(field.group); - script.toggleControl(field.group, "pfl"); + script.toggleControl(activeGroup, "pfl"); }; TraktorS3.selectTrackHandler = function(field) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); var delta = 1; - if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[field.group]) { + if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[activeGroup]) { delta = -1; } - if (TraktorS3.shiftPressed[field.group]) { + if (TraktorS3.shiftPressed[activeGroup]) { engine.setValue("[Library]", "MoveHorizontal", delta); } else { engine.setValue("[Library]", "MoveVertical", delta); } - TraktorS3.browseKnobEncoderState[field.group] = field.value; + TraktorS3.browseKnobEncoderState[activeGroup] = field.value; }; TraktorS3.loadTrackHandler = function(field) { HIDDebug("LOAD TRACK!!!"); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "eject", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "eject", field.value); } else { - engine.setValue(field.group, "LoadSelectedTrack", field.value); + engine.setValue(activeGroup, "LoadSelectedTrack", field.value); } }; TraktorS3.addTrackHandler = function(field) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); TraktorS3.outputHandler(field.value, field.group, "addTrack"); - if (TraktorS3.shiftPressed[field.group]) { + if (TraktorS3.shiftPressed[activeGroup]) { engine.setValue("[Library]", "AutoDjAddTop", field.value); } else { engine.setValue("[Library]", "AutoDjAddBottom", field.value); @@ -470,56 +484,60 @@ TraktorS3.maximizeLibraryHandler = function(field) { }; TraktorS3.selectLoopHandler = function(field) { - if ((field.value + 1) % 16 === TraktorS3.loopKnobEncoderState[field.group]) { - script.triggerControl(field.group, "loop_halve"); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if ((field.value + 1) % 16 === TraktorS3.loopKnobEncoderState[activeGroup]) { + script.triggerControl(activeGroup, "loop_halve"); } else { - script.triggerControl(field.group, "loop_double"); + script.triggerControl(activeGroup, "loop_double"); } - TraktorS3.loopKnobEncoderState[field.group] = field.value; + TraktorS3.loopKnobEncoderState[activeGroup] = field.value; }; TraktorS3.activateLoopHandler = function(field) { if (field.value === 0) { return; } + var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "reloop_toggle", field.value); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "reloop_toggle", field.value); } else { - engine.setValue(field.group, "beatloop_activate", field.value); + engine.setValue(activeGroup, "beatloop_activate", field.value); } }; TraktorS3.selectBeatjumpHandler = function(field) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); var delta = 1; - if ((field.value + 1) % 16 === TraktorS3.moveKnobEncoderState[field.group]) { + if ((field.value + 1) % 16 === TraktorS3.moveKnobEncoderState[activeGroup]) { delta = -1; } - if (TraktorS3.shiftPressed[field.group]) { - var beatjump_size = engine.getValue(field.group, "beatjump_size"); + if (TraktorS3.shiftPressed[activeGroup]) { + var beatjump_size = engine.getValue(activeGroup, "beatjump_size"); if (delta > 0) { - engine.setValue(field.group, "beatjump_size", beatjump_size * 2); + engine.setValue(activeGroup, "beatjump_size", beatjump_size * 2); } else { - engine.setValue(field.group, "beatjump_size", beatjump_size / 2); + engine.setValue(activeGroup, "beatjump_size", beatjump_size / 2); } } else { if (delta < 0) { - script.triggerControl(field.group, "beatjump_backward"); + script.triggerControl(activeGroup, "beatjump_backward"); } else { - script.triggerControl(field.group, "beatjump_forward"); + script.triggerControl(activeGroup, "beatjump_forward"); } } - TraktorS3.moveKnobEncoderState[field.group] = field.value; + TraktorS3.moveKnobEncoderState[activeGroup] = field.value; }; TraktorS3.activateBeatjumpHandler = function(field) { - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "reloop_andstop", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "reloop_andstop", field.value); } else { - engine.setValue(field.group, "beatlooproll_activate", field.value); + engine.setValue(activeGroup, "beatlooproll_activate", field.value); } }; @@ -527,6 +545,7 @@ TraktorS3.quantizeHandler = function(field) { if (field.value === 0) { return; } + var activeGroup = TraktorS3.controller.resolveGroup(field.group); // TODO: fix quantize var res = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); @@ -560,7 +579,8 @@ TraktorS3.microphoneHandler = function(field) { }; TraktorS3.parameterHandler = function(field) { - engine.setParameter(field.group, field.name, field.value / 4095); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + engine.setParameter(activeGroup, field.name, field.value / 4095); }; TraktorS3.samplerPregainHandler = function(field) { @@ -572,10 +592,11 @@ TraktorS3.samplerPregainHandler = function(field) { }; TraktorS3.jogTouchHandler = function(field) { - if (TraktorS3.wheelTouchInertiaTimer[field.group] != 0) { + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.wheelTouchInertiaTimer[activeGroup] != 0) { // The wheel was touched again, reset the timer. - engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[field.group]); - TraktorS3.wheelTouchInertiaTimer[field.group] = 0; + engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[activeGroup]); + TraktorS3.wheelTouchInertiaTimer[activeGroup] = 0; } if (field.value !== 0) { var deckNumber = TraktorS3.controller.resolveDeck(group); @@ -583,28 +604,29 @@ TraktorS3.jogTouchHandler = function(field) { } else { // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. // Depending on how fast the platter was moving, lengthen the time we'll wait. - var scratchRate = Math.abs(engine.getValue(field.group, "scratch2")); + var scratchRate = Math.abs(engine.getValue(activeGroup, "scratch2")); // Note: inertiaTime multiplier is controller-specific and should be factored out. var inertiaTime = Math.pow(1.8, scratchRate) * 2; if (inertiaTime < 100) { // Just do it now. - TraktorS3.finishJogTouch(field.group); + TraktorS3.finishJogTouch(activeGroup); } else { - TraktorS3.controller.wheelTouchInertiaTimer[field.group] = engine.beginTimer( - inertiaTime, "TraktorS3.finishJogTouch(\"" + field.group + "\")", true); + TraktorS3.controller.wheelTouchInertiaTimer[activeGroup] = engine.beginTimer( + inertiaTime, "TraktorS3.finishJogTouch(\"" + activeGroup + "\")", true); } } }; TraktorS3.jogHandler = function(field) { - var deltas = TraktorS3.wheelDeltas(field.group, field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); var tick_delta = deltas[0]; var time_delta = deltas[1]; var velocity = TraktorS3.scalerJog(tick_delta, time_delta); HIDDebug("VELO: " + velocity); - engine.setValue(field.group, "jog", velocity); - if (engine.getValue(field.group, "scratch2_enable")) { + engine.setValue(activeGroup, "jog", velocity); + if (engine.getValue(activeGroup, "scratch2_enable")) { var deckNumber = TraktorS3.controller.resolveDeck(group); engine.scratchTick(deckNumber, tick_delta); } @@ -707,10 +729,11 @@ TraktorS3.fxHandler = function(field) { }; TraktorS3.reverseHandler = function(field) { - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "reverseroll", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "reverseroll", field.value); } else { - engine.setValue(field.group, "reverse", field.value); + engine.setValue(activeGroup, "reverse", field.value); } TraktorS3.outputHandler(field.value, field.group, "reverse"); @@ -720,15 +743,17 @@ TraktorS3.fluxHandler = function(field) { if (field.value === 0) { return; } + var activeGroup = TraktorS3.controller.resolveGroup(field.group); - script.toggleControl(field.group, "slip_enabled"); + script.toggleControl(activeGroup, "slip_enabled"); }; TraktorS3.beatgridHandler = function(field) { - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(field.group, "beats_translate_match_alignment", field.value); + var activeGroup = TraktorS3.controller.resolveGroup(field.group); + if (TraktorS3.shiftPressed[activeGroup]) { + engine.setValue(activeGroup, "beats_translate_match_alignment", field.value); } else { - engine.setValue(field.group, "beats_translate_curpos", field.value); + engine.setValue(activeGroup, "beats_translate_curpos", field.value); } TraktorS3.outputHandler(field.value, field.group, "grid"); @@ -783,7 +808,7 @@ TraktorS3.debugLights = function() { }; TraktorS3.registerOutputPackets = function() { - var outputA = new HIDPacket(TraktorS3.controller, "outputA", 0x80); + var outputA = new HIDPacket("outputA", 0x80); outputA.addOutput("deck1", "shift", 0x01, "B"); outputA.addOutput("deck2", "shift", 0x1A, "B"); @@ -849,7 +874,7 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputA); - var outputB = new HIDPacket(TraktorS3.controller, "outputB", 0x81); + var outputB = new HIDPacket("outputB", 0x81); var VuOffsets = { "[Channel3]": 0x01, @@ -922,19 +947,6 @@ TraktorS3.linkOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; -// TraktorS3.vuMeterHandler = function (value, group, key) { -// var vuKeys = Object.keys(TraktorS3.vuMeterThresholds); -// for (var i = 0; i < vuKeys.length; ++i) { -// // Avoid spamming HID by only sending last LED update -// var last = (i === (vuKeys.length - 1)); -// if (TraktorS3.vuMeterThresholds[vuKeys[i]] > value) { -// TraktorS3.controller.setOutput(group, vuKeys[i], 0x00, last); -// } else { -// TraktorS3.controller.setOutput(group, vuKeys[i], 0x7E, last); -// } -// } -// }; - TraktorS3.vuMeterHandler = function(value, group, key) { // This handler is called a lot so it should be as fast as possible. diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 82bebaff6e4..79f8af1455e 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -122,8 +122,7 @@ HIDModifierList.prototype.setCallback = function(name, callback) { * @param header (optional) list of bytes to match from beginning * of packet. Do NOT put the report ID in this; use * the reportId parameter instead. */ -HIDPacket = function(controller, name, reportId, callback, header) { - this.controller = controller; +HIDPacket = function(name, reportId, callback, header) { this.name = name; this.header = header; this.callback = callback; @@ -223,14 +222,6 @@ HIDPacket.prototype.unpack = function(data, field) { return value; }; -HIDPacket.prototype.registerGroup = function(group) { - if (group in this.groups) { - HIDDebug("Group already registered: " + group); - return; - } - this.groups[group] = {}; -}; - /** Find HID packet group matching name. * Create group if create is true */ HIDPacket.prototype.getGroup = function(name, create) { @@ -285,16 +276,7 @@ HIDPacket.prototype.getField = function(group, name) { return undefined; } - // Resolve virtual decks automatically. - HIDDebug("GROUPP????? " + group); - if (this.controller.virtualDecks.indexOf(group) !== -1) { - HIDDebug("resolving!"); - group = this.controller.resolveGroup(group); - HIDDebug("and.... " + group); - } - var control_group = this.groups[group]; - HIDDebug("groups? " + this.groups + " " + group + " " + control_group); if (field_id in control_group) return control_group[field_id]; @@ -693,40 +675,6 @@ HIDPacket.prototype.send = function(debug) { controller.send(data, data.length, this.reportId); }; -// ActiveDecks = function () { -// this.activeDeckBitfield = undefined; -// this.numDecks = 0; -// } - -// // Hypothetically ActiveDecks could work with any multiple of two. In practice it'll either -// // be 2 or 4. -// ActiveDecks.setDeckCount = function (count) { -// this.numDecks = count; -// } - -// // Returns the deck number of the only active deck. -// ActiveDecks.getSingleActiveDeck { -// if (this.activeDeck === undefined) { -// return undefined; -// } - -// var activeCount = 0; -// var val = this.activeDeckBitfield; -// // Kernighan bitmask counting algorithm. It only -// // needs as many loop iterations as there are set values in the bitfield. -// while (val != 0) { -// val = val & (val - 1); -// ++activeCount; -// } -// if (activeCount === 0) { -// return undefined; -// } -// if (activeCount > 1) { -// HIDDebug("ERROR: expected only 1 active deck, got " + activeCount); -// return undefined; -// } -// return - /** * HID Controller Class * @@ -735,9 +683,8 @@ HIDPacket.prototype.send = function(debug) { * * initialized by default false, you should set this to true when * controller is found and everything is OK - * activeDeck by default undefined, is list of ints that map - * names 'deck','deck1' and 'deck2' to actual [ChannelX]. - * Supports multiple switches, and each switch can select 2 or 4 decks. + * activeDeck by default undefined, used to map the virtual deck + * names 'deck','deck1' and 'deck2' to actual [ChannelX] * isScratchEnabled set to true, when button 'jog_touch' is active * buttonStates valid state values for buttons, should contain fields * released (default 0) and pressed (default 1) @@ -860,29 +807,24 @@ HIDController.prototype.resolveDeckGroup = function(deck) { * a real mixxx group value, just return it as it without mapping. */ HIDController.prototype.resolveGroup = function(group) { var channel_name = /\[Channel[0-9]+\]/; - if (group != undefined && group.match(channel_name)) { + if (group != undefined && group.match(channel_name)) return group; - } if (this.valid_groups.indexOf(group) != -1) { return group; } - // activeDeck could refer to more than one active deck per controller, - // so we return the lowest deck if (group == "deck" || group == undefined) { if (this.activeDeck == undefined) return undefined; return "[Channel" + this.activeDeck + "]"; } - HIDDebug("here?? " + this.activeDeck); if (this.activeDeck == 1 || this.activeDeck == 2) { - if (group === "deck1") return "[Channel1]"; - if (group === "deck2") return "[Channel2]"; + if (group == "deck1") return "[Channel1]"; + if (group == "deck2") return "[Channel2]"; } if (this.activeDeck == 3 || this.activeDeck == 4) { - if (group === "deck1") return "[Channel3]"; - if (group === "deck2") return "[Channel4]"; + if (group == "deck1") return "[Channel3]"; + if (group == "deck2") return "[Channel4]"; } - HIDDebug("whoop whoop undef"); return undefined; }; From 7563f01e27ce1b29b7ced8db8fdb2e0871add658 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 1 Aug 2020 10:50:18 -0400 Subject: [PATCH 06/84] Traktor S3: Bind more of the library buttons --- .../Traktor-Kontrol-S3-hid-scripts.js | 105 ++++++++++++------ 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 12c3905baa7..ffc207c8830 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -10,7 +10,7 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* Traktor Kontrol S3 HID controller script v1.00 */ -/* Last modification: July 2020 */ +/* Last modification: August 2020 */ /* Author: Owen Williams */ /* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ /* */ @@ -28,6 +28,7 @@ var TraktorS3 = new function() { this.controller = new HIDController(); this.shiftPressed = {"deck1": false, "deck2": false}; + this.previewPressed = {"deck1": false, "deck2": false}; this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode @@ -152,10 +153,14 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "deck1", "!LoadSelectedTrack", 0x09, 0x01, this.loadTrackHandler); this.registerInputButton(messageShort, "deck2", "!LoadSelectedTrack", 0x09, 0x08, this.loadTrackHandler); - this.registerInputButton(messageShort, "deck1", "!MaximizeLibrary", 0x01, 0x40, this.maximizeLibraryHandler); - this.registerInputButton(messageShort, "deck2", "!MaximizeLibrary", 0x04, 0x80, this.maximizeLibraryHandler); - // this.registerInputButton(messageShort, "deck1", "!AddTrack", 0x01, 0x04, this.addTrackHandler); - // this.registerInputButton(messageShort, "deck2", "!AddTrack", 0x04, 0x10, this.addTrackHandler); + this.registerInputButton(messageShort, "deck1", "!PreviewTrack", 0x01, 0x08, this.previewTrackHandler); + this.registerInputButton(messageShort, "deck2", "!PreviewTrack", 0x04, 0x10, this.previewTrackHandler); + + this.registerInputButton(messageShort, "deck1", "!LibraryFocus", 0x01, 0x40, this.LibraryFocusHandler); + this.registerInputButton(messageShort, "deck2", "!LibraryFocus", 0x04, 0x80, this.LibraryFocusHandler); + + this.registerInputButton(messageShort, "deck1", "!AddTrack", 0x01, 0x20, this.cueAutoDJHandler); + this.registerInputButton(messageShort, "deck2", "!AddTrack", 0x04, 0x40, this.cueAutoDJHandler); // // Loop control // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 @@ -196,14 +201,14 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "deck1", "!slip_enabled", 0x01, 0x02, this.fluxHandler); this.registerInputButton(messageShort, "deck2", "!slip_enabled", 0x04, 0x04, this.fluxHandler); - this.registerInputButton(messageShort, "deck1", "!grid", 0x01, 0x08, this.beatgridHandler); + this.registerInputButton(messageShort, "deck1", "!grid", 0x01, 0x80, this.beatgridHandler); this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x01, this.beatgridHandler); // // TODO: implement jog // this.registerInputButton(messageShort, "deck1", "!grid", 0x02, 0x01, this.jogHandler); // this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x02, this.jpgHandler); - // Unmapped: preview, star, list, encoder touches, jog + // Unmapped: star, encoder touches, jog // DECK ASSIGNMENT this.controller.registerInputPacket(messageShort); @@ -319,7 +324,7 @@ TraktorS3.deckSwitchHandler = function(field) { TraktorS3.playHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); HIDDebug("group?? " + activeGroup); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "start_stop", field.value); } else if (field.value === 1) { HIDDebug("sooooo play?"); @@ -329,7 +334,7 @@ TraktorS3.playHandler = function(field) { TraktorS3.cueHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "cue_gotoandstop", field.value); } else { engine.setValue(activeGroup, "cue_default", field.value); @@ -340,7 +345,7 @@ TraktorS3.cueHandler = function(field) { TraktorS3.shiftHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); HIDDebug("SHIFT!" + activeGroup + " " + field.value); - TraktorS3.shiftPressed[activeGroup] = field.value; + TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); TraktorS3.outputHandler(field.value, field.group, "shift"); }; @@ -356,7 +361,7 @@ TraktorS3.keylockHandler = function(field) { TraktorS3.syncHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { // engine.setValue(activeGroup, "sync_enabled", field.value); } else if (field.value === 1) { //TODO: latching not working? @@ -403,7 +408,7 @@ TraktorS3.numberButtonHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); if (TraktorS3.padModeState[activeGroup] === 0) { // Hotcues mode - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "hotcue_" + padNumber + "_clear", field.value); } else { engine.setValue(activeGroup, "hotcue_" + padNumber + "_activate", field.value); @@ -411,7 +416,7 @@ TraktorS3.numberButtonHandler = function(field) { } else { // Samples mode var sampler = TraktorS3.samplerHotcuesRelation[activeGroup][padNumber]; - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { engine.setValue("[Sampler" + sampler + "]", "cue_default", field.value); @@ -444,43 +449,66 @@ TraktorS3.selectTrackHandler = function(field) { if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[activeGroup]) { delta = -1; } + TraktorS3.browseKnobEncoderState[activeGroup] = field.value; + + // When preview is held, rotating the library encoder scrolls through the previewing track. + if (TraktorS3.previewPressed[field.group]) { + var playPosition = engine.getValue("[PreviewDeck1]", "playposition"); + HIDDebug("current playpos " + playPosition); + if (delta > 0) { + playPosition += 0.0125; + } else { + playPosition -= 0.0125; + } + HIDDebug("new playpos " + playPosition); + engine.setValue("[PreviewDeck1]", "playposition", playPosition); + return; + } - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue("[Library]", "MoveHorizontal", delta); } else { engine.setValue("[Library]", "MoveVertical", delta); } - - TraktorS3.browseKnobEncoderState[activeGroup] = field.value; }; TraktorS3.loadTrackHandler = function(field) { HIDDebug("LOAD TRACK!!!"); var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "eject", field.value); } else { engine.setValue(activeGroup, "LoadSelectedTrack", field.value); } }; -TraktorS3.addTrackHandler = function(field) { +TraktorS3.previewTrackHandler = function(field) { + if (field.value === 1) { + TraktorS3.previewPressed[field.group] = true; + engine.setValue("[PreviewDeck1]", "LoadSelectedTrackAndPlay", 1); + } else { + TraktorS3.previewPressed[field.group] = false; + engine.setValue("[PreviewDeck1]", "play", 0); + } +}; + +TraktorS3.cueAutoDJHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); TraktorS3.outputHandler(field.value, field.group, "addTrack"); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue("[Library]", "AutoDjAddTop", field.value); } else { engine.setValue("[Library]", "AutoDjAddBottom", field.value); } }; -TraktorS3.maximizeLibraryHandler = function(field) { +TraktorS3.LibraryFocusHandler = function(field) { if (field.value === 0) { return; } - script.toggleControl("[Master]", "maximize_library"); + script.toggleControl("[Library]", "MoveFocus"); }; TraktorS3.selectLoopHandler = function(field) { @@ -499,11 +527,16 @@ TraktorS3.activateLoopHandler = function(field) { return; } var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var isLoopActive = engine.getValue(activeGroup, "loop_enabled"); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "reloop_toggle", field.value); } else { - engine.setValue(activeGroup, "beatloop_activate", field.value); + if (isLoopActive) { + engine.setValue(activeGroup, "reloop_toggle", field.value); + } else { + engine.setValue(activeGroup, "beatloop_activate", field.value); + } } }; @@ -514,7 +547,7 @@ TraktorS3.selectBeatjumpHandler = function(field) { delta = -1; } - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { var beatjump_size = engine.getValue(activeGroup, "beatjump_size"); if (delta > 0) { engine.setValue(activeGroup, "beatjump_size", beatjump_size * 2); @@ -534,7 +567,7 @@ TraktorS3.selectBeatjumpHandler = function(field) { TraktorS3.activateBeatjumpHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "reloop_andstop", field.value); } else { engine.setValue(activeGroup, "beatlooproll_activate", field.value); @@ -730,7 +763,7 @@ TraktorS3.fxHandler = function(field) { TraktorS3.reverseHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "reverseroll", field.value); } else { engine.setValue(activeGroup, "reverse", field.value); @@ -750,7 +783,7 @@ TraktorS3.fluxHandler = function(field) { TraktorS3.beatgridHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - if (TraktorS3.shiftPressed[activeGroup]) { + if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "beats_translate_match_alignment", field.value); } else { engine.setValue(activeGroup, "beats_translate_curpos", field.value); @@ -866,8 +899,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "grid", 0x08, "B"); outputA.addOutput("deck2", "grid", 0x20, "B"); - outputA.addOutput("deck1", "MaximizeLibrary", 0x07, "B"); - outputA.addOutput("deck2", "MaximizeLibrary", 0x21, "B"); + outputA.addOutput("deck1", "LibraryFocus", 0x07, "B"); + outputA.addOutput("deck2", "LibraryFocus", 0x21, "B"); // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); @@ -1111,21 +1144,21 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("[ChannelX]", "fxButton4", softLight, false); TraktorS3.controller.setOutput("deck1", "reverse", softLight, false); - TraktorS3.controller.setOutput("deck", "reverse", softLight, false); + TraktorS3.controller.setOutput("deck2", "reverse", softLight, false); current = (engine.getValue("deck1", "slip_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "slip_enabled", current, false); - current = (engine.getValue("deck", "slip_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck", "slip_enabled", current, false); + current = (engine.getValue("deck2", "slip_enabled")) ? fullLight : softLight; + TraktorS3.controller.setOutput("deck2", "slip_enabled", current, false); TraktorS3.controller.setOutput("deck1", "addTrack", softLight, false); - TraktorS3.controller.setOutput("deck", "addTrack", softLight, false); + TraktorS3.controller.setOutput("deck2", "addTrack", softLight, false); TraktorS3.controller.setOutput("deck1", "grid", softLight, false); - TraktorS3.controller.setOutput("deck", "grid", softLight, false); + TraktorS3.controller.setOutput("deck2", "grid", softLight, false); - TraktorS3.controller.setOutput("deck1", "MaximizeLibrary", softLight, false); - TraktorS3.controller.setOutput("deck", "MaximizeLibrary", softLight, false); + TraktorS3.controller.setOutput("deck1", "LibraryFocus", softLight, false); + TraktorS3.controller.setOutput("deck2", "LibraryFocus", softLight, false); TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); From 873eb534f755caeab0057a0193dd4057437bda96 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 2 Aug 2020 11:45:37 -0400 Subject: [PATCH 07/84] Traktor S3: Master Vus, start implementing color lights --- .../Traktor-Kontrol-S3-hid-scripts.js | 279 +++++++++++++----- res/controllers/common-hid-packet-parser.js | 5 +- 2 files changed, 208 insertions(+), 76 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index ffc207c8830..f81f12ad22e 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -17,6 +17,8 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ +/* * RGB mapping (use roland dj-505 for reference) */ +/* * wheel colors (animations!!!!) */ /* * touch for track browse, loop control, beatjump */ /* * jog button */ /* * sync latch */ @@ -60,8 +62,13 @@ var TraktorS3 = new function() { "[Channel1]": {}, "[Channel2]": {}, "[Channel3]": {}, - "[Channel4]": {} + "[Channel4]": {}, + }; + this.masterVuConnections = { + "VuMeterL": {}, + "VuMeterR": {} }; + this.clipConnections = { "[Channel1]": {}, "[Channel2]": {}, @@ -69,11 +76,65 @@ var TraktorS3 = new function() { "[Channel4]": {} }; + // The S3 has a set of predefined colors for many buttons. They are not + // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. + this.colorPalette = { + OFF: 0x00, + RED: 0x04, + CARROT: 0x08, + ORANGE: 0x0C, + HONEY: 0x10, + YELLOW: 0x14, + LIME: 0x18, + GREEN: 0x1C, + AQUA: 0x20, + CELESTE: 0x24, + SKY: 0x28, + BLUE: 0x2C, + PURPLE: 0x30, + FUSCHIA: 0x34, + MAGENTA: 0x38, + AZALEA: 0x3C, + SALMON: 0x40, + WHITE: 0x44 + }; + + this.DeckColors = { + "[Channel1]": this.colorPalette.ORANGE, + "[Channel2]": this.colorPalette.ORANGE, + "[Channel3]": this.colorPalette.SKY, + "[Channel4]": this.colorPalette.SKY + }; + + this.colorMap = new ColorMapper({ + 0xCC0000: this.colorPalette.RED, + 0xCC5E00: this.colorPalette.CARROT, + 0xCC7800: this.colorPalette.ORANGE, + 0xCC9200: this.colorPalette.HONEY, + + 0xCCCC00: this.colorPalette.YELLOW, + 0x81CC00: this.colorPalette.LIME, + 0x00CC00: this.colorPalette.GREEN, + 0x00CC49: this.colorPalette.AQUA, + + 0x00CCCC: this.colorPalette.CELESTE, + 0x0091CC: this.colorPalette.SKY, + 0x0000CC: this.colorPalette.BLUE, + 0xCC00CC: this.colorPalette.PURPLE, + + 0xCC0091: this.colorPalette.FUSCHIA, + 0xCC0079: this.colorPalette.MAGENTA, + 0xCC477E: this.colorPalette.AZALEA, + 0xCC4761: this.colorPalette.SALMON, + + 0xCCCCCC: this.colorPalette.WHITE, + }); + // Sampler callbacks this.samplerCallbacks = []; this.samplerHotcuesRelation = { "deck1": { - 1: 1, 2: 2, 3: 3, 4: 4, 5: 9, 6: 10, 7: 11, 8: 12 + 1: 1, 2: 2, 3: 3, 4: 4, 5: 9, 6: 10, 7: 11, 8: 12 }, "deck2": { 1: 5, 2: 6, 3: 7, 4: 8, 5: 13, 6: 14, 7: 15, 8: 16 } @@ -88,7 +149,7 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - // TraktorS3.debugLights(); + TraktorS3.debugLights(); }; TraktorS3.registerInputPackets = function() { @@ -313,17 +374,21 @@ TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, message.setCallback(group, name, callback); }; -TraktorS3.deckSwitchHandler = function(field) { +TraktorS3.deckSwitchHandler = function (field) { + if (field.value === 0) { + return; + } for (var key in field) { var value = field[key]; HIDDebug("what is a field: " + key + ": " + value); } TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); + TraktorS3.lightDeck(true); }; TraktorS3.playHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - HIDDebug("group?? " + activeGroup); + // HIDDebug("group?? " + activeGroup); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "start_stop", field.value); } else if (field.value === 1) { @@ -347,6 +412,7 @@ TraktorS3.shiftHandler = function(field) { HIDDebug("SHIFT!" + activeGroup + " " + field.value); TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); + // Shift is only white TraktorS3.outputHandler(field.value, field.group, "shift"); }; @@ -379,26 +445,26 @@ TraktorS3.padModeHandler = function(field) { // If we are in hotcues mode and samples mode is activated engine.setValue("[Samplers]", "show_samplers", 1); TraktorS3.padModeState[activeGroup] = 1; - TraktorS3.outputHandler(0, field.group, "hotcues"); - TraktorS3.outputHandler(1, field.group, "samples"); + TraktorS3.colorOutputHandler(0, field.group, "hotcues"); + TraktorS3.colorOutputHandler(1, field.group, "samples"); // Light LEDs for all slots with loaded samplers for (var key in TraktorS3.samplerHotcuesRelation[activeGroup]) { if (TraktorS3.samplerHotcuesRelation[activeGroup].hasOwnProperty(key)) { var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[activeGroup][key] + "]", "track_loaded"); - TraktorS3.outputHandler(loaded, field.group, "pad_" + key); + TraktorS3.colorOutputHandler(loaded, field.group, "pad_" + key); } } } else if (field.name === "!hotcues") { // If we are in samples mode and hotcues mode is activated TraktorS3.padModeState[activeGroup] = 0; - TraktorS3.outputHandler(1, field.group, "hotcues"); - TraktorS3.outputHandler(0, field.group, "samples"); + TraktorS3.colorOutputHandler(1, field.group, "hotcues"); + TraktorS3.colorOutputHandler(0, field.group, "samples"); // Light LEDs for all enabled hotcues for (var i = 1; i <= 8; ++i) { var active = engine.getValue(activeGroup, "hotcue_" + i + "_enabled"); - TraktorS3.outputHandler(active, field.group, "pad_" + i); + TraktorS3.colorOutputHandler(active, field.group, "pad_" + i); } } }; @@ -494,7 +560,7 @@ TraktorS3.previewTrackHandler = function(field) { TraktorS3.cueAutoDJHandler = function(field) { var activeGroup = TraktorS3.controller.resolveGroup(field.group); - TraktorS3.outputHandler(field.value, field.group, "addTrack"); + TraktorS3.colorOutputHandler(field.value, field.group, "addTrack"); if (TraktorS3.shiftPressed[field.group]) { engine.setValue("[Library]", "AutoDjAddTop", field.value); @@ -758,7 +824,7 @@ TraktorS3.fxHandler = function(field) { engine.setValue(group, "group_[Channel2]_enable", TraktorS3.fxButtonState[fxNumber]); engine.setValue(group, "group_[Channel3]_enable", TraktorS3.fxButtonState[fxNumber]); engine.setValue(group, "group_[Channel4]_enable", TraktorS3.fxButtonState[fxNumber]); - TraktorS3.outputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "fxButton" + fxNumber); + TraktorS3.colorOutputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "fxButton" + fxNumber); }; TraktorS3.reverseHandler = function(field) { @@ -789,30 +855,35 @@ TraktorS3.beatgridHandler = function(field) { engine.setValue(activeGroup, "beats_translate_curpos", field.value); } - TraktorS3.outputHandler(field.value, field.group, "grid"); + TraktorS3.colorOutputHandler(field.value, field.group, "grid"); }; +function sleepFor( sleepDuration ){ + var now = new Date().getTime(); + while(new Date().getTime() < now + sleepDuration){ /* do nothing */ } +} + TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). - //var data_strings = ["80 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 0A 0A 0A 0A 0A 0A 0A 0A 00 7F 00 00 00 00 0A 0A 0A 0A 0A 0A", - // "81 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0B 03 00 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 7F 0A 0A 0A 0A 0A 7F 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A", - // "82 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 00 00 7F 7F 7F 7F 7F 7F 00 00 7F 7F 7F 7F 7F 00 00 00 7F 7F 7F 7F 7F 7F 00 00 7F 7F 7F 7F 7F 00 00 00"]; - // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F var data_strings = [ - " FF 00 00 FF FF FF 00 00 00 00 FF FF FF 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 FF 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "FF ", - " FF 00 00 00 00 00 00 00 00 00 00 00 00 FF " + - "FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF " + - "FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 00 " + - "FF 00 00 00 FF 00 00 FF 00 00 00 00 FF 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + " 7C 7C 7C 2C 2C 2C 2C FF 2C 2E FF 2C 2C 3F " + + "FF 2C 7E FF 00 00 FF 2C 2C 2C 2C 7C 7C 7C FF 2C " + + "2C 2C 2C 2C 2E 0C 2C 2C 2E 00 2C 7C 00 00 00 00 " + + "00 00 00 00 7E 0C 0C 0C 0C 7C 7C 7C 70 04 1C 2C " + + "14 0E 7C 7C 00 2E 00 2E 00 2E 00 2E 00 00 00 00 " + + "00 00 00 00 ", + " FF 00 00 00 00 00 00 00 00 00 FF 00 FF 00 " + + "FF 00 00 00 00 00 00 00 FF 00 00 00 00 FF FF FF " + + "FF 00 00 00 FF 00 00 00 FF FF 00 00 00 FF 00 00 " + + "00 00 00 00 FF 00 00 FF 00 00 FF 00 FF 00 FF FF " + + "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 FF", ]; + var data = [Object(), Object()]; + // var c = 4 * 0x10; + for (i = 0; i < data.length; i++) { var ok = true; var splitted = data_strings[i].split(/\s+/); @@ -834,12 +905,34 @@ TraktorS3.debugLights = function() { } data[i][j] = b; } + // if (i === 0) { + // for (k = 0; k < data[0].length; k++) { + // data[0][k] = 0x30; + // } + // } + // for (d = 0; d < 8; d++) { + // data[0][0x11+d] = (d+1) * 4 + 2; + // } + // for (d = 0; d < 8; d++) { + // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; + // } if (ok) { + // HIDDebug("value: 0x" + c.toString(16)); controller.send(data[i], data[i].length, 0x80 + i); } } }; +// 4 levels of every color, starting at 0x04 +// last colors at 0x40, then just whites +// until 0x80 when it wraps around again +// 0 red/orange: off, red, orange, wheat, +// 1 yellows/greens: sun, parchment, kelp, grass +// 2 blues: tropical water, baby blue, sky, cerulean +// 3 purples/pinks: purple, orchid, don't use, azalea +// 4 salmon, white, white, white + + TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); @@ -905,6 +998,11 @@ TraktorS3.registerOutputPackets = function() { // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); + outputA.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); + outputA.addOutput("[ChannelX]", "fxButton2", 0x3D, "B"); + outputA.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); + outputA.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); + this.controller.registerOutputPacket(outputA); var outputB = new HIDPacket("outputB", 0x81); @@ -921,29 +1019,37 @@ TraktorS3.registerOutputPackets = function() { } } + var MasterVuOffsets = { + "VuMeterL": 0x3D, + "VuMeterR": 0x46 + }; + for (i = 0; i < 8; i++) { + outputB.addOutput("[Master]", "!" + "VuMeterL" + i, MasterVuOffsets["VuMeterL"] + i, "B"); + outputB.addOutput("[Master]", "!" + "VuMeterR" + i, MasterVuOffsets["VuMeterR"] + i, "B"); + } + + outputB.addOutput("[Master]", "PeakIndicatorL", 0x45, "B"); + outputB.addOutput("[Master]", "PeakIndicatorR", 0x4E, "B"); + + outputB.addOutput("[Channel3]", "PeakIndicator", 0x0F, "B"); outputB.addOutput("[Channel1]", "PeakIndicator", 0x1E, "B"); outputB.addOutput("[Channel2]", "PeakIndicator", 0x2D, "B"); - outputB.addOutput("[Channel3]", "PeakIndicator", 0x09, "B"); outputB.addOutput("[Channel4]", "PeakIndicator", 0x3C, "B"); - outputB.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); - outputB.addOutput("[ChannelX]", "fxButton2", 0x3D, "B"); - outputB.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); - outputB.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); - this.controller.registerOutputPacket(outputB); + // Play is always green this.linkOutput("deck1", "play_indicator", this.outputHandler); this.linkOutput("deck2", "play_indicator", this.outputHandler); - this.linkOutput("deck1", "cue_indicator", this.outputHandler); - this.linkOutput("deck2", "cue_indicator", this.outputHandler); + this.linkOutput("deck1", "cue_indicator", this.colorOutputHandler); + this.linkOutput("deck2", "cue_indicator", this.colorOutputHandler); - this.linkOutput("deck1", "sync_enabled", this.outputHandler); - this.linkOutput("deck2", "sync_enabled", this.outputHandler); + this.linkOutput("deck1", "sync_enabled", this.colorOutputHandler); + this.linkOutput("deck2", "sync_enabled", this.colorOutputHandler); - this.linkOutput("deck1", "keylock", this.outputHandler); - this.linkOutput("deck2", "keylock", this.outputHandler); + this.linkOutput("deck1", "keylock", this.colorOutputHandler); + this.linkOutput("deck2", "keylock", this.colorOutputHandler); for (var i = 1; i <= 8; ++i) { TraktorS3.controller.linkOutput("deck1", "pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); @@ -960,12 +1066,18 @@ TraktorS3.registerOutputPackets = function() { this.linkOutput("[Microphone]", "talkover", this.outputHandler); - // VuMeter + // Channel VuMeters for (var i = 1; i <= 4; i++) { - this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.vuMeterHandler); + this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.channelVuMeterHandler); this.clipConnections[i] = engine.makeConnection("[Channel" + i + "]", "PeakIndicator", this.peakOutputHandler); } + // Master VuMeters + this.masterVuConnections["VuMeterL"] = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); + this.masterVuConnections["VuMeterR"] = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); + this.linkOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); + this.linkOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); + // Sampler callbacks for (i = 1; i <= 16; ++i) { this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); @@ -980,26 +1092,32 @@ TraktorS3.linkOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; -TraktorS3.vuMeterHandler = function(value, group, key) { - // This handler is called a lot so it should be as fast as possible. +TraktorS3.channelVuMeterHandler = function (value, group, key) { + TraktorS3.vuMeterHandler(value, group, key, 14); +} - // VU is drawn on 14 segments, the 15th indicates clip. - // Figure out number of fully-illuminated segments. - var scaledValue = value * 14.0; +TraktorS3.masterVuMeterHandler = function (value, group, key) { + TraktorS3.vuMeterHandler(value, group, key, 8); +} + +TraktorS3.vuMeterHandler = function(value, group, key, segments) { + // This handler is called a lot so it should be as fast as possible. + // HIDDebug("group??" + group + " " + key); + var scaledValue = value * segments; var fullIllumCount = Math.floor(scaledValue); // Figure out how much the partially-illuminated segment is illuminated. var partialIllum = (scaledValue - fullIllumCount) * 0x7F; - for (i = 0; i < 14; i++) { - var key = "!" + "VuMeter" + i; + for (i = 0; i < segments; i++) { + var segmentKey = "!" + key + i; if (i < fullIllumCount) { // Don't update lights until they're all done, so the last term is false. - TraktorS3.controller.setOutput(group, key, 0x7F, false); + TraktorS3.controller.setOutput(group, segmentKey, 0x7F, false); } else if (i == fullIllumCount) { - TraktorS3.controller.setOutput(group, key, partialIllum, false); + TraktorS3.controller.setOutput(group, segmentKey, partialIllum, false); } else { - TraktorS3.controller.setOutput(group, key, 0x00, false); + TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); } } TraktorS3.controller.OutputPackets["outputB"].send(); @@ -1014,15 +1132,26 @@ TraktorS3.peakOutputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, true); }; +// outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { - // Custom value for multi-colored LEDs var ledValue = value; if (value === 0 || value === false) { // Off value - ledValue = 0x7C; + ledValue = 0x30; } else if (value === 1 || value === true) { // On value - ledValue = 0x7E; + ledValue = 0x33; + } + + TraktorS3.controller.setOutput(group, key, ledValue, true); +}; + +// colorOutputHandler drives lights that have the palettized multicolor lights. +TraktorS3.colorOutputHandler = function (value, group, key) { + var activeGroup = TraktorS3.controller.resolveGroup(group); + var ledValue = TraktorS3.DeckColors[activeGroup]; + if (value === 1 || value === true) { + ledValue += 0x02; } TraktorS3.controller.setOutput(group, key, ledValue, true); @@ -1085,29 +1214,32 @@ TraktorS3.resolveSampler = function(group) { }; TraktorS3.lightDeck = function(switchOff) { - var softLight = 0x7C; - var fullLight = 0x7E; + var softLight = 0x30; + var fullLight = 0x33; if (switchOff) { softLight = 0x00; fullLight = 0x00; } - var current = (engine.getValue("deck1", "play_indicator") ? fullLight : softLight); + var groupA = TraktorS3.controller.resolveGroup("deck1"); + var groupB = TraktorS3.controller.resolveGroup("deck2"); + + var current = (engine.getValue(groupA, "play_indicator") ? fullLight : softLight); TraktorS3.controller.setOutput("deck1", "play_indicator", current, false); - current = (engine.getValue("deck2", "play_indicator")) ? fullLight : softLight; + current = (engine.getValue(groupB, "play_indicator")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "play_indicator", current, false); - current = (engine.getValue("deck1", "cue_indicator")) ? fullLight : softLight; + current = (engine.getValue(groupA, "cue_indicator")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "cue_indicator", current, false); - current = (engine.getValue("deck2", "cue_indicator")) ? fullLight : softLight; + current = (engine.getValue(groupB, "cue_indicator")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "cue_indicator", current, false); TraktorS3.controller.setOutput("deck1", "shift", softLight, false); TraktorS3.controller.setOutput("deck2", "shift", softLight, false); - current = (engine.getValue("deck1", "sync_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupA, "sync_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "sync_enabled", current, false); - current = (engine.getValue("deck2", "sync_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupB, "sync_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "sync_enabled", current, false); // Hotcues mode is default start value @@ -1117,22 +1249,22 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("deck1", "samples", softLight, false); TraktorS3.controller.setOutput("deck2", "samples", softLight, false); - current = (engine.getValue("deck1", "keylock")) ? fullLight : softLight; + current = (engine.getValue(groupA, "keylock")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "keylock", current, false); - current = (engine.getValue("deck2", "keylock")) ? fullLight : softLight; + current = (engine.getValue(groupB, "keylock")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "keylock", current, false); for (var i = 1; i <= 8; ++i) { - current = (engine.getValue("deck1", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupA, "hotcue_" + i + "_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "pad_" + i, current, false); - current = (engine.getValue("deck2", "hotcue_" + i + "_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupB, "hotcue_" + i + "_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "pad_" + i, current, false); } - current = (engine.getValue("[Channel1]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "pfl", current, false); - current = (engine.getValue("[Channel2]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "pfl", current, false); + current = (engine.getValue(groupA, "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput(groupA, "pfl", current, false); + current = (engine.getValue(groupB, "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput(groupB, "pfl", current, false); current = (engine.getValue("[Channel3]", "pfl")) ? fullLight : softLight; TraktorS3.controller.setOutput("[Channel3]", "pfl", current, false); current = (engine.getValue("[Channel4]", "pfl")) ? fullLight : softLight; @@ -1146,9 +1278,9 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("deck1", "reverse", softLight, false); TraktorS3.controller.setOutput("deck2", "reverse", softLight, false); - current = (engine.getValue("deck1", "slip_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupA, "slip_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck1", "slip_enabled", current, false); - current = (engine.getValue("deck2", "slip_enabled")) ? fullLight : softLight; + current = (engine.getValue(groupB, "slip_enabled")) ? fullLight : softLight; TraktorS3.controller.setOutput("deck2", "slip_enabled", current, false); TraktorS3.controller.setOutput("deck1", "addTrack", softLight, false); @@ -1164,6 +1296,7 @@ TraktorS3.lightDeck = function(switchOff) { // For the last output we should send the packet finally current = (engine.getValue("[Microphone]", "talkover")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Microphone]", "talkover", current, true); }; diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 79f8af1455e..6a57332ad85 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -452,7 +452,7 @@ HIDPacket.prototype.addOutput = function(group, name, offset, pack, bitmask, cal if (field != undefined) { if (bitmask == undefined) { HIDDebug( - "ERROR: overwrite non-bitmask control " + group + "." + name + "ERROR: overwrite non-bitmask control: " + group + ". " + name + ", " + field.id + " already exists" ); return; } @@ -516,8 +516,7 @@ HIDPacket.prototype.setCallback = function(group, name, callback) { return; } if (field == undefined) { - HIDDebug("setCallback: field for " + field_id + " not found" - ); + HIDDebug("setCallback: field for " + field_id + " not found"); return; } if (field.type == "bitvector") { From 7d7dbf70dd90eb13cbd0c11e6ba60062720de261 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 2 Aug 2020 12:24:17 -0400 Subject: [PATCH 08/84] Traktor S3: Lights behaving better, still a ways to go --- .../Traktor-Kontrol-S3-hid-scripts.js | 101 ++++++++++-------- res/controllers/common-hid-packet-parser.js | 2 + 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index f81f12ad22e..10593459c56 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -17,6 +17,7 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ +/* * lights are still wonky, sometimes needs a couple tries to go through?? */ /* * RGB mapping (use roland dj-505 for reference) */ /* * wheel colors (animations!!!!) */ /* * touch for track browse, loop control, beatjump */ @@ -149,6 +150,7 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); + // TraktorS3.lightDeck(true); TraktorS3.debugLights(); }; @@ -374,16 +376,21 @@ TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, message.setCallback(group, name, callback); }; -TraktorS3.deckSwitchHandler = function (field) { +TraktorS3.deckSwitchHandler = function(field) { if (field.value === 0) { return; } - for (var key in field) { - var value = field[key]; - HIDDebug("what is a field: " + key + ": " + value); + // for (var key in field) { + // var value = field[key]; + // HIDDebug("what is a field: " + key + ": " + value); + // } + var requestedDeck = TraktorS3.controller.resolveDeck(field.group); + HIDDebug("requested " + requestedDeck + " " + TraktorS3.controller.activeDeck); + TraktorS3.lightDeck(false); + if (requestedDeck === TraktorS3.controller.activeDeck) { + return; } TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); - TraktorS3.lightDeck(true); }; TraktorS3.playHandler = function(field) { @@ -858,31 +865,30 @@ TraktorS3.beatgridHandler = function(field) { TraktorS3.colorOutputHandler(field.value, field.group, "grid"); }; -function sleepFor( sleepDuration ){ +function sleepFor(sleepDuration) { var now = new Date().getTime(); - while(new Date().getTime() < now + sleepDuration){ /* do nothing */ } + while (new Date().getTime() < now + sleepDuration) { /* do nothing */ } } TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var data_strings = [ - " 7C 7C 7C 2C 2C 2C 2C FF 2C 2E FF 2C 2C 3F " + - "FF 2C 7E FF 00 00 FF 2C 2C 2C 2C 7C 7C 7C FF 2C " + + " 7C 7C 7C 2C 2C 2C 2C 39 2C 2E FF 2C 2C 3F " + + "FF 2C 7E FF 00 FF FF FF 2C 2C 2C 7C 7C 7C FF 2C " + "2C 2C 2C 2C 2E 0C 2C 2C 2E 00 2C 7C 00 00 00 00 " + - "00 00 00 00 7E 0C 0C 0C 0C 7C 7C 7C 70 04 1C 2C " + - "14 0E 7C 7C 00 2E 00 2E 00 2E 00 2E 00 00 00 00 " + - "00 00 00 00 ", - " FF 00 00 00 00 00 00 00 00 00 FF 00 FF 00 " + - "FF 00 00 00 00 00 00 00 FF 00 00 00 00 FF FF FF " + - "FF 00 00 00 FF 00 00 00 FF FF 00 00 00 FF 00 00 " + - "00 00 00 00 FF 00 00 FF 00 00 FF 00 FF 00 FF FF " + - "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 FF", + "00 FF 00 00 7E 0C 0C 0C 0C 7C 7C 7C 70 04 1C FF " + + "14 00 7C 7C 00 FF 00 2E 00 2E 00 FF 00 00 00 00 " + + "00 00 FF 00 ", + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 00", ]; var data = [Object(), Object()]; - // var c = 4 * 0x10; for (i = 0; i < data.length; i++) { var ok = true; @@ -917,7 +923,6 @@ TraktorS3.debugLights = function() { // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; // } if (ok) { - // HIDDebug("value: 0x" + c.toString(16)); controller.send(data[i], data[i].length, 0x80 + i); } } @@ -1003,6 +1008,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); outputA.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); + outputA.addOutput("[Master]", "PeakIndicatorL", 0x53, "B"); + this.controller.registerOutputPacket(outputA); var outputB = new HIDPacket("outputB", 0x81); @@ -1092,13 +1099,13 @@ TraktorS3.linkOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; -TraktorS3.channelVuMeterHandler = function (value, group, key) { +TraktorS3.channelVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler(value, group, key, 14); -} +}; -TraktorS3.masterVuMeterHandler = function (value, group, key) { +TraktorS3.masterVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler(value, group, key, 8); -} +}; TraktorS3.vuMeterHandler = function(value, group, key, segments) { // This handler is called a lot so it should be as fast as possible. @@ -1147,14 +1154,18 @@ TraktorS3.outputHandler = function(value, group, key) { }; // colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorOutputHandler = function (value, group, key) { +TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { var activeGroup = TraktorS3.controller.resolveGroup(group); var ledValue = TraktorS3.DeckColors[activeGroup]; + HIDDebug("group!" + activeGroup + " color " + ledValue); if (value === 1 || value === true) { ledValue += 0x02; } + if (sendPacket === undefined) { + sendPacket = true; + } - TraktorS3.controller.setOutput(group, key, ledValue, true); + TraktorS3.controller.setOutput(group, key, ledValue, sendPacket); }; TraktorS3.hotcueOutputHandler = function(value, group, key) { @@ -1214,6 +1225,7 @@ TraktorS3.resolveSampler = function(group) { }; TraktorS3.lightDeck = function(switchOff) { + HIDDebug("--------------------light deck"); var softLight = 0x30; var fullLight = 0x33; if (switchOff) { @@ -1230,29 +1242,29 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("deck2", "play_indicator", current, false); current = (engine.getValue(groupA, "cue_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck1", "cue_indicator", current, false); + TraktorS3.colorOutputHandler(current, "deck1", "cue_indicator", false); current = (engine.getValue(groupB, "cue_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "cue_indicator", current, false); + TraktorS3.colorOutputHandler(current, "deck2", "cue_indicator", false); TraktorS3.controller.setOutput("deck1", "shift", softLight, false); TraktorS3.controller.setOutput("deck2", "shift", softLight, false); current = (engine.getValue(groupA, "sync_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck1", "sync_enabled", current, false); + TraktorS3.colorOutputHandler(current, "deck1", "sync_enabled"); current = (engine.getValue(groupB, "sync_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "sync_enabled", current, false); + TraktorS3.colorOutputHandler(current, "deck2", "sync_enabled"); // Hotcues mode is default start value - TraktorS3.controller.setOutput("deck1", "hotcues", fullLight, false); - TraktorS3.controller.setOutput("deck2", "hotcues", fullLight, false); + TraktorS3.colorOutputHandler(true, "deck1", "hotcues"); + TraktorS3.colorOutputHandler(true, "deck2", "hotcues"); TraktorS3.controller.setOutput("deck1", "samples", softLight, false); TraktorS3.controller.setOutput("deck2", "samples", softLight, false); current = (engine.getValue(groupA, "keylock")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck1", "keylock", current, false); + TraktorS3.colorOutputHandler(current, "deck1", "keylock"); current = (engine.getValue(groupB, "keylock")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "keylock", current, false); + TraktorS3.colorOutputHandler(current, "deck2", "keylock"); for (var i = 1; i <= 8; ++i) { current = (engine.getValue(groupA, "hotcue_" + i + "_enabled")) ? fullLight : softLight; @@ -1261,10 +1273,10 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("deck2", "pad_" + i, current, false); } - current = (engine.getValue(groupA, "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput(groupA, "pfl", current, false); - current = (engine.getValue(groupB, "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput(groupB, "pfl", current, false); + current = (engine.getValue("[Channel1]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel1]", "pfl", current, false); + current = (engine.getValue("[Channel2]", "pfl")) ? fullLight : softLight; + TraktorS3.controller.setOutput("[Channel2]", "pfl", current, false); current = (engine.getValue("[Channel3]", "pfl")) ? fullLight : softLight; TraktorS3.controller.setOutput("[Channel3]", "pfl", current, false); current = (engine.getValue("[Channel4]", "pfl")) ? fullLight : softLight; @@ -1286,18 +1298,21 @@ TraktorS3.lightDeck = function(switchOff) { TraktorS3.controller.setOutput("deck1", "addTrack", softLight, false); TraktorS3.controller.setOutput("deck2", "addTrack", softLight, false); - TraktorS3.controller.setOutput("deck1", "grid", softLight, false); - TraktorS3.controller.setOutput("deck2", "grid", softLight, false); + TraktorS3.colorOutputHandler(0, "deck1", "grid"); + TraktorS3.colorOutputHandler(0, "deck2", "grid"); - TraktorS3.controller.setOutput("deck1", "LibraryFocus", softLight, false); - TraktorS3.controller.setOutput("deck2", "LibraryFocus", softLight, false); + TraktorS3.colorOutputHandler(0, "deck1", "LibraryFocus"); + TraktorS3.colorOutputHandler(0, "deck2", "LibraryFocus"); - TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); + // TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); // For the last output we should send the packet finally - current = (engine.getValue("[Microphone]", "talkover")) ? fullLight : softLight; + // current = (engine.getValue("[Microphone]", "talkover")) ? fullLight : softLight; TraktorS3.controller.setOutput("[Microphone]", "talkover", current, true); + TraktorS3.controller.OutputPackets["outputA"].send(true); + // TraktorS3.controller.OutputPackets["outputB"].send(true); + HIDDebug("--------------------light deck DONE"); }; TraktorS3.messageCallback = function(_packet, data) { diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 6a57332ad85..9c2481b3e75 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -645,6 +645,7 @@ HIDPacket.prototype.parse = function(data) { * field object values are packed to the HID packet according to the * field type. */ HIDPacket.prototype.send = function(debug) { + HIDDebug("~~~~~~~~~~~~~~~~"); var data = []; if (this.header !== undefined) { @@ -1513,6 +1514,7 @@ HIDController.prototype.setOutput = function(group, name, value, send_packet) { HIDDebug("setOutput: unknown field: " + group + "." + name); return; } + HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); field.value = value << field.bit_offset; field.toggle = value << field.bit_offset; if (send_packet) From dccade5261ef4f7bd12bfd3fc67ccec80c02f7f6 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 2 Aug 2020 11:29:57 -0500 Subject: [PATCH 09/84] HidController: filter redundant messages --- src/controllers/hid/hidcontroller.cpp | 13 ++++++++++--- src/controllers/hid/hidcontroller.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index 5fc6e621974..d2787e94a75 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -18,7 +18,8 @@ HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig) : Controller(pConfig), - m_pHidDevice(NULL) { + m_lastIncomingData(), + m_pHidDevice(nullptr) { // Copy required variables from deviceInfo, which will be freed after // this class is initialized by caller. hid_vendor_id = deviceInfo.vendor_id; @@ -250,8 +251,14 @@ bool HidController::poll() { return false; } else if (result > 0) { Trace process("HidController process packet"); - auto data = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); - receive(data, mixxx::Time::elapsed()); + auto byteArray = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); + if (byteArray == m_lastIncomingData) { + continue; + } else { + // force a copy + m_lastIncomingData = QByteArray(byteArray.data()); + } + receive(byteArray, mixxx::Time::elapsed()); } } diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index 543684ee766..32f16d5a2b5 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -84,6 +84,7 @@ class HidController final : public Controller { QString hid_serial; QString hid_manufacturer; QString hid_product; + QByteArray m_lastIncomingData; QString m_sUID; hid_device* m_pHidDevice; From ec872e515920c63fa39eec0d7b50d3b4b7223b09 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 2 Aug 2020 15:59:28 -0400 Subject: [PATCH 10/84] Traktor S3: Try to get deck switching working --- .../Traktor-Kontrol-S3-hid-scripts.js | 193 ++++++++++++++---- res/controllers/common-hid-packet-parser.js | 50 +++-- 2 files changed, 187 insertions(+), 56 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 10593459c56..1e37a51c13b 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -16,9 +16,15 @@ /* */ /////////////////////////////////////////////////////////////////////////////////// /* */ -/* TODO: */ +/* TODO: +/* * Active deck stuff: +/* * activedeck still only supports one switch +/* * colors assume single "off" instead of a dim version of the on color. +/* * I don't think I can use the built-in deck switchign because of the different +/* kinds of lights -- library assumes one type of led +/* /* * lights are still wonky, sometimes needs a couple tries to go through?? */ -/* * RGB mapping (use roland dj-505 for reference) */ +/* * deck lights */ /* * wheel colors (animations!!!!) */ /* * touch for track browse, loop control, beatjump */ /* * jog button */ @@ -79,8 +85,8 @@ var TraktorS3 = new function() { // The S3 has a set of predefined colors for many buttons. They are not // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. - this.colorPalette = { - OFF: 0x00, + this.controller.LEDDimColors = { + off: 0x00, RED: 0x04, CARROT: 0x08, ORANGE: 0x0C, @@ -100,35 +106,58 @@ var TraktorS3 = new function() { WHITE: 0x44 }; - this.DeckColors = { - "[Channel1]": this.colorPalette.ORANGE, - "[Channel2]": this.colorPalette.ORANGE, - "[Channel3]": this.colorPalette.SKY, - "[Channel4]": this.colorPalette.SKY + // The S3 has a set of predefined colors for many buttons. They are not + // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. + this.controller.LEDColors = { + off: 0x02, + RED: 0x06, + CARROT: 0x0A, + ORANGE: 0x0E, + HONEY: 0x12, + YELLOW: 0x16, + LIME: 0x1A, + GREEN: 0x1E, + AQUA: 0x22, + CELESTE: 0x26, + SKY: 0x2A, + BLUE: 0x2E, + PURPLE: 0x32, + FUSCHIA: 0x36, + MAGENTA: 0x3A, + AZALEA: 0x3E, + SALMON: 0x42, + WHITE: 0x46 + }; + + this.controller.deckOutputColors = { + 1: "ORANGE", + 2: "ORANGE", + 3: "SKY", + 4: "SKY" }; this.colorMap = new ColorMapper({ - 0xCC0000: this.colorPalette.RED, - 0xCC5E00: this.colorPalette.CARROT, - 0xCC7800: this.colorPalette.ORANGE, - 0xCC9200: this.colorPalette.HONEY, - - 0xCCCC00: this.colorPalette.YELLOW, - 0x81CC00: this.colorPalette.LIME, - 0x00CC00: this.colorPalette.GREEN, - 0x00CC49: this.colorPalette.AQUA, - - 0x00CCCC: this.colorPalette.CELESTE, - 0x0091CC: this.colorPalette.SKY, - 0x0000CC: this.colorPalette.BLUE, - 0xCC00CC: this.colorPalette.PURPLE, - - 0xCC0091: this.colorPalette.FUSCHIA, - 0xCC0079: this.colorPalette.MAGENTA, - 0xCC477E: this.colorPalette.AZALEA, - 0xCC4761: this.colorPalette.SALMON, - - 0xCCCCCC: this.colorPalette.WHITE, + 0xCC0000: this.controller.LEDColors.RED, + 0xCC5E00: this.controller.LEDColors.CARROT, + 0xCC7800: this.controller.LEDColors.ORANGE, + 0xCC9200: this.controller.LEDColors.HONEY, + + 0xCCCC00: this.controller.LEDColors.YELLOW, + 0x81CC00: this.controller.LEDColors.LIME, + 0x00CC00: this.controller.LEDColors.GREEN, + 0x00CC49: this.controller.LEDColors.AQUA, + + 0x00CCCC: this.controller.LEDColors.CELESTE, + 0x0091CC: this.controller.LEDColors.SKY, + 0x0000CC: this.controller.LEDColors.BLUE, + 0xCC00CC: this.controller.LEDColors.PURPLE, + + 0xCC0091: this.controller.LEDColors.FUSCHIA, + 0xCC0079: this.controller.LEDColors.MAGENTA, + 0xCC477E: this.controller.LEDColors.AZALEA, + 0xCC4761: this.controller.LEDColors.SALMON, + + 0xCCCCCC: this.controller.LEDColors.WHITE, }); // Sampler callbacks @@ -420,7 +449,7 @@ TraktorS3.shiftHandler = function(field) { TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); // Shift is only white - TraktorS3.outputHandler(field.value, field.group, "shift"); + TraktorS3.deckOutputHandler(field.value, field.group, "shift"); }; TraktorS3.keylockHandler = function(field) { @@ -456,9 +485,9 @@ TraktorS3.padModeHandler = function(field) { TraktorS3.colorOutputHandler(1, field.group, "samples"); // Light LEDs for all slots with loaded samplers - for (var key in TraktorS3.samplerHotcuesRelation[activeGroup]) { - if (TraktorS3.samplerHotcuesRelation[activeGroup].hasOwnProperty(key)) { - var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[activeGroup][key] + "]", "track_loaded"); + for (var key in TraktorS3.samplerHotcuesRelation[field.group]) { + if (TraktorS3.samplerHotcuesRelation[field.group].hasOwnProperty(key)) { + var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[field.group][key] + "]", "track_loaded"); TraktorS3.colorOutputHandler(loaded, field.group, "pad_" + key); } } @@ -488,7 +517,7 @@ TraktorS3.numberButtonHandler = function(field) { } } else { // Samples mode - var sampler = TraktorS3.samplerHotcuesRelation[activeGroup][padNumber]; + var sampler = TraktorS3.samplerHotcuesRelation[field.group][padNumber]; if (TraktorS3.shiftPressed[field.group]) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { @@ -842,7 +871,7 @@ TraktorS3.reverseHandler = function(field) { engine.setValue(activeGroup, "reverse", field.value); } - TraktorS3.outputHandler(field.value, field.group, "reverse"); + TraktorS3.deckOutputHandler(field.value, field.group, "reverse"); }; TraktorS3.fluxHandler = function(field) { @@ -877,8 +906,8 @@ TraktorS3.debugLights = function() { " 7C 7C 7C 2C 2C 2C 2C 39 2C 2E FF 2C 2C 3F " + "FF 2C 7E FF 00 FF FF FF 2C 2C 2C 7C 7C 7C FF 2C " + "2C 2C 2C 2C 2E 0C 2C 2C 2E 00 2C 7C 00 00 00 00 " + - "00 FF 00 00 7E 0C 0C 0C 0C 7C 7C 7C 70 04 1C FF " + - "14 00 7C 7C 00 FF 00 2E 00 2E 00 FF 00 00 00 00 " + + "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + @@ -1008,7 +1037,16 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); outputA.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); - outputA.addOutput("[Master]", "PeakIndicatorL", 0x53, "B"); + var wheelOffsets = { + "deck1": 0x44, + "deck2": 0x4B + }; + for (ch in wheelOffsets) { + for (i = 0; i < 8; i++) { + outputA.addOutput(ch, "!" + "wheel" + i, wheelOffsets[ch] + i, "B"); + } + } + this.controller.registerOutputPacket(outputA); @@ -1046,8 +1084,10 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputB); // Play is always green - this.linkOutput("deck1", "play_indicator", this.outputHandler); - this.linkOutput("deck2", "play_indicator", this.outputHandler); + this.linkOutput("deck1", "play_indicator", this.deckOutputHandler); + this.linkOutput("deck2", "play_indicator", this.deckOutputHandler); + this.linkOutput("deck1", "play_indicator", this.wheelOutputHandler); + this.linkOutput("deck2", "play_indicator", this.wheelOutputHandler); this.linkOutput("deck1", "cue_indicator", this.colorOutputHandler); this.linkOutput("deck2", "cue_indicator", this.colorOutputHandler); @@ -1068,8 +1108,8 @@ TraktorS3.registerOutputPackets = function() { this.linkOutput("[Channel3]", "pfl", this.outputHandler); this.linkOutput("[Channel4]", "pfl", this.outputHandler); - this.linkOutput("deck1", "slip_enabled", this.outputHandler); - this.linkOutput("deck2", "slip_enabled", this.outputHandler); + this.linkOutput("deck1", "slip_enabled", this.deckOutputHandler); + this.linkOutput("deck2", "slip_enabled", this.deckOutputHandler); this.linkOutput("[Microphone]", "talkover", this.outputHandler); @@ -1141,6 +1181,18 @@ TraktorS3.peakOutputHandler = function(value, group, key) { // outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { + + HIDDebug("REGULAR OUTPUT GROUP " + group); + // var updatedDeck = TraktorS3.controller.resolveDeck(group); + // if (updatedDeck !== TraktorS3.controller.activeDeck) { + // return; + // } + // if (updatedDeck === 1 || updatedDeck === 3) { + // group = "deck1"; + // } else if (updatedDeck === 2 || updatedDeck === 4) { + // group = "deck2"; + // } + var ledValue = value; if (value === 0 || value === false) { // Off value @@ -1153,10 +1205,60 @@ TraktorS3.outputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, true); }; +// deckOutputHandler drives lights that only have one color. +TraktorS3.deckOutputHandler = function(value, group, key) { + // incoming value will be a channel, we have to resolve back to + // deck. + var updatedDeck = TraktorS3.controller.resolveDeck(group); + HIDDebug("updatedeck: " + updatedDeck); + if (updatedDeck != TraktorS3.controller.activeDeck) { + HIDDebug("does not match " + updatedDeck + " " + TraktorS3.controller.activeDeck); + return; + } + if (updatedDeck == 1 || updatedDeck == 3) { + group = "deck1"; + } else if (updatedDeck == 2 || updatedDeck == 4) { + group = "deck2"; + } + HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + value); + + var ledValue = value; + if (value === 0 || value === false) { + // Off value + ledValue = 0x30; + } else if (value === 1 || value === true) { + // On value + ledValue = 0x33; + } + + TraktorS3.controller.setOutput(group, key, ledValue, true); +}; + +TraktorS3.wheelOutputHandler = function(value, group, _key) { + var activeGroup = TraktorS3.controller.resolveGroup(group); + for (var i = 0; i < 8; i++) { + var sendPacket = (i == 7); + TraktorS3.colorOutputHandler(value, group, "!wheel" + i, sendPacket); + } +}; + // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { + // Reject update if it's for a deck that's not selected. + var updatedDeck = TraktorS3.controller.resolveDeck(group); + if (updatedDeck !== TraktorS3.controller.activeDeck) { + return; + } + HIDDebug("updatedeck? " + updatedDeck); + if (updatedDeck == 1 || updatedDeck == 3) { + group = "deck1"; + } else if (updatedDeck == 2 || updatedDeck == 4) { + group = "deck2"; + } + + HIDDebug("color output: " + group + "." + key + " " + value); var activeGroup = TraktorS3.controller.resolveGroup(group); - var ledValue = TraktorS3.DeckColors[activeGroup]; + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; HIDDebug("group!" + activeGroup + " color " + ledValue); if (value === 1 || value === true) { ledValue += 0x02; @@ -1165,6 +1267,8 @@ TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { sendPacket = true; } + HIDDebug("So I think: " + group + " " + key); + TraktorS3.controller.setOutput(group, key, ledValue, sendPacket); }; @@ -1225,6 +1329,7 @@ TraktorS3.resolveSampler = function(group) { }; TraktorS3.lightDeck = function(switchOff) { + // return; HIDDebug("--------------------light deck"); var softLight = 0x30; var fullLight = 0x33; diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 9c2481b3e75..90fbb2a46f4 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -645,7 +645,7 @@ HIDPacket.prototype.parse = function(data) { * field object values are packed to the HID packet according to the * field type. */ HIDPacket.prototype.send = function(debug) { - HIDDebug("~~~~~~~~~~~~~~~~"); + // HIDDebug("~~~~~~~~~~~~~~~~"); var data = []; if (this.header !== undefined) { @@ -689,6 +689,7 @@ HIDPacket.prototype.send = function(debug) { * buttonStates valid state values for buttons, should contain fields * released (default 0) and pressed (default 1) * LEDColors possible Output colors named, must contain 'off' value + * LEDDimColors Can be undefined. Names output colors, dim/unlit state * deckOutputColors Which colors to use for each deck. Default 'on' for first * four decks. Values are like {1: 'red', 2: 'green' } * and must reference valid OutputColors fields. @@ -1410,7 +1411,6 @@ HIDController.prototype.switchDeck = function(deck) { this.disconnectDeck(); for (var packet_name in this.OutputPackets) { packet = this.OutputPackets[packet_name]; - var send_packet = false; for (var group_name in packet.groups) { var group = packet.groups[group_name]; for (var field_name in group) { @@ -1424,18 +1424,29 @@ HIDController.prototype.switchDeck = function(deck) { controlgroup = this.resolveGroup(bit.mapped_group); engine.connectControl(controlgroup, bit.mapped_name, bit.mapped_callback, true); engine.connectControl(new_group, bit.mapped_name, bit.mapped_callback); + HIDDebug("new group: " + new_group); var value = engine.getValue(new_group, bit.mapped_name); - HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value); - if (value) + HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value + " "); + HIDDebug("wat" + deck + " " + + this.deckOutputColors[deck] + " " + this.LEDColors[this.deckOutputColors[deck]]); + if (value) { this.setOutput( bit.group, bit.name, - this.LEDColors[this.deckOutputColors[deck]] + this.LEDColors[this.deckOutputColors[deck]], + false ); - else + } else { + var dim = this.LEDColors.off; + if (this.LEDDimColors !== undefined) { + dim = this.LEDDimColors[this.deckOutputColors[deck]]; + } this.setOutput( bit.group, bit.name, - this.LEDColors.off + dim, + false + ); + } } continue; } @@ -1447,19 +1458,32 @@ HIDController.prototype.switchDeck = function(deck) { engine.connectControl(controlgroup, field.mapped_name, field.mapped_callback, true); engine.connectControl(new_group, field.mapped_name, field.mapped_callback); var value = engine.getValue(new_group, field.mapped_name); - if (value) + HIDDebug("new group: " + new_group + " " + field.mapped_name + " :" + value); + HIDDebug("some seocond thins " + deck + " " + this.deckOutputColors[deck] + " " + this.LEDColors[this.deckOutputColors[deck]]); + if (value) { this.setOutput( field.group, field.name, - this.LEDColors[this.deckOutputColors[deck]] + this.LEDColors[this.deckOutputColors[deck]], + false ); - else + } else { + var dim = this.LEDColors.off; + if (this.LEDDimColors !== undefined) { + dim = this.LEDDimColors[this.deckOutputColors[deck]]; + } this.setOutput( field.group, field.name, - this.LEDColors.off + dim, + false ); + } } } } + for (p in this.OutputPackets) { + HIDDebug("packets???" + p); + this.OutputPackets[p].send(); + } this.activeDeck = deck; if (this.connectDeck != undefined) this.connectDeck(); @@ -1514,7 +1538,9 @@ HIDController.prototype.setOutput = function(group, name, value, send_packet) { HIDDebug("setOutput: unknown field: " + group + "." + name); return; } - HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); + if (value !== undefined) { + HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); + } field.value = value << field.bit_offset; field.toggle = value << field.bit_offset; if (send_packet) From 1b0d65a7cf922b43ecb2a6cd24743b6c736c2bcc Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 2 Aug 2020 18:53:00 -0500 Subject: [PATCH 11/84] ControllerManager: make kPollInterval a static member so it can be referenced outside ControllerManager --- src/controllers/controllermanager.cpp | 10 +++++----- src/controllers/controllermanager.h | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index a232fbfa294..b6ab1f485e8 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -27,18 +27,18 @@ #include "controllers/bulk/bulkenumerator.h" #endif -namespace { // http://developer.qt.nokia.com/wiki/Threads_Events_QObjects // Poll every 1ms (where possible) for good controller response #ifdef __LINUX__ // Many Linux distros ship with the system tick set to 250Hz so 1ms timer // reportedly causes CPU hosage. See Bug #990992 rryan 6/2012 -const int kPollIntervalMillis = 5; +const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(5); #else -const int kPollIntervalMillis = 1; +const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(1); #endif +namespace { /// Strip slashes and spaces from device name, so that it can be used as config /// key or a filename. QString sanitizeDeviceName(QString name) { @@ -97,7 +97,7 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig) QDir().mkpath(userPresets); } - m_pollTimer.setInterval(kPollIntervalMillis); + m_pollTimer.setInterval(kPollInterval.toIntegerMillis()); connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(pollDevices())); @@ -359,7 +359,7 @@ void ControllerManager::pollDevices() { } mixxx::Duration duration = mixxx::Time::elapsed() - start; - if (duration > mixxx::Duration::fromMillis(kPollIntervalMillis)) { + if (duration > kPollInterval) { m_skipPoll = true; } //qDebug() << "ControllerManager::pollDevices()" << duration << start; diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 8837d903580..fba7f704624 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -30,6 +30,8 @@ class ControllerManager : public QObject { ControllerManager(UserSettingsPointer pConfig); virtual ~ControllerManager(); + static const mixxx::Duration kPollInterval; + QList getControllers() const; QList getControllerList(bool outputDevices=true, bool inputDevices=true); ControllerLearningEventFilter* getControllerLearningEventFilter() const; From 73e6460c87db5876544a78f8ec8731eaf05930e1 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 2 Aug 2020 18:59:10 -0500 Subject: [PATCH 12/84] HidController: stop polling when exceeding poll interval This solution avoids a deep copy of the incoming data. --- src/controllers/hid/hidcontroller.cpp | 14 +++++++------- src/controllers/hid/hidcontroller.h | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index d2787e94a75..b720a1d2ac0 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -10,6 +10,7 @@ #include #include "util/path.h" // for PATH_MAX on Windows +#include "controllers/controllermanager.h" #include "controllers/hid/hidcontroller.h" #include "controllers/defs_controllers.h" #include "util/trace.h" @@ -18,7 +19,6 @@ HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig) : Controller(pConfig), - m_lastIncomingData(), m_pHidDevice(nullptr) { // Copy required variables from deviceInfo, which will be freed after // this class is initialized by caller. @@ -245,19 +245,19 @@ bool HidController::poll() { Trace hidRead("HidController poll"); int result = 1; + auto loopStartTime = mixxx::Time::elapsed(); while (result > 0) { + // Failsafe in case the script takes too long. This can happen if a controller constitutively spams + // HID messages even if there are no changes since the last message, for example the Gemini GMX. + if (mixxx::Time::elapsed() - loopStartTime >= ControllerManager::kPollInterval) { + return true; + } result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0])); if (result == -1) { return false; } else if (result > 0) { Trace process("HidController process packet"); auto byteArray = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); - if (byteArray == m_lastIncomingData) { - continue; - } else { - // force a copy - m_lastIncomingData = QByteArray(byteArray.data()); - } receive(byteArray, mixxx::Time::elapsed()); } } diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index 32f16d5a2b5..543684ee766 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -84,7 +84,6 @@ class HidController final : public Controller { QString hid_serial; QString hid_manufacturer; QString hid_product; - QByteArray m_lastIncomingData; QString m_sUID; hid_device* m_pHidDevice; From 759a0c236ad46e6a91f741fb52c5d23866d5bf2b Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 2 Aug 2020 18:59:53 -0500 Subject: [PATCH 13/84] HidController: clang-format #includes --- src/controllers/hid/hidcontroller.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index b720a1d2ac0..264c8ff0c89 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -6,16 +6,17 @@ * */ -#include +#include "controllers/hid/hidcontroller.h" + #include +#include -#include "util/path.h" // for PATH_MAX on Windows +#include "controllers/controllerdebug.h" #include "controllers/controllermanager.h" -#include "controllers/hid/hidcontroller.h" #include "controllers/defs_controllers.h" -#include "util/trace.h" -#include "controllers/controllerdebug.h" +#include "util/path.h" // for PATH_MAX on Windows #include "util/time.h" +#include "util/trace.h" HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig) : Controller(pConfig), From f5f650167d924496f7f3ce13476e3289139209b6 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 2 Aug 2020 23:13:21 -0400 Subject: [PATCH 14/84] Traktor S3: good progress on lights --- .../Traktor-Kontrol-S3-hid-scripts.js | 620 +++++++++++------- .../Traktor-Kontrol-S4-MK2-hid-scripts.js | 2 +- res/controllers/common-hid-packet-parser.js | 6 +- 3 files changed, 377 insertions(+), 251 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 1e37a51c13b..aa39a57f794 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -18,7 +18,6 @@ /* */ /* TODO: /* * Active deck stuff: -/* * activedeck still only supports one switch /* * colors assume single "off" instead of a dim version of the on color. /* * I don't think I can use the built-in deck switchign because of the different /* kinds of lights -- library assumes one type of led @@ -39,7 +38,19 @@ var TraktorS3 = new function() { this.shiftPressed = {"deck1": false, "deck2": false}; this.previewPressed = {"deck1": false, "deck2": false}; this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; - this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode + this.padModeState = { "deck1": 0, "deck2": 0 }; // 0 = Hotcues Mode, 1 = Samples Mode + + // When true, packets will not be sent to the controller. Good for doing mass updates. + this.freeze_lights = false; + + // Active deck switches -- common-hid-packet-parser only has one active deck status per + // Controller object. + this.activeDecks = { + 1: true, + 2: true, + 3: false, + 4: false + }; // Knob encoder states (hold values between 0x0 and 0xF) // Rotate to the right is +1 and to the left is means -1 @@ -85,8 +96,8 @@ var TraktorS3 = new function() { // The S3 has a set of predefined colors for many buttons. They are not // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. - this.controller.LEDDimColors = { - off: 0x00, + this.controller.LEDColors = { + OFF: 0x00, RED: 0x04, CARROT: 0x08, ORANGE: 0x0C, @@ -106,34 +117,34 @@ var TraktorS3 = new function() { WHITE: 0x44 }; - // The S3 has a set of predefined colors for many buttons. They are not - // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. - this.controller.LEDColors = { - off: 0x02, - RED: 0x06, - CARROT: 0x0A, - ORANGE: 0x0E, - HONEY: 0x12, - YELLOW: 0x16, - LIME: 0x1A, - GREEN: 0x1E, - AQUA: 0x22, - CELESTE: 0x26, - SKY: 0x2A, - BLUE: 0x2E, - PURPLE: 0x32, - FUSCHIA: 0x36, - MAGENTA: 0x3A, - AZALEA: 0x3E, - SALMON: 0x42, - WHITE: 0x46 - }; + // // The S3 has a set of predefined colors for many buttons. They are not + // // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. + // this.controller.LEDColors = { + // off: 0x02, + // RED: 0x06, + // CARROT: 0x0A, + // ORANGE: 0x0E, + // HONEY: 0x12, + // YELLOW: 0x16, + // LIME: 0x1A, + // GREEN: 0x1E, + // AQUA: 0x22, + // CELESTE: 0x26, + // SKY: 0x2A, + // BLUE: 0x2E, + // PURPLE: 0x32, + // FUSCHIA: 0x36, + // MAGENTA: 0x3A, + // AZALEA: 0x3E, + // SALMON: 0x42, + // WHITE: 0x46 + // }; this.controller.deckOutputColors = { - 1: "ORANGE", - 2: "ORANGE", - 3: "SKY", - 4: "SKY" + 1: "AZALEA", + 2: "AZALEA", + 3: "CELESTE", + 4: "CELESTE" }; this.colorMap = new ColorMapper({ @@ -170,8 +181,6 @@ var TraktorS3 = new function() { } }; - this.controller.switchDeck(1); - this.controller.switchDeck(2); }; TraktorS3.init = function(_id) { @@ -181,6 +190,11 @@ TraktorS3.init = function(_id) { // TraktorS3.lightDeck(true); TraktorS3.debugLights(); + + TraktorS3.lightDeck("[Channel3]"); + TraktorS3.lightDeck("[Channel4]"); + TraktorS3.lightDeck("[Channel1]"); + TraktorS3.lightDeck("[Channel2]"); }; TraktorS3.registerInputPackets = function() { @@ -409,22 +423,48 @@ TraktorS3.deckSwitchHandler = function(field) { if (field.value === 0) { return; } - // for (var key in field) { - // var value = field[key]; - // HIDDebug("what is a field: " + key + ": " + value); + // // for (var key in field) { + // // var value = field[key]; + // // HIDDebug("what is a field: " + key + ": " + value); + // // } + // var requestedDeck = TraktorS3.controller.resolveDeck(field.group); + // HIDDebug("requested " + requestedDeck + " " + TraktorS3.controller.activeDeck); + // TraktorS3.lightDeck(false); + // if (requestedDeck === TraktorS3.controller.activeDeck) { + // return; // } - var requestedDeck = TraktorS3.controller.resolveDeck(field.group); - HIDDebug("requested " + requestedDeck + " " + TraktorS3.controller.activeDeck); - TraktorS3.lightDeck(false); - if (requestedDeck === TraktorS3.controller.activeDeck) { - return; + // TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); + + HIDDebug("group?? " + field.group); + + if (field.group === "[Channel1]") { + HIDDebug("HERE!"); + TraktorS3.activeDecks[1] = true; + TraktorS3.activeDecks[3] = false; + } else if (field.group === "[Channel3]") { + TraktorS3.activeDecks[3] = true; + TraktorS3.activeDecks[1] = false; + } else if (field.group === "[Channel2]") { + TraktorS3.activeDecks[2] = true; + TraktorS3.activeDecks[4] = false; + } else if (field.group === "[Channel4]") { + TraktorS3.activeDecks[4] = true; + TraktorS3.activeDecks[2] = false; + } else { + HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); } - TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); + engine.softTakeoverIgnoreNextValue(field.group, "rate"); + TraktorS3.lightDeck(field.group); }; TraktorS3.playHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); - // HIDDebug("group?? " + activeGroup); + HIDDebug("WELLL?? " + field.group + " " + field.id); + var activeGroup = TraktorS3.deckToGroup(field.group); + HIDDebug("hmmm " + activeGroup); + if (!activeGroup) { + return; + } + HIDDebug("play group?? " + activeGroup + " " + field.group); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "start_stop", field.value); } else if (field.value === 1) { @@ -434,7 +474,10 @@ TraktorS3.playHandler = function(field) { }; TraktorS3.cueHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "cue_gotoandstop", field.value); } else { @@ -444,7 +487,10 @@ TraktorS3.cueHandler = function(field) { TraktorS3.shiftHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } HIDDebug("SHIFT!" + activeGroup + " " + field.value); TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); @@ -453,7 +499,10 @@ TraktorS3.shiftHandler = function(field) { }; TraktorS3.keylockHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (field.value === 0) { return; } @@ -462,7 +511,10 @@ TraktorS3.keylockHandler = function(field) { }; TraktorS3.syncHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.shiftPressed[field.group]) { // engine.setValue(activeGroup, "sync_enabled", field.value); } else if (field.value === 1) { @@ -475,7 +527,10 @@ TraktorS3.padModeHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.padModeState[activeGroup] === 0 && field.name === "!samples") { // If we are in hotcues mode and samples mode is activated @@ -507,7 +562,10 @@ TraktorS3.padModeHandler = function(field) { TraktorS3.numberButtonHandler = function(field) { var padNumber = parseInt(field.id[field.id.length - 1]); - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.padModeState[activeGroup] === 0) { // Hotcues mode if (TraktorS3.shiftPressed[field.group]) { @@ -540,13 +598,21 @@ TraktorS3.headphoneHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + // var activeGroup = TraktorS3.resolveDeckIfActive(group); + // if (!activeGroup) { + // return; + // } - script.toggleControl(activeGroup, "pfl"); + HIDDebug("PFL! " + field.group); + + script.toggleControl(field.group, "pfl"); }; TraktorS3.selectTrackHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } var delta = 1; if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[activeGroup]) { delta = -1; @@ -576,7 +642,10 @@ TraktorS3.selectTrackHandler = function(field) { TraktorS3.loadTrackHandler = function(field) { HIDDebug("LOAD TRACK!!!"); - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "eject", field.value); } else { @@ -594,16 +663,19 @@ TraktorS3.previewTrackHandler = function(field) { } }; -TraktorS3.cueAutoDJHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); - TraktorS3.colorOutputHandler(field.value, field.group, "addTrack"); +// TraktorS3.cueAutoDJHandler = function(field) { +// var activeGroup = TraktorS3.resolveDeckIfActive(group); +// if (!activeGroup) { +// return; +// } +// TraktorS3.colorOutputHandler(field.value, field.group, "addTrack"); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue("[Library]", "AutoDjAddTop", field.value); - } else { - engine.setValue("[Library]", "AutoDjAddBottom", field.value); - } -}; +// if (TraktorS3.shiftPressed[field.group]) { +// engine.setValue("[Library]", "AutoDjAddTop", field.value); +// } else { +// engine.setValue("[Library]", "AutoDjAddBottom", field.value); +// } +// }; TraktorS3.LibraryFocusHandler = function(field) { if (field.value === 0) { @@ -614,7 +686,10 @@ TraktorS3.LibraryFocusHandler = function(field) { }; TraktorS3.selectLoopHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if ((field.value + 1) % 16 === TraktorS3.loopKnobEncoderState[activeGroup]) { script.triggerControl(activeGroup, "loop_halve"); } else { @@ -628,7 +703,10 @@ TraktorS3.activateLoopHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } var isLoopActive = engine.getValue(activeGroup, "loop_enabled"); if (TraktorS3.shiftPressed[field.group]) { @@ -643,7 +721,10 @@ TraktorS3.activateLoopHandler = function(field) { }; TraktorS3.selectBeatjumpHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } var delta = 1; if ((field.value + 1) % 16 === TraktorS3.moveKnobEncoderState[activeGroup]) { delta = -1; @@ -668,7 +749,10 @@ TraktorS3.selectBeatjumpHandler = function(field) { }; TraktorS3.activateBeatjumpHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "reloop_andstop", field.value); } else { @@ -680,7 +764,10 @@ TraktorS3.quantizeHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } // TODO: fix quantize var res = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); @@ -714,7 +801,10 @@ TraktorS3.microphoneHandler = function(field) { }; TraktorS3.parameterHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } engine.setParameter(activeGroup, field.name, field.value / 4095); }; @@ -726,15 +816,25 @@ TraktorS3.samplerPregainHandler = function(field) { } }; -TraktorS3.jogTouchHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); +TraktorS3.jogTouchHandler = function (field) { + HIDDebug("jogtouch! " + field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } + HIDDebug("jog active group " + activeGroup); if (TraktorS3.wheelTouchInertiaTimer[activeGroup] != 0) { // The wheel was touched again, reset the timer. engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[activeGroup]); TraktorS3.wheelTouchInertiaTimer[activeGroup] = 0; } if (field.value !== 0) { - var deckNumber = TraktorS3.controller.resolveDeck(group); + var deckNumber = TraktorS3.controller.resolveDeck(activeGroup); + if (deckNumber === undefined) { + HIDDebug("############################## error, deck 0???"); + return; + } + HIDDebug("scratchen " + deckNumber); engine.scratchEnable(deckNumber, 1024, 33.3333, 0.125, 0.125/8, true); } else { // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. @@ -753,7 +853,10 @@ TraktorS3.jogTouchHandler = function(field) { }; TraktorS3.jogHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); var tick_delta = deltas[0]; var time_delta = deltas[1]; @@ -864,7 +967,7 @@ TraktorS3.fxHandler = function(field) { }; TraktorS3.reverseHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "reverseroll", field.value); } else { @@ -878,13 +981,13 @@ TraktorS3.fluxHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); script.toggleControl(activeGroup, "slip_enabled"); }; TraktorS3.beatgridHandler = function(field) { - var activeGroup = TraktorS3.controller.resolveGroup(field.group); + var activeGroup = TraktorS3.deckToGroup(field.group); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "beats_translate_match_alignment", field.value); } else { @@ -903,9 +1006,9 @@ TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var data_strings = [ - " 7C 7C 7C 2C 2C 2C 2C 39 2C 2E FF 2C 2C 3F " + - "FF 2C 7E FF 00 FF FF FF 2C 2C 2C 7C 7C 7C FF 2C " + - "2C 2C 2C 2C 2E 0C 2C 2C 2E 00 2C 7C 00 00 00 00 " + + " 7C 7C 00 2C 2C 2C 2C 39 2C 00 FF FF 2C 00 " + + "FF 2C 7E DD 00 FF FF FF 2C 2C 2C 7C 7C 7C FF 2C " + + "2C 2C FF 2C FF FF 2C 2C 2E FF 2C 7C FF 00 00 00 " + "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", @@ -957,16 +1060,6 @@ TraktorS3.debugLights = function() { } }; -// 4 levels of every color, starting at 0x04 -// last colors at 0x40, then just whites -// until 0x80 when it wraps around again -// 0 red/orange: off, red, orange, wheat, -// 1 yellows/greens: sun, parchment, kelp, grass -// 2 blues: tropical water, baby blue, sky, cerulean -// 3 purples/pinks: purple, orchid, don't use, azalea -// 4 salmon, white, white, white - - TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); @@ -979,6 +1072,11 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "reverse", 0x03, "B"); outputA.addOutput("deck2", "reverse", 0x1C, "B"); + outputA.addOutput("[Channel1]", "!deck_A", 0x0A, "B"); + outputA.addOutput("[Channel2]", "!deck_B", 0x23, "B"); + outputA.addOutput("[Channel3]", "!deck_C", 0x0B, "B"); + outputA.addOutput("[Channel4]", "!deck_D", 0x24, "B"); + outputA.addOutput("deck1", "keylock", 0x0D, "B"); outputA.addOutput("deck2", "keylock", 0x26, "B"); @@ -1020,8 +1118,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Channel3]", "pfl", 0x38, "B"); outputA.addOutput("[Channel4]", "pfl", 0x3B, "B"); - // outputA.addOutput("deck1", "addTrack", 0x03, "B"); - // outputA.addOutput("deck2", "addTrack", 0x2A, "B"); + outputA.addOutput("deck1", "addTrack", 0x03, "B"); + outputA.addOutput("deck2", "addTrack", 0x2A, "B"); outputA.addOutput("deck1", "grid", 0x08, "B"); outputA.addOutput("deck2", "grid", 0x20, "B"); @@ -1038,8 +1136,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); var wheelOffsets = { - "deck1": 0x44, - "deck2": 0x4B + "deck1": 0x43, + "deck2": 0x4A }; for (ch in wheelOffsets) { for (i = 0; i < 8; i++) { @@ -1047,7 +1145,6 @@ TraktorS3.registerOutputPackets = function() { } } - this.controller.registerOutputPacket(outputA); var outputB = new HIDPacket("outputB", 0x81); @@ -1084,34 +1181,23 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputB); // Play is always green - this.linkOutput("deck1", "play_indicator", this.deckOutputHandler); - this.linkOutput("deck2", "play_indicator", this.deckOutputHandler); - this.linkOutput("deck1", "play_indicator", this.wheelOutputHandler); - this.linkOutput("deck2", "play_indicator", this.wheelOutputHandler); - - this.linkOutput("deck1", "cue_indicator", this.colorOutputHandler); - this.linkOutput("deck2", "cue_indicator", this.colorOutputHandler); - - this.linkOutput("deck1", "sync_enabled", this.colorOutputHandler); - this.linkOutput("deck2", "sync_enabled", this.colorOutputHandler); - - this.linkOutput("deck1", "keylock", this.colorOutputHandler); - this.linkOutput("deck2", "keylock", this.colorOutputHandler); - - for (var i = 1; i <= 8; ++i) { - TraktorS3.controller.linkOutput("deck1", "pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - TraktorS3.controller.linkOutput("deck2", "pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - } - - this.linkOutput("[Channel1]", "pfl", this.outputHandler); - this.linkOutput("[Channel2]", "pfl", this.outputHandler); - this.linkOutput("[Channel3]", "pfl", this.outputHandler); - this.linkOutput("[Channel4]", "pfl", this.outputHandler); + TraktorS3.linkDeckOutputs("play_indicator", this.wheelOutputHandler); + TraktorS3.linkDeckOutputs("cue_indicator", this.colorOutputHandler); + TraktorS3.linkDeckOutputs("sync_enabled", this.colorOutputHandler); + TraktorS3.linkDeckOutputs("keylock", this.colorOutputHandler); + TraktorS3.linkDeckOutputs("slip_enabled", this.deckOutputHandler); + + // for (var i = 1; i <= 8; ++i) { + // TraktorS3.linkDeckOutputs("pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // TraktorS3.linkDeckOutputs("pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // } - this.linkOutput("deck1", "slip_enabled", this.deckOutputHandler); - this.linkOutput("deck2", "slip_enabled", this.deckOutputHandler); + TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); + TraktorS3.linkChannelOutput("[Channel2]", "pfl", this.outputHandler); + TraktorS3.linkChannelOutput("[Channel3]", "pfl", this.outputHandler); + TraktorS3.linkChannelOutput("[Channel4]", "pfl", this.outputHandler); - this.linkOutput("[Microphone]", "talkover", this.outputHandler); + // this.linkOutput("[Microphone]", "talkover", this.outputHandler); // Channel VuMeters for (var i = 1; i <= 4; i++) { @@ -1122,23 +1208,143 @@ TraktorS3.registerOutputPackets = function() { // Master VuMeters this.masterVuConnections["VuMeterL"] = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); this.masterVuConnections["VuMeterR"] = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); - this.linkOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); - this.linkOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); + this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); + this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); // Sampler callbacks for (i = 1; i <= 16; ++i) { this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", this.samplesOutputHandler)); } - - TraktorS3.lightDeck(false); }; /* Helper function to link output in a short form */ -TraktorS3.linkOutput = function(group, name, callback) { +TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; +TraktorS3.linkDeckOutputs = function(key, callback) { + // Linking outputs is a little tricky because the library doesn't quite do what I want. But this + // method works. + TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); + engine.connectControl("[Channel3]", key, callback); + TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); + engine.connectControl("[Channel4]", key, callback); +} + +TraktorS3.deckToGroup = function (deck) { + if (deck === "deck1") { + if (this.activeDecks[1]) { + return "[Channel1]"; + } else if (this.activeDecks[3]) { + return "[Channel3]"; + } + } else if (deck === "deck2") { + if (this.activeDecks[2]) { + return "[Channel2]"; + } else if (this.activeDecks[4]) { + return "[Channel4]"; + } + } + // Return original value, it's already a group + return deck; +} + +TraktorS3.resolveDeckIfActive = function(group) { + var controller = TraktorS3.controller; + if (group === "[Channel1]") { + if (controller.left_deck_C) { + return undefined; + } + return "deck1"; + } else if (group === "[Channel3]") { + if (!controller.left_deck_C) { + return undefined; + } + return "deck1"; + } else if (group === "[Channel2]") { + if (controller.right_deck_D) { + return undefined; + } + return "deck2"; + } else if (group === "[Channel4]") { + if (!controller.right_deck_D) { + return undefined; + } + return "deck2"; + } + return undefined; +} + +TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { + var group_ob = packet.groups[output_group_name]; + // HIDDebug("heyooooo group! " + output_group_name); + for (var field_name in group_ob) { + field = group_ob[field_name]; + if (field.name[0] === "!") { + // HIDDebug("skipping" + field_name); + continue; + } + if (field.mapped_callback !== undefined) { + var value = engine.getValue(co_group_name, field.name); + field.mapped_callback(value, co_group_name, field.name); + } + // No callback, no light! + } +} + +TraktorS3.lightDeck = function(group) { + // Freeze the lights while we do this update so we don't spam HID. + this.controller.freeze_lights = true; + for (var packet_name in this.controller.OutputPackets) { + packet = this.controller.OutputPackets[packet_name]; + var deck_group_name = "deck1"; + if (group === "[Channel2]" || group === "[Channel4]") { + deck_group_name = "deck2"; + } + + HIDDebug("light groups! " + deck_group_name + " " + group); + TraktorS3.lightGroup(packet, deck_group_name, group); + TraktorS3.lightGroup(packet, group, group); + // Shift is a weird key because there's no CO that it is actually associated with. + // TraktorS3.outputHandler(0, group, "!shift"); + } + +// // FX buttons +// var packet = this.controller.OutputPackets["output3"]; +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit1]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit2]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect1]", "[EffectRack1_EffectUnit1_Effect1]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect2]", "[EffectRack1_EffectUnit1_Effect2]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect3]", "[EffectRack1_EffectUnit1_Effect3]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect1]", "[EffectRack1_EffectUnit2_Effect1]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect2]", "[EffectRack1_EffectUnit2_Effect2]"); +// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect3]", "[EffectRack1_EffectUnit2_Effect3]"); + + // Selected deck lights + var ctrlr = TraktorS3.controller; + if (group === "[Channel1]") { + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + 0x02, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]], false); + } else if (group === "[Channel2]") { + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + 0x02, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]], false); + } else if (group === "[Channel3]") { + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + 0x02, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]], false); + } else if (group === "[Channel4]") { + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + 0x02, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]], false); + } + + this.controller.freeze_lights = false; + // And now send them all. + for (packet_name in this.controller.OutputPackets) { + var packet_ob = this.controller.OutputPackets[packet_name]; + packet_ob.send(); + } +} + TraktorS3.channelVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler(value, group, key, 14); }; @@ -1176,43 +1382,38 @@ TraktorS3.peakOutputHandler = function(value, group, key) { ledValue = 0x7E; } - TraktorS3.controller.setOutput(group, key, ledValue, true); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); }; // outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { HIDDebug("REGULAR OUTPUT GROUP " + group); - // var updatedDeck = TraktorS3.controller.resolveDeck(group); - // if (updatedDeck !== TraktorS3.controller.activeDeck) { - // return; - // } - // if (updatedDeck === 1 || updatedDeck === 3) { - // group = "deck1"; - // } else if (updatedDeck === 2 || updatedDeck === 4) { - // group = "deck2"; - // } var ledValue = value; if (value === 0 || value === false) { // Off value - ledValue = 0x30; + ledValue = 0x04; } else if (value === 1 || value === true) { // On value - ledValue = 0x33; + ledValue = 0xFF; } - TraktorS3.controller.setOutput(group, key, ledValue, true); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); }; // deckOutputHandler drives lights that only have one color. TraktorS3.deckOutputHandler = function(value, group, key) { // incoming value will be a channel, we have to resolve back to // deck. - var updatedDeck = TraktorS3.controller.resolveDeck(group); - HIDDebug("updatedeck: " + updatedDeck); - if (updatedDeck != TraktorS3.controller.activeDeck) { - HIDDebug("does not match " + updatedDeck + " " + TraktorS3.controller.activeDeck); + // HIDDebug("deckoutput group " + group); + var updatedDeck = TraktorS3.resolveDeckIfActive(group); + if (!updatedDeck) { + return; + } + // HIDDebug("deckoutput updatedeck: " + updatedDeck); + if (TraktorS3.activeDecks[updatedDeck]) { + HIDDebug("regdeck is not active: " + updatedDeck); return; } if (updatedDeck == 1 || updatedDeck == 3) { @@ -1220,46 +1421,63 @@ TraktorS3.deckOutputHandler = function(value, group, key) { } else if (updatedDeck == 2 || updatedDeck == 4) { group = "deck2"; } - HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + value); + // HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + value); var ledValue = value; if (value === 0 || value === false) { // Off value - ledValue = 0x30; + ledValue = 0x22; } else if (value === 1 || value === true) { // On value - ledValue = 0x33; + ledValue = 0x77; } - TraktorS3.controller.setOutput(group, key, ledValue, true); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + // TraktorS3.controller.setOutput(group, key, ledValue, true); }; -TraktorS3.wheelOutputHandler = function(value, group, _key) { - var activeGroup = TraktorS3.controller.resolveGroup(group); +TraktorS3.wheelOutputHandler = function (value, group, key) { + // Also call regular handler + TraktorS3.deckOutputHandler(value, group, key); + + // var activeGroup = TraktorS3.deckToGroup(group); + var deck = TraktorS3.controller.resolveDeck(group); + if (deck === undefined) { + return; + } + // var ctrlr = TraktorS3.controller; + // var ledValue = ctrlr.LEDColors[ctrlr.deckOutputColors[deck]]; + // HIDDebug("wheel! of! " + deck + " " + ctrlr.deckOutputColors[deck] + " " + ledValue ); + // if (value) { + // ledValue += 0x02; + // } for (var i = 0; i < 8; i++) { var sendPacket = (i == 7); + // HIDDebug("wheel! " + ledValue.toString(16) + " " + sendPacket); TraktorS3.colorOutputHandler(value, group, "!wheel" + i, sendPacket); } }; // colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { +TraktorS3.colorOutputHandler = function (value, group, key, sendPacket) { + HIDDebug("coloroutput! " + value + " " + group + " " + key); // Reject update if it's for a deck that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); - if (updatedDeck !== TraktorS3.controller.activeDeck) { + if (!TraktorS3.activeDecks[updatedDeck]) { + HIDDebug("colordeck is not active: " + updatedDeck); return; } - HIDDebug("updatedeck? " + updatedDeck); + // HIDDebug("color updatedeck? " + updatedDeck); if (updatedDeck == 1 || updatedDeck == 3) { group = "deck1"; } else if (updatedDeck == 2 || updatedDeck == 4) { group = "deck2"; } - HIDDebug("color output: " + group + "." + key + " " + value); - var activeGroup = TraktorS3.controller.resolveGroup(group); + // HIDDebug("color output: " + group + "." + key + " " + value); + // var activeGroup = TraktorS3.deckToGroup(group); var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; - HIDDebug("group!" + activeGroup + " color " + ledValue); + // HIDDebug("group!" + activeGroup + " color " + ledValue); if (value === 1 || value === true) { ledValue += 0x02; } @@ -1267,7 +1485,7 @@ TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { sendPacket = true; } - HIDDebug("So I think: " + group + " " + key); + // HIDDebug("So I think: " + group + " " + key); TraktorS3.controller.setOutput(group, key, ledValue, sendPacket); }; @@ -1328,98 +1546,6 @@ TraktorS3.resolveSampler = function(group) { return result[1]; }; -TraktorS3.lightDeck = function(switchOff) { - // return; - HIDDebug("--------------------light deck"); - var softLight = 0x30; - var fullLight = 0x33; - if (switchOff) { - softLight = 0x00; - fullLight = 0x00; - } - - var groupA = TraktorS3.controller.resolveGroup("deck1"); - var groupB = TraktorS3.controller.resolveGroup("deck2"); - - var current = (engine.getValue(groupA, "play_indicator") ? fullLight : softLight); - TraktorS3.controller.setOutput("deck1", "play_indicator", current, false); - current = (engine.getValue(groupB, "play_indicator")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "play_indicator", current, false); - - current = (engine.getValue(groupA, "cue_indicator")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck1", "cue_indicator", false); - current = (engine.getValue(groupB, "cue_indicator")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck2", "cue_indicator", false); - - TraktorS3.controller.setOutput("deck1", "shift", softLight, false); - TraktorS3.controller.setOutput("deck2", "shift", softLight, false); - - current = (engine.getValue(groupA, "sync_enabled")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck1", "sync_enabled"); - current = (engine.getValue(groupB, "sync_enabled")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck2", "sync_enabled"); - - // Hotcues mode is default start value - TraktorS3.colorOutputHandler(true, "deck1", "hotcues"); - TraktorS3.colorOutputHandler(true, "deck2", "hotcues"); - - TraktorS3.controller.setOutput("deck1", "samples", softLight, false); - TraktorS3.controller.setOutput("deck2", "samples", softLight, false); - - current = (engine.getValue(groupA, "keylock")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck1", "keylock"); - current = (engine.getValue(groupB, "keylock")) ? fullLight : softLight; - TraktorS3.colorOutputHandler(current, "deck2", "keylock"); - - for (var i = 1; i <= 8; ++i) { - current = (engine.getValue(groupA, "hotcue_" + i + "_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck1", "pad_" + i, current, false); - current = (engine.getValue(groupB, "hotcue_" + i + "_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "pad_" + i, current, false); - } - - current = (engine.getValue("[Channel1]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel1]", "pfl", current, false); - current = (engine.getValue("[Channel2]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel2]", "pfl", current, false); - current = (engine.getValue("[Channel3]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel3]", "pfl", current, false); - current = (engine.getValue("[Channel4]", "pfl")) ? fullLight : softLight; - TraktorS3.controller.setOutput("[Channel4]", "pfl", current, false); - - TraktorS3.controller.setOutput("[ChannelX]", "fxButton1", softLight, false); - TraktorS3.controller.setOutput("[ChannelX]", "fxButton2", softLight, false); - TraktorS3.controller.setOutput("[ChannelX]", "fxButton3", softLight, false); - TraktorS3.controller.setOutput("[ChannelX]", "fxButton4", softLight, false); - - TraktorS3.controller.setOutput("deck1", "reverse", softLight, false); - TraktorS3.controller.setOutput("deck2", "reverse", softLight, false); - - current = (engine.getValue(groupA, "slip_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck1", "slip_enabled", current, false); - current = (engine.getValue(groupB, "slip_enabled")) ? fullLight : softLight; - TraktorS3.controller.setOutput("deck2", "slip_enabled", current, false); - - TraktorS3.controller.setOutput("deck1", "addTrack", softLight, false); - TraktorS3.controller.setOutput("deck2", "addTrack", softLight, false); - - TraktorS3.colorOutputHandler(0, "deck1", "grid"); - TraktorS3.colorOutputHandler(0, "deck2", "grid"); - - TraktorS3.colorOutputHandler(0, "deck1", "LibraryFocus"); - TraktorS3.colorOutputHandler(0, "deck2", "LibraryFocus"); - - // TraktorS3.controller.setOutput("[ChannelX]", "quantize", softLight, false); - - // For the last output we should send the packet finally - // current = (engine.getValue("[Microphone]", "talkover")) ? fullLight : softLight; - - TraktorS3.controller.setOutput("[Microphone]", "talkover", current, true); - TraktorS3.controller.OutputPackets["outputA"].send(true); - // TraktorS3.controller.OutputPackets["outputB"].send(true); - HIDDebug("--------------------light deck DONE"); -}; - TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { if (data.hasOwnProperty(name)) { @@ -1430,7 +1556,7 @@ TraktorS3.messageCallback = function(_packet, data) { TraktorS3.shutdown = function() { // Deactivate all LEDs - TraktorS3.lightDeck(true); + // TraktorS3.lightDeck(true); HIDDebug("TraktorS3: Shutdown done!"); }; diff --git a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js index 2a3f322454b..7a37ac43ec1 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js @@ -1428,7 +1428,7 @@ TraktorS4MK2.scalerSlider = function(group, name, value) { } TraktorS4MK2.resolveDeckIfActive = function(group) { - var controller = TraktorS4MK2.controller; + var controller = TraktorS4MK2.controllger; if (group === "[Channel1]") { if (controller.left_deck_C) { return undefined; diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 90fbb2a46f4..dd6f6887173 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1538,9 +1538,9 @@ HIDController.prototype.setOutput = function(group, name, value, send_packet) { HIDDebug("setOutput: unknown field: " + group + "." + name); return; } - if (value !== undefined) { - HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); - } + // if (value !== undefined) { + // HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); + // } field.value = value << field.bit_offset; field.toggle = value << field.bit_offset; if (send_packet) From adbcd3f4fb3a61b214af43affba2b50cdf34dc95 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 4 Aug 2020 12:16:15 -0400 Subject: [PATCH 15/84] Traktor S3: more lights --- .../Traktor-Kontrol-S3-hid-scripts.js | 282 +++++++++--------- res/controllers/common-hid-packet-parser.js | 7 +- src/engine/controls/ratecontrol.cpp | 1 + src/mixer/basetrackplayer.cpp | 4 +- 4 files changed, 143 insertions(+), 151 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index aa39a57f794..5887c03b50b 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -38,7 +38,7 @@ var TraktorS3 = new function() { this.shiftPressed = {"deck1": false, "deck2": false}; this.previewPressed = {"deck1": false, "deck2": false}; this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; - this.padModeState = { "deck1": 0, "deck2": 0 }; // 0 = Hotcues Mode, 1 = Samples Mode + this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode // When true, packets will not be sent to the controller. Good for doing mass updates. this.freeze_lights = false; @@ -117,34 +117,11 @@ var TraktorS3 = new function() { WHITE: 0x44 }; - // // The S3 has a set of predefined colors for many buttons. They are not - // // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. - // this.controller.LEDColors = { - // off: 0x02, - // RED: 0x06, - // CARROT: 0x0A, - // ORANGE: 0x0E, - // HONEY: 0x12, - // YELLOW: 0x16, - // LIME: 0x1A, - // GREEN: 0x1E, - // AQUA: 0x22, - // CELESTE: 0x26, - // SKY: 0x2A, - // BLUE: 0x2E, - // PURPLE: 0x32, - // FUSCHIA: 0x36, - // MAGENTA: 0x3A, - // AZALEA: 0x3E, - // SALMON: 0x42, - // WHITE: 0x46 - // }; - this.controller.deckOutputColors = { - 1: "AZALEA", - 2: "AZALEA", - 3: "CELESTE", - 4: "CELESTE" + 1: "CARROT", + 2: "CARROT", + 3: "SKY", + 4: "SKY" }; this.colorMap = new ColorMapper({ @@ -291,8 +268,8 @@ TraktorS3.registerInputPackets = function() { // // Jog wheels this.registerInputButton(messageShort, "deck1", "!jog_touch", 0x0A, 0x10, this.jogTouchHandler); this.registerInputButton(messageShort, "deck2", "!jog_touch", 0x0A, 0x20, this.jogTouchHandler); - this.registerInputJog(messageShort, "deck1", "!jog", 0x0E, 0xFFFFFF, this.jogHandler); - this.registerInputJog(messageShort, "deck2", "!jog", 0x12, 0xFFFFFF, this.jogHandler); + this.registerInputJog(messageShort, "deck1", "!jog", 0x0E, 0xFFFFFFFF, this.jogHandler); + this.registerInputJog(messageShort, "deck2", "!jog", 0x12, 0xFFFFFFFF, this.jogHandler); // // FX Buttons this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); @@ -495,7 +472,8 @@ TraktorS3.shiftHandler = function(field) { TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); // Shift is only white - TraktorS3.deckOutputHandler(field.value, field.group, "shift"); + HIDDebug("me light shift " + field.group + " " + field.value); + TraktorS3.outputHandler(field.value, field.group, "shift"); }; TraktorS3.keylockHandler = function(field) { @@ -600,7 +578,7 @@ TraktorS3.headphoneHandler = function(field) { } // var activeGroup = TraktorS3.resolveDeckIfActive(group); // if (!activeGroup) { - // return; + // return; // } HIDDebug("PFL! " + field.group); @@ -816,7 +794,7 @@ TraktorS3.samplerPregainHandler = function(field) { } }; -TraktorS3.jogTouchHandler = function (field) { +TraktorS3.jogTouchHandler = function(field) { HIDDebug("jogtouch! " + field.group); var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { @@ -835,10 +813,10 @@ TraktorS3.jogTouchHandler = function (field) { return; } HIDDebug("scratchen " + deckNumber); - engine.scratchEnable(deckNumber, 1024, 33.3333, 0.125, 0.125/8, true); + engine.scratchEnable(deckNumber, 768, 33.3333, 0.125, 0.125/32, true); } else { - // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. - // Depending on how fast the platter was moving, lengthen the time we'll wait. + // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. + // Depending on how fast the platter was moving, lengthen the time we'll wait. var scratchRate = Math.abs(engine.getValue(activeGroup, "scratch2")); // Note: inertiaTime multiplier is controller-specific and should be factored out. var inertiaTime = Math.pow(1.8, scratchRate) * 2; @@ -858,10 +836,12 @@ TraktorS3.jogHandler = function(field) { return; } var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); + HIDDebug("delta: " + deltas); var tick_delta = deltas[0]; - var time_delta = deltas[1]; + var time_delta = deltas[1] / 0x100; + HIDDebug("time delt: " + time_delta.toString(16)); - var velocity = TraktorS3.scalerJog(tick_delta, time_delta); + var velocity = TraktorS3.scalerJog(activeGroup, tick_delta, time_delta); HIDDebug("VELO: " + velocity); engine.setValue(activeGroup, "jog", velocity); if (engine.getValue(activeGroup, "scratch2_enable")) { @@ -870,12 +850,13 @@ TraktorS3.jogHandler = function(field) { } }; -TraktorS3.scalerJog = function(tick_delta, time_delta) { +TraktorS3.scalerJog = function(group, tick_delta, time_delta) { // If it's playing nudge var multiplier = 1.0; if (TraktorS3.shiftPressed["deck1"] || TraktorS3.shiftPressed["deck2"]) { multiplier = 100.0; } + if (engine.getValue(group, "play")) { return multiplier * (tick_delta / time_delta) / 3; } @@ -886,8 +867,11 @@ TraktorS3.scalerJog = function(tick_delta, time_delta) { TraktorS3.wheelDeltas = function(deckNumber, value) { // When the wheel is touched, four bytes change, but only the first behaves predictably. // It looks like the wheel is 1024 ticks per revolution. + HIDDebug("VALUE: 0x" + value.toString(16)); var tickval = value & 0xFF; - var timeval = value >>> 16; + var timeval = value >>> 8; + // HIDDebug("tick: " + tickval.toString(16)); + HIDDebug("time: " + timeval.toString(16)); var prevTick = 0; var prevTime = 0; @@ -899,7 +883,8 @@ TraktorS3.wheelDeltas = function(deckNumber, value) { if (prevTime > timeval) { // We looped around. Adjust current time so that subtraction works. - timeval += 0x10000; + HIDDebug("LOOP---------------------------------------------------------------"); + timeval += 0x100000; } var timeDelta = timeval - prevTime; if (timeDelta === 0) { @@ -1006,8 +991,8 @@ TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var data_strings = [ - " 7C 7C 00 2C 2C 2C 2C 39 2C 00 FF FF 2C 00 " + - "FF 2C 7E DD 00 FF FF FF 2C 2C 2C 7C 7C 7C FF 2C " + + " 7C 7C FF 2C 2C 2C 2C 39 2C 00 FF FF 2C 00 " + + "FF 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 7C FF 2C " + "2C 2C FF 2C FF FF 2C 2C 2E FF 2C 7C FF 00 00 00 " + "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + @@ -1066,6 +1051,9 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "shift", 0x01, "B"); outputA.addOutput("deck2", "shift", 0x1A, "B"); + outputA.addOutput("deck1", "preview", 0x04, "B"); + outputA.addOutput("deck2", "preview", 0x1D, "B"); + outputA.addOutput("deck1", "slip_enabled", 0x02, "B"); outputA.addOutput("deck2", "slip_enabled", 0x1B, "B"); @@ -1224,15 +1212,15 @@ TraktorS3.linkChannelOutput = function(group, name, callback) { }; TraktorS3.linkDeckOutputs = function(key, callback) { - // Linking outputs is a little tricky because the library doesn't quite do what I want. But this - // method works. - TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); - engine.connectControl("[Channel3]", key, callback); - TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); - engine.connectControl("[Channel4]", key, callback); -} + // Linking outputs is a little tricky because the library doesn't quite do what I want. But this + // method works. + TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); + engine.connectControl("[Channel3]", key, callback); + TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); + engine.connectControl("[Channel4]", key, callback); +}; -TraktorS3.deckToGroup = function (deck) { +TraktorS3.deckToGroup = function(deck) { if (deck === "deck1") { if (this.activeDecks[1]) { return "[Channel1]"; @@ -1248,102 +1236,103 @@ TraktorS3.deckToGroup = function (deck) { } // Return original value, it's already a group return deck; -} +}; TraktorS3.resolveDeckIfActive = function(group) { - var controller = TraktorS3.controller; - if (group === "[Channel1]") { - if (controller.left_deck_C) { - return undefined; - } - return "deck1"; - } else if (group === "[Channel3]") { - if (!controller.left_deck_C) { - return undefined; - } - return "deck1"; - } else if (group === "[Channel2]") { - if (controller.right_deck_D) { - return undefined; - } - return "deck2"; - } else if (group === "[Channel4]") { - if (!controller.right_deck_D) { - return undefined; - } - return "deck2"; - } - return undefined; -} + var controller = TraktorS3.controller; + if (group === "[Channel1]") { + if (controller.left_deck_C) { + return undefined; + } + return "deck1"; + } else if (group === "[Channel3]") { + if (!controller.left_deck_C) { + return undefined; + } + return "deck1"; + } else if (group === "[Channel2]") { + if (controller.right_deck_D) { + return undefined; + } + return "deck2"; + } else if (group === "[Channel4]") { + if (!controller.right_deck_D) { + return undefined; + } + return "deck2"; + } + return undefined; +}; TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { var group_ob = packet.groups[output_group_name]; // HIDDebug("heyooooo group! " + output_group_name); - for (var field_name in group_ob) { - field = group_ob[field_name]; - if (field.name[0] === "!") { + for (var field_name in group_ob) { + field = group_ob[field_name]; + if (field.name[0] === "!") { // HIDDebug("skipping" + field_name); - continue; - } - if (field.mapped_callback !== undefined) { - var value = engine.getValue(co_group_name, field.name); - field.mapped_callback(value, co_group_name, field.name); - } + continue; + } + if (field.mapped_callback !== undefined) { + var value = engine.getValue(co_group_name, field.name); + field.mapped_callback(value, co_group_name, field.name); + } // No callback, no light! - } -} + } +}; TraktorS3.lightDeck = function(group) { - // Freeze the lights while we do this update so we don't spam HID. - this.controller.freeze_lights = true; - for (var packet_name in this.controller.OutputPackets) { - packet = this.controller.OutputPackets[packet_name]; - var deck_group_name = "deck1"; - if (group === "[Channel2]" || group === "[Channel4]") { - deck_group_name = "deck2"; - } - - HIDDebug("light groups! " + deck_group_name + " " + group); - TraktorS3.lightGroup(packet, deck_group_name, group); - TraktorS3.lightGroup(packet, group, group); - // Shift is a weird key because there's no CO that it is actually associated with. - // TraktorS3.outputHandler(0, group, "!shift"); - } - -// // FX buttons -// var packet = this.controller.OutputPackets["output3"]; -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit1]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit2]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect1]", "[EffectRack1_EffectUnit1_Effect1]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect2]", "[EffectRack1_EffectUnit1_Effect2]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect3]", "[EffectRack1_EffectUnit1_Effect3]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect1]", "[EffectRack1_EffectUnit2_Effect1]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect2]", "[EffectRack1_EffectUnit2_Effect2]"); -// TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect3]", "[EffectRack1_EffectUnit2_Effect3]"); - - // Selected deck lights + // Freeze the lights while we do this update so we don't spam HID. + this.controller.freeze_lights = true; + for (var packet_name in this.controller.OutputPackets) { + packet = this.controller.OutputPackets[packet_name]; + var deck_group_name = "deck1"; + if (group === "[Channel2]" || group === "[Channel4]") { + deck_group_name = "deck2"; + } + + HIDDebug("light groups! " + deck_group_name + " " + group); + TraktorS3.lightGroup(packet, deck_group_name, group); + TraktorS3.lightGroup(packet, group, group); + // Shift is a weird key because there's no CO that it is actually associated with. + TraktorS3.outputHandler(0, deck_group_name, "shift"); + TraktorS3.outputHandler(0, deck_group_name, "preview"); + } + + // // FX buttons + // var packet = this.controller.OutputPackets["output3"]; + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit1]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit2]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect1]", "[EffectRack1_EffectUnit1_Effect1]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect2]", "[EffectRack1_EffectUnit1_Effect2]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect3]", "[EffectRack1_EffectUnit1_Effect3]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect1]", "[EffectRack1_EffectUnit2_Effect1]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect2]", "[EffectRack1_EffectUnit2_Effect2]"); + // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect3]", "[EffectRack1_EffectUnit2_Effect3]"); + + // Selected deck lights var ctrlr = TraktorS3.controller; - if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + 0x02, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]], false); - } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + 0x02, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]], false); - } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + 0x02, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]], false); - } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + 0x02, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]], false); - } - - this.controller.freeze_lights = false; - // And now send them all. - for (packet_name in this.controller.OutputPackets) { - var packet_ob = this.controller.OutputPackets[packet_name]; - packet_ob.send(); - } -} + if (group === "[Channel1]") { + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + 0x02, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]], false); + } else if (group === "[Channel2]") { + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + 0x02, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]], false); + } else if (group === "[Channel3]") { + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + 0x02, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]], false); + } else if (group === "[Channel4]") { + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + 0x02, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]], false); + } + + this.controller.freeze_lights = false; + // And now send them all. + for (packet_name in this.controller.OutputPackets) { + var packet_ob = this.controller.OutputPackets[packet_name]; + packet_ob.send(); + } +}; TraktorS3.channelVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler(value, group, key, 14); @@ -1354,6 +1343,7 @@ TraktorS3.masterVuMeterHandler = function(value, group, key) { }; TraktorS3.vuMeterHandler = function(value, group, key, segments) { + return; // This handler is called a lot so it should be as fast as possible. // HIDDebug("group??" + group + " " + key); var scaledValue = value * segments; @@ -1406,12 +1396,13 @@ TraktorS3.outputHandler = function(value, group, key) { TraktorS3.deckOutputHandler = function(value, group, key) { // incoming value will be a channel, we have to resolve back to // deck. - // HIDDebug("deckoutput group " + group); + HIDDebug("deckoutput group " + group); var updatedDeck = TraktorS3.resolveDeckIfActive(group); if (!updatedDeck) { + HIDDebug("WRONG DECK " + group + " " + updatedDeck); return; } - // HIDDebug("deckoutput updatedeck: " + updatedDeck); + HIDDebug("deckoutput updatedeck: " + updatedDeck); if (TraktorS3.activeDecks[updatedDeck]) { HIDDebug("regdeck is not active: " + updatedDeck); return; @@ -1421,22 +1412,19 @@ TraktorS3.deckOutputHandler = function(value, group, key) { } else if (updatedDeck == 2 || updatedDeck == 4) { group = "deck2"; } - // HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + value); - var ledValue = value; - if (value === 0 || value === false) { - // Off value - ledValue = 0x22; - } else if (value === 1 || value === true) { + var ledValue = 0x20; + if (value === 1 || value === true) { // On value ledValue = 0x77; } + HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + ledValue); TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); // TraktorS3.controller.setOutput(group, key, ledValue, true); }; -TraktorS3.wheelOutputHandler = function (value, group, key) { +TraktorS3.wheelOutputHandler = function(value, group, key) { // Also call regular handler TraktorS3.deckOutputHandler(value, group, key); @@ -1449,7 +1437,7 @@ TraktorS3.wheelOutputHandler = function (value, group, key) { // var ledValue = ctrlr.LEDColors[ctrlr.deckOutputColors[deck]]; // HIDDebug("wheel! of! " + deck + " " + ctrlr.deckOutputColors[deck] + " " + ledValue ); // if (value) { - // ledValue += 0x02; + // ledValue += 0x02; // } for (var i = 0; i < 8; i++) { var sendPacket = (i == 7); @@ -1459,7 +1447,7 @@ TraktorS3.wheelOutputHandler = function (value, group, key) { }; // colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorOutputHandler = function (value, group, key, sendPacket) { +TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { HIDDebug("coloroutput! " + value + " " + group + " " + key); // Reject update if it's for a deck that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index dd6f6887173..1a07fb6255e 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1538,9 +1538,10 @@ HIDController.prototype.setOutput = function(group, name, value, send_packet) { HIDDebug("setOutput: unknown field: " + group + "." + name); return; } - // if (value !== undefined) { - // HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); - // } + HIDDebug("eh???"); + if (value !== undefined) { + HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); + } field.value = value << field.bit_offset; field.toggle = value << field.bit_offset; if (send_packet) diff --git a/src/engine/controls/ratecontrol.cpp b/src/engine/controls/ratecontrol.cpp index 2378710a7e3..74bce34d486 100644 --- a/src/engine/controls/ratecontrol.cpp +++ b/src/engine/controls/ratecontrol.cpp @@ -440,6 +440,7 @@ double RateControl::calculateSpeed(double baserate, double speed, bool paused, // New scratch behavior - overrides playback speed (and old behavior) if (useScratch2Value) { + qDebug() << "scratch val" << scratchFactor; rate = scratchFactor; } else { // add temp rate, but don't go backwards diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 2058d66d6a4..ebd7b70fd32 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -525,7 +525,9 @@ TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const { void BaseTrackPlayerImpl::slotCloneDeck() { Syncable* syncable = m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel); - slotCloneChannel(syncable->getChannel()); + if (syncable) { + slotCloneChannel(syncable->getChannel()); + } } void BaseTrackPlayerImpl::slotCloneFromGroup(const QString& group) { From 76404cc6890930c557ea33739b189bc38b0460ae Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 4 Aug 2020 12:47:41 -0400 Subject: [PATCH 16/84] Traktor S3: Fix color lights, add more --- .../Traktor-Kontrol-S3-hid-scripts.js | 137 ++++++++++-------- 1 file changed, 76 insertions(+), 61 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 5887c03b50b..bc1dd12a3ad 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -118,7 +118,7 @@ var TraktorS3 = new function() { }; this.controller.deckOutputColors = { - 1: "CARROT", + 1: "HONEY", 2: "CARROT", 3: "SKY", 4: "SKY" @@ -160,20 +160,6 @@ var TraktorS3 = new function() { }; -TraktorS3.init = function(_id) { - TraktorS3.registerInputPackets(); - TraktorS3.registerOutputPackets(); - HIDDebug("TraktorS3: Init done!"); - - // TraktorS3.lightDeck(true); - TraktorS3.debugLights(); - - TraktorS3.lightDeck("[Channel3]"); - TraktorS3.lightDeck("[Channel4]"); - TraktorS3.lightDeck("[Channel1]"); - TraktorS3.lightDeck("[Channel2]"); -}; - TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -412,10 +398,7 @@ TraktorS3.deckSwitchHandler = function(field) { // } // TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); - HIDDebug("group?? " + field.group); - if (field.group === "[Channel1]") { - HIDDebug("HERE!"); TraktorS3.activeDecks[1] = true; TraktorS3.activeDecks[3] = false; } else if (field.group === "[Channel3]") { @@ -473,7 +456,7 @@ TraktorS3.shiftHandler = function(field) { engine.setValue("[Controls]", "touch_shift", field.value); // Shift is only white HIDDebug("me light shift " + field.group + " " + field.value); - TraktorS3.outputHandler(field.value, field.group, "shift"); + TraktorS3.outputHandler(field.value, field.group, "!shift"); }; TraktorS3.keylockHandler = function(field) { @@ -639,23 +622,26 @@ TraktorS3.previewTrackHandler = function(field) { TraktorS3.previewPressed[field.group] = false; engine.setValue("[PreviewDeck1]", "play", 0); } + TraktorS3.colorOutputHandler(field.value, field.group, "!PreviewTrack"); }; -// TraktorS3.cueAutoDJHandler = function(field) { -// var activeGroup = TraktorS3.resolveDeckIfActive(group); -// if (!activeGroup) { -// return; -// } -// TraktorS3.colorOutputHandler(field.value, field.group, "addTrack"); +TraktorS3.cueAutoDJHandler = function (field) { + TraktorS3.colorOutputHandler(field.value, field.group, "!AddTrack"); -// if (TraktorS3.shiftPressed[field.group]) { -// engine.setValue("[Library]", "AutoDjAddTop", field.value); -// } else { -// engine.setValue("[Library]", "AutoDjAddBottom", field.value); -// } -// }; + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } + + if (TraktorS3.shiftPressed[field.group]) { + engine.setValue("[Library]", "AutoDjAddTop", field.value); + } else { + engine.setValue("[Library]", "AutoDjAddBottom", field.value); + } +}; TraktorS3.LibraryFocusHandler = function(field) { + TraktorS3.colorOutputHandler(field.value, field.group, "!LibraryFocus"); if (field.value === 0) { return; } @@ -982,18 +968,31 @@ TraktorS3.beatgridHandler = function(field) { TraktorS3.colorOutputHandler(field.value, field.group, "grid"); }; -function sleepFor(sleepDuration) { - var now = new Date().getTime(); - while (new Date().getTime() < now + sleepDuration) { /* do nothing */ } -} +// function sleepFor(sleepDuration) { +// var now = new Date().getTime(); +// while (new Date().getTime() < now + sleepDuration) { /* do nothing */ } +// } + +TraktorS3.init = function(_id) { + TraktorS3.registerInputPackets(); + TraktorS3.registerOutputPackets(); + HIDDebug("TraktorS3: Init done!"); + + // TraktorS3.lightDeck("[Channel3]"); + // TraktorS3.lightDeck("[Channel4]"); + TraktorS3.lightDeck("[Channel1]"); + TraktorS3.lightDeck("[Channel2]"); + + // TraktorS3.debugLights(); +}; TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var data_strings = [ - " 7C 7C FF 2C 2C 2C 2C 39 2C 00 FF FF 2C 00 " + - "FF 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 7C FF 2C " + - "2C 2C FF 2C FF FF 2C 2C 2E FF 2C 7C FF 00 00 00 " + + " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 2C 00 " + + "11 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 7C FF 2C " + + "FF 2C FF 2C FF FF 2C 2C 2E FF 2C 7C FF 00 00 00 " + "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", @@ -1048,11 +1047,8 @@ TraktorS3.debugLights = function() { TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); - outputA.addOutput("deck1", "shift", 0x01, "B"); - outputA.addOutput("deck2", "shift", 0x1A, "B"); - - outputA.addOutput("deck1", "preview", 0x04, "B"); - outputA.addOutput("deck2", "preview", 0x1D, "B"); + outputA.addOutput("deck1", "!shift", 0x01, "B"); + outputA.addOutput("deck2", "!shift", 0x1A, "B"); outputA.addOutput("deck1", "slip_enabled", 0x02, "B"); outputA.addOutput("deck2", "slip_enabled", 0x1B, "B"); @@ -1060,6 +1056,18 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "reverse", 0x03, "B"); outputA.addOutput("deck2", "reverse", 0x1C, "B"); + outputA.addOutput("deck1", "!PreviewTrack", 0x04, "B"); + outputA.addOutput("deck2", "!PreviewTrack", 0x1D, "B"); + + outputA.addOutput("deck1", "!PreviewTrack", 0x04, "B"); + outputA.addOutput("deck2", "!PreviewTrack", 0x1D, "B"); + + outputA.addOutput("deck1", "!AddTrack", 0x06, "B"); + outputA.addOutput("deck2", "!AddTrack", 0x1F, "B"); + + outputA.addOutput("deck1", "!LibraryFocus", 0x07, "B"); + outputA.addOutput("deck2", "!LibraryFocus", 0x20, "B"); + outputA.addOutput("[Channel1]", "!deck_A", 0x0A, "B"); outputA.addOutput("[Channel2]", "!deck_B", 0x23, "B"); outputA.addOutput("[Channel3]", "!deck_C", 0x0B, "B"); @@ -1112,9 +1120,6 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "grid", 0x08, "B"); outputA.addOutput("deck2", "grid", 0x20, "B"); - outputA.addOutput("deck1", "LibraryFocus", 0x07, "B"); - outputA.addOutput("deck2", "LibraryFocus", 0x21, "B"); - // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); @@ -1294,9 +1299,13 @@ TraktorS3.lightDeck = function(group) { HIDDebug("light groups! " + deck_group_name + " " + group); TraktorS3.lightGroup(packet, deck_group_name, group); TraktorS3.lightGroup(packet, group, group); - // Shift is a weird key because there's no CO that it is actually associated with. - TraktorS3.outputHandler(0, deck_group_name, "shift"); - TraktorS3.outputHandler(0, deck_group_name, "preview"); + + // These lights are different because either they aren't associated with a CO, or + // there are two buttons that point to the same CO. + TraktorS3.deckOutputHandler(0, deck_group_name, "!shift"); + TraktorS3.colorOutputHandler(0, deck_group_name, "!PreviewTrack"); + TraktorS3.colorOutputHandler(0, deck_group_name, "!AddTrack"); + TraktorS3.colorOutputHandler(0, deck_group_name, "!LibraryFocus"); } // // FX buttons @@ -1449,23 +1458,29 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { HIDDebug("coloroutput! " + value + " " + group + " " + key); - // Reject update if it's for a deck that's not selected. + // Reject update if it's for a specific channel that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); - if (!TraktorS3.activeDecks[updatedDeck]) { - HIDDebug("colordeck is not active: " + updatedDeck); - return; - } - // HIDDebug("color updatedeck? " + updatedDeck); - if (updatedDeck == 1 || updatedDeck == 3) { - group = "deck1"; - } else if (updatedDeck == 2 || updatedDeck == 4) { - group = "deck2"; + if (updatedDeck != undefined) { + if (!TraktorS3.activeDecks[updatedDeck]) { + HIDDebug("colordeck is not active: " + updatedDeck); + return; + } + // HIDDebug("color updatedeck? " + updatedDeck); + if (updatedDeck == 1 || updatedDeck == 3) { + group = "deck1"; + } else if (updatedDeck == 2 || updatedDeck == 4) { + group = "deck2"; + } + } else { + // update was for a deck in general. Pick the color based on the appropriate channel + // for this deck. + updatedDeck = TraktorS3.controller.resolveDeck(TraktorS3.deckToGroup(group)); } + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; // HIDDebug("color output: " + group + "." + key + " " + value); // var activeGroup = TraktorS3.deckToGroup(group); - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; - // HIDDebug("group!" + activeGroup + " color " + ledValue); + // HIDDebug("group!" + group + " color " + ledValue); if (value === 1 || value === true) { ledValue += 0x02; } From 907068ef99721020b59e98f625d4209e7d15661b Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 4 Aug 2020 13:02:59 -0400 Subject: [PATCH 17/84] Traktor S3: configurable dim/bright color values --- .../Traktor-Kontrol-S3-hid-scripts.js | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index bc1dd12a3ad..52d40188d74 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -117,8 +117,12 @@ var TraktorS3 = new function() { WHITE: 0x44 }; + // Each color has four brightnesses. + this.LEDDimValue = 0x00; + this.LEDBrightValue = 0x02; + this.controller.deckOutputColors = { - 1: "HONEY", + 1: "CARROT", 2: "CARROT", 3: "SKY", 4: "SKY" @@ -1322,17 +1326,17 @@ TraktorS3.lightDeck = function(group) { // Selected deck lights var ctrlr = TraktorS3.controller; if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + 0x02, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]], false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + 0x02, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]], false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + 0x02, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]], false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + 0x02, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]], false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDDimValue, false); } this.controller.freeze_lights = false; @@ -1352,7 +1356,7 @@ TraktorS3.masterVuMeterHandler = function(value, group, key) { }; TraktorS3.vuMeterHandler = function(value, group, key, segments) { - return; + // return; // This handler is called a lot so it should be as fast as possible. // HIDDebug("group??" + group + " " + key); var scaledValue = value * segments; @@ -1386,7 +1390,6 @@ TraktorS3.peakOutputHandler = function(value, group, key) { // outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { - HIDDebug("REGULAR OUTPUT GROUP " + group); var ledValue = value; @@ -1482,7 +1485,9 @@ TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { // var activeGroup = TraktorS3.deckToGroup(group); // HIDDebug("group!" + group + " color " + ledValue); if (value === 1 || value === true) { - ledValue += 0x02; + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; } if (sendPacket === undefined) { sendPacket = true; From bce2fe86fd0b0af53072496f1f15592eb3aaf5e3 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 4 Aug 2020 23:58:22 -0400 Subject: [PATCH 18/84] Traktor S3: pad buttons --- .../Traktor-Kontrol-S3-hid-scripts.js | 292 +++++++++--------- res/controllers/common-hid-packet-parser.js | 9 +- 2 files changed, 148 insertions(+), 153 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 52d40188d74..c02093eccff 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -17,12 +17,6 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: -/* * Active deck stuff: -/* * colors assume single "off" instead of a dim version of the on color. -/* * I don't think I can use the built-in deck switchign because of the different -/* kinds of lights -- library assumes one type of led -/* -/* * lights are still wonky, sometimes needs a couple tries to go through?? */ /* * deck lights */ /* * wheel colors (animations!!!!) */ /* * touch for track browse, loop control, beatjump */ @@ -35,7 +29,8 @@ var TraktorS3 = new function() { this.controller = new HIDController(); - this.shiftPressed = {"deck1": false, "deck2": false}; + this.shiftPressed = { "deck1": false, "deck2": false }; + this.syncPressedTimer = { "deck1": 0, "deck2": 0 }; this.previewPressed = {"deck1": false, "deck2": false}; this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode @@ -154,14 +149,6 @@ var TraktorS3 = new function() { // Sampler callbacks this.samplerCallbacks = []; - this.samplerHotcuesRelation = { - "deck1": { - 1: 1, 2: 2, 3: 3, 4: 4, 5: 9, 6: 10, 7: 11, 8: 12 - }, "deck2": { - 1: 5, 2: 6, 3: 7, 4: 8, 5: 13, 6: 14, 7: 15, 8: 16 - } - }; - }; TraktorS3.registerInputPackets = function() { @@ -250,7 +237,7 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "deck2", "!ActivateBeatjump", 0x09, 0x10, this.activateBeatjumpHandler); // // There is only one button on the controller, we use to toggle quantization for all channels - // this.registerInputButton(messageShort, "[ChannelX]", "!quantize", 0x06, 0x40, this.quantizeHandler); + // this.registerInputButton(messageShort, "[Channel1]", "!quantize", 0x06, 0x40, this.quantizeHandler); // // Microphone // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); @@ -274,8 +261,8 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "deck1", "!slip_enabled", 0x01, 0x02, this.fluxHandler); this.registerInputButton(messageShort, "deck2", "!slip_enabled", 0x04, 0x04, this.fluxHandler); - this.registerInputButton(messageShort, "deck1", "!grid", 0x01, 0x80, this.beatgridHandler); - this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x01, this.beatgridHandler); + this.registerInputButton(messageShort, "deck1", "quantize", 0x01, 0x80, this.quantizeHandler); + this.registerInputButton(messageShort, "deck2", "quantize", 0x05, 0x01, this.quantizeHandler); // // TODO: implement jog // this.registerInputButton(messageShort, "deck1", "!grid", 0x02, 0x01, this.jogHandler); @@ -390,17 +377,6 @@ TraktorS3.deckSwitchHandler = function(field) { if (field.value === 0) { return; } - // // for (var key in field) { - // // var value = field[key]; - // // HIDDebug("what is a field: " + key + ": " + value); - // // } - // var requestedDeck = TraktorS3.controller.resolveDeck(field.group); - // HIDDebug("requested " + requestedDeck + " " + TraktorS3.controller.activeDeck); - // TraktorS3.lightDeck(false); - // if (requestedDeck === TraktorS3.controller.activeDeck) { - // return; - // } - // TraktorS3.controller.switchDeck(TraktorS3.controller.resolveDeck(field.group)); if (field.group === "[Channel1]") { TraktorS3.activeDecks[1] = true; @@ -451,27 +427,19 @@ TraktorS3.cueHandler = function(field) { TraktorS3.shiftHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - HIDDebug("SHIFT!" + activeGroup + " " + field.value); - TraktorS3.shiftPressed[field.group] = field.value; engine.setValue("[Controls]", "touch_shift", field.value); - // Shift is only white - HIDDebug("me light shift " + field.group + " " + field.value); + TraktorS3.shiftPressed[field.group] = field.value; TraktorS3.outputHandler(field.value, field.group, "!shift"); }; TraktorS3.keylockHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { + if (field.value === 0) { return; } - if (field.value === 0) { + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { return; } - script.toggleControl(activeGroup, "keylock"); }; @@ -488,50 +456,44 @@ TraktorS3.syncHandler = function(field) { } }; +// This handles when the mode buttons for the pads is pressed. TraktorS3.padModeHandler = function(field) { if (field.value === 0) { return; } - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.padModeState[activeGroup] === 0 && field.name === "!samples") { + if (TraktorS3.padModeState[field.group] === 0 && field.name === "!samples") { // If we are in hotcues mode and samples mode is activated engine.setValue("[Samplers]", "show_samplers", 1); - TraktorS3.padModeState[activeGroup] = 1; - TraktorS3.colorOutputHandler(0, field.group, "hotcues"); - TraktorS3.colorOutputHandler(1, field.group, "samples"); - - // Light LEDs for all slots with loaded samplers - for (var key in TraktorS3.samplerHotcuesRelation[field.group]) { - if (TraktorS3.samplerHotcuesRelation[field.group].hasOwnProperty(key)) { - var loaded = engine.getValue("[Sampler" + TraktorS3.samplerHotcuesRelation[field.group][key] + "]", "track_loaded"); - TraktorS3.colorOutputHandler(loaded, field.group, "pad_" + key); - } - } + TraktorS3.padModeState[field.group] = 1; } else if (field.name === "!hotcues") { // If we are in samples mode and hotcues mode is activated - TraktorS3.padModeState[activeGroup] = 0; - TraktorS3.colorOutputHandler(1, field.group, "hotcues"); - TraktorS3.colorOutputHandler(0, field.group, "samples"); - - // Light LEDs for all enabled hotcues - for (var i = 1; i <= 8; ++i) { - var active = engine.getValue(activeGroup, "hotcue_" + i + "_enabled"); - TraktorS3.colorOutputHandler(active, field.group, "pad_" + i); - } + TraktorS3.padModeState[field.group] = 0; } + TraktorS3.lightPads(field.group); }; -TraktorS3.numberButtonHandler = function(field) { +TraktorS3.numberButtonHandler = function (field) { + HIDDebug("number buttons!"); var padNumber = parseInt(field.id[field.id.length - 1]); + var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { + HIDDebug("not active " + field.group + " " + activeGroup); return; } - if (TraktorS3.padModeState[activeGroup] === 0) { + if (TraktorS3.padModeState[field.group] === 0) { + HIDDebug("hotcue button pressed"); + + // Light the button based on whether it's being pushed or not, and if there's a hotcue + // set for this position or not. + var ledValue = field.value; + if (!field.value) { + ledValue = engine.getValue(activeGroup, "hotcue_" + padNumber + "_enabled"); + HIDDebug("disable, val is " + ledValue); + } + TraktorS3.colorOutputHandler(ledValue, field.group, "!pad_" + padNumber); + // Hotcues mode if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "hotcue_" + padNumber + "_clear", field.value); @@ -540,7 +502,18 @@ TraktorS3.numberButtonHandler = function(field) { } } else { // Samples mode - var sampler = TraktorS3.samplerHotcuesRelation[field.group][padNumber]; + HIDDebug("sampler button pressed"); + var sampler = padNumber; + if (field.group === "deck2") { + sampler += 8; + } + + var ledValue = field.value; + if (!field.value) { + ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); + } + TraktorS3.colorOutputHandler(ledValue, field.group, "!pad_" + padNumber); + if (TraktorS3.shiftPressed[field.group]) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { @@ -551,7 +524,11 @@ TraktorS3.numberButtonHandler = function(field) { } else { var loaded = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); if (loaded) { - engine.setValue("[Sampler" + sampler + "]", "cue_gotoandplay", field.value); + if (field.value) { + engine.setValue("[Sampler" + sampler + "]", "cue_gotoandplay", field.value); + } else { + engine.setValue("[Sampler" + sampler + "]", "stop", 1); + } } else { engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); } @@ -563,13 +540,6 @@ TraktorS3.headphoneHandler = function(field) { if (field.value === 0) { return; } - // var activeGroup = TraktorS3.resolveDeckIfActive(group); - // if (!activeGroup) { - // return; - // } - - HIDDebug("PFL! " + field.group); - script.toggleControl(field.group, "pfl"); }; @@ -728,22 +698,6 @@ TraktorS3.activateBeatjumpHandler = function(field) { } }; -TraktorS3.quantizeHandler = function(field) { - if (field.value === 0) { - return; - } - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - - // TODO: fix quantize - var res = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); - engine.setValue("[Channel1]", "quantize", res); - engine.setValue("[Channel2]", "quantize", res); - TraktorS3.outputHandler(res, field.group, "quantize"); -}; - TraktorS3.microphoneHandler = function(field) { if (field.value) { if (TraktorS3.microphonePressedTimer === 0) { @@ -961,15 +915,18 @@ TraktorS3.fluxHandler = function(field) { script.toggleControl(activeGroup, "slip_enabled"); }; -TraktorS3.beatgridHandler = function(field) { +TraktorS3.quantizeHandler = function (field) { + if (field.value === 0) { + return; + } var activeGroup = TraktorS3.deckToGroup(field.group); if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "beats_translate_match_alignment", field.value); - } else { engine.setValue(activeGroup, "beats_translate_curpos", field.value); + } else { + script.toggleControl(activeGroup, "quantize"); + // engine.setValue(activeGroup, "quantize", newState); + // TraktorS3.colorOutputHandler(newState, field.group, "quantize"); } - - TraktorS3.colorOutputHandler(field.value, field.group, "grid"); }; // function sleepFor(sleepDuration) { @@ -982,8 +939,8 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - // TraktorS3.lightDeck("[Channel3]"); - // TraktorS3.lightDeck("[Channel4]"); + TraktorS3.lightDeck("[Channel3]"); + TraktorS3.lightDeck("[Channel4]"); TraktorS3.lightDeck("[Channel1]"); TraktorS3.lightDeck("[Channel2]"); @@ -995,8 +952,8 @@ TraktorS3.debugLights = function() { // bytes do what). var data_strings = [ " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 2C 00 " + - "11 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 7C FF 2C " + - "FF 2C FF 2C FF FF 2C 2C 2E FF 2C 7C FF 00 00 00 " + + "00 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + + "FF 00 00 00 FF 00 2C 2C 00 FF 2C 7C FF 00 00 00 " + "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", @@ -1095,23 +1052,23 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "sync_enabled", 0x0C, "B"); outputA.addOutput("deck2", "sync_enabled", 0x25, "B"); - outputA.addOutput("deck1", "pad_1", 0x12, "B"); - outputA.addOutput("deck1", "pad_2", 0x13, "B"); - outputA.addOutput("deck1", "pad_3", 0x14, "B"); - outputA.addOutput("deck1", "pad_4", 0x15, "B"); - outputA.addOutput("deck1", "pad_5", 0x16, "B"); - outputA.addOutput("deck1", "pad_6", 0x17, "B"); - outputA.addOutput("deck1", "pad_7", 0x18, "B"); - outputA.addOutput("deck1", "pad_8", 0x19, "B"); - - outputA.addOutput("deck2", "pad_1", 0x2B, "B"); - outputA.addOutput("deck2", "pad_2", 0x2C, "B"); - outputA.addOutput("deck2", "pad_3", 0x2D, "B"); - outputA.addOutput("deck2", "pad_4", 0x2E, "B"); - outputA.addOutput("deck2", "pad_5", 0x2F, "B"); - outputA.addOutput("deck2", "pad_6", 0x30, "B"); - outputA.addOutput("deck2", "pad_7", 0x31, "B"); - outputA.addOutput("deck2", "pad_8", 0x32, "B"); + outputA.addOutput("deck1", "!pad_1", 0x12, "B"); + outputA.addOutput("deck1", "!pad_2", 0x13, "B"); + outputA.addOutput("deck1", "!pad_3", 0x14, "B"); + outputA.addOutput("deck1", "!pad_4", 0x15, "B"); + outputA.addOutput("deck1", "!pad_5", 0x16, "B"); + outputA.addOutput("deck1", "!pad_6", 0x17, "B"); + outputA.addOutput("deck1", "!pad_7", 0x18, "B"); + outputA.addOutput("deck1", "!pad_8", 0x19, "B"); + + outputA.addOutput("deck2", "!pad_1", 0x2B, "B"); + outputA.addOutput("deck2", "!pad_2", 0x2C, "B"); + outputA.addOutput("deck2", "!pad_3", 0x2D, "B"); + outputA.addOutput("deck2", "!pad_4", 0x2E, "B"); + outputA.addOutput("deck2", "!pad_5", 0x2F, "B"); + outputA.addOutput("deck2", "!pad_6", 0x30, "B"); + outputA.addOutput("deck2", "!pad_7", 0x31, "B"); + outputA.addOutput("deck2", "!pad_8", 0x32, "B"); outputA.addOutput("[Channel1]", "pfl", 0x39, "B"); outputA.addOutput("[Channel2]", "pfl", 0x3A, "B"); @@ -1121,10 +1078,9 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "addTrack", 0x03, "B"); outputA.addOutput("deck2", "addTrack", 0x2A, "B"); - outputA.addOutput("deck1", "grid", 0x08, "B"); - outputA.addOutput("deck2", "grid", 0x20, "B"); + outputA.addOutput("deck1", "quantize", 0x08, "B"); + outputA.addOutput("deck2", "quantize", 0x21, "B"); - // outputA.addOutput("[ChannelX]", "quantize", 0x3C, "B"); // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); outputA.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); @@ -1183,10 +1139,13 @@ TraktorS3.registerOutputPackets = function() { TraktorS3.linkDeckOutputs("sync_enabled", this.colorOutputHandler); TraktorS3.linkDeckOutputs("keylock", this.colorOutputHandler); TraktorS3.linkDeckOutputs("slip_enabled", this.deckOutputHandler); + TraktorS3.linkDeckOutputs("quantize", this.colorOutputHandler); // for (var i = 1; i <= 8; ++i) { // TraktorS3.linkDeckOutputs("pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); // TraktorS3.linkDeckOutputs("pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); // } TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); @@ -1227,6 +1186,11 @@ TraktorS3.linkDeckOutputs = function(key, callback) { engine.connectControl("[Channel3]", key, callback); TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); engine.connectControl("[Channel4]", key, callback); + + // TraktorS3.controller.linkOutput("[Channel1]", key, "deck1", key, callback); + // TraktorS3.controller.linkOutput("[Channel2]", key, "deck2", key, callback); + // TraktorS3.controller.linkOutput("[Channel3]", key, "deck1", key, callback); + // TraktorS3.controller.linkOutput("[Channel4]", key, "deck2", key, callback); }; TraktorS3.deckToGroup = function(deck) { @@ -1284,12 +1248,40 @@ TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { } if (field.mapped_callback !== undefined) { var value = engine.getValue(co_group_name, field.name); + HIDDebug("callback for " + co_group_name + " " + field.name); field.mapped_callback(value, co_group_name, field.name); } // No callback, no light! } }; +TraktorS3.lightPads = function (group) { + HIDDebug("LIGHT PADS " + group); + var activeGroup = TraktorS3.deckToGroup(group); + // Samplers + if (TraktorS3.padModeState[group] === 1) { + TraktorS3.colorOutputHandler(0, activeGroup, "hotcues"); + TraktorS3.colorOutputHandler(1, activeGroup, "samples"); + HIDDebug("SAMPLERS"); + for (var i = 1; i <= 8; i++) { + var idx = i; + if (group === "deck2") { + idx += 8; + } + var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); + TraktorS3.colorOutputHandler(loaded, activeGroup, "!pad_" + idx); + } + } else { + HIDDebug("HOT CUES!!"); + TraktorS3.colorOutputHandler(1, activeGroup, "hotcues"); + TraktorS3.colorOutputHandler(0, activeGroup, "samples"); + for (var i = 1; i <= 8; ++i) { + var active = engine.getValue(activeGroup, "hotcue_" + i + "_enabled"); + TraktorS3.colorOutputHandler(active, activeGroup, "!pad_" + i); + } + } +} + TraktorS3.lightDeck = function(group) { // Freeze the lights while we do this update so we don't spam HID. this.controller.freeze_lights = true; @@ -1304,6 +1296,8 @@ TraktorS3.lightDeck = function(group) { TraktorS3.lightGroup(packet, deck_group_name, group); TraktorS3.lightGroup(packet, group, group); + TraktorS3.lightPads(deck_group_name); + // These lights are different because either they aren't associated with a CO, or // there are two buttons that point to the same CO. TraktorS3.deckOutputHandler(0, deck_group_name, "!shift"); @@ -1409,21 +1403,21 @@ TraktorS3.deckOutputHandler = function(value, group, key) { // incoming value will be a channel, we have to resolve back to // deck. HIDDebug("deckoutput group " + group); - var updatedDeck = TraktorS3.resolveDeckIfActive(group); - if (!updatedDeck) { - HIDDebug("WRONG DECK " + group + " " + updatedDeck); - return; - } - HIDDebug("deckoutput updatedeck: " + updatedDeck); - if (TraktorS3.activeDecks[updatedDeck]) { - HIDDebug("regdeck is not active: " + updatedDeck); - return; - } - if (updatedDeck == 1 || updatedDeck == 3) { - group = "deck1"; - } else if (updatedDeck == 2 || updatedDeck == 4) { - group = "deck2"; - } + // var updatedDeck = TraktorS3.resolveDeckIfActive(group); + // if (!updatedDeck) { + // HIDDebug("WRONG DECK " + group + " " + updatedDeck); + // return; + // } + // HIDDebug("deckoutput updatedeck: " + updatedDeck); + // if (TraktorS3.activeDecks[updatedDeck]) { + // HIDDebug("regdeck is not active: " + updatedDeck); + // return; + // } + // if (updatedDeck == 1 || updatedDeck == 3) { + // group = "deck1"; + // } else if (updatedDeck == 2 || updatedDeck == 4) { + // group = "deck2"; + // } var ledValue = 0x20; if (value === 1 || value === true) { @@ -1438,6 +1432,7 @@ TraktorS3.deckOutputHandler = function(value, group, key) { TraktorS3.wheelOutputHandler = function(value, group, key) { // Also call regular handler + HIDDebug("call regular????? " + value + " " + group); TraktorS3.deckOutputHandler(value, group, key); // var activeGroup = TraktorS3.deckToGroup(group); @@ -1498,27 +1493,26 @@ TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { TraktorS3.controller.setOutput(group, key, ledValue, sendPacket); }; +TraktorS3.padLEDHandler = function (deck) { + +} + TraktorS3.hotcueOutputHandler = function(value, group, key) { // Light button LED only when we are in hotcue mode if (TraktorS3.padModeState[group] === 0) { - TraktorS3.outputHandler(value, group, key); + TraktorS3.colorOutputHandler(value, group, key); } }; TraktorS3.samplesOutputHandler = function(value, group, key) { - // Sampler 1-4, 9-12 -> Channel1 - // Samples 5-8, 13-16 -> Channel2 + // Sampler 1-8 -> Channel1 + // Samples 9-16 -> Channel2 var sampler = TraktorS3.resolveSampler(group); var deck = "deck1"; var num = sampler; if (sampler === undefined) { return; - } else if (sampler > 4 && sampler < 9) { - deck = "deck2"; - num = sampler - 4; - } else if (sampler > 8 && sampler < 13) { - num = sampler - 4; - } else if (sampler > 12 && sampler < 17) { + } else if (sampler > 8 && sampler < 17) { deck = "deck2"; num = sampler - 8; } @@ -1528,13 +1522,13 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { if (key === "play" && engine.getValue(group, "track_loaded")) { if (value) { // Green light on play - TraktorS3.outputHandler(0x9E, deck, "pad_" + num); + TraktorS3.colorOutputHandler(0x9E, deck, "!pad_" + num); } else { // Reset LED to full white light - TraktorS3.outputHandler(1, deck, "pad_" + num); + TraktorS3.colorOutputHandler(1, deck, "!pad_" + num); } } else if (key === "track_loaded") { - TraktorS3.outputHandler(value, deck, "pad_" + num); + TraktorS3.colorOutputHandler(value, deck, "!pad_" + num); } } }; diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 76001df7e7b..d104c69ab21 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1554,10 +1554,11 @@ HIDController.prototype.setOutput = function(group, name, value, send_packet) { HIDDebug("setOutput: unknown field: " + group + "." + name); return; } - HIDDebug("eh???"); - if (value !== undefined) { - HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); - } + // if (value !== undefined) { + // HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); + // } else { + // HIDDebug("undefined :("); + // } field.value = value << field.bit_offset; field.toggle = value << field.bit_offset; if (send_packet) From 6bd797b0c54bbb04337ef693a05c8d376a3243b9 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 5 Aug 2020 00:10:27 -0400 Subject: [PATCH 19/84] Traktor S3: light up the reverse light --- .../Traktor-Kontrol-S3-hid-scripts.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c02093eccff..2a1f6ff4a0a 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -877,7 +877,8 @@ TraktorS3.finishJogTouch = function(group) { } }; -TraktorS3.fxHandler = function(field) { +TraktorS3.fxHandler = function (field) { + HIDDebug("FX HANDLER"); if (field.value === 0) { return; } @@ -903,7 +904,7 @@ TraktorS3.reverseHandler = function(field) { engine.setValue(activeGroup, "reverse", field.value); } - TraktorS3.deckOutputHandler(field.value, field.group, "reverse"); + TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); }; TraktorS3.fluxHandler = function(field) { @@ -1014,8 +1015,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("deck1", "slip_enabled", 0x02, "B"); outputA.addOutput("deck2", "slip_enabled", 0x1B, "B"); - outputA.addOutput("deck1", "reverse", 0x03, "B"); - outputA.addOutput("deck2", "reverse", 0x1C, "B"); + outputA.addOutput("deck1", "!reverse", 0x03, "B"); + outputA.addOutput("deck2", "!reverse", 0x1C, "B"); outputA.addOutput("deck1", "!PreviewTrack", 0x04, "B"); outputA.addOutput("deck2", "!PreviewTrack", 0x1D, "B"); @@ -1141,13 +1142,6 @@ TraktorS3.registerOutputPackets = function() { TraktorS3.linkDeckOutputs("slip_enabled", this.deckOutputHandler); TraktorS3.linkDeckOutputs("quantize", this.colorOutputHandler); - // for (var i = 1; i <= 8; ++i) { - // TraktorS3.linkDeckOutputs("pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // TraktorS3.linkDeckOutputs("pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // } - TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel2]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel3]", "pfl", this.outputHandler); @@ -1304,6 +1298,7 @@ TraktorS3.lightDeck = function(group) { TraktorS3.colorOutputHandler(0, deck_group_name, "!PreviewTrack"); TraktorS3.colorOutputHandler(0, deck_group_name, "!AddTrack"); TraktorS3.colorOutputHandler(0, deck_group_name, "!LibraryFocus"); + TraktorS3.deckOutputHandler(0, deck_group_name, "!reverse"); } // // FX buttons From ac58bc291bbd6d5088b33a57eefe194015bfe356 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 5 Aug 2020 23:10:37 -0400 Subject: [PATCH 20/84] Traktor S3: Most functions working decently --- .../Traktor-Kontrol-S3-hid-scripts.js | 164 ++++++++++++------ 1 file changed, 114 insertions(+), 50 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 2a1f6ff4a0a..f2c6d1dd5c8 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -273,8 +273,8 @@ TraktorS3.registerInputPackets = function() { this.controller.registerInputPacket(messageShort); - this.registerInputScaler(messageLong, "deck1", "rate", 0x01, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "deck2", "rate", 0x0D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "deck1", "rate", 0x01, 0xFFFF, this.pitchSliderHandler); + this.registerInputScaler(messageLong, "deck2", "rate", 0x0D, 0xFFFF, this.pitchSliderHandler); this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); @@ -319,6 +319,9 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover("deck1", "rate", true); engine.softTakeover("deck2", "rate", true); + engine.softTakeover("deck1", "pitch_adjust", true); + engine.softTakeover("deck2", "pitch_adjust", true); + engine.softTakeover("deck1", "volume", true); engine.softTakeover("deck2", "volume", true); @@ -448,11 +451,38 @@ TraktorS3.syncHandler = function(field) { if (!activeGroup) { return; } + if (TraktorS3.shiftPressed[field.group]) { - // engine.setValue(activeGroup, "sync_enabled", field.value); - } else if (field.value === 1) { - //TODO: latching not working? - engine.setValue(activeGroup, "sync_enabled", field.value); + engine.setValue(activeGroup, "beatsync_phase", field.value); + // Light LED while pressed + TraktorS3.colorOutputHandler(field.value, field.group, "sync_enabled"); + } else { + if (field.value) { + if (engine.getValue(activeGroup, 'sync_enabled') === 0) { + script.triggerControl(activeGroup, "beatsync"); + // Start timer to measure how long button is pressed + TraktorS3.syncPressedTimer[field.group] = engine.beginTimer(300, function () { + engine.setValue(activeGroup, "sync_enabled", 1); + // Reset sync button timer state if active + if (TraktorS3.syncPressedTimer[field.group] !== 0) { + TraktorS3.syncPressedTimer[field.group] = 0; + } + }, true); + + // Light corresponding LED when button is pressed + TraktorS3.colorOutputHandler(1, field.group, "sync_enabled"); + } else { + // Deactivate sync lock + // LED is turned off by the callback handler for sync_enabled + engine.setValue(activeGroup, "sync_enabled", 0); + } + } else { + if (TraktorS3.syncPressedTimer[field.group] !== 0) { + // Timer still running -> stop it and unlight LED + engine.stopTimer(TraktorS3.syncPressedTimer[field.group]); + TraktorS3.colorOutputHandler(0, field.group, "sync_enabled"); + } + } } }; @@ -722,6 +752,20 @@ TraktorS3.microphoneHandler = function(field) { } }; +TraktorS3.pitchSliderHandler = function(field) { + var activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } + if (TraktorS3.shiftPressed[field.group]) { + // To match the pitch change from adjusting the rate, flip the pitch + // adjustment. + engine.setParameter(activeGroup, "pitch_adjust", 1.0 - (field.value / 4095)); + } else { + engine.setParameter(activeGroup, "rate", field.value / 4095); + } +}; + TraktorS3.parameterHandler = function(field) { var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { @@ -940,10 +984,10 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - TraktorS3.lightDeck("[Channel3]"); - TraktorS3.lightDeck("[Channel4]"); - TraktorS3.lightDeck("[Channel1]"); - TraktorS3.lightDeck("[Channel2]"); + TraktorS3.lightDeck("[Channel3]", false); + TraktorS3.lightDeck("[Channel4]", false); + TraktorS3.lightDeck("[Channel1]", false); + TraktorS3.lightDeck("[Channel2]", true); // TraktorS3.debugLights(); }; @@ -952,10 +996,10 @@ TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var data_strings = [ - " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 2C 00 " + - "00 2C 7E DD 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + + " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + + "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + "FF 00 00 00 FF 00 2C 2C 00 FF 2C 7C FF 00 00 00 " + - "00 FF 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + + "00 00 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + @@ -1276,9 +1320,12 @@ TraktorS3.lightPads = function (group) { } } -TraktorS3.lightDeck = function(group) { +TraktorS3.lightDeck = function (group, send_packets) { + if (send_packets === undefined) { + send_packets = true; + } // Freeze the lights while we do this update so we don't spam HID. - this.controller.freeze_lights = true; + TraktorS3.freeze_lights = true; for (var packet_name in this.controller.OutputPackets) { packet = this.controller.OutputPackets[packet_name]; var deck_group_name = "deck1"; @@ -1328,11 +1375,13 @@ TraktorS3.lightDeck = function(group) { ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDDimValue, false); } - this.controller.freeze_lights = false; + TraktorS3.freeze_lights = false; // And now send them all. - for (packet_name in this.controller.OutputPackets) { - var packet_ob = this.controller.OutputPackets[packet_name]; - packet_ob.send(); + if (send_packets) { + for (packet_name in this.controller.OutputPackets) { + var packet_ob = this.controller.OutputPackets[packet_name]; + packet_ob.send(); + } } }; @@ -1398,21 +1447,6 @@ TraktorS3.deckOutputHandler = function(value, group, key) { // incoming value will be a channel, we have to resolve back to // deck. HIDDebug("deckoutput group " + group); - // var updatedDeck = TraktorS3.resolveDeckIfActive(group); - // if (!updatedDeck) { - // HIDDebug("WRONG DECK " + group + " " + updatedDeck); - // return; - // } - // HIDDebug("deckoutput updatedeck: " + updatedDeck); - // if (TraktorS3.activeDecks[updatedDeck]) { - // HIDDebug("regdeck is not active: " + updatedDeck); - // return; - // } - // if (updatedDeck == 1 || updatedDeck == 3) { - // group = "deck1"; - // } else if (updatedDeck == 2 || updatedDeck == 4) { - // group = "deck2"; - // } var ledValue = 0x20; if (value === 1 || value === true) { @@ -1422,7 +1456,6 @@ TraktorS3.deckOutputHandler = function(value, group, key) { HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + ledValue); TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); - // TraktorS3.controller.setOutput(group, key, ledValue, true); }; TraktorS3.wheelOutputHandler = function(value, group, key) { @@ -1435,21 +1468,26 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { if (deck === undefined) { return; } - // var ctrlr = TraktorS3.controller; - // var ledValue = ctrlr.LEDColors[ctrlr.deckOutputColors[deck]]; - // HIDDebug("wheel! of! " + deck + " " + ctrlr.deckOutputColors[deck] + " " + ledValue ); - // if (value) { - // ledValue += 0x02; - // } + + var sendPacket = !TraktorS3.freeze_lights; + TraktorS3.freeze_lights = true; for (var i = 0; i < 8; i++) { - var sendPacket = (i == 7); - // HIDDebug("wheel! " + ledValue.toString(16) + " " + sendPacket); - TraktorS3.colorOutputHandler(value, group, "!wheel" + i, sendPacket); + // HIDDebug("wheel! " + ledValue.toString(16)); + TraktorS3.colorOutputHandler(value, group, "!wheel" + i); + } + if (sendPacket) { + for (packet_name in TraktorS3.controller.OutputPackets) { + var packet_ob = TraktorS3.controller.OutputPackets[packet_name]; + packet_ob.send(); + } + // Only unset freeze_lights if it wasn't already true when we + // entered this function. + TraktorS3.freeze_lights = false; } }; // colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { +TraktorS3.colorOutputHandler = function(value, group, key) { HIDDebug("coloroutput! " + value + " " + group + " " + key); // Reject update if it's for a specific channel that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); @@ -1479,13 +1517,10 @@ TraktorS3.colorOutputHandler = function(value, group, key, sendPacket) { } else { ledValue += TraktorS3.LEDDimValue; } - if (sendPacket === undefined) { - sendPacket = true; - } // HIDDebug("So I think: " + group + " " + key); - TraktorS3.controller.setOutput(group, key, ledValue, sendPacket); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); }; TraktorS3.padLEDHandler = function (deck) { @@ -1553,7 +1588,36 @@ TraktorS3.messageCallback = function(_packet, data) { TraktorS3.shutdown = function() { // Deactivate all LEDs - // TraktorS3.lightDeck(true); + var data_strings = [ + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 ", + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + ]; + + var data = [Object(), Object()]; + + + for (i = 0; i < data.length; i++) { + var ok = true; + var splitted = data_strings[i].split(/\s+/); + data[i].length = splitted.length; + for (j = 0; j < splitted.length; j++) { + var byte_str = splitted[j]; + if (byte_str.length === 0) { + continue; + } + data[i][j] = parseInt(byte_str, 16); + } + controller.send(data[i], data[i].length, 0x80 + i); + } HIDDebug("TraktorS3: Shutdown done!"); }; From 520e942044705a1f898ea75a1644f4d75eab76ac Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 6 Aug 2020 00:35:31 -0400 Subject: [PATCH 21/84] Traktor S3: hotcue colors! --- .../Traktor-Kontrol-S3-hid-scripts.js | 45 +++++++++++-------- src/controllers/colormapper.cpp | 12 ++++- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index f2c6d1dd5c8..065840e5b1e 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -17,12 +17,12 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: -/* * deck lights */ -/* * wheel colors (animations!!!!) */ +/* * wheel animations */ /* * touch for track browse, loop control, beatjump */ /* * jog button */ -/* * sync latch */ -/* * quantize */ +/* * star button */ +/* * hotcue colors */ +/* * FX buttons */ /* */ /////////////////////////////////////////////////////////////////////////////////// @@ -513,16 +513,7 @@ TraktorS3.numberButtonHandler = function (field) { return; } if (TraktorS3.padModeState[field.group] === 0) { - HIDDebug("hotcue button pressed"); - - // Light the button based on whether it's being pushed or not, and if there's a hotcue - // set for this position or not. - var ledValue = field.value; - if (!field.value) { - ledValue = engine.getValue(activeGroup, "hotcue_" + padNumber + "_enabled"); - HIDDebug("disable, val is " + ledValue); - } - TraktorS3.colorOutputHandler(ledValue, field.group, "!pad_" + padNumber); + TraktorS3.lightHotcue(activeGroup, padNumber); // Hotcues mode if (TraktorS3.shiftPressed[field.group]) { @@ -1293,6 +1284,25 @@ TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { } }; +TraktorS3.colorForHotcue = function (group, num) { + var colorCode = engine.getValue(group, "hotcue_" + num + "_color"); + HIDDebug("color code for " + group + " " + num + " is " + colorCode); + return TraktorS3.colorMap.getValueForNearestColor(colorCode); +} + +TraktorS3.lightHotcue = function (group, number) { + var deck = TraktorS3.resolveDeckIfActive(group); + var active = engine.getValue(group, "hotcue_" + number + "_enabled"); + var ledValue = TraktorS3.controller.LEDColors.WHITE; + if (active) { + ledValue = TraktorS3.colorForHotcue(group, number); + ledValue += TraktorS3.LEDDimValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput(deck, "!pad_" + number, ledValue, !TraktorS3.freeze_lights); +} + TraktorS3.lightPads = function (group) { HIDDebug("LIGHT PADS " + group); var activeGroup = TraktorS3.deckToGroup(group); @@ -1307,15 +1317,14 @@ TraktorS3.lightPads = function (group) { idx += 8; } var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); - TraktorS3.colorOutputHandler(loaded, activeGroup, "!pad_" + idx); + TraktorS3.colorOutputHandler(loaded, group, "!pad_" + idx); } } else { HIDDebug("HOT CUES!!"); TraktorS3.colorOutputHandler(1, activeGroup, "hotcues"); TraktorS3.colorOutputHandler(0, activeGroup, "samples"); for (var i = 1; i <= 8; ++i) { - var active = engine.getValue(activeGroup, "hotcue_" + i + "_enabled"); - TraktorS3.colorOutputHandler(active, activeGroup, "!pad_" + i); + TraktorS3.lightHotcue(activeGroup, i); } } } @@ -1488,7 +1497,7 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorOutputHandler = function(value, group, key) { - HIDDebug("coloroutput! " + value + " " + group + " " + key); + // HIDDebug("coloroutput! " + value + " " + group + " " + key); // Reject update if it's for a specific channel that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); if (updatedDeck != undefined) { diff --git a/src/controllers/colormapper.cpp b/src/controllers/colormapper.cpp index 75e5128597e..8d1a542632a 100644 --- a/src/controllers/colormapper.cpp +++ b/src/controllers/colormapper.cpp @@ -17,6 +17,7 @@ double colorDistance(QRgb a, QRgb b) { // of costly computations. In contrast, this is a low-cost // approximation and should be sufficiently accurate. // More details: https://www.compuphase.com/cmetric.htm + qDebug() << "distance between " << QColor(a) << "and" << QColor(b); long mean_red = (static_cast(qRed(a)) + static_cast(qRed(b))) / 2; long delta_red = static_cast(qRed(a)) - static_cast(qRed(b)); long delta_green = static_cast(qGreen(a)) - static_cast(qGreen(b)); @@ -30,6 +31,7 @@ double colorDistance(QRgb a, QRgb b) { } // namespace QRgb ColorMapper::getNearestColor(QRgb desiredColor) { + qDebug() << "ColorMapper::getNearestColor"; // If desired color is already in cache, use cache entry const auto iCachedColor = m_cache.constFind(desiredColor); if (iCachedColor != m_cache.constEnd()) { @@ -50,14 +52,18 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { double nearestColorDistance = qInf(); for (auto i = m_availableColors.constBegin(); i != m_availableColors.constEnd(); ++i) { const QRgb availableColor = i.key(); + qDebug() << "possible color" << QColor(availableColor); double distance = colorDistance(desiredColor, availableColor); + qDebug() << "distance is " << distance; if (distance < nearestColorDistance) { + qDebug() << "new possible color"; nearestColorDistance = distance; iNearestColor = i; } } if (iNearestColor != m_availableColors.constEnd()) { const QRgb nearestColor = iNearestColor.key(); + qDebug() << "found something good!"; if (kLogger.traceEnabled()) { kLogger.trace() << "Found matching color" @@ -66,6 +72,7 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { << desiredColor; } m_cache.insert(desiredColor, nearestColor); + qDebug() << "returning " << QColor(nearestColor); return nearestColor; } @@ -75,5 +82,8 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { } QVariant ColorMapper::getValueForNearestColor(QRgb desiredColor) { - return m_availableColors.value(getNearestColor(desiredColor)); + qDebug() << "getting value for " << desiredColor; + auto ret = m_availableColors.value(getNearestColor(desiredColor)); + qDebug() << "it's " << m_availableColors << " SO LIKE "<< ret; + return ret; } From 0a2d951b48bcc9934ee7780e5cc1bf1f3ae527da Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 8 Aug 2020 22:10:11 -0400 Subject: [PATCH 22/84] TraktorS3: fx buttons --- .../Traktor-Kontrol-S3-hid-scripts.js | 271 +++++++++++------- 1 file changed, 162 insertions(+), 109 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 065840e5b1e..6f713b43220 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -17,6 +17,7 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: +/* * scratching still fucked up */ /* * wheel animations */ /* * touch for track browse, loop control, beatjump */ /* * jog button */ @@ -29,10 +30,18 @@ var TraktorS3 = new function() { this.controller = new HIDController(); - this.shiftPressed = { "deck1": false, "deck2": false }; - this.syncPressedTimer = { "deck1": 0, "deck2": 0 }; + this.shiftPressed = {"deck1": false, "deck2": false}; + this.syncPressedTimer = {"deck1": 0, "deck2": 0}; this.previewPressed = {"deck1": false, "deck2": false}; - this.fxButtonState = {1: false, 2: false, 3: false, 4: false}; + // "5" is the "filter" button below the other 4. + this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: true}; + this.fxEnabledState = { + "[Channel1]": true, + "[Channel2]": true, + "[Channel3]": true, + "[Channel4]": true, + }; + this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode // When true, packets will not be sent to the controller. Good for doing mass updates. @@ -123,6 +132,14 @@ var TraktorS3 = new function() { 4: "SKY" }; + this.fxLEDValue = { + 1: this.controller.LEDColors.RED, + 2: this.controller.LEDColors.GREEN, + 3: this.controller.LEDColors.BLUE, + 4: this.controller.LEDColors.YELLOW, + 5: this.controller.LEDColors.PURPLE, + }; + this.colorMap = new ColorMapper({ 0xCC0000: this.controller.LEDColors.RED, 0xCC5E00: this.controller.LEDColors.CARROT, @@ -253,6 +270,12 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); + + this.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x48, this.fxEnableHandler); // // Rev / FLUX / GRID this.registerInputButton(messageShort, "deck1", "!reverse", 0x01, 0x04, this.reverseHandler); @@ -302,10 +325,10 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter2", 0x33, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel1]]", "super1", 0x39, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel2]]", "super1", 0x3B, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel3]]", "super1", 0x37, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[QuickEffectRack1_[Channel4]]", "super1", 0x3D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel1]", "!super", 0x39, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel2]", "!super", 0x3B, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel3]", "!super", 0x37, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel4]", "!super", 0x3D, 0xFFFF, this.superHandler); this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); @@ -401,17 +424,13 @@ TraktorS3.deckSwitchHandler = function(field) { }; TraktorS3.playHandler = function(field) { - HIDDebug("WELLL?? " + field.group + " " + field.id); var activeGroup = TraktorS3.deckToGroup(field.group); - HIDDebug("hmmm " + activeGroup); if (!activeGroup) { return; } - HIDDebug("play group?? " + activeGroup + " " + field.group); if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "start_stop", field.value); } else if (field.value === 1) { - HIDDebug("sooooo play?"); script.toggleControl(activeGroup, "play"); } }; @@ -455,13 +474,13 @@ TraktorS3.syncHandler = function(field) { if (TraktorS3.shiftPressed[field.group]) { engine.setValue(activeGroup, "beatsync_phase", field.value); // Light LED while pressed - TraktorS3.colorOutputHandler(field.value, field.group, "sync_enabled"); + TraktorS3.colorDeckOutputHandler(field.value, field.group, "sync_enabled"); } else { if (field.value) { - if (engine.getValue(activeGroup, 'sync_enabled') === 0) { + if (engine.getValue(activeGroup, "sync_enabled") === 0) { script.triggerControl(activeGroup, "beatsync"); // Start timer to measure how long button is pressed - TraktorS3.syncPressedTimer[field.group] = engine.beginTimer(300, function () { + TraktorS3.syncPressedTimer[field.group] = engine.beginTimer(300, function() { engine.setValue(activeGroup, "sync_enabled", 1); // Reset sync button timer state if active if (TraktorS3.syncPressedTimer[field.group] !== 0) { @@ -470,7 +489,7 @@ TraktorS3.syncHandler = function(field) { }, true); // Light corresponding LED when button is pressed - TraktorS3.colorOutputHandler(1, field.group, "sync_enabled"); + TraktorS3.colorDeckOutputHandler(1, field.group, "sync_enabled"); } else { // Deactivate sync lock // LED is turned off by the callback handler for sync_enabled @@ -480,7 +499,7 @@ TraktorS3.syncHandler = function(field) { if (TraktorS3.syncPressedTimer[field.group] !== 0) { // Timer still running -> stop it and unlight LED engine.stopTimer(TraktorS3.syncPressedTimer[field.group]); - TraktorS3.colorOutputHandler(0, field.group, "sync_enabled"); + TraktorS3.colorDeckOutputHandler(0, field.group, "sync_enabled"); } } } @@ -503,13 +522,11 @@ TraktorS3.padModeHandler = function(field) { TraktorS3.lightPads(field.group); }; -TraktorS3.numberButtonHandler = function (field) { - HIDDebug("number buttons!"); +TraktorS3.numberButtonHandler = function(field) { var padNumber = parseInt(field.id[field.id.length - 1]); var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { - HIDDebug("not active " + field.group + " " + activeGroup); return; } if (TraktorS3.padModeState[field.group] === 0) { @@ -523,7 +540,6 @@ TraktorS3.numberButtonHandler = function (field) { } } else { // Samples mode - HIDDebug("sampler button pressed"); var sampler = padNumber; if (field.group === "deck2") { sampler += 8; @@ -533,7 +549,7 @@ TraktorS3.numberButtonHandler = function (field) { if (!field.value) { ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); } - TraktorS3.colorOutputHandler(ledValue, field.group, "!pad_" + padNumber); + TraktorS3.colorDeckOutputHandler(ledValue, field.group, "!pad_" + padNumber); if (TraktorS3.shiftPressed[field.group]) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); @@ -578,13 +594,11 @@ TraktorS3.selectTrackHandler = function(field) { // When preview is held, rotating the library encoder scrolls through the previewing track. if (TraktorS3.previewPressed[field.group]) { var playPosition = engine.getValue("[PreviewDeck1]", "playposition"); - HIDDebug("current playpos " + playPosition); if (delta > 0) { playPosition += 0.0125; } else { playPosition -= 0.0125; } - HIDDebug("new playpos " + playPosition); engine.setValue("[PreviewDeck1]", "playposition", playPosition); return; } @@ -597,7 +611,6 @@ TraktorS3.selectTrackHandler = function(field) { }; TraktorS3.loadTrackHandler = function(field) { - HIDDebug("LOAD TRACK!!!"); var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { return; @@ -617,11 +630,11 @@ TraktorS3.previewTrackHandler = function(field) { TraktorS3.previewPressed[field.group] = false; engine.setValue("[PreviewDeck1]", "play", 0); } - TraktorS3.colorOutputHandler(field.value, field.group, "!PreviewTrack"); + TraktorS3.colorDeckOutputHandler(field.value, field.group, "!PreviewTrack"); }; -TraktorS3.cueAutoDJHandler = function (field) { - TraktorS3.colorOutputHandler(field.value, field.group, "!AddTrack"); +TraktorS3.cueAutoDJHandler = function(field) { + TraktorS3.colorDeckOutputHandler(field.value, field.group, "!AddTrack"); var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { @@ -636,7 +649,7 @@ TraktorS3.cueAutoDJHandler = function (field) { }; TraktorS3.LibraryFocusHandler = function(field) { - TraktorS3.colorOutputHandler(field.value, field.group, "!LibraryFocus"); + TraktorS3.colorDeckOutputHandler(field.value, field.group, "!LibraryFocus"); if (field.value === 0) { return; } @@ -912,23 +925,65 @@ TraktorS3.finishJogTouch = function(group) { } }; -TraktorS3.fxHandler = function (field) { - HIDDebug("FX HANDLER"); +TraktorS3.superHandler = function(field) { + // The super knob drives all the supers! + // engine.setParameter(activeGroup, field.name, field.value / 4095); + var group = field.group; + var value = field.value / 4095.; + engine.setParameter("[QuickEffectRack1_" + group + "]", "super1", value); + for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { + if (TraktorS3.fxButtonState[fxNumber]) { + engine.setParameter("[EffectRack1_EffectUnit" + fxNumber +"]", "super1", value); + } + } +}; + +TraktorS3.fxHandler = function(field) { if (field.value === 0) { return; } - var fxNumber = parseInt(field.id[field.id.length - 1]); - var group = "[EffectRack1_EffectUnit" + fxNumber + "]"; // Toggle effect unit TraktorS3.fxButtonState[fxNumber] = !TraktorS3.fxButtonState[fxNumber]; + var ledValue = TraktorS3.fxLEDValue[fxNumber]; + if (TraktorS3.fxButtonState[fxNumber]) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.freeze_lights); + // TraktorS3.colorOutputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "!fxButton" + fxNumber); + TraktorS3.toggleFX(); +}; + +TraktorS3.fxEnableHandler = function(field) { + if (field.value === 0) { + return; + } + TraktorS3.fxEnabledState[field.group] = !TraktorS3.fxEnabledState[field.group]; + TraktorS3.colorOutputHandler(TraktorS3.fxEnabledState[field.group], field.group, "!fxEnabled"); + TraktorS3.toggleFX(); +}; + +TraktorS3.toggleFX = function() { + // This is an AND operation. We go through each channel, and if + // the fx is ON, we turn the effect ON. We turn OFF no matter what. + for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { + for (var ch = 1; ch <= 4; ch++) { + var group = "[Channel" + ch + "]"; + var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + var fxKey = "group_[Channel1]_enable"; + if (fxNumber === 5) { + fxGroup = "[QuickEffectRack1_[Channel" + ch + "]_Effect1]"; + fxKey = "enabled"; + } + + var newState = TraktorS3.fxEnabledState[group] && TraktorS3.fxButtonState[fxNumber]; + engine.setValue(fxGroup, fxKey, newState); + } + } - engine.setValue(group, "group_[Channel1]_enable", TraktorS3.fxButtonState[fxNumber]); - engine.setValue(group, "group_[Channel2]_enable", TraktorS3.fxButtonState[fxNumber]); - engine.setValue(group, "group_[Channel3]_enable", TraktorS3.fxButtonState[fxNumber]); - engine.setValue(group, "group_[Channel4]_enable", TraktorS3.fxButtonState[fxNumber]); - TraktorS3.colorOutputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "fxButton" + fxNumber); }; TraktorS3.reverseHandler = function(field) { @@ -951,7 +1006,7 @@ TraktorS3.fluxHandler = function(field) { script.toggleControl(activeGroup, "slip_enabled"); }; -TraktorS3.quantizeHandler = function (field) { +TraktorS3.quantizeHandler = function(field) { if (field.value === 0) { return; } @@ -961,7 +1016,7 @@ TraktorS3.quantizeHandler = function (field) { } else { script.toggleControl(activeGroup, "quantize"); // engine.setValue(activeGroup, "quantize", newState); - // TraktorS3.colorOutputHandler(newState, field.group, "quantize"); + // TraktorS3.colorDeckOutputHandler(newState, field.group, "quantize"); } }; @@ -989,9 +1044,9 @@ TraktorS3.debugLights = function() { var data_strings = [ " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + - "FF 00 00 00 FF 00 2C 2C 00 FF 2C 7C FF 00 00 00 " + - "00 00 00 00 7E 0C 0C 0C 0C FF FF FF 70 FF 1C FF " + - "14 FF 40 FF FF FF 00 2E 00 2E FF 00 00 FF 00 00 " + + "FF 00 00 00 FF 00 FF 2C 00 FF 2C 7C FF 00 00 00 " + + "00 00 00 00 7E 0C 0C FF 0C FF FF FF FF FF FF FF " + + "FF FF 40 FF FF FF 00 FF FF 2E FF 00 00 FF 00 00 " + "00 00 FF 00 ", " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + @@ -1119,10 +1174,16 @@ TraktorS3.registerOutputPackets = function() { // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); - outputA.addOutput("[ChannelX]", "fxButton1", 0x3C, "B"); - outputA.addOutput("[ChannelX]", "fxButton2", 0x3D, "B"); - outputA.addOutput("[ChannelX]", "fxButton3", 0x3E, "B"); - outputA.addOutput("[ChannelX]", "fxButton4", 0x3F, "B"); + outputA.addOutput("[ChannelX]", "!fxButton1", 0x3C, "B"); + outputA.addOutput("[ChannelX]", "!fxButton2", 0x3D, "B"); + outputA.addOutput("[ChannelX]", "!fxButton3", 0x3E, "B"); + outputA.addOutput("[ChannelX]", "!fxButton4", 0x3F, "B"); + outputA.addOutput("[ChannelX]", "!fxButton5", 0x40, "B"); + + outputA.addOutput("[Channel3]", "!fxEnabled", 0x34, "B"); + outputA.addOutput("[Channel1]", "!fxEnabled", 0x35, "B"); + outputA.addOutput("[Channel2]", "!fxEnabled", 0x36, "B"); + outputA.addOutput("[Channel4]", "!fxEnabled", 0x37, "B"); var wheelOffsets = { "deck1": 0x43, @@ -1171,11 +1232,11 @@ TraktorS3.registerOutputPackets = function() { // Play is always green TraktorS3.linkDeckOutputs("play_indicator", this.wheelOutputHandler); - TraktorS3.linkDeckOutputs("cue_indicator", this.colorOutputHandler); - TraktorS3.linkDeckOutputs("sync_enabled", this.colorOutputHandler); - TraktorS3.linkDeckOutputs("keylock", this.colorOutputHandler); + TraktorS3.linkDeckOutputs("cue_indicator", this.colorDeckOutputHandler); + TraktorS3.linkDeckOutputs("sync_enabled", this.colorDeckOutputHandler); + TraktorS3.linkDeckOutputs("keylock", this.colorDeckOutputHandler); TraktorS3.linkDeckOutputs("slip_enabled", this.deckOutputHandler); - TraktorS3.linkDeckOutputs("quantize", this.colorOutputHandler); + TraktorS3.linkDeckOutputs("quantize", this.colorDeckOutputHandler); TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel2]", "pfl", this.outputHandler); @@ -1268,29 +1329,25 @@ TraktorS3.resolveDeckIfActive = function(group) { TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { var group_ob = packet.groups[output_group_name]; - // HIDDebug("heyooooo group! " + output_group_name); for (var field_name in group_ob) { field = group_ob[field_name]; if (field.name[0] === "!") { - // HIDDebug("skipping" + field_name); continue; } if (field.mapped_callback !== undefined) { var value = engine.getValue(co_group_name, field.name); - HIDDebug("callback for " + co_group_name + " " + field.name); field.mapped_callback(value, co_group_name, field.name); } // No callback, no light! } }; -TraktorS3.colorForHotcue = function (group, num) { +TraktorS3.colorForHotcue = function(group, num) { var colorCode = engine.getValue(group, "hotcue_" + num + "_color"); - HIDDebug("color code for " + group + " " + num + " is " + colorCode); return TraktorS3.colorMap.getValueForNearestColor(colorCode); -} +}; -TraktorS3.lightHotcue = function (group, number) { +TraktorS3.lightHotcue = function(group, number) { var deck = TraktorS3.resolveDeckIfActive(group); var active = engine.getValue(group, "hotcue_" + number + "_enabled"); var ledValue = TraktorS3.controller.LEDColors.WHITE; @@ -1301,35 +1358,48 @@ TraktorS3.lightHotcue = function (group, number) { ledValue += TraktorS3.LEDDimValue; } TraktorS3.controller.setOutput(deck, "!pad_" + number, ledValue, !TraktorS3.freeze_lights); -} +}; -TraktorS3.lightPads = function (group) { - HIDDebug("LIGHT PADS " + group); +TraktorS3.lightPads = function(group) { var activeGroup = TraktorS3.deckToGroup(group); // Samplers if (TraktorS3.padModeState[group] === 1) { - TraktorS3.colorOutputHandler(0, activeGroup, "hotcues"); - TraktorS3.colorOutputHandler(1, activeGroup, "samples"); - HIDDebug("SAMPLERS"); + TraktorS3.colorDeckOutputHandler(0, activeGroup, "hotcues"); + TraktorS3.colorDeckOutputHandler(1, activeGroup, "samples"); for (var i = 1; i <= 8; i++) { var idx = i; if (group === "deck2") { idx += 8; } var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); - TraktorS3.colorOutputHandler(loaded, group, "!pad_" + idx); + TraktorS3.colorDeckOutputHandler(loaded, group, "!pad_" + idx); } } else { - HIDDebug("HOT CUES!!"); - TraktorS3.colorOutputHandler(1, activeGroup, "hotcues"); - TraktorS3.colorOutputHandler(0, activeGroup, "samples"); + TraktorS3.colorDeckOutputHandler(1, activeGroup, "hotcues"); + TraktorS3.colorDeckOutputHandler(0, activeGroup, "samples"); for (var i = 1; i <= 8; ++i) { TraktorS3.lightHotcue(activeGroup, i); } } -} +}; -TraktorS3.lightDeck = function (group, send_packets) { +TraktorS3.lightFX = function() { + for (var ch = 1; ch <= 4; ch++) { + var group = "[Channel" + ch + "]"; + TraktorS3.colorOutputHandler(TraktorS3.fxEnabledState[group], group, "!fxEnabled"); + } + for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { + var ledValue = TraktorS3.fxLEDValue[fxNumber]; + if (TraktorS3.fxButtonState[fxNumber]) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.freeze_lights); + } +}; + +TraktorS3.lightDeck = function(group, send_packets) { if (send_packets === undefined) { send_packets = true; } @@ -1342,7 +1412,6 @@ TraktorS3.lightDeck = function (group, send_packets) { deck_group_name = "deck2"; } - HIDDebug("light groups! " + deck_group_name + " " + group); TraktorS3.lightGroup(packet, deck_group_name, group); TraktorS3.lightGroup(packet, group, group); @@ -1351,22 +1420,12 @@ TraktorS3.lightDeck = function (group, send_packets) { // These lights are different because either they aren't associated with a CO, or // there are two buttons that point to the same CO. TraktorS3.deckOutputHandler(0, deck_group_name, "!shift"); - TraktorS3.colorOutputHandler(0, deck_group_name, "!PreviewTrack"); - TraktorS3.colorOutputHandler(0, deck_group_name, "!AddTrack"); - TraktorS3.colorOutputHandler(0, deck_group_name, "!LibraryFocus"); + TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!PreviewTrack"); + TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!AddTrack"); + TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!LibraryFocus"); TraktorS3.deckOutputHandler(0, deck_group_name, "!reverse"); } - - // // FX buttons - // var packet = this.controller.OutputPackets["output3"]; - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit1]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit2]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect1]", "[EffectRack1_EffectUnit1_Effect1]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect2]", "[EffectRack1_EffectUnit1_Effect2]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect3]", "[EffectRack1_EffectUnit1_Effect3]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect1]", "[EffectRack1_EffectUnit2_Effect1]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect2]", "[EffectRack1_EffectUnit2_Effect2]"); - // TraktorS3.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect3]", "[EffectRack1_EffectUnit2_Effect3]"); + TraktorS3.lightFX(); // Selected deck lights var ctrlr = TraktorS3.controller; @@ -1405,7 +1464,6 @@ TraktorS3.masterVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler = function(value, group, key, segments) { // return; // This handler is called a lot so it should be as fast as possible. - // HIDDebug("group??" + group + " " + key); var scaledValue = value * segments; var fullIllumCount = Math.floor(scaledValue); @@ -1437,8 +1495,6 @@ TraktorS3.peakOutputHandler = function(value, group, key) { // outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { - HIDDebug("REGULAR OUTPUT GROUP " + group); - var ledValue = value; if (value === 0 || value === false) { // Off value @@ -1455,15 +1511,11 @@ TraktorS3.outputHandler = function(value, group, key) { TraktorS3.deckOutputHandler = function(value, group, key) { // incoming value will be a channel, we have to resolve back to // deck. - HIDDebug("deckoutput group " + group); - var ledValue = 0x20; if (value === 1 || value === true) { // On value ledValue = 0x77; } - HIDDebug("DECK: OUTPUT GROUP " + group + " " + key + " " + ledValue); - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); }; @@ -1482,7 +1534,7 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { TraktorS3.freeze_lights = true; for (var i = 0; i < 8; i++) { // HIDDebug("wheel! " + ledValue.toString(16)); - TraktorS3.colorOutputHandler(value, group, "!wheel" + i); + TraktorS3.colorDeckOutputHandler(value, group, "!wheel" + i); } if (sendPacket) { for (packet_name in TraktorS3.controller.OutputPackets) { @@ -1497,15 +1549,26 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorOutputHandler = function(value, group, key) { - // HIDDebug("coloroutput! " + value + " " + group + " " + key); + var deck = TraktorS3.controller.resolveDeck(group); + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[deck]]; + HIDDebug("wat " + deck + " " + TraktorS3.controller.deckOutputColors[deck] + " " + ledValue); + if (value === 1 || value === true) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + HIDDebug("VALUE: " + ledValue); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); +}; + +// colorDeckOutputHandler drives lights that have the palettized multicolor lights. +TraktorS3.colorDeckOutputHandler = function(value, group, key) { // Reject update if it's for a specific channel that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); if (updatedDeck != undefined) { if (!TraktorS3.activeDecks[updatedDeck]) { - HIDDebug("colordeck is not active: " + updatedDeck); return; } - // HIDDebug("color updatedeck? " + updatedDeck); if (updatedDeck == 1 || updatedDeck == 3) { group = "deck1"; } else if (updatedDeck == 2 || updatedDeck == 4) { @@ -1518,28 +1581,18 @@ TraktorS3.colorOutputHandler = function(value, group, key) { } var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; - // HIDDebug("color output: " + group + "." + key + " " + value); - // var activeGroup = TraktorS3.deckToGroup(group); - // HIDDebug("group!" + group + " color " + ledValue); if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { ledValue += TraktorS3.LEDDimValue; } - - // HIDDebug("So I think: " + group + " " + key); - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); }; -TraktorS3.padLEDHandler = function (deck) { - -} - TraktorS3.hotcueOutputHandler = function(value, group, key) { // Light button LED only when we are in hotcue mode if (TraktorS3.padModeState[group] === 0) { - TraktorS3.colorOutputHandler(value, group, key); + TraktorS3.colorDeckOutputHandler(value, group, key); } }; @@ -1561,13 +1614,13 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { if (key === "play" && engine.getValue(group, "track_loaded")) { if (value) { // Green light on play - TraktorS3.colorOutputHandler(0x9E, deck, "!pad_" + num); + TraktorS3.colorDeckOutputHandler(0x9E, deck, "!pad_" + num); } else { // Reset LED to full white light - TraktorS3.colorOutputHandler(1, deck, "!pad_" + num); + TraktorS3.colorDeckOutputHandler(1, deck, "!pad_" + num); } } else if (key === "track_loaded") { - TraktorS3.colorOutputHandler(value, deck, "!pad_" + num); + TraktorS3.colorDeckOutputHandler(value, deck, "!pad_" + num); } } }; From 29bb6e7714f205d0456ca30ef43210235a821ac0 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 8 Aug 2020 22:27:31 -0400 Subject: [PATCH 23/84] Traktor S3: clean up lint problems --- .../Traktor-Kontrol-S3-hid-scripts.js | 173 +++++++++--------- 1 file changed, 85 insertions(+), 88 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6f713b43220..ce7a7b4f050 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1,8 +1,8 @@ /////////////////////////////////////////////////////////////////////////////////// // JSHint configuration // /////////////////////////////////////////////////////////////////////////////////// -/* global engine */ -/* global script */ +// /* global engine */ +// /* global script */ /* global HIDDebug */ /* global HIDPacket */ /* global HIDController */ @@ -45,7 +45,7 @@ var TraktorS3 = new function() { this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode // When true, packets will not be sent to the controller. Good for doing mass updates. - this.freeze_lights = false; + this.batchingOutputs = false; // Active deck switches -- common-hid-packet-parser only has one active deck status per // Controller object. @@ -703,11 +703,11 @@ TraktorS3.selectBeatjumpHandler = function(field) { } if (TraktorS3.shiftPressed[field.group]) { - var beatjump_size = engine.getValue(activeGroup, "beatjump_size"); + var beatjumpSize = engine.getValue(activeGroup, "beatjump_size"); if (delta > 0) { - engine.setValue(activeGroup, "beatjump_size", beatjump_size * 2); + engine.setValue(activeGroup, "beatjump_size", beatjumpSize * 2); } else { - engine.setValue(activeGroup, "beatjump_size", beatjump_size / 2); + engine.setValue(activeGroup, "beatjump_size", beatjumpSize / 2); } } else { if (delta < 0) { @@ -793,7 +793,7 @@ TraktorS3.jogTouchHandler = function(field) { return; } HIDDebug("jog active group " + activeGroup); - if (TraktorS3.wheelTouchInertiaTimer[activeGroup] != 0) { + if (TraktorS3.wheelTouchInertiaTimer[activeGroup] !== 0) { // The wheel was touched again, reset the timer. engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[activeGroup]); TraktorS3.wheelTouchInertiaTimer[activeGroup] = 0; @@ -829,20 +829,20 @@ TraktorS3.jogHandler = function(field) { } var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); HIDDebug("delta: " + deltas); - var tick_delta = deltas[0]; - var time_delta = deltas[1] / 0x100; - HIDDebug("time delt: " + time_delta.toString(16)); + var tickDelta = deltas[0]; + var timeDelta = deltas[1] / 0x100; + HIDDebug("time delt: " + timeDelta.toString(16)); - var velocity = TraktorS3.scalerJog(activeGroup, tick_delta, time_delta); + var velocity = TraktorS3.scalerJog(activeGroup, tickDelta, timeDelta); HIDDebug("VELO: " + velocity); engine.setValue(activeGroup, "jog", velocity); if (engine.getValue(activeGroup, "scratch2_enable")) { - var deckNumber = TraktorS3.controller.resolveDeck(group); - engine.scratchTick(deckNumber, tick_delta); + var deckNumber = TraktorS3.controller.resolveDeck(field.group); + engine.scratchTick(deckNumber, tickDelta); } }; -TraktorS3.scalerJog = function(group, tick_delta, time_delta) { +TraktorS3.scalerJog = function(group, tickDelta, timeDelta) { // If it's playing nudge var multiplier = 1.0; if (TraktorS3.shiftPressed["deck1"] || TraktorS3.shiftPressed["deck2"]) { @@ -850,10 +850,10 @@ TraktorS3.scalerJog = function(group, tick_delta, time_delta) { } if (engine.getValue(group, "play")) { - return multiplier * (tick_delta / time_delta) / 3; + return multiplier * (tickDelta / timeDelta) / 3; } - return (tick_delta / time_delta) * multiplier * 2.0; + return (tickDelta / timeDelta) * multiplier * 2.0; }; TraktorS3.wheelDeltas = function(deckNumber, value) { @@ -907,7 +907,7 @@ TraktorS3.finishJogTouch = function(group) { return; }*/ var play = engine.getValue(group, "play"); - if (play != 0) { + if (play !== 0) { // If we are playing, just hand off to the engine. engine.scratchDisable(deckNumber, true); } else { @@ -952,7 +952,7 @@ TraktorS3.fxHandler = function(field) { } else { ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); // TraktorS3.colorOutputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "!fxButton" + fxNumber); TraktorS3.toggleFX(); }; @@ -1041,7 +1041,7 @@ TraktorS3.init = function(_id) { TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). - var data_strings = [ + var dataStrings = [ " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + "FF 00 00 00 FF 00 FF 2C 00 FF 2C 7C FF 00 00 00 " + @@ -1058,24 +1058,24 @@ TraktorS3.debugLights = function() { var data = [Object(), Object()]; - for (i = 0; i < data.length; i++) { + for (var i = 0; i < data.length; i++) { var ok = true; - var splitted = data_strings[i].split(/\s+/); + var splitted = dataStrings[i].split(/\s+/); HIDDebug("i " + i + " " + splitted); data[i].length = splitted.length; - for (j = 0; j < splitted.length; j++) { - var byte_str = splitted[j]; - if (byte_str.length === 0) { + for (var j = 0; j < splitted.length; j++) { + var byteStr = splitted[j]; + if (byteStr.length === 0) { continue; } - if (byte_str.length !== 2) { + if (byteStr.length !== 2) { ok = false; - HIDDebug("not two characters?? " + byte_str); + HIDDebug("not two characters?? " + byteStr); } - var b = parseInt(byte_str, 16); + var b = parseInt(byteStr, 16); if (b < 0 || b > 255) { ok = false; - HIDDebug("number out of range: " + byte_str + " " + b); + HIDDebug("number out of range: " + byteStr + " " + b); } data[i][j] = b; } @@ -1091,7 +1091,7 @@ TraktorS3.debugLights = function() { // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; // } if (ok) { - controller.send(data[i], data[i].length, 0x80 + i); + TraktorS3.controller.send(data[i], data[i].length, 0x80 + i); } } }; @@ -1189,8 +1189,8 @@ TraktorS3.registerOutputPackets = function() { "deck1": 0x43, "deck2": 0x4A }; - for (ch in wheelOffsets) { - for (i = 0; i < 8; i++) { + for (var ch in wheelOffsets) { + for (var i = 0; i < 8; i++) { outputA.addOutput(ch, "!" + "wheel" + i, wheelOffsets[ch] + i, "B"); } } @@ -1246,7 +1246,7 @@ TraktorS3.registerOutputPackets = function() { // this.linkOutput("[Microphone]", "talkover", this.outputHandler); // Channel VuMeters - for (var i = 1; i <= 4; i++) { + for (i = 1; i <= 4; i++) { this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.channelVuMeterHandler); this.clipConnections[i] = engine.makeConnection("[Channel" + i + "]", "PeakIndicator", this.peakOutputHandler); } @@ -1327,16 +1327,16 @@ TraktorS3.resolveDeckIfActive = function(group) { return undefined; }; -TraktorS3.lightGroup = function(packet, output_group_name, co_group_name) { - var group_ob = packet.groups[output_group_name]; - for (var field_name in group_ob) { - field = group_ob[field_name]; +TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { + var groupOb = packet.groups[outputGroupName]; + for (var fieldName in groupOb) { + var field = groupOb[fieldName]; if (field.name[0] === "!") { continue; } if (field.mapped_callback !== undefined) { - var value = engine.getValue(co_group_name, field.name); - field.mapped_callback(value, co_group_name, field.name); + var value = engine.getValue(coGroupName, field.name); + field.mapped_callback(value, coGroupName, field.name); } // No callback, no light! } @@ -1357,7 +1357,7 @@ TraktorS3.lightHotcue = function(group, number) { } else { ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput(deck, "!pad_" + number, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(deck, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); }; TraktorS3.lightPads = function(group) { @@ -1377,7 +1377,7 @@ TraktorS3.lightPads = function(group) { } else { TraktorS3.colorDeckOutputHandler(1, activeGroup, "hotcues"); TraktorS3.colorDeckOutputHandler(0, activeGroup, "samples"); - for (var i = 1; i <= 8; ++i) { + for (i = 1; i <= 8; ++i) { TraktorS3.lightHotcue(activeGroup, i); } } @@ -1395,35 +1395,35 @@ TraktorS3.lightFX = function() { } else { ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); } }; -TraktorS3.lightDeck = function(group, send_packets) { - if (send_packets === undefined) { - send_packets = true; +TraktorS3.lightDeck = function(group, sendPackets) { + if (sendPackets === undefined) { + sendPackets = true; } // Freeze the lights while we do this update so we don't spam HID. - TraktorS3.freeze_lights = true; - for (var packet_name in this.controller.OutputPackets) { - packet = this.controller.OutputPackets[packet_name]; - var deck_group_name = "deck1"; + TraktorS3.batchingOutputs = true; + for (var packetName in this.controller.OutputPackets) { + var packet = this.controller.OutputPackets[packetName]; + var deckGroupName = "deck1"; if (group === "[Channel2]" || group === "[Channel4]") { - deck_group_name = "deck2"; + deckGroupName = "deck2"; } - TraktorS3.lightGroup(packet, deck_group_name, group); + TraktorS3.lightGroup(packet, deckGroupName, group); TraktorS3.lightGroup(packet, group, group); - TraktorS3.lightPads(deck_group_name); + TraktorS3.lightPads(deckGroupName); // These lights are different because either they aren't associated with a CO, or // there are two buttons that point to the same CO. - TraktorS3.deckOutputHandler(0, deck_group_name, "!shift"); - TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!PreviewTrack"); - TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!AddTrack"); - TraktorS3.colorDeckOutputHandler(0, deck_group_name, "!LibraryFocus"); - TraktorS3.deckOutputHandler(0, deck_group_name, "!reverse"); + TraktorS3.deckOutputHandler(0, deckGroupName, "!shift"); + TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!PreviewTrack"); + TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!AddTrack"); + TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!LibraryFocus"); + TraktorS3.deckOutputHandler(0, deckGroupName, "!reverse"); } TraktorS3.lightFX(); @@ -1443,12 +1443,11 @@ TraktorS3.lightDeck = function(group, send_packets) { ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDDimValue, false); } - TraktorS3.freeze_lights = false; + TraktorS3.batchingOutputs = false; // And now send them all. - if (send_packets) { - for (packet_name in this.controller.OutputPackets) { - var packet_ob = this.controller.OutputPackets[packet_name]; - packet_ob.send(); + if (sendPackets) { + for (packetName in this.controller.OutputPackets) { + this.controller.OutputPackets[packetName].send(); } } }; @@ -1470,12 +1469,12 @@ TraktorS3.vuMeterHandler = function(value, group, key, segments) { // Figure out how much the partially-illuminated segment is illuminated. var partialIllum = (scaledValue - fullIllumCount) * 0x7F; - for (i = 0; i < segments; i++) { + for (var i = 0; i < segments; i++) { var segmentKey = "!" + key + i; if (i < fullIllumCount) { // Don't update lights until they're all done, so the last term is false. TraktorS3.controller.setOutput(group, segmentKey, 0x7F, false); - } else if (i == fullIllumCount) { + } else if (i === fullIllumCount) { TraktorS3.controller.setOutput(group, segmentKey, partialIllum, false); } else { TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); @@ -1490,7 +1489,7 @@ TraktorS3.peakOutputHandler = function(value, group, key) { ledValue = 0x7E; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; // outputHandler drives lights that only have one color. @@ -1504,7 +1503,7 @@ TraktorS3.outputHandler = function(value, group, key) { ledValue = 0xFF; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; // deckOutputHandler drives lights that only have one color. @@ -1516,7 +1515,7 @@ TraktorS3.deckOutputHandler = function(value, group, key) { // On value ledValue = 0x77; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; TraktorS3.wheelOutputHandler = function(value, group, key) { @@ -1530,20 +1529,19 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { return; } - var sendPacket = !TraktorS3.freeze_lights; - TraktorS3.freeze_lights = true; + var sendPacket = !TraktorS3.batchingOutputs; + TraktorS3.batchingOutputs = true; for (var i = 0; i < 8; i++) { // HIDDebug("wheel! " + ledValue.toString(16)); TraktorS3.colorDeckOutputHandler(value, group, "!wheel" + i); } if (sendPacket) { - for (packet_name in TraktorS3.controller.OutputPackets) { - var packet_ob = TraktorS3.controller.OutputPackets[packet_name]; - packet_ob.send(); + for (var packetName in TraktorS3.controller.OutputPackets) { + TraktorS3.controller.OutputPackets[packetName].send(); } - // Only unset freeze_lights if it wasn't already true when we + // Only unset batchingOutputs if it wasn't already true when we // entered this function. - TraktorS3.freeze_lights = false; + TraktorS3.batchingOutputs = false; } }; @@ -1558,20 +1556,20 @@ TraktorS3.colorOutputHandler = function(value, group, key) { ledValue += TraktorS3.LEDDimValue; } HIDDebug("VALUE: " + ledValue); - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; // colorDeckOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorDeckOutputHandler = function(value, group, key) { // Reject update if it's for a specific channel that's not selected. var updatedDeck = TraktorS3.controller.resolveDeck(group); - if (updatedDeck != undefined) { + if (updatedDeck !== undefined) { if (!TraktorS3.activeDecks[updatedDeck]) { return; } - if (updatedDeck == 1 || updatedDeck == 3) { + if (updatedDeck === "1" || updatedDeck === "3") { group = "deck1"; - } else if (updatedDeck == 2 || updatedDeck == 4) { + } else if (updatedDeck === "2" || updatedDeck === "4") { group = "deck2"; } } else { @@ -1586,7 +1584,7 @@ TraktorS3.colorDeckOutputHandler = function(value, group, key) { } else { ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.freeze_lights); + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; TraktorS3.hotcueOutputHandler = function(value, group, key) { @@ -1642,7 +1640,7 @@ TraktorS3.resolveSampler = function(group) { TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { - if (data.hasOwnProperty(name)) { + if (Object.prototype.hasOwnProperty.call(data, name)) { TraktorS3.controller.processButton(data[name]); } } @@ -1650,7 +1648,7 @@ TraktorS3.messageCallback = function(_packet, data) { TraktorS3.shutdown = function() { // Deactivate all LEDs - var data_strings = [ + var dataStrings = [ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + @@ -1667,18 +1665,17 @@ TraktorS3.shutdown = function() { var data = [Object(), Object()]; - for (i = 0; i < data.length; i++) { - var ok = true; - var splitted = data_strings[i].split(/\s+/); + for (var i = 0; i < data.length; i++) { + var splitted = dataStrings[i].split(/\s+/); data[i].length = splitted.length; - for (j = 0; j < splitted.length; j++) { - var byte_str = splitted[j]; - if (byte_str.length === 0) { + for (var j = 0; j < splitted.length; j++) { + var byteStr = splitted[j]; + if (byteStr.length === 0) { continue; } - data[i][j] = parseInt(byte_str, 16); + data[i][j] = parseInt(byteStr, 16); } - controller.send(data[i], data[i].length, 0x80 + i); + TraktorS3.controller.send(data[i], data[i].length, 0x80 + i); } HIDDebug("TraktorS3: Shutdown done!"); From f496ede715a7a65708e53686058c8109f41e8ef8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 00:08:40 -0400 Subject: [PATCH 24/84] Traktor S3: fix jog wheels The Traktor jog wheels have their own time calculation, so we don't need to use Mixxx's smoothing code. --- .../Traktor-Kontrol-S3-hid-scripts.js | 104 ++++++++---------- src/engine/controls/ratecontrol.cpp | 1 - 2 files changed, 43 insertions(+), 62 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index ce7a7b4f050..6640e345323 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1,8 +1,6 @@ /////////////////////////////////////////////////////////////////////////////////// // JSHint configuration // /////////////////////////////////////////////////////////////////////////////////// -// /* global engine */ -// /* global script */ /* global HIDDebug */ /* global HIDPacket */ /* global HIDController */ @@ -10,14 +8,14 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* Traktor Kontrol S3 HID controller script v1.00 */ -/* Last modification: August 2020 */ +/* Last modification: August 2020 */ /* Author: Owen Williams */ /* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ /* */ /////////////////////////////////////////////////////////////////////////////////// /* */ -/* TODO: -/* * scratching still fucked up */ +/* TODO: */ +/* * scratching still fucked up */ /* * wheel animations */ /* * touch for track browse, loop control, beatjump */ /* * jog button */ @@ -69,9 +67,10 @@ var TraktorS3 = new function() { this.syncPressedTimer = {"deck1": 0, "deck2": 0}; // Timer to distinguish between short and long press // Jog wheels - this.pitchBendMultiplier = 1.1; + // tickReceived is used to detect when the platter has stopped moving. + this.tickReceived = [false, false]; this.lastTickVal = [0, 0]; - this.lastTickTime = [0.0, 0.0]; + this.lastTickTime = [0, 0]; this.wheelTouchInertiaTimer = { "[Channel1]": 0, "[Channel2]": 0, @@ -787,25 +786,21 @@ TraktorS3.samplerPregainHandler = function(field) { }; TraktorS3.jogTouchHandler = function(field) { - HIDDebug("jogtouch! " + field.group); var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { return; } - HIDDebug("jog active group " + activeGroup); if (TraktorS3.wheelTouchInertiaTimer[activeGroup] !== 0) { - // The wheel was touched again, reset the timer. + // The wheel was touched again, reset the timer. engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[activeGroup]); TraktorS3.wheelTouchInertiaTimer[activeGroup] = 0; } if (field.value !== 0) { var deckNumber = TraktorS3.controller.resolveDeck(activeGroup); if (deckNumber === undefined) { - HIDDebug("############################## error, deck 0???"); return; } - HIDDebug("scratchen " + deckNumber); - engine.scratchEnable(deckNumber, 768, 33.3333, 0.125, 0.125/32, true); + engine.setValue(activeGroup, "scratch2_enable", true); } else { // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. // Depending on how fast the platter was moving, lengthen the time we'll wait. @@ -816,7 +811,7 @@ TraktorS3.jogTouchHandler = function(field) { // Just do it now. TraktorS3.finishJogTouch(activeGroup); } else { - TraktorS3.controller.wheelTouchInertiaTimer[activeGroup] = engine.beginTimer( + TraktorS3.wheelTouchInertiaTimer[activeGroup] = engine.beginTimer( inertiaTime, "TraktorS3.finishJogTouch(\"" + activeGroup + "\")", true); } } @@ -827,33 +822,34 @@ TraktorS3.jogHandler = function(field) { if (!activeGroup) { return; } + TraktorS3.tickReceived[activeGroup] = true; var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); - HIDDebug("delta: " + deltas); var tickDelta = deltas[0]; - var timeDelta = deltas[1] / 0x100; - HIDDebug("time delt: " + timeDelta.toString(16)); + var timeDelta = deltas[1]; - var velocity = TraktorS3.scalerJog(activeGroup, tickDelta, timeDelta); - HIDDebug("VELO: " + velocity); - engine.setValue(activeGroup, "jog", velocity); - if (engine.getValue(activeGroup, "scratch2_enable")) { - var deckNumber = TraktorS3.controller.resolveDeck(field.group); - engine.scratchTick(deckNumber, tickDelta); - } -}; + // The scratch rate is the ratio of the wheel's speed to "regular" speed, + // which we're going to call 33.33 RPM. It's 768 ticks for a circle, and + // 400000 ticks per second, and 33.33 RPM is 1.8 seconds per rotation, so + // the standard speend is 768 / (400000 * 1.8) + var thirtyThree = 768 / 720000; -TraktorS3.scalerJog = function(group, tickDelta, timeDelta) { - // If it's playing nudge - var multiplier = 1.0; - if (TraktorS3.shiftPressed["deck1"] || TraktorS3.shiftPressed["deck2"]) { - multiplier = 100.0; - } + // Our actual speed is tickDelta / timeDelta. Take the ratio of those to get the + // rate ratio. + var velocity = (tickDelta / timeDelta) / thirtyThree; - if (engine.getValue(group, "play")) { - return multiplier * (tickDelta / timeDelta) / 3; + // The Mixxx scratch code tries to do accumulation and time calculation itself. + // This controller is better, so just use its values. + if (engine.getValue(activeGroup, "scratch2_enable")) { + engine.setValue(activeGroup, "scratch2", velocity); + } else { + // If we're playing, just nudge. + if (engine.getValue(activeGroup, "play")) { + velocity /= 4; + } else { + velocity *= 2; + } + engine.setValue(activeGroup, "jog", velocity); } - - return (tickDelta / timeDelta) * multiplier * 2.0; }; TraktorS3.wheelDeltas = function(deckNumber, value) { @@ -862,8 +858,6 @@ TraktorS3.wheelDeltas = function(deckNumber, value) { HIDDebug("VALUE: 0x" + value.toString(16)); var tickval = value & 0xFF; var timeval = value >>> 8; - // HIDDebug("tick: " + tickval.toString(16)); - HIDDebug("time: " + timeval.toString(16)); var prevTick = 0; var prevTime = 0; @@ -875,16 +869,18 @@ TraktorS3.wheelDeltas = function(deckNumber, value) { if (prevTime > timeval) { // We looped around. Adjust current time so that subtraction works. - HIDDebug("LOOP---------------------------------------------------------------"); timeval += 0x100000; } var timeDelta = timeval - prevTime; if (timeDelta === 0) { // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. + // This is almost certainly not going to happen on this controller. timeDelta = 1; } var tickDelta = 0; + + // Very generous 8bit loop-around detection. if (prevTick >= 200 && tickval <= 100) { tickDelta = tickval + 256 - prevTick; } else if (prevTick <= 100 && tickval >= 200) { @@ -898,31 +894,17 @@ TraktorS3.wheelDeltas = function(deckNumber, value) { TraktorS3.finishJogTouch = function(group) { TraktorS3.wheelTouchInertiaTimer[group] = 0; - var deckNumber = TraktorS3.controller.resolveDeck(group); - // No vinyl button (yet) - /*if (this.vinylActive) { - // Vinyl button still being pressed, don't disable scratch mode yet. - this.wheelTouchInertiaTimer[group] = engine.beginTimer( - 100, "VestaxVCI400.Decks." + this.deckIdentifier + ".finishJogTouch()", true); - return; - }*/ - var play = engine.getValue(group, "play"); - if (play !== 0) { - // If we are playing, just hand off to the engine. - engine.scratchDisable(deckNumber, true); + + // If we've received no ticks since the last call, we are stopped. + if (!TraktorS3.tickReceived[group]) { + engine.setValue(group, "scratch2", 0.0); + engine.setValue(group, "scratch2_enable", false); } else { - // If things are paused, there will be a non-smooth handoff between scratching and jogging. - // Instead, keep scratch on until the platter is not moving. - var scratchRate = Math.abs(engine.getValue(group, "scratch2")); - if (scratchRate < 0.01) { - // The platter is basically stopped, now we can disable scratch and hand off to jogging. - engine.scratchDisable(deckNumber, false); - } else { - // Check again soon. - TraktorS3.wheelTouchInertiaTimer[group] = engine.beginTimer( - 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); - } + // Check again soon. + TraktorS3.wheelTouchInertiaTimer[group] = engine.beginTimer( + 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); } + TraktorS3.tickReceived[group] = false; }; TraktorS3.superHandler = function(field) { diff --git a/src/engine/controls/ratecontrol.cpp b/src/engine/controls/ratecontrol.cpp index 74bce34d486..2378710a7e3 100644 --- a/src/engine/controls/ratecontrol.cpp +++ b/src/engine/controls/ratecontrol.cpp @@ -440,7 +440,6 @@ double RateControl::calculateSpeed(double baserate, double speed, bool paused, // New scratch behavior - overrides playback speed (and old behavior) if (useScratch2Value) { - qDebug() << "scratch val" << scratchFactor; rate = scratchFactor; } else { // add temp rate, but don't go backwards From d19b312511327d9f24f7a445306e42553177ca52 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 12:12:19 -0400 Subject: [PATCH 25/84] TraktorS3: revert mistake diffs --- .../Traktor-Kontrol-S4-MK2-hid-scripts.js | 2 +- res/controllers/common-hid-packet-parser.js | 1394 ++++++++--------- 2 files changed, 684 insertions(+), 712 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js index 7a37ac43ec1..2a3f322454b 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js @@ -1428,7 +1428,7 @@ TraktorS4MK2.scalerSlider = function(group, name, value) { } TraktorS4MK2.resolveDeckIfActive = function(group) { - var controller = TraktorS4MK2.controllger; + var controller = TraktorS4MK2.controller; if (group === "[Channel1]") { if (controller.left_deck_C) { return undefined; diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index d104c69ab21..ace9ad57d9d 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1,7 +1,7 @@ /** Common HID script debugging function. Just to get logging with 'HID' prefix. */ HIDDebug = function(message) { - print("HID " + message); -}; + print("HID " + message) +} /** HID Bit Vector Class * @@ -9,104 +9,104 @@ HIDDebug = function(message) { * created by HIDPacket addControl and addOutput and should not be * created manually. */ HIDBitVector = function() { - this.size = 0; - this.bits = {}; -}; + this.size = 0 + this.bits = {} +} /** Return bit offset based on bitmask */ HIDBitVector.prototype.getOffset = function(bitmask) { for (var i = 0; i < 32; i++) if ((1 & bitmask >> i) != 0) - return i; - return 0; -}; + return i + return 0 +} /** Add a control bitmask to the HIDBitVector */ HIDBitVector.prototype.addBitMask = function(group, name, bitmask) { - var bit = {}; - bit.type = "button"; - bit.packet = undefined; - bit.id = group + "." + name; - bit.group = group; - bit.name = name; - bit.mapped_group = undefined; - bit.mapped_name = undefined; - bit.bitmask = bitmask; - bit.bitmask = bitmask; - bit.bit_offset = this.getOffset(bitmask); - bit.callback = undefined; - bit.value = undefined; - bit.auto_repeat = undefined; - bit.auto_repeat_interval = undefined; - this.bits[bit.id] = bit; -}; + var bit = {} + bit.type = "button" + bit.packet = undefined + bit.id = group + "." + name + bit.group = group + bit.name = name + bit.mapped_group = undefined + bit.mapped_name = undefined + bit.bitmask = bitmask + bit.bitmask = bitmask + bit.bit_offset = this.getOffset(bitmask) + bit.callback = undefined + bit.value = undefined + bit.auto_repeat = undefined + bit.auto_repeat_interval = undefined + this.bits[bit.id] = bit +} /** Add a Output control bitmask to the HIDBitVector */ HIDBitVector.prototype.addOutputMask = function(group, name, bitmask) { - var bit = {}; - bit.type = "output"; - bit.packet = undefined; - bit.id = group + "." + name; - bit.group = group; - bit.name = name; - bit.mapped_group = undefined; - bit.mapped_name = undefined; - bit.bitmask = bitmask; - bit.bit_offset = this.getOffset(bitmask); - bit.callback = undefined; - bit.value = undefined; - bit.toggle = undefined; - this.bits[bit.id] = bit; -}; + var bit = {} + bit.type = "output" + bit.packet = undefined + bit.id = group + "." + name + bit.group = group + bit.name = name + bit.mapped_group = undefined + bit.mapped_name = undefined + bit.bitmask = bitmask + bit.bit_offset = this.getOffset(bitmask) + bit.callback = undefined + bit.value = undefined + bit.toggle = undefined + this.bits[bit.id] = bit +} /** HID Modifiers object * * Wraps all defined modifiers to one object with uniform API. * Don't call directly, this is available as HIDController.modifiers */ HIDModifierList = function() { - this.modifiers = Object(); - this.callbacks = Object(); -}; + this.modifiers = Object() + this.callbacks = Object() +} /** Add a new modifier to controller. */ HIDModifierList.prototype.add = function(name) { if (name in this.modifiers) { - HIDDebug("Modifier already defined: " + name); - return; + HIDDebug("Modifier already defined: " + name) + return } - this.modifiers[name] = undefined; -}; + this.modifiers[name] = undefined +} /** Set modifier value */ HIDModifierList.prototype.set = function(name, value) { if ((!name in this.modifiers)) { - HIDDebug("Unknown modifier: " + name); - return; + HIDDebug("Unknown modifier: " + name) + return } - this.modifiers[name] = value; + this.modifiers[name] = value if (name in this.callbacks) { - var callback = this.callbacks[name]; - callback(value); + var callback = this.callbacks[name] + callback(value) } -}; +} /** Get modifier value */ HIDModifierList.prototype.get = function(name) { if (!(name in this.modifiers)) { - HIDDebug("Unknown modifier: " + name); - return false; + HIDDebug("Unknown modifier: " + name) + return false } - return this.modifiers[name]; -}; + return this.modifiers[name] +} /** Set modifier callback (update function after modifier state changes) */ HIDModifierList.prototype.setCallback = function(name, callback) { if ((!name in this.modifiers)) { HIDDebug("Unknown modifier: " + name); - return; + return } - this.callbacks[name] = callback; -}; + this.callbacks[name] = callback +} /** * HID Packet object @@ -123,69 +123,69 @@ HIDModifierList.prototype.setCallback = function(name, callback) { * of packet. Do NOT put the report ID in this; use * the reportId parameter instead. */ HIDPacket = function(name, reportId, callback, header) { - this.name = name; - this.header = header; - this.callback = callback; + this.name = name + this.header = header + this.callback = callback - this.reportId = 0; + this.reportId = 0 if (reportId !== undefined) { - this.reportId = reportId; + this.reportId = reportId } - this.groups = {}; + this.groups = {} // Size of various 'pack' values in bytes - this.packSizes = {b: 1, B: 1, h: 2, H: 2, i: 4, I: 4}; - this.signedPackFormats = ["b", "h", "i"]; -}; + this.packSizes = {b: 1, B: 1, h: 2, H: 2, i: 4, I: 4} + this.signedPackFormats = ["b", "h", "i"] +} /** Pack a field value to the packet. * Can only pack bits and byte values, patches welcome. */ HIDPacket.prototype.pack = function(data, field) { - var value; + var value if (!(field.pack in this.packSizes)) { - HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack); - return; + HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack) + return } - var bytes = this.packSizes[field.pack]; - var signed = false; + var bytes = this.packSizes[field.pack] + var signed = false if (this.signedPackFormats.indexOf(field.pack) != -1) - signed = true; + signed = true if (field.type == "bitvector") { // TODO - fix multi byte bit vector outputs if (bytes > 1) { - HIDDebug("ERROR: packing multibyte bit vectors not yet supported"); - return; + HIDDebug("ERROR: packing multibyte bit vectors not yet supported") + return } for (bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; - data[field.offset] = data[field.offset] | bit.value; + var bit = field.value.bits[bit_id] + data[field.offset] = data[field.offset] | bit.value } - return; + return } - value = (field.value != undefined) ? field.value : 0; + value = (field.value != undefined) ? field.value : 0 if (value < field.min || value > field.max) { - HIDDebug("ERROR " + field.id + " packed value out of range: " + value); - return; + HIDDebug("ERROR " + field.id + " packed value out of range: " + value) + return } for (var byte_index = 0; byte_index < bytes; byte_index++) { - var index = field.offset + byte_index; + var index = field.offset + byte_index if (signed) { if (value >= 0) { - data[index] = (value >> (byte_index * 8)) & 255; + data[index] = (value >> (byte_index * 8)) & 255 } else { - data[index] = 255 - ((-(value + 1) >> (byte_index * 8)) & 255); + data[index] = 255 - ((-(value + 1) >> (byte_index * 8)) & 255) } } else { - data[index] = (value >> (byte_index * 8)) & 255; + data[index] = (value >> (byte_index * 8)) & 255 } } -}; +} /** Parse and return field value matching the 'pack' field from field attributes. * Valid values are: @@ -196,135 +196,135 @@ HIDPacket.prototype.pack = function(data, field) { * i signed integer * I unsigned integer */ HIDPacket.prototype.unpack = function(data, field) { - var value = 0; + var value = 0 if (!(field.pack in this.packSizes)) { - HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack); - return; + HIDDebug("ERROR parsing packed value: invalid pack format " + field.pack) + return } - var bytes = this.packSizes[field.pack]; - var signed = false; + var bytes = this.packSizes[field.pack] + var signed = false if (this.signedPackFormats.indexOf(field.pack) != -1) - signed = true; + signed = true for (field_byte = 0; field_byte < bytes; field_byte++) { if (data[field.offset + field_byte] == 255 && field_byte == 4) - value += 0; + value += 0 else - value += data[field.offset + field_byte] * Math.pow(2, (field_byte * 8)); + value += data[field.offset + field_byte] * Math.pow(2, (field_byte * 8)) } if (signed) { - var max_value = Math.pow(2, bytes * 8); - var split = max_value / 2 - 1; + var max_value = Math.pow(2, bytes * 8) + var split = max_value / 2 - 1 if (value > split) - value = value - max_value; + value = value - max_value } - return value; -}; + return value +} /** Find HID packet group matching name. * Create group if create is true */ HIDPacket.prototype.getGroup = function(name, create) { if (this.groups == undefined) - this.groups = {}; + this.groups = {} if (name in this.groups) - return this.groups[name]; + return this.groups[name] if (!create) - return undefined; - this.groups[name] = {}; - return this.groups[name]; -}; + return undefined + this.groups[name] = {} + return this.groups[name] +} /** Lookup HID packet field matching given offset and pack type * Returns undefined if no patching field can be found. */ HIDPacket.prototype.getFieldByOffset = function(offset, pack) { if (!(pack in this.packSizes)) { - HIDDebug("Unknown pack string " + pack); - return undefined; + HIDDebug("Unknown pack string " + pack) + return undefined } - var end_offset = offset + this.packSizes[pack]; - var group; - var field; + var end_offset = offset + this.packSizes[pack] + var group + var field for (var group_name in this.groups) { - group = this.groups[group_name]; + group = this.groups[group_name] for (var field_id in group) { - field = group[field_id]; + field = group[field_id] // Same field offset if (field.offset == offset) - return field; + return field // 7-8 8-9 // Offset for smaller packet inside multibyte field if (field.offset < offset && field.end_offset >= end_offset) - return field; + return field // Packet offset starts inside field, may overflow if (field.offset < offset && field.end_offset > offset) - return field; + return field // Packet start before field, ends or overflows field if (field.offset > offset && field.offset < end_offset) - return field; + return field } } - return undefined; -}; + return undefined +} /** Return a field by group and name from the packet, * Returns undefined if field could not be found */ HIDPacket.prototype.getField = function(group, name) { - var field_id = group + "." + name; + var field_id = group + "." + name if (!(group in this.groups)) { - HIDDebug("PACKET " + this.name + " group not found " + group); - return undefined; + HIDDebug("PACKET " + this.name + " group not found " + group) + return undefined } - var control_group = this.groups[group]; + var control_group = this.groups[group] if (field_id in control_group) - return control_group[field_id]; + return control_group[field_id] // Lookup for bit fields in bitvector matching field name for (var group_name in this.groups) { - var control_group = this.groups[group_name]; + var control_group = this.groups[group_name] for (field_name in control_group) { - var field = control_group[field_name]; + var field = control_group[field_name] if (field == undefined || field.type != "bitvector") - continue; + continue for (bit_name in field.value.bits) { - var bit = field.value.bits[bit_name]; + var bit = field.value.bits[bit_name] if (bit.id == field_id) { - return field; + return field } } } } // Field not found - return undefined; -}; + return undefined +} /** Return reference to a bit in a bitvector field */ HIDPacket.prototype.lookupBit = function(group, name) { - var field = this.getField(group, name); + var field = this.getField(group, name) if (field == undefined) { - HIDDebug("Bitvector match not found: " + group + "." + name); - return undefined; + HIDDebug("Bitvector match not found: " + group + "." + name) + return undefined } - var bit_id = group + "." + name; + var bit_id = group + "." + name for (bit_name in field.value.bits) { - var bit = field.value.bits[bit_name]; + var bit = field.value.bits[bit_name] if (bit.id == bit_id) - return bit; + return bit } - HIDDebug("BUG: bit not found after successful field lookup"); - return undefined; -}; + HIDDebug("BUG: bit not found after successful field lookup") + return undefined +} /** Remove a control registered. Normally not needed */ HIDPacket.prototype.removeControl = function(group, name) { - var control_group = this.getGroup(group); + var control_group = this.getGroup(group) if (!(name in control_group)) { - HIDDebug("Field not in control group " + group + ": " + name); - return; + HIDDebug("Field not in control group " + group + ": " + name) + return } - delete control_group[name]; -}; + delete control_group[name] +} /** Register a numeric value to parse from input packet * @@ -338,28 +338,28 @@ HIDPacket.prototype.removeControl = function(group, name) { * @param callback callback function for the control */ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, isEncoder, callback) { - var control_group = this.getGroup(group, true); - var bitvector = undefined; + var control_group = this.getGroup(group, true) + var bitvector = undefined if (control_group == undefined) { - HIDDebug("ERROR creating HID packet group " + group); - return; + HIDDebug("ERROR creating HID packet group " + group) + return } if (!(pack in this.packSizes)) { - HIDDebug("Unknown pack value " + pack); - return; + HIDDebug("Unknown pack value " + pack) + return } - var field = this.getFieldByOffset(offset, pack); + var field = this.getFieldByOffset(offset, pack) if (field != undefined) { if (bitmask == undefined) { - HIDDebug("ERROR registering offset " + offset + " pack " + pack); + HIDDebug("ERROR registering offset " + offset + " pack " + pack) HIDDebug( "ERROR trying to overwrite non-bitmask control " + group + " " + name - ); - return; + ) + return } - bitvector = field.value; - bitvector.addBitMask(group, name, bitmask); + bitvector = field.value + bitvector.addBitMask(group, name, bitmask) if (callback !== undefined) { if (typeof callback !== "function") { HIDDebug("ERROR callback provided for " + group + "." + name + " is not a function."); @@ -367,64 +367,64 @@ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, is } this.setCallback(group, name, callback); } - return; - } - - field = {}; - field.packet = undefined; - field.id = group + "." + name; - field.group = group; - field.name = name; - field.mapped_group = undefined; - field.mapped_name = undefined; - field.pack = pack; - field.offset = offset; - field.end_offset = offset + this.packSizes[field.pack]; - field.bitmask = bitmask; - field.isEncoder = isEncoder; - field.callback = undefined; - field.soft_takeover = false; - field.ignored = false; - field.auto_repeat = undefined; - field.auto_repeat_interval = undefined; - - var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8); + return + } + + field = {} + field.packet = undefined + field.id = group + "." + name + field.group = group + field.name = name + field.mapped_group = undefined + field.mapped_name = undefined + field.pack = pack + field.offset = offset + field.end_offset = offset + this.packSizes[field.pack] + field.bitmask = bitmask + field.isEncoder = isEncoder + field.callback = undefined + field.soft_takeover = false + field.ignored = false + field.auto_repeat = undefined + field.auto_repeat_interval = undefined + + var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8) if (this.signedPackFormats.indexOf(pack) != -1) { - field.min = 0 - (packet_max_value / 2) + 1; - field.max = (packet_max_value / 2) - 1; + field.min = 0 - (packet_max_value / 2) + 1 + field.max = (packet_max_value / 2) - 1 } else { - field.min = 0; - field.max = packet_max_value - 1; + field.min = 0 + field.max = packet_max_value - 1 } if (bitmask == undefined || bitmask == packet_max_value) { - field.type = "control"; - field.value = undefined; - field.delta = 0; - field.mindelta = 0; + field.type = "control" + field.value = undefined + field.delta = 0 + field.mindelta = 0 } else { if (this.signedPackFormats.indexOf(pack) != -1) { - HIDDebug("ERROR registering bitvector: signed fields not supported"); - return; + HIDDebug("ERROR registering bitvector: signed fields not supported") + return } // Create a new bitvector field and add the bit to that // TODO - accept controls with bitmask < packet_max_value - field_name = "bitvector_" + offset; - field.type = "bitvector"; - field.name = field_name; - field.id = group + "." + field_name; - bitvector = new HIDBitVector(field.max); - bitvector.size = field.max; - bitvector.addBitMask(group, name, bitmask); - field.value = bitvector; - field.delta = undefined; - field.soft_takeover = undefined; - field.mindelta = undefined; + field_name = "bitvector_" + offset + field.type = "bitvector" + field.name = field_name + field.id = group + "." + field_name + bitvector = new HIDBitVector(field.max) + bitvector.size = field.max + bitvector.addBitMask(group, name, bitmask) + field.value = bitvector + field.delta = undefined + field.soft_takeover = undefined + field.mindelta = undefined } // Add the new field to the packet - control_group[field.id] = field; - + control_group[field.id] = field + if (callback !== undefined) { if (typeof callback !== "function") { HIDDebug("ERROR callback provided for " + group + "." + name + " is not a function."); @@ -432,7 +432,7 @@ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, is } this.setCallback(group, name, callback); } -}; +} /** Register a Output control field or Output control bit to output packet * Output control field: @@ -447,250 +447,250 @@ HIDPacket.prototype.addControl = function(group, name, offset, pack, bitmask, is * (0 is automatically populated with the reportId) * @param pack control packing format for pack(), one of b/B, h/H, i/I */ HIDPacket.prototype.addOutput = function(group, name, offset, pack, bitmask, callback) { - var control_group = this.getGroup(group, true); - var field; - var bitvector = undefined; - var field_id = group + "." + name; + var control_group = this.getGroup(group, true) + var field + var bitvector = undefined + var field_id = group + "." + name if (control_group == undefined) { - return; + return } if (!(pack in this.packSizes)) { - HIDDebug("ERROR: unknown Output control pack value " + pack); - return; + HIDDebug("ERROR: unknown Output control pack value " + pack) + return } // Adjust offset by 1 because the reportId was previously considered part of the payload // but isn't anymore and we can't be bothered to adjust every single script manually - offset -= 1; + offset -= 1 // Check if we are adding a Output bit to existing bitvector - field = this.getFieldByOffset(offset, pack); + field = this.getFieldByOffset(offset, pack) if (field != undefined) { if (bitmask == undefined) { HIDDebug( - "ERROR: overwrite non-bitmask control: " + group + ". " + name + ", " + field.id + " already exists" - ); - return; + "ERROR: overwrite non-bitmask control " + group + "." + name + ) + return } - bitvector = field.value; - bitvector.addOutputMask(group, name, bitmask); - return; - } - - field = {}; - field.id = field_id; - field.group = group; - field.name = name; - field.mapped_group = undefined; - field.mapped_name = undefined; - field.pack = pack; - field.offset = offset; - field.end_offset = offset + this.packSizes[field.pack]; - field.bitmask = bitmask; - field.callback = callback; - field.toggle = undefined; - - var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8); + bitvector = field.value + bitvector.addOutputMask(group, name, bitmask) + return + } + + field = {} + field.id = field_id + field.group = group + field.name = name + field.mapped_group = undefined + field.mapped_name = undefined + field.pack = pack + field.offset = offset + field.end_offset = offset + this.packSizes[field.pack] + field.bitmask = bitmask + field.callback = callback + field.toggle = undefined + + var packet_max_value = Math.pow(2, this.packSizes[field.pack] * 8) if (this.signedPackFormats.indexOf(pack) != -1) { - field.min = 0 - (packet_max_value / 2) + 1; - field.max = (packet_max_value / 2) - 1; + field.min = 0 - (packet_max_value / 2) + 1 + field.max = (packet_max_value / 2) - 1 } else { - field.min = 0; - field.max = packet_max_value - 1; + field.min = 0 + field.max = packet_max_value - 1 } if (bitmask == undefined || bitmask == packet_max_value) { - field.type = "output"; - field.value = undefined; - field.delta = undefined; - field.mindelta = undefined; + field.type = "output" + field.value = undefined + field.delta = undefined + field.mindelta = undefined } else { // Create new Output bitvector control field, add bit to it // rewrite name to use bitvector instead - field_name = "bitvector_" + offset; - field.type = "bitvector"; - field.id = group + "." + field_name; - field.name = field_name; - bitvector = new HIDBitVector(); - bitvector.size = field.max; - bitvector.addOutputMask(group, name, bitmask); - field.value = bitvector; - field.delta = undefined; - field.mindelta = undefined; + field_name = "bitvector_" + offset + field.type = "bitvector" + field.id = group + "." + field_name + field.name = field_name + bitvector = new HIDBitVector() + bitvector.size = field.max + bitvector.addOutputMask(group, name, bitmask) + field.value = bitvector + field.delta = undefined + field.mindelta = undefined } // Add Output to HID packet - control_group[field.id] = field; -}; + control_group[field.id] = field +} /** Register a callback to field or a bit vector bit. * Does not make sense for Output fields but you can do that. */ HIDPacket.prototype.setCallback = function(group, name, callback) { - var field = this.getField(group, name); - var field_id = group + "." + name; + var field = this.getField(group, name) + var field_id = group + "." + name if (callback == undefined) { - HIDDebug("Callback to add was undefined for " + field_id); - return; + HIDDebug("Callback to add was undefined for " + field_id) + return } if (field == undefined) { - HIDDebug("setCallback: field for " + field_id + " not found"); - return; + HIDDebug("setCallback: field for " + field_id + " not found" + ) + return } if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; + var bit = field.value.bits[bit_id] if (bit_id != field_id) - continue; - bit.callback = callback; - return; + continue + bit.callback = callback + return } - HIDDebug("ERROR: BIT NOT FOUND " + field_id); + HIDDebug("ERROR: BIT NOT FOUND " + field_id) } else { - field.callback = callback; + field.callback = callback } -}; +} /** Set 'ignored' flag for field to given value (true or false) * If field is ignored, it is not reported in 'delta' objects. */ HIDPacket.prototype.setIgnored = function(group, name, ignored) { - var field = this.getField(group, name); + var field = this.getField(group, name) if (field == undefined) { - HIDDebug("ERROR setting ignored flag for " + group + " " + name); - return; + HIDDebug("ERROR setting ignored flag for " + group + " " + name) + return } - field.ignored = ignored; -}; + field.ignored = ignored +} /** Adjust field's minimum delta value. * Input value changes smaller than this are not reported in delta */ HIDPacket.prototype.setMinDelta = function(group, name, mindelta) { - field = this.getField(group, name); + field = this.getField(group, name) if (field == undefined) { - HIDDebug("ERROR adjusting mindelta for " + group + " " + name); - return; + HIDDebug("ERROR adjusting mindelta for " + group + " " + name) + return } if (field.type == "bitvector") { - HIDDebug("ERROR setting mindelta for bitvector packet does not make sense"); - return; + HIDDebug("ERROR setting mindelta for bitvector packet does not make sense") + return } - field.mindelta = mindelta; -}; + field.mindelta = mindelta +} /** Parse bitvector field values, returning object with the named bits set. * Value must be a valid unsigned byte to parse, with enough bits. * Returns list of modified bits (delta) */ HIDPacket.prototype.parseBitVector = function(field, value) { - var bits = {}; - var bit; - var new_value; + var bits = {} + var bit + var new_value for (var bit_id in field.value.bits) { - bit = field.value.bits[bit_id]; - new_value = (bit.bitmask & value) >> bit.bit_offset; + bit = field.value.bits[bit_id] + new_value = (bit.bitmask & value) >> bit.bit_offset if (bit.value != undefined && bit.value != new_value) - bits[bit_id] = bit; - bit.value = new_value; + bits[bit_id] = bit + bit.value = new_value } - return bits; -}; + return bits +} /** Parse input packet fields from data. * Data is expected to be a Packet() received from HID device. * Returns list of changed fields with new value. * BitVectors are returned as bits you can iterate separately. */ HIDPacket.prototype.parse = function(data) { - var field_changes = {}; - var group; - var group_name; - var field; - var field_id; + var field_changes = {} + var group + var group_name + var field + var field_id for (group_name in this.groups) { - group = this.groups[group_name]; + group = this.groups[group_name] for (field_id in group) { - field = group[field_id]; + field = group[field_id] if (field == undefined) - continue; + continue - var value = this.unpack(data, field); + var value = this.unpack(data, field) if (value == undefined) { - HIDDebug("Error parsing packet field value for " + field_id); - return; + HIDDebug("Error parsing packet field value for " + field_id) + return } if (field.type == "bitvector") { // Bitvector deltas are checked in parseBitVector - var changed_bits = this.parseBitVector(field, value); + var changed_bits = this.parseBitVector(field, value) for (var bit_name in changed_bits) - field_changes[bit_name] = changed_bits[bit_name]; + field_changes[bit_name] = changed_bits[bit_name] } else if (field.type == "control") { if (field.value == value && field.mindelta != undefined) - continue; + continue if (field.ignored || field.value == undefined) { - field.value = value; - continue; + field.value = value + continue } - var change; + var change if (field.isEncoder) { if (field.value == field.max && value == field.min) { - change = 1; - field.delta = 1; + change = 1 + field.delta = 1 } else if (value == field.max && field.value == field.min) { - change = 1; - field.delta = -1; + change = 1 + field.delta = -1 } else { - change = 1; - field.delta = value - field.value; + change = 1 + field.delta = value - field.value } - field.value = value; + field.value = value } else { - change = Math.abs(value - field.value); - field.delta = value - field.value; + change = Math.abs(value - field.value) + field.delta = value - field.value } if (field.mindelta == undefined || change > field.mindelta) { - field_changes[field.id] = field; - field.value = value; + field_changes[field.id] = field + field.value = value } } } } - return field_changes; -}; + return field_changes +} /** Send this HID packet to device. * First the header bytes are copied to beginning of packet, then * field object values are packed to the HID packet according to the * field type. */ HIDPacket.prototype.send = function(debug) { - // HIDDebug("~~~~~~~~~~~~~~~~"); - var data = []; + var data = [] if (this.header !== undefined) { for (header_byte = 0; header_byte < this.header.length; header_byte++) { - data[header_byte] = this.header[header_byte]; + data[header_byte] = this.header[header_byte] } } for (var group_name in this.groups) { - var group = this.groups[group_name]; + var group = this.groups[group_name] for (var field_name in group) { - this.pack(data, group[field_name]); + this.pack(data, group[field_name]) } } if (debug) { - var packet_string = ""; + var packet_string = "" for (var d in data) { if (data[d] < 0x10) { // Add padding for bytes smaller than 10 - packet_string += "0"; + packet_string += "0" } - packet_string += data[d].toString(16) + " "; + packet_string += data[d].toString(16) + " " } - HIDDebug("Sending packet with Report ID " + this.reportId + ": " + packet_string); + HIDDebug("Sending packet with Report ID " + this.reportId + ": " + packet_string) } - controller.send(data, data.length, this.reportId); -}; + controller.send(data, data.length, this.reportId) +} /** * HID Controller Class @@ -725,43 +725,43 @@ HIDPacket.prototype.send = function(debug) { * scratchRampOnDisable If 'ramp' is used when disabling scratch */ HIDController = function() { - this.initialized = false; - this.activeDeck = undefined; + this.initialized = false + this.activeDeck = undefined - this.InputPackets = {}; - this.OutputPackets = {}; + this.InputPackets = {} + this.OutputPackets = {} // Default input control packet name: can be modified for controllers // which can swap modes (wiimote for example) - this.defaultPacket = "control"; + this.defaultPacket = "control" // Callback functions called by deck switching. Undefined by default - this.disconnectDeck = undefined; - this.connectDeck = undefined; + this.disconnectDeck = undefined + this.connectDeck = undefined // Scratch parameter defaults for this.scratchEnable function // override for custom control - this.isScratchEnabled = false; - this.scratchintervalsPerRev = 128; - this.scratchRPM = 33 + 1 / 3; - this.scratchAlpha = 1.0 / 8; - this.scratchBeta = this.scratchAlpha / 32; - this.scratchRampOnEnable = false; - this.scratchRampOnDisable = false; + this.isScratchEnabled = false + this.scratchintervalsPerRev = 128 + this.scratchRPM = 33 + 1 / 3 + this.scratchAlpha = 1.0 / 8 + this.scratchBeta = this.scratchAlpha / 32 + this.scratchRampOnEnable = false + this.scratchRampOnDisable = false // Button states available - this.buttonStates = {released: 0, pressed: 1}; + this.buttonStates = {released: 0, pressed: 1} // Output color values to send - this.LEDColors = {off: 0x0, on: 0x7f}; + this.LEDColors = {off: 0x0, on: 0x7f} // Toggle buttons this.toggleButtons = ["play", "pfl", "keylock", "quantize", "reverse", "slip_enabled", "group_[Channel1]_enable", "group_[Channel2]_enable", - "group_[Channel3]_enable", "group_[Channel4]_enable"]; + "group_[Channel3]_enable", "group_[Channel4]_enable"] // Override to set specific colors for multicolor button Output per deck - this.deckOutputColors = {1: "on", 2: "on", 3: "on", 4: "on"}; + this.deckOutputColors = {1: "on", 2: "on", 3: "on", 4: "on"} // Mapping of automatic deck switching with deckSwitch function - this.virtualDecks = ["deck", "deck1", "deck2", "deck3", "deck4"]; - this.deckSwitchMap = {1: 2, 2: 1, 3: 4, 4: 3, undefined: 1}; + this.virtualDecks = ["deck", "deck1", "deck2", "deck3", "deck4"] + this.deckSwitchMap = {1: 2, 2: 1, 3: 4, 4: 3, undefined: 1} // Standard target groups available in mixxx. This is used by // HID packet parser to recognize group parameters we should @@ -773,212 +773,212 @@ HIDController = function() { "[Master]", "[PreviewDeck1]", "[Effects]", "[Playlist]", "[Flanger]", "[Microphone]", "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit3]", "[EffectRack1_EffectUnit4]", - "[InternalClock]"]; + "[InternalClock]"] // Set to value in ms to update Outputs periodically - this.OutputUpdateInterval = undefined; + this.OutputUpdateInterval = undefined - this.modifiers = new HIDModifierList(); - this.scalers = {}; - this.timers = {}; + this.modifiers = new HIDModifierList() + this.scalers = {} + this.timers = {} - this.auto_repeat_interval = 100; -}; + this.auto_repeat_interval = 100 +} /** Function to close the controller object cleanly */ HIDController.prototype.close = function() { for (var name in this.timers) { - var timer = this.timers[name]; + var timer = this.timers[name] if (timer == undefined) - continue; - engine.stopTimer(timer); - this.timers[name] = undefined; + continue + engine.stopTimer(timer) + this.timers[name] = undefined } -}; +} /** Initialize our packet data and callbacks. This does not seem to * work when executed from here, but we keep a stub just in case. */ HIDController.prototype.initializePacketData = function() { -}; +} /** Return deck number from deck name. Deck name can't be virtual deck name * in this function call. */ HIDController.prototype.resolveDeck = function(group) { if (group == undefined) - return undefined; - var result = group.match(/\[Channel[0-9]+\]/); + return undefined + var result = group.match(/\[Channel[0-9]+\]/) if (!result) - return undefined; - var str = group.replace(/\[Channel/, ""); - return str.substring(0, str.length - 1); -}; + return undefined + var str = group.replace(/\[Channel/, "") + return str.substring(0, str.length - 1) +} /** Return the group name from given deck number. */ HIDController.prototype.resolveDeckGroup = function(deck) { if (deck == undefined) - return undefined; - return "[Channel" + deck + "]"; -}; + return undefined + return "[Channel" + deck + "]" +} /** Map virtual deck names to real deck group. If group is already * a real mixxx group value, just return it as it without mapping. */ HIDController.prototype.resolveGroup = function(group) { - var channel_name = /\[Channel[0-9]+\]/; + var channel_name = /\[Channel[0-9]+\]/ if (group != undefined && group.match(channel_name)) - return group; + return group if (this.valid_groups.indexOf(group) != -1) { - return group; + return group } if (group == "deck" || group == undefined) { if (this.activeDeck == undefined) - return undefined; - return "[Channel" + this.activeDeck + "]"; + return undefined + return "[Channel" + this.activeDeck + "]" } if (this.activeDeck == 1 || this.activeDeck == 2) { - if (group == "deck1") return "[Channel1]"; - if (group == "deck2") return "[Channel2]"; + if (group == "deck1") return "[Channel1]" + if (group == "deck2") return "[Channel2]" } if (this.activeDeck == 3 || this.activeDeck == 4) { - if (group == "deck1") return "[Channel3]"; - if (group == "deck2") return "[Channel4]"; + if (group == "deck1") return "[Channel3]" + if (group == "deck2") return "[Channel4]" } - return undefined; -}; + return undefined +} /** Find Output control matching give group and name * Returns undefined if output field can't be found. */ HIDController.prototype.getOutputField = function(m_group, m_name) { for (var packet_name in this.OutputPackets) { - var packet = this.OutputPackets[packet_name]; + var packet = this.OutputPackets[packet_name] for (var group_name in packet.groups) { - var group = packet.groups[group_name]; + var group = packet.groups[group_name] for (var field_name in group) { - var field = group[field_name]; + var field = group[field_name] if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; + var bit = field.value.bits[bit_id] if (bit.mapped_group == m_group && bit.mapped_name == m_name) - return bit; + return bit if (bit.group == m_group && bit.name == m_name) - return bit; + return bit } - continue; + continue } if (field.mapped_group == m_group && field.mapped_name == m_name) - return field; + return field if (field.group == m_group && field.name == m_name) - return field; + return field } } } - return undefined; -}; + return undefined +} /** Find input packet matching given name. * Returns undefined if input packet name is not registered. */ HIDController.prototype.getInputPacket = function(name) { if (!(name in this.InputPackets)) - return undefined; - return this.InputPackets[name]; -}; + return undefined + return this.InputPackets[name] +} /** Find output packet matching given name * Returns undefined if output packet name is not registered. */ HIDController.prototype.getOutputPacket = function(name) { if (!(name in this.OutputPackets)) - return undefined; - return this.OutputPackets[name]; -}; + return undefined + return this.OutputPackets[name] +} /** Set input packet callback afterwards */ HIDController.prototype.setPacketCallback = function(packet, callback) { - var input_packet = this.getInputPacket(packet); - input_packet.callback = callback; -}; + var input_packet = this.getInputPacket(packet) + input_packet.callback = callback +} /** Register packet field's callback. * If packet has callback, it is still parsed but no field processing is done, * callback is called directly after unpacking fields from packet. */ HIDController.prototype.setCallback = function(packet, group, name, callback) { - var input_packet = this.getInputPacket(packet); + var input_packet = this.getInputPacket(packet) if (input_packet == undefined) { - HIDDebug("Input packet not found " + packet); - return; + HIDDebug("Input packet not found " + packet) + return } - input_packet.setCallback(group, name, callback); -}; + input_packet.setCallback(group, name, callback) +} /** Register scaling function for a control name * This does not check if given control name is valid */ HIDController.prototype.setScaler = function(name, callback) { if (name in this.scalers) - return; - this.scalers[name] = callback; -}; + return + this.scalers[name] = callback +} /** Lookup scaling function for control * Returns undefined if function is not registered. */ HIDController.prototype.getScaler = function(name, callback) { if (!(name in this.scalers)) - return undefined; - return this.scalers[name]; -}; + return undefined + return this.scalers[name] +} /** Change type of a previously defined field to modifier and register it */ HIDController.prototype.linkModifier = function(group, name, modifier) { - var packet = this.getInputPacket(this.defaultPacket); + var packet = this.getInputPacket(this.defaultPacket) if (packet == undefined) { HIDDebug( "ERROR creating modifier: input packet " + this.defaultPacket + " not found" - ); - return; + ) + return } - var bit_id = group + "." + name; - var field = packet.lookupBit(group, name); + var bit_id = group + "." + name + var field = packet.lookupBit(group, name) if (field == undefined) { - HIDDebug("BIT field not found: " + bit_id); - return; + HIDDebug("BIT field not found: " + bit_id) + return } - field.group = "modifiers"; - field.name = modifier; - this.modifiers.set(modifier); -}; + field.group = "modifiers" + field.name = modifier + this.modifiers.set(modifier) +} /** TODO - implement unlinking of modifiers */ HIDController.prototype.unlinkModifier = function(group, name, modifier) { - HIDDebug("Unlinking of modifiers not yet implemented"); -}; + HIDDebug("Unlinking of modifiers not yet implemented") +} /** Link a previously declared HID control to actual mixxx control */ HIDController.prototype.linkControl = function(group, name, m_group, m_name, callback) { - var field; - var packet = this.getInputPacket(this.defaultPacket); + var field + var packet = this.getInputPacket(this.defaultPacket) if (packet == undefined) { - HIDDebug("ERROR creating modifier: input packet " + this.defaultPacket + " not found"); - return; + HIDDebug("ERROR creating modifier: input packet " + this.defaultPacket + " not found") + return } - field = packet.getField(group, name); + field = packet.getField(group, name) if (field == undefined) { - HIDDebug("Field not found: " + group + "." + name); - return; + HIDDebug("Field not found: " + group + "." + name) + return } if (field.type == "bitvector") { - field = packet.lookupBit(group, name); + field = packet.lookupBit(group, name) if (field == undefined) { - HIDDebug("bit not found: " + group + "." + name); - return; + HIDDebug("bit not found: " + group + "." + name) + return } } - field.mapped_group = m_group; - field.mapped_name = m_name; + field.mapped_group = m_group + field.mapped_name = m_name if (callback != undefined) - field.callback = callback; -}; + field.callback = callback +} /** TODO - implement unlinking of controls */ HIDController.prototype.unlinkControl = function(group, name) { -}; +} /** Register HID input packet type to controller. * Input packets can be responses from device to queries, or control @@ -987,25 +987,25 @@ HIDController.prototype.unlinkControl = function(group, name) { HIDController.prototype.registerInputPacket = function(packet) { // Find modifiers and other special cases from packet fields for (var group_name in packet.groups) { - var group = packet.groups[group_name]; + var group = packet.groups[group_name] for (var field_name in group) { - var field = group[field_name]; - field.packet = packet; + var field = group[field_name] + field.packet = packet if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; - bit.packet = packet; + var bit = field.value.bits[bit_id] + bit.packet = packet if (bit.group == "modifiers") - this.modifiers.add(bit.name); + this.modifiers.add(bit.name) } } else { if (field.group == "modifiers") - this.modifiers.add(field.name); + this.modifiers.add(field.name) } } } - this.InputPackets[packet.name] = packet; -}; + this.InputPackets[packet.name] = packet +} /** Register HID output packet type to controller * There are no special Output control output packets, just register Outputs to any @@ -1014,35 +1014,35 @@ HIDController.prototype.registerInputPacket = function(packet) { * If you need other data structures, patches are welcome, or you can just do it * manually in your script without registering the packet. */ HIDController.prototype.registerOutputPacket = function(packet) { - this.OutputPackets[packet.name] = packet; + this.OutputPackets[packet.name] = packet // Link packet to all fields for (var group_name in packet.groups) { - var group = packet.groups[group_name]; + var group = packet.groups[group_name] for (var field_name in group) { - var field = group[field_name]; - field.packet = packet; + var field = group[field_name] + field.packet = packet if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; - bit.packet = packet; + var bit = field.value.bits[bit_id] + bit.packet = packet } } } } -}; +} /** Parse a received input packet fields with "unpack" calls to fields * Calls packet callback and returns, if packet callback was defined * Calls processIncomingPacket and processes automated events there. * If defined, calls processDelta for results after processing automated fields */ HIDController.prototype.parsePacket = function(data, length) { - var packet; - var changed_data; + var packet + var changed_data if (this.InputPackets == undefined) { - return; + return } for (var name in this.InputPackets) { - packet = this.InputPackets[name]; + packet = this.InputPackets[name] // When the device uses multiple report types with report IDs, hidapi // prepends the report ID to the data sent to Mixxx. If the device @@ -1050,37 +1050,37 @@ HIDController.prototype.parsePacket = function(data, length) { // reportId as 0. In this case, hidapi only sends the data of the // report to Mixxx without a report ID. if (packet.reportId !== 0 && packet.reportId !== data[0]) { - continue; + continue } if (packet.header !== undefined) { for (var header_byte = 0; header_byte < packet.header.length; header_byte++) { if (packet.header[header_byte] != data[header_byte]) { - packet = undefined; - break; + packet = undefined + break } } if (packet === undefined) - continue; + continue } - changed_data = packet.parse(data); + changed_data = packet.parse(data) if (packet.callback != undefined) { - packet.callback(packet, changed_data); - return; + packet.callback(packet, changed_data) + return } // Process named group controls if (packet.name == this.defaultPacket) - this.processIncomingPacket(packet, changed_data); + this.processIncomingPacket(packet, changed_data) // Process generic changed_data packet, if callback is defined if (this.processDelta != undefined) - this.processDelta(packet, changed_data); + this.processDelta(packet, changed_data) if (this.postProcessDelta != undefined) - this.postProcessDelta(packet, changed_data); - return; + this.postProcessDelta(packet, changed_data) + return } - HIDDebug("Received unknown packet of " + length + " bytes"); - for (var i in data) HIDDebug("BYTE " + data[i]); -}; + HIDDebug("Received unknown packet of " + length + " bytes") + for (var i in data) HIDDebug("BYTE " + data[i]) +} /** Process the modified field values (delta) from input packet fields for * input control packet, if packet name is in this.defaultPacket. @@ -1100,177 +1100,177 @@ HIDController.prototype.parsePacket = function(data, length) { * - Finally tries run matching engine.setValue() function for control * fields in default mixxx groups. Not done if a callback was defined. */ HIDController.prototype.processIncomingPacket = function(packet, delta) { - var field; + var field for (var name in delta) { if (this.ignoredControlChanges != undefined && this.ignoredControlChanges.indexOf(name) != -1) - continue; - field = delta[name]; + continue + field = delta[name] if (field.type == "button") - this.processButton(field); + this.processButton(field) else if (field.type == "control") - this.processControl(field); + this.processControl(field) else - HIDDebug("Unknown field " + field.name + " type " + field.type); + HIDDebug("Unknown field " + field.name + " type " + field.type) } -}; +} /** Get active group for this field */ HIDController.prototype.getActiveFieldGroup = function(field) { if (field.mapped_group != undefined) { - return this.resolveGroup(field.mapped_group); + return this.resolveGroup(field.mapped_group) } - group = field.group; + group = field.group if (group == undefined) { if (this.activeDeck != undefined) - return "[Channel" + this.activeDeck + "]"; + return "[Channel" + this.activeDeck + "]" } if (this.valid_groups.indexOf(group) != -1) { //HIDDebug("Resolving group " + group); - return this.resolveGroup(group); + return this.resolveGroup(group) } - return group; -}; + return group +} /** Get active control name from field */ HIDController.prototype.getActiveFieldControl = function(field) { if (field.mapped_name != undefined) - return field.mapped_name; - return field.name; -}; + return field.mapped_name + return field.name +} /** Process given button field, triggering events */ HIDController.prototype.processButton = function(field) { - var group = this.getActiveFieldGroup(field); - var control = this.getActiveFieldControl(field); + var group = this.getActiveFieldGroup(field) + var control = this.getActiveFieldControl(field) if (group == undefined) { HIDDebug("processButton: Could not resolve group from " + field.group + " " + field.mapped_group + " " + field.name + " " + field.mapped_name - ); - return; + ) + return } if (group == "modifiers") { if (field.value != 0) - this.modifiers.set(control, true); + this.modifiers.set(control, true) else - this.modifiers.set(control, false); - return; + this.modifiers.set(control, false) + return } if (field.auto_repeat) { - timer_id = "auto_repeat_" + field.id; + timer_id = "auto_repeat_" + field.id if (field.value) { - this.startAutoRepeatTimer(timer_id, field.auto_repeat_interval); + this.startAutoRepeatTimer(timer_id, field.auto_repeat_interval) } else { - this.stopAutoRepeatTimer(timer_id); + this.stopAutoRepeatTimer(timer_id) } } if (field.callback != undefined) { - field.callback(field); - return; + field.callback(field) + return } if (control == "jog_touch") { if (group != undefined) { if (field.value == this.buttonStates.pressed) - this.enableScratch(group, true); + this.enableScratch(group, true) else - this.enableScratch(group, false); + this.enableScratch(group, false) } - return; + return } if (this.toggleButtons.indexOf(control) != -1) { if (field.value == this.buttonStates.released) - return; + return if (engine.getValue(group, control)) { if (control == "play") - engine.setValue(group, "stop", true); + engine.setValue(group, "stop", true) else - engine.setValue(group, control, false); + engine.setValue(group, control, false) } else { - engine.setValue(group, control, true); + engine.setValue(group, control, true) } - return; + return } if (field.auto_repeat && field.value == this.buttonStates.pressed) { - HIDDebug("Callback for " + field.group); - engine.setValue(group, control, field.auto_repeat(field)); + HIDDebug("Callback for " + field.group) + engine.setValue(group, control, field.auto_repeat(field)) } else if (engine.getValue(group, control) == false) { - engine.setValue(group, control, true); + engine.setValue(group, control, true) } else { - engine.setValue(group, control, false); + engine.setValue(group, control, false) } -}; +} /** Process given control field, triggering events */ HIDController.prototype.processControl = function(field) { - var value; - var group = this.getActiveFieldGroup(field); - var control = this.getActiveFieldControl(field); + var value + var group = this.getActiveFieldGroup(field) + var control = this.getActiveFieldControl(field) if (group == undefined) { HIDDebug("processControl: Could not resolve group from " + field.group + " " + field.mapped_group + " " + field.name + " " + field.mapped_name - ); - return; + ) + return } if (field.callback != undefined) { - value = field.callback(field); - return; + value = field.callback(field) + return } if (group == "modifiers") { - this.modifiers.set(control, field.value); - return; + this.modifiers.set(control, field.value) + return } if (control == "jog_wheel") { // Handle jog wheel scratching transparently - this.jog_wheel(field); - return; + this.jog_wheel(field) + return } // Call value scaler if defined and send mixxx signal - value = field.value; - scaler = this.getScaler(control); + value = field.value + scaler = this.getScaler(control) if (field.isEncoder) { - var field_delta = field.delta; + var field_delta = field.delta if (scaler != undefined) - field_delta = scaler(group, control, field_delta); - engine.setValue(group, control, field_delta); + field_delta = scaler(group, control, field_delta) + engine.setValue(group, control, field_delta) } else { if (scaler != undefined) { - value = scaler(group, control, value); + value = scaler(group, control, value) // See the Traktor S4 script for how to use this. If the scaler function has this // parameter set to true, we use the effects-engine setParameter call instead of // setValue. if (scaler.useSetParameter) { - engine.setParameter(group, control, value); - return; + engine.setParameter(group, control, value) + return } } - engine.setValue(group, control, value); + engine.setValue(group, control, value) } -}; +} /** Toggle control state from toggle button */ HIDController.prototype.toggle = function(group, control, value) { if (value == this.buttonStates.released) - return; - var status = engine.getValue(group, control) != true; - engine.setValue(group, control, status); -}; + return + var status = (engine.getValue(group, control) == true) ? false : true + engine.setValue(group, control, status) +} /** Toggle play/pause state */ HIDController.prototype.togglePlay = function(group, field) { if (field.value == this.buttonStates.released) - return; - var status = !(engine.getValue(group, "play")); + return + var status = (engine.getValue(group, "play")) ? false : true if (!status) - engine.setValue(group, "stop", true); + engine.setValue(group, "stop", true) else - engine.setValue(group, "play", true); -}; + engine.setValue(group, "play", true) +} /** Processing of the 'jog_touch' special button name, which is used to detect * when scratching should be enabled. @@ -1284,23 +1284,23 @@ HIDController.prototype.togglePlay = function(group, field) { * Sets the internal 'isScratchEnabled attribute to false, and calls scratchDisable * to end scratching mode */ HIDController.prototype.enableScratch = function(group, status) { - var deck = this.resolveDeck(group); + var deck = this.resolveDeck(group) if (status) { - this.isScratchEnabled = true; + this.isScratchEnabled = true engine.scratchEnable(deck, this.scratchintervalsPerRev, this.scratchRPM, this.scratchAlpha, this.scratchBeta, this.rampedScratchEnable - ); - if (this.enableScratchCallback != undefined) this.enableScratchCallback(true); + ) + if (this.enableScratchCallback != undefined) this.enableScratchCallback(true) } else { - this.isScratchEnabled = false; - engine.scratchDisable(deck, this.rampedScratchDisable); - if (this.enableScratchCallback != undefined) this.enableScratchCallback(false); + this.isScratchEnabled = false + engine.scratchDisable(deck, this.rampedScratchDisable) + if (this.enableScratchCallback != undefined) this.enableScratchCallback(false) } -}; +} /** Default jog scratching function. Used to handle jog move events from special * input control field called 'jog_wheel'. Handles both 'scratch' and 'jog' mixxx @@ -1319,260 +1319,232 @@ HIDController.prototype.enableScratch = function(group, status) { * both negative and positive values. */ HIDController.prototype.jog_wheel = function(field) { - var scaler = undefined; - var active_group = this.getActiveFieldGroup(field); - var value = undefined; + var scaler = undefined + var active_group = this.getActiveFieldGroup(field) + var value = undefined if (field.isEncoder) - value = field.delta; + value = field.delta else - value = field.value; + value = field.value if (this.isScratchEnabled) { - var deck = this.resolveDeck(active_group); + var deck = this.resolveDeck(active_group) if (deck == undefined) - return; - scaler = this.getScaler("jog_scratch"); + return + scaler = this.getScaler("jog_scratch") if (scaler != undefined) - value = scaler(active_group, "jog_scratch", value); + value = scaler(active_group, "jog_scratch", value) else - HIDDebug("WARNING non jog_scratch scaler, you likely want one"); - engine.scratchTick(deck, value); + HIDDebug("WARNING non jog_scratch scaler, you likely want one") + engine.scratchTick(deck, value) } else { if (active_group == undefined) - return; - scaler = this.getScaler("jog"); + return + scaler = this.getScaler("jog") if (scaler != undefined) - value = scaler(active_group, "jog", value); + value = scaler(active_group, "jog", value) else - HIDDebug("WARNING non jog scaler, you likely want one"); - engine.setValue(active_group, "jog", value); + HIDDebug("WARNING non jog scaler, you likely want one") + engine.setValue(active_group, "jog", value) } -}; +} HIDController.prototype.stopAutoRepeatTimer = function(timer_id) { if (this.timers[timer_id]) { - engine.stopTimer(this.timers[timer_id]); - delete this.timers[timer_id]; + engine.stopTimer(this.timers[timer_id]) + delete this.timers[timer_id] } else { //HIDDebug("No such autorepeat timer: " + timer_id); } -}; +} /** Toggle field autorepeat on or off */ HIDController.prototype.setAutoRepeat = function(group, name, callback, interval) { - var packet = this.getInputPacket(this.defaultPacket); - var field = packet.getField(group, name); + var packet = this.getInputPacket(this.defaultPacket) + var field = packet.getField(group, name) if (field == undefined) { - HIDDebug("setAutoRepeat: field not found " + group + "." + name); - return; + HIDDebug("setAutoRepeat: field not found " + group + "." + name) + return } - field.auto_repeat = callback; + field.auto_repeat = callback if (interval) - field.auto_repeat_interval = interval; + field.auto_repeat_interval = interval else - field.auto_repeat_interval = controller.auto_repeat_interval; + field.auto_repeat_interval = controller.auto_repeat_interval if (callback) - callback(field); -}; + callback(field) +} /** Callback for auto repeat timer to send again the values for * buttons and controls marked as 'auto_repeat' * Timer must be defined from actual controller side, because of * callback call namespaces and 'this' reference */ HIDController.prototype.autorepeatTimer = function() { - var group_name; - var group; - var field; - var field_name; - var bit_name; - var bit; - var packet = this.InputPackets[this.defaultPacket]; + var group_name + var group + var field + var field_name + var bit_name + var bit + var packet = this.InputPackets[this.defaultPacket] for (group_name in packet.groups) { - group = packet.groups[group_name]; + group = packet.groups[group_name] for (field_name in group) { - field = group[field_name]; + field = group[field_name] if (field.type != "bitvector") { if (field.auto_repeat) - this.processControl(field); - continue; + this.processControl(field) + continue } for (bit_name in field.value.bits) { - bit = field.value.bits[bit_name]; + bit = field.value.bits[bit_name] if (bit.auto_repeat) - this.processButton(bit); + this.processButton(bit) } } } -}; +} /** Toggle active deck and update virtual output field control mappings */ HIDController.prototype.switchDeck = function(deck) { - var packet; - var field; - var controlgroup; + var packet + var field + var controlgroup if (deck == undefined) { if (this.activeDeck == undefined) { - deck = 1; + deck = 1 } else { // This is unusable: num_decks has always minimum 4 decks // var totalDecks = engine.getValue("[Master]","num_decks"); // deck = (this.activeDeck+1) % totalDecks; - deck = this.deckSwitchMap[this.activeDeck]; + deck = this.deckSwitchMap[this.activeDeck] if (deck == undefined) - deck = 1; + deck = 1 } } - new_group = this.resolveDeckGroup(deck); - HIDDebug("Switching to deck " + deck + " group " + new_group); + new_group = this.resolveDeckGroup(deck) + HIDDebug("Switching to deck " + deck + " group " + new_group) if (this.disconnectDeck != undefined) - this.disconnectDeck(); + this.disconnectDeck() for (var packet_name in this.OutputPackets) { - packet = this.OutputPackets[packet_name]; + packet = this.OutputPackets[packet_name] + var send_packet = false for (var group_name in packet.groups) { - var group = packet.groups[group_name]; + var group = packet.groups[group_name] for (var field_name in group) { - field = group[field_name]; + field = group[field_name] if (field.type == "bitvector") { for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; + var bit = field.value.bits[bit_id] if (this.virtualDecks.indexOf(bit.mapped_group) == -1) - continue; - send_packet = true; - controlgroup = this.resolveGroup(bit.mapped_group); - engine.connectControl(controlgroup, bit.mapped_name, bit.mapped_callback, true); - engine.connectControl(new_group, bit.mapped_name, bit.mapped_callback); - HIDDebug("new group: " + new_group); - var value = engine.getValue(new_group, bit.mapped_name); - HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value + " "); - HIDDebug("wat" + deck + " " + - this.deckOutputColors[deck] + " " + this.LEDColors[this.deckOutputColors[deck]]); - if (value) { + continue + send_packet = true + controlgroup = this.resolveGroup(bit.mapped_group) + engine.connectControl(controlgroup, bit.mapped_name, bit.mapped_callback, true) + engine.connectControl(new_group, bit.mapped_name, bit.mapped_callback) + var value = engine.getValue(new_group, bit.mapped_name) + HIDDebug("BIT " + bit.group + "." + bit.name + " value " + value) + if (value) this.setOutput( bit.group, bit.name, - this.LEDColors[this.deckOutputColors[deck]], - false - ); - } else { - var dim = this.LEDColors.off; - if (this.LEDDimColors !== undefined) { - dim = this.LEDDimColors[this.deckOutputColors[deck]]; - } + this.LEDColors[this.deckOutputColors[deck]] + ) + else this.setOutput( bit.group, bit.name, - dim, - false - - ); - } + this.LEDColors.off + ) } - continue; + continue } // Only move outputs of virtual decks if (this.virtualDecks.indexOf(field.mapped_group) == -1) - continue; - send_packet = true; - controlgroup = this.resolveGroup(field.mapped_group); - engine.connectControl(controlgroup, field.mapped_name, field.mapped_callback, true); - engine.connectControl(new_group, field.mapped_name, field.mapped_callback); - var value = engine.getValue(new_group, field.mapped_name); - HIDDebug("new group: " + new_group + " " + field.mapped_name + " :" + value); - HIDDebug("some seocond thins " + deck + " " + this.deckOutputColors[deck] + " " + this.LEDColors[this.deckOutputColors[deck]]); - if (value) { + continue + send_packet = true + controlgroup = this.resolveGroup(field.mapped_group) + engine.connectControl(controlgroup, field.mapped_name, field.mapped_callback, true) + engine.connectControl(new_group, field.mapped_name, field.mapped_callback) + var value = engine.getValue(new_group, field.mapped_name) + if (value) this.setOutput( field.group, field.name, - this.LEDColors[this.deckOutputColors[deck]], - false - ); - } else { - var dim = this.LEDColors.off; - if (this.LEDDimColors !== undefined) { - dim = this.LEDDimColors[this.deckOutputColors[deck]]; - } + this.LEDColors[this.deckOutputColors[deck]] + ) + else this.setOutput( field.group, field.name, - dim, - false - ); - } + this.LEDColors.off + ) } } } - for (p in this.OutputPackets) { - HIDDebug("packets???" + p); - this.OutputPackets[p].send(); - } - this.activeDeck = deck; + this.activeDeck = deck if (this.connectDeck != undefined) - this.connectDeck(); -}; + this.connectDeck() +} /** Link a virtual HID Output to mixxx control */ HIDController.prototype.linkOutput = function(group, name, m_group, m_name, callback) { - var controlgroup; - var field = this.getOutputField(group, name); + var controlgroup + var field = this.getOutputField(group, name) if (field == undefined) { - HIDDebug("Linked output not found: " + group + "." + name); - return; + HIDDebug("Linked output not found: " + group + "." + name) + return } if (field.mapped_group != undefined) { - HIDDebug("Output already linked: " + field.mapped_group); - return; - } - controlgroup = this.resolveGroup(m_group); - field.mapped_group = m_group; - field.mapped_name = m_name; - field.mapped_callback = callback; - engine.connectControl(controlgroup, m_name, callback); + HIDDebug("Output already linked: " + field.mapped_group) + return + } + controlgroup = this.resolveGroup(m_group) + field.mapped_group = m_group + field.mapped_name = m_name + field.mapped_callback = callback + engine.connectControl(controlgroup, m_name, callback) if (engine.getValue(controlgroup, m_name)) - this.setOutput(m_group, m_name, "on"); + this.setOutput(m_group, m_name, "on") else - this.setOutput(m_group, m_name, "off"); -}; + this.setOutput(m_group, m_name, "off") +} /** Unlink a virtual HID Output from mixxx control */ HIDController.prototype.unlinkOutput = function(group, name, callback) { - var field = this.getOutputField(group, name); - var controlgroup; + var field = this.getOutputField(group, name) + var controlgroup if (field == undefined) { - HIDDebug("Unlinked output not found: " + group + "." + name); - return; + HIDDebug("Unlinked output not found: " + group + "." + name) + return } if (field.mapped_group == undefined || field.mapped_name == undefined) { - HIDDebug("Unlinked output not mapped: " + group + "." + name); - return; + HIDDebug("Unlinked output not mapped: " + group + "." + name) + return } - controlgroup = this.resolveGroup(field.mapped_group); - engine.connectControl(controlgroup, field.mapped_name, callback, true); - field.mapped_group = undefined; - field.mapped_name = undefined; - field.mapped_callback = undefined; -}; + controlgroup = this.resolveGroup(field.mapped_group) + engine.connectControl(controlgroup, field.mapped_name, callback, true) + field.mapped_group = undefined + field.mapped_name = undefined + field.mapped_callback = undefined +} /** Set output state to given value */ HIDController.prototype.setOutput = function(group, name, value, send_packet) { - var field = this.getOutputField(group, name); + var field = this.getOutputField(group, name) if (field == undefined) { - HIDDebug("setOutput: unknown field: " + group + "." + name); - return; - } - // if (value !== undefined) { - // HIDDebug("lighting: " + field.id + " 0x" + value.toString(16)); - // } else { - // HIDDebug("undefined :("); - // } - field.value = value << field.bit_offset; - field.toggle = value << field.bit_offset; + HIDDebug("setOutput: unknown field: " + group + "." + name) + return + } + field.value = value << field.bit_offset + field.toggle = value << field.bit_offset if (send_packet) - field.packet.send(); -}; + field.packet.send() +} /** Set Output to toggle between two values. Reset with setOutput(name,'off') */ HIDController.prototype.setOutputToggle = function(group, name, toggle_value) { - var field = this.getOutputField(group, name); + var field = this.getOutputField(group, name) if (field == undefined) { - HIDDebug("setOutputToggle: unknown field " + group + "." + name); - return; + HIDDebug("setOutputToggle: unknown field " + group + "." + name) + return } - field.value = toggle_value << field.bit_offset; - field.toggle = toggle_value << field.bit_offset; - field.packet.send(); -}; + field.value = toggle_value << field.bit_offset + field.toggle = toggle_value << field.bit_offset + field.packet.send() +} From e610604ec968bded37506d2cd2422d3f3cea3ef0 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 12:16:25 -0400 Subject: [PATCH 26/84] Traktor S3: pitch slider relative mode --- .../Traktor-Kontrol-S3-hid-scripts.js | 143 ++++++++++++------ 1 file changed, 93 insertions(+), 50 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6640e345323..f9bfa589191 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -15,19 +15,40 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ -/* * scratching still fucked up */ +/* * slider relative mode */ /* * wheel animations */ -/* * touch for track browse, loop control, beatjump */ +/* * touch for track browse, loop control, beatjump? */ /* * jog button */ /* * star button */ -/* * hotcue colors */ -/* * FX buttons */ /* */ /////////////////////////////////////////////////////////////////////////////////// var TraktorS3 = new function() { this.controller = new HIDController(); + + // ==== Friendly User Configuration ==== + // The pitch slider can operate either in absolute or relative mode. + // In absolute mode: + // * Moving the pitch slider works like normal + // * Mixxx will use soft-takeover + // * Pressing shift will adjust musical pitch instead of rate + // * Keylock toggles on with down-press. + // + // In relative mode: + // * The slider always moves, unless it has hit the end of the range inside Mixxx + // * No soft-takeover + // * Hold shift to move the pitch slider without adjusting the rate + // * Hold keylock and move the pitch slider to adjust musical pitch + // * keylock will still toggle on, but on release, not press. + this.pitchSliderRelativeMode = true; + + // State for relative mode + this.pitchSliderLastValue = {"deck1": -1, "deck2": -1}; + this.keylockPressed = {"deck1": false, "deck2": false}; + this.keyAdjusted = {"deck1": false, "deck2": false}; + + // State for other controls. this.shiftPressed = {"deck1": false, "deck2": false}; this.syncPressedTimer = {"deck1": 0, "deck2": 0}; this.previewPressed = {"deck1": false, "deck2": false}; @@ -39,8 +60,8 @@ var TraktorS3 = new function() { "[Channel3]": true, "[Channel4]": true, }; - - this.padModeState = {"deck1": 0, "deck2": 0}; // 0 = Hotcues Mode, 1 = Samples Mode + // 0 = Hotcues Mode, 1 = Samples Mode + this.padModeState = {"deck1": 0, "deck2": 0}; // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; @@ -286,13 +307,6 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "deck1", "quantize", 0x01, 0x80, this.quantizeHandler); this.registerInputButton(messageShort, "deck2", "quantize", 0x05, 0x01, this.quantizeHandler); - // // TODO: implement jog - // this.registerInputButton(messageShort, "deck1", "!grid", 0x02, 0x01, this.jogHandler); - // this.registerInputButton(messageShort, "deck2", "!grid", 0x05, 0x02, this.jpgHandler); - - // Unmapped: star, encoder touches, jog - // DECK ASSIGNMENT - this.controller.registerInputPacket(messageShort); this.registerInputScaler(messageLong, "deck1", "rate", 0x01, 0xFFFF, this.pitchSliderHandler); @@ -331,24 +345,23 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); - // this.registerInputScaler(messageLong, "[Sampler]", "pregain", 0x17, 0xFFFF, this.samplerPregainHandler); this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); this.controller.registerInputPacket(messageLong); // Soft takeover for all knobs - engine.softTakeover("deck1", "rate", true); - engine.softTakeover("deck2", "rate", true); - - engine.softTakeover("deck1", "pitch_adjust", true); - engine.softTakeover("deck2", "pitch_adjust", true); - - engine.softTakeover("deck1", "volume", true); - engine.softTakeover("deck2", "volume", true); - - engine.softTakeover("deck1", "pregain", true); - engine.softTakeover("deck2", "pregain", true); + for (var ch = 1; ch <= 4; ch++) { + var group = "[Channel" + ch + "]"; + if (!TraktorS3.pitchSliderRelativeMode) { + engine.softTakeover(group, "rate", true); + } + engine.softTakeover(group, "pitch_adjust", true); + engine.softTakeover(group, "volume", true); + engine.softTakeover(group, "pregain", true); + engine.softTakeover(group, "pregain", true); + engine.softTakeover("[QuickEffectRack1_" +group + "]", "super1", true); + } engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); @@ -363,11 +376,6 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter2", true); engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter3", true); - engine.softTakeover("[QuickEffectRack1_[Channel1]]", "super1", true); - engine.softTakeover("[QuickEffectRack1_[Channel2]]", "super1", true); - engine.softTakeover("[QuickEffectRack1_[Channel3]]", "super1", true); - engine.softTakeover("[QuickEffectRack1_[Channel4]]", "super1", true); - engine.softTakeover("[Master]", "crossfader", true); engine.softTakeover("[Master]", "gain", true); engine.softTakeover("[Master]", "headMix", true); @@ -376,10 +384,6 @@ TraktorS3.registerInputPackets = function() { for (var i = 1; i <= 16; ++i) { engine.softTakeover("[Sampler" + i + "]", "pregain", true); } - - // Dirty hack to set initial values in the packet parser - var data = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - TraktorS3.incomingData(data); }; TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { @@ -454,13 +458,31 @@ TraktorS3.shiftHandler = function(field) { }; TraktorS3.keylockHandler = function(field) { - if (field.value === 0) { + var activeGroup; + if (TraktorS3.pitchSliderRelativeMode) { + if (field.value) { + TraktorS3.keylockPressed[field.group] = true; + TraktorS3.keyAdjusted[field.group] = false; + return; + } + TraktorS3.keylockPressed[field.group] = false; + activeGroup = TraktorS3.deckToGroup(field.group); + if (!activeGroup) { + return; + } + if (!TraktorS3.keyAdjusted[field.group]) { + script.toggleControl(activeGroup, "keylock"); + } return; } - var activeGroup = TraktorS3.deckToGroup(field.group); + activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { return; } + + if (field.value === 0) { + return; + } script.toggleControl(activeGroup, "keylock"); }; @@ -760,12 +782,45 @@ TraktorS3.pitchSliderHandler = function(field) { if (!activeGroup) { return; } + var value = field.value / 4095; + if (TraktorS3.pitchSliderRelativeMode) { + if (TraktorS3.pitchSliderLastValue[field.group] === -1) { + TraktorS3.pitchSliderLastValue[field.group] = value; + } else { + // If shift is pressed, don't update any values. + if (TraktorS3.shiftPressed[field.group]) { + TraktorS3.pitchSliderLastValue[field.group] = value; + return; + } + + var relVal; + if (TraktorS3.keylockPressed[field.group]) { + relVal = 1.0 - engine.getParameter(activeGroup, "pitch_adjust"); + } else { + relVal = engine.getParameter(activeGroup, "rate"); + } + relVal += value - TraktorS3.pitchSliderLastValue[field.group]; + TraktorS3.pitchSliderLastValue[field.group] = value; + value = Math.max(0.0, Math.min(1.0, relVal)); + + if (TraktorS3.keylockPressed[field.group]) { + // To match the pitch change from adjusting the rate, flip the pitch + // adjustment. + engine.setParameter(activeGroup, "pitch_adjust", 1.0 - value); + TraktorS3.keyAdjusted[field.group] = true; + } else { + engine.setParameter(activeGroup, "rate", value); + } + } + return; + } + if (TraktorS3.shiftPressed[field.group]) { // To match the pitch change from adjusting the rate, flip the pitch // adjustment. - engine.setParameter(activeGroup, "pitch_adjust", 1.0 - (field.value / 4095)); + engine.setParameter(activeGroup, "pitch_adjust", 1.0 - value); } else { - engine.setParameter(activeGroup, "rate", field.value / 4095); + engine.setParameter(activeGroup, "rate", value); } }; @@ -777,14 +832,6 @@ TraktorS3.parameterHandler = function(field) { engine.setParameter(activeGroup, field.name, field.value / 4095); }; -TraktorS3.samplerPregainHandler = function(field) { - // Map sampler gain knob of all sampler together. - // Dirty hack, but the best we can do for now. - for (var i = 1; i <= 16; ++i) { - engine.setParameter("[Sampler" + i + "]", field.name, field.value / 4095); - } -}; - TraktorS3.jogTouchHandler = function(field) { var activeGroup = TraktorS3.deckToGroup(field.group); if (!activeGroup) { @@ -855,7 +902,6 @@ TraktorS3.jogHandler = function(field) { TraktorS3.wheelDeltas = function(deckNumber, value) { // When the wheel is touched, four bytes change, but only the first behaves predictably. // It looks like the wheel is 1024 ticks per revolution. - HIDDebug("VALUE: 0x" + value.toString(16)); var tickval = value & 0xFF; var timeval = value >>> 8; var prevTick = 0; @@ -1502,7 +1548,6 @@ TraktorS3.deckOutputHandler = function(value, group, key) { TraktorS3.wheelOutputHandler = function(value, group, key) { // Also call regular handler - HIDDebug("call regular????? " + value + " " + group); TraktorS3.deckOutputHandler(value, group, key); // var activeGroup = TraktorS3.deckToGroup(group); @@ -1531,13 +1576,11 @@ TraktorS3.wheelOutputHandler = function(value, group, key) { TraktorS3.colorOutputHandler = function(value, group, key) { var deck = TraktorS3.controller.resolveDeck(group); var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[deck]]; - HIDDebug("wat " + deck + " " + TraktorS3.controller.deckOutputColors[deck] + " " + ledValue); if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { ledValue += TraktorS3.LEDDimValue; } - HIDDebug("VALUE: " + ledValue); TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; From 888d513ef78032a705e51285e3d626dc7ac27816 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 14:41:08 -0400 Subject: [PATCH 27/84] Traktor S3: animate segments around wheels --- .../Traktor-Kontrol-S3-hid-scripts.js | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index f9bfa589191..53345987352 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -15,8 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ -/* * slider relative mode */ -/* * wheel animations */ +/* * wheel blink for end of track */ /* * touch for track browse, loop control, beatjump? */ /* * jog button */ /* * star button */ @@ -148,8 +147,8 @@ var TraktorS3 = new function() { this.controller.deckOutputColors = { 1: "CARROT", 2: "CARROT", - 3: "SKY", - 4: "SKY" + 3: "BLUE", + 4: "BLUE" }; this.fxLEDValue = { @@ -384,6 +383,11 @@ TraktorS3.registerInputPackets = function() { for (var i = 1; i <= 16; ++i) { engine.softTakeover("[Sampler" + i + "]", "pregain", true); } + + engine.connectControl("[Channel1]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel2]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel3]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel4]", "playposition", TraktorS3.spinnyAngleChanged); }; TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { @@ -1546,6 +1550,54 @@ TraktorS3.deckOutputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; +TraktorS3.spinnyAngleChanged = function(value, group) { + var deck = TraktorS3.resolveDeckIfActive(group); + if (deck === undefined) { + return; + } + + var deckNum = TraktorS3.controller.resolveDeck(group); + + // How many segments away from the actual angle should we light? + // (in both directions, so "2" will light up to four segments) + var dimDistance = 2.5; + // ugly hack just for testing -- assume 5 minute track for now + var elapsed = value * 6 * 60; + + var rotations = elapsed * (1 / 1.8); // 1/1.8 is rotations per second + // Calculate angle from 0-1.0 + var angle = rotations - Math.floor(rotations); + // The wheel has 8 segments + var wheelAngle = 8.0 * angle; + for (var seg = 0; seg < 8; seg++) { + var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[deckNum]]; + // We have 5 levels of brightness to choose from, including "off". + var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (brightVal <= 0) { + TraktorS3.controller.setOutput(deck, "!wheel" + seg, 0x00, false); + } else { + brightVal -= 1; + TraktorS3.controller.setOutput(deck, "!wheel" + seg, ledValue + brightVal, false); + } + } + TraktorS3.controller.OutputPackets["outputA"].send(); +}; + +// Finds the shortest distance between two angles on the wheel, assuming +// 0-8.0 angle value. +TraktorS3.wheelSegmentDistance = function(segNum, angle) { + // Account for wraparound + if (Math.abs(segNum - angle) > 4) { + if (angle > segNum) { + segNum += 8; + } else { + angle += 8; + } + } + return Math.abs(angle - segNum); +}; + TraktorS3.wheelOutputHandler = function(value, group, key) { // Also call regular handler TraktorS3.deckOutputHandler(value, group, key); From 69f35e1b124748973fa2c4314486d73da398c6d8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 22:08:17 -0400 Subject: [PATCH 28/84] Traktor S3: Major refactor to be more objecty --- .../Traktor-Kontrol-S3-hid-scripts.js | 1389 ++++++++--------- 1 file changed, 674 insertions(+), 715 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 53345987352..de642e90fa5 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -40,17 +40,10 @@ var TraktorS3 = new function() { // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. - this.pitchSliderRelativeMode = true; + this.pitchSliderRelativeMode = false; // State for relative mode - this.pitchSliderLastValue = {"deck1": -1, "deck2": -1}; - this.keylockPressed = {"deck1": false, "deck2": false}; - this.keyAdjusted = {"deck1": false, "deck2": false}; - // State for other controls. - this.shiftPressed = {"deck1": false, "deck2": false}; - this.syncPressedTimer = {"deck1": 0, "deck2": 0}; - this.previewPressed = {"deck1": false, "deck2": false}; // "5" is the "filter" button below the other 4. this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: true}; this.fxEnabledState = { @@ -59,14 +52,12 @@ var TraktorS3 = new function() { "[Channel3]": true, "[Channel4]": true, }; - // 0 = Hotcues Mode, 1 = Samples Mode - this.padModeState = {"deck1": 0, "deck2": 0}; // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; - // Active deck switches -- common-hid-packet-parser only has one active deck status per - // Controller object. + // // Active deck switches -- common-hid-packet-parser only has one active deck status per + // // Controller object. this.activeDecks = { 1: true, 2: true, @@ -74,30 +65,9 @@ var TraktorS3 = new function() { 4: false }; - // Knob encoder states (hold values between 0x0 and 0xF) - // Rotate to the right is +1 and to the left is means -1 - this.browseKnobEncoderState = {"deck1": 0, "deck2": 0}; - this.loopKnobEncoderState = {"deck1": 0, "deck2": 0}; - this.moveKnobEncoderState = {"deck1": 0, "deck2": 0}; - // Microphone button this.microphonePressedTimer = 0; // Timer to distinguish between short and long press - // Sync buttons - this.syncPressedTimer = {"deck1": 0, "deck2": 0}; // Timer to distinguish between short and long press - - // Jog wheels - // tickReceived is used to detect when the platter has stopped moving. - this.tickReceived = [false, false]; - this.lastTickVal = [0, 0]; - this.lastTickTime = [0, 0]; - this.wheelTouchInertiaTimer = { - "[Channel1]": 0, - "[Channel2]": 0, - "[Channel3]": 0, - "[Channel4]": 0 - }; - // VuMeter this.vuConnections = { "[Channel1]": {}, @@ -145,10 +115,10 @@ var TraktorS3 = new function() { this.LEDBrightValue = 0x02; this.controller.deckOutputColors = { - 1: "CARROT", - 2: "CARROT", - 3: "BLUE", - 4: "BLUE" + "[Channel1]": "CARROT", + "[Channel2]": "CARROT", + "[Channel3]": "BLUE", + "[Channel4]": "BLUE" }; this.fxLEDValue = { @@ -187,381 +157,225 @@ var TraktorS3 = new function() { this.samplerCallbacks = []; }; -TraktorS3.registerInputPackets = function() { - var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); - var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); +TraktorS3.bind = function(fn, obj) { + return function() { + return fn.apply(obj, arguments); + }; +}; - this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); +//// Deck Objects //// - this.registerInputButton(messageShort, "deck1", "!play", 0x03, 0x01, this.playHandler); - this.registerInputButton(messageShort, "deck2", "!play", 0x06, 0x02, this.playHandler); +TraktorS3.Deck = function(parentS3, deckNumber, group) { + this.parentS3 = parentS3; + this.deckNumber = deckNumber; + this.group = group; + this.activeChannel = "[Channel" + deckNumber + "]"; + this.shiftPressed = false; - this.registerInputButton(messageShort, "deck1", "!cue_default", 0x02, 0x80, this.cueHandler); - this.registerInputButton(messageShort, "deck2", "!cue_default", 0x06, 0x01, this.cueHandler); + this.pitchSliderLastValue = -1; + this.keylockPressed = false; + this.keyAdjusted = false; - this.registerInputButton(messageShort, "deck1", "!shift", 0x01, 0x01, this.shiftHandler); - this.registerInputButton(messageShort, "deck2", "!shift", 0x04, 0x02, this.shiftHandler); + // State for other controls. + this.syncPressedTimer = 0; + this.previewPressed = false; + this.padModeState = 0; - this.registerInputButton(messageShort, "deck1", "!sync", 0x02, 0x08, this.syncHandler); - this.registerInputButton(messageShort, "deck2", "!sync", 0x05, 0x10, this.syncHandler); + // Jog wheels + // tickReceived is used to detect when the platter has stopped moving. + this.tickReceived = false; + this.lastTickVal = 0; + this.lastTickTime = 0; + this.wheelTouchInertiaTimer = 0; - this.registerInputButton(messageShort, "deck1", "!keylock", 0x02, 0x10, this.keylockHandler); - this.registerInputButton(messageShort, "deck2", "!keylock", 0x05, 0x20, this.keylockHandler); + // Knob encoder states (hold values between 0x0 and 0xF) + // Rotate to the right is +1 and to the left is means -1 + this.browseKnobEncoderState = 0; + this.loopKnobEncoderState = 0; + this.moveKnobEncoderState = 0; + // Sync buttons + // Timer to distinguish between short and long press + this.syncPressedTimer = 0; +}; - this.registerInputButton(messageShort, "deck1", "!hotcues", 0x02, 0x20, this.padModeHandler); - this.registerInputButton(messageShort, "deck2", "!hotcues", 0x05, 0x40, this.padModeHandler); +TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { + if (this.deckNumber === 2) { + deckOffset = deck2Offset; + deckBitmask = deck2Bitmask; + } + TraktorS3.registerInputButton(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); +}; - this.registerInputButton(messageShort, "deck1", "!samples", 0x02, 0x40, this.padModeHandler); - this.registerInputButton(messageShort, "deck2", "!samples", 0x05, 0x80, this.padModeHandler); +TraktorS3.Deck.prototype.defineJog = function(message, group, name, deckOffset, deck2Offset, callback) { + // Jog wheels have 4 byte input + if (this.deckNumber === 2) { + deckOffset = deck2Offset; + } + message.addControl(group, name, deckOffset, "I", 0xFFFFFFFF); + message.setCallback(group, name, callback); +}; - // // Number pad buttons (Hotcues or Samplers depending on current mode) - this.registerInputButton(messageShort, "deck1", "!pad_1", 0x03, 0x02, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_2", 0x03, 0x04, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_3", 0x03, 0x08, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_4", 0x03, 0x10, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_5", 0x03, 0x20, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_6", 0x03, 0x40, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_7", 0x03, 0x80, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck1", "!pad_8", 0x04, 0x01, this.numberButtonHandler); +TraktorS3.Deck.prototype.defineScaler = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { + if (this.deckNumber === 2) { + deckOffset = deck2Offset; + deckBitmask = deck2Bitmask; + } + TraktorS3.registerInputScaler(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); +}; - this.registerInputButton(messageShort, "deck2", "!pad_1", 0x06, 0x04, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_2", 0x06, 0x08, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_3", 0x06, 0x10, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_4", 0x06, 0x20, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_5", 0x06, 0x40, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_6", 0x06, 0x80, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_7", 0x07, 0x01, this.numberButtonHandler); - this.registerInputButton(messageShort, "deck2", "!pad_8", 0x07, 0x02, this.numberButtonHandler); +TraktorS3.Deck.prototype.registerInputButtons = function(messageShort, messageLong) { + var deckFn = TraktorS3.Deck.prototype; + this.defineButton(messageShort, "!play", 0x03, 0x01, 0x06, 0x02, deckFn.playHandler); + this.defineButton(messageShort, "!cue_default", 0x02, 0x80, 0x06, 0x01, deckFn.cueHandler); + this.defineButton(messageShort, "!shift", 0x01, 0x01, 0x04, 0x02, deckFn.shiftHandler); + this.defineButton(messageShort, "!sync", 0x02, 0x08, 0x05, 0x10, deckFn.syncHandler); + this.defineButton(messageShort, "!keylock", 0x02, 0x10, 0x05, 0x20, deckFn.keylockHandler); + this.defineButton(messageShort, "!hotcues", 0x02, 0x20, 0x05, 0x40, deckFn.padModeHandler); + this.defineButton(messageShort, "!samples", 0x02, 0x40, 0x05, 0x80, deckFn.padModeHandler); - // // Headphone buttons - this.registerInputButton(messageShort, "[Channel1]", "!pfl", 0x08, 0x01, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pfl", 0x08, 0x02, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel3]", "!pfl", 0x07, 0x80, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel4]", "!pfl", 0x08, 0x04, this.headphoneHandler); + this.defineButton(messageShort, "!pad_1", 0x03, 0x02, 0x06, 0x04, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_2", 0x03, 0x04, 0x06, 0x08, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_3", 0x03, 0x08, 0x06, 0x10, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_4", 0x03, 0x10, 0x06, 0x20, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_5", 0x03, 0x20, 0x06, 0x40, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_6", 0x03, 0x40, 0x06, 0x80, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_7", 0x03, 0x80, 0x07, 0x01, deckFn.numberButtonHandler); + this.defineButton(messageShort, "!pad_8", 0x04, 0x01, 0x07, 0x02, deckFn.numberButtonHandler); - // // Track browsing // TODO: bind touch: 0x09/0x40, 0x0A/0x02 - this.registerInputButton(messageShort, "deck1", "!SelectTrack", 0x0B, 0x0F, this.selectTrackHandler); - this.registerInputButton(messageShort, "deck2", "!SelectTrack", 0x0C, 0xF0, this.selectTrackHandler); - this.registerInputButton(messageShort, "deck1", "!LoadSelectedTrack", 0x09, 0x01, this.loadTrackHandler); - this.registerInputButton(messageShort, "deck2", "!LoadSelectedTrack", 0x09, 0x08, this.loadTrackHandler); - - this.registerInputButton(messageShort, "deck1", "!PreviewTrack", 0x01, 0x08, this.previewTrackHandler); - this.registerInputButton(messageShort, "deck2", "!PreviewTrack", 0x04, 0x10, this.previewTrackHandler); - - this.registerInputButton(messageShort, "deck1", "!LibraryFocus", 0x01, 0x40, this.LibraryFocusHandler); - this.registerInputButton(messageShort, "deck2", "!LibraryFocus", 0x04, 0x80, this.LibraryFocusHandler); - - this.registerInputButton(messageShort, "deck1", "!AddTrack", 0x01, 0x20, this.cueAutoDJHandler); - this.registerInputButton(messageShort, "deck2", "!AddTrack", 0x04, 0x40, this.cueAutoDJHandler); + this.defineButton(messageShort, "!SelectTrack", 0x0B, 0x0F, 0x0C, 0xF0, deckFn.selectTrackHandler); + this.defineButton(messageShort, "!LoadSelectedTrack", 0x09, 0x01, 0x09, 0x08, deckFn.loadTrackHandler); + this.defineButton(messageShort, "!PreviewTrack", 0x01, 0x08, 0x04, 0x10, deckFn.previewTrackHandler); + this.defineButton(messageShort, "!LibraryFocus", 0x01, 0x40, 0x04, 0x80, deckFn.LibraryFocusHandler); + this.defineButton(messageShort, "!AddTrack", 0x01, 0x20, 0x04, 0x40, deckFn.cueAutoDJHandler); - // // Loop control + // Loop control // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 - this.registerInputButton(messageShort, "deck1", "!SelectLoop", 0x0C, 0x0F, this.selectLoopHandler); - this.registerInputButton(messageShort, "deck2", "!SelectLoop", 0x0D, 0xF0, this.selectLoopHandler); - this.registerInputButton(messageShort, "deck1", "!ActivateLoop", 0x09, 0x04, this.activateLoopHandler); - this.registerInputButton(messageShort, "deck2", "!ActivateLoop", 0x09, 0x20, this.activateLoopHandler); - - // // Beatjump - // TODO: bind touch detections: 0x09/0x80, 0x0A/0x04 - this.registerInputButton(messageShort, "deck1", "!SelectBeatjump", 0x0B, 0xF0, this.selectBeatjumpHandler); - this.registerInputButton(messageShort, "deck2", "!SelectBeatjump", 0x0D, 0x0F, this.selectBeatjumpHandler); - this.registerInputButton(messageShort, "deck1", "!ActivateBeatjump", 0x09, 0x02, this.activateBeatjumpHandler); - this.registerInputButton(messageShort, "deck2", "!ActivateBeatjump", 0x09, 0x10, this.activateBeatjumpHandler); - - // // There is only one button on the controller, we use to toggle quantization for all channels - // this.registerInputButton(messageShort, "[Channel1]", "!quantize", 0x06, 0x40, this.quantizeHandler); - - // // Microphone - // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); - - // // Jog wheels - this.registerInputButton(messageShort, "deck1", "!jog_touch", 0x0A, 0x10, this.jogTouchHandler); - this.registerInputButton(messageShort, "deck2", "!jog_touch", 0x0A, 0x20, this.jogTouchHandler); - this.registerInputJog(messageShort, "deck1", "!jog", 0x0E, 0xFFFFFFFF, this.jogHandler); - this.registerInputJog(messageShort, "deck2", "!jog", 0x12, 0xFFFFFFFF, this.jogHandler); - - // // FX Buttons - this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); - - this.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x48, this.fxEnableHandler); - - // // Rev / FLUX / GRID - this.registerInputButton(messageShort, "deck1", "!reverse", 0x01, 0x04, this.reverseHandler); - this.registerInputButton(messageShort, "deck2", "!reverse", 0x04, 0x08, this.reverseHandler); - - this.registerInputButton(messageShort, "deck1", "!slip_enabled", 0x01, 0x02, this.fluxHandler); - this.registerInputButton(messageShort, "deck2", "!slip_enabled", 0x04, 0x04, this.fluxHandler); - - this.registerInputButton(messageShort, "deck1", "quantize", 0x01, 0x80, this.quantizeHandler); - this.registerInputButton(messageShort, "deck2", "quantize", 0x05, 0x01, this.quantizeHandler); - - this.controller.registerInputPacket(messageShort); - - this.registerInputScaler(messageLong, "deck1", "rate", 0x01, 0xFFFF, this.pitchSliderHandler); - this.registerInputScaler(messageLong, "deck2", "rate", 0x0D, 0xFFFF, this.pitchSliderHandler); - - this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel3]", "volume", 0x03, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel4]", "volume", 0x09, 0xFFFF, this.parameterHandler); + this.defineButton(messageShort, "!SelectLoop", 0x0C, 0x0F, 0x0D, 0xF0, deckFn.selectLoopHandler); + this.defineButton(messageShort, "!ActivateLoop", 0x09, 0x04, 0x09, 0x20, deckFn.activateLoopHandler); - this.registerInputScaler(messageLong, "[Channel1]", "pregain", 0x11, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel2]", "pregain", 0x13, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel3]", "pregain", 0x0F, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel4]", "pregain", 0x15, 0xFFFF, this.parameterHandler); - - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter3", 0x25, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter2", 0x27, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter1", 0x29, 0xFFFF, this.parameterHandler); + // Rev / FLUX / GRID + this.defineButton(messageShort, "!reverse", 0x01, 0x04, 0x04, 0x08, deckFn.reverseHandler); + this.defineButton(messageShort, "!slip_enabled", 0x01, 0x02, 0x04, 0x04, deckFn.fluxHandler); + this.defineButton(messageShort, "quantize", 0x01, 0x80, 0x05, 0x01, deckFn.quantizeHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter3", 0x2B, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter2", 0x2D, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter1", 0x2F, 0xFFFF, this.parameterHandler); - - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter3", 0x1F, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter2", 0x21, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter1", 0x23, 0xFFFF, this.parameterHandler); - - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter3", 0x31, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter2", 0x33, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); - - this.registerInputScaler(messageLong, "[Channel1]", "!super", 0x39, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel2]", "!super", 0x3B, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel3]", "!super", 0x37, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel4]", "!super", 0x3D, 0xFFFF, this.superHandler); - - this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); - - this.controller.registerInputPacket(messageLong); - - // Soft takeover for all knobs - for (var ch = 1; ch <= 4; ch++) { - var group = "[Channel" + ch + "]"; - if (!TraktorS3.pitchSliderRelativeMode) { - engine.softTakeover(group, "rate", true); - } - engine.softTakeover(group, "pitch_adjust", true); - engine.softTakeover(group, "volume", true); - engine.softTakeover(group, "pregain", true); - engine.softTakeover(group, "pregain", true); - engine.softTakeover("[QuickEffectRack1_" +group + "]", "super1", true); - } - - engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); - engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); - engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); - engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter1", true); - engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter2", true); - engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter3", true); - engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter1", true); - engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter2", true); - engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter3", true); - engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter1", true); - engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter2", true); - engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter3", true); - - engine.softTakeover("[Master]", "crossfader", true); - engine.softTakeover("[Master]", "gain", true); - engine.softTakeover("[Master]", "headMix", true); - engine.softTakeover("[Master]", "headGain", true); - - for (var i = 1; i <= 16; ++i) { - engine.softTakeover("[Sampler" + i + "]", "pregain", true); - } - - engine.connectControl("[Channel1]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel2]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel3]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel4]", "playposition", TraktorS3.spinnyAngleChanged); -}; - -TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { - // Jog wheels have 4 byte input - message.addControl(group, name, offset, "I", bitmask); - message.setCallback(group, name, callback); -}; + // Beatjump + // TODO: bind touch detections: 0x09/0x80, 0x0A/0x04 + this.defineButton(messageShort, "!SelectBeatjump", 0x0B, 0xF0, 0x0D, 0x0F, deckFn.selectBeatjumpHandler); + this.defineButton(messageShort, "!ActivateBeatjump", 0x09, 0x02, 0x09, 0x10, deckFn.activateBeatjumpHandler); -TraktorS3.registerInputScaler = function(message, group, name, offset, bitmask, callback) { - message.addControl(group, name, offset, "H", bitmask); - message.setCallback(group, name, callback); -}; + // Jog wheels + this.defineButton(messageShort, "!jog_touch", 0x0A, 0x10, 0x0A, 0x20, deckFn.jogTouchHandler); + this.defineJog(messageShort, "!jog", 0x0E, 0x12, deckFn.jogHandler); -TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, callback) { - message.addControl(group, name, offset, "B", bitmask); - message.setCallback(group, name, callback); + this.defineScaler(messageLong, "rate", 0x01, 0xFFFF, 0x0D, 0xFFFF, deckFn.pitchSliderHandler); }; -TraktorS3.deckSwitchHandler = function(field) { - if (field.value === 0) { - return; - } - - if (field.group === "[Channel1]") { - TraktorS3.activeDecks[1] = true; - TraktorS3.activeDecks[3] = false; - } else if (field.group === "[Channel3]") { - TraktorS3.activeDecks[3] = true; - TraktorS3.activeDecks[1] = false; - } else if (field.group === "[Channel2]") { - TraktorS3.activeDecks[2] = true; - TraktorS3.activeDecks[4] = false; - } else if (field.group === "[Channel4]") { - TraktorS3.activeDecks[4] = true; - TraktorS3.activeDecks[2] = false; - } else { - HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); - } - engine.softTakeoverIgnoreNextValue(field.group, "rate"); - TraktorS3.lightDeck(field.group); -}; - -TraktorS3.playHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "start_stop", field.value); +TraktorS3.Deck.prototype.playHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "start_stop", field.value); } else if (field.value === 1) { - script.toggleControl(activeGroup, "play"); + script.toggleControl(this.activeChannel, "play"); } }; -TraktorS3.cueHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "cue_gotoandstop", field.value); +TraktorS3.Deck.prototype.cueHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "cue_gotoandstop", field.value); } else { - engine.setValue(activeGroup, "cue_default", field.value); + engine.setValue(this.activeChannel, "cue_default", field.value); } }; - -TraktorS3.shiftHandler = function(field) { +TraktorS3.Deck.prototype.shiftHandler = function(field) { engine.setValue("[Controls]", "touch_shift", field.value); - TraktorS3.shiftPressed[field.group] = field.value; + this.shiftPressed = field.value; TraktorS3.outputHandler(field.value, field.group, "!shift"); }; -TraktorS3.keylockHandler = function(field) { - var activeGroup; - if (TraktorS3.pitchSliderRelativeMode) { - if (field.value) { - TraktorS3.keylockPressed[field.group] = true; - TraktorS3.keyAdjusted[field.group] = false; - return; - } - TraktorS3.keylockPressed[field.group] = false; - activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (!TraktorS3.keyAdjusted[field.group]) { - script.toggleControl(activeGroup, "keylock"); - } - return; - } - activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - - if (field.value === 0) { - return; - } - script.toggleControl(activeGroup, "keylock"); -}; - -TraktorS3.syncHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "beatsync_phase", field.value); +TraktorS3.Deck.prototype.syncHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "beatsync_phase", field.value); // Light LED while pressed - TraktorS3.colorDeckOutputHandler(field.value, field.group, "sync_enabled"); + this.colorOutputHandler(field.value, "sync_enabled"); } else { if (field.value) { - if (engine.getValue(activeGroup, "sync_enabled") === 0) { - script.triggerControl(activeGroup, "beatsync"); + if (engine.getValue(this.activeChannel, "sync_enabled") === 0) { + script.triggerControl(this.activeChannel, "beatsync"); // Start timer to measure how long button is pressed - TraktorS3.syncPressedTimer[field.group] = engine.beginTimer(300, function() { - engine.setValue(activeGroup, "sync_enabled", 1); + this.syncPressedTimer = engine.beginTimer(300, function() { + engine.setValue(this.activeChannel, "sync_enabled", 1); // Reset sync button timer state if active - if (TraktorS3.syncPressedTimer[field.group] !== 0) { - TraktorS3.syncPressedTimer[field.group] = 0; + if (this.syncPressedTimer !== 0) { + this.syncPressedTimer = 0; } }, true); // Light corresponding LED when button is pressed - TraktorS3.colorDeckOutputHandler(1, field.group, "sync_enabled"); + this.colorOutputHandler(1, "sync_enabled"); } else { // Deactivate sync lock // LED is turned off by the callback handler for sync_enabled - engine.setValue(activeGroup, "sync_enabled", 0); + engine.setValue(this.activeChannel, "sync_enabled", 0); } } else { - if (TraktorS3.syncPressedTimer[field.group] !== 0) { + if (this.syncPressedTimer !== 0) { // Timer still running -> stop it and unlight LED - engine.stopTimer(TraktorS3.syncPressedTimer[field.group]); - TraktorS3.colorDeckOutputHandler(0, field.group, "sync_enabled"); + engine.stopTimer(this.syncPressedTimer); + this.colorOutputHandler(0, "sync_enabled"); } } } }; +TraktorS3.Deck.prototype.keylockHandler = function(field) { + if (TraktorS3.pitchSliderRelativeMode) { + if (field.value) { + this.keylockPressed = true; + this.keyAdjusted = false; + return; + } + this.keylockPressed = false; + if (!this.keyAdjusted) { + script.toggleControl(this.activeChannel, "keylock"); + } + return; + } + + if (field.value === 0) { + return; + } + script.toggleControl(this.activeChannel, "keylock"); +}; + // This handles when the mode buttons for the pads is pressed. -TraktorS3.padModeHandler = function(field) { +TraktorS3.Deck.prototype.padModeHandler = function(field) { if (field.value === 0) { return; } - if (TraktorS3.padModeState[field.group] === 0 && field.name === "!samples") { + if (this.padModeState === 0 && field.name === "!samples") { // If we are in hotcues mode and samples mode is activated engine.setValue("[Samplers]", "show_samplers", 1); - TraktorS3.padModeState[field.group] = 1; + this.padModeState = 1; } else if (field.name === "!hotcues") { // If we are in samples mode and hotcues mode is activated - TraktorS3.padModeState[field.group] = 0; + this.padModeState = 0; } TraktorS3.lightPads(field.group); }; -TraktorS3.numberButtonHandler = function(field) { +TraktorS3.Deck.prototype.numberButtonHandler = function(field) { var padNumber = parseInt(field.id[field.id.length - 1]); - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.padModeState[field.group] === 0) { - TraktorS3.lightHotcue(activeGroup, padNumber); + if (this.padModeState === 0) { + TraktorS3.lightHotcue(this.activeChannel, padNumber); // Hotcues mode - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "hotcue_" + padNumber + "_clear", field.value); + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "hotcue_" + padNumber + "_clear", field.value); } else { - engine.setValue(activeGroup, "hotcue_" + padNumber + "_activate", field.value); + engine.setValue(this.activeChannel, "hotcue_" + padNumber + "_activate", field.value); } } else { // Samples mode @@ -574,9 +388,9 @@ TraktorS3.numberButtonHandler = function(field) { if (!field.value) { ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); } - TraktorS3.colorDeckOutputHandler(ledValue, field.group, "!pad_" + padNumber); + this.colorOutputHandler(ledValue, "!pad_" + padNumber); - if (TraktorS3.shiftPressed[field.group]) { + if (this.shiftPressed) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { engine.setValue("[Sampler" + sampler + "]", "cue_default", field.value); @@ -598,26 +412,15 @@ TraktorS3.numberButtonHandler = function(field) { } }; -TraktorS3.headphoneHandler = function(field) { - if (field.value === 0) { - return; - } - script.toggleControl(field.group, "pfl"); -}; - -TraktorS3.selectTrackHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } +TraktorS3.Deck.prototype.selectTrackHandler = function(field) { var delta = 1; - if ((field.value + 1) % 16 === TraktorS3.browseKnobEncoderState[activeGroup]) { + if ((field.value + 1) % 16 === this.browseKnobEncoderState) { delta = -1; } - TraktorS3.browseKnobEncoderState[activeGroup] = field.value; + this.browseKnobEncoderState = field.value; // When preview is held, rotating the library encoder scrolls through the previewing track. - if (TraktorS3.previewPressed[field.group]) { + if (this.previewPressed) { var playPosition = engine.getValue("[PreviewDeck1]", "playposition"); if (delta > 0) { playPosition += 0.0125; @@ -628,338 +431,568 @@ TraktorS3.selectTrackHandler = function(field) { return; } - if (TraktorS3.shiftPressed[field.group]) { + if (this.shiftPressed) { engine.setValue("[Library]", "MoveHorizontal", delta); } else { engine.setValue("[Library]", "MoveVertical", delta); } }; -TraktorS3.loadTrackHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "eject", field.value); +TraktorS3.Deck.prototype.loadTrackHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "eject", field.value); } else { - engine.setValue(activeGroup, "LoadSelectedTrack", field.value); + engine.setValue(this.activeChannel, "LoadSelectedTrack", field.value); } }; -TraktorS3.previewTrackHandler = function(field) { +TraktorS3.Deck.prototype.previewTrackHandler = function(field) { if (field.value === 1) { - TraktorS3.previewPressed[field.group] = true; + this.previewPressed = true; engine.setValue("[PreviewDeck1]", "LoadSelectedTrackAndPlay", 1); } else { - TraktorS3.previewPressed[field.group] = false; + this.previewPressed = false; engine.setValue("[PreviewDeck1]", "play", 0); } - TraktorS3.colorDeckOutputHandler(field.value, field.group, "!PreviewTrack"); + this.colorOutputHandler(field.value, "!PreviewTrack"); }; -TraktorS3.cueAutoDJHandler = function(field) { - TraktorS3.colorDeckOutputHandler(field.value, field.group, "!AddTrack"); - - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { +TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { + this.colorOutputHandler(field.value, "!LibraryFocus"); + if (field.value === 0) { return; } - if (TraktorS3.shiftPressed[field.group]) { + script.toggleControl("[Library]", "MoveFocus"); +}; + +TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { + this.colorOutputHandler(field.value, "!AddTrack"); + + if (this.shiftPressed) { engine.setValue("[Library]", "AutoDjAddTop", field.value); } else { engine.setValue("[Library]", "AutoDjAddBottom", field.value); } }; -TraktorS3.LibraryFocusHandler = function(field) { - TraktorS3.colorDeckOutputHandler(field.value, field.group, "!LibraryFocus"); + +TraktorS3.Deck.prototype.selectLoopHandler = function(field) { + if ((field.value + 1) % 16 === this.loopKnobEncoderState) { + script.triggerControl(this.activeChannel, "loop_halve"); + } else { + script.triggerControl(this.activeChannel, "loop_double"); + } + + this.loopKnobEncoderState = field.value; +}; + +TraktorS3.Deck.prototype.activateLoopHandler = function(field) { if (field.value === 0) { return; } + var isLoopActive = engine.getValue(this.activeChannel, "loop_enabled"); - script.toggleControl("[Library]", "MoveFocus"); + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "reloop_toggle", field.value); + } else { + if (isLoopActive) { + engine.setValue(this.activeChannel, "reloop_toggle", field.value); + } else { + engine.setValue(this.activeChannel, "beatloop_activate", field.value); + } + } +}; + +TraktorS3.Deck.prototype.selectBeatjumpHandler = function(field) { + var delta = 1; + if ((field.value + 1) % 16 === this.moveKnobEncoderState) { + delta = -1; + } + + if (this.shiftPressed) { + var beatjumpSize = engine.getValue(this.activeChannel, "beatjump_size"); + if (delta > 0) { + engine.setValue(this.activeChannel, "beatjump_size", beatjumpSize * 2); + } else { + engine.setValue(this.activeChannel, "beatjump_size", beatjumpSize / 2); + } + } else { + if (delta < 0) { + script.triggerControl(this.activeChannel, "beatjump_backward"); + } else { + script.triggerControl(this.activeChannel, "beatjump_forward"); + } + } + + this.moveKnobEncoderState = field.value; }; -TraktorS3.selectLoopHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { +TraktorS3.Deck.prototype.activateBeatjumpHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "reloop_andstop", field.value); + } else { + engine.setValue(this.activeChannel, "beatlooproll_activate", field.value); + } +}; + +TraktorS3.Deck.prototype.reverseHandler = function(field) { + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "reverseroll", field.value); + } else { + engine.setValue(this.activeChannel, "reverse", field.value); + } + + TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); +}; + +TraktorS3.Deck.prototype.fluxHandler = function(field) { + if (field.value === 0) { return; } - if ((field.value + 1) % 16 === TraktorS3.loopKnobEncoderState[activeGroup]) { - script.triggerControl(activeGroup, "loop_halve"); + script.toggleControl(this.activeChannel, "slip_enabled"); +}; + +TraktorS3.Deck.prototype.quantizeHandler = function(field) { + if (field.value === 0) { + return; + } + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "beats_translate_curpos", field.value); + } else { + script.toggleControl(this.activeChannel, "quantize"); + // engine.setValue(this.activeChannel, "quantize", newState); + // TraktorS3.colorDeckOutputHandler(newState, field.group, "quantize"); + } +}; + +TraktorS3.Deck.prototype.jogTouchHandler = function(field) { + if (this.wheelTouchInertiaTimer !== 0) { + // The wheel was touched again, reset the timer. + engine.stopTimer(this.wheelTouchIntertiaTimer); + this.wheelTouchIntertiaTimer = 0; + } + if (field.value !== 0) { + engine.setValue(this.activeChannel, "scratch2_enable", true); + } else { + // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. + // Depending on how fast the platter was moving, lengthen the time we'll wait. + var scratchRate = Math.abs(engine.getValue(this.activeChannel, "scratch2")); + // Note: inertiaTime multiplier is controller-specific and should be factored out. + var inertiaTime = Math.pow(1.8, scratchRate) * 2; + if (inertiaTime < 100) { + // Just do it now. + TraktorS3.finishJogTouch(this.group); + } else { + this.wheelTouchIntertiaTimer = engine.beginTimer( + inertiaTime, "TraktorS3.finishJogTouch(\"" + this.group + "\")", true); + } + } +}; + +TraktorS3.Deck.prototype.wheelDeltas = function(value) { + // When the wheel is touched, four bytes change, but only the first behaves predictably. + // It looks like the wheel is 1024 ticks per revolution. + var tickval = value & 0xFF; + var timeval = value >>> 8; + var prevTick = 0; + var prevTime = 0; + + // Group 1 and 2 -> Array index 0 and 1 + prevTick = this.lastTickVal; + prevTime = this.lastTickTime; + this.lastTickVal = tickval; + this.lastTickTime = timeval; + + if (prevTime > timeval) { + // We looped around. Adjust current time so that subtraction works. + timeval += 0x100000; + } + var timeDelta = timeval - prevTime; + if (timeDelta === 0) { + // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. + // This is almost certainly not going to happen on this controller. + timeDelta = 1; + } + + var tickDelta = 0; + + // Very generous 8bit loop-around detection. + if (prevTick >= 200 && tickval <= 100) { + tickDelta = tickval + 256 - prevTick; + } else if (prevTick <= 100 && tickval >= 200) { + tickDelta = tickval - prevTick - 256; } else { - script.triggerControl(activeGroup, "loop_double"); + tickDelta = tickval - prevTick; } - TraktorS3.loopKnobEncoderState[activeGroup] = field.value; + return [tickDelta, timeDelta]; }; -TraktorS3.activateLoopHandler = function(field) { - if (field.value === 0) { - return; - } - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - var isLoopActive = engine.getValue(activeGroup, "loop_enabled"); +// NOTE: this a global function because it gets called by timers! +TraktorS3.finishJogTouch = function(group) { + var deck = TraktorS3.Decks[group]; + + deck.wheelTouchInertiaTimer = 0; + + // If we've received no ticks since the last call, we are stopped. + if (!deck.tickReceived) { + engine.setValue(group, "scratch2", 0.0); + engine.setValue(group, "scratch2_enable", false); + } else { + // Check again soon. + deck.wheelTouchInertiaTimer = engine.beginTimer( + 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); + } + this.tickReceived = false; +}; + +TraktorS3.Deck.prototype.jogHandler = function(field) { + this.tickReceived = true; + var deltas = this.wheelDeltas(field.value); + var tickDelta = deltas[0]; + var timeDelta = deltas[1]; + + // The scratch rate is the ratio of the wheel's speed to "regular" speed, + // which we're going to call 33.33 RPM. It's 768 ticks for a circle, and + // 400000 ticks per second, and 33.33 RPM is 1.8 seconds per rotation, so + // the standard speend is 768 / (400000 * 1.8) + var thirtyThree = 768 / 720000; + + // Our actual speed is tickDelta / timeDelta. Take the ratio of those to get the + // rate ratio. + var velocity = (tickDelta / timeDelta) / thirtyThree; + + // The Mixxx scratch code tries to do accumulation and time calculation itself. + // This controller is better, so just use its values. + if (engine.getValue(this.activeChannel, "scratch2_enable")) { + engine.setValue(this.activeChannel, "scratch2", velocity); + } else { + // If we're playing, just nudge. + if (engine.getValue(this.activeChannel, "play")) { + velocity /= 4; + } else { + velocity *= 2; + } + engine.setValue(this.activeChannel, "jog", velocity); + } +}; + +TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { + var value = field.value / 4095; + if (this.pitchSliderRelativeMode) { + if (this.pitchSliderLastValue === -1) { + this.pitchSliderLastValue = value; + } else { + // If shift is pressed, don't update any values. + if (this.shiftPressed) { + this.pitchSliderLastValue = value; + return; + } + + var relVal; + if (this.keylockPressed) { + relVal = 1.0 - engine.getParameter(this.activeChannel, "pitch_adjust"); + } else { + relVal = engine.getParameter(this.activeChannel, "rate"); + } + relVal += value - this.pitchSliderLastValue; + this.pitchSliderLastValue = value; + value = Math.max(0.0, Math.min(1.0, relVal)); + + if (this.keylockPressed) { + // To match the pitch change from adjusting the rate, flip the pitch + // adjustment. + engine.setParameter(this.activeChannel, "pitch_adjust", 1.0 - value); + this.keyAdjusted = true; + } else { + engine.setParameter(this.activeChannel, "rate", value); + } + } + return; + } + + HIDDebug("pitch slider: " + field.value + " " + value); + + if (this.shiftPressed) { + // To match the pitch change from adjusting the rate, flip the pitch + // adjustment. + engine.setParameter(this.activeChannel, "pitch_adjust", 1.0 - value); + } else { + engine.setParameter(this.activeChannel, "rate", value); + } +}; + + +//// Deck Outputs //// + +TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) { + switch (this.deckNumber) { + case 1: + packet.addOutput(this.group, name, offsetA, "B"); + break; + case 2: + packet.addOutput(this.group, name, offsetB, "B"); + break; + } +}; + +TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { + // this.defineButton(messageShort, "!play", 0x03, 0x01, 0x06, 0x02, deckFn.playHandler); + this.defineOutput(outputA, "!shift", 0x01, 0x1A); + this.defineOutput(outputA, "slip_enabled", 0x02, 0x1B); + this.defineOutput(outputA, "!reverse", 0x03, 0x1C); + this.defineOutput(outputA, "!PreviewTrack", 0x04, 0x1D); + this.defineOutput(outputA, "!AddTrack", 0x06, 0x1); + this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); + this.defineOutput(outputA, "keylock", 0x0D, 0x26); + this.defineOutput(outputA, "hotcues", 0x0E, 0x27); + this.defineOutput(outputA, "samples", 0x0F, 0x28); + this.defineOutput(outputA, "cue_indicator", 0x10, 0x29); + this.defineOutput(outputA, "play_indicator", 0x11, 0x2A); + this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); + this.defineOutput(outputA, "!pad_1", 0x12, 0x2B); + this.defineOutput(outputA, "!pad_2", 0x13, 0x2C); + this.defineOutput(outputA, "!pad_3", 0x14, 0x2D); + this.defineOutput(outputA, "!pad_4", 0x15, 0x2E); + this.defineOutput(outputA, "!pad_5", 0x16, 0x2F); + this.defineOutput(outputA, "!pad_6", 0x17, 0x30); + this.defineOutput(outputA, "!pad_7", 0x18, 0x31); + this.defineOutput(outputA, "!pad_8", 0x19, 0x32); + +}; + +TraktorS3.Deck.prototype.deckBaseColor = function() { + return TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[this.activeChannel]]; +}; + +// colorDeckOutputHandler drives lights that have the palettized multicolor lights. +TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { + var ledValue = this.deckBaseColor(); + + if (value === 1 || value === true) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); +}; + +//// Channel Objects //// + +TraktorS3.Channel = function(parentS3, parentDeck, group) { + this.parentS3 = parentS3; + this.parentDeck = parentDeck; + this.group = group; +}; + +TraktorS3.registerInputPackets = function() { + var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); + var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); + + for (var idx in TraktorS3.Decks) { + var deck = TraktorS3.Decks[idx]; + deck.registerInputButtons(messageShort, messageLong); + } + + this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); + + // // Headphone buttons + this.registerInputButton(messageShort, "[Channel1]", "!pfl", 0x08, 0x01, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel2]", "!pfl", 0x08, 0x02, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel3]", "!pfl", 0x07, 0x80, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel4]", "!pfl", 0x08, 0x04, this.headphoneHandler); + + + + + + // // There is only one button on the controller, we use to toggle quantization for all channels + // this.registerInputButton(messageShort, "[Channel1]", "!quantize", 0x06, 0x40, this.quantizeHandler); + + // // Microphone + // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); + + + + // // FX Buttons + this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); + this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); + + this.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, this.fxEnableHandler); + this.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x48, this.fxEnableHandler); + + + + this.controller.registerInputPacket(messageShort); + + + + this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel3]", "volume", 0x03, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel4]", "volume", 0x09, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[Channel1]", "pregain", 0x11, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel2]", "pregain", 0x13, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel3]", "pregain", 0x0F, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Channel4]", "pregain", 0x15, 0xFFFF, this.parameterHandler); + + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter3", 0x25, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter2", 0x27, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter1", 0x29, 0xFFFF, this.parameterHandler); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "reloop_toggle", field.value); - } else { - if (isLoopActive) { - engine.setValue(activeGroup, "reloop_toggle", field.value); - } else { - engine.setValue(activeGroup, "beatloop_activate", field.value); - } - } -}; + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter3", 0x2B, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter2", 0x2D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel2]_Effect1]", "parameter1", 0x2F, 0xFFFF, this.parameterHandler); -TraktorS3.selectBeatjumpHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - var delta = 1; - if ((field.value + 1) % 16 === TraktorS3.moveKnobEncoderState[activeGroup]) { - delta = -1; - } + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter3", 0x1F, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter2", 0x21, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel3]_Effect1]", "parameter1", 0x23, 0xFFFF, this.parameterHandler); - if (TraktorS3.shiftPressed[field.group]) { - var beatjumpSize = engine.getValue(activeGroup, "beatjump_size"); - if (delta > 0) { - engine.setValue(activeGroup, "beatjump_size", beatjumpSize * 2); - } else { - engine.setValue(activeGroup, "beatjump_size", beatjumpSize / 2); - } - } else { - if (delta < 0) { - script.triggerControl(activeGroup, "beatjump_backward"); - } else { - script.triggerControl(activeGroup, "beatjump_forward"); - } - } + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter3", 0x31, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter2", 0x33, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); - TraktorS3.moveKnobEncoderState[activeGroup] = field.value; -}; + this.registerInputScaler(messageLong, "[Channel1]", "!super", 0x39, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel2]", "!super", 0x3B, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel3]", "!super", 0x37, 0xFFFF, this.superHandler); + this.registerInputScaler(messageLong, "[Channel4]", "!super", 0x3D, 0xFFFF, this.superHandler); -TraktorS3.activateBeatjumpHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "reloop_andstop", field.value); - } else { - engine.setValue(activeGroup, "beatlooproll_activate", field.value); - } -}; + this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); -TraktorS3.microphoneHandler = function(field) { - if (field.value) { - if (TraktorS3.microphonePressedTimer === 0) { - // Start timer to measure how long button is pressed - TraktorS3.microphonePressedTimer = engine.beginTimer(300, function() { - // Reset microphone button timer status if active - if (TraktorS3.microphonePressedTimer !== 0) { - TraktorS3.microphonePressedTimer = 0; - } - }, true); - } + this.controller.registerInputPacket(messageLong); - script.toggleControl("[Microphone]", "talkover"); - } else { - // Button is released, check if timer is still running - if (TraktorS3.microphonePressedTimer !== 0) { - // short klick -> permanent activation - TraktorS3.microphonePressedTimer = 0; - } else { - engine.setValue("[Microphone]", "talkover", 0); + // Soft takeover for all knobs + for (var ch = 1; ch <= 4; ch++) { + var group = "[Channel" + ch + "]"; + if (!TraktorS3.pitchSliderRelativeMode) { + engine.softTakeover(group, "rate", true); } + engine.softTakeover(group, "pitch_adjust", true); + // engine.softTakeover(group, "volume", true); + // engine.softTakeover(group, "pregain", true); + engine.softTakeover("[QuickEffectRack1_" +group + "]", "super1", true); } -}; -TraktorS3.pitchSliderHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - var value = field.value / 4095; - if (TraktorS3.pitchSliderRelativeMode) { - if (TraktorS3.pitchSliderLastValue[field.group] === -1) { - TraktorS3.pitchSliderLastValue[field.group] = value; - } else { - // If shift is pressed, don't update any values. - if (TraktorS3.shiftPressed[field.group]) { - TraktorS3.pitchSliderLastValue[field.group] = value; - return; - } + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel2]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel3]_Effect1]", "parameter3", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter1", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter2", true); + engine.softTakeover("[EqualizerRack1_[Channel4]_Effect1]", "parameter3", true); - var relVal; - if (TraktorS3.keylockPressed[field.group]) { - relVal = 1.0 - engine.getParameter(activeGroup, "pitch_adjust"); - } else { - relVal = engine.getParameter(activeGroup, "rate"); - } - relVal += value - TraktorS3.pitchSliderLastValue[field.group]; - TraktorS3.pitchSliderLastValue[field.group] = value; - value = Math.max(0.0, Math.min(1.0, relVal)); + // engine.softTakeover("[Master]", "crossfader", true); + engine.softTakeover("[Master]", "gain", true); + engine.softTakeover("[Master]", "headMix", true); + engine.softTakeover("[Master]", "headGain", true); - if (TraktorS3.keylockPressed[field.group]) { - // To match the pitch change from adjusting the rate, flip the pitch - // adjustment. - engine.setParameter(activeGroup, "pitch_adjust", 1.0 - value); - TraktorS3.keyAdjusted[field.group] = true; - } else { - engine.setParameter(activeGroup, "rate", value); - } - } - return; + for (var i = 1; i <= 16; ++i) { + engine.softTakeover("[Sampler" + i + "]", "pregain", true); } - if (TraktorS3.shiftPressed[field.group]) { - // To match the pitch change from adjusting the rate, flip the pitch - // adjustment. - engine.setParameter(activeGroup, "pitch_adjust", 1.0 - value); - } else { - engine.setParameter(activeGroup, "rate", value); - } + engine.connectControl("[Channel1]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel2]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel3]", "playposition", TraktorS3.spinnyAngleChanged); + engine.connectControl("[Channel4]", "playposition", TraktorS3.spinnyAngleChanged); }; -TraktorS3.parameterHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - engine.setParameter(activeGroup, field.name, field.value / 4095); +TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { + // Jog wheels have 4 byte input + message.addControl(group, name, offset, "I", bitmask); + message.setCallback(group, name, callback); }; -TraktorS3.jogTouchHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { - return; - } - if (TraktorS3.wheelTouchInertiaTimer[activeGroup] !== 0) { - // The wheel was touched again, reset the timer. - engine.stopTimer(TraktorS3.wheelTouchInertiaTimer[activeGroup]); - TraktorS3.wheelTouchInertiaTimer[activeGroup] = 0; - } - if (field.value !== 0) { - var deckNumber = TraktorS3.controller.resolveDeck(activeGroup); - if (deckNumber === undefined) { - return; - } - engine.setValue(activeGroup, "scratch2_enable", true); - } else { - // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. - // Depending on how fast the platter was moving, lengthen the time we'll wait. - var scratchRate = Math.abs(engine.getValue(activeGroup, "scratch2")); - // Note: inertiaTime multiplier is controller-specific and should be factored out. - var inertiaTime = Math.pow(1.8, scratchRate) * 2; - if (inertiaTime < 100) { - // Just do it now. - TraktorS3.finishJogTouch(activeGroup); - } else { - TraktorS3.wheelTouchInertiaTimer[activeGroup] = engine.beginTimer( - inertiaTime, "TraktorS3.finishJogTouch(\"" + activeGroup + "\")", true); - } - } +TraktorS3.registerInputScaler = function(message, group, name, offset, bitmask, callback) { + message.addControl(group, name, offset, "H", bitmask); + message.setCallback(group, name, callback); +}; + +TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, callback) { + message.addControl(group, name, offset, "B", bitmask); + message.setCallback(group, name, callback); }; -TraktorS3.jogHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (!activeGroup) { +TraktorS3.deckSwitchHandler = function(field) { + if (field.value === 0) { return; } - TraktorS3.tickReceived[activeGroup] = true; - var deltas = TraktorS3.wheelDeltas(activeGroup, field.value); - var tickDelta = deltas[0]; - var timeDelta = deltas[1]; - - // The scratch rate is the ratio of the wheel's speed to "regular" speed, - // which we're going to call 33.33 RPM. It's 768 ticks for a circle, and - // 400000 ticks per second, and 33.33 RPM is 1.8 seconds per rotation, so - // the standard speend is 768 / (400000 * 1.8) - var thirtyThree = 768 / 720000; - - // Our actual speed is tickDelta / timeDelta. Take the ratio of those to get the - // rate ratio. - var velocity = (tickDelta / timeDelta) / thirtyThree; - // The Mixxx scratch code tries to do accumulation and time calculation itself. - // This controller is better, so just use its values. - if (engine.getValue(activeGroup, "scratch2_enable")) { - engine.setValue(activeGroup, "scratch2", velocity); + if (field.group === "[Channel1]") { + TraktorS3.activeDecks[1] = true; + TraktorS3.activeDecks[3] = false; + } else if (field.group === "[Channel3]") { + TraktorS3.activeDecks[3] = true; + TraktorS3.activeDecks[1] = false; + } else if (field.group === "[Channel2]") { + TraktorS3.activeDecks[2] = true; + TraktorS3.activeDecks[4] = false; + } else if (field.group === "[Channel4]") { + TraktorS3.activeDecks[4] = true; + TraktorS3.activeDecks[2] = false; } else { - // If we're playing, just nudge. - if (engine.getValue(activeGroup, "play")) { - velocity /= 4; - } else { - velocity *= 2; - } - engine.setValue(activeGroup, "jog", velocity); + HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); } + engine.softTakeoverIgnoreNextValue(field.group, "rate"); + TraktorS3.lightDeck(field.group); }; -TraktorS3.wheelDeltas = function(deckNumber, value) { - // When the wheel is touched, four bytes change, but only the first behaves predictably. - // It looks like the wheel is 1024 ticks per revolution. - var tickval = value & 0xFF; - var timeval = value >>> 8; - var prevTick = 0; - var prevTime = 0; - - // Group 1 and 2 -> Array index 0 and 1 - prevTick = this.lastTickVal[deckNumber - 1]; - prevTime = this.lastTickTime[deckNumber - 1]; - this.lastTickVal[deckNumber - 1] = tickval; - this.lastTickTime[deckNumber - 1] = timeval; - - if (prevTime > timeval) { - // We looped around. Adjust current time so that subtraction works. - timeval += 0x100000; - } - var timeDelta = timeval - prevTime; - if (timeDelta === 0) { - // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. - // This is almost certainly not going to happen on this controller. - timeDelta = 1; +TraktorS3.headphoneHandler = function(field) { + if (field.value === 0) { + return; } + script.toggleControl(field.group, "pfl"); +}; - var tickDelta = 0; - - // Very generous 8bit loop-around detection. - if (prevTick >= 200 && tickval <= 100) { - tickDelta = tickval + 256 - prevTick; - } else if (prevTick <= 100 && tickval >= 200) { - tickDelta = tickval - prevTick - 256; - } else { - tickDelta = tickval - prevTick; - } +// TraktorS3.microphoneHandler = function(field) { +// if (field.value) { +// if (TraktorS3.microphonePressedTimer === 0) { +// // Start timer to measure how long button is pressed +// TraktorS3.microphonePressedTimer = engine.beginTimer(300, function() { +// // Reset microphone button timer status if active +// if (TraktorS3.microphonePressedTimer !== 0) { +// TraktorS3.microphonePressedTimer = 0; +// } +// }, true); +// } + +// script.toggleControl("[Microphone]", "talkover"); +// } else { +// // Button is released, check if timer is still running +// if (TraktorS3.microphonePressedTimer !== 0) { +// // short klick -> permanent activation +// TraktorS3.microphonePressedTimer = 0; +// } else { +// engine.setValue("[Microphone]", "talkover", 0); +// } +// } +// }; - return [tickDelta, timeDelta]; -}; -TraktorS3.finishJogTouch = function(group) { - TraktorS3.wheelTouchInertiaTimer[group] = 0; - // If we've received no ticks since the last call, we are stopped. - if (!TraktorS3.tickReceived[group]) { - engine.setValue(group, "scratch2", 0.0); - engine.setValue(group, "scratch2_enable", false); - } else { - // Check again soon. - TraktorS3.wheelTouchInertiaTimer[group] = engine.beginTimer( - 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); - } - TraktorS3.tickReceived[group] = false; +TraktorS3.parameterHandler = function(field) { + engine.setParameter(field.group, field.name, field.value / 4095); }; TraktorS3.superHandler = function(field) { // The super knob drives all the supers! - // engine.setParameter(activeGroup, field.name, field.value / 4095); var group = field.group; var value = field.value / 4095.; engine.setParameter("[QuickEffectRack1_" + group + "]", "super1", value); @@ -993,8 +1026,8 @@ TraktorS3.fxEnableHandler = function(field) { if (field.value === 0) { return; } - TraktorS3.fxEnabledState[field.group] = !TraktorS3.fxEnabledState[field.group]; - TraktorS3.colorOutputHandler(TraktorS3.fxEnabledState[field.group], field.group, "!fxEnabled"); + this.fxEnabledState = !this.fxEnabledState; + TraktorS3.colorOutputHandler(this.fxEnabledState, field.group, "!fxEnabled"); TraktorS3.toggleFX(); }; @@ -1018,46 +1051,21 @@ TraktorS3.toggleFX = function() { }; -TraktorS3.reverseHandler = function(field) { - var activeGroup = TraktorS3.deckToGroup(field.group); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "reverseroll", field.value); - } else { - engine.setValue(activeGroup, "reverse", field.value); - } - - TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); -}; - -TraktorS3.fluxHandler = function(field) { - if (field.value === 0) { - return; - } - var activeGroup = TraktorS3.deckToGroup(field.group); - script.toggleControl(activeGroup, "slip_enabled"); -}; -TraktorS3.quantizeHandler = function(field) { - if (field.value === 0) { - return; - } - var activeGroup = TraktorS3.deckToGroup(field.group); - if (TraktorS3.shiftPressed[field.group]) { - engine.setValue(activeGroup, "beats_translate_curpos", field.value); - } else { - script.toggleControl(activeGroup, "quantize"); - // engine.setValue(activeGroup, "quantize", newState); - // TraktorS3.colorDeckOutputHandler(newState, field.group, "quantize"); - } -}; +TraktorS3.init = function(_id) { + this.Decks = { + "deck1": new TraktorS3.Deck(this, 1, "deck1"), + "deck2": new TraktorS3.Deck(this, 2, "deck2"), + }; -// function sleepFor(sleepDuration) { -// var now = new Date().getTime(); -// while (new Date().getTime() < now + sleepDuration) { /* do nothing */ } -// } + this.Channels = { + "[Channel1]": new TraktorS3.Channel(this, this.Decks.deck1, "[Channel1]"), + "[Channel2]": new TraktorS3.Channel(this, this.Decks.deck2, "[Channel2]"), + "[Channel3]": new TraktorS3.Channel(this, this.Decks.deck1, "[Channel3]"), + "[Channel4]": new TraktorS3.Channel(this, this.Decks.deck2, "[Channel4]") + }; -TraktorS3.init = function(_id) { TraktorS3.registerInputPackets(); TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); @@ -1130,68 +1138,19 @@ TraktorS3.debugLights = function() { TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); + var outputB = new HIDPacket("outputB", 0x81); - outputA.addOutput("deck1", "!shift", 0x01, "B"); - outputA.addOutput("deck2", "!shift", 0x1A, "B"); - - outputA.addOutput("deck1", "slip_enabled", 0x02, "B"); - outputA.addOutput("deck2", "slip_enabled", 0x1B, "B"); - - outputA.addOutput("deck1", "!reverse", 0x03, "B"); - outputA.addOutput("deck2", "!reverse", 0x1C, "B"); - - outputA.addOutput("deck1", "!PreviewTrack", 0x04, "B"); - outputA.addOutput("deck2", "!PreviewTrack", 0x1D, "B"); - - outputA.addOutput("deck1", "!PreviewTrack", 0x04, "B"); - outputA.addOutput("deck2", "!PreviewTrack", 0x1D, "B"); - - outputA.addOutput("deck1", "!AddTrack", 0x06, "B"); - outputA.addOutput("deck2", "!AddTrack", 0x1F, "B"); - - outputA.addOutput("deck1", "!LibraryFocus", 0x07, "B"); - outputA.addOutput("deck2", "!LibraryFocus", 0x20, "B"); + for (var idx in TraktorS3.Decks) { + var deck = TraktorS3.Decks[idx]; + deck.registerOutputs(outputA, outputB); + } outputA.addOutput("[Channel1]", "!deck_A", 0x0A, "B"); outputA.addOutput("[Channel2]", "!deck_B", 0x23, "B"); outputA.addOutput("[Channel3]", "!deck_C", 0x0B, "B"); outputA.addOutput("[Channel4]", "!deck_D", 0x24, "B"); - outputA.addOutput("deck1", "keylock", 0x0D, "B"); - outputA.addOutput("deck2", "keylock", 0x26, "B"); - outputA.addOutput("deck1", "hotcues", 0x0E, "B"); - outputA.addOutput("deck2", "hotcues", 0x27, "B"); - - outputA.addOutput("deck1", "samples", 0x0F, "B"); - outputA.addOutput("deck2", "samples", 0x28, "B"); - - outputA.addOutput("deck1", "cue_indicator", 0x10, "B"); - outputA.addOutput("deck2", "cue_indicator", 0x29, "B"); - - outputA.addOutput("deck1", "play_indicator", 0x11, "B"); - outputA.addOutput("deck2", "play_indicator", 0x2A, "B"); - - outputA.addOutput("deck1", "sync_enabled", 0x0C, "B"); - outputA.addOutput("deck2", "sync_enabled", 0x25, "B"); - - outputA.addOutput("deck1", "!pad_1", 0x12, "B"); - outputA.addOutput("deck1", "!pad_2", 0x13, "B"); - outputA.addOutput("deck1", "!pad_3", 0x14, "B"); - outputA.addOutput("deck1", "!pad_4", 0x15, "B"); - outputA.addOutput("deck1", "!pad_5", 0x16, "B"); - outputA.addOutput("deck1", "!pad_6", 0x17, "B"); - outputA.addOutput("deck1", "!pad_7", 0x18, "B"); - outputA.addOutput("deck1", "!pad_8", 0x19, "B"); - - outputA.addOutput("deck2", "!pad_1", 0x2B, "B"); - outputA.addOutput("deck2", "!pad_2", 0x2C, "B"); - outputA.addOutput("deck2", "!pad_3", 0x2D, "B"); - outputA.addOutput("deck2", "!pad_4", 0x2E, "B"); - outputA.addOutput("deck2", "!pad_5", 0x2F, "B"); - outputA.addOutput("deck2", "!pad_6", 0x30, "B"); - outputA.addOutput("deck2", "!pad_7", 0x31, "B"); - outputA.addOutput("deck2", "!pad_8", 0x32, "B"); outputA.addOutput("[Channel1]", "pfl", 0x39, "B"); outputA.addOutput("[Channel2]", "pfl", 0x3A, "B"); @@ -1229,7 +1188,6 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputA); - var outputB = new HIDPacket("outputB", 0x81); var VuOffsets = { "[Channel3]": 0x01, @@ -1393,11 +1351,12 @@ TraktorS3.lightHotcue = function(group, number) { }; TraktorS3.lightPads = function(group) { - var activeGroup = TraktorS3.deckToGroup(group); + // var activeGroup = TraktorS3.deckToGroup(group); + var deck = TraktorS3.Decks[group]; // Samplers - if (TraktorS3.padModeState[group] === 1) { - TraktorS3.colorDeckOutputHandler(0, activeGroup, "hotcues"); - TraktorS3.colorDeckOutputHandler(1, activeGroup, "samples"); + if (deck.padModeState === 1) { + TraktorS3.colorDeckOutputHandler(0, deck.activeChannel, "hotcues"); + TraktorS3.colorDeckOutputHandler(1, deck.activeChannel, "samples"); for (var i = 1; i <= 8; i++) { var idx = i; if (group === "deck2") { @@ -1407,10 +1366,10 @@ TraktorS3.lightPads = function(group) { TraktorS3.colorDeckOutputHandler(loaded, group, "!pad_" + idx); } } else { - TraktorS3.colorDeckOutputHandler(1, activeGroup, "hotcues"); - TraktorS3.colorDeckOutputHandler(0, activeGroup, "samples"); + TraktorS3.colorDeckOutputHandler(1, deck.activeChannel, "hotcues"); + TraktorS3.colorDeckOutputHandler(0, deck.activeChannel, "samples"); for (i = 1; i <= 8; ++i) { - TraktorS3.lightHotcue(activeGroup, i); + TraktorS3.lightHotcue(deck.activeChannel, i); } } }; From a5def7e9e7af17a72b07990f7c1259d916472941 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 9 Aug 2020 22:32:30 -0400 Subject: [PATCH 29/84] Traktor S3: refactor continues, outputs starting to work again --- .../Traktor-Kontrol-S3-hid-scripts.js | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index de642e90fa5..7436297b2ae 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -727,6 +727,7 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { //// Deck Outputs //// TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) { + HIDDebug("eh? " + this.group + " " + name); switch (this.deckNumber) { case 1: packet.addOutput(this.group, name, offsetA, "B"); @@ -738,7 +739,7 @@ TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) }; TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { - // this.defineButton(messageShort, "!play", 0x03, 0x01, 0x06, 0x02, deckFn.playHandler); + HIDDebug("REGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"); this.defineOutput(outputA, "!shift", 0x01, 0x1A); this.defineOutput(outputA, "slip_enabled", 0x02, 0x1B); this.defineOutput(outputA, "!reverse", 0x03, 0x1C); @@ -751,6 +752,7 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "cue_indicator", 0x10, 0x29); this.defineOutput(outputA, "play_indicator", 0x11, 0x2A); this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); + this.defineOutput(outputA, "!pad_1", 0x12, 0x2B); this.defineOutput(outputA, "!pad_2", 0x13, 0x2C); this.defineOutput(outputA, "!pad_3", 0x14, 0x2D); @@ -760,6 +762,33 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!pad_7", 0x18, 0x31); this.defineOutput(outputA, "!pad_8", 0x19, 0x32); + this.defineOutput(outputA, "addTrack", 0x03, 0x2A); + this.defineOutput(outputA, "quantize", 0x08, 0x21); + + var wheelOffsets = [0x43, 0x4A]; + for (var i = 0; i < 8; i++) { + this.defineOutput(outputA, "!" + "wheel" + i, wheelOffsets[0] + i, wheelOffsets[1] + i); + } +}; + +TraktorS3.Deck.prototype.defineLink = function(key, callback) { + switch (this.deckNumber) { + case 1: + HIDDebug("DEfINing LINK"); + TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); + engine.connectControl("[Channel3]", key, callback); + break; + case 2: + TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); + engine.connectControl("[Channel4]", key, callback); + break; + } +}; + +TraktorS3.Deck.prototype.linkOutputs = function() { + var deckFn = TraktorS3.Deck.prototype; + HIDDebug("LINK " + deckFn.wheelOutputHandler); + this.defineLink("play_indicator", TraktorS3.bind(deckFn.wheelOutputHandler, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { @@ -778,6 +807,30 @@ TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; +TraktorS3.Deck.prototype.wheelOutputHandler = function(value, group, _key) { + // Also call regular handler + // TraktorS3.deckOutputHandler(value, group, key); + HIDDebug("WHEEL"); + + if (group !== this.activeChannel) { + return; + } + + var sendPacket = !TraktorS3.batchingOutputs; + TraktorS3.batchingOutputs = true; + for (var i = 0; i < 8; i++) { + this.colorOutputHandler(value, "!wheel" + i); + } + if (sendPacket) { + for (var packetName in TraktorS3.controller.OutputPackets) { + TraktorS3.controller.OutputPackets[packetName].send(); + } + // Only unset batchingOutputs if it wasn't already true when we + // entered this function. + TraktorS3.batchingOutputs = false; + } +}; + //// Channel Objects //// TraktorS3.Channel = function(parentS3, parentDeck, group) { @@ -1150,19 +1203,11 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Channel3]", "!deck_C", 0x0B, "B"); outputA.addOutput("[Channel4]", "!deck_D", 0x24, "B"); - - outputA.addOutput("[Channel1]", "pfl", 0x39, "B"); outputA.addOutput("[Channel2]", "pfl", 0x3A, "B"); outputA.addOutput("[Channel3]", "pfl", 0x38, "B"); outputA.addOutput("[Channel4]", "pfl", 0x3B, "B"); - outputA.addOutput("deck1", "addTrack", 0x03, "B"); - outputA.addOutput("deck2", "addTrack", 0x2A, "B"); - - outputA.addOutput("deck1", "quantize", 0x08, "B"); - outputA.addOutput("deck2", "quantize", 0x21, "B"); - // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); outputA.addOutput("[ChannelX]", "!fxButton1", 0x3C, "B"); @@ -1176,16 +1221,6 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Channel2]", "!fxEnabled", 0x36, "B"); outputA.addOutput("[Channel4]", "!fxEnabled", 0x37, "B"); - var wheelOffsets = { - "deck1": 0x43, - "deck2": 0x4A - }; - for (var ch in wheelOffsets) { - for (var i = 0; i < 8; i++) { - outputA.addOutput(ch, "!" + "wheel" + i, wheelOffsets[ch] + i, "B"); - } - } - this.controller.registerOutputPacket(outputA); @@ -1195,8 +1230,8 @@ TraktorS3.registerOutputPackets = function() { "[Channel2]": 0x1F, "[Channel4]": 0x2E }; - for (ch in VuOffsets) { - for (i = 0; i < 14; i++) { + for (var ch in VuOffsets) { + for (var i = 0; i < 14; i++) { outputB.addOutput(ch, "!" + "VuMeter" + i, VuOffsets[ch] + i, "B"); } } @@ -1220,8 +1255,14 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputB); + + for (idx in TraktorS3.Decks) { + deck = TraktorS3.Decks[idx]; + deck.linkOutputs(outputA, outputB); + } + // Play is always green - TraktorS3.linkDeckOutputs("play_indicator", this.wheelOutputHandler); + // TraktorS3.linkDeckOutputs("play_indicator", this.wheelOutputHandler); TraktorS3.linkDeckOutputs("cue_indicator", this.colorDeckOutputHandler); TraktorS3.linkDeckOutputs("sync_enabled", this.colorDeckOutputHandler); TraktorS3.linkDeckOutputs("keylock", this.colorDeckOutputHandler); @@ -1557,31 +1598,7 @@ TraktorS3.wheelSegmentDistance = function(segNum, angle) { return Math.abs(angle - segNum); }; -TraktorS3.wheelOutputHandler = function(value, group, key) { - // Also call regular handler - TraktorS3.deckOutputHandler(value, group, key); - - // var activeGroup = TraktorS3.deckToGroup(group); - var deck = TraktorS3.controller.resolveDeck(group); - if (deck === undefined) { - return; - } - var sendPacket = !TraktorS3.batchingOutputs; - TraktorS3.batchingOutputs = true; - for (var i = 0; i < 8; i++) { - // HIDDebug("wheel! " + ledValue.toString(16)); - TraktorS3.colorDeckOutputHandler(value, group, "!wheel" + i); - } - if (sendPacket) { - for (var packetName in TraktorS3.controller.OutputPackets) { - TraktorS3.controller.OutputPackets[packetName].send(); - } - // Only unset batchingOutputs if it wasn't already true when we - // entered this function. - TraktorS3.batchingOutputs = false; - } -}; // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.colorOutputHandler = function(value, group, key) { @@ -1644,7 +1661,7 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { } // If we are in samples modes light corresponding LED - if (TraktorS3.padModeState[deck] === 1) { + if (this.padModeState === 1) { if (key === "play" && engine.getValue(group, "track_loaded")) { if (value) { // Green light on play From 483dabd4a2e5ce5f18098e743ed16a419b1629d8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Mon, 10 Aug 2020 23:39:59 -0400 Subject: [PATCH 30/84] Traktor S3: finish refactor! --- .../Traktor-Kontrol-S3-hid-scripts.js | 886 ++++++++---------- 1 file changed, 384 insertions(+), 502 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 7436297b2ae..50cecd0db55 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1,6 +1,7 @@ /////////////////////////////////////////////////////////////////////////////////// // JSHint configuration // /////////////////////////////////////////////////////////////////////////////////// +/* global controller */ /* global HIDDebug */ /* global HIDPacket */ /* global HIDController */ @@ -15,6 +16,8 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ +/* * finish objecting refactor, esp channel-specific stuff +/* * and light /* * wheel blink for end of track */ /* * touch for track browse, loop control, beatjump? */ /* * jog button */ @@ -22,7 +25,6 @@ /* */ /////////////////////////////////////////////////////////////////////////////////// - var TraktorS3 = new function() { this.controller = new HIDController(); @@ -40,7 +42,7 @@ var TraktorS3 = new function() { // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. - this.pitchSliderRelativeMode = false; + this.pitchSliderRelativeMode = true; // State for relative mode @@ -56,15 +58,6 @@ var TraktorS3 = new function() { // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; - // // Active deck switches -- common-hid-packet-parser only has one active deck status per - // // Controller object. - this.activeDecks = { - 1: true, - 2: true, - 3: false, - 4: false - }; - // Microphone button this.microphonePressedTimer = 0; // Timer to distinguish between short and long press @@ -114,7 +107,7 @@ var TraktorS3 = new function() { this.LEDDimValue = 0x00; this.LEDBrightValue = 0x02; - this.controller.deckOutputColors = { + this.deckOutputColors = { "[Channel1]": "CARROT", "[Channel2]": "CARROT", "[Channel3]": "BLUE", @@ -165,23 +158,22 @@ TraktorS3.bind = function(fn, obj) { //// Deck Objects //// -TraktorS3.Deck = function(parentS3, deckNumber, group) { - this.parentS3 = parentS3; +TraktorS3.Deck = function(deckNumber, group) { this.deckNumber = deckNumber; this.group = group; this.activeChannel = "[Channel" + deckNumber + "]"; this.shiftPressed = false; + // State for pitch slider relative mode this.pitchSliderLastValue = -1; this.keylockPressed = false; this.keyAdjusted = false; - // State for other controls. this.syncPressedTimer = 0; this.previewPressed = false; this.padModeState = 0; - // Jog wheels + // Jog wheel state // tickReceived is used to detect when the platter has stopped moving. this.tickReceived = false; this.lastTickVal = 0; @@ -193,9 +185,6 @@ TraktorS3.Deck = function(parentS3, deckNumber, group) { this.browseKnobEncoderState = 0; this.loopKnobEncoderState = 0; this.moveKnobEncoderState = 0; - // Sync buttons - // Timer to distinguish between short and long press - this.syncPressedTimer = 0; }; TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { @@ -223,7 +212,7 @@ TraktorS3.Deck.prototype.defineScaler = function(msg, name, deckOffset, deckBitm TraktorS3.registerInputScaler(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); }; -TraktorS3.Deck.prototype.registerInputButtons = function(messageShort, messageLong) { +TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { var deckFn = TraktorS3.Deck.prototype; this.defineButton(messageShort, "!play", 0x03, 0x01, 0x06, 0x02, deckFn.playHandler); this.defineButton(messageShort, "!cue_default", 0x02, 0x80, 0x06, 0x01, deckFn.cueHandler); @@ -247,7 +236,7 @@ TraktorS3.Deck.prototype.registerInputButtons = function(messageShort, messageLo this.defineButton(messageShort, "!LoadSelectedTrack", 0x09, 0x01, 0x09, 0x08, deckFn.loadTrackHandler); this.defineButton(messageShort, "!PreviewTrack", 0x01, 0x08, 0x04, 0x10, deckFn.previewTrackHandler); this.defineButton(messageShort, "!LibraryFocus", 0x01, 0x40, 0x04, 0x80, deckFn.LibraryFocusHandler); - this.defineButton(messageShort, "!AddTrack", 0x01, 0x20, 0x04, 0x40, deckFn.cueAutoDJHandler); + this.defineButton(messageShort, "!QueueAutoDJ", 0x01, 0x20, 0x04, 0x40, deckFn.cueAutoDJHandler); // Loop control // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 @@ -367,49 +356,53 @@ TraktorS3.Deck.prototype.padModeHandler = function(field) { TraktorS3.Deck.prototype.numberButtonHandler = function(field) { var padNumber = parseInt(field.id[field.id.length - 1]); + var action = ""; + // Hotcues mode if (this.padModeState === 0) { TraktorS3.lightHotcue(this.activeChannel, padNumber); - - // Hotcues mode if (this.shiftPressed) { - engine.setValue(this.activeChannel, "hotcue_" + padNumber + "_clear", field.value); + action = "_clear"; } else { - engine.setValue(this.activeChannel, "hotcue_" + padNumber + "_activate", field.value); - } - } else { - // Samples mode - var sampler = padNumber; - if (field.group === "deck2") { - sampler += 8; + action = "_activate"; } + engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); + return; + } - var ledValue = field.value; - if (!field.value) { - ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); - } - this.colorOutputHandler(ledValue, "!pad_" + padNumber); + // Samples mode + var sampler = padNumber; + if (field.group === "deck2") { + sampler += 8; + } - if (this.shiftPressed) { - var playing = engine.getValue("[Sampler" + sampler + "]", "play"); - if (playing) { - engine.setValue("[Sampler" + sampler + "]", "cue_default", field.value); - } else { - engine.setValue("[Sampler" + sampler + "]", "eject", field.value); - } + var ledValue = field.value; + if (!field.value) { + ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); + } + this.colorOutputHandler(ledValue, "!pad_" + padNumber); + + if (this.shiftPressed) { + var playing = engine.getValue("[Sampler" + sampler + "]", "play"); + if (playing) { + action = "cue_default"; } else { - var loaded = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); - if (loaded) { - if (field.value) { - engine.setValue("[Sampler" + sampler + "]", "cue_gotoandplay", field.value); - } else { - engine.setValue("[Sampler" + sampler + "]", "stop", 1); - } - } else { - engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); - } + action = "eject"; } + engine.setValue("[Sampler" + sampler + "]", action, field.value); + return; } + var loaded = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); + if (loaded) { + if (field.value) { + action = "cue_gotoandplay"; + } else { + action = "stop"; + } + engine.setValue("[Sampler" + sampler + "]", action, 1); + return; + } + engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); }; TraktorS3.Deck.prototype.selectTrackHandler = function(field) { @@ -467,7 +460,7 @@ TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { }; TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { - this.colorOutputHandler(field.value, "!AddTrack"); + this.colorOutputHandler(field.value, "!QueueAutoDJ"); if (this.shiftPressed) { engine.setValue("[Library]", "AutoDjAddTop", field.value); @@ -680,7 +673,7 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { var value = field.value / 4095; - if (this.pitchSliderRelativeMode) { + if (TraktorS3.pitchSliderRelativeMode) { if (this.pitchSliderLastValue === -1) { this.pitchSliderLastValue = value; } else { @@ -712,8 +705,6 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { return; } - HIDDebug("pitch slider: " + field.value + " " + value); - if (this.shiftPressed) { // To match the pitch change from adjusting the rate, flip the pitch // adjustment. @@ -727,7 +718,6 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { //// Deck Outputs //// TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) { - HIDDebug("eh? " + this.group + " " + name); switch (this.deckNumber) { case 1: packet.addOutput(this.group, name, offsetA, "B"); @@ -739,19 +729,19 @@ TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) }; TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { - HIDDebug("REGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"); this.defineOutput(outputA, "!shift", 0x01, 0x1A); this.defineOutput(outputA, "slip_enabled", 0x02, 0x1B); this.defineOutput(outputA, "!reverse", 0x03, 0x1C); this.defineOutput(outputA, "!PreviewTrack", 0x04, 0x1D); - this.defineOutput(outputA, "!AddTrack", 0x06, 0x1); + this.defineOutput(outputA, "!QueueAutoDJ", 0x06, 0x1F); this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); + this.defineOutput(outputA, "quantize", 0x08, 0x21); + this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); this.defineOutput(outputA, "keylock", 0x0D, 0x26); this.defineOutput(outputA, "hotcues", 0x0E, 0x27); this.defineOutput(outputA, "samples", 0x0F, 0x28); this.defineOutput(outputA, "cue_indicator", 0x10, 0x29); this.defineOutput(outputA, "play_indicator", 0x11, 0x2A); - this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); this.defineOutput(outputA, "!pad_1", 0x12, 0x2B); this.defineOutput(outputA, "!pad_2", 0x13, 0x2C); @@ -762,10 +752,9 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!pad_7", 0x18, 0x31); this.defineOutput(outputA, "!pad_8", 0x19, 0x32); - this.defineOutput(outputA, "addTrack", 0x03, 0x2A); - this.defineOutput(outputA, "quantize", 0x08, 0x21); + // this.defineOutput(outputA, "addTrack", 0x03, 0x2A); - var wheelOffsets = [0x43, 0x4A]; + var wheelOffsets = [0x43, 0x4B]; for (var i = 0; i < 8; i++) { this.defineOutput(outputA, "!" + "wheel" + i, wheelOffsets[0] + i, wheelOffsets[1] + i); } @@ -774,7 +763,6 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { TraktorS3.Deck.prototype.defineLink = function(key, callback) { switch (this.deckNumber) { case 1: - HIDDebug("DEfINing LINK"); TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); engine.connectControl("[Channel3]", key, callback); break; @@ -787,15 +775,65 @@ TraktorS3.Deck.prototype.defineLink = function(key, callback) { TraktorS3.Deck.prototype.linkOutputs = function() { var deckFn = TraktorS3.Deck.prototype; - HIDDebug("LINK " + deckFn.wheelOutputHandler); this.defineLink("play_indicator", TraktorS3.bind(deckFn.wheelOutputHandler, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { - return TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[this.activeChannel]]; + return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.activeChannel]]; +}; + +TraktorS3.Deck.prototype.colorForHotcue = function(num) { + var colorCode = engine.getValue(this.activeChannel, "hotcue_" + num + "_color"); + return TraktorS3.colorMap.getValueForNearestColor(colorCode); +}; + +TraktorS3.Deck.prototype.lightHotcue = function(number) { + var active = engine.getValue(this.activeChannel, "hotcue_" + number + "_enabled"); + var ledValue = TraktorS3.controller.LEDColors.WHITE; + if (active) { + ledValue = this.colorForHotcue(number); + ledValue += TraktorS3.LEDDimValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput(this.group, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); +}; + +TraktorS3.Deck.prototype.lightPads = function() { + // Samplers + if (this.padModeState === 1) { + this.colorOutputHandler(0, "hotcues"); + this.colorOutputHandler(1, "samples"); + for (var i = 1; i <= 8; i++) { + var idx = i; + if (this.group === "deck2") { + idx += 8; + } + var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); + this.colorOutputHandler(loaded, "!pad_" + i); + } + } else { + this.colorOutputHandler(1, "hotcues"); + this.colorOutputHandler(0, "samples"); + for (i = 1; i <= 8; ++i) { + this.lightHotcue(i); + } + } }; -// colorDeckOutputHandler drives lights that have the palettized multicolor lights. +// deckOutputHandler drives lights that only have one color. +TraktorS3.Deck.prototype.outputHandler = function(value, key) { + // incoming value will be a channel, we have to resolve back to + // deck. + var ledValue = 0x20; + if (value === 1 || value === true) { + // On value + ledValue = 0x77; + } + TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); +}; + +// colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { var ledValue = this.deckBaseColor(); @@ -807,10 +845,9 @@ TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.Deck.prototype.wheelOutputHandler = function(value, group, _key) { +TraktorS3.Deck.prototype.wheelOutputHandler = function(value, group, key) { // Also call regular handler - // TraktorS3.deckOutputHandler(value, group, key); - HIDDebug("WHEEL"); + this.outputHandler(value, key); if (group !== this.activeChannel) { return; @@ -831,12 +868,100 @@ TraktorS3.Deck.prototype.wheelOutputHandler = function(value, group, _key) { } }; +///////////////////////// //// Channel Objects //// - -TraktorS3.Channel = function(parentS3, parentDeck, group) { - this.parentS3 = parentS3; +//// +//// Channels don't have much state, just the fx button state. +TraktorS3.Channel = function(parentDeck, group) { this.parentDeck = parentDeck; this.group = group; + this.fxEnabledState = false; +}; + +TraktorS3.Channel.prototype.fxEnableHandler = function(field) { + if (field.value === 0) { + return; + } + + this.fxEnabledState = !this.fxEnabledState; + TraktorS3.colorOutputHandler(this.fxEnabledState, this.group, "!fxEnabled"); + TraktorS3.toggleFX(); +}; + +// Finds the shortest distance between two angles on the wheel, assuming +// 0-8.0 angle value. +TraktorS3.wheelSegmentDistance = function(segNum, angle) { + // Account for wraparound + if (Math.abs(segNum - angle) > 4) { + if (angle > segNum) { + segNum += 8; + } else { + angle += 8; + } + } + return Math.abs(angle - segNum); +}; + +TraktorS3.Channel.prototype.spinnyAngleChanged = function(value) { + if (this.parentDeck.activeChannel !== this.group) { + return; + } + + // How many segments away from the actual angle should we light? + // (in both directions, so "2" will light up to four segments) + var dimDistance = 2.5; + // ugly hack just for testing -- assume 5 minute track for now + var elapsed = value * 6 * 60; + + var rotations = elapsed * (1 / 1.8); // 1/1.8 is rotations per second + // Calculate angle from 0-1.0 + var angle = rotations - Math.floor(rotations); + // The wheel has 8 segments + var wheelAngle = 8.0 * angle; + for (var seg = 0; seg < 8; seg++) { + var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; + // We have 5 levels of brightness to choose from, including "off". + var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (brightVal <= 0) { + TraktorS3.controller.setOutput(this.parentDeck, "!wheel" + seg, 0x00, false); + } else { + brightVal -= 1; + TraktorS3.controller.setOutput(this.parentDeck, "!wheel" + seg, ledValue + brightVal, false); + } + } + TraktorS3.controller.OutputPackets["outputA"].send(); +}; + +// colorOutputHandler drives lights that have the palettized multicolor lights. +TraktorS3.Channel.prototype.colorOutputHandler = function(value, key) { + var ledValue = TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; + if (value === 1 || value === true) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); +}; + +TraktorS3.deckSwitchHandler = function(field) { + if (field.value === 0) { + return; + } + + if (field.group === "[Channel1]") { + TraktorS3.Decks["deck1"].activeChannel = field.group; + } else if (field.group === "[Channel3]") { + TraktorS3.Decks["deck1"].activeChannel = field.group; + } else if (field.group === "[Channel2]") { + TraktorS3.Decks["deck2"].activeChannel = field.group; + } else if (field.group === "[Channel4]") { + TraktorS3.Decks["deck2"].activeChannel = field.group; + } else { + HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); + } + engine.softTakeoverIgnoreNextValue(field.group, "rate"); + TraktorS3.lightDeck(field.group); }; TraktorS3.registerInputPackets = function() { @@ -845,7 +970,7 @@ TraktorS3.registerInputPackets = function() { for (var idx in TraktorS3.Decks) { var deck = TraktorS3.Decks[idx]; - deck.registerInputButtons(messageShort, messageLong); + deck.registerInputs(messageShort, messageLong); } this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); @@ -853,42 +978,34 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); - // // Headphone buttons - this.registerInputButton(messageShort, "[Channel1]", "!pfl", 0x08, 0x01, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel2]", "!pfl", 0x08, 0x02, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel3]", "!pfl", 0x07, 0x80, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel4]", "!pfl", 0x08, 0x04, this.headphoneHandler); - - - - - - // // There is only one button on the controller, we use to toggle quantization for all channels - // this.registerInputButton(messageShort, "[Channel1]", "!quantize", 0x06, 0x40, this.quantizeHandler); - - // // Microphone - // this.registerInputButton(messageShort, "[Microphone]", "!talkover", 0x06, 0x80, this.microphoneHandler); - - - - // // FX Buttons + var group = "[Channel1]"; + TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x10, + TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); + group = "[Channel2]"; + TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x08, + TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); + group = "[Channel3]"; + TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x20, + TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); + group = "[Channel4]"; + TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x40, + TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); + + // Headphone buttons + this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.parameterHandler); + this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.parameterHandler); + this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.parameterHandler); + this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.parameterHandler); + + // FX Buttons this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); - this.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, this.fxEnableHandler); - this.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x48, this.fxEnableHandler); - - - this.controller.registerInputPacket(messageShort); - - this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel3]", "volume", 0x03, 0xFFFF, this.parameterHandler); @@ -899,6 +1016,7 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[Channel3]", "pregain", 0x0F, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel4]", "pregain", 0x15, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter3", 0x25, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter2", 0x27, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter1", 0x29, 0xFFFF, this.parameterHandler); @@ -929,7 +1047,7 @@ TraktorS3.registerInputPackets = function() { // Soft takeover for all knobs for (var ch = 1; ch <= 4; ch++) { - var group = "[Channel" + ch + "]"; + group = "[Channel" + ch + "]"; if (!TraktorS3.pitchSliderRelativeMode) { engine.softTakeover(group, "rate", true); } @@ -961,10 +1079,15 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover("[Sampler" + i + "]", "pregain", true); } - engine.connectControl("[Channel1]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel2]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel3]", "playposition", TraktorS3.spinnyAngleChanged); - engine.connectControl("[Channel4]", "playposition", TraktorS3.spinnyAngleChanged); + for (ch in TraktorS3.Channels) { + var chanob = TraktorS3.Channels[ch]; + engine.connectControl(ch, "playposition", + TraktorS3.bind(TraktorS3.Channel.prototype.spinnyAngleChanged, chanob)); + } + + // Dirty hack to set initial values in the packet parser + var data = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + TraktorS3.incomingData(data); }; TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { @@ -983,28 +1106,8 @@ TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, message.setCallback(group, name, callback); }; -TraktorS3.deckSwitchHandler = function(field) { - if (field.value === 0) { - return; - } - - if (field.group === "[Channel1]") { - TraktorS3.activeDecks[1] = true; - TraktorS3.activeDecks[3] = false; - } else if (field.group === "[Channel3]") { - TraktorS3.activeDecks[3] = true; - TraktorS3.activeDecks[1] = false; - } else if (field.group === "[Channel2]") { - TraktorS3.activeDecks[2] = true; - TraktorS3.activeDecks[4] = false; - } else if (field.group === "[Channel4]") { - TraktorS3.activeDecks[4] = true; - TraktorS3.activeDecks[2] = false; - } else { - HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); - } - engine.softTakeoverIgnoreNextValue(field.group, "rate"); - TraktorS3.lightDeck(field.group); +TraktorS3.parameterHandler = function(field) { + engine.setParameter(field.group, field.name, field.value / 4095); }; TraktorS3.headphoneHandler = function(field) { @@ -1014,36 +1117,6 @@ TraktorS3.headphoneHandler = function(field) { script.toggleControl(field.group, "pfl"); }; -// TraktorS3.microphoneHandler = function(field) { -// if (field.value) { -// if (TraktorS3.microphonePressedTimer === 0) { -// // Start timer to measure how long button is pressed -// TraktorS3.microphonePressedTimer = engine.beginTimer(300, function() { -// // Reset microphone button timer status if active -// if (TraktorS3.microphonePressedTimer !== 0) { -// TraktorS3.microphonePressedTimer = 0; -// } -// }, true); -// } - -// script.toggleControl("[Microphone]", "talkover"); -// } else { -// // Button is released, check if timer is still running -// if (TraktorS3.microphonePressedTimer !== 0) { -// // short klick -> permanent activation -// TraktorS3.microphonePressedTimer = 0; -// } else { -// engine.setValue("[Microphone]", "talkover", 0); -// } -// } -// }; - - - -TraktorS3.parameterHandler = function(field) { - engine.setParameter(field.group, field.name, field.value / 4095); -}; - TraktorS3.superHandler = function(field) { // The super knob drives all the supers! var group = field.group; @@ -1071,16 +1144,6 @@ TraktorS3.fxHandler = function(field) { ledValue += TraktorS3.LEDDimValue; } TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); - // TraktorS3.colorOutputHandler(TraktorS3.fxButtonState[fxNumber], field.group, "!fxButton" + fxNumber); - TraktorS3.toggleFX(); -}; - -TraktorS3.fxEnableHandler = function(field) { - if (field.value === 0) { - return; - } - this.fxEnabledState = !this.fxEnabledState; - TraktorS3.colorOutputHandler(this.fxEnabledState, field.group, "!fxEnabled"); TraktorS3.toggleFX(); }; @@ -1089,7 +1152,7 @@ TraktorS3.toggleFX = function() { // the fx is ON, we turn the effect ON. We turn OFF no matter what. for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { for (var ch = 1; ch <= 4; ch++) { - var group = "[Channel" + ch + "]"; + var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; var fxKey = "group_[Channel1]_enable"; if (fxNumber === 5) { @@ -1097,98 +1160,13 @@ TraktorS3.toggleFX = function() { fxKey = "enabled"; } - var newState = TraktorS3.fxEnabledState[group] && TraktorS3.fxButtonState[fxNumber]; + var newState = channel.fxEnabledState && TraktorS3.fxButtonState[fxNumber]; engine.setValue(fxGroup, fxKey, newState); } } }; - - -TraktorS3.init = function(_id) { - this.Decks = { - "deck1": new TraktorS3.Deck(this, 1, "deck1"), - "deck2": new TraktorS3.Deck(this, 2, "deck2"), - }; - - this.Channels = { - "[Channel1]": new TraktorS3.Channel(this, this.Decks.deck1, "[Channel1]"), - "[Channel2]": new TraktorS3.Channel(this, this.Decks.deck2, "[Channel2]"), - "[Channel3]": new TraktorS3.Channel(this, this.Decks.deck1, "[Channel3]"), - "[Channel4]": new TraktorS3.Channel(this, this.Decks.deck2, "[Channel4]") - }; - - TraktorS3.registerInputPackets(); - TraktorS3.registerOutputPackets(); - HIDDebug("TraktorS3: Init done!"); - - TraktorS3.lightDeck("[Channel3]", false); - TraktorS3.lightDeck("[Channel4]", false); - TraktorS3.lightDeck("[Channel1]", false); - TraktorS3.lightDeck("[Channel2]", true); - - // TraktorS3.debugLights(); -}; - -TraktorS3.debugLights = function() { - // Call this if you want to just send raw packets to the controller (good for figuring out what - // bytes do what). - var dataStrings = [ - " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + - "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF 00 " + - "FF 00 00 00 FF 00 FF 2C 00 FF 2C 7C FF 00 00 00 " + - "00 00 00 00 7E 0C 0C FF 0C FF FF FF FF FF FF FF " + - "FF FF 40 FF FF FF 00 FF FF 2E FF 00 00 FF 00 00 " + - "00 00 FF 00 ", - " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 00", - ]; - - var data = [Object(), Object()]; - - - for (var i = 0; i < data.length; i++) { - var ok = true; - var splitted = dataStrings[i].split(/\s+/); - HIDDebug("i " + i + " " + splitted); - data[i].length = splitted.length; - for (var j = 0; j < splitted.length; j++) { - var byteStr = splitted[j]; - if (byteStr.length === 0) { - continue; - } - if (byteStr.length !== 2) { - ok = false; - HIDDebug("not two characters?? " + byteStr); - } - var b = parseInt(byteStr, 16); - if (b < 0 || b > 255) { - ok = false; - HIDDebug("number out of range: " + byteStr + " " + b); - } - data[i][j] = b; - } - // if (i === 0) { - // for (k = 0; k < data[0].length; k++) { - // data[0][k] = 0x30; - // } - // } - // for (d = 0; d < 8; d++) { - // data[0][0x11+d] = (d+1) * 4 + 2; - // } - // for (d = 0; d < 8; d++) { - // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; - // } - if (ok) { - TraktorS3.controller.send(data[i], data[i].length, 0x80 + i); - } - } -}; - TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); var outputB = new HIDPacket("outputB", 0x81); @@ -1223,7 +1201,6 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputA); - var VuOffsets = { "[Channel3]": 0x01, "[Channel1]": 0x10, @@ -1255,27 +1232,24 @@ TraktorS3.registerOutputPackets = function() { this.controller.registerOutputPacket(outputB); - for (idx in TraktorS3.Decks) { deck = TraktorS3.Decks[idx]; deck.linkOutputs(outputA, outputB); } // Play is always green - // TraktorS3.linkDeckOutputs("play_indicator", this.wheelOutputHandler); - TraktorS3.linkDeckOutputs("cue_indicator", this.colorDeckOutputHandler); - TraktorS3.linkDeckOutputs("sync_enabled", this.colorDeckOutputHandler); - TraktorS3.linkDeckOutputs("keylock", this.colorDeckOutputHandler); - TraktorS3.linkDeckOutputs("slip_enabled", this.deckOutputHandler); - TraktorS3.linkDeckOutputs("quantize", this.colorDeckOutputHandler); + // TraktorS3.linkDeckOutputs("play_indicator", TraktorS3.Deck.prototype.wheelOutputHandler); + TraktorS3.linkDeckOutputs("cue_indicator", TraktorS3.Deck.prototype.colorOutputHandler); + TraktorS3.linkDeckOutputs("sync_enabled", TraktorS3.Deck.prototype.colorOutputHandler); + TraktorS3.linkDeckOutputs("keylock", TraktorS3.Deck.prototype.colorOutputHandler); + TraktorS3.linkDeckOutputs("slip_enabled", TraktorS3.Deck.prototype.outputHandler); + TraktorS3.linkDeckOutputs("quantize", TraktorS3.Deck.prototype.colorOutputHandler); TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel2]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel3]", "pfl", this.outputHandler); TraktorS3.linkChannelOutput("[Channel4]", "pfl", this.outputHandler); - // this.linkOutput("[Microphone]", "talkover", this.outputHandler); - // Channel VuMeters for (i = 1; i <= 4; i++) { this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.channelVuMeterHandler); @@ -1288,6 +1262,13 @@ TraktorS3.registerOutputPackets = function() { this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); + // for (i = 1; i <= 8; ++i) { + // TraktorS3.linkDeckOutputs("pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // TraktorS3.linkDeckOutputs("pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); + // } + // Sampler callbacks for (i = 1; i <= 16; ++i) { this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); @@ -1300,62 +1281,24 @@ TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; +TraktorS3.wrapOutput = function(callback) { + return function(value, _group, name) { + callback(value, name); + }; +}; + TraktorS3.linkDeckOutputs = function(key, callback) { // Linking outputs is a little tricky because the library doesn't quite do what I want. But this // method works. - TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); - engine.connectControl("[Channel3]", key, callback); - TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); - engine.connectControl("[Channel4]", key, callback); - - // TraktorS3.controller.linkOutput("[Channel1]", key, "deck1", key, callback); - // TraktorS3.controller.linkOutput("[Channel2]", key, "deck2", key, callback); - // TraktorS3.controller.linkOutput("[Channel3]", key, "deck1", key, callback); - // TraktorS3.controller.linkOutput("[Channel4]", key, "deck2", key, callback); -}; - -TraktorS3.deckToGroup = function(deck) { - if (deck === "deck1") { - if (this.activeDecks[1]) { - return "[Channel1]"; - } else if (this.activeDecks[3]) { - return "[Channel3]"; - } - } else if (deck === "deck2") { - if (this.activeDecks[2]) { - return "[Channel2]"; - } else if (this.activeDecks[4]) { - return "[Channel4]"; - } - } - // Return original value, it's already a group - return deck; -}; + var deck = TraktorS3.Decks["deck1"]; -TraktorS3.resolveDeckIfActive = function(group) { - var controller = TraktorS3.controller; - if (group === "[Channel1]") { - if (controller.left_deck_C) { - return undefined; - } - return "deck1"; - } else if (group === "[Channel3]") { - if (!controller.left_deck_C) { - return undefined; - } - return "deck1"; - } else if (group === "[Channel2]") { - if (controller.right_deck_D) { - return undefined; - } - return "deck2"; - } else if (group === "[Channel4]") { - if (!controller.right_deck_D) { - return undefined; - } - return "deck2"; - } - return undefined; + var wrapped = TraktorS3.wrapOutput(TraktorS3.bind(callback, deck)); + TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, wrapped); + engine.connectControl("[Channel3]", key, wrapped); + deck = TraktorS3.Decks["deck2"]; + wrapped = TraktorS3.wrapOutput(TraktorS3.bind(callback, deck)); + TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, wrapped); + engine.connectControl("[Channel4]", key, wrapped); }; TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { @@ -1365,60 +1308,20 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { if (field.name[0] === "!") { continue; } + HIDDebug("field to light?: " + field.group + " " + field.name); if (field.mapped_callback !== undefined) { var value = engine.getValue(coGroupName, field.name); + HIDDebug("has callback " + value); field.mapped_callback(value, coGroupName, field.name); } // No callback, no light! } }; -TraktorS3.colorForHotcue = function(group, num) { - var colorCode = engine.getValue(group, "hotcue_" + num + "_color"); - return TraktorS3.colorMap.getValueForNearestColor(colorCode); -}; - -TraktorS3.lightHotcue = function(group, number) { - var deck = TraktorS3.resolveDeckIfActive(group); - var active = engine.getValue(group, "hotcue_" + number + "_enabled"); - var ledValue = TraktorS3.controller.LEDColors.WHITE; - if (active) { - ledValue = TraktorS3.colorForHotcue(group, number); - ledValue += TraktorS3.LEDDimValue; - } else { - ledValue += TraktorS3.LEDDimValue; - } - TraktorS3.controller.setOutput(deck, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); -}; - -TraktorS3.lightPads = function(group) { - // var activeGroup = TraktorS3.deckToGroup(group); - var deck = TraktorS3.Decks[group]; - // Samplers - if (deck.padModeState === 1) { - TraktorS3.colorDeckOutputHandler(0, deck.activeChannel, "hotcues"); - TraktorS3.colorDeckOutputHandler(1, deck.activeChannel, "samples"); - for (var i = 1; i <= 8; i++) { - var idx = i; - if (group === "deck2") { - idx += 8; - } - var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); - TraktorS3.colorDeckOutputHandler(loaded, group, "!pad_" + idx); - } - } else { - TraktorS3.colorDeckOutputHandler(1, deck.activeChannel, "hotcues"); - TraktorS3.colorDeckOutputHandler(0, deck.activeChannel, "samples"); - for (i = 1; i <= 8; ++i) { - TraktorS3.lightHotcue(deck.activeChannel, i); - } - } -}; - TraktorS3.lightFX = function() { - for (var ch = 1; ch <= 4; ch++) { - var group = "[Channel" + ch + "]"; - TraktorS3.colorOutputHandler(TraktorS3.fxEnabledState[group], group, "!fxEnabled"); + for (var ch in TraktorS3.Channels) { + var chanob = TraktorS3.Channels[ch]; + chanob.colorOutputHandler(chanob.fxEnabledState, "!fxEnabled"); } for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; @@ -1437,6 +1340,7 @@ TraktorS3.lightDeck = function(group, sendPackets) { } // Freeze the lights while we do this update so we don't spam HID. TraktorS3.batchingOutputs = true; + HIDDebug("lighting" + group); for (var packetName in this.controller.OutputPackets) { var packet = this.controller.OutputPackets[packetName]; var deckGroupName = "deck1"; @@ -1444,35 +1348,39 @@ TraktorS3.lightDeck = function(group, sendPackets) { deckGroupName = "deck2"; } + var deck = TraktorS3.Decks[deckGroupName]; + + HIDDebug("light deck group " + deckGroupName); TraktorS3.lightGroup(packet, deckGroupName, group); + HIDDebug("light channel group " + group); TraktorS3.lightGroup(packet, group, group); - TraktorS3.lightPads(deckGroupName); + deck.lightPads(); // These lights are different because either they aren't associated with a CO, or // there are two buttons that point to the same CO. - TraktorS3.deckOutputHandler(0, deckGroupName, "!shift"); - TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!PreviewTrack"); - TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!AddTrack"); - TraktorS3.colorDeckOutputHandler(0, deckGroupName, "!LibraryFocus"); - TraktorS3.deckOutputHandler(0, deckGroupName, "!reverse"); + deck.outputHandler(0, "!shift"); + deck.colorOutputHandler(0, "!PreviewTrack"); + deck.colorOutputHandler(0, "!QueueAutoDJ"); + deck.colorOutputHandler(0, "!LibraryFocus"); + deck.outputHandler(0, "!reverse"); } TraktorS3.lightFX(); // Selected deck lights var ctrlr = TraktorS3.controller; if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[ctrlr.deckOutputColors[3]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[ctrlr.deckOutputColors[1]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[ctrlr.deckOutputColors[4]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[ctrlr.deckOutputColors[2]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); } TraktorS3.batchingOutputs = false; @@ -1538,125 +1446,31 @@ TraktorS3.outputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; -// deckOutputHandler drives lights that only have one color. -TraktorS3.deckOutputHandler = function(value, group, key) { - // incoming value will be a channel, we have to resolve back to - // deck. - var ledValue = 0x20; - if (value === 1 || value === true) { - // On value - ledValue = 0x77; - } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); -}; - -TraktorS3.spinnyAngleChanged = function(value, group) { - var deck = TraktorS3.resolveDeckIfActive(group); - if (deck === undefined) { - return; - } - - var deckNum = TraktorS3.controller.resolveDeck(group); - - // How many segments away from the actual angle should we light? - // (in both directions, so "2" will light up to four segments) - var dimDistance = 2.5; - // ugly hack just for testing -- assume 5 minute track for now - var elapsed = value * 6 * 60; - - var rotations = elapsed * (1 / 1.8); // 1/1.8 is rotations per second - // Calculate angle from 0-1.0 - var angle = rotations - Math.floor(rotations); - // The wheel has 8 segments - var wheelAngle = 8.0 * angle; - for (var seg = 0; seg < 8; seg++) { - var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[deckNum]]; - // We have 5 levels of brightness to choose from, including "off". - var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); - if (brightVal <= 0) { - TraktorS3.controller.setOutput(deck, "!wheel" + seg, 0x00, false); - } else { - brightVal -= 1; - TraktorS3.controller.setOutput(deck, "!wheel" + seg, ledValue + brightVal, false); - } - } - TraktorS3.controller.OutputPackets["outputA"].send(); -}; - -// Finds the shortest distance between two angles on the wheel, assuming -// 0-8.0 angle value. -TraktorS3.wheelSegmentDistance = function(segNum, angle) { - // Account for wraparound - if (Math.abs(segNum - angle) > 4) { - if (angle > segNum) { - segNum += 8; - } else { - angle += 8; - } - } - return Math.abs(angle - segNum); -}; - - - -// colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorOutputHandler = function(value, group, key) { - var deck = TraktorS3.controller.resolveDeck(group); - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[deck]]; - if (value === 1 || value === true) { - ledValue += TraktorS3.LEDBrightValue; - } else { - ledValue += TraktorS3.LEDDimValue; +TraktorS3.resolveSampler = function(group) { + if (group === undefined) { + return undefined; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); -}; -// colorDeckOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.colorDeckOutputHandler = function(value, group, key) { - // Reject update if it's for a specific channel that's not selected. - var updatedDeck = TraktorS3.controller.resolveDeck(group); - if (updatedDeck !== undefined) { - if (!TraktorS3.activeDecks[updatedDeck]) { - return; - } - if (updatedDeck === "1" || updatedDeck === "3") { - group = "deck1"; - } else if (updatedDeck === "2" || updatedDeck === "4") { - group = "deck2"; - } - } else { - // update was for a deck in general. Pick the color based on the appropriate channel - // for this deck. - updatedDeck = TraktorS3.controller.resolveDeck(TraktorS3.deckToGroup(group)); - } - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.controller.deckOutputColors[updatedDeck]]; + var result = group.match(script.samplerRegEx); - if (value === 1 || value === true) { - ledValue += TraktorS3.LEDBrightValue; - } else { - ledValue += TraktorS3.LEDDimValue; + if (result === null) { + return undefined; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); -}; -TraktorS3.hotcueOutputHandler = function(value, group, key) { - // Light button LED only when we are in hotcue mode - if (TraktorS3.padModeState[group] === 0) { - TraktorS3.colorDeckOutputHandler(value, group, key); - } + // Return sample number + return result[1]; }; TraktorS3.samplesOutputHandler = function(value, group, key) { // Sampler 1-8 -> Channel1 // Samples 9-16 -> Channel2 var sampler = TraktorS3.resolveSampler(group); - var deck = "deck1"; + var deck = TraktorS3.Decks["deck1"]; var num = sampler; if (sampler === undefined) { return; } else if (sampler > 8 && sampler < 17) { - deck = "deck2"; + deck = TraktorS3.Decks["deck2"]; num = sampler - 8; } @@ -1665,36 +1479,83 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { if (key === "play" && engine.getValue(group, "track_loaded")) { if (value) { // Green light on play - TraktorS3.colorDeckOutputHandler(0x9E, deck, "!pad_" + num); + deck.colorOutputHandler(0x9E, "!pad_" + num); } else { // Reset LED to full white light - TraktorS3.colorDeckOutputHandler(1, deck, "!pad_" + num); + deck.colorDeckOutputHandler(1, "!pad_" + num); } } else if (key === "track_loaded") { - TraktorS3.colorDeckOutputHandler(value, deck, "!pad_" + num); + deck.colorDeckOutputHandler(value, "!pad_" + num); } } }; -TraktorS3.resolveSampler = function(group) { - if (group === undefined) { - return undefined; +TraktorS3.messageCallback = function(_packet, data) { + for (var name in data) { + if (Object.prototype.hasOwnProperty.call(data, name)) { + TraktorS3.controller.processButton(data[name]); + } } +}; - var result = group.match(script.samplerRegEx); +TraktorS3.incomingData = function(data, length) { + TraktorS3.controller.parsePacket(data, length); +}; - if (result === null) { - return undefined; - } +TraktorS3.debugLights = function() { + // Call this if you want to just send raw packets to the controller (good for figuring out what + // bytes do what). + var dataStrings = [ + " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + + "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF FF " + + "FF 00 00 00 FF FF FF 2C 00 FF 2C 7C FF 00 00 00 " + + "00 00 00 00 7E 0C FF FF 0C FF FF FF FF FF FF FF " + + "FF FF 40 FF FF FF 00 FF FF 2E FF 00 00 FF 00 00 " + + "00 00 FF 00 ", + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 00", + ]; - // Return sample number - return result[1]; -}; + var data = [Object(), Object()]; -TraktorS3.messageCallback = function(_packet, data) { - for (var name in data) { - if (Object.prototype.hasOwnProperty.call(data, name)) { - TraktorS3.controller.processButton(data[name]); + + for (var i = 0; i < data.length; i++) { + var ok = true; + var splitted = dataStrings[i].split(/\s+/); + HIDDebug("i " + i + " " + splitted); + data[i].length = splitted.length; + for (var j = 0; j < splitted.length; j++) { + var byteStr = splitted[j]; + if (byteStr.length === 0) { + continue; + } + if (byteStr.length !== 2) { + ok = false; + HIDDebug("not two characters?? " + byteStr); + } + var b = parseInt(byteStr, 16); + if (b < 0 || b > 255) { + ok = false; + HIDDebug("number out of range: " + byteStr + " " + b); + } + data[i][j] = b; + } + // if (i === 0) { + // for (k = 0; k < data[0].length; k++) { + // data[0][k] = 0x30; + // } + // } + // for (d = 0; d < 8; d++) { + // data[0][0x11+d] = (d+1) * 4 + 2; + // } + // for (d = 0; d < 8; d++) { + // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; + // } + if (ok) { + controller.send(data[i], data[i].length, 0x80 + i); } } }; @@ -1728,12 +1589,33 @@ TraktorS3.shutdown = function() { } data[i][j] = parseInt(byteStr, 16); } - TraktorS3.controller.send(data[i], data[i].length, 0x80 + i); + controller.send(data[i], data[i].length, 0x80 + i); } HIDDebug("TraktorS3: Shutdown done!"); }; -TraktorS3.incomingData = function(data, length) { - TraktorS3.controller.parsePacket(data, length); +TraktorS3.init = function(_id) { + this.Decks = { + "deck1": new TraktorS3.Deck(1, "deck1"), + "deck2": new TraktorS3.Deck(2, "deck2"), + }; + + this.Channels = { + "[Channel1]": new TraktorS3.Channel(this.Decks["deck1"], "[Channel1]"), + "[Channel2]": new TraktorS3.Channel(this.Decks["deck2"], "[Channel2]"), + "[Channel3]": new TraktorS3.Channel(this.Decks["deck1"], "[Channel3]"), + "[Channel4]": new TraktorS3.Channel(this.Decks["deck2"], "[Channel4]") + }; + + TraktorS3.registerInputPackets(); + TraktorS3.registerOutputPackets(); + HIDDebug("TraktorS3: Init done!"); + + TraktorS3.lightDeck("[Channel3]", false); + TraktorS3.lightDeck("[Channel4]", false); + TraktorS3.lightDeck("[Channel1]", false); + TraktorS3.lightDeck("[Channel2]", true); + + // TraktorS3.debugLights(); }; From 2b76faa4d7ac7768b5ca9fd266bf3ff05c1a4bf8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 11 Aug 2020 00:06:38 -0400 Subject: [PATCH 31/84] Traktor S3: Fix some bugs. Pretty stable again --- .../Traktor-Kontrol-S3-hid-scripts.js | 259 +++++++++--------- 1 file changed, 123 insertions(+), 136 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 50cecd0db55..6137b5125ad 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -195,13 +195,13 @@ TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitm TraktorS3.registerInputButton(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); }; -TraktorS3.Deck.prototype.defineJog = function(message, group, name, deckOffset, deck2Offset, callback) { +TraktorS3.Deck.prototype.defineJog = function(message, name, deckOffset, deck2Offset, callback) { // Jog wheels have 4 byte input if (this.deckNumber === 2) { deckOffset = deck2Offset; } - message.addControl(group, name, deckOffset, "I", 0xFFFFFFFF); - message.setCallback(group, name, callback); + message.addControl(this.group, name, deckOffset, "I", 0xFFFFFFFF); + message.setCallback(this.group, name, TraktorS3.bind(callback, this)); }; TraktorS3.Deck.prototype.defineScaler = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { @@ -351,7 +351,7 @@ TraktorS3.Deck.prototype.padModeHandler = function(field) { // If we are in samples mode and hotcues mode is activated this.padModeState = 0; } - TraktorS3.lightPads(field.group); + this.lightPads(); }; TraktorS3.Deck.prototype.numberButtonHandler = function(field) { @@ -630,14 +630,14 @@ TraktorS3.finishJogTouch = function(group) { // If we've received no ticks since the last call, we are stopped. if (!deck.tickReceived) { - engine.setValue(group, "scratch2", 0.0); - engine.setValue(group, "scratch2_enable", false); + engine.setValue(deck.activeChannel, "scratch2", 0.0); + engine.setValue(deck.activeChannel, "scratch2_enable", false); } else { // Check again soon. deck.wheelTouchInertiaTimer = engine.beginTimer( 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); } - this.tickReceived = false; + deck.tickReceived = false; }; TraktorS3.Deck.prototype.jogHandler = function(field) { @@ -884,7 +884,7 @@ TraktorS3.Channel.prototype.fxEnableHandler = function(field) { } this.fxEnabledState = !this.fxEnabledState; - TraktorS3.colorOutputHandler(this.fxEnabledState, this.group, "!fxEnabled"); + this.colorOutputHandler(this.fxEnabledState, "!fxEnabled"); TraktorS3.toggleFX(); }; @@ -924,10 +924,10 @@ TraktorS3.Channel.prototype.spinnyAngleChanged = function(value) { // We have 5 levels of brightness to choose from, including "off". var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); if (brightVal <= 0) { - TraktorS3.controller.setOutput(this.parentDeck, "!wheel" + seg, 0x00, false); + TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, 0x00, false); } else { brightVal -= 1; - TraktorS3.controller.setOutput(this.parentDeck, "!wheel" + seg, ledValue + brightVal, false); + TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, ledValue + brightVal, false); } } TraktorS3.controller.OutputPackets["outputA"].send(); @@ -944,26 +944,6 @@ TraktorS3.Channel.prototype.colorOutputHandler = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.deckSwitchHandler = function(field) { - if (field.value === 0) { - return; - } - - if (field.group === "[Channel1]") { - TraktorS3.Decks["deck1"].activeChannel = field.group; - } else if (field.group === "[Channel3]") { - TraktorS3.Decks["deck1"].activeChannel = field.group; - } else if (field.group === "[Channel2]") { - TraktorS3.Decks["deck2"].activeChannel = field.group; - } else if (field.group === "[Channel4]") { - TraktorS3.Decks["deck2"].activeChannel = field.group; - } else { - HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); - } - engine.softTakeoverIgnoreNextValue(field.group, "rate"); - TraktorS3.lightDeck(field.group); -}; - TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -978,13 +958,13 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); - var group = "[Channel1]"; + var group = "[Channel3]"; + TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x08, + TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); + group = "[Channel1]"; TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x10, TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); group = "[Channel2]"; - TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x08, - TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); - group = "[Channel3]"; TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x20, TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); group = "[Channel4]"; @@ -1124,11 +1104,31 @@ TraktorS3.superHandler = function(field) { engine.setParameter("[QuickEffectRack1_" + group + "]", "super1", value); for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { if (TraktorS3.fxButtonState[fxNumber]) { - engine.setParameter("[EffectRack1_EffectUnit" + fxNumber +"]", "super1", value); + engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); } } }; +TraktorS3.deckSwitchHandler = function(field) { + if (field.value === 0) { + return; + } + + if (field.group === "[Channel1]") { + TraktorS3.Decks["deck1"].activeChannel = field.group; + } else if (field.group === "[Channel3]") { + TraktorS3.Decks["deck1"].activeChannel = field.group; + } else if (field.group === "[Channel2]") { + TraktorS3.Decks["deck2"].activeChannel = field.group; + } else if (field.group === "[Channel4]") { + TraktorS3.Decks["deck2"].activeChannel = field.group; + } else { + HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); + } + engine.softTakeoverIgnoreNextValue(field.group, "rate"); + TraktorS3.lightDeck(field.group); +}; + TraktorS3.fxHandler = function(field) { if (field.value === 0) { return; @@ -1154,7 +1154,7 @@ TraktorS3.toggleFX = function() { for (var ch = 1; ch <= 4; ch++) { var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - var fxKey = "group_[Channel1]_enable"; + var fxKey = "group_[Channel" + ch + "]_enable"; if (fxNumber === 5) { fxGroup = "[QuickEffectRack1_[Channel" + ch + "]_Effect1]"; fxKey = "enabled"; @@ -1238,7 +1238,7 @@ TraktorS3.registerOutputPackets = function() { } // Play is always green - // TraktorS3.linkDeckOutputs("play_indicator", TraktorS3.Deck.prototype.wheelOutputHandler); + TraktorS3.linkDeckOutputs("play_indicator", TraktorS3.Deck.prototype.wheelOutputHandler); TraktorS3.linkDeckOutputs("cue_indicator", TraktorS3.Deck.prototype.colorOutputHandler); TraktorS3.linkDeckOutputs("sync_enabled", TraktorS3.Deck.prototype.colorOutputHandler); TraktorS3.linkDeckOutputs("keylock", TraktorS3.Deck.prototype.colorOutputHandler); @@ -1262,13 +1262,6 @@ TraktorS3.registerOutputPackets = function() { this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); - // for (i = 1; i <= 8; ++i) { - // TraktorS3.linkDeckOutputs("pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // // TraktorS3.linkDeckOutputs("!pad_" + i, "deck2", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // TraktorS3.linkDeckOutputs("pad_" + i, "deck1", "hotcue_" + i + "_enabled", this.hotcueOutputHandler); - // } - // Sampler callbacks for (i = 1; i <= 16; ++i) { this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); @@ -1276,7 +1269,6 @@ TraktorS3.registerOutputPackets = function() { } }; -/* Helper function to link output in a short form */ TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; @@ -1301,97 +1293,6 @@ TraktorS3.linkDeckOutputs = function(key, callback) { engine.connectControl("[Channel4]", key, wrapped); }; -TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { - var groupOb = packet.groups[outputGroupName]; - for (var fieldName in groupOb) { - var field = groupOb[fieldName]; - if (field.name[0] === "!") { - continue; - } - HIDDebug("field to light?: " + field.group + " " + field.name); - if (field.mapped_callback !== undefined) { - var value = engine.getValue(coGroupName, field.name); - HIDDebug("has callback " + value); - field.mapped_callback(value, coGroupName, field.name); - } - // No callback, no light! - } -}; - -TraktorS3.lightFX = function() { - for (var ch in TraktorS3.Channels) { - var chanob = TraktorS3.Channels[ch]; - chanob.colorOutputHandler(chanob.fxEnabledState, "!fxEnabled"); - } - for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { - var ledValue = TraktorS3.fxLEDValue[fxNumber]; - if (TraktorS3.fxButtonState[fxNumber]) { - ledValue += TraktorS3.LEDBrightValue; - } else { - ledValue += TraktorS3.LEDDimValue; - } - TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); - } -}; - -TraktorS3.lightDeck = function(group, sendPackets) { - if (sendPackets === undefined) { - sendPackets = true; - } - // Freeze the lights while we do this update so we don't spam HID. - TraktorS3.batchingOutputs = true; - HIDDebug("lighting" + group); - for (var packetName in this.controller.OutputPackets) { - var packet = this.controller.OutputPackets[packetName]; - var deckGroupName = "deck1"; - if (group === "[Channel2]" || group === "[Channel4]") { - deckGroupName = "deck2"; - } - - var deck = TraktorS3.Decks[deckGroupName]; - - HIDDebug("light deck group " + deckGroupName); - TraktorS3.lightGroup(packet, deckGroupName, group); - HIDDebug("light channel group " + group); - TraktorS3.lightGroup(packet, group, group); - - deck.lightPads(); - - // These lights are different because either they aren't associated with a CO, or - // there are two buttons that point to the same CO. - deck.outputHandler(0, "!shift"); - deck.colorOutputHandler(0, "!PreviewTrack"); - deck.colorOutputHandler(0, "!QueueAutoDJ"); - deck.colorOutputHandler(0, "!LibraryFocus"); - deck.outputHandler(0, "!reverse"); - } - TraktorS3.lightFX(); - - // Selected deck lights - var ctrlr = TraktorS3.controller; - if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); - } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); - } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); - } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); - } - - TraktorS3.batchingOutputs = false; - // And now send them all. - if (sendPackets) { - for (packetName in this.controller.OutputPackets) { - this.controller.OutputPackets[packetName].send(); - } - } -}; - TraktorS3.channelVuMeterHandler = function(value, group, key) { TraktorS3.vuMeterHandler(value, group, key, 14); }; @@ -1490,6 +1391,92 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { } }; +TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { + var groupOb = packet.groups[outputGroupName]; + for (var fieldName in groupOb) { + var field = groupOb[fieldName]; + if (field.name[0] === "!") { + continue; + } + if (field.mapped_callback !== undefined) { + var value = engine.getValue(coGroupName, field.name); + field.mapped_callback(value, coGroupName, field.name); + } + // No callback, no light! + } +}; + +TraktorS3.lightFX = function() { + for (var ch in TraktorS3.Channels) { + var chanob = TraktorS3.Channels[ch]; + chanob.colorOutputHandler(chanob.fxEnabledState, "!fxEnabled"); + } + for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { + var ledValue = TraktorS3.fxLEDValue[fxNumber]; + if (TraktorS3.fxButtonState[fxNumber]) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue += TraktorS3.LEDDimValue; + } + TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); + } +}; + +TraktorS3.lightDeck = function(group, sendPackets) { + if (sendPackets === undefined) { + sendPackets = true; + } + // Freeze the lights while we do this update so we don't spam HID. + TraktorS3.batchingOutputs = true; + for (var packetName in this.controller.OutputPackets) { + var packet = this.controller.OutputPackets[packetName]; + var deckGroupName = "deck1"; + if (group === "[Channel2]" || group === "[Channel4]") { + deckGroupName = "deck2"; + } + + var deck = TraktorS3.Decks[deckGroupName]; + + TraktorS3.lightGroup(packet, deckGroupName, group); + TraktorS3.lightGroup(packet, group, group); + + deck.lightPads(); + + // These lights are different because either they aren't associated with a CO, or + // there are two buttons that point to the same CO. + deck.outputHandler(0, "!shift"); + deck.colorOutputHandler(0, "!PreviewTrack"); + deck.colorOutputHandler(0, "!QueueAutoDJ"); + deck.colorOutputHandler(0, "!LibraryFocus"); + deck.outputHandler(0, "!reverse"); + } + TraktorS3.lightFX(); + + // Selected deck lights + var ctrlr = TraktorS3.controller; + if (group === "[Channel1]") { + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); + } else if (group === "[Channel2]") { + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); + } else if (group === "[Channel3]") { + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); + } else if (group === "[Channel4]") { + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); + } + + TraktorS3.batchingOutputs = false; + // And now send them all. + if (sendPackets) { + for (packetName in this.controller.OutputPackets) { + this.controller.OutputPackets[packetName].send(); + } + } +}; + TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { if (Object.prototype.hasOwnProperty.call(data, name)) { From d009e3aa6b31dcdbb9dd7967b92041dc2ad532c1 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 11 Aug 2020 15:29:03 -0400 Subject: [PATCH 32/84] Traktor S3: better timers --- .../Traktor-Kontrol-S3-hid-scripts.js | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6137b5125ad..d52c1bec928 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -16,8 +16,6 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ -/* * finish objecting refactor, esp channel-specific stuff -/* * and light /* * wheel blink for end of track */ /* * touch for track browse, loop control, beatjump? */ /* * jog button */ @@ -562,8 +560,8 @@ TraktorS3.Deck.prototype.quantizeHandler = function(field) { TraktorS3.Deck.prototype.jogTouchHandler = function(field) { if (this.wheelTouchInertiaTimer !== 0) { // The wheel was touched again, reset the timer. - engine.stopTimer(this.wheelTouchIntertiaTimer); - this.wheelTouchIntertiaTimer = 0; + engine.stopTimer(this.wheelTouchInertiaTimer); + this.wheelTouchInertiaTimer = 0; } if (field.value !== 0) { engine.setValue(this.activeChannel, "scratch2_enable", true); @@ -575,23 +573,23 @@ TraktorS3.Deck.prototype.jogTouchHandler = function(field) { var inertiaTime = Math.pow(1.8, scratchRate) * 2; if (inertiaTime < 100) { // Just do it now. - TraktorS3.finishJogTouch(this.group); + this.finishJogTouch(); } else { - this.wheelTouchIntertiaTimer = engine.beginTimer( - inertiaTime, "TraktorS3.finishJogTouch(\"" + this.group + "\")", true); + this.wheelTouchInertiaTimer = engine.beginTimer( + inertiaTime, this.finishJogTouch, true); } } }; TraktorS3.Deck.prototype.wheelDeltas = function(value) { - // When the wheel is touched, four bytes change, but only the first behaves predictably. - // It looks like the wheel is 1024 ticks per revolution. + // When the wheel is touched, 1 byte measures distance ticks, the other + // three represent a timer value. We can use the amount of time required for + // the number of ticks to elapse to get a velocity. var tickval = value & 0xFF; var timeval = value >>> 8; var prevTick = 0; var prevTime = 0; - // Group 1 and 2 -> Array index 0 and 1 prevTick = this.lastTickVal; prevTime = this.lastTickTime; this.lastTickVal = tickval; @@ -604,7 +602,7 @@ TraktorS3.Deck.prototype.wheelDeltas = function(value) { var timeDelta = timeval - prevTime; if (timeDelta === 0) { // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. - // This is almost certainly not going to happen on this controller. + // (This is almost certainly not going to happen on this controller.) timeDelta = 1; } @@ -622,22 +620,19 @@ TraktorS3.Deck.prototype.wheelDeltas = function(value) { return [tickDelta, timeDelta]; }; -// NOTE: this a global function because it gets called by timers! -TraktorS3.finishJogTouch = function(group) { - var deck = TraktorS3.Decks[group]; - - deck.wheelTouchInertiaTimer = 0; +TraktorS3.Deck.prototype.finishJogTouch = function() { + this.wheelTouchInertiaTimer = 0; // If we've received no ticks since the last call, we are stopped. - if (!deck.tickReceived) { - engine.setValue(deck.activeChannel, "scratch2", 0.0); - engine.setValue(deck.activeChannel, "scratch2_enable", false); + if (!this.tickReceived) { + engine.setValue(this.activeChannel, "scratch2", 0.0); + engine.setValue(this.activeChannel, "scratch2_enable", false); } else { // Check again soon. - deck.wheelTouchInertiaTimer = engine.beginTimer( - 100, "TraktorS3.finishJogTouch(\"" + group + "\")", true); + this.wheelTouchInertiaTimer = engine.beginTimer( + 100, this.finishJogTouch, true); } - deck.tickReceived = false; + this.tickReceived = false; }; TraktorS3.Deck.prototype.jogHandler = function(field) { From f1d3b1285e6191f85d748417a0da02bef1970a44 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 16 Aug 2020 21:58:03 -0400 Subject: [PATCH 33/84] Traktor S3: more rearranging (untested) --- .../Traktor-Kontrol-S3-hid-scripts.js | 478 +++++++++++------- 1 file changed, 286 insertions(+), 192 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index d52c1bec928..44154436cc7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -15,11 +15,12 @@ /* */ /////////////////////////////////////////////////////////////////////////////////// /* */ -/* TODO: */ -/* * wheel blink for end of track */ -/* * touch for track browse, loop control, beatjump? */ +/* TODO: */ +/* * should we be lighting things inside input handlers? no because we want */ +/* things to light up if activated in GUI, not controller. */ +/* * touch for track browse, loop control, beatjump? */ /* * jog button */ -/* * star button */ +/* * star button */ /* */ /////////////////////////////////////////////////////////////////////////////////// @@ -44,7 +45,8 @@ var TraktorS3 = new function() { // State for relative mode - // "5" is the "filter" button below the other 4. + // "5" is the "filter" button below the other 4. It starts on but the + // others start off. this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: true}; this.fxEnabledState = { "[Channel1]": true, @@ -60,24 +62,11 @@ var TraktorS3 = new function() { this.microphonePressedTimer = 0; // Timer to distinguish between short and long press // VuMeter - this.vuConnections = { - "[Channel1]": {}, - "[Channel2]": {}, - "[Channel3]": {}, - "[Channel4]": {}, - }; this.masterVuConnections = { "VuMeterL": {}, "VuMeterR": {} }; - this.clipConnections = { - "[Channel1]": {}, - "[Channel2]": {}, - "[Channel3]": {}, - "[Channel4]": {} - }; - // The S3 has a set of predefined colors for many buttons. They are not // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. this.controller.LEDColors = { @@ -105,6 +94,7 @@ var TraktorS3 = new function() { this.LEDDimValue = 0x00; this.LEDBrightValue = 0x02; + // More User-friendly config: you can choose whatever colors you like for each deck! this.deckOutputColors = { "[Channel1]": "CARROT", "[Channel2]": "CARROT", @@ -112,6 +102,7 @@ var TraktorS3 = new function() { "[Channel4]": "BLUE" }; + // FX 5 is the Filter this.fxLEDValue = { 1: this.controller.LEDColors.RED, 2: this.controller.LEDColors.GREEN, @@ -144,7 +135,7 @@ var TraktorS3 = new function() { 0xCCCCCC: this.controller.LEDColors.WHITE, }); - // Sampler callbacks + // callbacks this.samplerCallbacks = []; }; @@ -155,7 +146,8 @@ TraktorS3.bind = function(fn, obj) { }; //// Deck Objects //// - +// Decks are the physical controllers on either side of the controller. +// Each Deck can control 2 channels. TraktorS3.Deck = function(deckNumber, group) { this.deckNumber = deckNumber; this.group = group; @@ -167,8 +159,10 @@ TraktorS3.Deck = function(deckNumber, group) { this.keylockPressed = false; this.keyAdjusted = false; + // Various states this.syncPressedTimer = 0; this.previewPressed = false; + // state 0 is hotcues, 1 is samplers this.padModeState = 0; // Jog wheel state @@ -185,6 +179,19 @@ TraktorS3.Deck = function(deckNumber, group) { this.moveKnobEncoderState = 0; }; +TraktorS3.Deck.prototype.activateChannel = function(channel) { + if (channel.parentDeck !== this) { + HIDDebug("Programming ERROR: tried to activate a channel with a deck that is not its parent"); + return; + } + this.activeChannel = channel.group; + engine.softTakeoverIgnoreNextValue(this.activeChannel, "rate"); + TraktorS3.lightDeck(this.activeChannel); +}; + +// defineButton allows us to configure either the right deck or the left deck, depending on which +// is appropriate. This avoids extra logic in the function where we define all the magic numbers. +// We use a similar approach in the other define funcs. TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { if (this.deckNumber === 2) { deckOffset = deck2Offset; @@ -202,6 +209,7 @@ TraktorS3.Deck.prototype.defineJog = function(message, name, deckOffset, deck2Of message.setCallback(this.group, name, TraktorS3.bind(callback, this)); }; +// defineScaler configures ranged controls like knobs and sliders. TraktorS3.Deck.prototype.defineScaler = function(msg, name, deckOffset, deckBitmask, deck2Offset, deck2Bitmask, fn) { if (this.deckNumber === 2) { deckOffset = deck2Offset; @@ -275,6 +283,9 @@ TraktorS3.Deck.prototype.cueHandler = function(field) { }; TraktorS3.Deck.prototype.shiftHandler = function(field) { + // Mixxx only knows about one shift value, but this controller has two shift buttons. + // This control object could get confused if both physical buttons are pushed at the same + // time. engine.setValue("[Controls]", "touch_shift", field.value); this.shiftPressed = field.value; TraktorS3.outputHandler(field.value, field.group, "!shift"); @@ -285,43 +296,58 @@ TraktorS3.Deck.prototype.syncHandler = function(field) { engine.setValue(this.activeChannel, "beatsync_phase", field.value); // Light LED while pressed this.colorOutputHandler(field.value, "sync_enabled"); - } else { - if (field.value) { - if (engine.getValue(this.activeChannel, "sync_enabled") === 0) { - script.triggerControl(this.activeChannel, "beatsync"); - // Start timer to measure how long button is pressed - this.syncPressedTimer = engine.beginTimer(300, function() { - engine.setValue(this.activeChannel, "sync_enabled", 1); - // Reset sync button timer state if active - if (this.syncPressedTimer !== 0) { - this.syncPressedTimer = 0; - } - }, true); - - // Light corresponding LED when button is pressed - this.colorOutputHandler(1, "sync_enabled"); - } else { - // Deactivate sync lock - // LED is turned off by the callback handler for sync_enabled - engine.setValue(this.activeChannel, "sync_enabled", 0); - } + return; + } + + // Unshifted + if (field.value) { + // We have to reimplement push-to-lock because it's only defined in the midi code + // in Mixxx. + if (engine.getValue(this.activeChannel, "sync_enabled") === 0) { + script.triggerControl(this.activeChannel, "beatsync"); + // Start timer to measure how long button is pressed + this.syncPressedTimer = engine.beginTimer(300, function() { + engine.setValue(this.activeChannel, "sync_enabled", 1); + // Reset sync button timer state if active + if (this.syncPressedTimer !== 0) { + this.syncPressedTimer = 0; + } + }, true); + + // Light corresponding LED when button is pressed + this.colorOutputHandler(1, "sync_enabled"); } else { - if (this.syncPressedTimer !== 0) { - // Timer still running -> stop it and unlight LED - engine.stopTimer(this.syncPressedTimer); - this.colorOutputHandler(0, "sync_enabled"); - } + // Deactivate sync lock + // LED is turned off by the callback handler for sync_enabled + engine.setValue(this.activeChannel, "sync_enabled", 0); + } + } else { + if (this.syncPressedTimer !== 0) { + // Timer still running -> stop it and unlight LED + engine.stopTimer(this.syncPressedTimer); + this.colorOutputHandler(0, "sync_enabled"); } } }; TraktorS3.Deck.prototype.keylockHandler = function(field) { + // shift + keylock resets pitch (in either mode). + if (this.shiftPressed) { + if (field.value) { + engine.setValue(this.activeChannel, "pitch_adjust_set_default", 1); + } + return; + } if (TraktorS3.pitchSliderRelativeMode) { if (field.value) { + // In relative mode on down-press, reset the values and note that + // the button is pressed. this.keylockPressed = true; this.keyAdjusted = false; return; } + // On release, note that the button is released, and if the key *wasn't* adjusted, + // activate keylock. this.keylockPressed = false; if (!this.keyAdjusted) { script.toggleControl(this.activeChannel, "keylock"); @@ -329,6 +355,7 @@ TraktorS3.Deck.prototype.keylockHandler = function(field) { return; } + // By default, do a basic press-to-toggle action. if (field.value === 0) { return; } @@ -358,13 +385,16 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { // Hotcues mode if (this.padModeState === 0) { - TraktorS3.lightHotcue(this.activeChannel, padNumber); - if (this.shiftPressed) { - action = "_clear"; - } else { - action = "_activate"; + // XXX: Should we be lighting things here? I think not. + // TraktorS3.lightHotcue(padNumber, field.value); + if (field.value) { + if (this.shiftPressed) { + action = "_clear"; + } else { + action = "_activate"; + } + engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); } - engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); return; } @@ -374,11 +404,12 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { sampler += 8; } - var ledValue = field.value; - if (!field.value) { - ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); - } - this.colorOutputHandler(ledValue, "!pad_" + padNumber); + // var ledValue = field.value; + // if (!field.value) { + // ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); + // } + // XXX: Again, should we be lighting? + // this.colorOutputHandler(ledValue, "!pad_" + padNumber); if (this.shiftPressed) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); @@ -438,6 +469,7 @@ TraktorS3.Deck.prototype.loadTrackHandler = function(field) { }; TraktorS3.Deck.prototype.previewTrackHandler = function(field) { + this.colorOutputHandler(field.value, "!PreviewTrack"); if (field.value === 1) { this.previewPressed = true; engine.setValue("[PreviewDeck1]", "LoadSelectedTrackAndPlay", 1); @@ -445,7 +477,6 @@ TraktorS3.Deck.prototype.previewTrackHandler = function(field) { this.previewPressed = false; engine.setValue("[PreviewDeck1]", "play", 0); } - this.colorOutputHandler(field.value, "!PreviewTrack"); }; TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { @@ -459,7 +490,6 @@ TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { this.colorOutputHandler(field.value, "!QueueAutoDJ"); - if (this.shiftPressed) { engine.setValue("[Library]", "AutoDjAddTop", field.value); } else { @@ -528,13 +558,12 @@ TraktorS3.Deck.prototype.activateBeatjumpHandler = function(field) { }; TraktorS3.Deck.prototype.reverseHandler = function(field) { + TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); if (this.shiftPressed) { engine.setValue(this.activeChannel, "reverseroll", field.value); } else { engine.setValue(this.activeChannel, "reverse", field.value); } - - TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); }; TraktorS3.Deck.prototype.fluxHandler = function(field) { @@ -552,8 +581,6 @@ TraktorS3.Deck.prototype.quantizeHandler = function(field) { engine.setValue(this.activeChannel, "beats_translate_curpos", field.value); } else { script.toggleControl(this.activeChannel, "quantize"); - // engine.setValue(this.activeChannel, "quantize", newState); - // TraktorS3.colorDeckOutputHandler(newState, field.group, "quantize"); } }; @@ -627,6 +654,7 @@ TraktorS3.Deck.prototype.finishJogTouch = function() { if (!this.tickReceived) { engine.setValue(this.activeChannel, "scratch2", 0.0); engine.setValue(this.activeChannel, "scratch2_enable", false); + this.playIndicatorHandler(0, this.activeChannel); } else { // Check again soon. this.wheelTouchInertiaTimer = engine.beginTimer( @@ -644,7 +672,7 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { // The scratch rate is the ratio of the wheel's speed to "regular" speed, // which we're going to call 33.33 RPM. It's 768 ticks for a circle, and // 400000 ticks per second, and 33.33 RPM is 1.8 seconds per rotation, so - // the standard speend is 768 / (400000 * 1.8) + // the standard speed is 768 / (400000 * 1.8) var thirtyThree = 768 / 720000; // Our actual speed is tickDelta / timeDelta. Take the ratio of those to get the @@ -709,7 +737,6 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { } }; - //// Deck Outputs //// TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) { @@ -770,24 +797,64 @@ TraktorS3.Deck.prototype.defineLink = function(key, callback) { TraktorS3.Deck.prototype.linkOutputs = function() { var deckFn = TraktorS3.Deck.prototype; - this.defineLink("play_indicator", TraktorS3.bind(deckFn.wheelOutputHandler, this)); + this.defineLink("play_indicator", TraktorS3.bind(deckFn.playIndicatorHandler, this)); + this.defineLink("cue_indicator", TraktorS3.bind(deckFn.colorOutputHandler, this)); + this.defineLink("sync_enabled", TraktorS3.bind(deckFn.colorOutputHandler, this)); + this.defineLink("keylock", TraktorS3.bind(deckFn.colorOutputHandler, this)); + this.defineLink("slip_enabled", TraktorS3.bind(deckFn.outputHandler, this)); + this.defineLink("quantize", TraktorS3.bind(deckFn.colorOutputHandler, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.activeChannel]]; }; +// deckOutputHandler drives lights that only have one color. +TraktorS3.Deck.prototype.outputHandler = function(value, key) { + // incoming value will be a channel, we have to resolve back to + // deck. + var ledValue = 0x20; + if (value === 1 || value === true) { + // On value + ledValue = 0x77; + } + TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); +}; + +// colorOutputHandler drives lights that have the palettized multicolor lights. +TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { + var ledValue = this.deckBaseColor(); + + if (value === 1 || value === true) { + ledValue += TraktorS3.LEDBrightValue; + } else { + ledValue = 0x00; + } + TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); +}; + +TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, key) { + // Also call regular handler + HIDDebug("INDICATOR HANDLER" + value); + this.outputHandler(value, key); + this.wheelOutputByValue(group, value); +}; + TraktorS3.Deck.prototype.colorForHotcue = function(num) { var colorCode = engine.getValue(this.activeChannel, "hotcue_" + num + "_color"); return TraktorS3.colorMap.getValueForNearestColor(colorCode); }; TraktorS3.Deck.prototype.lightHotcue = function(number) { - var active = engine.getValue(this.activeChannel, "hotcue_" + number + "_enabled"); + var loaded = engine.getValue(this.activeChannel, "hotcue_" + number + "_enabled"); + var active = engine.getValue(this.activeChannel, "hotcue_" + number + "_activate"); var ledValue = TraktorS3.controller.LEDColors.WHITE; - if (active) { + if (loaded) { ledValue = this.colorForHotcue(number); ledValue += TraktorS3.LEDDimValue; + } + if (active) { + ledValue += TraktorS3.LEDBrightValue; } else { ledValue += TraktorS3.LEDDimValue; } @@ -816,50 +883,35 @@ TraktorS3.Deck.prototype.lightPads = function() { } }; -// deckOutputHandler drives lights that only have one color. -TraktorS3.Deck.prototype.outputHandler = function(value, key) { - // incoming value will be a channel, we have to resolve back to - // deck. - var ledValue = 0x20; - if (value === 1 || value === true) { - // On value - ledValue = 0x77; +TraktorS3.Deck.prototype.wheelOutputByValue = function(group, value) { + if (group !== this.activeChannel) { + return; } - TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); -}; -// colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue = 0x00; } - TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); + this.wheelOutputHandler(group, + [ledValue, ledValue, ledValue, ledValue, ledValue, ledValue, ledValue, ledValue]); }; -TraktorS3.Deck.prototype.wheelOutputHandler = function(value, group, key) { - // Also call regular handler - this.outputHandler(value, key); - +TraktorS3.Deck.prototype.wheelOutputHandler = function(group, valueArray) { if (group !== this.activeChannel) { return; } var sendPacket = !TraktorS3.batchingOutputs; - TraktorS3.batchingOutputs = true; for (var i = 0; i < 8; i++) { - this.colorOutputHandler(value, "!wheel" + i); + TraktorS3.controller.setOutput(this.group, "!wheel" + i, valueArray[i], false); } if (sendPacket) { for (var packetName in TraktorS3.controller.OutputPackets) { TraktorS3.controller.OutputPackets[packetName].send(); } - // Only unset batchingOutputs if it wasn't already true when we - // entered this function. - TraktorS3.batchingOutputs = false; } }; @@ -871,6 +923,15 @@ TraktorS3.Channel = function(parentDeck, group) { this.parentDeck = parentDeck; this.group = group; this.fxEnabledState = false; + + this.trackDurationSec = 0; + this.endOfTrackTimer = 0; + this.endOfTrack = false; + this.endOfTrackBlinkState = 0; + + this.vuConnection = {}; + this.clipConnection = {}; + this.hotcueCallbacks = []; }; TraktorS3.Channel.prototype.fxEnableHandler = function(field) { @@ -897,7 +958,32 @@ TraktorS3.wheelSegmentDistance = function(segNum, angle) { return Math.abs(angle - segNum); }; -TraktorS3.Channel.prototype.spinnyAngleChanged = function(value) { +TraktorS3.Channel.prototype.trackLoadedHandler = function() { + var trackSamples = engine.getValue(this.group, "track_samples"); + if (trackSamples === 0) { + this.trackDurationSec = 0; + return; + } + var trackSampleRate = engine.getValue(this.group, "track_samplerate"); + // Assume stereo. + this.trackDurationSec = trackSamples / 2.0 / trackSampleRate; +}; + +TraktorS3.Channel.prototype.endOfTrackHandler = function(value) { + this.endOfTrack = value; + if (!value) { + if (this.endOfTrackTimer) { + engine.stopTimer(this.endOfTrackTimer); + this.endOfTrackTimer = 0; + } + return; + } + this.endOfTrackTimer = engine.beginTimer(400, function() { + this.endOfTrackBlinkState = !this.endOfTrackBlinkState; + }, false); +}; + +TraktorS3.Channel.prototype.playpositionChanged = function(value) { if (this.parentDeck.activeChannel !== this.group) { return; } @@ -905,40 +991,115 @@ TraktorS3.Channel.prototype.spinnyAngleChanged = function(value) { // How many segments away from the actual angle should we light? // (in both directions, so "2" will light up to four segments) var dimDistance = 2.5; - // ugly hack just for testing -- assume 5 minute track for now - var elapsed = value * 6 * 60; + if (this.trackDurationSec === 0) { + var samples = engine.getValue(this.group, "track_samples"); + if (samples > 0) { + this.trackLoadedHandler(); + } else { + // No track loaded, abort + return; + } + } + var elapsed = value * this.trackDurationSec; var rotations = elapsed * (1 / 1.8); // 1/1.8 is rotations per second // Calculate angle from 0-1.0 var angle = rotations - Math.floor(rotations); // The wheel has 8 segments var wheelAngle = 8.0 * angle; + var baseLedValue = this.channelBaseColor(); + var segValues = [0, 0, 0, 0, 0, 0, 0, 0]; for (var seg = 0; seg < 8; seg++) { var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; - // We have 5 levels of brightness to choose from, including "off". var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (this.endOfTrack) { + // if (this.endOfTrackBlinkState) { + brightVal = brightVal > 0x03 ? 0x04 : 0x01; + // } else { + // brightVal = brightVal > 0x02 ? 0x01 : 0x00; + // } + // segValues[seg] = brightVal; + } if (brightVal <= 0) { - TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, 0x00, false); + segValues[seg] = 0x00; + // TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, 0x00, false); } else { - brightVal -= 1; - TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, ledValue + brightVal, false); + segValues[seg] = baseLedValue + brightVal - 1; + // TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, baseLedValue + brightVal, false); } + HIDDebug("seg " + seg + " " + segValues[seg].toString(16)); + + } + this.parentDeck.wheelOutputHandler(this.group, segValues); +}; + +TraktorS3.Channels.prototype.linkOutputs = function() { + this.vuConnection = engine.makeConnection(this.group, "VuMeter", this.channelVuMeterHandler); + this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutputHandler); + for (var j = 1; j <= 8; j++) { + this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", this.hotcuesOutputHandler)); + this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", this.hotcuesOutputHandler)); + TraktorS3.linkChannelOutput(this.group, "pfl", this.outputHandler); } - TraktorS3.controller.OutputPackets["outputA"].send(); +}; + +TraktorS3.Channel.prototype.channelBaseColor = function() { + return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; }; // colorOutputHandler drives lights that have the palettized multicolor lights. TraktorS3.Channel.prototype.colorOutputHandler = function(value, key) { - var ledValue = TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; + var ledValue = this.channelBaseColor(); + if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue = 0x00; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; +TraktorS3.Channel.prototype.hotcuesOutputHandler = function(_value, group, key) { + var deck = TraktorS3.Channels[group].parentDeck; + if (deck.activeChannel !== group) { + // Not active, ignore + return; + } + var cueNum = key.match(/hotcue_(\d+)_/); + deck.lightHotcue(cueNum); +}; + +TraktorS3.Channel.prototype.channelVuMeterHandler = function(value, _group, key) { + this.vuMeterHandler(value, key, 14); +}; + +TraktorS3.Channel.prototype.masterVuMeterHandler = function(value, _group, key) { + this.vuMeterHandler(value, key, 8); +}; + +TraktorS3.Channel.prototype.vuMeterHandler = function(value, key, segments) { + // return; + // This handler is called a lot so it should be as fast as possible. + var scaledValue = value * segments; + var fullIllumCount = Math.floor(scaledValue); + + // Figure out how much the partially-illuminated segment is illuminated. + var partialIllum = (scaledValue - fullIllumCount) * 0x7F; + + for (var i = 0; i < segments; i++) { + var segmentKey = "!" + key + i; + if (i < fullIllumCount) { + // Don't update lights until they're all done, so the last term is false. + TraktorS3.controller.setOutput(this.group, segmentKey, 0x7F, false); + } else if (i === fullIllumCount) { + TraktorS3.controller.setOutput(this.group, segmentKey, partialIllum, false); + } else { + TraktorS3.controller.setOutput(this.group, segmentKey, 0x00, false); + } + } + TraktorS3.controller.OutputPackets["outputB"].send(); +}; + TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -1020,7 +1181,7 @@ TraktorS3.registerInputPackets = function() { this.controller.registerInputPacket(messageLong); - // Soft takeover for all knobs + // Soft takeovers for (var ch = 1; ch <= 4; ch++) { group = "[Channel" + ch + "]"; if (!TraktorS3.pitchSliderRelativeMode) { @@ -1057,7 +1218,11 @@ TraktorS3.registerInputPackets = function() { for (ch in TraktorS3.Channels) { var chanob = TraktorS3.Channels[ch]; engine.connectControl(ch, "playposition", - TraktorS3.bind(TraktorS3.Channel.prototype.spinnyAngleChanged, chanob)); + TraktorS3.bind(TraktorS3.Channel.prototype.playpositionChanged, chanob)); + engine.connectControl(ch, "track_loaded", + TraktorS3.bind(TraktorS3.Channel.prototype.trackLoadedHandler, chanob)); + engine.connectControl(ch, "end_of_track", + TraktorS3.bind(TraktorS3.Channel.prototype.endOfTrackHandler, chanob)); } // Dirty hack to set initial values in the packet parser @@ -1109,19 +1274,9 @@ TraktorS3.deckSwitchHandler = function(field) { return; } - if (field.group === "[Channel1]") { - TraktorS3.Decks["deck1"].activeChannel = field.group; - } else if (field.group === "[Channel3]") { - TraktorS3.Decks["deck1"].activeChannel = field.group; - } else if (field.group === "[Channel2]") { - TraktorS3.Decks["deck2"].activeChannel = field.group; - } else if (field.group === "[Channel4]") { - TraktorS3.Decks["deck2"].activeChannel = field.group; - } else { - HIDDebug("Traktor S4MK2: Unrecognized packet group: " + field.group); - } - engine.softTakeoverIgnoreNextValue(field.group, "rate"); - TraktorS3.lightDeck(field.group); + var channel = TraktorS3.Channels[field.group]; + var deck = channel.parentDeck; + deck.activateChannel(channel); }; TraktorS3.fxHandler = function(field) { @@ -1144,7 +1299,8 @@ TraktorS3.fxHandler = function(field) { TraktorS3.toggleFX = function() { // This is an AND operation. We go through each channel, and if - // the fx is ON, we turn the effect ON. We turn OFF no matter what. + // the fitler button is ON and the fx is ON, we turn the effect ON. + // We turn OFF if either is false. for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { for (var ch = 1; ch <= 4; ch++) { var channel = TraktorS3.Channels["[Channel" + ch + "]"]; @@ -1159,7 +1315,6 @@ TraktorS3.toggleFX = function() { engine.setValue(fxGroup, fxKey, newState); } } - }; TraktorS3.registerOutputPackets = function() { @@ -1181,8 +1336,6 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Channel3]", "pfl", 0x38, "B"); outputA.addOutput("[Channel4]", "pfl", 0x3B, "B"); - // outputA.addOutput("[Microphone]", "talkover", 0x3D, "B"); - outputA.addOutput("[ChannelX]", "!fxButton1", 0x3C, "B"); outputA.addOutput("[ChannelX]", "!fxButton2", 0x3D, "B"); outputA.addOutput("[ChannelX]", "!fxButton3", 0x3E, "B"); @@ -1229,26 +1382,12 @@ TraktorS3.registerOutputPackets = function() { for (idx in TraktorS3.Decks) { deck = TraktorS3.Decks[idx]; - deck.linkOutputs(outputA, outputB); + deck.linkOutputs(); } - // Play is always green - TraktorS3.linkDeckOutputs("play_indicator", TraktorS3.Deck.prototype.wheelOutputHandler); - TraktorS3.linkDeckOutputs("cue_indicator", TraktorS3.Deck.prototype.colorOutputHandler); - TraktorS3.linkDeckOutputs("sync_enabled", TraktorS3.Deck.prototype.colorOutputHandler); - TraktorS3.linkDeckOutputs("keylock", TraktorS3.Deck.prototype.colorOutputHandler); - TraktorS3.linkDeckOutputs("slip_enabled", TraktorS3.Deck.prototype.outputHandler); - TraktorS3.linkDeckOutputs("quantize", TraktorS3.Deck.prototype.colorOutputHandler); - - TraktorS3.linkChannelOutput("[Channel1]", "pfl", this.outputHandler); - TraktorS3.linkChannelOutput("[Channel2]", "pfl", this.outputHandler); - TraktorS3.linkChannelOutput("[Channel3]", "pfl", this.outputHandler); - TraktorS3.linkChannelOutput("[Channel4]", "pfl", this.outputHandler); - - // Channel VuMeters - for (i = 1; i <= 4; i++) { - this.vuConnections[i] = engine.makeConnection("[Channel" + i + "]", "VuMeter", this.channelVuMeterHandler); - this.clipConnections[i] = engine.makeConnection("[Channel" + i + "]", "PeakIndicator", this.peakOutputHandler); + for (idx in TraktorS3.Channels) { + var chan = TraktorS3.Channels[idx]; + chan.linkOutputs(); } // Master VuMeters @@ -1274,60 +1413,6 @@ TraktorS3.wrapOutput = function(callback) { }; }; -TraktorS3.linkDeckOutputs = function(key, callback) { - // Linking outputs is a little tricky because the library doesn't quite do what I want. But this - // method works. - var deck = TraktorS3.Decks["deck1"]; - - var wrapped = TraktorS3.wrapOutput(TraktorS3.bind(callback, deck)); - TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, wrapped); - engine.connectControl("[Channel3]", key, wrapped); - deck = TraktorS3.Decks["deck2"]; - wrapped = TraktorS3.wrapOutput(TraktorS3.bind(callback, deck)); - TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, wrapped); - engine.connectControl("[Channel4]", key, wrapped); -}; - -TraktorS3.channelVuMeterHandler = function(value, group, key) { - TraktorS3.vuMeterHandler(value, group, key, 14); -}; - -TraktorS3.masterVuMeterHandler = function(value, group, key) { - TraktorS3.vuMeterHandler(value, group, key, 8); -}; - -TraktorS3.vuMeterHandler = function(value, group, key, segments) { - // return; - // This handler is called a lot so it should be as fast as possible. - var scaledValue = value * segments; - var fullIllumCount = Math.floor(scaledValue); - - // Figure out how much the partially-illuminated segment is illuminated. - var partialIllum = (scaledValue - fullIllumCount) * 0x7F; - - for (var i = 0; i < segments; i++) { - var segmentKey = "!" + key + i; - if (i < fullIllumCount) { - // Don't update lights until they're all done, so the last term is false. - TraktorS3.controller.setOutput(group, segmentKey, 0x7F, false); - } else if (i === fullIllumCount) { - TraktorS3.controller.setOutput(group, segmentKey, partialIllum, false); - } else { - TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); - } - } - TraktorS3.controller.OutputPackets["outputB"].send(); -}; - -TraktorS3.peakOutputHandler = function(value, group, key) { - var ledValue = 0x00; - if (value) { - ledValue = 0x7E; - } - - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); -}; - // outputHandler drives lights that only have one color. TraktorS3.outputHandler = function(value, group, key) { var ledValue = value; @@ -1342,6 +1427,15 @@ TraktorS3.outputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; +TraktorS3.peakOutputHandler = function(value, group, key) { + var ledValue = 0x00; + if (value) { + ledValue = 0x7E; + } + + TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); +}; + TraktorS3.resolveSampler = function(group) { if (group === undefined) { return undefined; From 2f518b834b425cdf8ca8417877bcc73e228a69e8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 18 Aug 2020 15:51:54 -0400 Subject: [PATCH 34/84] Traktor S3: Make sure lights light up if triggered from gui, not just controller --- .../Traktor-Kontrol-S3-hid-scripts.js | 140 +++++++++--------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 44154436cc7..47d0b219f37 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -48,12 +48,6 @@ var TraktorS3 = new function() { // "5" is the "filter" button below the other 4. It starts on but the // others start off. this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: true}; - this.fxEnabledState = { - "[Channel1]": true, - "[Channel2]": true, - "[Channel3]": true, - "[Channel4]": true, - }; // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; @@ -288,14 +282,14 @@ TraktorS3.Deck.prototype.shiftHandler = function(field) { // time. engine.setValue("[Controls]", "touch_shift", field.value); this.shiftPressed = field.value; - TraktorS3.outputHandler(field.value, field.group, "!shift"); + TraktorS3.basicOutputHandler(field.value, field.group, "!shift"); }; TraktorS3.Deck.prototype.syncHandler = function(field) { if (this.shiftPressed) { engine.setValue(this.activeChannel, "beatsync_phase", field.value); // Light LED while pressed - this.colorOutputHandler(field.value, "sync_enabled"); + this.colorOutput(field.value, "sync_enabled"); return; } @@ -315,7 +309,7 @@ TraktorS3.Deck.prototype.syncHandler = function(field) { }, true); // Light corresponding LED when button is pressed - this.colorOutputHandler(1, "sync_enabled"); + this.colorOutput(1, "sync_enabled"); } else { // Deactivate sync lock // LED is turned off by the callback handler for sync_enabled @@ -325,7 +319,7 @@ TraktorS3.Deck.prototype.syncHandler = function(field) { if (this.syncPressedTimer !== 0) { // Timer still running -> stop it and unlight LED engine.stopTimer(this.syncPressedTimer); - this.colorOutputHandler(0, "sync_enabled"); + this.colorOutput(0, "sync_enabled"); } } }; @@ -409,7 +403,7 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { // ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); // } // XXX: Again, should we be lighting? - // this.colorOutputHandler(ledValue, "!pad_" + padNumber); + // this.colorOutput(ledValue, "!pad_" + padNumber); if (this.shiftPressed) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); @@ -469,7 +463,7 @@ TraktorS3.Deck.prototype.loadTrackHandler = function(field) { }; TraktorS3.Deck.prototype.previewTrackHandler = function(field) { - this.colorOutputHandler(field.value, "!PreviewTrack"); + this.colorOutput(field.value, "!PreviewTrack"); if (field.value === 1) { this.previewPressed = true; engine.setValue("[PreviewDeck1]", "LoadSelectedTrackAndPlay", 1); @@ -480,7 +474,7 @@ TraktorS3.Deck.prototype.previewTrackHandler = function(field) { }; TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { - this.colorOutputHandler(field.value, "!LibraryFocus"); + this.colorOutput(field.value, "!LibraryFocus"); if (field.value === 0) { return; } @@ -489,7 +483,7 @@ TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { }; TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { - this.colorOutputHandler(field.value, "!QueueAutoDJ"); + this.colorOutput(field.value, "!QueueAutoDJ"); if (this.shiftPressed) { engine.setValue("[Library]", "AutoDjAddTop", field.value); } else { @@ -558,7 +552,7 @@ TraktorS3.Deck.prototype.activateBeatjumpHandler = function(field) { }; TraktorS3.Deck.prototype.reverseHandler = function(field) { - TraktorS3.deckOutputHandler(field.value, field.group, "!reverse"); + this.basicOutput(field.value, "reverse"); if (this.shiftPressed) { engine.setValue(this.activeChannel, "reverseroll", field.value); } else { @@ -753,7 +747,7 @@ TraktorS3.Deck.prototype.defineOutput = function(packet, name, offsetA, offsetB) TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!shift", 0x01, 0x1A); this.defineOutput(outputA, "slip_enabled", 0x02, 0x1B); - this.defineOutput(outputA, "!reverse", 0x03, 0x1C); + this.defineOutput(outputA, "reverse", 0x03, 0x1C); this.defineOutput(outputA, "!PreviewTrack", 0x04, 0x1D); this.defineOutput(outputA, "!QueueAutoDJ", 0x06, 0x1F); this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); @@ -801,16 +795,21 @@ TraktorS3.Deck.prototype.linkOutputs = function() { this.defineLink("cue_indicator", TraktorS3.bind(deckFn.colorOutputHandler, this)); this.defineLink("sync_enabled", TraktorS3.bind(deckFn.colorOutputHandler, this)); this.defineLink("keylock", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("slip_enabled", TraktorS3.bind(deckFn.outputHandler, this)); + this.defineLink("slip_enabled", TraktorS3.bind(deckFn.colorOutputHandler, this)); this.defineLink("quantize", TraktorS3.bind(deckFn.colorOutputHandler, this)); + this.defineLink("reverse", TraktorS3.bind(deckFn.basicOutputHandler, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.activeChannel]]; }; -// deckOutputHandler drives lights that only have one color. -TraktorS3.Deck.prototype.outputHandler = function(value, key) { +// outputHandler drives lights that only have one color. +TraktorS3.Deck.prototype.basicOutputHandler = function(value, _group, key) { + this.basicOutput(value, key); +}; + +TraktorS3.Deck.prototype.basicOutput = function(value, key) { // incoming value will be a channel, we have to resolve back to // deck. var ledValue = 0x20; @@ -821,22 +820,25 @@ TraktorS3.Deck.prototype.outputHandler = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; -// colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.Deck.prototype.colorOutputHandler = function(value, key) { +TraktorS3.Deck.prototype.colorOutputHandler = function(value, _group, key) { + this.colorOutput(value, key); +}; + +// colorOutput drives lights that have the palettized multicolor lights. +TraktorS3.Deck.prototype.colorOutput = function(value, key) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { - ledValue = 0x00; + ledValue += TraktorS3.LEDDimValue; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, key) { // Also call regular handler - HIDDebug("INDICATOR HANDLER" + value); - this.outputHandler(value, key); + this.basicOutput(value, key); this.wheelOutputByValue(group, value); }; @@ -864,19 +866,19 @@ TraktorS3.Deck.prototype.lightHotcue = function(number) { TraktorS3.Deck.prototype.lightPads = function() { // Samplers if (this.padModeState === 1) { - this.colorOutputHandler(0, "hotcues"); - this.colorOutputHandler(1, "samples"); + this.colorOutput(0, "hotcues"); + this.colorOutput(1, "samples"); for (var i = 1; i <= 8; i++) { var idx = i; if (this.group === "deck2") { idx += 8; } var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); - this.colorOutputHandler(loaded, "!pad_" + i); + this.colorOutput(loaded, "!pad_" + i); } } else { - this.colorOutputHandler(1, "hotcues"); - this.colorOutputHandler(0, "samples"); + this.colorOutput(1, "hotcues"); + this.colorOutput(0, "samples"); for (i = 1; i <= 8; ++i) { this.lightHotcue(i); } @@ -922,7 +924,7 @@ TraktorS3.Deck.prototype.wheelOutputHandler = function(group, valueArray) { TraktorS3.Channel = function(parentDeck, group) { this.parentDeck = parentDeck; this.group = group; - this.fxEnabledState = false; + this.fxEnabledState = true; this.trackDurationSec = 0; this.endOfTrackTimer = 0; @@ -940,7 +942,7 @@ TraktorS3.Channel.prototype.fxEnableHandler = function(field) { } this.fxEnabledState = !this.fxEnabledState; - this.colorOutputHandler(this.fxEnabledState, "!fxEnabled"); + this.colorOutput(this.fxEnabledState, "!fxEnabled"); TraktorS3.toggleFX(); }; @@ -990,7 +992,6 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { // How many segments away from the actual angle should we light? // (in both directions, so "2" will light up to four segments) - var dimDistance = 2.5; if (this.trackDurationSec === 0) { var samples = engine.getValue(this.group, "track_samples"); if (samples > 0) { @@ -1008,38 +1009,39 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { // The wheel has 8 segments var wheelAngle = 8.0 * angle; var baseLedValue = this.channelBaseColor(); + // Reduce the dimming distance at the end of track. + var dimDistance = this.endOfTrack ? 2.5 : 1.5; var segValues = [0, 0, 0, 0, 0, 0, 0, 0]; for (var seg = 0; seg < 8; seg++) { var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); if (this.endOfTrack) { - // if (this.endOfTrackBlinkState) { - brightVal = brightVal > 0x03 ? 0x04 : 0x01; - // } else { - // brightVal = brightVal > 0x02 ? 0x01 : 0x00; - // } - // segValues[seg] = brightVal; + dimDistance = 1.5; + brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (this.endOfTrackBlinkState) { + brightVal = brightVal > 0x03 ? 0x04 : 0x02; + } else { + brightVal = brightVal > 0x02 ? 0x04 : 0x00; + } } if (brightVal <= 0) { segValues[seg] = 0x00; - // TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, 0x00, false); } else { segValues[seg] = baseLedValue + brightVal - 1; - // TraktorS3.controller.setOutput(this.parentDeck.group, "!wheel" + seg, baseLedValue + brightVal, false); } - HIDDebug("seg " + seg + " " + segValues[seg].toString(16)); - } this.parentDeck.wheelOutputHandler(this.group, segValues); }; -TraktorS3.Channels.prototype.linkOutputs = function() { - this.vuConnection = engine.makeConnection(this.group, "VuMeter", this.channelVuMeterHandler); +TraktorS3.Channel.prototype.linkOutputs = function() { + this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.bind(TraktorS3.Channel.prototype.channelVuMeterHandler, this)); this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutputHandler); for (var j = 1; j <= 8; j++) { - this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", this.hotcuesOutputHandler)); - this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", this.hotcuesOutputHandler)); - TraktorS3.linkChannelOutput(this.group, "pfl", this.outputHandler); + this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", + TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); + this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", + TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); + TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.basicOutputHandler); } }; @@ -1047,14 +1049,13 @@ TraktorS3.Channel.prototype.channelBaseColor = function() { return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; }; -// colorOutputHandler drives lights that have the palettized multicolor lights. -TraktorS3.Channel.prototype.colorOutputHandler = function(value, key) { +// colorOutput drives lights that have the palettized multicolor lights. +TraktorS3.Channel.prototype.colorOutput = function(value, key) { var ledValue = this.channelBaseColor(); - if (value === 1 || value === true) { ledValue += TraktorS3.LEDBrightValue; } else { - ledValue = 0x00; + ledValue += TraktorS3.LEDDimValue; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; @@ -1065,7 +1066,12 @@ TraktorS3.Channel.prototype.hotcuesOutputHandler = function(_value, group, key) // Not active, ignore return; } - var cueNum = key.match(/hotcue_(\d+)_/); + var matches = key.match(/hotcue_(\d+)_/); + if (matches.length !== 2) { + HIDDebug("Didn't get expected hotcue number from string: " + key); + return; + } + var cueNum = matches[1]; deck.lightHotcue(cueNum); }; @@ -1128,10 +1134,10 @@ TraktorS3.registerInputPackets = function() { TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); // Headphone buttons - this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.parameterHandler); - this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.parameterHandler); - this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.parameterHandler); - this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.parameterHandler); + this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.buttonHandler); + this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.buttonHandler); + this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.buttonHandler); + this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.buttonHandler); // FX Buttons this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); @@ -1152,7 +1158,6 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[Channel3]", "pregain", 0x0F, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel4]", "pregain", 0x15, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter3", 0x25, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter2", 0x27, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel1]_Effect1]", "parameter1", 0x29, 0xFFFF, this.parameterHandler); @@ -1190,7 +1195,7 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover(group, "pitch_adjust", true); // engine.softTakeover(group, "volume", true); // engine.softTakeover(group, "pregain", true); - engine.softTakeover("[QuickEffectRack1_" +group + "]", "super1", true); + engine.softTakeover("[QuickEffectRack1_" + group + "]", "super1", true); } engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); @@ -1414,7 +1419,7 @@ TraktorS3.wrapOutput = function(callback) { }; // outputHandler drives lights that only have one color. -TraktorS3.outputHandler = function(value, group, key) { +TraktorS3.basicOutputHandler = function(value, group, key) { var ledValue = value; if (value === 0 || value === false) { // Off value @@ -1469,13 +1474,13 @@ TraktorS3.samplesOutputHandler = function(value, group, key) { if (key === "play" && engine.getValue(group, "track_loaded")) { if (value) { // Green light on play - deck.colorOutputHandler(0x9E, "!pad_" + num); + deck.colorOutput(0x9E, "!pad_" + num); } else { // Reset LED to full white light - deck.colorDeckOutputHandler(1, "!pad_" + num); + deck.colorOutput(1, "!pad_" + num); } } else if (key === "track_loaded") { - deck.colorDeckOutputHandler(value, "!pad_" + num); + deck.colorOutput(value, "!pad_" + num); } } }; @@ -1498,7 +1503,7 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { TraktorS3.lightFX = function() { for (var ch in TraktorS3.Channels) { var chanob = TraktorS3.Channels[ch]; - chanob.colorOutputHandler(chanob.fxEnabledState, "!fxEnabled"); + chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); } for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; @@ -1533,11 +1538,10 @@ TraktorS3.lightDeck = function(group, sendPackets) { // These lights are different because either they aren't associated with a CO, or // there are two buttons that point to the same CO. - deck.outputHandler(0, "!shift"); - deck.colorOutputHandler(0, "!PreviewTrack"); - deck.colorOutputHandler(0, "!QueueAutoDJ"); - deck.colorOutputHandler(0, "!LibraryFocus"); - deck.outputHandler(0, "!reverse"); + deck.basicOutput(0, "!shift"); + deck.colorOutput(0, "!PreviewTrack"); + deck.colorOutput(0, "!QueueAutoDJ"); + deck.colorOutput(0, "!LibraryFocus"); } TraktorS3.lightFX(); From f8ad615742c76ecc91e2c2fb48b6709963df862f Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 18 Aug 2020 16:07:19 -0400 Subject: [PATCH 35/84] Traktor S3: Fix hotcues Also simplify some wrappers --- .../Traktor-Kontrol-S3-hid-scripts.js | 59 +++++++------------ 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 47d0b219f37..ff7b6ba7724 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -379,16 +379,13 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { // Hotcues mode if (this.padModeState === 0) { - // XXX: Should we be lighting things here? I think not. - // TraktorS3.lightHotcue(padNumber, field.value); - if (field.value) { - if (this.shiftPressed) { - action = "_clear"; - } else { - action = "_activate"; - } - engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); + if (this.shiftPressed) { + action = "_clear"; + } else { + action = "_activate"; } + HIDDebug("setting " + "hotcue_" + padNumber + action + " " + field.value); + engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); return; } @@ -398,13 +395,6 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { sampler += 8; } - // var ledValue = field.value; - // if (!field.value) { - // ledValue = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); - // } - // XXX: Again, should we be lighting? - // this.colorOutput(ledValue, "!pad_" + padNumber); - if (this.shiftPressed) { var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { @@ -791,24 +781,29 @@ TraktorS3.Deck.prototype.defineLink = function(key, callback) { TraktorS3.Deck.prototype.linkOutputs = function() { var deckFn = TraktorS3.Deck.prototype; + + var colorOutputHandler = function(value, _group, key) { + this.colorOutput(value, key); + }; + + var basicOutputHandler = function(value, _group, key) { + this.basicOutput(value, key); + }; + this.defineLink("play_indicator", TraktorS3.bind(deckFn.playIndicatorHandler, this)); - this.defineLink("cue_indicator", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("sync_enabled", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("keylock", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("slip_enabled", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("quantize", TraktorS3.bind(deckFn.colorOutputHandler, this)); - this.defineLink("reverse", TraktorS3.bind(deckFn.basicOutputHandler, this)); + this.defineLink("cue_indicator", TraktorS3.bind(colorOutputHandler, this)); + this.defineLink("sync_enabled", TraktorS3.bind(colorOutputHandler, this)); + this.defineLink("keylock", TraktorS3.bind(colorOutputHandler, this)); + this.defineLink("slip_enabled", TraktorS3.bind(colorOutputHandler, this)); + this.defineLink("quantize", TraktorS3.bind(colorOutputHandler, this)); + this.defineLink("reverse", TraktorS3.bind(basicOutputHandler, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.activeChannel]]; }; -// outputHandler drives lights that only have one color. -TraktorS3.Deck.prototype.basicOutputHandler = function(value, _group, key) { - this.basicOutput(value, key); -}; - +// basicOutput drives lights that only have one color. TraktorS3.Deck.prototype.basicOutput = function(value, key) { // incoming value will be a channel, we have to resolve back to // deck. @@ -820,10 +815,6 @@ TraktorS3.Deck.prototype.basicOutput = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.Deck.prototype.colorOutputHandler = function(value, _group, key) { - this.colorOutput(value, key); -}; - // colorOutput drives lights that have the palettized multicolor lights. TraktorS3.Deck.prototype.colorOutput = function(value, key) { var ledValue = this.deckBaseColor(); @@ -1412,12 +1403,6 @@ TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; -TraktorS3.wrapOutput = function(callback) { - return function(value, _group, name) { - callback(value, name); - }; -}; - // outputHandler drives lights that only have one color. TraktorS3.basicOutputHandler = function(value, group, key) { var ledValue = value; From 7fe78b24dbac3fef038993219ec054d3f236c634 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 18 Aug 2020 22:43:32 -0400 Subject: [PATCH 36/84] Traktor S3: revert colormapper changes --- .../Traktor-Kontrol-S3-hid-scripts.js | 50 ++++++++++--------- src/controllers/colormapper.cpp | 12 +---- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index ff7b6ba7724..4d5a0e59b0c 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -133,6 +133,7 @@ var TraktorS3 = new function() { this.samplerCallbacks = []; }; +// Mixxx's javascript doesn't support .bind natively, so here's a simple version. TraktorS3.bind = function(fn, obj) { return function() { return fn.apply(obj, arguments); @@ -156,7 +157,7 @@ TraktorS3.Deck = function(deckNumber, group) { // Various states this.syncPressedTimer = 0; this.previewPressed = false; - // state 0 is hotcues, 1 is samplers + // padModeState 0 is hotcues, 1 is samplers this.padModeState = 0; // Jog wheel state @@ -195,10 +196,10 @@ TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitm }; TraktorS3.Deck.prototype.defineJog = function(message, name, deckOffset, deck2Offset, callback) { - // Jog wheels have 4 byte input if (this.deckNumber === 2) { deckOffset = deck2Offset; } + // Jog wheels have four byte input: 1 byte for distance ticks, and 3 bytes for a timecode. message.addControl(this.group, name, deckOffset, "I", 0xFFFFFFFF); message.setCallback(this.group, name, TraktorS3.bind(callback, this)); }; @@ -243,9 +244,10 @@ TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { this.defineButton(messageShort, "!SelectLoop", 0x0C, 0x0F, 0x0D, 0xF0, deckFn.selectLoopHandler); this.defineButton(messageShort, "!ActivateLoop", 0x09, 0x04, 0x09, 0x20, deckFn.activateLoopHandler); - // Rev / FLUX / GRID + // Rev / Flux / Grid / Jog this.defineButton(messageShort, "!reverse", 0x01, 0x04, 0x04, 0x08, deckFn.reverseHandler); this.defineButton(messageShort, "!slip_enabled", 0x01, 0x02, 0x04, 0x04, deckFn.fluxHandler); + // Grid button this.defineButton(messageShort, "quantize", 0x01, 0x80, 0x05, 0x01, deckFn.quantizeHandler); // Beatjump @@ -260,6 +262,15 @@ TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { this.defineScaler(messageLong, "rate", 0x01, 0xFFFF, 0x0D, 0xFFFF, deckFn.pitchSliderHandler); }; +TraktorS3.Deck.prototype.shiftHandler = function(field) { + // Mixxx only knows about one shift value, but this controller has two shift buttons. + // This control object could get confused if both physical buttons are pushed at the same + // time. + engine.setValue("[Controls]", "touch_shift", field.value); + this.shiftPressed = field.value; + TraktorS3.basicOutputHandler(field.value, field.group, "!shift"); +}; + TraktorS3.Deck.prototype.playHandler = function(field) { if (this.shiftPressed) { engine.setValue(this.activeChannel, "start_stop", field.value); @@ -276,15 +287,6 @@ TraktorS3.Deck.prototype.cueHandler = function(field) { } }; -TraktorS3.Deck.prototype.shiftHandler = function(field) { - // Mixxx only knows about one shift value, but this controller has two shift buttons. - // This control object could get confused if both physical buttons are pushed at the same - // time. - engine.setValue("[Controls]", "touch_shift", field.value); - this.shiftPressed = field.value; - TraktorS3.basicOutputHandler(field.value, field.group, "!shift"); -}; - TraktorS3.Deck.prototype.syncHandler = function(field) { if (this.shiftPressed) { engine.setValue(this.activeChannel, "beatsync_phase", field.value); @@ -542,11 +544,11 @@ TraktorS3.Deck.prototype.activateBeatjumpHandler = function(field) { }; TraktorS3.Deck.prototype.reverseHandler = function(field) { - this.basicOutput(field.value, "reverse"); + // this.basicOutput(field.value, "reverse"); if (this.shiftPressed) { - engine.setValue(this.activeChannel, "reverseroll", field.value); - } else { engine.setValue(this.activeChannel, "reverse", field.value); + } else { + engine.setValue(this.activeChannel, "reverseroll", field.value); } }; @@ -1184,8 +1186,6 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover(group, "rate", true); } engine.softTakeover(group, "pitch_adjust", true); - // engine.softTakeover(group, "volume", true); - // engine.softTakeover(group, "pregain", true); engine.softTakeover("[QuickEffectRack1_" + group + "]", "super1", true); } @@ -1297,19 +1297,23 @@ TraktorS3.toggleFX = function() { // This is an AND operation. We go through each channel, and if // the fitler button is ON and the fx is ON, we turn the effect ON. // We turn OFF if either is false. - for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { - for (var ch = 1; ch <= 4; ch++) { + + // The only exception is the Filter effect. If the channel fxenable + // is off, the Filter effect is still automatically enabled. + // If the fxenable button is on, the Filter effect is only enabled if + // the Filter FX button is enabled. + for (var ch = 1; ch <= 4; ch++) { + for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; var fxKey = "group_[Channel" + ch + "]_enable"; - if (fxNumber === 5) { - fxGroup = "[QuickEffectRack1_[Channel" + ch + "]_Effect1]"; - fxKey = "enabled"; - } var newState = channel.fxEnabledState && TraktorS3.fxButtonState[fxNumber]; engine.setValue(fxGroup, fxKey, newState); } + newState = !channel.fxEnabledState || TraktorS3.fxButtonState[5]; + engine.setValue("[QuickEffectRack1_[Channel" + ch + "]_Effect1]", "enabled", + newState); } }; diff --git a/src/controllers/colormapper.cpp b/src/controllers/colormapper.cpp index 8d1a542632a..75e5128597e 100644 --- a/src/controllers/colormapper.cpp +++ b/src/controllers/colormapper.cpp @@ -17,7 +17,6 @@ double colorDistance(QRgb a, QRgb b) { // of costly computations. In contrast, this is a low-cost // approximation and should be sufficiently accurate. // More details: https://www.compuphase.com/cmetric.htm - qDebug() << "distance between " << QColor(a) << "and" << QColor(b); long mean_red = (static_cast(qRed(a)) + static_cast(qRed(b))) / 2; long delta_red = static_cast(qRed(a)) - static_cast(qRed(b)); long delta_green = static_cast(qGreen(a)) - static_cast(qGreen(b)); @@ -31,7 +30,6 @@ double colorDistance(QRgb a, QRgb b) { } // namespace QRgb ColorMapper::getNearestColor(QRgb desiredColor) { - qDebug() << "ColorMapper::getNearestColor"; // If desired color is already in cache, use cache entry const auto iCachedColor = m_cache.constFind(desiredColor); if (iCachedColor != m_cache.constEnd()) { @@ -52,18 +50,14 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { double nearestColorDistance = qInf(); for (auto i = m_availableColors.constBegin(); i != m_availableColors.constEnd(); ++i) { const QRgb availableColor = i.key(); - qDebug() << "possible color" << QColor(availableColor); double distance = colorDistance(desiredColor, availableColor); - qDebug() << "distance is " << distance; if (distance < nearestColorDistance) { - qDebug() << "new possible color"; nearestColorDistance = distance; iNearestColor = i; } } if (iNearestColor != m_availableColors.constEnd()) { const QRgb nearestColor = iNearestColor.key(); - qDebug() << "found something good!"; if (kLogger.traceEnabled()) { kLogger.trace() << "Found matching color" @@ -72,7 +66,6 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { << desiredColor; } m_cache.insert(desiredColor, nearestColor); - qDebug() << "returning " << QColor(nearestColor); return nearestColor; } @@ -82,8 +75,5 @@ QRgb ColorMapper::getNearestColor(QRgb desiredColor) { } QVariant ColorMapper::getValueForNearestColor(QRgb desiredColor) { - qDebug() << "getting value for " << desiredColor; - auto ret = m_availableColors.value(getNearestColor(desiredColor)); - qDebug() << "it's " << m_availableColors << " SO LIKE "<< ret; - return ret; + return m_availableColors.value(getNearestColor(desiredColor)); } From 4696655e3a99e7d6ffc0929098a6b49210b5ef9c Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 18 Aug 2020 22:45:26 -0400 Subject: [PATCH 37/84] Traktor S3: Make debugmode a var --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 4d5a0e59b0c..bf4298d5122 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -27,6 +27,8 @@ var TraktorS3 = new function() { this.controller = new HIDController(); + this.debugMode = false; + // ==== Friendly User Configuration ==== // The pitch slider can operate either in absolute or relative mode. // In absolute mode: @@ -1686,5 +1688,7 @@ TraktorS3.init = function(_id) { TraktorS3.lightDeck("[Channel1]", false); TraktorS3.lightDeck("[Channel2]", true); - // TraktorS3.debugLights(); + if (TraktorS3.debugMode) { + TraktorS3.debugLights(); + } }; From 3de6d0bfe03674a8d029d797ff8168e42859c392 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 19 Aug 2020 10:56:37 -0400 Subject: [PATCH 38/84] Traktor S3: enable soft takeover for quickeffect --- .../Traktor-Kontrol-S3-hid-scripts.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index bf4298d5122..464d1739fa2 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -49,7 +49,7 @@ var TraktorS3 = new function() { // "5" is the "filter" button below the other 4. It starts on but the // others start off. - this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: true}; + this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: false}; // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; @@ -919,7 +919,7 @@ TraktorS3.Deck.prototype.wheelOutputHandler = function(group, valueArray) { TraktorS3.Channel = function(parentDeck, group) { this.parentDeck = parentDeck; this.group = group; - this.fxEnabledState = true; + this.fxEnabledState = false; this.trackDurationSec = 0; this.endOfTrackTimer = 0; @@ -936,6 +936,7 @@ TraktorS3.Channel.prototype.fxEnableHandler = function(field) { return; } + engine.softTakeoverIgnoreNextValue("[QuickEffectRack1_" + this.group + "]", "super1"); this.fxEnabledState = !this.fxEnabledState; this.colorOutput(this.fxEnabledState, "!fxEnabled"); TraktorS3.toggleFX(); @@ -1257,13 +1258,16 @@ TraktorS3.headphoneHandler = function(field) { TraktorS3.superHandler = function(field) { // The super knob drives all the supers! - var group = field.group; + var chan = TraktorS3.Channels[field.group]; var value = field.value / 4095.; - engine.setParameter("[QuickEffectRack1_" + group + "]", "super1", value); - for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { - if (TraktorS3.fxButtonState[fxNumber]) { - engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); + if (chan.fxEnabledState) { + for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { + if (TraktorS3.fxButtonState[fxNumber]) { + engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); + } } + } else { + engine.setParameter("[QuickEffectRack1_" + chan.group + "]", "super1", value); } }; From f5788668c2ef3ff0c1f8076566cea34c6542f487 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 19 Aug 2020 13:53:42 -0400 Subject: [PATCH 39/84] Update res/controllers/Traktor-Kontrol-S3-hid-scripts.js remove obsolete hint config Co-authored-by: Jan Holthuis --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 464d1739fa2..ef3608395f3 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1,12 +1,4 @@ /////////////////////////////////////////////////////////////////////////////////// -// JSHint configuration // -/////////////////////////////////////////////////////////////////////////////////// -/* global controller */ -/* global HIDDebug */ -/* global HIDPacket */ -/* global HIDController */ -/* jshint -W016 */ -/////////////////////////////////////////////////////////////////////////////////// /* */ /* Traktor Kontrol S3 HID controller script v1.00 */ /* Last modification: August 2020 */ From d575a544b9ff6a63a255d5531436a8806b16983d Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 19 Aug 2020 13:53:50 -0400 Subject: [PATCH 40/84] Update res/controllers/Traktor-Kontrol-S3-hid-scripts.js Co-authored-by: Jan Holthuis --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index ef3608395f3..051ca717cd4 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -71,7 +71,7 @@ var TraktorS3 = new function() { SKY: 0x28, BLUE: 0x2C, PURPLE: 0x30, - FUSCHIA: 0x34, + FUCHSIA: 0x34, MAGENTA: 0x38, AZALEA: 0x3C, SALMON: 0x40, From 946cdf670f3cb8689cde60d12c6a00b0135389d2 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 19 Aug 2020 13:59:00 -0400 Subject: [PATCH 41/84] Traktor S3: notes --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 051ca717cd4..9a8a5207289 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -5,6 +5,8 @@ /* Author: Owen Williams */ /* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ /* */ +/* For linter: */ +/* global HIDController, HIDDebug, HIDPacket, controller */ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ @@ -1610,17 +1612,6 @@ TraktorS3.debugLights = function() { } data[i][j] = b; } - // if (i === 0) { - // for (k = 0; k < data[0].length; k++) { - // data[0][k] = 0x30; - // } - // } - // for (d = 0; d < 8; d++) { - // data[0][0x11+d] = (d+1) * 4 + 2; - // } - // for (d = 0; d < 8; d++) { - // data[0][0x2A + d] = (d + 1) * 4 + 32 + 2; - // } if (ok) { controller.send(data[i], data[i].length, 0x80 + i); } From 4a9a2da24d75fdb3bf81c245f67534ab461b5a87 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 19 Aug 2020 16:46:22 -0400 Subject: [PATCH 42/84] Update res/controllers/Traktor-Kontrol-S3-hid-scripts.js Co-authored-by: Jan Holthuis --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 9a8a5207289..43bde9d52e7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -117,7 +117,7 @@ var TraktorS3 = new function() { 0x0000CC: this.controller.LEDColors.BLUE, 0xCC00CC: this.controller.LEDColors.PURPLE, - 0xCC0091: this.controller.LEDColors.FUSCHIA, + 0xCC0091: this.controller.LEDColors.FUCHSIA, 0xCC0079: this.controller.LEDColors.MAGENTA, 0xCC477E: this.controller.LEDColors.AZALEA, 0xCC4761: this.controller.LEDColors.SALMON, From 8b849f49b292afa45f8d5e60ef6cac4567b4c563 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 20 Aug 2020 12:09:40 -0400 Subject: [PATCH 43/84] Traktor S3: make user configs global --- .../Traktor-Kontrol-S3-hid-scripts.js | 93 ++++++++++--------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 43bde9d52e7..8739b68651f 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -18,36 +18,45 @@ /* */ /////////////////////////////////////////////////////////////////////////////////// +// ==== Friendly User Configuration ==== +// The pitch slider can operate either in absolute or relative mode. +// In absolute mode: +// * Moving the pitch slider works like normal +// * Mixxx will use soft-takeover +// * Pressing shift will adjust musical pitch instead of rate +// * Keylock toggles on with down-press. +// +// In relative mode: +// * The slider always moves, unless it has hit the end of the range inside Mixxx +// * No soft-takeover +// * Hold shift to move the pitch slider without adjusting the rate +// * Hold keylock and move the pitch slider to adjust musical pitch +// * keylock will still toggle on, but on release, not press. +var TraktorS3PitchSliderRelativeMode = true; + +// You can choose whatever colors you like for each channel. The list of colors is: +// RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, +// PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE +var TraktorS3ChannelColors = { + "[Channel1]": "CARROT", + "[Channel2]": "CARROT", + "[Channel3]": "BLUE", + "[Channel4]": "BLUE" +}; + +// Set to true to output debug messages and debug light outputs. +var TraktorS3DebugMode = false; + + var TraktorS3 = new function() { this.controller = new HIDController(); - this.debugMode = false; - - // ==== Friendly User Configuration ==== - // The pitch slider can operate either in absolute or relative mode. - // In absolute mode: - // * Moving the pitch slider works like normal - // * Mixxx will use soft-takeover - // * Pressing shift will adjust musical pitch instead of rate - // * Keylock toggles on with down-press. - // - // In relative mode: - // * The slider always moves, unless it has hit the end of the range inside Mixxx - // * No soft-takeover - // * Hold shift to move the pitch slider without adjusting the rate - // * Hold keylock and move the pitch slider to adjust musical pitch - // * keylock will still toggle on, but on release, not press. - this.pitchSliderRelativeMode = true; - - // State for relative mode - - // "5" is the "filter" button below the other 4. It starts on but the - // others start off. - this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: false}; - // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; + // "5" is the "filter" button below the other 4. + this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: false}; + // Microphone button this.microphonePressedTimer = 0; // Timer to distinguish between short and long press @@ -84,14 +93,6 @@ var TraktorS3 = new function() { this.LEDDimValue = 0x00; this.LEDBrightValue = 0x02; - // More User-friendly config: you can choose whatever colors you like for each deck! - this.deckOutputColors = { - "[Channel1]": "CARROT", - "[Channel2]": "CARROT", - "[Channel3]": "BLUE", - "[Channel4]": "BLUE" - }; - // FX 5 is the Filter this.fxLEDValue = { 1: this.controller.LEDColors.RED, @@ -330,7 +331,7 @@ TraktorS3.Deck.prototype.keylockHandler = function(field) { } return; } - if (TraktorS3.pitchSliderRelativeMode) { + if (TraktorS3PitchSliderRelativeMode) { if (field.value) { // In relative mode on down-press, reset the values and note that // the button is pressed. @@ -678,7 +679,7 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { var value = field.value / 4095; - if (TraktorS3.pitchSliderRelativeMode) { + if (TraktorS3PitchSliderRelativeMode) { if (this.pitchSliderLastValue === -1) { this.pitchSliderLastValue = value; } else { @@ -798,7 +799,7 @@ TraktorS3.Deck.prototype.linkOutputs = function() { }; TraktorS3.Deck.prototype.deckBaseColor = function() { - return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.activeChannel]]; + return TraktorS3.controller.LEDColors[TraktorS3ChannelColors[this.activeChannel]]; }; // basicOutput drives lights that only have one color. @@ -1036,7 +1037,7 @@ TraktorS3.Channel.prototype.linkOutputs = function() { }; TraktorS3.Channel.prototype.channelBaseColor = function() { - return TraktorS3.controller.LEDColors[TraktorS3.deckOutputColors[this.group]]; + return TraktorS3.controller.LEDColors[TraktorS3ChannelColors[this.group]]; }; // colorOutput drives lights that have the palettized multicolor lights. @@ -1179,7 +1180,7 @@ TraktorS3.registerInputPackets = function() { // Soft takeovers for (var ch = 1; ch <= 4; ch++) { group = "[Channel" + ch + "]"; - if (!TraktorS3.pitchSliderRelativeMode) { + if (!TraktorS3PitchSliderRelativeMode) { engine.softTakeover(group, "rate", true); } engine.softTakeover(group, "pitch_adjust", true); @@ -1537,17 +1538,17 @@ TraktorS3.lightDeck = function(group, sendPackets) { // Selected deck lights var ctrlr = TraktorS3.controller; if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3.deckOutputColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); } TraktorS3.batchingOutputs = false; @@ -1675,7 +1676,7 @@ TraktorS3.init = function(_id) { TraktorS3.lightDeck("[Channel1]", false); TraktorS3.lightDeck("[Channel2]", true); - if (TraktorS3.debugMode) { + if (TraktorS3DebugMode) { TraktorS3.debugLights(); } }; From bb2394fcba512f12446622352750d1b3366ba142 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 20 Aug 2020 12:21:18 -0400 Subject: [PATCH 44/84] Traktor S3: make button brightnesses global too --- .../Traktor-Kontrol-S3-hid-scripts.js | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 8739b68651f..cf31bd83cfd 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -34,9 +34,10 @@ // * keylock will still toggle on, but on release, not press. var TraktorS3PitchSliderRelativeMode = true; -// You can choose whatever colors you like for each channel. The list of colors is: +// You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, // PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE +// Some colors may look odd because of how they are encoded inside the controller. var TraktorS3ChannelColors = { "[Channel1]": "CARROT", "[Channel2]": "CARROT", @@ -44,6 +45,10 @@ var TraktorS3ChannelColors = { "[Channel4]": "BLUE" }; +// Each color has four brightnesses, so these values can be between 0 and 3. +var TraktorS3LEDDimValue = 0x00; +var TraktorS3LEDBrightValue = 0x02; + // Set to true to output debug messages and debug light outputs. var TraktorS3DebugMode = false; @@ -57,9 +62,6 @@ var TraktorS3 = new function() { // "5" is the "filter" button below the other 4. this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: false}; - // Microphone button - this.microphonePressedTimer = 0; // Timer to distinguish between short and long press - // VuMeter this.masterVuConnections = { "VuMeterL": {}, @@ -89,9 +91,6 @@ var TraktorS3 = new function() { WHITE: 0x44 }; - // Each color has four brightnesses. - this.LEDDimValue = 0x00; - this.LEDBrightValue = 0x02; // FX 5 is the Filter this.fxLEDValue = { @@ -819,9 +818,9 @@ TraktorS3.Deck.prototype.colorOutput = function(value, key) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; @@ -843,12 +842,12 @@ TraktorS3.Deck.prototype.lightHotcue = function(number) { var ledValue = TraktorS3.controller.LEDColors.WHITE; if (loaded) { ledValue = this.colorForHotcue(number); - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } if (active) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput(this.group, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); }; @@ -883,7 +882,7 @@ TraktorS3.Deck.prototype.wheelOutputByValue = function(group, value) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { ledValue = 0x00; } @@ -1044,9 +1043,9 @@ TraktorS3.Channel.prototype.channelBaseColor = function() { TraktorS3.Channel.prototype.colorOutput = function(value, key) { var ledValue = this.channelBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; @@ -1286,9 +1285,9 @@ TraktorS3.fxHandler = function(field) { TraktorS3.fxButtonState[fxNumber] = !TraktorS3.fxButtonState[fxNumber]; var ledValue = TraktorS3.fxLEDValue[fxNumber]; if (TraktorS3.fxButtonState[fxNumber]) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); TraktorS3.toggleFX(); @@ -1498,9 +1497,9 @@ TraktorS3.lightFX = function() { for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; if (TraktorS3.fxButtonState[fxNumber]) { - ledValue += TraktorS3.LEDBrightValue; + ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3.LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); } @@ -1538,17 +1537,17 @@ TraktorS3.lightDeck = function(group, sendPackets) { // Selected deck lights var ctrlr = TraktorS3.controller; if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3LEDBrightValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3LEDDimValue, false); } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3LEDBrightValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3LEDDimValue, false); } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3LEDBrightValue, false); + ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3LEDDimValue, false); } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); + ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3LEDBrightValue, false); + ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3LEDDimValue, false); } TraktorS3.batchingOutputs = false; From 2b8c81733edb3ab428d3ce064ed3ddbe16de0113 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 20 Aug 2020 18:32:41 -0400 Subject: [PATCH 45/84] Traktor S3: Implement channel 4 input mode Fix some whoopses --- .../Traktor-Kontrol-S3-hid-scripts.js | 213 +++++++++++++----- 1 file changed, 152 insertions(+), 61 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index cf31bd83cfd..3f246eeafd8 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -10,9 +10,6 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ -/* * should we be lighting things inside input handlers? no because we want */ -/* things to light up if activated in GUI, not controller. */ -/* * touch for track browse, loop control, beatjump? */ /* * jog button */ /* * star button */ /* */ @@ -32,7 +29,7 @@ // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. -var TraktorS3PitchSliderRelativeMode = true; +var TraktorS3PitchSliderRelativeMode = false; // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, @@ -125,6 +122,12 @@ var TraktorS3 = new function() { 0xCCCCCC: this.controller.LEDColors.WHITE, }); + // State for controller input loudness setting + this.inputModeLine = false; + + // If true, channel 4 is in input mode + this.channel4InputMode = false; + // callbacks this.samplerCallbacks = []; }; @@ -382,7 +385,6 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { } else { action = "_activate"; } - HIDDebug("setting " + "hotcue_" + padNumber + action + " " + field.value); engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); return; } @@ -1024,18 +1026,21 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { }; TraktorS3.Channel.prototype.linkOutputs = function() { - this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.bind(TraktorS3.Channel.prototype.channelVuMeterHandler, this)); + this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.channelVuMeterHandler); this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutputHandler); for (var j = 1; j <= 8; j++) { this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); - TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.basicOutputHandler); + TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutputHandler); } }; TraktorS3.Channel.prototype.channelBaseColor = function() { + if (this.group === "[Channel4]" && TraktorS3.channel4InputMode) { + return TraktorS3.controller.LEDColors[TraktorS3.controller.LEDColors.OFF]; + } return TraktorS3.controller.LEDColors[TraktorS3ChannelColors[this.group]]; }; @@ -1045,7 +1050,7 @@ TraktorS3.Channel.prototype.colorOutput = function(value, key) { if (value === 1 || value === true) { ledValue += TraktorS3LEDBrightValue; } else { - ledValue += TraktorS3LEDDimValue; + ledValue += TraktorS3LEDDimValue; } TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; @@ -1065,37 +1070,6 @@ TraktorS3.Channel.prototype.hotcuesOutputHandler = function(_value, group, key) deck.lightHotcue(cueNum); }; -TraktorS3.Channel.prototype.channelVuMeterHandler = function(value, _group, key) { - this.vuMeterHandler(value, key, 14); -}; - -TraktorS3.Channel.prototype.masterVuMeterHandler = function(value, _group, key) { - this.vuMeterHandler(value, key, 8); -}; - -TraktorS3.Channel.prototype.vuMeterHandler = function(value, key, segments) { - // return; - // This handler is called a lot so it should be as fast as possible. - var scaledValue = value * segments; - var fullIllumCount = Math.floor(scaledValue); - - // Figure out how much the partially-illuminated segment is illuminated. - var partialIllum = (scaledValue - fullIllumCount) * 0x7F; - - for (var i = 0; i < segments; i++) { - var segmentKey = "!" + key + i; - if (i < fullIllumCount) { - // Don't update lights until they're all done, so the last term is false. - TraktorS3.controller.setOutput(this.group, segmentKey, 0x7F, false); - } else if (i === fullIllumCount) { - TraktorS3.controller.setOutput(this.group, segmentKey, partialIllum, false); - } else { - TraktorS3.controller.setOutput(this.group, segmentKey, 0x00, false); - } - } - TraktorS3.controller.OutputPackets["outputB"].send(); -}; - TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -1124,10 +1098,10 @@ TraktorS3.registerInputPackets = function() { TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); // Headphone buttons - this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.buttonHandler); - this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.buttonHandler); - this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.buttonHandler); - this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.buttonHandler); + this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.headphoneHandler); // FX Buttons this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); @@ -1136,6 +1110,9 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); + // EXT Button + this.registerInputButton(messageShort, "[Master]", "!extButton", 0x07, 0x04, this.extModeHandler); + this.controller.registerInputPacket(messageShort); this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); @@ -1183,9 +1160,14 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover(group, "rate", true); } engine.softTakeover(group, "pitch_adjust", true); + engine.softTakeover(group, "volume", true); + engine.softTakeover(group, "pregain", true); engine.softTakeover("[QuickEffectRack1_" + group + "]", "super1", true); } + engine.softTakeover("[Microphone]", "volume", true); + engine.softTakeover("[Microphone]", "pregain", true); + engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter1", true); engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter2", true); engine.softTakeover("[EqualizerRack1_[Channel1]_Effect1]", "parameter3", true); @@ -1240,14 +1222,22 @@ TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, }; TraktorS3.parameterHandler = function(field) { - engine.setParameter(field.group, field.name, field.value / 4095); + if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + engine.setParameter("[Microphone]", field.name, field.value / 4095); + } else { + engine.setParameter(field.group, field.name, field.value / 4095); + } }; TraktorS3.headphoneHandler = function(field) { if (field.value === 0) { return; } - script.toggleControl(field.group, "pfl"); + if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + script.toggleControl("[Microphone]", "pfl"); + } else { + script.toggleControl(field.group, "pfl"); + } }; TraktorS3.superHandler = function(field) { @@ -1317,6 +1307,29 @@ TraktorS3.toggleFX = function() { } }; +TraktorS3.extModeHandler = function(field) { + if (!field.value) { + TraktorS3.basicOutputHandler(TraktorS3.channel4InputMode, field.group, field.name); + return; + } + if (TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed) { + TraktorS3.basicOutputHandler(field.value, field.group, field.name); + TraktorS3.inputModeLine = !TraktorS3.inputModeLine; + TraktorS3.setInputLineMode(TraktorS3.inputModeLine); + return; + } + TraktorS3.channel4InputMode = !TraktorS3.channel4InputMode; + if (TraktorS3.channel4InputMode) { + engine.softTakeoverIgnoreNextValue("[Microphone]", "volume"); + engine.softTakeoverIgnoreNextValue("[Microphone]", "pregain"); + } else { + engine.softTakeoverIgnoreNextValue("[Channel4]", "volume"); + engine.softTakeoverIgnoreNextValue("[Channel4]", "pregain"); + } + TraktorS3.lightDeck("[Channel4]"); + TraktorS3.basicOutputHandler(TraktorS3.channel4InputMode, field.group, field.name); +}; + TraktorS3.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); var outputB = new HIDPacket("outputB", 0x81); @@ -1347,6 +1360,8 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Channel2]", "!fxEnabled", 0x36, "B"); outputA.addOutput("[Channel4]", "!fxEnabled", 0x37, "B"); + outputA.addOutput("[Master]", "!extButton", 0x33, "B"); + this.controller.registerOutputPacket(outputA); var VuOffsets = { @@ -1390,6 +1405,8 @@ TraktorS3.registerOutputPackets = function() { chan.linkOutputs(); } + engine.connectControl("[Microphone]", "pfl", this.pflOutputHandler); + // Master VuMeters this.masterVuConnections["VuMeterL"] = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); this.masterVuConnections["VuMeterR"] = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); @@ -1407,6 +1424,21 @@ TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; +TraktorS3.pflOutputHandler = function(value, group, key) { + if (group === "[Microphone]" && TraktorS3.channel4InputMode) { + TraktorS3.basicOutputHandler(value, "[Channel4]", key); + return; + } + if (group === "[Channel4]" && !TraktorS3.channel4InputMode) { + TraktorS3.basicOutputHandler(value, group, key); + return; + } + if (group.match(/^\[Channel[123]\]$/)) { + TraktorS3.basicOutputHandler(value, group, key); + } + // Unhandled case, ignore. +}; + // outputHandler drives lights that only have one color. TraktorS3.basicOutputHandler = function(value, group, key) { var ledValue = value; @@ -1430,6 +1462,36 @@ TraktorS3.peakOutputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; +TraktorS3.channelVuMeterHandler = function(value, group, key) { + TraktorS3.vuMeterHandler(value, group, key, 14); +}; + +TraktorS3.masterVuMeterHandler = function(value, group, key) { + TraktorS3.vuMeterHandler(value, group, key, 8); +}; + +TraktorS3.vuMeterHandler = function(value, group, key, segments) { + // This handler is called a lot so it should be as fast as possible. + var scaledValue = value * segments; + var fullIllumCount = Math.floor(scaledValue); + + // Figure out how much the partially-illuminated segment is illuminated. + var partialIllum = (scaledValue - fullIllumCount) * 0x7F; + + for (var i = 0; i < segments; i++) { + var segmentKey = "!" + key + i; + if (i < fullIllumCount) { + // Don't update lights until they're all done, so the last term is false. + TraktorS3.controller.setOutput(group, segmentKey, 0x7F, false); + } else if (i === fullIllumCount) { + TraktorS3.controller.setOutput(group, segmentKey, partialIllum, false); + } else { + TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); + } + } + TraktorS3.controller.OutputPackets["outputB"].send(); +}; + TraktorS3.resolveSampler = function(group) { if (group === undefined) { return undefined; @@ -1492,7 +1554,11 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { TraktorS3.lightFX = function() { for (var ch in TraktorS3.Channels) { var chanob = TraktorS3.Channels[ch]; - chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); + if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { + chanob.colorOutput(false, "!fxEnabled"); + } else { + chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); + } } for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; @@ -1531,6 +1597,9 @@ TraktorS3.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!PreviewTrack"); deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); + if (group === "[Channel4]") { + TraktorS3.basicOutputHandler(0, "[Master]", "!extButton"); + } } TraktorS3.lightFX(); @@ -1559,6 +1628,18 @@ TraktorS3.lightDeck = function(group, sendPackets) { } }; +// A special packet sent to the controller switches between mic and line +// input modes. if lineMode is true, sets input to line. Otherwise, mic. +TraktorS3.setInputLineMode = function(lineMode) { + var packet = Object(); + packet.length = 33; + packet[0] = 0x20; + if (lineMode) { + packet[1] = 0x08; + } + controller.send(packet, packet.length, 0xF4); +}; + TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { if (Object.prototype.hasOwnProperty.call(data, name)) { @@ -1575,20 +1656,23 @@ TraktorS3.debugLights = function() { // Call this if you want to just send raw packets to the controller (good for figuring out what // bytes do what). var dataStrings = [ - " 7C 00 35 2C 2C FF 2C 39 FF 00 FF FF 00 35 " + - "00 2C 7E 00 00 FF FF FF 2C 2C 20 7C 7C 00 FF FF " + - "FF 00 00 00 FF FF FF 2C 00 FF 2C 7C FF 00 00 00 " + - "00 00 00 00 7E 0C FF FF 0C FF FF FF FF FF FF FF " + - "FF FF 40 FF FF FF 00 FF FF 2E FF 00 00 FF 00 00 " + - "00 00 FF 00 ", - " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "22 22 22 22 00 00 00 00 00 00 00 00 00 00 00 00 " + + "22 22 22 22 00 00 00 00 00 00 00 00 00 00 00 00 " + + "FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 ", + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 FF FF FF 00 FF 00 00 00 FF 00 00", + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + "20 08 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00" ]; - var data = [Object(), Object()]; + var data = [Object(), Object(), Object()]; for (var i = 0; i < data.length; i++) { @@ -1613,9 +1697,14 @@ TraktorS3.debugLights = function() { data[i][j] = b; } if (ok) { - controller.send(data[i], data[i].length, 0x80 + i); + var header = 0x80 + i; + if (i === 2) { + header = 0xF4; + } + controller.send(data[i], data[i].length, header); } } + TraktorS3.setInputLineMode(false); }; TraktorS3.shutdown = function() { @@ -1670,12 +1759,14 @@ TraktorS3.init = function(_id) { TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - TraktorS3.lightDeck("[Channel3]", false); - TraktorS3.lightDeck("[Channel4]", false); - TraktorS3.lightDeck("[Channel1]", false); - TraktorS3.lightDeck("[Channel2]", true); - if (TraktorS3DebugMode) { TraktorS3.debugLights(); + } else { + TraktorS3.lightDeck("[Channel3]", false); + TraktorS3.lightDeck("[Channel4]", false); + TraktorS3.lightDeck("[Channel1]", false); + TraktorS3.lightDeck("[Channel2]", true); } + + TraktorS3.setInputLineMode(TraktorS3.inputModeLine); }; From 05d6a99fc67791e202fe1b5bc1e99cb1f5cf3a78 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 20 Aug 2020 22:08:33 -0400 Subject: [PATCH 46/84] Traktor S3: remove redundant connections --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 3f246eeafd8..5941085eede 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1028,12 +1028,12 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { TraktorS3.Channel.prototype.linkOutputs = function() { this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.channelVuMeterHandler); this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutputHandler); + TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutputHandler); for (var j = 1; j <= 8; j++) { this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); - TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutputHandler); } }; From 0d70fe8c7aa6bc49821602dfc5cff2a9268acd62 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 20 Aug 2020 22:38:23 -0400 Subject: [PATCH 47/84] Traktor S3: FX for input channel --- .../Traktor-Kontrol-S3-hid-scripts.js | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 5941085eede..6a27934f90f 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -128,6 +128,8 @@ var TraktorS3 = new function() { // If true, channel 4 is in input mode this.channel4InputMode = false; + this.inputFxEnabledState = false; + // callbacks this.samplerCallbacks = []; }; @@ -933,8 +935,13 @@ TraktorS3.Channel.prototype.fxEnableHandler = function(field) { } engine.softTakeoverIgnoreNextValue("[QuickEffectRack1_" + this.group + "]", "super1"); - this.fxEnabledState = !this.fxEnabledState; - this.colorOutput(this.fxEnabledState, "!fxEnabled"); + if (this.group === "[Channel4]" && TraktorS3.channel4InputMode) { + TraktorS3.inputFxEnabledState = !TraktorS3.inputFxEnabledState; + this.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); + } else { + this.fxEnabledState = !this.fxEnabledState; + this.colorOutput(this.fxEnabledState, "!fxEnabled"); + } TraktorS3.toggleFX(); }; @@ -1241,10 +1248,14 @@ TraktorS3.headphoneHandler = function(field) { }; TraktorS3.superHandler = function(field) { - // The super knob drives all the supers! + // The super knob drives all the supers -- if they are enabled. var chan = TraktorS3.Channels[field.group]; var value = field.value / 4095.; - if (chan.fxEnabledState) { + var enabled = chan.fxEnabledState; + if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + enabled = TraktorS3.inputFxEnabledState; + } + if (enabled) { for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { if (TraktorS3.fxButtonState[fxNumber]) { engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); @@ -1293,15 +1304,21 @@ TraktorS3.toggleFX = function() { // If the fxenable button is on, the Filter effect is only enabled if // the Filter FX button is enabled. for (var ch = 1; ch <= 4; ch++) { + var chEnabled = channel.fxEnabledState; + if (ch === 4 && TraktorS3.channel4InputMode) { + chEnabled = TraktorS3.inputFxEnabledState; + } for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; var fxKey = "group_[Channel" + ch + "]_enable"; - - var newState = channel.fxEnabledState && TraktorS3.fxButtonState[fxNumber]; + var newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; + if (ch === 4 && TraktorS3.channel4InputMode) { + fxKey = "group_[Microphone]_enable"; + } engine.setValue(fxGroup, fxKey, newState); } - newState = !channel.fxEnabledState || TraktorS3.fxButtonState[5]; + newState = !chEnabled || TraktorS3.fxButtonState[5]; engine.setValue("[QuickEffectRack1_[Channel" + ch + "]_Effect1]", "enabled", newState); } @@ -1555,7 +1572,7 @@ TraktorS3.lightFX = function() { for (var ch in TraktorS3.Channels) { var chanob = TraktorS3.Channels[ch]; if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { - chanob.colorOutput(false, "!fxEnabled"); + chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); } else { chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); } From 55e32a13a3230e106f4716e8bcd6a7cbda7c6848 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Fri, 21 Aug 2020 17:09:54 -0400 Subject: [PATCH 48/84] Traktor S3: fix keylock light and input fx microphone doesn't have a quickeffect. Make master gain adjustable with shift+gain --- .../Traktor-Kontrol-S3-hid-scripts.js | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6a27934f90f..08edb6b5367 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -269,6 +269,9 @@ TraktorS3.Deck.prototype.shiftHandler = function(field) { // time. engine.setValue("[Controls]", "touch_shift", field.value); this.shiftPressed = field.value; + if (field.value) { + engine.softTakeoverIgnoreNextValue("[Master]", "gain"); + } TraktorS3.basicOutputHandler(field.value, field.group, "!shift"); }; @@ -318,12 +321,10 @@ TraktorS3.Deck.prototype.syncHandler = function(field) { // LED is turned off by the callback handler for sync_enabled engine.setValue(this.activeChannel, "sync_enabled", 0); } - } else { - if (this.syncPressedTimer !== 0) { - // Timer still running -> stop it and unlight LED - engine.stopTimer(this.syncPressedTimer); - this.colorOutput(0, "sync_enabled"); - } + } else if (this.syncPressedTimer !== 0) { + // Timer still running -> stop it and unlight LED + engine.stopTimer(this.syncPressedTimer); + this.colorOutput(0, "sync_enabled"); } }; @@ -333,30 +334,32 @@ TraktorS3.Deck.prototype.keylockHandler = function(field) { if (field.value) { engine.setValue(this.activeChannel, "pitch_adjust_set_default", 1); } - return; - } - if (TraktorS3PitchSliderRelativeMode) { + } else if (TraktorS3PitchSliderRelativeMode) { if (field.value) { // In relative mode on down-press, reset the values and note that // the button is pressed. this.keylockPressed = true; this.keyAdjusted = false; - return; - } - // On release, note that the button is released, and if the key *wasn't* adjusted, - // activate keylock. - this.keylockPressed = false; - if (!this.keyAdjusted) { - script.toggleControl(this.activeChannel, "keylock"); + } else { + // On release, note that the button is released, and if the key *wasn't* adjusted, + // activate keylock. + this.keylockPressed = false; + if (!this.keyAdjusted) { + script.toggleControl(this.activeChannel, "keylock"); + } } - return; + } else if (field.value) { + // In absolute mode, do a simple toggle on down-press. + script.toggleControl(this.activeChannel, "keylock"); } - // By default, do a basic press-to-toggle action. - if (field.value === 0) { - return; + // Adjust the light on release depending on keylock status. Down-press is always lit. + if (!field.value) { + var val = engine.getValue(this.activeChannel, "keylock"); + this.colorOutput(val, "keylock"); + } else { + this.colorOutput(1, "keylock"); } - script.toggleControl(this.activeChannel, "keylock"); }; // This handles when the mode buttons for the pads is pressed. @@ -1154,7 +1157,7 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[Channel4]", "!super", 0x3D, 0xFFFF, this.superHandler); this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.parameterHandler); + this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.masterGainHandler); this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); @@ -1190,8 +1193,8 @@ TraktorS3.registerInputPackets = function() { // engine.softTakeover("[Master]", "crossfader", true); engine.softTakeover("[Master]", "gain", true); - engine.softTakeover("[Master]", "headMix", true); - engine.softTakeover("[Master]", "headGain", true); + // engine.softTakeover("[Master]", "headMix", true); + // engine.softTakeover("[Master]", "headGain", true); for (var i = 1; i <= 16; ++i) { engine.softTakeover("[Sampler" + i + "]", "pregain", true); @@ -1236,6 +1239,14 @@ TraktorS3.parameterHandler = function(field) { } }; +TraktorS3.masterGainHandler = function(field) { + // Only adjust if shift is held. This will still adjust the sound card + // volume but it at least allows for control of Mixxx's master gain. + if (TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed) { + engine.setParameter(field.group, field.name, field.value / 4095); + } +}; + TraktorS3.headphoneHandler = function(field) { if (field.value === 0) { return; @@ -1261,7 +1272,8 @@ TraktorS3.superHandler = function(field) { engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); } } - } else { + } else if (field.group !== "[Channel4]" || !TraktorS3.channel4InputMode) { + // There is no quickeffect for the microphone. engine.setParameter("[QuickEffectRack1_" + chan.group + "]", "super1", value); } }; @@ -1307,20 +1319,22 @@ TraktorS3.toggleFX = function() { var chEnabled = channel.fxEnabledState; if (ch === 4 && TraktorS3.channel4InputMode) { chEnabled = TraktorS3.inputFxEnabledState; + } else { + // There is no quickeffect for the microphone + var newState = !chEnabled || TraktorS3.fxButtonState[5]; + engine.setValue("[QuickEffectRack1_[Channel" + ch + "]_Effect1]", "enabled", + newState); } for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; var fxKey = "group_[Channel" + ch + "]_enable"; - var newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; + newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; if (ch === 4 && TraktorS3.channel4InputMode) { fxKey = "group_[Microphone]_enable"; } engine.setValue(fxGroup, fxKey, newState); } - newState = !chEnabled || TraktorS3.fxButtonState[5]; - engine.setValue("[QuickEffectRack1_[Channel" + ch + "]_Effect1]", "enabled", - newState); } }; @@ -1651,7 +1665,7 @@ TraktorS3.setInputLineMode = function(lineMode) { var packet = Object(); packet.length = 33; packet[0] = 0x20; - if (lineMode) { + if (!lineMode) { packet[1] = 0x08; } controller.send(packet, packet.length, 0xF4); From d89004d8815efbd0e5d7d2582cd5612669699216 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 23 Aug 2020 12:27:25 -0400 Subject: [PATCH 49/84] Traktor S3: toggle whole chain --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 08edb6b5367..49141d47158 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1322,7 +1322,7 @@ TraktorS3.toggleFX = function() { } else { // There is no quickeffect for the microphone var newState = !chEnabled || TraktorS3.fxButtonState[5]; - engine.setValue("[QuickEffectRack1_[Channel" + ch + "]_Effect1]", "enabled", + engine.setValue("[QuickEffectRack1_[Channel" + ch + "]]", "enabled", newState); } for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { From ce52010a71a6e818eb2bdac151e430539ffef4a7 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 23 Aug 2020 23:00:12 -0400 Subject: [PATCH 50/84] Traktor S3: batch light updates for less HID spamming The wheels and VU meters cause a lot of packets to be sent -- instead, batch up the values and send them every gui tick. Also simplify FX routing by only using super knob for the filter. --- .../Traktor-Kontrol-S3-hid-scripts.js | 235 +++++++++++------- 1 file changed, 141 insertions(+), 94 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 49141d47158..6417930ea0d 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -29,7 +29,7 @@ // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. -var TraktorS3PitchSliderRelativeMode = false; +var TraktorS3PitchSliderRelativeMode = true; // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, @@ -59,12 +59,21 @@ var TraktorS3 = new function() { // "5" is the "filter" button below the other 4. this.fxButtonState = {1: false, 2: false, 3: false, 4: false, 5: false}; - // VuMeter - this.masterVuConnections = { - "VuMeterL": {}, - "VuMeterR": {} + this.masterVuMeter = { + "VuMeterL": { + connection: null, + updated: false, + value: 0 + }, + "VuMeterR": { + connection: null, + updated: false, + value: 0 + } }; + this.guiTickConnection = {}; + // The S3 has a set of predefined colors for many buttons. They are not // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. this.controller.LEDColors = { @@ -272,7 +281,7 @@ TraktorS3.Deck.prototype.shiftHandler = function(field) { if (field.value) { engine.softTakeoverIgnoreNextValue("[Master]", "gain"); } - TraktorS3.basicOutputHandler(field.value, field.group, "!shift"); + TraktorS3.basicOutput(field.value, field.group, "!shift"); }; TraktorS3.Deck.prototype.playHandler = function(field) { @@ -787,21 +796,21 @@ TraktorS3.Deck.prototype.defineLink = function(key, callback) { TraktorS3.Deck.prototype.linkOutputs = function() { var deckFn = TraktorS3.Deck.prototype; - var colorOutputHandler = function(value, _group, key) { + var colorOutput = function(value, _group, key) { this.colorOutput(value, key); }; - var basicOutputHandler = function(value, _group, key) { + var basicOutput = function(value, _group, key) { this.basicOutput(value, key); }; this.defineLink("play_indicator", TraktorS3.bind(deckFn.playIndicatorHandler, this)); - this.defineLink("cue_indicator", TraktorS3.bind(colorOutputHandler, this)); - this.defineLink("sync_enabled", TraktorS3.bind(colorOutputHandler, this)); - this.defineLink("keylock", TraktorS3.bind(colorOutputHandler, this)); - this.defineLink("slip_enabled", TraktorS3.bind(colorOutputHandler, this)); - this.defineLink("quantize", TraktorS3.bind(colorOutputHandler, this)); - this.defineLink("reverse", TraktorS3.bind(basicOutputHandler, this)); + this.defineLink("cue_indicator", TraktorS3.bind(colorOutput, this)); + this.defineLink("sync_enabled", TraktorS3.bind(colorOutput, this)); + this.defineLink("keylock", TraktorS3.bind(colorOutput, this)); + this.defineLink("slip_enabled", TraktorS3.bind(colorOutput, this)); + this.defineLink("quantize", TraktorS3.bind(colorOutput, this)); + this.defineLink("reverse", TraktorS3.bind(basicOutput, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { @@ -893,20 +902,19 @@ TraktorS3.Deck.prototype.wheelOutputByValue = function(group, value) { } else { ledValue = 0x00; } - this.wheelOutputHandler(group, + this.wheelOutput(group, [ledValue, ledValue, ledValue, ledValue, ledValue, ledValue, ledValue, ledValue]); }; -TraktorS3.Deck.prototype.wheelOutputHandler = function(group, valueArray) { +TraktorS3.Deck.prototype.wheelOutput = function(group, valueArray) { if (group !== this.activeChannel) { return; } - var sendPacket = !TraktorS3.batchingOutputs; for (var i = 0; i < 8; i++) { TraktorS3.controller.setOutput(this.group, "!wheel" + i, valueArray[i], false); } - if (sendPacket) { + if (!TraktorS3.batchingOutputs) { for (var packetName in TraktorS3.controller.OutputPackets) { TraktorS3.controller.OutputPackets[packetName].send(); } @@ -923,9 +931,13 @@ TraktorS3.Channel = function(parentDeck, group) { this.fxEnabledState = false; this.trackDurationSec = 0; + this.positionUpdated = false; + this.curPosition = -1; this.endOfTrackTimer = 0; this.endOfTrack = false; this.endOfTrackBlinkState = 0; + this.vuMeterUpdated = false; + this.vuMeterValue = 0; this.vuConnection = {}; this.clipConnection = {}; @@ -937,7 +949,6 @@ TraktorS3.Channel.prototype.fxEnableHandler = function(field) { return; } - engine.softTakeoverIgnoreNextValue("[QuickEffectRack1_" + this.group + "]", "super1"); if (this.group === "[Channel4]" && TraktorS3.channel4InputMode) { TraktorS3.inputFxEnabledState = !TraktorS3.inputFxEnabledState; this.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); @@ -1003,47 +1014,24 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { return; } } - var elapsed = value * this.trackDurationSec; + this.curPosition = value * this.trackDurationSec; + this.positionUpdated = true; +}; - var rotations = elapsed * (1 / 1.8); // 1/1.8 is rotations per second - // Calculate angle from 0-1.0 - var angle = rotations - Math.floor(rotations); - // The wheel has 8 segments - var wheelAngle = 8.0 * angle; - var baseLedValue = this.channelBaseColor(); - // Reduce the dimming distance at the end of track. - var dimDistance = this.endOfTrack ? 2.5 : 1.5; - var segValues = [0, 0, 0, 0, 0, 0, 0, 0]; - for (var seg = 0; seg < 8; seg++) { - var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); - var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); - if (this.endOfTrack) { - dimDistance = 1.5; - brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); - if (this.endOfTrackBlinkState) { - brightVal = brightVal > 0x03 ? 0x04 : 0x02; - } else { - brightVal = brightVal > 0x02 ? 0x04 : 0x00; - } - } - if (brightVal <= 0) { - segValues[seg] = 0x00; - } else { - segValues[seg] = baseLedValue + brightVal - 1; - } - } - this.parentDeck.wheelOutputHandler(this.group, segValues); +TraktorS3.Channel.prototype.vuMeterHandler = function(value) { + this.vuMeterUpdated = true; + this.vuMeterValue = value; }; TraktorS3.Channel.prototype.linkOutputs = function() { - this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.channelVuMeterHandler); - this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutputHandler); - TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutputHandler); + this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.bind(TraktorS3.Channel.prototype.vuMeterHandler, this)); + this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutput); + TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutput); for (var j = 1; j <= 8; j++) { this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", - TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); + TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", - TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutputHandler, this))); + TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); } }; @@ -1065,7 +1053,7 @@ TraktorS3.Channel.prototype.colorOutput = function(value, key) { TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.Channel.prototype.hotcuesOutputHandler = function(_value, group, key) { +TraktorS3.Channel.prototype.hotcuesOutput = function(_value, group, key) { var deck = TraktorS3.Channels[group].parentDeck; if (deck.activeChannel !== group) { // Not active, ignore @@ -1080,6 +1068,43 @@ TraktorS3.Channel.prototype.hotcuesOutputHandler = function(_value, group, key) deck.lightHotcue(cueNum); }; +// Returns true if there was an update. +TraktorS3.Channel.prototype.lightWheelPosition = function() { + if (!this.positionUpdated) { + return false; + } + this.positionUpdated = false; + var rotations = this.curPosition * (1 / 1.8); // 1/1.8 is rotations per second + // Calculate angle from 0-1.0 + var angle = rotations - Math.floor(rotations); + // The wheel has 8 segments + var wheelAngle = 8.0 * angle; + var baseLedValue = this.channelBaseColor(); + // Reduce the dimming distance at the end of track. + var dimDistance = this.endOfTrack ? 2.5 : 1.5; + var segValues = [0, 0, 0, 0, 0, 0, 0, 0]; + for (var seg = 0; seg < 8; seg++) { + var distance = TraktorS3.wheelSegmentDistance(seg, wheelAngle); + var brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (this.endOfTrack) { + dimDistance = 1.5; + brightVal = Math.round(4 * (1.0 - (distance / dimDistance))); + if (this.endOfTrackBlinkState) { + brightVal = brightVal > 0x03 ? 0x04 : 0x02; + } else { + brightVal = brightVal > 0x02 ? 0x04 : 0x00; + } + } + if (brightVal <= 0) { + segValues[seg] = 0x00; + } else { + segValues[seg] = baseLedValue + brightVal - 1; + } + } + this.parentDeck.wheelOutput(this.group, segValues); + return true; +}; + TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -1262,17 +1287,7 @@ TraktorS3.superHandler = function(field) { // The super knob drives all the supers -- if they are enabled. var chan = TraktorS3.Channels[field.group]; var value = field.value / 4095.; - var enabled = chan.fxEnabledState; - if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { - enabled = TraktorS3.inputFxEnabledState; - } - if (enabled) { - for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { - if (TraktorS3.fxButtonState[fxNumber]) { - engine.setParameter("[EffectRack1_EffectUnit" + fxNumber + "]", "super1", value); - } - } - } else if (field.group !== "[Channel4]" || !TraktorS3.channel4InputMode) { + if (field.group !== "[Channel4]" || !TraktorS3.channel4InputMode) { // There is no quickeffect for the microphone. engine.setParameter("[QuickEffectRack1_" + chan.group + "]", "super1", value); } @@ -1308,7 +1323,7 @@ TraktorS3.fxHandler = function(field) { TraktorS3.toggleFX = function() { // This is an AND operation. We go through each channel, and if - // the fitler button is ON and the fx is ON, we turn the effect ON. + // the filter button is ON and the fx is ON, we turn the effect ON. // We turn OFF if either is false. // The only exception is the Filter effect. If the channel fxenable @@ -1316,6 +1331,7 @@ TraktorS3.toggleFX = function() { // If the fxenable button is on, the Filter effect is only enabled if // the Filter FX button is enabled. for (var ch = 1; ch <= 4; ch++) { + var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var chEnabled = channel.fxEnabledState; if (ch === 4 && TraktorS3.channel4InputMode) { chEnabled = TraktorS3.inputFxEnabledState; @@ -1326,7 +1342,6 @@ TraktorS3.toggleFX = function() { newState); } for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { - var channel = TraktorS3.Channels["[Channel" + ch + "]"]; var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; var fxKey = "group_[Channel" + ch + "]_enable"; newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; @@ -1340,11 +1355,11 @@ TraktorS3.toggleFX = function() { TraktorS3.extModeHandler = function(field) { if (!field.value) { - TraktorS3.basicOutputHandler(TraktorS3.channel4InputMode, field.group, field.name); + TraktorS3.basicOutput(TraktorS3.channel4InputMode, field.group, field.name); return; } if (TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed) { - TraktorS3.basicOutputHandler(field.value, field.group, field.name); + TraktorS3.basicOutput(field.value, field.group, field.name); TraktorS3.inputModeLine = !TraktorS3.inputModeLine; TraktorS3.setInputLineMode(TraktorS3.inputModeLine); return; @@ -1358,7 +1373,7 @@ TraktorS3.extModeHandler = function(field) { engine.softTakeoverIgnoreNextValue("[Channel4]", "pregain"); } TraktorS3.lightDeck("[Channel4]"); - TraktorS3.basicOutputHandler(TraktorS3.channel4InputMode, field.group, field.name); + TraktorS3.basicOutput(TraktorS3.channel4InputMode, field.group, field.name); }; TraktorS3.registerOutputPackets = function() { @@ -1436,18 +1451,19 @@ TraktorS3.registerOutputPackets = function() { chan.linkOutputs(); } - engine.connectControl("[Microphone]", "pfl", this.pflOutputHandler); + engine.connectControl("[Microphone]", "pfl", this.pflOutput); // Master VuMeters - this.masterVuConnections["VuMeterL"] = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); - this.masterVuConnections["VuMeterR"] = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); - this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutputHandler); - this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutputHandler); + this.masterVuMeter["VuMeterL"].connection = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); + this.masterVuMeter["VuMeterR"].connection = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); + this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutput); + this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutput); + this.guiTickConnection = engine.makeConnection("[Master]", "guiTick50ms", this.guiTickHandler); // Sampler callbacks for (i = 1; i <= 16; ++i) { - this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutputHandler)); - this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", this.samplesOutputHandler)); + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutput)); + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", this.samplesOutput)); } }; @@ -1455,23 +1471,23 @@ TraktorS3.linkChannelOutput = function(group, name, callback) { TraktorS3.controller.linkOutput(group, name, group, name, callback); }; -TraktorS3.pflOutputHandler = function(value, group, key) { +TraktorS3.pflOutput = function(value, group, key) { if (group === "[Microphone]" && TraktorS3.channel4InputMode) { - TraktorS3.basicOutputHandler(value, "[Channel4]", key); + TraktorS3.basicOutput(value, "[Channel4]", key); return; } if (group === "[Channel4]" && !TraktorS3.channel4InputMode) { - TraktorS3.basicOutputHandler(value, group, key); + TraktorS3.basicOutput(value, group, key); return; } if (group.match(/^\[Channel[123]\]$/)) { - TraktorS3.basicOutputHandler(value, group, key); + TraktorS3.basicOutput(value, group, key); } // Unhandled case, ignore. }; -// outputHandler drives lights that only have one color. -TraktorS3.basicOutputHandler = function(value, group, key) { +// Output drives lights that only have one color. +TraktorS3.basicOutput = function(value, group, key) { var ledValue = value; if (value === 0 || value === false) { // Off value @@ -1484,7 +1500,7 @@ TraktorS3.basicOutputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.peakOutputHandler = function(value, group, key) { +TraktorS3.peakOutput = function(value, group, key) { var ledValue = 0x00; if (value) { ledValue = 0x7E; @@ -1493,15 +1509,12 @@ TraktorS3.peakOutputHandler = function(value, group, key) { TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); }; -TraktorS3.channelVuMeterHandler = function(value, group, key) { - TraktorS3.vuMeterHandler(value, group, key, 14); +TraktorS3.masterVuMeterHandler = function(value, _group, key) { + TraktorS3.masterVuMeter[key].updated = true; + TraktorS3.masterVuMeter[key].value = value; }; -TraktorS3.masterVuMeterHandler = function(value, group, key) { - TraktorS3.vuMeterHandler(value, group, key, 8); -}; - -TraktorS3.vuMeterHandler = function(value, group, key, segments) { +TraktorS3.vuMeterOutput = function(value, group, key, segments) { // This handler is called a lot so it should be as fast as possible. var scaledValue = value * segments; var fullIllumCount = Math.floor(scaledValue); @@ -1520,7 +1533,9 @@ TraktorS3.vuMeterHandler = function(value, group, key, segments) { TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); } } - TraktorS3.controller.OutputPackets["outputB"].send(); + if (!TraktorS3.batchingOutputs) { + TraktorS3.controller.OutputPackets["outputB"].send(); + } }; TraktorS3.resolveSampler = function(group) { @@ -1538,7 +1553,7 @@ TraktorS3.resolveSampler = function(group) { return result[1]; }; -TraktorS3.samplesOutputHandler = function(value, group, key) { +TraktorS3.samplesOutput = function(value, group, key) { // Sampler 1-8 -> Channel1 // Samples 9-16 -> Channel2 var sampler = TraktorS3.resolveSampler(group); @@ -1629,7 +1644,7 @@ TraktorS3.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); if (group === "[Channel4]") { - TraktorS3.basicOutputHandler(0, "[Master]", "!extButton"); + TraktorS3.basicOutput(0, "[Master]", "!extButton"); } } TraktorS3.lightFX(); @@ -1659,6 +1674,38 @@ TraktorS3.lightDeck = function(group, sendPackets) { } }; +// Render wheel positions, channel VU meters, and master vu meters +TraktorS3.guiTickHandler = function() { + TraktorS3.batchingOutputs = true; + var gotUpdate = false; + gotUpdate |= TraktorS3.Channels[TraktorS3.Decks["deck1"].activeChannel].lightWheelPosition(); + gotUpdate |= TraktorS3.Channels[TraktorS3.Decks["deck2"].activeChannel].lightWheelPosition(); + + for (var vu in TraktorS3.masterVuMeter) { + if (TraktorS3.masterVuMeter[vu].updated) { + TraktorS3.vuMeterOutput(TraktorS3.masterVuMeter[vu].value, "[Master]", vu, 8); + TraktorS3.masterVuMeter[vu].updated = false; + gotUpdate = true; + } + } + for (var ch = 1; ch <= 4; ch++) { + var chan = TraktorS3.Channels["[Channel" + ch + "]"]; + if (chan.vuMeterUpdated) { + TraktorS3.vuMeterOutput(chan.vuMeterValue, chan.group, "VuMeter", 14); + chan.vuMeterUpdated = false; + gotUpdate = true; + } + } + + TraktorS3.batchingOutputs = false; + + if (gotUpdate) { + for (var packetName in TraktorS3.controller.OutputPackets) { + TraktorS3.controller.OutputPackets[packetName].send(); + } + } +}; + // A special packet sent to the controller switches between mic and line // input modes. if lineMode is true, sets input to line. Otherwise, mic. TraktorS3.setInputLineMode = function(lineMode) { From 958cb7012d460fb4fc30e3fe7094502c035dbe39 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 27 Aug 2020 14:09:30 -0400 Subject: [PATCH 51/84] Traktor S3: Start rewriting fx mapping --- .../Traktor-Kontrol-S3-hid-scripts.js | 308 ++++++++++++++---- 1 file changed, 238 insertions(+), 70 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6417930ea0d..b08a79dde98 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -944,21 +944,6 @@ TraktorS3.Channel = function(parentDeck, group) { this.hotcueCallbacks = []; }; -TraktorS3.Channel.prototype.fxEnableHandler = function(field) { - if (field.value === 0) { - return; - } - - if (this.group === "[Channel4]" && TraktorS3.channel4InputMode) { - TraktorS3.inputFxEnabledState = !TraktorS3.inputFxEnabledState; - this.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); - } else { - this.fxEnabledState = !this.fxEnabledState; - this.colorOutput(this.fxEnabledState, "!fxEnabled"); - } - TraktorS3.toggleFX(); -}; - // Finds the shortest distance between two angles on the wheel, assuming // 0-8.0 angle value. TraktorS3.wheelSegmentDistance = function(segNum, angle) { @@ -1105,6 +1090,234 @@ TraktorS3.Channel.prototype.lightWheelPosition = function() { return true; }; +// FXControl is an object that manages the gray area in the middle of the +// controller: the fx control knobs, fxenable buttons, and fx select buttons. +TraktorS3.FXControl = function() { + // 0 is filter, 1-4 are FX Units 1-4 + this.FILTER_EFFECT = 0; + this.activeFX = this.FILTER_EFFECT; + + this.enablePressed = ""; + this.selectPressed = -1; +}; + +TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLong) { + // FX Buttons + var fxFn = TraktorS3.FXControl.prototype; + TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, TraktorS3.bind(fxFn.fxSelectHandler, this)); + TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, TraktorS3.bind(fxFn.fxSelectHandler, this)); + TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, TraktorS3.bind(fxFn.fxSelectHandler, this)); + TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, TraktorS3.bind(fxFn.fxSelectHandler, this)); + TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx0", 0x08, 0x80, TraktorS3.bind(fxFn.fxSelectHandler, this)); + + TraktorS3.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, TraktorS3.bind(fxFn.fxEnableHandler, this)); + TraktorS3.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, TraktorS3.bind(fxFn.fxEnableHandler, this)); + TraktorS3.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, TraktorS3.bind(fxFn.fxEnableHandler, this)); + TraktorS3.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x40, TraktorS3.bind(fxFn.fxEnableHandler, this)); + + TraktorS3.registerInputScaler(messageLong, "[Channel1]", "!fxKnob", 0x39, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + TraktorS3.registerInputScaler(messageLong, "[Channel2]", "!fxKnob", 0x3B, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + TraktorS3.registerInputScaler(messageLong, "[Channel3]", "!fxKnob", 0x37, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + TraktorS3.registerInputScaler(messageLong, "[Channel4]", "!fxKnob", 0x3D, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); +}; + +TraktorS3.FXControl.prototype.channelToIndex = function(group) { + var result = group.match(script.channelRegEx); + if (result === null) { + HIDDebug("barf" + group); + return undefined; + } + // Unmap from channel number to button index. + switch (result[1]) { + case "1": + return 2; + case "2": + return 3; + case "3": + return 1; + case "4": + return 4; + } + return undefined; +}; + +TraktorS3.FXControl.prototype.StatusDebug = function() { + HIDDebug("active: " + this.activeFX + + " enablepressed? " + this.enablePressed + + " selectpressed? " + this.selectPressed); + for (var i = 1; i <= 4; i++) { + var focus = engine.getValue("[EffectRack1_EffectUnit" + i + "]", "focused_effect"); + if (focus) { + HIDDebug("FX" + i + " focus"); + } + } +}; + +TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { + var group = "[EffectRack1_EffectUnit" + fxNum + "]"; + var newState = !engine.getValue(group, "focused_effect"); + + if (!newState) { + engine.setValue(group, "focused_effect", 0); + return; + } + // If we are setting a different unit to be enabled, the others become + // disabled. + for (var i = 1; i <= 4; i++) { + group = "[EffectRack1_EffectUnit" + i + "]"; + engine.setValue(group, "focused_effect", i === fxNum); + } +}; + +// pressing FX SELECT buttons changes the activeFX +// press again to focus, press again to unfocus. (focus should blink) +TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { + HIDDebug("FX SELECT " + field.group + " " + field.name + " " + field.value); + var fxNumber = parseInt(field.id[field.id.length - 1]); + if (field.value === 0) { + if (this.selectPressed === fxNumber) { + this.selectPressed = -1; + } + this.StatusDebug(); + return; + } + this.selectPressed = fxNumber; + + // If any fxEnable button is pressed, we are toggling fx unit assignment. + if (this.enablePressed !== "") { + var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + var fxKey = "group_" + this.enablePressed + "_enable"; + script.toggleControl(fxGroup, fxKey); + this.StatusDebug(); + return; + } + + var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + + // Clicked the same fx select, toggle focus state. + if (this.activeFX === fxNumber) { + // script.toggleControl(fxGroup, "focused_effect"); + this.toggleFocusEnable(fxNumber); + this.StatusDebug(); + return; + } + + // Default: set new activefx + // TODO: disable soft takeover for fx knobs + this.activeFX = fxNumber; + this.StatusDebug(); +}; + +// in unfocus mode, tap.... does nothing? Just highlights which FX are enabled for that deck while +// held. +TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { + HIDDebug("FX ENABLE " + field.group + " " + field.name + " " + field.value); + if (field.value === 0) { + if (this.enablePressed === field.group) { + this.enablePressed = ""; + } + this.StatusDebug(); + return; + } + + // in unfocus mode, preess and hold + tap fxselect to enable/disable per channel + // if select was pressed first though, ignore pressing enable as a press-and-hold. + if (this.selectPressed === -1) { + HIDDebug("TOGGLE"); + this.enablePressed = field.group; + } + + // in focus mode, tap fxenable enables/disables individual fx in units. + // var fxGroupPrefix = TraktorS3.fxGroupPrefix(this.); + // if (fxGroupPrefix === undefined) { + // HIDDebug("Programming Error: Didn't match channel number in fxSelectHandler: " + field.group); + // return; + // } + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + if (focusedEffect > 0) { + HIDDebug("toggle subeffect!"); + var buttonNumber = this.channelToIndex(field.group); + if (buttonNumber === undefined) { + HIDDebug("Programming Error: unexpectedly couldn't parse group: " + field.group); + return; + } + + var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; + var key = "enabled"; + HIDDebug("flipping " + group + " " + key); + script.toggleControl(group, key); + } + this.StatusDebug(); +}; + +TraktorS3.fxGroupPrefix = function(group) { + var channelMatch = group.match(script.channelRegEx); + if (channelMatch === undefined) { + return undefined; + } + return fxGroupPrefix = "[EffectRack1_EffectUnit" + channelMatch[1]; +}; + +TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { + HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); + var value = field.value / 4095.; + var knobIdx = this.channelToIndex(field.group); + + // unfocus: twisting knobs will adjust metaknob (or quickeffect) for that effect + if (this.activeFX === this.FILTER_EFFECT) { + if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + // There is no quickeffect for the microphone, do nothing. + this.StatusDebug(); + return; + } + HIDDebug("HERE?2 " + "[QuickEffectRack1_" + field.group + "]"); + engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); + this.StatusDebug(); + return; + } + + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + HIDDebug("asking for: " + fxGroupPrefix + "]"); + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + if (focusedEffect > 0) { + // focus: shift + adjust selects effect + // if (TraktorS3.anyShiftPressed()) { + // // engine.setValue(field.group, "load_preset", knobIdx+1); + // } + HIDDebug("focused?? "); + + // focus: adjusts params for that effect + HIDDebug("PARAM: " + fxGroupPrefix + "_Effect" + focusedEffect + "]"); + engine.setParameter(fxGroupPrefix + "_Effect" + focusedEffect + "]", + "parameter" + knobIdx, + field.value / 4096); + this.StatusDebug(); + return; + } + + // unfocus: other fx selected: adjust meta knob per channel + //XXXXX there's only meta knob per effect, not channel! + HIDDebug("HERE?"); + engine.setParameter(fxGroupPrefix + "]", + "super1", + field.value / 4096); + this.StatusDebug(); +}; + +// FX LIGHTS: +// if a button is pressed, definitely it should be on +// if enable is pressed, +// light the selects with what that channel has enabled. +// else if enable is not pressed, +// if focused, select blinks for selected effect +// if unfocused, selects is solid for selected effect +// if unfocused, enables are lit depending on which channels have that effect enabled. (for filter, +// that's all) +// if focused, enables are lit depending on which units are active. + + + TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); @@ -1119,35 +1332,17 @@ TraktorS3.registerInputPackets = function() { this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); - var group = "[Channel3]"; - TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x08, - TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); - group = "[Channel1]"; - TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x10, - TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); - group = "[Channel2]"; - TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x20, - TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); - group = "[Channel4]"; - TraktorS3.registerInputButton(messageShort, group, "!fxEnabled", 0x07, 0x40, - TraktorS3.bind(TraktorS3.Channel.prototype.fxEnableHandler, this.Channels[group])); - // Headphone buttons this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.headphoneHandler); this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.headphoneHandler); this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.headphoneHandler); this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.headphoneHandler); - // FX Buttons - this.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, this.fxHandler); - this.registerInputButton(messageShort, "[ChannelX]", "!fx5", 0x08, 0x80, this.fxHandler); - // EXT Button this.registerInputButton(messageShort, "[Master]", "!extButton", 0x07, 0x04, this.extModeHandler); + this.fxController.registerInputs(messageShort, messageLong); + this.controller.registerInputPacket(messageShort); this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); @@ -1176,11 +1371,6 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter2", 0x33, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Channel1]", "!super", 0x39, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel2]", "!super", 0x3B, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel3]", "!super", 0x37, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Channel4]", "!super", 0x3D, 0xFFFF, this.superHandler); - this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.masterGainHandler); this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); @@ -1264,10 +1454,14 @@ TraktorS3.parameterHandler = function(field) { } }; +TraktorS3.anyShiftPressed = function() { + return TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed; +}; + TraktorS3.masterGainHandler = function(field) { // Only adjust if shift is held. This will still adjust the sound card // volume but it at least allows for control of Mixxx's master gain. - if (TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed) { + if (TraktorS3.anyShiftPressed()) { engine.setParameter(field.group, field.name, field.value / 4095); } }; @@ -1283,16 +1477,6 @@ TraktorS3.headphoneHandler = function(field) { } }; -TraktorS3.superHandler = function(field) { - // The super knob drives all the supers -- if they are enabled. - var chan = TraktorS3.Channels[field.group]; - var value = field.value / 4095.; - if (field.group !== "[Channel4]" || !TraktorS3.channel4InputMode) { - // There is no quickeffect for the microphone. - engine.setParameter("[QuickEffectRack1_" + chan.group + "]", "super1", value); - } -}; - TraktorS3.deckSwitchHandler = function(field) { if (field.value === 0) { return; @@ -1303,24 +1487,6 @@ TraktorS3.deckSwitchHandler = function(field) { deck.activateChannel(channel); }; -TraktorS3.fxHandler = function(field) { - if (field.value === 0) { - return; - } - var fxNumber = parseInt(field.id[field.id.length - 1]); - - // Toggle effect unit - TraktorS3.fxButtonState[fxNumber] = !TraktorS3.fxButtonState[fxNumber]; - var ledValue = TraktorS3.fxLEDValue[fxNumber]; - if (TraktorS3.fxButtonState[fxNumber]) { - ledValue += TraktorS3LEDBrightValue; - } else { - ledValue += TraktorS3LEDDimValue; - } - TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); - TraktorS3.toggleFX(); -}; - TraktorS3.toggleFX = function() { // This is an AND operation. We go through each channel, and if // the filter button is ON and the fx is ON, we turn the effect ON. @@ -1358,7 +1524,7 @@ TraktorS3.extModeHandler = function(field) { TraktorS3.basicOutput(TraktorS3.channel4InputMode, field.group, field.name); return; } - if (TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed) { + if (TraktorS3.anyShiftPressed()) { TraktorS3.basicOutput(field.value, field.group, field.name); TraktorS3.inputModeLine = !TraktorS3.inputModeLine; TraktorS3.setInputLineMode(TraktorS3.inputModeLine); @@ -1833,6 +1999,8 @@ TraktorS3.init = function(_id) { "[Channel4]": new TraktorS3.Channel(this.Decks["deck2"], "[Channel4]") }; + this.fxController = new TraktorS3.FXControl(); + TraktorS3.registerInputPackets(); TraktorS3.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); From c9844c9ed4eb2d8a2c95385f933a39811c8ed25b Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 30 Aug 2020 23:05:16 -0400 Subject: [PATCH 52/84] Traktor S3: write a controller unit test! Very initial commit --- CMakeLists.txt | 1 + .../Traktor-Kontrol-S3-hid-scripts.js | 115 ++++++++--- src/controllers/controllerengine.cpp | 36 +++- src/controllers/controllerengine.h | 18 +- src/test/controllerengine_test.cpp | 3 +- .../controllers/Traktor_Kontrol_S3_test.cpp | 181 ++++++++++++++++++ 6 files changed, 308 insertions(+), 46 deletions(-) create mode 100644 src/test/controllers/Traktor_Kontrol_S3_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e05c3993605..dc92068a066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1271,6 +1271,7 @@ add_executable(mixxx-test src/test/metadatatest.cpp src/test/metaknob_link_test.cpp src/test/midicontrollertest.cpp + src/test/controllers/Traktor_Kontrol_S3_test.cpp src/test/mixxxtest.cpp src/test/movinginterquartilemean_test.cpp src/test/nativeeffects_test.cpp diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index b08a79dde98..e4738f3a9ec 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -389,7 +389,7 @@ TraktorS3.Deck.prototype.padModeHandler = function(field) { }; TraktorS3.Deck.prototype.numberButtonHandler = function(field) { - var padNumber = parseInt(field.id[field.id.length - 1]); + var padNumber = parseInt(field.name[field.name.length - 1]); var action = ""; // Hotcues mode @@ -1097,8 +1097,26 @@ TraktorS3.FXControl = function() { this.FILTER_EFFECT = 0; this.activeFX = this.FILTER_EFFECT; - this.enablePressed = ""; - this.selectPressed = -1; + this.enablePressed = { + "[Channel1]": false, + "[Channel2]": false, + "[Channel3]": false, + "[Channel4]": false + }; + this.selectPressed = { + 0: false, + 1: false, + 2: false, + 3: false, + 4: false + }; + + // States + this.STATE_FILTER = 0; + this.STATE_EFFECT = 1; + this.STATE_FOCUS = 2; + + this.currentState = this.STATE_FILTER; }; TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLong) { @@ -1173,39 +1191,74 @@ TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { // press again to focus, press again to unfocus. (focus should blink) TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { HIDDebug("FX SELECT " + field.group + " " + field.name + " " + field.value); - var fxNumber = parseInt(field.id[field.id.length - 1]); - if (field.value === 0) { - if (this.selectPressed === fxNumber) { - this.selectPressed = -1; - } - this.StatusDebug(); + var fxNumber = parseInt(field.name[field.name.length - 1]); + HIDDebug("number " + fxNumber); + this.selectPressed[fxNumber] = field.value; + + if (!field.value) { + // do lights return; } - this.selectPressed = fxNumber; - // If any fxEnable button is pressed, we are toggling fx unit assignment. - if (this.enablePressed !== "") { - var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - var fxKey = "group_" + this.enablePressed + "_enable"; - script.toggleControl(fxGroup, fxKey); - this.StatusDebug(); - return; + switch (this.currentState) { + case this.STATE_FILTER: + // If we push filter and filter is already pushed, do nothing. + if (fxNumber === 0) { + this.currentState = this.STATE_FILTER; + } else { + // Select this filter instead. + // initiate state change + // this.activateState(this.STATE_EFFECT, fxNumber); + this.currentState = this.STATE_EFFECT; + } + this.activeFX = fxNumber; + break; + case this.STATE_EFFECT: + if (fxNumber === 0) { + this.currentState = this.STATE_FILTER; + } else { + // Select this filter instead. + // initiate state change + // this.activateState(this.STATE_EFFECT, fxNumber); + this.currentState = this.STATE_EFFECT; + } + this.activeFX = fxNumber; + break; + case this.STATE_FOCUS: + break; } + // if (field.value === 0) { + // if (this.selectPressed === fxNumber) { + // this.selectPressed = -1; + // } + // this.StatusDebug(); + // return; + // } + // this.selectPressed = fxNumber; + + // // If any fxEnable button is pressed, we are toggling fx unit assignment. + // if (this.enablePressed !== "") { + // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + // var fxKey = "group_" + this.enablePressed + "_enable"; + // script.toggleControl(fxGroup, fxKey); + // this.StatusDebug(); + // return; + // } - var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - // Clicked the same fx select, toggle focus state. - if (this.activeFX === fxNumber) { - // script.toggleControl(fxGroup, "focused_effect"); - this.toggleFocusEnable(fxNumber); - this.StatusDebug(); - return; - } + // // Clicked the same fx select, toggle focus state. + // if (this.activeFX === fxNumber) { + // // script.toggleControl(fxGroup, "focused_effect"); + // this.toggleFocusEnable(fxNumber); + // this.StatusDebug(); + // return; + // } - // Default: set new activefx - // TODO: disable soft takeover for fx knobs - this.activeFX = fxNumber; - this.StatusDebug(); + // // Default: set new activefx + // // TODO: disable soft takeover for fx knobs + // this.activeFX = fxNumber; + // this.StatusDebug(); }; // in unfocus mode, tap.... does nothing? Just highlights which FX are enabled for that deck while @@ -1256,7 +1309,7 @@ TraktorS3.fxGroupPrefix = function(group) { if (channelMatch === undefined) { return undefined; } - return fxGroupPrefix = "[EffectRack1_EffectUnit" + channelMatch[1]; + return "[EffectRack1_EffectUnit" + channelMatch[1]; }; TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { @@ -1380,7 +1433,7 @@ TraktorS3.registerInputPackets = function() { // Soft takeovers for (var ch = 1; ch <= 4; ch++) { - group = "[Channel" + ch + "]"; + var group = "[Channel" + ch + "]"; if (!TraktorS3PitchSliderRelativeMode) { engine.softTakeover(group, "rate", true); } diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index a79600a5b6b..b96bbf572ec 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -403,7 +403,7 @@ Input: 'this' object if applicable, Code string Output: false if an exception -------- ------------------------------------------------------ */ bool ControllerEngine::internalExecute( - QScriptValue thisObject, const QString& scriptCode) { + QScriptValue thisObject, const QString& scriptCode, QScriptValue* outValue) { // A special version of safeExecute since we're evaluating strings, not actual functions // (execute() would print an error that it's not a function every time a timer fires.) if (m_pEngine == nullptr) { @@ -426,7 +426,7 @@ bool ControllerEngine::internalExecute( return false; } - return internalExecute(thisObject, scriptFunction, QScriptValueList()); + return internalExecute(thisObject, scriptFunction, QScriptValueList(), outValue); } /* -------- ------------------------------------------------------ @@ -436,7 +436,8 @@ Output: false if an exception -------- ------------------------------------------------------ */ bool ControllerEngine::internalExecute(QScriptValue thisObject, QScriptValue functionObject, - QScriptValueList args) { + QScriptValueList args, + QScriptValue* outValue) { if (m_pEngine == nullptr) { qDebug() << "ControllerEngine::execute: No script engine exists!"; return false; @@ -456,7 +457,12 @@ bool ControllerEngine::internalExecute(QScriptValue thisObject, } // If it does happen to be a function, call it. + qDebug() << "execing!"; QScriptValue rc = functionObject.call(thisObject, args); + qDebug() << "we execed the func and the outvalue is " << rc.toString(); + if (outValue != nullptr) { + *outValue = rc; + } if (!rc.isValid()) { qDebug() << "QScriptValue is not a function or ..."; return false; @@ -482,7 +488,7 @@ bool ControllerEngine::execute(QScriptValue functionObject, args << QScriptValue(value); args << QScriptValue(status); args << QScriptValue(group); - return internalExecute(m_pEngine->globalObject(), functionObject, args); + return internalExecute(m_pEngine->globalObject(), functionObject, args, nullptr); } bool ControllerEngine::execute(QScriptValue function, @@ -495,7 +501,7 @@ bool ControllerEngine::execute(QScriptValue function, QScriptValueList args; args << m_pBaClass->newInstance(data); args << QScriptValue(data.size()); - return internalExecute(m_pEngine->globalObject(), function, args); + return internalExecute(m_pEngine->globalObject(), function, args, nullptr); } /* -------- ------------------------------------------------------ @@ -1035,6 +1041,16 @@ void ControllerEngine::trigger(QString group, QString name) { Output: false if the script file has errors or doesn't exist -------- ------------------------------------------------------ */ bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { + return evaluateWithReturn(scriptFile, nullptr); +} + +/* -------- ------------------------------------------------------ + Purpose: Evaluate a script file + Input: Script filename + Output: false if the script file has errors or doesn't exist, and the result + of the script if outValue is not nullptr. + -------- ------------------------------------------------------ */ +bool ControllerEngine::evaluateWithReturn(const QFileInfo& scriptFile, QScriptValue* outValue) { if (m_pEngine == nullptr) { return false; } @@ -1086,7 +1102,10 @@ bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { } // Evaluate the code - QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode, filename); + QScriptValue scriptResult = m_pEngine->evaluate(scriptCode, filename); + if (outValue != nullptr) { + *outValue = scriptResult; + } // Record errors if (checkException(true)) { @@ -1189,10 +1208,9 @@ void ControllerEngine::timerEvent(QTimerEvent *event) { } if (timerTarget.callback.isString()) { - internalExecute(timerTarget.context, timerTarget.callback.toString()); + internalExecute(timerTarget.context, timerTarget.callback.toString(), nullptr); } else if (timerTarget.callback.isFunction()) { - internalExecute(timerTarget.context, timerTarget.callback, - QScriptValueList()); + internalExecute(timerTarget.context, timerTarget.callback, QScriptValueList(), nullptr); } } diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index dad8c785bc6..b6ff2a4cc14 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -169,9 +169,18 @@ class ControllerEngine : public QObject { private: bool syntaxIsValid(const QString& scriptCode, const QString& filename = QString()); bool evaluate(const QFileInfo& scriptFile); - bool internalExecute(QScriptValue thisObject, const QString& scriptCode); - bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, - QScriptValueList arguments); + QScriptValue evaluateDirect(const QString& program) { + return m_pEngine->evaluate(program); + } + + bool evaluateWithReturn(const QFileInfo& filepath, QScriptValue* outValue); + bool internalExecute(QScriptValue thisObject, + const QString& scriptCode, + QScriptValue* outValue); + bool internalExecute(QScriptValue thisObject, + QScriptValue functionObject, + QScriptValueList arguments, + QScriptValue* outValue); void initializeScriptEngine(); void uninitializeScriptEngine(); @@ -182,7 +191,7 @@ class ControllerEngine : public QObject { void callFunctionOnObjects(QList, const QString&, QScriptValueList args = QScriptValueList()); bool checkException(bool bFatal = false); - QScriptEngine *m_pEngine; + QScriptEngine* m_pEngine; ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); @@ -220,6 +229,7 @@ class ControllerEngine : public QObject { QList m_lastScriptFiles; friend class ControllerEngineTest; + friend class ControllerTest; }; #endif diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 642b4b98966..47681ea3162 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -32,8 +32,7 @@ class ControllerEngineTest : public MixxxTest { bool execute(const QString& functionName) { QScriptValue function = cEngine->wrapFunctionCode(functionName, 0); - return cEngine->internalExecute(QScriptValue(), function, - QScriptValueList()); + return cEngine->internalExecute(QScriptValue(), function, QScriptValueList(), nullptr); } void processEvents() { diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp new file mode 100644 index 00000000000..1ea1b914cca --- /dev/null +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -0,0 +1,181 @@ +#include +#include + +#include "control/controlobject.h" +#include "control/controlpotmeter.h" +#include "controllers/controllerdebug.h" +#include "controllers/controllerengine.h" +#include "controllers/softtakeover.h" +#include "preferences/usersettings.h" +#include "test/mixxxtest.h" +#include "util/color/colorpalette.h" +#include "util/memory.h" +#include "util/time.h" + +class ControllerTest : public MixxxTest { + protected: + void SetUp() override { + mixxx::Time::setTestMode(true); + mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); + QThread::currentThread()->setObjectName("Main"); + m_pCEngine = new ControllerEngine(nullptr, config()); + ControllerDebug::enable(); + m_pCEngine->setPopups(false); + } + + void TearDown() override { + m_pCEngine->gracefulShutdown(); + delete m_pCEngine; + mixxx::Time::setTestMode(false); + } + + QScriptValue evaluate(const QString& program) { + return m_pCEngine->evaluateDirect(program); + } + + // Executes a text string of javascript, can contain newlines. + // Returns the value resulting from the execution. + QScriptValue executeScript(const QString& scriptText) { + ScopedTemporaryFile script(makeTemporaryFile(scriptText)); + QScriptValue ret; + m_pCEngine->evaluateWithReturn(script->fileName(), &ret); + return ret; + } + + void processEvents() { + // QCoreApplication::processEvents() only processes events that were + // queued when the method was called. Hence, all subsequent events that + // are emitted while processing those queued events will not be + // processed and are enqueued for the next event processing cycle. + // Calling processEvents() twice ensures that at least all queued and + // the next round of emitted events are processed. + application()->processEvents(); + application()->processEvents(); + } + + ControllerEngine* m_pCEngine; +}; + +class TraktorS3Test : public ControllerTest { + protected: + void SetUp() override { + ControllerTest::SetUp(); + QString hidScript = "./res/controllers/common-hid-packet-parser.js"; + ASSERT_TRUE(m_pCEngine->evaluate(hidScript)); + ASSERT_FALSE(m_pCEngine->hasErrors(hidScript)); + ASSERT_TRUE(m_pCEngine->evaluate(m_sScriptFile)); + ASSERT_FALSE(m_pCEngine->hasErrors(m_sScriptFile)); + + // Create useful objects and getters + executeScript( + "var TestOb = {};" + "TestOb.fxc = new TraktorS3.FXControl(); " + "var getState = function() {" + " return TestOb.fxc.currentState;" + "};" + "var getActiveFx = function() {" + " return TestOb.fxc.activeFX;" + "};" + "var getSelectPressed = function() {" + " return TestOb.fxc.selectPressed;" + "};" + "var getEnablePressed = function() {" + " return TestOb.fxc.enablePressed;" + "};"); + } + + private: + const QString m_sScriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; + bool m_bStateFnDefined = false; + bool m_bAciveFXFnDefined = false; +}; + +TEST_F(TraktorS3Test, FXSelectFX) { + ASSERT_TRUE(executeScript( + "var pressFx2 = { " + " group: '[ChannelX]', " + " name: '!fx2', " + " value: 1, " + "}; " + "var unpressFx2 = { " + " group: '[ChannelX]', " + " name: '!fx2', " + " value: 0, " + "}; " + "var pressFilter = { " + " group: '[ChannelX]', " + " name: '!fx0', " + " value: 1, " + "}; " + "var unpressFilter = { " + " group: '[ChannelX]', " + " name: '!fx0', " + " value: 0, " + "}; ") + .isValid()); + + // QScriptValue ret2 = executeScript("getState();"); + // qDebug() << "hmm what about execute" << ret2.toString(); + + QScriptValue ret3 = evaluate("getState();"); + qDebug() << "even easier?" << ret3.toString(); + + EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); + + // First try pressing a select button and releasing + executeScript("TestOb.fxc.fxSelectHandler(pressFx2);"); + auto ret = evaluate("getSelectPressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array1[5] = {false, false, true, false, false}; + for (int i = 0; i < 5; ++i) { + EXPECT_TRUE(ret.property(i).isValid()); + EXPECT_EQ(expected_array1[i], ret.property(i).toBool()); + } + auto what = evaluate("getState();"); + EXPECT_TRUE(what.isNumber()); + EXPECT_EQ(1, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Now unpress select and release + ret = executeScript( + "TestOb.fxc.fxSelectHandler(unpressFx2);" + "getSelectPressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array2[5] = {false, false, false, false, false}; + for (int i = 0; i < 5; ++i) { + EXPECT_TRUE(ret.property(i).isValid()); + EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); + } + EXPECT_EQ(1, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Now press filter button and release + ret = executeScript( + "TestOb.fxc.fxSelectHandler(pressFilter); " + "getSelectPressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array3[5] = {true, false, false, false, false}; + for (int i = 0; i < 5; ++i) { + EXPECT_TRUE(ret.property(i).isValid()); + EXPECT_EQ(expected_array3[i], ret.property(i).toBool()); + } + EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); + + ret = executeScript( + "TestOb.fxc.fxSelectHandler(unpressFilter); " + "getSelectPressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array4[5] = {false, false, false, false, false}; + for (int i = 0; i < 5; ++i) { + EXPECT_TRUE(ret.property(i).isValid()); + EXPECT_EQ(expected_array4[i], ret.property(i).toBool()); + } + EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); +} From daaf969e3cc28591ed4b0ff7a68e8a67387106e0 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Mon, 31 Aug 2020 10:27:53 -0400 Subject: [PATCH 53/84] Traktor S3: refactor testing, js testing works now --- .../Traktor-Kontrol-S3-hid-scripts.js | 12 +- src/controllers/controllerengine.cpp | 24 ++- src/controllers/controllerengine.h | 11 +- .../controllers/Traktor_Kontrol_S3_test.cpp | 146 +++++++++--------- src/test/controllers/controllertest.h | 46 ++++++ 5 files changed, 149 insertions(+), 90 deletions(-) create mode 100644 src/test/controllers/controllertest.h diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index e4738f3a9ec..2440d10d473 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1217,10 +1217,14 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { if (fxNumber === 0) { this.currentState = this.STATE_FILTER; } else { - // Select this filter instead. - // initiate state change - // this.activateState(this.STATE_EFFECT, fxNumber); - this.currentState = this.STATE_EFFECT; + if (fxNumber === this.activeFX) { + this.currentState = this.STATE_FOCUS; + } else { + // Select this filter instead. + // initiate state change + // this.activateState(this.STATE_EFFECT, fxNumber); + this.currentState = this.STATE_EFFECT; + } } this.activeFX = fxNumber; break; diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index b96bbf572ec..69eeefa51fa 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -457,9 +457,7 @@ bool ControllerEngine::internalExecute(QScriptValue thisObject, } // If it does happen to be a function, call it. - qDebug() << "execing!"; QScriptValue rc = functionObject.call(thisObject, args); - qDebug() << "we execed the func and the outvalue is " << rc.toString(); if (outValue != nullptr) { *outValue = rc; } @@ -1041,7 +1039,7 @@ void ControllerEngine::trigger(QString group, QString name) { Output: false if the script file has errors or doesn't exist -------- ------------------------------------------------------ */ bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { - return evaluateWithReturn(scriptFile, nullptr); + return evaluateScriptWithReturn(scriptFile, nullptr); } /* -------- ------------------------------------------------------ @@ -1050,7 +1048,8 @@ bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { Output: false if the script file has errors or doesn't exist, and the result of the script if outValue is not nullptr. -------- ------------------------------------------------------ */ -bool ControllerEngine::evaluateWithReturn(const QFileInfo& scriptFile, QScriptValue* outValue) { +bool ControllerEngine::evaluateScriptWithReturn( + const QFileInfo& scriptFile, QScriptValue* outValue) { if (m_pEngine == nullptr) { return false; } @@ -1096,6 +1095,23 @@ bool ControllerEngine::evaluateWithReturn(const QFileInfo& scriptFile, QScriptVa scriptCode.append('\n'); input.close(); + return evaluateWithReturn(scriptCode, filename, outValue); +} + +/* -------- ------------------------------------------------------ + Purpose: Evaluate javascript + Input: Script code, can contain newlines. Filename can be empty if not + applicable + Output: false if the script file has errors or doesn't exist, and the result + of the script if outValue is not nullptr. + -------- ------------------------------------------------------ */ +bool ControllerEngine::evaluateWithReturn(const QString& scriptCode, + const QString& filename, + QScriptValue* outValue) { + if (m_pEngine == nullptr) { + return false; + } + // Check syntax if (!syntaxIsValid(scriptCode, filename)) { return false; diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index b6ff2a4cc14..56d40d9ab4c 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -167,13 +167,14 @@ class ControllerEngine : public QObject { void errorDialogButton(const QString& key, QMessageBox::StandardButton button); private: + // Filename is only for informational purposes. bool syntaxIsValid(const QString& scriptCode, const QString& filename = QString()); bool evaluate(const QFileInfo& scriptFile); - QScriptValue evaluateDirect(const QString& program) { - return m_pEngine->evaluate(program); - } - - bool evaluateWithReturn(const QFileInfo& filepath, QScriptValue* outValue); + bool evaluateScriptWithReturn(const QFileInfo& filepath, QScriptValue* outValue); + // Filename is only for informational purposes, it can be empty QString if not applicable. + bool evaluateWithReturn(const QString& program, + const QString& filename, + QScriptValue* outValue); bool internalExecute(QScriptValue thisObject, const QString& scriptCode, QScriptValue* outValue); diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index 1ea1b914cca..b00cc831a11 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -6,55 +6,7 @@ #include "controllers/controllerdebug.h" #include "controllers/controllerengine.h" #include "controllers/softtakeover.h" -#include "preferences/usersettings.h" -#include "test/mixxxtest.h" -#include "util/color/colorpalette.h" -#include "util/memory.h" -#include "util/time.h" - -class ControllerTest : public MixxxTest { - protected: - void SetUp() override { - mixxx::Time::setTestMode(true); - mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); - QThread::currentThread()->setObjectName("Main"); - m_pCEngine = new ControllerEngine(nullptr, config()); - ControllerDebug::enable(); - m_pCEngine->setPopups(false); - } - - void TearDown() override { - m_pCEngine->gracefulShutdown(); - delete m_pCEngine; - mixxx::Time::setTestMode(false); - } - - QScriptValue evaluate(const QString& program) { - return m_pCEngine->evaluateDirect(program); - } - - // Executes a text string of javascript, can contain newlines. - // Returns the value resulting from the execution. - QScriptValue executeScript(const QString& scriptText) { - ScopedTemporaryFile script(makeTemporaryFile(scriptText)); - QScriptValue ret; - m_pCEngine->evaluateWithReturn(script->fileName(), &ret); - return ret; - } - - void processEvents() { - // QCoreApplication::processEvents() only processes events that were - // queued when the method was called. Hence, all subsequent events that - // are emitted while processing those queued events will not be - // processed and are enqueued for the next event processing cycle. - // Calling processEvents() twice ensures that at least all queued and - // the next round of emitted events are processed. - application()->processEvents(); - application()->processEvents(); - } - - ControllerEngine* m_pCEngine; -}; +#include "test/controllers/controllertest.h" class TraktorS3Test : public ControllerTest { protected: @@ -67,7 +19,7 @@ class TraktorS3Test : public ControllerTest { ASSERT_FALSE(m_pCEngine->hasErrors(m_sScriptFile)); // Create useful objects and getters - executeScript( + evaluate( "var TestOb = {};" "TestOb.fxc = new TraktorS3.FXControl(); " "var getState = function() {" @@ -84,14 +36,18 @@ class TraktorS3Test : public ControllerTest { "};"); } + enum states { + STATE_FILTER, + STATE_EFFECT, + STATE_FOCUS + }; + private: const QString m_sScriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; - bool m_bStateFnDefined = false; - bool m_bAciveFXFnDefined = false; }; -TEST_F(TraktorS3Test, FXSelectFX) { - ASSERT_TRUE(executeScript( +TEST_F(TraktorS3Test, FXSelectButtonSimple) { + ASSERT_TRUE(evaluate( "var pressFx2 = { " " group: '[ChannelX]', " " name: '!fx2', " @@ -114,17 +70,11 @@ TEST_F(TraktorS3Test, FXSelectFX) { "}; ") .isValid()); - // QScriptValue ret2 = executeScript("getState();"); - // qDebug() << "hmm what about execute" << ret2.toString(); - - QScriptValue ret3 = evaluate("getState();"); - qDebug() << "even easier?" << ret3.toString(); - - EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); // First try pressing a select button and releasing - executeScript("TestOb.fxc.fxSelectHandler(pressFx2);"); + evaluate("TestOb.fxc.fxSelectHandler(pressFx2);"); auto ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); @@ -133,15 +83,12 @@ TEST_F(TraktorS3Test, FXSelectFX) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array1[i], ret.property(i).toBool()); } - auto what = evaluate("getState();"); - EXPECT_TRUE(what.isNumber()); - EXPECT_EQ(1, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); // Now unpress select and release - ret = executeScript( - "TestOb.fxc.fxSelectHandler(unpressFx2);" - "getSelectPressed();"); + evaluate("TestOb.fxc.fxSelectHandler(unpressFx2);"); + ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); bool expected_array2[5] = {false, false, false, false, false}; @@ -149,13 +96,12 @@ TEST_F(TraktorS3Test, FXSelectFX) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); } - EXPECT_EQ(1, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); // Now press filter button and release - ret = executeScript( - "TestOb.fxc.fxSelectHandler(pressFilter); " - "getSelectPressed();"); + evaluate("TestOb.fxc.fxSelectHandler(pressFilter);"); + ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); bool expected_array3[5] = {true, false, false, false, false}; @@ -163,12 +109,11 @@ TEST_F(TraktorS3Test, FXSelectFX) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array3[i], ret.property(i).toBool()); } - EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); - ret = executeScript( - "TestOb.fxc.fxSelectHandler(unpressFilter); " - "getSelectPressed();"); + evaluate("TestOb.fxc.fxSelectHandler(unpressFilter);"); + ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); bool expected_array4[5] = {false, false, false, false, false}; @@ -176,6 +121,53 @@ TEST_F(TraktorS3Test, FXSelectFX) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array4[i], ret.property(i).toBool()); } - EXPECT_EQ(0, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); } + +TEST_F(TraktorS3Test, FXSelectFocusToggle) { + ASSERT_TRUE(evaluate( + "var pressFx2 = { " + " group: '[ChannelX]', " + " name: '!fx2', " + " value: 1, " + "}; " + "var unpressFx2 = { " + " group: '[ChannelX]', " + " name: '!fx2', " + " value: 0, " + "}; " + "var pressFilter = { " + " group: '[ChannelX]', " + " name: '!fx0', " + " value: 1, " + "}; " + "var unpressFilter = { " + " group: '[ChannelX]', " + " name: '!fx0', " + " value: 0, " + "}; ") + .isValid()); + + // Press FX2 and release + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + auto ret = evaluate("getSelectPressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array2[5] = {false, false, false, false, false}; + for (int i = 0; i < 5; ++i) { + EXPECT_TRUE(ret.property(i).isValid()); + EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); + } + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Press again + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); +} diff --git a/src/test/controllers/controllertest.h b/src/test/controllers/controllertest.h new file mode 100644 index 00000000000..e7bd3ba59da --- /dev/null +++ b/src/test/controllers/controllertest.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "controllers/controllerdebug.h" +#include "controllers/controllerengine.h" +#include "test/mixxxtest.h" +#include "util/time.h" + +class ControllerTest : public MixxxTest { + protected: + void SetUp() override { + mixxx::Time::setTestMode(true); + mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); + QThread::currentThread()->setObjectName("Main"); + m_pCEngine = new ControllerEngine(nullptr, config()); + ControllerDebug::enable(); + m_pCEngine->setPopups(false); + } + + void TearDown() override { + m_pCEngine->gracefulShutdown(); + delete m_pCEngine; + mixxx::Time::setTestMode(false); + } + + QScriptValue evaluate(const QString& program) { + QScriptValue ret; + EXPECT_TRUE(m_pCEngine->evaluateWithReturn(program, QString(), &ret)); + return ret; + } + + void processEvents() { + // QCoreApplication::processEvents() only processes events that were + // queued when the method was called. Hence, all subsequent events that + // are emitted while processing those queued events will not be + // processed and are enqueued for the next event processing cycle. + // Calling processEvents() twice ensures that at least all queued and + // the next round of emitted events are processed. + application()->processEvents(); + application()->processEvents(); + } + + ControllerEngine* m_pCEngine; +}; \ No newline at end of file From d31a7978ae90196e03fd9a2c7583fe7b85db57d6 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Mon, 31 Aug 2020 17:05:01 -0400 Subject: [PATCH 54/84] Traktor S3: wip -- more effects reworking --- .../Traktor-Kontrol-S3-hid-scripts.js | 139 ++++++++++-------- .../controllers/Traktor_Kontrol_S3_test.cpp | 122 +++++++++++---- 2 files changed, 169 insertions(+), 92 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 2440d10d473..71148d75181 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1187,19 +1187,42 @@ TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { } }; +TraktorS3.FXControl.prototype.anyEnablePressed = function() { + for (var key in this.enablePressed) { + if (this.enablePressed[key]) { + return true; + } + } + return false; +}; + // pressing FX SELECT buttons changes the activeFX // press again to focus, press again to unfocus. (focus should blink) TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { - HIDDebug("FX SELECT " + field.group + " " + field.name + " " + field.value); var fxNumber = parseInt(field.name[field.name.length - 1]); - HIDDebug("number " + fxNumber); this.selectPressed[fxNumber] = field.value; if (!field.value) { - // do lights + // do lights? return; } + // // If any fxEnable button is pressed, we are toggling fx unit assignment. + // if (this.enablePressed !== "") { + if (this.anyEnablePressed()) { + var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + for (var key in this.enablePressed) { + if (this.enablePressed[key]) { + HIDDebug("key? " + key); + var fxKey = "group_" + key + "_enable"; + script.toggleControl(fxGroup, fxKey); + } + } + // this.StatusDebug(); + return; + } + + // Un-shifted presses switch (this.currentState) { case this.STATE_FILTER: // If we push filter and filter is already pushed, do nothing. @@ -1229,83 +1252,69 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { this.activeFX = fxNumber; break; case this.STATE_FOCUS: + if (fxNumber === 0) { + this.currentState = this.STATE_FILTER; + } else { + this.currentState = this.STATE_EFFECT; + } + this.activeFX = fxNumber; break; } - // if (field.value === 0) { - // if (this.selectPressed === fxNumber) { - // this.selectPressed = -1; - // } - // this.StatusDebug(); - // return; - // } - // this.selectPressed = fxNumber; - - // // If any fxEnable button is pressed, we are toggling fx unit assignment. - // if (this.enablePressed !== "") { - // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - // var fxKey = "group_" + this.enablePressed + "_enable"; - // script.toggleControl(fxGroup, fxKey); - // this.StatusDebug(); - // return; - // } - // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - - // // Clicked the same fx select, toggle focus state. - // if (this.activeFX === fxNumber) { - // // script.toggleControl(fxGroup, "focused_effect"); - // this.toggleFocusEnable(fxNumber); - // this.StatusDebug(); - // return; - // } - - // // Default: set new activefx - // // TODO: disable soft takeover for fx knobs - // this.activeFX = fxNumber; - // this.StatusDebug(); }; // in unfocus mode, tap.... does nothing? Just highlights which FX are enabled for that deck while // held. TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { - HIDDebug("FX ENABLE " + field.group + " " + field.name + " " + field.value); - if (field.value === 0) { - if (this.enablePressed === field.group) { - this.enablePressed = ""; - } - this.StatusDebug(); + HIDDebug("we are here: " + field.group + " " + field.value); + this.enablePressed[field.group] = field.value; + + if (!field.value) { + // do lights? return; } - // in unfocus mode, preess and hold + tap fxselect to enable/disable per channel - // if select was pressed first though, ignore pressing enable as a press-and-hold. - if (this.selectPressed === -1) { - HIDDebug("TOGGLE"); - this.enablePressed = field.group; - } + // switch (field.group) { + // } - // in focus mode, tap fxenable enables/disables individual fx in units. - // var fxGroupPrefix = TraktorS3.fxGroupPrefix(this.); - // if (fxGroupPrefix === undefined) { - // HIDDebug("Programming Error: Didn't match channel number in fxSelectHandler: " + field.group); + // HIDDebug("FX ENABLE " + field.group + " " + field.name + " " + field.value); + // if (field.value === 0) { + // if (this.enablePressed === field.group) { + // this.enablePressed = ""; + // } + // this.StatusDebug(); // return; // } - var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; - var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - if (focusedEffect > 0) { - HIDDebug("toggle subeffect!"); - var buttonNumber = this.channelToIndex(field.group); - if (buttonNumber === undefined) { - HIDDebug("Programming Error: unexpectedly couldn't parse group: " + field.group); - return; - } - var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; - var key = "enabled"; - HIDDebug("flipping " + group + " " + key); - script.toggleControl(group, key); - } - this.StatusDebug(); + // // in unfocus mode, preess and hold + tap fxselect to enable/disable per channel + // // if select was pressed first though, ignore pressing enable as a press-and-hold. + // if (this.selectPressed === -1) { + // HIDDebug("TOGGLE"); + // this.enablePressed = field.group; + // } + + // // in focus mode, tap fxenable enables/disables individual fx in units. + // // var fxGroupPrefix = TraktorS3.fxGroupPrefix(this.); + // // if (fxGroupPrefix === undefined) { + // // HIDDebug("Programming Error: Didn't match channel number in fxSelectHandler: " + field.group); + // // return; + // // } + // var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + // var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + // if (focusedEffect > 0) { + // HIDDebug("toggle subeffect!"); + // var buttonNumber = this.channelToIndex(field.group); + // if (buttonNumber === undefined) { + // HIDDebug("Programming Error: unexpectedly couldn't parse group: " + field.group); + // return; + // } + + // var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; + // var key = "enabled"; + // HIDDebug("flipping " + group + " " + key); + // script.toggleControl(group, key); + // } + // this.StatusDebug(); }; TraktorS3.fxGroupPrefix = function(group) { diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index b00cc831a11..98286e3c089 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -12,11 +12,15 @@ class TraktorS3Test : public ControllerTest { protected: void SetUp() override { ControllerTest::SetUp(); - QString hidScript = "./res/controllers/common-hid-packet-parser.js"; + const QString commonScript = "./res/controllers/common-controller-scripts.js"; + const QString hidScript = "./res/controllers/common-hid-packet-parser.js"; + const QString scriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; + ASSERT_TRUE(m_pCEngine->evaluate(commonScript)); + ASSERT_FALSE(m_pCEngine->hasErrors(commonScript)); ASSERT_TRUE(m_pCEngine->evaluate(hidScript)); ASSERT_FALSE(m_pCEngine->hasErrors(hidScript)); - ASSERT_TRUE(m_pCEngine->evaluate(m_sScriptFile)); - ASSERT_FALSE(m_pCEngine->hasErrors(m_sScriptFile)); + ASSERT_TRUE(m_pCEngine->evaluate(scriptFile)); + ASSERT_FALSE(m_pCEngine->hasErrors(scriptFile)); // Create useful objects and getters evaluate( @@ -41,9 +45,6 @@ class TraktorS3Test : public ControllerTest { STATE_EFFECT, STATE_FOCUS }; - - private: - const QString m_sScriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; }; TEST_F(TraktorS3Test, FXSelectButtonSimple) { @@ -127,26 +128,12 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { TEST_F(TraktorS3Test, FXSelectFocusToggle) { ASSERT_TRUE(evaluate( - "var pressFx2 = { " - " group: '[ChannelX]', " - " name: '!fx2', " - " value: 1, " - "}; " - "var unpressFx2 = { " - " group: '[ChannelX]', " - " name: '!fx2', " - " value: 0, " - "}; " - "var pressFilter = { " - " group: '[ChannelX]', " - " name: '!fx0', " - " value: 1, " - "}; " - "var unpressFilter = { " - " group: '[ChannelX]', " - " name: '!fx0', " - " value: 0, " - "}; ") + "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" + "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" + "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" + "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" + "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" + "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") .isValid()); // Press FX2 and release @@ -164,10 +151,91 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - // Press again + // Press 2 again, focus + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Press again, back to effect mode + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Press 2 again, focus evaluate( "TestOb.fxc.fxSelectHandler(pressFx2); " "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + + // Press 3, effect + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx3); " + "TestOb.fxc.fxSelectHandler(unpressFx3);"); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + EXPECT_EQ(3, evaluate("getActiveFx();").toInt32()); + + // Press 2, press 2, press filter = filter + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);" + "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(unpressFx2);" + "TestOb.fxc.fxSelectHandler(pressFilter); " + "TestOb.fxc.fxSelectHandler(unpressFilter);"); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); + EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); } + +TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { + ASSERT_TRUE(evaluate( + "var pressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" + "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" + "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" + "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" + "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" + "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" + "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") + .isValid()); + + // Press FXEnable 1 and release + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1); "); + auto ret = evaluate("getEnablePressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array1[4] = {true, false, false, false}; + for (int i = 0; i < 4; ++i) { + QString group = QString("[Channel%1]").arg(i + 1); + EXPECT_TRUE(ret.property(group).isValid()); + EXPECT_EQ(expected_array1[i], ret.property(group).toBool()); + } + + evaluate( + "TestOb.fxc.fxEnableHandler(unpressFxEnable1); "); + ret = evaluate("getEnablePressed();"); + ASSERT_TRUE(ret.isValid()); + + bool expected_array2[5] = {false, false, false, false}; + for (int i = 1; i <= 4; ++i) { + QString group = QString("[Channel%1]").arg(i); + EXPECT_TRUE(ret.property(group).isValid()); + EXPECT_EQ(expected_array2[i], ret.property(group).toBool()); + } + + // Press enable 1, fx2, should enable effect unit 2 for channel 1 + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxSelectHandler(pressFx2);"); + + EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", + "group_1_enable")) + ->get()); +} \ No newline at end of file From 3f2381903d6ee67b100a8eea03cc1065c4be4821 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 1 Sep 2020 11:07:16 -0400 Subject: [PATCH 55/84] Traktor S3: add a test for enable/disable effect units per channel --- .../Traktor-Kontrol-S3-hid-scripts.js | 28 ++++++------ .../controllers/Traktor_Kontrol_S3_test.cpp | 43 ++++++++++++++++++- src/test/controllers/controllertest.h | 6 ++- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 71148d75181..ca41f64438d 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1207,24 +1207,22 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { return; } - // // If any fxEnable button is pressed, we are toggling fx unit assignment. - // if (this.enablePressed !== "") { - if (this.anyEnablePressed()) { - var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - for (var key in this.enablePressed) { - if (this.enablePressed[key]) { - HIDDebug("key? " + key); - var fxKey = "group_" + key + "_enable"; - script.toggleControl(fxGroup, fxKey); + switch (this.currentState) { + case this.STATE_FILTER: + // If any fxEnable button is pressed, we are toggling fx unit assignment. + if (this.anyEnablePressed()) { + var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + for (var key in this.enablePressed) { + if (this.enablePressed[key]) { + HIDDebug("key? " + key); + var fxKey = "group_" + key + "_enable"; + script.toggleControl(fxGroup, fxKey); + } } + // this.StatusDebug(); + return; } - // this.StatusDebug(); - return; - } - // Un-shifted presses - switch (this.currentState) { - case this.STATE_FILTER: // If we push filter and filter is already pushed, do nothing. if (fxNumber === 0) { this.currentState = this.STATE_FILTER; diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index 98286e3c089..cb8dc52a07f 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -6,12 +6,17 @@ #include "controllers/controllerdebug.h" #include "controllers/controllerengine.h" #include "controllers/softtakeover.h" +#include "effects/effectchain.h" +#include "effects/effectsmanager.h" #include "test/controllers/controllertest.h" +#include "test/signalpathtest.h" class TraktorS3Test : public ControllerTest { protected: void SetUp() override { ControllerTest::SetUp(); + m_pRack = m_pEffectsManager->addStandardEffectRack(); + const QString commonScript = "./res/controllers/common-controller-scripts.js"; const QString hidScript = "./res/controllers/common-hid-packet-parser.js"; const QString scriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; @@ -45,6 +50,7 @@ class TraktorS3Test : public ControllerTest { STATE_EFFECT, STATE_FOCUS }; + StandardEffectRackPointer m_pRack; }; TEST_F(TraktorS3Test, FXSelectButtonSimple) { @@ -233,9 +239,42 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { // Press enable 1, fx2, should enable effect unit 2 for channel 1 evaluate( "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxSelectHandler(pressFx2);"); + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_1_enable")) + "group_[Channel1]_enable")) ->get()); + + // Press enable fx2 again, should disable effect unit 2 for channel 1 + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", + "group_[Channel1]_enable")) + ->get()); + + // Unpress fxenable, back where we started. + evaluate( + "TestOb.fxc.fxEnableHandler(unpressFxEnable1); "); + ret = evaluate("getEnablePressed();"); + ASSERT_TRUE(ret.isValid()); + + for (int i = 1; i <= 4; ++i) { + QString group = QString("[Channel%1]").arg(i); + EXPECT_TRUE(ret.property(group).isValid()); + EXPECT_EQ(expected_array2[i], ret.property(group).toBool()); + } + + // If we're not in filter mode, fxenable doesn't cause us to enable/disable units + // (this would enable/disable the effectunit, but that's tested elsewhere) + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx3);" + "TestOb.fxc.fxSelectHandler(unpressFx3);" + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxSelectHandler(pressFx2);"); + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", + "group_[Channel1]_enable")) + ->get()); } \ No newline at end of file diff --git a/src/test/controllers/controllertest.h b/src/test/controllers/controllertest.h index e7bd3ba59da..62b108de430 100644 --- a/src/test/controllers/controllertest.h +++ b/src/test/controllers/controllertest.h @@ -5,10 +5,12 @@ #include "controllers/controllerdebug.h" #include "controllers/controllerengine.h" -#include "test/mixxxtest.h" +#include "test/signalpathtest.h" #include "util/time.h" -class ControllerTest : public MixxxTest { +// ControllerTest inherits from BaseSignalPathTest so that all of the standard +// channels, effects units, etc exist. +class ControllerTest : public BaseSignalPathTest { protected: void SetUp() override { mixxx::Time::setTestMode(true); From d0fc61d0cec38d6f7de4361e6bc80192ec186176 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 1 Sep 2020 15:47:49 -0400 Subject: [PATCH 56/84] TraktorS3: more effects mapping and testing --- .../Traktor-Kontrol-S3-hid-scripts.js | 169 +++++++++----- .../controllers/Traktor_Kontrol_S3_test.cpp | 207 ++++++++++++++++-- src/test/controllers/controllertest.h | 13 ++ 3 files changed, 323 insertions(+), 66 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index ca41f64438d..e45c966169b 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1171,20 +1171,29 @@ TraktorS3.FXControl.prototype.StatusDebug = function() { } }; -TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { - var group = "[EffectRack1_EffectUnit" + fxNum + "]"; - var newState = !engine.getValue(group, "focused_effect"); - - if (!newState) { - engine.setValue(group, "focused_effect", 0); - return; - } - // If we are setting a different unit to be enabled, the others become - // disabled. - for (var i = 1; i <= 4; i++) { - group = "[EffectRack1_EffectUnit" + i + "]"; - engine.setValue(group, "focused_effect", i === fxNum); +// TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { +// var group = "[EffectRack1_EffectUnit" + fxNum + "]"; +// var newState = !engine.getValue(group, "focused_effect"); + +// if (!newState) { +// engine.setValue(group, "focused_effect", 0); +// return; +// } +// // If we are setting a different unit to be enabled, the others become +// // disabled. +// for (var i = 1; i <= 4; i++) { +// group = "[EffectRack1_EffectUnit" + i + "]"; +// engine.setValue(group, "focused_effect", i === fxNum); +// } +// }; + +TraktorS3.FXControl.prototype.firstPressedSelect = function() { + for (var idx in this.selectPressed) { + if (this.selectPressed[idx]) { + return idx; + } } + return undefined; }; TraktorS3.FXControl.prototype.anyEnablePressed = function() { @@ -1238,14 +1247,14 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { if (fxNumber === 0) { this.currentState = this.STATE_FILTER; } else { - if (fxNumber === this.activeFX) { - this.currentState = this.STATE_FOCUS; - } else { - // Select this filter instead. - // initiate state change - // this.activateState(this.STATE_EFFECT, fxNumber); - this.currentState = this.STATE_EFFECT; - } + // if (fxNumber === this.activeFX) { + // this.currentState = this.STATE_FOCUS; + // } else { + // // Select this filter instead. + // // initiate state change + // // this.activateState(this.STATE_EFFECT, fxNumber); + this.currentState = this.STATE_EFFECT; + // } } this.activeFX = fxNumber; break; @@ -1272,8 +1281,40 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { return; } - // switch (field.group) { - // } + HIDDebug("eh?-------------"); + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + var buttonNumber = this.channelToIndex(field.group); + switch (this.currentState) { + case this.STATE_FILTER: + HIDDebug("filter"); + break; + case this.STATE_EFFECT: + HIDDebug("effect mode"); + if (this.firstPressedSelect()) { + HIDDebug("change to focus"); + // Choose the first pressed select button only. + this.currentState = this.STATE_FOCUS; + HIDDebug("focusing " + fxGroupPrefix + " " + buttonNumber); + // var focusedEffect = engine.getValue(effectUnitGroup, "focused_effect"); + engine.setValue(fxGroupPrefix + "]", "focused_effect", buttonNumber); + } else { + HIDDebug("effect togg? " + field.group); + var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; + var key = "enabled"; + HIDDebug("toggling " + group + " " + key); + script.toggleControl(group, key); + } + break; + case this.STATE_FOCUS: + HIDDebug("focus mode"); + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; + key = "parameter" + buttonNumber; + HIDDebug("toggling " + group + " " + key); + script.toggleControl(group, key); + break; + } // HIDDebug("FX ENABLE " + field.group + " " + field.name + " " + field.value); // if (field.value === 0) { @@ -1326,47 +1367,71 @@ TraktorS3.fxGroupPrefix = function(group) { TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); var value = field.value / 4095.; + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; var knobIdx = this.channelToIndex(field.group); - // unfocus: twisting knobs will adjust metaknob (or quickeffect) for that effect - if (this.activeFX === this.FILTER_EFFECT) { + switch (this.currentState) { + case this.STATE_FILTER: + HIDDebug("filter"); if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { // There is no quickeffect for the microphone, do nothing. - this.StatusDebug(); + // this.StatusDebug(); return; } HIDDebug("HERE?2 " + "[QuickEffectRack1_" + field.group + "]"); engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); - this.StatusDebug(); - return; - } - - var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; - HIDDebug("asking for: " + fxGroupPrefix + "]"); - var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - if (focusedEffect > 0) { - // focus: shift + adjust selects effect + break; + case this.STATE_EFFECT: // if (TraktorS3.anyShiftPressed()) { - // // engine.setValue(field.group, "load_preset", knobIdx+1); + // HIDDebug("load preset"); + // engine.setValue(effectUnitGroup, "load_preset", buttonNumber+1); + // } else { + HIDDebug("effect"); + if (knobIdx === 4) { + engine.setParameter(fxGroupPrefix + "]", "mix", value); + } else { + HIDDebug("set " + group + " meta"); + var group = fxGroupPrefix + "_Effect" + knobIdx + "]"; + engine.setParameter(group, "meta", value); + } // } - HIDDebug("focused?? "); - - // focus: adjusts params for that effect - HIDDebug("PARAM: " + fxGroupPrefix + "_Effect" + focusedEffect + "]"); - engine.setParameter(fxGroupPrefix + "_Effect" + focusedEffect + "]", - "parameter" + knobIdx, - field.value / 4096); - this.StatusDebug(); - return; + break; + case this.STATE_FOCUS: + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + HIDDebug("focus " + focusedEffect); + group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; + var key = "parameter" + knobIdx; + HIDDebug("set " + group + " " + key); + engine.setParameter(group, key, value); + break; } - // unfocus: other fx selected: adjust meta knob per channel - //XXXXX there's only meta knob per effect, not channel! - HIDDebug("HERE?"); - engine.setParameter(fxGroupPrefix + "]", - "super1", - field.value / 4096); - this.StatusDebug(); + // var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + // HIDDebug("asking for: " + fxGroupPrefix + "]"); + // var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + // if (focusedEffect > 0) { + // // focus: shift + adjust selects effect + // // if (TraktorS3.anyShiftPressed()) { + // // // engine.setValue(field.group, "load_preset", knobIdx+1); + // // } + // HIDDebug("focused?? "); + + // // focus: adjusts params for that effect + // HIDDebug("PARAM: " + fxGroupPrefix + "_Effect" + focusedEffect + "]"); + // engine.setParameter(fxGroupPrefix + "_Effect" + focusedEffect + "]", + // "parameter" + knobIdx, + // field.value / 4096); + // this.StatusDebug(); + // return; + // } + + // // unfocus: other fx selected: adjust meta knob per channel + // //XXXXX there's only meta knob per effect, not channel! + // HIDDebug("HERE?"); + // engine.setParameter(fxGroupPrefix + "]", + // "super1", + // field.value / 4096); + // this.StatusDebug(); }; // FX LIGHTS: diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index cb8dc52a07f..e3bca1457d7 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -15,7 +15,6 @@ class TraktorS3Test : public ControllerTest { protected: void SetUp() override { ControllerTest::SetUp(); - m_pRack = m_pEffectsManager->addStandardEffectRack(); const QString commonScript = "./res/controllers/common-controller-scripts.js"; const QString hidScript = "./res/controllers/common-hid-packet-parser.js"; @@ -42,7 +41,12 @@ class TraktorS3Test : public ControllerTest { "};" "var getEnablePressed = function() {" " return TestOb.fxc.enablePressed;" - "};"); + "};" + // Mock out shift key. + "TestOb.shiftPressed = false;" + "TraktorS3.anyShiftPressed = function() {" + " return TestOb.shiftPressed;" + "}"); } enum states { @@ -50,9 +54,10 @@ class TraktorS3Test : public ControllerTest { STATE_EFFECT, STATE_FOCUS }; - StandardEffectRackPointer m_pRack; }; +// Test tapping fx select buttons to toggle states -- Filter for filter state, any fx unit +// for effect state. TEST_F(TraktorS3Test, FXSelectButtonSimple) { ASSERT_TRUE(evaluate( "var pressFx2 = { " @@ -132,8 +137,11 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); } +// Hold FX button + tap effect enable focuses that effect. TEST_F(TraktorS3Test, FXSelectFocusToggle) { ASSERT_TRUE(evaluate( + "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" @@ -157,9 +165,11 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - // Press 2 again, focus + // Press 2 and tap enable2, focus second effect evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxEnableHandler(pressFxEnable2);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); @@ -171,13 +181,6 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - // Press 2 again, focus - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - // Press 3, effect evaluate( "TestOb.fxc.fxSelectHandler(pressFx3); " @@ -187,17 +190,27 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { // Press 2, press 2, press filter = filter evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " - "TestOb.fxc.fxSelectHandler(unpressFx2);" "TestOb.fxc.fxSelectHandler(pressFx2); " "TestOb.fxc.fxSelectHandler(unpressFx2);" "TestOb.fxc.fxSelectHandler(pressFilter); " "TestOb.fxc.fxSelectHandler(unpressFilter);"); EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); + + // Hold filter, press enable, noop + evaluate( + "TestOb.fxc.fxSelectHandler(pressFilter); " + "TestOb.fxc.fxEnableHandler(pressFxEnable2);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" + "TestOb.fxc.fxSelectHandler(unpressFilter);"); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); + EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); } +// Test Enable buttons + FX Select buttons to enable/disable fx units per channel. +// This is only available during Filter state. TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { + // IMPORTANT: Channel 1 is the second button (CABD mapping). ASSERT_TRUE(evaluate( "var pressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 1 };" "var unpressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 0 };" @@ -237,6 +250,7 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { } // Press enable 1, fx2, should enable effect unit 2 for channel 1 + // Keep enable pressed evaluate( "TestOb.fxc.fxEnableHandler(pressFxEnable1);" "TestOb.fxc.fxSelectHandler(pressFx2);" @@ -273,8 +287,173 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { "TestOb.fxc.fxSelectHandler(pressFx3);" "TestOb.fxc.fxSelectHandler(unpressFx3);" "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" "TestOb.fxc.fxSelectHandler(pressFx2);"); EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", "group_[Channel1]_enable")) ->get()); +} + +// In FX Mode, the FX Enable buttons toggle effect units +TEST_F(TraktorS3Test, FXModeFXEnable) { + // IMPORTANT: Channel 3 is the first button. + ASSERT_TRUE(evaluate( + "var pressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" + "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" + "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" + "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" + "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };") + .isValid()); + + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "enabled")) + ->get()); + + // Enable effect mode + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + + // Effect Unit 1 is toggled + EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "enabled")) + ->get()); + + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + + // Effect Unit 1 is toggled + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "enabled")) + ->get()); +} + +// In Focus Mode, the FX Enable buttons toggle effect parameter values +TEST_F(TraktorS3Test, FocusModeFXEnable) { + // IMPORTANT: Channel 3 is the first button. + ASSERT_TRUE(evaluate( + "var pressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" + "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" + "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" + "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" + "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };") + .isValid()); + + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "enabled")) + ->get()); + + // Enable focus mode for fx2, effect 1. + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); + + // Effect1 in Unit 2 is toggled + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "parameter1")) + ->get()); + + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + + // Effect Unit 1 is toggled + EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "parameter1")) + ->get()); + + evaluate( + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + + // Effect Unit 1 is toggled + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", + "parameter1")) + ->get()); +} + +// Test knob behavior in different states +TEST_F(TraktorS3Test, KnobTest) { + ASSERT_TRUE(evaluate( + "var pressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" + "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" + "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" + "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" + "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" + "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" + "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") + .isValid()); + + // STATE_FILTER: knobs control quickeffects + evaluate( + "TestOb.fxc.fxSelectHandler(pressFilter);" + "TestOb.fxc.fxSelectHandler(unpressFilter);"); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); + + evaluate( + "TestOb.fxc.fxKnobHandler( { group: '[Channel1]', name: '!fxKnob', " + "value: 0.75*4095 } );"); + EXPECT_FLOAT_EQ(0.75, + ControlObject::getControl( + ConfigKey("[QuickEffectRack1_[Channel1]]", "super1")) + ->get()); + + // STATE_EFFECT: knobs control effectunit meta knobs + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + + // Note, Channel2 is the third knob + evaluate( + "TestOb.fxc.fxKnobHandler( { group: '[Channel2]', name: '!fxKnob', " + "value: 0.62*4095 } );"); + EXPECT_FLOAT_EQ(0.62, + ControlObject::getControl( + ConfigKey("[EffectRack1_EffectUnit2_Effect3]", "meta")) + ->get()); + + // Knob 4 is the mix knob + evaluate( + "TestOb.fxc.fxKnobHandler( { group: '[Channel4]', name: '!fxKnob', " + "value: 0.22*4095 } );"); + EXPECT_FLOAT_EQ(0.22, + ControlObject::getControl( + ConfigKey("[EffectRack1_EffectUnit2]", "mix")) + ->get()); + + // Set state to Focus -- knobs control effect parameters + evaluate( + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" + "TestOb.fxc.fxSelectHandler(unpressFx2);"); + EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); + + evaluate( + "TestOb.fxc.fxKnobHandler( { group: '[Channel3]', name: '!fxKnob', " + "value: 0.12*4095 } );"); + EXPECT_FLOAT_EQ(0.12, + ControlObject::getControl( + ConfigKey( + "[EffectRack1_EffectUnit2_Effect2]", "parameter1")) + ->get()); } \ No newline at end of file diff --git a/src/test/controllers/controllertest.h b/src/test/controllers/controllertest.h index 62b108de430..119869f982d 100644 --- a/src/test/controllers/controllertest.h +++ b/src/test/controllers/controllertest.h @@ -5,6 +5,7 @@ #include "controllers/controllerdebug.h" #include "controllers/controllerengine.h" +#include "effects/effectrack.h" #include "test/signalpathtest.h" #include "util/time.h" @@ -19,6 +20,16 @@ class ControllerTest : public BaseSignalPathTest { m_pCEngine = new ControllerEngine(nullptr, config()); ControllerDebug::enable(); m_pCEngine->setPopups(false); + m_pRack = m_pEffectsManager->addStandardEffectRack(); + // EffectChainSlotPointer pChainSlot = pRack->getEffectChainSlot(iChainNumber); + // pChainSlot->registerInputChannel(m_master); + m_pQuickRack = m_pEffectsManager->addQuickEffectRack(); + // Only 3 decks in BaseSignalPathTest + m_pQuickRack->setupForGroup("[Channel1]"); + m_pQuickRack->setupForGroup("[Channel2]"); + m_pQuickRack->setupForGroup("[Channel3]"); + // pChainSlot = pRack->getEffectChainSlot(iChainNumber); + // pChainSlot->registerInputChannel(m_master); } void TearDown() override { @@ -45,4 +56,6 @@ class ControllerTest : public BaseSignalPathTest { } ControllerEngine* m_pCEngine; + StandardEffectRackPointer m_pRack; + QuickEffectRackPointer m_pQuickRack; }; \ No newline at end of file From c730525a1164fd41535e51f6b75fdfabbeefe88f Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 1 Sep 2020 15:49:41 -0400 Subject: [PATCH 57/84] Traktor S3: remove commented cruft --- .../Traktor-Kontrol-S3-hid-scripts.js | 90 +------------------ 1 file changed, 1 insertion(+), 89 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index e45c966169b..bbdc6900c6d 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1205,8 +1205,6 @@ TraktorS3.FXControl.prototype.anyEnablePressed = function() { return false; }; -// pressing FX SELECT buttons changes the activeFX -// press again to focus, press again to unfocus. (focus should blink) TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { var fxNumber = parseInt(field.name[field.name.length - 1]); this.selectPressed[fxNumber] = field.value; @@ -1244,20 +1242,7 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { this.activeFX = fxNumber; break; case this.STATE_EFFECT: - if (fxNumber === 0) { - this.currentState = this.STATE_FILTER; - } else { - // if (fxNumber === this.activeFX) { - // this.currentState = this.STATE_FOCUS; - // } else { - // // Select this filter instead. - // // initiate state change - // // this.activateState(this.STATE_EFFECT, fxNumber); - this.currentState = this.STATE_EFFECT; - // } - } - this.activeFX = fxNumber; - break; + // Fallthrough intended case this.STATE_FOCUS: if (fxNumber === 0) { this.currentState = this.STATE_FILTER; @@ -1270,8 +1255,6 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; }; -// in unfocus mode, tap.... does nothing? Just highlights which FX are enabled for that deck while -// held. TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { HIDDebug("we are here: " + field.group + " " + field.value); this.enablePressed[field.group] = field.value; @@ -1315,45 +1298,6 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { script.toggleControl(group, key); break; } - - // HIDDebug("FX ENABLE " + field.group + " " + field.name + " " + field.value); - // if (field.value === 0) { - // if (this.enablePressed === field.group) { - // this.enablePressed = ""; - // } - // this.StatusDebug(); - // return; - // } - - // // in unfocus mode, preess and hold + tap fxselect to enable/disable per channel - // // if select was pressed first though, ignore pressing enable as a press-and-hold. - // if (this.selectPressed === -1) { - // HIDDebug("TOGGLE"); - // this.enablePressed = field.group; - // } - - // // in focus mode, tap fxenable enables/disables individual fx in units. - // // var fxGroupPrefix = TraktorS3.fxGroupPrefix(this.); - // // if (fxGroupPrefix === undefined) { - // // HIDDebug("Programming Error: Didn't match channel number in fxSelectHandler: " + field.group); - // // return; - // // } - // var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; - // var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - // if (focusedEffect > 0) { - // HIDDebug("toggle subeffect!"); - // var buttonNumber = this.channelToIndex(field.group); - // if (buttonNumber === undefined) { - // HIDDebug("Programming Error: unexpectedly couldn't parse group: " + field.group); - // return; - // } - - // var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; - // var key = "enabled"; - // HIDDebug("flipping " + group + " " + key); - // script.toggleControl(group, key); - // } - // this.StatusDebug(); }; TraktorS3.fxGroupPrefix = function(group) { @@ -1382,10 +1326,6 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); break; case this.STATE_EFFECT: - // if (TraktorS3.anyShiftPressed()) { - // HIDDebug("load preset"); - // engine.setValue(effectUnitGroup, "load_preset", buttonNumber+1); - // } else { HIDDebug("effect"); if (knobIdx === 4) { engine.setParameter(fxGroupPrefix + "]", "mix", value); @@ -1394,7 +1334,6 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { var group = fxGroupPrefix + "_Effect" + knobIdx + "]"; engine.setParameter(group, "meta", value); } - // } break; case this.STATE_FOCUS: var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); @@ -1405,33 +1344,6 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { engine.setParameter(group, key, value); break; } - - // var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; - // HIDDebug("asking for: " + fxGroupPrefix + "]"); - // var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - // if (focusedEffect > 0) { - // // focus: shift + adjust selects effect - // // if (TraktorS3.anyShiftPressed()) { - // // // engine.setValue(field.group, "load_preset", knobIdx+1); - // // } - // HIDDebug("focused?? "); - - // // focus: adjusts params for that effect - // HIDDebug("PARAM: " + fxGroupPrefix + "_Effect" + focusedEffect + "]"); - // engine.setParameter(fxGroupPrefix + "_Effect" + focusedEffect + "]", - // "parameter" + knobIdx, - // field.value / 4096); - // this.StatusDebug(); - // return; - // } - - // // unfocus: other fx selected: adjust meta knob per channel - // //XXXXX there's only meta knob per effect, not channel! - // HIDDebug("HERE?"); - // engine.setParameter(fxGroupPrefix + "]", - // "super1", - // field.value / 4096); - // this.StatusDebug(); }; // FX LIGHTS: From 9a2a924cecb45e50d50b9f63731a26cdc3124cec Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 1 Sep 2020 20:00:56 -0400 Subject: [PATCH 58/84] Traktor S3: start testing lights --- .../Traktor-Kontrol-S3-hid-scripts.js | 206 ++++++++++++++---- .../controllers/Traktor_Kontrol_S3_test.cpp | 26 +++ 2 files changed, 190 insertions(+), 42 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index bbdc6900c6d..6c5b2ddcdb7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1096,6 +1096,7 @@ TraktorS3.FXControl = function() { // 0 is filter, 1-4 are FX Units 1-4 this.FILTER_EFFECT = 0; this.activeFX = this.FILTER_EFFECT; + this.controller = TraktorS3.controller; this.enablePressed = { "[Channel1]": false, @@ -1110,6 +1111,13 @@ TraktorS3.FXControl = function() { 3: false, 4: false }; + this.selectBlinkState = { + 0: false, + 1: false, + 2: false, + 3: false, + 4: false + }; // States this.STATE_FILTER = 0; @@ -1196,6 +1204,15 @@ TraktorS3.FXControl.prototype.firstPressedSelect = function() { return undefined; }; +TraktorS3.FXControl.prototype.firstPressedEnable = function() { + for (var ch in this.enablePressed) { + if (this.enablePressed[ch]) { + return ch; + } + } + return undefined; +}; + TraktorS3.FXControl.prototype.anyEnablePressed = function() { for (var key in this.enablePressed) { if (this.enablePressed[key]) { @@ -1211,6 +1228,7 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { if (!field.value) { // do lights? + this.lightFX(); return; } @@ -1226,20 +1244,18 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { script.toggleControl(fxGroup, fxKey); } } - // this.StatusDebug(); - return; - } - - // If we push filter and filter is already pushed, do nothing. - if (fxNumber === 0) { - this.currentState = this.STATE_FILTER; } else { - // Select this filter instead. - // initiate state change - // this.activateState(this.STATE_EFFECT, fxNumber); - this.currentState = this.STATE_EFFECT; + // If we push filter and filter is already pushed, do nothing. + if (fxNumber === 0) { + this.currentState = this.STATE_FILTER; + } else { + // Select this filter instead. + // initiate state change + // this.activateState(this.STATE_EFFECT, fxNumber); + this.currentState = this.STATE_EFFECT; + } + this.activeFX = fxNumber; } - this.activeFX = fxNumber; break; case this.STATE_EFFECT: // Fallthrough intended @@ -1252,7 +1268,7 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { this.activeFX = fxNumber; break; } - // var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + this.lightFX(); }; TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { @@ -1261,10 +1277,10 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { if (!field.value) { // do lights? + this.lightFX(); return; } - HIDDebug("eh?-------------"); var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; var buttonNumber = this.channelToIndex(field.group); switch (this.currentState) { @@ -1298,15 +1314,16 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { script.toggleControl(group, key); break; } + this.lightFX(); }; -TraktorS3.fxGroupPrefix = function(group) { - var channelMatch = group.match(script.channelRegEx); - if (channelMatch === undefined) { - return undefined; - } - return "[EffectRack1_EffectUnit" + channelMatch[1]; -}; +// TraktorS3.fxGroupPrefix = function(group) { +// var channelMatch = group.match(script.channelRegEx); +// if (channelMatch === undefined) { +// return undefined; +// } +// return "[EffectRack1_EffectUnit" + channelMatch[1]; +// }; TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); @@ -1346,6 +1363,18 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { } }; +TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, enabled) { + var ledValue = TraktorS3.fxLEDValue[fxNumber]; + ledValue += enabled ? TraktorS3LEDBrightValue : TraktorS3LEDDimValue; + return ledValue; +}; + +TraktorS3.FXControl.prototype.getChannelColor = function(group, enabled) { + var ledValue = TraktorS3.controller.LEDColors[TraktorS3ChannelColors[group]]; + ledValue += enabled ? TraktorS3LEDBrightValue : TraktorS3LEDDimValue; + return ledValue; +}; + // FX LIGHTS: // if a button is pressed, definitely it should be on // if enable is pressed, @@ -1356,7 +1385,100 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { // if unfocused, enables are lit depending on which channels have that effect enabled. (for filter, // that's all) // if focused, enables are lit depending on which units are active. +TraktorS3.FXControl.prototype.lightFX = function() { + // Idx zero is filter button + TraktorS3.batchingOutputs = true; + // Loop through select buttons + for (var idx = 0; idx < 5; idx++) { + this.lightSelect(idx); + } + for (var ch = 1; ch <= 4; ch++) { + var channel = "[Channel" + ch + "]"; + this.lightEnabled(channel); + } + + TraktorS3.batchingOutputs = false; +}; + +TraktorS3.FXControl.prototype.lightSelect = function(idx) { + var enabled = false; + switch (this.currentState) { + case this.STATE_FILTER: + HIDDebug("filter"); + if (idx === 0) { + // filter select button highlighted unless any enable is pressed + if (!this.anyEnablePressed()) { + enabled = true; + } + } else { + // select buttons highlighted if fx unit enabled for the pressed channel + var pressed = this.firstPressedSelect(); + if (pressed && pressed !== "0") { + var fxGroup = "[EffectRack1_EffectUnit" + idx + "]"; + var fxKey = "group_[Channel" + pressed + "]_enable"; + if (engine.getParameter(fxGroup, fxKey)) { + enabled = true; + } + } + } + break; + case this.STATE_EFFECT: + HIDDebug("effect"); + // select button highlighted for active effect + enabled = (idx === this.activeFX); + break; + case this.STATE_FOCUS: + HIDDebug("focus"); + // select button blinking for active effect + break; + } + var ledValue = this.getFXSelectLEDValue(idx, enabled); + this.controller.setOutput("[ChannelX]", "!fxButton" + idx, ledValue, false); +}; + +TraktorS3.FXControl.prototype.lightEnabled = function(channel) { + var enabled = false; + switch (this.currentState) { + case this.STATE_FILTER: + HIDDebug("filter"); + // enable buttons have regular deck colors + // enable buttons highlighted if pressed or if any fx unit enabled. + // var ledValue = this.getFXSelectLEDValue(idx, enabled) + break; + case this.STATE_EFFECT: + HIDDebug("effect"); + // enable buttons have same color as effect + // enable buttons highlighted if pressed or effect in unit is enabled + break; + case this.STATE_FOCUS: + HIDDebug("focus"); + // enable buttons have same color as effect + // enable buttons highlighted if... button and 1?? + break; + } + var ledValue = this.getChannelColor(channel, enabled); + this.controller.setOutput(channel, "!fxEnabled", ledValue, false); +}; +// for (var ch in TraktorS3.Channels) { +// var chanob = TraktorS3.Channels[ch]; +// if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { +// chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); +// } else { +// chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); +// } +// } +// for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { +// var ledValue = TraktorS3.fxLEDValue[fxNumber]; +// if (TraktorS3.fxButtonState[fxNumber]) { +// ledValue += TraktorS3LEDBrightValue; +// } else { +// ledValue += TraktorS3LEDDimValue; +// } +// TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); +// } +// HIDDebug("exit"); +// }; TraktorS3.registerInputPackets = function() { @@ -1606,7 +1728,7 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[ChannelX]", "!fxButton2", 0x3D, "B"); outputA.addOutput("[ChannelX]", "!fxButton3", 0x3E, "B"); outputA.addOutput("[ChannelX]", "!fxButton4", 0x3F, "B"); - outputA.addOutput("[ChannelX]", "!fxButton5", 0x40, "B"); + outputA.addOutput("[ChannelX]", "!fxButton0", 0x40, "B"); outputA.addOutput("[Channel3]", "!fxEnabled", 0x34, "B"); outputA.addOutput("[Channel1]", "!fxEnabled", 0x35, "B"); @@ -1804,25 +1926,25 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { } }; -TraktorS3.lightFX = function() { - for (var ch in TraktorS3.Channels) { - var chanob = TraktorS3.Channels[ch]; - if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { - chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); - } else { - chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); - } - } - for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { - var ledValue = TraktorS3.fxLEDValue[fxNumber]; - if (TraktorS3.fxButtonState[fxNumber]) { - ledValue += TraktorS3LEDBrightValue; - } else { - ledValue += TraktorS3LEDDimValue; - } - TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); - } -}; +// TraktorS3.lightFX = function() { +// for (var ch in TraktorS3.Channels) { +// var chanob = TraktorS3.Channels[ch]; +// if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { +// chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); +// } else { +// chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); +// } +// } +// for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { +// var ledValue = TraktorS3.fxLEDValue[fxNumber]; +// if (TraktorS3.fxButtonState[fxNumber]) { +// ledValue += TraktorS3LEDBrightValue; +// } else { +// ledValue += TraktorS3LEDDimValue; +// } +// TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); +// } +// }; TraktorS3.lightDeck = function(group, sendPackets) { if (sendPackets === undefined) { @@ -1854,7 +1976,7 @@ TraktorS3.lightDeck = function(group, sendPackets) { TraktorS3.basicOutput(0, "[Master]", "!extButton"); } } - TraktorS3.lightFX(); + // TraktorS3.lightFX(); // Selected deck lights var ctrlr = TraktorS3.controller; diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index e3bca1457d7..f7b8dc727d3 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -47,6 +47,31 @@ class TraktorS3Test : public ControllerTest { "TraktorS3.anyShiftPressed = function() {" " return TestOb.shiftPressed;" "}"); + + // Mock out controller for testing lights + evaluate( + "TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, enabled) {" + " return fxNumber*10 + (enabled ? 1 : 0);" + "};" + "TraktorS3.FXControl.prototype.getChannelColor = function(group, enabled) {" + " return parseInt(group[8])*10 + (enabled ? 1 : 0);" + "};" + "TestOb.fxc.controller = new function() {" + " this.lightMap = {}; " + " this.setOutput = function(group, key, value, batching) {" + " if (!(group in this.lightMap)) {" + " this.lightMap[group] = {};" + " }" + " HIDDebug('light: ' + group + ' ' + key + ' ' + value);" + " this.lightMap[group][key] = value;" + " };" + "};" + "var getLight = function(group, key) {" + " if (!(group in TestOb.fxc.controller.lightMap)) {" + " return undefined;" + " }" + " return TestOb.fxc.controller.lightMap[group][key];" + "};"); } enum states { @@ -97,6 +122,7 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { } EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + EXPECT_EQ(21, evaluate("getLight('[ChannelX]', '!fxButton2');").toInt32()); // Now unpress select and release evaluate("TestOb.fxc.fxSelectHandler(unpressFx2);"); From c7e34f3ee2b815616ba9a3082da157d8bee0454a Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 1 Sep 2020 23:30:06 -0400 Subject: [PATCH 59/84] Traktor S3: more tests --- .../Traktor-Kontrol-S3-hid-scripts.js | 50 ++++++++++----- .../controllers/Traktor_Kontrol_S3_test.cpp | 61 ++++++++++++++++++- src/test/controllers/controllertest.h | 1 + src/test/signalpathtest.cpp | 1 + src/test/signalpathtest.h | 16 ++++- 5 files changed, 110 insertions(+), 19 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 6c5b2ddcdb7..8e90e0867f7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1285,10 +1285,10 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { var buttonNumber = this.channelToIndex(field.group); switch (this.currentState) { case this.STATE_FILTER: - HIDDebug("filter"); + // HIDDebug("filter"); break; case this.STATE_EFFECT: - HIDDebug("effect mode"); + // HIDDebug("effect mode"); if (this.firstPressedSelect()) { HIDDebug("change to focus"); // Choose the first pressed select button only. @@ -1333,7 +1333,7 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { switch (this.currentState) { case this.STATE_FILTER: - HIDDebug("filter"); + // HIDDebug("filter"); if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { // There is no quickeffect for the microphone, do nothing. // this.StatusDebug(); @@ -1343,7 +1343,7 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); break; case this.STATE_EFFECT: - HIDDebug("effect"); + // HIDDebug("effect"); if (knobIdx === 4) { engine.setParameter(fxGroupPrefix + "]", "mix", value); } else { @@ -1405,7 +1405,7 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { var enabled = false; switch (this.currentState) { case this.STATE_FILTER: - HIDDebug("filter"); + // HIDDebug("filter"); if (idx === 0) { // filter select button highlighted unless any enable is pressed if (!this.anyEnablePressed()) { @@ -1424,12 +1424,12 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { } break; case this.STATE_EFFECT: - HIDDebug("effect"); + // HIDDebug("effect"); // select button highlighted for active effect enabled = (idx === this.activeFX); break; case this.STATE_FOCUS: - HIDDebug("focus"); + // HIDDebug("focus"); // select button blinking for active effect break; } @@ -1439,25 +1439,47 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { TraktorS3.FXControl.prototype.lightEnabled = function(channel) { var enabled = false; + var buttonNumber = this.channelToIndex(channel); switch (this.currentState) { case this.STATE_FILTER: - HIDDebug("filter"); + // HIDDebug("filter"); // enable buttons have regular deck colors - // enable buttons highlighted if pressed or if any fx unit enabled. + // enable buttons highlighted if pressed or if any fx unit enabled for channel. + if (this.enablePressed[channel]) { + enabled = true; + } + for (var idx = 1; idx <= 4 && !enabled; idx++) { + var group = "[EffectRack1_EffectUnit" + idx + "]"; + var key = "group_" + channel + "_enable"; + if (engine.getParameter(group, key)) { + enabled = true; + } + } // var ledValue = this.getFXSelectLEDValue(idx, enabled) + var ledValue = this.getChannelColor(channel, enabled); break; case this.STATE_EFFECT: - HIDDebug("effect"); - // enable buttons have same color as effect + // HIDDebug("effect"); + // enable buttons have same color as active effect // enable buttons highlighted if pressed or effect in unit is enabled + if (this.enablePressed[channel]) { + enabled = true; + } + group = "[EffectRack1_EffectUnit" + this.activeFX + "_Effect" + buttonNumber + "]"; + key = "enabled"; + HIDDebug("checking " + group + " " + key); + if (engine.getParameter(group, key)) { + enabled = true; + } + ledValue = this.getFXSelectLEDValue(this.activeFX, enabled); break; case this.STATE_FOCUS: - HIDDebug("focus"); - // enable buttons have same color as effect + // HIDDebug("focus"); + // enable buttons have same color as active effect // enable buttons highlighted if... button and 1?? + ledValue = this.getFXSelectLEDValue(this.activeFX, enabled); break; } - var ledValue = this.getChannelColor(channel, enabled); this.controller.setOutput(channel, "!fxEnabled", ledValue, false); }; // for (var ch in TraktorS3.Channels) { diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index f7b8dc727d3..cb1c57c2f23 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -51,7 +51,7 @@ class TraktorS3Test : public ControllerTest { // Mock out controller for testing lights evaluate( "TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, enabled) {" - " return fxNumber*10 + (enabled ? 1 : 0);" + " return fxNumber*10 + (enabled ? 6 : 5);" "};" "TraktorS3.FXControl.prototype.getChannelColor = function(group, enabled) {" " return parseInt(group[8])*10 + (enabled ? 1 : 0);" @@ -74,6 +74,24 @@ class TraktorS3Test : public ControllerTest { "};"); } + void CheckSelectLights(const std::vector& expected) { + EXPECT_EQ(5, expected.size()); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(expected[i], + evaluate(QString("getLight('[ChannelX]', '!fxButton%1');").arg(i)).toInt32()); + } + } + + void CheckEnableLights(const std::vector& expected) { + EXPECT_EQ(4, expected.size()); + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(expected[i], + evaluate(QString("getLight('[Channel%1]', '!fxEnabled');") + .arg(i + 1)) + .toInt32()); + } + } + enum states { STATE_FILTER, STATE_EFFECT, @@ -120,9 +138,11 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array1[i], ret.property(i).toBool()); } + SCOPED_TRACE(""); + CheckSelectLights({5, 15, 26, 35, 45}); + CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - EXPECT_EQ(21, evaluate("getLight('[ChannelX]', '!fxButton2');").toInt32()); // Now unpress select and release evaluate("TestOb.fxc.fxSelectHandler(unpressFx2);"); @@ -134,6 +154,9 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); } + SCOPED_TRACE(""); + CheckSelectLights({5, 15, 26, 35, 45}); + CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); @@ -147,6 +170,9 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { EXPECT_TRUE(ret.property(i).isValid()); EXPECT_EQ(expected_array3[i], ret.property(i).toBool()); } + SCOPED_TRACE(""); + CheckSelectLights({6, 15, 25, 35, 45}); + CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); @@ -190,15 +216,25 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { } EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + SCOPED_TRACE(""); + CheckSelectLights({5, 15, 26, 35, 45}); + CheckEnableLights({25, 25, 25, 25}); - // Press 2 and tap enable2, focus second effect + // Press fx2 and enable2, focus third effect (channel 2 button is third button) evaluate( "TestOb.fxc.fxSelectHandler(pressFx2);" "TestOb.fxc.fxEnableHandler(pressFxEnable2);" "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); + EXPECT_EQ(3, + ControlObject::getControl( + ConfigKey("[EffectRack1_EffectUnit2]", "focused_effect")) + ->get()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + SCOPED_TRACE(""); + // CheckSelectLights({5, 15, 26, 35, 45}); + // CheckEnableLights({0, 10, 21, 30}); // Press again, back to effect mode evaluate( @@ -206,6 +242,9 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); + SCOPED_TRACE(""); + CheckSelectLights({5, 15, 26, 35, 45}); + CheckEnableLights({25, 25, 25, 25}); // Press 3, effect evaluate( @@ -213,6 +252,9 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { "TestOb.fxc.fxSelectHandler(unpressFx3);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(3, evaluate("getActiveFx();").toInt32()); + SCOPED_TRACE(""); + CheckSelectLights({5, 15, 25, 36, 45}); + CheckEnableLights({0, 10, 20, 30}); // Press 2, press 2, press filter = filter evaluate( @@ -285,6 +327,9 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", "group_[Channel1]_enable")) ->get()); + SCOPED_TRACE(""); + CheckEnableLights({1, 10, 20, 30}); + CheckSelectLights({5, 16, 25, 35, 45}); // Press enable fx2 again, should disable effect unit 2 for channel 1 evaluate( @@ -343,6 +388,9 @@ TEST_F(TraktorS3Test, FXModeFXEnable) { "TestOb.fxc.fxSelectHandler(pressFx2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + SCOPED_TRACE(""); + CheckEnableLights({25, 25, 25, 25}); + CheckSelectLights({5, 15, 26, 35, 45}); evaluate( "TestOb.fxc.fxEnableHandler(pressFxEnable1);" @@ -352,6 +400,10 @@ TEST_F(TraktorS3Test, FXModeFXEnable) { EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "enabled")) ->get()); + SCOPED_TRACE(""); + // Channel 3 is the first button + CheckEnableLights({25, 25, 26, 25}); + CheckSelectLights({5, 15, 26, 35, 45}); evaluate( "TestOb.fxc.fxEnableHandler(pressFxEnable1);" @@ -361,6 +413,9 @@ TEST_F(TraktorS3Test, FXModeFXEnable) { EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "enabled")) ->get()); + SCOPED_TRACE(""); + CheckEnableLights({25, 25, 25, 25}); + CheckSelectLights({5, 15, 26, 35, 45}); } // In Focus Mode, the FX Enable buttons toggle effect parameter values diff --git a/src/test/controllers/controllertest.h b/src/test/controllers/controllertest.h index 119869f982d..9e7da8642e1 100644 --- a/src/test/controllers/controllertest.h +++ b/src/test/controllers/controllertest.h @@ -28,6 +28,7 @@ class ControllerTest : public BaseSignalPathTest { m_pQuickRack->setupForGroup("[Channel1]"); m_pQuickRack->setupForGroup("[Channel2]"); m_pQuickRack->setupForGroup("[Channel3]"); + m_pQuickRack->setupForGroup("[Channel4]"); // pChainSlot = pRack->getEffectChainSlot(iChainNumber); // pChainSlot->registerInputChannel(m_master); } diff --git a/src/test/signalpathtest.cpp b/src/test/signalpathtest.cpp index a2ef827e180..df09a8ea90b 100644 --- a/src/test/signalpathtest.cpp +++ b/src/test/signalpathtest.cpp @@ -6,6 +6,7 @@ const QString BaseSignalPathTest::m_sInternalClockGroup = QStringLiteral("[Inter const QString BaseSignalPathTest::m_sGroup1 = QStringLiteral("[Channel1]"); const QString BaseSignalPathTest::m_sGroup2 = QStringLiteral("[Channel2]"); const QString BaseSignalPathTest::m_sGroup3 = QStringLiteral("[Channel3]"); +const QString BaseSignalPathTest::m_sGroup4 = QStringLiteral("[Channel4]"); const QString BaseSignalPathTest::m_sPreviewGroup = QStringLiteral("[PreviewDeck1]"); const QString BaseSignalPathTest::m_sSamplerGroup = QStringLiteral("[Sampler1]"); const double BaseSignalPathTest::kDefaultRateRange = 0.08; diff --git a/src/test/signalpathtest.h b/src/test/signalpathtest.h index 10d55416517..c4d3faa88d0 100644 --- a/src/test/signalpathtest.h +++ b/src/test/signalpathtest.h @@ -73,10 +73,18 @@ class BaseSignalPathTest : public MixxxTest { m_pVisualsManager, EngineChannel::CENTER, m_sGroup2); m_pMixerDeck3 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, m_pVisualsManager, EngineChannel::CENTER, m_sGroup3); + m_pMixerDeck4 = new Deck(NULL, + m_pConfig, + m_pEngineMaster, + m_pEffectsManager, + m_pVisualsManager, + EngineChannel::CENTER, + m_sGroup4); m_pChannel1 = m_pMixerDeck1->getEngineDeck(); m_pChannel2 = m_pMixerDeck2->getEngineDeck(); m_pChannel3 = m_pMixerDeck3->getEngineDeck(); + m_pChannel4 = m_pMixerDeck4->getEngineDeck(); m_pPreview1 = new PreviewDeck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, m_pVisualsManager, EngineChannel::CENTER, m_sPreviewGroup); ControlObject::set(ConfigKey(m_sPreviewGroup, "file_bpm"), 2.0); @@ -91,6 +99,7 @@ class BaseSignalPathTest : public MixxxTest { addDeck(m_pChannel1); addDeck(m_pChannel2); addDeck(m_pChannel3); + addDeck(m_pChannel4); m_pEngineSync = m_pEngineMaster->getEngineSync(); ControlObject::set(ConfigKey("[Master]", "enabled"), 1.0); @@ -102,9 +111,11 @@ class BaseSignalPathTest : public MixxxTest { delete m_pMixerDeck1; delete m_pMixerDeck2; delete m_pMixerDeck3; + delete m_pMixerDeck4; m_pChannel1 = NULL; m_pChannel2 = NULL; m_pChannel3 = NULL; + m_pChannel4 = NULL; m_pEngineSync = NULL; delete m_pPreview1; @@ -209,13 +220,14 @@ class BaseSignalPathTest : public MixxxTest { EffectsManager* m_pEffectsManager; EngineSync* m_pEngineSync; TestEngineMaster* m_pEngineMaster; - Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3; - EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3; + Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3, *m_pMixerDeck4; + EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3, *m_pChannel4; PreviewDeck* m_pPreview1; static const QString m_sGroup1; static const QString m_sGroup2; static const QString m_sGroup3; + static const QString m_sGroup4; static const QString m_sMasterGroup; static const QString m_sInternalClockGroup; static const QString m_sPreviewGroup; From b507664f6bfa276bdff2db41ae2eea35fd2461aa Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 3 Sep 2020 10:38:07 -0400 Subject: [PATCH 60/84] Traktor S3: tests pass! --- .../Traktor-Kontrol-S3-hid-scripts.js | 336 +++++++--------- src/effects/effectslot.cpp | 4 +- .../controllers/Traktor_Kontrol_S3_test.cpp | 360 ++++++++++++------ 3 files changed, 390 insertions(+), 310 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 8e90e0867f7..b8d38af7267 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -100,11 +100,11 @@ var TraktorS3 = new function() { // FX 5 is the Filter this.fxLEDValue = { + 0: this.controller.LEDColors.PURPLE, 1: this.controller.LEDColors.RED, 2: this.controller.LEDColors.GREEN, 3: this.controller.LEDColors.BLUE, 4: this.controller.LEDColors.YELLOW, - 5: this.controller.LEDColors.PURPLE, }; this.colorMap = new ColorMapper({ @@ -1125,6 +1125,11 @@ TraktorS3.FXControl = function() { this.STATE_FOCUS = 2; this.currentState = this.STATE_FILTER; + + // Light states + this.LIGHT_OFF = 0; + this.LIGHT_DIM = 1; + this.LIGHT_BRIGHT = 2; }; TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLong) { @@ -1150,7 +1155,7 @@ TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLon TraktorS3.FXControl.prototype.channelToIndex = function(group) { var result = group.match(script.channelRegEx); if (result === null) { - HIDDebug("barf" + group); + HIDDebug("barf " + group); return undefined; } // Unmap from channel number to button index. @@ -1167,34 +1172,6 @@ TraktorS3.FXControl.prototype.channelToIndex = function(group) { return undefined; }; -TraktorS3.FXControl.prototype.StatusDebug = function() { - HIDDebug("active: " + this.activeFX + - " enablepressed? " + this.enablePressed + - " selectpressed? " + this.selectPressed); - for (var i = 1; i <= 4; i++) { - var focus = engine.getValue("[EffectRack1_EffectUnit" + i + "]", "focused_effect"); - if (focus) { - HIDDebug("FX" + i + " focus"); - } - } -}; - -// TraktorS3.FXControl.prototype.toggleFocusEnable = function(fxNum) { -// var group = "[EffectRack1_EffectUnit" + fxNum + "]"; -// var newState = !engine.getValue(group, "focused_effect"); - -// if (!newState) { -// engine.setValue(group, "focused_effect", 0); -// return; -// } -// // If we are setting a different unit to be enabled, the others become -// // disabled. -// for (var i = 1; i <= 4; i++) { -// group = "[EffectRack1_EffectUnit" + i + "]"; -// engine.setValue(group, "focused_effect", i === fxNum); -// } -// }; - TraktorS3.FXControl.prototype.firstPressedSelect = function() { for (var idx in this.selectPressed) { if (this.selectPressed[idx]) { @@ -1239,8 +1216,8 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; for (var key in this.enablePressed) { if (this.enablePressed[key]) { - HIDDebug("key? " + key); var fxKey = "group_" + key + "_enable"; + // HIDDebug("key? " + fxGroup + " " + fxKey); script.toggleControl(fxGroup, fxKey); } } @@ -1272,7 +1249,7 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { }; TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { - HIDDebug("we are here: " + field.group + " " + field.value); + // HIDDebug("we are here: " + field.group + " " + field.value); this.enablePressed[field.group] = field.value; if (!field.value) { @@ -1285,10 +1262,8 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { var buttonNumber = this.channelToIndex(field.group); switch (this.currentState) { case this.STATE_FILTER: - // HIDDebug("filter"); break; case this.STATE_EFFECT: - // HIDDebug("effect mode"); if (this.firstPressedSelect()) { HIDDebug("change to focus"); // Choose the first pressed select button only. @@ -1305,11 +1280,9 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { } break; case this.STATE_FOCUS: - HIDDebug("focus mode"); var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; - key = "parameter" + buttonNumber; + key = "button_parameter" + buttonNumber; HIDDebug("toggling " + group + " " + key); script.toggleControl(group, key); break; @@ -1317,16 +1290,8 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { this.lightFX(); }; -// TraktorS3.fxGroupPrefix = function(group) { -// var channelMatch = group.match(script.channelRegEx); -// if (channelMatch === undefined) { -// return undefined; -// } -// return "[EffectRack1_EffectUnit" + channelMatch[1]; -// }; - TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { - HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); + // HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); var value = field.value / 4095.; var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; var knobIdx = this.channelToIndex(field.group); @@ -1336,10 +1301,8 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { // HIDDebug("filter"); if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { // There is no quickeffect for the microphone, do nothing. - // this.StatusDebug(); return; } - HIDDebug("HERE?2 " + "[QuickEffectRack1_" + field.group + "]"); engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); break; case this.STATE_EFFECT: @@ -1347,49 +1310,48 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { if (knobIdx === 4) { engine.setParameter(fxGroupPrefix + "]", "mix", value); } else { - HIDDebug("set " + group + " meta"); var group = fxGroupPrefix + "_Effect" + knobIdx + "]"; engine.setParameter(group, "meta", value); } break; case this.STATE_FOCUS: var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); - HIDDebug("focus " + focusedEffect); group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; var key = "parameter" + knobIdx; - HIDDebug("set " + group + " " + key); engine.setParameter(group, key, value); break; } }; -TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, enabled) { +TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; - ledValue += enabled ? TraktorS3LEDBrightValue : TraktorS3LEDDimValue; - return ledValue; + switch (status) { + case this.LIGHT_OFF: + return ledValue; + case this.LIGHT_DIM: + return ledValue + 0x01; + case this.LIGHT_BRIGHT: + return ledValue + 0x02; + } }; -TraktorS3.FXControl.prototype.getChannelColor = function(group, enabled) { +TraktorS3.FXControl.prototype.getChannelColor = function(group, status) { var ledValue = TraktorS3.controller.LEDColors[TraktorS3ChannelColors[group]]; - ledValue += enabled ? TraktorS3LEDBrightValue : TraktorS3LEDDimValue; - return ledValue; -}; - -// FX LIGHTS: -// if a button is pressed, definitely it should be on -// if enable is pressed, -// light the selects with what that channel has enabled. -// else if enable is not pressed, -// if focused, select blinks for selected effect -// if unfocused, selects is solid for selected effect -// if unfocused, enables are lit depending on which channels have that effect enabled. (for filter, -// that's all) -// if focused, enables are lit depending on which units are active. + switch (status) { + case this.LIGHT_OFF: + return ledValue; + case this.LIGHT_DIM: + return ledValue + 0x01; + case this.LIGHT_BRIGHT: + return ledValue + 0x02; + } +}; + TraktorS3.FXControl.prototype.lightFX = function() { - // Idx zero is filter button TraktorS3.batchingOutputs = true; // Loop through select buttons + // Idx zero is filter button for (var idx = 0; idx < 5; idx++) { this.lightSelect(idx); } @@ -1399,109 +1361,126 @@ TraktorS3.FXControl.prototype.lightFX = function() { } TraktorS3.batchingOutputs = false; + for (var packetName in this.controller.OutputPackets) { + this.controller.OutputPackets[packetName].send(); + } }; TraktorS3.FXControl.prototype.lightSelect = function(idx) { - var enabled = false; + var status = this.LIGHT_OFF; + var ledValue = 0x00; switch (this.currentState) { case this.STATE_FILTER: - // HIDDebug("filter"); - if (idx === 0) { - // filter select button highlighted unless any enable is pressed - if (!this.anyEnablePressed()) { - enabled = true; - } + // Always light when pressed + if (this.selectPressed[idx]) { + status = this.LIGHT_BRIGHT; } else { - // select buttons highlighted if fx unit enabled for the pressed channel - var pressed = this.firstPressedSelect(); - if (pressed && pressed !== "0") { - var fxGroup = "[EffectRack1_EffectUnit" + idx + "]"; - var fxKey = "group_[Channel" + pressed + "]_enable"; - if (engine.getParameter(fxGroup, fxKey)) { - enabled = true; + if (idx === 0) { + // Filter select button highlighted in filter mode unless any enable is pressed + if (!this.anyEnablePressed()) { + status = this.LIGHT_BRIGHT; + } + } else { + // select buttons on if fx unit enabled for the pressed channel, + // otherwise disabled. + status = this.LIGHT_DIM; + var pressed = this.firstPressedEnable(); + if (pressed) { + var fxGroup = "[EffectRack1_EffectUnit" + idx + "]"; + var fxKey = "group_" + pressed + "_enable"; + if (engine.getParameter(fxGroup, fxKey)) { + HIDDebug("bright! " + fxGroup + " " + fxKey); + status = this.LIGHT_BRIGHT; + } else { + status = this.LIGHT_OFF; + } } } + ledValue = this.getFXSelectLEDValue(idx, status); } break; case this.STATE_EFFECT: - // HIDDebug("effect"); - // select button highlighted for active effect - enabled = (idx === this.activeFX); + // Highlight if pressed, disable if active effect. + // Otherwise off. + if (this.selectPressed[idx]) { + status = this.LIGHT_BRIGHT; + } else if (idx === this.activeFX) { + status = this.LIGHT_BRIGHT; + } break; case this.STATE_FOCUS: - // HIDDebug("focus"); - // select button blinking for active effect + // TODO: Blink for focused effect + if (this.selectPressed[idx]) { + status = this.LIGHT_BRIGHT; + } else if (idx === this.activeFX) { + status = this.LIGHT_BRIGHT; + } break; } - var ledValue = this.getFXSelectLEDValue(idx, enabled); + ledValue = this.getFXSelectLEDValue(idx, status); this.controller.setOutput("[ChannelX]", "!fxButton" + idx, ledValue, false); }; TraktorS3.FXControl.prototype.lightEnabled = function(channel) { - var enabled = false; + var status = this.LIGHT_OFF; + var ledValue = 0x00; var buttonNumber = this.channelToIndex(channel); switch (this.currentState) { case this.STATE_FILTER: - // HIDDebug("filter"); - // enable buttons have regular deck colors // enable buttons highlighted if pressed or if any fx unit enabled for channel. + // Highlight if pressed. if (this.enablePressed[channel]) { - enabled = true; - } - for (var idx = 1; idx <= 4 && !enabled; idx++) { - var group = "[EffectRack1_EffectUnit" + idx + "]"; - var key = "group_" + channel + "_enable"; - if (engine.getParameter(group, key)) { - enabled = true; + status = this.LIGHT_BRIGHT; + } else { + for (var idx = 1; idx <= 4 && status === this.LIGHT_OFF; idx++) { + var group = "[EffectRack1_EffectUnit" + idx + "]"; + var key = "group_" + channel + "_enable"; + if (engine.getParameter(group, key)) { + status = this.LIGHT_DIM; + } } } - // var ledValue = this.getFXSelectLEDValue(idx, enabled) - var ledValue = this.getChannelColor(channel, enabled); + // Enable buttons have regular deck colors + ledValue = this.getChannelColor(channel, status); break; case this.STATE_EFFECT: - // HIDDebug("effect"); - // enable buttons have same color as active effect - // enable buttons highlighted if pressed or effect in unit is enabled if (this.enablePressed[channel]) { - enabled = true; - } - group = "[EffectRack1_EffectUnit" + this.activeFX + "_Effect" + buttonNumber + "]"; - key = "enabled"; - HIDDebug("checking " + group + " " + key); - if (engine.getParameter(group, key)) { - enabled = true; + status = this.LIGHT_BRIGHT; + } else { + // off if nothing loaded, dim if loaded, bright if enabled. + group = "[EffectRack1_EffectUnit" + this.activeFX + "_Effect" + buttonNumber + "]"; + if (engine.getParameter(group, "loaded")) { + status = this.LIGHT_DIM; + } + if (engine.getParameter(group, "enabled")) { + status = this.LIGHT_BRIGHT; + } } - ledValue = this.getFXSelectLEDValue(this.activeFX, enabled); + // Colors match effect colors so it's obvious we're in a different mode + ledValue = this.getFXSelectLEDValue(this.activeFX, status); break; case this.STATE_FOCUS: - // HIDDebug("focus"); - // enable buttons have same color as active effect - // enable buttons highlighted if... button and 1?? - ledValue = this.getFXSelectLEDValue(this.activeFX, enabled); + if (this.enablePressed[channel]) { + status = this.LIGHT_BRIGHT; + } else { + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; + key = "button_parameter" + buttonNumber; + // Off if not loaded, dim if loaded, bright if enabled. + if (engine.getParameter(group, key + "_loaded")) { + status = this.LIGHT_DIM; + } + if (engine.getParameter(group, key)) { + status = this.LIGHT_BRIGHT; + } + } + // Colors match effect colors so it's obvious we're in a different mode + ledValue = this.getFXSelectLEDValue(this.activeFX, status); break; } this.controller.setOutput(channel, "!fxEnabled", ledValue, false); }; -// for (var ch in TraktorS3.Channels) { -// var chanob = TraktorS3.Channels[ch]; -// if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { -// chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); -// } else { -// chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); -// } -// } -// for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { -// var ledValue = TraktorS3.fxLEDValue[fxNumber]; -// if (TraktorS3.fxButtonState[fxNumber]) { -// ledValue += TraktorS3LEDBrightValue; -// } else { -// ledValue += TraktorS3LEDDimValue; -// } -// TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); -// } -// HIDDebug("exit"); -// }; - TraktorS3.registerInputPackets = function() { var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); @@ -1672,37 +1651,37 @@ TraktorS3.deckSwitchHandler = function(field) { deck.activateChannel(channel); }; -TraktorS3.toggleFX = function() { - // This is an AND operation. We go through each channel, and if - // the filter button is ON and the fx is ON, we turn the effect ON. - // We turn OFF if either is false. - - // The only exception is the Filter effect. If the channel fxenable - // is off, the Filter effect is still automatically enabled. - // If the fxenable button is on, the Filter effect is only enabled if - // the Filter FX button is enabled. - for (var ch = 1; ch <= 4; ch++) { - var channel = TraktorS3.Channels["[Channel" + ch + "]"]; - var chEnabled = channel.fxEnabledState; - if (ch === 4 && TraktorS3.channel4InputMode) { - chEnabled = TraktorS3.inputFxEnabledState; - } else { - // There is no quickeffect for the microphone - var newState = !chEnabled || TraktorS3.fxButtonState[5]; - engine.setValue("[QuickEffectRack1_[Channel" + ch + "]]", "enabled", - newState); - } - for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { - var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; - var fxKey = "group_[Channel" + ch + "]_enable"; - newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; - if (ch === 4 && TraktorS3.channel4InputMode) { - fxKey = "group_[Microphone]_enable"; - } - engine.setValue(fxGroup, fxKey, newState); - } - } -}; +// TraktorS3.toggleFX = function() { +// // This is an AND operation. We go through each channel, and if +// // the filter button is ON and the fx is ON, we turn the effect ON. +// // We turn OFF if either is false. + +// // The only exception is the Filter effect. If the channel fxenable +// // is off, the Filter effect is still automatically enabled. +// // If the fxenable button is on, the Filter effect is only enabled if +// // the Filter FX button is enabled. +// for (var ch = 1; ch <= 4; ch++) { +// var channel = TraktorS3.Channels["[Channel" + ch + "]"]; +// var chEnabled = channel.fxEnabledState; +// if (ch === 4 && TraktorS3.channel4InputMode) { +// chEnabled = TraktorS3.inputFxEnabledState; +// } else { +// // There is no quickeffect for the microphone +// var newState = !chEnabled || TraktorS3.fxButtonState[5]; +// engine.setValue("[QuickEffectRack1_[Channel" + ch + "]]", "enabled", +// newState); +// } +// for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { +// var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; +// var fxKey = "group_[Channel" + ch + "]_enable"; +// newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; +// if (ch === 4 && TraktorS3.channel4InputMode) { +// fxKey = "group_[Microphone]_enable"; +// } +// engine.setValue(fxGroup, fxKey, newState); +// } +// } +// }; TraktorS3.extModeHandler = function(field) { if (!field.value) { @@ -1948,26 +1927,6 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { } }; -// TraktorS3.lightFX = function() { -// for (var ch in TraktorS3.Channels) { -// var chanob = TraktorS3.Channels[ch]; -// if (ch === "[Channel4]" && TraktorS3.channel4InputMode) { -// chanob.colorOutput(TraktorS3.inputFxEnabledState, "!fxEnabled"); -// } else { -// chanob.colorOutput(chanob.fxEnabledState, "!fxEnabled"); -// } -// } -// for (var fxNumber = 1; fxNumber <= 5; fxNumber++) { -// var ledValue = TraktorS3.fxLEDValue[fxNumber]; -// if (TraktorS3.fxButtonState[fxNumber]) { -// ledValue += TraktorS3LEDBrightValue; -// } else { -// ledValue += TraktorS3LEDDimValue; -// } -// TraktorS3.controller.setOutput("[ChannelX]", "!fxButton" + fxNumber, ledValue, !TraktorS3.batchingOutputs); -// } -// }; - TraktorS3.lightDeck = function(group, sendPackets) { if (sendPackets === undefined) { sendPackets = true; @@ -2197,6 +2156,7 @@ TraktorS3.init = function(_id) { TraktorS3.lightDeck("[Channel4]", false); TraktorS3.lightDeck("[Channel1]", false); TraktorS3.lightDeck("[Channel2]", true); + TraktorS3.fxController.lightFX(); } TraktorS3.setInputLineMode(TraktorS3.inputModeLine); diff --git a/src/effects/effectslot.cpp b/src/effects/effectslot.cpp index c91083558a2..75541c7dcbe 100644 --- a/src/effects/effectslot.cpp +++ b/src/effects/effectslot.cpp @@ -151,8 +151,8 @@ EffectButtonParameterSlotPointer EffectSlot::getEffectButtonParameterSlot(unsign } void EffectSlot::loadEffect(EffectPointer pEffect, bool adoptMetaknobPosition) { - //qDebug() << debugString() << "loadEffect" - // << (pEffect ? pEffect->getManifest().name() : "(null)"); + qDebug() << debugString() << "loadEffect" + << (pEffect ? pEffect->getManifest()->name() : "(null)"); if (pEffect) { m_pEffect = pEffect; m_pControlLoaded->forceSet(1.0); diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index cb1c57c2f23..1c0cf1d9f4e 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -7,15 +7,42 @@ #include "controllers/controllerengine.h" #include "controllers/softtakeover.h" #include "effects/effectchain.h" +#include "effects/effectslot.h" #include "effects/effectsmanager.h" +#include "test/baseeffecttest.h" #include "test/controllers/controllertest.h" #include "test/signalpathtest.h" class TraktorS3Test : public ControllerTest { protected: + TraktorS3Test() { + m_pTestBackend = new TestEffectBackend(); + m_pEffectsManager->addEffectsBackend(m_pTestBackend); + } + void SetUp() override { ControllerTest::SetUp(); + // Load a few effects so we can test that part. + EffectManifestPointer pManifest(new EffectManifest()); + pManifest->setId("org.mixxx.test.effect"); + pManifest->setName("Test Effect1"); + pManifest->addParameter(); + registerTestEffect(pManifest, false); + EffectPointer pEffect = m_pEffectsManager->instantiateEffect(pManifest->id()); + + EffectChainPointer pChain(new EffectChain(m_pEffectsManager, + "org.mixxx.test.chain1")); + + for (int chain = 0; chain < 2; ++chain) { + auto chainSlot = m_pRack->getEffectChainSlot(chain); + chainSlot->loadEffectChainToSlot(pChain); + for (int effect = 0; effect < 2; ++effect) { + auto effectSlot = chainSlot->getEffectSlot(effect); + effectSlot->loadEffect(pEffect, false); + } + } + const QString commonScript = "./res/controllers/common-controller-scripts.js"; const QString hidScript = "./res/controllers/common-hid-packet-parser.js"; const QString scriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; @@ -50,11 +77,13 @@ class TraktorS3Test : public ControllerTest { // Mock out controller for testing lights evaluate( - "TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, enabled) {" - " return fxNumber*10 + (enabled ? 6 : 5);" + "TraktorS3.FXControl.prototype.getFXSelectLEDValue = " + "function(fxNumber, status) {" + " return fxNumber*10 + status;" "};" - "TraktorS3.FXControl.prototype.getChannelColor = function(group, enabled) {" - " return parseInt(group[8])*10 + (enabled ? 1 : 0);" + "TraktorS3.FXControl.prototype.getChannelColor = " + "function(group, status) {" + " return this.channelToIndex(group)*10 + status;" "};" "TestOb.fxc.controller = new function() {" " this.lightMap = {}; " @@ -70,26 +99,78 @@ class TraktorS3Test : public ControllerTest { " if (!(group in TestOb.fxc.controller.lightMap)) {" " return undefined;" " }" + " HIDDebug('whats the frequency ' + group + ' ' + key + ' ' + " + "TestOb.fxc.controller.lightMap[group][key]);" " return TestOb.fxc.controller.lightMap[group][key];" "};"); } + void registerTestEffect(EffectManifestPointer pManifest, bool willAddToEngine) { + MockEffectProcessor* pProcessor = new MockEffectProcessor(); + MockEffectInstantiator* pInstantiator = new MockEffectInstantiator(); + + if (willAddToEngine) { + EXPECT_CALL(*pInstantiator, instantiate(_, _)) + .Times(1) + .WillOnce(Return(pProcessor)); + } + + m_pTestBackend->registerEffect(pManifest->id(), + pManifest, + EffectInstantiatorPointer(pInstantiator)); + } + + void CheckSelectPressed(const std::vector& expected, const QScriptValue& got) { + EXPECT_EQ(5, expected.size()); + for (int i = 0; i < 0; ++i) { + EXPECT_TRUE(got.property(i).isValid()); + EXPECT_EQ(expected[i], got.property(i).toBool()); + } + } + + // Checks that the correct enabled buttons are pressed. The expected values are in + // physical order, not channel order. + void CheckEnabledPressed(const std::vector& expected, const QScriptValue& got) { + EXPECT_EQ(4, expected.size()); + EXPECT_TRUE(got.property("[Channel3]").isValid()); + EXPECT_EQ(expected[0], got.property("[Channel3]").toBool()); + EXPECT_TRUE(got.property("[Channel1]").isValid()); + EXPECT_EQ(expected[1], got.property("[Channel1]").toBool()); + EXPECT_TRUE(got.property("[Channel2]").isValid()); + EXPECT_EQ(expected[2], got.property("[Channel2]").toBool()); + EXPECT_TRUE(got.property("[Channel4]").isValid()); + EXPECT_EQ(expected[3], got.property("[Channel4]").toBool()); + } + + // For the list of lights, the tens digit is the effect number or channel number, and the + // ones digit is 0 for off, 1 for dim, and 2 for bright. void CheckSelectLights(const std::vector& expected) { EXPECT_EQ(5, expected.size()); - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 0; ++i) { EXPECT_EQ(expected[i], - evaluate(QString("getLight('[ChannelX]', '!fxButton%1');").arg(i)).toInt32()); + evaluate(QString("getLight('[ChannelX]', '!fxButton%1');").arg(i)).toInt32()) + << "failed on select light: " << i; } } + // Checks that the enable lights are lit correctly. The expected values are in + // physical order, not channel order. + // For the list of lights, the tens digit is the effect number or channel number, and the + // ones digit is 0 for off, 1 for dim, and 2 for bright. void CheckEnableLights(const std::vector& expected) { EXPECT_EQ(4, expected.size()); - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(expected[i], - evaluate(QString("getLight('[Channel%1]', '!fxEnabled');") - .arg(i + 1)) - .toInt32()); - } + EXPECT_EQ(expected[0], + evaluate(QString("getLight('[Channel3]', '!fxEnabled');")) + .toInt32()); + EXPECT_EQ(expected[1], + evaluate(QString("getLight('[Channel1]', '!fxEnabled');")) + .toInt32()); + EXPECT_EQ(expected[2], + evaluate(QString("getLight('[Channel2]', '!fxEnabled');")) + .toInt32()); + EXPECT_EQ(expected[3], + evaluate(QString("getLight('[Channel4]', '!fxEnabled');")) + .toInt32()); } enum states { @@ -97,6 +178,8 @@ class TraktorS3Test : public ControllerTest { STATE_EFFECT, STATE_FOCUS }; + + TestEffectBackend* m_pTestBackend; }; // Test tapping fx select buttons to toggle states -- Filter for filter state, any fx unit @@ -132,15 +215,12 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { evaluate("TestOb.fxc.fxSelectHandler(pressFx2);"); auto ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); - - bool expected_array1[5] = {false, false, true, false, false}; - for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(ret.property(i).isValid()); - EXPECT_EQ(expected_array1[i], ret.property(i).toBool()); + { + SCOPED_TRACE(""); + CheckSelectPressed({false, false, true, false, false}, ret); + CheckSelectLights({0, 10, 22, 30, 40}); + CheckEnableLights({21, 21, 20, 20}); } - SCOPED_TRACE(""); - CheckSelectLights({5, 15, 26, 35, 45}); - CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); @@ -148,15 +228,12 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { evaluate("TestOb.fxc.fxSelectHandler(unpressFx2);"); ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); - - bool expected_array2[5] = {false, false, false, false, false}; - for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(ret.property(i).isValid()); - EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); + { + SCOPED_TRACE(""); + CheckSelectPressed({false, false, false, false, false}, ret); + CheckSelectLights({0, 10, 21, 30, 40}); + CheckEnableLights({21, 21, 20, 20}); } - SCOPED_TRACE(""); - CheckSelectLights({5, 15, 26, 35, 45}); - CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); @@ -164,15 +241,12 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { evaluate("TestOb.fxc.fxSelectHandler(pressFilter);"); ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); - - bool expected_array3[5] = {true, false, false, false, false}; - for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(ret.property(i).isValid()); - EXPECT_EQ(expected_array3[i], ret.property(i).toBool()); + { + SCOPED_TRACE(""); + CheckSelectPressed({true, false, false, false, false}, ret); + CheckSelectLights({2, 11, 21, 31, 41}); + CheckEnableLights({11, 21, 31, 41}); } - SCOPED_TRACE(""); - CheckSelectLights({6, 15, 25, 35, 45}); - CheckEnableLights({25, 25, 25, 25}); EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); @@ -180,10 +254,9 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); - bool expected_array4[5] = {false, false, false, false, false}; - for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(ret.property(i).isValid()); - EXPECT_EQ(expected_array4[i], ret.property(i).toBool()); + { + SCOPED_TRACE(""); + CheckSelectPressed({false, false, false, false, false}, ret); } EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); @@ -209,16 +282,14 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { auto ret = evaluate("getSelectPressed();"); ASSERT_TRUE(ret.isValid()); - bool expected_array2[5] = {false, false, false, false, false}; - for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(ret.property(i).isValid()); - EXPECT_EQ(expected_array2[i], ret.property(i).toBool()); - } EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - SCOPED_TRACE(""); - CheckSelectLights({5, 15, 26, 35, 45}); - CheckEnableLights({25, 25, 25, 25}); + { + SCOPED_TRACE(""); + CheckSelectPressed({false, false, false, false, false}, ret); + CheckSelectLights({0, 10, 21, 30, 40}); + CheckEnableLights({21, 21, 20, 20}); + } // Press fx2 and enable2, focus third effect (channel 2 button is third button) evaluate( @@ -232,9 +303,11 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { ConfigKey("[EffectRack1_EffectUnit2]", "focused_effect")) ->get()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - SCOPED_TRACE(""); - // CheckSelectLights({5, 15, 26, 35, 45}); - // CheckEnableLights({0, 10, 21, 30}); + { + SCOPED_TRACE(""); + CheckSelectLights({0, 10, 22, 30, 40}); + CheckEnableLights({20, 20, 20, 20}); + } // Press again, back to effect mode evaluate( @@ -242,9 +315,11 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - SCOPED_TRACE(""); - CheckSelectLights({5, 15, 26, 35, 45}); - CheckEnableLights({25, 25, 25, 25}); + { + SCOPED_TRACE(""); + CheckSelectLights({0, 10, 21, 30, 40}); + CheckEnableLights({21, 21, 20, 20}); + } // Press 3, effect evaluate( @@ -252,9 +327,11 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { "TestOb.fxc.fxSelectHandler(unpressFx3);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); EXPECT_EQ(3, evaluate("getActiveFx();").toInt32()); - SCOPED_TRACE(""); - CheckSelectLights({5, 15, 25, 36, 45}); - CheckEnableLights({0, 10, 20, 30}); + { + SCOPED_TRACE(""); + CheckSelectLights({0, 10, 20, 31, 40}); + CheckEnableLights({30, 30, 30, 30}); + } // Press 2, press 2, press filter = filter evaluate( @@ -278,12 +355,9 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { // Test Enable buttons + FX Select buttons to enable/disable fx units per channel. // This is only available during Filter state. TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { - // IMPORTANT: Channel 1 is the second button (CABD mapping). ASSERT_TRUE(evaluate( - "var pressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 0 };" - "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" @@ -292,64 +366,88 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") .isValid()); - // Press FXEnable 1 and release - evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1); "); + // For some reason, some effects start out enabled. + EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit1]", + "group_[Channel1]_enable")) + ->get()); + EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", + "group_[Channel1]_enable")) + ->get()); + EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit3]", + "group_[Channel3]_enable")) + ->get()); + + // Press FXEnable for Channel 3 and release + evaluate("TestOb.fxc.fxEnableHandler(pressFxEnable3);"); auto ret = evaluate("getEnablePressed();"); ASSERT_TRUE(ret.isValid()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - bool expected_array1[4] = {true, false, false, false}; - for (int i = 0; i < 4; ++i) { - QString group = QString("[Channel%1]").arg(i + 1); - EXPECT_TRUE(ret.property(group).isValid()); - EXPECT_EQ(expected_array1[i], ret.property(group).toBool()); + { + SCOPED_TRACE(""); + CheckEnabledPressed({true, false, false, false}, ret); + CheckEnableLights({12, 21, 31, 41}); + CheckSelectLights({0, 10, 20, 32, 40}); } evaluate( - "TestOb.fxc.fxEnableHandler(unpressFxEnable1); "); + "TestOb.fxc.fxEnableHandler(unpressFxEnable3); "); ret = evaluate("getEnablePressed();"); ASSERT_TRUE(ret.isValid()); - bool expected_array2[5] = {false, false, false, false}; - for (int i = 1; i <= 4; ++i) { - QString group = QString("[Channel%1]").arg(i); - EXPECT_TRUE(ret.property(group).isValid()); - EXPECT_EQ(expected_array2[i], ret.property(group).toBool()); + { + SCOPED_TRACE(""); + CheckEnabledPressed({false, false, false, false}, ret); + CheckEnableLights({11, 21, 31, 41}); + CheckSelectLights({2, 11, 21, 31, 41}); } - // Press enable 1, fx2, should enable effect unit 2 for channel 1 + // Go back to filter mode. Press enable ch3, fx2, should enable effect unit 2 for channel 3 // Keep enable pressed evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" + "TestOb.fxc.fxSelectHandler(pressFilter);" + "TestOb.fxc.fxSelectHandler(unpressFilter);" + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" "TestOb.fxc.fxSelectHandler(pressFx2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel1]_enable")) + "group_[Channel3]_enable")) ->get()); - SCOPED_TRACE(""); - CheckEnableLights({1, 10, 20, 30}); - CheckSelectLights({5, 16, 25, 35, 45}); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); + { + SCOPED_TRACE(""); + CheckEnableLights({12, 21, 31, 41}); + CheckSelectLights({0, 10, 22, 32, 40}); + } - // Press enable fx2 again, should disable effect unit 2 for channel 1 + // Press enable fx2 again, should enable effect unit 2 for channel 1 evaluate( "TestOb.fxc.fxSelectHandler(pressFx2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel1]_enable")) + "group_[Channel3]_enable")) ->get()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); + { + SCOPED_TRACE(""); + CheckEnableLights({12, 21, 31, 41}); + CheckSelectLights({0, 10, 20, 32, 40}); + } // Unpress fxenable, back where we started. evaluate( - "TestOb.fxc.fxEnableHandler(unpressFxEnable1); "); + "TestOb.fxc.fxEnableHandler(unpressFxEnable3); "); ret = evaluate("getEnablePressed();"); ASSERT_TRUE(ret.isValid()); + EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - for (int i = 1; i <= 4; ++i) { - QString group = QString("[Channel%1]").arg(i); - EXPECT_TRUE(ret.property(group).isValid()); - EXPECT_EQ(expected_array2[i], ret.property(group).toBool()); + { + SCOPED_TRACE(""); + CheckEnabledPressed({false, false, false, false}, ret); + CheckEnableLights({11, 21, 31, 41}); + CheckSelectLights({2, 11, 21, 31, 41}); } // If we're not in filter mode, fxenable doesn't cause us to enable/disable units @@ -357,11 +455,12 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { evaluate( "TestOb.fxc.fxSelectHandler(pressFx3);" "TestOb.fxc.fxSelectHandler(unpressFx3);" - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" - "TestOb.fxc.fxSelectHandler(pressFx2);"); + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxSelectHandler(pressFx2);" + "TestOb.fxc.fxSelectHandler(unpressFx2);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel1]_enable")) + "group_[Channel3]_enable")) ->get()); } @@ -369,8 +468,8 @@ TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { TEST_F(TraktorS3Test, FXModeFXEnable) { // IMPORTANT: Channel 3 is the first button. ASSERT_TRUE(evaluate( - "var pressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" @@ -388,42 +487,48 @@ TEST_F(TraktorS3Test, FXModeFXEnable) { "TestOb.fxc.fxSelectHandler(pressFx2);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - SCOPED_TRACE(""); - CheckEnableLights({25, 25, 25, 25}); - CheckSelectLights({5, 15, 26, 35, 45}); + { + SCOPED_TRACE(""); + CheckEnableLights({21, 21, 20, 20}); + CheckSelectLights({0, 10, 21, 30, 40}); + } evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); // Effect Unit 1 is toggled EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "enabled")) ->get()); - SCOPED_TRACE(""); - // Channel 3 is the first button - CheckEnableLights({25, 25, 26, 25}); - CheckSelectLights({5, 15, 26, 35, 45}); + { + SCOPED_TRACE(""); + // Channel 3 is the first button + CheckEnableLights({22, 22, 20, 20}); + CheckSelectLights({0, 10, 21, 30, 40}); + } evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); // Effect Unit 1 is toggled EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "enabled")) ->get()); - SCOPED_TRACE(""); - CheckEnableLights({25, 25, 25, 25}); - CheckSelectLights({5, 15, 26, 35, 45}); + { + SCOPED_TRACE(""); + CheckEnableLights({21, 21, 20, 20}); + CheckSelectLights({0, 10, 21, 30, 40}); + } } // In Focus Mode, the FX Enable buttons toggle effect parameter values TEST_F(TraktorS3Test, FocusModeFXEnable) { // IMPORTANT: Channel 3 is the first button. ASSERT_TRUE(evaluate( - "var pressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable1 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" + "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" + "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" @@ -439,33 +544,48 @@ TEST_F(TraktorS3Test, FocusModeFXEnable) { // Enable focus mode for fx2, effect 1. evaluate( "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); // Effect1 in Unit 2 is toggled EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "parameter1")) + "button_parameter1")) ->get()); + { + SCOPED_TRACE(""); + CheckEnableLights({20, 20, 20, 20}); + CheckSelectLights({0, 10, 22, 30, 40}); + } evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); // Effect Unit 1 is toggled EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "parameter1")) + "button_parameter1")) ->get()); + { + SCOPED_TRACE(""); + CheckEnableLights({22, 20, 20, 20}); + CheckSelectLights({0, 10, 22, 30, 40}); + } evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);"); + "TestOb.fxc.fxEnableHandler(pressFxEnable3);" + "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); // Effect Unit 1 is toggled EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "parameter1")) + "button_parameter1")) ->get()); + { + SCOPED_TRACE(""); + CheckEnableLights({20, 20, 20, 20}); + CheckSelectLights({0, 10, 22, 30, 40}); + } } // Test knob behavior in different states From 938c1ab8007cacbb5ce94a57c854adafbf1bc24d Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 3 Sep 2020 14:16:33 -0400 Subject: [PATCH 61/84] Traktor S3: Effects mapping mostly done TODO: * shift + knobs in focus mode to select effects --- .../Traktor-Kontrol-S3-hid-scripts.js | 144 ++++++++++++------ .../controllers/Traktor_Kontrol_S3_test.cpp | 48 ++++-- 2 files changed, 137 insertions(+), 55 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index b8d38af7267..5a7462c86d8 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1121,8 +1121,11 @@ TraktorS3.FXControl = function() { // States this.STATE_FILTER = 0; - this.STATE_EFFECT = 1; - this.STATE_FOCUS = 2; + // State for when an effect select has been pressed, but not released yet. + this.STATE_EFFECT_INIT = 1; + // State for when an effect select has been pressed and released. + this.STATE_EFFECT = 2; + this.STATE_FOCUS = 3; this.currentState = this.STATE_FILTER; @@ -1130,6 +1133,9 @@ TraktorS3.FXControl = function() { this.LIGHT_OFF = 0; this.LIGHT_DIM = 1; this.LIGHT_BRIGHT = 2; + + this.focusBlinkState = false; + this.focusBlinkTimer = 0; }; TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLong) { @@ -1199,12 +1205,37 @@ TraktorS3.FXControl.prototype.anyEnablePressed = function() { return false; }; +TraktorS3.FXControl.prototype.changeState = function(newState) { + if (newState === this.currentState) { + return; + } + var oldState = this.currentState; + this.currentState = newState; + if (oldState === this.STATE_FOCUS) { + engine.stopTimer(this.focusBlinkTimer); + this.focusBlinkTimer = 0; + } + if (newState === this.STATE_FOCUS) { + this.focusBlinkTimer = engine.beginTimer(150, function() { + this.focusBlinkState = !this.focusBlinkState; + this.lightFX(); + }, false); + } +}; + TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { var fxNumber = parseInt(field.name[field.name.length - 1]); this.selectPressed[fxNumber] = field.value; if (!field.value) { - // do lights? + if (fxNumber === this.activeFX) { + if (this.currentState === this.STATE_EFFECT) { + HIDDebug("unpress back to filter!"); + this.changeState(this.STATE_FILTER); + } else if (this.currentState === this.STATE_EFFECT_INIT) { + this.changeState(this.STATE_EFFECT); + } + } this.lightFX(); return; } @@ -1213,34 +1244,42 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { case this.STATE_FILTER: // If any fxEnable button is pressed, we are toggling fx unit assignment. if (this.anyEnablePressed()) { - var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; for (var key in this.enablePressed) { if (this.enablePressed[key]) { - var fxKey = "group_" + key + "_enable"; - // HIDDebug("key? " + fxGroup + " " + fxKey); + if (fxNumber === 0) { + var fxGroup = "[QuickEffectRack1_" + key + "_Effect1]"; + var fxKey = "enabled"; + } else { + fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; + fxKey = "group_" + key + "_enable"; + } script.toggleControl(fxGroup, fxKey); } } } else { - // If we push filter and filter is already pushed, do nothing. if (fxNumber === 0) { - this.currentState = this.STATE_FILTER; + this.changeState(this.STATE_FILTER); } else { - // Select this filter instead. - // initiate state change - // this.activateState(this.STATE_EFFECT, fxNumber); - this.currentState = this.STATE_EFFECT; + this.changeState(this.STATE_EFFECT_INIT); } this.activeFX = fxNumber; } break; - case this.STATE_EFFECT: + case this.STATE_EFFECT_INIT: // Fallthrough intended + case this.STATE_EFFECT: + if (fxNumber === 0) { + this.changeState(this.STATE_FILTER); + } else if (fxNumber !== this.activeFX) { + this.changeState(this.STATE_EFFECT_INIT); + } + this.activeFX = fxNumber; + break; case this.STATE_FOCUS: if (fxNumber === 0) { - this.currentState = this.STATE_FILTER; + this.changeState(this.STATE_FILTER); } else { - this.currentState = this.STATE_EFFECT; + this.changeState(this.STATE_EFFECT_INIT); } this.activeFX = fxNumber; break; @@ -1253,7 +1292,6 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { this.enablePressed[field.group] = field.value; if (!field.value) { - // do lights? this.lightFX(); return; } @@ -1263,11 +1301,13 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { switch (this.currentState) { case this.STATE_FILTER: break; + case this.STATE_EFFECT_INIT: + // Fallthrough intended case this.STATE_EFFECT: if (this.firstPressedSelect()) { HIDDebug("change to focus"); // Choose the first pressed select button only. - this.currentState = this.STATE_FOCUS; + this.changeState(this.STATE_FOCUS); HIDDebug("focusing " + fxGroupPrefix + " " + buttonNumber); // var focusedEffect = engine.getValue(effectUnitGroup, "focused_effect"); engine.setValue(fxGroupPrefix + "]", "focused_effect", buttonNumber); @@ -1305,6 +1345,8 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { } engine.setParameter("[QuickEffectRack1_" + field.group + "]", "super1", value); break; + case this.STATE_EFFECT_INIT: + // Fallthrough intended case this.STATE_EFFECT: // HIDDebug("effect"); if (knobIdx === 4) { @@ -1327,9 +1369,9 @@ TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) { var ledValue = TraktorS3.fxLEDValue[fxNumber]; switch (status) { case this.LIGHT_OFF: - return ledValue; + return 0x00; case this.LIGHT_DIM: - return ledValue + 0x01; + return ledValue; case this.LIGHT_BRIGHT: return ledValue + 0x02; } @@ -1339,9 +1381,9 @@ TraktorS3.FXControl.prototype.getChannelColor = function(group, status) { var ledValue = TraktorS3.controller.LEDColors[TraktorS3ChannelColors[group]]; switch (status) { case this.LIGHT_OFF: - return ledValue; + return 0x00; case this.LIGHT_DIM: - return ledValue + 0x01; + return ledValue; case this.LIGHT_BRIGHT: return ledValue + 0x02; } @@ -1357,7 +1399,7 @@ TraktorS3.FXControl.prototype.lightFX = function() { } for (var ch = 1; ch <= 4; ch++) { var channel = "[Channel" + ch + "]"; - this.lightEnabled(channel); + this.lightEnable(channel); } TraktorS3.batchingOutputs = false; @@ -1375,30 +1417,30 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { if (this.selectPressed[idx]) { status = this.LIGHT_BRIGHT; } else { - if (idx === 0) { - // Filter select button highlighted in filter mode unless any enable is pressed - if (!this.anyEnablePressed()) { - status = this.LIGHT_BRIGHT; + // select buttons on if fx unit enabled for the pressed channel, + // otherwise disabled. + status = this.LIGHT_DIM; + var pressed = this.firstPressedEnable(); + if (pressed) { + if (idx === 0) { + var fxGroup = "[QuickEffectRack1_" + pressed + "_Effect1]"; + var fxKey = "enabled"; + } else { + fxGroup = "[EffectRack1_EffectUnit" + idx + "]"; + fxKey = "group_" + pressed + "_enable"; } - } else { - // select buttons on if fx unit enabled for the pressed channel, - // otherwise disabled. - status = this.LIGHT_DIM; - var pressed = this.firstPressedEnable(); - if (pressed) { - var fxGroup = "[EffectRack1_EffectUnit" + idx + "]"; - var fxKey = "group_" + pressed + "_enable"; - if (engine.getParameter(fxGroup, fxKey)) { - HIDDebug("bright! " + fxGroup + " " + fxKey); - status = this.LIGHT_BRIGHT; - } else { - status = this.LIGHT_OFF; - } + if (engine.getParameter(fxGroup, fxKey)) { + HIDDebug("bright! " + fxGroup + " " + fxKey); + status = this.LIGHT_BRIGHT; + } else { + status = this.LIGHT_OFF; } } ledValue = this.getFXSelectLEDValue(idx, status); } break; + case this.STATE_EFFECT_INIT: + // Fallthrough intended case this.STATE_EFFECT: // Highlight if pressed, disable if active effect. // Otherwise off. @@ -1409,11 +1451,22 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { } break; case this.STATE_FOCUS: - // TODO: Blink for focused effect + // if blink state is false, only like active fx bright + // if blink state is true, active fx is bright and selected effect + // is dim. if those are the same, active fx is dim if (this.selectPressed[idx]) { status = this.LIGHT_BRIGHT; - } else if (idx === this.activeFX) { - status = this.LIGHT_BRIGHT; + } else { + if (idx === this.activeFX) { + status = this.LIGHT_BRIGHT; + } + if (this.focusBlinkState) { + var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; + var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); + if (idx === focusedEffect) { + status = this.LIGHT_DIM; + } + } } break; } @@ -1421,7 +1474,7 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { this.controller.setOutput("[ChannelX]", "!fxButton" + idx, ledValue, false); }; -TraktorS3.FXControl.prototype.lightEnabled = function(channel) { +TraktorS3.FXControl.prototype.lightEnable = function(channel) { var status = this.LIGHT_OFF; var ledValue = 0x00; var buttonNumber = this.channelToIndex(channel); @@ -1429,6 +1482,7 @@ TraktorS3.FXControl.prototype.lightEnabled = function(channel) { case this.STATE_FILTER: // enable buttons highlighted if pressed or if any fx unit enabled for channel. // Highlight if pressed. + status = this.LIGHT_DIM; if (this.enablePressed[channel]) { status = this.LIGHT_BRIGHT; } else { @@ -1443,6 +1497,8 @@ TraktorS3.FXControl.prototype.lightEnabled = function(channel) { // Enable buttons have regular deck colors ledValue = this.getChannelColor(channel, status); break; + case this.STATE_EFFECT_INIT: + // Fallthrough intended case this.STATE_EFFECT: if (this.enablePressed[channel]) { status = this.LIGHT_BRIGHT; diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index 1c0cf1d9f4e..9769d2a3fa9 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -75,23 +75,28 @@ class TraktorS3Test : public ControllerTest { " return TestOb.shiftPressed;" "}"); - // Mock out controller for testing lights + // Mock out functions and controller for testing lights evaluate( - "TraktorS3.FXControl.prototype.getFXSelectLEDValue = " - "function(fxNumber, status) {" + "TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) {" " return fxNumber*10 + status;" "};" - "TraktorS3.FXControl.prototype.getChannelColor = " - "function(group, status) {" + "TraktorS3.FXControl.prototype.getChannelColor = function(group, status) {" " return this.channelToIndex(group)*10 + status;" "};" + // stub out state changer so we don't do time-based blinking + "TraktorS3.FXControl.prototype.changeState = function(newState) {" + " this.currentState = newState;" + "};" + "var setBlink = function(state) { " + " TestOb.fxc.controller.focusBlinkState = state;" + "};" "TestOb.fxc.controller = new function() {" " this.lightMap = {}; " " this.setOutput = function(group, key, value, batching) {" " if (!(group in this.lightMap)) {" " this.lightMap[group] = {};" " }" - " HIDDebug('light: ' + group + ' ' + key + ' ' + value);" + // " HIDDebug('light: ' + group + ' ' + key + ' ' + value);" " this.lightMap[group][key] = value;" " };" "};" @@ -99,8 +104,8 @@ class TraktorS3Test : public ControllerTest { " if (!(group in TestOb.fxc.controller.lightMap)) {" " return undefined;" " }" - " HIDDebug('whats the frequency ' + group + ' ' + key + ' ' + " - "TestOb.fxc.controller.lightMap[group][key]);" + // " HIDDebug('whats the frequency ' + group + ' ' + key + ' ' + " + // "TestOb.fxc.controller.lightMap[group][key]);" " return TestOb.fxc.controller.lightMap[group][key];" "};"); } @@ -175,6 +180,7 @@ class TraktorS3Test : public ControllerTest { enum states { STATE_FILTER, + STATE_EFFECT_INIT, STATE_EFFECT, STATE_FOCUS }; @@ -221,7 +227,7 @@ TEST_F(TraktorS3Test, FXSelectButtonSimple) { CheckSelectLights({0, 10, 22, 30, 40}); CheckEnableLights({21, 21, 20, 20}); } - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); + EXPECT_EQ(STATE_EFFECT_INIT, evaluate("getState();").toInt32()); EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); // Now unpress select and release @@ -293,9 +299,13 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { // Press fx2 and enable2, focus third effect (channel 2 button is third button) evaluate( + "HIDDebug('1');" "TestOb.fxc.fxSelectHandler(pressFx2);" + "HIDDebug('2');" "TestOb.fxc.fxEnableHandler(pressFxEnable2);" + "HIDDebug('3');" "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" + "HIDDebug('4');" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); EXPECT_EQ(3, @@ -308,6 +318,11 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { CheckSelectLights({0, 10, 22, 30, 40}); CheckEnableLights({20, 20, 20, 20}); } + evaluate("setBlink(true);"); + { + SCOPED_TRACE(""); + CheckSelectLights({0, 10, 21, 30, 40}); + } // Press again, back to effect mode evaluate( @@ -558,15 +573,26 @@ TEST_F(TraktorS3Test, FocusModeFXEnable) { CheckEnableLights({20, 20, 20, 20}); CheckSelectLights({0, 10, 22, 30, 40}); } + evaluate("setBlink(true);"); + { + SCOPED_TRACE(""); + CheckSelectLights({0, 10, 22, 31, 40}); + } evaluate( "TestOb.fxc.fxEnableHandler(pressFxEnable3);" "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - // Effect Unit 1 is toggled + // Effect 1 button parameter 1 is toggled EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "button_parameter1")) ->get()); + { + SCOPED_TRACE(""); + CheckEnableLights({22, 20, 20, 20}); + CheckSelectLights({0, 10, 22, 31, 40}); + } + evaluate("setBlink(true);"); { SCOPED_TRACE(""); CheckEnableLights({22, 20, 20, 20}); @@ -577,7 +603,7 @@ TEST_F(TraktorS3Test, FocusModeFXEnable) { "TestOb.fxc.fxEnableHandler(pressFxEnable3);" "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - // Effect Unit 1 is toggled + // Effect button parameter toggled back EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", "button_parameter1")) ->get()); From d9fa36c8e8228a0abee8534d433e866f6ec96618 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 3 Sep 2020 14:18:21 -0400 Subject: [PATCH 62/84] Traktor S3: debug cleanup --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 13 ------------- src/test/controllers/Traktor_Kontrol_S3_test.cpp | 4 ---- 2 files changed, 17 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 5a7462c86d8..7933e9506e0 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1161,7 +1161,6 @@ TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLon TraktorS3.FXControl.prototype.channelToIndex = function(group) { var result = group.match(script.channelRegEx); if (result === null) { - HIDDebug("barf " + group); return undefined; } // Unmap from channel number to button index. @@ -1230,7 +1229,6 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { if (!field.value) { if (fxNumber === this.activeFX) { if (this.currentState === this.STATE_EFFECT) { - HIDDebug("unpress back to filter!"); this.changeState(this.STATE_FILTER); } else if (this.currentState === this.STATE_EFFECT_INIT) { this.changeState(this.STATE_EFFECT); @@ -1288,7 +1286,6 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { }; TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { - // HIDDebug("we are here: " + field.group + " " + field.value); this.enablePressed[field.group] = field.value; if (!field.value) { @@ -1305,17 +1302,12 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { // Fallthrough intended case this.STATE_EFFECT: if (this.firstPressedSelect()) { - HIDDebug("change to focus"); // Choose the first pressed select button only. this.changeState(this.STATE_FOCUS); - HIDDebug("focusing " + fxGroupPrefix + " " + buttonNumber); - // var focusedEffect = engine.getValue(effectUnitGroup, "focused_effect"); engine.setValue(fxGroupPrefix + "]", "focused_effect", buttonNumber); } else { - HIDDebug("effect togg? " + field.group); var group = fxGroupPrefix + "_Effect" + buttonNumber + "]"; var key = "enabled"; - HIDDebug("toggling " + group + " " + key); script.toggleControl(group, key); } break; @@ -1323,7 +1315,6 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { var focusedEffect = engine.getValue(fxGroupPrefix + "]", "focused_effect"); group = fxGroupPrefix + "_Effect" + focusedEffect + "]"; key = "button_parameter" + buttonNumber; - HIDDebug("toggling " + group + " " + key); script.toggleControl(group, key); break; } @@ -1331,14 +1322,12 @@ TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { }; TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { - // HIDDebug("FX KNOB " + field.group + " " + field.name + " " + field.value); var value = field.value / 4095.; var fxGroupPrefix = "[EffectRack1_EffectUnit" + this.activeFX; var knobIdx = this.channelToIndex(field.group); switch (this.currentState) { case this.STATE_FILTER: - // HIDDebug("filter"); if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { // There is no quickeffect for the microphone, do nothing. return; @@ -1348,7 +1337,6 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { case this.STATE_EFFECT_INIT: // Fallthrough intended case this.STATE_EFFECT: - // HIDDebug("effect"); if (knobIdx === 4) { engine.setParameter(fxGroupPrefix + "]", "mix", value); } else { @@ -1430,7 +1418,6 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { fxKey = "group_" + pressed + "_enable"; } if (engine.getParameter(fxGroup, fxKey)) { - HIDDebug("bright! " + fxGroup + " " + fxKey); status = this.LIGHT_BRIGHT; } else { status = this.LIGHT_OFF; diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp index 9769d2a3fa9..d3f2fa3eedd 100644 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ b/src/test/controllers/Traktor_Kontrol_S3_test.cpp @@ -299,13 +299,9 @@ TEST_F(TraktorS3Test, FXSelectFocusToggle) { // Press fx2 and enable2, focus third effect (channel 2 button is third button) evaluate( - "HIDDebug('1');" "TestOb.fxc.fxSelectHandler(pressFx2);" - "HIDDebug('2');" "TestOb.fxc.fxEnableHandler(pressFxEnable2);" - "HIDDebug('3');" "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" - "HIDDebug('4');" "TestOb.fxc.fxSelectHandler(unpressFx2);"); EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); EXPECT_EQ(3, From a7ab2b3565925d90e5b7372da20f889858a5cf42 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 3 Sep 2020 15:02:49 -0400 Subject: [PATCH 63/84] revert unintentional changes --- src/controllers/controllermanager.cpp | 10 +++++----- src/controllers/controllermanager.h | 2 -- src/effects/effectslot.cpp | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index b6ab1f485e8..a232fbfa294 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -27,18 +27,18 @@ #include "controllers/bulk/bulkenumerator.h" #endif +namespace { // http://developer.qt.nokia.com/wiki/Threads_Events_QObjects // Poll every 1ms (where possible) for good controller response #ifdef __LINUX__ // Many Linux distros ship with the system tick set to 250Hz so 1ms timer // reportedly causes CPU hosage. See Bug #990992 rryan 6/2012 -const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(5); +const int kPollIntervalMillis = 5; #else -const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(1); +const int kPollIntervalMillis = 1; #endif -namespace { /// Strip slashes and spaces from device name, so that it can be used as config /// key or a filename. QString sanitizeDeviceName(QString name) { @@ -97,7 +97,7 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig) QDir().mkpath(userPresets); } - m_pollTimer.setInterval(kPollInterval.toIntegerMillis()); + m_pollTimer.setInterval(kPollIntervalMillis); connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(pollDevices())); @@ -359,7 +359,7 @@ void ControllerManager::pollDevices() { } mixxx::Duration duration = mixxx::Time::elapsed() - start; - if (duration > kPollInterval) { + if (duration > mixxx::Duration::fromMillis(kPollIntervalMillis)) { m_skipPoll = true; } //qDebug() << "ControllerManager::pollDevices()" << duration << start; diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index fba7f704624..8837d903580 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -30,8 +30,6 @@ class ControllerManager : public QObject { ControllerManager(UserSettingsPointer pConfig); virtual ~ControllerManager(); - static const mixxx::Duration kPollInterval; - QList getControllers() const; QList getControllerList(bool outputDevices=true, bool inputDevices=true); ControllerLearningEventFilter* getControllerLearningEventFilter() const; diff --git a/src/effects/effectslot.cpp b/src/effects/effectslot.cpp index 75541c7dcbe..c91083558a2 100644 --- a/src/effects/effectslot.cpp +++ b/src/effects/effectslot.cpp @@ -151,8 +151,8 @@ EffectButtonParameterSlotPointer EffectSlot::getEffectButtonParameterSlot(unsign } void EffectSlot::loadEffect(EffectPointer pEffect, bool adoptMetaknobPosition) { - qDebug() << debugString() << "loadEffect" - << (pEffect ? pEffect->getManifest()->name() : "(null)"); + //qDebug() << debugString() << "loadEffect" + // << (pEffect ? pEffect->getManifest().name() : "(null)"); if (pEffect) { m_pEffect = pEffect; m_pControlLoaded->forceSet(1.0); From a0eeb51e16a2195badf551614e07e2cec91d99c1 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 3 Sep 2020 15:03:43 -0400 Subject: [PATCH 64/84] another unintentional commit --- src/controllers/hid/hidcontroller.cpp | 37 ++++++++++----------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index 264c8ff0c89..08bff7ef126 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -6,21 +6,19 @@ * */ -#include "controllers/hid/hidcontroller.h" - -#include #include +#include -#include "controllers/controllerdebug.h" -#include "controllers/controllermanager.h" -#include "controllers/defs_controllers.h" #include "util/path.h" // for PATH_MAX on Windows -#include "util/time.h" +#include "controllers/hid/hidcontroller.h" +#include "controllers/defs_controllers.h" #include "util/trace.h" +#include "controllers/controllerdebug.h" +#include "util/time.h" HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig) : Controller(pConfig), - m_pHidDevice(nullptr) { + m_pHidDevice(NULL) { // Copy required variables from deviceInfo, which will be freed after // this class is initialized by caller. hid_vendor_id = deviceInfo.vendor_id; @@ -245,22 +243,13 @@ int HidController::close() { bool HidController::poll() { Trace hidRead("HidController poll"); - int result = 1; - auto loopStartTime = mixxx::Time::elapsed(); - while (result > 0) { - // Failsafe in case the script takes too long. This can happen if a controller constitutively spams - // HID messages even if there are no changes since the last message, for example the Gemini GMX. - if (mixxx::Time::elapsed() - loopStartTime >= ControllerManager::kPollInterval) { - return true; - } - result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0])); - if (result == -1) { - return false; - } else if (result > 0) { - Trace process("HidController process packet"); - auto byteArray = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); - receive(byteArray, mixxx::Time::elapsed()); - } + int result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0])); + if (result == -1) { + return false; + } else if (result > 0) { + Trace process("HidController process packet"); + QByteArray outData(reinterpret_cast(m_pPollData), result); + receive(outData, mixxx::Time::elapsed()); } return true; From 81a0638ae7aae1dbdd46c23d4df5a6824a7bffb4 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 22 Sep 2020 23:07:12 -0400 Subject: [PATCH 65/84] Traktor S3: Sync with master version --- .../Traktor-Kontrol-S3-hid-scripts.js | 576 +++++++++--------- 1 file changed, 294 insertions(+), 282 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 7933e9506e0..7d39a542666 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -15,6 +15,8 @@ /* */ /////////////////////////////////////////////////////////////////////////////////// +var TraktorS3 = {}; + // ==== Friendly User Configuration ==== // The pitch slider can operate either in absolute or relative mode. // In absolute mode: @@ -29,13 +31,13 @@ // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. -var TraktorS3PitchSliderRelativeMode = true; +TraktorS3.PitchSliderRelativeMode = true; // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, // PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE // Some colors may look odd because of how they are encoded inside the controller. -var TraktorS3ChannelColors = { +TraktorS3.ChannelColors = { "[Channel1]": "CARROT", "[Channel2]": "CARROT", "[Channel3]": "BLUE", @@ -43,15 +45,14 @@ var TraktorS3ChannelColors = { }; // Each color has four brightnesses, so these values can be between 0 and 3. -var TraktorS3LEDDimValue = 0x00; -var TraktorS3LEDBrightValue = 0x02; +TraktorS3.LEDDimValue = 0x00; +TraktorS3.LEDBrightValue = 0x02; // Set to true to output debug messages and debug light outputs. -var TraktorS3DebugMode = false; - +TraktorS3.DebugMode = false; -var TraktorS3 = new function() { - this.controller = new HIDController(); +TraktorS3.Controller = function() { + this.hid = new HIDController(); // When true, packets will not be sent to the controller. Good for doing mass updates. this.batchingOutputs = false; @@ -76,7 +77,7 @@ var TraktorS3 = new function() { // The S3 has a set of predefined colors for many buttons. They are not // mapped by RGB, but 16 colors, each with 4 levels of brightness, plus white. - this.controller.LEDColors = { + this.hid.LEDColors = { OFF: 0x00, RED: 0x04, CARROT: 0x08, @@ -100,35 +101,35 @@ var TraktorS3 = new function() { // FX 5 is the Filter this.fxLEDValue = { - 0: this.controller.LEDColors.PURPLE, - 1: this.controller.LEDColors.RED, - 2: this.controller.LEDColors.GREEN, - 3: this.controller.LEDColors.BLUE, - 4: this.controller.LEDColors.YELLOW, + 0: this.hid.LEDColors.PURPLE, + 1: this.hid.LEDColors.RED, + 2: this.hid.LEDColors.GREEN, + 3: this.hid.LEDColors.BLUE, + 4: this.hid.LEDColors.YELLOW, }; this.colorMap = new ColorMapper({ - 0xCC0000: this.controller.LEDColors.RED, - 0xCC5E00: this.controller.LEDColors.CARROT, - 0xCC7800: this.controller.LEDColors.ORANGE, - 0xCC9200: this.controller.LEDColors.HONEY, - - 0xCCCC00: this.controller.LEDColors.YELLOW, - 0x81CC00: this.controller.LEDColors.LIME, - 0x00CC00: this.controller.LEDColors.GREEN, - 0x00CC49: this.controller.LEDColors.AQUA, - - 0x00CCCC: this.controller.LEDColors.CELESTE, - 0x0091CC: this.controller.LEDColors.SKY, - 0x0000CC: this.controller.LEDColors.BLUE, - 0xCC00CC: this.controller.LEDColors.PURPLE, - - 0xCC0091: this.controller.LEDColors.FUCHSIA, - 0xCC0079: this.controller.LEDColors.MAGENTA, - 0xCC477E: this.controller.LEDColors.AZALEA, - 0xCC4761: this.controller.LEDColors.SALMON, - - 0xCCCCCC: this.controller.LEDColors.WHITE, + 0xCC0000: this.hid.LEDColors.RED, + 0xCC5E00: this.hid.LEDColors.CARROT, + 0xCC7800: this.hid.LEDColors.ORANGE, + 0xCC9200: this.hid.LEDColors.HONEY, + + 0xCCCC00: this.hid.LEDColors.YELLOW, + 0x81CC00: this.hid.LEDColors.LIME, + 0x00CC00: this.hid.LEDColors.GREEN, + 0x00CC49: this.hid.LEDColors.AQUA, + + 0x00CCCC: this.hid.LEDColors.CELESTE, + 0x0091CC: this.hid.LEDColors.SKY, + 0x0000CC: this.hid.LEDColors.BLUE, + 0xCC00CC: this.hid.LEDColors.PURPLE, + + 0xCC0091: this.hid.LEDColors.FUCHSIA, + 0xCC0079: this.hid.LEDColors.MAGENTA, + 0xCC477E: this.hid.LEDColors.AZALEA, + 0xCC4761: this.hid.LEDColors.SALMON, + + 0xCCCCCC: this.hid.LEDColors.WHITE, }); // State for controller input loudness setting @@ -137,8 +138,6 @@ var TraktorS3 = new function() { // If true, channel 4 is in input mode this.channel4InputMode = false; - this.inputFxEnabledState = false; - // callbacks this.samplerCallbacks = []; }; @@ -153,7 +152,8 @@ TraktorS3.bind = function(fn, obj) { //// Deck Objects //// // Decks are the physical controllers on either side of the controller. // Each Deck can control 2 channels. -TraktorS3.Deck = function(deckNumber, group) { +TraktorS3.Deck = function(controller, deckNumber, group) { + this.controller = controller; this.deckNumber = deckNumber; this.group = group; this.activeChannel = "[Channel" + deckNumber + "]"; @@ -191,7 +191,7 @@ TraktorS3.Deck.prototype.activateChannel = function(channel) { } this.activeChannel = channel.group; engine.softTakeoverIgnoreNextValue(this.activeChannel, "rate"); - TraktorS3.lightDeck(this.activeChannel); + this.controller.lightDeck(this.activeChannel); }; // defineButton allows us to configure either the right deck or the left deck, depending on which @@ -202,7 +202,7 @@ TraktorS3.Deck.prototype.defineButton = function(msg, name, deckOffset, deckBitm deckOffset = deck2Offset; deckBitmask = deck2Bitmask; } - TraktorS3.registerInputButton(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); + this.controller.registerInputButton(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); }; TraktorS3.Deck.prototype.defineJog = function(message, name, deckOffset, deck2Offset, callback) { @@ -220,7 +220,7 @@ TraktorS3.Deck.prototype.defineScaler = function(msg, name, deckOffset, deckBitm deckOffset = deck2Offset; deckBitmask = deck2Bitmask; } - TraktorS3.registerInputScaler(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); + this.controller.registerInputScaler(msg, this.group, name, deckOffset, deckBitmask, TraktorS3.bind(fn, this)); }; TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { @@ -281,7 +281,7 @@ TraktorS3.Deck.prototype.shiftHandler = function(field) { if (field.value) { engine.softTakeoverIgnoreNextValue("[Master]", "gain"); } - TraktorS3.basicOutput(field.value, field.group, "!shift"); + this.controller.basicOutput(field.value, field.group, "!shift"); }; TraktorS3.Deck.prototype.playHandler = function(field) { @@ -343,7 +343,7 @@ TraktorS3.Deck.prototype.keylockHandler = function(field) { if (field.value) { engine.setValue(this.activeChannel, "pitch_adjust_set_default", 1); } - } else if (TraktorS3PitchSliderRelativeMode) { + } else if (TraktorS3.PitchSliderRelativeMode) { if (field.value) { // In relative mode on down-press, reset the values and note that // the button is pressed. @@ -601,7 +601,7 @@ TraktorS3.Deck.prototype.jogTouchHandler = function(field) { this.finishJogTouch(); } else { this.wheelTouchInertiaTimer = engine.beginTimer( - inertiaTime, this.finishJogTouch, true); + inertiaTime, TraktorS3.bind(TraktorS3.Deck.prototype.finishJogTouch, this), true); } } }; @@ -656,7 +656,7 @@ TraktorS3.Deck.prototype.finishJogTouch = function() { } else { // Check again soon. this.wheelTouchInertiaTimer = engine.beginTimer( - 100, this.finishJogTouch, true); + 100, TraktorS3.bind(TraktorS3.Deck.prototype.finishJogTouch, this), true); } this.tickReceived = false; }; @@ -694,7 +694,7 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { var value = field.value / 4095; - if (TraktorS3PitchSliderRelativeMode) { + if (TraktorS3.PitchSliderRelativeMode) { if (this.pitchSliderLastValue === -1) { this.pitchSliderLastValue = value; } else { @@ -783,19 +783,17 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { TraktorS3.Deck.prototype.defineLink = function(key, callback) { switch (this.deckNumber) { case 1: - TraktorS3.controller.linkOutput("deck1", key, "[Channel1]", key, callback); + this.controller.hid.linkOutput("deck1", key, "[Channel1]", key, callback); engine.connectControl("[Channel3]", key, callback); break; case 2: - TraktorS3.controller.linkOutput("deck2", key, "[Channel2]", key, callback); + this.controller.hid.linkOutput("deck2", key, "[Channel2]", key, callback); engine.connectControl("[Channel4]", key, callback); break; } }; TraktorS3.Deck.prototype.linkOutputs = function() { - var deckFn = TraktorS3.Deck.prototype; - var colorOutput = function(value, _group, key) { this.colorOutput(value, key); }; @@ -804,7 +802,7 @@ TraktorS3.Deck.prototype.linkOutputs = function() { this.basicOutput(value, key); }; - this.defineLink("play_indicator", TraktorS3.bind(deckFn.playIndicatorHandler, this)); + this.defineLink("play_indicator", TraktorS3.bind(TraktorS3.Deck.prototype.playIndicatorHandler, this)); this.defineLink("cue_indicator", TraktorS3.bind(colorOutput, this)); this.defineLink("sync_enabled", TraktorS3.bind(colorOutput, this)); this.defineLink("keylock", TraktorS3.bind(colorOutput, this)); @@ -814,7 +812,7 @@ TraktorS3.Deck.prototype.linkOutputs = function() { }; TraktorS3.Deck.prototype.deckBaseColor = function() { - return TraktorS3.controller.LEDColors[TraktorS3ChannelColors[this.activeChannel]]; + return this.controller.hid.LEDColors[TraktorS3.ChannelColors[this.activeChannel]]; }; // basicOutput drives lights that only have one color. @@ -826,7 +824,7 @@ TraktorS3.Deck.prototype.basicOutput = function(value, key) { // On value ledValue = 0x77; } - TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); + this.controller.hid.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); }; // colorOutput drives lights that have the palettized multicolor lights. @@ -834,11 +832,11 @@ TraktorS3.Deck.prototype.colorOutput = function(value, key) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3LEDBrightValue; + ledValue += TraktorS3.LEDBrightValue; } else { - ledValue += TraktorS3LEDDimValue; + ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); + this.controller.hid.setOutput(this.group, key, ledValue, !this.controller.batchingOutputs); }; TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, key) { @@ -849,23 +847,23 @@ TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, key) { TraktorS3.Deck.prototype.colorForHotcue = function(num) { var colorCode = engine.getValue(this.activeChannel, "hotcue_" + num + "_color"); - return TraktorS3.colorMap.getValueForNearestColor(colorCode); + return this.controller.colorMap.getValueForNearestColor(colorCode); }; TraktorS3.Deck.prototype.lightHotcue = function(number) { var loaded = engine.getValue(this.activeChannel, "hotcue_" + number + "_enabled"); var active = engine.getValue(this.activeChannel, "hotcue_" + number + "_activate"); - var ledValue = TraktorS3.controller.LEDColors.WHITE; + var ledValue = this.controller.hid.LEDColors.WHITE; if (loaded) { ledValue = this.colorForHotcue(number); - ledValue += TraktorS3LEDDimValue; + ledValue += TraktorS3.LEDDimValue; } if (active) { - ledValue += TraktorS3LEDBrightValue; + ledValue += TraktorS3.LEDBrightValue; } else { - ledValue += TraktorS3LEDDimValue; + ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput(this.group, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); + this.controller.hid.setOutput(this.group, "!pad_" + number, ledValue, !TraktorS3.batchingOutputs); }; TraktorS3.Deck.prototype.lightPads = function() { @@ -898,7 +896,7 @@ TraktorS3.Deck.prototype.wheelOutputByValue = function(group, value) { var ledValue = this.deckBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3LEDBrightValue; + ledValue += TraktorS3.LEDBrightValue; } else { ledValue = 0x00; } @@ -912,11 +910,11 @@ TraktorS3.Deck.prototype.wheelOutput = function(group, valueArray) { } for (var i = 0; i < 8; i++) { - TraktorS3.controller.setOutput(this.group, "!wheel" + i, valueArray[i], false); + this.controller.hid.setOutput(this.group, "!wheel" + i, valueArray[i], false); } if (!TraktorS3.batchingOutputs) { - for (var packetName in TraktorS3.controller.OutputPackets) { - TraktorS3.controller.OutputPackets[packetName].send(); + for (var packetName in this.controller.hid.OutputPackets) { + this.controller.hid.OutputPackets[packetName].send(); } } }; @@ -925,7 +923,8 @@ TraktorS3.Deck.prototype.wheelOutput = function(group, valueArray) { //// Channel Objects //// //// //// Channels don't have much state, just the fx button state. -TraktorS3.Channel = function(parentDeck, group) { +TraktorS3.Channel = function(controller, parentDeck, group) { + this.controller = controller; this.parentDeck = parentDeck; this.group = group; this.fxEnabledState = false; @@ -1010,8 +1009,8 @@ TraktorS3.Channel.prototype.vuMeterHandler = function(value) { TraktorS3.Channel.prototype.linkOutputs = function() { this.vuConnection = engine.makeConnection(this.group, "VuMeter", TraktorS3.bind(TraktorS3.Channel.prototype.vuMeterHandler, this)); - this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.peakOutput); - TraktorS3.linkChannelOutput(this.group, "pfl", TraktorS3.pflOutput); + this.clipConnection = engine.makeConnection(this.group, "PeakIndicator", TraktorS3.bind(TraktorS3.Controller.prototype.peakOutput, this.controller)); + this.controller.linkChannelOutput(this.group, "pfl", TraktorS3.bind(TraktorS3.Controller.prototype.pflOutput, this.controller)); for (var j = 1; j <= 8; j++) { this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_enabled", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); @@ -1021,25 +1020,25 @@ TraktorS3.Channel.prototype.linkOutputs = function() { }; TraktorS3.Channel.prototype.channelBaseColor = function() { - if (this.group === "[Channel4]" && TraktorS3.channel4InputMode) { - return TraktorS3.controller.LEDColors[TraktorS3.controller.LEDColors.OFF]; + if (this.group === "[Channel4]" && this.controller.channel4InputMode) { + return this.controller.hid.LEDColors[this.controller.hid.LEDColors.OFF]; } - return TraktorS3.controller.LEDColors[TraktorS3ChannelColors[this.group]]; + return this.controller.hid.LEDColors[TraktorS3.ChannelColors[this.group]]; }; // colorOutput drives lights that have the palettized multicolor lights. TraktorS3.Channel.prototype.colorOutput = function(value, key) { var ledValue = this.channelBaseColor(); if (value === 1 || value === true) { - ledValue += TraktorS3LEDBrightValue; + ledValue += TraktorS3.LEDBrightValue; } else { - ledValue += TraktorS3LEDDimValue; + ledValue += TraktorS3.LEDDimValue; } - TraktorS3.controller.setOutput(this.group, key, ledValue, !TraktorS3.batchingOutputs); + this.controller.hid.setOutput(this.group, key, ledValue, !this.controller.batchingOutputs); }; TraktorS3.Channel.prototype.hotcuesOutput = function(_value, group, key) { - var deck = TraktorS3.Channels[group].parentDeck; + var deck = this.controller.Channels[group].parentDeck; if (deck.activeChannel !== group) { // Not active, ignore return; @@ -1092,11 +1091,11 @@ TraktorS3.Channel.prototype.lightWheelPosition = function() { // FXControl is an object that manages the gray area in the middle of the // controller: the fx control knobs, fxenable buttons, and fx select buttons. -TraktorS3.FXControl = function() { +TraktorS3.FXControl = function(controller) { // 0 is filter, 1-4 are FX Units 1-4 this.FILTER_EFFECT = 0; this.activeFX = this.FILTER_EFFECT; - this.controller = TraktorS3.controller; + this.controller = controller; this.enablePressed = { "[Channel1]": false, @@ -1141,21 +1140,21 @@ TraktorS3.FXControl = function() { TraktorS3.FXControl.prototype.registerInputs = function(messageShort, messageLong) { // FX Buttons var fxFn = TraktorS3.FXControl.prototype; - TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, TraktorS3.bind(fxFn.fxSelectHandler, this)); - TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, TraktorS3.bind(fxFn.fxSelectHandler, this)); - TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, TraktorS3.bind(fxFn.fxSelectHandler, this)); - TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, TraktorS3.bind(fxFn.fxSelectHandler, this)); - TraktorS3.registerInputButton(messageShort, "[ChannelX]", "!fx0", 0x08, 0x80, TraktorS3.bind(fxFn.fxSelectHandler, this)); + this.controller.registerInputButton(messageShort, "[ChannelX]", "!fx1", 0x08, 0x08, TraktorS3.bind(fxFn.fxSelectHandler, this)); + this.controller.registerInputButton(messageShort, "[ChannelX]", "!fx2", 0x08, 0x10, TraktorS3.bind(fxFn.fxSelectHandler, this)); + this.controller.registerInputButton(messageShort, "[ChannelX]", "!fx3", 0x08, 0x20, TraktorS3.bind(fxFn.fxSelectHandler, this)); + this.controller.registerInputButton(messageShort, "[ChannelX]", "!fx4", 0x08, 0x40, TraktorS3.bind(fxFn.fxSelectHandler, this)); + this.controller.registerInputButton(messageShort, "[ChannelX]", "!fx0", 0x08, 0x80, TraktorS3.bind(fxFn.fxSelectHandler, this)); - TraktorS3.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, TraktorS3.bind(fxFn.fxEnableHandler, this)); - TraktorS3.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, TraktorS3.bind(fxFn.fxEnableHandler, this)); - TraktorS3.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, TraktorS3.bind(fxFn.fxEnableHandler, this)); - TraktorS3.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x40, TraktorS3.bind(fxFn.fxEnableHandler, this)); + this.controller.registerInputButton(messageShort, "[Channel3]", "!fxEnabled", 0x07, 0x08, TraktorS3.bind(fxFn.fxEnableHandler, this)); + this.controller.registerInputButton(messageShort, "[Channel1]", "!fxEnabled", 0x07, 0x10, TraktorS3.bind(fxFn.fxEnableHandler, this)); + this.controller.registerInputButton(messageShort, "[Channel2]", "!fxEnabled", 0x07, 0x20, TraktorS3.bind(fxFn.fxEnableHandler, this)); + this.controller.registerInputButton(messageShort, "[Channel4]", "!fxEnabled", 0x07, 0x40, TraktorS3.bind(fxFn.fxEnableHandler, this)); - TraktorS3.registerInputScaler(messageLong, "[Channel1]", "!fxKnob", 0x39, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); - TraktorS3.registerInputScaler(messageLong, "[Channel2]", "!fxKnob", 0x3B, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); - TraktorS3.registerInputScaler(messageLong, "[Channel3]", "!fxKnob", 0x37, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); - TraktorS3.registerInputScaler(messageLong, "[Channel4]", "!fxKnob", 0x3D, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + this.controller.registerInputScaler(messageLong, "[Channel1]", "!fxKnob", 0x39, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + this.controller.registerInputScaler(messageLong, "[Channel2]", "!fxKnob", 0x3B, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + this.controller.registerInputScaler(messageLong, "[Channel3]", "!fxKnob", 0x37, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); + this.controller.registerInputScaler(messageLong, "[Channel4]", "!fxKnob", 0x3D, 0xFFFF, TraktorS3.bind(fxFn.fxKnobHandler, this)); }; TraktorS3.FXControl.prototype.channelToIndex = function(group) { @@ -1208,23 +1207,53 @@ TraktorS3.FXControl.prototype.changeState = function(newState) { if (newState === this.currentState) { return; } + + // Ignore next values for all knob actions. This is safe to do for all knobs + // even if we're ignoring knobs that aren't active in the new state. + for (var ch = 1; ch <= 4; ch++) { + var group = "[Channel" + ch + "]"; + engine.softTakeoverIgnoreNextValue("[QuickEffectRack1_" + group + "]", "super1"); + } + for (var unit = 1; unit <= 4; unit++) { + group = "[EffectRack1_EffectUnit" + unit + "]"; + key = "mix"; + engine.softTakeoverIgnoreNextValue(group, key); + for (var effect = 1; effect <= 4; effect++) { + group = "[EffectRack1_EffectUnit" + unit + "_Effect" + effect + "]"; + key = "meta"; + engine.softTakeoverIgnoreNextValue(group, key); + for (var param = 1; param <= 4; param++) { + var key = "parameter" + param; + engine.softTakeoverIgnoreNextValue(group, key); + } + } + } + var oldState = this.currentState; this.currentState = newState; if (oldState === this.STATE_FOCUS) { engine.stopTimer(this.focusBlinkTimer); this.focusBlinkTimer = 0; } - if (newState === this.STATE_FOCUS) { + switch (newState) { + case this.STATE_FILTER: + break; + case this.STATE_EFFECT_INIT: + break; + case this.STATE_EFFECT: + break; + case this.STATE_FOCUS: this.focusBlinkTimer = engine.beginTimer(150, function() { - this.focusBlinkState = !this.focusBlinkState; - this.lightFX(); + TraktorS3.kontrol.fxController.focusBlinkState = !TraktorS3.kontrol.fxController.focusBlinkState; + TraktorS3.kontrol.fxController.lightFX(); }, false); } }; TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { var fxNumber = parseInt(field.name[field.name.length - 1]); - this.selectPressed[fxNumber] = field.value; + // Coerce to boolean + this.selectPressed[fxNumber] = !!field.value; if (!field.value) { if (fxNumber === this.activeFX) { @@ -1286,7 +1315,8 @@ TraktorS3.FXControl.prototype.fxSelectHandler = function(field) { }; TraktorS3.FXControl.prototype.fxEnableHandler = function(field) { - this.enablePressed[field.group] = field.value; + // Coerce to boolean + this.enablePressed[field.group] = !!field.value; if (!field.value) { this.lightFX(); @@ -1328,7 +1358,7 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { switch (this.currentState) { case this.STATE_FILTER: - if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + if (field.group === "[Channel4]" && this.controller.channel4InputMode) { // There is no quickeffect for the microphone, do nothing. return; } @@ -1354,7 +1384,7 @@ TraktorS3.FXControl.prototype.fxKnobHandler = function(field) { }; TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) { - var ledValue = TraktorS3.fxLEDValue[fxNumber]; + var ledValue = this.controller.fxLEDValue[fxNumber]; switch (status) { case this.LIGHT_OFF: return 0x00; @@ -1366,7 +1396,7 @@ TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) { }; TraktorS3.FXControl.prototype.getChannelColor = function(group, status) { - var ledValue = TraktorS3.controller.LEDColors[TraktorS3ChannelColors[group]]; + var ledValue = this.controller.hid.LEDColors[TraktorS3.ChannelColors[group]]; switch (status) { case this.LIGHT_OFF: return 0x00; @@ -1378,7 +1408,7 @@ TraktorS3.FXControl.prototype.getChannelColor = function(group, status) { }; TraktorS3.FXControl.prototype.lightFX = function() { - TraktorS3.batchingOutputs = true; + this.controller.batchingOutputs = true; // Loop through select buttons // Idx zero is filter button @@ -1390,9 +1420,9 @@ TraktorS3.FXControl.prototype.lightFX = function() { this.lightEnable(channel); } - TraktorS3.batchingOutputs = false; - for (var packetName in this.controller.OutputPackets) { - this.controller.OutputPackets[packetName].send(); + this.controller.batchingOutputs = false; + for (var packetName in this.controller.hid.OutputPackets) { + this.controller.hid.OutputPackets[packetName].send(); } }; @@ -1458,7 +1488,7 @@ TraktorS3.FXControl.prototype.lightSelect = function(idx) { break; } ledValue = this.getFXSelectLEDValue(idx, status); - this.controller.setOutput("[ChannelX]", "!fxButton" + idx, ledValue, false); + this.controller.hid.setOutput("[ChannelX]", "!fxButton" + idx, ledValue, false); }; TraktorS3.FXControl.prototype.lightEnable = function(channel) { @@ -1522,15 +1552,15 @@ TraktorS3.FXControl.prototype.lightEnable = function(channel) { ledValue = this.getFXSelectLEDValue(this.activeFX, status); break; } - this.controller.setOutput(channel, "!fxEnabled", ledValue, false); + this.controller.hid.setOutput(channel, "!fxEnabled", ledValue, false); }; -TraktorS3.registerInputPackets = function() { - var messageShort = new HIDPacket("shortmessage", 0x01, this.messageCallback); - var messageLong = new HIDPacket("longmessage", 0x02, this.messageCallback); +TraktorS3.Controller.prototype.registerInputPackets = function() { + var messageShort = new HIDPacket("shortmessage", 0x01, TraktorS3.messageCallback); + var messageLong = new HIDPacket("longmessage", 0x02, TraktorS3.messageCallback); - for (var idx in TraktorS3.Decks) { - var deck = TraktorS3.Decks[idx]; + for (var idx in this.Decks) { + var deck = this.Decks[idx]; deck.registerInputs(messageShort, messageLong); } @@ -1550,7 +1580,7 @@ TraktorS3.registerInputPackets = function() { this.fxController.registerInputs(messageShort, messageLong); - this.controller.registerInputPacket(messageShort); + this.hid.registerInputPacket(messageShort); this.registerInputScaler(messageLong, "[Channel1]", "volume", 0x05, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Channel2]", "volume", 0x07, 0xFFFF, this.parameterHandler); @@ -1583,12 +1613,12 @@ TraktorS3.registerInputPackets = function() { this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); - this.controller.registerInputPacket(messageLong); + this.hid.registerInputPacket(messageLong); // Soft takeovers for (var ch = 1; ch <= 4; ch++) { var group = "[Channel" + ch + "]"; - if (!TraktorS3PitchSliderRelativeMode) { + if (!TraktorS3.PitchSliderRelativeMode) { engine.softTakeover(group, "rate", true); } engine.softTakeover(group, "pitch_adjust", true); @@ -1596,6 +1626,20 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover(group, "pregain", true); engine.softTakeover("[QuickEffectRack1_" + group + "]", "super1", true); } + for (var unit = 1; unit <= 4; unit++) { + group = "[EffectRack1_EffectUnit" + unit + "]"; + var key = "mix"; + engine.softTakeover(group, key, true); + for (var effect = 1; effect <= 4; effect++) { + group = "[EffectRack1_EffectUnit" + unit + "_Effect" + effect + "]"; + key = "meta"; + engine.softTakeover(group, key, true); + for (var param = 1; param <= 4; param++) { + key = "parameter" + param; + engine.softTakeover(group, key, true); + } + } + } engine.softTakeover("[Microphone]", "volume", true); engine.softTakeover("[Microphone]", "pregain", true); @@ -1622,8 +1666,8 @@ TraktorS3.registerInputPackets = function() { engine.softTakeover("[Sampler" + i + "]", "pregain", true); } - for (ch in TraktorS3.Channels) { - var chanob = TraktorS3.Channels[ch]; + for (ch in this.Channels) { + var chanob = this.Channels[ch]; engine.connectControl(ch, "playposition", TraktorS3.bind(TraktorS3.Channel.prototype.playpositionChanged, chanob)); engine.connectControl(ch, "track_loaded", @@ -1637,124 +1681,92 @@ TraktorS3.registerInputPackets = function() { TraktorS3.incomingData(data); }; -TraktorS3.registerInputJog = function(message, group, name, offset, bitmask, callback) { +TraktorS3.Controller.prototype.registerInputJog = function(message, group, name, offset, bitmask, callback) { // Jog wheels have 4 byte input message.addControl(group, name, offset, "I", bitmask); message.setCallback(group, name, callback); }; -TraktorS3.registerInputScaler = function(message, group, name, offset, bitmask, callback) { +TraktorS3.Controller.prototype.registerInputScaler = function(message, group, name, offset, bitmask, callback) { message.addControl(group, name, offset, "H", bitmask); message.setCallback(group, name, callback); }; -TraktorS3.registerInputButton = function(message, group, name, offset, bitmask, callback) { +TraktorS3.Controller.prototype.registerInputButton = function(message, group, name, offset, bitmask, callback) { message.addControl(group, name, offset, "B", bitmask); message.setCallback(group, name, callback); }; -TraktorS3.parameterHandler = function(field) { - if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { +TraktorS3.Controller.prototype.parameterHandler = function(field) { + if (field.group === "[Channel4]" && this.channel4InputMode) { engine.setParameter("[Microphone]", field.name, field.value / 4095); } else { engine.setParameter(field.group, field.name, field.value / 4095); } }; -TraktorS3.anyShiftPressed = function() { - return TraktorS3.Decks["deck1"].shiftPressed || TraktorS3.Decks["deck2"].shiftPressed; +TraktorS3.Controller.prototype.anyShiftPressed = function() { + return this.Decks["deck1"].shiftPressed || this.Decks["deck2"].shiftPressed; }; -TraktorS3.masterGainHandler = function(field) { +TraktorS3.Controller.prototype.masterGainHandler = function(field) { // Only adjust if shift is held. This will still adjust the sound card // volume but it at least allows for control of Mixxx's master gain. - if (TraktorS3.anyShiftPressed()) { + if (this.anyShiftPressed()) { engine.setParameter(field.group, field.name, field.value / 4095); } }; -TraktorS3.headphoneHandler = function(field) { +TraktorS3.Controller.prototype.headphoneHandler = function(field) { if (field.value === 0) { return; } - if (field.group === "[Channel4]" && TraktorS3.channel4InputMode) { + if (field.group === "[Channel4]" && this.channel4InputMode) { script.toggleControl("[Microphone]", "pfl"); } else { script.toggleControl(field.group, "pfl"); } }; -TraktorS3.deckSwitchHandler = function(field) { +TraktorS3.Controller.prototype.deckSwitchHandler = function(field) { if (field.value === 0) { return; } - var channel = TraktorS3.Channels[field.group]; + var channel = this.Channels[field.group]; var deck = channel.parentDeck; deck.activateChannel(channel); }; -// TraktorS3.toggleFX = function() { -// // This is an AND operation. We go through each channel, and if -// // the filter button is ON and the fx is ON, we turn the effect ON. -// // We turn OFF if either is false. - -// // The only exception is the Filter effect. If the channel fxenable -// // is off, the Filter effect is still automatically enabled. -// // If the fxenable button is on, the Filter effect is only enabled if -// // the Filter FX button is enabled. -// for (var ch = 1; ch <= 4; ch++) { -// var channel = TraktorS3.Channels["[Channel" + ch + "]"]; -// var chEnabled = channel.fxEnabledState; -// if (ch === 4 && TraktorS3.channel4InputMode) { -// chEnabled = TraktorS3.inputFxEnabledState; -// } else { -// // There is no quickeffect for the microphone -// var newState = !chEnabled || TraktorS3.fxButtonState[5]; -// engine.setValue("[QuickEffectRack1_[Channel" + ch + "]]", "enabled", -// newState); -// } -// for (var fxNumber = 1; fxNumber <= 4; fxNumber++) { -// var fxGroup = "[EffectRack1_EffectUnit" + fxNumber + "]"; -// var fxKey = "group_[Channel" + ch + "]_enable"; -// newState = chEnabled && TraktorS3.fxButtonState[fxNumber]; -// if (ch === 4 && TraktorS3.channel4InputMode) { -// fxKey = "group_[Microphone]_enable"; -// } -// engine.setValue(fxGroup, fxKey, newState); -// } -// } -// }; - -TraktorS3.extModeHandler = function(field) { +TraktorS3.Controller.prototype.extModeHandler = function(field) { if (!field.value) { - TraktorS3.basicOutput(TraktorS3.channel4InputMode, field.group, field.name); + this.basicOutput(this.channel4InputMode, field.group, field.name); return; } - if (TraktorS3.anyShiftPressed()) { - TraktorS3.basicOutput(field.value, field.group, field.name); - TraktorS3.inputModeLine = !TraktorS3.inputModeLine; - TraktorS3.setInputLineMode(TraktorS3.inputModeLine); + if (this.anyShiftPressed()) { + this.basicOutput(field.value, field.group, field.name); + this.inputModeLine = !this.inputModeLine; + this.setInputLineMode(this.inputModeLine); return; } - TraktorS3.channel4InputMode = !TraktorS3.channel4InputMode; - if (TraktorS3.channel4InputMode) { + this.channel4InputMode = !this.channel4InputMode; + if (this.channel4InputMode) { engine.softTakeoverIgnoreNextValue("[Microphone]", "volume"); engine.softTakeoverIgnoreNextValue("[Microphone]", "pregain"); } else { engine.softTakeoverIgnoreNextValue("[Channel4]", "volume"); engine.softTakeoverIgnoreNextValue("[Channel4]", "pregain"); } - TraktorS3.lightDeck("[Channel4]"); - TraktorS3.basicOutput(TraktorS3.channel4InputMode, field.group, field.name); + this.lightDeck("[Channel4]"); + this.basicOutput(this.channel4InputMode, field.group, field.name); }; -TraktorS3.registerOutputPackets = function() { +TraktorS3.Controller.prototype.registerOutputPackets = function() { var outputA = new HIDPacket("outputA", 0x80); var outputB = new HIDPacket("outputB", 0x81); - for (var idx in TraktorS3.Decks) { - var deck = TraktorS3.Decks[idx]; + for (var idx in this.Decks) { + var deck = this.Decks[idx]; deck.registerOutputs(outputA, outputB); } @@ -1781,7 +1793,7 @@ TraktorS3.registerOutputPackets = function() { outputA.addOutput("[Master]", "!extButton", 0x33, "B"); - this.controller.registerOutputPacket(outputA); + this.hid.registerOutputPacket(outputA); var VuOffsets = { "[Channel3]": 0x01, @@ -1812,55 +1824,55 @@ TraktorS3.registerOutputPackets = function() { outputB.addOutput("[Channel2]", "PeakIndicator", 0x2D, "B"); outputB.addOutput("[Channel4]", "PeakIndicator", 0x3C, "B"); - this.controller.registerOutputPacket(outputB); + this.hid.registerOutputPacket(outputB); - for (idx in TraktorS3.Decks) { - deck = TraktorS3.Decks[idx]; + for (idx in this.Decks) { + deck = this.Decks[idx]; deck.linkOutputs(); } - for (idx in TraktorS3.Channels) { - var chan = TraktorS3.Channels[idx]; + for (idx in this.Channels) { + var chan = this.Channels[idx]; chan.linkOutputs(); } engine.connectControl("[Microphone]", "pfl", this.pflOutput); // Master VuMeters - this.masterVuMeter["VuMeterL"].connection = engine.makeConnection("[Master]", "VuMeterL", this.masterVuMeterHandler); - this.masterVuMeter["VuMeterR"].connection = engine.makeConnection("[Master]", "VuMeterR", this.masterVuMeterHandler); - this.linkChannelOutput("[Master]", "PeakIndicatorL", this.peakOutput); - this.linkChannelOutput("[Master]", "PeakIndicatorR", this.peakOutput); - this.guiTickConnection = engine.makeConnection("[Master]", "guiTick50ms", this.guiTickHandler); + this.masterVuMeter["VuMeterL"].connection = engine.makeConnection("[Master]", "VuMeterL", TraktorS3.bind(TraktorS3.Controller.prototype.masterVuMeterHandler, this)); + this.masterVuMeter["VuMeterR"].connection = engine.makeConnection("[Master]", "VuMeterR", TraktorS3.bind(TraktorS3.Controller.prototype.masterVuMeterHandler, this)); + this.linkChannelOutput("[Master]", "PeakIndicatorL", TraktorS3.bind(TraktorS3.Controller.prototype.peakOutput, this)); + this.linkChannelOutput("[Master]", "PeakIndicatorR", TraktorS3.bind(TraktorS3.Controller.prototype.peakOutput, this)); + this.guiTickConnection = engine.makeConnection("[Master]", "guiTick50ms", TraktorS3.bind(TraktorS3.Controller.prototype.guiTickHandler, this)); // Sampler callbacks - for (i = 1; i <= 16; ++i) { - this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", this.samplesOutput)); - this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", this.samplesOutput)); + for (i = 1; i <= 8; ++i) { + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", TraktorS3.bind(TraktorS3.Controller.prototype.samplesOutput, this))); + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", TraktorS3.bind(TraktorS3.Controller.prototype.samplesOutput, this))); } }; -TraktorS3.linkChannelOutput = function(group, name, callback) { - TraktorS3.controller.linkOutput(group, name, group, name, callback); +TraktorS3.Controller.prototype.linkChannelOutput = function(group, name, callback) { + this.hid.linkOutput(group, name, group, name, callback); }; -TraktorS3.pflOutput = function(value, group, key) { - if (group === "[Microphone]" && TraktorS3.channel4InputMode) { - TraktorS3.basicOutput(value, "[Channel4]", key); +TraktorS3.Controller.prototype.pflOutput = function(value, group, key) { + if (group === "[Microphone]" && this.channel4InputMode) { + this.basicOutput(value, "[Channel4]", key); return; } - if (group === "[Channel4]" && !TraktorS3.channel4InputMode) { - TraktorS3.basicOutput(value, group, key); + if (group === "[Channel4]" && !this.channel4InputMode) { + this.basicOutput(value, group, key); return; } if (group.match(/^\[Channel[123]\]$/)) { - TraktorS3.basicOutput(value, group, key); + this.basicOutput(value, group, key); } // Unhandled case, ignore. }; // Output drives lights that only have one color. -TraktorS3.basicOutput = function(value, group, key) { +TraktorS3.Controller.prototype.basicOutput = function(value, group, key) { var ledValue = value; if (value === 0 || value === false) { // Off value @@ -1870,24 +1882,24 @@ TraktorS3.basicOutput = function(value, group, key) { ledValue = 0xFF; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); + this.hid.setOutput(group, key, ledValue, !this.batchingOutputs); }; -TraktorS3.peakOutput = function(value, group, key) { +TraktorS3.Controller.prototype.peakOutput = function(value, group, key) { var ledValue = 0x00; if (value) { ledValue = 0x7E; } - TraktorS3.controller.setOutput(group, key, ledValue, !TraktorS3.batchingOutputs); + this.hid.setOutput(group, key, ledValue, !this.batchingOutputs); }; -TraktorS3.masterVuMeterHandler = function(value, _group, key) { - TraktorS3.masterVuMeter[key].updated = true; - TraktorS3.masterVuMeter[key].value = value; +TraktorS3.Controller.prototype.masterVuMeterHandler = function(value, _group, key) { + this.masterVuMeter[key].updated = true; + this.masterVuMeter[key].value = value; }; -TraktorS3.vuMeterOutput = function(value, group, key, segments) { +TraktorS3.Controller.prototype.vuMeterOutput = function(value, group, key, segments) { // This handler is called a lot so it should be as fast as possible. var scaledValue = value * segments; var fullIllumCount = Math.floor(scaledValue); @@ -1899,19 +1911,19 @@ TraktorS3.vuMeterOutput = function(value, group, key, segments) { var segmentKey = "!" + key + i; if (i < fullIllumCount) { // Don't update lights until they're all done, so the last term is false. - TraktorS3.controller.setOutput(group, segmentKey, 0x7F, false); + this.hid.setOutput(group, segmentKey, 0x7F, false); } else if (i === fullIllumCount) { - TraktorS3.controller.setOutput(group, segmentKey, partialIllum, false); + this.hid.setOutput(group, segmentKey, partialIllum, false); } else { - TraktorS3.controller.setOutput(group, segmentKey, 0x00, false); + this.hid.setOutput(group, segmentKey, 0x00, false); } } - if (!TraktorS3.batchingOutputs) { - TraktorS3.controller.OutputPackets["outputB"].send(); + if (!this.batchingOutputs) { + this.hid.OutputPackets["outputB"].send(); } }; -TraktorS3.resolveSampler = function(group) { +TraktorS3.Controller.prototype.resolveSampler = function(group) { if (group === undefined) { return undefined; } @@ -1926,16 +1938,16 @@ TraktorS3.resolveSampler = function(group) { return result[1]; }; -TraktorS3.samplesOutput = function(value, group, key) { +TraktorS3.Controller.prototype.samplesOutput = function(value, group, key) { // Sampler 1-8 -> Channel1 // Samples 9-16 -> Channel2 - var sampler = TraktorS3.resolveSampler(group); - var deck = TraktorS3.Decks["deck1"]; + var sampler = this.resolveSampler(group); + var deck = this.Decks["deck1"]; var num = sampler; if (sampler === undefined) { return; } else if (sampler > 8 && sampler < 17) { - deck = TraktorS3.Decks["deck2"]; + deck = this.Decks["deck2"]; num = sampler - 8; } @@ -1955,7 +1967,7 @@ TraktorS3.samplesOutput = function(value, group, key) { } }; -TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { +TraktorS3.Controller.prototype.lightGroup = function(packet, outputGroupName, coGroupName) { var groupOb = packet.groups[outputGroupName]; for (var fieldName in groupOb) { var field = groupOb[fieldName]; @@ -1970,23 +1982,23 @@ TraktorS3.lightGroup = function(packet, outputGroupName, coGroupName) { } }; -TraktorS3.lightDeck = function(group, sendPackets) { +TraktorS3.Controller.prototype.lightDeck = function(group, sendPackets) { if (sendPackets === undefined) { sendPackets = true; } // Freeze the lights while we do this update so we don't spam HID. - TraktorS3.batchingOutputs = true; - for (var packetName in this.controller.OutputPackets) { - var packet = this.controller.OutputPackets[packetName]; + this.batchingOutputs = true; + for (var packetName in this.hid.OutputPackets) { + var packet = this.hid.OutputPackets[packetName]; var deckGroupName = "deck1"; if (group === "[Channel2]" || group === "[Channel4]") { deckGroupName = "deck2"; } - var deck = TraktorS3.Decks[deckGroupName]; + var deck = this.Decks[deckGroupName]; - TraktorS3.lightGroup(packet, deckGroupName, group); - TraktorS3.lightGroup(packet, group, group); + this.lightGroup(packet, deckGroupName, group); + this.lightGroup(packet, group, group); deck.lightPads(); @@ -1997,72 +2009,71 @@ TraktorS3.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); if (group === "[Channel4]") { - TraktorS3.basicOutput(0, "[Master]", "!extButton"); + this.basicOutput(0, "[Master]", "!extButton"); } } - // TraktorS3.lightFX(); + // this.lightFX(); // Selected deck lights - var ctrlr = TraktorS3.controller; if (group === "[Channel1]") { - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3LEDBrightValue, false); - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3LEDDimValue, false); + this.hid.setOutput("[Channel1]", "!deck_A", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel1]"]] + TraktorS3.LEDBrightValue, false); + this.hid.setOutput("[Channel3]", "!deck_C", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel3]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel2]") { - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3LEDBrightValue, false); - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3LEDDimValue, false); + this.hid.setOutput("[Channel2]", "!deck_B", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel2]"]] + TraktorS3.LEDBrightValue, false); + this.hid.setOutput("[Channel4]", "!deck_D", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel4]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel3]") { - ctrlr.setOutput("[Channel3]", "!deck_C", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel3]"]] + TraktorS3LEDBrightValue, false); - ctrlr.setOutput("[Channel1]", "!deck_A", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel1]"]] + TraktorS3LEDDimValue, false); + this.hid.setOutput("[Channel3]", "!deck_C", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel3]"]] + TraktorS3.LEDBrightValue, false); + this.hid.setOutput("[Channel1]", "!deck_A", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel1]"]] + TraktorS3.LEDDimValue, false); } else if (group === "[Channel4]") { - ctrlr.setOutput("[Channel4]", "!deck_D", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel4]"]] + TraktorS3LEDBrightValue, false); - ctrlr.setOutput("[Channel2]", "!deck_B", ctrlr.LEDColors[TraktorS3ChannelColors["[Channel2]"]] + TraktorS3LEDDimValue, false); + this.hid.setOutput("[Channel4]", "!deck_D", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel4]"]] + TraktorS3.LEDBrightValue, false); + this.hid.setOutput("[Channel2]", "!deck_B", this.hid.LEDColors[TraktorS3.ChannelColors["[Channel2]"]] + TraktorS3.LEDDimValue, false); } - TraktorS3.batchingOutputs = false; + this.batchingOutputs = false; // And now send them all. if (sendPackets) { - for (packetName in this.controller.OutputPackets) { - this.controller.OutputPackets[packetName].send(); + for (packetName in this.hid.OutputPackets) { + this.hid.OutputPackets[packetName].send(); } } }; // Render wheel positions, channel VU meters, and master vu meters -TraktorS3.guiTickHandler = function() { - TraktorS3.batchingOutputs = true; +TraktorS3.Controller.prototype.guiTickHandler = function() { + this.batchingOutputs = true; var gotUpdate = false; - gotUpdate |= TraktorS3.Channels[TraktorS3.Decks["deck1"].activeChannel].lightWheelPosition(); - gotUpdate |= TraktorS3.Channels[TraktorS3.Decks["deck2"].activeChannel].lightWheelPosition(); + gotUpdate |= this.Channels[this.Decks["deck1"].activeChannel].lightWheelPosition(); + gotUpdate |= this.Channels[this.Decks["deck2"].activeChannel].lightWheelPosition(); - for (var vu in TraktorS3.masterVuMeter) { - if (TraktorS3.masterVuMeter[vu].updated) { - TraktorS3.vuMeterOutput(TraktorS3.masterVuMeter[vu].value, "[Master]", vu, 8); - TraktorS3.masterVuMeter[vu].updated = false; + for (var vu in this.masterVuMeter) { + if (this.masterVuMeter[vu].updated) { + this.vuMeterOutput(this.masterVuMeter[vu].value, "[Master]", vu, 8); + this.masterVuMeter[vu].updated = false; gotUpdate = true; } } for (var ch = 1; ch <= 4; ch++) { - var chan = TraktorS3.Channels["[Channel" + ch + "]"]; + var chan = this.Channels["[Channel" + ch + "]"]; if (chan.vuMeterUpdated) { - TraktorS3.vuMeterOutput(chan.vuMeterValue, chan.group, "VuMeter", 14); + this.vuMeterOutput(chan.vuMeterValue, chan.group, "VuMeter", 14); chan.vuMeterUpdated = false; gotUpdate = true; } } - TraktorS3.batchingOutputs = false; + this.batchingOutputs = false; if (gotUpdate) { - for (var packetName in TraktorS3.controller.OutputPackets) { - TraktorS3.controller.OutputPackets[packetName].send(); + for (var packetName in this.hid.OutputPackets) { + this.hid.OutputPackets[packetName].send(); } } }; // A special packet sent to the controller switches between mic and line // input modes. if lineMode is true, sets input to line. Otherwise, mic. -TraktorS3.setInputLineMode = function(lineMode) { - var packet = Object(); +TraktorS3.Controller.prototype.setInputLineMode = function(lineMode) { + var packet = Array(); packet.length = 33; packet[0] = 0x20; if (!lineMode) { @@ -2074,13 +2085,13 @@ TraktorS3.setInputLineMode = function(lineMode) { TraktorS3.messageCallback = function(_packet, data) { for (var name in data) { if (Object.prototype.hasOwnProperty.call(data, name)) { - TraktorS3.controller.processButton(data[name]); + TraktorS3.kontrol.hid.processButton(data[name]); } } }; TraktorS3.incomingData = function(data, length) { - TraktorS3.controller.parsePacket(data, length); + TraktorS3.kontrol.hid.parsePacket(data, length); }; TraktorS3.debugLights = function() { @@ -2103,16 +2114,16 @@ TraktorS3.debugLights = function() { "00" ]; - var data = [Object(), Object(), Object()]; + var data = [Array(), Array(), Array()]; for (var i = 0; i < data.length; i++) { var ok = true; - var splitted = dataStrings[i].split(/\s+/); - HIDDebug("i " + i + " " + splitted); - data[i].length = splitted.length; - for (var j = 0; j < splitted.length; j++) { - var byteStr = splitted[j]; + var tokens = dataStrings[i].split(/\s+/); + HIDDebug("i " + i + " " + tokens); + data[i].length = tokens.length; + for (var j = 0; j < tokens.length; j++) { + var byteStr = tokens[j]; if (byteStr.length === 0) { continue; } @@ -2135,7 +2146,7 @@ TraktorS3.debugLights = function() { controller.send(data[i], data[i].length, header); } } - TraktorS3.setInputLineMode(false); + TraktorS3.kontrol.setInputLineMode(false); }; TraktorS3.shutdown = function() { @@ -2154,14 +2165,14 @@ TraktorS3.shutdown = function() { "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", ]; - var data = [Object(), Object()]; + var data = [Array(), Array()]; for (var i = 0; i < data.length; i++) { - var splitted = dataStrings[i].split(/\s+/); - data[i].length = splitted.length; - for (var j = 0; j < splitted.length; j++) { - var byteStr = splitted[j]; + var tokens = dataStrings[i].split(/\s+/); + data[i].length = tokens.length; + for (var j = 0; j < tokens.length; j++) { + var byteStr = tokens[j]; if (byteStr.length === 0) { continue; } @@ -2174,33 +2185,34 @@ TraktorS3.shutdown = function() { }; TraktorS3.init = function(_id) { - this.Decks = { - "deck1": new TraktorS3.Deck(1, "deck1"), - "deck2": new TraktorS3.Deck(2, "deck2"), + this.kontrol = new TraktorS3.Controller(); + this.kontrol.Decks = { + "deck1": new TraktorS3.Deck(this.kontrol, 1, "deck1"), + "deck2": new TraktorS3.Deck(this.kontrol, 2, "deck2"), }; - this.Channels = { - "[Channel1]": new TraktorS3.Channel(this.Decks["deck1"], "[Channel1]"), - "[Channel2]": new TraktorS3.Channel(this.Decks["deck2"], "[Channel2]"), - "[Channel3]": new TraktorS3.Channel(this.Decks["deck1"], "[Channel3]"), - "[Channel4]": new TraktorS3.Channel(this.Decks["deck2"], "[Channel4]") + this.kontrol.Channels = { + "[Channel1]": new TraktorS3.Channel(this.kontrol, this.kontrol.Decks["deck1"], "[Channel1]"), + "[Channel3]": new TraktorS3.Channel(this.kontrol, this.kontrol.Decks["deck1"], "[Channel3]"), + "[Channel4]": new TraktorS3.Channel(this.kontrol, this.kontrol.Decks["deck2"], "[Channel4]"), + "[Channel2]": new TraktorS3.Channel(this.kontrol, this.kontrol.Decks["deck2"], "[Channel2]"), }; - this.fxController = new TraktorS3.FXControl(); + this.kontrol.fxController = new TraktorS3.FXControl(this.kontrol); - TraktorS3.registerInputPackets(); - TraktorS3.registerOutputPackets(); + this.kontrol.registerInputPackets(); + this.kontrol.registerOutputPackets(); HIDDebug("TraktorS3: Init done!"); - if (TraktorS3DebugMode) { + if (TraktorS3.DebugMode) { TraktorS3.debugLights(); } else { - TraktorS3.lightDeck("[Channel3]", false); - TraktorS3.lightDeck("[Channel4]", false); - TraktorS3.lightDeck("[Channel1]", false); - TraktorS3.lightDeck("[Channel2]", true); - TraktorS3.fxController.lightFX(); + this.kontrol.lightDeck("[Channel3]", false); + this.kontrol.lightDeck("[Channel4]", false); + this.kontrol.lightDeck("[Channel1]", false); + this.kontrol.lightDeck("[Channel2]", true); + this.kontrol.fxController.lightFX(); } - TraktorS3.setInputLineMode(TraktorS3.inputModeLine); + this.kontrol.setInputLineMode(TraktorS3.inputModeLine); }; From 8eff1eb91c8fa84548fc283317b510fa6a21902d Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 22 Sep 2020 23:09:33 -0400 Subject: [PATCH 66/84] Traktor S3: revert test for 2.3 --- CMakeLists.txt | 1 - src/controllers/controllerengine.cpp | 51 +- src/controllers/controllerengine.h | 14 +- src/test/controllerengine_test.cpp | 2 +- .../controllers/Traktor_Kontrol_S3_test.cpp | 682 ------------------ src/test/controllers/controllertest.h | 62 -- src/test/signalpathtest.cpp | 1 - src/test/signalpathtest.h | 16 +- 8 files changed, 13 insertions(+), 816 deletions(-) delete mode 100644 src/test/controllers/Traktor_Kontrol_S3_test.cpp delete mode 100644 src/test/controllers/controllertest.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 576aa4b6288..2201689a2d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1284,7 +1284,6 @@ add_executable(mixxx-test src/test/metadatatest.cpp src/test/metaknob_link_test.cpp src/test/midicontrollertest.cpp - src/test/controllers/Traktor_Kontrol_S3_test.cpp src/test/mixxxtest.cpp src/test/movinginterquartilemean_test.cpp src/test/nativeeffects_test.cpp diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index cee783194f4..a7bb9e32c5d 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -417,7 +417,7 @@ Input: 'this' object if applicable, Code string Output: false if an exception -------- ------------------------------------------------------ */ bool ControllerEngine::internalExecute( - QScriptValue thisObject, const QString& scriptCode, QScriptValue* outValue) { + QScriptValue thisObject, const QString& scriptCode) { // A special version of safeExecute since we're evaluating strings, not actual functions // (execute() would print an error that it's not a function every time a timer fires.) if (m_pEngine == nullptr) { @@ -440,7 +440,7 @@ bool ControllerEngine::internalExecute( return false; } - return internalExecute(thisObject, scriptFunction, QScriptValueList(), outValue); + return internalExecute(thisObject, scriptFunction, QScriptValueList()); } /* -------- ------------------------------------------------------ @@ -450,8 +450,7 @@ Output: false if an exception -------- ------------------------------------------------------ */ bool ControllerEngine::internalExecute(QScriptValue thisObject, QScriptValue functionObject, - QScriptValueList args, - QScriptValue* outValue) { + QScriptValueList args) { if (m_pEngine == nullptr) { qDebug() << "ControllerEngine::execute: No script engine exists!"; return false; @@ -476,9 +475,6 @@ bool ControllerEngine::internalExecute(QScriptValue thisObject, // If it does happen to be a function, call it. QScriptValue rc = functionObject.call(thisObject, args); - if (outValue != nullptr) { - *outValue = rc; - } if (!rc.isValid()) { qWarning() << "QScriptValue is not a function or ..."; // Throw a debug assertion if controllerDebug is enabled @@ -506,7 +502,7 @@ bool ControllerEngine::execute(QScriptValue functionObject, args << QScriptValue(value); args << QScriptValue(status); args << QScriptValue(group); - return internalExecute(m_pEngine->globalObject(), functionObject, args, nullptr); + return internalExecute(m_pEngine->globalObject(), functionObject, args); } bool ControllerEngine::execute(QScriptValue function, @@ -519,7 +515,7 @@ bool ControllerEngine::execute(QScriptValue function, QScriptValueList args; args << m_pBaClass->newInstance(data); args << QScriptValue(data.size()); - return internalExecute(m_pEngine->globalObject(), function, args, nullptr); + return internalExecute(m_pEngine->globalObject(), function, args); } /* -------- ------------------------------------------------------ @@ -1061,17 +1057,6 @@ void ControllerEngine::trigger(QString group, QString name) { Output: false if the script file has errors or doesn't exist -------- ------------------------------------------------------ */ bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { - return evaluateScriptWithReturn(scriptFile, nullptr); -} - -/* -------- ------------------------------------------------------ - Purpose: Evaluate a script file - Input: Script filename - Output: false if the script file has errors or doesn't exist, and the result - of the script if outValue is not nullptr. - -------- ------------------------------------------------------ */ -bool ControllerEngine::evaluateScriptWithReturn( - const QFileInfo& scriptFile, QScriptValue* outValue) { if (m_pEngine == nullptr) { return false; } @@ -1117,33 +1102,13 @@ bool ControllerEngine::evaluateScriptWithReturn( scriptCode.append('\n'); input.close(); - return evaluateWithReturn(scriptCode, filename, outValue); -} - -/* -------- ------------------------------------------------------ - Purpose: Evaluate javascript - Input: Script code, can contain newlines. Filename can be empty if not - applicable - Output: false if the script file has errors or doesn't exist, and the result - of the script if outValue is not nullptr. - -------- ------------------------------------------------------ */ -bool ControllerEngine::evaluateWithReturn(const QString& scriptCode, - const QString& filename, - QScriptValue* outValue) { - if (m_pEngine == nullptr) { - return false; - } - // Check syntax if (!syntaxIsValid(scriptCode, filename)) { return false; } // Evaluate the code - QScriptValue scriptResult = m_pEngine->evaluate(scriptCode, filename); - if (outValue != nullptr) { - *outValue = scriptResult; - } + QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode, filename); // Record errors if (checkException(true)) { @@ -1246,9 +1211,9 @@ void ControllerEngine::timerEvent(QTimerEvent *event) { } if (timerTarget.callback.isString()) { - internalExecute(timerTarget.context, timerTarget.callback.toString(), nullptr); + internalExecute(timerTarget.context, timerTarget.callback.toString()); } else if (timerTarget.callback.isFunction()) { - internalExecute(timerTarget.context, timerTarget.callback, QScriptValueList(), nullptr); + internalExecute(timerTarget.context, timerTarget.callback, QScriptValueList()); } } diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 56d40d9ab4c..d89d48b836a 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -167,21 +167,12 @@ class ControllerEngine : public QObject { void errorDialogButton(const QString& key, QMessageBox::StandardButton button); private: - // Filename is only for informational purposes. bool syntaxIsValid(const QString& scriptCode, const QString& filename = QString()); bool evaluate(const QFileInfo& scriptFile); - bool evaluateScriptWithReturn(const QFileInfo& filepath, QScriptValue* outValue); - // Filename is only for informational purposes, it can be empty QString if not applicable. - bool evaluateWithReturn(const QString& program, - const QString& filename, - QScriptValue* outValue); - bool internalExecute(QScriptValue thisObject, - const QString& scriptCode, - QScriptValue* outValue); + bool internalExecute(QScriptValue thisObject, const QString& scriptCode); bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, - QScriptValueList arguments, - QScriptValue* outValue); + QScriptValueList arguments); void initializeScriptEngine(); void uninitializeScriptEngine(); @@ -230,7 +221,6 @@ class ControllerEngine : public QObject { QList m_lastScriptFiles; friend class ControllerEngineTest; - friend class ControllerTest; }; #endif diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 47681ea3162..3b66515f190 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -32,7 +32,7 @@ class ControllerEngineTest : public MixxxTest { bool execute(const QString& functionName) { QScriptValue function = cEngine->wrapFunctionCode(functionName, 0); - return cEngine->internalExecute(QScriptValue(), function, QScriptValueList(), nullptr); + return cEngine->internalExecute(QScriptValue(), function, QScriptValueList()); } void processEvents() { diff --git a/src/test/controllers/Traktor_Kontrol_S3_test.cpp b/src/test/controllers/Traktor_Kontrol_S3_test.cpp deleted file mode 100644 index d3f2fa3eedd..00000000000 --- a/src/test/controllers/Traktor_Kontrol_S3_test.cpp +++ /dev/null @@ -1,682 +0,0 @@ -#include -#include - -#include "control/controlobject.h" -#include "control/controlpotmeter.h" -#include "controllers/controllerdebug.h" -#include "controllers/controllerengine.h" -#include "controllers/softtakeover.h" -#include "effects/effectchain.h" -#include "effects/effectslot.h" -#include "effects/effectsmanager.h" -#include "test/baseeffecttest.h" -#include "test/controllers/controllertest.h" -#include "test/signalpathtest.h" - -class TraktorS3Test : public ControllerTest { - protected: - TraktorS3Test() { - m_pTestBackend = new TestEffectBackend(); - m_pEffectsManager->addEffectsBackend(m_pTestBackend); - } - - void SetUp() override { - ControllerTest::SetUp(); - - // Load a few effects so we can test that part. - EffectManifestPointer pManifest(new EffectManifest()); - pManifest->setId("org.mixxx.test.effect"); - pManifest->setName("Test Effect1"); - pManifest->addParameter(); - registerTestEffect(pManifest, false); - EffectPointer pEffect = m_pEffectsManager->instantiateEffect(pManifest->id()); - - EffectChainPointer pChain(new EffectChain(m_pEffectsManager, - "org.mixxx.test.chain1")); - - for (int chain = 0; chain < 2; ++chain) { - auto chainSlot = m_pRack->getEffectChainSlot(chain); - chainSlot->loadEffectChainToSlot(pChain); - for (int effect = 0; effect < 2; ++effect) { - auto effectSlot = chainSlot->getEffectSlot(effect); - effectSlot->loadEffect(pEffect, false); - } - } - - const QString commonScript = "./res/controllers/common-controller-scripts.js"; - const QString hidScript = "./res/controllers/common-hid-packet-parser.js"; - const QString scriptFile = "./res/controllers/Traktor-Kontrol-S3-hid-scripts.js"; - ASSERT_TRUE(m_pCEngine->evaluate(commonScript)); - ASSERT_FALSE(m_pCEngine->hasErrors(commonScript)); - ASSERT_TRUE(m_pCEngine->evaluate(hidScript)); - ASSERT_FALSE(m_pCEngine->hasErrors(hidScript)); - ASSERT_TRUE(m_pCEngine->evaluate(scriptFile)); - ASSERT_FALSE(m_pCEngine->hasErrors(scriptFile)); - - // Create useful objects and getters - evaluate( - "var TestOb = {};" - "TestOb.fxc = new TraktorS3.FXControl(); " - "var getState = function() {" - " return TestOb.fxc.currentState;" - "};" - "var getActiveFx = function() {" - " return TestOb.fxc.activeFX;" - "};" - "var getSelectPressed = function() {" - " return TestOb.fxc.selectPressed;" - "};" - "var getEnablePressed = function() {" - " return TestOb.fxc.enablePressed;" - "};" - // Mock out shift key. - "TestOb.shiftPressed = false;" - "TraktorS3.anyShiftPressed = function() {" - " return TestOb.shiftPressed;" - "}"); - - // Mock out functions and controller for testing lights - evaluate( - "TraktorS3.FXControl.prototype.getFXSelectLEDValue = function(fxNumber, status) {" - " return fxNumber*10 + status;" - "};" - "TraktorS3.FXControl.prototype.getChannelColor = function(group, status) {" - " return this.channelToIndex(group)*10 + status;" - "};" - // stub out state changer so we don't do time-based blinking - "TraktorS3.FXControl.prototype.changeState = function(newState) {" - " this.currentState = newState;" - "};" - "var setBlink = function(state) { " - " TestOb.fxc.controller.focusBlinkState = state;" - "};" - "TestOb.fxc.controller = new function() {" - " this.lightMap = {}; " - " this.setOutput = function(group, key, value, batching) {" - " if (!(group in this.lightMap)) {" - " this.lightMap[group] = {};" - " }" - // " HIDDebug('light: ' + group + ' ' + key + ' ' + value);" - " this.lightMap[group][key] = value;" - " };" - "};" - "var getLight = function(group, key) {" - " if (!(group in TestOb.fxc.controller.lightMap)) {" - " return undefined;" - " }" - // " HIDDebug('whats the frequency ' + group + ' ' + key + ' ' + " - // "TestOb.fxc.controller.lightMap[group][key]);" - " return TestOb.fxc.controller.lightMap[group][key];" - "};"); - } - - void registerTestEffect(EffectManifestPointer pManifest, bool willAddToEngine) { - MockEffectProcessor* pProcessor = new MockEffectProcessor(); - MockEffectInstantiator* pInstantiator = new MockEffectInstantiator(); - - if (willAddToEngine) { - EXPECT_CALL(*pInstantiator, instantiate(_, _)) - .Times(1) - .WillOnce(Return(pProcessor)); - } - - m_pTestBackend->registerEffect(pManifest->id(), - pManifest, - EffectInstantiatorPointer(pInstantiator)); - } - - void CheckSelectPressed(const std::vector& expected, const QScriptValue& got) { - EXPECT_EQ(5, expected.size()); - for (int i = 0; i < 0; ++i) { - EXPECT_TRUE(got.property(i).isValid()); - EXPECT_EQ(expected[i], got.property(i).toBool()); - } - } - - // Checks that the correct enabled buttons are pressed. The expected values are in - // physical order, not channel order. - void CheckEnabledPressed(const std::vector& expected, const QScriptValue& got) { - EXPECT_EQ(4, expected.size()); - EXPECT_TRUE(got.property("[Channel3]").isValid()); - EXPECT_EQ(expected[0], got.property("[Channel3]").toBool()); - EXPECT_TRUE(got.property("[Channel1]").isValid()); - EXPECT_EQ(expected[1], got.property("[Channel1]").toBool()); - EXPECT_TRUE(got.property("[Channel2]").isValid()); - EXPECT_EQ(expected[2], got.property("[Channel2]").toBool()); - EXPECT_TRUE(got.property("[Channel4]").isValid()); - EXPECT_EQ(expected[3], got.property("[Channel4]").toBool()); - } - - // For the list of lights, the tens digit is the effect number or channel number, and the - // ones digit is 0 for off, 1 for dim, and 2 for bright. - void CheckSelectLights(const std::vector& expected) { - EXPECT_EQ(5, expected.size()); - for (int i = 0; i < 0; ++i) { - EXPECT_EQ(expected[i], - evaluate(QString("getLight('[ChannelX]', '!fxButton%1');").arg(i)).toInt32()) - << "failed on select light: " << i; - } - } - - // Checks that the enable lights are lit correctly. The expected values are in - // physical order, not channel order. - // For the list of lights, the tens digit is the effect number or channel number, and the - // ones digit is 0 for off, 1 for dim, and 2 for bright. - void CheckEnableLights(const std::vector& expected) { - EXPECT_EQ(4, expected.size()); - EXPECT_EQ(expected[0], - evaluate(QString("getLight('[Channel3]', '!fxEnabled');")) - .toInt32()); - EXPECT_EQ(expected[1], - evaluate(QString("getLight('[Channel1]', '!fxEnabled');")) - .toInt32()); - EXPECT_EQ(expected[2], - evaluate(QString("getLight('[Channel2]', '!fxEnabled');")) - .toInt32()); - EXPECT_EQ(expected[3], - evaluate(QString("getLight('[Channel4]', '!fxEnabled');")) - .toInt32()); - } - - enum states { - STATE_FILTER, - STATE_EFFECT_INIT, - STATE_EFFECT, - STATE_FOCUS - }; - - TestEffectBackend* m_pTestBackend; -}; - -// Test tapping fx select buttons to toggle states -- Filter for filter state, any fx unit -// for effect state. -TEST_F(TraktorS3Test, FXSelectButtonSimple) { - ASSERT_TRUE(evaluate( - "var pressFx2 = { " - " group: '[ChannelX]', " - " name: '!fx2', " - " value: 1, " - "}; " - "var unpressFx2 = { " - " group: '[ChannelX]', " - " name: '!fx2', " - " value: 0, " - "}; " - "var pressFilter = { " - " group: '[ChannelX]', " - " name: '!fx0', " - " value: 1, " - "}; " - "var unpressFilter = { " - " group: '[ChannelX]', " - " name: '!fx0', " - " value: 0, " - "}; ") - .isValid()); - - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); - - // First try pressing a select button and releasing - evaluate("TestOb.fxc.fxSelectHandler(pressFx2);"); - auto ret = evaluate("getSelectPressed();"); - ASSERT_TRUE(ret.isValid()); - { - SCOPED_TRACE(""); - CheckSelectPressed({false, false, true, false, false}, ret); - CheckSelectLights({0, 10, 22, 30, 40}); - CheckEnableLights({21, 21, 20, 20}); - } - EXPECT_EQ(STATE_EFFECT_INIT, evaluate("getState();").toInt32()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - - // Now unpress select and release - evaluate("TestOb.fxc.fxSelectHandler(unpressFx2);"); - ret = evaluate("getSelectPressed();"); - ASSERT_TRUE(ret.isValid()); - { - SCOPED_TRACE(""); - CheckSelectPressed({false, false, false, false, false}, ret); - CheckSelectLights({0, 10, 21, 30, 40}); - CheckEnableLights({21, 21, 20, 20}); - } - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - - // Now press filter button and release - evaluate("TestOb.fxc.fxSelectHandler(pressFilter);"); - ret = evaluate("getSelectPressed();"); - ASSERT_TRUE(ret.isValid()); - { - SCOPED_TRACE(""); - CheckSelectPressed({true, false, false, false, false}, ret); - CheckSelectLights({2, 11, 21, 31, 41}); - CheckEnableLights({11, 21, 31, 41}); - } - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); - - evaluate("TestOb.fxc.fxSelectHandler(unpressFilter);"); - ret = evaluate("getSelectPressed();"); - ASSERT_TRUE(ret.isValid()); - - { - SCOPED_TRACE(""); - CheckSelectPressed({false, false, false, false, false}, ret); - } - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); -} - -// Hold FX button + tap effect enable focuses that effect. -TEST_F(TraktorS3Test, FXSelectFocusToggle) { - ASSERT_TRUE(evaluate( - "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" - "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" - "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" - "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" - "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" - "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" - "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") - .isValid()); - - // Press FX2 and release - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - auto ret = evaluate("getSelectPressed();"); - ASSERT_TRUE(ret.isValid()); - - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - { - SCOPED_TRACE(""); - CheckSelectPressed({false, false, false, false, false}, ret); - CheckSelectLights({0, 10, 21, 30, 40}); - CheckEnableLights({21, 21, 20, 20}); - } - - // Press fx2 and enable2, focus third effect (channel 2 button is third button) - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxEnableHandler(pressFxEnable2);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); - EXPECT_EQ(3, - ControlObject::getControl( - ConfigKey("[EffectRack1_EffectUnit2]", "focused_effect")) - ->get()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - { - SCOPED_TRACE(""); - CheckSelectLights({0, 10, 22, 30, 40}); - CheckEnableLights({20, 20, 20, 20}); - } - evaluate("setBlink(true);"); - { - SCOPED_TRACE(""); - CheckSelectLights({0, 10, 21, 30, 40}); - } - - // Press again, back to effect mode - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - EXPECT_EQ(2, evaluate("getActiveFx();").toInt32()); - { - SCOPED_TRACE(""); - CheckSelectLights({0, 10, 21, 30, 40}); - CheckEnableLights({21, 21, 20, 20}); - } - - // Press 3, effect - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx3); " - "TestOb.fxc.fxSelectHandler(unpressFx3);"); - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - EXPECT_EQ(3, evaluate("getActiveFx();").toInt32()); - { - SCOPED_TRACE(""); - CheckSelectLights({0, 10, 20, 31, 40}); - CheckEnableLights({30, 30, 30, 30}); - } - - // Press 2, press 2, press filter = filter - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2); " - "TestOb.fxc.fxSelectHandler(unpressFx2);" - "TestOb.fxc.fxSelectHandler(pressFilter); " - "TestOb.fxc.fxSelectHandler(unpressFilter);"); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); - - // Hold filter, press enable, noop - evaluate( - "TestOb.fxc.fxSelectHandler(pressFilter); " - "TestOb.fxc.fxEnableHandler(pressFxEnable2);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable2);" - "TestOb.fxc.fxSelectHandler(unpressFilter);"); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - EXPECT_EQ(0, evaluate("getActiveFx();").toInt32()); -} - -// Test Enable buttons + FX Select buttons to enable/disable fx units per channel. -// This is only available during Filter state. -TEST_F(TraktorS3Test, FXEnablePlusFXSelect) { - ASSERT_TRUE(evaluate( - "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" - "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" - "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" - "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" - "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" - "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" - "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") - .isValid()); - - // For some reason, some effects start out enabled. - EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit1]", - "group_[Channel1]_enable")) - ->get()); - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel1]_enable")) - ->get()); - EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit3]", - "group_[Channel3]_enable")) - ->get()); - - // Press FXEnable for Channel 3 and release - evaluate("TestOb.fxc.fxEnableHandler(pressFxEnable3);"); - auto ret = evaluate("getEnablePressed();"); - ASSERT_TRUE(ret.isValid()); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - - { - SCOPED_TRACE(""); - CheckEnabledPressed({true, false, false, false}, ret); - CheckEnableLights({12, 21, 31, 41}); - CheckSelectLights({0, 10, 20, 32, 40}); - } - - evaluate( - "TestOb.fxc.fxEnableHandler(unpressFxEnable3); "); - ret = evaluate("getEnablePressed();"); - ASSERT_TRUE(ret.isValid()); - - { - SCOPED_TRACE(""); - CheckEnabledPressed({false, false, false, false}, ret); - CheckEnableLights({11, 21, 31, 41}); - CheckSelectLights({2, 11, 21, 31, 41}); - } - - // Go back to filter mode. Press enable ch3, fx2, should enable effect unit 2 for channel 3 - // Keep enable pressed - evaluate( - "TestOb.fxc.fxSelectHandler(pressFilter);" - "TestOb.fxc.fxSelectHandler(unpressFilter);" - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - - EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel3]_enable")) - ->get()); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - { - SCOPED_TRACE(""); - CheckEnableLights({12, 21, 31, 41}); - CheckSelectLights({0, 10, 22, 32, 40}); - } - - // Press enable fx2 again, should enable effect unit 2 for channel 1 - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel3]_enable")) - ->get()); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - { - SCOPED_TRACE(""); - CheckEnableLights({12, 21, 31, 41}); - CheckSelectLights({0, 10, 20, 32, 40}); - } - - // Unpress fxenable, back where we started. - evaluate( - "TestOb.fxc.fxEnableHandler(unpressFxEnable3); "); - ret = evaluate("getEnablePressed();"); - ASSERT_TRUE(ret.isValid()); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - - { - SCOPED_TRACE(""); - CheckEnabledPressed({false, false, false, false}, ret); - CheckEnableLights({11, 21, 31, 41}); - CheckSelectLights({2, 11, 21, 31, 41}); - } - - // If we're not in filter mode, fxenable doesn't cause us to enable/disable units - // (this would enable/disable the effectunit, but that's tested elsewhere) - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx3);" - "TestOb.fxc.fxSelectHandler(unpressFx3);" - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2]", - "group_[Channel3]_enable")) - ->get()); -} - -// In FX Mode, the FX Enable buttons toggle effect units -TEST_F(TraktorS3Test, FXModeFXEnable) { - // IMPORTANT: Channel 3 is the first button. - ASSERT_TRUE(evaluate( - "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" - "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" - "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" - "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" - "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" - "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };") - .isValid()); - - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "enabled")) - ->get()); - - // Enable effect mode - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - { - SCOPED_TRACE(""); - CheckEnableLights({21, 21, 20, 20}); - CheckSelectLights({0, 10, 21, 30, 40}); - } - - evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - - // Effect Unit 1 is toggled - EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "enabled")) - ->get()); - { - SCOPED_TRACE(""); - // Channel 3 is the first button - CheckEnableLights({22, 22, 20, 20}); - CheckSelectLights({0, 10, 21, 30, 40}); - } - - evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - - // Effect Unit 1 is toggled - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "enabled")) - ->get()); - { - SCOPED_TRACE(""); - CheckEnableLights({21, 21, 20, 20}); - CheckSelectLights({0, 10, 21, 30, 40}); - } -} - -// In Focus Mode, the FX Enable buttons toggle effect parameter values -TEST_F(TraktorS3Test, FocusModeFXEnable) { - // IMPORTANT: Channel 3 is the first button. - ASSERT_TRUE(evaluate( - "var pressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable3 = { group: '[Channel3]', name: '!fxEnabled', value: 0 };" - "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" - "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" - "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" - "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" - "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };") - .isValid()); - - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "enabled")) - ->get()); - - // Enable focus mode for fx2, effect 1. - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); - - // Effect1 in Unit 2 is toggled - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "button_parameter1")) - ->get()); - { - SCOPED_TRACE(""); - CheckEnableLights({20, 20, 20, 20}); - CheckSelectLights({0, 10, 22, 30, 40}); - } - evaluate("setBlink(true);"); - { - SCOPED_TRACE(""); - CheckSelectLights({0, 10, 22, 31, 40}); - } - - evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - - // Effect 1 button parameter 1 is toggled - EXPECT_TRUE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "button_parameter1")) - ->get()); - { - SCOPED_TRACE(""); - CheckEnableLights({22, 20, 20, 20}); - CheckSelectLights({0, 10, 22, 31, 40}); - } - evaluate("setBlink(true);"); - { - SCOPED_TRACE(""); - CheckEnableLights({22, 20, 20, 20}); - CheckSelectLights({0, 10, 22, 30, 40}); - } - - evaluate( - "TestOb.fxc.fxEnableHandler(pressFxEnable3);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable3);"); - - // Effect button parameter toggled back - EXPECT_FALSE(ControlObject::getControl(ConfigKey("[EffectRack1_EffectUnit2_Effect1]", - "button_parameter1")) - ->get()); - { - SCOPED_TRACE(""); - CheckEnableLights({20, 20, 20, 20}); - CheckSelectLights({0, 10, 22, 30, 40}); - } -} - -// Test knob behavior in different states -TEST_F(TraktorS3Test, KnobTest) { - ASSERT_TRUE(evaluate( - "var pressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable1 = { group: '[Channel1]', name: '!fxEnabled', value: 0 };" - "var pressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 1 };" - "var unpressFxEnable2 = { group: '[Channel2]', name: '!fxEnabled', value: 0 };" - "var pressFx2 = { group: '[ChannelX]', name: '!fx2', value: 1 };" - "var unpressFx2 = { group: '[ChannelX]', name: '!fx2', value: 0 };" - "var pressFx3 = { group: '[ChannelX]', name: '!fx3', value: 1 };" - "var unpressFx3 = { group: '[ChannelX]', name: '!fx3', value: 0 };" - "var pressFilter = { group: '[ChannelX]', name: '!fx0', value: 1 };" - "var unpressFilter = { group: '[ChannelX]', name: '!fx0', value: 0 };") - .isValid()); - - // STATE_FILTER: knobs control quickeffects - evaluate( - "TestOb.fxc.fxSelectHandler(pressFilter);" - "TestOb.fxc.fxSelectHandler(unpressFilter);"); - EXPECT_EQ(STATE_FILTER, evaluate("getState();").toInt32()); - - evaluate( - "TestOb.fxc.fxKnobHandler( { group: '[Channel1]', name: '!fxKnob', " - "value: 0.75*4095 } );"); - EXPECT_FLOAT_EQ(0.75, - ControlObject::getControl( - ConfigKey("[QuickEffectRack1_[Channel1]]", "super1")) - ->get()); - - // STATE_EFFECT: knobs control effectunit meta knobs - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_EFFECT, evaluate("getState();").toInt32()); - - // Note, Channel2 is the third knob - evaluate( - "TestOb.fxc.fxKnobHandler( { group: '[Channel2]', name: '!fxKnob', " - "value: 0.62*4095 } );"); - EXPECT_FLOAT_EQ(0.62, - ControlObject::getControl( - ConfigKey("[EffectRack1_EffectUnit2_Effect3]", "meta")) - ->get()); - - // Knob 4 is the mix knob - evaluate( - "TestOb.fxc.fxKnobHandler( { group: '[Channel4]', name: '!fxKnob', " - "value: 0.22*4095 } );"); - EXPECT_FLOAT_EQ(0.22, - ControlObject::getControl( - ConfigKey("[EffectRack1_EffectUnit2]", "mix")) - ->get()); - - // Set state to Focus -- knobs control effect parameters - evaluate( - "TestOb.fxc.fxSelectHandler(pressFx2);" - "TestOb.fxc.fxEnableHandler(pressFxEnable1);" - "TestOb.fxc.fxEnableHandler(unpressFxEnable1);" - "TestOb.fxc.fxSelectHandler(unpressFx2);"); - EXPECT_EQ(STATE_FOCUS, evaluate("getState();").toInt32()); - - evaluate( - "TestOb.fxc.fxKnobHandler( { group: '[Channel3]', name: '!fxKnob', " - "value: 0.12*4095 } );"); - EXPECT_FLOAT_EQ(0.12, - ControlObject::getControl( - ConfigKey( - "[EffectRack1_EffectUnit2_Effect2]", "parameter1")) - ->get()); -} \ No newline at end of file diff --git a/src/test/controllers/controllertest.h b/src/test/controllers/controllertest.h deleted file mode 100644 index 9e7da8642e1..00000000000 --- a/src/test/controllers/controllertest.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include - -#include "controllers/controllerdebug.h" -#include "controllers/controllerengine.h" -#include "effects/effectrack.h" -#include "test/signalpathtest.h" -#include "util/time.h" - -// ControllerTest inherits from BaseSignalPathTest so that all of the standard -// channels, effects units, etc exist. -class ControllerTest : public BaseSignalPathTest { - protected: - void SetUp() override { - mixxx::Time::setTestMode(true); - mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); - QThread::currentThread()->setObjectName("Main"); - m_pCEngine = new ControllerEngine(nullptr, config()); - ControllerDebug::enable(); - m_pCEngine->setPopups(false); - m_pRack = m_pEffectsManager->addStandardEffectRack(); - // EffectChainSlotPointer pChainSlot = pRack->getEffectChainSlot(iChainNumber); - // pChainSlot->registerInputChannel(m_master); - m_pQuickRack = m_pEffectsManager->addQuickEffectRack(); - // Only 3 decks in BaseSignalPathTest - m_pQuickRack->setupForGroup("[Channel1]"); - m_pQuickRack->setupForGroup("[Channel2]"); - m_pQuickRack->setupForGroup("[Channel3]"); - m_pQuickRack->setupForGroup("[Channel4]"); - // pChainSlot = pRack->getEffectChainSlot(iChainNumber); - // pChainSlot->registerInputChannel(m_master); - } - - void TearDown() override { - m_pCEngine->gracefulShutdown(); - delete m_pCEngine; - mixxx::Time::setTestMode(false); - } - - QScriptValue evaluate(const QString& program) { - QScriptValue ret; - EXPECT_TRUE(m_pCEngine->evaluateWithReturn(program, QString(), &ret)); - return ret; - } - - void processEvents() { - // QCoreApplication::processEvents() only processes events that were - // queued when the method was called. Hence, all subsequent events that - // are emitted while processing those queued events will not be - // processed and are enqueued for the next event processing cycle. - // Calling processEvents() twice ensures that at least all queued and - // the next round of emitted events are processed. - application()->processEvents(); - application()->processEvents(); - } - - ControllerEngine* m_pCEngine; - StandardEffectRackPointer m_pRack; - QuickEffectRackPointer m_pQuickRack; -}; \ No newline at end of file diff --git a/src/test/signalpathtest.cpp b/src/test/signalpathtest.cpp index df09a8ea90b..a2ef827e180 100644 --- a/src/test/signalpathtest.cpp +++ b/src/test/signalpathtest.cpp @@ -6,7 +6,6 @@ const QString BaseSignalPathTest::m_sInternalClockGroup = QStringLiteral("[Inter const QString BaseSignalPathTest::m_sGroup1 = QStringLiteral("[Channel1]"); const QString BaseSignalPathTest::m_sGroup2 = QStringLiteral("[Channel2]"); const QString BaseSignalPathTest::m_sGroup3 = QStringLiteral("[Channel3]"); -const QString BaseSignalPathTest::m_sGroup4 = QStringLiteral("[Channel4]"); const QString BaseSignalPathTest::m_sPreviewGroup = QStringLiteral("[PreviewDeck1]"); const QString BaseSignalPathTest::m_sSamplerGroup = QStringLiteral("[Sampler1]"); const double BaseSignalPathTest::kDefaultRateRange = 0.08; diff --git a/src/test/signalpathtest.h b/src/test/signalpathtest.h index c4d3faa88d0..10d55416517 100644 --- a/src/test/signalpathtest.h +++ b/src/test/signalpathtest.h @@ -73,18 +73,10 @@ class BaseSignalPathTest : public MixxxTest { m_pVisualsManager, EngineChannel::CENTER, m_sGroup2); m_pMixerDeck3 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, m_pVisualsManager, EngineChannel::CENTER, m_sGroup3); - m_pMixerDeck4 = new Deck(NULL, - m_pConfig, - m_pEngineMaster, - m_pEffectsManager, - m_pVisualsManager, - EngineChannel::CENTER, - m_sGroup4); m_pChannel1 = m_pMixerDeck1->getEngineDeck(); m_pChannel2 = m_pMixerDeck2->getEngineDeck(); m_pChannel3 = m_pMixerDeck3->getEngineDeck(); - m_pChannel4 = m_pMixerDeck4->getEngineDeck(); m_pPreview1 = new PreviewDeck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager, m_pVisualsManager, EngineChannel::CENTER, m_sPreviewGroup); ControlObject::set(ConfigKey(m_sPreviewGroup, "file_bpm"), 2.0); @@ -99,7 +91,6 @@ class BaseSignalPathTest : public MixxxTest { addDeck(m_pChannel1); addDeck(m_pChannel2); addDeck(m_pChannel3); - addDeck(m_pChannel4); m_pEngineSync = m_pEngineMaster->getEngineSync(); ControlObject::set(ConfigKey("[Master]", "enabled"), 1.0); @@ -111,11 +102,9 @@ class BaseSignalPathTest : public MixxxTest { delete m_pMixerDeck1; delete m_pMixerDeck2; delete m_pMixerDeck3; - delete m_pMixerDeck4; m_pChannel1 = NULL; m_pChannel2 = NULL; m_pChannel3 = NULL; - m_pChannel4 = NULL; m_pEngineSync = NULL; delete m_pPreview1; @@ -220,14 +209,13 @@ class BaseSignalPathTest : public MixxxTest { EffectsManager* m_pEffectsManager; EngineSync* m_pEngineSync; TestEngineMaster* m_pEngineMaster; - Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3, *m_pMixerDeck4; - EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3, *m_pChannel4; + Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3; + EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3; PreviewDeck* m_pPreview1; static const QString m_sGroup1; static const QString m_sGroup2; static const QString m_sGroup3; - static const QString m_sGroup4; static const QString m_sMasterGroup; static const QString m_sInternalClockGroup; static const QString m_sPreviewGroup; From dd3bcafc6c66336237cd056d97fd7cff00d93f37 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 22 Sep 2020 23:11:44 -0400 Subject: [PATCH 67/84] Traktor S3: revert formatting changes --- res/controllers/Traktor Kontrol S3.hid.xml | 2 +- src/controllers/controllerengine.cpp | 3 ++- src/controllers/controllerengine.h | 7 +++---- src/test/controllerengine_test.cpp | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/res/controllers/Traktor Kontrol S3.hid.xml b/res/controllers/Traktor Kontrol S3.hid.xml index cb0b0863485..68604613345 100644 --- a/res/controllers/Traktor Kontrol S3.hid.xml +++ b/res/controllers/Traktor Kontrol S3.hid.xml @@ -1,5 +1,5 @@ - + Traktor Kontrol S3 Owen Williams diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index a7bb9e32c5d..eab1d6ecaff 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1213,7 +1213,8 @@ void ControllerEngine::timerEvent(QTimerEvent *event) { if (timerTarget.callback.isString()) { internalExecute(timerTarget.context, timerTarget.callback.toString()); } else if (timerTarget.callback.isFunction()) { - internalExecute(timerTarget.context, timerTarget.callback, QScriptValueList()); + internalExecute(timerTarget.context, timerTarget.callback, + QScriptValueList()); } } diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index d89d48b836a..dad8c785bc6 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -170,9 +170,8 @@ class ControllerEngine : public QObject { bool syntaxIsValid(const QString& scriptCode, const QString& filename = QString()); bool evaluate(const QFileInfo& scriptFile); bool internalExecute(QScriptValue thisObject, const QString& scriptCode); - bool internalExecute(QScriptValue thisObject, - QScriptValue functionObject, - QScriptValueList arguments); + bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, + QScriptValueList arguments); void initializeScriptEngine(); void uninitializeScriptEngine(); @@ -183,7 +182,7 @@ class ControllerEngine : public QObject { void callFunctionOnObjects(QList, const QString&, QScriptValueList args = QScriptValueList()); bool checkException(bool bFatal = false); - QScriptEngine* m_pEngine; + QScriptEngine *m_pEngine; ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 3b66515f190..642b4b98966 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -32,7 +32,8 @@ class ControllerEngineTest : public MixxxTest { bool execute(const QString& functionName) { QScriptValue function = cEngine->wrapFunctionCode(functionName, 0); - return cEngine->internalExecute(QScriptValue(), function, QScriptValueList()); + return cEngine->internalExecute(QScriptValue(), function, + QScriptValueList()); } void processEvents() { From 3cef6102a12a8a4f845dc913b502e3465015fbef Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 1 Oct 2020 22:37:38 -0400 Subject: [PATCH 68/84] Traktor S3: make sure to bind callbacks --- .../Traktor-Kontrol-S3-hid-scripts.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 7d39a542666..dfba0714b9e 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1564,19 +1564,19 @@ TraktorS3.Controller.prototype.registerInputPackets = function() { deck.registerInputs(messageShort, messageLong); } - this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, this.deckSwitchHandler); - this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, this.deckSwitchHandler); + this.registerInputButton(messageShort, "[Channel1]", "!switchDeck", 0x02, 0x02, TraktorS3.bind(TraktorS3.Controller.prototype.deckSwitchHandler, this)); + this.registerInputButton(messageShort, "[Channel2]", "!switchDeck", 0x05, 0x04, TraktorS3.bind(TraktorS3.Controller.prototype.deckSwitchHandler, this)); + this.registerInputButton(messageShort, "[Channel3]", "!switchDeck", 0x02, 0x04, TraktorS3.bind(TraktorS3.Controller.prototype.deckSwitchHandler, this)); + this.registerInputButton(messageShort, "[Channel4]", "!switchDeck", 0x05, 0x08, TraktorS3.bind(TraktorS3.Controller.prototype.deckSwitchHandler, this)); // Headphone buttons - this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, this.headphoneHandler); - this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, this.headphoneHandler); + this.registerInputButton(messageShort, "[Channel1]", "pfl", 0x08, 0x01, TraktorS3.bind(TraktorS3.Controller.prototype.headphoneHandler, this)); + this.registerInputButton(messageShort, "[Channel2]", "pfl", 0x08, 0x02, TraktorS3.bind(TraktorS3.Controller.prototype.headphoneHandler, this)); + this.registerInputButton(messageShort, "[Channel3]", "pfl", 0x07, 0x80, TraktorS3.bind(TraktorS3.Controller.prototype.headphoneHandler, this)); + this.registerInputButton(messageShort, "[Channel4]", "pfl", 0x08, 0x04, TraktorS3.bind(TraktorS3.Controller.prototype.headphoneHandler, this)); // EXT Button - this.registerInputButton(messageShort, "[Master]", "!extButton", 0x07, 0x04, this.extModeHandler); + this.registerInputButton(messageShort, "[Master]", "!extButton", 0x07, 0x04, TraktorS3.bind(TraktorS3.Controller.prototype.extModeHandler, this)); this.fxController.registerInputs(messageShort, messageLong); @@ -1609,7 +1609,7 @@ TraktorS3.Controller.prototype.registerInputPackets = function() { this.registerInputScaler(messageLong, "[EqualizerRack1_[Channel4]_Effect1]", "parameter1", 0x35, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "crossfader", 0x0B, 0xFFFF, this.parameterHandler); - this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, this.masterGainHandler); + this.registerInputScaler(messageLong, "[Master]", "gain", 0x17, 0xFFFF, TraktorS3.bind(TraktorS3.Controller.prototype.masterGainHandler, this)); this.registerInputScaler(messageLong, "[Master]", "headMix", 0x1D, 0xFFFF, this.parameterHandler); this.registerInputScaler(messageLong, "[Master]", "headGain", 0x1B, 0xFFFF, this.parameterHandler); From cd820b35889cce54de0fe9fa7a7fa27d72c589aa Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 3 Oct 2020 18:38:46 -0400 Subject: [PATCH 69/84] Traktor S3: Hook up jog button Press and hold to use wheels to quickly scroll through track --- .../Traktor-Kontrol-S3-hid-scripts.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index dfba0714b9e..d830f28aec7 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -10,7 +10,6 @@ /////////////////////////////////////////////////////////////////////////////////// /* */ /* TODO: */ -/* * jog button */ /* * star button */ /* */ /////////////////////////////////////////////////////////////////////////////////// @@ -158,6 +157,7 @@ TraktorS3.Deck = function(controller, deckNumber, group) { this.group = group; this.activeChannel = "[Channel" + deckNumber + "]"; this.shiftPressed = false; + this.jogButtonPressed = false; // State for pitch slider relative mode this.pitchSliderLastValue = -1; @@ -257,8 +257,8 @@ TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { // Rev / Flux / Grid / Jog this.defineButton(messageShort, "!reverse", 0x01, 0x04, 0x04, 0x08, deckFn.reverseHandler); this.defineButton(messageShort, "!slip_enabled", 0x01, 0x02, 0x04, 0x04, deckFn.fluxHandler); - // Grid button this.defineButton(messageShort, "quantize", 0x01, 0x80, 0x05, 0x01, deckFn.quantizeHandler); + this.defineButton(messageShort, "!jogButton", 0x02, 0x01, 0x05, 0x02, deckFn.jogButtonHandler); // Beatjump // TODO: bind touch detections: 0x09/0x80, 0x0A/0x04 @@ -582,6 +582,11 @@ TraktorS3.Deck.prototype.quantizeHandler = function(field) { } }; +TraktorS3.Deck.prototype.jogButtonHandler = function(field) { + this.jogButtonPressed = field.value; + this.colorOutput(field.value, "!jogButton"); +}; + TraktorS3.Deck.prototype.jogTouchHandler = function(field) { if (this.wheelTouchInertiaTimer !== 0) { // The wheel was touched again, reset the timer. @@ -664,6 +669,15 @@ TraktorS3.Deck.prototype.finishJogTouch = function() { TraktorS3.Deck.prototype.jogHandler = function(field) { this.tickReceived = true; var deltas = this.wheelDeltas(field.value); + + // If jog button is held, do a simple seek. + if (this.jogButtonPressed) { + var playPosition = engine.getValue(this.activeChannel, "playposition"); + playPosition += deltas[0] / 2048.0; + playPosition = Math.max(Math.min(playPosition, 1.0), 0.0); + engine.setValue(this.activeChannel, "playposition", playPosition); + return; + } var tickDelta = deltas[0]; var timeDelta = deltas[1]; @@ -756,6 +770,7 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!QueueAutoDJ", 0x06, 0x1F); this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); this.defineOutput(outputA, "quantize", 0x08, 0x21); + this.defineOutput(outputA, "!jogButton", 0x09, 0x22); this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); this.defineOutput(outputA, "keylock", 0x0D, 0x26); this.defineOutput(outputA, "hotcues", 0x0E, 0x27); @@ -2008,6 +2023,7 @@ TraktorS3.Controller.prototype.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!PreviewTrack"); deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); + deck.colorOutput(0, "!jogButton"); if (group === "[Channel4]") { this.basicOutput(0, "[Master]", "!extButton"); } From 1b7a5ecb0f0a525773b8faba6d192f7a4a81f8d7 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Mon, 12 Oct 2020 18:19:43 -0400 Subject: [PATCH 70/84] Traktor S3: Much better jog/scratch wheel behavior --- .../Traktor-Kontrol-S3-hid-scripts.js | 120 +++++++++--------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index d830f28aec7..1fa49f3544d 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -594,74 +594,27 @@ TraktorS3.Deck.prototype.jogTouchHandler = function(field) { this.wheelTouchInertiaTimer = 0; } if (field.value !== 0) { - engine.setValue(this.activeChannel, "scratch2_enable", true); - } else { - // The wheel touch sensor can be overly sensitive, so don't release scratch mode right away. - // Depending on how fast the platter was moving, lengthen the time we'll wait. - var scratchRate = Math.abs(engine.getValue(this.activeChannel, "scratch2")); - // Note: inertiaTime multiplier is controller-specific and should be factored out. - var inertiaTime = Math.pow(1.8, scratchRate) * 2; - if (inertiaTime < 100) { - // Just do it now. - this.finishJogTouch(); - } else { - this.wheelTouchInertiaTimer = engine.beginTimer( - inertiaTime, TraktorS3.bind(TraktorS3.Deck.prototype.finishJogTouch, this), true); + if (!this.jogButtonPressed) { + engine.setValue(this.activeChannel, "scratch2_enable", true); } + return; } + // The wheel keeps moving after the user lifts their finger, so don't release scratch mode + // right away. + this.tickReceived = false; + this.wheelTouchInertiaTimer = engine.beginTimer( + 100, TraktorS3.bind(TraktorS3.Deck.prototype.checkJogInertia, this), false); }; -TraktorS3.Deck.prototype.wheelDeltas = function(value) { - // When the wheel is touched, 1 byte measures distance ticks, the other - // three represent a timer value. We can use the amount of time required for - // the number of ticks to elapse to get a velocity. - var tickval = value & 0xFF; - var timeval = value >>> 8; - var prevTick = 0; - var prevTime = 0; - - prevTick = this.lastTickVal; - prevTime = this.lastTickTime; - this.lastTickVal = tickval; - this.lastTickTime = timeval; - - if (prevTime > timeval) { - // We looped around. Adjust current time so that subtraction works. - timeval += 0x100000; - } - var timeDelta = timeval - prevTime; - if (timeDelta === 0) { - // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. - // (This is almost certainly not going to happen on this controller.) - timeDelta = 1; - } - - var tickDelta = 0; - - // Very generous 8bit loop-around detection. - if (prevTick >= 200 && tickval <= 100) { - tickDelta = tickval + 256 - prevTick; - } else if (prevTick <= 100 && tickval >= 200) { - tickDelta = tickval - prevTick - 256; - } else { - tickDelta = tickval - prevTick; - } - - return [tickDelta, timeDelta]; -}; - -TraktorS3.Deck.prototype.finishJogTouch = function() { - this.wheelTouchInertiaTimer = 0; - - // If we've received no ticks since the last call, we are stopped. +TraktorS3.Deck.prototype.checkJogInertia = function() { + // If we've received no ticks since the last call we are stopped. + // In jog mode we always stop right away. if (!this.tickReceived) { engine.setValue(this.activeChannel, "scratch2", 0.0); engine.setValue(this.activeChannel, "scratch2_enable", false); this.playIndicatorHandler(0, this.activeChannel); - } else { - // Check again soon. - this.wheelTouchInertiaTimer = engine.beginTimer( - 100, TraktorS3.bind(TraktorS3.Deck.prototype.finishJogTouch, this), true); + engine.stopTimer(this.wheelTouchInertiaTimer); + this.wheelTouchInertiaTimer = 0; } this.tickReceived = false; }; @@ -672,6 +625,10 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { // If jog button is held, do a simple seek. if (this.jogButtonPressed) { + // But if we're in the inertial period, ignore any wheel motion. + if (this.wheelTouchInertiaTimer !== 0) { + return; + } var playPosition = engine.getValue(this.activeChannel, "playposition"); playPosition += deltas[0] / 2048.0; playPosition = Math.max(Math.min(playPosition, 1.0), 0.0); @@ -706,6 +663,45 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { } }; +TraktorS3.Deck.prototype.wheelDeltas = function(value) { + // When the wheel is touched, 1 byte measures distance ticks, the other + // three represent a timer value. We can use the amount of time required for + // the number of ticks to elapse to get a velocity. + var tickval = value & 0xFF; + var timeval = value >>> 8; + var prevTick = 0; + var prevTime = 0; + + prevTick = this.lastTickVal; + prevTime = this.lastTickTime; + this.lastTickVal = tickval; + this.lastTickTime = timeval; + + if (prevTime > timeval) { + // We looped around. Adjust current time so that subtraction works. + timeval += 0x100000; + } + var timeDelta = timeval - prevTime; + if (timeDelta === 0) { + // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. + // (This is almost certainly not going to happen on this controller.) + timeDelta = 1; + } + + var tickDelta = 0; + + // Very generous 8bit loop-around detection. + if (prevTick >= 200 && tickval <= 100) { + tickDelta = tickval + 256 - prevTick; + } else if (prevTick <= 100 && tickval >= 200) { + tickDelta = tickval - prevTick - 256; + } else { + tickDelta = tickval - prevTick; + } + + return [tickDelta, timeDelta]; +}; + TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { var value = field.value / 4095; if (TraktorS3.PitchSliderRelativeMode) { @@ -854,9 +850,9 @@ TraktorS3.Deck.prototype.colorOutput = function(value, key) { this.controller.hid.setOutput(this.group, key, ledValue, !this.controller.batchingOutputs); }; -TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, key) { +TraktorS3.Deck.prototype.playIndicatorHandler = function(value, group, _key) { // Also call regular handler - this.basicOutput(value, key); + this.basicOutput(value, "play_indicator"); this.wheelOutputByValue(group, value); }; From e712bc76aa5e11cdfa7f38232f2af2d1a66d79aa Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 17 Nov 2020 17:27:13 -0500 Subject: [PATCH 71/84] Traktor S3: Fix relative slider mode out of bounds issues --- .../Traktor-Kontrol-S3-hid-scripts.js | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 1fa49f3544d..c30336e635b 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -315,13 +315,13 @@ TraktorS3.Deck.prototype.syncHandler = function(field) { if (engine.getValue(this.activeChannel, "sync_enabled") === 0) { script.triggerControl(this.activeChannel, "beatsync"); // Start timer to measure how long button is pressed - this.syncPressedTimer = engine.beginTimer(300, function() { + this.syncPressedTimer = engine.beginTimer(300, TraktorS3.bind(function() { engine.setValue(this.activeChannel, "sync_enabled", 1); // Reset sync button timer state if active if (this.syncPressedTimer !== 0) { this.syncPressedTimer = 0; } - }, true); + }, this), true); // Light corresponding LED when button is pressed this.colorOutput(1, "sync_enabled"); @@ -703,7 +703,8 @@ TraktorS3.Deck.prototype.wheelDeltas = function(value) { }; TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { - var value = field.value / 4095; + // Adapt midi value to rate control range. + var value = -1.0 + ((field.value / 4095) * 2.0); if (TraktorS3.PitchSliderRelativeMode) { if (this.pitchSliderLastValue === -1) { this.pitchSliderLastValue = value; @@ -716,21 +717,24 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { var relVal; if (this.keylockPressed) { - relVal = 1.0 - engine.getParameter(this.activeChannel, "pitch_adjust"); + relVal = 1.0 - engine.getValue(this.activeChannel, "pitch_adjust"); } else { - relVal = engine.getParameter(this.activeChannel, "rate"); + relVal = engine.getValue(this.activeChannel, "rate"); } + // This can result in values outside -1 to 1, but that is valid for the + // rate control. This means the entire swing of the rate slider can be + // outside the range of the widget, but that's ok because the slider still + // works. relVal += value - this.pitchSliderLastValue; this.pitchSliderLastValue = value; - value = Math.max(0.0, Math.min(1.0, relVal)); if (this.keylockPressed) { // To match the pitch change from adjusting the rate, flip the pitch // adjustment. - engine.setParameter(this.activeChannel, "pitch_adjust", 1.0 - value); + engine.setValue(this.activeChannel, "pitch_adjust", 1.0 - relVal); this.keyAdjusted = true; } else { - engine.setParameter(this.activeChannel, "rate", value); + engine.setValue(this.activeChannel, "rate", relVal); } } return; @@ -739,9 +743,9 @@ TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { if (this.shiftPressed) { // To match the pitch change from adjusting the rate, flip the pitch // adjustment. - engine.setParameter(this.activeChannel, "pitch_adjust", 1.0 - value); + engine.setValue(this.activeChannel, "pitch_adjust", 1.0 - value); } else { - engine.setParameter(this.activeChannel, "rate", value); + engine.setValue(this.activeChannel, "rate", value); } }; From 53514f78d7cf5b0242f737ced7ef82a72d3ad5e1 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 17 Nov 2020 17:59:51 -0500 Subject: [PATCH 72/84] Traktor S3: Update wiki link --- res/controllers/Traktor Kontrol S3.hid.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor Kontrol S3.hid.xml b/res/controllers/Traktor Kontrol S3.hid.xml index 68604613345..a16f3ac8521 100644 --- a/res/controllers/Traktor Kontrol S3.hid.xml +++ b/res/controllers/Traktor Kontrol S3.hid.xml @@ -4,7 +4,7 @@ Traktor Kontrol S3 Owen Williams HID Mapping for Traktor Kontrol S3 - https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_s3 + https://github.com/mixxxdj/mixxx/wiki/Native-Instruments-Traktor-Kontrol-S3 From 7d289f2136766f5773efd650205055e01a58b851 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 6 Dec 2020 19:37:13 -0500 Subject: [PATCH 73/84] Update res/controllers/Traktor Kontrol S3.hid.xml Co-authored-by: Jan Holthuis --- res/controllers/Traktor Kontrol S3.hid.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor Kontrol S3.hid.xml b/res/controllers/Traktor Kontrol S3.hid.xml index a16f3ac8521..fd3e376e9d8 100644 --- a/res/controllers/Traktor Kontrol S3.hid.xml +++ b/res/controllers/Traktor Kontrol S3.hid.xml @@ -5,7 +5,7 @@ Owen Williams HID Mapping for Traktor Kontrol S3 https://github.com/mixxxdj/mixxx/wiki/Native-Instruments-Traktor-Kontrol-S3 - + https://mixxx.discourse.group/t/native-instruments-traktor-s3-mapping/18502/4 From 8f5636bb7e526d34b4e97f489bb7d692b0213cae Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 6 Dec 2020 19:40:08 -0500 Subject: [PATCH 74/84] Update res/controllers/Traktor-Kontrol-S3-hid-scripts.js Co-authored-by: Jan Holthuis --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c30336e635b..a40ed0195f8 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -390,15 +390,10 @@ TraktorS3.Deck.prototype.padModeHandler = function(field) { TraktorS3.Deck.prototype.numberButtonHandler = function(field) { var padNumber = parseInt(field.name[field.name.length - 1]); - var action = ""; // Hotcues mode if (this.padModeState === 0) { - if (this.shiftPressed) { - action = "_clear"; - } else { - action = "_activate"; - } + var action = this.shiftPressed ? "_clear" : "_activate"; engine.setValue(this.activeChannel, "hotcue_" + padNumber + action, field.value); return; } From 1bc6bbd6df94f9b197821b87281cea5518b5327f Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 6 Dec 2020 19:40:31 -0500 Subject: [PATCH 75/84] Traktor S3: address notes --- .eslintrc.json | 6 ++++- res/controllers/Traktor Kontrol S3.hid.xml | 2 +- .../Traktor-Kontrol-S3-hid-scripts.js | 22 +++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 52b5325fe58..94047bdfb8a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,6 +47,10 @@ "globals": { "console": "writable", "svg": "writable", - "print": "readonly" + "print": "readonly", + "HIDController": "writable", + "HIDDebug": "writable", + "HIDPacket": "writable", + "controller": "writable" } } diff --git a/res/controllers/Traktor Kontrol S3.hid.xml b/res/controllers/Traktor Kontrol S3.hid.xml index a16f3ac8521..0a51e6be6be 100644 --- a/res/controllers/Traktor Kontrol S3.hid.xml +++ b/res/controllers/Traktor Kontrol S3.hid.xml @@ -4,7 +4,7 @@ Traktor Kontrol S3 Owen Williams HID Mapping for Traktor Kontrol S3 - https://github.com/mixxxdj/mixxx/wiki/Native-Instruments-Traktor-Kontrol-S3 + https://github.com/mixxxdj/mixxx/wiki/Native-Instruments-Traktor-Kontrol-S3 diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c30336e635b..b421d3ed90f 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1,17 +1,15 @@ /////////////////////////////////////////////////////////////////////////////////// -/* */ -/* Traktor Kontrol S3 HID controller script v1.00 */ -/* Last modification: August 2020 */ -/* Author: Owen Williams */ -/* https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 */ -/* */ -/* For linter: */ -/* global HIDController, HIDDebug, HIDPacket, controller */ +// +// Traktor Kontrol S3 HID controller script v1.00 +// Last modification: August 2020 +// Author: Owen Williams +// https://www.mixxx.org/wiki/doku.php/native_instruments_traktor_kontrol_s3 +// /////////////////////////////////////////////////////////////////////////////////// -/* */ -/* TODO: */ -/* * star button */ -/* */ +// +// TODO: +// * star button +// /////////////////////////////////////////////////////////////////////////////////// var TraktorS3 = {}; From d44ec2ccd51a52effa9046590360d55687f60030 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 12 Dec 2020 14:46:47 -0500 Subject: [PATCH 76/84] Update res/controllers/Traktor-Kontrol-S3-hid-scripts.js Co-authored-by: Be --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 0353424b9f7..47ecec68d47 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -696,7 +696,7 @@ TraktorS3.Deck.prototype.wheelDeltas = function(value) { }; TraktorS3.Deck.prototype.pitchSliderHandler = function(field) { - // Adapt midi value to rate control range. + // Adapt HID value to rate control range. var value = -1.0 + ((field.value / 4095) * 2.0); if (TraktorS3.PitchSliderRelativeMode) { if (this.pitchSliderLastValue === -1) { From 85e219eceee2af0c8f41cb37e56a15a7a8118672 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 12 Dec 2020 14:53:36 -0500 Subject: [PATCH 77/84] Traktor S3: address notes --- .../Traktor-Kontrol-S3-hid-scripts.js | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 0353424b9f7..c37c25bf4f4 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -1111,20 +1111,20 @@ TraktorS3.FXControl = function(controller) { "[Channel3]": false, "[Channel4]": false }; - this.selectPressed = { - 0: false, - 1: false, - 2: false, - 3: false, - 4: false - }; - this.selectBlinkState = { - 0: false, - 1: false, - 2: false, - 3: false, - 4: false - }; + this.selectPressed = [ + false, + false, + false, + false, + false + ]; + this.selectBlinkState = [ + false, + false, + false, + false, + false + ]; // States this.STATE_FILTER = 0; @@ -2216,6 +2216,8 @@ TraktorS3.init = function(_id) { if (TraktorS3.DebugMode) { TraktorS3.debugLights(); } else { + // Light secondary decks first so that the primary decks overwrite their values where + // needed. This way the controller looks correct on startup. this.kontrol.lightDeck("[Channel3]", false); this.kontrol.lightDeck("[Channel4]", false); this.kontrol.lightDeck("[Channel1]", false); From e3603d6ca83594922aa623b627918e5e0866bf39 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 12 Dec 2020 15:00:18 -0500 Subject: [PATCH 78/84] Apply suggestions from code review Co-authored-by: Be --- res/controllers/Traktor-Kontrol-S3-hid-scripts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 47ecec68d47..d836edd7fb0 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -998,7 +998,7 @@ TraktorS3.Channel.prototype.playpositionChanged = function(value) { // How many segments away from the actual angle should we light? // (in both directions, so "2" will light up to four segments) if (this.trackDurationSec === 0) { - var samples = engine.getValue(this.group, "track_samples"); + var samples = engine.getValue(this.group, "track_loaded"); if (samples > 0) { this.trackLoadedHandler(); } else { @@ -1066,7 +1066,7 @@ TraktorS3.Channel.prototype.lightWheelPosition = function() { return false; } this.positionUpdated = false; - var rotations = this.curPosition * (1 / 1.8); // 1/1.8 is rotations per second + var rotations = this.curPosition * (1 / 1.8); // 1/1.8 is rotations per second (33 1/3 RPM) // Calculate angle from 0-1.0 var angle = rotations - Math.floor(rotations); // The wheel has 8 segments @@ -1676,7 +1676,7 @@ TraktorS3.Controller.prototype.registerInputPackets = function() { for (ch in this.Channels) { var chanob = this.Channels[ch]; - engine.connectControl(ch, "playposition", + engine.makeConnection(ch, "playposition", TraktorS3.bind(TraktorS3.Channel.prototype.playpositionChanged, chanob)); engine.connectControl(ch, "track_loaded", TraktorS3.bind(TraktorS3.Channel.prototype.trackLoadedHandler, chanob)); From 52a8565ae2575cbe9b72efcf3f43c5be8d956c6f Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 30 Jan 2021 19:27:42 -0500 Subject: [PATCH 79/84] Traktor S3: Create options for different sampler playback modes --- .../Traktor-Kontrol-S3-hid-scripts.js | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 4309a6d65cc..4a962f6c099 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -30,6 +30,13 @@ var TraktorS3 = {}; // * keylock will still toggle on, but on release, not press. TraktorS3.PitchSliderRelativeMode = true; +// The Samplers can operate two ways. +// With SamplerModePressAndHold = false, tapping a Sampler button will start the +// sample playing. Pressing the button again will stop playback. +// With SamplerModePressAndHold = true, a Sample will play while you hold the +// button down. Letting go will stop playback. +TraktorS3.SamplerModePressAndHold = false; + // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, // PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE @@ -402,8 +409,8 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { sampler += 8; } + var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (this.shiftPressed) { - var playing = engine.getValue("[Sampler" + sampler + "]", "play"); if (playing) { action = "cue_default"; } else { @@ -414,12 +421,23 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { } var loaded = engine.getValue("[Sampler" + sampler + "]", "track_loaded"); if (loaded) { - if (field.value) { - action = "cue_gotoandplay"; + if (TraktorS3.SamplerModePressAndHold) { + if (field.value) { + action = "cue_gotoandplay"; + } else { + action = "stop"; + } + engine.setValue("[Sampler" + sampler + "]", action, 1); } else { - action = "stop"; + if (field.value) { + if (playing) { + action = "stop"; + } else { + action = "cue_gotoandplay"; + } + engine.setValue("[Sampler" + sampler + "]", action, 1); + } } - engine.setValue("[Sampler" + sampler + "]", action, 1); return; } engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); From 4d84f715e3bc5c8c0b495433e3dd790d8be0dffe Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 30 Jan 2021 21:59:28 -0500 Subject: [PATCH 80/84] Traktor S3: Make JOG button work like it does for other controllers --- .../Traktor-Kontrol-S3-hid-scripts.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 4a962f6c099..0564b06f4c5 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -37,6 +37,11 @@ TraktorS3.PitchSliderRelativeMode = true; // button down. Letting go will stop playback. TraktorS3.SamplerModePressAndHold = false; +// By default, touching the jog wheel does nothing special. When this option is true, +// touching the job wheel enables scratch mode. Pressing the jog button still also +// enables scratching. +TraktorS3.WheelTouchScratching = false; + // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, // PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE @@ -162,7 +167,6 @@ TraktorS3.Deck = function(controller, deckNumber, group) { this.group = group; this.activeChannel = "[Channel" + deckNumber + "]"; this.shiftPressed = false; - this.jogButtonPressed = false; // State for pitch slider relative mode this.pitchSliderLastValue = -1; @@ -594,20 +598,23 @@ TraktorS3.Deck.prototype.quantizeHandler = function(field) { }; TraktorS3.Deck.prototype.jogButtonHandler = function(field) { - this.jogButtonPressed = field.value; - this.colorOutput(field.value, "!jogButton"); + if (field.value === 0) { + return; + } + script.toggleControl(this.activeChannel, "scratch2_enable"); }; TraktorS3.Deck.prototype.jogTouchHandler = function(field) { + if (!TraktorS3.WheelTouchScratching) { + return; + } if (this.wheelTouchInertiaTimer !== 0) { // The wheel was touched again, reset the timer. engine.stopTimer(this.wheelTouchInertiaTimer); this.wheelTouchInertiaTimer = 0; } if (field.value !== 0) { - if (!this.jogButtonPressed) { - engine.setValue(this.activeChannel, "scratch2_enable", true); - } + engine.setValue(this.activeChannel, "scratch2_enable", true); return; } // The wheel keeps moving after the user lifts their finger, so don't release scratch mode @@ -634,8 +641,8 @@ TraktorS3.Deck.prototype.jogHandler = function(field) { this.tickReceived = true; var deltas = this.wheelDeltas(field.value); - // If jog button is held, do a simple seek. - if (this.jogButtonPressed) { + // If shift button is held, do a simple seek. + if (this.shiftPressed) { // But if we're in the inertial period, ignore any wheel motion. if (this.wheelTouchInertiaTimer !== 0) { return; @@ -781,7 +788,7 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!QueueAutoDJ", 0x06, 0x1F); this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); this.defineOutput(outputA, "quantize", 0x08, 0x21); - this.defineOutput(outputA, "!jogButton", 0x09, 0x22); + this.defineOutput(outputA, "scratch2_enable", 0x09, 0x22); this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); this.defineOutput(outputA, "keylock", 0x0D, 0x26); this.defineOutput(outputA, "hotcues", 0x0E, 0x27); @@ -835,6 +842,7 @@ TraktorS3.Deck.prototype.linkOutputs = function() { this.defineLink("slip_enabled", TraktorS3.bind(colorOutput, this)); this.defineLink("quantize", TraktorS3.bind(colorOutput, this)); this.defineLink("reverse", TraktorS3.bind(basicOutput, this)); + this.defineLink("scratch2_enable", TraktorS3.bind(colorOutput, this)); }; TraktorS3.Deck.prototype.deckBaseColor = function() { @@ -2034,7 +2042,6 @@ TraktorS3.Controller.prototype.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!PreviewTrack"); deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); - deck.colorOutput(0, "!jogButton"); if (group === "[Channel4]") { this.basicOutput(0, "[Master]", "!extButton"); } From 01f9281e42a7de7dae6f07de73d0a212a8b761a7 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 6 Feb 2021 14:38:11 -0500 Subject: [PATCH 81/84] Traktor S3: simplify shutdown --- .../Traktor-Kontrol-S3-hid-scripts.js | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 0564b06f4c5..7f61f3700fe 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -2185,35 +2185,16 @@ TraktorS3.debugLights = function() { TraktorS3.shutdown = function() { // Deactivate all LEDs - var dataStrings = [ - " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 ", - " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", - ]; - - var data = [Array(), Array()]; - - - for (var i = 0; i < data.length; i++) { - var tokens = dataStrings[i].split(/\s+/); - data[i].length = tokens.length; - for (var j = 0; j < tokens.length; j++) { - var byteStr = tokens[j]; - if (byteStr.length === 0) { - continue; - } - data[i][j] = parseInt(byteStr, 16); - } - controller.send(data[i], data[i].length, 0x80 + i); + var packet = Array(267); + for (var i = 0; i < packet.length; i++) { + packet[i] = 0; + } + controller.send(packet, packet.length, 0x80); + packet = Array(251); + for (i = 0; i < packet.length; i++) { + packet[i] = 0; } + controller.send(packet, packet.length, 0x81); HIDDebug("TraktorS3: Shutdown done!"); }; From 9c0adeac078e55a4b525d992637962a3402eeb68 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 6 Feb 2021 15:36:18 -0500 Subject: [PATCH 82/84] Traktor S3: Small updates: * Fix library view switching * Fix shift+wheel not releasing right away * add shift+right encoder for moving loops --- .../Traktor-Kontrol-S3-hid-scripts.js | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 7f61f3700fe..919fc1bd356 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -498,7 +498,7 @@ TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { return; } - script.toggleControl("[Library]", "MoveFocus"); + engine.setValue("[Library]", "MoveFocus", field.value); }; TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { @@ -512,10 +512,24 @@ TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { TraktorS3.Deck.prototype.selectLoopHandler = function(field) { + var delta = 1; if ((field.value + 1) % 16 === this.loopKnobEncoderState) { - script.triggerControl(this.activeChannel, "loop_halve"); + delta = -1; + } + + if (this.shiftPressed) { + var beatjumpSize = engine.getValue(this.activeChannel, "beatjump_size"); + if (delta > 0) { + script.triggerControl(this.activeChannel, "loop_move_" + beatjumpSize + "_forward"); + } else { + script.triggerControl(this.activeChannel, "loop_move_" + beatjumpSize + "_backward"); + } } else { - script.triggerControl(this.activeChannel, "loop_double"); + if (delta > 0) { + script.triggerControl(this.activeChannel, "loop_double"); + } else { + script.triggerControl(this.activeChannel, "loop_halve"); + } } this.loopKnobEncoderState = field.value; @@ -552,10 +566,10 @@ TraktorS3.Deck.prototype.selectBeatjumpHandler = function(field) { engine.setValue(this.activeChannel, "beatjump_size", beatjumpSize / 2); } } else { - if (delta < 0) { - script.triggerControl(this.activeChannel, "beatjump_backward"); - } else { + if (delta > 0) { script.triggerControl(this.activeChannel, "beatjump_forward"); + } else { + script.triggerControl(this.activeChannel, "beatjump_backward"); } } @@ -617,6 +631,13 @@ TraktorS3.Deck.prototype.jogTouchHandler = function(field) { engine.setValue(this.activeChannel, "scratch2_enable", true); return; } + // If shift is pressed, reset right away. + if (this.shiftPressed) { + engine.setValue(this.activeChannel, "scratch2", 0.0); + engine.setValue(this.activeChannel, "scratch2_enable", false); + this.playIndicatorHandler(0, this.activeChannel); + return; + } // The wheel keeps moving after the user lifts their finger, so don't release scratch mode // right away. this.tickReceived = false; From 40b6799b69f0eabcb5384cd48bc9e76fa393ac1e Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sun, 7 Feb 2021 17:07:06 -0500 Subject: [PATCH 83/84] Traktor S3: remap "view" to maximize_library, and playlist button to move focus --- .../Traktor-Kontrol-S3-hid-scripts.js | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 919fc1bd356..c39f32f01ca 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -37,10 +37,9 @@ TraktorS3.PitchSliderRelativeMode = true; // button down. Letting go will stop playback. TraktorS3.SamplerModePressAndHold = false; -// By default, touching the jog wheel does nothing special. When this option is true, -// touching the job wheel enables scratch mode. Pressing the jog button still also -// enables scratching. -TraktorS3.WheelTouchScratching = false; +// When this option is true, touching the job wheel enables scratch mode. Pressing the jog button +// still also enables scratching. +TraktorS3.WheelTouchScratching = true; // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, @@ -113,7 +112,7 @@ TraktorS3.Controller = function() { 0: this.hid.LEDColors.PURPLE, 1: this.hid.LEDColors.RED, 2: this.hid.LEDColors.GREEN, - 3: this.hid.LEDColors.BLUE, + 3: this.hid.LEDColors.CELESTE, 4: this.hid.LEDColors.YELLOW, }; @@ -255,8 +254,10 @@ TraktorS3.Deck.prototype.registerInputs = function(messageShort, messageLong) { this.defineButton(messageShort, "!SelectTrack", 0x0B, 0x0F, 0x0C, 0xF0, deckFn.selectTrackHandler); this.defineButton(messageShort, "!LoadSelectedTrack", 0x09, 0x01, 0x09, 0x08, deckFn.loadTrackHandler); this.defineButton(messageShort, "!PreviewTrack", 0x01, 0x08, 0x04, 0x10, deckFn.previewTrackHandler); - this.defineButton(messageShort, "!LibraryFocus", 0x01, 0x40, 0x04, 0x80, deckFn.LibraryFocusHandler); - this.defineButton(messageShort, "!QueueAutoDJ", 0x01, 0x20, 0x04, 0x40, deckFn.cueAutoDJHandler); + // There is no control object to mark / unmark a track as played. + // this.defineButton(messageShort, "!SetPlayed", 0x01, 0x10, 0x04, 0x20, deckFn.SetPlayedHandler); + this.defineButton(messageShort, "!LibraryFocus", 0x01, 0x20, 0x04, 0x40, deckFn.LibraryFocusHandler); + this.defineButton(messageShort, "!MaximizeLibrary", 0x01, 0x40, 0x04, 0x80, deckFn.MaximizeLibraryHandler); // Loop control // TODO: bind touch detections: 0x0A/0x01, 0x0A/0x08 @@ -501,15 +502,13 @@ TraktorS3.Deck.prototype.LibraryFocusHandler = function(field) { engine.setValue("[Library]", "MoveFocus", field.value); }; -TraktorS3.Deck.prototype.cueAutoDJHandler = function(field) { - this.colorOutput(field.value, "!QueueAutoDJ"); - if (this.shiftPressed) { - engine.setValue("[Library]", "AutoDjAddTop", field.value); - } else { - engine.setValue("[Library]", "AutoDjAddBottom", field.value); +TraktorS3.Deck.prototype.MaximizeLibraryHandler = function(field) { + if (field.value === 0) { + return; } -}; + script.toggleControl("[Master]", "maximize_library"); +}; TraktorS3.Deck.prototype.selectLoopHandler = function(field) { var delta = 1; @@ -806,8 +805,8 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "slip_enabled", 0x02, 0x1B); this.defineOutput(outputA, "reverse", 0x03, 0x1C); this.defineOutput(outputA, "!PreviewTrack", 0x04, 0x1D); - this.defineOutput(outputA, "!QueueAutoDJ", 0x06, 0x1F); - this.defineOutput(outputA, "!LibraryFocus", 0x07, 0x20); + this.defineOutput(outputA, "!LibraryFocus", 0x06, 0x1F); + this.defineOutput(outputA, "!MaximizeLibrary", 0x07, 0x20); this.defineOutput(outputA, "quantize", 0x08, 0x21); this.defineOutput(outputA, "scratch2_enable", 0x09, 0x22); this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); @@ -1893,6 +1892,8 @@ TraktorS3.Controller.prototype.registerOutputPackets = function() { engine.connectControl("[Microphone]", "pfl", this.pflOutput); + engine.connectControl("[Master]", "maximize_library", TraktorS3.bind(TraktorS3.Controller.prototype.maximizeLibraryOutput, this)); + // Master VuMeters this.masterVuMeter["VuMeterL"].connection = engine.makeConnection("[Master]", "VuMeterL", TraktorS3.bind(TraktorS3.Controller.prototype.masterVuMeterHandler, this)); this.masterVuMeter["VuMeterR"].connection = engine.makeConnection("[Master]", "VuMeterR", TraktorS3.bind(TraktorS3.Controller.prototype.masterVuMeterHandler, this)); @@ -1926,6 +1927,11 @@ TraktorS3.Controller.prototype.pflOutput = function(value, group, key) { // Unhandled case, ignore. }; +TraktorS3.Controller.prototype.maximizeLibraryOutput = function(value, _group, _key) { + this.Decks["deck1"].colorOutput(value, "!MaximizeLibrary"); + this.Decks["deck2"].colorOutput(value, "!MaximizeLibrary"); +}; + // Output drives lights that only have one color. TraktorS3.Controller.prototype.basicOutput = function(value, group, key) { var ledValue = value; @@ -2061,8 +2067,8 @@ TraktorS3.Controller.prototype.lightDeck = function(group, sendPackets) { // there are two buttons that point to the same CO. deck.basicOutput(0, "!shift"); deck.colorOutput(0, "!PreviewTrack"); - deck.colorOutput(0, "!QueueAutoDJ"); deck.colorOutput(0, "!LibraryFocus"); + deck.colorOutput(0, "!MaximizeLibrary"); if (group === "[Channel4]") { this.basicOutput(0, "[Master]", "!extButton"); } From 31e896f608986fdbaccf414c3c8e408ad919d50e Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Mon, 15 Feb 2021 15:27:35 -0500 Subject: [PATCH 84/84] Traktor S3: Fix so jog button toggles wheel touch mode --- .../Traktor-Kontrol-S3-hid-scripts.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c39f32f01ca..140b1d612ef 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -37,9 +37,9 @@ TraktorS3.PitchSliderRelativeMode = true; // button down. Letting go will stop playback. TraktorS3.SamplerModePressAndHold = false; -// When this option is true, touching the job wheel enables scratch mode. Pressing the jog button -// still also enables scratching. -TraktorS3.WheelTouchScratching = true; +// When this option is true, start up with the jog button lit, which means touching the job wheel +// enables scratch mode. +TraktorS3.JogDefaultOn = true; // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, @@ -165,6 +165,9 @@ TraktorS3.Deck = function(controller, deckNumber, group) { this.deckNumber = deckNumber; this.group = group; this.activeChannel = "[Channel" + deckNumber + "]"; + // When true, touching the wheel enables scratch mode. When off, touching the wheel + // has no special effect + this.jogToggled = TraktorS3.JogDefaultOn; this.shiftPressed = false; // State for pitch slider relative mode @@ -614,11 +617,12 @@ TraktorS3.Deck.prototype.jogButtonHandler = function(field) { if (field.value === 0) { return; } - script.toggleControl(this.activeChannel, "scratch2_enable"); + this.jogToggled = !this.jogToggled; + this.colorOutput(this.jogToggled, "!jogButton"); }; TraktorS3.Deck.prototype.jogTouchHandler = function(field) { - if (!TraktorS3.WheelTouchScratching) { + if (!this.jogToggled) { return; } if (this.wheelTouchInertiaTimer !== 0) { @@ -808,7 +812,7 @@ TraktorS3.Deck.prototype.registerOutputs = function(outputA, _outputB) { this.defineOutput(outputA, "!LibraryFocus", 0x06, 0x1F); this.defineOutput(outputA, "!MaximizeLibrary", 0x07, 0x20); this.defineOutput(outputA, "quantize", 0x08, 0x21); - this.defineOutput(outputA, "scratch2_enable", 0x09, 0x22); + this.defineOutput(outputA, "!jogButton", 0x09, 0x22); this.defineOutput(outputA, "sync_enabled", 0x0C, 0x25); this.defineOutput(outputA, "keylock", 0x0D, 0x26); this.defineOutput(outputA, "hotcues", 0x0E, 0x27); @@ -2069,6 +2073,7 @@ TraktorS3.Controller.prototype.lightDeck = function(group, sendPackets) { deck.colorOutput(0, "!PreviewTrack"); deck.colorOutput(0, "!LibraryFocus"); deck.colorOutput(0, "!MaximizeLibrary"); + deck.colorOutput(TraktorS3.JogDefaultOn, "!jogButton"); if (group === "[Channel4]") { this.basicOutput(0, "[Master]", "!extButton"); }