changeset 22:58f03008ea05

- Added Seek APIs - Added new API for interruption handling like on iOS - Made support for ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING an opt-in compile define because iOS kept reporting it supported this even though it didn't and some people reported that on Mac, this sometimes failed (but I couldn't reproduce). - Made number of sources unsigned - Some minor 64-bit clean ups
author Eric Wing <ewing . public |-at-| gmail . com>
date Fri, 24 Dec 2010 03:23:02 -0800
parents 14c22fc3c753
children ee96f91ba887
files ALmixer.c ALmixer.h
diffstat 2 files changed, 485 insertions(+), 148 deletions(-) [+]
line wrap: on
line diff
--- a/ALmixer.c	Fri Dec 24 03:17:10 2010 -0800
+++ b/ALmixer.c	Fri Dec 24 03:23:02 2010 -0800
@@ -8,19 +8,14 @@
 #include "ALmixer.h"
 
 #ifdef ALMIXER_COMPILE_WITHOUT_SDL
-	#include "ALmixer_rwops.h"
+	#include "ALmixer_RWops.h"
 	#include "SoundDecoder.h"
 #else
 	#include "SDL_sound.h"
 #endif
 
-#ifdef ANDROID_NDK
-	#include <AL/al.h>
-	#include <AL/alc.h>
-#else
-	#include "al.h" /* OpenAL */
-	#include "alc.h" /* For creating OpenAL contexts */
-#endif
+#include "al.h" /* OpenAL */
+#include "alc.h" /* For creating OpenAL contexts */
 
 #ifdef __APPLE__
 	/* For performance things like ALC_CONVERT_DATA_UPON_LOADING */
@@ -29,15 +24,14 @@
 	 * define was moved to a new header file and renamed to
 	 * ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING.
 	 */
+	#include <TargetConditionals.h>
 /*
-	#include <TargetConditionals.h>
 	#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
-
+		#include <AudioToolbox/AudioToolbox.h>
 	#else
 		#include <OpenAL/MacOSX_OALExtensions.h>
 	#endif
 */
-
 #endif
 
 /* For malloc, bsearch, qsort */
@@ -235,6 +229,8 @@
 
 static LinkedList* s_listOfALmixerData = NULL;
 
+/* Special stuff for iOS interruption handling */
+static ALCcontext* s_interruptionContext = NULL;
 
 
 #ifdef __APPLE__
@@ -323,8 +319,6 @@
 			LARGE_INTEGER current_time;
 			QueryPerformanceCounter(&current_time);
 			return (ALuint)((current_time.QuadPart - s_ticksBaseTime.QuadPart) * 1000 * s_hiResSecondsPerTick);
-		#elif ANDROID_NDK
-
 		#else /* assuming POSIX */
 			/* clock_gettime is POSIX.1-2001 */
 			struct timespec current_time;
@@ -374,7 +368,7 @@
 struct ALmixer_Data
 {
 	ALboolean decoded_all; /* dictates different behaviors */
-	ALint total_time; /* total playing time of sample (msec) */
+	ALint total_time; /* total playing time of sample (msec), obsolete now that we pushed our changes to SDL_sound */
 	
 	ALuint in_use; /* needed to prevent sharing for streams */
 	ALboolean eof; /* flag for eof, only used for streams  */
@@ -609,43 +603,6 @@
 	ALmixer_Channel_List[channel].almixer_data = NULL;
 }
 
-
-#if 0
-/* Not needed anymore because not doing any fileext checks.
- *
- * Unfortunately, strcasecmp isn't portable so here's a
- * reimplementation of it (taken from SDL_sound)
- */
-static int ALmixer_strcasecmp(const char* x, const char* y)
-{
-	int ux, uy;
-	
-	if (x == y)  /* same pointer? Both NULL? */
-		return(0);
-
-	if (x == NULL)
-		return(-1);
-
-	if (y == NULL)
-		return(1);
-	   
-	do
-	{
-		ux = toupper((int) *x);
-		uy = toupper((int) *y);
-		if (ux > uy)
-			return(1);
-		else if (ux < uy)
-			return(-1);
-		x++;
-		y++;
-	} while ((ux) && (uy));
-
-    return(0);
-}
-#endif
-
-					
 /* What shoud this return?
  * 127 for signed, 255 for unsigned
  */
