//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2013 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Numark N4'
//-----------------------------------------------------------------------------
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 N4";
}
//----------------------------------------------------------------------------- 
// 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 "00013F470019";
	//F07E00060200013F47001900010000007F7F7F7F004E0301010101010901040001030101F7 - Numark N4 full sysex

}
//----------------------------------------------------------------------------- 
// 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 "N4";
	return false;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "N4";
	return false;
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var jogWheelsInScratchMode=[0,0,0,0];
var jwSearchCountLeft=[0,0,0,0];
var jwSearchCountRight=[0,0,0,0];
var loopingInBlinkLed=[-1,-1,-1,-1];// -1 means disabled
// effects
var cutoffValue=[63,63,63,63];
var flangerValue=[0,0,0,0];
var beatwawValue=[0,0,0,0];
var echoValue=[0,0,0,0];
// shift key pressed
var shiftMode=[0,0,0,0];// 0 disabled; 1 enabled
//----------------------------------------------------------------------------- 
// value is 0x7F for left (-1); 0x01 for right(+1);
function trimEncoderValue(value, increment, initialValue)
{
	var outValue = 0;
	var outIncrement = 0;
	if(value>0x40) // left (decrease)
		outIncrement = increment * (value - 0x80);
	else // left (increase)
		outIncrement = increment * (value);
	outValue = initialValue + outIncrement;
	if(outValue<0) outValue = 0;
	if(outValue>0x7F) outValue = 0x7F;
	return outValue;
}
//----------------------------------------------------------------------------- 
// 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 N4 ['+uniqueId+']');// this outputs a string to DebugView app on Win	
	// send sysex init
	var sysex1=[0xF0, 0x00, 0x01, 0x3F, 0x7F, 0x47, 0x62, 0x00, 0x01, 0x01, 0xF7];//F000013F7F4762000101F7
	sendMidiSysEx(uniqueId, sysex1, sysex1.length);// on
	var sysex2=[0xF0, 0x00, 0x01, 0x3F, 0x7F, 0x47, 0x60, 0x00, 0x01, 0x54, 0x01, 0x00, 0x00, 0x00, 0x00, 0xF7];//F000013F7F47600001540100000000F7
	sendMidiSysEx(uniqueId, sysex2, sysex2.length);// request fader/knobs current values
	
	// VU-meters
	sendMidi(uniqueId,0xB0,0,0x34,0x00);// left  [0x00..0x08]
	sendMidi(uniqueId,0xB0,0,0x35,0x00);// right [0x00..0x08]
	
	// set decks A+B as default (consequently setting decks C+D off by default)
	sendMidi(uniqueId,0xB0,0,0x11,1);// dot 1 (deck A selected)
//	sendMidi(uniqueId,0xB0,0,0x11,2);// dot 3 (deck C selected)
	sendMidi(uniqueId,0xB0,0,0x28,1);// dot 2 (deck B selected)
//	sendMidi(uniqueId,0xB0,0,0x28,2);// dot 4 (deck D selected)

	// the DECK buttons led color (one for each side)
	sendMidi(uniqueId,0xB0,0,0x50,0);// deck 1 (deck A selected)
//	sendMidi(uniqueId,0xB0,0,0x50,1);// deck 3 (deck C selected)
	sendMidi(uniqueId,0xB0,0,0x51,0);// deck 2 (deck B selected)
