//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2016. All Rights Reserved. -----------------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Numark NDX500'
// last revision: 2018-06-27
//Author: Tollen Francisco, 2018-06-27
//
//-----------------------------------------------------------------------------
function getLanguageVersion()
{
	return 1;// do not modify this, this should be 1 for all scripts
}
//-----------------------------------------------------------------------------
function getFileVersion()
{
	return 1;// update this as needed
}
//-----------------------------------------------------------------------------
// return a user friendly name for the controller supported
function getControllerUserFriendlyName()
{
	return "Numark NDX500";
}
//-----------------------------------------------------------------------------
// return the communication type that the controller supports
// this can be either 'midi' or 'hid' (please return only one of them in case the controller supports both)
function getControllerType()
{
	return 'midi';
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// preferred way to identify the controller (using the USB device VID and PID -- vendor and product id)
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 MacOSX; 2 Linux; -1 unknown
function getSupportedHidDeviceVID(osId)
{
	return false;
}
//-----------------------------------------------------------------------------
function getSupportedHidDevicePID(osId)
{
	return false;
}
//-----------------------------------------------------------------------------
// this is NOT the preferred way to identify the controller but you should use
// it if the controller does not support standard USB VID and PID identification
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 MacOSX; 2 Linux; -1 unknown
function getSupportedHidDeviceName(osId)
{
	return false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// preferred way to identify the controller (using the USB device VID and PID -- vendor and product id)
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 MacOSX; 2 Linux; -1 unknown
function getSupportedMidiDeviceVID(osId)
{
	return 0x15E4;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0x0034;
}
//-----------------------------------------------------------------------------
// preferred way to identify the controller (if it supports MIDI SysEx)
// return false if the controller does not support this kind of identification
function getSupportedMidiSysExIdString(osId)
{
	// sysex response to 'F0 7E 7F 06 01 F7' (identification request sysex)
	// please note that the response contains 5 bytes in hex format containing
	// the Manufacturer ID (3 bytes) and Model Number (2 bytes)
	// ATTN: the returned value should omit the standard 'F0 7E 00 06 02' message start
	return false;

}
//-----------------------------------------------------------------------------
// this is NOT the preferred way to identify the controller but you should use
// it if the controller does not support standard MIDI SysEx identification
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 MacOSX; 2 Linux; -1 unknown
function getSupportedMidiDeviceNameForInput(osId)
{
	return false;

}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	return false;

}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var jogWheelsInScratchMode = [0,0,0,0];
var timeStatus = [0,0,0,0];// 0 means elapsed, 1 remained
lastTextDisplayed = [0,0,0,0];
var loopingInBlinkLed = [0,0,0,0];
var lcd_intro_text = 'PCDJ DEX3';

midi_current_deck = 0; //this is a work around for LCD text, because we do not know exactly what deck the controller currently operates on.
//-----------------------------------------------------------------------------
// this is called when the controller is initialized and the control is passed to this script
// use this to initialize any variables you might need, the controller' leds, etc
// uniqueId is a unique integer between 0..15 that is guaranteed not to change for
// a certain opened controller -- please note that there can be many controllers
// of the same type in use at the same time ! Use uniqueId to identify between them
function controllerInit(uniqueId)
{
	debug('controllerInit Numark NDX500 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)

	for(var i = 0; i<=3; i++){ //turn off scratch led
		sendMidi(uniqueId, 0x80,i,0x10,0x7F);
		sendMidi(uniqueId,0x90,i,0x47,0x7F); // elapsed LCD (var is initiated 0
		sendMidi(uniqueId,0x90,i,0x4D,(getValue('play', i) ? 0x7F : 0x00));// play LCD
		sendMidi(uniqueId,0x90,i,0x4B,(getValue('cue', i) ? 0x7F : 0x00)); //cue LCD
		sendMidi(uniqueId,0x90,i,0x57,(getValue('keyLock', i) ? 0x7F : 0x00));// MT LCD
		sendMidi(uniqueId,0xB0,i,0x24,0x01); // m/c/f/icons
		sendMidi(uniqueId,0xB0,i,0x31,0X01); //time digit on
		sendMidi(uniqueId,0xB0,i,0x32,0x01); //BPM digit on
		sendMidi(uniqueId,0x90,i,0x51,0X7F); //BPM icon on
		sendMidi(uniqueId,0xB0,i,0x34,0X01); //pitch digit on
		sendMidi(uniqueId,0xB0,i,0x37,0X01); //pitch decimal on
		sendMidi(uniqueId,0x90,i,0x52,0X7F); //pitch icon on
		sendMidi(uniqueId,0x90,i,0x3F,0x00); // scratch
		sendMidi(uniqueId,0x90,i,0x40,0x00); // search

		setLCDText(uniqueId, lcd_intro_text, i);
	}



	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

}
//-----------------------------------------------------------------------------
// called just before the controller will be deinitialized and the control will go away from the script
// use to power off any powered leds, etc
function controllerDeinit(uniqueId)
{
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
	for(var i = 0; i<=3; i++){ //turn off scratch led
		sendMidi(uniqueId, 0x80,i,0x10,0x7F);
		sendMidi(uniqueId,0xB0,i,0x21,0x00); //pitch change LED off
		sendMidi(uniqueId,0xB0,i,0x25,0x00); //progress bar lcd off
		sendMidi(uniqueId,0x90,i,0x4D,0x00); //play LCD
		sendMidi(uniqueId,0x90,i,0x4B,0x00); //cue LCD
		sendMidi(uniqueId,0x90,i,0x4C,0x00); //pause LCD
		sendMidi(uniqueId,0x90,i,0x57,0x00); // MT LCD
		sendMidi(uniqueId,0x90,i,0x7F,0x00); //all LCD's off
		sendMidi(uniqueId,0x90,i,0x7E,0x00); //all LED's off
		sendMidi(uniqueId,0x90,i,0x47,0x00); // elapsed LCD off
		sendMidi(uniqueId,0x90,i,0x4A,0x00); // Remain LCD off
		sendMidi(uniqueId,0xB0,i,0x24,0x00); // m/c/f/icons
		sendMidi(uniqueId,0xB0,i,0x31,0X00); //time off
		sendMidi(uniqueId,0xB0,i,0x32,0X00); //BPM digit off
		sendMidi(uniqueId,0x90,i,0x51,0X00); //BPM icon off
		sendMidi(uniqueId,0xB0,i,0x34,0X00); //pitch digit off
		sendMidi(uniqueId,0xB0,i,0x37,0X00); //pitch decimal off
		sendMidi(uniqueId,0x90,i,0x52,0X00); //pitch icon off
		sendMidi(uniqueId,0x90,i,0x3F,0x00); // scratch
		sendMidi(uniqueId,0x90,i,0x40,0x00); // search
		sendMidi(uniqueId,0x90,i,0x7E,0x00); // all led off

		/*
			var name = 'Good Bye!';
			var nameLen = Math.min(9, name.length);
			if((nameLen>0)&&(lastTextDisplayed[i] != name))
			{
				debug('name='+name+' nameLen='+nameLen);
				//var sysexData = new Array(10+nameLen);
				var sysexData = new Array(9+nameLen);
				sysexData[0] = 0xF0;
				sysexData[1] = 0x00;
				sysexData[2] = 0x01;
				sysexData[3] = 0x3F;
				sysexData[4] = 0x34;
				sysexData[5] = i;
				sysexData[6] = Math.floor(nameLen/256);// most significant N
				sysexData[7] = nameLen%256;// least significant N
				for(var j=0; j<nameLen; j++) sysexData[8+j] = name.charCodeAt(j);// data
				sysexData[8+nameLen] = 0xF7;
				// send the sysex
				sendMidiSysEx(uniqueId, sysexData, sysexData.length);
				lastTextDisplayed[i] = name;
			}
		*/
		setLCDText(uniqueId, 'Good Bye!', i);
	}

	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

}
//-----------------------------------------------------------------------------
// this is your main target function to implement the controller -- this is called
// for each and every MIDI message that comes from the controller itself
// return true if the message was processed by the script and false to let it
// pass thru the software (eg. so it can be assigned by the user using the LEARN function)
function messageReceived(uniqueId,status,channel,data1,data2)
{
	//debug('messageReceived ['+uniqueId+'] - status:'+status+' channel:'+channel+' data1:'+data1+' data2:'+data2);
	var messageNotProcessed=false;
	midi_current_deck = channel;


    //if(channel==2) channel = 1;else if(channel==1) channel = 2; // use this to switch channel; 2 and 3 instead of multiple if's
	switch(status)
	{
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xE0:
		call('setDeckPitch', channel, 0x7F-data2);
		break;

	case 0xB0:
		switch(data1)
		{
		case 0x00:

			break;
		case 0x06:

			break;
		case 0x0A:
			break;
		case 0x0C:
			break;
		case 0x16:
			break;
		case 0x08:
			break;
		case 0x09:
			break;

		case 0x21:
			if(data2==1) call('lbMoveCursor', 1);else call('lbMoveCursor', -1);
			break;
		case 0x22:
			if(jogWheelsInScratchMode[channel] > 0){
				var val1 = 0x7F - data2 - 0x40;
				call('scratchDeckJW', channel, val1, 600);
			} else {

				var val1 = 0x7F - data2 - 0x40;
				call('moveDeckJogWheel', channel, val1, 300);
			}
			break;
		default: messageNotProcessed = true;
		}
	break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		//if(data2==0x40) data2 = 0x7F;
		if(status == 0x80){
			data2 = 0x00;
		} else {
			data2 = 0x7F;
		}

		switch(data1)
		{
		case 0x00:
			break;
		case 0x01:


			break;
		case 0x02:
			//pressing folder button changes folder/file/sidelist browser focus
			if(data2)
				call('lbToggleSelect', data2);
			break;
		case 0x03:
			//deck load (knob press)
			if(data2) call('deckLBLoad', channel, data2);
			break;
		case 0x04:
			call('deckTapBpm', channel, data2);
			break;
		case 0x05:
			call('deckCue', channel, data2);
		break;
		case 0x06:
			call('deckPlayPause', channel, data2); //play pause

			break;
		case 0x07:
			if(!getValue('looping', channel)) call('deckLoopInEnd', channel, data2);else call('deckLoopExit', channel, data2);
			break;
		case 0x08:
			if(!getValue('looping', channel)) call('deckLoopInEnd', channel, data2);else call('deckLoopExit', channel, data2);
			break;
		case 0x09:
			if(getValue('looping',channel)){
				call('deckLoopExit', channel, data2);
			} else {
				call('deckLoopReloop', channel, data2);
			}
			break;
		case 0x0A:
			call('record', data2);
			break;
		case 0x0B:
			call('deckCuePos1', channel, data2);
			break;
		case 0x0C:
			call('deckCuePos2', channel, data2);
			break;
		case 0x0D:
			call('deckCuePos3', channel, data2);
			break;
		case 0x0E:
			call('deckBrake', channel, data2);
			break;
		case 0x0F:
			call('deckReverse', channel, data2);
			break;
		case 0x10:
				if(data2 == 0x7F){
					jogWheelsInScratchMode[channel] = (jogWheelsInScratchMode[channel]+1)%2;

					sendMidi(uniqueId,(jogWheelsInScratchMode[channel] ? 0x90 : 0x80),channel,0x10,0x7F);
				}
			break;
		case 0x11:
			break;
		case 0x12:
				timeStatus[channel] = (timeStatus[channel]+1)%2;
				sendMidi(uniqueId,0x90,channel,0x4A,(timeStatus[channel]==1 ? 0x7F : 0x00)); //remain lcd
				sendMidi(uniqueId,0x90,channel,0x47,(timeStatus[channel]==0 ? 0x7F : 0x00)); //elapsed lcd
			break;
		case 0x13:
			break;
		case 0x14:
			break;
		case 0x15:
			call('deckKeyLock', channel, data2);
			break;
		case 0x16:
			if(data2>0x3F)
			{
				var currentPitchRange = getValue('pitchRange');// we only want 1, 3, 5

				switch(currentPitchRange)
				{
				case 1: currentPitchRange = 2; break;
				case 2: currentPitchRange = 3; break;
				case 3:
				case 4:
				case 5:
				case 6: currentPitchRange = 1; break;
				}
				call('setPitchRange', currentPitchRange);
			}


			break;
		case 0x17:
			call('deckBendDown', channel, data2);
		break;
		case 0x18:
			call('deckBendUp', channel, data2);
		break;

		case 0x19:
			if(jogWheelsInScratchMode[channel]){
				call('holdDeckJogWheel', channel, data2);
			}
			break;

		case 0x1B:

			break;
		case 0x21://loop in.
			break;
		case 0x22://loop out.
			break;
		case 0x23:
			break;
		case 0x24:
			break;

		case 0x31:
			break;
		case 0x32:
			break;
		case 0x33:
			break;
		case 0x34:
			break;


		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	default: messageNotProcessed = true;
	}
	if(messageNotProcessed) debug('messageReceived ['+uniqueId+'] - status:'+status+' channel:'+channel+' data1:'+data1+' data2:'+data2);
	return !messageNotProcessed;
}
//-----------------------------------------------------------------------------
// this is called by the software whenever something important changes (eg. play status, song changed, etc)
// depending on the notification type it may refer to a global event (deck is -1) or a specific deck (deck >= 0)
// and can contain any/all of the parameters: param 1 is an integer, param 2 is a float, param 3 is a string
function notificationReceived(uniqueId,name,deck,param1,param2,param3)
{
	//debug('notif ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	var nameLen = name.length;

	if(name=='play')
	{
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x06, 0x7F); //play
		sendMidi(uniqueId, 0x90,deck,0x4D,(param1 ? 0x7F : 0x00)); //play lcd
	}
	if(name=='cue')
	{
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x05, 0x7F); //cue
		sendMidi(uniqueId, 0x90,deck,0x4B,(param1 ? 0x7F : 0x00)); //cue lcd
	}

	if(name=='recording') 		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),0,0x0A, 0x7F);// record

	if(name=='keyLock') 	sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck, 0x15, 0x7F);//keylock

	if(name=='reverse')		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck, 0x0F, 0x7F);//reverse

	if(name=='cuepos1') {
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x0B, 0x7F);

	}//cue 1
	if(name=='cuepos2'){
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x0C, 0x7F);

	}
	if(name=='cuepos3'){
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x0D, 0x7F);

	}
	if(name=='brake'){
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x0E, 0x7F);

	}


	if(name=='loopIn') {

		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x07, 0x7F);
		if(param1) loopingInBlinkLed[deck] = 0;else loopingInBlinkLed[deck] = -1;

		sendMidi(uniqueId, 0x80,deck,0x08, 0x7F);
	}
	if(name=='looping'){
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x07, 0x7F);
		sendMidi(uniqueId,(param1 ? 0x90 : 0x80),deck,0x08, 0x7F);
	}

	if(name=='pitchRange')
		{
				if(param1==1)
					{
						sendMidi(uniqueId,0xB0,0,0x21,1);
						sendMidi(uniqueId,0xB0,1,0x21,1);
					}
				if(param1==2)
					{
						sendMidi(uniqueId,0xB0,0,0x21,2);
				 		sendMidi(uniqueId,0xB0,1,0x21,2);
				 	}
				if(param1==3)
					{
						sendMidi(uniqueId,0xB0,0,0x21,3);
						sendMidi(uniqueId,0xB0,1,0x21,3);
					}
		}



}