@@ -1141,21 +1098,30 @@
 /* Converts milliseconds to byte positions.
  * This is needed for seeking on predecoded samples 
  */
-static ALuint Convert_Msec_To_Byte_Pos(Sound_AudioInfo *info, ALuint ms)
-{
-	float frames_per_ms;
-	ALuint frame_offset;
-	ALuint frame_size;
-	if(info == NULL)
+static ALuint Convert_Msec_To_Byte_Pos(Sound_AudioInfo* audio_info, ALuint number_of_milliseconds)
+{
+	ALuint bytes_per_sample;
+	ALuint bytes_per_frame;
+	float bytes_per_millisecond;
+	float byte_position;
+
+	if(audio_info == NULL)
 	{
 		fprintf(stderr, "Error, info is NULL\n");
 	}
 
-	/* "frames" == "sample frames" */
-	frames_per_ms = ((float) info->rate) / 1000.0f;
-	frame_offset = (ALuint) (frames_per_ms * ((float) ms));
-	frame_size = (ALuint) ((info->format & 0xFF) / 8) * info->channels;
-	return frame_offset * frame_size;
+	/* SDL has a mask trick I was not aware of. Mask the upper bits
+	 * of the format, and you get 8 or 16 which is the bits per sample.
+ 	 * Divide by 8bits_per_bytes and you get bytes_per_sample
+	 * I tested this under 32-bit and 64-bit and big and little endian
+	 * to make sure this still works since I have since moved from 
+	 * Uint32 to unspecified size types like ALuint.
+	 */
+	bytes_per_sample = (ALuint) ((audio_info->format & 0xFF) / 8);
+	bytes_per_frame = bytes_per_sample * audio_info->channels;
+	bytes_per_millisecond = (float)bytes_per_frame * (audio_info->rate / 1000.0f);
+	byte_position = ((float)(number_of_milliseconds)) * bytes_per_millisecond;  
+	return (ALuint)(byte_position + 0.5);
 }
 
 static ALint Set_Predecoded_Seek_Position(ALmixer_Data* data, ALuint byte_position)
@@ -2330,7 +2296,7 @@
 	ALint channel;
 	if(0 == source)
 	{
-		return Internal_RewindChannel(-1) + 1;
+		return Internal_RewindChannel(-1);
 	}
 	
 	channel = Internal_GetChannel(source);
@@ -2339,7 +2305,7 @@
 		ALmixer_SetError("Cannot rewind source: %s", ALmixer_GetError());
 		return 0;
 	}
-	return Internal_RewindChannel(channel) + 1;
+	return Internal_RewindChannel(channel);
 }
 
 
@@ -3220,6 +3186,219 @@
 }			
 		
 
