//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2015 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Reloop Beatmix 2/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 Beatmix 2/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;// BeatMix 4
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDevicePID(osId)
{
	return 0x7270;// BeatMix 4
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceVID2(osId)
{
	return 0x26AD;// BeatMix 2
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDevicePID2(osId)
{
	return 0x726F;// BeatMix 2
}
//----------------------------------------------------------------------------- 
// 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 '00206E7270';// BeatMix 4
	// full SysEx ID
	// Beatmix 2: F0 7E 00 06 02 00 20 6E 72 6F XX XX XX XX XX XX F7
	// Beatmix 4: F0 7E 00 06 02 00 20 6E 72 70 XX XX XX XX XX XX F7
}
//----------------------------------------------------------------------------- 
function getSupportedMidiSysExIdString2(osId)
{
	return '00206E726F';// BeatMix 2
}
//----------------------------------------------------------------------------- 
// 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 "BeatMix4";
	//return false;
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForInput2(osId)
{
	return "BeatMix2";
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	return "BeatMix4";
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput2(osId)
{
	return "BeatMix2";
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var jogWheelsInScratchMode=[0,0,0,0];
var lastPoweredJWled=[0,0,0,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 Reloop Beatmix 2/4 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	//------------------------------------
	/*
	Both Beatmix units needs to activate the memorization first to remember the PAD LED status. 
	This is done by sending a specific MIDI Note OFF message order to the device:

	09 91 30 00/09 91 34 00/09 91 38 00/09 91 3C 00/09 92 30 00/09 92 34 00/09 92 38 00/09 92 3C 00

	Please note that this should be sent 2 seconds after the device has powered up.
	*/
	sendMidi(uniqueId, 0x90, 1, 0x30, 0x00);
	sendMidi(uniqueId, 0x90, 1, 0x34, 0x00);
	sendMidi(uniqueId, 0x90, 1, 0x38, 0x00);
	sendMidi(uniqueId, 0x90, 1, 0x3C, 0x00);
	//
	sendMidi(uniqueId, 0x90, 2, 0x30, 0x00);
	sendMidi(uniqueId, 0x90, 2, 0x34, 0x00);
	sendMidi(uniqueId, 0x90, 2, 0x38, 0x00);
	sendMidi(uniqueId, 0x90, 2, 0x3C, 0x00);
	//------------------------------------
	callSendAllNotifications(uniqueId, true);// send all notifications to the controller (so that we don't have to manually init all leds/lcd screens)
}
//----------------------------------------------------------------------------- 
// 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 Beatmix 2/4 ['+uniqueId+']');
	for(var deck=0; deck<4; deck++)
	{
		sendMidi(uniqueId, 0x90, deck+1, 0x30 + lastPoweredJWled[deck], 0x00);// power off the last powered jog-wheel led
	}
	//------------------------------------
	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	
		if(channel<5) call('setDeckPitch', channel-1, 0x7F - data2);
		break;
	//-----------------------------------------------
	// controllers: sliders, knobs, jog-wheels, encoders ('moving' things)
	case 0xB0:
		switch(data1)
		{
		case 0x01: 	if(channel<5) call('setDeckCutoff', channel-1, data2);
				   	if(channel==5) call('setMonitorGain', data2);
				   	break;
		case 0x02: 	if(channel<5) call('deckFxFlangerDW', channel-1, data2);
				   	if(channel==5) call('setCuePgmMix', data2);
				   	break;
		case 0x03:	if(channel<5) call('deckFxBeatwawDW', channel-1, data2); 
					if(channel==5) call('setSamplerMasterVolume', data2); 
					break;

		case 0x10: if(channel<5) call('setDeckGain', channel-1, data2); break;
		case 0x11: if(channel<5) call('setDeckEqHigh', channel-1, data2); break; 
		case 0x12: if(channel<5) call('setDeckEqMid', channel-1, data2); break; 
		case 0x13: if(channel<5) call('setDeckEqLow', channel-1, data2); break; 
		case 0x14: if(channel<5) call('setDeckVolume', channel-1, data2); break; 

		case 0x20: if(channel<5) call('setDeckPitchKey', channel-1, data2); break; 

		case 0x2F: call('setCrossfader', data2); break;

		// we use Shift because when we change decks between 1 and 3, 0x01 always sends to channel 1 instead
		case 0x41: if(channel==1) call('setDeckCutoff', 2, data2);
				   if(channel==2) call('setDeckCutoff', 3, data2);
				   break;
		case 0x42: if(channel==1) call('deckFxFlangerDW', 2, data2);
				   if(channel==2) call('deckFxFlangerDW', 3, data2);
				   break;
		case 0x43: if(channel==1) call('deckFxBeatwawDW', 2, data2);
				   if(channel==2) call('deckFxBeatwawDW', 3, data2);
				   break;
				   		
		case 0x61: // loop knob x2 or /2 
			if(channel<5)
			{
				if(data2 < 0x40) 
				{
					// simulate a fast button press (a note on/off)
					call('deckLoopLess', channel-1, 0x7F); 
					call('deckLoopLess', channel-1, 0x00); 
				}else 
				{
					// simulate a fast button press (a note on/off)
					call('deckLoopMore', channel-1, 0x7F); 
					call('deckLoopMore', channel-1, 0x00); 
				}
			}
			break;

		// ATTN: jog-wheel has a different mode when SHIFT is also pressed
		// params: (deck, increment, total positions per complete revolution) 
		case 0x60: // jog-wheel
			var val1 = data2-0x40;
			if(channel<5)
			{
				if(jogWheelsInScratchMode[channel-1]) 
					call('scratchDeckJW', channel-1, val1, 800);
				else 
					call('moveDeckJogWheel', channel-1, val1, 800);
			}
			if(channel==5) call('lbMoveCursor', val1);
			break;
		case 0x70: // Shift + jog-wheel
			var val1 = data2-0x40;
			if(channel<5) call('searchDeckJW', channel-1, val1, 800);// skip +/- 1 bar for every 15degrees the jog-wheel moves
			break;
					
		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		//---------------------------
		// PADS mode A - blue
		// top
		case 0x00: if(channel<5) call('deckCuePos1', channel-1, data2); break; 
		case 0x01: if(channel<5) call('deckCuePos2', channel-1, data2); break; 
		case 0x02: if(channel<5) call('deckCuePos3', channel-1, data2); break; 
		case 0x03: if(channel<5) call('deckCuePos4', channel-1, data2); break; 
		// bottom
		case 0x04: if(channel<5) call('deckLoop1', channel-1, data2); break; 
		case 0x05: if(channel<5) call('deckLoop2', channel-1, data2); break; 
		case 0x06: if(channel<5) call('deckLoop4', channel-1, data2); break; 
		case 0x07: if(channel<5) call('deckLoop8', channel-1, data2); break; 
		
		// Shift + mode A
		// top
		case 0x08: if(channel<5) call('deckEqLowKill', channel-1, data2);
				   if(channel==5) if(data2>0x3F) call('lbToggleSelect', data2);
				   break;
		case 0x09: if(channel<5) call('deckEqMidKill', channel-1, data2); 
				   if(channel==5) if(data2>0x3F) call('lbToggleSelect', data2);
				   break;
		case 0x0A: if(channel<5) call('deckEqHighKill', channel-1, data2); break; 
		case 0x0B: if(channel<5) call('deckBrake', channel-1, data2); break; 
		// bottom
		case 0x0C: if(channel<5) call('deckLoopInEnd', channel-1, data2); break;
		case 0x0D: if(channel<5) call('deckLoopPrev', channel-1, data2); break; 
		case 0x0E: if(channel<5) call('deckLoopNext', channel-1, data2); break; 
		case 0x0F: if(channel<5) call('deckLoopExitOrReloop', channel-1, data2); break;// deckLoopExit
				   
		// PADS mode B - red (sampler)
		// top
		case 0x10: if(channel<5) call('samplerInstant', 1, data2); break;
		case 0x11: if(channel<5) call('samplerInstant', 2, data2); break;
		case 0x12: if(channel<5) call('samplerInstant', 3, data2); break;
		case 0x13: if(channel<5) call('samplerInstant', 4, data2); break;
		// bottom
		case 0x14: if(channel<5) call('samplerInstant', 5, data2); break;
		case 0x15: if(channel<5) call('samplerInstant', 6, data2); break;
		case 0x16: if(channel<5) call('samplerInstant', 7, data2); break;
		case 0x17: if(channel<5) call('samplerInstant', 8, data2); break;

		// Shift + mode B (instant beats)
		// top
		case 0x18: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:4); break; // 1/16 beat
		case 0x19: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:8); break; // 1/8 beat
		case 0x1A: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:16); break; // 1/4 beat
		case 0x1B: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:32); break; // 1/2 beat
		// bottom
		case 0x1C: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:64); break; // 1 beat
		case 0x1D: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:128); break; // 2 beats
		case 0x1E: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:256); break; // 4 beats
		case 0x1F: if(channel<5) call('deckLoopInstant64ths', channel-1, (data2==0)?-1:512); break; // 8 beats

		//---------------------------
		case 0x20: break;// SHIFT key (not used directly)				   

		case 0x21: if(channel<5) call('deckSync', channel-1, data2); break;
		case 0x22: if(channel<5) call('deckCuePlay', channel-1, data2); break;
		case 0x23: if(channel<5) call('deckCue', channel-1, data2); break;
		case 0x24: if(channel<5) call('deckPlayPause', channel-1, data2); break;
		case 0x25: // beats/tap -- used for in/out/exit looping
			if(channel<5)
			{
				if(data2>0x3F)
				{
					if(!getValue('looping', channel-1)) 
					{
						// simulate a fast button press (a note on/off)
						call('deckLoopInEnd', channel-1, 0x7F); 
						call('deckLoopInEnd', channel-1, 0x00); 
					}else 
					{
						// simulate a fast button press (a note on/off)
						call('deckLoopExit', channel-1, 0x7F); 
						call('deckLoopExit', channel-1, 0x00); 
					}
				}
			}
			break;
		case 0x26: 
			if(channel<5) call('deckBendDown', channel-1, data2); 
			sendMidi(uniqueId,0x90,channel,0x25,data2);// light up the led while the button is being pressed
			break;
		case 0x27: 
			if(channel<5) call('deckBendUp', channel-1, data2); 
			sendMidi(uniqueId,0x90,channel,0x24,data2);// light up the led while the button is being pressed
			break;
		
		// jog-wheel hold
		case 0x2F: // normal
		case 0x3F: if(channel<5) call('holdDeckJogWheel', channel-1, data2); break;

		case 0x50: if(channel<5) if(data2>0x3F) call('deckLBLoad', channel-1, data2); break;
		case 0x52: if(channel<5) call('deckMonitor', channel-1, data2); break;

		//---------------------------
		// SHIFTed keys (non-pads only)
		case 0x61: if(channel<5) call('deckMatch', channel-1, data2); break;// Shift + Sync
		case 0x62: if(channel<5) call('deckKeyLock', channel-1, data2); break;// Shift + CUP
		case 0x63: if(channel<5) call('deckBackToTop', channel-1, data2); break;// Shift + CUE
		case 0x64: if(channel<5) call('deckReverse', channel-1, data2); break; // Shift + PlayPause
		case 0x65: if(channel<5) call('deckTapBpm', channel-1, data2); break;// Shift + beats/tap
		case 0x66: break;// Shift + bend-
		case 0x67: break;// Shift + bend+
						
		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);
	deck = deck+1;// we need to do this because deck A starts at 1 (not at 0) channel
	if(name=='onbeat') 		 sendMidi(uniqueId,0x90,deck,0x20,(param1 ? 0x7F : 0x00));	
	//if(name=='cup') 		 sendMidi(uniqueId,0x90,deck,0x21,(param1 ? 0x7F : 0x00));	
	if(name=='cue') 		 sendMidi(uniqueId,0x90,deck,0x22,(param1 ? 0x7F : 0x00));	
	if(name=='play') 		 sendMidi(uniqueId,0x90,deck,0x23,(param1 ? 0x7F : 0x00));	

	if(name=='monitor') 	 sendMidi(uniqueId,0x90,deck,0x52,(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,0x50,((param1!=0 && param1!=32) ? 0x7F : 0x00));// set led on during loading

	if(name=='keyLock') 	sendMidi(uniqueId,0x90,deck,0x61,(param1 ? 0x7F : 0x00));// Shift + CUP
	if(name=='reverse') 	sendMidi(uniqueId,0x90,deck,0x63,(param1 ? 0x7F : 0x00));// Shift + PlayPause

	//-------------------
	// pads LEDs values: 0 both off; 1-42 both blue&red on; 43-85 blue on; 86-127 red on;
	//-------------------
	// PADS mode A - blue
	// top
	if(name=='cuepos1')		 sendMidi(uniqueId,0x90,deck,0x00,(param1 ? 43 : 0x00));
	if(name=='cuepos2')		 sendMidi(uniqueId,0x90,deck,0x01,(param1 ? 43 : 0x00));
	if(name=='cuepos3')		 sendMidi(uniqueId,0x90,deck,0x02,(param1 ? 43 : 0x00));
	if(name=='cuepos4')		 sendMidi(uniqueId,0x90,deck,0x03,(param1 ? 43 : 0x00));
	// bottom
	if(name=='loopSize')
	{
		sendMidi(uniqueId,0x90,deck,0x04,((param1==64)  ? 43 : 0x00));// loop 1
		sendMidi(uniqueId,0x90,deck,0x05,((param1==128) ? 43 : 0x00));// loop 2
		sendMidi(uniqueId,0x90,deck,0x06,((param1==256) ? 43 : 0x00));// loop 4
		sendMidi(uniqueId,0x90,deck,0x07,((param1==512) ? 43 : 0x00));// loop 8
	}
	
	// Shift + mode A
	// top
	if(name=='killLow')		 sendMidi(uniqueId,0x90,deck,0x40,(param1 ? 43 : 0x00));// was: 0x48
	if(name=='killMid')		 sendMidi(uniqueId,0x90,deck,0x41,(param1 ? 43 : 0x00));
	if(name=='killHigh')	 sendMidi(uniqueId,0x90,deck,0x42,(param1 ? 43 : 0x00));
	if(name=='brake')		 sendMidi(uniqueId,0x90,deck,0x43,(param1 ? 43 : 0x00));
	// bottom
	if(name=='loopBlink')	 
	{
		sendMidi(uniqueId,0x90,deck,0x44,(param1 ? 43 : 0x00));// deckLoopInEnd// was: 0x4C
		sendMidi(uniqueId,0x90,deck,0x47,(param1 ? 43 : 0x00));// deckLoopExitOrReloop
	}
	// not used (we use 'loopBlink' instead)
	//if(name=='loopIn')	 sendMidi(uniqueId,0x90,deck,0x4C 0x44,(param1 ? 43 : 0x00));// deckLoopInEnd
	//if(name=='none')		 sendMidi(uniqueId,0x90,deck,0x4D 0x45,(param1 ? 43 : 0x00));// deckLoopPrev
	//if(name=='none')	 	 sendMidi(uniqueId,0x90,deck,0x4E 0x46,(param1 ? 43 : 0x00));// deckLoopNext
	//if(name=='looping')	 sendMidi(uniqueId,0x90,deck,0x4F 0x47,(param1 ? 43 : 0x00));// deckLoopExitOrReloop
	
	//-------------------
	// PADS mode B - red (sampler)
	// top
	if(name=='sampler1') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x08,(param1 ? 86 : 0x00));// 'sampler1' getValue(sampler2loaded)
	if(name=='sampler2') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x09,(param1 ? 86 : 0x00));
	if(name=='sampler3') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0A,(param1 ? 86 : 0x00));
	if(name=='sampler4') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0B,(param1 ? 86 : 0x00));
	// bottom
	if(name=='sampler5') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0C,(param1 ? 86 : 0x00));
	if(name=='sampler6') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0D,(param1 ? 86 : 0x00));
	if(name=='sampler7') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0E,(param1 ? 86 : 0x00));
	if(name=='sampler8') for(var d=0; d<4; d++) sendMidi(uniqueId,0x90,d+1,0x0F,(param1 ? 86 : 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+']');
	var totalJwLeds = 16;
	for(var deck=0; deck<4; deck++)
	{
		// 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]);
		// actually light on/off the leds
		if(lastPoweredJWled[deck] != finalLed)
		{
			// set to off prev led
			sendMidi(uniqueId, 0x90, deck+1, 0x30 + lastPoweredJWled[deck], 0x00);
			// set to on next led
			sendMidi(uniqueId, 0x90, deck+1, 0x30 + finalLed, 0x7F);
			// update last led
			//debug('2led='+finalLed+', last='+lastPoweredJWled[deck]);
			lastPoweredJWled[deck] = finalLed;
		}
	}
}
//----------------------------------------------------------------------------- 
