//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	AudioFilePlayer.cpp
//
//	$Log: AudioFilePlayer.cpp,v $
//	Revision 1.15  2003/06/04 20:40:22  baron
//	Remove old comment about CBR only support.
//	
//	Revision 1.14  2003/04/18 23:05:14  baron
//	Use AudioFileReadPackets() in the special case of short files that are read entirely into memory.
//	
//	Revision 1.13  2003/04/18 17:37:43  baron
//	Make play file code work for aac.
//	
//	Revision 1.12  2003/04/09 22:33:11  baron
//	Pass audio data around by packets instead of bytes.
//	
//	Revision 1.11  2003/03/19 21:33:47  baron
//	Add magic cookie support. (QDesign and Purevoice work now)
//	
//	Revision 1.10  2003/03/18 20:22:32  bills
//	fix warning
//	
//	Revision 1.9  2003/03/18 20:21:45  bills
//	add a diagnostic to print the num packets
//	
//	Revision 1.8  2003/03/13 22:11:33  baron
//	Remove V1 code. Replace usage of AudioFile Read/WriteByte APIs with the Read/WritePacket versions.
//	
//	Revision 1.7  2002/07/14 21:03:49  bills
//	add notificaitons in the case of rendering errors
//	
//	Revision 1.6  2002/06/18 06:09:33  bills
//	change to render proc for V2 units
//	
//	Revision 1.5  2002/06/02 04:38:23  bills
//	tweaks to now fully support v1 or v2 audio unit
//	
//	Revision 1.4  2002/06/01 23:58:19  bills
//	first pass at v2 units
//	
//	Revision 1.3  2002/05/19 23:23:23  bills
//	cleanup header
//	
//	Revision 1.2  2002/05/18 03:46:21  bills
//	add some additional API
//	
//	Revision 1.1  2002/05/18 01:19:47  bills
//	new location
//	
//	Revision 1.1  2002/05/17 07:56:45  bills
//	new dir loc
//	
//	Revision 1.5  2002/05/17 07:48:05  bills
//	Add FilePlay "C" API
//	
//	Revision 1.4  2002/05/16 09:22:09  bills
//	add notifications for file underruns
//	
//	Revision 1.3  2002/05/16 09:07:13  bills
//	use a single read thread for files
//	
//	Revision 1.2  2002/05/15 06:21:59  bills
//	now use AudioConverter to do file reading
//	
//	Revision 1.1  2002/05/14 08:12:23  bills
//	initial checkin
//	
// 	
//	General class for playing back a file
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "AudioFilePlayer.h"

#if DEBUG
    #define LOG_DATA_FLOW 0
#endif

OSStatus 	AudioFileManager::FileRenderProc (void 						*inRefCon, 
										AudioUnitRenderActionFlags		*inActionFlags,
										const AudioTimeStamp 			*inTimeStamp, 
										UInt32 							inBusNumber,
										UInt32							inNumFrames, 
										AudioBufferList 				*ioData)
{
	AudioFileManager* THIS = (AudioFileManager*)inRefCon;
	return THIS->Render (*ioData, inNumFrames);
}

OSStatus	AudioFileManager::Render (AudioBufferList &ioData, UInt32 inNumFrames)
{
	UInt32 numDataPacketsNeeded = inNumFrames;
#if LOG_DATA_FLOW
    fprintf(stdout, "***** requested packets from Render Proc = %ld\n", numDataPacketsNeeded);
#endif
	OSStatus result = AudioConverterFillComplexBuffer(	mParentConverter, 
                                                        ACComplexInputProc, 
                                                        this,
                                                        &numDataPacketsNeeded, 
                                                        &ioData, 
                                                        0);
#if LOG_DATA_FLOW
    fprintf(stdout, "***** Render Proc returned packets = %ld\n", numDataPacketsNeeded);
#endif
			
		if (result)	fprintf (stderr, "AudioConverterFillComplexBuffer:%ld,%-4.4s\n", result, (char*)&result);

#if 0
		//debug test assumes framePerPacket == 1
	if (numDataPacketsNeeded != inNumFrames) 
		printf ("after conv:%ld,%ld\n", numDataPacketsNeeded, inNumFrames);
#endif
	
	if (!result)
		AfterRender ();
	else
		GetParent().DoNotification (result);
	
	return result;
}