+static ALint Internal_SeekChannel(ALint channel, ALuint msec)
+{
+	ALint retval = 0;
+	ALenum error;
+	ALint state;
+	ALint running_count = 0;
+	
+	if(0 == msec)
+	{
+		return Internal_RewindChannel(channel);
+	}
+	
+	if(channel >= Number_of_Channels_global)
+	{
+		ALmixer_SetError("Cannot seek channel %d because it exceeds maximum number of channels (%d)\n", channel, Number_of_Channels_global);
+		return -1;
+	}
+	
+	if((error = alGetError()) != AL_NO_ERROR)
+	{
+		fprintf(stderr, "24Testing error: %s\n",
+				alGetString(error));				
+	}
+	/* Clear error */
+	alGetError();
+	
+	/* If the user specified a specific channel */
+	if(channel >= 0)
+	{
+		/* only need to process channel if in use */
+		if(ALmixer_Channel_List[channel].channel_in_use)
+		{
+			
+			/* What should I do? Do I just rewind the channel
+			 * or also rewind the data? Since the data is
+			 * shared, let's make it the user's responsibility
+			 * to rewind the data.
+			 */
+			if(ALmixer_Channel_List[channel].almixer_data->decoded_all)
+			{
+				/* convert milliseconds to seconds */
+				ALfloat sec_offset = msec / 1000.0f;
+
+				alGetSourcei(
+							 ALmixer_Channel_List[channel].alsource,
+							 AL_SOURCE_STATE, &state
+							 );
+				if((error = alGetError()) != AL_NO_ERROR)
+				{
+					fprintf(stderr, "25Testing error: %s\n",
+							alGetString(error));				
+				}
+				/* OpenAL seek */
+				alSourcef(ALmixer_Channel_List[channel].alsource, AL_SEC_OFFSET, sec_offset);
+				if((error = alGetError()) != AL_NO_ERROR)
+				{
+					ALmixer_SetError("%s",
+									 alGetString(error) );
+					retval = -1;
+				}
+				/* Need to resume playback if it was originally playing */
+				if(AL_PLAYING == state)
+				{
+					alSourcePlay(ALmixer_Channel_List[channel].alsource);
+					if((error = alGetError()) != AL_NO_ERROR)
+					{
+						ALmixer_SetError("%s",
+										 alGetString(error) );
+						retval = -1;
+					}
+				}
+				else if(AL_PAUSED == state)
+				{
+					/* HACK: The problem is that when paused, after
+					 * the Rewind, I can't get it off the INITIAL
+					 * state without restarting
+					 */
+					alSourcePlay(ALmixer_Channel_List[channel].alsource);
+					if((error = alGetError()) != AL_NO_ERROR)
+					{
+						fprintf(stderr, "25Testing error: %s\n",
+								alGetString(error));				
+					}
+					alSourcePause(ALmixer_Channel_List[channel].alsource);
+					if((error = alGetError()) != AL_NO_ERROR)
+					{
+						ALmixer_SetError("%s",
+										 alGetString(error) );
+						retval = -1;
+					}
+				}
+			}
+			else
+			{
+				/* Streamed data is different. Rewinding the channel
+				 * does no good. Rewinding the data will have an
+				 * effect, but it will be lagged based on how
+				 * much data is queued. Recommend users call Halt
+				 * before rewind if they want immediate results.
+				 */
+				retval = Internal_SeekData(ALmixer_Channel_List[channel].almixer_data, msec);
+			}
+		}
+	}
+	/* The user wants to rewind all channels */
+	else
+	{
+		ALint i;
+		ALfloat sec_offset = msec / 1000.0f;
+
+		for(i=0; i<Number_of_Channels_global; i++)
+		{
+			/* only need to process channel if in use */
+			if(ALmixer_Channel_List[i].channel_in_use)
+			{
+				/* What should I do? Do I just rewind the channel
+				 * or also rewind the data? Since the data is
+				 * shared, let's make it the user's responsibility
+				 * to rewind the data.
+				 */
+				if(ALmixer_Channel_List[i].almixer_data->decoded_all)
+				{
+					alGetSourcei(
+								 ALmixer_Channel_List[i].alsource,
+								 AL_SOURCE_STATE, &state
+								 );
+					if((error = alGetError()) != AL_NO_ERROR)
+					{
+						fprintf(stderr, "26Testing error: %s\n",
+								alGetString(error));				
+					}
+
+					alSourcef(ALmixer_Channel_List[channel].alsource, AL_SEC_OFFSET, sec_offset);
+					if((error = alGetError()) != AL_NO_ERROR)
+					{
+						ALmixer_SetError("%s",
+										 alGetString(error) );
+						retval = -1;
+					}
+					/* Need to resume playback if it was originally playing */
+					if(AL_PLAYING == state)
+					{
+						alSourcePlay(ALmixer_Channel_List[i].alsource);
+						if((error = alGetError()) != AL_NO_ERROR)
+						{
+							ALmixer_SetError("%s",
+											 alGetString(error) );
+							retval = -1;
+						}
+					}
+					else if(AL_PAUSED == state)
+					{
+						/* HACK: The problem is that when paused, after
+						 * the Rewind, I can't get it off the INITIAL
+						 * state without restarting
+						 */
+						alSourcePlay(ALmixer_Channel_List[i].alsource);
+						if((error = alGetError()) != AL_NO_ERROR)
+						{
+							fprintf(stderr, "27Testing error: %s\n",
+									alGetString(error));				
+						}
+						alSourcePause(ALmixer_Channel_List[i].alsource);
+						if((error = alGetError()) != AL_NO_ERROR)
+						{
+							ALmixer_SetError("%s",
+											 alGetString(error) );
+							retval = -1;
+						}
+					}
+				}
+				else
+				{
+					/* Streamed data is different. Rewinding the channel
+					 * does no good. Rewinding the data will have an
+					 * effect, but it will be lagged based on how
+					 * much data is queued. Recommend users call Halt
+					 * before rewind if they want immediate results.
+					 */
+					running_count += Internal_SeekData(ALmixer_Channel_List[i].almixer_data, msec);
+				}
+			}
+		}
+	}
+	if(-1 == retval)
+	{
+		return -1;
+	}
+	else
+	{
+		return running_count;
+	}
+	
+}
+
+static ALint Internal_SeekSource(ALuint source, ALuint msec)
+{
+	ALint channel;
+	if(0 == source)
+	{
+		return Internal_SeekChannel(-1, msec);
+	}
+	
+	channel = Internal_GetChannel(source);
+	if(-1 == channel)
+	{
+		ALmixer_SetError("Cannot seek source: %s", ALmixer_GetError());
+		return 0;
+	}
+	return Internal_SeekChannel(channel, msec);
+}
+
+
 
 static ALint Internal_FadeInChannelTimed(ALint channel, ALmixer_Data* data, ALint loops, ALuint fade_ticks, ALint expire_ticks)
 {
@@ -4592,6 +4771,15 @@
 #endif
 		return 0;
 	}
