diff --git a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js index 4b07bd025f9e..75226a8e556f 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js @@ -102,17 +102,17 @@ TraktorS4MK2.registerInputPackets = function() { MessageShort.addControl("deck1", "loop_out", 0x0E, "B", 0x08); MessageShort.addControl("deck1", "loop_in", 0x0E, "B", 0x04); MessageShort.addControl("deck1", "slip_enabled", 0x0E, "B", 0x02); - MessageShort.addControl("deck1", "!reset", 0x0E, "B", 0x01); + MessageShort.addControl("deck1", "reset_key", 0x0E, "B", 0x01); MessageShort.addControl("deck1", "beatloop_activate", 0x13, "B", 0x02); MessageShort.addControl("deck1", "!loop_activate", 0x13, "B", 0x01); MessageShort.addControl("deck1", "!jog_touch", 0x11, "B", 0x01); MessageShort.addControl("deck1", "!jog_wheel", 0x01, "I"); MessageShort.addControl("deck1", "!deckswitch", 0x0F, "B", 0x20); MessageShort.addControl("deck1", "!load_track", 0x0F, "B", 0x10); - MessageShort.addControl("deck1", "!FX1", 0x12, "B", 0x80); - MessageShort.addControl("deck1", "!FX2", 0x12, "B", 0x40); - MessageShort.addControl("deck1", "!FX3", 0x12, "B", 0x20); - MessageShort.addControl("deck1", "!FX4", 0x12, "B", 0x10); + MessageShort.addControl("deck1", "!FXon", 0x12, "B", 0x80); + MessageShort.addControl("[EffectRack1_EffectUnit1_Effect1]", "!FXButton", 0x12, "B", 0x40); + MessageShort.addControl("[EffectRack1_EffectUnit1_Effect2]", "!FXButton", 0x12, "B", 0x20); + MessageShort.addControl("[EffectRack1_EffectUnit1_Effect3]", "!FXButton", 0x12, "B", 0x10); MessageShort.addControl("[EffectRack1_EffectUnit1]", "show_parameters", 0x11, "B", 0x08); MessageShort.addControl("deck2", "!shift", 0x0C, "B", 0x08); @@ -130,17 +130,17 @@ TraktorS4MK2.registerInputPackets = function() { MessageShort.addControl("deck2", "loop_out", 0x0B, "B", 0x08); MessageShort.addControl("deck2", "loop_in", 0x0B, "B", 0x04); MessageShort.addControl("deck2", "slip_enabled", 0x0B, "B", 0x02); - MessageShort.addControl("deck2", "!reset", 0x0B, "B", 0x01); + MessageShort.addControl("deck2", "reset_key", 0x0B, "B", 0x01); MessageShort.addControl("deck2", "beatloop_activate", 0x13, "B", 0x10); MessageShort.addControl("deck2", "!loop_activate", 0x13, "B", 0x08); MessageShort.addControl("deck2", "!jog_touch", 0x11, "B", 0x02); MessageShort.addControl("deck2", "!jog_wheel", 0x05, "I"); MessageShort.addControl("deck2", "!deckswitch", 0x0A, "B", 0x20); MessageShort.addControl("deck2", "!load_track", 0x0A, "B", 0x10); - MessageShort.addControl("deck2", "!FX1", 0x10, "B", 0x08); - MessageShort.addControl("deck2", "!FX2", 0x10, "B", 0x04); - MessageShort.addControl("deck2", "!FX3", 0x10, "B", 0x02); - MessageShort.addControl("deck2", "!FX4", 0x10, "B", 0x01); + MessageShort.addControl("deck2", "!FXon", 0x10, "B", 0x08); + MessageShort.addControl("[EffectRack1_EffectUnit2_Effect1]", "!FXButton", 0x10, "B", 0x04); + MessageShort.addControl("[EffectRack1_EffectUnit2_Effect2]", "!FXButton", 0x10, "B", 0x02); + MessageShort.addControl("[EffectRack1_EffectUnit2_Effect3]", "!FXButton", 0x10, "B", 0x01); MessageShort.addControl("[EffectRack1_EffectUnit2]", "show_parameters", 0x11, "B", 0x04); MessageShort.addControl("[Channel1]", "pfl", 0x0F, "B", 0x40); @@ -165,8 +165,11 @@ TraktorS4MK2.registerInputPackets = function() { MessageShort.addControl("[Playlist]", "LoadSelectedIntoFirstStopped", 0x13, "B", 0x04); MessageShort.addControl("[PreviewDeck1]", "!previewdeck", 0x0F, "B", 0x01); + MessageShort.addControl("[Recording]", "toggle_recording", 0x0F, "B", 0x04); + MessageShort.addControl("[Master]", "!quantize", 0x0A, "B", 0x08); + MessageShort.addControl("[Master]", "!snap", 0x0A, "B", 0x02) MessageShort.setCallback("deck1", "!shift", this.shiftHandler); MessageShort.setCallback("deck2", "!shift", this.shiftHandler); @@ -204,6 +207,18 @@ TraktorS4MK2.registerInputPackets = function() { MessageShort.setCallback("deck2", "!remix4", this.remixHandler); MessageShort.setCallback("[PreviewDeck1]", "!previewdeck", this.previewDeckHandler); MessageShort.setCallback("[Master]", "!quantize", this.quantizeHandler); + MessageShort.setCallback("[Master]", "!snap", this.snapHandler); + + MessageShort.setCallback("[EffectRack1_EffectUnit1_Effect1]", "!FXButton", this.FXButtonHandler); + MessageShort.setCallback("[EffectRack1_EffectUnit1_Effect2]", "!FXButton", this.FXButtonHandler); + MessageShort.setCallback("[EffectRack1_EffectUnit1_Effect3]", "!FXButton", this.FXButtonHandler); + + MessageShort.setCallback("[EffectRack1_EffectUnit2_Effect1]", "!FXButton", this.FXButtonHandler); + MessageShort.setCallback("[EffectRack1_EffectUnit2_Effect2]", "!FXButton", this.FXButtonHandler); + MessageShort.setCallback("[EffectRack1_EffectUnit2_Effect3]", "!FXButton", this.FXButtonHandler); + + + // TODO: the rest of the "!" controls. this.controller.registerInputPacket(MessageShort); @@ -225,14 +240,22 @@ TraktorS4MK2.registerInputPackets = function() { MessageLong.setCallback("deck2", "!loopsize", this.callbackLoopSize); MessageLong.addControl("[EffectRack1_EffectUnit1]", "mix", 0x3F, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit1_Effect1]", "meta", 0x41, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit1_Effect2]", "meta", 0x43, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit1_Effect3]", "meta", 0x45, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit1_Effect1]", "!FXMeta", 0x41, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit1_Effect2]", "!FXMeta", 0x43, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit1_Effect3]", "!FXMeta", 0x45, "H"); + + MessageLong.setCallback("[EffectRack1_EffectUnit1_Effect1]", "!FXMeta",this.FXMetaHandler); + MessageLong.setCallback("[EffectRack1_EffectUnit1_Effect2]", "!FXMeta",this.FXMetaHandler); + MessageLong.setCallback("[EffectRack1_EffectUnit1_Effect3]", "!FXMeta",this.FXMetaHandler); MessageLong.addControl("[EffectRack1_EffectUnit2]", "mix", 0x47, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit2_Effect1]", "meta", 0x49, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit2_Effect2]", "meta", 0x4B, "H"); - MessageLong.addControl("[EffectRack1_EffectUnit2_Effect3]", "meta", 0x4D, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit2_Effect1]", "!FXMeta", 0x49, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit2_Effect2]", "!FXMeta", 0x4B, "H"); + MessageLong.addControl("[EffectRack1_EffectUnit2_Effect3]", "!FXMeta", 0x4D, "H"); + + MessageLong.setCallback("[EffectRack1_EffectUnit2_Effect1]", "!FXMeta",this.FXMetaHandler); + MessageLong.setCallback("[EffectRack1_EffectUnit2_Effect2]", "!FXMeta",this.FXMetaHandler); + MessageLong.setCallback("[EffectRack1_EffectUnit2_Effect3]", "!FXMeta",this.FXMetaHandler); MessageLong.addControl("[Channel1]", "volume", 0x37, "H"); MessageLong.addControl("[QuickEffectRack1_[Channel1]]", "super1", 0x1D, "H"); @@ -306,6 +329,8 @@ TraktorS4MK2.registerOutputPackets = function() { } } + + Output1.addOutput("[Channel1]", "PeakIndicator", 0x0F, "B"); Output1.addOutput("[Channel2]", "PeakIndicator", 0x17, "B"); Output1.addOutput("[Channel3]", "PeakIndicator", 0x07, "B"); @@ -314,6 +339,8 @@ TraktorS4MK2.registerOutputPackets = function() { Output1.addOutput("[Master]", "!usblight", 0x2A, "B"); Output1.addOutput("[Master]", "!quantize", 0x31, "B"); Output1.addOutput("[InternalClock]", "sync_master", 0x30, "B"); + Output1.addOutput("[Recording]", "status", 0x34, "B" ); + this.controller.registerOutputPacket(Output1); Output2.addOutput("deck1", "!shift", 0x1D, "B"); @@ -365,6 +392,7 @@ TraktorS4MK2.registerOutputPackets = function() { Output2.addOutput("[PreviewDeck1]", "play_indicator", 0x3D, "B"); + // Note: this logic means remix button actions are not switchable without reloading the script. // Once we have support for controller preferences, this can be changed. if (TraktorS4MK2.RemixSlotButtonAction === "SAMPLES") { @@ -412,9 +440,24 @@ TraktorS4MK2.registerOutputPackets = function() { Output3.addOutput("[EffectRack1_EffectUnit1]", "group_[Channel4]_enable", 0x0B, "B"); Output3.addOutput("[EffectRack1_EffectUnit2]", "group_[Channel4]_enable", 0x0C, "B"); + Output3.addOutput("[EffectRack1_EffectUnit1_Effect1]", "enabled", 0x02, "B"); + Output3.addOutput("[EffectRack1_EffectUnit1_Effect2]", "enabled", 0x03, "B"); + Output3.addOutput("[EffectRack1_EffectUnit1_Effect3]", "enabled", 0x04, "B"); + + Output3.addOutput("[EffectRack1_EffectUnit2_Effect1]", "enabled", 0x0E, "B"); + Output3.addOutput("[EffectRack1_EffectUnit2_Effect2]", "enabled", 0x0F, "B"); + Output3.addOutput("[EffectRack1_EffectUnit2_Effect3]", "enabled", 0x10, "B"); + Output3.addOutput("[EffectRack1_EffectUnit1]", "show_parameters", 0x11, "B"); + Output3.addOutput("[EffectRack1_EffectUnit2]", "show_parameters", 0x12, "B"); + + for (i=0; i < 16; i++){ + Output3.addOutput("deck1", "!loopSize"+i, 0x1B+i, "B"); + Output3.addOutput("deck2", "!loopSize"+i, 0x2B+i, "B"); + } + this.controller.registerOutputPacket(Output3); // Link up control objects to their outputs @@ -455,8 +498,16 @@ TraktorS4MK2.registerOutputPackets = function() { TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit1]", "show_parameters", TraktorS4MK2.outputChannelCallback); TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit2]", "show_parameters", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit1_Effect1]", "enabled", TraktorS4MK2.outputChannelCallback) + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit1_Effect2]", "enabled", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit1_Effect3]", "enabled", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit2_Effect1]", "enabled", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit2_Effect2]", "enabled", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[EffectRack1_EffectUnit2_Effect3]", "enabled", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[PreviewDeck1]", "play_indicator", TraktorS4MK2.outputChannelCallback); TraktorS4MK2.linkChannelOutput("[InternalClock]", "sync_master", TraktorS4MK2.outputChannelCallback); + TraktorS4MK2.linkChannelOutput("[Recording]", "status", TraktorS4MK2.outputChannelCallback); if (TraktorS4MK2.RemixSlotButtonAction === "SAMPLES") { TraktorS4MK2.linkChannelOutput("[Sampler1]", "play_indicator", TraktorS4MK2.outputChannelCallback); @@ -484,6 +535,12 @@ TraktorS4MK2.registerOutputPackets = function() { engine.connectControl("[Channel2]", "loop_enabled", "TraktorS4MK2.onLoopEnabledChanged"); engine.connectControl("[Channel3]", "loop_enabled", "TraktorS4MK2.onLoopEnabledChanged"); engine.connectControl("[Channel4]", "loop_enabled", "TraktorS4MK2.onLoopEnabledChanged"); + + engine.connectControl("[Channel1]", "beatloop_size", "TraktorS4MK2.onLoopSizeChanged"); + engine.connectControl("[Channel2]", "beatloop_size", "TraktorS4MK2.onLoopSizeChanged"); + engine.connectControl("[Channel3]", "beatloop_size", "TraktorS4MK2.onLoopSizeChanged"); + engine.connectControl("[Channel4]", "beatloop_size", "TraktorS4MK2.onLoopSizeChanged"); + } TraktorS4MK2.linkDeckOutputs = function(key, callback) { @@ -534,6 +591,15 @@ TraktorS4MK2.lightDeck = function(group) { var packet = this.controller.OutputPackets["output3"]; TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit1]", "[EffectRack1_EffectUnit1]"); TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit2]", "[EffectRack1_EffectUnit2]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect1]", "[EffectRack1_EffectUnit1_Effect1]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect2]", "[EffectRack1_EffectUnit1_Effect2]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit1_Effect3]", "[EffectRack1_EffectUnit1_Effect3]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect1]", "[EffectRack1_EffectUnit2_Effect1]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect2]", "[EffectRack1_EffectUnit2_Effect2]"); + TraktorS4MK2.lightGroup(packet, "[EffectRack1_EffectUnit2_Effect3]", "[EffectRack1_EffectUnit2_Effect3]"); + + // Loop size indicator + TraktorS4MK2.loopSizeSet(group); // Selected deck lights if (group === "[Channel1]") { @@ -578,6 +644,7 @@ TraktorS4MK2.pointlessLightShow = function() { packets[i][j] = k; } } + controller.send(packets[0], packets[0].length, 0); controller.send(packets[1], packets[1].length, 0); controller.send(packets[2], packets[2].length, 0); @@ -607,7 +674,9 @@ TraktorS4MK2.init = function(id) { TraktorS4MK2.controller.setOutput("[Master]", "!quantize", 0x7F * TraktorS4MK2.master_quantize, true); TraktorS4MK2.controller.setOutput("[Master]", "!usblight", 0x7F, true); + TraktorS4MK2.outputChannelCallback(engine.getValue("[InternalClock]", "sync_master"), "[InternalClock]", "sync_master"); + TraktorS4MK2.outputChannelCallback(engine.getValue("[Recording]", "status"), "[Recording]", "status"); TraktorS4MK2.lightDeck("[PreviewDeck1]"); // Light 3 and 4 first so we get the mixer lights on, then do 1 and 2 since those are active // on startup. @@ -1024,10 +1093,19 @@ TraktorS4MK2.wheelDeltas = function(group, value) { } TraktorS4MK2.scalerJog = function(tick_delta, time_delta) { + // If it's playing nudge if (engine.getValue(group, "play")) { return (tick_delta / time_delta) / 3; } else { - return (tick_delta / time_delta) * 2.0; + //If shift pressed, fast search through tracks + // otherwise search normal speed + if (TraktorS4MK2.controller.shift_pressed['deck1'] || + TraktorS4MK2.controller.shift_pressed['deck2'] + ){ + return (tick_delta / time_delta) * 20.0; + } else { + return (tick_delta / time_delta) * 2.0; + } } } @@ -1088,6 +1166,37 @@ TraktorS4MK2.quantizeHandler = function(field) { TraktorS4MK2.controller.setOutput("[Master]", "!quantize", 0x7F * TraktorS4MK2.master_quantize, true); } +TraktorS4MK2.snapHandler = function(field) { + if (field.value === 0) { + return; + } + library_maximized = engine.getValue("[Master]", "maximize_library"); + engine.setValue("[Master]", "maximize_library", !library_maximized); +} + +TraktorS4MK2.FXButtonHandler = function(field){ + if(field.value){ + if ( + TraktorS4MK2.controller.shift_pressed['deck1'] || + TraktorS4MK2.controller.shift_pressed['deck2'] + ) { + engine.setValue(field.group, "effect_selector", 1); + } else { + engine.setValue(field.group, "enabled", !engine.getValue(field.group, "enabled")); + } + } + TraktorS4MK2.lightDeck(field.group); +} + +TraktorS4MK2.FXMetaHandler = function(field){ + engine.setValue(field.group, "meta", field.value / parseFloat(4096)); +} + + +TraktorS4MK2.outputFXCallback = function(value, group, key) { + TraktorS4MK2.controller.setOutput(group, key, value * 0x7F, !TraktorS4MK2.controller.freeze_lights); +} + TraktorS4MK2.callbackPregain = function(field) { // TODO: common-hid-packet-parser looks like it should do deltas, but I can't get them to work. prev_pregain = TraktorS4MK2.controller.prev_pregain[field.group]; @@ -1103,8 +1212,20 @@ TraktorS4MK2.callbackPregain = function(field) { delta = -0.05; } - var cur_pregain = engine.getValue(group, "pregain"); - engine.setValue(group, "pregain", cur_pregain + delta); + if ( + TraktorS4MK2.controller.shift_pressed['deck1'] || + TraktorS4MK2.controller.shift_pressed['deck2'] + ) { + if (delta > 0){ + engine.setValue(group, 'beats_translate_later', true); + } else if (delta < 0){ + engine.setValue(group, 'beats_translate_earlier', true); + } + } else { + var cur_pregain = engine.getValue(group, "pregain"); + engine.setValue(group, "pregain", cur_pregain + delta); + } + } TraktorS4MK2.callbackLoopMove = function(field) { @@ -1138,6 +1259,70 @@ TraktorS4MK2.callbackLoopMove = function(field) { } } +TraktorS4MK2.sendLoopSizeMessage = function(deck, firstChar, secondChar, firstDot, secondDot) { +// display_num must be a string with two chars + // Do the second number first + TraktorS4MK2.displayCharLoopCounter(deck, 0, firstChar); + TraktorS4MK2.displayCharLoopCounter(deck, 1, secondChar); + TraktorS4MK2.displayCharLoopDot(deck, 1, firstDot); + TraktorS4MK2.displayCharLoopDot(deck, 0, secondDot); + +} + +TraktorS4MK2.displayCharLoopCounter = function(deck, charPos, character){ + // charPost is 0 or 1 for first or second character on the display + // the display is placed like this: + // o -- 4 -- + // | | + // 5 3 + // | | + // -- 1 -- + // | | + // 6 2 + // | | + // -- 7 -- + // Where the numbers respresent each segment of the display + // and the dot before it is 0 (but this is dealt with in displayCharLoopDot) + + var numArray = { + '': [], //empty + 0: [2,3,4,5,6,7], + 1: [2,3], + 2: [1,3,4,6,7], + 3: [1,2,3,4,7], + 4: [1,2,3,5], + 5: [4,5,1,2,7], + 6: [4,5,1,2,6,7], + 7: [4,3,2], + 8: [1,2,3,4,5,6,7], + 9: [5,4,1,3,2,7], +// Add a few special characters + 'h': [5,1,6,2], + 'n': [6,1,2], + 'o': [6,1,2,7], + '-': [1], + 'b': [5,6,7,2,1], + 'c': [1,6,7], + 'u': [6,2,7], + } + + for (j = 0; j < 8; j++) { + loop_key = 8*charPos + j; + var key = "!loopSize" + loop_key; + TraktorS4MK2.controller.setOutput( + deck, key, (numArray[character].indexOf(j) > -1)*0x7F, // if it's in the array turn it on, off otherwise + !TraktorS4MK2.controller.freeze_lights + ); + } +} + +TraktorS4MK2.displayCharLoopDot = function(deck, charPos, on){ + var key = "!loopSize" + 8*charPos; + TraktorS4MK2.controller.setOutput( + deck, key, on*0x7F, + !TraktorS4MK2.controller.freeze_lights + ); +} TraktorS4MK2.callbackLoopSize = function(field) { var splitted = field.id.split("."); var group = splitted[0] @@ -1187,8 +1372,15 @@ TraktorS4MK2.callbackBrowse = function(field) { } else { delta = -1; } - - engine.setValue("[Playlist]", "SelectTrackKnob", delta); + if ( + TraktorS4MK2.controller.shift_pressed["deck1"] || + TraktorS4MK2.controller.shift_pressed["deck2"] + ) { + engine.setValue("[Playlist]", "SelectPlaylist", delta); + } + else { + engine.setValue("[Playlist]", "SelectTrackKnob", delta); + } } TraktorS4MK2.scalerParameter = function(group, name, value) { @@ -1351,3 +1543,32 @@ TraktorS4MK2.onLoopEnabledChanged = function(value, group, key) { TraktorS4MK2.outputCallbackLoop(value, group, "loop_in"); TraktorS4MK2.outputCallbackLoop(value, group, "loop_out"); } + +TraktorS4MK2.onLoopSizeChanged = function(value, group, key) { + var deck = TraktorS4MK2.resolveDeckIfActive(group); + //deal with single digit values + if (value.toString().length === 1){ + TraktorS4MK2.sendLoopSizeMessage(deck, '', value, false, false); + // values with two digits + } else if (value.toString().length === 2 ) { + TraktorS4MK2.sendLoopSizeMessage(deck, value.toString().split("")[0], value.toString().split("")[1], false, false); + // deal with fraction beats + } else if (1 > value > 0 ) { + if (value === 0.5){ + TraktorS4MK2.sendLoopSizeMessage(deck, '-', 2, false, false); + } else if (value === 0.25){ + TraktorS4MK2.sendLoopSizeMessage(deck, '-', 4, false, false); + } else if (value === 0.125){ + TraktorS4MK2.sendLoopSizeMessage(deck, '-', 8, false, false); + } else { + TraktorS4MK2.sendLoopSizeMessage(deck, '-', 'n', false, false); + } + // deal with larger Loops + } else if (value.toString().length > 2){ + TraktorS4MK2.sendLoopSizeMessage(deck, value.toString().split("")[0], value.toString().split("")[1], true, true); + } +} + +TraktorS4MK2.loopSizeSet = function(group) { + TraktorS4MK2.onLoopSizeChanged(engine.getValue(group, "beatloop_size"), group); +}