OSStatus 	AudioFileManager::ACComplexInputProc (AudioConverterRef		inAudioConverter,
								UInt32*								ioNumberDataPackets,
								AudioBufferList*					ioData,
								AudioStreamPacketDescription** 		outDataPacketDescription,
								void*								inUserData)
{
	AudioFileManager* 				THIS = (AudioFileManager*)inUserData;
	AudioBuffer* 					firstBuffer = ioData->mBuffers;
    UInt32							packetCount = 0;
    AudioStreamPacketDescription	*packetDescs = NULL;
    
#if 0
	// we can leave the channels alone.. this would have been set for us
	// (that's why ioData is both in and out -> in has channels set
	UInt32 size = firstBuffer->mDataByteSize;
	void* ptr = firstBuffer->mData;
	UInt32 framesIn = *ioNumberDataPackets;
#endif

#if LOG_DATA_FLOW
    fprintf(stdout, "***** ACComplexInputProc - requested packets from AC = %ld\n", *ioNumberDataPackets);
#endif
    
    OSStatus res = THIS->GetFileData (&firstBuffer->mData, &firstBuffer->mDataByteSize, &packetCount, &packetDescs);

#if LOG_DATA_FLOW
    fprintf(stdout, "***** ACComplexInputProc - packets returned to AC = %ld\n", packetCount);
#endif

	if (firstBuffer->mDataByteSize == 0)
		*ioNumberDataPackets = 0;
	else 
    {
        if(outDataPacketDescription)
            *outDataPacketDescription = packetDescs;
        *ioNumberDataPackets = packetCount;
	}

#if 0
    // this is debug code to just check out how the data handling is doing
	printf ("fIn = %ld, fOut=%ld, bytesIn=%ld, bytesOut=%ld, ptrIn=%x, ptrOut=%x\n",
					framesIn, *ioNumberDataPackets, 
					size, firstBuffer->mDataByteSize,
					(int)ptr, (int)firstBuffer->mData);
#endif
	
	return res;
}

AudioFileManager::~AudioFileManager ()
{
	if (mFileBuffer) {
		free (mFileBuffer);
		mFileBuffer = 0;
	}
}

AudioFilePlayer::AudioFilePlayer (const FSRef& 			inFileRef)
	: mConnected (false),
	  mAudioFileManager (0),
	  mConverter (0),
	  mNotifier (0)
{
	SInt64 fileDataSize  = 0;
	SInt64 packetCount  = 0;
    UInt32 maxPacketSize = 0;
	
	OpenFile (inFileRef, fileDataSize, packetCount, maxPacketSize);

#if DEBUG
	printf ("There are %qd frames (packets) in this file\n", packetCount);
#endif
		
    // we'll automatically load files that are smaller than 256k
	if (fileDataSize < (1024 * 256)) 
    {
		mAudioFileManager = new AudioFileData (	*this, 
                                                mAudioFileID, 
                                                fileDataSize,
                                                packetCount,
                                                maxPacketSize,
                                                packetCount);
		mUsingReaderThread = false;
	} 
    else 
    {
		// we'll also check to see if the file is smaller
		// than what we would create buffers for
					
        // we want about a seconds worth of data for the buffer
        int secsPackets = UInt32 (mFileDescription.mSampleRate / mFileDescription.mFramesPerPacket);
		
#if DEBUG
		PrintStreamDesc (&mFileDescription);
#endif
						
        mAudioFileManager = new AudioFileReaderThread (	*this, 
                                                        mAudioFileID, 
                                                        fileDataSize,
                                                        packetCount,
                                                        maxPacketSize,
                                                        secsPackets);
        mUsingReaderThread = true;
	}
}