+
+	/* Bypass if in interruption event */
+	if(NULL == alcGetCurrentContext())
+	{
+#ifdef ENABLE_ALMIXER_THREADS
+		SDL_UnlockMutex(s_simpleLock);
+#endif
+		return 0;
+	}
 	
 	/* Check the quick flag to see if anything needs updating */
 	/* If anything is playing, then we have to do work */
@@ -4701,8 +4889,9 @@
 					t = (ALfloat) delta_time * ALmixer_Channel_List[i].fade_inv_time;
 					current_volume = (1.0f-t) * ALmixer_Channel_List[i].fade_start_volume 
 						+ t * ALmixer_Channel_List[i].fade_end_volume;
+					/*
 					fprintf(stderr, "start_vol=%f, end_vol:%f, current_volume: %f\n", ALmixer_Channel_List[i].fade_start_volume, ALmixer_Channel_List[i].fade_end_volume, current_volume);
-
+					*/
 					/* Set the volume */
 					alSourcef(ALmixer_Channel_List[i].alsource,
 						AL_GAIN, current_volume);
@@ -5994,7 +6183,7 @@
  * the user will need to restart it if they need it after
  * OpenAL shuts down.
  */
-ALboolean ALmixer_Init(ALuint frequency, ALint num_sources, ALuint refresh)
+ALboolean ALmixer_Init(ALuint frequency, ALuint num_sources, ALuint refresh)
 {
 	ALCdevice* dev;
 	ALCcontext* context;
@@ -6362,42 +6551,48 @@
 	 *	This feature is toggled on/off by using the alDisable() & alEnable() APIs. This 
 	 *	setting will be applied to all subsequent 
 	 *	calls to alBufferData().
+	 * 
+	 * Update: Some people keep reporting that they see the enable fail on Mac, but I can't reproduce it myself.
+	 * Rather than deal with it right now, I think I am going to make it an opt-in thing.
 	 */	
-#ifdef __APPLE__
+#if defined(__APPLE__) && defined(ALMIXER_USE_OSX_CONVERT_DATA_UPON_LOADING)
 /*
+ iPhone is getting this enum, but is failing on the enable, so I guess I'll define it out.
+ */
 	#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
-
+	 
 	#else
+		/* iOS reports this enum exists, but loading it always fails, so make it Mac only. */
+		ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING");
+		 /*
+		 fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum);
+		 */
+		if(0 != convert_data_enum)
+		{
+			alEnable(convert_data_enum);		
+		}
+		if( (AL_NO_ERROR != alGetError()) )
+		{
+			fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
+			ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
+		}
 	#endif
-*/
-	ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING");
-	/*
-	fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum);
-	*/
-	if(0 != convert_data_enum)
-	{
-		alEnable(convert_data_enum);		
-	}
-	if( (AL_NO_ERROR != alGetError()) )
-	{
-	fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
-		ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
-	}
-	
-#endif
+	
+#endif /* __APPLE__ */
 	
 
 	
 	
 	ALmixer_Initialized = 1;
 
-	if(num_sources <= 0)
+	if(num_sources == 0)
 	{
 		Number_of_Channels_global = ALMIXER_DEFAULT_NUM_CHANNELS;
 	}
 	else
 	{
-		Number_of_Channels_global = num_sources;
+		/* probably should make Number_of_Channels_global an ALuint, but need to cast which_channel all the time */
+		Number_of_Channels_global = (ALint)num_sources;
 	}
 	Number_of_Reserve_Channels_global = 0;
 	Is_Playing_global = 0;
@@ -6901,34 +7096,38 @@
 	 *	This feature is toggled on/off by using the alDisable() & alEnable() APIs. This 
 	 *	setting will be applied to all subsequent 
 	 *	calls to alBufferData().
+	 * Update: Some people keep reporting that they see the enable fail on Mac, but I can't reproduce it myself.
+	 * Rather than deal with it right now, I think I am going to make it an opt-in thing.
 	 */	
-#ifdef __APPLE__
-	/*
-	 #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
-	 
-	 #else
-	 #endif
-	 */
-	ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING");
+#if defined(__APPLE__) && defined(ALMIXER_USE_OSX_CONVERT_DATA_UPON_LOADING)
 	/*
-	fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum);
-	*/
-	if(0 != convert_data_enum)
-	{
-		alEnable(convert_data_enum);		
-	}
-	if( (AL_NO_ERROR != alGetError()) )
-	{
-		fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
-		ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
-	}
+	 iPhone is getting this enum, but is failing on the enable, so I guess I'll define it out.
+	 */
+	#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
+	 
+	#else
+		/* iOS reports this enum exists, but loading it always fails, so make it Mac only. */
+		ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING");
+		 /*
+		 fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum);
+		 */
+		if(0 != convert_data_enum)
+		{
+			alEnable(convert_data_enum);		
+		}
+		if( (AL_NO_ERROR != alGetError()) )
+		{
+			fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
+			ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed");
+		}
+	#endif
 #endif
 	
 	return AL_TRUE;
 }
 
 	
