/****************************/
/*      AUDIO.C				*/
/* (c)2004 Pangea Software  */
/* By Brian Greenstone      */
/****************************/


/****************************/
/*    EXTERNS				*/
/****************************/

extern	FSSpec		gMyResourcesFolderFSSpec;


/****************************/
/*    PROTOTYPES            */
/****************************/

static void *MySongThread(void *in);
pascal void EndOfSongCallback(QTCallBack cb, long refCon);
static void OAL_Init(void);


/****************************/
/*    CONSTANTS             */
/****************************/

#define	MAX_SOUND_BUFFERS		30

#define	MAX_PATHNAME_LEN		1000			// maximum size of a full pathname for our WAV files

/**********************/
/*     VARIABLES      */
/**********************/

static CGrafPtr		gQTDummyPort = nil;
Movie				gSongMovie = nil;
static	Boolean		gSongPlayingFlag = false;
static	Boolean		gLoopSongFlag = false;

static volatile  Boolean gInQuicktimeFunction = false;
static volatile	 gInMoviesTask = false;

static	QTCallBack		gMovieCallback;
static	QTCallBackUPP	gMovieCallbackUPP;	


static	ALuint	gSoundBuffers[MAX_SOUND_BUFFERS];


#define	NUM_WAVS	3

static	char	*gWAVFiles[NUM_WAVS] =
{
	"\pwave1.wav",
	"\pwave2.wav",
	"\pwave3.wav",
};


/***************** INIT AUDIO **********************/
//
// Initializes Quicktime, and creates a pthread for updating MoviesTask()
//

void InitAudio(void)
{
	pthread_t 			myThread;
	OSErr				iErr;

		/* ACTIVATE QUICKTIME */

	EnterMovies();

	
		/* INIT MOVIES TASK THREAD */
					
	iErr = pthread_create(&myThread, NULL, MySongThread, nil);
	if (iErr != noErr)
		DoFatalAlert("\ppthread_create failed");
		
		
		/* INIT OPENAL */
		
	OAL_Init();
	
	OAL_LoadWAVFiles(NUM_WAVS, gWAVFiles);

}

#pragma mark ____ QUICKTIME ______


/********************* MY SONG THREAD ************************/
//
// A code thread dedicated to updateing MoviesTask()
//

static void *MySongThread(void *in)
{
AbsoluteTime	expTime;
#pragma unused (in)

	while(true)						// loop inside here forever
	{
			/* CALCULATE THE TIME OF NEXT LOOP */
			//
			// Calculate current time + 100 milliseconds.
			// ThatÕs when weÕll want to loop thru again.
			//

		expTime = AddDurationToAbsolute(100 * kDurationMillisecond,
										UpTime());
		
		/* UPDATE MOVIES TASK */
		//
		// We must be very careful not to call MoviesTask()
		// while some other Quicktime call is in progress!
		//
		
		if (!gInQuicktimeFunction)
		{		
			if (gSongMovie)			
			{
				gInMoviesTask = true;
				MoviesTask(gSongMovie, 0);
				gInMoviesTask = false;
			}
		}

			/* WAIT UNTIL ITÕS TIME TO LOOP AGAIN */

		MPDelayUntil(&expTime);
	}
	
    return 0;
}





/*********************** PLAY SONG ***********************/
//
// Loads the specified sound file and starts playing it.
//
// INPUT:	songFileName = pascal string that is the audio file's filename.
//			loopFlag = true if we want the song to loop, false if only play once.
//

