/*
auto talk version 1.18
7-6-2004 
Written by: Phil Barrenger
phil@whiz.com.au


Description:
Auto Talk uses markers that you have created on an audio track to set keys on a
pre existing custom parameter set.  This was specifically made to aid in the 
animating of lip sync, allowing you to create markers for all ths mouth shapes
and then have the script make rough animation.

There are a feew specifics that you need to understand for the script to work
1) the markers 'Value' (not it's 'name') must be the same as the name of a 
	parameter in your custom parameter set.
	So if I have a marker with the 'value' of 'fff' then for any animation to be 
	created I also need a parameter in my custom parameter set to be called fff.
	
	
2) You must have an audio clip selected in the mixer before you run the script
	
3) Your markers can be on any level.







Usage:

1)Select your audio clip in the mixer

1)Run the script - from a script editor or drag it to a toolbar and create a button

3)When prompted select the custom pset that holds the parameters to be animated.

4)A window appears!

5)Set the values as you like, press ok and stand back while it makes the keys


-------- Input Description ----------------------------------

Input 1 - Replace the current fcurve? -
	if checked any animation that already exists on the parameters in your custom pset will be erased.
	if unchecked then the existing keys will remain.
	
Input 1.5 - time range -
	allows you to set a range (in frames) that markers must exist within for keys to be set.
	
	
Input 2 - Key Values -
	Allows you to set the maximum and minimum key values.
	
	
Input 3 - Tangents -
	if "Auto compute tangents relative to marker length?" is checked then the script works out the best tangents based on the
	duration of each marker.
	if unchecked then the script uses "Keys tangent width" as a constant tangent width
	
	
Input 4 - Curve offset (time) -
	offsets the whole fcurve of each parameter in your custom pset by the given value
	
	
Input 5 - "offset individual keys from their markers" -
	"offset the first key from the start of the marker"
		- offsets the first key from the start of the marker by minus the input value - So if your marker starts at 
		frame 3 and you input a value of 2 the keyframe will be set at frame 1 ie 3 - 2 = 1
	
	"offset the last key from the end of the marker"
		- offsets the last key from the end of the marker by the input value - So end marker = 3  input val = 2 therefore the 
		keyframe will be set at 5. 3+2=5

Input 6 - "Randomise key values by percentage + and - current value"
	- applies a random value to each key.  The key is then modified by + and - the percentage that you input.
	So if my maximum key value is set at 10 and i apply a random factor to the maximum keys of %10 then
	I'll get values ranging from 9 to 11 (ie +-%10)


------------------------------------------------------------------------------------------------------




features to add:
maybe marker types - so that keys only get set for specific types


REVISIONS:
1.15 Added specific audio clip support - choose your sudio clip rather then your model
1.16 Added key offset for the middle/max key
1.17 fixed a bug whereby a fcurve was being created on all parameters of the pset
	regardless of whether there was a corresponding marker
1.18 added the ability to set a range for the keys to be set within

*/
//------------------------------------------------------------------------------------------------------


//GLOBALS

var oroot = Application.ActiveProject.ActiveScene.Root ;
var psettest = 0 ;
var oselclip = Selection(0);
var opset ;

var ostarttime = Dictionary.GetObject ("PlayControl.In") ;
var oendtime = Dictionary.GetObject ("PlayControl.Out") ;
var starttime = ostarttime.value ;
var endtime = oendtime.value



//get the current selection and check to make sure its an audio clip

	
if (classname(oselclip) !== "Clip")
	{
	logmessage("you must select an Audio Clip before you run the script" , siError)
	}

		
//PICK A CUSTOM PARAMETER SET
	
	
	var opicker, opicked , but, opset ;

		opicker = pickObject ("select a custom pset", "select a custom pset", opicked, but)
		opset = opicker(2)
		logmessage (classname(opset))
	
			if (classname(opset) == "CustomProperty")
				{
				logmessage("picked object is a custom Pset")
				psettest = 1 ;
				}
			else
				{
				logmessage("you need to select a custom parameter set", siError)
				}

	
	

//______________________________________________________________________

//run a test to see if a custom pset was selected