-ALboolean ALmixer_InitMixer(ALint num_sources)
+ALboolean ALmixer_InitMixer(ALuint num_sources)
 {	
 	ALint i;
 	ALenum error;
@@ -6962,13 +7161,13 @@
 	 */
 #endif
 
-	if(num_sources <= 0)
+	if(num_sources == 0)
 	{
 		Number_of_Channels_global = ALMIXER_DEFAULT_NUM_CHANNELS;
 	}
 	else
 	{
-		Number_of_Channels_global = num_sources;
+		Number_of_Channels_global = (ALint)num_sources;
 	}
 	Number_of_Reserve_Channels_global = 0;
 	Is_Playing_global = 0;
@@ -7105,7 +7304,54 @@
 	return AL_TRUE;
 }
 
-
+void ALmixer_BeginInterruption()
+{
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_LockMutex(s_simpleLock);
+#endif
+	s_interruptionContext = alcGetCurrentContext();
+	if(NULL != s_interruptionContext)
+	{
+		/* iOS alcSuspendContext is a no-op */
+		alcSuspendContext(s_interruptionContext);
+		alcMakeContextCurrent(NULL);
+	}
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_UnlockMutex(s_simpleLock);
+#endif		
+}
+
+void ALmixer_EndInterruption()
+{
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_LockMutex(s_simpleLock);
+#endif
+
+	/* Note: iOS, you need to set the AudioSession active.
+	 * But if the AudioSession is not initialized, this SetActive 
+	 * call fails.
+	 * So this is probably better if calling app sets this.
+	 */
+/*
+#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
+	OSStatus the_error = AudioSessionSetActive(true);
+	if(noErr != the_error)
+	{
+		fprintf(stderr, "Error setting audio session active! %d\n", the_error);
+	}
+#endif
+ */
+	
+	if(NULL != s_interruptionContext)
+	{
+		alcMakeContextCurrent(s_interruptionContext);
+		alcProcessContext(s_interruptionContext);
+		s_interruptionContext = NULL;
+	}
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_UnlockMutex(s_simpleLock);
+#endif
+}
 
 /* Keep the return value void to allow easy use with
  * atexit()
@@ -7123,6 +7369,34 @@
 #ifdef ENABLE_ALMIXER_THREADS
 	SDL_LockMutex(s_simpleLock);
 #endif
+	
+	/* Several things we need to do:
+	 First, we need to check if we are in an interruption.
+	 If so, we need to reactivate the alcContext so we can call OpenAL functions.
+	 Next, we should delete the OpenAL sources.
+	 Next, we need to free all the sound data via ALmixer_FreeData().
+	 Finally, we can delete the OpenAL context.
+	 */
+	
+	context = alcGetCurrentContext();
+	if(NULL == context)
+	{
+		/* We might have an interruption event where the current context is NULL */
+		if(NULL == s_interruptionContext)
+		{
+			/* Nothing left to try. I think we're done. */
+			fprintf(stderr, "ALmixer_Quit: Assertion Error. Expecting to find an OpenAL context, but could not find one.\n");
+			return;
+		}
+		else 
+		{
+			context = s_interruptionContext;
+			/* reactivate the context so we can call OpenAL functions */
+			alcMakeContextCurrent(context);
+			s_interruptionContext = NULL;
+		}
+	}	
+	
 	/* Shutdown everything before closing context */
 	Internal_HaltChannel(-1, AL_FALSE);
 	
