//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2016. All Rights Reserved. -----------------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'VMS 5.0'
// last revision: 
//Author: Tollen Francisco, 2017-05-25
//
//----------------------------------------------------------------------------- 
var prevValue = 0;
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 "VMS5.0 AUDIO MIDI Device";
}
//----------------------------------------------------------------------------- 
// 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 0x04D8;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDevicePID(osId)
{	
	return 0x0A0D;	
}
//----------------------------------------------------------------------------- 
// 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 "000170010004";
	
}
//----------------------------------------------------------------------------- 
// 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 "VMS5.0 AUDIO MIDI Device";
	
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var btn_ab = false;
var btn_cd = false;
var btn_cue_shift_a = false;
var btn_cue_shift_c = false;
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// 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 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	//sendMidi(uniqueId, status, channel, data1, data2);
	//for(var i=1; i<=128; i++) sendMidi(uniqueId,0x90,0,i,0x00);
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
	for(var x = 0; x <= 3; x++){
		sendMidi(uniqueId,0x90,0,44 + x, 0x00);
	}
	
	
}
//----------------------------------------------------------------------------- 
// 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)
{
	debug('controllerDeinit ['+uniqueId+']');
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
	// set all leds off
	//for(var i=1; i<=128; i++) sendMidi(uniqueId,0x90,0,i,0x00);
	for(var x = 0; x <= 3; x++){
		sendMidi(uniqueId,0x90,0,44 + x, 0x00);
	}
}
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var loopingInBlinkLed=[-1,-1,-1,-1];// -1 means disabled
var bReloopLastAction=[-1,-1,-1,-1];// used to properly simulate button press and release
var jogWheelsInScratchMode=0;
var jogWheelsLastMostSignificant=[-1,-1,-1,-1];// not a valid position
var jogWheelsLastValue=[-1,-1,-1,-1];// start with non-valid values
//----------------------------------------------------------------------------- 
// 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)
{
	var messageNotProcessed=false;
	switch(status)
	{
	//-----------------------------------------------
	// controllers: pitch sliders ('moving' things)
	case 0xE0:
		// attn: we're only using 7bit	
		if(channel==0) call('setDeckPitch', (btn_ab == false ? 0 : 2), 0x7F-data2);
		if(channel==1) call('setDeckPitch', (btn_cd == false ? 1 : 3), 0x7F-data2);
		
		break;
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		// global
		case 0x1F: call('setCrossfader', 0x7F-data2); break;
		case 0x1C: call('setMasterGain', data2); break;
		

		// per deck
		//midilog 2 for channel 1
		case 0x08: call('setDeckEqLow', 0, data2); break;
		case 0x07: call('setDeckEqMid', 0, data2); break;
		case 0x06: call('setDeckEqHigh', 0, data2); break;
		case 0x09: call('setDeckVolume', 0, data2); break;
		case 0x05: call('setDeckGain', 0, data2); break;
		case 0x22: call('setCuePgmMix', data2); break;
		case 0x23: call('setMasterGain', data2); break;
		case 0x27: call('setDeckCutoff', 0, data2);break;
		case 0x2C: call('setDeckCutoff', 1, data2);break;
		// jog-wheel
		case 0x31: //  most-significant 0x00-0x7F jog-wheel
			var deck = 0;
			jogWheelsLastMostSignificant[deck] = data2;
			break;
		case 0x30: // least-significant 0x00-0x7F jog-wheel
			var deck = 0;
			var val1 = 128 * jogWheelsLastMostSignificant[deck] + data2;// 0..16383 [128*128 = 16384 positions]
			if(jogWheelsLastValue[deck]==-1)
			{
				jogWheelsLastValue[deck] = val1;
			}else
			{
				var jwDiff = val1 - jogWheelsLastValue[deck];// new - old
				if(jwDiff<-8192) jwDiff = jwDiff + 16384;// correct if it wraps arround (cw)
				if(jwDiff> 8192) jwDiff = jwDiff - 16384;// correct if it wraps arround (ccw) -- 0x16 *128 + 0x3F
				
				// save new pos
				jogWheelsLastValue[deck] = val1;
				// params: (deck, increment, total positions per complete revolution) 
				if(jogWheelsInScratchMode) call('scratchDeckJW', (btn_ab == false ? 0 : 2), jwDiff, 2730);else call('moveDeckJogWheel', (btn_ab == false ? 0 : 2), jwDiff, 2730);// 16384/6
			}
			break;
		
		//deck B
		
		case 0x0D: call('setDeckEqLow', 1, data2); break;
		case 0x0C: call('setDeckEqMid', 1, data2); break;
		case 0x0B: call('setDeckEqHigh', 1, data2); break;
		case 0x0E: call('setDeckVolume', 1, data2); break;
		case 0x0A: call('setDeckGain', 1, data2); break;
		// jog-wheel
		case 0x33: //  most-significant 0x00-0x7F jog-wheel
			var deck = 1;
			jogWheelsLastMostSignificant[deck] = data2;
			break;
		case 0x32: // least-significant 0x00-0x7F jog-wheel
			var deck = 1;
			var val1 = 128 * jogWheelsLastMostSignificant[deck] + data2;// 0..16383 [128*128 = 16384 positions]
			if(jogWheelsLastValue[deck]==-1)
			{
				jogWheelsLastValue[deck] = val1;
			}else
			{
				var jwDiff = val1 - jogWheelsLastValue[deck];// new - old
				if(jwDiff<-8192) jwDiff = jwDiff + 16384;// correct if it wraps arround (cw)
				if(jwDiff> 8192) jwDiff = jwDiff - 16384;// correct if it wraps arround (ccw) -- 0x16 *128 + 0x3F
				
				// save new pos
				jogWheelsLastValue[deck] = val1;
				// params: (deck, increment, total positions per complete revolution) 
				if(jogWheelsInScratchMode) call('scratchDeckJW', (btn_cd == false ? 1 : 3), jwDiff, 2730);else call('moveDeckJogWheel', (btn_cd == false ? 1 : 3), jwDiff, 2730);// 16384/6
			}
			break;
		
		//deck C
		case 0x03: call('setDeckEqLow', 2, data2); break;
		case 0x02: call('setDeckEqMid', 2, data2); break;
		case 0x01: call('setDeckEqHigh', 2, data2); break;
		case 0x04: call('setDeckVolume', 2, data2); break;
		case 0x00: call('setDeckGain', 2, data2); break;
		
		//deck D
		case 0x12: call('setDeckEqLow', 3, data2); break;
		case 0x11: call('setDeckEqMid', 3, data2); break;
		case 0x10: call('setDeckEqHigh', 3, data2); break;
		case 0x13: call('setDeckVolume', 3, data2); break;
		case 0x0F: call('setDeckGain', 3, data2); break;
		
		case 0x34: 
		var newValue = 0;
		if(prevValue > data2){
			newValue = -1;
		} else {
			newValue = 1;
		}
		call('lbMoveCursor', newValue);
		prevValue = data2;
			break;
		default: messageNotProcessed = true;
		}
		break;
	
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		if((status==0x80)&&(data2==0x40)) data2 = 0;
		switch(data1)
		{
		
		
		case 0x0F: call('deckBendDown', (btn_ab == false ? 0 : 2), data2); sendMidi(uniqueId,0x90,0,0x0F,(data2 ? 0x7F : 0x00)); break;
		case 0x10: call('deckBendUp', (btn_cd == false ? 1 : 3), data2); sendMidi(uniqueId,0x90,0,0x10,(data2 ? 0x7F : 0x00)); break;
		case 0x12: call('deckKeyLock', (btn_ab == false ? 0 : 2), data2); break;
		case 0x28: call('deckLoopLess', (btn_ab == false ? 0 : 2), data2); break;
		case 0x29: call('deckLoopMore', (btn_ab == false ? 0 : 2), data2); break;
					
		case 0x27: 
					if(data2>0x3F){ jogWheelsInScratchMode = (jogWheelsInScratchMode+1)%2;} 
					break;	
		case 0x0C: call('deckPlayPause', (btn_ab == false ? 0 : 2), data2); break;
		case 0x0D: call('deckCue', (btn_ab == false ? 0 : 2), data2); break;
		case 0x0E: if(data2) {
						(btn_ab = (btn_ab ? false : true)); 
						sendMidi(uniqueId,0x90,0,0x0E,(btn_ab == false ? 0x00 : 0x7F));
					}
		break;
		
		
		case 0x15: btn_cue_shift_a =  (data2 > 0x40 ? true : false); break;
		case 0x1F: if(btn_cue_shift_a == false) { call('deckCuePos1', (btn_ab == false ? 0 : 2), data2); } else { call('deckCuePos1Clear', (btn_ab == false ? 0 : 2), data2); } break;
		case 0x20: if(btn_cue_shift_a == false) { call('deckCuePos2', (btn_ab == false ? 0 : 2), data2); } else { call('deckCuePos2Clear', (btn_ab == false ? 0 : 2), data2); } break;
		case 0x21: if(btn_cue_shift_a == false) { call('deckCuePos3', (btn_ab == false ? 0 : 2), data2); } else { call('deckCuePos3Clear', (btn_ab == false ? 0 : 2), data2); } break;
		case 0x22: if(btn_cue_shift_a == false) { call('deckCuePos4', (btn_ab == false ? 0 : 2), data2); } else { call('deckCuePos4Clear', (btn_ab == false ? 0 : 2), data2); } break;
		
		
		case 0x23: call('deckLoop1_8', (btn_ab == false ? 0 : 2), data2);  break;
		case 0x24: call('deckLoop1_4', (btn_ab == false ? 0 : 2), data2); break;
		case 0x25: call('deckLoop1_2', (btn_ab == false ? 0 : 2), data2); break;
		case 0x26: call('deckLoop1', (btn_ab == false ? 0 : 2), data2); break;
		case 0x34: call('deckKeyLock', (btn_cd == false ? 1 : 3), data2); break;
		case 0x37: btn_cue_shift_c =  (data2 > 0x40 ? true : false); break;
		
		case 0x45: call('deckLoop1_8', (btn_cd == false ? 1 : 3), data2); break;
		case 0x46: call('deckLoop1_4', (btn_cd == false ? 1 : 3), data2); break;
		case 0x47: call('deckLoop1_2', (btn_cd == false ? 1 : 3), data2); break;
		case 0x48: call('deckLoop1',(btn_cd == false ? 1 : 3), data2); break;
		
		case 0x11: call('deckSync', (btn_ab == false ? 0 : 2), data2); break;
		
		case 0x67: if(data2) call('deckLBLoad', (btn_ab == false ? 0 : 2), 0); break;// load deck A
		case 0x68: if(data2) call('deckLBLoad', (btn_cd == false ? 1 : 3), 0); break;// load deck B
		
		case 0x31: call('deckBendDown', (btn_cd == false ? 1 : 3), data2); sendMidi(uniqueId,0x90,0,0x31,(data2 ? 0x7F : 0x00)); break;
		case 0x32: call('deckBendUp', (btn_cd == false ? 1 : 3), data2); sendMidi(uniqueId,0x90,0,0x32,(data2 ? 0x7F : 0x00)); break;
		case 0x4A: call('deckLoopLess', (btn_cd == false ? 1 : 3), data2); break;		
		case 0x4B:  call('deckLoopMore', (btn_cd == false ? 1 : 3), data2); break;				
		case 0x49: if(data2>0x3F){ jogWheelsInScratchMode = (jogWheelsInScratchMode+1)%2;} 
					break;	
		case 0x4F: if(jogWheelsInScratchMode) call('holdDeckJogWheel', (btn_ab == false ? 0 : 2), data2); break;
		case 0x50: if(jogWheelsInScratchMode) call('holdDeckJogWheel', (btn_cd == false ? 1 : 3), data2); break;
		
		case 0x2F: call('deckCue', (btn_cd == false ? 1 : 3), data2); break;
		case 0x2E: call('deckPlayPause', (btn_cd == false ? 1 : 3), data2); break;
		case 0x30: 
			if(data2) {
				(btn_cd = (btn_cd ? false : true)); 
				sendMidi(uniqueId,0x90,0,0x30,(btn_cd == false ? 0x00 : 0x7F));
			}
			break;
		case 0x41: if(btn_cue_shift_c == false) { call('deckCuePos1', (btn_cd == false ? 1 : 3), data2); } else { call('deckCuePos1Clear', (btn_cd == false ? 1 : 3), data2); } break;
		case 0x42: if(btn_cue_shift_c == false) { call('deckCuePos2', (btn_cd == false ? 1 : 3), data2); } else { call('deckCuePos2Clear', (btn_cd == false ? 1 : 3), data2); } break;
		case 0x43: if(btn_cue_shift_c == false) { call('deckCuePos3', (btn_cd == false ? 1 : 3), data2); } else { call('deckCuePos3Clear', (btn_cd == false ? 1 : 3), data2); } break;
		case 0x44: if(btn_cue_shift_c == false) { call('deckCuePos4', (btn_cd == false ? 1 : 3), data2); } else { call('deckCuePos4Clear', (btn_cd == false ? 1 : 3), data2); } break;
	
		case 0x33: call('deckSync', (btn_cd == false ? 1 : 3), data2); break;
		
		case 0x2A: call('deckMonitor', 0, data2); break;
		case 0x2B: call('deckMonitor', 1, data2); break;
		case 0x2C: call('deckMonitor', 2, data2); break;
		case 0x2D: call('deckMonitor', 3, data2); break;
		
		
		case 0x69: if(data2) call('lbToggleSelect', data2); 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 d=0; if(deck==1) d=34;// 46 - 12 (cue code from deck B - cue code from deck A)
	if(name=='cue') 		sendMidi(uniqueId,0x90,0,13+d,(param1 ? 0x7F : 0x00));
	if(name=='play') 		sendMidi(uniqueId,0x90,0,12+d,(param1 ? 0x7F : 0x00));
	
	if(name=='onbeat') 		sendMidi(uniqueId,0x90,0,17+d,(param1 ? 0x7F : 0x00));// 17 for red; 22 for blue;	
	if(name=='cuepos1')		sendMidi(uniqueId,0x90,0, ((deck == 0) ? 0x1F: 0x41),(param1 ? 0x7F : 0x00));// red
	if(name=='cuepos2')		sendMidi(uniqueId,0x90,0, ((deck == 0) ? 0x20: 0x42),(param1 ? 0x7F : 0x00));// red
	if(name=='cuepos3')		sendMidi(uniqueId,0x90,0, ((deck == 0) ? 0x21: 0x43),(param1 ? 0x7F : 0x00));// red
	if(name=='cuepos4')		sendMidi(uniqueId,0x90,0, ((deck == 0) ? 0x22: 0x44),(param1 ? 0x7F : 0x00));// red
	

	if(name=='monitor') 	 sendMidi(uniqueId,0x90,0,44 + deck,(param1 ? 0x7F : 0x00));
	
	
	if(name=='loopIn')
	{
		sendMidi(uniqueId,0x90,0,31+d,(param1 ? 0x7F : 0x00));// Loop In Red (on or off)
		// activate blinking for the LoopIn led (so the user knows to press it again)
		if(param1) loopingInBlinkLed[deck] = 0;else loopingInBlinkLed[deck] = -1;
	}
	if(name=='looping')
	{
		sendMidi(uniqueId,0x90,0,31+d,0x00);// Loop In Red (off)		
		sendMidi(uniqueId,0x90,0,32+d,(param1 ? 0x7F : 0x00));// Loop Out Red (on or off)
		sendMidi(uniqueId,0x90,0,33+d,(param1 ? 0x7F : 0x00));// Reloop Red (on or off)
		sendMidi(uniqueId,0x90,0,34+d,0x00);// Loop Red (off)
	}

	if(name=='keyLock'){
		var kd = false;
		if(deck == 0){
			kd = 0x12;
		}
		if(deck == 1){
			kd = 0x34;
		}
		if(kd){
			sendMidi(uniqueId,0x90,0,kd,(param1 ? 0x7F : 0x00));// red -- Vinyl (KeyLock)
		}
		
	}
}
//----------------------------------------------------------------------------- 
// 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)
function timerTick(uniqueId)
{
	//debug('timerTick ['+uniqueId+']');
	for(var i=0; i<2; i++)
	{
		// loop in led
		if(loopingInBlinkLed[i]!=-1)
		{
			// actually set the led
			sendMidi(uniqueId,0x90,0,31+(i*34),((loopingInBlinkLed[i]<2) ? 0x7F : 0x00));// Loop In Red (on or off)
			loopingInBlinkLed[i]++;
			if(loopingInBlinkLed[i]>=4) loopingInBlinkLed[i] = 0;
		}
	}
}
//----------------------------------------------------------------------------- 