if (psettest == 1)
{
		//insert a temporary custom pset to throw values to the functions

		var otempPSet = ActiveSceneRoot.AddProperty("CustomProperty",false,"auto talk") ;  
 
 
 		otempPSet.AddParameter3( "st" , siFloat , starttime , starttime , endtime, false) ;
 		otempPSet.AddParameter3( "et" , siFloat , endtime , starttime , endtime, false) ;

		otempPSet.AddParameter3( "keymin", siFloat,0,-100,100,false) ; 
		otempPSet.AddParameter3( "keymax", siFloat,1,-100,100,false) ; 
		otempPSet.AddParameter3( "ppgoffset", siInt4,0,-20,20,false) ; 
		otempPSet.AddParameter3( "randomise", siFloat,0,0,100,false) ;
		otempPSet.AddParameter3( "randomisemin", siFloat,0,0,100,false) ;
		otempPSet.AddParameter3( "tanwidth", siFloat,1.5,0,20,false) ;
		otempPSet.AddParameter3( "reltan" , siBool, true, null , null , false) ;
		otempPSet.AddParameter3( "overlapstart", siFloat,2,-20,20,false) ;
		otempPSet.AddParameter3( "offsetmiddle" , siFloat, 0 , -1 , 1 , false) ;
		otempPSet.AddParameter3( "overlapend", siFloat,2,-20,20,false) ;
		otempPSet.AddParameter3( "replacecurve" , siBool, false, null , null , false) ;
		



		var oPPGLayout = otempPSet.PPGLayout ;

		oreplacecurve = oPPGLayout.Additem ( "replacecurve" , "Replace the current fcurve? (deletes current animation)" , siControlCheck )
		
		oPPGLayout.Addgroup( "time range" )
			ost = oPPGLayout.Additem( "st" , "Start at frame:" , siControlNumber ) ;
			oet = oPPGLayout.Additem( "et" , "End at frame:" , siControlNumber ) ;
			
		oPPGLayout.endgroup() ;	

		oPPGLayout.Addgroup( "key values" )
		omin = oPPGLayout.Additem( "keymin" , "minimum key value" , siControlNumber ) ;
		omax = oPPGLayout.Additem( "keymax" , "maximum key value" , siControlNumber ) ;

			oPPGLayout.Addgroup( "Tangents" )
			oreltan = oPPGLayout.Additem ( "reltan" , "Auto compute tangents relative to marker length? (reccomended)" , siControlCheck )
			otangent = oPPGLayout.Additem ( "tanwidth" , "Keys tangent width (only if Auto comp is off)" , siControlNumber ) ;
			oPPGLayout.endGroup()
	
		oPPGLayout.endgroup() ;

		oPPGLayout.Addgroup( "Curve offset (time)" ) ;
		omax = oPPGLayout.Additem( "ppgoffset" , "offset keys in time" , siControlNumber ) ;
		oPPGLayout.endgroup() ;

		oPPGLayout.AddGroup("offset individual keys from their markers") ;

			ooverlapstart = oPPGLayout.Additem( "overlapstart" , "offset the first key from the start of the marker" , siControlNumber ) ;
			ooverlapend = oPPGLayout.Additem( "overlapend" , "offset the last key from the end of the marker" , siControlNumber ) ;
			oPPGLayout.AddGroup("offset MAX key relative to start and end keys")
				ooffsetmiddle = oPPGLayout.Additem ("offsetmiddle" , "offset MAX/middle key" , siControlNumber)
				oPPGLayout.endGroup()
			
		oPPGLayout.endGroup() ;

		oPPGLayout.AddGroup("Randomise key values by percentage + and - current value") ;
			oPPGLayout.AddGroup("") ;
			orand = oPPGLayout.Additem( "randomise" , "randomise Maximum keys" , siControlNumber ) ;
			orandmin = oPPGLayout.Additem( "randomisemin" , "randomise Minimum keys" , siControlNumber ) ;

			oPPGLayout.endGroup() ;
			oPPGLayout.endGroup() ;

		
		//______________________________________________________________________

		try 
			{ 
			InspectObj( otempPSet, "", "auto talk", siModal ); 		
	
			customPsetSliders(oselclip, opset)		
			}	
	
	
		catch( e )
			{ 
			Logmessage( "Cancel/Exit auto talk" ); 
			} 
} //end if statement from start of ppg layout

//______________________________________________________________________

// FUNCTION to pick a custom pset and get it's params

