/*=============================================================================
	CAAUProcessor.h
 
	$Log: CAAUProcessor.cpp,v $
	Revision 1.2  2004/03/16 20:24:29  bills
	finish this guy
	
	Revision 1.1  2004/03/13 01:34:15  bills
	initial checkin
	

	Created by William Stewart on Thurs Mar 12 2004.
	Copyright (c) 2004 Apple Computer. All rights reserved.

=============================================================================*/
#include "CAAUProcessor.h"						

static OSStatus SilenceInputCallback (void 		*inRefCon, 
					AudioUnitRenderActionFlags *ioActionFlags, 
					const AudioTimeStamp 		*inTimeStamp, 
					UInt32 						inBusNumber, 
					UInt32 						inNumberFrames, 
					AudioBufferList 			*ioData)
{
	AudioBuffer *buf = ioData->mBuffers;
	for (UInt32 i = ioData->mNumberBuffers; i--; ++buf)
		memset((Byte *)buf->mData, 0, buf->mDataByteSize);
		
		//provide a hint that our input data is silent.
	*ioActionFlags &= kAudioUnitRenderAction_OutputIsSilence;
	return noErr;
}

static AURenderCallbackStruct sSilentCallback = { SilenceInputCallback, NULL };


CAAUProcessor::CAAUProcessor (const CAComponent& inComp)
	: mPreflightABL(NULL)
{
	OSStatus result = CAAudioUnit::Open (inComp, mUnit);
	if (result)
		throw result;
	memset (&mUserCallback, 0, sizeof (AURenderCallbackStruct));
	mMaxTailTime = 10.;
}

CAAUProcessor::~CAAUProcessor ()
{
	if (mPreflightABL)
		delete mPreflightABL;
}

inline OSStatus		SetInputCallback (CAAudioUnit &inUnit, AURenderCallbackStruct &inInputCallback)
{
	return inUnit.SetProperty (kAudioUnitProperty_SetRenderCallback, 
											kAudioUnitScope_Input, 
											0,
											&inInputCallback, 
											sizeof(inInputCallback));
}

OSStatus		CAAUProcessor::EstablishInputCallback (AURenderCallbackStruct &inInputCallback)
{
	OSStatus result = SetInputCallback (mUnit, inInputCallback);
	if (!result)
		memcpy (&mUserCallback, &inInputCallback, sizeof(AURenderCallbackStruct));
	else
		memset (&mUserCallback, 0, sizeof (AURenderCallbackStruct));
	return result;
}

OSStatus		CAAUProcessor::SetAUPreset (CFPropertyListRef 			inPreset)
{
	return mUnit.SetProperty (kAudioUnitProperty_ClassInfo, 
									kAudioUnitScope_Global, 
									0, 
									&inPreset, 
									sizeof(inPreset));
}

UInt32			CAAUProcessor::MaxFramesPerRender () const
{
	UInt32 maxFrames;
	UInt32 propSize = sizeof (maxFrames);
	if (mUnit.GetProperty (kAudioUnitProperty_MaximumFramesPerSlice,
							kAudioUnitScope_Global, 0, &maxFrames, &propSize))
	{
		return 0;
	}
	return maxFrames;
}

OSStatus		CAAUProcessor::SetMaxFramesPerRender (UInt32 inMaxFrames)
{
	return mUnit.SetProperty (kAudioUnitProperty_MaximumFramesPerSlice,
							kAudioUnitScope_Global, 0, &inMaxFrames, sizeof(inMaxFrames));
}

OSStatus		CAAUProcessor::Initialize (const CAStreamBasicDescription 	&inInputDesc,
											const CAStreamBasicDescription 	&inOutputDesc,
												UInt64 						inNumInputSamples)
{
	return DoInitialisation (inInputDesc, inOutputDesc, inNumInputSamples, MaxFramesPerRender());
}

OSStatus		CAAUProcessor::Reinitialize (UInt32 inNewMaxFrames)
{
	OSStatus result;
	CAStreamBasicDescription inputDesc, outputDesc;
	
	require_noerr (result = mUnit.GetFormat (kAudioUnitScope_Input, 0, inputDesc), home);
	require_noerr (result = mUnit.GetFormat (kAudioUnitScope_Output, 0, outputDesc), home);
	
	require_noerr (result = DoInitialisation (inputDesc, outputDesc, mNumInputSamples, inNewMaxFrames), home);
	
home:
	return result;
}


