//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2012 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Stanton SCS.4DJ'
//----------------------------------------------------------------------------- 
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 "Stanton SCS.4DJ";
}
//----------------------------------------------------------------------------- 
// 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 (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 "0001602C01";
	// small '0001602C01', full '0001602C0100010403070FF7'
}
//----------------------------------------------------------------------------- 
// 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 "SCS.4DJ";
	return false;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "SCS.4DJ";
	return false;
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var jogWheelsInScratchMode=[0,0,0,0];
var loopingInBlinkLed=[-1,-1,-1,-1];// -1 means disabled
var bReloopLastAction=[-1,-1,-1,-1];// used to properly simulate button press and release
var effectSelected=[1,1];// 0 autopan, 1 flanger, 2 beatwaw, 3 echo;
//----------------------------------------------------------------------------- 
function setCurrentEffect(uniqueId, deck, fx)
{
	effectSelected[deck] = fx;
	if(deck==0)
	{
		sendMidi(uniqueId,0x90,0,0x0A,(fx==0) ? 0x7F : 0x00);// autopan
		sendMidi(uniqueId,0x90,0,0x0C,(fx==1) ? 0x7F : 0x00);// flanger
		sendMidi(uniqueId,0x90,0,0x0E,(fx==2) ? 0x7F : 0x00);// beatwaw
		sendMidi(uniqueId,0x90,0,0x10,(fx==3) ? 0x7F : 0x00);// echo
	}
	if(deck==1)
	{
		sendMidi(uniqueId,0x90,0,0x09,(fx==0) ? 0x7F : 0x00);// autopan
		sendMidi(uniqueId,0x90,0,0x0B,(fx==1) ? 0x7F : 0x00);// flanger
		sendMidi(uniqueId,0x90,0,0x0D,(fx==2) ? 0x7F : 0x00);// beatwaw
		sendMidi(uniqueId,0x90,0,0x0F,(fx==3) ? 0x7F : 0x00);// echo
	}
}
//----------------------------------------------------------------------------- 
// 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 SCS.4DJ ['+uniqueId+']');// this outputs a string to DebugView app on Win
	/*
	call('sleep', 3600);// delay for 4 s to allow the MIDI out to settle (after about 3.5 seconds the controller automatically sends a clear-all-leds command)
	for(var i=0x01; i<0x7F; i++)
	{
		sendMidi(uniqueId,0x90,0,i,0x00);// off
		if(i % 10 == 0) call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	}
	call('sleep', 500);// extra delay
	*/
	// deck A
	sendMidi(uniqueId,0x90,0,0x24,(getValue('play', 0) ? 0x7F : 0x00));// play-pause
	sendMidi(uniqueId,0x90,0,0x30,(getValue('keyLock', 0) ? 0x7F : 0x00));// keylock
	sendMidi(uniqueId,0x90,0,0x12, 0x00);// loop in
	sendMidi(uniqueId,0x90,0,0x18, (getValue('looping', 0) ? 0x7F : 0x00));// reloop
	sendMidi(uniqueId,0x90,0,0x1E,(getValue('monitor', 0) ? 0x7F : 0x00));// monitor
	sendMidi(uniqueId,0x90,0,0x36,(getValue('pitchIs0', 0) ? 0x7F : 0x00));// pitch 0 A
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	
	// deck B
	sendMidi(uniqueId,0x90,0,0x23,(getValue('play', 1) ? 0x7F : 0x00));// play-pause
	sendMidi(uniqueId,0x90,0,0x31,(getValue('keyLock', 1) ? 0x7F : 0x00));// keylock
	sendMidi(uniqueId,0x90,0,0x11, 0x00);// loop in
	sendMidi(uniqueId,0x90,0,0x17, (getValue('looping', 0) ? 0x7F : 0x00));// reloop	
	sendMidi(uniqueId,0x90,0,0x1D,(getValue('monitor', 1) ? 0x7F : 0x00));// monitor
	sendMidi(uniqueId,0x90,0,0x37,(getValue('pitchIs0', 1) ? 0x7F : 0x00));// pitch 0 B	
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	
	// NOT USED:
	sendMidi(uniqueId,0x90,0, 0x32, 0x00);// A level (used as on-beat)
	sendMidi(uniqueId,0x90,0, 0x33, 0x00);// B level (used as on-beat)
	sendMidi(uniqueId,0x90,0, 0x34, 0x00);// A autodj
	sendMidi(uniqueId,0x90,0, 0x35, 0x00);// B autodj
	sendMidi(uniqueId,0xB0,0, 0x30, 0x00);// L vu-meter
	sendMidi(uniqueId,0xB0,0, 0x31, 0x00);// R vu-meter
	
	// current effects
	setCurrentEffect(uniqueId, 0, 1);// flanger
	setCurrentEffect(uniqueId, 1, 1);// flanger
	/*
	for(var i=0x01; i<0x7F; i++)
	{
		sendMidi(uniqueId,0x90,0,i,0x7F);// off
		if(i % 10 == 0) 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+']');
	sendMidi(uniqueId,0x90,0,0x24,0x00);// play-pause
	sendMidi(uniqueId,0x90,0,0x30,0x00);// keylock
	sendMidi(uniqueId,0x90,0,0x12,0x00);// loop in
	sendMidi(uniqueId,0x90,0,0x18,0x00);// reloop
	sendMidi(uniqueId,0x90,0,0x1E,0x00);// monitor
	sendMidi(uniqueId,0x90,0,0x36,0x00);// pitch 0 A
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	
	// deck B
	sendMidi(uniqueId,0x90,0,0x23,0x00);// play-pause
	sendMidi(uniqueId,0x90,0,0x31,0x00);// keylock
	sendMidi(uniqueId,0x90,0,0x11,0x00);// loop in
	sendMidi(uniqueId,0x90,0,0x17,0x00);// reloop	
	sendMidi(uniqueId,0x90,0,0x1D,0x00);// monitor
	sendMidi(uniqueId,0x90,0,0x37,0x00);// pitch 0 B	
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	
	sendMidi(uniqueId,0x90,0, 0x32, 0x00);// A level (used as on-beat)
	sendMidi(uniqueId,0x90,0, 0x33, 0x00);// B level (used as on-beat)
	sendMidi(uniqueId,0x90,0, 0x34, 0x00);// A autodj
	sendMidi(uniqueId,0x90,0, 0x35, 0x00);// B autodj
	sendMidi(uniqueId,0xB0,0, 0x30, 0x00);// L vu-meter
	sendMidi(uniqueId,0xB0,0, 0x31, 0x00);// R vu-meter
}
//----------------------------------------------------------------------------- 
// 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: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		// global
		case 0x41: call('setCrossfader', data2); break;
		//case 0x15: call('setMasterGain', data2/2); break;		
		//case 0x0B: call('setMonitorGain', data2); break;
		case 0x42: call('setCuePgmMix', data2); break;
		case 0x40: // move track
			var val1 = data2-0x40;
			call('lbMoveCursor', val1);
			break;

		// deck A
		case 0x11: call('setDeckVolume', 0, data2); break;
		case 0x01: call('setDeckPitch', 0, data2); break;
		case 0x0B: call('setDeckEqLow', 0, data2); break;
		case 0x09: call('setDeckEqMid', 0, data2); break;
		case 0x07: call('setDeckEqHigh', 0, data2); break;
		case 0x0D:
		case 0x0F: // params: (deck, increment, total positions per complete revolution) 
			if(data2!=0x40)
			{
				var val1 = data2-0x40;
				if(jogWheelsInScratchMode[0]) call('scratchDeckJW', 0, val1, 256);else call('moveDeckJogWheel', 0, val1, 256);				
			}
			break;
		/*case 0x1B: // FX select (move between fx)
			var deck = 0;
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			// move to the next effect
			effectsSelected[deck] += val1;
			if(effectsSelected[deck]<0) effectsSelected[deck] += effectsTotal;
			effectsSelected[deck] = effectsSelected[deck] % effectsTotal;
			// re-set the led
			sendMidi(uniqueId,0x90,0,(deck==0)?0x63:0x64,(getValue(effectsName[effectsSelected[deck]], deck) ? 0x7F : 0x00));// effect
			break;
			*/
		case 0x03: // FX control 1 (dry-wet)
			if(effectSelected[0]==0) call('deckFxPanDW', 0, data2);
			if(effectSelected[0]==1) call('deckFxFlangerDW', 0, data2);
			if(effectSelected[0]==2) call('deckFxBeatwawDW', 0, data2);
			if(effectSelected[0]==3) call('deckFxEchoDW', 0, data2);
			break;
		case 0x05: call('setDeckCutoff', 0, data2); break;
				
		// deck B
		case 0x12: call('setDeckVolume', 1, data2); break;
		case 0x02: call('setDeckPitch', 1, data2); break;
		case 0x0C: call('setDeckEqLow', 1, data2); break;
		case 0x0A: call('setDeckEqMid', 1, data2); break;
		case 0x08: call('setDeckEqHigh', 1, data2); break;
		case 0x10:
		case 0x0E: // params: (deck, increment, total positions per complete revolution) 
			if(data2!=0x40)
			{
				var val1 = data2-0x40;
				if(jogWheelsInScratchMode[1]) call('scratchDeckJW', 1, val1, 256);else call('moveDeckJogWheel', 1, val1, 256);				
			}
			break;
		case 0x04: // FX control 1 (dry-wet)
			if(effectSelected[1]==0) call('deckFxPanDW', 1, data2);
			if(effectSelected[1]==1) call('deckFxFlangerDW', 1, data2);
			if(effectSelected[1]==2) call('deckFxBeatwawDW', 1, data2);
			if(effectSelected[1]==3) call('deckFxEchoDW', 1, data2);
			break;
		case 0x06: call('setDeckCutoff', 1, data2); break;  // FX control 2 (param 1)
		
		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// global
		//case 0x69:
		//case 0x4F: if(data2>0x3F) call('lbToggleSelect', data2); break;
		case 0x45: call('deckLBLoad', 0, data2); break;
		case 0x46: call('deckLBLoad', 1, data2); break;
		case 0x41: if(data2>0x3F) call('lbBrowserSelect', data2); break;
		case 0x42: if(data2>0x3F) call('lbListSelect', data2); break;
		case 0x44: call('record', data2); break;

		// deck A
		// 0 autopan, 1 flanger, 2 beatwaw, 3 echo;
		case 0x0A: setCurrentEffect(uniqueId, 0, 0); break;
		case 0x0C: setCurrentEffect(uniqueId, 0, 1); break;
		case 0x0E: setCurrentEffect(uniqueId, 0, 2); break;
		case 0x10: setCurrentEffect(uniqueId, 0, 3); break;
		
		case 0x28: call('deckSync', 0, data2); break;
		case 0x26: call('deckCue', 0, data2); break;
		case 0x24: call('deckPlayPause', 0, data2); break;
		case 0x2A: call('deckCuePlay', 0, data2); break;
		case 0x1E: call('deckMonitor', 0, data2); break;
		case 0x1A: call('deckBendDown', 0, data2); break;
		case 0x1C: call('deckBendUp', 0, data2); break;		
		//case 0x51: call('deckKeyLock', 0, data2); break; ?	
		case 0x01: call('deckReverse', 0, data2); break;	
		case 0x2C: call('holdDeckJogWheel', 0, data2); jogWheelsInScratchMode[0] = (jogWheelsInScratchMode[0]+1)%2; break;
		case 0x03: call('deckBrake', 0, data2); break;
		case 0x05: call('deckCuePos1', 0, data2); break;
		case 0x07: call('deckCuePos2', 0, data2); break;		
		//case 0x63: call(effectsAction[effectsSelected[0]], 0, data2); break;// toggle current effect
		// loop
		case 0x16: call('deckLoopMore', 0, data2); break;
		case 0x14: call('deckLoopLess', 0, data2); break;
		case 0x12: call('deckLoopInEnd', 0, data2); break;
		//case 0x54: call('deckLoopExit', 0, data2); break;
		//case 0x55: call('deckLoopReloop', 0, data2); break;		
		case 0x18:
			var deck = 0;
			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;		
		
		// deck B
		// 0 autopan, 1 flanger, 2 beatwaw, 3 echo;
		case 0x09: setCurrentEffect(uniqueId, 1, 0); break;
		case 0x0B: setCurrentEffect(uniqueId, 1, 1); break;
		case 0x0D: setCurrentEffect(uniqueId, 1, 2); break;
		case 0x0F: setCurrentEffect(uniqueId, 1, 3); break;
		
		case 0x27: call('deckSync', 1, data2); break;
		case 0x25: call('deckCue', 1, data2); break;
		case 0x23: call('deckPlayPause', 1, data2); break;
		case 0x29: call('deckCuePlay', 1, data2); break;
		case 0x1D: call('deckMonitor', 1, data2); break;
		case 0x19: call('deckBendDown', 1, data2); break;
		case 0x1B: call('deckBendUp', 1, data2); break;
		//case 0x52: call('deckKeyLock', 1, data2); break;
		case 0x02: call('deckReverse', 1, data2); break;
		case 0x2B: call('holdDeckJogWheel', 1, data2); jogWheelsInScratchMode[1] = (jogWheelsInScratchMode[1]+1)%2; break;
		case 0x04: call('deckBrake', 1, data2); break;
		case 0x06: call('deckCuepos1', 1, data2); break;
		case 0x08: call('deckCuepos2', 1, data2); break; // !!!CROSSFADER SWITCH
		//case 0x64: call(effectsAction[effectsSelected[1]], 1, data2); break;// toggle current effect
		// loop
		case 0x15: call('deckLoopMore', 1, data2); break;
		case 0x13: call('deckLoopLess', 1, data2); break;
		case 0x11: call('deckLoopInEnd', 1, data2); break;
		//case 0x57: call('deckLoopExit', 1, data2); break;
		//case 0x58: call('deckLoopReloop', 1, data2); break;		
		case 0x17:
			var 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;		
		
		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)
{
	if(deck>=2) return;
	//debug('notif ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	if(name=='play') 		sendMidi(uniqueId,0x90,0,(deck==0)?0x24:0x23,(param1 ? 0x7F : 0x00));
	if(name=='monitor') 	sendMidi(uniqueId,0x90,0,(deck==0)?0x1E:0x1D,(param1 ? 0x7F : 0x00));
	if(name=='keyLock')		sendMidi(uniqueId,0x90,0,(deck==0)?0x30:0x31,(param1 ? 0x7F : 0x00));
	if(name=='pitchIs0')	sendMidi(uniqueId,0x90,0,(deck==0)?0x36:0x37,(param1 ? 0x7F : 0x00));	
	if(name=='onbeat') 		sendMidi(uniqueId,0x90,0,(deck==0)?0x32:0x33,(param1 ? 0x7F : 0x00));
	
	if(name=='loopIn')
	{
		sendMidi(uniqueId,0x90,0,(deck==0)?0x12:0x11,(param1 ? 0x7F : 0x00));// Loop In (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,(deck==0)?0x12:0x11,0x00);// Loop In (off)		
		sendMidi(uniqueId,0x90,0,(deck==0)?0x18:0x17,(param1 ? 0x7F : 0x00));// Reloop (on or off)
	}
}
//----------------------------------------------------------------------------- 
function getMIDIValueForVU(vu_val)
{
	// translating VU-meter signal from decibels (dB) to 0..127 MIDI range
	var val = 0;
	var step = 18.15;
	if(vu_val>-40) val =  1 * step;
	if(vu_val>-28) val =  2 * step;
	if(vu_val>-20) val =  3 * step;
	if(vu_val>-14) val =  4 * step;
	if(vu_val>-10) val =  5 * step;
	if(vu_val> -6) val =  6 * step;
	if(vu_val> -3) val =  7 * step;
	return Math.floor(val);
}
//----------------------------------------------------------------------------- 
//var timer1=0;
// 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,(i==0)?0x12:0x11,((loopingInBlinkLed[i]<2) ? 0x7F : 0x00));// Loop In (on or off)
			loopingInBlinkLed[i]++;
			if(loopingInBlinkLed[i]>=4) loopingInBlinkLed[i] = 0;
		}
	}
	//timer1++; if(timer1<2) return;else timer1 = 0;
	var vuMasterL = getValue('vumeterMasterL');// float dB
	var vuMasterR = getValue('vumeterMasterR');// float dB
	sendMidi(uniqueId,0xB0,0, 0x30, getMIDIValueForVU(vuMasterL));// L vu-meter
	sendMidi(uniqueId,0xB0,0, 0x31, getMIDIValueForVU(vuMasterR));// R vu-meter
}
//----------------------------------------------------------------------------- 
