//-----------------------------------------------------------------------------
//------------ Copyright (c)2003-2016 Xylio. All Rights Reserved. -------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Hercules DJControl Jogvision'
//-----------------------------------------------------------------------------
// TODO:
// for version 2 we should add:
// - support for high-resolution faders/knobs (see TODO below)
//-----------------------------------------------------------------------------
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 "Hercules DJControl Jogvision";
}
//-----------------------------------------------------------------------------
// 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 getSupportedMidiDeviceVID(osId)
{
	return 0x06F8;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0xB11C;
}
//-----------------------------------------------------------------------------
/*
function getSupportedMidiDeviceVID2(osId)
{
	return 0x06F8;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID2(osId)
{
	return 0xB11C;
}
*/
//-----------------------------------------------------------------------------
// 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 "00014E020013";
	// 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 00 06 02 00 01 4E 02 00 13 00 01 00 00 00 F7 -- Hercules DJControl Jogvision complete 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==0) return "Hercules DJControl Jogvision";
	return "DJControl Jogvision";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==0) return "Hercules DJControl Jogvision";
	return "DJControl Jogvision";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var jogWheelsInScratchMode=0;
var lastBeatPos=[0,0,0,0];// 1,2,3,4 (0 is disabled)
var cutoffValue=[63,63,63,63];// filter value for setDeckCutoff (63 is at the middle so it means disabled)
//-----------------------------------------------------------------------------
// this shuts off the previous led and sets to on the new one
function setBeatPosLed(uniqueId,deck,beatpos)
{
	if(lastBeatPos[deck] == beatpos) return;// nothing to do
	// shuts off the previous led
	switch(lastBeatPos[deck])
	{
	case 1: sendMidi(uniqueId,0x90,deck,0x3A,0x00); break;
	case 2: sendMidi(uniqueId,0x90,deck,0x3B,0x00); break;
	case 3: sendMidi(uniqueId,0x90,deck,0x3C,0x00); break;
	case 4: sendMidi(uniqueId,0x90,deck,0x3D,0x00); break;
	}
	// save the last pos
	lastBeatPos[deck] = beatpos;
	// power on the new one
	switch(lastBeatPos[deck])
	{
	case 1: sendMidi(uniqueId,0x90,deck,0x3A,0x7F); break;
	case 2: sendMidi(uniqueId,0x90,deck,0x3B,0x7F); break;
	case 3: sendMidi(uniqueId,0x90,deck,0x3C,0x7F); break;
	case 4: sendMidi(uniqueId,0x90,deck,0x3D,0x7F); break;
	}
}
//-----------------------------------------------------------------------------
function setVUmeter(uniqueId, deck, dbValue)
{
	var vuCurrent = 0;// temp
	if(dbValue>-36) vuCurrent = 1;
	if(dbValue>-24) vuCurrent = 2;
	if(dbValue>-16) vuCurrent = 3;
	if(dbValue>-10) vuCurrent = 4;
	if(dbValue>-6)  vuCurrent = 5;
	if(dbValue>-3)  vuCurrent = 6;
	// control all leds at the same time with one MIDI command
	sendMidi(uniqueId, 0x90, deck, 0x44, vuCurrent);
}
//-----------------------------------------------------------------------------
// 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;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// the 8 PAD LEDs of the "Jogvision" can each display different pre-defined colors
var rgbToMidi = [
  // off
  '#000000', '0x00',
  // part 1: RGB led (normal values) to MIDI code (19 colors)
  '#CC0000', '0x01',  '#CC4400', '0x02',  '#CC8800', '0x03',  '#CCCC00', '0x04',
  '#88CC00', '0x05',  '#44CC00', '0x06',  '#00CC00', '0x07',  '#00CC44', '0x08',
  '#00CC88', '0x09',  '#00CCCC', '0x0A',  '#0088CC', '0x0B',  '#0044CC', '0x0C',
  '#0000CC', '0x0D',  '#4400CC', '0x0E',  '#8800CC', '0x0F',  '#CC00CC', '0x10',
  '#CC0088', '0x11',  '#CC0044', '0x12',  '#FFFFFF', '0x13',
  // part 2: RGB led (dimmed values) to MIDI code (19 colors - low lightness as per manual)
  '#CC0000', '0x21',  '#CC4400', '0x22',  '#CC8800', '0x23',  '#CCCC00', '0x24',
  '#88CC00', '0x25',  '#44CC00', '0x26',  '#00CC00', '0x27',  '#00CC44', '0x28',
  '#00CC88', '0x29',  '#00CCCC', '0x2A',  '#0088CC', '0x2B',  '#0044CC', '0x2C',
  '#0000CC', '0x2D',  '#4400CC', '0x2E',  '#8800CC', '0x2F',  '#CC00CC', '0x30',
  '#CC0088', '0x31',  '#CC0044', '0x32',  '#FFFFFF', '0x33',
  // part 3: RGB led (blink values) to MIDI code (19 colors - normal lightness as in part 1)
  '#CC0000', '0x41',  '#CC4400', '0x42',  '#CC8800', '0x43',  '#CCCC00', '0x44',
  '#88CC00', '0x45',  '#44CC00', '0x46',  '#00CC00', '0x47',  '#00CC44', '0x48',
  '#00CC88', '0x49',  '#00CCCC', '0x4A',  '#0088CC', '0x4B',  '#0044CC', '0x4C',
  '#0000CC', '0x4D',  '#4400CC', '0x4E',  '#8800CC', '0x4F',  '#CC00CC', '0x50',
  '#CC0088', '0x51',  '#CC0044', '0x52',  '#FFFFFF', '0x53',
];
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// 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 Jogvision ['+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 Jogvision ASIO|||Hercules DJ Control Jogvision|||Hercules DJControl Jogvision|||DJControl Jogvision|||Speakers (Hercules DJControl Jogvision)|||Speakers (DJControl Jogvision)"))
	{
		//debug('AudioDevice was set OK');
		setSetting('AudioDevice2', "NO AUDIO");
	}
	//sendMidi(uniqueId, status, channel, data1, data2);
	sendMidi(uniqueId,0x90,0,0x45,(jogWheelsInScratchMode ? 0x7F : 0x00));// scratch mode
	sendMidi(uniqueId,0x90,0,0x46,(jogWheelsInScratchMode ? 0x7F : 0x00));// scratch mode
	// scratch mode off for jog-wheels
	sendMidi(uniqueId,0xB0,0,0x7E,(jogWheelsInScratchMode ? 0x7F : 0x00));
	for(var deck=0; deck<2; deck++)
	{
		sendMidi(uniqueId,0x90,deck,0x58,0x7F);// jog-wheel projector (side light)
	}
	/*
	sendMidi(uniqueId,0x90,0,0x37,0x00);// cue
	sendMidi(uniqueId,0x90,0,0x38,0x00);// mix
	sendMidi(uniqueId,0x90,0,0x42,(getValue('autoDj') ? 0x7F : 0x00));// automix
	sendMidi(uniqueId,0x90,0,0x41,0x00);// record
	sendMidi(uniqueId,0x90,0,0x17,0x00);// filter on A
	sendMidi(uniqueId,0x90,0,0x32,0x00);// filter on B
	*/

	// send all colors to the host - see example of using 'getNearestColorInTable()' in the 'notificationReceived()' function below
	// we only use the first 20 colors, since the next are either too dim to use or are special modes (eg. blink)
	setColorTable(uniqueId, rgbToMidi, 20);

	//------------------------------------
	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
	sendMidi(uniqueId,0x90,0,0x45,0x00);// scratch mode
	sendMidi(uniqueId,0x90,0,0x46,0x00);// scratch mode
	/*
	sendMidi(uniqueId,0x90,0,0x38,0x00);// cue
	sendMidi(uniqueId,0x90,0,0x37,0x00);// mix
	sendMidi(uniqueId,0x90,0,0x42,0x00);// automix
	sendMidi(uniqueId,0x90,0,0x17,0x00);// filter on A
	sendMidi(uniqueId,0x90,0,0x32,0x00);// filter on B
	*/
	//------------------------------------
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually de-init all leds/lcd screens)
	for(var deck=0; deck<2; deck++)
	{
		setVUmeter(uniqueId, deck, -90.0);
		setBeatPosLed(uniqueId, deck, 0);
		//sendMidi(uniqueId,0x90,deck,0x58,0x00);// jog-wheel projector (side light)
		// scratch mode off for jog-wheels
		sendMidi(uniqueId,0xB0,0,0x7E,0x00);
		// the jog-wheel (exterior) 32-leds circle (jog-wheel angle/pos)
		sendMidi(uniqueId,0xB0,deck,0x60,0x00);
		// the jog-wheel (middle) 16-leds circle (track position)
		sendMidi(uniqueId,0xB0,deck,0x61,0x00);
		// track led (near the jog-wheels)
		sendMidi(uniqueId,0x90,deck,0x45,0x00);
	}
}
//-----------------------------------------------------------------------------
// 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 0x00: call('setCrossfader', data2); break;// 0x00 MSB, 0x20 LSB
		// TODO: implement high-resolution faders/knobs
		//case 0x00: call('setCrossfader', data2, true); break;// 0x00 MSB, 0x20 LSB -- true means: use high-resolution MIDI signals; we wait (per controller/script) for both values to arrive before actually changing anything
		case 0x4E: // move track
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			call('lbMoveCursor', val1);
			break;//library curser up down

		// per deck
		case 0x01: call('setDeckVolume', channel, data2); break;
		case 0x02: call('setDeckPitch',  channel, data2); break;
		case 0x03: call('setDeckEqHigh', channel, data2); break;
		case 0x04: call('setDeckEqMid',  channel, data2); break;
		case 0x05: call('setDeckEqLow',  channel, data2); break;

		case 0x0F: // IR proximity sensor (global)
			call('deckFxFlangerDW', channel, data2);
			break;

		// jog-wheel with Shift
		case 0x40: // without touch
		case 0x41: // with touch (hold)
		// jog-wheel with Shift
		case 0x43: // with touch (hold)
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			// params: (deck, increment, total positions per complete revolution)
			if(jogWheelsInScratchMode) call('scratchDeckJW', channel, val1, 768);else call('moveDeckJogWheel', channel, val1, 768);
			break;
		// jog-wheel with Shift
		case 0x42: // without touch
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			// params: (deck, increment, total positions per complete revolution)
			call('searchDeckJW', channel, val1, 768);
			break;

		case 0x44: // Loop: Size (encoder)
			if(data2>0x40) // to the left (0x7F = -1)
			{
				call('deckLoopLess', channel, 0x7F);
				call('deckLoopLess', channel, 0x00);
			}else // to the right (0x01 = +1)
			{
				call('deckLoopMore', channel, 0x7F);
				call('deckLoopMore', channel, 0x00);
			}
			break;
		case 0x45: // Loop: Size (encoder) MULTI FX MODE
		case 0x46: // Loop: Size (encoder) FX MODE
		case 0x47: // Loop: Size (encoder) FX MODE + MULTI FX MODE; ATTN: all three codes 0x47,48,49 are generated at the same time!
			{
				//var val1 = (data2>0x40 ? data2-0x80 : data2);
				cutoffValue[channel] = trimEncoderValue(data2, 2, cutoffValue[channel]);
				call('setDeckCutoff', channel, cutoffValue[channel]);
				break;
			}
		case 0x48: break;
		case 0x49: break;

		case 0x4A: break;// Loop: Size + SHIFT (encoder)
		case 0x4B: break;// Loop: Size + SHIFT (encoder) MULTI FX MODE
		case 0x4C: break;// Loop: Size + SHIFT (encoder) FX MODE
		case 0x4D: break;// Loop: Size + SHIFT (encoder) MULTI FX MODE + MULTI FX MODE


		// the CUE and MIX buttons send a note on-off and a control (CC) with the current status
		case 0x51:
			if(data2>0x3F)
			{
				// mix only
				call('setCuePgmMix', 127);
				//sendMidi(uniqueId,0x90,0,0x4C,0x7F);// mix ON
				//sendMidi(uniqueId,0x90,0,0x4D,0x00);// cue OFF
			}else
			{
				// cue(s) only
				call('setCuePgmMix', 0);
				//sendMidi(uniqueId,0x90,0,0x4C,0x00);// mix OFF
				//sendMidi(uniqueId,0x90,0,0x4D,0x7F);// cue ON
			}
			break;

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// global
		//case 0x45: // VINYL button sends both 0x45 and 0x46 (note on-off) when pressed/released
		case 0x46:
			if(data2>0x3F)
			{
				jogWheelsInScratchMode = (jogWheelsInScratchMode + 1) % 2;
				sendMidi(uniqueId,0x90,0,0x45,(jogWheelsInScratchMode ? 0x7F : 0x00));
				sendMidi(uniqueId,0x90,0,0x46,(jogWheelsInScratchMode ? 0x7F : 0x00));
				// scratch mode
				sendMidi(uniqueId,0xB0,0,0x7E,(jogWheelsInScratchMode ? 0x7F : 0x00));
			}
			break;
		case 0x47: call('mixNext', data2); break;// View (Panel)
		case 0x48: call('setAutoDj', data2); break;// Load Prepare (Area)

		case 0x4E: call('micOnAir', data2); break;
		case 0x4F: if(data2>0x3F) call('lbToggleSelect', data2); break;

		// decks

		// ATTN: pads send signal as pressing strength - and they send it continuously ! (eg. NoteOne -> lots of AfterTouch -> NoteOff)
		// PADS mode 1 and 3
		case 0x10: call('deckCuePos1', channel, data2); break;
		case 0x11: call('deckCuePos2', channel, data2); break;
		case 0x12: call('deckCuePos3', channel, data2); break;
		case 0x13: call('deckCuePos4', channel, data2); break;
		case 0x14: call('deckCuePos5', channel, data2); break;
		case 0x15: call('deckCuePos6', channel, data2); break;
		case 0x16: call('deckCuePos7', channel, data2); break;
		case 0x17: call('deckCuePos8', channel, data2); break;
		// PADS mode 2 and 4
		case 0x20: call('sampler1', data2); break;
		case 0x21: call('sampler2', data2); break;
		case 0x22: call('sampler3', data2); break;
		case 0x23: call('sampler4', data2); break;
		case 0x24: call('sampler5', data2); break;
		case 0x25: call('sampler6', data2); break;
		case 0x26: call('sampler7', data2); break;
		case 0x27: call('sampler8', data2); break;

		// transport
		case 0x30: call('deckSync', channel, data2); break;
		case 0x31: call('deckCue', channel, data2); break;
		case 0x32: call('deckPlayPause', channel, data2); break;
		case 0x33: call('deckMonitor', channel, data2); break;
		case 0x34: call('deckKeyLock', channel, data2); break;
		// transport + SHIFT
		case 0x35: call('deckMatch', channel, data2); break;// sync
		case 0x36: call('deckCuePlay', channel, data2); break;// cue
		case 0x37: call('deckReverse', channel, data2); break;// play
		//case 0x38: call('deckReverse', channel, data2); break;// monitor + SHIFT
		case 0x39: call('deckLoopMode', channel, data2); break;// Key-Lock + SHIFT (SLIP)

		case 0x51: if(data2>0x3F) call('deckLBLoad', channel, data2); break;
		case 0x55: call('holdDeckJogWheel', channel, data2); break;

		// looping (there are 3 buttons: x1/2, ON, x2)
		case 0x00: call('deckLoopInEnd', channel, data2); break;
		case 0x01:
			if(data2>0x40)
			{
				if(getValue('looping', channel))
				{
					call('deckLoopExit', channel, 0x7F);
					call('deckLoopExit', channel, 0x00);
				}else
				{
					call('deckLoopInEnd', channel, 0x7F);
					call('deckLoopInEnd', channel, 0x00);
					//call('deckLoopReloop', channel, 0x7F);
					//call('deckLoopReloop', channel, 0x00);
				}
			}
			break;
		case 0x02: //call('deckLoopMore', channel, data2); break;
			if(data2>0x40)
			{
				if(getValue('looping', channel))
				{
					call('deckLoopMore', channel, 0x7F);
					call('deckLoopMore', channel, 0x00);
				}else
				{
					call('deckLoopReloop', channel, 0x7F);
					call('deckLoopReloop', channel, 0x00);
				}
			}
			break;
		case 0x03: break;// AirFX (no need to control it)

		// FX single
		case 0x04: call('deckFxEcho', channel, data2); break;// fx1
		case 0x05: call('deckFxBeatwaw', channel, data2); break;// fx2
		case 0x06: call('deckFxReverb', channel, data2); break;// fx3
		case 0x07: break;// AirFX (no need to control it)

		// looping + SHIFT (there are 3 buttons: x1/2, ON, x2)
		case 0x08: call('deckLoopPrev', channel, data2); break;
		case 0x09: call('deckLoopReloop', channel, data2); break;
		case 0x0A: call('deckLoopNext', channel, data2); break;
		case 0x0B: break;// AirFX (no need to control it)

		// FX single + SHIFT
		case 0x0C: break;
		case 0x0D: break;
		case 0x0E: break;
		case 0x0F: break;// AirFX (no need to control it)

		case 0x56: // SHIFT (per deck)
			// light the TRACK led near the jog-wheel to signal the user that he can move thru the track itself (instead of bend/scratch)
			sendMidi(uniqueId,0x90,channel,0x45,data2?0x0D:0x07);// 0x0D = deep blue, 07 = full green
			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 nameLen = name.length;
	if(name=='beatPos'){ setBeatPosLed(uniqueId, deck, param1); return;}

	if(name=='onbeat') 		sendMidi(uniqueId,0x90,deck,0x30,(param1 ? 0x7F : 0x00));// sync button
	if(name=='cue') 		sendMidi(uniqueId,0x90,deck,0x31,(param1 ? 0x7F : 0x00));
	if(name=='play') 		sendMidi(uniqueId,0x90,deck,0x32,(param1 ? 0x7F : 0x00));
	if(name=='monitor') 	sendMidi(uniqueId,0x90,deck,0x33,(param1 ? 0x7F : 0x00));
	if(name=='keyLock') 	sendMidi(uniqueId,0x90,deck,0x34,(param1 ? 0x7F : 0x00));
	if(name=='loopBlink') 	sendMidi(uniqueId,0x90,deck,0x01,(param1 ? 0x7F : 0x00));// the ON loop button
	if(name=='reverse') 	sendMidi(uniqueId,0x90,deck,0x37,(param1 ? 0x7F : 0x00));// play + Shift

	//if(name=='onAir') 		sendMidi(uniqueId,0x90,0,0x40,(param1 ? 0x7F : 0x00));// onair led is controlled by controller itself
	if(name=='autoDj') 		sendMidi(uniqueId,0x90,0,0x48,(param1 ? 0x7F : 0x00));// automix

	// justLoaded: 1,2 (loading in progress), 4 (loading ended), 8 (progress), 16 (detection started), 32 (detection finished);
	if(name=='justLoaded')
	{
		if(param1!=0 && param1!=32)
		{
			// loading
			sendMidi(uniqueId,0x90,deck,0x45,0x41);// 64 = red animation; 0x41 = red blink;
		}else
		{
			// loading finished
			sendMidi(uniqueId,0x90,deck,0x45,0x07);// 07 = full green
		}
	}

	// effects
	if(name=='fxEcho') 		sendMidi(uniqueId,0x90,deck,0x04,(param1 ? 0x7F : 0x00));// fx1
	if(name=='fxBeatwaw') 	sendMidi(uniqueId,0x90,deck,0x05,(param1 ? 0x7F : 0x00));// fx2
	if(name=='fxReverb') 	sendMidi(uniqueId,0x90,deck,0x06,(param1 ? 0x7F : 0x00));// fx3

	// PADS mode 1 and 3
	//-----------------
	// hot cue
	if(nameLen==7)
	{
		var name1 = name.substr(0, nameLen-1);// start index (0-based), len
		if(name1=='cuepos')
		{
			// the the index of the cuepos (1..8)
			var idx = name.substr(nameLen-1); //debug('idx='+idx);
			if(idx <= 8)
			{
				// with custom cue colors (provided by the app itself)
				var ledValue = parseInt(getNearestColorInTable(uniqueId, getValue('cuecolor'+idx, deck)));
				sendMidi(uniqueId,0x90,deck,0x10+(idx-1),(param1 ? ledValue : 0x00));
				// classic (with the controller standard colors)
				//sendMidi(uniqueId,0x90,deck,0x10,(param1 ? 0x7F : 0x00));
			}
		}
	}
	// classic cuepos implementation (without custom colors)
	/*
	if(name=='cuepos1') 	sendMidi(uniqueId,0x90,deck,0x10,(param1 ? 0x7F : 0x00));
	if(name=='cuepos2') 	sendMidi(uniqueId,0x90,deck,0x11,(param1 ? 0x7F : 0x00));
	if(name=='cuepos3') 	sendMidi(uniqueId,0x90,deck,0x12,(param1 ? 0x7F : 0x00));
	if(name=='cuepos4') 	sendMidi(uniqueId,0x90,deck,0x13,(param1 ? 0x7F : 0x00));
	if(name=='cuepos5') 	sendMidi(uniqueId,0x90,deck,0x14,(param1 ? 0x7F : 0x00));
	if(name=='cuepos6') 	sendMidi(uniqueId,0x90,deck,0x15,(param1 ? 0x7F : 0x00));
	if(name=='cuepos7') 	sendMidi(uniqueId,0x90,deck,0x16,(param1 ? 0x7F : 0x00));
	if(name=='cuepos8') 	sendMidi(uniqueId,0x90,deck,0x17,(param1 ? 0x7F : 0x00));
	*/
	//-----------------

	if(name=='loopMode')
	{
		// deck A
		sendMidi(uniqueId,0x90,0,0x39,(param1 ? 0x7F : 0x00));
		// deck B
		sendMidi(uniqueId,0x90,1,0x39,(param1 ? 0x7F : 0x00));
	}

	// sampler leds
	if(nameLen==13)
	{
		var name1 = name.substr(0, nameLen-1);
		if(name1=='samplerBlink')
		{
			// PADS mode 2 and 4
			// left side (deck A)
			if(name=='samplerBlink1') sendMidi(uniqueId,0x90,0,0x20,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink2') sendMidi(uniqueId,0x90,0,0x21,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink3') sendMidi(uniqueId,0x90,0,0x22,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink4') sendMidi(uniqueId,0x90,0,0x23,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink5') sendMidi(uniqueId,0x90,0,0x24,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink6') sendMidi(uniqueId,0x90,0,0x25,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink7') sendMidi(uniqueId,0x90,0,0x26,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink8') sendMidi(uniqueId,0x90,0,0x27,(param1 ? 0x7F : 0x00));
			// right side (deck B)
			if(name=='samplerBlink1') sendMidi(uniqueId,0x90,1,0x20,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink2') sendMidi(uniqueId,0x90,1,0x21,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink3') sendMidi(uniqueId,0x90,1,0x22,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink4') sendMidi(uniqueId,0x90,1,0x23,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink5') sendMidi(uniqueId,0x90,1,0x24,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink6') sendMidi(uniqueId,0x90,1,0x25,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink7') sendMidi(uniqueId,0x90,1,0x26,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink8') sendMidi(uniqueId,0x90,1,0x27,(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 deck=0; deck<2; deck++)
	{
		// VU-meters
		var dbValue = getValue('vumeterDeck', deck);// float dB
		setVUmeter(uniqueId, deck, dbValue);

		// track position
		var timeElapsed = getValue('timeElapsed', deck);// float seconds
		var timeRemain = getValue('timeRemain', deck);// float seconds
		//var timePos = Math.floor(100*timeElapsed/(timeElapsed + timeRemain));// 0..100 integer
		var timePos = timeElapsed / (timeElapsed + timeRemain);// 0.0 .. 1.0 float
		// the jog-wheel (middle) 16-leds circle (position)
		sendMidi(uniqueId,0xB0,deck,0x61, Math.round(timePos * 0x7F));

		// jog-wheel position (angle)
		var jwAngle = getValue('jogWheelAngle', deck);// float degrees
		// the jog-wheel (exterior) 32-leds circle (jog-wheel angle/pos)
		sendMidi(uniqueId,0xB0,deck,0x60, Math.round((jwAngle * 0x7E / 360.0) + 1));// 1..0x7F (ATTN: not from 0 but from 1)

		//var bpm = getValue('bpm', deck);// float bpm
		//var pitch = getValue('pitch', deck);// integer pitch
		//debug('deck '+i+'  timeElapsed='+timeElapsed+'s  timeRemain='+timeRemain+'s  BPM='+bpm+'  pitch='+pitch);
		//if(lastBpm[i] != bpm) showTrackValue(uniqueId, i, Math.floor(bpm));
		//if(timeStatus[i]==0) showTimeValue(uniqueId, i, timeElapsed);else showTimeValue(uniqueId, i, timeRemain);
		//if(lastPitch[i] != pitch) showPitchValue(uniqueId, i, pitch);
		//lastBpm[i] = bpm;
		//lastPitch[i] = pitch;
	}
}
//-----------------------------------------------------------------------------
