//-----------------------------------------------------------------------------
//-------- Copyright (c)2003-2013 XYLIO INFO SRL. All Rights Reserved. --------
//-----------------------------------------------------------------------------
// file implementing MIDI controller 'Behringer BCD3000' (based on BCD2000 script)
//----------------------------------------------------------------------------- 
// http://www.behringer.com/assets/BCD2000_P0234_Top_XXL.png
// http://www.behringer.com/assets/BCD3000_P0758_Top_XXL.png
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 "Behringer BCD3000";
}
//----------------------------------------------------------------------------- 
// 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 "00014E020008";
	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)
{
	//if(osId==1) return "Behringer BCD3000";
	return "BCD-3000";
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForInput2(osId)
{
	return "BCD3000";
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput(osId)
{
	return "BCD-3000";
}
//----------------------------------------------------------------------------- 
function getSupportedMidiDeviceNameForOutput2(osId)
{
	return "BCD3000";
}
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
//----------------------------------------------------------------------------- 
// some variables necessary only for this particular script
var jogWheelsInScratchMode=[0,0,0,0];
var loopingInBlinkLed=[-1,-1,-1,-1];// -1 means disabled
//----------------------------------------------------------------------------- 
// BCD2000 and BCD3000 leds (common)
var led_assignA = 6;
var led_assignB = 5;
var led_assignC = 4;
var led_assignD = 3;
var led_scratchA = 19;
var led_scratchB = 11;
var led_syncA = 20;
var led_syncB = 12;
var led_looponoffA = 21;
var led_looponoffB = 13;
var led_cuthiA = 22;
var led_cutmidA = 23;
var led_cutlowA = 24;
var led_cuthiB = 14;
var led_cutmidB = 15;
var led_cutlowB = 16;
var led_phones_split = 25;
// BCD2000-only leds
/*
var led_onair = 7;
var led_talkover = 8;
var led_playpauseA = 17;
var led_playpauseB = 9;
var led_cueA = 18;
var led_cueB = 10;
var led_inputA = 2;
var led_inputB = 1;
var led_monitorA = -1;// N/A
var led_monitorB = -1;// N/A
*/
// BCD3000-only leds
var led_onair = -1;// N/A
var led_talkover = -1;// N/A
var led_playpauseA = 18;// cueA from BCD2000
var led_playpauseB = 10;// cueB from BCD2000
var led_cueA = 17;// playpauseA from BCD2000
var led_cueB = 9;// playpauseB from BCD2000
var led_inputA = 8;// talkover from BCD2000
var led_inputB = 7;// onair from BCD2000
var led_monitorA = 2;// inputA from BCD2000
var led_monitorB = 1;// inputB from BCD2000
//----------------------------------------------------------------------------- 
// on the BCD2000 (maybe in the BCD3000 too?) the audio input 1 acts either 
// as a line/phono in or as a microphone input. This functionality can be 
// switched by the software (by sending a MIDI command to the controller)
function setAnalogAToMicrophone(uniqueId, enableMike)
{
	var onoff = 0;
	if(enableMike) onoff = 1;
	sendMidi(uniqueId,0xC0,0,onoff,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 Behringer BCD3000 ['+uniqueId+']');// this outputs a string to DebugView app on Win
	//---------------------------------------
	// set all leds off
	for(var led=1; led<26; led++)
	{
		sendMidi(uniqueId,0xB0,0,led,0x40);
		if(led%5==0) call('sleep', 5);// delay for 5ms to allow the MIDI out buffer to flush
	}
	// delay for 300ms to allow the user to notice the leds
	call('sleep', 300);
	// set all leds off
	for(var led=1; led<26; led++)
	{
		sendMidi(uniqueId,0xB0,0,led,0x00);
		if(led%5==0) call('sleep', 5);// delay for 5ms to allow the MIDI out buffer to flush
	}
	//---------------------------------------
	// enable mic in by default
	setAnalogAToMicrophone(uniqueId, true);
	//---------------------------------------
	//sendMidi(uniqueId, status, channel, data1, data2);
	for(var deck=0; deck<2; deck++)
	{
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_cueA:led_cueB,(getValue('cue', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_playpauseA:led_playpauseB,(getValue('play', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_inputA:led_inputB,(getValue('analogIn', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_syncA:led_syncB,(getValue('onbeat', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_looponoffA:led_looponoffB,(getValue('looping', deck) ? 0x7F : 0x00));		
		call('sleep', 5);// delay for 5ms to allow the MIDI out buffer to flush
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_cuthiA:led_cuthiB,(getValue('killHigh', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_cutmidA:led_cutmidB,(getValue('killMid', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_cutlowA:led_cutlowB,(getValue('killLow', deck) ? 0x7F : 0x00));
		if(led_monitorA>-1)
		{
			sendMidi(uniqueId,0xB0,0,(deck==0)?led_monitorA:led_monitorB,(getValue('monitor', deck) ? 0x7F : 0x00));
		}
		// global
		if(deck==0)
		{
			//sendMidi(uniqueId,0xB0,0,led_phones_split,(getValue('phonesSplit') ? 0x7F : 0x00));// no notification for split
			if(led_talkover>-1) sendMidi(uniqueId,0xB0,0,led_talkover,(getValue('talkOver') ? 0x7F : 0x00));
			if(led_onair>-1) sendMidi(uniqueId,0xB0,0,led_onair,(getValue('onAir') ? 0x7F : 0x00));		
		}
		call('sleep', 5);// delay for 5ms to allow the MIDI out buffer to flush
		/*
		// we don't currently use these leds (so we leave them off)
		led_assignD, 3
		led_assignC, 4
		led_assignB, 5
		led_assignA, 6
		
		sendMidi(uniqueId,0x90,0,(deck==0)?0x05:0x19,(getValue('fxFlanger', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,0,(deck==0)?0x06:0x1A,(getValue('fxEcho', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,0,(deck==0)?0x07:0x1B,(getValue('fxDelay', deck) ? 0x7F : 0x00));
		sendMidi(uniqueId,0x90,0,(deck==0)?0x08:0x1C,(getValue('fxReverb', deck) ? 0x7F : 0x00));
		*/
	}
	//---------------------------------------
	// request knobs and faders
	sendMidi(uniqueId,0xB0,0,100,0);
}
//----------------------------------------------------------------------------- 
// 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 all leds off
	for(var led=1; led<26; led++)
	{
		sendMidi(uniqueId,0xB0,0,led,0x00);
		if(led%5==0) call('sleep', 5);// delay for 5ms to allow the MIDI out buffer to flush
	}
}
//----------------------------------------------------------------------------- 
// 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
		case 0x01: call('setCrossfader', data2); break;		
		case 0x11: call('setCuePgmMix', data2); break;// BCD3000: setCuePgmMix / setCuePflMix

		// per deck
		// deck A
		case 0x00: call('setDeckVolume', 0, data2); break;
		case 0x03: call('setDeckEqLow', 0, data2); break;
		case 0x04: call('setDeckEqMid', 0, data2); break;
		case 0x05: call('setDeckEqHigh', 0, data2); break;
		case 0x06: call('setDeckGain', 0, data2); break;
		case 0x0B: call('setDeckPitch', 0, data2); break;
		case 0x13: // params: (deck, increment, total positions per complete revolution) 
			var val1 = (data2>0x40)?1:-1;
			if(jogWheelsInScratchMode[0]) call('scratchDeckJW', 0, val1, 96);else call('moveDeckJogWheel', 0, val1, 96);
			break;

		// deck B
		case 0x02: call('setDeckVolume', 1, data2); break;
		case 0x07: call('setDeckEqLow', 1, data2); break;
		case 0x08: call('setDeckEqMid', 1, data2); break;
		case 0x09: call('setDeckEqHigh', 1, data2); break;
		case 0x0A: call('setDeckGain', 1, data2); break;
		case 0x0C: call('setDeckPitch', 1, data2); break;
		case 0x12: // params: (deck, increment, total positions per complete revolution) 
			var val1 = (data2>0x40)?1:-1;
			if(jogWheelsInScratchMode[1]) call('scratchDeckJW', 1, val1, 96);else call('moveDeckJogWheel', 1, val1, 96);
			break;
			
		// the 4 assignable knobs (0x0D, 0x0E, 0x0F, 0x10)  // maybe use flanger + filter for each deck
		case 0x0D: call('deckFxFlangerDW', 0, data2); break;// 1/A -- used as flanger for deck A
		case 0x0E: call('setDeckCutoff', 0, data2); break;// 2/B -- used as cutoff filter for deck A
		case 0x0F: call('deckFxFlangerDW', 1, data2); break;// 3/C -- used as flanger for deck B
		case 0x10: call('setDeckCutoff', 1, data2); break;// 4/D -- used as cutoff filter for deck B
		
		default: messageNotProcessed = true;
		}
		break;
	//-----------------------------------------------
	// note on/off: buttons, switches ('pressing' things)
	case 0x80:
	case 0x90:
		switch(data1)
		{
		// global
		//case 0x00: if(data2>0x3F) call('lbListSelect', 1); break;
		//case 0x00: if(data2>0x3F) call('lbBrowserSelect', 1); break;
		case 0x1C: if(data2>0x3F) call('lbToggleSelect', data2); break;// toggle browser/playlist
		//case 0x1C: call('phonesSplit', data2); break;// on BCD2000 is marked as 'Phones Split/Master Out'; on BCD3000 is marked as 'Key Deck A+B';
		case 0x1D: call('deckAnalogIn', 0, data2); break;// BCD3000: deckAnalogIn 0 / micTalkOver
		case 0x1E: call('deckAnalogIn', 1, data2); break;// BCD3000: deckAnalogIn 1 / micOnAir
	
		// the 4 assignable buttons (0x1F, 0x20, 0x21, 0x22)
		case 0x1F: if(data2>0x3F) call('deckLBLoad', 0, data2); break;// load A
		case 0x20: if(data2>0x3F) call('lbMoveCursor', +1); break;// down
		case 0x21: if(data2>0x3F) call('lbMoveCursor', -1); break;// up
		case 0x22: if(data2>0x3F) call('deckLBLoad', 1, data2); break;// load B

		// per deck
		// deck A
		case 0x0C: call('deckEqLowKill', 0, data2); break;
		case 0x0D: call('deckEqMidKill', 0, data2); break;
		case 0x0E: call('deckEqHighKill', 0, data2); break;
		case 0x02: call('deckBendDown', 0, data2); break;
		case 0x03: call('deckBendUp', 0, data2); break;
		case 0x00: call('deckSearchLeft', 0, data2); break;
		case 0x01: call('deckSearchRight', 0, data2); break;		
		case 0x0F: call('deckLoopInEnd', 0, data2); break;
		case 0x05: call('deckLoopExit', 0, data2); break;
		case 0x10: call('deckSync', 0, data2); break;
		case 0x11: 
			if(data2>0x3F)
			{ 
				jogWheelsInScratchMode[0] = (jogWheelsInScratchMode[0] + 1) % 2; 
				sendMidi(uniqueId,0xB0,0,led_scratchA,(jogWheelsInScratchMode[0] ? 0x7F : 0x00));
			} 
			break;
		case 0x12: call('deckPlayPause', 0, data2); break;// BCD3000: deckPlayPause / deckCue
		case 0x13: call('deckCue', 0, data2); break;// BCD3000: deckCue / deckPlayPause
		case 0x04: call('deckSetCue', 0, data2); break;// BCD3000: deckSetCue / deckBackToTop
		case 0x23: call('deckMonitor', 0, data2); break;// BCD3000: deckMonitor / deckAnalogIn
		
		// deck B
		case 0x14: call('deckEqLowKill', 1, data2); break;
		case 0x15: call('deckEqMidKill', 1, data2); break;
		case 0x16: call('deckEqHighKill', 1, data2); break;
		case 0x08: call('deckBendDown', 1, data2); break;
		case 0x09: call('deckBendUp', 1, data2); break;
		case 0x06: call('deckSearchLeft', 1, data2); break;
		case 0x07: call('deckSearchRight', 1, data2); break;
		case 0x17: call('deckLoopInEnd', 1, data2); break;
		case 0x0B: call('deckLoopExit', 1, data2); break;
		case 0x18: call('deckSync', 1, data2); break;
		case 0x19: 
			if(data2>0x3F)
			{ 
				jogWheelsInScratchMode[1] = (jogWheelsInScratchMode[1] + 1) % 2; 
				sendMidi(uniqueId,0xB0,0,led_scratchB,(jogWheelsInScratchMode[1] ? 0x7F : 0x00));
			} 
			break;
		case 0x1A: call('deckPlayPause', 1, data2); break;// BCD3000: deckPlayPause / deckCue
		case 0x1B: call('deckCue', 1, data2); break;// BCD3000: deckCue / deckPlayPause
		case 0x0A: call('deckSetCue', 1, data2); break;// BCD3000: deckSetCue / deckBackToTop
		case 0x24: call('deckMonitor', 1, data2); break;// BCD3000: deckMonitor / deckAnalogIn
		/*
		//not used
		case 0x19: call('deckFxFlanger', 1, data2); break;
		case 0x1A: call('deckFxEcho', 1, data2); break;
		case 0x1B: call('deckFxDelay', 1, data2); break;
		case 0x1C: call('deckFxReverb', 1, 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 ['+uniqueId+'] name='+name + ' deck='+deck + ' p1(int)='+param1 + ' p2(float)='+param2 + ' p3(str)='+param3);
	if(deck>=2) return;
	if(name=='cue') 		sendMidi(uniqueId,0xB0,0,(deck==0)?led_cueA:led_cueB,(param1 ? 0x7F : 0x00));
	if(name=='play') 		sendMidi(uniqueId,0xB0,0,(deck==0)?led_playpauseA:led_playpauseB,(param1 ? 0x7F : 0x00));
	if(name=='analogIn') 	
	{
		if(deck==0) setAnalogAToMicrophone(uniqueId, (param1 ? false : true));// enable mic input when the analog input is off
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_inputA:led_inputB,(param1 ? 0x7F : 0x00));
	}
	if(name=='onbeat') 		sendMidi(uniqueId,0xB0,0,(deck==0)?led_syncA:led_syncB,(param1 ? 0x7F : 0x00));
	if(name=='killHigh') 	sendMidi(uniqueId,0xB0,0,(deck==0)?led_cuthiA:led_cuthiB,(param1 ? 0x7F : 0x00));
	if(name=='killMid') 	sendMidi(uniqueId,0xB0,0,(deck==0)?led_cutmidA:led_cutmidB,(param1 ? 0x7F : 0x00));
	if(name=='killLow') 	sendMidi(uniqueId,0xB0,0,(deck==0)?led_cutlowA:led_cutlowB,(param1 ? 0x7F : 0x00));	
	if(name=='loopIn')
	{
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_looponoffA:led_looponoffB,(param1 ? 0x7F : 0x00));		
		// activate blinking for the LoopIn led (so the user knows to press it again)
		if(param1) loopingInBlinkLed[deck] = 0;else loopingInBlinkLed[deck] = -1;
	}
	if(name=='looping')
	{
		sendMidi(uniqueId,0xB0,0,(deck==0)?led_looponoffA:led_looponoffB,(param1 ? 0x7F : 0x00));		
	}	
	//if(name=='phonesSplit') sendMidi(uniqueId,0xB0,0,led_phones_split,(param1 ? 0x7F : 0x00));// no notification for split
	if(name=='talkOver') 	
	{
		if(led_talkover>-1) sendMidi(uniqueId,0xB0,0,led_talkover,(param1 ? 0x7F : 0x00));
	}
	if(name=='onAir') 
	{
		//setAnalogAToMicrophone(uniqueId, (param1 ? true : false));// enable/disable mic input
		if(led_onair>-1) sendMidi(uniqueId,0xB0,0,led_onair,(param1 ? 0x7F : 0x00));
	}
	if(name=='monitor') 
	{
		if(led_monitorA>-1) sendMidi(uniqueId,0xB0,0,(deck==0)?led_monitorA:led_monitorB,(param1 ? 0x7F : 0x00));
	}
	/*
	// we don't currently use these leds (so we leave them off)
	led_assignD, 3
	led_assignC, 4
	led_assignB, 5
	led_assignA, 6

	if(name=='fxFlanger') 	sendMidi(uniqueId,0xB0,0,(deck==0)?0x05:0x19,(param1 ? 0x7F : 0x00));
	if(name=='fxEcho') 		sendMidi(uniqueId,0xB0,0,(deck==0)?0x06:0x1A,(param1 ? 0x7F : 0x00));
	if(name=='fxDelay') 	sendMidi(uniqueId,0xB0,0,(deck==0)?0x07:0x1B,(param1 ? 0x7F : 0x00));
	if(name=='fxReverb') 	sendMidi(uniqueId,0xB0,0,(deck==0)?0x08:0x1C,(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+']');
	for(var i=0; i<2; i++)
	{
		// loop in led
		if(loopingInBlinkLed[i]!=-1)
		{
			// actually set the led
			sendMidi(uniqueId,0xB0,0,(i==0)?led_looponoffA:led_looponoffB,((loopingInBlinkLed[i]<2) ? 0x7F : 0x00));		
			loopingInBlinkLed[i]++;
			if(loopingInBlinkLed[i]>=4) loopingInBlinkLed[i] = 0;
		}
	}
}
//----------------------------------------------------------------------------- 
