//-----------------------------------------------------------------------------
//------------ Copyright (c)2003-2020 Xylio. All Rights Reserved. -------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Hercules DJControl Inpulse 200'
//-----------------------------------------------------------------------------
/*
ATTN:
- this script is very similar to the Inpulse 300 one (but has less functionality and some differences)
- MASTER button is firmware-implemented (it duplicates the master signal to the headphones)
- BEATMATCH GUIDE button is firmware-implemented (and it controls if the guide leds are used or not - we don't have to do anything extra)
*/
function getLanguageVersion()
{
	return 1;// do not modify this, this should be 1 for all scripts
}
//-----------------------------------------------------------------------------
function getFileVersion()
{
	return 2;// update this as needed
}
//-----------------------------------------------------------------------------
// return a user friendly name for the controller supported
function getControllerUserFriendlyName()
{
	return "DJControl Inpulse 200";
}
//-----------------------------------------------------------------------------
// 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 macOS; 2 Linux; -1 unknown
function getSupportedMidiDeviceVID(osId)
{
	return 0x06F8;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0xB126;// Inpulse 200
}
//-----------------------------------------------------------------------------
// 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 "00014E020019";// Inpulse 200
	// F0 7E 00 06 02 00 01 4E 02 00 02 00 01 00 00 00 F7 -- Hercules DJ Console MK2 complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 03 00 01 00 00 00 F7 -- Hercules DJ Control MP3 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 04 00 01 00 00 00 F7 -- Hercules DJ Console RMX complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 05 00 01 00 00 00 F7 -- Hercules DJ Control Steel complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 07 00 01 00 00 00 F7 -- Hercules DJ Control MP3e2 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 08 00 01 00 00 00 F7 -- Hercules DJ Console MK4 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0B 00 01 00 00 00 F7 -- Hercules DJ Control Air complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0C 00 01 00 00 00 F7 -- Hercules DJ Control Instinct complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 10 00 01 00 00 00 F7 -- Hercules DJ Control Air+ complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0A 00 01 00 00 00 F7 -- Hercules DJ4Set complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 09 00 01 00 00 00 F7 -- Hercules DJ Console 4-Mx
	// F0 7E 7F 06 02 00 01 4E 02 00 16 00 01 00 00 00 F7 -- Hercules DJ Control Compact
	// F0 7E 7F 06 02 00 01 4E 02 00 12 00 01 00 00 00 F7 -- Hercules Universal DJ
	// F0 7E 7F 06 02 00 01 4E 02 00 1B 00 01 00 00 00 F7 -- Hercules DJControl Starlight
	// F0 7E 7F 06 02 00 01 4E 02 00 19 00 01 00 00 00 F7 -- Hercules DJControl Inpulse 200
	// F0 7E 7F 06 02 00 01 4E 02 00 1A 00 01 00 00 00 F7 -- Hercules DJControl Inpulse 300
}
//-----------------------------------------------------------------------------
// 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 macOS; 2 Linux; -1 unknown
function getSupportedMidiDeviceNameForInput(osId)
{
	//if(osId==1) return "DJControl Inpulse 200";
	return "DJControl Inpulse 200";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "DJControl Inpulse 200";
	return "DJControl Inpulse 200";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// the Energy meter (backlight on browser ring) can display an RGB color (pre-defined colors)
// send MIDI: 90 05 Value
var rgbToMidi = [
	// 0x
	'#000000', '0x00', '#000451', '0x01', '#0010A3', '0x02', '#001EF5', '0x03',
	'#0A2304', '0x04', '#092452', '0x05', '#0329A3', '0x06', '#0030F5', '0x07',
	'#1C460F', '0x08', '#1B4753', '0x09', '#1849A4', '0x0A', '#124DF5', '0x0B',
	'#2E6B1B', '0x0C', '#2D6B56', '0x0D', '#2C6CA5', '0x0E', '#296FF6', '0x0F',
	// 1x
	'#408E27', '0x10', '#3F8E5A', '0x11', '#3E8FA7', '0x12', '#3C91F7', '0x13',
	'#52B234', '0x14', '#51B260', '0x15', '#51B3AA', '0x16', '#4FB5F9', '0x17',
	'#63D640', '0x18', '#63D666', '0x19', '#62D6AD', '0x1A', '#61D7FB', '0x1B',
	'#75FA4C', '0x1C', '#75FA6E', '0x1D', '#74FAB1', '0x1E', '#73FBFD', '0x1F',
	// 2x
	'#4D0A05', '0x20', '#4D0D52', '0x21', '#4C16A3', '0x22', '#4B22F5', '0x23',
	'#4F2609', '0x24', '#4F2852', '0x25', '#4E2CA4', '0x26', '#4C33F5', '0x27',
	'#534812', '0x28', '#534954', '0x29', '#524BA4', '0x2A', '#504FF5', '0x2B',
	'#5A6C1D', '0x2C', '#5A6C57', '0x2D', '#596DA6', '0x2E', '#5870F6', '0x2F',
	// 3x
	'#638F29', '0x30', '#638F5B', '0x31', '#6290A7', '0x32', '#6192F7', '0x33',
	'#6FB335', '0x34', '#6FB360', '0x35', '#6EB4AA', '0x36', '#6DB5F9', '0x37',
	'#7CD641', '0x38', '#7BD667', '0x39', '#7BD7AD', '0x3A', '#7AD8FB', '0x3B',
	'#8AFA4D', '0x3C', '#8AFA6E', '0x3D', '#89FBB1', '0x3E', '#88FCFD', '0x3F',
	// 4x
	'#9C1E14', '0x40', '#9C2054', '0x41', '#9C25A4', '0x42', '#9B2DF6', '0x43',
	'#9D3016', '0x44', '#9D3155', '0x45', '#9C35A5', '0x46', '#9C3BF6', '0x47',
	'#9F4D1C', '0x48', '#9E4E56', '0x49', '#9E50A5', '0x4A', '#9D54F6', '0x4B',
	'#A26F24', '0x4C', '#A26F59', '0x4D', '#A171A7', '0x4E', '#A173F7', '0x4F',
	// 5x
	'#A6912E', '0x50', '#A6915D', '0x51', '#A692A8', '0x52', '#A594F8', '0x53',
	'#ADB539', '0x54', '#ADB562', '0x55', '#ACB6AB', '0x56', '#ACB7F9', '0x57',
	'#B4D744', '0x58', '#B4D869', '0x59', '#B4D8AE', '0x5A', '#B3D9FB', '0x5B',
	'#BDFB50', '0x5C', '#BDFB70', '0x5D', '#BDFCB2', '0x5E', '#BCFDFE', '0x5F',
	// 6x
	'#EB3323', '0x60', '#EB3459', '0x61', '#EA37A6', '0x62', '#EA3CF7', '0x63',
	'#EB3F25', '0x64', '#EB4059', '0x65', '#EB42A7', '0x66', '#EA47F7', '0x67',
	'#EC5729', '0x68', '#EC575B', '0x69', '#EC59A7', '0x6A', '#EB5CF7', '0x6B',
	'#EE752F', '0x6C', '#EE755E', '0x6D', '#EE77A9', '0x6E', '#ED79F8', '0x6F',
	// 7x
	'#F19637', '0x70', '#F19661', '0x71', '#F197AA', '0x72', '#F099F9', '0x73',
	'#F5B840', '0x74', '#F5B866', '0x75', '#F4B9AD', '0x76', '#F4BAFB', '0x77',
	'#FADA4A', '0x78', '#FADA6C', '0x79', '#F9DBB0', '0x7A', '#F9DCFD', '0x7B',
	'#FFFD54', '0x7C', '#FFFE73', '0x7D', '#FFFEB4', '0x7E', '#FFFFFF', '0x7F',
];
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var assistantEnabled=0;
var lastAssistantLEDColorCode=0;
var jogWheelsInScratchMode=[0,0,0,0];
var previousAutoLoopLightLed=[-1,-1,-1,-1];// 4 decks leds
// we store the leds current status so we don't overload the MIDI driver with unnecessary LED changes
var lastLEDStateBeatAlignL=[0,0,0,0];
var lastLEDStateBeatAlignR=[0,0,0,0];
var lastLEDStateTempoAlignUp=[0,0,0,0];
var lastLEDStateTempoAlignDown=[0,0,0,0];
//-----------------------------------------------------------------------------
// 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 Hercules DJControl Inpulse 200 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	// setting audio (the actual routing is done automatically for this controller so
	// we don't need to manually set "RoutingOutHeadphones", "RoutingOutDeckA", etc
	// ATTN: we first try to set ASIO mode, and only then regular CoreAudio, DirectSound audio/WDM/Wasapi
	if(setAudioDevice("DJControl Inpulse 200 ASIO|||Hercules DJ Control Inpulse 200|||Hercules DJControl Inpulse 200|||DJControl Inpulse 200|||Speakers (DJControl Inpulse 200) (WASAPI)|||Speakers (DJControl Inpulse 200)"))
	{
		//debug('AudioDevice was set OK');
		setSetting('AudioDevice2', "NO AUDIO");
	}

	//sendMidi(uniqueId, status, channel, data1, data2);
	for(var deck=0; deck<2; deck++)
	{
		sendMidi(uniqueId,0x90,deck+1,0x03,(jogWheelsInScratchMode[deck] ? 0x7F : 0x00));// scratch mode
	}

	// assistant
	sendMidi(uniqueId, 0x90, 0, 0x03, assistantEnabled ? 0x7F : 0x00);

	// the Energy meter (backlight on browser ring) RGB color
	// send all colors to the host - see below an example of using 'getNearestColorInTable()'
	setColorTable(uniqueId, rgbToMidi, 128);
	// test #1 - with custom cue colors (provided by the app itself)
	//var ledValue = parseInt(getNearestColorInTable(uniqueId, getValue('cuecolor'+idx, deck)));
	//sendMidi(uniqueId,0x90,0,0x05,(param1 ? ledValue : 0x00));
	// test #2 - actual color provided by the script directly
	//var ledValue = parseInt(getNearestColorInTable(uniqueId, '#00AEFF'));//'#FF0000'));
	//sendMidi(uniqueId,0x90,0,0x05,ledValue);
	sendMidi(uniqueId,0x90,0,0x05,0x00);// turn off the Energy meter

	//------------------------------------
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
	// update all analog controls (eg. fader/knob positions)
	sendMidi(uniqueId,0xB0,0,0x7F,0x7F);
}
//-----------------------------------------------------------------------------
// 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 leds off
	for(var deck=0; deck<2; deck++)
	{
		sendMidi(uniqueId,0x90,deck+1,0x03,0x00);// scratch mode
		setBeatmatchGuide_BeatAlign(uniqueId, deck, 0, 0);
	}
	setBeatmatchGuide_Tempo(uniqueId, 0, 0, 0, 0);
	sendMidi(uniqueId,0x90,0,0x05,0x00);// turn off the Energy meter
	sendMidi(uniqueId,0x90,0,0x03,0x00);// turn off the assistant
	//------------------------------------
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually de-init all leds/lcd screens)
}
//-----------------------------------------------------------------------------
// 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 or per-deck
		case 0x00: if(channel == 0) call('setCrossfader', data2);else call('setDeckVolume', channel-1, data2); break;// 0x00 low-res + 0x20 high-res
		case 0x01: // 0x01 low-res + 0x21 high-res
			if(channel == 0)
			{
				var val1 = (data2>0x40 ? data2-0x80 : data2);
				call('lbMoveCursor', val1);
			}else
			{
				call('setDeckCutoff', channel-1, data2);
			}
			break;
		case 0x02: if(channel > 0)  call('setDeckEqLow', channel-1, data2); break;// 0x02 low-res + 0x22 high-res
		case 0x03: if(channel == 0) call('setMasterVolume', data2);else call('setDeckEqMid', channel-1, data2); break;// 0x03 low-res + 0x23 high-res
		case 0x04: if(channel == 0) call('setMonitorVolume', data2);else call('setDeckEqHigh', channel-1, data2); break;// 0x04 low-res + 0x24 high-res
		case 0x05: if(channel == 0) /*call('setMonitorVolume', data2)*/;else call('setDeckGain', channel-1, data2); break;// 0x05 low-res + 0x25 high-res
		case 0x08: if(channel > 0)  call('setDeckPitch', channel-1, data2); break;// 0x08 low-res + 0x28 high-res
		// jog-wheels
		case 0x09: // params: (deck, increment, total positions per complete revolution) -- without hold
		{
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			var deck = channel - 1;
			if(channel < 4)
			{
				if(jogWheelsInScratchMode[deck])
					call('scratchDeckJW', deck, val1, 512);
				else
					call('moveDeckJogWheel', deck, val1, 512);
			}else
			{
				var deck = channel - 4;
				call('searchDeckJW', deck, val1, 512);
			}
			break;
		}
		case 0x0A: // with hold
		{
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			var deck = channel - 1;
			if(channel < 4)
			{
				call('scratchDeckJW', deck, val1, 512);
			}else
			{
				var deck = channel - 4;
				call('searchDeckJW', deck, val1, 512);
			}
			break;
		}

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		// global or per-deck
		switch(data1)
		{
		case 0x00:
			if(channel == 0) if(data2>0x3F) call('lbToggleSelect', data2);// browse encoder press
			if(channel >= 6) call('deckCuePos1', channel-6, data2);// PADs HOT-CUE 1
			break;
		case 0x01:
			if(channel >= 6) call('deckCuePos2', channel-6, data2);// PADs HOT-CUE 2
			break;
		case 0x02:
			if(channel >= 6) call('deckCuePos3', channel-6, data2);// PADs HOT-CUE 3
			break;
		case 0x03:
			if(channel >= 6) call('deckCuePos4', channel-6, data2);// PADs HOT-CUE 4
			if(channel == 0) // ASSISTANT button (under the browse encoder)
			{
				// use it as toggle
				//if(data2 > 0x3F) call('lbToggleSelect', data2);

				// use it to assist the beat-matching (when enabled it changes the color of the browser/energy ring)
				if(data2 > 0x3F)
				{
					assistantEnabled = (assistantEnabled + 1) % 2;
					sendMidi(uniqueId, 0x90, 0, 0x03, assistantEnabled ? 0x7F : 0x00);
				}
			}
			if((channel > 0) && (channel < 5)) // vinyl
			{
				if(data2 > 0x3F)
				{
					var deck = channel - 1;
					jogWheelsInScratchMode[deck] = (jogWheelsInScratchMode[deck] + 1) % 2;
					sendMidi(uniqueId,0x90,1+deck,0x03,(jogWheelsInScratchMode[deck] ? 0x7F : 0x00));
					break;
				}
			}
			break;

		case 0x05:
			if(channel == 1) call('deckSync',  0, data2);
			if(channel == 2) call('deckSync',  1, data2);
			// with Shift
			if(channel == 4) call('deckMatch', 0, data2);
			if(channel == 5) call('deckMatch', 1, data2);
			break;
		case 0x06:
			if(channel == 1) call('deckCue', 0, data2);
			if(channel == 2) call('deckCue', 1, data2);
			// with Shift
			if(channel == 4) call('deckCuePlay', 0, data2);
			if(channel == 5) call('deckCuePlay', 1, data2);
			break;
		case 0x07:
			if(channel == 1) call('deckPlayPause', 0, data2);
			if(channel == 2) call('deckPlayPause', 1, data2);
			// with Shift
			if(channel == 4) call('deckReverse', 0, data2);
			if(channel == 5) call('deckReverse', 1, data2);
			break;

		case 0x08:
			if(channel >= 6) call('deckCuePos5', channel-6, data2);// PADs HOT-CUE 1 + SHIFT
			if((channel > 0) && (channel < 5)) call('holdDeckJogWheel', channel-1, data2);// jog-wheel touch
			break;
		case 0x09:
			if(channel >= 6) call('deckCuePos6', channel-6, data2);// PADs HOT-CUE 2 + SHIFT
			if(channel == 1) call('deckLoopInEnd', 0, data2);// LOOP IN
			if(channel == 2) call('deckLoopInEnd', 1, data2);// LOOP IN
			if(channel == 4) call('deckLoopPrev', 0, data2);// LOOP IN + SHIFT
			if(channel == 5) call('deckLoopPrev', 1, data2);// LOOP IN + SHIFT
			break;
		case 0x0A:
			if(channel >= 6) call('deckCuePos7', channel-6, data2);// PADs HOT-CUE 3 + SHIFT
			if(channel == 1) call('deckLoopExitOrReloop', 0, data2);// LOOP OUT
			if(channel == 2) call('deckLoopExitOrReloop', 1, data2);// LOOP OUT
			if(channel == 4) call('deckLoopNext', 0, data2);// LOOP OUT + SHIFT
			if(channel == 5) call('deckLoopNext', 1, data2);// LOOP OUT + SHIFT
			break;
		case 0x0B:
			if(channel >= 6) call('deckCuePos8', channel-6, data2);// PADs HOT-CUE 4 + SHIFT
			break;

		case 0x0C: if(channel > 0) call('deckMonitor', channel-1, data2); break;
		case 0x0D: if(channel > 0) if(data2>0x3F) call('deckLBLoad', channel-1, data2); break;// LOAD

		case 0x10:
			if(channel >= 6) call('deckLoopRoll1', channel-6, data2);// PADs ROLL 1
			break;
		case 0x11:
			if(channel >= 6) call('deckLoopRoll2', channel-6, data2);// PADs ROLL 2
			break;
		case 0x12:
			if(channel >= 6) call('deckLoopRoll3', channel-6, data2);// PADs ROLL 3
			break;
		case 0x13:
			if(channel >= 6) call('deckLoopRoll4', channel-6, data2);// PADs ROLL 4
			break;
		// with Shift
		case 0x18:
			if(channel >= 6) call('deckLoopRoll5', channel-6, data2);// PADs ROLL 1 + SHIFT
			break;
		case 0x19:
			if(channel >= 6) call('deckLoopRoll6', channel-6, data2);// PADs ROLL 2 + SHIFT
			break;
		case 0x1A:
			if(channel >= 6) call('deckLoopRoll7', channel-6, data2);// PADs ROLL 3 + SHIFT
			break;
		case 0x1B:
			if(channel >= 6) call('deckLoopRoll8', channel-6, data2);// PADs ROLL 4 + SHIFT
			break;
			/*
			case 0x2E: call('setAutoDj', data2); break;
			case 0x2B: call('record', data2); break;
			case 0x0D: call('deckBrake', 0, data2); break;
			*/

		case 0x20:
			if(channel >= 6) call('deckFxFlanger', channel-6, data2);// PADs FX 1
			break;
		case 0x21:
			if(channel >= 6) call('deckFxEcho', channel-6, data2);// PADs FX 2
			break;
		case 0x22:
			if(channel >= 6) call('deckFxBeatwaw', channel-6, data2);// PADs FX 3
			break;
		case 0x23:
			if(channel >= 6) call('deckFxReverb', channel-6, data2);// PADs FX 4
			break;
		// PADs FX 1..4 + SHIFT (0x28, 0x29, 0x2A, 0x2B) - not used/implemented (left for the user to map)

		case 0x30:
			if(channel == 6) call('sampler1', data2);else call('sampler5', data2);// PADs SAMPLER 1
			break;
		case 0x31:
			if(channel == 6) call('sampler2', data2);else call('sampler6', data2);// PADs SAMPLER 2
			break;
		case 0x32:
			if(channel == 6) call('sampler3', data2);else call('sampler7', data2);// PADs SAMPLER 3
			break;
		case 0x33:
			if(channel == 6) call('sampler4', data2);else call('sampler8', data2);// PADs SAMPLER 4
			break;
		// PADs SAMPLER 1..4 + SHIFT (0x38, 0x39, 0x3A, 0x3B) - not used/implemented (left for the user to map)

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	default: messageNotProcessed = true;
	}
	// ATTN: do NOT call this unless you are debugging the script itself (logging is pretty slow)
	//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 nameLen = name.length;

	/*
	if(name=='autoDj') 		sendMidi(uniqueId,0x90,0,0x2E,(param1 ? 0x7F : 0x00));// automix
	if(name=='recording')	sendMidi(uniqueId,0x90,0,0x2B,(param1 ? 0x7F : 0x00));// record
	if(name=='brake') 		sendMidi(uniqueId,0x90,0,(deck==0)?0x0D:0x3D,(param1 ? 0x7F : 0x00));
	*/

	if(name=='onbeat') 		sendMidi(uniqueId,0x90, deck+1, 0x05, (param1 ? 0x7F : 0x00));
	if(name=='cue') 		sendMidi(uniqueId,0x90, deck+1, 0x06, (param1 ? 0x7F : 0x00));
	if(name=='play') 		sendMidi(uniqueId,0x90, deck+1, 0x07, (param1 ? 0x7F : 0x00));
	// with Shift
	if(name=='reverse') 	sendMidi(uniqueId,0x90, deck+4, 0x07, (param1 ? 0x7F : 0x00));

	if(name=='monitor') 	sendMidi(uniqueId,0x90, deck+1, 0x0C, (param1 ? 0x7F : 0x00));

	// Pads - Hot-Cue
	if(name=='cuepos1') 	sendMidi(uniqueId,0x90, deck+6, 0x00, (param1 ? 0x7F : 0x00));
	if(name=='cuepos2') 	sendMidi(uniqueId,0x90, deck+6, 0x01, (param1 ? 0x7F : 0x00));
	if(name=='cuepos3') 	sendMidi(uniqueId,0x90, deck+6, 0x02, (param1 ? 0x7F : 0x00));
	if(name=='cuepos4') 	sendMidi(uniqueId,0x90, deck+6, 0x03, (param1 ? 0x7F : 0x00));
	// Shift
	if(name=='cuepos5') 	sendMidi(uniqueId,0x90, deck+6, 0x08, (param1 ? 0x7F : 0x00));
	if(name=='cuepos6') 	sendMidi(uniqueId,0x90, deck+6, 0x09, (param1 ? 0x7F : 0x00));
	if(name=='cuepos7') 	sendMidi(uniqueId,0x90, deck+6, 0x0A, (param1 ? 0x7F : 0x00));
	if(name=='cuepos8') 	sendMidi(uniqueId,0x90, deck+6, 0x0B, (param1 ? 0x7F : 0x00));

	// Pads - Loop
	if(name=='loopSize')
	{
		var loopSize = getValue('loopSize', deck);
		sendMidi(uniqueId,0x90, deck+6, 0x10, (loopSize==64  ? 0x7F : 0x00));// loop 1
		sendMidi(uniqueId,0x90, deck+6, 0x11, (loopSize==128 ? 0x7F : 0x00));// loop 2
		sendMidi(uniqueId,0x90, deck+6, 0x12, (loopSize==256 ? 0x7F : 0x00));// loop 4
		sendMidi(uniqueId,0x90, deck+6, 0x13, (loopSize==512 ? 0x7F : 0x00));// loop 8
	}

	if(name=='loopBlink')
	{
		sendMidi(uniqueId,0x90, deck+1, 0x09, (param1 ? 0x7F : 0x00));// deckLoopInEnd // PADs LOOP 1 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
		sendMidi(uniqueId,0x90, deck+1, 0x0A, (param1 ? 0x7F : 0x00));// deckLoopExitOrReloop // PADs LOOP 2 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
	}

	// Pads - FX
	if(name=='fxFlanger') 	sendMidi(uniqueId,0x90, deck+6, 0x20, (param1 ? 0x7F : 0x00));
	if(name=='fxEcho') 		sendMidi(uniqueId,0x90, deck+6, 0x21, (param1 ? 0x7F : 0x00));
	if(name=='fxBeatwaw') 	sendMidi(uniqueId,0x90, deck+6, 0x22, (param1 ? 0x7F : 0x00));
	if(name=='fxReverb') 	sendMidi(uniqueId,0x90, deck+6, 0x23, (param1 ? 0x7F : 0x00));

	// Pads - Sampler
	if(nameLen==13)
	{
		var name1 = name.substr(0, nameLen-1);
		if(name1=='samplerBlink')
		{
			// deck A
			if(name=='samplerBlink1') sendMidi(uniqueId,0x90,6,0x30,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink2') sendMidi(uniqueId,0x90,6,0x31,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink3') sendMidi(uniqueId,0x90,6,0x32,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink4') sendMidi(uniqueId,0x90,6,0x33,(param1 ? 0x7F : 0x00));
			// deck B
			if(name=='samplerBlink5') sendMidi(uniqueId,0x90,7,0x30,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink6') sendMidi(uniqueId,0x90,7,0x31,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink7') sendMidi(uniqueId,0x90,7,0x32,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink8') sendMidi(uniqueId,0x90,7,0x33,(param1 ? 0x7F : 0x00));
		}
	}
}
//-----------------------------------------------------------------------------
function setBeatmatchGuide_Tempo(uniqueId, Aup, Adown, Bup, Bdown)
{
	// we cache old values (so we don't overload tthe MIDI driver with unnecessary LED changes)
	// Tempo guide with Beatmatch Guide = ON
	// deck A
	if(lastLEDStateTempoAlignUp[0] != Aup)
	{
		lastLEDStateTempoAlignUp[0] = Aup;
		sendMidi(uniqueId,0x90, 1, 0x1E, Aup);// A up led
	}
	if(lastLEDStateTempoAlignDown[0] != Adown)
	{
		lastLEDStateTempoAlignDown[0] = Adown;
		sendMidi(uniqueId,0x90, 1, 0x1F, Adown);// A down led
	}
	// deck B
	if(lastLEDStateTempoAlignUp[1] != Bup)
	{
		lastLEDStateTempoAlignUp[1] = Bup;
		sendMidi(uniqueId,0x90, 2, 0x1E, Bup);// B up led
	}
	if(lastLEDStateTempoAlignDown[1] != Bdown)
	{
		lastLEDStateTempoAlignDown[1] = Bdown;
		sendMidi(uniqueId,0x90, 2, 0x1F, Bdown);// B down led
	}
}
//-----------------------------------------------------------------------------
function setBeatmatchGuide_BeatAlign(uniqueId, deck, left, right)
{
	// Beat-Align guide with Beatmatch Guide = ON
	if(lastLEDStateBeatAlignL[deck] != left)
	{
		lastLEDStateBeatAlignL[deck] = left;
		sendMidi(uniqueId,0x90, 1+deck, 0x1C, left);// jw left led
	}
	if(lastLEDStateBeatAlignR[deck] != right)
	{
		lastLEDStateBeatAlignR[deck] = right;
		sendMidi(uniqueId,0x90, 1+deck, 0x1D, right);// jw right led
	}
}
//-----------------------------------------------------------------------------
/*
function componentToHex(c) {
  var hex = c.toString(16);
  return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
  return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function rgbToHexAlt(r, g, b) {
  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  } : null;
}
// tests
alert(rgbToHex(0, 51, 255)); // #0033ff
alert(hexToRgb("#0033ff").g); // "51";
*/
function colorInterpolate(r1, g1, b1, r2, g2, b2, percentage) // percentage is actually [0..1]
{
	var r = Math.round(r1 + (r2 - r1) * percentage);
	var g = Math.round(g1 + (g2 - g1) * percentage);
	var b = Math.round(b1 + (b2 - b1) * percentage);
	return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
//-----------------------------------------------------------------------------
// 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+']');

	//-----------------------------------------------
	// BEATMATCH GUIDE
	// tempo guide (pitch up/down)
	var currentBpm0 = getValue('currentBpm', 0);// float
	var currentBpm1 = getValue('currentBpm', 1);// float
	var currentBpmDiffAbs = 0;
	var tempGuideDirection = 0;// 0 nothing, -1 A is faster, +1 A is slower;
	if((currentBpm0 > 0) && (currentBpm1 > 0))
	{
		// only if the BPM difference is semnificative
		currentBpmDiffAbs = Math.abs(currentBpm0 - currentBpm1);
		if(currentBpmDiffAbs > 0.5)
		{
			if(currentBpm0 > currentBpm1) tempGuideDirection = -1;else tempGuideDirection = 1;
		}
	}
	//debug('tempGuideDirection' + tempGuideDirection);
	var Aup=0, Adown=0, Bup=0, Bdown=0;
	if(tempGuideDirection != 0)
	{
		// deck A
		Aup   = (tempGuideDirection < 0) ? 0x7F : 0x00;// A up led
		Adown = (tempGuideDirection > 0) ? 0x7F : 0x00;// A down led
		// deck B
		Bup   = (tempGuideDirection > 0) ? 0x7F : 0x00;// B up led
		Bdown = (tempGuideDirection < 0) ? 0x7F : 0x00;// B down led
	}
	setBeatmatchGuide_Tempo(uniqueId, Aup, Adown, Bup, Bdown);

	//-----------------------------------------------
	// beat-align guide (jog-wheel left/right)
	var beatDiff=[0,0,0,0];
	for(var deck=0; deck<2; deck++)
	{
		beatDiff[deck] = getValue('beatDiff', deck);// float [-1..1]
		var left=0, right=0;
		if(Math.abs(beatDiff[deck]) > 0.05)
		{
			// Beat-Align guide with Beatmatch Guide = ON
			left  = (beatDiff[deck] > 0) ? 0x7F : 0x00;// jw left led
			right = (beatDiff[deck] < 0) ? 0x7F : 0x00;// jw right led
		}
		setBeatmatchGuide_BeatAlign(uniqueId, deck, left, right);
	}

	//-----------------------------------------------
	// assistant
	var newAssistantLEDColorCode = 0x00;
	if(assistantEnabled)
	{
		// NOTE: the score can be calculated to be discrete or continous for a semaphore (red, yellow, green)
		// 1). discrete score (0, 1 or 2) -- much faster than the continous method (and clearer for the user)
		var score = 0;
		if(tempGuideDirection == 0) score++;
		if((score > 0) && (Math.abs(beatDiff[0]) + Math.abs(beatDiff[1]) <= 0.05)) score++;
		// semaphore
		var ledColor = '#FF0000';// red
		if(score == 1) ledColor = '#FADA4A';// yellow
		if(score == 2) ledColor = '#75FA4C';// green
		/*
		// 2). continous score [0..1] (red..green) -- we don't use it because it's CPU intensive and the color resolution is not that great - so not very useful as feedback to the user
		var score = Math.abs(beatDiff[0]) + Math.abs(beatDiff[1]);// this should be [0..1] since only one of the 2 will be active at a time
		if(score > 1.0) score = 1.0;
		// reverse and scale it now [0.5 .. 0]
		score = 0.5 - score*0.5;
		// bpm difference (if it's more than 0)
		if(currentBpmDiffAbs > 10.0) currentBpmDiffAbs = 10.0;
		score += 0.5 - (currentBpmDiffAbs / 20.0);
		var ledColor = colorInterpolate(0xEB, 0x33, 0x23, 0x75, 0xFA, 0x4C, score);// red-green
		*/
		// set the LED color
		//debug('s='+score);
		newAssistantLEDColorCode = parseInt(getNearestColorInTable(uniqueId, ledColor));
	}
	if(lastAssistantLEDColorCode != newAssistantLEDColorCode)
	{
		lastAssistantLEDColorCode = newAssistantLEDColorCode;
		sendMidi(uniqueId,0x90,0,0x05,newAssistantLEDColorCode);
	}
}
//-----------------------------------------------------------------------------
