diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index fea38f8b2e4..9381cb9365e 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -32,6 +32,13 @@ var MC7000 = {}; // USER VARIABLES BEGIN // ///////////////////////////////////*/ +// switch on experimental features +// experimental features are: +// 1) Beat LED in Slicer mode (counts every 8 beats AFTER the CUE point) +// only works for CONSTANT TEMPO tracks +// needs beat grid and CUE point set +MC7000.experimental = false; + // Wanna have Needle Search active while playing a track ? // In any case Needle Search is available holding "SHIFT" down. // can be true or false (recommended: false) @@ -61,18 +68,18 @@ MC7000.VinylModeOn = true; // default: true // Scratch algorithm parameters MC7000.scratchParams = { - recordSpeed: 33.3, // default: 33.3 + recordSpeed: 33 + 1/3, // default: 33 + 1/3 alpha: (1.0/10), // default: (1.0/10) beta: (1.0/10)/32 // default: (1.0/10)/32 }; -// Sensitivity of the jog wheel (also depends on audio latency) -MC7000.jogParams = { - // Sensitivity factor (0.5 for half, 2 for double sensitivity) - jogSensitivity: 1, // default: 1 - // this will limit the parameter of "jog" (keep between 0.5 and 3) - maxJogValue: 3 // default: 3 -}; +// Sensitivity factor of the jog wheel (also depends on audio latency) +// 0.5 for half, 2 for double sensitivity - Recommendation: +// set to 0.5 with audio buffer set to 50ms +// set to 1 with audio buffer set to 25ms +// set to 3 with audio buffer set to 5ms + +MC7000.jogSensitivity = 1; // default: 1.0 with audio buffer set to 23ms /*///////////////////////////////// // USER VARIABLES END // @@ -99,7 +106,7 @@ MC7000.currentRateRangeIndex = [0, 0, 0, 0]; // initialize the "factor" function for Spinback MC7000.factor = []; -//Set Shift button state to false for default +//Set Shift button state to false as default MC7000.shift = [false, false, false, false]; // initialize the PAD Mode to Hot Cue and all others off when starting @@ -114,32 +121,46 @@ MC7000.PADModeSampler = [false, false, false, false]; MC7000.PADModeVelSamp = [false, false, false, false]; MC7000.PADModePitch = [false, false, false, false]; -// Beatloop Roll sizes +// PAD Mode 'Beatloop Roll' sizes MC7000.beatLoopRoll = [1 / 16, 1 / 8, 1 / 4, 1 / 2, 1, 2, 4, 8]; +// PAD Mode - 'Fixed Loop' sizes +MC7000.fixedLoop = [1, 2, 4, 8, 16, 32, 64, 128]; + +// PAD Mode - 'Beatjump' sizes +MC7000.beatJump = [1, 2, 4, 8, 16, 32, 64, 128]; + // Define the MIDI signal for red LED at VU Meters MC7000.VuMeterLEDPeakValue = 0x76; +// initialize variables to compare LED status for VU, Jog and PAD LEDs +MC7000.prevVuLevel = [0, 0, 0, 0]; +MC7000.prevJogLED = [0, 0, 0, 0]; +MC7000.prevPadLED = [0, 0, 0, 0]; + // PAD Mode Colors MC7000.padColor = { "alloff": 0x01, // switch off completely // Hot Cue - "hotcueon": 0x04, // darkblue Hot Cue active - "hotcueoff": 0x02, // lightblue Hot Cue inactive + "hotcueon": 0x04, // blue Hot Cue active + "hotcueoff": 0x02, // dark blue Hot Cue inactive // Cue Loop - "cueloopon": 0x0D, // Cueloop colour for activated cue point - "cueloopoff": 0x1A, // Cueloop colour inactive + "cueloopon": 0x0D, // green Cueloop colour for activated cue point + "cueloopoff": 0x1A, // dark green Cueloop colour inactive // Roll - "rollon": 0x20, // BeatloopRoll active colour - "rolloff": 0x06, // BeatloopRoll off colour + "rollon": 0x20, // cyan BeatloopRoll active colour + "rolloff": 0x06, // dark cyan BeatloopRoll off colour + // Saved Loop + "fixedloopon": 0x3D, // yellow Saved Loop active + "fixedloopoff": 0x15, // dark yellow Saved Loop active // Slicer - "sliceron": 0x11, // activated Slicer - "slicerJumpFwd": 0x31, // Sliver forward jump - "slicerJumpBack": 0x31, // Sliver backward jump + "sliceron": 0x11, // dark red activated Slicer + "slicerJumpFwd": 0x31, // red Sliver forward jump + "slicerJumpBack": 0x31, // red Sliver backward jump // Sampler - "samplerloaded": 0x38, // dark pink Sampler loaded colour + "samplerloaded": 0x38, // pink Sampler loaded colour "samplerplay": 0x09, // green Sampler playing - "sampleroff": 0x12 // light pink Sampler standard colour + "sampleroff": 0x12 // dark pink Sampler standard colour }; /* DECK INITIALIZATION */ @@ -158,15 +179,17 @@ MC7000.init = function() { engine.makeConnection("[Channel3]", "VuMeter", MC7000.VuMeter); engine.makeConnection("[Channel4]", "VuMeter", MC7000.VuMeter); - // Platter Ring LED + // Platter Ring LED mode midi.sendShortMsg(0x90, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x91, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x92, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x93, 0x64, MC7000.modeSingleLED); - engine.makeConnection("[Channel1]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel2]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel3]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel4]", "playposition", MC7000.JogLed); + + // Track Position LEDs for Jog Wheel and Slicer + engine.makeConnection("[Channel1]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel2]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel3]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel4]", "playposition", MC7000.TrackPositionLEDs); // Vinyl mode LEDs midi.sendShortMsg(0x90, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); @@ -188,8 +211,8 @@ MC7000.init = function() { engine.makeConnection("[Sampler"+i+"]", "play", MC7000.SamplerLED); } - // Activate Timer for Controller Status SysEx to avoid conflicts with Softtakeover - engine.beginTimer(3000, MC7000.delayedSysEx, true); + // send Controller Status SysEx message delayed to avoid conflicts with Softtakeover + engine.beginTimer(2000, MC7000.delayedSysEx, true); }; // SysEx message to receive all knob and fader positions @@ -215,6 +238,7 @@ MC7000.samplerLevel = function(channel, control, value) { // PAD Mode Hot Cue MC7000.padModeCue = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -232,9 +256,9 @@ MC7000.padModeCue = function(channel, control, value, status, group) { // change PAD color when switching to Hot Cue Mode for (var i = 1; i <= 8; i++) { if (engine.getValue(group, "hotcue_" + i + "_enabled", true)) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueon); } else { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueoff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueoff); } } }; @@ -242,6 +266,7 @@ MC7000.padModeCue = function(channel, control, value, status, group) { // PAD Mode Cue Loop MC7000.padModeCueLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -257,14 +282,15 @@ MC7000.padModeCueLoop = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Flip MC7000.padModeFlip = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -280,14 +306,15 @@ MC7000.padModeFlip = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i, MC7000.padColor.alloff); } }; // PAD Mode Roll MC7000.padModeRoll = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -303,14 +330,15 @@ MC7000.padModeRoll = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // change PAD color when switching to Roll Mode - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.rolloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rolloff); } }; // PAD Mode Saved Loop MC7000.padModeSavedLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -325,15 +353,17 @@ MC7000.padModeSavedLoop = function(channel, control, value, status, group) { MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + // change PAD color when switching to Saved Loop Mode + for (var i = 0; i < 8; i++) { + var activeLED = engine.getValue(group, "beatloop_" + MC7000.fixedLoop[i] + "_enabled") ? MC7000.padColor.fixedloopon : MC7000.padColor.fixedloopoff; + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, activeLED); } }; // PAD Mode Slicer MC7000.padModeSlicer = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -349,14 +379,15 @@ MC7000.padModeSlicer = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // change PAD color when switching to Slicer Mode - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.sliceron); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.sliceron); } }; // PAD Mode Slicer Loop MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -372,14 +403,15 @@ MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Sampler MC7000.padModeSampler = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -397,11 +429,11 @@ MC7000.padModeSampler = function(channel, control, value, status, group) { // change PAD color when switching to Sampler Mode for (var i = 1; i <= 8; i++) { if (engine.getValue("[Sampler" + i + "]", "play")) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.samplerplay); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.samplerplay); } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.sampleroff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.sampleroff); } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 1 && engine.getValue("[Sampler" + i + "]", "play") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.samplerloaded); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.samplerloaded); } } }; @@ -409,6 +441,7 @@ MC7000.padModeSampler = function(channel, control, value, status, group) { // PAD Mode Velocity Sampler MC7000.padModeVelSamp = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -424,14 +457,15 @@ MC7000.padModeVelSamp = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Pitch MC7000.padModePitch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -447,19 +481,21 @@ MC7000.padModePitch = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = true; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i, MC7000.padColor.alloff); } }; // PAD buttons MC7000.PadButtons = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; + var i, j; // activate and clear Hot Cues if (MC7000.PADModeCue[deckNumber] && engine.getValue(group, "track_loaded") === 1) { - for (var i = 1; i <= 8; i++) { + for (i = 1; i <= 8; i++) { if (control === 0x14 + i - 1 && value >= 0x01) { engine.setValue(group, "hotcue_" + i + "_activate", true); } else { @@ -467,7 +503,7 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } if (control === 0x1C + i - 1 && value >= 0x01) { engine.setValue(group, "hotcue_" + i + "_clear", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.hotcueoff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.hotcueoff); } } } else if (MC7000.PADModeCueLoop[deckNumber]) { @@ -475,30 +511,48 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } else if (MC7000.PADModeFlip[deckNumber]) { return; } else if (MC7000.PADModeRoll[deckNumber]) { - // todo: - // check for actual beatloop_size and apply back after a PAD Roll + // TODO(all): check for actual beatloop_size and apply back after a PAD Roll i = control - 0x14; if (control === 0x14 + i && value > 0x00) { engine.setValue(group, "beatlooproll_" + MC7000.beatLoopRoll[i] + "_activate", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i, MC7000.padColor.rollon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rollon); } else if (control === 0x14 + i && value === 0x00) { engine.setValue(group, "beatlooproll_activate", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i, MC7000.padColor.rolloff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rolloff); } } else if (MC7000.PADModeSavedLoop[deckNumber]) { - return; + if (value === 0x00) { + return; // don't respond to note off messages + } + i = control - 0x14; + engine.setValue(group, "beatloop_" + MC7000.fixedLoop[i] + "_toggle", true); + for (j =0; j < 8; j++) { + var activeLED = engine.getValue(group, "beatloop_" + MC7000.fixedLoop[j] + "_enabled") ? MC7000.padColor.fixedloopon : MC7000.padColor.fixedloopoff; + midi.sendShortMsg(0x94 + deckOffset, 0x14 + j, activeLED); + } } else if (MC7000.PADModeSlicer[deckNumber]) { if (value > 0) { - var beats = 1 << (control % 4); - if (control > 0x17) { - engine.setValue(group, "beatjump_" + beats + "_backward", value); - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.slicerJumpBack); - } else { - engine.setValue(group, "beatjump_" + beats + "_forward", value); - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.slicerJumpFwd); + i = control - 0x14; // unshifted button + j = control - 0x1C; // shifted button + // forward buttons (PAD buttons upper row) + if (control >= 0x14 && control <= 0x17) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[i] + "_forward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpFwd); + // backward buttons (PAD buttons lower row) + } else if (control >= 0x18 && control <= 0x1B) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[i - 4] + "_backward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpBack); + // forward buttons (PAD buttons upper row - shifted controls) + } else if (control >= 0x1C && control <= 0x1F) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[j + 4] + "_forward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpFwd); + // backward buttons (PAD buttons lower row - shifted controls) + } else if (control >= 0x20 && control <= 0x23) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[j] + "_backward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpBack); } } else { - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.sliceron); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.sliceron); } } else if (MC7000.PADModeSlicerLoop[deckNumber]) { return; @@ -513,10 +567,10 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } else if (control === 0x1C + i - 1 && value >= 0x01) { if (engine.getValue("[Sampler" + i + "]", "play") === 1) { engine.setValue("[Sampler" + i + "]", "cue_gotoandstop", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.samplerloaded); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.samplerloaded); } else { engine.setValue("[Sampler" + i + "]", "eject", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.sampleroff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.sampleroff); engine.setValue("[Sampler" + i + "]", "eject", 0); } } @@ -528,12 +582,12 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } }; -// Toggle Shift Button +// Shift Button MC7000.shiftButton = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - MC7000.shift[deckNumber - 1] = ! MC7000.shift[deckNumber - 1]; - midi.sendShortMsg(0x90 + deckNumber - 1, 0x32, - MC7000.shift[deckNumber - 1] ? 0x7F : 0x01); + var deckOffset = script.deckFromGroup(group) - 1; + MC7000.shift[deckOffset] = ! MC7000.shift[deckOffset]; + midi.sendShortMsg(0x90 + deckOffset, 0x32, + MC7000.shift[deckOffset] ? 0x7F : 0x01); }; // Toggle Vinyl Mode @@ -541,10 +595,10 @@ MC7000.vinylModeToggle = function(channel, control, value, status, group) { if (value === 0x00) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); - MC7000.isVinylMode[deckNumber - 1] = !MC7000.isVinylMode[deckNumber - 1]; - midi.sendShortMsg(0x90 + deckNumber - 1, 0x07, - MC7000.isVinylMode[deckNumber - 1] ? 0x7F : 0x01); + var deckOffset = script.deckFromGroup(group) - 1; + MC7000.isVinylMode[deckOffset] = !MC7000.isVinylMode[deckOffset]; + midi.sendShortMsg(0x90 + deckOffset, 0x07, + MC7000.isVinylMode[deckOffset] ? 0x7F : 0x01); }; // Use select button to load and eject track from deck @@ -585,7 +639,8 @@ MC7000.loadButton = function(channel, control, value, status, group) { // The button that enables/disables scratching MC7000.wheelTouch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); - if (MC7000.isVinylMode[deckNumber - 1]) { + var deckOffset = deckNumber - 1; + if (MC7000.isVinylMode[deckOffset]) { if (value === 0x7F) { engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, @@ -599,22 +654,27 @@ MC7000.wheelTouch = function(channel, control, value, status, group) { // The wheel that actually controls the scratching MC7000.wheelTurn = function(channel, control, value, status, group) { + // TODO(all): check for latency and use it to normalize the jog factor so jog wont be + // depending on audio latency anymore. + // A: For a control that centers on 0: var numTicks = (value < 0x64) ? value : (value - 128); + var adjustedSpeed = numTicks * MC7000.jogSensitivity * 25; var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (engine.isScratching(deckNumber)) { // Scratch! - engine.scratchTick(deckNumber, numTicks); + engine.scratchTick(deckNumber, numTicks * MC7000.jogSensitivity); } else { - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { // While Shift Button pressed -> Search through track - var jogSearch = 5000 * numTicks / MC7000.jogWheelTicksPerRevolution * MC7000.jogParams.jogSensitivity; + var jogSearch = 300 * adjustedSpeed / MC7000.jogWheelTicksPerRevolution; engine.setValue(group, "jog", jogSearch); } else { // While Shift Button released -> Pitch Bend - var jogDelta = numTicks / MC7000.jogWheelTicksPerRevolution * MC7000.jogParams.jogSensitivity * 30; + var jogDelta = adjustedSpeed / MC7000.jogWheelTicksPerRevolution; var jogAbsolute = jogDelta + engine.getValue(group, "jog"); - engine.setValue(group, "jog", Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + engine.setValue(group, "jog", jogAbsolute); } } }; @@ -646,7 +706,7 @@ MC7000.needleSearchStripPosition = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (MC7000.needleSearchTouched[deckNumber]) { - var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary gigits to the left and add LSB + var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary digits to the left and add LSB var position = (fullValue / 0x3FFF); // divide by all possible positions to get relative between 0 - 1 engine.setParameter(group, "playposition", position); } @@ -669,13 +729,13 @@ MC7000.nextRateRange = function(midichan, control, value, status, group) { if (value === 0) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // increment currentRateRangeIndex and check for overflow - if (++MC7000.currentRateRangeIndex[deckNumber - 1] === + if (++MC7000.currentRateRangeIndex[deckOffset] === MC7000.rateRanges.length) { - MC7000.currentRateRangeIndex[deckNumber - 1] = 0; + MC7000.currentRateRangeIndex[deckOffset] = 0; } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckOffset]]); }; // Previous Rate range toggle @@ -683,19 +743,19 @@ MC7000.prevRateRange = function(midichan, control, value, status, group) { if (value === 0) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // decrement currentRateRangeIndex and check for underflow - if (--MC7000.currentRateRangeIndex[deckNumber - 1] < 0) { - MC7000.currentRateRangeIndex[deckNumber - 1] = MC7000.rateRanges.length - 1; + if (--MC7000.currentRateRangeIndex[deckOffset] < 0) { + MC7000.currentRateRangeIndex[deckOffset] = MC7000.rateRanges.length - 1; } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckOffset]]); }; // Key & Waveform zoom Select MC7000.keySelect = function(midichan, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // While Shift Button is pressed: Waveform Zoom - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { if (value === 0x7F) { script.triggerControl(group, "waveform_zoom_up", 100); } else { @@ -713,12 +773,12 @@ MC7000.keySelect = function(midichan, control, value, status, group) { // Key & Waveform zoom Reset MC7000.keyReset = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; if (value === 0x00) { return; } // While Shift Button is pressed: Waveform Zoom Reset - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { script.triggerControl(group, "waveform_zoom_set_default", 100); // While Shift Button is released: Key Reset } else { @@ -746,12 +806,12 @@ MC7000.stopTime = function(channel, control, value, status, group) { MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; }; -// Use the CENSOR button as Spinback with STOP TIME adjusted length +// Use SHIFT + CENSOR button as Spinback with STOP TIME adjusted length MC7000.reverse = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value > 0) { // while the button is pressed spin back - engine.brake(deckNumber, true, MC7000.factor[deckNumber], -15); // start at a rate of -15 and decrease by "factor" + engine.brake(deckNumber, true, MC7000.factor[deckNumber], - 15); // start at a rate of -15 and decrease by "factor" } else { // when releasing the button the track starts softly again engine.softStart(deckNumber, true, MC7000.factor[deckNumber]); @@ -784,7 +844,7 @@ MC7000.crossFaderCurve = function(control, value) { // Set FX wet/dry value MC7000.fxWetDry = function(channel, control, value, status, group) { - var numTicks = (value < 0x64) ? value: (value - 128); + var numTicks = (value < 0x64) ? value : (value - 128); var newVal = engine.getValue(group, "mix") + numTicks/64*2; engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); }; @@ -815,40 +875,86 @@ MC7000.sortLibrary = function(channel, control, value) { /* LEDs for VuMeter */ // VuMeters only for Channel 1-4 / Master is on Hardware MC7000.VuMeter = function(value, group) { - var deckNumber = script.deckFromGroup(group), - vuLevelOutValue = engine.getValue(group, "PeakIndicator") ? MC7000.VuMeterLEDPeakValue : Math.pow(value, 4) * (MC7000.VuMeterLEDPeakValue - 1); - - midi.sendShortMsg(0xB0 + deckNumber - 1, 0x1F, vuLevelOutValue); + var deckOffset = script.deckFromGroup(group) - 1; + // sends either PeakIndicator or scales value (0..1) to (0..117) while truncating to each LED + var vuLevelOutValue = engine.getValue(group, "PeakIndicator") ? MC7000.VuMeterLEDPeakValue : Math.floor(Math.pow(value, 2.5) * 9) * 13; + // only send Midi signal when LED value has changed + if (MC7000.prevVuLevel[deckOffset] !== vuLevelOutValue) { + midi.sendShortMsg(0xB0 + deckOffset, 0x1F, vuLevelOutValue); + MC7000.prevVuLevel[deckOffset] = vuLevelOutValue; + } }; -/* LEDs around Jog wheel */ -MC7000.JogLed = function(value, group) { +// Spinning Platter LEDs & Slicer Loop PAD LEDs as beat counter +// pulled together for the calculation to be done only once and then +// send LED signals to Jog or PAD LEDs when needed. +MC7000.TrackPositionLEDs = function(value, group) { // do nothing before track starts if (value === 0) { return; } - var deckNumber = script.deckFromGroup(group), - trackDuration = engine.getValue(group, "duration"), - position = value * trackDuration / 60 * MC7000.scratchParams.recordSpeed, - // LED ring contains 48 segments with each LED activated by the next even number - jogLedOutValue = 48 * 2, - activeLED = MC7000.isVinylMode[deckNumber - 1] ? Math.round(position * jogLedOutValue) % jogLedOutValue : value * jogLedOutValue; + // lets define some variables first + var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; + var trackDuration = engine.getValue(group, "duration"); // in seconds + var beatLength = engine.getValue(group, "file_bpm") / 60; // in Beats Per Seconds + var cuePosition = engine.getValue(group, "cue_point") / engine.getValue(group, "track_samplerate") / 2; // in seconds + var playPosition = value * trackDuration; // in seconds + var jogLEDPosition = playPosition / 60 * MC7000.scratchParams.recordSpeed; + var jogLEDNumber = 48; // LED ring contains 48 segments each triggered by the next even Midi value + // check for Vinyl Mode and decide to spin the Jog LED or show play position + var activeJogLED = MC7000.isVinylMode[deckOffset] ? Math.round(jogLEDPosition * jogLEDNumber) % jogLEDNumber : Math.round(value * jogLEDNumber); + // count the beats (1 to 8) after the CUE point + var beatCountLED = (Math.floor((playPosition - cuePosition) * beatLength) % 8); //calculate PAD LED position + + // TODO(all): check for playposition < (trackduration - warning length) for sending position signals + // check if a Jog LED has changed and if so then send the signal to the next Jog LED + if (MC7000.prevJogLED[deckOffset] !== activeJogLED) { + midi.sendShortMsg(0x90 + deckOffset, 0x06, activeJogLED * 2); // only each 2nd midi signal triggers the next LED + MC7000.prevJogLED[deckOffset] = activeJogLED; + } + // TODO(all): else blink the platter LEDs - midi.sendShortMsg(0x90 + deckNumber - 1, 0x06, activeLED); + // check if Slicer mode is active and illuminate PAD LEDs counting with the beat while playing + if (!MC7000.experimental) { + return; + } + if (MC7000.PADModeSlicer[deckNumber]) { + // only send new LED status when beatCountLED really changes + if (MC7000.prevPadLED[deckOffset] !== beatCountLED) { + // first set all LEDs to default color incl shifted + for (var i = 0; i < 16; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.sliceron); + } + // now chose which PAD LED to turn on (+8 means shifted PAD LEDs) + if (beatCountLED === 0) { + midi.sendShortMsg(0x94 + deckOffset, 0x14, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + 8, MC7000.padColor.hotcueon); + } else if (beatCountLED === 7) { + midi.sendShortMsg(0x94 + deckOffset, 0x1B, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x1B + 8, MC7000.padColor.hotcueon); + } else if (beatCountLED > 0 && beatCountLED < 7) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + beatCountLED, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + 8 + beatCountLED, MC7000.padColor.hotcueon); + } + } + MC7000.prevPadLED[deckOffset] = beatCountLED; + } }; // initial HotCue LED when loading a track with already existing hotcues MC7000.HotCueLED = function(value, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (MC7000.PADModeCue[deckNumber]) { for (var i = 1; i <= 8; i++) { if (value === 1) { if (engine.getValue(group, "hotcue_"+i+"_enabled") === 1) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueon); } } else { if (engine.getValue(group, "hotcue_"+i+"_enabled") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueoff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueoff); } } }