//-----------------------------------------------------------------------------
// this is called from time to time by the software (several times per second)
// (it is guaranteed that 'timerTick' will get executed ALWAYS right after notificationReceived)
var timer1=0;
var timer2=0;

function timerTick(uniqueId)
{

	timer1++; if(timer1>15) timer1 = 0;
	timer2++; if(timer2>35) timer2 = 0;


	for(var i=0;i<4;i++){
		var timeElapsed 	= getValue('timeElapsed', i);// float seconds
		var timeRemain 		= getValue('timeRemain', i);// float seconds
		var bpm 			= getValue('currentBpm', i);// float bpm
		var pitch 			= getValue('pitch', i);// pitch (100th of percents)

		if(timer1 % 3 == 0)
		{
			// progress bar
			if(timeStatus[i]==0)
			{
				// elapsed
				var tempTime = timeElapsed / (timeElapsed+timeRemain);// this will result in [0..1]
				sendMidi(uniqueId,0xB0,i,0x25, 1 + tempTime*19);// 1..20
				showTimeValue(uniqueId, i, timeElapsed);
			}else
			{
				// remaining
				var tempTime = 1.0 - (timeRemain / (timeElapsed+timeRemain));// this will result in [1..0]
				sendMidi(uniqueId,0xB0,i,0x25, 21 + tempTime*19);// 21..40
				showTimeValue(uniqueId, i, timeRemain);
			}

			// BPM value
			sendMidi(uniqueId,0xB0,i,0x2C,bpm%128);
			sendMidi(uniqueId,0xB0,i,0x4C,Math.floor(bpm/128));
			// pitch value
			if(pitch>=0)
			{
				sendMidi(uniqueId,0xB0,i,0x2D,pitch%128);
				sendMidi(uniqueId,0xB0,i,0x4D,Math.floor(pitch/128));
			}else
			{
			    var tmpPitch = 8192 + pitch;
				sendMidi(uniqueId,0xB0,i,0x2D, tmpPitch%128);
				sendMidi(uniqueId,0xB0,i,0x4D, 64 + Math.floor(tmpPitch/128));
			}

		}

		if(timer2 % 10 == 0)
		{
			var name = getValue('artist-title', midi_current_deck, 9);

			//There is no way to tell what deck the controller is set to, and the artist-title data is always sent to controller via channel 0x00.
			//We are deciding what text to display based on what controller channel we last received input from via messageReceived
			setLCDText(uniqueId, name, midi_current_deck);
		}

		if(loopingInBlinkLed[i]!=-1)
		{
			// actually set the led
			sendMidi(uniqueId,((loopingInBlinkLed[i]<2) ? 0x90 : 0x80),i,0x08,0x7F);// Loop In (on or off)
			loopingInBlinkLed[i]++;
			if(loopingInBlinkLed[i]>=4) loopingInBlinkLed[i] = 0;
		}
	}
}