OSStatus		CAAUProcessor::DoInitialisation (const CAStreamBasicDescription 	&inInputFormat,
												const CAStreamBasicDescription 		&inOutputFormat,
												UInt64								inNumInputSamples,
												UInt32 								inMaxFrames)
{
	OSStatus result;
	
	if (inNumInputSamples == 0 && IsOfflineAU())
		return kAudioUnitErr_InvalidOfflineRender;
		
	mNumInputSamples = inNumInputSamples;
	
		// first check that we can do this number of channels
	if (mUnit.CanDo (inInputFormat.NumberChannels(), inOutputFormat.NumberChannels()) == false)
		require_noerr (result = kAudioUnitErr_FailedInitialization, home);
	
	// just uninitialise the AU as a matter of course
	require_noerr (result = mUnit.Uninitialize(), home);

	require_noerr (result = mUnit.SetFormat (kAudioUnitScope_Input, 0, inInputFormat), home); 
	require_noerr (result = mUnit.SetFormat (kAudioUnitScope_Output, 0, inOutputFormat), home); 
	require_noerr (result = SetMaxFramesPerRender (inMaxFrames), home);
	
		// if we're any AU but an offline AU, we should tell it that we've processing offline
	if (!IsOfflineAU()) {
		UInt32 isOffline = (IsOfflineContext() ? 1 : 0);
			// don't care whether this succeeds of fails as many AU's don't care about this
			// but the ones that do its important that they are told their render context
		mUnit.SetProperty (kAudioUnitProperty_OfflineRender, kAudioUnitScope_Global, 0, &isOffline, sizeof(isOffline));
	} else {
			// tell the offline unit how many input samples we wish to process...
		mUnit.SetProperty (kAudioOfflineUnitProperty_InputSize,
												kAudioUnitScope_Global, 0,
												&mNumInputSamples, sizeof(mNumInputSamples));
	}
	
	require_noerr (result = mUnit.Initialize(), home);

	require_noerr (result = SetInputCallback (mUnit, mUserCallback), home);
	
	CalculateRemainderSamples (inOutputFormat.mSampleRate);

	// finally reset our time stamp
	// the time stamp we use with the AU Render - only sample count is valid
	memset (&mRenderTimeStamp, 0, sizeof(mRenderTimeStamp));
	mRenderTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;

	// now, if we're NOT an offline AU, preflighting is not required
	// if we are an offline AU, we should preflight.. an offline AU will tell us when its preflighting is done
	mPreflightDone = false;

	if (mPreflightABL) {
		delete mPreflightABL;
		mPreflightABL = NULL;
	}
	
	mPreflightABL = new AUOutputBL (inOutputFormat);

	mLastPercentReported = 0;
	
home:
	return result;
}

void		CAAUProcessor::CalculateRemainderSamples (Float64 inSampleRate)
{
	mLatencySamples = 0;
	mTailSamplesToProcess = 0;
	mTailSamples = 0;
	mTailSamplesRemaining = 0;
	
		// nothing to do because we're not processing offline
	if (IsOfflineContext() == false) return;
		
		// because an offline unit has some indeterminancy about what it does with the input samples
		// it is *required* to deal internally with both latency and tail
	if (!IsOfflineAU()) 
	{
			// when offline we need to deal with both latency and tail
			
		// if the AU has latency - how many samples at the start will be zero?
		// we'll end up chucking these away.
		Float64 renderTimeProps;
		UInt32 propSize = sizeof (renderTimeProps);
		OSStatus result = mUnit.GetProperty (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0,
													&renderTimeProps, &propSize);
		
		Float64 latencySamples = 0;
		if (result == noErr) // we have latency to deal with - its reported in seconds
			latencySamples = renderTimeProps * inSampleRate;
			
			// AU tail
			// if the AU has a tail - we'll pull that many zeroes through at the end to flush
			// out this tail - think of a decaying digital delay or reverb...
		result = mUnit.GetProperty (kAudioUnitProperty_TailTime, kAudioUnitScope_Global, 0,
													&renderTimeProps, &propSize);
		if (renderTimeProps > mMaxTailTime)
			renderTimeProps = mMaxTailTime;
		Float64 tailSamples = 0;
		if (result == noErr)
			tailSamples = renderTimeProps * inSampleRate;
		
		// this dictates how many samples at the end we need to pull through...
		// we add latency to tail because we throw the latency samples away from the start of the rendering
		// and we have to pull that many samples after the end of course to get the last of the original data
		// then to that is added the tail of the effect...
		mTailSamplesToProcess = UInt32(tailSamples + latencySamples);
		mTailSamples = UInt32(tailSamples);
		mLatencySamples = UInt32(latencySamples);
	}
}

