//-----------------------------------------------------------------------------
//----------------- (c)2003-2019 Xylio. All Rights Reserved. ------------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Reloop Mixon 4'
//-----------------------------------------------------------------------------
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 Mixon 4";
}
//-----------------------------------------------------------------------------
// 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 Mixon 4
	//return 0x200C;// Reloop RMP-4
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0x8EB6;// Reloop Mixon 4
}
//-----------------------------------------------------------------------------
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 '00206E262D0E36';
	// 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 "Mixon 4";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForInput2(osId)
{
	return "Reloop Mixon 4";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	return "Mixon 4";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput2(osId)
{
	return "Reloop Mixon 4";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var baseChannel = 4;// eg. Deck A gets data from MIDI channel 5, Deck B from 6, 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)
//-----------------------------------------------------------------------------
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
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 Mixon 4 ['+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
	// "Mixon 4" -- macOS CoreAudio / CoreMIDI device name
	// "Reloop MIXON 4 ASIO Driver v1.0.2" - Windows 10 latest ASIO driver - ATTN: doesn't seem to be working, ASIO device appears but there are no outputs available to select from;
	if(setAudioDevice("DISABLED Reloop MIXON 4 ASIO***|||Mixon 4|||Reloop Mixon 4|||Reloop Mixon4|||ReloopMixon4|||Mixon4|||Speakers (Mixon 4)|||Speakers (Mixon4)|||Speakers (Reloop Mixon 4)|||Speakers (Reloop Mixon4)"))
	{
		//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 Mixon 4 ['+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);
	}
	//------------------------------------
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually de-init all leds/lcd screens)
}
//-----------------------------------------------------------------------------
// this is your main target function to implement the controller -- this is called
// for each and every MIDI message that comes from the controller itself
// return true if the message was processed by the script and false to let it
// pass thru the software (eg. so it can be assigned by the user using the LEARN function)
function messageReceived(uniqueId,status,channel,data1,data2)
{
	var messageNotProcessed=false;
	switch(status)
	{
	//-----------------------------------------------
	// controllers: 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', channel - baseChannel, 0x7F - data2);
		break;
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		case 0x00:
			switch(channel)
			{
			case 0x08: call('deckFxFlangerDW', 0, data2); break;// FX knob 1 (left)
			case 0x09: call('deckFxFlangerDW', 1, data2); break;// FX knob 1 (right)
			case 0x0A: call('setCrossfader', data2); break;// channel 10
			default:
				if(channel >= baseChannel) call('setDeckGain', channel - baseChannel, data2);
			}
			break;
		case 0x01:
			switch(channel)
			{
			case 0x08: call('deckFxEchoDW', 0, data2); break;// FX knob 2 (left)
			case 0x09: call('deckFxEchoDW', 1, data2); break;// FX knob 2 (right)
			case 0x0A: call('setCFCurve', data2); break;
			default:
				if(channel >= baseChannel) call('setDeckEqHigh', channel - baseChannel, data2);
			}
			break;
		case 0x02:
			switch(channel)
			{
			case 0x08: call('deckFxBeatwawDW', 0, data2); break;// FX knob 3 (left)
			case 0x09: call('deckFxBeatwawDW', 1, data2); break;// FX knob 3 (right)
			case 0x0A: call('setSamplerMasterVolume', data2); break;
			default:
				if(channel >= baseChannel) call('setDeckEqMid',  channel - baseChannel, data2);
			}
			break;
		case 0x03:
			switch(channel)
			{
			case 0x08: // FX knob 4 (left) - ENCODER!
				fx4EncoderValue[0] = trimEncoderValue(data2, 8, fx4EncoderValue[0]);
				call('deckFxReverbDW', 0, fx4EncoderValue[0]);
				break;
			case 0x09: // FX knob 4 (right) - ENCODER!
				fx4EncoderValue[1] = trimEncoderValue(data2, 8, fx4EncoderValue[1]);
				call('deckFxReverbDW', 1, fx4EncoderValue[1]);
				break;
			case 0x0A: call('setCuePgmMix', data2); break;
			default:
				if(channel >= baseChannel) call('setDeckEqLow',  channel - baseChannel, data2);
			}
			break;
		case 0x04:
			if(channel == 10) // browse
			{
				var val1 = data2-0x40;
				call('lbMoveCursor', val1);
			}else // filter(lpf,hpf)/fx
			{
				if(channel >= baseChannel) call('setDeckCutoff', channel - baseChannel, data2);
			}
			break;
		case 0x05: if(channel >= baseChannel) call('setDeckVolume', channel - baseChannel, data2); break;

		// ATTN: jog-wheel has a different mode when SHIFT is also pressed
		// params: (deck, increment, total positions per complete revolution)
		case 0x06: // jog-wheel
			if(channel >= baseChannel)
			{
				var val1 = data2-0x40;
				call('moveDeckJogWheel', channel - baseChannel, val1, 800);
			}
			break;
		case 0x46: // jog-wheel + Shift (Skip)
			if(channel >= baseChannel)
			{
				var val1 = data2-0x40;
				// skip +/- 1 bar for every 15degrees the jog-wheel moves
				call('searchDeckJW', channel - baseChannel, val1, 800);
			}
			break;

		case 0x07:
			if(channel == 10)
			{
				call('setMonitorVolume', data2);
			}else
			{
				//if(channel >= baseChannel) call('setDeckEqLow',  channel - baseChannel, data2);
			}
			break;

		// loop size encoder
		case 0x08:
			if(channel >= baseChannel)
			{
				var val1 = data2-0x40;
				var deck = channel - baseChannel;
				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;
		// loop size encoder + SHIFT (move loop)
		case 0x48:
			if(channel >= baseChannel)
			{
				var val1 = data2-0x40;
				var deck = channel - baseChannel;
				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;

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// Hot Cue (1)
		case 0x00:
			switch(channel)
			{
			case 0x08: call('deckFxFlanger', 0, data2); break;// FX button 1 (left)
			case 0x09: call('deckFxFlanger', 1, data2); break;// FX button 1 (right)
			case 0x0A: if(data2>0x3F) call('lbToggleSelect', data2); break;// channel 10
			default:
				if(channel < 4) call('deckCuePos1', channel, data2);
			}
			break;
		case 0x01:
			switch(channel)
			{
			case 0x08: call('deckFxEcho', 0, data2); break;// FX button 2 (left)
			case 0x09: call('deckFxEcho', 1, data2); break;// FX button 2 (right)
			case 0x0A: if(data2>0x3F) call('lbToggleSelect', data2); break;
			default:
				if(channel < 4) call('deckCuePos2', channel, data2);
				if(channel >= baseChannel)
				{
					/*
					// small M1 (for decks A, C) and M2 (for decks B, D) buttons; led is HW controlled only;
					// the buttons are near the CutOff filter knobs
					// de la off la on: (pt. deck D)
					From Mixon 4	Note On	    5-8	$01	$7F
					From Mixon 4	Note Off	5-8	$01	$00
					From Mixon 4	Control	    5-8	$04	$40 CC
					// de la on la off: (pt. deck D)
					From Mixon 4	Note On	    5-8	$01	$7F
					From Mixon 4	Note Off	5-8	$01	$00
					From Mixon 4	Control	   9/10	$00	$3D CC
						for the last one: ch 9, $00 $04 deck A, C (this is macro mode M1); ch 10, $00 $3D for decks B, D (this is macro mode M2);
					*/
					// the problem is that we cannot control the LED (since it's only controlled by the hardware)
					// so we cannot use any significant functions for this button
					//call('deckTimecodeIn', channel - baseChannel, data2);// deckAnalogIn
				}
			}
			break;

		case 0x02:
			switch(channel)
			{
			case 0x08: call('deckFxBeatwaw', 0, data2); break;// FX button 1 (left)
			case 0x09: call('deckFxBeatwaw', 1, data2); break;// FX button 1 (right)
			default:
				if(channel < 4) call('deckCuePos3', channel, data2);
				//if(channel >= baseChannel) call('deckLoadNext', channel - baseChannel, data2);
				if(channel >= baseChannel) if(data2>0x3F) call('deckLBLoad', channel - baseChannel, data2);
			}
			break;
		case 0x03:
			if(channel < 4) call('deckCuePos4', channel, data2);
			if(channel >= baseChannel) call('deckMonitor', channel - baseChannel, data2);
			break;

		// for each deck there is an "A-thru-B" setting underneath the volume fader to assign the channel to the crossfader
		// 0x7F means that mode is ON, 0x00 means it's off; we always receive first the ON mode (see the examples);
		// example: set for deck A -- from left (A) to middle (THRU):
		// From Mixon 4	Note On	    5	$05	$7F
		// From Mixon 4	Note Off	5	$04	$00
		// From Mixon 4	Note Off	5	$06	$00
		// example: set for deck A -- from middle (THRU) to right (B):
		// From Mixon 4	Note On	    5	$06	$7F
		// From Mixon 4	Note Off	5	$04	$00
		// From Mixon 4	Note Off	5	$05	$00
		//
		case 0x04: // to left side of CF (A)
		case 0x05: // skip the CF (THRU)
		case 0x06: // to right side of CF (B)
			switch(channel)
			{
			case 0x08: if(data1 == 0x04) call('deckFxReverb', 0, data2); break;// FX button 4 (left)  under encoder
			case 0x09: if(data1 == 0x04) call('deckFxReverb', 1, data2); break;// FX button 4 (right) under encoder
			default:
				if(data1 == 0x04) if(channel < 4) call('deckCuePos5', channel, data2);
				if(data1 == 0x05) if(channel < 4) call('deckCuePos6', channel, data2);
				if(data1 == 0x06) if(channel < 4) call('deckCuePos7', channel, data2);
				if(channel >= baseChannel)
				{
					// we only deal with the on state (0x7F) -- ATTN: we don't receive on/off notes events but only current status (see examples above)
					if(data2 == 0x7F)
					{
						var deck = channel - baseChannel;
						switch(data1)
						{
						case 0x04: // to left side of CF (A)
							// direct setting
							call('deckSetCFAssign', deck, 0);
							// simulate a fast button press (a note on/off)
							//call('deckCFAssignToLeft', deck, 0x7F);
							//call('deckCFAssignToLeft', deck, 0x00);
							break;
						case 0x05: // skip the CF (THRU)
							// direct setting
							call('deckSetCFAssign', deck, 1);
							// simulate a fast button press (a note on/off)
							//call('deckCFAssignToThru', deck, 0x7F);
							//call('deckCFAssignToThru', deck, 0x00);
							break;
						case 0x06: // to right side of CF (B)
							// direct setting
							call('deckSetCFAssign', deck, 2);
							// simulate a fast button press (a note on/off)
							//call('deckCFAssignToRight', deck, 0x7F);
							//call('deckCFAssignToRight', deck, 0x00);
							break;
						}
					}
				}
			}
			break;

		case 0x07:
			if(channel < 4) call('deckCuePos8', channel, data2);
			if(channel >= baseChannel)
			{
				var deck = channel - baseChannel;
				call('holdDeckJogWheel', deck, data2); // Touch/JOG
			}
			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);
			// Loop In/Out (Auto Loop button)
			if(channel >= baseChannel)
			{
				if(data2>0x3F)
				{
					var deck = channel - baseChannel;
					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 0x09:
			if(channel < 4) call('deckLoopRoll2', channel, data2);
			break;
		case 0x0A:
			if(channel < 4) call('deckLoopRoll3', channel, data2);
			// Param- / Param+
			if(channel >= baseChannel)
			{
				call('deckBendDown', channel - baseChannel, data2);
				sendMidi(uniqueId,0x90,channel,data1,data2);
			}
			break;
		case 0x0B:
			if(channel < 4) call('deckLoopRoll4', channel, data2);
			// Param- / Param+
			if(channel >= baseChannel)
			{
				call('deckBendUp', channel - baseChannel, data2);
				sendMidi(uniqueId,0x90,channel,data1,data2);
			}
			break;
		case 0x0C:
			if(channel < 4) call('deckLoopRoll5', channel, data2);
			if(channel >= baseChannel) call('deckPlayPause', channel - baseChannel, data2);
			break;
		case 0x0D:
			if(channel < 4) call('deckLoopRoll6', channel, data2);
			if(channel >= baseChannel) call('deckCue', channel - baseChannel, data2);
			break;
		case 0x0E:
			if(channel < 4) call('deckLoopRoll7', channel, data2);
			if(channel >= baseChannel) call('deckSync', channel - baseChannel, data2);
			break;
		case 0x0F:
			if(channel < 4) call('deckLoopRoll8', channel, data2);
			if(channel >= baseChannel) call('deckLoopMode', channel - baseChannel, data2);
			break;

		// Sampler (3)
		case 0x10:
			if(channel < 4) call('sampler1', data2);
			if(channel >= baseChannel) call('deckKeyLock', channel - baseChannel, data2);
			break;
		case 0x11:
			if(channel < 4) call('sampler2', data2);
			if(channel >= baseChannel) call('deckBrake', channel - baseChannel, data2); // Key Sync button
			break;
		case 0x12:
			if(channel < 4) call('sampler3', data2);
			break;
		case 0x13:
			if(channel < 4) call('sampler4', data2);
			break;
		case 0x14:
			if(channel < 4) call('sampler5', data2);
			break;
		case 0x15:
			if(channel < 4) call('sampler6', data2);
			break;
		case 0x16:
			if(channel < 4) call('sampler7', data2);
			break;
		case 0x17:
			if(channel < 4) call('sampler8', data2);
			break;

		// Slicer (4)
		case 0x18:
			if(channel < 4) call('deckSliceCue1', channel, data2);
			break;
		case 0x19:
			if(channel < 4) call('deckSliceCue2', channel, data2);
			break;
		case 0x1A:
			if(channel < 4) call('deckSliceCue3', channel, data2);
			break;
		case 0x1B:
			if(channel < 4) call('deckSliceCue4', channel, data2);
			break;
		case 0x1C:
			if(channel < 4) call('deckSliceCue5', channel, data2);
			break;
		case 0x1D:
			if(channel < 4) call('deckSliceCue6', channel, data2);
			break;
		case 0x1E:
			if(channel < 4) call('deckSliceCue7', channel, data2);
			break;
		case 0x1F:
			if(channel < 4) call('deckSliceCue8', channel, data2);
			break;

		// Cue Loop (5) (hot cue BLINK mode)
		case 0x20:
			if(channel < 4) call('deckLoopCue1', channel, data2);
			break;
		case 0x21:
			if(channel < 4) call('deckLoopCue2', channel, data2);
			break;
		case 0x22:
			if(channel < 4) call('deckLoopCue3', channel, data2);
			break;
		case 0x23:
			if(channel < 4) call('deckLoopCue4', channel, data2);
			break;
		case 0x24:
			if(channel < 4) call('deckLoopCue5', channel, data2);
			break;
		case 0x25:
			if(channel < 4) call('deckLoopCue6', channel, data2);
			break;
		case 0x26:
			if(channel < 4) call('deckLoopCue7', channel, data2);
			break;
		case 0x27:
			if(channel < 4) call('deckLoopCue8', channel, data2);
			break;

		// Saved Loops (6) (loop roll BLINK mode)
		case 0x28:
			if(channel < 4) call('deckLoopPoint1', channel, data2);
			break;
		case 0x29:
			if(channel < 4) call('deckLoopPoint2', channel, data2);
			break;
		case 0x2A:
			if(channel < 4) call('deckLoopPoint3', channel, data2);
			break;
		case 0x2B:
			if(channel < 4) call('deckLoopPoint4', channel, data2);
			break;
		case 0x2C:
			if(channel < 4) call('deckLoopPoint5', channel, data2);
			break;
		case 0x2D:
			if(channel < 4) call('deckLoopPoint6', channel, data2);
			break;
		case 0x2E:
			if(channel < 4) call('deckLoopPoint7', channel, data2);
			break;
		case 0x2F:
			if(channel < 4) call('deckLoopPoint8', channel, data2);
			break;

		// Pitch Play (7) (sampler BLINK mode)
		case 0x30:
			if(channel < 4) call('deckPitchCue1', channel, data2);
			break;
		case 0x31:
			if(channel < 4) call('deckPitchCue2', channel, data2);
			break;
		case 0x32:
			if(channel < 4) call('deckPitchCue3', channel, data2);
			break;
		case 0x33:
			if(channel < 4) call('deckPitchCue4', channel, data2);
			break;
		case 0x34:
			if(channel < 4) call('deckPitchCue5', channel, data2);
			break;
		case 0x35:
			if(channel < 4) call('deckPitchCue6', channel, data2);
			break;
		case 0x36:
			if(channel < 4) call('deckPitchCue7', channel, data2);
			break;
		case 0x37:
			if(channel < 4) call('deckPitchCue8', channel, data2);
			break;

		// Slicer Loop (8) (slicer BLINK mode)
		case 0x38:
			if(channel < 4) call('deckSliceLoopCue1', channel, data2);
			break;
		case 0x39:
			if(channel < 4) call('deckSliceLoopCue2', channel, data2);
			break;
		case 0x3A:
			if(channel < 4) call('deckSliceLoopCue3', channel, data2);
			break;
		case 0x3B:
			if(channel < 4) call('deckSliceLoopCue4', channel, data2);
			break;
		case 0x3C:
			if(channel < 4) call('deckSliceLoopCue5', channel, data2);
			break;
		case 0x3D:
			if(channel < 4) call('deckSliceLoopCue6', channel, data2);
			break;
		case 0x3E:
			if(channel < 4) call('deckSliceLoopCue7', channel, data2);
			break;
		case 0x3F:
			if(channel < 4) call('deckSliceLoopCue8', channel, data2);
			break;

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

		// Auto Loop button + SHIFT
		case 0x48: if(channel >= baseChannel) call('deckLoopExitOrReloop', channel - baseChannel, data2); break;// Reloop/Exit
		//case 0x49: break;
		// Param- / Param+ + SHIFT
		case 0x4A: if(channel >= baseChannel) call('deckSearchLeft', channel - baseChannel, data2); break;
		case 0x4B: if(channel >= baseChannel) call('deckSearchRight', channel - baseChannel, data2); break;
		case 0x4C: if(channel >= baseChannel) call('deckCuePlay', channel - baseChannel, data2); break;// Play + SHIFT (Stutter)
		case 0x4D: if(channel >= baseChannel) call('deckBackToTop', channel - baseChannel, data2); break;// Cue + SHIFT (Back to top / Start)
		case 0x4E: if(channel >= baseChannel) call('deckReverse', channel - baseChannel, data2); break;// Sync + SHIFT (Sync Off)
		case 0x4F: break;
		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;
		case 0x51: if(channel >= baseChannel) call('deckResetPitch', channel - baseChannel, data2); break;// Key Sync button + SHIFT (Censor)

		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,0x00+(idx-1),(param1 ? ledValue : 0x00));
					sendMidi(uniqueId,0x90,deck,0x40+(idx-1),(param1 ? ledValue : 0x00));// with Shift
				}else
				{
					// normal (no colors)
					sendMidi(uniqueId,0x90,deck,0x00+(idx-1),(param1 ? 0x7F : 0x00));
					sendMidi(uniqueId,0x90,deck,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,0x02,((param1!=0 && param1!=32) ? 0x7F : 0x00));// set led on during loading // Eject
	if(name=='monitor')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x03,(param1 ? 0x7F : 0x00));

	if(name=='play')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x0C,(param1 ? 0x7F : 0x00));
	if(name=='cue')	 			sendMidi(uniqueId,0x90,deck+baseChannel,0x0D,(param1 ? 0x7F : 0x00));
	if(name=='onbeat')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x0E,(param1 ? 0x7F : 0x00));// sync
	if(name=='loopMode')	 	for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+baseChannel,0x0F,(param1 ? 0x7F : 0x00));// Slip // 0 vinyl mode, 1 slip mode
	if(name=='keyLock')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x10,(param1 ? 0x7F : 0x00));
	if(name=='brake')	 		sendMidi(uniqueId,0x90,deck+baseChannel,0x11,(param1 ? 0x7F : 0x00));

	if(name=='pitchIs0') 		sendMidi(uniqueId,0x90,deck+baseChannel,0x15,(param1 ? 0x7F : 0x00));

	if(name=='reverse')			sendMidi(uniqueId,0x90,deck+baseChannel,0x4E,(param1 ? 0x7F : 0x00));// sync off

	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)
	{
		if(name=='fxFlanger') 	sendMidi(uniqueId,0x90,8+deck,0x00,(param1 ? 0x7F : 0x00));// fx1
		if(name=='fxEcho') 		sendMidi(uniqueId,0x90,8+deck,0x01,(param1 ? 0x7F : 0x00));// fx2
		if(name=='fxBeatwaw') 	sendMidi(uniqueId,0x90,8+deck,0x02,(param1 ? 0x7F : 0x00));// fx3
		if(name=='fxReverb') 	sendMidi(uniqueId,0x90,8+deck,0x04,(param1 ? 0x7F : 0x00));// fx4
	}
}
//-----------------------------------------------------------------------------
// 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);
	}
}
//-----------------------------------------------------------------------------