function setLCDText(uniqueId, str_text, i)
{
	var name = str_text;
	var nameLen = Math.min(9, name.length);
	if((nameLen>0)&&(lastTextDisplayed[i] != name))
	{
		//debug(i+': ' +'name='+name+' nameLen='+nameLen);
		//var sysexData = new Array(10+nameLen);
		var sysexData = new Array(9+nameLen);
		sysexData[0] = 0xF0;
		sysexData[1] = 0x00;
		sysexData[2] = 0x01;
		sysexData[3] = 0x3F;
		sysexData[4] = 0x34;
		sysexData[5] = 0x00;
		sysexData[6] = Math.floor(nameLen/256);// most significant N
		sysexData[7] = nameLen%256;// least significant N
		for(var j=0; j<nameLen; j++) sysexData[8+j] = name.charCodeAt(j);// data
		sysexData[8+nameLen] = 0xF7;
		// send the sysex
		sendMidiSysEx(uniqueId, sysexData, sysexData.length);
		lastTextDisplayed[i] = name;
	}
}

function showTimeValue(uniqueId, deck, value)
{
	// split time in min/sec/100ths
	var sec = Math.floor(value);
	var hsec = Math.floor((value - sec)*100);
	var min = Math.floor(sec/60);
    sec = sec % 60;
	sendMidi(uniqueId, 0xB0, deck, 0x29, min);
	sendMidi(uniqueId, 0xB0, deck, 0x2A, sec);
	sendMidi(uniqueId, 0xB0, deck, 0x2B, hsec);
}
//-----------------------------------------------------------------------------