// you can put a rate scalar here to play the file faster or slower
// by multiplying the same rate by the desired factor 
// eg fileSampleRate * 2 -> twice as fast
// before you create the AudioConverter
void	AudioFilePlayer::SetDestination (AudioUnit				&inDestUnit, 
								int			 					inBusNumber)
{
	if (mConnected) throw static_cast<OSStatus>(-1); //can't set dest if already engaged
 
	mPlayUnit = inDestUnit;
	mBusNumber = inBusNumber;

	OSStatus result = noErr;
	
	if (mConverter) {
		result = AudioConverterDispose (mConverter);
			THROW_RESULT("AudioConverterDispose")
	}
	
	AudioStreamBasicDescription		destDesc;
	UInt32	size = sizeof (destDesc);
	result = AudioUnitGetProperty (inDestUnit,
							kAudioUnitProperty_StreamFormat,
							kAudioUnitScope_Input,
							inBusNumber,
							&destDesc,
							&size);
		THROW_RESULT("AudioUnitGetProperty")

#if DEBUG
	PrintStreamDesc (&destDesc);
#endif

		//we can "down" cast a component instance to a component
	ComponentDescription desc;
	result = GetComponentInfo ((Component)inDestUnit, &desc, 0, 0, 0);
		THROW_RESULT("GetComponentInfo")
			
// 	a "neat" trick:
// 	if you want to play the file back faster or slower then you can 
// 	alter the sample rate of the fileDescription before you create the converter.

// thus...
//	mFileDescription.mSampleRate *= 2; // the file will play back twice as fast
// 	mFileDescription.mSampeRate *= 0.5; // half speed

	result = AudioConverterNew (&mFileDescription, &destDesc, &mConverter);
		THROW_RESULT("AudioConverterNew")

    UInt32	magicCookieSize = 0;
    result = AudioFileGetPropertyInfo(	mAudioFileID,
                                        kAudioFilePropertyMagicCookieData,
                                        &magicCookieSize,
                                        NULL); 
    if (result == noErr)
    {
        void 	*magicCookie = calloc (1, magicCookieSize);
        if (magicCookie) 
        {
            result = AudioFileGetProperty (	mAudioFileID, 
                                            kAudioFilePropertyMagicCookieData, 
                                            &magicCookieSize, 
                                            magicCookie);       
            // Give the AudioConverter the magic cookie decompression params if there are any
            if (result == noErr)
            {
                result = AudioConverterSetProperty(	mConverter, 
                                                    kAudioConverterDecompressionMagicCookie, 
                                                    magicCookieSize, 
                                                    magicCookie);
            }
            if (magicCookie) free(magicCookie);
        }
    }

	// if we have a mono source, we're going to copy each channel into
	// the destination's channel source...
	if (mFileDescription.mChannelsPerFrame == 1) {
		SInt32* channelMap = new SInt32 [destDesc.mChannelsPerFrame];
		for (unsigned int i = 0; i < destDesc.mChannelsPerFrame; ++i)
			channelMap[i] = 0; //set first channel to all output channels
			
		result = AudioConverterSetProperty(mConverter,
							kAudioConverterChannelMap,
							(sizeof(SInt32) * destDesc.mChannelsPerFrame),
							channelMap);
			THROW_RESULT("AudioConverterSetProperty")
		
		delete [] channelMap;
	}
	
#if 0
	// this uses the better quality SRC
	UInt32 srcID = kAudioUnitSRCAlgorithm_Polyphase;
	result = AudioConverterSetProperty(mConverter,
					kAudioConverterSampleRateConverterAlgorithm, 
					sizeof(srcID), 
					&srcID);
		THROW_RESULT("AudioConverterSetProperty")
#endif
}

AudioFilePlayer::~AudioFilePlayer()
{
	Disconnect();
		
	if (mAudioFileManager) {
		delete mAudioFileManager;
		mAudioFileManager = 0;
	}
	
	if (mAudioFileID) {
		::AudioFileClose (mAudioFileID);
		mAudioFileID = 0;
	}

	if (mConverter) {
		AudioConverterDispose (mConverter);
		mConverter = 0;
	}
}