void PlaySong(Str255 songFileName, Boolean loopFlag)
{
	OSErr 		iErr;
	FSSpec		spec;
	short		myRefNum;
	GrafPtr		oldPort;
	TimeValue 	timeNow, duration;
	Fixed 		playRate;
	Media 		theMedia;
	
			/********************************/
			/* CREATE MOVIE FROM SOUND FILE */
			/********************************/
			
	/* CREATE FSSPEC FOR THE SOUND FILE */
	
	iErr = FSMakeFSSpec(gMyResourcesFolderFSSpec.vRefNum,
						gMyResourcesFolderFSSpec.parID,
						songFileName,
						&spec);
	if (iErr)
		DoFatalAlert("\pSong file not found");
	
	
	/* GOT TO SET A DUMMY PORT FOR QUICKTIME */
	//
	// Even though weÕre only playing audio, we must set a
	// dummy port for Quicktime to ÒrenderÓ into.
	//
	
	if (gQTDummyPort == nil)					// create a blank graf port
		gQTDummyPort = CreateNewPort();
	
	GetPort(&oldPort);						// backup current port	 
	SetPort(gQTDummyPort);					// set dummy as active port
	
	
	/* OPEN THE SOUND FILE AS A QT MOVIE */
	
	iErr = OpenMovieFile(&spec, &myRefNum, fsRdPerm);
	if ((myRefNum == 0) || (iErr != noErr))
		DoFatalAlert("\pError opening movie file");
	
	
	/* CREATE A NEW QT MOVIE FROM THE OPEN FILE */
	
	NewMovieFromFile(&gSongMovie,
					 myRefNum,
					 0,
					 nil,
					 newMovieActive,
					 nil);
	
	CloseMovieFile(myRefNum);
	
			/******************/	
			/* SET MOVIE INFO */
			/******************/
	
	/* SET MOVIE PLAY HINTS */			
	
	SetMoviePlayHints(gSongMovie,
					  0, 						// turn these hints off	
					  hintsUseSoundInterp|hintsHighQuality);	
	
	if (loopFlag)								// turn loop hint on
		SetMoviePlayHints(gSongMovie, hintsLoop, hintsLoop);	
		
														
	/* PRE-LOAD MOVIE INTO RAM */
	
	timeNow = GetMovieTime(gSongMovie, nil);
	duration = GetMovieDuration(gSongMovie);
	playRate = GetMoviePreferredRate(gSongMovie);
	theMedia = GetTrackMedia(GetMovieIndTrack(gSongMovie, 1));		
	
	LoadMovieIntoRam(gSongMovie, timeNow, duration, keepInRam);
	LoadMediaIntoRam(theMedia, timeNow, duration, keepInRam);
	
	PrePrerollMovie(gSongMovie, timeNow, playRate, nil, nil);
	PrerollMovie(gSongMovie, timeNow, playRate); 
	
	
			/* SET LOOPING CALLBACK */
			
	if (loopFlag)
	{
		TimeBase timebase = GetMovieTimeBase(gSongMovie);	// get timebase of movie
		gMovieCallback = NewCallBack(timebase,				// create a new callback ref
							callBackAtExtremes);

		gMovieCallbackUPP = NewQTCallBackUPP(EndOfSongCallback);	// create callback UPP
			
		iErr = CallMeWhen(gMovieCallback,				// callback ref
					gMovieCallbackUPP,					// function to callback
					0,
					triggerAtStop,						// call when movie stops
					0,0);
					
		if (iErr != noErr)
			DoFatalAlert("\pcould not create callback");
	}
	
	
				/***************************/
				/* START THE MUSIC PLAYING */
				/***************************/
	
	StartMovie(gSongMovie);
	
	gSongPlayingFlag = true;
	gLoopSongFlag = loopFlag;								// remember if we want it to loop
	
	
				/* RESTORE THE PORT */
	
	SetPort(oldPort);
	
}

/***************** END OF SONG CALLBACK *********************/
//
// This gets called by MoviesTask() when the movie has ended.
// Here we rewind the song and get it playing again to make it loop.
//
// NOTE:  Since this is called from MoviesTask() and we only call
// MoviesTask() inside our thread function, we don't need to use
// our safety flags gInQuicktimeFunction or gInMoviesTask because
// these were already dealt with when MoviesTask() was called.
// Also, be careful what we call inside this function since we're
// in a thread.  Not all OS calls are thread-safe!
//

