function NumarkTotalControl() {} NumarkTotalControl.init = function(id) { // called when the MIDI device is opened & set up NumarkTotalControl.id = id; // Store the ID of this device for later use NumarkTotalControl.directoryMode = false; NumarkTotalControl.scratchMode = false; NumarkTotalControl.scratchTimer = [-1, -1]; NumarkTotalControl.simpleCue = false; NumarkTotalControl.extendedLooping = false; NumarkTotalControl.oldLoopStart = [-1, -1]; NumarkTotalControl.extendedLoopingType = { "None": 0, "SetBegin": 1, "SetLength": 2 }; NumarkTotalControl.extendedLoopingState = [NumarkTotalControl.extendedLoopingType.None, NumarkTotalControl.extendedLoopingType.None]; NumarkTotalControl.extendedLoopingChanged = [false, false]; NumarkTotalControl.extendedLoopingLEDState = [false, false]; NumarkTotalControl.extendedLoopingLEDTimer = [-1, -1]; NumarkTotalControl.extendedLoopingJogCarryOver = [0, 0]; NumarkTotalControl.quantizeLEDState = false; NumarkTotalControl.quantizeLEDTimer = -1; NumarkTotalControl.leds = [ // Common { "directory": 0x56, "simpleCue": 0x33, "scratchMode": 0x31, "extendedLooping": 0x44, "quantize": 0x45 }, // Deck 1 { "rate": 0x34, "tap": 0x30, "loopIn": 0x3a, "loopOut": 0x3b, "loopHalve": 0x38, "loopDouble": 0x39 }, // Deck 2 { "rate": 0x43, "tap": 0x47, "loopIn": 0x4a, "loopOut": 0x4b, "loopHalve": 0x48, "loopDouble": 0x49 } ]; // Doesn't work ?!? engine.softTakeover("[Channel1]", "rate", true); engine.softTakeover("[Channel2]", "rate", true); NumarkTotalControl.setLED(NumarkTotalControl.leds[1]["rate"], true); // Turn on 0 rate lights NumarkTotalControl.setLED(NumarkTotalControl.leds[2]["rate"], true); // Turn on 0 rate lights engine.connectControl("[Channel1]", "loop_enabled", "NumarkTotalControl.loopLEDs"); engine.connectControl("[Channel2]", "loop_enabled", "NumarkTotalControl.loopLEDs"); engine.connectControl("[Channel1]", "quantize", "NumarkTotalControl.quantizeLED"); engine.connectControl("[Channel2]", "quantize", "NumarkTotalControl.quantizeLED"); } NumarkTotalControl.shutdown = function(id) { // called when the MIDI device is closed engine.connectControl("[Channel1]", "loop_enabled", "NumarkTotalControl.loopLEDs", true); engine.connectControl("[Channel2]", "loop_enabled", "NumarkTotalControl.loopLEDs", true); var lowestLED = 0x30; var highestLED = 0x56; for (var i=lowestLED; i<=highestLED; i++) { NumarkTotalControl.setLED(i, false); // Turn off all the lights } } NumarkTotalControl.groupToDeck = function(group) { var matches = group.match(/^\[Channel(\d+)\]$/); if (matches == null) { return -1; } else { return matches[1]; } } NumarkTotalControl.samplesPerBeat = function(group) { var sampleRate = engine.getValue(group, "track_samplerate"); // FIXME: Get correct channel count for current deck var channels = 2; var bpm = engine.getValue(group, "file_bpm"); return channels * sampleRate * 60 / bpm; } NumarkTotalControl.setLED = function(value, status) { if (status) { status = 0x64; } else { status = 0x00; } midi.sendShortMsg(0x90, value, status); } NumarkTotalControl.selectKnob = function(channel, control, value, status, group) { if (value > 63) { value = value - 128; } if (NumarkTotalControl.directoryMode) { if (value > 0) { for (var i = 0; i < value; i++) { engine.setValue(group, "SelectNextPlaylist", 1); } } else { for (var i = 0; i < -value; i++) { engine.setValue(group, "SelectPrevPlaylist", 1); } } } else { engine.setValue(group, "SelectTrackKnob", value); } } NumarkTotalControl.loopIn = function(channel, control, value, status, group) { if (value) { if (engine.getValue(group, "loop_enabled")) { engine.setValue(group, "reloop_exit", 1); } engine.setValue(group, "loop_in", 1); engine.setValue(group, "loop_end_position", -1); } } NumarkTotalControl.loopOut = function(channel, control, value, status, group) { if (value) { var start = engine.getValue(group, "loop_start_position"); var end = engine.getValue(group, "loop_end_position"); if (start != -1) { if (end != -1) { // Loop In and Out set -> call Reloop/Exit engine.setValue(group, "reloop_exit", 1); } else { // Loop In set -> call Loop Out if (NumarkTotalControl.extendedLooping) { var deck = NumarkTotalControl.groupToDeck(group); if (NumarkTotalControl.oldLoopStart[deck-1] == -1) { // Get current position by temporary setting loop start NumarkTotalControl.oldLoopStart[deck-1] = start; engine.setValue(group, "loop_in", 1); engine.beginTimer(20, "NumarkTotalControl.loopExtendedAdjustment('" + group + "')", true); } } else { engine.setValue(group, "loop_out", 1); } } } } } // Adjust loop length NumarkTotalControl.loopExtendedAdjustment = function(group) { var deck = NumarkTotalControl.groupToDeck(group); // Check if temporary loop start is already set var start = engine.getValue(group, "loop_start_position"); if (start == NumarkTotalControl.oldLoopStart[deck-1]) { // Still old loop start -> retry later engine.beginTimer(20, "NumarkTotalControl.loopExtendedAdjustment('" + group + "')", true); return; } // Restore loop start position var currentPosition = start; start = NumarkTotalControl.oldLoopStart[deck-1]; engine.setValue(group, "loop_start_position", start); NumarkTotalControl.oldLoopStart[deck-1] = -1; var len = currentPosition - start; // Calculate nearest beat var beatSamples = NumarkTotalControl.samplesPerBeat(group); var lenInBeats = len / beatSamples; if (lenInBeats > 1) { //print("Full: " + Math.ceil(lenInBeats)); //print("Mult2: " + (Math.ceil(lenInBeats / 2) * 2)); //print("Pot2: " + Math.pow(2, Math.ceil(Math.log(lenInBeats) / Math.log(2)))); // Round to full beats //lenInBeats = Math.ceil(lenInBeats); // Round to 2*x beats //lenInBeats = Math.ceil(lenInBeats / 2) * 2; // Round to 2^x beats lenInBeats = Math.pow(2, Math.ceil(Math.log(lenInBeats) / Math.log(2))); } else { //print("Full: 1 / " + Math.floor(1 / lenInBeats)); //print("Mult2: 1 / " + (Math.floor(1 / lenInBeats / 2) * 2)); //print("Pot2: 1 / " + Math.pow(2, Math.floor(Math.log(1 / lenInBeats) / Math.log(2)))); // Round to beat fragments //lenInBeats = 1 / Math.floor(1 / lenInBeats); // Round to fragments of 2*x beats //lenInBeats = 1 / (Math.floor(1 / lenInBeats / 2) * 2); // Round to fragments of 2^x beats lenInBeats = 1 / Math.pow(2, Math.floor(Math.log(1 / lenInBeats) / Math.log(2))); } len = lenInBeats * beatSamples; // Set calculated loop end engine.setValue(group, "loop_end_position", start + len); // Start looping engine.setValue(group, "reloop_exit", 1); } // Activates alternative function // Called by timer after button was 1sec pressed or by jog wheel movement NumarkTotalControl.loopExtendedChange = function(group, timerCall) { var deck = NumarkTotalControl.groupToDeck(group); if (!NumarkTotalControl.extendedLoopingChanged[deck-1]) { if (!timerCall) { // Stop extended loop change timer engine.stopTimer(NumarkTotalControl.extendedLoopingLEDTimer[deck-1]); } NumarkTotalControl.extendedLoopingChanged[deck-1] = true; // Get current LED status NumarkTotalControl.extendedLoopingLEDState[deck-1] = engine.getValue(group, "loop_enabled"); // Start LED blink timer NumarkTotalControl.loopLEDBlink(deck); NumarkTotalControl.extendedLoopingLEDTimer[deck-1] = engine.beginTimer(333, "NumarkTotalControl.loopLEDBlink(" + deck + ")"); } } // Set LEDs to current loop status NumarkTotalControl.loopLEDs = function(value, group, key) { var status = false; var deck = NumarkTotalControl.groupToDeck(group); if (value) { status = true; } NumarkTotalControl.setLED(NumarkTotalControl.leds[deck]["loopOut"], status); if (!NumarkTotalControl.extendedLooping) { status = false; } NumarkTotalControl.setLED(NumarkTotalControl.leds[deck]["loopHalve"], status); NumarkTotalControl.setLED(NumarkTotalControl.leds[deck]["loopDouble"], status); } // Let LED blink on alternative function NumarkTotalControl.loopLEDBlink = function(deck) { var led; switch (NumarkTotalControl.extendedLoopingState[deck-1]) { case NumarkTotalControl.extendedLoopingType.SetBegin: led = NumarkTotalControl.leds[deck]["loopHalve"]; break; case NumarkTotalControl.extendedLoopingType.SetLength: led = NumarkTotalControl.leds[deck]["loopDouble"]; break; default: return; } if (NumarkTotalControl.extendedLoopingLEDState[deck-1]) { NumarkTotalControl.extendedLoopingLEDState[deck-1] = false; } else { NumarkTotalControl.extendedLoopingLEDState[deck-1] = true; } NumarkTotalControl.setLED(led, NumarkTotalControl.extendedLoopingLEDState[deck-1]); } NumarkTotalControl.extendedFunctionButton = function(normalFunction, extendedLoopingFactor, extendedLoopingType, group, value) { var deck = NumarkTotalControl.groupToDeck(group); if (NumarkTotalControl.extendedLooping) { var start = engine.getValue(group, "loop_start_position"); var end = engine.getValue(group, "loop_end_position"); if (value) { if ((start != -1) && (end != -1) && (NumarkTotalControl.extendedLoopingState[deck-1] == NumarkTotalControl.extendedLoopingType.None)) { NumarkTotalControl.extendedLoopingState[deck-1] = extendedLoopingType; NumarkTotalControl.extendedLoopingChanged[deck-1] = false; NumarkTotalControl.extendedLoopingJogCarryOver[deck-1] = 0; // Start alternative function timer -> activated after button was 500msec pressed or jog wheel movement (see jogWheel function) NumarkTotalControl.extendedLoopingLEDTimer[deck-1] = engine.beginTimer(500, "NumarkTotalControl.loopExtendedChange('" + group + "', true)", true); } } else { // Check if alternative function wasn't used if (!NumarkTotalControl.extendedLoopingChanged[deck-1] && (start != -1) && (end != -1)) { // Call default function engine.setValue(group, "loop_end_position", start + (end - start) * extendedLoopingFactor); } // Stop LED blink or extended loop change timer engine.stopTimer(NumarkTotalControl.extendedLoopingLEDTimer[deck-1]); NumarkTotalControl.extendedLoopingLEDTimer[deck-1] = -1; // Reset LEDs NumarkTotalControl.loopLEDs(engine.getValue(group, "loop_enabled"), group, "loop_enabled"); // Reset alternative function variables NumarkTotalControl.extendedLoopingState[deck-1] = NumarkTotalControl.extendedLoopingType.None; NumarkTotalControl.extendedLoopingChanged[deck-1] = false; NumarkTotalControl.extendedLoopingJogCarryOver[deck-1] = 0; } } else { if (value) { engine.setValue(group, normalFunction, 1); } else { engine.setValue(group, normalFunction, 0); } } } NumarkTotalControl.leftFunction = function(channel, control, value, status, group) { NumarkTotalControl.extendedFunctionButton("rate_temp_down", 0.5, NumarkTotalControl.extendedLoopingType.SetBegin, group, value); } NumarkTotalControl.rightFunction = function(channel, control, value, status, group) { NumarkTotalControl.extendedFunctionButton("rate_temp_up", 2, NumarkTotalControl.extendedLoopingType.SetLength, group, value); } // Set LED to current quantize status NumarkTotalControl.quantizeLED = function(value, group, key) { var deck1 = engine.getValue("[Channel1]", "quantize"); var deck2 = engine.getValue("[Channel2]", "quantize"); if (deck1 == deck2) { if (NumarkTotalControl.quantizeLEDTimer != -1) { engine.stopTimer(NumarkTotalControl.quantizeLEDTimer); NumarkTotalControl.quantizeLEDTimer = -1; } NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["quantize"], deck1); } else if (NumarkTotalControl.quantizeLEDTimer == -1) { NumarkTotalControl.quantizeLEDBlink(); NumarkTotalControl.quantizeLEDTimer = engine.beginTimer(333, "NumarkTotalControl.quantizeLEDBlink()"); } } // Let LED blink on unequal quantize status NumarkTotalControl.quantizeLEDBlink = function() { NumarkTotalControl.quantizeLEDStatus = !NumarkTotalControl.quantizeLEDStatus; NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["quantize"], NumarkTotalControl.quantizeLEDStatus); } NumarkTotalControl.finePitch = function(channel, control, value, status, group) { if (value > 63) { value = value - 128; } engine.setValue(group, "rate", engine.getValue(group, "rate") + value / 512); } // Fixes cue_set glitches NumarkTotalControl.setCue = function(channel, control, value, status, group) { if (value) { engine.setValue(group, "cue_set", 1); } } // If playing, stutters from cuepoint; otherwise jumps to cuepoint and stops NumarkTotalControl.playFromCue = function(channel, control, value, status, group) { if (NumarkTotalControl.simpleCue) { if (value) { if (engine.getValue(group, "play")) { engine.setValue(group, "cue_goto", 1); } else { engine.setValue(group, "cue_gotoandstop", 1); } } } else { if (value) { if (engine.getValue(group, "play")) { engine.setValue(group, "play", 0); engine.setValue(group, "cue_gotoandstop", 1); } else { engine.setValue(group, "cue_preview", 1); } } else { engine.setValue(group, "cue_preview", 0); } } } // Jog values: (counter) fast slow still slow fast (clockwise) // Jog values: 064 127 - 001 063 NumarkTotalControl.jogWheel = function(channel, control, value, status, group) { var deck = NumarkTotalControl.groupToDeck(group); var adjustedJog = parseFloat(value); var posNeg = 1; if (adjustedJog > 63) { // Counter-clockwise posNeg = -1; adjustedJog = value - 128; } if (NumarkTotalControl.extendedLoopingState[deck-1] == NumarkTotalControl.extendedLoopingType.SetBegin) { var start = engine.getValue(group, "loop_start_position"); var end = engine.getValue(group, "loop_end_position"); if ((start != -1) && (end != -1)) { // Activate alternative function SetBegin NumarkTotalControl.loopExtendedChange(group, false); // Adjust jog speed // FIXME: Get correct channel count from deck var channels = 2; var sampleRate = engine.getValue(group, "track_samplerate"); adjustedJog = adjustedJog * channels * sampleRate / 600; // Move loop engine.setValue(group, "loop_start_position", start + adjustedJog); engine.setValue(group, "loop_end_position", end + adjustedJog); } } else if (NumarkTotalControl.extendedLoopingState[deck-1] == NumarkTotalControl.extendedLoopingType.SetLength) { var start = engine.getValue(group, "loop_start_position"); var end = engine.getValue(group, "loop_end_position"); if ((start != -1) && (end != -1)) { // Activate alternative function SetLength NumarkTotalControl.loopExtendedChange(group, false); // Adjust jog speed and add remaining value from last jog change adjustedJog = adjustedJog / 40 + NumarkTotalControl.extendedLoopingJogCarryOver[deck-1]; var beats; // Round to full beats if (adjustedJog > 0) { beats = Math.floor(adjustedJog); } else { beats = Math.ceil(adjustedJog); } // Save remaining value for next jog change NumarkTotalControl.extendedLoopingJogCarryOver[deck-1] = adjustedJog - beats; // Set new loop end engine.setValue(group, "loop_end_position", end + beats * NumarkTotalControl.samplesPerBeat(group)); } } else if (NumarkTotalControl.scratchMode) { if (NumarkTotalControl.scratchTimer[deck-1] == -1) { engine.scratchEnable(deck, 128, 33+1/3, 1.0/8, (1.0/8)/32); } else { engine.stopTimer(NumarkTotalControl.scratchTimer[deck-1]); } engine.scratchTick(deck, adjustedJog); NumarkTotalControl.scratchTimer[deck-1] = engine.beginTimer(20, "NumarkTotalControl.jogWheelStopScratch(" + deck + ")", true); } else { var gammaInputRange = 64; // Max jog speed var maxOutFraction = 0.5; // Where on the curve it should peak; 0.5 is half-way var sensitivity = 0.5; // Adjustment gamma var gammaOutputRange = 3; // Max rate change if (engine.getValue(group,"play")) { adjustedJog = posNeg * gammaOutputRange * Math.pow(Math.abs(adjustedJog) / (gammaInputRange * maxOutFraction), sensitivity); } else { adjustedJog = gammaOutputRange * adjustedJog / (gammaInputRange * maxOutFraction); } engine.setValue(group, "jog", adjustedJog); } } NumarkTotalControl.jogWheelStopScratch = function(deck) { NumarkTotalControl.scratchTimer[deck-1] = -1; engine.scratchDisable(deck); } NumarkTotalControl.tap = function(channel, control, value, status, group) { var deck = NumarkTotalControl.groupToDeck(group); if (value) { NumarkTotalControl.setLED(NumarkTotalControl.leds[deck]["tap"], true); bpm.tapButton(deck); } else { NumarkTotalControl.setLED(NumarkTotalControl.leds[deck]["tap"], false); } } NumarkTotalControl.toggleDirectoryMode = function(channel, control, value, status, group) { // Toggle setting and light if (value) { NumarkTotalControl.directoryMode = !NumarkTotalControl.directoryMode; NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["directory"], NumarkTotalControl.directoryMode); } } NumarkTotalControl.toggleScratchMode = function(channel, control, value, status, group) { // Toggle setting and light if (value) { NumarkTotalControl.scratchMode = !NumarkTotalControl.scratchMode; NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["scratchMode"], NumarkTotalControl.scratchMode); } } NumarkTotalControl.toggleSimpleCue = function(channel, control, value, status, group) { // Toggle setting and light if (value) { NumarkTotalControl.simpleCue = !NumarkTotalControl.simpleCue; NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["simpleCue"], NumarkTotalControl.simpleCue); } } NumarkTotalControl.toggleExtendedLooping = function(channel, control, value, status, group) { // Toggle setting and light if (value) { NumarkTotalControl.extendedLooping = !NumarkTotalControl.extendedLooping; NumarkTotalControl.setLED(NumarkTotalControl.leds[0]["extendedLooping"], NumarkTotalControl.extendedLooping); NumarkTotalControl.loopLEDs(engine.getValue("[Channel1]", "loop_enabled"), "[Channel1]", "loop_enabled"); NumarkTotalControl.loopLEDs(engine.getValue("[Channel2]", "loop_enabled"), "[Channel2]", "loop_enabled"); } } NumarkTotalControl.toggleQuantize = function(channel, control, value, status, group) { // Toggle setting if (value) { var newValue = !(engine.getValue("[Channel1]", "quantize") && engine.getValue("[Channel2]", "quantize")); engine.setValue("[Channel1]", "quantize", newValue); engine.setValue("[Channel2]", "quantize", newValue); } }