CFStringRef		CAAUProcessor::GetOLPreflightName () const
{
	if (OfflineAUNeedsPreflight()) 
	{
		CFStringRef str;
		UInt32 size = sizeof(str);
		OSStatus result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightName,
												kAudioUnitScope_Global, 0,
												&str, &size);
		return result ? NULL : str;
	}
	return NULL; // says NO to preflighting
}

bool		CAAUProcessor::OfflineAUNeedsPreflight () const
{
	if (IsOfflineAU()) {
		UInt32 preflightRequirements;
		UInt32 size = sizeof(preflightRequirements);
		OSStatus result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements,
												kAudioUnitScope_Global, 0,
												&preflightRequirements, &size);
		if (result)
			return false;
		return preflightRequirements;
	}
	return false;
}

OSStatus	CAAUProcessor::Preflight (bool inProcessPreceedingTail)
{
		//we're preflighting again, so reset ourselves
	if (mPreflightDone) {
		mPreflightDone = false;
		// the time stamp we use with the AU Render - only sample count is valid
		memset (&mRenderTimeStamp, 0, sizeof(mRenderTimeStamp));
		mRenderTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
	}

	UInt32 numFrames = MaxFramesPerRender();
	if (numFrames == 0)
		return kAudioUnitErr_InvalidProperty;
	
	OSStatus result = noErr;
	if (!IsOfflineAU()) 
	{
		if ((IsOfflineContext() == false && inProcessPreceedingTail) || IsOfflineContext())
		{
			// re-establish the user's input callback
			require_noerr (result = SetInputCallback (mUnit, mUserCallback), home);

			// Consume the number of input samples indicated by the AU's latency or tail
			// based on whether the AU is being used in an offline context or not.
			UInt32 latSamps = IsOfflineContext() ? mLatencySamples : mTailSamples;	
			
			while (latSamps > 0)
			{
				if (latSamps < numFrames)
					numFrames = latSamps;
					
					// process the samples (the unit's input callback will read the samples
					// from the file and convert them to float for processing
				AudioUnitRenderActionFlags renderFlags = 0;
				mPreflightABL->Prepare();
				require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, numFrames, mPreflightABL->ABL()), home);
		
				mRenderTimeStamp.mSampleTime += numFrames;
				latSamps -= numFrames;
			}
			if (IsOfflineContext())
				mRenderTimeStamp.mSampleTime = mLatencySamples;
		}
		else
		{
			// processing real-time but not processing preceeding tail, so we should preroll the AU
			require_noerr (result = mUnit.Preroll(numFrames), home);
			
			// re-establish the user's input callback
			require_noerr (result = SetInputCallback (mUnit, mUserCallback), home);
			
			mRenderTimeStamp.mSampleTime = 0;
		}
	}
	else
	{
			// re-establish the user's input callback
		require_noerr (result = SetInputCallback (mUnit, mUserCallback), home);
		
		UInt32 preflightRequirements;
		UInt32 size; size = sizeof(preflightRequirements);
		require_noerr (result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements,
												kAudioUnitScope_Global, 0,
												&preflightRequirements, &size), home);
												
			// 0 indicates none, otherwise optional or required -> we do it for either
		if (preflightRequirements) 
		{
			for (;;) {
				// here we need to do the preflight loop - we don't expect any data back, but have to 
				// give the offline unit all of its input data to allow it to prepare its processing
				AudioUnitRenderActionFlags renderFlags = kAudioOfflineUnitRenderAction_Preflight;
				mPreflightABL->Prepare();
				require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, numFrames, mPreflightABL->ABL()), home);
				mRenderTimeStamp.mSampleTime += numFrames;
		
				if (renderFlags & kAudioOfflineUnitRenderAction_Complete)
					break;
			}
		}
		// the time stamp we use with the AU Render - only sample count is valid
		mRenderTimeStamp.mSampleTime = 0;
	}

	if (result == noErr) {
		mPreflightDone = true;
	}
	
