//-----------------------------------------------------------------------------
//----------------- (c)2003-2020 Xylio. All Rights Reserved. ------------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Reloop Touch'
//-----------------------------------------------------------------------------
/*
TODO:
- [later] pads have 8 different modes but can't seem to activate them directly from the firmware/device
		HOT CUE, ROLL, SLICER, SAMPLER
		CUE LOOP, SAVED LOOP, BEAT JUMP, KEY CUE
DONE:
- 7" screen 1024x600 (16:9.375) + touch -- ATTN: on Windows we get the touch to operate on the main screen instead (driver bug?!?)
	Windows: "RELOOP-TOUCH" ("DisplayLink USB Device")
	macOS: "RELOOP-TOUCH Display"

	From macOS System Information -> USB:
	USB2.0 Hub:
		Product ID:	0x6572
		Vendor ID:	0x04b4  (Cypress Semiconductor)
		Version:	32.99
		Speed:	Up to 480Mb/sec
		Location ID:	0x14240000 / 2
		*** child devices:
			TOUCH: (audio device: max. 48000Hz/16bit/4ch output only)
				Product ID:	0x8eb7
				Vendor ID:	0x26ad
				Version:	2.00
				Speed:	Up to 12Mb/sec
				Manufacturer:	RELOOP
				Location ID:	0x14243000 / 4
			RELOOP-TOUCH: (video/monitor device)
				Product ID:	0xff00
				Vendor ID:	0x17e9  (DisplayLink (UK) Ltd.)
				Version:	46.56
				Serial Number:	2165322URT9099-150615-001-001
				Speed:	Up to 480Mb/sec
				Manufacturer:	DisplayLink
				Location ID:	0x14242000 / 5
			SiS HID Touch Controller: (touch controller)
				Product ID:	0x12a9
				Vendor ID:	0x0457  (Silicon Integrated Systems Corp.)
				Version:	2.00
				Speed:	Up to 12Mb/sec
				Manufacturer:	USBest Technology
				Location ID:	0x14241000 / 6
*/
//-----------------------------------------------------------------------------
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 "Reloop Touch";
}
//-----------------------------------------------------------------------------
// 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 (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 0x26AD;// Reloop Touch
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0x8EB7;// Reloop Touch
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceVID2(osId)
{
	//return 0x26AD;
	return false;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID2(osId)
{
	//return 0x726F;
	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 '00206E262D0000';
	// full SysEx ID response for Reloop Mixon 4: F07E000602 00206E262D0E3600002118F7
	// full SysEx ID response for Reloop Touch:   F07E000602 00206E262D000001040000F7
}
//-----------------------------------------------------------------------------
function getSupportedMidiSysExIdString2(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 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)
{
	return "TOUCH";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForInput2(osId)
{
	return "Reloop Touch";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	return "TOUCH";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput2(osId)
{
	return "Reloop Touch";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var baseChannel = 1;// eg. Deck A gets data from MIDI channel 1, Deck B from 2, etc
var lastJogWheelPoweredLed_B1=[-1,-1,-1,-1];
var lastJogWheelPoweredLed_B2=[-1,-1,-1,-1];
var fx4EncoderValue=[0,0,0,0];// fx4 value (0 means disabled)
var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
//-----------------------------------------------------------------------------
function setVUmeterDeck(uniqueId, deck, dbValue)
{
	// decks VUs (5 leds: -18, -6, 0, 4, CLP) -- we substract ~8dB (it just looks better with our data)
	var vuCurrent = 0;// all off
	if(dbValue > -24) vuCurrent = 0x10;
	if(dbValue > -12) vuCurrent = 0x25;
	if(dbValue >  -8) vuCurrent = 0x40;
	if(dbValue >  -4) vuCurrent = 0x55;
	if(dbValue >  -1) vuCurrent = 0x7F;
	// control all leds at the same time with one MIDI command
	sendMidi(uniqueId, 0xB0, deck+baseChannel, 0x1E, vuCurrent);
}
//-----------------------------------------------------------------------------
function setVUmeterMaster(uniqueId, midiCode, dbValue)
{
	// master VUs (10 leds: -24, -12, -6, -3, -1, 0, 1, 2, 4, CLP)
	var vuCurrent = 0;// all off
	if(dbValue > -36) vuCurrent = 0x0C;// -24
	if(dbValue > -24) vuCurrent = 0x18;// -12
	if(dbValue > -18) vuCurrent = 0x24;//  -6
	if(dbValue > -12) vuCurrent = 0x30;//  -3
	if(dbValue >  -8) vuCurrent = 0x3C;//  -1
	if(dbValue >  -6) vuCurrent = 0x48;//   0
	if(dbValue >  -4) vuCurrent = 0x55;//   1
	if(dbValue >  -2) vuCurrent = 0x62;//   2
	if(dbValue >   0) vuCurrent = 0x6E;//   4
	if(dbValue >   2) vuCurrent = 0x7F;// CLP (+5)
	// control all leds at the same time with one MIDI command
	sendMidi(uniqueId, 0xB0, 0x0A, midiCode, vuCurrent);
}
//-----------------------------------------------------------------------------
// value is 0x3F for left (-1); 0x41 for right(+1);
function trimEncoderValue(value, increment, initialValue)
{
	var outIncrement = (value-0x40) * increment;
	var outValue = initialValue + outIncrement;
	if(outValue<0) outValue = 0;
	if(outValue>0x7F) outValue = 0x7F;
	return outValue;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// the 16 PAD LEDs of the "Reloop Mixon 4" can each display 128 different pre-defined colors
// ATTN: we did not have any access to the documentation but this seems to work OK also for the Reloop Touch
var rgbToMidi = [
  // part 1: RGB led (normal values) to MIDI code (16 rows x 4 pairs = 64)
  '#000000', '0x00',  '#000054', '0x01',  '#0000A8', '0x02',  '#0000FF', '0x03',
  '#005400', '0x04',  '#005454', '0x05',  '#0054A8', '0x06',  '#0054FF', '0x07',
  '#00A800', '0x08',  '#00A854', '0x09',  '#00A8A8', '0x0A',  '#00A8FF', '0x0B',
  '#00FF00', '0x0C',  '#00FF54', '0x0D',  '#00FFA8', '0x0E',  '#00FFFF', '0x0F',
  '#540000', '0x10',  '#540054', '0x11',  '#5400A8', '0x12',  '#5400FF', '0x13',
  '#545400', '0x14',  '#545454', '0x15',  '#5454A8', '0x16',  '#5454FF', '0x17',
  '#54A800', '0x18',  '#54A854', '0x19',  '#54A8A8', '0x1A',  '#54A8FF', '0x1B',
  '#54FF00', '0x1C',  '#54FF54', '0x1D',  '#54FFA8', '0x1E',  '#54FFFF', '0x1F',
  '#A80000', '0x20',  '#A80054', '0x21',  '#A800A8', '0x22',  '#A800FF', '0x23',
  '#A85400', '0x24',  '#A85454', '0x25',  '#A854A8', '0x26',  '#A854FF', '0x27',
  '#A8A800', '0x28',  '#A8A854', '0x29',  '#A8A8A8', '0x2A',  '#A8A8FF', '0x2B',
  '#A8FF00', '0x2C',  '#A8FF54', '0x2D',  '#A8FFA8', '0x2E',  '#A8FFFF', '0x2F',
  '#FF0000', '0x30',  '#FF0054', '0x31',  '#FF00A8', '0x32',  '#FF00FF', '0x33',
  '#FF5400', '0x34',  '#FF5454', '0x35',  '#FF54A8', '0x36',  '#FF54FF', '0x37',
  '#FFA800', '0x38',  '#FFA854', '0x39',  '#FFA8A8', '0x3A',  '#FFA8FF', '0x3B',
  '#FFFF00', '0x3C',  '#FFFF54', '0x3D',  '#FFFFA8', '0x3E',  '#FFFFFF', '0x3F',

  // part 2: RGB led (dim values) to MIDI code (16 rows x 4 pairs = 64)
  '#000000', '0x40',  '#00000F', '0x41',  '#000021', '0x42',  '#000033', '0x43',
  '#000F00', '0x44',  '#000F0F', '0x45',  '#000F21', '0x46',  '#000F33', '0x47',
  '#002100', '0x48',  '#00210F', '0x49',  '#002121', '0x4A',  '#002133', '0x4B',
  '#003300', '0x4C',  '#00330F', '0x4D',  '#003321', '0x4E',  '#003333', '0x4F',
  '#0F0000', '0x50',  '#0F000F', '0x51',  '#0F0021', '0x52',  '#0F0033', '0x53',
  '#0F0F00', '0x54',  '#0F0F0F', '0x55',  '#0F0F21', '0x56',  '#0F0F33', '0x57',
  '#0F2100', '0x58',  '#0F210F', '0x59',  '#0F2121', '0x5A',  '#0F2133', '0x5B',
  '#0F3300', '0x5C',  '#0F330F', '0x5D',  '#0F3321', '0x5E',  '#0F3333', '0x5F',
  '#210000', '0x60',  '#21000F', '0x61',  '#210021', '0x62',  '#210033', '0x63',
  '#210F00', '0x64',  '#210F0F', '0x65',  '#210F21', '0x66',  '#210F33', '0x67',
  '#212100', '0x68',  '#21210F', '0x69',  '#212121', '0x6A',  '#212133', '0x6B',
  '#213300', '0x6C',  '#21330F', '0x6D',  '#213321', '0x6E',  '#213333', '0x6F',
  '#330000', '0x70',  '#33000F', '0x71',  '#330021', '0x72',  '#330033', '0x73',
  '#330F00', '0x74',  '#330F0F', '0x75',  '#330F21', '0x76',  '#330F33', '0x77',
  '#332100', '0x78',  '#33210F', '0x79',  '#332121', '0x7A',  '#332133', '0x7B',
  '#333300', '0x7C',  '#33330F', '0x7D',  '#333321', '0x7E',  '#333333', '0x7F',
];
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// 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 Reloop Touch ['+uniqueId+']');// this outputs a string to DebugView app on Win

	//------------------------------------
	// enable an extra skin on the controller screen (when it will be available - it make take up to 5-10 seconds for the display to be detected by the OS)
	if(! openExtraScreen("Reloop Touch", "RELOOP-TOUCH***|||DisplayLink USB Device"))
	{
		debug('error: openExtraScreen was NOT set OK');
	}

	// enable screen USB/driver + UPDD touch device/driver
	var sysex1=[0xF0, 0x0C, 0x31, 0xF7];
	var sysex2=[0xF0, 0x0C, 0x33, 0xF7];
	sendMidiSysEx(uniqueId, sysex1, sysex1.length);// enable screen display
	sendMidiSysEx(uniqueId, sysex2, sysex2.length);// enable touch panel

	// exit shop/standby mode
	var sysex3=[0xF0, 0x2A, 0x3D, 0x10, 0xF7];
	sendMidiSysEx(uniqueId, sysex3, sysex3.length);// set normal mode (exit shop/standby mode)

	//------------------------------------
	// 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("Reloop Touch ASIO|||TOUCH|||Reloop Touch|||ReloopTouch|||Speakers (TOUCH)|||Speakers (Reloop Touch)|||Speakers (ReloopTouch)"))
	{
		//debug('AudioDevice was set OK');
		setSetting('AudioDevice2', "NO AUDIO");
	}

	// send all colors to the host - see example of using 'getNearestColorInTable()' in the 'notificationReceived()' function below
	// we only use the first 64 colors, since the next 64 are too dim to be usable for our needs
	setColorTable(uniqueId, rgbToMidi, 64);

	//------------------------------------
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)

	//------------------------------------
	// SysEx init commands:
	// - Deck Restriction	F0 0A 00 F7	-- Disable Deck 3&4
	// - Deck Restriction	F0 0A 01 F7	-- Enable Deck 3&4
	// - Identify Request	F0 00 20 7F 03 01 F7 -- Request all initial position of controls

	// enable decks C & D
	//var sysex1=[0xF0, 0x0A, 0x01, 0xF7];
	//sendMidiSysEx(uniqueId, sysex1, sysex1.length);

	// update all analog controls (eg. fader/knob positions)
	//var sysex2=[0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7];
	//sendMidiSysEx(uniqueId, sysex2, sysex2.length);
}
//-----------------------------------------------------------------------------
// 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 Reloop Touch ['+uniqueId+']');

	// set master VU-meters to off
	//setVUmeterMaster(uniqueId, 0x05, -90.0);
	//setVUmeterMaster(uniqueId, 0x06, -90.0);
	// per deck
	for(var deck=0; deck<4; deck++)
	{
		// jog-wheel leds
		//sendMidi(uniqueId, 0xB0, deck+baseChannel, 0x06, 0);// without SHIFT
		//sendMidi(uniqueId, 0xB0, deck+baseChannel, 0x46, 0);// with SHIFT

		// VU-meter
		//setVUmeterDeck(uniqueId, deck, -90.0);

		sendMidi(uniqueId,0x90,deck+baseChannel,0x29,0x00);// PAD MODE
		sendMidi(uniqueId,0x90,deck+baseChannel,0x2A,0x00);// PARAM
	}
	//------------------------------------
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually de-init all leds/lcd screens)

	// disable screen USB/driver + UPDD touch device/driver
	closeExtraScreen("Reloop Touch");
	var sysex1=[0xF0, 0x0C, 0x30, 0xF7];
	var sysex2=[0xF0, 0x0C, 0x32, 0xF7];
	sendMidiSysEx(uniqueId, sysex1, sysex1.length);// disable screen
	sendMidiSysEx(uniqueId, sysex2, sysex2.length);// disable touch

	// there's no point in entering standby mode after we exit since it may be annoying for the user
	//var sysex3=[0xF0, 0x2A, 0x3D, 0x11, 0xF7];
	//sendMidiSysEx(uniqueId, sysex3, sysex3.length);// exit normal mode (to shop/standby mode)
}
//-----------------------------------------------------------------------------
// 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;
	var deck = channel - baseChannel;
	var isLeftDeck = (deck % 2 == 0);
	switch(status)
	{
	//-----------------------------------------------
	// controllers: pitch sliders ('moving' things)
	case 0xE0:
		// attn: we're only using 7bit (we use only data2, we should use data1 as well)
		if(channel >= baseChannel) call('setDeckPitch', deck, data2);
		break;

	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
			// master volume is analog only

			case 0x01:
				switch(channel)
				{
				case 0x05: call('setMonitorVolume', data2); break;
				default:
					if(channel >= baseChannel)
					{
						// A: Video Trans, B: FX1
						if(isLeftDeck)
							call('deckFxReverbDW', deck, data2);
						else
							call('deckFxFlangerDW', deck, data2);
					}
				}
				break;
			case 0x02:
				switch(channel)
				{
				case 0x05: call('setCuePgmMix', data2); break;
				default:
					if(channel >= baseChannel)
					{
						// A: FX1, B: FX2
						if(isLeftDeck)
							call('deckFxFlangerDW', deck, data2);
						else
							call('deckFxEchoDW', deck, data2);
					}
				}
				break;
			case 0x03:
				switch(channel)
				{
				//case 0x05: call('setCuePgmMix', data2); break;
				default:
					if(channel >= baseChannel)
					{
						// A: FX2, B: FX3
						if(isLeftDeck)
							call('deckFxEchoDW', deck, data2);
						else
							call('deckFxBeatwawDW', deck, data2);
					}
				}
				break;
			case 0x04:
				switch(channel)
				{
				//case 0x05: call('setCuePgmMix', data2); break;
				default:
					if(channel >= baseChannel)
					{
						// A: FX3, B: Video FX
						if(isLeftDeck)
							call('deckFxBeatwawDW', deck, data2);
						else
							call('deckFxReverbDW', deck, data2);
					}
				}
				break;

			case 0x10: if(channel >= baseChannel) call('setDeckGain',   deck, data2); break;
			case 0x11: if(channel >= baseChannel) call('setDeckEqHigh', deck, data2); break;
			case 0x32: if(channel >= baseChannel) call('setDeckEqMid',  deck, data2); break;// EQ Low + SHIFT
			case 0x12: if(channel >= baseChannel) call('setDeckEqLow',  deck, data2); break;
			case 0x13: if(channel >= baseChannel) call('setDeckCutoff', deck, data2); break;
			case 0x14: if(channel >= baseChannel) call('setDeckVolume', deck, data2); break;

			case 0x2F: if(channel == 0x05) call('setCrossfader', data2); break;

			case 0x60:
				switch(channel)
				{
				case 0x05: call('lbMoveCursor', data2-0x40); break;// browse
				default:
					// jog-wheels
					// ATTN: jog-wheel has a different mode when SHIFT is also pressed
					// params: (deck, increment, total positions per complete revolution)
					if(channel >= baseChannel) call('moveDeckJogWheel', deck, data2-0x40, 600);
				}
				break;

			case 0x70:
				switch(channel)
				{
				//case 0x05: call('lbMoveCursor', data2-0x40); break;// browse
				default:
					// jog-wheels + SHIFT (Skip mode)
					// skip +/- 1 bar for every 15degrees the jog-wheel moves
					if(channel >= baseChannel) call('searchDeckJW', deck, data2-0x40, 600);
				}
				break;

			case 0x61: // XCODER encoder (resize loop)
				switch(channel)
				{
				//case 0x05: call('lbMoveCursor', data2-0x40); break;// browse
				default:
					if(channel >= baseChannel)
					{
						var val1 = data2-0x40;
						// loop size encoder
						if(val1 < 0)
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopLess', deck, 0x7F);
							call('deckLoopLess', deck, 0x00);
						}else
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopMore', deck, 0x7F);
							call('deckLoopMore', deck, 0x00);
						}
					}
				}
				break;

			case 0x71: // XCODER encoder + SHIFT (move loop)
				switch(channel)
				{
				//case 0x05: call('lbMoveCursor', data2-0x40); break;// browse
				default:
					if(channel >= baseChannel)
					{
						var val1 = data2-0x40;
						// loop size encoder + SHIFT (move loop)
						if(val1 < 0)
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopPrev', deck, 0x7F);
							call('deckLoopPrev', deck, 0x00);
						}else
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopNext', deck, 0x7F);
							call('deckLoopNext', deck, 0x00);
						}
					}
				}
				break;
			/*
		case 0x03:
			switch(channel)
			{
			case 0x0A: call('setSamplerMasterVolume', data2); break;
			case 0x0A: call('setCFCurve', data2); break;
			case 0x08: // FX knob 4 (left) - ENCODER!
				fx4EncoderValue[0] = trimEncoderValue(data2, 8, fx4EncoderValue[0]);
				call('deckFxReverbDW', 0, fx4EncoderValue[0]);
				break;
			}
			break;
			*/
		default: messageNotProcessed = true;
		}
		break;

	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// Hot Cue (1)
		case 0x00: // pad 1
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos1', deck, data2); break;
						case 1: call('sampler1', data2); break;
						case 2: call('deckLoopRoll1', deck, data2); break;
					}
				}
			}
			break;
		case 0x01: // pad 2
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos2', deck, data2); break;
						case 1: call('sampler2', data2); break;
						case 2: call('deckLoopRoll2', deck, data2); break;
					}
				}
			}
			break;
		case 0x02: // pad 3
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos3', deck, data2); break;
						case 1: call('sampler3', data2); break;
						case 2: call('deckLoopRoll3', deck, data2); break;
					}
				}
			}
			break;
		case 0x03: // pad 4
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos4', deck, data2); break;
						case 1: call('sampler4', data2); break;
						case 2: call('deckLoopRoll4', deck, data2); break;
					}
				}
			}
			break;
		case 0x04: // pad 5
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos5', deck, data2); break;
						case 1: call('sampler5', data2); break;
						case 2: call('deckLoopRoll5', deck, data2); break;
					}
				}
			}
			break;
		case 0x05: // pad 6
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					switch(padsMode[deck])
					{
						case 0: call('deckCuePos6', deck, data2); break;
						case 1: call('sampler6', data2); break;
						case 2: call('deckLoopRoll6', deck, data2); break;
					}
				}
			}
			break;
		//
		// EQ MODE: (currently not used/implemented)
		//
		// when moved to the left side (3-BAND):
		//17:25:05.468	From TOUCH	Note On	6	95 07 00
		//17:25:05.468	From TOUCH	Note On	6	95 06 7F
		//
		// when moved to the right side (FILTER EQ):
		//17:26:03.552	From TOUCH	Note On	6	95 06 00
		//17:26:03.552	From TOUCH	Note On	6	95 07 7F
		case 0x06: // pad 7
			//if(channel == 5) if(data2>0x3F) call('lbToggleSelect', data2);// EQ MODE: 3-BAND (data2: 0 OFF, 0x7F ON)
			if(channel >= baseChannel)
			{
				//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
				switch(padsMode[deck])
				{
					case 0: call('deckCuePos7', deck, data2); break;
					case 1: call('sampler7', data2); break;
					case 2: call('deckLoopRoll7', deck, data2); break;
				}
			}
			break;
		case 0x07: // pad 8
			//if(channel == 5) if(data2>0x3F) call('lbToggleSelect', data2);// EQ MODE: FILTER EQ (data2: 0 OFF, 0x7F ON)
			if(channel >= baseChannel)
			{
				//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
				switch(padsMode[deck])
				{
					case 0: call('deckCuePos8', deck, data2); break;
					case 1: call('sampler8', data2); break;
					case 2: call('deckLoopRoll8', deck, data2); break;
				}
			}
			break;
		case 0x08:
			if(channel == 5) if(data2>0x3F) call('lbToggleSelect', data2);// BACK button (under BROWSE encoder)
			break;
		case 0x09:
			if(channel == 5) if(data2>0x3F) call('lbToggleSelect', data2);// BROWSE encoder button
			//if(channel < 4) call('deckLoopRoll2', channel, data2);
			break;

		//
		case 0x21:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckSync', deck, data2);// SYNC
			}
			break;
		//
		case 0x23:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckCue', deck, data2);// CUE
			}
			break;
		case 0x24:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckPlayPause', deck, data2);// PLAY/PAUSE
			}
			break;
		case 0x25:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				// XCODER encoder button (Loop In/Out)
				if(channel >= baseChannel)
				{
					if(data2>0x3F)
					{
						if(! getValue('looping', deck))
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopInEnd', deck, 0x7F);
							call('deckLoopInEnd', deck, 0x00);
						}else
						{
							// simulate a fast button press (a note on/off)
							call('deckLoopExitOrReloop', deck, 0x7F);
							call('deckLoopExitOrReloop', deck, 0x00);
						}
					}
				}
			}
			break;
		case 0x26:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) // PITCH BEND <-
				{
					call('deckBendDown', deck, data2);
					sendMidi(uniqueId,0x90,channel,data1,data2);
				}
			}
			break;
		case 0x27:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) // PITCH BEND ->
				{
					call('deckBendUp', deck, data2);
					sendMidi(uniqueId,0x90,channel,data1,data2);
				}
			}
			break;

		//
		case 0x29: // PAD MODE (regular button that can light up)
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if((channel >= baseChannel) && (data2 >= 0x3F))
				{
					// toggle mode
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					//padsMode[deck] = (padsMode[deck] + 1) % 3;
					if(padsMode[deck] == 1) padsMode[deck] = 0;else padsMode[deck] = 1;
					switch(padsMode[deck])
					{
						case 0:
							sendMidi(uniqueId,0x90,channel,0x29,0x00);
							sendMidi(uniqueId,0x90,channel,0x2A,0x00);
							break;
						case 1:
							sendMidi(uniqueId,0x90,channel,0x29,0x7F);
							sendMidi(uniqueId,0x90,channel,0x2A,0x00);
							break;
						case 2:
							sendMidi(uniqueId,0x90,channel,0x29,0x00);
							sendMidi(uniqueId,0x90,channel,0x2A,0x7F);
							break;
					}
				}
			}
			break;
		case 0x2A: // PARAM (regular button that can light up)
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if((channel >= baseChannel) && (data2 >= 0x3F))
				{
					// toggle mode
					//var padsMode=[0,0,0,0];// 0 NORMAL, 1 PAD MODE, 2 PARAM
					//padsMode[deck] = (padsMode[deck] + 1) % 3;
					if(padsMode[deck] == 2) padsMode[deck] = 0;else padsMode[deck] = 2;
					switch(padsMode[deck])
					{
						case 0:
							sendMidi(uniqueId,0x90,channel,0x29,0x00);
							sendMidi(uniqueId,0x90,channel,0x2A,0x00);
							break;
						case 1:
							sendMidi(uniqueId,0x90,channel,0x29,0x7F);
							sendMidi(uniqueId,0x90,channel,0x2A,0x00);
							break;
						case 2:
							sendMidi(uniqueId,0x90,channel,0x29,0x00);
							sendMidi(uniqueId,0x90,channel,0x2A,0x7F);
							break;
					}
				}
			}
			break;

		case 0x2B:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckReverse', deck, data2);// MODE button (under XCODER encoder)
			}
			break;
		case 0x2C:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckLoopMode', deck, data2);// ON (under A: Video Trans, B: FX1)
			}
			break;
		//
		case 0x31:
			switch(channel)
			{
			//case 0x05: call('setMonitorVolume', data2); break;
			default:
				if(channel >= baseChannel)
				{
					// A: Video Trans, B: FX1
					if(isLeftDeck)
						call('deckFxReverb', deck, data2);
					else
						call('deckFxFlanger', deck, data2);
				}
			}
			break;
		case 0x32:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					// A: FX1, B: FX2
					if(isLeftDeck)
						call('deckFxFlanger', deck, data2);
					else
						call('deckFxEcho', deck, data2);
				}
			}
			break;
		case 0x33:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					// A: FX2, B: FX3
					if(isLeftDeck)
						call('deckFxEcho', deck, data2);
					else
						call('deckFxBeatwaw', deck, data2);
				}
			}
			break;
		case 0x34:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel)
				{
					// A: FX3, B: Video FX
					if(isLeftDeck)
						call('deckFxBeatwaw', deck, data2);
					else
						call('deckFxReverb', deck, data2);
				}
			}
			break;

		//
		case 0x39:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) if(data2>0x3F) call('deckLBLoad', deck, data2);// LOAD1..4 buttons (above the eq)
				//if(channel >= baseChannel) call('deckLoadNext', deck, data2);
			}
			break;
		//
		case 0x3C:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('deckMonitor', deck, data2);// CH1..4 buttons (above the volume faders)
			}
			break;

		//
		case 0x3F:
			switch(channel)
			{
			//case 0x05: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('holdDeckJogWheel', deck, data2);// jog-wheel hold (metal touch)
			}
			break;

		//
		/*
		// Hot Cue (1)
		case 0x08:
			if(channel < 4) call('deckCuePos1', channel, data2);
			...
			if(channel < 4) call('deckCuePos8', channel, data2);
			break;

		// Loop Roll (2)
		// from user's manual: Loop Roll: A loop is set with the number of beats assigned to the respective pad:
		//		1/32 (Pad1) 1/16 (Pad 2) 1/8 (Pad2) 1/4 (Pad3) 1/2 (Pad4) 1/1 (Pad5) 2/1(Pad6) 4/1 (Pad7) 8/1 (Pad8).
		//		During loop roll playback the normal playback will be continued in the background (autmatically activated SLIP mode).
		case 0x08:
			if(channel < 4) call('deckLoopRoll1', channel, data2);
			...
			if(channel < 4) call('deckLoopRoll8', channel, data2);
			break;

		// Sampler (3)
		case 0x10:
			if(channel < 4) call('sampler1', data2);
			...
			if(channel < 4) call('sampler8', data2);
			break;

		// Slicer (4)
		case 0x18:
			if(channel < 4) call('deckSliceCue1', channel, data2);
			...
			if(channel < 4) call('deckSliceCue8', channel, data2);
			break;

		// Cue Loop (5) (hot cue BLINK mode)
		case 0x20:
			if(channel < 4) call('deckLoopCue1', channel, data2);
			...
			if(channel < 4) call('deckLoopCue8', channel, data2);
			break;

		// Saved Loops (6) (loop roll BLINK mode)
		case 0x28:
			if(channel < 4) call('deckLoopPoint1', channel, data2);
			...
			if(channel < 4) call('deckLoopPoint8', channel, data2);
			break;

		// Pitch Play (7) (sampler BLINK mode)
		case 0x30:
			if(channel < 4) call('deckPitchCue1', channel, data2);
			...
			if(channel < 4) call('deckPitchCue8', channel, data2);
			break;

		// Slicer Loop (8) (slicer BLINK mode)
		case 0x38:
			if(channel < 4) call('deckSliceLoopCue1', channel, data2);
			...
			if(channel < 4) call('deckSliceLoopCue8', channel, data2);
			break;
		*/

		//--------------------------
		// with SHIFT
		// Hot Cue (1) + SHIFT
		case 0x40: if(channel >= baseChannel) call('deckCuePos1Clear', deck, data2); break;
		case 0x41: if(channel >= baseChannel) call('deckCuePos2Clear', deck, data2); break;
		case 0x42: if(channel >= baseChannel) call('deckCuePos3Clear', deck, data2); break;
		case 0x43: if(channel >= baseChannel) call('deckCuePos4Clear', deck, data2); break;
		case 0x44: if(channel >= baseChannel) call('deckCuePos5Clear', deck, data2); break;
		case 0x45: if(channel >= baseChannel) call('deckCuePos6Clear', deck, data2); break;
		case 0x46: if(channel >= baseChannel) call('deckCuePos7Clear', deck, data2); break;
		case 0x47: if(channel >= baseChannel) call('deckCuePos8Clear', deck, data2); break;

		// XCODER encoder button + SHIFT
		case 0x61: if(channel >= baseChannel) call('deckResetPitch',  deck, data2); break;// Sync + SHIFT (Sync Off)
		case 0x63: if(channel >= baseChannel) call('deckBackToTop',   deck, data2); break;// Cue + SHIFT (Back to top / Start)
		case 0x64: if(channel >= baseChannel) call('deckCuePlay',     deck, data2); break;// Play + SHIFT (Stutter)
		case 0x65: if(channel >= baseChannel) call('deckLoopExitOrReloop', deck, data2); break;// Reloop/Exit
		case 0x66: if(channel >= baseChannel) call('deckSearchLeft',  deck, data2); break;// BEND <-
		case 0x67: if(channel >= baseChannel) call('deckSearchRight', deck, data2); break;// BEND ->
		case 0x6B: if(channel >= baseChannel) call('deckBrake',       deck, data2); break;// MODE + SHIFT (Brake)
		case 0x6C: if(channel >= baseChannel) call('deckKeyLock',     deck, data2); break;// SLIP + SHIFT (KeyLock)

		/*
		case 0x50: // Pitch Range (we simply toggle between 16% (default) and 50% (led ON)
			if(data2>0x3F)
			{
				// on the controller: 16%, 50%
				var currentPitchRange = getValue('pitchRange');// 1 = 4%; 2 = 8%; 3 = 16%; 4 = 24%; 5 = 32%; 6 = 50%;
				//currentPitchRange = currentPitchRange + 1; if(currentPitchRange>6) currentPitchRange = 1;
				if(currentPitchRange != 3)
					currentPitchRange = 3;// 16%
				else
					currentPitchRange = 6;// 50%
				call('setPitchRange', currentPitchRange);
			}
			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);
	var nameLen = name.length;
	//-----------------
	// 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)
			{
				if(param1)
				{
					/*
   					//-----------
					// documentation example (long way):
					//
	   				// 1. get the color value (html format) of the deck's cue pos number (idx)
	   				var cueColorCode = getValue('cuecolor'+idx, deck);
	   				// 2. since the controller does NOT support all RGB spectrum, we get the nearest available
		   			//    color on the controller PAD's RGB LED (a list of colors was sent earlier using setColorTable())
					var midiColorCode = getNearestColorInTable(uniqueId, cueColorCode);//'#00FF00');// it returns a string with whatever is in the colors table
					// 3. set the color
					sendMidi(uniqueId,0x90,deck,0x00+(idx-1),(param1 ? parseInt(midiColorCode) : 0x00));
   					//debug('deck '+deck+', cuecolor'+idx+': original color='+cueColorCode+', midi color='+midiColorCode+')');
	   				//-----------
					*/
					// color
					var ledValue = parseInt(getNearestColorInTable(uniqueId, getValue('cuecolor'+idx, deck)));
					sendMidi(uniqueId,0x90,deck+baseChannel,0x00+(idx-1),(param1 ? ledValue : 0x00));
					sendMidi(uniqueId,0x90,deck+baseChannel,0x40+(idx-1),(param1 ? ledValue : 0x00));// with Shift
				}else
				{
					// normal (no colors)
					sendMidi(uniqueId,0x90,deck+baseChannel,0x00+(idx-1),(param1 ? 0x7F : 0x00));
					sendMidi(uniqueId,0x90,deck+baseChannel,0x40+(idx-1),(param1 ? 0x7F : 0x00));// with Shift
				}
			}
		}
	}
	// classic cuepos implementation (without colors)
	/*
	if(name=='cuepos1')	 		sendMidi(uniqueId,0x90,deck,0x00,(param1 ? 0x7F : 0x00));
	if(name=='cuepos2')	 		sendMidi(uniqueId,0x90,deck,0x01,(param1 ? 0x7F : 0x00));
	if(name=='cuepos3')	 		sendMidi(uniqueId,0x90,deck,0x02,(param1 ? 0x7F : 0x00));
	if(name=='cuepos4')	 		sendMidi(uniqueId,0x90,deck,0x03,(param1 ? 0x7F : 0x00));
	if(name=='cuepos5')	 		sendMidi(uniqueId,0x90,deck,0x04,(param1 ? 0x7F : 0x00));
	if(name=='cuepos6')	 		sendMidi(uniqueId,0x90,deck,0x05,(param1 ? 0x7F : 0x00));
	if(name=='cuepos7')	 		sendMidi(uniqueId,0x90,deck,0x06,(param1 ? 0x7F : 0x00));
	if(name=='cuepos8')	 		sendMidi(uniqueId,0x90,deck,0x07,(param1 ? 0x7F : 0x00));
	*/
	//-----------------
