//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2012 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'American Audio Versadeck'
//----------------------------------------------------------------------------- 
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 "VERSA DECK MIDI";
}
//----------------------------------------------------------------------------- 
// 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 (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
	//if(osId==1) return false;
	return "000170200C";
	
}

//----------------------------------------------------------------------------- 
// 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)
{
	//if(osId==1) return "American Audio Versadeck";
	return false;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "American Audio Versadeck";
	return 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);
	
	//deck A
	for(var deck=0; deck<2; deck++)
	{
		var i=0;
		if(deck==1) i=2;// 46 - 12 (cue code from deck B - cue code from deck A)
		sendMidi(uniqueId,0x90,i,0x1F,(getValue('cue', deck) ? 0x7F : 0x00)); //Cue
		sendMidi(uniqueId,0x90,i,0x20,(getValue('play', deck) ? 0x7F : 0x00)); //Play/Pause A
		sendMidi(uniqueId,0x90,i,0x08, (getValue('cuepos1', deck) ? 0x7F : 0x00));// hot cue 1
		sendMidi(uniqueId,0x90,i,0x09, (getValue('cuepos2', deck) ? 0x7F : 0x00));// hot cue 2
		sendMidi(uniqueId,0x90,i,0x0A, (getValue('cuepos3', deck) ? 0x7F : 0x00));// hot cue 3
		sendMidi(uniqueId,0x90,i,0x0B, (getValue('cuepos4', deck) ? 0x7F : 0x00));// hot cue 4
		sendMidi(uniqueId,0x90,i,0x02, (getValue('looping', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x1D, (getValue('keyLock', deck) ? 0x7F : 0x00));// Vinyl (KeyLock)
		sendMidi(uniqueId,0x90,i,0x0D, (getValue('monitor', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x03, 0x00);// Loop Out Blue
		sendMidi(uniqueId,0x90,i,0x04, 0x00);// Reloop Blue
		sendMidi(uniqueId,0x90,i,0x0F, (getValue('deckFxEcho',deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x10, (getValue('deckFxFlanger',deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x11, (getValue('deckReverse',deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x12, (getValue('deckFxPhaser',deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,i,0x13, (getValue('deckBrake',deck) ? 0x7F : 0x00));
		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)
{
	debug('controllerDeinit ['+uniqueId+']');
	// set all leds off
		for(var deck=0; deck<2; deck++)
	{
		var i=0;
		if(deck==1) i=2;// 46 - 12 (cue code from deck B - cue code from deck A)
		sendMidi(uniqueId,0x90,i,0x1F, 0x00); //Cue
		sendMidi(uniqueId,0x90,i,0x20, 0x00); //Play/Pause A
		sendMidi(uniqueId,0x90,i,0x08, 0x00);// hot cue 1
		sendMidi(uniqueId,0x90,i,0x09, 0x00);// hot cue 2
		sendMidi(uniqueId,0x90,i,0x0A, 0x00);// hot cue 3
		sendMidi(uniqueId,0x90,i,0x0B, 0x00);// hot cue 4
		sendMidi(uniqueId,0x90,i,0x02, 0x00);
		sendMidi(uniqueId,0x90,i,0x0D, 0x00);// monitor
		sendMidi(uniqueId,0x90,i,0x1D, 0x00);// Vinyl (KeyLock)
		sendMidi(uniqueId,0x90,i,0x03, 0x00);// Loop Out Blue
		sendMidi(uniqueId,0x90,i,0x04, 0x00);// Reloop Blue
		sendMidi(uniqueId,0x92,i,0x1F, 0x00); //Cue
		sendMidi(uniqueId,0x92,i,0x20, 0x00); //Play/Pause A
		sendMidi(uniqueId,0x92,i,0x08, 0x00);// hot cue 1
		sendMidi(uniqueId,0x92,i,0x09, 0x00);// hot cue 2
		sendMidi(uniqueId,0x92,i,0x0A, 0x00);// hot cue 3
		sendMidi(uniqueId,0x92,i,0x0B, 0x00);// hot cue 4
		sendMidi(uniqueId,0x92,i,0x02, 0x00);// Reloop Red
		sendMidi(uniqueId,0x92,i,0x1D, 0x00);// Vinyl (KeyLock)
		sendMidi(uniqueId,0x92,i,0x03, 0x00);// Loop Out Blue
		sendMidi(uniqueId,0x92,i,0x04, 0x00);// Reloop Blue
		sendMidi(uniqueId,0x90,i,0x0F, 0x00);// Echo
		sendMidi(uniqueId,0x90,i,0x10, 0x00);// Flanger
		sendMidi(uniqueId,0x90,i,0x11, 0x00);// Reverse
		sendMidi(uniqueId,0x90,i,0x12, 0x00);// Phaser
		sendMidi(uniqueId,0x90,i,0x13, 0x00);// Brake
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	}
}
//----------------------------------------------------------------------------- 
// 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
var lastCursorValue=0;
//----------------------------------------------------------------------------- 
// 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', 0, data2);
		if(channel==2) call('setDeckPitch', 1, data2);
		//if(channel==4) call('setDeckPitch', 2, 0x7F-data2);
		//if(channel==5) call('setDeckPitch', 3, 0x7F-data2);
		break;
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{

		// per deck
		// deck A
		case 0x17: 
				if(data2==0x3F) call('deckLoopPrev', 0, data2);
				if(data2>0x40) call('deckLoopNext', 0, data2); 
				break;
		case 0x16:
				if(data2==0x3F) call('lbMoveCursor', -1); 
				if(data2>0x40) call('lbMoveCursor', +1); 
				break;
		// jog-wheel
		case 0x18: 
			var val1 = data2-0x40;
			if(jogWheelsInScratchMode[channel]) call('scratchDeckJW', 0, val1, 145);else call('moveDeckJogWheel', 0, val1, 145);
			break;
		case 0x28: // least-significant 0x00-0x7F jog-wheel
			var val1 = data2-0x40;
			if(channel==2) if(jogWheelsInScratchMode[channel]) call('scratchDeckJW', 1, val1, 145);else call('moveDeckJogWheel', 1, val1, 145);
			break;
		
		//deck B
		case 0x26: 
				if(channel==2) if(data2==0x3F) call('lbMoveCursor', -1);
				if(channel==2) if(data2>0x40) call('lbMoveCursor', +1);
				break;
		case 0x27: 
				if(channel==2) if(data2==0x3F) call('deckLoopPrev', 1, data2);
				if(channel==2) if(data2>0x40) call('deckLoopNext', 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
				//debug('wheel old='+jogWheelsLastValue[deck]+' new='+val1+' jwDiff='+jwDiff);
				// save new pos
				jogWheelsLastValue[deck] = val1;
				// params: (deck, increment, total positions per complete revolution) 
				if(jogWheelsInScratchMode) call('scratchDeckJW', deck, jwDiff, 2730);else call('moveDeckJogWheel', deck, jwDiff, 2730);// 16384/6
			}
			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)
		{
		// global
		//case 0x29: if(data2>0x3F) call('lbMoveCursor', -1); break;
		//case 0x2A: if(data2>0x3F) call('lbMoveCursor', +1); break;
		//case 0x2B: if(data2>0x3F) call('lbListSelect', 1); break;
		case 0x06: 
				if(channel==0) if(data2>0x3F) call('lbToggleSelect', data2);
				if(channel==2) if(data2>0x3F) call('lbToggleSelect', data2); 
				break;
		case 0x05: 
				if(channel==0) if(data2>0x3F) call('deckLoopMore', 0, data2); 
				if(channel==2) if(data2>0x3F) call('deckLoopMore', 1, data2);
				break;
		case 0x0D:
				if(channel==0) if(data2>0x3F) call('deckMonitor', 0, data2);
				if(channel==2) if(data2>0x3F) call('deckMonitor', 1, data2);
				break;
		case 0x15:
				if(channel==0) if(data2>0x3F) call('deckResetPitch', 0, data2);
				if(channel==2) if(data2>0x3F) call('deckResetPitch', 1, data2);
				break;
		case 0x01: 
				if(channel==0) call('deckLBLoad', 0, data2);// deck A
				if(channel==2) call('deckLBLoad', 1, data2);// deck B
				break;
		case 0x02: 
				if(channel==0) call('deckLoopInEnd', 0, data2);// deck A
				if(channel==2) call('deckLoopInEnd', 1, data2);// deck B
				break;
		case 0x03: 
				if(channel==0) call('deckLoopExit', 0, data2); // deck A
				if(channel==2) call('deckLoopExit', 1, data2); // deck B
				break;
		case 0x04:
			var deck = 0;
			if(channel==2) deck=1;
			var looping = getValue('looping', deck);
			if(bReloopLastAction[deck]==-1) // if no action was already started
			{
				if(looping)
				{
					bReloopLastAction[deck] = 1;
					call('deckLoopExit', deck, data2);
				}else
				{
					bReloopLastAction[deck] = 0;
					call('deckLoopReloop', deck, data2); 
				}
			}else
			{
				if(bReloopLastAction[deck])
					call('deckLoopExit', deck, data2);
				else
					call('deckLoopReloop', deck, data2);
				// reset
				bReloopLastAction[deck] = -1;
			}
			break;
		case 0x1E: 
				if(channel==0) call('deckCuePlay', 0, data2); // deck A
				if(channel==2) call('deckCuePlay', 1, data2); // deck B
				break;
		case 0x1F: 
				if(channel==0) call('deckCue', 0, data2); // deck A
				if(channel==2) call('deckCue', 1, data2); // deck B
				break;
		case 0x20: 
				if(channel==0) call('deckPlayPause', 0, data2); // deck A
				if(channel==2) call('deckPlayPause', 1, data2); // deck B
				break;
		case 0x21: 
				if(channel==0) call('deckSync', 0, data2); // deck A
				if(channel==2) call('deckSync', 1, data2); // deck B
				break;
		case 0x22: 
				if(channel==0) call('deckBendDown', 0, data2); // deck A
				if(channel==2) call('deckBendDown', 1, data2); // deck B
				break;
		case 0x23: 
				if(channel==0) call('deckBendUp', 0, data2); // deck A
				if(channel==2) call('deckBendUp', 1, data2); // deck B
				break;
		case 0x16: 
				if(channel==0) call('deckSearchLeft', 0, data2); // deck A
				if(channel==2) call('deckSearchLeft', 1, data2); // deck B
				break;
		case 0x17: 
				if(channel==0) call('deckSearchRight', 0, data2); // deck A
				if(channel==2) call('deckSearchRight', 1, data2); // deck B
				break;
		case 0x1D: 
				if(channel==0) call('deckKeyLock', 0, data2); // deck A
				if(channel==2) call('deckKeyLock', 1, data2); // deck B
				break;
		case 0x08: 
				if(channel==0) call('deckCuePos1', 0, data2); // deck A
				if(channel==2) call('deckCuePos1', 1, data2); // deck B
				break;
		case 0x09: 
				if(channel==0) call('deckCuePos2', 0, data2); // deck A
				if(channel==2) call('deckCuePos2', 1, data2); // deck B
				break;
		case 0x0A: 
				if(channel==0) call('deckCuePos3', 0, data2); // deck A
				if(channel==2) call('deckCuePos3', 1, data2); // deck B
				break;
		case 0x0B: 
				if(channel==0) call('deckCuePos4', 0, data2); // deck A
				if(channel==2) call('deckCuePos4', 1, data2); // deck B
				break;
		case 0x0F:
				if(channel==0) call('deckFxEcho', 0, data2); // deck A
				if(channel==2) call('deckFxEcho', 1, data2);
				break;
		case 0x10:
				if(channel==0) call('deckFxFlanger', 0, data2);
				if(channel==2) call('deckFxFlanger', 1, data2);
				break;
		case 0x11:
				if(channel==0) call('deckReverse', 0, data2);
				if(channel==2) call('deckReverse', 1, data2);
				break;
		case 0x12:
				if(channel==0) call('deckFxPhaser', 0, data2);
				if(channel==2) call('deckFxPhaser', 1, data2);
				break;
		case 0x13:
				if(channel==0) call('deckBrake', 0, data2);
				if(channel==2) call('deckBrake', 1, 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);
	if(deck>=2) return;
	var d=0; if(deck==1) d=2;// 46 - 12 (cue code from deck B - cue code from deck A)
	if(name=='cue') 		sendMidi(uniqueId,0x90,d,0x1F,(param1 ? 0x7F : 0x00));
	if(name=='play') 		sendMidi(uniqueId,0x90,d,0x20,(param1 ? 0x7F : 0x00));
	if(name=='onbeat') 		sendMidi(uniqueId,0x90,d,0x21,(param1 ? 0x7F : 0x00));// 17 for red; 22 for blue;
	if(deck<2)
	{
		if(name=='cuepos1')	sendMidi(uniqueId,0x90,d,0x08,(param1 ? 0x7F : 0x00));// red
		if(name=='cuepos2')	sendMidi(uniqueId,0x90,d,0x09,(param1 ? 0x7F : 0x00));// red
		if(name=='cuepos3')	sendMidi(uniqueId,0x90,d,0x0A,(param1 ? 0x7F : 0x00));// red
		if(name=='cuepos4')	sendMidi(uniqueId,0x90,d,0x0B,(param1 ? 0x7F : 0x00));// red
	}
	if(name=='loopIn')
	{
		sendMidi(uniqueId,0x90,d,0x02,(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,d,0x02,0x00);// Loop In Red (off)		
		sendMidi(uniqueId,0x90,d,0x03,(param1 ? 0x7F : 0x00));// Loop Out Red (on or off)
		sendMidi(uniqueId,0x90,d,0x04,(param1 ? 0x7F : 0x00));// Reloop Red (on or off)
	}
	if(name=='monitor')		sendMidi(uniqueId,0x90,d,0x0D,(param1 ? 0x7F : 0x00));
	if(name=='keyLock')		sendMidi(uniqueId,0x90,d,0x1D,(param1 ? 0x7F : 0x00));// red -- Vinyl (KeyLock)
	if(name=='fxEcho')		sendMidi(uniqueId,0x90,d,0x0F,(param1 ? 0x7F : 0x00));
	if(name=='fxFlanger')	sendMidi(uniqueId,0x90,d,0x10,(param1 ? 0x7F : 0x00));
	if(name=='reverse')		sendMidi(uniqueId,0x90,d,0x11,(param1 ? 0x7F : 0x00));
	if(name=='fxPhaser')	sendMidi(uniqueId,0x90,d,0x12,(param1 ? 0x7F : 0x00));
	if(name=='brake')		sendMidi(uniqueId,0x90,d,0x13,(param1 ? 0x7F : 0x00));
}
//----------------------------------------------------------------------------- 
// 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;
		}
	}
}
//----------------------------------------------------------------------------- 