home:
	return result;
}

OSStatus 	CAAUProcessor::OfflineAUPreflight (UInt32 inNumFrames, bool &outIsDone)
{
	if (!IsOfflineAU())
		return paramErr;
	if (mNumInputSamples == 0)
		return paramErr;

	UInt32 preflightRequirements;
	UInt32 size = sizeof(preflightRequirements);
	OSStatus result;
	require_noerr (result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements,
												kAudioUnitScope_Global, 0,
												&preflightRequirements, &size), home);
												
		// 0 indicates none, otherwise optional or required -> we do it for either
	if (preflightRequirements) 
	{
		AudioUnitRenderActionFlags renderFlags = kAudioOfflineUnitRenderAction_Preflight;
		mPreflightABL->Prepare();
		require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, inNumFrames, mPreflightABL->ABL()), home);
		mRenderTimeStamp.mSampleTime += inNumFrames;
		
		if (renderFlags & kAudioOfflineUnitRenderAction_Complete) {
			outIsDone = true;
			mRenderTimeStamp.mSampleTime = 0;
			mPreflightDone = true;
			mLastPercentReported = 0;
		}
	}
	else
	{
		outIsDone = true;
		mRenderTimeStamp.mSampleTime = 0;
		mPreflightDone = true;
		mLastPercentReported = 0;
	}
	
home:
	return result;
}

void SetBufferListToNumFrames (AudioBufferList &list, UInt32 inNumFrames)
{
	for (unsigned int i = 0; i < list.mNumberBuffers; ++i) {
		AudioBuffer &buf = list.mBuffers[i];
		if (buf.mDataByteSize > 0)
			buf.mDataByteSize = inNumFrames * sizeof (Float32);
	}
}

OSStatus	CAAUProcessor::Render (AudioBufferList 		*ioData, 
									UInt32 				&ioNumFrames, 
									bool				&outIsSilence,
									bool 				*outOLCompleted, 
									bool 				*outOLRequiresPostProcess)
{
	if (IsOfflineContext())
	{
		if (!mPreflightDone)
			return kAudioUnitErr_InvalidOfflineRender;
	
			// YES - this is correct!!! you have to provide both if rendering in an offline Context
		*outOLCompleted = false;
		*outOLRequiresPostProcess = false;

		if (!IsOfflineAU() && !mUnit.Comp().Desc().IsFConv()) 
		{
				// have we processed the input we expect too?
				// in an offline case, we want to create output that matches the input
				// for an OfflineAU type, it manages this internally, so we don't have to do anything
				// for a FormatConverter AU, we don't know and can't tell, so we can't do anything here
				// for any other AU type (effect, instrument) the Prime assumption is that it will 
				// ask for the same number of frames of input as it is asked to output
				// so we can ask what it is doing, and get a sample accurate output (which is input + tail time)
			if (mRenderTimeStamp.mSampleTime + ioNumFrames >= InputSampleCount()) 
			{
					// if we fall into here, we have just a partial number of input samples left 
					// (less input less than what we've been asked to produce output for.
				*outOLCompleted = true;
					// we require post processing if we've got some tail (or latency) samples to flush through
				*outOLRequiresPostProcess = mTailSamplesToProcess > 0;
				if (InputSampleCount() > mRenderTimeStamp.mSampleTime) {
					ioNumFrames = (UInt32)(InputSampleCount() - mRenderTimeStamp.mSampleTime);
				} else {
					ioNumFrames = 0;
				}
				mTailSamplesRemaining = mTailSamplesToProcess;
					// we've got no input samples to process this time.
				SetBufferListToNumFrames (*ioData, ioNumFrames);
				if (ioNumFrames == 0) {
					if (*outOLRequiresPostProcess)
						SetInputCallback (mUnit, sSilentCallback);
					else
						mUnit.GlobalReset (); //flush this out, as we're done with this phase
					return noErr;
				}
			}
		}
		AudioUnitRenderActionFlags renderFlags = IsOfflineAU() ? kAudioOfflineUnitRenderAction_Render : 0;
		OSStatus result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData);
		if (result) {
			if (mUnit.Comp().Desc().IsFConv()) { 
				// this is the only way we can tell we're done with a FormatConverter AU 
				// - ie. client returns an error from input
				result = noErr;
				*outOLCompleted = true;
				*outOLRequiresPostProcess = mTailSamplesToProcess > 0;
				ioNumFrames = 0;
				SetBufferListToNumFrames (*ioData, ioNumFrames);
			} else
				return result;
		}
		mRenderTimeStamp.mSampleTime += ioNumFrames;
		outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence);
		
			// if we're an Offline AU type, it will set this flag on completion of its processing
		if (renderFlags & kAudioOfflineUnitRenderAction_Complete) {
			// we now need to calculate how many frames we rendered.
				// as we're dealing with PCM non-interleaved buffers, we can calculate the numFrames simply
			ioNumFrames = ioData->mBuffers[0].mDataByteSize / sizeof(Float32);
			*outOLCompleted = true;
			*outOLRequiresPostProcess = false;
			mUnit.GlobalReset (); //flush this out, as we're done with this phase
		} else {
			if (*outOLCompleted) {
				if (*outOLRequiresPostProcess)
					result = SetInputCallback (mUnit, sSilentCallback);
				else
					mUnit.GlobalReset (); //flush this out, as we're done with this phase
			}
		}
	
		return result;
	}