@@ -7134,7 +7408,7 @@
 
 	SDL_DestroyMutex(s_simpleLock);
 #endif
-
+	
 	/* Delete all the OpenAL sources */
 	for(i=0; i<Number_of_Channels_global; i++)
 	{
@@ -7143,7 +7417,7 @@
 	/* Delete all the channels */
 	free(ALmixer_Channel_List);
 	free(Source_Map_List);
-	
+
 	/* Reset the Number_of_Channels just in case somebody
 	 * tries using a ALmixer function.
 	 * I probably should put "Initialized" checks everywhere,
@@ -7151,21 +7425,7 @@
 	 */
 	Number_of_Channels_global = 0;
 	
-	context = alcGetCurrentContext();
-	if(NULL == context)
-	{
-		return;
-	}
-	/* Need to get the device before I close the context */
-	dev = alcGetContextsDevice(context);
-	alcDestroyContext(context);
-	
-	if(NULL == dev)
-	{
-		return;	
-	}
-	alcCloseDevice(dev);
-	
+
 	/* Delete the list of ALmixerData's before Sound_Quit deletes
 	 * its own underlying information and I potentially have dangling pointers.
 	 */
@@ -7177,7 +7437,20 @@
 	}
 	LinkedList_Free(s_listOfALmixerData);
 	s_listOfALmixerData = NULL;