/*
	// hot loops
	if(name=='looppos1')	 	sendMidi(uniqueId,0x90,deck,0x28,(param1 ? 0x7F : 0x00));
	if(name=='looppos2')	 	sendMidi(uniqueId,0x90,deck,0x29,(param1 ? 0x7F : 0x00));
	if(name=='looppos3')	 	sendMidi(uniqueId,0x90,deck,0x2A,(param1 ? 0x7F : 0x00));
	if(name=='looppos4')	 	sendMidi(uniqueId,0x90,deck,0x2B,(param1 ? 0x7F : 0x00));
	if(name=='looppos5')	 	sendMidi(uniqueId,0x90,deck,0x2C,(param1 ? 0x7F : 0x00));
	if(name=='looppos6')	 	sendMidi(uniqueId,0x90,deck,0x2D,(param1 ? 0x7F : 0x00));
	if(name=='looppos7')	 	sendMidi(uniqueId,0x90,deck,0x2E,(param1 ? 0x7F : 0x00));
	if(name=='looppos8')	 	sendMidi(uniqueId,0x90,deck,0x2F,(param1 ? 0x7F : 0x00));

	// sampler (sampler1 .. sampler8 -- classic; samplerBlink1 .. samplerBlink8 -- with blink)
	if(name=='samplerBlink1') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x10,(param1 ? 0x7F : 0x00));// 'sampler1' getValue(sampler2loaded)
	if(name=='samplerBlink2') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x11,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink3') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x12,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink4') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x13,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink5') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x14,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink6') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x15,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink7') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x16,(param1 ? 0x7F : 0x00));
	if(name=='samplerBlink8') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d,0x17,(param1 ? 0x7F : 0x00));
*/
	//-----------------

	// justLoaded: 1,2 (loading in progress), 4 (loading ended), 8 (progress), 16 (detection started), 32 (detection finished);
	if(name=='justLoaded') 	 	sendMidi(uniqueId,0x90,deck+baseChannel,0x39,((param1!=0 && param1!=32) ? 0x7F : 0x00));// set led on during loading // Eject
	if(name=='monitor')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x3C,(param1 ? 0x7F : 0x00));
	if(name=='onbeat')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x21,(param1 ? 0x7F : 0x00));// sync
	if(name=='cue')	 			sendMidi(uniqueId,0x90,deck+baseChannel,0x23,(param1 ? 0x7F : 0x00));
	if(name=='play')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x24,(param1 ? 0x7F : 0x00));
	if(name=='reverse')			sendMidi(uniqueId,0x90,deck+baseChannel,0x2B,(param1 ? 0x7F : 0x00));// sync off
	if(name=='loopMode')	 	for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+baseChannel,0x2C,(param1 ? 0x7F : 0x00));// Slip // 0 vinyl mode, 1 slip mode
	if(name=='brake')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x6B,(param1 ? 0x7F : 0x00));
	if(name=='keyLock')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x6C,(param1 ? 0x7F : 0x00));
	if(name=='pitchIs0')
	{
		sendMidi(uniqueId,0x90,deck+baseChannel,0x3F,(param1 ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,deck+baseChannel,0x7F,(param1 ? 0x7F : 0x00));
	}
	/*
	if(name=='loopBlink')
	{
		sendMidi(uniqueId,0x90,deck+baseChannel,0x08,(param1 ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,deck+baseChannel,0x48,(param1 ? 0x7F : 0x00));// with Shift
	}
	if(name=='loopSize')
	{
		// there are 5 leds for the loop size and these have 3 modes: off, blink (for <1 beat loops), on (for >=1 beat loops); see docs;
		var finalValue = 0x00;// 0 all off, 1 led1 on ... 5 led5 on, 6 all leds on, 7 led1 blink ... 0x0B led5 blink, 0x0C all leds blink
		switch(param1)
		{
		// blink/off
		case    2: finalValue = 0x07; break;// loop 1/32
		case    4: finalValue = 0x08; break;// loop 1/16
		case    8: finalValue = 0x09; break;// loop 1/8
		case   16: finalValue = 0x0A; break;// loop 1/4
		case   32: finalValue = 0x0B; break;// loop 1/2
		// on/off
		case   64: finalValue = 0x01; break;// loop 1
		case  128: finalValue = 0x02; break;// loop 2
		case  256: finalValue = 0x03; break;// loop 4
		case  512: finalValue = 0x04; break;// loop 8
		case 1024: finalValue = 0x05; break;// loop 16
		}
		// set all 5 leds at once
		sendMidi(uniqueId,0x90,deck+baseChannel,0x09,finalValue);
	}
	if(name=='pitchRange')
	{
		// 1 = 4%; 2 = 8%; 3 = 16%; 4 = 24%; 5 = 32%; 6 = 50%;
		// set led to on when pitch range is 50%
		for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+baseChannel,0x50,((param1==6) ? 0x7F : 0x00));
	}
	*/

	// effects
	if(deck % 2 == 0)
	{
		// left side
		if(name=='fxReverb')    sendMidi(uniqueId,0x90,baseChannel+deck,0x31,(param1 ? 0x7F : 0x00));// video trans.
		if(name=='fxFlanger')   sendMidi(uniqueId,0x90,baseChannel+deck,0x32,(param1 ? 0x7F : 0x00));// fx1
		if(name=='fxEcho') 	    sendMidi(uniqueId,0x90,baseChannel+deck,0x33,(param1 ? 0x7F : 0x00));// fx2
		if(name=='fxBeatwaw')   sendMidi(uniqueId,0x90,baseChannel+deck,0x34,(param1 ? 0x7F : 0x00));// fx3
	}else
	{
		// right side
		if(name=='fxFlanger') 	sendMidi(uniqueId,0x90,baseChannel+deck,0x31,(param1 ? 0x7F : 0x00));// fx1
		if(name=='fxEcho') 		sendMidi(uniqueId,0x90,baseChannel+deck,0x32,(param1 ? 0x7F : 0x00));// fx2
		if(name=='fxBeatwaw') 	sendMidi(uniqueId,0x90,baseChannel+deck,0x33,(param1 ? 0x7F : 0x00));// fx3
		if(name=='fxReverb') 	sendMidi(uniqueId,0x90,baseChannel+deck,0x34,(param1 ? 0x7F : 0x00));// video fx
	}
}
//-----------------------------------------------------------------------------
// 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+']');
	/*
	// global
	// master VU-meters
	setVUmeterMaster(uniqueId, 0x05, getValue('vumeterMasterL'));
	setVUmeterMaster(uniqueId, 0x06, getValue('vumeterMasterR'));

	// per deck
	var totalJwLeds = 16;// 0x10
	for(var deck=0; deck<4; deck++)
	{
		//-----------------------------
		// track progress (jog-wheel behaviour #1)
		var t_elapsed = getValue('timeElapsed', deck);// float seconds
		var t_remain = getValue('timeRemain', deck);
		var t_total = t_elapsed + t_remain;
		//debug('t_elapsed='+t_elapsed+', t_remain='+t_remain+', t_total='+t_total);
		var jwValue1 = (totalJwLeds * t_elapsed) / t_total;
		if(lastJogWheelPoweredLed_B1[deck] != jwValue1)
		{
			lastJogWheelPoweredLed_B1[deck] = jwValue1;
			sendMidi(uniqueId, 0xB0, deck+baseChannel, 0x46, jwValue1);// with SHIFT (so that the user can see when he scrolls/seeks)
		}
		//-----------------------------
		// spindle position progress (jog-wheel behaviour #2)
		// get the jog-wheel angle and map to the corresponding led (the closest one to that angle)
		var jwAngle = getValue('jogWheelAngle', deck);// float degrees
		var finalLed = Math.round(totalJwLeds * jwAngle/360.0);
		if(finalLed<0) finalLed = 0;
		if(finalLed>=totalJwLeds) finalLed = totalJwLeds-1;
		// reverse the led number (since it's counter-clockwise)
		//finalLed = (totalJwLeds-1) - finalLed;
		//debug('led='+finalLed+', last='+lastPoweredJWled[deck]);
		var jwValue2 = 0x12 + finalLed;
		if(lastJogWheelPoweredLed_B2[deck] != jwValue2)
		{
			lastJogWheelPoweredLed_B2[deck] = jwValue2;
			sendMidi(uniqueId, 0xB0, deck+baseChannel, 0x06, jwValue2);// without SHIFT
			//if(deck==1) debug('led='+finalLed+', jwValue2='+jwValue2);
		}
		//-----------------------------
		// VU-meter
		var dbValue = getValue('vumeterDeck', deck);// float dB
		setVUmeterDeck(uniqueId, deck, dbValue);
	}
	*/
}
//-----------------------------------------------------------------------------