pascal void EndOfSongCallback(QTCallBack callback, long refCon)
{
#pragma unused (callback, refCon)

	GoToBeginningOfMovie(gSongMovie);	// rewind song

	CallMeWhen(gMovieCallback,			// reinstate the callback
				gMovieCallbackUPP,
				0,
				triggerAtStop,
				0,0);
					
	StartMovie(gSongMovie);				// start playing again	
}



/*********************** FREE OUR SONG MOVIE *********************/
//
// Stops a song from playing and frees up the Quicktime movie.
//

void FreeOurSongMovie(void)
{
			/* BE SAFE */
			
	gInQuicktimeFunction = true;	// dont allow song task to start anything new
	while(gInMoviesTask);			// DONT DO ANYTHING WHILE IN THE SONG TASK!!

		
		/* DISPOSE THE LOOP CALLBACK */
		
	if (gLoopSongFlag)
	{
		DisposeCallBack(gMovieCallback);
		DisposeQTCallBackUPP(gMovieCallbackUPP);	
	}

		
		/* STOP AND DISPOSE MOVIE */
				
	gSongPlayingFlag = false;		// clear this

	StopMovie(gSongMovie);			// stop the movie
	DisposeMovie(gSongMovie);		// dispose of the movie
		
	gSongMovie = nil;
	
	gInQuicktimeFunction = false;
}


#pragma mark ____ OPENAL _____


/************** INIT OPENAL ****************/

static void OAL_Init(void)
{
	ALCcontext	*al_context;
	ALCdevice	*al_device;
	OSErr		iErr;


			/* CREATE AN OPENAL CONTEXT */
			
	al_device	=	alcOpenDevice(nil);
	al_context	=	alcCreateContext(al_device,0);
	alcMakeContextCurrent(al_context);

	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\pcreating OpenAL context failed");


		/* SET SOME STATE INFO */
				
	alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
	alListenerf(AL_GAIN,1.0);
	alEnable(ALC_CONVERT_DATA_UPON_LOADING);		// convert to Core Audio format
	alSetInteger(ALC_SPATIAL_RENDERING_QUALITY, ALC_SPATIAL_RENDERING_QUALITY_LOW);
	alSetInteger(ALC_RENDER_CHANNEL_COUNT, ALC_RENDER_CHANNEL_COUNT_STEREO);	// assume just 2 speakers
        
//	alDopplerFactor(1);
//	alDopplerVelocity(1000);
		
		
	iErr= alGetError();
	if (iErr != AL_NO_ERROR)
		DoFatalAlert("\psetting OpenAL state failed");

		/* INIT ALUT */
		
	alutInit(nil, nil);
	
}


/********************** SET LISTENER INFO *********************/
//
// The listener is where our ears our in 3D space.  It is equivalent to
// the location of the OpenGL camera, so we call this from the
// camera update function OGL_SetCamera().
//

void OAL_SetListenerInfo(OALPoint3D *coord, OALVector3D *aim, OALVector3D *up, OALVector3D *vector)
{
OALOrientation	orientation;

			/* SET ORIENTATION STRUCT */
			//
			// When we pass the orientation to alListener() it's 6 consecutive
			// floats representing the direction-vector followed by the up-vector.
			//
			
	orientation.aim = *aim;
	orientation.up = *up;

    alListenerfv(AL_ORIENTATION, (ALfloat *)&orientation);
	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\palListenerfv failed: AL_ORIENTATION");	

    alListenerfv(AL_POSITION,    (ALfloat *)coord);
	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\palListenerfv failed: AL_POSITION");	

    alListenerfv(AL_VELOCITY,    (ALfloat *)vector);
	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\palListenerfv failed: AL_VELOCITY");	
}


/******************* PLAY SOUND 3D *************************/
//
// Play the desired buffer on a new sound Source using the input parameters.
//
// INPUT:	bufferNum	= sound effect # to play.
//			coord		= 3D coord of the sound source
//			vector		= velocity vector of the sound source
//			pitch		= frequency multiplier to raise or lower the pitch (1.0 = normal).
//			gain		= volume multiplier to raise or lower the volume (1.0 = normal).
//			loop		= flag to indicate if this sound loops.
//
// OUTPUT:	an OpenAL source #
//