-
+	
+	
+	/* Need to get the device before I close the context */
+	dev = alcGetContextsDevice(context);
+	
+	alcMakeContextCurrent(NULL);
+	alcDestroyContext(context);
+	
+	if(NULL == dev)
+	{
+		return;	
+	}
+	alcCloseDevice(dev);
+	
 	Sound_Quit();
 
 #ifdef ALMIXER_COMPILE_WITHOUT_SDL
@@ -7627,6 +7900,8 @@
 			 */
 			ret_data->total_bytes = 0;
 
+			ret_data->total_time = Sound_GetDuration(sample);
+
 			/* Create buffers for data
 			 */
 			ret_data->buffer = (ALuint*)malloc( sizeof(ALuint) * max_queue_buffers);
@@ -8155,6 +8430,13 @@
 	{
 		return;
 	}
+
+	/* Bypass if in interruption event */
+	if(NULL == alcGetCurrentContext())
+	{
+		fprintf(stderr, "ALmixer_FreeData: Programmer Error. You cannot delete data when the OpenAL content is currently NULL. You may have already called ALmixer_Quit() or are in an interruption event\n");
+		return;
+	}
 	
 	if(data->decoded_all)
 	{
@@ -8166,12 +8448,10 @@
 			Sound_FreeSample(data->sample);
 		}
 		alDeleteBuffers(1, data->buffer);
-	if((error = alGetError()) != AL_NO_ERROR)
-	{
-		fprintf(stderr, "70Testing error: %s\n",
-			alGetString(error));				
-	}
-
+		if((error = alGetError()) != AL_NO_ERROR)
+		{
+			fprintf(stderr, "ALmixer_FreeData: alDeleteBuffers failed. %s\n", alGetString(error));				
+		}
 	}
 	else
 	{
@@ -8193,11 +8473,10 @@
 			
 		Sound_FreeSample(data->sample);
 		alDeleteBuffers(data->max_queue_buffers, data->buffer);		
-	if((error = alGetError()) != AL_NO_ERROR)
-	{
-		fprintf(stderr, "71Testing error: %s\n",
-			alGetString(error));				
-	}
+		if((error = alGetError()) != AL_NO_ERROR)
+		{
+			fprintf(stderr, "ALmixer_FreeData: alDeleteBuffers failed. %s\n", alGetString(error));				
+		}
 	}
 	free(data->buffer);
 
@@ -8214,6 +8493,7 @@
 	{
 		return -1;
 	}
+
 	return data->total_time;
 }
 
@@ -8502,6 +8782,32 @@
 	return retval;
 }
 
