//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2011 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Denon DN-HC4500'
//----------------------------------------------------------------------------- 
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 "Denon DN-HC4500";
}
//----------------------------------------------------------------------------- 
// 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 (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 "0040030300";
}
//----------------------------------------------------------------------------- 
// this is NOT the preferred way to identify the controller but you should use
// it if the controller does not support standard MIDI SysEx identification
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 MacOSX; 2 Linux; -1 unknown
function getSupportedMidiDeviceNameForInput(osId)
{
	//if(osId==1) return "DN-HC4500";
	return false;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "DN-HC4500";
	return false;
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// ATTN: character '¶' is used as a placeholder for non-existing/non-valid chars
// LSB values are lines, MSB values are columns
var VFDsegment=
[
	['¶', '¶', ' ', '0', '@', 'P', '`', 'p', 'á', 'â', 'a', '¶', 'Á', 'Å', 'Ã', 'ã'], // 0
	['¶', '│', '!', '1', 'A', 'Q', 'a', 'q', 'à', 'ä', 'ɑ', '¹', 'À', 'Ä', 'Å', 'å'], // 1
	['¶', '¶', '"', '2', 'B', 'R', 'b', 'r', 'é', 'ê', '©', '²', 'É', 'Ê', 'Æ', 'æ'], // 2
	['¶', '¶', '#', '3', 'C', 'S', 'c', 's', 'è', 'ë', ' ', '³', 'È', 'Ë', 'Œ', 'œ'], // 3
	['¶', '¶', '¶', '4', 'D', 'T', 'd', 't', 'í', 'î', 'ĝ', '±', 'Í', 'Î', 'ŷ', 'ŵ'], // 4
	['¶', '¶', '%', '5', 'E', 'U', 'e', 'u', 'ì', 'ï', 'ě', 'İ', 'Ì', 'Ï', 'Ӳ', 'ӳ'], // 5
	['¶', '¶', '&', '6', 'F', 'V', 'f', 'v', 'ó', 'ô', 'ň', 'ń', 'Ó', 'Ô', 'Õ', 'õ'], // 6
	['¶', '¶', '\'','7', 'G', 'W', 'g', 'w', 'ò', 'ö', 'õ', 'ũ', 'Ò', 'Ö', 'Ø', 'ø'], // 7
	['¶', '¶', '(', '8', 'H', 'X', 'h', 'x', 'ú', 'û', 'π', 'µ', 'Ú', 'Û', 'Þ', 'þ'], // 8
	['¶', '¶', ')', '9', 'I', 'Y', 'i', 'y', 'ù', 'ü', '₠', '¿', 'Ù', 'Ü', 'Ƞ', 'ƞ'], // 9
	['¶', '¶', '*', ':', 'J', 'Z', 'j', 'z', 'Ñ', 'ñ', '£', '%', 'Ř', 'ř', 'Ŕ', 'ŕ'], // 10
	['¶', '¶', '+', ';', 'K', '[', 'k', '{', 'Ҁ', 'ҁ', '$', 'º', 'Č', 'č', 'Ć', 'ć'], // 11
	['¶', '¶', ',', '<', 'L', '\\','l', '|', 'Ş', 'ş', '←', '¼', 'Š', 'š', 'Ś', 'ś'], // 12
	['¶', '¶', '-', '=', 'M', ']', 'm', '}', 'β', 'ğ', '↑', '½', 'Ž', 'ž', 'Ź', 'ź'], // 13
	['¶', '¶', '.', '>', 'N', '^', 'n', '¯', '¡', 'İ', '→', '¾', 'Đ', 'đ', '¢', 'ⱦ'], // 14
	['¶', '¶', '/', '?', 'O', '_', 'o', ' ', 'Ĳ', 'ĳ', '↓', '§', 'Ŀ', 'ŀ', '¶', '¶']  // 15
];
//----------------------------------------------------------------------------- 
// since this script runs in a single thread, we don't care about these global vars to be accessed improperly
var lastMSB=2, lastLSB=0;
function getCharVDFCode(chr)
{
	lastMSB=2; lastLSB=0;
	/*
	// normal slow mode
	for(var i=0; i<16; i++)
	{
		for(var j=0; j<16; j++)
		{
			if(chr==VFDsegment[i][j])
			{
				lastLSB = i;
				lastMSB = j;
				return;
			}
		}
	}
	*/
	// fast mode (ignore first 2 columns and also go on columns first, since most chars are there)
	for(var j=2; j<16; j++)
	{
		for(var i=0; i<16; i++)
		{
			if(chr==VFDsegment[i][j])
			{
				lastLSB = i;
				lastMSB = j;
				return;
			}
		}
	}
}
//----------------------------------------------------------------------------- 
// actual screens
// main - first line (artist)
var VFDsegment1MSB=[0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D];// ATTN: 0x06 is missing
var VFDsegment1LSB=[0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D];// ATTN: 0x26 is missing
// main - second line (title)
var VFDsegment2MSB=[0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19];
var VFDsegment2LSB=[0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39];
//----------------------------------------------------------------------------- 
function showMainText(uniqueId, deck, text1, text2, show)
{
	// first line (artist)
	if((show==-1)||(show==1))
	{
		var len1 = text1.length; if(len1>12) len1 = 12;
		for(var i=0; i<12; i++)
		{
			lastMSB=2; lastLSB=0;
			if(i<len1) getCharVDFCode(text1.charAt(i));
			// send MSB and LSB
			sendMidi(uniqueId, 0xB0, deck, VFDsegment1MSB[i], lastMSB);
			sendMidi(uniqueId, 0xB0, deck, VFDsegment1LSB[i], lastLSB);
		}
	}
	// second line (title)
	if((show==-1)||(show==2))
	{
		var len2 = text2.length; if(len2>12) len2 = 12;
		for(var i=0; i<12; i++)
		{
			lastMSB=2; lastLSB=0;
			if(i<len2) getCharVDFCode(text2.charAt(i));
			// send MSB and LSB
			sendMidi(uniqueId, 0xB0, deck, VFDsegment2MSB[i], lastMSB);
			sendMidi(uniqueId, 0xB0, deck, VFDsegment2LSB[i], lastLSB);
		}
	}
}
//----------------------------------------------------------------------------- 
// this is actually used as a BPM value
function showTrackValue(uniqueId, deck, value)
{
	sendMidi(uniqueId, 0xB0, deck, 0x41, Math.abs(value % 100));// LSB
	if(value<0)
		sendMidi(uniqueId, 0xB0, deck, 0x40, 110);// MSB
	else
		sendMidi(uniqueId, 0xB0, deck, 0x40, (value<100)?111:Math.floor(value/100));// MSB
}
//----------------------------------------------------------------------------- 
// value is a float (seconds)
function showTimeValue(uniqueId, deck, value)
{
	// split time in min/sec/100ths
	var sec = Math.floor(value);
	var hsec = Math.floor((value - sec)*100);
	var min = Math.floor(sec/60);
	var sec = sec % 60;
	//debug('showTimeValue ['+uniqueId+'] = '+min+':'+sec+'.'+hsec);
	// display
	sendMidi(uniqueId, 0xB0, deck, 0x42, min);
	sendMidi(uniqueId, 0xB0, deck, 0x43, sec);
	sendMidi(uniqueId, 0xB0, deck, 0x44, hsec);
}
//----------------------------------------------------------------------------- 
// an integer from -9999 to +9999 (representing -99.99% .. +99.99% pitch)
function showPitchValue(uniqueId, deck, value)
{
	// split pitch value
	var p1 = Math.floor(Math.abs(value)/100);
	var p2 = Math.floor(Math.abs(value)%100);
	if(value==0) 
		sendMidi(uniqueId, 0xB0, deck, 0x45, 0);
	else if(value>0)
		sendMidi(uniqueId, 0xB0, deck, 0x45, 1);
	else
		sendMidi(uniqueId, 0xB0, deck, 0x45, 2);
	sendMidi(uniqueId, 0xB0, deck, 0x46, p1);
	sendMidi(uniqueId, 0xB0, deck, 0x47, p2);
}
//----------------------------------------------------------------------------- 
// an integer 0..100 representing percents
function showTrackPosition(uniqueId, deck, value)
{
	sendMidi(uniqueId, 0xB0, deck, 0x48, value);// 0x48 normal / 0x49 reversed
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
var autoDjState=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 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	//sendMidi(uniqueId, status, channel, data1, data2);
	autoDjState = getValue('autoDj');
	// reset/update leds/displays
	for(var i=0; i<2; i++)
	{
		// displays
		showMainText(uniqueId, i, getValue('artist', i), getValue('title', i), -1);
		showTrackValue(uniqueId, i, 0);
		showTimeValue(uniqueId, i, 0);
		showPitchValue(uniqueId, i, 0);
		showTrackPosition(uniqueId, i, 0);
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		
		// power off all of the VFDs (0x4D on, 0x4E off, 0x4F blink)
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x01);// T.
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x02);// Remain
		sendMidi(uniqueId,0xB0,i, 0x4D, 0x03);// Elapsed
		
		sendMidi(uniqueId,0xB0,i, autoDjState?0x4D:0x4E, 0x04);// Cont.
		sendMidi(uniqueId,0xB0,i, autoDjState?0x4E:0x4D, 0x05);// Single
		
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x06);// Bpm
		sendMidi(uniqueId,0xB0,i, 0x4D, 0x07);// m
		sendMidi(uniqueId,0xB0,i, 0x4D, 0x08);// s
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x09);// f
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x0A);// Pitch dot right
		sendMidi(uniqueId,0xB0,i, 0x4D, 0x0B);// Pitch dot center
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x0C);// Pitch dot left
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x10);// mp3
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x11);// wav
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		// 0x12 not defined
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x13);// KB
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x14);// Key Adj.
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x15);// Memo
		sendMidi(uniqueId,0xB0,i, (getValue('looping', i) ? 0x4D : 0x4E), 0x16);// ( A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x17);// ( A2 side
		sendMidi(uniqueId,0xB0,i, (getValue('looping', i) ? 0x4D : 0x4E), 0x18);// ) A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x19);// ) A2 side
		sendMidi(uniqueId,0xB0,i, (getValue('looping', i) ? 0x4D : 0x4E), 0x1A);// A1
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1B);// A2
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1C);// B: A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1D);// B: A2 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1E);// scratch ring out side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1F);// scratch ring in side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x20);// touch dot
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x21);// track position blank (no on state)
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush

		// leds (0x4A on, 0x4B off, 0x4C blink)
		sendMidi(uniqueId,0xB0,i, 0x4A, 0x1E);// param knob
		sendMidi(uniqueId,0xB0,i, 0x4A, 0x3B);// jw light		
		sendMidi(uniqueId,0xB0,i, 0x4A, 0x05);// jog mode green
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x06);// jog mode orange
		sendMidi(uniqueId,0xB0,i, (getValue('cue', i) ? 0x4A : 0x4B), 0x26);// cue
		sendMidi(uniqueId,0xB0,i, (getValue('play', i) ? 0x4A : 0x4B), 0x27);// play
		sendMidi(uniqueId,0xB0,i, (getValue('onbeat', i) ? 0x4A : 0x4B), 0x09);// onbeat (0x09 green / 0x0A orange)
		sendMidi(uniqueId,0xB0,i, (getValue('cuepos1', i) ? 0x4A : 0x4B), 0x11);// cuepos1 (0x11 normal / 0x12 dimmer)
		sendMidi(uniqueId,0xB0,i, (getValue('cuepos2', i) ? 0x4A : 0x4B), 0x13);// cuepos2 (0x13 normal / 0x14 dimmer)
		sendMidi(uniqueId,0xB0,i, (getValue('cuepos3', i) ? 0x4A : 0x4B), 0x15);// cuepos3 (0x15 normal / 0x16 dimmer)
		sendMidi(uniqueId,0xB0,i, (getValue('cuepos4', i) ? 0x4A : 0x4B), 0x17);// cuepos4 (0x17 normal / 0x18 dimmer)
		sendMidi(uniqueId,0xB0,i, (getValue('cuepos5', i) ? 0x4A : 0x4B), 0x19);// cuepos5 (0x19 normal / 0x1A dimmer)
		sendMidi(uniqueId,0xB0,i, (getValue('keyLock', i) ? 0x4A : 0x4B), 0x07);// keyLock (0x07 green / 0x08 orange)
		sendMidi(uniqueId,0xB0,i, (getValue('fxFlanger', i) ? 0x4A : 0x4B), 0x0B);// fxFlanger
		sendMidi(uniqueId,0xB0,i, (getValue('fxEcho', i) ? 0x4A : 0x4B), 0x0D);// fxEcho
		sendMidi(uniqueId,0xB0,i, (getValue('fxReverb', i) ? 0x4A : 0x4B), 0x0F);// fxReverb		
		sendMidi(uniqueId,0xB0,i, (getValue('looping', i) ? 0x4A : 0x4B), 0x24);// a1
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x25);// a2
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush		
	}
}
//----------------------------------------------------------------------------- 
// called just before the controller will be deinitialized and the control will go away from the script
// use to power off any powered leds, etc
function controllerDeinit(uniqueId)
{
	debug('controllerDeinit ['+uniqueId+']');
	for(var i=0; i<2; i++)
	{
		// displays
		showMainText(uniqueId, i, '', '', -1);
		showTrackValue(uniqueId, i, 0);
		showTimeValue(uniqueId, i, 0);
		showPitchValue(uniqueId, i, 0);
		showTrackPosition(uniqueId, i, 0);
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		
		// power off all of the VFDs (0x4D on, 0x4E off, 0x4F blink)
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x01);// T.
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x02);// Remain
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x03);// Elapsed
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x04);// Cont.
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x05);// Single
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x06);// Bpm
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x07);// m
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x08);// s
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x09);// f
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x0A);// Pitch dot right
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x0B);// Pitch dot center
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x0C);// Pitch dot left
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x10);// mp3
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x11);// wav
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		// 0x12 not defined
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x13);// KB
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x14);// Key Adj.
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x15);// Memo
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x16);// ( A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x17);// ( A2 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x18);// ) A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x19);// ) A2 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1A);// A1
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1B);// A2
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1C);// B: A1 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1D);// B: A2 side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1E);// scratch ring out side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x1F);// scratch ring in side
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x20);// touch dot
		sendMidi(uniqueId,0xB0,i, 0x4E, 0x21);// track position blank (no on state)
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush
		
		// leds (0x4A on, 0x4B off, 0x4C blink)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x1E);// param knob
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x3B);// jw light		
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x05);// jog mode green
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x06);// jog mode orange
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x26);// cue
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x27);// play
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x09);// onbeat (0x09 green / 0x0A orange)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x11);// cuepos1 (0x11 normal / 0x12 dimmer)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x13);// cuepos2 (0x13 normal / 0x14 dimmer)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x15);// cuepos3 (0x15 normal / 0x16 dimmer)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x17);// cuepos4 (0x17 normal / 0x18 dimmer)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x19);// cuepos5 (0x19 normal / 0x1A dimmer)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x07);// keyLock (0x07 green / 0x08 orange)
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x0B);// fxFlanger
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x0D);// fxEcho
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x0F);// fxReverb		
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x24);// a1
		sendMidi(uniqueId,0xB0,i, 0x4B, 0x25);// a2
		call('sleep', 10);// delay for 10ms to allow the MIDI out buffer to flush		
	}
}
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var jogWheelStatus=[0,0,0,0];// 0 means normal mode, 1 scratch mode, 2 seek mode
var timeStatus=[0,0,0,0];// 0 means elapsed, 1 remained
var flipStatus=[0,0,0,0];// 0 means off, 1 means on
//----------------------------------------------------------------------------- 
// 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;
	//debug('DN_HC4500 d1='+data1+'d2='+data2);
	switch(status)
	{
	//-----------------------------------------------
	// pitch slider
	case 0xE0: call('setDeckPitch', channel, data2); break;// attn: we're only using 7bit
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		// per deck
		case 0x51: 
			//var jogWheelStatus=[0,0,0,0];// 0 means normal mode, 1 scratch mode, 2 seek mode
			var tmpIncrement = data2-0x40;
			switch(jogWheelStatus[channel])
			{
			case 1: // scratch mode only (no matter if it's playing or not)
				call('scratchDeckJW', channel, data2-0x40, 1500); // params: (deck, increment, total positions per complete revolution)
				break;
			case 2: // jog seek mode only (no matter if it's playing or not)
				for(var i=tmpIncrement; i<0; i++) call('deckSkipBeatPrev', channel);
				for(var i=0; i<tmpIncrement; i++) call('deckSkipBeatNext', channel);
				break;
			default:// normal mode (bend when playing / scratch when paused)
				call('moveDeckJogWheel', channel, data2-0x40, 1500); // params: (deck, increment, total positions per complete revolution)
			}
			break;
		case 0x54: if(data2==0) call('lbMoveCursor', 1);else call('lbMoveCursor', -1); break;
		default: messageNotProcessed = true;
		}
		break;
		
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// global
		//case 0x38: call('setCrossfader', data2); break;
		// per deck
		case 0x12: call('deckFxFlanger', channel, data2); break;
		case 0x13: call('deckFxEcho', channel, data2); break;
		case 0x14: call('deckFxReverb', channel, data2); break;
		
		case 0x17: call('deckCuePos1', channel, data2); break;
		case 0x18: call('deckCuePos2', channel, data2); break;
		case 0x19: call('deckCuePos3', channel, data2); break;
		case 0x20: call('deckCuePos4', channel, data2); break;		
		case 0x21: call('deckCuePos5', channel, data2); break;		

		case 0x26: call('setAutoDj', data2); break;		 
		case 0x36: flipStatus[channel] = (data2>0x3F)?1:0; break;
		case 0x37: 
		case 0x38: 
		case 0x39: 
			var loopIn = getValue('loopIn', channel);
			var looping = getValue('looping', channel);
			if((data1==0x38)&&(looping))
			{
				call('deckLoopMore', channel, data2); 
			}else
			{
				if((!loopIn && (data1!=0x39))|| loopIn || looping) call('deckLoopInEnd', channel, data2); 
			}
			break;
		case 0x40:
			var looping = getValue('looping', channel);
			if(looping)
				call('deckLoopExit', channel, data2);
			else
				call('deckLoopReloop', channel, data2); 
			break;
		
		case 0x09: call('deckBendDown', channel, data2); break;
		case 0x08: call('deckBendUp', channel, data2); break;
		case 0x11: call('deckSearchLeft', channel, data2); break;
		case 0x10: call('deckSearchRight', channel, data2); break;
		case 0x05: call('deckKeyLock', channel, data2); break;
		case 0x07: call('deckSync', channel, data2); break;
		case 0x42: call('deckCue', channel, data2); break;
		case 0x43: call('deckPlayPause', channel, data2); break;
		case 0x23: 
			if(data2>0x3F) 
			{
				timeStatus[channel] = (timeStatus[channel]+1)%2; 
				sendMidi(uniqueId,0xB0, channel, ((timeStatus[channel]==1) ? 0x4D : 0x4E), 0x02);// Remain
				sendMidi(uniqueId,0xB0, channel, ((timeStatus[channel]==0) ? 0x4D : 0x4E), 0x03);// Elapsed
			}
			break;
		case 0x30: if(flipStatus[channel]) call('deckSkipBeatNext', channel, data2);else call('deckSkipBeatPrev', channel, data2); break;
		/*
		case 0x10: call('deckMonitor', 0, data2); break;
		case 0x13: call('deckAnalogIn', 0, data2); sendMidi(uniqueId, 0x90, channel, 0x20, data2); break;// TODO: led
		*/
		case 0x02: if(data2>0x3F) call('lbToggleSelect', data2); break;
		case 0x28: if(data2>0x3F) call('deckLBLoad', channel, data2); break;
		case 0x04: // Jog Mode (toggle normal/scratch always)
			//var jogWheelStatus=[0,0,0,0];// 0 means normal mode, 1 scratch mode, 2 seek mode
			if(data2>0x3F) // only on button press
			{
				// test if we're already in this mode and if so reset it to default
				if(jogWheelStatus[channel]==1)
				{
					jogWheelStatus[channel] = 0;
					sendMidi(uniqueId,0xB0,channel,0x4A,0x05);// jog mode green (on)
					sendMidi(uniqueId,0xB0,channel,0x4B,0x06);// jog mode orange (off)
				}else
				{
					if(jogWheelStatus[channel]==2) // see if we were in seek mode (so light off that led)
					{
						//sendMidi(uniqueId,0x90,channel,0x4A,0x00);
					}
					jogWheelStatus[channel] = 1;
					sendMidi(uniqueId,0xB0,channel,0x4B,0x05);// green (off)
					sendMidi(uniqueId,0xB0,channel,0x4C,0x06);// orange (blink)
				}
				//sendMidi(uniqueId,0x90,channel,0x34,((jogWheelStatus[channel]==1) ? 0x7F : 0x00));
			}
			break;
		case 0x51: if(jogWheelStatus[channel]==1) call('holdDeckJogWheel', channel, data2); 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_DN_HC4500 ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	// Denon has a pretty strange way to receive leds (the value and id of the led are reversed in the MIDI message)
	// 0x4A on ; 0x4B off ; 0x4C blink
	if(name=='cue') 	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x26);
	if(name=='play') 	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x27);
	if(name=='onbeat')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x09);// 0x09 green / 0x0A orange
	if(name=='cuepos1')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x11);// 0x11 normal / 0x12 dimmer
	if(name=='cuepos2')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x13);// 0x13 normal / 0x14 dimmer
	if(name=='cuepos3')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x15);// 0x15 normal / 0x16 dimmer
	if(name=='cuepos4')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x17);// 0x17 normal / 0x18 dimmer
	if(name=='cuepos5')	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x19);// 0x19 normal / 0x1A dimmer
	if(name=='keyLock') sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B), 0x07);// 0x07 green / 0x08 orange
	if(name=='artist') 	showMainText(uniqueId, deck, param3, "", 1);
	if(name=='title') 	showMainText(uniqueId, deck, "", param3, 2);
	if(name=='fxFlanger') 	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B),0x0B);
	if(name=='fxEcho') 		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B),0x0D);
	if(name=='fxReverb') 	sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B),0x0F);
	if(name=='loopIn')
	{
		// buttons leds
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4C : 0x4B),0x24);
		//sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4C : 0x4B),0x25);
		// display loop vfd
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4F : 0x4E),0x16);// ( side
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4F : 0x4E),0x1A);// A1
	}
	if(name=='looping')
	{
		// buttons leds
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B),0x24);
		//sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4A : 0x4B),0x25);
		// display loop vfd
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4D : 0x4E),0x16);// ( side
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4D : 0x4E),0x1A);// A1
		sendMidi(uniqueId,0xB0,deck,(param1 ? 0x4D : 0x4E),0x18);// ) side
	}
	if(name=="autoDj")
	{
		autoDjState = getValue('autoDj');
		for(var i=0; i<2; i++)
		{
			sendMidi(uniqueId,0xB0,i, autoDjState?0x4D:0x4E, 0x04);// Cont.
			sendMidi(uniqueId,0xB0,i, autoDjState?0x4E:0x4D, 0x05);// Single
		}
	}
}
//----------------------------------------------------------------------------- 
var lastBpm=[0,0];
var lastPitch=[0,0];
// this is called from time to time by the software (several times per second)
// (it is guaranteed that 'timerTick' will get executed ALWAYS right after notificationReceived)
function timerTick(uniqueId)
{
	//debug('timerTick ['+uniqueId+']');
	for(var i=0; i<2; i++)
	{
		// getValue() -- 2 params -- name[,deck]
		var timeElapsed = getValue('timeElapsed', i);// float seconds
		var timeRemain = getValue('timeRemain', i);// float seconds
		var bpm = getValue('bpm', i);// float bpm
		var pitch = getValue('pitch', i);// 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);
		showTrackPosition(uniqueId, i, Math.floor(100*timeElapsed/(timeElapsed + timeRemain)));
		lastBpm[i] = bpm;
		lastPitch[i] = pitch;
	}
}
//----------------------------------------------------------------------------- 