// rendering in a RT context:
	AudioUnitRenderActionFlags renderFlags = 0;
	OSStatus result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData);
	if (!result) {
		mRenderTimeStamp.mSampleTime += ioNumFrames;
		outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence);
	}
	return result;	
}
	
OSStatus	CAAUProcessor::PostProcess (AudioBufferList 	*ioData, 
										UInt32 				&ioNumFrames, 
										bool				&outIsSilence,
										bool 				&outDone)
{
	if (IsOfflineAU() || !IsOfflineContext()) 
		return kAudioUnitErr_CannotDoInCurrentContext;
	
	outDone = false;
	
		// we've got less samples to process than we've been asked to process
	if (mTailSamplesRemaining <= SInt32(ioNumFrames)) {
		outDone = true;
		ioNumFrames = mTailSamplesRemaining > 0 ? mTailSamplesRemaining : 0;
		SetBufferListToNumFrames (*ioData, ioNumFrames);
		if (ioNumFrames == 0)
			return noErr;
	}
	
	AudioUnitRenderActionFlags renderFlags = 0;
	OSStatus result;
	require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData), home);
	mRenderTimeStamp.mSampleTime += ioNumFrames;
	mTailSamplesRemaining -= ioNumFrames;
	outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence);
			
	if (outDone) {
		require_noerr (result = SetInputCallback (mUnit, mUserCallback), home);
		mUnit.GlobalReset (); //flush this out, as we're done with this phase
	}
home:
	return result;
}		

Float32		CAAUProcessor::GetOLPercentComplete ()
{
	if (!IsOfflineContext())
		return 0;

	Float32 percentDone = mLastPercentReported;
		
	if (IsOfflineAU()) 
	{
		// we get the output size every time, as this can change as parameters are changed
		UInt64 numOutputSamples = mNumInputSamples;
		UInt32 propSize = sizeof(numOutputSamples);
		mUnit.GetProperty (kAudioOfflineUnitProperty_OutputSize,
							kAudioUnitScope_Global, 0, &numOutputSamples, &propSize);
		
		percentDone = (mRenderTimeStamp.mSampleTime / Float64(numOutputSamples)) * 100.;
	}
	else
	{
		percentDone = (mRenderTimeStamp.mSampleTime / Float64(mNumInputSamples + mTailSamples)) * 100.;
	}
	
	if (percentDone > mLastPercentReported)
		mLastPercentReported = percentDone;

	return mLastPercentReported;
}