//	sendMidi(uniqueId,0xB0,0,0x51,1);// deck 4 (deck D selected)

	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	// we set 1+3 on the left side and 2+4 on the right side of the crossfader
	// vid-fade leds (the 4 buttons on each side of the crossfader)
	sendMidi(uniqueId,0xB0,1,0x44,0x7F);// 1L
	sendMidi(uniqueId,0xB0,1,0x46,0x00);// 2L
	sendMidi(uniqueId,0xB0,1,0x48,0x7F);// 3L
	sendMidi(uniqueId,0xB0,1,0x4A,0x00);// 4L
	sendMidi(uniqueId,0xB0,1,0x45,0x00);// 1R
	sendMidi(uniqueId,0xB0,1,0x47,0x7F);// 2R
	sendMidi(uniqueId,0xB0,1,0x49,0x00);// 3R
	sendMidi(uniqueId,0xB0,1,0x4B,0x7F);// 4R
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

	// x-fader leds (the 4 buttons on each side of the crossfader)
	sendMidi(uniqueId,0xB0,0,0x44,0x7F);// 1L
	sendMidi(uniqueId,0xB0,0,0x46,0x00);// 2L
	sendMidi(uniqueId,0xB0,0,0x48,0x7F);// 3L
	sendMidi(uniqueId,0xB0,0,0x4A,0x00);// 4L
	sendMidi(uniqueId,0xB0,0,0x45,0x00);// 1R
	sendMidi(uniqueId,0xB0,0,0x47,0x7F);// 2R
	sendMidi(uniqueId,0xB0,0,0x49,0x00);// 3R
	sendMidi(uniqueId,0xB0,0,0x4B,0x7F);// 4R
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

	for(var deck=0; deck<4; deck++)
	{
		sendMidi(uniqueId,0xB0,deck+1,0x15,0x00);// shift
		sendMidi(uniqueId,0xB0,deck+1,0x3D,0x00);// bend down
		sendMidi(uniqueId,0xB0,deck+1,0x3C,0x00);// bend up
		sendMidi(uniqueId,0xB0,deck+1,0x12,(jogWheelsInScratchMode[deck] ? 0x7F : 0x00));// scratch

		sendMidi(uniqueId,0xB0,deck+1,0x08,(getValue('cue', deck) ? 0x7F : 0x00));// cue
		sendMidi(uniqueId,0xB0,deck+1,0x09,(getValue('play', deck) ? 0x7F : 0x00));// play
		sendMidi(uniqueId,0xB0,0,0x1E + deck,(getValue('play', deck) ? 0x7F : 0x00));// active (the triangle led on top of the load buttons)
		
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		sendMidi(uniqueId,0xB0,deck+1,0x07,0x00);// sync
		sendMidi(uniqueId,0xB0,deck+1,0x16,0x00);// tap
		
		sendMidi(uniqueId,0xB0,deck+1,0x10,(getValue('keyLock', deck) ? 0x7F : 0x00));// keyLock
		sendMidi(uniqueId,0xB0,deck+1,0x37,(getValue('pitchIs0', deck) ? 0x7F : 0x00));// pitchIs0
		sendMidi(uniqueId,0xB0,deck+1,0x1C,(getValue('reverse', deck) ? 0x7F : 0x00));// reverse -- pitch range led
		sendMidi(uniqueId,0xB0,0,0x40+deck,(getValue('monitor', deck) ? 0x7F : 0x00));// monitor

		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		sendMidi(uniqueId,0xB0,deck+1,0x0B,(getValue('cuepos1', deck) ? 0x7F : 0x00));// cuepos1
		sendMidi(uniqueId,0xB0,deck+1,0x0C,(getValue('cuepos2', deck) ? 0x7F : 0x00));// cuepos2
		sendMidi(uniqueId,0xB0,deck+1,0x0D,(getValue('cuepos3', deck) ? 0x7F : 0x00));// cuepos3
		sendMidi(uniqueId,0xB0,deck+1,0x0E,(getValue('cuepos4', deck) ? 0x7F : 0x00));// cuepos4

		sendMidi(uniqueId,0xB0,deck+1,0x18,0x00);// Loop In (off)		
		sendMidi(uniqueId,0xB0,deck+1,0x19,(getValue('looping', deck) ? 0x7F : 0x00));// Loop Out (on or off)
		sendMidi(uniqueId,0xB0,deck+1,0x1A,(getValue('looping', deck) ? 0x7F : 0x00));// Reloop (on or off)
		sendMidi(uniqueId,0xB0,deck+1,0x1B,0x00);// Loop more (off)
		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+']');
	// instead of powering off all leds one by one we simply send a special sysex command to the controller
	var sysex1=[0xF0, 0x00, 0x01, 0x3F, 0x7F, 0x47, 0xB0, 0x39, 0x00, 0x01, 0xF7];//F000013F7F47B0390001F7
	sendMidiSysEx(uniqueId, sysex1, sysex1.length);// leds off
	var sysex2=[0xF0, 0x00, 0x01, 0x3F, 0x7F, 0x47, 0x62, 0x00, 0x01, 0x00, 0xF7];//F000013F7F4762000100F7
	sendMidiSysEx(uniqueId, sysex2, sysex2.length);// off
	
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
	// the following are NOT set to off by the sysex

	// monitor leds
	sendMidi(uniqueId,0xB0,0,0x40,0x00);
	sendMidi(uniqueId,0xB0,0,0x41,0x00);
	sendMidi(uniqueId,0xB0,0,0x42,0x00);
	sendMidi(uniqueId,0xB0,0,0x43,0x00);

	// vid-fade leds (the 4 buttons on each side of the crossfader)
	sendMidi(uniqueId,0xB0,1,0x44,0x00);// 1L
	sendMidi(uniqueId,0xB0,1,0x46,0x00);// 2L
	sendMidi(uniqueId,0xB0,1,0x48,0x00);// 3L
	sendMidi(uniqueId,0xB0,1,0x4A,0x00);// 4L	
	sendMidi(uniqueId,0xB0,1,0x45,0x00);// 1R
	sendMidi(uniqueId,0xB0,1,0x47,0x00);// 2R
	sendMidi(uniqueId,0xB0,1,0x49,0x00);// 3R
	sendMidi(uniqueId,0xB0,1,0x4B,0x00);// 4R
	call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

	// x-fader leds (the 4 buttons on each side of the crossfader)
	sendMidi(uniqueId,0xB0,0,0x44,0x00);// 1L
	sendMidi(uniqueId,0xB0,0,0x46,0x00);// 2L
	sendMidi(uniqueId,0xB0,0,0x48,0x00);// 3L
	sendMidi(uniqueId,0xB0,0,0x4A,0x00);// 4L
	sendMidi(uniqueId,0xB0,0,0x45,0x00);// 1R
	sendMidi(uniqueId,0xB0,0,0x47,0x00);// 2R
	sendMidi(uniqueId,0xB0,0,0x49,0x00);// 3R
	sendMidi(uniqueId,0xB0,0,0x4B,0x00);// 4R
}
//----------------------------------------------------------------------------- 
// 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)
		{
		// these are 7-bit LSB for faders/knobs (we don't use them)
		case 0x21://pitch faders
		case 0x27:
		case 0x28:
		case 0x29:
		case 0x2A:
		case 0x2B:
		case 0x2C:
		case 0x2D:
		case 0x2E:
		case 0x2F:
		case 0x30:
		case 0x31:
		case 0x32:		
		case 0x33:
		case 0x34:
		case 0x35:
		case 0x36:
		case 0x37:
		case 0x38:
		case 0x39:
		case 0x3A:
		case 0x3B:
		case 0x3C:
			break;
		//----------------------------------
		// global
		case 0x07: call('setCrossfader', data2); break;
		case 0x12: call('setCuePgmMix', data2); break;
		case 0x44: // move track
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			call('lbMoveCursor', val1);
			break;
			
			// change between decks (this is a TOGGLE mode signal)
		case 0x50:
			if(data2==0x00) // deck A
			{
				sendMidi(uniqueId,0xB0,0,0x11,1);// dot 1 (deck A selected)
				sendMidi(uniqueId,0xB0,0,0x50,0);// deck 1 (deck A selected)
			}else // deck C
			{
				sendMidi(uniqueId,0xB0,0,0x11,2);// dot 3 (deck C selected)
				sendMidi(uniqueId,0xB0,0,0x50,1);// deck 3 (deck C selected)
			}
			break;
		case 0x51:
			if(data2==0x00) // deck B
			{
				sendMidi(uniqueId,0xB0,0,0x28,1);// dot 2 (deck B selected)
				sendMidi(uniqueId,0xB0,0,0x51,0);// deck 2 (deck B selected)
			}else // deck D
			{
				sendMidi(uniqueId,0xB0,0,0x28,2);// dot 4 (deck D selected)
				sendMidi(uniqueId,0xB0,0,0x51,1);// deck 4 (deck D selected)
			}
			break;

		// common
		case 0x01: call('setDeckPitch', channel-1, 0x7F-data2); break;
		case 0x20: // params: (deck, increment, total positions per complete revolution) 
			var val1 = (data2>0x40 ? data2-0x80 : data2); 
			if(shiftMode[channel-1]==0)
			{
				// normal mode (pitch bend / scratch)
				if(jogWheelsInScratchMode[channel-1]) call('scratchDeckJW', channel-1, val1, 600);else call('moveDeckJogWheel', channel-1, val1, 600);
			}else
			{
				// shift mode (search left/right)
				if(val1<0)
				{
					jwSearchCountLeft[channel-1]++;
					jwSearchCountRight[channel-1]=0;
					if(jwSearchCountLeft[channel-1]>8)
					{
						call('deckSkipBarPrev', channel-1, 0x7F);
						call('deckSkipBarPrev', channel-1, 0x00);
						jwSearchCountLeft[channel-1]=0;
					}
				}else
				{
					jwSearchCountRight[channel-1]++;
					jwSearchCountLeft[channel-1]=0;
					if(jwSearchCountRight[channel-1]>8)
					{
						call('deckSkipBarNext', channel-1, 0x7F);
						call('deckSkipBarNext', channel-1, 0x00);
						jwSearchCountRight[channel-1]=0;
					}
				}
			}
			break;
			
		// encoder effects
		case 0x57: // control 1 encoder (cutoff filter)
			cutoffValue[channel-1] = trimEncoderValue(data2, 2, cutoffValue[channel-1]); 
			call('setDeckCutoff', channel-1, cutoffValue[channel-1]); 
			break;
		case 0x58: // control 2 encoder (fx flanger)
			flangerValue[channel-1] = trimEncoderValue(data2, 3, flangerValue[channel-1]); 
			call('deckFxFlangerDW', channel-1, flangerValue[channel-1]); 
			break;
		case 0x59: // control 3 encoder (fx beawaw)
			beatwawValue[channel-1] = trimEncoderValue(data2, 3, beatwawValue[channel-1]); 
			call('deckFxBeatwawDW', channel-1, beatwawValue[channel-1]); 
			break;
		case 0x5A: // control 4 encoder (fx echo)
			echoValue[channel-1] = trimEncoderValue(data2, 3, echoValue[channel-1]); 
			call('deckFxEchoDW', channel-1, echoValue[channel-1]); 
			break;
			
		// deck A
		case 0x08: call('setDeckVolume', 0, data2); break;
		case 0x09: call('setDeckEqLow', 0, data2); break;
		case 0x0A: call('setDeckEqMid', 0, data2); break;
		case 0x0B: call('setDeckEqHigh', 0, data2); break;
		case 0x0C: call('setDeckGain', 0, data2); break;
			
		// deck B
		case 0x0D: call('setDeckVolume', 1, data2); break;
		case 0x0E: call('setDeckEqLow', 1, data2); break;
		case 0x0F: call('setDeckEqMid', 1, data2); break;
		case 0x10: call('setDeckEqHigh', 1, data2); break;
		case 0x11: call('setDeckGain', 1, data2); break;
	
		//deck C		
		case 0x13: call('setDeckVolume', 2, data2); break;
		case 0x14: call('setDeckEqLow', 2, data2); break;
		case 0x15: call('setDeckEqMid', 2, data2); break;
		case 0x16: call('setDeckEqHigh', 2, data2); break;
		case 0x17: call('setDeckGain', 2, data2); break;
			
		//deck D
		case 0x18: call('setDeckVolume', 3, data2); break;
		case 0x19: call('setDeckEqLow', 3, data2); break;
		case 0x1A: call('setDeckEqMid', 3, data2); break;
		case 0x1B: call('setDeckEqHigh', 3, data2); break;
		case 0x1C: call('setDeckGain', 3, data2); break;
		
		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// global
		case 0x08: if(data2>0x3F) call('lbToggleSelect', data2); break;
		case 0x06: call('deckLBLoad', channel-1, data2); break;
		case 0x04: // select deck A/C
		case 0x05: // select deck B/D
			// we don't do nothing here since we already deal with this -- see 0xB0 0x50+0x51
			break;
			
		case 0x49: call('setDeckAnalogIn', 2, data2); break;// analog in deck C -- toggle mode: 0x7F on (line in 3); 0x00 off (pc);
		case 0x4A: call('setDeckAnalogIn', 3, data2); break;// analog in deck D -- toggle mode: 0x7F on (line in 4); 0x00 off (pc);
		
		// nothing to do here
		case 0x4B: break;// crossfader curve (toggle: 0x7F normal; 0x00 scratch mode;)
			
		// common
		case 0x0F: call('deckSync', channel-1, data2); break;
		case 0x10: 
			if(shiftMode[channel-1]==1)
				call('deckCuePlay', channel-1, data2);
			else
				call('deckCue', channel-1, data2);
			break;
		case 0x11: 
			if(shiftMode[channel-1]==1)
				call('deckReverse', channel-1, data2);
			else
				call('deckPlayPause', channel-1, data2);
			break;
			
		// shift mode
		case 0x12: 
			if(data2>0x3F) shiftMode[channel-1] = 1;else shiftMode[channel-1] = 0; 
			sendMidi(uniqueId,0xB0,channel,0x15,data2);
			break;
		
		// the 4 buttons under the 4 encoders (for each deck) -- used as hot cue points / sampler slots (when shift pressed)
		case 0x13:
			if(shiftMode[channel-1]==1)
			{
				var samplerSlot = 1; if(channel%2==0) samplerSlot += 4;
				if(data2>0x3F) call('samplerPlay', samplerSlot);
				// alternative actions
				//call('xxxxxx', channel-1, data2);//deckBrake deckEqLowKill deckSkipBeatNext deckSearchLeft/deckSearchRight
			}else
			{
				call('deckCuePos1', channel-1, data2);
			}
			break;
		case 0x14:
			if(shiftMode[channel-1]==1)
			{
				var samplerSlot = 2; if(channel%2==0) samplerSlot += 4;
				if(data2>0x3F) call('samplerPlay', samplerSlot);
			}else
			{
				call('deckCuePos2', channel-1, data2);
			}
			break;
		case 0x15:
			if(shiftMode[channel-1]==1)
			{
				var samplerSlot = 3; if(channel%2==0) samplerSlot += 4;
				if(data2>0x3F) call('samplerPlay', samplerSlot);
			}else
			{
				call('deckCuePos3', channel-1, data2);
			}
			break;
		case 0x16:
			if(shiftMode[channel-1]==1)
			{
				var samplerSlot = 4; if(channel%2==0) samplerSlot += 4;
				if(data2>0x3F) call('samplerPlay', samplerSlot);
			}else
			{
				call('deckCuePos4', channel-1, data2);
			}
			break;

		case 0x18: 
			call('deckBendDown', channel-1, data2); 
			sendMidi(uniqueId,0xB0,channel,0x3D,data2);
			break;
		case 0x19: 
			call('deckBendUp', channel-1, data2); 
			sendMidi(uniqueId,0xB0,channel,0x3C,data2);
			break;
			// pitch range cycle/toggle
		case 0x1A:
			// 1 = 4%; 2 = 8%; 3 = 16%; 4 = 24%; 5 = 32%; 6 = 50%;
			if(data2>0x3F)
			{ 
				var currentPitchRange = getValue('pitchRange');
				currentPitchRange = currentPitchRange + 1; if(currentPitchRange>6) currentPitchRange = 1;
				call('setPitchRange', currentPitchRange);
			}
			break;
		case 0x1B: call('deckKeyLock', channel-1, data2); break;
		case 0x1E:
			if(shiftMode[channel-1]==1)
				call('deckResetPitch', channel-1, data2);
			else
				call('deckTapBpm', channel-1, data2);
			break;
		case 0x21: 
			if(data2>0x3F)
			{ 
				jogWheelsInScratchMode[channel-1] = (jogWheelsInScratchMode[channel-1] + 1) % 2;
				sendMidi(uniqueId,0xB0,channel,0x12,(jogWheelsInScratchMode[channel-1] ? 0x7F : 0x00));
			} 
			break;
					
		// loop section
		case 0x27: 
			if(shiftMode[channel-1]==1)
				call('deckLoopLess', channel-1, data2);
			else				
				call('deckLoopInEnd', channel-1, data2);
			break;
		case 0x28: 
			if(shiftMode[channel-1]==1)
				call('deckLoopMore', channel-1, data2);
			else				
				call('deckLoopExit', channel-1, data2);
			break;
		case 0x29: 
			if(shiftMode[channel-1]==1)
			{
				call('deckLoopPrev', channel-1, data2);
			}else
			{
				if(!getValue('looping', channel-1)) call('deckLoopReloop', channel-1, data2);else call('deckLoopExit', channel-1, data2);//'loopIn'
			}
			break;
		case 0x2A:
			if(shiftMode[channel-1]==1)
				call('deckLoopNext', channel-1, data2);
			else				
				call('deckLoopMore', channel-1, data2);
			break;
			
		case 0x2C: if(shiftMode[channel-1]==0) call('holdDeckJogWheel', channel-1, data2); break;
			
		case 0x31: call('setDeckMonitor', 0, data2); break;// toggle mode (0x7F: on; 0x00: off)
		case 0x32: call('setDeckMonitor', 1, data2); break;// toggle mode (0x7F: on; 0x00: off)
		case 0x33: call('setDeckMonitor', 2, data2); break;// toggle mode (0x7F: on; 0x00: off)
		case 0x34: call('setDeckMonitor', 3, data2); break;// toggle mode (0x7F: on; 0x00: off)
		
		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>=4) return;
	//debug('notif ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	if(name=='cue') 		sendMidi(uniqueId,0xB0,deck+1,0x08,(param1 ? 0x7F : 0x00));
	if(name=='play')
	{
		sendMidi(uniqueId,0xB0,deck+1,0x09,(param1 ? 0x7F : 0x00));
		// signal if the track is active or not
		var isActive = param1;
		// ATTN: we might want to also test for the crossfader pos but then again we would need 
		// to change the leds when only the crossfader is moved and that is pretty cpu consuming
		sendMidi(uniqueId,0xB0,0,0x1E + deck,(isActive ? 0x7F : 0x00));
	}
	if(name=='onbeat') 		sendMidi(uniqueId,0xB0,deck+1,0x07,(param1 ? 0x7F : 0x00));// 0x16 TAP; 0x07 SYNC;
	if(name=='keyLock') 	sendMidi(uniqueId,0xB0,deck+1,0x10,(param1 ? 0x7F : 0x00));
	if(name=='pitchIs0') 	sendMidi(uniqueId,0xB0,deck+1,0x37,(param1 ? 0x7F : 0x00));
	if(name=='reverse') 	sendMidi(uniqueId,0xB0,deck+1,0x1C,(param1 ? 0x7F : 0x00));// pitch range led
	if(name=='monitor') 	sendMidi(uniqueId,0xB0,0,0x40+deck,(param1 ? 0x7F : 0x00));

	if(name=='cuepos1') 	sendMidi(uniqueId,0xB0,deck+1,0x0B,(param1 ? 0x7F : 0x00));
	if(name=='cuepos2') 	sendMidi(uniqueId,0xB0,deck+1,0x0C,(param1 ? 0x7F : 0x00));
	if(name=='cuepos3') 	sendMidi(uniqueId,0xB0,deck+1,0x0D,(param1 ? 0x7F : 0x00));
	if(name=='cuepos4') 	sendMidi(uniqueId,0xB0,deck+1,0x0E,(param1 ? 0x7F : 0x00));

	if(name=='loopIn')
	{
		sendMidi(uniqueId,0xB0,deck+1,0x18,(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,0xB0,deck+1,0x18,0x00);// Loop In (off)		
		sendMidi(uniqueId,0xB0,deck+1,0x19,(param1 ? 0x7F : 0x00));// Loop Out (on or off)
		sendMidi(uniqueId,0xB0,deck+1,0x1A,(param1 ? 0x7F : 0x00));// Reloop (on or off)
		sendMidi(uniqueId,0xB0,deck+1,0x1B,0x00);// Loop more (off)
	}
}
//----------------------------------------------------------------------------- 
// 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<4; i++)
	{
		// loop in led
		if(loopingInBlinkLed[i]!=-1)
		{
			// actually set the led
			sendMidi(uniqueId,0xB0,i+1,0x18,((loopingInBlinkLed[i]<2) ? 0x7F : 0x00));// Loop In (on or off)
			loopingInBlinkLed[i]++;
			if(loopingInBlinkLed[i]>=4) loopingInBlinkLed[i] = 0;
		}
	}
}
//----------------------------------------------------------------------------- 