ALuint OAL_PlaySound3D(int bufferNum, OALPoint3D *coord, OALVector3D *vector, float pitch, float gain, Boolean loop)
{
	ALuint	theSource;
	
				/* CREATE A SOUND SOURCE */
				//
				// Sound sources are points in space which make sound.
				//
	
	alGenSources(1, &theSource);
	
	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\palGenSources failed");	

				/* ASSIGN A SOUND TO THIS SOURCE */
				
	alSourcei(theSource, AL_BUFFER, gSoundBuffers[bufferNum]);


				/* SET SOURCE'S POSITION & VELOCITY */
				
	alSourcefv(theSource, AL_POSITION, (ALfloat *)coord);				
	alSourcefv(theSource, AL_VELOCITY, (ALfloat *)vector);				

				/* SET SOME PARAMETERS */
				//
				//
				// Setting the Rolloff Factor to 0 would disable distance attenuation.
				//
				
	alSourcef(theSource, AL_REFERENCE_DISTANCE, 3.0);		// distance where volume is 100%
	alSourcef(theSource, AL_ROLLOFF_FACTOR, .8);

	alSourcef (theSource, AL_PITCH, pitch);					// set pitch multiplier
	alSourcef (theSource, AL_GAIN, gain);					// set volume / gain multiplier

	alSourcei(theSource, AL_LOOPING, loop);		// set looping to AL_TRUE or AL_FALSE


			/* PLAY THE SOUND */

	alSourcePlay(theSource);					// play it!

	if (alGetError() != AL_NO_ERROR)
		DoFatalAlert("\palSourcePlay failed");	

	return(theSource);
}


/****************** LOAD WAV FILES ************************/
//
// Loads a series of WAV files to be used as sound effects.
//
// INPUT:	numfiles = # of WAV files to load.
//			filenames = array of c strings containing the filename of
//						each WAV file.
//

void OAL_LoadWAVFiles(short numFiles, char *filenames[])
{
	short	i;
	ALenum	format;
	ALsizei freq;
	void	*data;
	ALsizei size;
	OSErr	iErr;
	FSSpec	spec;
	static char	fullPathname[MAX_PATHNAME_LEN];
	
	if (numFiles > MAX_SOUND_BUFFERS)			// check for overflow
		DoFatalAlert("\pnumFiles > MAX_SOUND_BUFFERS");
		
	
			/*************************************/
			/* LOAD EACH WAV INTO A SOUND BUFFER */
			/*************************************/
			//
			// Buffers store the information about how a sound should
			// be played and the sound data itself.  So, here we
			// essentially create a buffer and load a WAV file into it.
			//
			
	for (i = 0; i < numFiles; i++)
	{
				/* CREATE AN FSSPEC FOR THIS WAV FILE */
					
		iErr = FSMakeFSSpec(gMyResourcesFolderFSSpec.vRefNum,
						gMyResourcesFolderFSSpec.parID,
						filenames[i],
						&spec);
		
		if (iErr != noErr)
			DoFatalAlert("\pCannot find our WAV file");
	
				/* CONVERT THE FSSPEC TO A FULL PATHNAME */
				
		GetFullPathFromFSSpec(&spec, fullPathname, 1000);
		
				/* LOAD THE WAV FROM THE PATHNAME */
				
		alutLoadWAVFile(fullPathname, &format, &data, &size, &freq);	
		if (alGetError() != AL_NO_ERROR)
			DoFatalAlert("\palutLoadWAVFile failed");

			/* ALLOCATE A BUFFER */
			
		alGenBuffers(1, &gSoundBuffers[i]);			
		if (alGetError() != AL_NO_ERROR)
			DoFatalAlert("\palGenBuffers failed!");

			/* COPY WAV'S DATA TO THE BUFFER */

		alBufferData(gSoundBuffers[i], format, data, size, freq);		
		if (alGetError() != AL_NO_ERROR)
			DoFatalAlert("\palBufferData failed");

			/* UNLOAD THE WAV FILE */
				
		alutUnloadWAV(format, data, size, freq);
	}
}