function customPsetSliders()
	{		
	//set up the variables from the temp pset
	var omin2 ,	omax2 , ooffset2 , otangetnval , orandvalmin , orandval, overlapS , overlapE, oreplace , offmid;	
	omin2 =    otempPSet.keymin.value ;
	omax2 =    otempPSet.keymax.value ;
	ooffset2 = otempPSet.ppgoffset.value ;
	orandval = (otempPSet.randomise.value * 2) ;
	orandvalmin = (otempPSet.randomisemin.value * 2) ;
	otangentval = otempPSet.tanwidth.value ;
	orelativetan = otempPSet.reltan.value ;
	overlapS = otempPSet.overlapstart.value
	overlapE = otempPSet.overlapend.value
	oreplace = otempPSet.replacecurve.value ;
	offmid = otempPSet.offsetmiddle.value * 0.7 ;
	ostartframetime = otempPSet.st.value ;
	oendframetime = otempPSet.et.value ;


		//logmessage("picked object is class name " + classname(opset))

			//check for the correct selection item
			if (classname(opset) == "CustomProperty")
				{
				//logmessage("you selected a custom property")
				
	
				var opsetparams = enumElements(opset)				

				for (var opsetsliders = new Enumerator(opsetparams) ; !opsetsliders.atEnd() ; opsetsliders.moveNext() )
					{
						var opsetSlidername = opsetsliders.item().name ;
						var opsetSlider = opsetsliders.item() ;

							//test to see if a fcurve already exists
							//logmessage(opsetSlider.isAnimated())
							
				
							var g2 = Dictionary.GetObject(oselclip + ".Markers") ;

							var og2 = enumElements(g2) ;

							for (om2 = new Enumerator(og2) ; !om2.atEnd(); om2.moveNext() )
								{
								var omarker2 = om2.item() ;

				
								// test to see if the markers name matches a sliders name
			
									//Get the Marker Name
			
									var omarkname2 = Dictionary.GetObject( omarker2 + ".value") ;
			
									var omarknameval2 = omarkname2.value ;
			
				
									//compare the marker name to the parameter slider name
							
									if (omarknameval2 == opsetSlidername)
										{					
					
										if (oreplace == false)
											{
												try
													{
													var ocurveExisting = GetSource(opsetSlider , siFCurveSource)
													var ocurve = ocurveExisting(0)	
													}
												catch (e)
													{
													var ocurve = opsetSlider.AddFCurve() ;
													}							
											}								
							
										else
											{
											var ocurve = opsetSlider.AddFCurve() ;
											}
										}
								}//end for loop through markers
									
																	
								
									
									
									
		
							// CALL THE FUNCTION and feed it with the slider name and a path to it.
							
						markerloop(opsetSlider , opsetSlidername, ocurve, omin2, omax2, ooffset2, orandval, orandvalmin, otangentval, orelativetan , overlapS ,overlapE, oselclip, offmid, ostartframetime , oendframetime ) ;
					
					}// end of for loop through the sliders
					// output of this loop are the sliders in the custom pset
				}// end of if classname = pset
	
			else
				{
				//logmessage("you must select a custom property set", siError)
				}
				
	} //end of customPsetSliders() function
	


//_________________________________________________________________________________________

	
	
//____ FUNCTION TO loop through the mixer and apply the keys to the sliders
// ____ function is called from withing customPsetSliders()