+ALint ALmixer_SeekChannel(ALint channel, ALuint msec)
+{
+	ALint retval;
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_LockMutex(s_simpleLock);
+#endif
+	retval = Internal_SeekChannel(channel, msec);
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_UnlockMutex(s_simpleLock);
+#endif
+	return retval;
+}
+
+ALint ALmixer_SeekSource(ALuint source, ALuint msec)
+{
+	ALint retval;
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_LockMutex(s_simpleLock);
+#endif
+	retval = Internal_SeekSource(source, msec);
+#ifdef ENABLE_ALMIXER_THREADS
+	SDL_UnlockMutex(s_simpleLock);
+#endif
+	return retval;
+}
+
 ALint ALmixer_FadeInChannelTimed(ALint channel, ALmixer_Data* data, ALint loops, ALuint fade_ticks, ALint expire_ticks)
 {
 	ALint retval;
--- a/ALmixer.h	Fri Dec 24 03:17:10 2010 -0800
+++ b/ALmixer.h	Fri Dec 24 03:23:02 2010 -0800
@@ -168,11 +168,7 @@
 
 
 /* Needed for OpenAL types since altypes.h was removed in 1.1 */
-#ifdef ANDROID_NDK
-	#include <AL/al.h>
-#else
-	#include "al.h"
-#endif
+#include "al.h"
 
 /* Set up for C function definitions, even when using C++ */
 #ifdef __cplusplus
@@ -300,7 +296,7 @@
 
 
 #ifdef ALMIXER_COMPILE_WITHOUT_SDL
-	#include "ALmixer_rwops.h"
+	#include "ALmixer_RWops.h"
 #else
 	#include "SDL_rwops.h"
 	/**
@@ -344,7 +340,7 @@
  * Pass in 0 or ALMIXER_DEFAULT_REFRESH to use OpenAL default behaviors.
  * @return Returns AL_FALSE on a failure or AL_TRUE if successfully initialized.
  */
-extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_Init(ALuint playback_frequency, ALint num_sources, ALuint refresh_rate);
+extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_Init(ALuint playback_frequency, ALuint num_sources, ALuint refresh_rate);
 
 /** 
  * InitContext will only initialize the OpenAL context (and not the mixer part).
@@ -371,9 +367,24 @@
  * the mixer system separately. I strongly recommend avoiding these two functions
  * and use the normal Init() function.
  */
-extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_InitMixer(ALint num_sources);
+extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_InitMixer(ALuint num_sources);
+
+/**
+ * (EXPERIMENTAL) Call to notify ALmixer that your device needs to handle an interruption.
+ * (EXPERIMENTAL) For devices like iOS that need special handling for interruption events like phone calls and alarms,
+ * this function will do the correct platform correct thing to handle the interruption w.r.t. OpenAL.
+ */
+extern ALMIXER_DECLSPEC void ALMIXER_CALL ALmixer_BeginInterruption(void);
 
 /**
+ * (EXPERIMENTAL) Call to notify ALmixer that your device needs to resume from an interruption.
+ * (EXPERIMENTAL) For devices like iOS that need special handling for interruption events like phone calls and alarms,
+ * this function will do the correct platform correct thing to resume from the interruption w.r.t. OpenAL.
+ */
+extern ALMIXER_DECLSPEC void ALMIXER_CALL ALmixer_EndInterruption(void);
+	
+	
+/**
  * This shuts down ALmixer. Please remember to free your ALmixer_Data* instances
  * before calling this method.
  */
@@ -1077,6 +1088,27 @@
 extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_SeekData(ALmixer_Data* almixer_data, ALuint msec_pos);
 
 /**
+ * Seeks the sound to the beginning that is playing on a specific channel.
+ * If decoded all, seek will instantly seek it. Data is not 
+ * affected, so it will start at the "Seek"'ed positiond.
+ * Streamed data will seek the actual data, but the effect
+ * may not be noticed until the currently buffered data is played.
+ * @param which_channel The channel to seek or -1 to seek all channels.
+ * @return The actual number of channels rewound on success or -1 on error.
+ */
+extern ALMIXER_DECLSPEC ALint ALMIXER_CALL ALmixer_SeekChannel(ALint which_channel, ALuint msec_pos);
+/**
+ * Seeks the sound to the beginning that is playing on a specific source.
+ * If decoded all, seek will instantly seek it. Data is not 
+ * affected, so it will start at the "Seek"'ed positiond.
+ * Streamed data will seek the actual data, but the effect
+ * may not be noticed until the currently buffered data is played.
+ * @param al_source The source to seek or 0 to seek all sources.
+ * @return The actual number of sources rewound on success or -1 on error.
+ */
+extern ALMIXER_DECLSPEC ALint ALMIXER_CALL ALmixer_SeekSource(ALuint al_source, ALuint msec_pos);
+
+/**
  * Pauses playback on a channel.
  * Pauses playback on a channel. Should have no effect on channels that aren't playing.
  * @param which_channel The channel to pause or -1 to pause all channels.
@@ -1440,8 +1472,7 @@
 extern ALMIXER_DECLSPEC ALuint ALMIXER_CALL ALmixer_CountUnreservedFreeChannels(void);
 
 /**
- * Returns the number of channels that are currently in use (playing/paused),
- * excluding the channels that have been reserved.
+ * Returns the number of channels that are currently in use (playing/paused).
  * @return The number of channels that are currently in use.
  * @see ALmixer_ReserveChannels
  */