From 2cdb0ead63e79e164b59702a88829dfcd338c22f Mon Sep 17 00:00:00 2001 From: perseo22 Date: Tue, 22 Sep 2020 14:11:32 +0200 Subject: [PATCH 1/7] Controller: Hercules DJControl Jogvision Complete rewrite, better code, fixed bugs, and improved mapping --- .../Hercules_DJControl_Jogvision-scripts.js | 897 ++++++++++-------- .../Hercules_DJControl_Jogvision.midi.xml | 98 +- 2 files changed, 552 insertions(+), 443 deletions(-) diff --git a/res/controllers/Hercules_DJControl_Jogvision-scripts.js b/res/controllers/Hercules_DJControl_Jogvision-scripts.js index 76c93006018..57156a387e8 100644 --- a/res/controllers/Hercules_DJControl_Jogvision-scripts.js +++ b/res/controllers/Hercules_DJControl_Jogvision-scripts.js @@ -1,422 +1,531 @@ -// **************************************************************************** // * Mixxx mapping script file for the Hercules DJControl Jogvision. -// * Author: DJ Phatso, contributions by Kerrick Staley and David TV -// * Version 1.16 (Jun 2020) -// * Version 1.15 (Jun 2020) -// * Version 1.14 (Jun 2020) -// * Version 1.13 (May 2020) -// * Version 1.11 (May 2020) -// * Version 1.10 (May 2020) -// * Version 1.9 (May 2020) -// * Version 1.8 (May 2020) -// * Version 1.6 (January 2020) -// * Version 1.5 (January 2020) -// * Version 1.4 (December 2019) -// * Version 1.3 (November 2019) -// * Version 1.2 (November 2019) -// * Version 1.1 (March 2019) +// * Author: DJ Phatso (contributions by Kerrick Staley, and fully rewritten, completed and enhanced by David TV) // * Forum: https://www.mixxx.org/forums/viewtopic.php?f=7&t=12580 // * Wiki: https://www.mixxx.org/wiki/doku.php/hercules_dj_control_jogvision -// -// Changes to v1.16 -// - Added a "beatDetection" variable to use old school one (way better beat match, but no song position relative) -// -// Changes to v1.15 -// - Reset shift-"Loop ON", to do what Hercules wanted it to be: select FX2 for given channel. Now, -// the loop creation is done with "MODE"+"Loop ON" combo (here, MODE key works like "SHIFT" key ;) -// -// Changes to v1.14 -// - Better beat_active matching -// -// Changes to v1.13 -// - Added "shift"+AIRFX to do a high pass (apart from the default low pass) -// - Added shift+ to set beatgrid at current play position -// -// Changes to v1.12 -// - Added "alternate" beat leds mode -// - Changed "follow" beats led algorithm with a better/more elegant version -// -// Changes to v1.11 -// - Changed beats led algorithm to match song beats (forward and backward) and fix leads positions, -// what allows backward led activation!! (didn't have that working previously) -// -// Changes to v1.10 -// - Changed jogwheel outer led movement based on playposition (much better!) -// - Added a user variable to set or not some effects at ini -// - Minor code convention corrections -// -// Changes to v1.9 -// - Added jogwheel outer led movement when scratch and back/forward spin -// -// Changes to v1.8 -// - Added normal, reverse, blink and follow modes for beat active led (see variable: beatActiveMode) -// -// Changes to v1.7 -// - Added back-spin (and forward-spin) effect on "vinyl" mode when jog wheel pushed from top -// -// Changes to v1.6 -// - Minor code-style changes (use camelCase, use "double quotes" and other if/else styles) -// -// Changes to v1.5 -// - Fixed regression not updating the VuMeters -// - Fixed regression not updating the CUE/MIX buttons -// -// Changes to v1.4 -// - Code cleanup and standarization -// - Added "orage" track led color (ala Serato) when play position is beyond the -// part of the track, but before the "end of track" Mixxx signal -// -// Changes to v1.3 -// - Enabled the creation of beatloop (beatloopActivate) by using SHIFT+LoopON -// - Changed "LOOP SIZE" to adjust the beatjumpSize (you still can change size -// with surrounding buttons: reloop_toggle key) -// - Added SHIFT+"LOOP SIZE" to move loop left or right by N beats (beatjump) -// -// Changes to v1.2 -// - Enabled Jogwheel Outer LED rotation -// - Enabled Beat LEDs -// -// Changes to v1.1 -// - Controller knob/slider values are queried on startup, so MIXXX is synced. -// - Fixed vinyl button behavior the first time it"s pressed. -// -// v1.0 : Original release -// -// **************************************************************************** - -// **************************************************************************** -// User available variables (you may modify them) -var beatActiveMode = "normal"; // normal (default), reverse, blink, follow, alternate -var beatDetection = "normal"; // normal (default), follow (song position accurate, beat much less accurate) -var initUpdateEffects = 1; // 1 (default) - set some effects values at startup; 0 - do not touch effects at startup - -// **************************************************************************** -// Other internal constants (you may NOT modify anything from here) -var on = 0x7F; -var off = 0x00; -var alpha = 1.0 / 8; -var beta = alpha / 16; -var speed = 33 + 1/3; -var ledSpeed = (speed / 60) * 127; -var masterLeds = 0x90; -var ledRotationTimer = 0; -var beatMax; -if (beatActiveMode.match(/^(?:normal|reverse)$/g)) { - beatMax = 4; -} else if (beatActiveMode.match(/^(?:blink|alternate)$/g)) { - beatMax = 2; -} else { - beatMax = 8; -} - -var DJCJV = {}; -DJCJV.vinylModeActive = true; -DJCJV.Channel = []; -DJCJV.Channel["[Channel1]"] = {"central": 0x90, "deck": 0xB0, "beatPosition": 1, "rotation": 0x00, "n": 1, "onBeat": 0, "beatsPassed": 0, "shiftPressed": 0, "modePressed": 0}; -DJCJV.Channel["[Channel2]"] = {"central": 0x91, "deck": 0xB1, "beatPosition": 1, "rotation": 0x00, "n": 2, "onBeat": 0, "beatsPassed": 0, "shiftPressed": 0, "modePressed": 0}; - -// **************************************************************************** -// General functions - -// Function to rotate jogs' outer led (borrowed from the 'Pioneer-DDJ-SX-scripts.js' mapping) -DJCJV.updateJogLeds = function(value, group, control) { - var elapsedTime = value * engine.getValue(group, "duration"); - var wheelPos = parseInt(((value >= 0) ? 0 : 127) + 1 + ((ledSpeed * elapsedTime) % 127)); - - // Only send midi message when the position is actually updated. - if (DJCJV.Channel[group].rotation !== wheelPos) { - midi.sendShortMsg(DJCJV.Channel[group].deck, 0x60, wheelPos); // Update the outer (spin) jog leds - DJCJV.wheelInnerUpdate(value, group, control); // Also update the inner jog leds with updated song position +// * Version: 1.25 (see changelog at https://github.com/mixxxdj/mixxx/pull/2370) + +// EXTRAS ADDED TO ORIGINAL MAPPING +// - MODE+Loop ON : set a loop_in mark (with curently defined loop_size), activate it, and enable slip mode +// - MODE+Loop X 1/2 / X 2 : do a 'beatjump_size' beats beatjump backward/forward +// - MODE+Loop Size Knob : decrease/increase pitch (only key, not tempo!) +// - MODE+JogWheel plate (playing) : scratch with 'Slip' ON (deactivate 'Slip' when plate is released) +// - MODE+JogWheel plate (stopped) : move song position backward/forward faster by 'quickMoveFactor' factor +// - MODE+Browser Knob Turn : move library selected position in groups of 'quickBrowseFactor' elements forward/backward +// - MODE+LOAD A|B : toggle 'quantize' for deck where MODE key is pressed +// - SHIFT+LOAD A|B : eject track from deck where SHIFT key is pressed +// - SHIFT+Browser Knob Press : activate (double-click) currently selected item in browser +// - SHIFT+Loop Size Knob : move existing loop forward/backward +// - SHIFT+JogWheelTouch : do a 'backspin' with 'spinBackBrakeFactor' and 'spinBackInitialSpeed' factors +// - SHIFT+MultiFX : set beatgrid to current position +// - SHIFT+Aircontrol Filter : do the reverse than standard, that is, high-pass filter + +// ---------------------------------------------------------------------------- +// CONFIGURABLE MAPPING PARAMETERS +var CFG = { + // USER Variables (you may modify them) + "user": { + "beatDetection": "normal", // normal|follow (follow=song position is more accurate with beat position, but beat detection is much less accurate) + "initUpdateEffects": 0.8, // [0..1] (if value > 0, set some effects values at startup with given float value) + "beatHelper": 1, // 0|1 (0=disabled; 1=use outer jog leg to indicate where to slide the pitch controller; see also 'CFG.fine.beatHelpSensitivity' variable) + "beatActiveMode": "follow" // normal|reverse|blink|follow|fill|bounce|alternate|off|on (see table below) + }, + // FINE TUNNING Variables (you may modify them, but not too far from the default values...) + "fine": { + "beatHelpSensitivity": 0.5, // [0..1) Max distance (float) in BPMs to be considered as a match (in use if CFG.user.beatHelper=1): the lower, the more sensitive. Values equal or bigger than 1 are reset to 0.9 and a warning is printed + "quickMoveFactor": 0.002, // [0..1] the smaller (float), the slower MODE+JogWheel will move 'playposition' (when such channel is NOT playing) + "quickBrowseFactor": 10, // [0..inf] the bigger, the faster MODE+Browser jog will move the cursor position in the library up/down + "spinBackBrakeFactor": 100, // (0..5000] the bigger, the softer the brake will be applied (0 = immediate stop; >=5000 = it will take almost forever to stop) + "spinBackInitialSpeed": 6, // (0..200] the bigger, the stronger will be the "back" impulse (1 = no spinbak, but stop and start sloooowly) + "mixGainFactor": 0.1 // (0..1) (float) the bigger, the faster the Pregain or Mix level will be updated } - DJCJV.Channel[group].rotation = wheelPos; - - DJCJV.Channel[group].beatsPassed = Math.round((value * engine.getValue(group, "duration")) * (engine.getValue(group, "bpm") / 60)); - DJCJV.Channel[group].beatPosition = Math.floor((DJCJV.Channel[group].beatsPassed % beatMax)) + 1; +}; - if (beatDetection === "follow") { - // If on beat_active, update the beat leds - if (engine.getValue(group, "beat_active") || ((engine.getValue(group, "beat_closest") < engine.getValue(group, "beat_next"))) && (!DJCJV.Channel[group].onBeat)) { - DJCJV.beatActive(0, group); - DJCJV.Channel[group].onBeat = true; - } else if (engine.getValue(group, "beat_closest") >= engine.getValue(group, "beat_next")) { - DJCJV.Channel[group].onBeat = false; +// BEATS leds lightning pattern depending on 'beatActiveMode', based on a 4x4 compass: +// +// beat number | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +// -------------------------------------------------------------------------------------------------------------------- +// led (#=ON; ·=OFF) | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | 1 2 3 4 | +// -------------------------------------------------------------------------------------------------------------------- +// | normal | # · · · | · # · · | · · # · | · · · # | # · · · | · # · · | · · # · | · · · # | +// |----------------------------------------------------------------------------------------------------------- +// | reverse | · # # # | # · # # | # # · # | # # # · | · # # # | # · # # | # # · # | # # # · | +// |----------------------------------------------------------------------------------------------------------- +// | blink | # # # # | · · · · | # # # # | · · · · | # # # # | · · · · | # # # # | · · · · | +// beat |----------------------------------------------------------------------------------------------------------- +// Active | follow | # · · · | # # · · | # # # · | # # # # | · # # # | · · # # | · · · # | · · · · | +// Mode |----------------------------------------------------------------------------------------------------------- +// | fill | # · · · | # # · · | # # # · | # # # # | # · · · | # # · · | # # # · | # # # # | +// |----------------------------------------------------------------------------------------------------------- +// | bounce | # · · · | · # · · | · · # · | · · · # | · · # · | · # · · | # · · · | · · · · | +// |----------------------------------------------------------------------------------------------------------- +// | alternate | # # · · | · · # # | # # · · | · · # # | # # · · | · · # # | # # · · | · · # # | +// |----------------------------------------------------------------------------------------------------------- +// | off | · · · · | · · · · | · · · · | · · · · | · · · · | · · · · | · · · · | · · · · | +// |----------------------------------------------------------------------------------------------------------- +// | on | # # # # | # # # # | # # # # | # # # # | # # # # | # # # # | # # # # | # # # # | + +// ---------------------------------------------------------------------------- +// GENERAL FUNCTIONS (nothing to be touched from here on...) +var DJCJV = { + // Some controller-related constants + "led": { + "beat1": 0x3A, + "beat2": 0x3B, + "beat3": 0x3C, + "beat4": 0x3D, + "track": 0x45, + "vinylMode": 0x46, + "headCue": 0x4C, + "headMix": 0x4D, + "outerJog": 0x60, + "innerJog": 0x61, + "vuMeter": 0x44, + "master": 0x90 + }, + "colour": { + "orange": 0x02, + "green": 0x07 + }, + "other": { + "alpha": 0.125, // + "beta": 0.0078125, // + "speed": 33.3333333, // + "ledSpeed": 70.55555485, // these are re-calculated at DJCJV.init + "beatMax": 4, // + "vinylModeActive": true, // + "beatHelpTimer": 0, // + "left": 127, + "pressed": 127, + "on": 0x7F, + "off": 0x00 + }, + "Channel": { + "[Channel1]": { + "central": 0x90, + "deck": 0xB0, + "beatPosition": 1, + "rotation": 0x00, + "n": 1, + "onBeat": 0, + "beatsPassed": 0, + "shiftPressed": 0, + "modePressed": 0 + }, + "[Channel2]": { + "central": 0x91, + "deck": 0xB1, + "beatPosition": 1, + "rotation": 0x00, + "n": 2, "onBeat": 0, + "beatsPassed": 0, + "shiftPressed": 0, + "modePressed": 0 + } + }, + // Initialization + "init": function(id) { + print(id+": initializing..."); + + // Prepare some musical constants + if (CFG.fine.beatHelpSensitivity >= 1) { + print(id+": WARNING: variable 'CFG.fine.beatHelpSensitivity' is set to a value equal or bigger than 1 ("+CFG.fine.beatHelpSensitivity+"). Setting it exactly to 0.9"); + CFG.fine.beatHelpSensitivity = 0.9; } - } -}; -// Initialization -DJCJV.init = function(id) { + // Prepare some musical constants + DJCJV.other.alpha = 1.0 / 8; + DJCJV.other.beta = DJCJV.other.alpha / 16; + DJCJV.other.speed = 33 + 1/3; + DJCJV.other.ledSpeed = (DJCJV.other.speed / 60) * 127; + + // Calculate the number of beats to be taken into account for the beat_leds, depending on the CFG.user.beatActiveMode + if (CFG.user.beatActiveMode.match(/^(?:normal|reverse|fill)$/g)) { + DJCJV.other.beatMax = 4; + } else if (CFG.user.beatActiveMode.match(/^(?:blink|alternate)$/g)) { + DJCJV.other.beatMax = 2; + } else { + DJCJV.other.beatMax = 8; // follow|bounce + } - print("Hercules DJControl Jogvision id: \""+id+"\" initializing..."); - print("Using beatActiveMode="+beatActiveMode+" (beatMax="+beatMax+")"); + print(id+": Using CFG.user.beatActiveMode="+CFG.user.beatActiveMode+" (beatMax="+DJCJV.other.beatMax+")"); + print(id+": Using CFG.user.beatDetection="+CFG.user.beatDetection); + print(id+":"+((CFG.user.initUpdateEffects === 0) ? " NOT " : " ") + "Initializing some effects at startup"); + print(id+":"+((CFG.user.beatHelper !== 1) ? " NOT " : " ") + "Using beat helper"); - // Set all LED states to off - midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x7F, off); - midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x7F, off); + // Set all LED states to off + midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x7F, DJCJV.other.off); + midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x7F, DJCJV.other.off); - // Set Vinyl button LED On. - midi.sendShortMsg(masterLeds, 0x45, on); - DJCJV.vinylModeActive = true; - midi.sendShortMsg(masterLeds, 0x46, on); + // Set Vinyl button LED On. + DJCJV.other.vinylModeActive = true; + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.vinylMode, DJCJV.other.on); - // Set Headphone CUE/MIX LED state - if (engine.getValue("[Master]", "headMix") > 0.5) { - midi.sendShortMsg(masterLeds, 0x4C, on); // headset "Mix" button LED - midi.sendShortMsg(masterLeds, 0x4D, off); - } else { - midi.sendShortMsg(masterLeds, 0x4C, off); - midi.sendShortMsg(masterLeds, 0x4D, on); // headset "Cue" button LED - } + // Set Headphone CUE/MIX LED state + if (engine.getValue("[Master]", "headMix") > 0.5) { + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headCue, DJCJV.other.on); // headset "Mix" button LED + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headMix, DJCJV.other.off); + } else { + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headCue, DJCJV.other.off); + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headMix, DJCJV.other.on); // headset "Cue" button LED + } - // Enable Soft takeover - engine.softTakeover("[Master]", "crossfader", true); - engine.softTakeover("[QuickEffectRack1_[Channel1]]", "super1", true); - engine.softTakeover("[QuickEffectRack1_[Channel2]]", "super1", true); + // Enable Soft takeover + engine.softTakeover("[Master]", "crossfader", true); + engine.softTakeover("[QuickEffectRack1_[Channel1]]", "super1", true); + engine.softTakeover("[QuickEffectRack1_[Channel2]]", "super1", true); - if (initUpdateEffects === 1) { - // Set effects Levels - Dry/Wet - Filters // Done to work around the limited amount of controls in the Jogvision controller - engine.setParameter("[EffectRack1_EffectUnit1_Effect1]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit1_Effect2]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit1_Effect3]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit2_Effect1]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit2_Effect2]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit2_Effect3]", "meta", 0.6); - engine.setParameter("[EffectRack1_EffectUnit1]", "mix", 0.6); - engine.setParameter("[EffectRack1_EffectUnit2]", "mix", 0.6); - engine.setParameter("[QuickEffectRack1_[Channel1]]", "super1", 0.5); - engine.setParameter("[QuickEffectRack1_[Channel2]]", "super1", 0.5); - } - - // Connect the VUMeters - engine.connectControl("[Channel1]", "VuMeter", "DJCJV.vuMeterUpdate"); - engine.connectControl("[Channel2]", "VuMeter", "DJCJV.vuMeterUpdate"); - - // Set inner jog leds to 0 - midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x61, 0); - midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x61, 0); - // Set outer jog leds to 0 - midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x60, 1); - midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x60, 1); - - // Enable jogs' outer leds rotation and Inner LEDs song position update - engine.connectControl("[Channel1]", "playposition", "DJCJV.updateJogLeds"); - engine.connectControl("[Channel2]", "playposition", "DJCJV.updateJogLeds"); - if (beatDetection === "normal") { - // Connect the beat_active with beat leds - engine.connectControl("[Channel1]", "beat_active", "DJCJV.beatActive"); - engine.connectControl("[Channel2]", "beat_active", "DJCJV.beatActive"); - engine.connectControl("[Channel1]", "stop", "DJCJV.beatInactive"); - engine.connectControl("[Channel2]", "stop", "DJCJV.beatInactive"); - } - - // Ask the controller to send all current knob/slider values over MIDI, which will update the corresponding GUI controls in MIXXX. - midi.sendShortMsg(0xB0, 0x7F, on); - - print("Hercules DJControl Jogvision id: \""+id+"\" initialized"); -}; - -// Finalization -DJCJV.shutdown = function() { - if (ledRotationTimer) { - engine.stopTimer(ledRotationTimer); - ledRotationTimer = 0; - } - // Set all LED states to off - midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x7F, off); - midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x7F, off); -}; - -// Beat led ACTIVATE (move) -DJCJV.beatActive = function(value, group, _control) { - if (value === 1) { - return; - } - var led = DJCJV.Channel[group].central; - var pos = DJCJV.Channel[group].beatPosition; - - // Normal - if (beatActiveMode === "normal") { - midi.sendShortMsg(led, 0x3A, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3B, pos === 2 ? on : off); - midi.sendShortMsg(led, 0x3C, pos === 3 ? on : off); - midi.sendShortMsg(led, 0x3D, pos === 4 ? on : off); - // Reverse - } else if (beatActiveMode === "reverse") { - midi.sendShortMsg(led, 0x3A, pos !== 1 ? on : off); - midi.sendShortMsg(led, 0x3B, pos !== 2 ? on : off); - midi.sendShortMsg(led, 0x3C, pos !== 3 ? on : off); - midi.sendShortMsg(led, 0x3D, pos !== 4 ? on : off); - // Reverse - } else if (beatActiveMode === "blink") { - midi.sendShortMsg(led, 0x3A, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3B, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3C, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3D, pos === 1 ? on : off); - // Follow - } else if (beatActiveMode === "follow") { - midi.sendShortMsg(led, 0x3A, ((pos >= 1) && (pos <= 4)) ? on : off); - midi.sendShortMsg(led, 0x3B, ((pos >= 2) && (pos <= 5)) ? on : off); - midi.sendShortMsg(led, 0x3C, ((pos >= 3) && (pos <= 6)) ? on : off); - midi.sendShortMsg(led, 0x3D, ((pos >= 4) && (pos <= 7)) ? on : off); - // Alternate - } else if (beatActiveMode === "alternate") { - midi.sendShortMsg(led, 0x3A, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3B, pos === 1 ? on : off); - midi.sendShortMsg(led, 0x3C, pos === 2 ? on : off); - midi.sendShortMsg(led, 0x3D, pos === 2 ? on : off); - } -}; -// Beat led DEACTIVATE (off all) -DJCJV.beatInactive = function(value, group, _control) { - midi.sendShortMsg(DJCJV.Channel[group].central, 0x3A, off); - midi.sendShortMsg(DJCJV.Channel[group].central, 0x3B, off); - midi.sendShortMsg(DJCJV.Channel[group].central, 0x3C, off); - midi.sendShortMsg(DJCJV.Channel[group].central, 0x3D, off); - - DJCJV.Channel[group].beatPosition = 1; -}; - -// Jogwheels inner LED display - Play position -DJCJV.wheelInnerUpdate = function(value, group, _control) { - var playPos = value * 127; - midi.sendShortMsg(DJCJV.Channel[group].deck, 0x61, playPos); - - // Also update the "track" led information - if (engine.getValue(group, "end_of_track")) { - // Let Mixxx"s engine turn flashing red automatically - return; - } else if (playPos > 64) { - // Turn "track" led to orange if position is beyond the half - midi.sendShortMsg(DJCJV.Channel[group].central, 0x45, 0x02); - } else { - // Turn "track" led to green if position is in the first half - midi.sendShortMsg(DJCJV.Channel[group].central, 0x45, 0x07); - } -}; + if (CFG.user.initUpdateEffects !== 0) { + engine.setParameter("[EffectRack1_EffectUnit1_Effect1]", "meta", CFG.user.initUpdateEffects); // Deck A, Effect 1 initial value + engine.setParameter("[EffectRack1_EffectUnit1_Effect2]", "meta", CFG.user.initUpdateEffects); // Deck A, Effect 2 initial value + engine.setParameter("[EffectRack1_EffectUnit1_Effect3]", "meta", CFG.user.initUpdateEffects); // Deck A, Effect 3 initial value + engine.setParameter("[EffectRack1_EffectUnit2_Effect1]", "meta", CFG.user.initUpdateEffects); // Deck B, Effect 1 initial value + engine.setParameter("[EffectRack1_EffectUnit2_Effect2]", "meta", CFG.user.initUpdateEffects); // Deck B, Effect 2 initial value + engine.setParameter("[EffectRack1_EffectUnit2_Effect3]", "meta", CFG.user.initUpdateEffects); // Deck B, Effect 3 initial value + engine.setParameter("[EffectRack1_EffectUnit1]", "mix", CFG.user.initUpdateEffects / 2); // Deck A, Effect Master mixer value + engine.setParameter("[EffectRack1_EffectUnit2]", "mix", CFG.user.initUpdateEffects / 2); // Deck A, Effect Master mixer value + engine.setParameter("[QuickEffectRack1_[Channel1]]", "super1", 0.5); // High/low filter Deck A (mapped to AIR FX) + engine.setParameter("[QuickEffectRack1_[Channel2]]", "super1", 0.5); // High/low filter Deck B (mapped to AIR FX) + } -// Vu Meter -DJCJV.vuMeterUpdate = function(value, group, _control) { - midi.sendShortMsg(DJCJV.Channel[group].central, 0x44, value * 6); -}; + // Connect the VUMeters + engine.connectControl("[Channel1]", "VuMeter", "DJCJV.vuMeterUpdate"); + engine.connectControl("[Channel2]", "VuMeter", "DJCJV.vuMeterUpdate"); + + // Set inner & outer jog leds to 0 + DJCJV.updateJogLeds(0, "[Channel1]"); + DJCJV.updateJogLeds(0, "[Channel2]"); + + // Enable jogs' outer leds rotation and Inner LEDs song position update + engine.connectControl("[Channel1]", "playposition", "DJCJV.updateJogLeds"); + engine.connectControl("[Channel2]", "playposition", "DJCJV.updateJogLeds"); + if (CFG.user.beatDetection === "normal") { + // Connect the beat_active with beat leds + engine.connectControl("[Channel1]", "beat_active", "DJCJV.beatActive"); + engine.connectControl("[Channel2]", "beat_active", "DJCJV.beatActive"); + engine.connectControl("[Channel1]", "stop", "DJCJV.beatInactive"); + engine.connectControl("[Channel2]", "stop", "DJCJV.beatInactive"); + } -// Headphone CUE/MIX buttons status -DJCJV.headCue = function(midino, control, value, status, group) { - if (engine.getValue(group, "headMix") === 0) { - engine.setValue(group, "headMix", -1.0); - midi.sendShortMsg(masterLeds, 0x4D, 0x7f); - midi.sendShortMsg(masterLeds, 0x4C, 0x00); - } -}; -DJCJV.headMix = function(midino, control, value, status, group) { - if (engine.getValue(group, "headMix") !== 1) { - engine.setValue(group, "headMix", 0); - midi.sendShortMsg(masterLeds, 0x4D, 0x00); - midi.sendShortMsg(masterLeds, 0x4C, 0x7f); - } -}; + if (CFG.user.beatHelper === 1) { + DJCJV.other.beatHelpTimer = engine.beginTimer(100, "DJCJV.beatHelp"); + } -// Filter (AIR FX) -DJCJV.Filter = function(channel, control, value, status, group) { - var deck = group.substr(18, 10); - var delta = DJCJV.Channel[deck].shiftPressed ? (value / 255) : (-1 * (value / 255)); - engine.setValue(group, "super1", 0.5 + delta); -}; + // Ask the controller to send all current knob/slider values over MIDI, which will update the corresponding GUI controls in MIXXX. + midi.sendShortMsg(0xB0, 0x7F, DJCJV.other.on); + + print(id+": initialized"); + }, + // Finalization + "shutdown": function() { + print(id+": finishing..."); + if (DJCJV.other.beatHelpTimer !== 0) { + engine.stopTimer(DJCJV.other.beatHelpTimer); + DJCJV.other.beatHelpTimer = 0; + } -// Sniff decks' SHIFT presses and store them -DJCJV.shiftKey = function(channel, control, value, status, group) { - DJCJV.Channel[group].shiftPressed = (value === 127); - return value; -}; + engine.setValue("[Channel1]", "eject", 1); + engine.setValue("[Channel2]", "eject", 1); + // /(Try to) Set all LED states to off + midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, 0x7F, DJCJV.other.off); + midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, 0x7F, DJCJV.other.off); + }, + // Beat helper + "beatHelp": function() { + var diff = engine.getValue("[Channel1]", "bpm") - engine.getValue("[Channel2]", "bpm"); + var move = 0; + + if (diff < (-1 * CFG.fine.beatHelpSensitivity)) { + // Move ch1 pitch up or ch2 pitch down + move = 4*diff + 127; + } else if (diff > CFG.fine.beatHelpSensitivity) { + // Move ch1 pitch down or ch2 pitch up + move = 4*diff; + } else { + // Beats match + move = 0; + } -// Sniff decks' MODE presses and store them -DJCJV.modeKey = function(channel, control, value, status, group) { - DJCJV.Channel[group].modePressed = (value === 127); - return value; -}; + if ((engine.getValue("[Channel1]", "play") !== 1) && (!engine.isScratching(DJCJV.Channel["[Channel1]"].n))) { + midi.sendShortMsg(DJCJV.Channel["[Channel1]"].deck, DJCJV.led.outerJog, move); + } + if ((engine.getValue("[Channel2]", "play") !== 1) && (!engine.isScratching(DJCJV.Channel["[Channel2]"].n))) { + midi.sendShortMsg(DJCJV.Channel["[Channel2]"].deck, DJCJV.led.outerJog, move); + } + }, + // Beat led ACTIVATE + "beatActive": function(value, group, _control) { + if ((value === 1) || (CFG.user.beatActiveMode === "off")) { + return; + } + var central = DJCJV.Channel[group].central; + var pos = DJCJV.Channel[group].beatPosition; + + if (CFG.user.beatActiveMode === "normal") { + midi.sendShortMsg(central, DJCJV.led.beat1, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, pos === 2 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, pos === 3 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, pos === 4 ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "reverse") { + midi.sendShortMsg(central, DJCJV.led.beat1, pos !== 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, pos !== 2 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, pos !== 3 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, pos !== 4 ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "blink") { + midi.sendShortMsg(central, DJCJV.led.beat1, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "follow") { + midi.sendShortMsg(central, DJCJV.led.beat1, ((pos >= 1) && (pos <= 4)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, ((pos >= 2) && (pos <= 5)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, ((pos >= 3) && (pos <= 6)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, ((pos >= 4) && (pos <= 7)) ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "bounce") { + midi.sendShortMsg(central, DJCJV.led.beat1, ((pos >= 1) && (pos <= 7)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, ((pos >= 2) && (pos <= 6)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, ((pos >= 3) && (pos <= 5)) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, ((pos >= 4) && (pos <= 4)) ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "fill") { + midi.sendShortMsg(central, DJCJV.led.beat1, (pos >= 1) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, (pos >= 2) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, (pos >= 3) ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, (pos >= 4) ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "alternate") { + midi.sendShortMsg(central, DJCJV.led.beat1, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat2, pos === 1 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat3, pos === 2 ? DJCJV.other.on : DJCJV.other.off); + midi.sendShortMsg(central, DJCJV.led.beat4, pos === 2 ? DJCJV.other.on : DJCJV.other.off); + } else if (CFG.user.beatActiveMode === "on") { + midi.sendShortMsg(central, DJCJV.led.beat1, DJCJV.other.on); + midi.sendShortMsg(central, DJCJV.led.beat2, DJCJV.other.on); + midi.sendShortMsg(central, DJCJV.led.beat3, DJCJV.other.on); + midi.sendShortMsg(central, DJCJV.led.beat4, DJCJV.other.on); + } -// Loop section -// SHIFT + Loop ON creates a loop at given point -DJCJV.beatloopActivate = function(channel, control, value, status, group) { - if (!DJCJV.Channel[group].modePressed) { + if (CFG.user.beatDetection !== "follow") { + if (pos >= DJCJV.other.beatMax) { + DJCJV.Channel[group].beatPosition = 1; + } else { + DJCJV.Channel[group].beatPosition = pos + 1; + } + } + }, + // Beat led DEACTIVATE + "beatInactive": function(value, group, _control) { + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.beat1, DJCJV.other.off); + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.beat2, DJCJV.other.off); + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.beat3, DJCJV.other.off); + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.beat4, DJCJV.other.off); + + DJCJV.Channel[group].beatPosition = 1; + }, + // Jogwheels inner LED display - Play position + "wheelInnerUpdate": function(value, group, _control) { + var playPos = value * 127; + midi.sendShortMsg(DJCJV.Channel[group].deck, DJCJV.led.innerJog, playPos); + + // Also update the "track" led + if (engine.getValue(group, "end_of_track")) { + return; // Let Mixxx manage the end_of_track (flashing red) status + } + // Depending on playPos value, turn "track" led to -----------------------------> DJCJV.colour.orange : DJCJV.colour.green + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.track, (playPos > 64) ? DJCJV.colour.orange : DJCJV.colour.green); + }, + // Function to rotate jogs' outer led (borrowed from the 'Pioneer-DDJ-SX-scripts.js' mapping) + "updateJogLeds": function(value, group, control) { + var elapsedTime = value * engine.getValue(group, "duration"); + var wheelPos = ((value >= 0) ? 0 : 127) + 1 + ((DJCJV.other.ledSpeed * elapsedTime) % 127); + + // Only send midi message when the position is actually updated. + if (DJCJV.Channel[group].rotation !== wheelPos) { + midi.sendShortMsg(DJCJV.Channel[group].deck, DJCJV.led.outerJog, wheelPos); // Update the outer (spin) jog leds + DJCJV.wheelInnerUpdate(value, group, control); // Also update the inner jog leds with updated song position + } + DJCJV.Channel[group].rotation = wheelPos; + + if (CFG.user.beatDetection === "follow") { + DJCJV.Channel[group].beatsPassed = Math.round((value * engine.getValue(group, "duration")) * (engine.getValue(group, "bpm") / 60)); + DJCJV.Channel[group].beatPosition = Math.floor((DJCJV.Channel[group].beatsPassed % DJCJV.other.beatMax)) + 1; + + // If on beat_active, update the beat leds + if (engine.getValue(group, "beat_active") || ((engine.getValue(group, "beat_closest") < engine.getValue(group, "beat_next"))) && (!DJCJV.Channel[group].onBeat)) { + DJCJV.beatActive(0, group); + DJCJV.Channel[group].onBeat = true; + } else if (engine.getValue(group, "beat_closest") >= engine.getValue(group, "beat_next")) { + DJCJV.Channel[group].onBeat = false; + } + } + }, + // Vu Meter (has to be scaled x6) + "vuMeterUpdate": function(value, group, _control) { + midi.sendShortMsg(DJCJV.Channel[group].central, DJCJV.led.vuMeter, value * 6); + }, + // Headphone CUE button + "headCue": function(midino, control, value, status, group) { + if (engine.getValue(group, "headMix") === 0) { + engine.setValue(group, "headMix", -1.0); + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headCue, DJCJV.other.off); + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headMix, DJCJV.other.on); + } + }, + // Headphone MIX button + "headMix": function(midino, control, value, status, group) { + if (engine.getValue(group, "headMix") !== 1) { + engine.setValue(group, "headMix", 0); + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headCue, DJCJV.other.on); + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.headMix, DJCJV.other.off); + } + }, + // Filter (AIR FX) + "Filter": function(channel, control, value, status, group) { + var deck = group.substr(18, 10); + // Filter ---------------------------------------> High-pass : Low-pass + var delta = DJCJV.Channel[deck].shiftPressed ? (value / 255) : (-1 * (value / 255)); + engine.setValue(group, "super1", 0.5 + delta); + }, + // Sniff decks' SHIFT presses and store them + "shiftKey": function(channel, control, value, status, group) { + DJCJV.Channel[group].shiftPressed = (value === DJCJV.other.pressed); return value; - } - engine.setValue(group, "beatloop_activate", (value !== 0)); -}; - -// SHIFT+FX(Loop) Knob MOVES the existing loop one beat forward/backward -DJCJV.beatjumpMove = function(channel, control, value, status, group) { - if (value > 64) { - engine.setValue(group, "beatjump_backward", 1); - engine.setValue(group, "beatjump_backward", 0); - } else { - engine.setValue(group, "beatjump_forward", 1); - engine.setValue(group, "beatjump_forward", 0); - } -}; -// FX(Loop) Knob changes the AMOUNT of beats to move the loop when requested -DJCJV.beatjumpSize = function(channel, control, value, status, group) { - var currentValue = engine.getValue(group, "beatjump_size"); - if (value > 64) { - engine.setValue(group, "beatjump_size", currentValue /= 2); - } else { - engine.setValue(group, "beatjump_size", currentValue *= 2); - } -}; - -// The Vinyl button, used to enable or disable scratching on the jog wheels. -DJCJV.vinylButton = function(channel, control, value, _status, _group) { - if (!value) { - return; - } - - if (DJCJV.vinylModeActive) { - DJCJV.vinylModeActive = false; - midi.sendShortMsg(masterLeds, 0x46, off); - } else { - DJCJV.vinylModeActive = true; - midi.sendShortMsg(masterLeds, 0x46, on); - } -}; - -// The pressure action over the jog wheel -DJCJV.wheelTouch = function(channel, control, value, status, group) { - if (value > 0 && (engine.getValue(group, "play") !== 1 || DJCJV.vinylModeActive)) { - engine.scratchEnable(DJCJV.Channel[group].n, 400, speed, alpha, beta); // Touching the wheel - } else { - engine.scratchDisable(DJCJV.Channel[group].n); // Released the wheel - } -}; - -// Using the top of wheel for scratching (Vinyl button On) and bending (Vinyl button Off) -DJCJV.scratchWheel = function(channel, control, value, status, group) { - if (engine.isScratching(DJCJV.Channel[group].n)) { - // Scratch! - if (value >= 64) { - // Backward - engine.scratchTick(DJCJV.Channel[group].n, value - 128); + }, + // Sniff decks' MODE presses and store them + "modeKey": function(channel, control, value, status, group) { + DJCJV.Channel[group].modePressed = (value === DJCJV.other.pressed); + return value; + }, + // MODE + Loop ON creates a loop at given point + "beatloopActivate": function(channel, control, value, status, group) { + // If "MODE" button IS pressed, activate beatloop at current playposition + if (DJCJV.Channel[group].modePressed) { + engine.setValue(group, "slip_enabled", value); + engine.setValue(group, "beatloop_activate", value); } else { - // Forward - engine.scratchTick(DJCJV.Channel[group].n, value); + engine.setValue(group, "reloop_toggle", value); + } + }, + // SHIFT+FX LOOP:SIZE Knob MOVES the existing loop beatjump_size beats forward/backward + "beatjumpMove": function(channel, control, value, status, group) { + // loop_move ---------------------------------------------> backward : forward (times 'beatjump_size') + engine.setValue(group, "loop_move", (value === DJCJV.other.left ? -1 : 1)*engine.getValue(group, "beatjump_size")); + }, + // FX LOOP:SIZE Knob changes the AMOUNT of beats to move the loop when requested (or 'pitch' if MODE is pressed) + "beatjumpSize": function(channel, control, value, status, group) { + if (DJCJV.Channel[group].modePressed) { + // change pith one half -------------------------------> (left) down : (right) up + engine.setValue(group, "pitch_"+(value === DJCJV.other.left ? "down" : "up"), 1); + } else { + // beatjump_size -------------------------------------------------------------------> half : double + var newBJS = engine.getValue(group, "beatjump_size") * ((value === DJCJV.other.left) ? 1/2 : 2); + engine.setValue(group, "beatjump_size", (newBJS >= 512) ? 512 : (newBJS <= 1/32 ? 1/32 : newBJS)); // Prevent moving beyond lower/upper limits + } + }, + // Loop X 1/2 or 2 pressed (halve/double loop, or beatjump backward/forward beatjump_size beats) + "beatLoopChange": function(channel, control, value, status, group) { + if (DJCJV.Channel[group].modePressed) { + // Do a 'beatjump_size' beats beatjump -------------> backward : forward + engine.setValue(group, "beatjump_"+(control === 0 ? "backward" : "forward"), value); + } else { + // Loop size change by ---------------------> halve : double + engine.setValue(group, control === 0 ? "loop_halve" : "loop_double", value); + } + }, + // FX(Mix) Knob changes the mix level for the effects + "mixLevel": function(channel, control, value, status, group) { + var mixValue = engine.getValue(group, "mix"); + // modify effects mix level -------------------------------------------------------------> decrease : increase (in mixGainFactor steps) + engine.setValue(group, "mix", mixValue + (CFG.fine.mixGainFactor * (value === DJCJV.other.left ? -1 : 1))); + }, + // The Vinyl button, used to enable or disable scratching on the jog wheels. + "vinylButton": function(channel, control, value, _status, _group) { + if (!value) { + return; } - } else { - // Pitch bend - DJCJV.bendWheel(channel, control, value, status, group); - } -}; -// Bending by either using the side of wheel, or with the Job surface when not in vinyl-mode -DJCJV.bendWheel = function(channel, control, value, status, group) { - // if scratching engaged, do back/forward spin (keep on scratching while jog wheel moves...) - if (engine.isScratching(DJCJV.Channel[group].n)) { - if (value >= 64) { - // Backward spin - engine.scratchTick(DJCJV.Channel[group].n, -1.5); + DJCJV.other.vinylModeActive = !DJCJV.other.vinylModeActive; + midi.sendShortMsg(DJCJV.led.master, DJCJV.led.vinylMode, DJCJV.other.vinylModeActive ? DJCJV.other.on : DJCJV.other.off); + }, + // The pressure action over the jog wheel + "wheelTouch": function(channel, control, value, status, group) { + if (value > 0 && (engine.getValue(group, "play") !== 1 || DJCJV.other.vinylModeActive)) { + // Start backSpin + if (DJCJV.Channel[group].shiftPressed) { + engine.setValue(group, "reverseroll", DJCJV.other.on); + // spinBack ------------------------------------------------------------------------------------------------> brake_strength, initial_speed + engine.spinback(parseInt(group.substring(8,9)), DJCJV.other.on, engine.getValue(group, "bpm") / CFG.fine.spinBackBrakeFactor, -1 * CFG.fine.spinBackInitialSpeed); + // Start scratch + } else { + // ... with slip mode on + if (DJCJV.Channel[group].modePressed && (engine.getValue(group, "play") === 1)) { + engine.setValue(group, "slip_enabled", DJCJV.other.on); + } + engine.scratchEnable(DJCJV.Channel[group].n, 400, DJCJV.other.speed, DJCJV.other.alpha, DJCJV.other.beta); // Wheel touched + } + // End scratch/backSpin + } else { + if (DJCJV.Channel[group].shiftPressed) { + engine.spinback(parseInt(group.substring(8,9)), DJCJV.other.off); + } + engine.setValue(group, "reverseroll", DJCJV.other.off); + engine.setValue(group, "slip_enabled", DJCJV.other.off); + engine.scratchDisable(DJCJV.Channel[group].n); // Wheel released + } + }, + // Using the top of wheel for scratching (Vinyl button On) and bending (Vinyl button Off) + "scratchWheel": function(channel, control, value, status, group) { + // If NOT playing, and MODE button is pressed, move 'playposition' + if ((DJCJV.Channel[group].modePressed) && ( engine.getValue(group, "play") !== 1 )) { + // new_playposition moves -------------------------------------------------> ( backward : forward ) times CFG.fine.quickMoveFactor + var newpos = engine.getValue(group, "playposition") + ((value === DJCJV.other.left ? -1 : 1 ) * CFG.fine.quickMoveFactor); + // Set playposition to ------------------------> start : end : new_playposition + engine.setValue(group, "playposition", newpos <= 0 ? 0 : (newpos >= 1 ? 1 : newpos)); + } else if (engine.isScratching(DJCJV.Channel[group].n)) { + // Scratch -------------------------------------------------------> Backward : Forward + engine.scratchTick(DJCJV.Channel[group].n, (value === DJCJV.other.left) ? -1 : 1); + } else { + // Goto: DJCJV.bendWheel function + DJCJV.bendWheel(channel, control, value, status, group); + } + }, + // Bending by either using the side of wheel, or with the Jog surface when not in vinyl-mode + "bendWheel": function(channel, control, value, status, group) { + if (engine.isScratching(DJCJV.Channel[group].n)) { + // Spin ----------------------------------------------------------> Backward : Forward + engine.scratchTick(DJCJV.Channel[group].n, (value === DJCJV.other.left) ? -1 : 1); + } else { + // Pitch bend --------------------------------------> Slow Down : Speed Up + engine.setValue(group, "jog", (value === DJCJV.other.left) ? -1 : 1); + } + }, + // Move 'pregain' knob + "preGain": function(channel, control, value, status, group) { + // pregain ------------------------------------------------------------------> decrease : increase + engine.setValue(group, "pregain", engine.getValue(group, "pregain") + ((value > 64 ? -1 : 1) * CFG.fine.mixGainFactor)); + }, + // Load track or toggle quantize + "loadTrack": function(channel, control, value, status, group) { + if (DJCJV.Channel[group].modePressed && value) { + engine.setValue(group, "quantize", ! engine.getValue(group, "quantize")); // Toggle quantize + } else { + engine.setValue(group, "LoadSelectedTrack", value); // Load track + } + }, + // BROWSER knob presses + "moveFocus": function(channel, control, value, status, group) { + if (DJCJV.Channel["[Channel1]"].shiftPressed || DJCJV.Channel["[Channel2]"].shiftPressed) { + engine.setValue(group, "GoToItem", value); // SHIFT pressed: activate (double-click) selected item + } else { + engine.setValue(group, "MoveFocus", value);// Normal press: toggle focus + } + }, + // BROWSER knob rotation: Browse library + "browseLibrary": function(channel, control, value, status, group) { + if (DJCJV.Channel["[Channel1]"].modePressed || DJCJV.Channel["[Channel2]"].modePressed) { + // Quick move -------------------------------------> ( up : down ) times CFG.fine.quickBrowseFactor + engine.setValue(group, "MoveVertical", ((value > 64) ? -1 : 1) * CFG.fine.quickBrowseFactor); } else { - // Forward spin - engine.scratchTick(DJCJV.Channel[group].n, 1.5); + // Slow move -----------------------------> up : down + engine.setValue(group, (value > 64) ? "MoveUp" : "MoveDown", 1); } - } else { - engine.setValue(group, "jog", (value >= 64) ? value - 128 : value); // Pitch bend } }; diff --git a/res/controllers/Hercules_DJControl_Jogvision.midi.xml b/res/controllers/Hercules_DJControl_Jogvision.midi.xml index fe123421f95..6c1cdfc1b42 100644 --- a/res/controllers/Hercules_DJControl_Jogvision.midi.xml +++ b/res/controllers/Hercules_DJControl_Jogvision.midi.xml @@ -152,10 +152,20 @@ [Channel1] - LoadSelectedTrack + DJCJV.loadTrack LOAD A button 0x90 0x51 + + + + + + [Channel1] + eject + EJECT A button + 0x90 + 0x53 @@ -196,22 +206,12 @@ [Channel1] - loop_halve + DJCJV.beatLoopChange Loop Half 0x90 0x00 - - - - - [Channel1] - reloop_toggle - Loop On button - 0x90 - 0x01 - - + @@ -226,12 +226,12 @@ [Channel1] - loop_double + DJCJV.beatLoopChange Loop Double 0x90 0x02 - + @@ -616,10 +616,20 @@ [Channel2] - LoadSelectedTrack + DJCJV.loadTrack LOAD B button 0x91 0x51 + + + + + + [Channel2] + eject + EJECT B button + 0x91 + 0x53 @@ -660,22 +670,12 @@ [Channel2] - loop_halve + DJCJV.beatLoopChange Loop Half 0x91 0x00 - - - - - [Channel2] - reloop_toggle - Loop On button - 0x91 - 0x01 - - + @@ -690,12 +690,12 @@ [Channel2] - loop_double + DJCJV.beatLoopChange Loop Double 0x91 0x02 - + @@ -1039,12 +1039,12 @@ [Library] - MoveFocus + DJCJV.moveFocus Browser button 0x90 0x4F - + @@ -1061,12 +1061,12 @@ [Library] - MoveVertical + DJCJV.browseLibrary Move Vertical (Browser Knob) 0xB0 0x4E - + @@ -1115,12 +1115,12 @@ [Channel1] - pregain + DJCJV.preGain Gain Deck A 0xB0 0x4F - + @@ -1213,7 +1213,7 @@ 0xB0 0x47 - + @@ -1223,7 +1223,7 @@ 0xB0 0x48 - + @@ -1233,17 +1233,17 @@ 0xB0 0x49 - + [EffectRack1_EffectUnit1] - mix + DJCJV.mixLevel Effect Rack 1 - Dry/Wet Level 0xB0 0x46 - + @@ -1292,12 +1292,12 @@ [Channel2] - pregain - Gain Deck A + DJCJV.preGain + Gain Deck B 0xB1 0x4F - + @@ -1390,7 +1390,7 @@ 0xB1 0x47 - + @@ -1400,7 +1400,7 @@ 0xB1 0x48 - + @@ -1410,17 +1410,17 @@ 0xB1 0x49 - + [EffectRack1_EffectUnit2] - mix + DJCJV.mixLevel Effect Rack 1 - Dry/Wet Level 0xB1 0x46 - + From 6d72d6e7b9cd75b7c7659619dc5b7ec9e005de95 Mon Sep 17 00:00:00 2001 From: perseo22 Date: Wed, 23 Sep 2020 08:59:12 +0200 Subject: [PATCH 2/7] Fixed some minor "CodeFactor" issues --- .../Hercules_DJControl_Jogvision-scripts.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/controllers/Hercules_DJControl_Jogvision-scripts.js b/res/controllers/Hercules_DJControl_Jogvision-scripts.js index 57156a387e8..0d726d60551 100644 --- a/res/controllers/Hercules_DJControl_Jogvision-scripts.js +++ b/res/controllers/Hercules_DJControl_Jogvision-scripts.js @@ -218,7 +218,7 @@ var DJCJV = { }, // Finalization "shutdown": function() { - print(id+": finishing..."); + print("Finishing..."); if (DJCJV.other.beatHelpTimer !== 0) { engine.stopTimer(DJCJV.other.beatHelpTimer); DJCJV.other.beatHelpTimer = 0; @@ -452,7 +452,7 @@ var DJCJV = { if (DJCJV.Channel[group].shiftPressed) { engine.setValue(group, "reverseroll", DJCJV.other.on); // spinBack ------------------------------------------------------------------------------------------------> brake_strength, initial_speed - engine.spinback(parseInt(group.substring(8,9)), DJCJV.other.on, engine.getValue(group, "bpm") / CFG.fine.spinBackBrakeFactor, -1 * CFG.fine.spinBackInitialSpeed); + engine.spinback(parseInt(group.substring(8, 9)), DJCJV.other.on, engine.getValue(group, "bpm") / CFG.fine.spinBackBrakeFactor, -1 * CFG.fine.spinBackInitialSpeed); // Start scratch } else { // ... with slip mode on @@ -464,7 +464,7 @@ var DJCJV = { // End scratch/backSpin } else { if (DJCJV.Channel[group].shiftPressed) { - engine.spinback(parseInt(group.substring(8,9)), DJCJV.other.off); + engine.spinback(parseInt(group.substring(8, 9)), DJCJV.other.off); } engine.setValue(group, "reverseroll", DJCJV.other.off); engine.setValue(group, "slip_enabled", DJCJV.other.off); @@ -474,9 +474,9 @@ var DJCJV = { // Using the top of wheel for scratching (Vinyl button On) and bending (Vinyl button Off) "scratchWheel": function(channel, control, value, status, group) { // If NOT playing, and MODE button is pressed, move 'playposition' - if ((DJCJV.Channel[group].modePressed) && ( engine.getValue(group, "play") !== 1 )) { + if ((DJCJV.Channel[group].modePressed) && (engine.getValue(group, "play") !== 1)) { // new_playposition moves -------------------------------------------------> ( backward : forward ) times CFG.fine.quickMoveFactor - var newpos = engine.getValue(group, "playposition") + ((value === DJCJV.other.left ? -1 : 1 ) * CFG.fine.quickMoveFactor); + var newpos = engine.getValue(group, "playposition") + ((value === DJCJV.other.left ? -1 : 1) * CFG.fine.quickMoveFactor); // Set playposition to ------------------------> start : end : new_playposition engine.setValue(group, "playposition", newpos <= 0 ? 0 : (newpos >= 1 ? 1 : newpos)); } else if (engine.isScratching(DJCJV.Channel[group].n)) { From a698973d2e0d7c76af167010afdcba8578b12230 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2020 17:01:31 +0200 Subject: [PATCH 3/7] Update Hercules_DJControl_Jogvision.midi.xml changed the "wiki" tag with the "manual" tag --- res/controllers/Hercules_DJControl_Jogvision.midi.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Hercules_DJControl_Jogvision.midi.xml b/res/controllers/Hercules_DJControl_Jogvision.midi.xml index 6c1cdfc1b42..aeb87ae3216 100644 --- a/res/controllers/Hercules_DJControl_Jogvision.midi.xml +++ b/res/controllers/Hercules_DJControl_Jogvision.midi.xml @@ -5,7 +5,7 @@ DJ Phatso for Hercules Technical Support MIDI Mapping for Hercules DJControl Jogvision https://www.mixxx.org/forums/viewtopic.php?f=7&t=12580 - https://www.mixxx.org/wiki/doku.php/hercules_dj_control_jogvision + hercules_djcontrol_jogvision From 8fdb9cb281dd84e379b3dd99af4e52f46eec6067 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 22 Oct 2020 14:08:07 +0200 Subject: [PATCH 4/7] Libarycontrol: backport of #3198: fix crash when trying to refocus the library while another Mixxx window has focus --- src/library/librarycontrol.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 117273d6d7a..dc1ae985bab 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -405,16 +405,26 @@ void LibraryControl::emitKeyEvent(QKeyEvent&& event) { } void LibraryControl::setLibraryFocus() { - if (m_pSidebarWidget) { - // XXX: Set the focus of the library panel directly instead of sending tab from sidebar - m_pSidebarWidget->setFocus(); - QKeyEvent event(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); - QApplication::sendEvent(m_pSidebarWidget, &event); + // TODO: Set the focus of the library panel directly instead of sending tab from sidebar + VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { + return; + } + // Try to focus the sidebar. + m_pSidebarWidget->setFocus(); + // This may have failed, for example when a Cover window still has focus, + // so make sure the sidebar is focused or we'll crash. + if (!m_pSidebarWidget->hasFocus()) { + return; } + // Send Tab to move focus to the Tracks table. + // Obviously only works as desired if the skin widgets are arranged + // accordingly. + QKeyEvent event(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); + QApplication::sendEvent(m_pSidebarWidget, &event); } void LibraryControl::slotSelectSidebarItem(double v) { - if (m_pSidebarWidget == NULL) { + VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { return; } if (v > 0) { @@ -472,8 +482,8 @@ void LibraryControl::slotGoToItem(double v) { slotToggleSelectedSidebarItem(v); } } - // TODO(xxx) instead of remote control the widgets individual, we should - // translate this into Alt+Return and handle it at each library widget + // TODO(xxx) instead of remote control the widgets individual, we should + // translate this into Alt+Return and handle it at each library widget // individual https://bugs.launchpad.net/mixxx/+bug/1758618 //emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Return, Qt::AltModifier}); } From c5e838cfe379e27af5fed90e904f56458786f930 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 23 Oct 2020 19:29:02 +0200 Subject: [PATCH 5/7] export sampler bank: add xml suffix also when file name contains '.' --- src/mixer/samplerbank.cpp | 53 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/mixer/samplerbank.cpp b/src/mixer/samplerbank.cpp index 54a9983a052..84c67965443 100644 --- a/src/mixer/samplerbank.cpp +++ b/src/mixer/samplerbank.cpp @@ -13,12 +13,18 @@ SamplerBank::SamplerBank(PlayerManager* pPlayerManager) : QObject(pPlayerManager), m_pPlayerManager(pPlayerManager) { DEBUG_ASSERT(m_pPlayerManager); + m_pCOLoadBank = std::make_unique(ConfigKey("[Sampler]", "LoadSamplerBank"), this); - connect(m_pCOLoadBank.get(), SIGNAL(valueChanged(double)), - this, SLOT(slotLoadSamplerBank(double))); + connect(m_pCOLoadBank.get(), + &ControlObject::valueChanged, + this, + &SamplerBank::slotLoadSamplerBank); + m_pCOSaveBank = std::make_unique(ConfigKey("[Sampler]", "SaveSamplerBank"), this); - connect(m_pCOSaveBank.get(), SIGNAL(valueChanged(double)), - this, SLOT(slotSaveSamplerBank(double))); + connect(m_pCOSaveBank.get(), + &ControlObject::valueChanged, + this, + &SamplerBank::slotSaveSamplerBank); m_pCONumSamplers = new ControlProxy(ConfigKey("[Master]", "num_samplers"), this); } @@ -31,11 +37,12 @@ void SamplerBank::slotSaveSamplerBank(double v) { return; } - QString fileFilter = tr("Mixxx Sampler Banks (*.xml)"); - QString samplerBankPath = QFileDialog::getSaveFileName( - NULL, tr("Save Sampler Bank"), + const QString xmlSuffix = QStringLiteral(".xml"); + QString fileFilter = tr("Mixxx Sampler Banks (*%1)").arg(xmlSuffix); + QString samplerBankPath = QFileDialog::getSaveFileName(nullptr, + tr("Save Sampler Bank"), QString(), - tr("Mixxx Sampler Banks (*.xml)"), + fileFilter, &fileFilter); if (samplerBankPath.isNull() || samplerBankPath.isEmpty()) { return; @@ -43,18 +50,15 @@ void SamplerBank::slotSaveSamplerBank(double v) { // Manually add extension due to bug in QFileDialog // via https://bugreports.qt-project.org/browse/QTBUG-27186 - // Can be removed after switch to Qt5 QFileInfo fileName(samplerBankPath); - if (fileName.suffix().isEmpty()) { - QString ext = fileFilter.section(".",1,1); - ext.chop(1); - samplerBankPath.append(".").append(ext); + if (fileName.suffix().isEmpty() || !fileName.suffix().endsWith(xmlSuffix)) { + samplerBankPath.append(xmlSuffix); } if (!saveSamplerBankToPath(samplerBankPath)) { - QMessageBox::warning(NULL, - tr("Error Saving Sampler Bank"), - tr("Could not write the sampler bank to '%1'.") + QMessageBox::warning(nullptr, + tr("Error Saving Sampler Bank"), + tr("Could not write the sampler bank to '%1'.") .arg(samplerBankPath)); } } @@ -65,7 +69,7 @@ bool SamplerBank::saveSamplerBankToPath(const QString& samplerBankPath) { // folder. We don't need access to this file on a regular basis so we do not // register a security bookmark. - VERIFY_OR_DEBUG_ASSERT(m_pPlayerManager != nullptr) { + VERIFY_OR_DEBUG_ASSERT(m_pPlayerManager) { qWarning() << "SamplerBank::saveSamplerBankToPath called with no PlayerManager"; return false; } @@ -84,7 +88,7 @@ bool SamplerBank::saveSamplerBankToPath(const QString& samplerBankPath) { for (unsigned int i = 0; i < m_pPlayerManager->numSamplers(); ++i) { Sampler* pSampler = m_pPlayerManager->getSampler(i + 1); - if (pSampler == NULL) { + if (!pSampler) { continue; } QDomElement samplerNode = doc.createElement(QString("sampler")); @@ -112,8 +116,7 @@ void SamplerBank::slotLoadSamplerBank(double v) { return; } - QString samplerBankPath = QFileDialog::getOpenFileName( - NULL, + QString samplerBankPath = QFileDialog::getOpenFileName(nullptr, tr("Load Sampler Bank"), QString(), tr("Mixxx Sampler Banks (*.xml)")); @@ -122,10 +125,10 @@ void SamplerBank::slotLoadSamplerBank(double v) { } if (!loadSamplerBankFromPath(samplerBankPath)) { - QMessageBox::warning(NULL, - tr("Error Reading Sampler Bank"), - tr("Could not open the sampler bank file '%1'.") - .arg(samplerBankPath)); + QMessageBox::warning(nullptr, + tr("Error Reading Sampler Bank"), + tr("Could not open the sampler bank file '%1'.") + .arg(samplerBankPath)); } } @@ -135,7 +138,7 @@ bool SamplerBank::loadSamplerBankFromPath(const QString& samplerBankPath) { // folder. We don't need access to this file on a regular basis so we do not // register a security bookmark. - VERIFY_OR_DEBUG_ASSERT(m_pPlayerManager != nullptr) { + VERIFY_OR_DEBUG_ASSERT(m_pPlayerManager) { qWarning() << "SamplerBank::loadSamplerBankFromPath called with no PlayerManager"; return false; } From 297f393472ce2bcebbe7995b87feec253b0b2daa Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 25 Oct 2020 16:30:21 +0100 Subject: [PATCH 6/7] Hercules DJControl Jogvision: Fix some codespell typos --- res/controllers/Hercules_DJControl_Jogvision-scripts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/controllers/Hercules_DJControl_Jogvision-scripts.js b/res/controllers/Hercules_DJControl_Jogvision-scripts.js index 0d726d60551..b91aa325618 100644 --- a/res/controllers/Hercules_DJControl_Jogvision-scripts.js +++ b/res/controllers/Hercules_DJControl_Jogvision-scripts.js @@ -5,7 +5,7 @@ // * Version: 1.25 (see changelog at https://github.com/mixxxdj/mixxx/pull/2370) // EXTRAS ADDED TO ORIGINAL MAPPING -// - MODE+Loop ON : set a loop_in mark (with curently defined loop_size), activate it, and enable slip mode +// - MODE+Loop ON : set a loop_in mark (with currently defined loop_size), activate it, and enable slip mode // - MODE+Loop X 1/2 / X 2 : do a 'beatjump_size' beats beatjump backward/forward // - MODE+Loop Size Knob : decrease/increase pitch (only key, not tempo!) // - MODE+JogWheel plate (playing) : scratch with 'Slip' ON (deactivate 'Slip' when plate is released) @@ -29,7 +29,7 @@ var CFG = { "beatHelper": 1, // 0|1 (0=disabled; 1=use outer jog leg to indicate where to slide the pitch controller; see also 'CFG.fine.beatHelpSensitivity' variable) "beatActiveMode": "follow" // normal|reverse|blink|follow|fill|bounce|alternate|off|on (see table below) }, - // FINE TUNNING Variables (you may modify them, but not too far from the default values...) + // FINE TUNING Variables (you may modify them, but not too far from the default values...) "fine": { "beatHelpSensitivity": 0.5, // [0..1) Max distance (float) in BPMs to be considered as a match (in use if CFG.user.beatHelper=1): the lower, the more sensitive. Values equal or bigger than 1 are reset to 0.9 and a warning is printed "quickMoveFactor": 0.002, // [0..1] the smaller (float), the slower MODE+JogWheel will move 'playposition' (when such channel is NOT playing) From face6c873714be4ba638b09fd14632a2993f8198 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 29 Oct 2020 16:48:07 +0100 Subject: [PATCH 7/7] changelog: fix library focus crash #3201 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 93fc56e0b27..21dae471d8e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ * Prevent moving a loop beyond track end #3117 lp:1799574 * Use 6 instead of only 4 compatible musical keys (major/minor) #3205 * Fix possible memory corruption using JACK on Linux +* Fix possible crash when trying to refocus the tracks table while another Mixxx window has focus #3201 ==== 2.2.4 2020-05-10 ====