function markerloop (opslider, opslidername, ocurve2, omin, omax, poffset, prand , prandmin, otan, userelative, overlapStart, overlapEnd, oselclip2, offmid2, osft, oeft )
	

	{
	

		
	//find an audio track and its markers and their various properties.
		//________________________________________________
					

		var g = Dictionary.GetObject(oselclip2 + ".Markers") ;

		var og = enumElements(g) ;

		for (om = new Enumerator(og) ; !om.atEnd(); om.moveNext() )
			{
			var omarker = om.item() ;
					
			// test to see if the markers name matches a sliders name
				
				//Get the Marker Name
				
				var omarkname = Dictionary.GetObject( omarker + ".value") ;
				var omarknameval = omarkname.value ;
				//logmessage( "the marker is called - " + omarkname.value) ;
				
				
				
			//compare the marker name to the parameter slider name
			//logmessage (opslidername + " is slider - and is marker " + omarknameval)
			
			if (omarknameval == opslidername)
				{
				//logmessage( "the marker - " + omarkname + " - has the same name as the slider - " + opslidername )
				
				
			
				
				// set a condition to only set keys if the marker start time is within the range
				// and the marker end time is within the range
				
				var ostartm = Dictionary.GetObject( omarker + ".startframe") ;
				var startframemarker = ostartm.value ;
				
				var odurm = Dictionary.GetObject( omarker + ".durationframe")
				var oendm = startframemarker + odurm.value ;
//logmessage("debug",sierror)
				var endframemarker =  oendm ;
				
				
				if (startframemarker >= osft && endframemarker <= oeft)
					{
				
				
				
						//get the marker start frame and add the offset here;
						var ostart = Dictionary.GetObject( omarker + ".startframe") ;
						var ostartval = (ostart.value + poffset);
						//logmessage ("the marker start value is " + ostartval)

	
						//Get the marker duration
						var odur = Dictionary.GetObject( omarker + ".durationframe") ;
						var odurval = odur.value ;
						//logmessage ("the marker duration is " + odurval) ;

						//Get the audio clips start offset so that the markers start 
						//frames can be offset to give real frame times
						var omixerstart = Dictionary.Getobject(oselclip2 + ".timectrl.startoffset") ;
						var omixerstartval = omixerstart.value - 1
						//logmessage("the mixer audio clip starts at frame " + omixerstartval)

	
						//set up the frame start, middle and end times
						//set up a catch for one frame long markers to ad a frame to either side
				
								
						if (odurval < 2)
							{	
							//logmessage("duration is less than 2 so im going to add frames")
							var oframestart = ostartval + omixerstartval - 1 - overlapStart;
							var oframeend =   ostartval + omixerstartval + odurval + 1 + overlapEnd;
							var oframemid = ((oframestart + oframeend ) / 2) + ( ( ( oframeend - oframestart ) / 2 ) * offmid2 )  ;
							//logmessage ("start = " + oframestart + " --- middle = " + oframemid + " ---- end = " + oframeend )
							}
					
						else
							{
							var oframestart = ostartval + omixerstartval - overlapStart ;
							var oframeend =   ostartval + omixerstartval + odurval + overlapEnd;
							var oframemid = ((oframestart + oframeend ) / 2)  + ( ( (oframeend - oframestart) / 2 ) * offmid2 ) ;
					
							//logmessage ("start = " + oframestart + " --- middle = " + oframemid + " ---- end = " + oframeend )
							}
				
						// setkeys on the corresponding slider
				
				
						//logmessage (classname(ocurve))
				
						//randomise the MAX key vals
						orandomval = (Math.random() - 0.5)
						var omaxrand = ((omax * (orandomval * (prand/100))) + omax)
				
				
						//randomise the MIN key vals
						orandomvalmin = (Math.random() - 0.5)
						var ominrand = ((omin * (orandomvalmin * (prandmin/100))) + omin)
				

						//setkey GetKey
				
						SaveKey(opslider , oframestart, ominrand)
						SaveKey(opslider , oframemid, omaxrand)
						SaveKey(opslider , oframeend, ominrand)
				
						// get the three keys just set and assign them to a variable ready for tangent manipulation
				
						k1 = ocurve2.GetKey(oframestart , 0.01)
						k2 = ocurve2.GetKey(oframemid , 0.01)
						k3 = ocurve2.GetKey(oframeend , 0.01)
				
				
							//logmessage ("hi ")
						
						// set the tangents of the zero keys to flat
				
						if (userelative == true)
							{
							var otan2 = odurval/4
							//logmessage ("tangent relative to odur is - " + otan2)
							}
						else
							{
							var otan2 = otan
							}
			
				
						k1.lefttanx = - otan2
						k1.lefttany = 0
						k1.righttanx = otan2
						k1.righttany = 0
				
						k2.lefttanx = - otan2
						k2.lefttany = 0
						k2.righttanx = otan2
						k2.righttany = 0				
				
						k3.lefttanx = - otan2
						k3.lefttany = 0
						k3.righttanx = otan2
						k3.righttany = 0				
				
				} // end of frame range test
							
			} // end of if - marker name test

		
		} // end of loop through the markers	
					
	} // end of function marker loop

if (psettest == 1)
	{
	DeleteObj( otempPSet ) ; 
	}

//________________________________________________