//-----------------------------------------------------------------------------
//------------ Copyright (c)2003-2020 Xylio. All Rights Reserved. -------------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Hercules DJControl Starlight'
//-----------------------------------------------------------------------------
function getLanguageVersion()
{
	return 1;// do not modify this, this should be 1 for all scripts
}
//-----------------------------------------------------------------------------
function getFileVersion()
{
	return 2;// update this as needed
}
//-----------------------------------------------------------------------------
// return a user friendly name for the controller supported
function getControllerUserFriendlyName()
{
	return "DJControl Starlight";
}
//-----------------------------------------------------------------------------
// return the communication type that the controller supports
// this can be either 'midi' or 'hid' (please return only one of them in case the controller supports both)
function getControllerType()
{
	return 'midi';
}
//-----------------------------------------------------------------------------
// preferred way to identify the controller (using the USB device VID and PID -- vendor and product id)
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 macOS; 2 Linux; -1 unknown
function getSupportedMidiDeviceVID(osId)
{
	return 0x06F8;
}
//-----------------------------------------------------------------------------
function getSupportedMidiDevicePID(osId)
{
	return 0xB128;
}
//-----------------------------------------------------------------------------
// 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 "00014E02001B";
	// F0 7E 00 06 02 00 01 4E 02 00 02 00 01 00 00 00 F7 -- Hercules DJ Console MK2 complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 03 00 01 00 00 00 F7 -- Hercules DJ Control MP3 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 04 00 01 00 00 00 F7 -- Hercules DJ Console RMX complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 05 00 01 00 00 00 F7 -- Hercules DJ Control Steel complete sysex
	// F0 7E 00 06 02 00 01 4E 01 00 07 00 01 00 00 00 F7 -- Hercules DJ Control MP3e2 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 08 00 01 00 00 00 F7 -- Hercules DJ Console MK4 complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0B 00 01 00 00 00 F7 -- Hercules DJ Control Air complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0C 00 01 00 00 00 F7 -- Hercules DJ Control Instinct complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 10 00 01 00 00 00 F7 -- Hercules DJ Control Air+ complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 0A 00 01 00 00 00 F7 -- Hercules DJ4Set complete sysex
	// F0 7E 00 06 02 00 01 4E 02 00 09 00 01 00 00 00 F7 -- Hercules DJ Console 4-Mx
	// F0 7E 7F 06 02 00 01 4E 02 00 16 00 01 00 00 00 F7 -- Hercules DJ Control Compact
	// F0 7E 7F 06 02 00 01 4E 02 00 12 00 01 00 00 00 F7 -- Hercules Universal DJ
	// F0 7E 7F 06 02 00 01 4E 02 00 1B 00 01 00 00 00 F7 -- Hercules DJControl Starlight
	// F0 7E 7F 06 02 00 01 4E 02 00 19 00 01 00 00 00 F7 -- Hercules DJControl Inpulse 200
	// F0 7E 7F 06 02 00 01 4E 02 00 1A 00 01 00 00 00 F7 -- Hercules DJControl Inpulse 300
}
//-----------------------------------------------------------------------------
// this is NOT the preferred way to identify the controller but you should use
// it if the controller does not support standard MIDI SysEx identification
// return false if the controller does not support this kind of identification
// osId -- 0 Windows; 1 macOS; 2 Linux; -1 unknown
function getSupportedMidiDeviceNameForInput(osId)
{
	//if(osId==1) return "DJControl Starlight";
	return "DJControl Starlight";
}
//-----------------------------------------------------------------------------
function getSupportedMidiDeviceNameForOutput(osId)
{
	//if(osId==1) return "DJControl Starlight";
	return "DJControl Starlight";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// some variables necessary only for this particular script
var jogWheelsInScratchMode=0;
var previousAutoLoopLightLed=[-1,-1,-1,-1];// 4 decks leds
//-----------------------------------------------------------------------------
// this is called when the controller is initialized and the control is passed to this script
// use this to initialize any variables you might need, the controller' leds, etc
// uniqueId is a unique integer between 0..15 that is guaranteed not to change for
// a certain opened controller -- please note that there can be many controllers
// of the same type in use at the same time ! Use uniqueId to identify between them
function controllerInit(uniqueId)
{
	debug('controllerInit Hercules DJControl Starlight ['+uniqueId+']');// this outputs a string to DebugView app on Win
	// setting audio (the actual routing is done automatically for this controller so
	// we don't need to manually set "RoutingOutHeadphones", "RoutingOutDeckA", etc
	// ATTN: we first try to set ASIO mode, and only then regular CoreAudio, DirectSound audio/WDM/Wasapi
	if(setAudioDevice("DJControl Starlight ASIO|||Hercules DJ Control Starlight|||Hercules DJControl Starlight|||DJControl Starlight|||Speakers (DJControl Starlight) (WASAPI)|||Speakers (DJControl Starlight)"))
	{
		//debug('AudioDevice was set OK');
		setSetting('AudioDevice2', "NO AUDIO");
	}

	//sendMidi(uniqueId, status, channel, data1, data2);
	sendMidi(uniqueId,0x90,1,0x03,(jogWheelsInScratchMode ? 0x7F : 0x00));// scratch mode

	// set big color led (underneath the controller) - RGB_HW_MODE1
	sendMidi(uniqueId,0x90,0,0x24,0x00);// always send 0x00 before any other command
	//sendMidi(uniqueId,0x90,0,0x24,0x7F);// RGB cycle mode
	// ATTN: we can also control per-deck color but what to use it for? (ideas: when loading a track, etc)

	//------------------------------------
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
	// update all analog controls (eg. fader/knob positions)
	sendMidi(uniqueId,0xB0,0,0x7F,0x7F);
}
//-----------------------------------------------------------------------------
// called just before the controller will be deinitialized and the control will go away from the script
// use to power off any powered leds, etc
function controllerDeinit(uniqueId)
{
	debug('controllerDeinit ['+uniqueId+']');
	// set leds off
	sendMidi(uniqueId,0x90,1,0x03,0x00);// scratch mode
	sendMidi(uniqueId,0x90,0,0x24,0x7F);// RGB cycle mode
	//------------------------------------
	callSendAllNotifications(uniqueId, false);// send all notifications to the controller (so that we don't have to manually de-init all leds/lcd screens)
}
//-----------------------------------------------------------------------------
// this is your main target function to implement the controller -- this is called
// for each and every MIDI message that comes from the controller itself
// return true if the message was processed by the script and false to let it
// pass thru the software (eg. so it can be assigned by the user using the LEARN function)
function messageReceived(uniqueId,status,channel,data1,data2)
{
	var messageNotProcessed=false;
	switch(status)
	{
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		// global or per-deck
		case 0x00: if(channel == 0) call('setCrossfader', data2);else call('setDeckVolume', channel-1, data2); break;// 0x00 low-res + 0x20 high-res
		case 0x01: if(channel > 0)  call('setDeckCutoff', channel-1, data2); break;// 0x01 low-res + 0x21 high-res
		case 0x02: if(channel > 0)  call('setDeckEqLow', channel-1, data2); break;// 0x02 low-res + 0x22 high-res
		case 0x03: if(channel == 0) call('setMasterVolume', data2); break;// 0x03 low-res + 0x23 high-res
		case 0x04: if(channel == 0) call('setMonitorVolume', data2); break;// 0x04 low-res + 0x24 high-res
		case 0x08: if(channel > 0)  call('setDeckPitch', channel-1, data2); break;// 0x08 low-res + 0x28 high-res
		// jog-wheels
		case 0x09: // params: (deck, increment, total positions per complete revolution) -- without hold
		{
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			var deck = channel - 1;
			if(channel < 4)
			{
				if(jogWheelsInScratchMode)
					call('scratchDeckJW', deck, val1, 256);
				else
					call('moveDeckJogWheel', deck, val1, 256);
			}else
			{
				var deck = channel - 4;
				call('searchDeckJW', deck, val1, 256);
			}
			break;
		}
		case 0x0A: // with hold
		{
			var val1 = (data2>0x40 ? data2-0x80 : data2);
			var deck = channel - 1;
			if(channel < 4)
			{
				call('scratchDeckJW', deck, val1, 256);
			}else
			{
				var deck = channel - 4;
				call('searchDeckJW', deck, val1, 256);
			}
			break;
		}

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		// global or per-deck
		switch(data1)
		{
		case 0x00:
			if(channel >= 6) call('deckCuePos1', channel-6, data2);// PADs HOT-CUE 1
			break;
		case 0x01:
			if(channel >= 6) call('deckCuePos2', channel-6, data2);// PADs HOT-CUE 2
			break;
		case 0x02:
			if(channel >= 6) call('deckCuePos3', channel-6, data2);// PADs HOT-CUE 3
			break;
		case 0x03:
			if(channel >= 6) call('deckCuePos4', channel-6, data2);// PADs HOT-CUE 4
			if(channel == 1) // vinyl (ATTN: when pressed it sends note on/off for both channels 1 and 2)
			{
				if(data2 > 0x3F)
				{
					var deck = channel - 1;
					jogWheelsInScratchMode = (jogWheelsInScratchMode + 1) % 2;
					sendMidi(uniqueId,0x90,1,0x03,(jogWheelsInScratchMode ? 0x7F : 0x00));
					break;
				}
			}
			break;
			// with Shift

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

		case 0x08:
			if(channel >= 6) call('deckCuePos5', channel-6, data2);// PADs HOT-CUE 1 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			if((channel > 0) && (channel < 5)) call('holdDeckJogWheel', channel-1, data2);// jog-wheel touch
			break;
		case 0x09:
			if(channel >= 6) call('deckCuePos6', channel-6, data2);// PADs HOT-CUE 2 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
		case 0x0A:
			if(channel >= 6) call('deckCuePos7', channel-6, data2);// PADs HOT-CUE 3 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
		case 0x0B:
			if(channel >= 6) call('deckCuePos8', channel-6, data2);// PADs HOT-CUE 4 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;

		case 0x0C: if(channel > 0) call('deckMonitor', channel-1, data2); break;

		case 0x10:
			if(channel >= 6) call('deckLoop1', channel-6, data2);// PADs LOOP 1
			break;
		case 0x11:
			if(channel >= 6) call('deckLoop2', channel-6, data2);// PADs LOOP 2
			break;
		case 0x12:
			if(channel >= 6) call('deckLoop4', channel-6, data2);// PADs LOOP 3
			break;
		case 0x13:
			if(channel >= 6) call('deckLoop8', channel-6, data2);// PADs LOOP 4
			break;
		// with Shift
		case 0x18:
			if(channel >= 6) call('deckLoopInEnd', channel-6, data2);// PADs LOOP 1 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
		case 0x19:
			if(channel >= 6) call('deckLoopExitOrReloop', channel-6, data2);// PADs LOOP 2 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
		case 0x1A:
			if(channel >= 6) call('deckLoopPrev', channel-6, data2);// PADs LOOP 3 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
		case 0x1B:
			if(channel >= 6) call('deckLoopNext', channel-6, data2);// PADs LOOP 4 + SHIFT (ATTN: led doesn't change when pressing SHIFT)
			break;
			/*
			case 0x2E: call('setAutoDj', data2); break;
			case 0x2B: call('record', data2); break;

			case 0x0D: call('deckBrake', 0, data2); break;
			case 0x0E: call('deckReverse', 0, data2); break;
			*/

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

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

		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	default: messageNotProcessed = true;
	}
	// ATTN: do NOT call this unless you are debugging the script itself (logging is pretty slow)
	//if(messageNotProcessed) debug('messageReceived ['+uniqueId+'] - status:'+status+' channel:'+channel+' data1:'+data1+' data2:'+data2);
	return !messageNotProcessed;
}
//-----------------------------------------------------------------------------
// this is called by the software whenever something important changes (eg. play status, song changed, etc)
// depending on the notification type it may refer to a global event (deck is -1) or a specific deck (deck >= 0)
// and can contain any/all of the parameters: param 1 is an integer, param 2 is a float, param 3 is a string
function notificationReceived(uniqueId,name,deck,param1,param2,param3)
{
	//debug('notif ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	if(deck>=2) return;
	var nameLen = name.length;

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

	// with shift
	if(name=='brake') 		sendMidi(uniqueId,0x90,0,(deck==0)?0x0D:0x3D,(param1 ? 0x7F : 0x00));
	if(name=='reverse')
	{
		sendMidi(uniqueId,0x90,0,(deck==0)?0x24:0x54,(param1 ? 0x7F : 0x00));// play
		sendMidi(uniqueId,0x90,0,(deck==0)?0x0E:0x3E,(param1 ? 0x7F : 0x00));// pad
	}
	*/

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

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

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

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

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

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

	//Pads - Sample
	if(nameLen==13)
	{
		var name1 = name.substr(0, nameLen-1);
		if(name1=='samplerBlink')
		{
			// deck A
			if(name=='samplerBlink1') sendMidi(uniqueId,0x90,6,0x30,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink2') sendMidi(uniqueId,0x90,6,0x31,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink3') sendMidi(uniqueId,0x90,6,0x32,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink4') sendMidi(uniqueId,0x90,6,0x33,(param1 ? 0x7F : 0x00));
			// deck B
			if(name=='samplerBlink5') sendMidi(uniqueId,0x90,7,0x30,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink6') sendMidi(uniqueId,0x90,7,0x31,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink7') sendMidi(uniqueId,0x90,7,0x32,(param1 ? 0x7F : 0x00));
			if(name=='samplerBlink8') sendMidi(uniqueId,0x90,7,0x33,(param1 ? 0x7F : 0x00));
		}
	}
}
//-----------------------------------------------------------------------------
// 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+']');
}
//-----------------------------------------------------------------------------