void 	AudioFilePlayer::Connect()
{

#if DEBUG
	printf ("Connect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0));
#endif

	if (!mConnected)
	{			
		mAudioFileManager->Connect(mConverter);
				
		// set the render callback for the file data to be supplied to the sound converter AU
        mRenderCallback.inputProc = AudioFileManager::FileRenderProc;
        mRenderCallback.inputProcRefCon = mAudioFileManager;
        
        OSStatus result = AudioUnitSetProperty (mPlayUnit, 
                            kAudioUnitProperty_SetRenderCallback, 
                            kAudioUnitScope_Input, 
                            mBusNumber,
                            &mRenderCallback, 
                            sizeof(mRenderCallback));
        THROW_RESULT("AudioUnitSetProperty")	
		mConnected = true;
	}
}

#warning This should redirect the calling of notification code to some other thread
void 	AudioFilePlayer::DoNotification (OSStatus inStatus) const
{
	AudioFilePlayer* THIS = const_cast<AudioFilePlayer*>(this);
	if (mNotifier) {
		(*mNotifier) (mRefCon, inStatus);
	} else {
		if (inStatus == kAudioFilePlay_FileIsFinished)
			THIS->Disconnect();
		else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun)
			THIS->Disconnect();
	}
}

void 	AudioFilePlayer::Disconnect ()
{
#if DEBUG
	printf ("Disconnect:%x,%ld, engaged=%d\n", (int)mPlayUnit, mBusNumber, (mConnected ? 1 : 0));
#endif
	if (mConnected)
	{
		mConnected = false;
			
        mRenderCallback.inputProc = 0;
        mRenderCallback.inputProcRefCon = 0;
        
        OSStatus result = AudioUnitSetProperty (mPlayUnit, 
                            kAudioUnitProperty_SetRenderCallback, 
                            kAudioUnitScope_Input, 
                            mBusNumber,
                            &mRenderCallback, 
                            sizeof(mRenderCallback));
        if (result) 
            fprintf(stderr, "AudioUnitSetProperty:RemoveRenderCallback:%ld", result);
		
		mAudioFileManager->Disconnect();
	}
}

void	AudioFilePlayer::SetLooping (bool inLoop) 
{ 
	mAudioFileManager->SetLooping (inLoop); 
}
	
bool	AudioFilePlayer::IsLooping () const 
{
	return mAudioFileManager->IsLooping(); 
}

void	AudioFilePlayer::OpenFile (const FSRef& inRef, SInt64& outFileDataSize, SInt64&	outPacketCount, UInt32&	outMaxPacketSize)
{		
	OSStatus result = AudioFileOpen (&inRef, fsRdPerm, 0, &mAudioFileID);
		THROW_RESULT("AudioFileOpen")
		
	UInt32 dataSize = sizeof(AudioStreamBasicDescription);
	result = AudioFileGetProperty (mAudioFileID, 
							kAudioFilePropertyDataFormat, 
							&dataSize, 
							&mFileDescription);
		THROW_RESULT("AudioFileGetProperty")
	
	dataSize = sizeof (SInt64);
	result = AudioFileGetProperty (mAudioFileID, 
							kAudioFilePropertyAudioDataByteCount, 
							&dataSize, 
							&outFileDataSize);
		THROW_RESULT("AudioFileGetProperty")

	dataSize = sizeof (SInt64);
	result = AudioFileGetProperty (mAudioFileID, 
							kAudioFilePropertyAudioDataPacketCount, 
							&dataSize, 
							&outPacketCount);
		THROW_RESULT("AudioFileGetProperty")
	dataSize = sizeof (UInt32);
	result = AudioFileGetProperty (mAudioFileID, 
							kAudioFilePropertyMaximumPacketSize, 
							&dataSize, 
							&outMaxPacketSize);
		THROW_RESULT("AudioFileGetProperty")
}

